diff --git a/README.md b/README.md index f7d6961..b0d95da 100644 --- a/README.md +++ b/README.md @@ -1,718 +1,718 @@ - - - - - -# Rivra -**(Minimal Ripple tool kit. Not just a router)** - - - -# Router + full-stack description: -Not just a router — Rivra is a complete toolkit combining routing, state management, storage, and full-stack app tooling, all fully accessible - -## -![npm](https://img.shields.io/npm/v/rivra) -![downloads](https://img.shields.io/npm/dt/rivra) -![license](https://img.shields.io/npm/l/rivra) -Ripple Logo - ------ - -> **Update:** - Rivra middleware now applies to the **client side** as well — not just API routes. -> Global Middleware and Plugins are now executed **across all routes and resources**, ensuring unified behavior. -> _(Page-specific middleware or plugin logic isn’t supported yet.)_ - ---- - - -## - -## Creating New Project - -```bash -npx rivra create my-app // wrapper around npm create ripple my-app. - -``` - -###### For existing ripple projects use the installation and init - -## Installation - -```bash -npm install rivra -npx rivra init -``` - -or - -```bash -yarn add rivra -npx rivra init -``` - - - -## Quick Start - -After initiating **Rivra** which has all the rivra routing components, the pages directory, /api (if selected full stack), the routes.ts file for your app modules and the configured App.ripple file will be visible in your project src dir. The App.ripple is optional to overwrite. - -### Directory Structure - -Here's an example `src/pages/` directory: - -```bash -pages/ -├── index.ripple # Home page -├── about.ripple # About page -└── users/ - └── [id]/ - └── user/ - └── [username]/ - └── comments/ -``` - -Here's an `api/` directory: - -```bash -api/ -├── index.ts # Root API entry point (can load all modules) -├── posts.ts # Top-level posts routes -└── users/ - └── [id]/ # Dynamic user ID - └── user/ - └── [username]/ # Dynamic username - └── comments/ - └── index.ts # User comments routes - -``` - -```ts -import type { Reply, Req, App } from "rivra" - -import type {Req, Reply, App} from "mivra" -export default async function postAPi(app: App) { - app.get("/", async (req: Req, reply: Reply) => { - - const param = req.params - const queries = req.query - - - return ({ message: "some dynamic route", params: param, query: queries }) - }) -} - -``` - - -### Rivra middlewares/plugins for fastify - -Rivra allows you to customize the server behaviours by leveraging on Fastify hooks and plugins. There are some times you may want to extend function. - ----- -```sql - -plugins/ - ├── middleware/ - │ ├── cors.ts → global middleware (order=1) - │ └── helmet.ts → global middleware (order=2) - ├── auth.md.ts → middleware only for /auth/* - ├── auth.pg.ts → /auth routes - ├── users/ - │ ├── users.md.ts → middleware only for /users/* - │ └── index.ts → /users routes - └── logger.ts → global plugin -``` - - -* Example of plugin -```ts -export const order = 2; // order from 1-10 gives you ability to prioritize the order which your hooks run. - -export default async function (req: Req, reply: Reply, app: App) { - console.log("global plugin triggered") - reply.header('X-Powered-By', 'Rivra'); - - if (req.url === "/api/users") { - reply.send({ message: "Users root route" }); - } else if (req.url === "/api/users/profile") { - reply.send({ message: "User profile route" }); - } - -} - - -``` - -* Example of middlewre -```ts - -export default function (req: Req, res: Reply,) { - console.log("Protected middleware Incoming:", req.method, req.url); - const truthy = true - if (!truthy) { - res.code(400).send({error: "Bad request"}) - return - } - if (req.url === "/api/blocked") { - res.code(403).send({ error: "Forbidden" }); - return; - } - -} - - -``` - -#### Here’s a simple and clear table that explains the difference between a plugin and a middleware and how Rivra handle them. - -| **Aspect** | **Plugin** | **Middleware** | -| ------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| **Purpose** | Extends or configures the Fastify instance (adds routes, hooks, decorators, schemas, etc.) | Intercepts and processes requests before or during route handling (authentication, logging, validation, etc.) | -| **Function Signature** | `(app: FastifyInstance) => void` | `(req: FastifyRequest, reply: FastifyReply, app: FastifyInstance) => void` | -| **Execution Time** | Runs **once** during server startup to register logic | Runs **on every incoming request** (depending on where it’s applied) | -| **Common Use Cases** | Adding routes, setting up databases, registering third-party plugins, global configurations | Authentication checks, access control, logging, pre-processing requests` | Using hooks like `app.addHook("preHandler", handler)` or route-level middlewares | -| **Scope** | Can be **scoped** to a prefix (e.g., `/api/protected`) | Can be **global** or **route-specific** | -| **Impact** | Modifies or enhances the app’s capabilities | Filters or modifies the request/response cycle | -| **File Naming Convention (in your system)** | `*.pg.ts` (Scoped Plugin) | `*.md.ts` (Middleware) | -| **Example (Plugin)** | `export default async function (app) { app.get('/hello', () => 'Hi');}` | | -| **Example (Middleware)** | | `t export default async function (req, reply, app) { if (!req.headers.auth) reply.code(401).send({ error: 'Unauthorized' });}` | - - -| Type | Example File | Prefix Applied | Loaded As | Order | -| ----------------- | ------------------------------- | -------------- | ---------- | -------------------------- | -| Global middleware | `/plugins/middleware/logger.ts` | none | middleware | before all plugins | -| Route middleware | `/plugins/auth/auth.md.ts` | `/auth` | middleware | after global, before route | -| Prefixed plugin | `/plugins/auth.pg.ts` | `/auth` | plugin | normal | -| Folder plugin | `/plugins/payments/index.ts` | `/payments` | plugin | normal | -| Global plugin | `/plugins/cors.ts` | none | plugin | normal | - - -Dynamic segments use `[param]` notation like `[id]` or `[username]`. - ---- - -### App Component - -```ts -import {PageRoutes} from "rivra/router" -import { modules } from "./routes"; - -export component App() { - -} -``` - -That's it! Your routing is now set up. `PageRoutes` automatically reads your `pages/` folder and matches routes, including dynamic parameters. - ---- - -### Link Component - -Use the `Link` component for navigation: - -```ts -import Link from "rivra/router" - -export component Navigation() { - -} -``` - -#### Props: - -| Prop | Type | Default | Description | -| ------------------ | --------------------- | ------- | --------------------------------------------- | -| `href` | `string` | — | Path to navigate to | -| `children` | `Component` | — | Content to render inside link | -| `onLoading` | `() => void` | — | Callback when navigation starts | -| `emitEvent` | `boolean` | `true` | Whether to trigger route events for this link | -| `loadingComponent` | `Component` | — | Optional component to show while loading | -| `className` | `string` | — | Additional CSS class names for styling | -| `queries` | `Record` | — | Optional query parameters for URLSearch | - - ---- - -### Router And Events - -You can subscribe to router events if you need custom behavior: - -```ts -import { useRouter } from "rivra/router" - -const router = useRouter(); - -router.on("start", path => console.log("Navigating to:", path)); -router.on("complete", path => console.log("Navigation finished:", path)); -router.on("change", path => console.log("Route changed:", path)); - - - -//Guard back navigation -router.beforePopState((url) => { - if (url === "/protected") { - console.log("Navigation blocked:", url); - return false; // Cancel navigation - } -}); - -// Navigate to a new route -router.push("/users/42?tab=posts"); -router.push("/users/42?tab=posts", true, false, {name: "John", age: 20}); // path, emitEvent, shallow (partial url change), queries - - -//Replace URL shallowly (no full sync)// -router.replace("/users/42?tab=profile", true, true); - -// Prefetch a route -router.prefetch("/about"); - -// Resolve href -console.log("Resolved href:", router.resolveHref("/contact?ref=home")); - -// Access reactive properties -console.log("Current path:", router.path); -console.log("Query params:", router.queries); -console.log("Dynamic params:", router.params); -console.log("Full URL:", router.asPath); - -// Access full URL info -console.log(router.host); -console.log(router.hostname); -console.log(router.origin); -console.log(router.protocol); -console.log(router.port); -console.log(router.href); -console.log(router.search); -console.log(router.hash); -``` - -* `start`: triggered before navigation begins -* `complete`: triggered after navigation finishes -* `change`: triggered on every path change - -You can opt out of events per `Link` with `emitEvent={false}`. - ---- - -### Dynamic Route Params - -Access route params and queries in any component: - -```ts -import { useRouter } from "rivra/router" - -export component UserProfile() { - const router = useRouter(); - - const id = router.params.id; // dynamic param - const username = router.params.username; - const queryName = router.queries.name; // URL query ?name=... - // or - const {params, queries} = router; - -
- {"User ID: " + id} - {"Username: " + username} - {"Query name: " + queryName} -
-} -``` - ---- - -### Global Loading Indicator (Optional) -you can disable it with props ```ts - -``` - -```ts -import {PageRoutes} from "rivra/router" -import { modules } from "./routes"; - -export component App() { - -} - -``` - - ---- - ---- - -### A minimal reactive store that manages shared state across your app with an intuitive API. It provides reactivity, persistence, and derived state — all in under a few lines of code. - -| Feature | Description | -| ------------------------------ | ---------------------------------------------------------------------------------------- | -| **`get()`** | Returns the current store value. | -| **`set(next)`** | Replaces the entire store value. | -| **`update(partialOrFn)`** | Merges new data into the store. Supports both object patching and callback styles. | -| **`subscribe(fn, selector?)`** | Reactively listens for state changes, optionally to a selected portion. | -| **`derive(selector)`** | Creates a new store derived from a specific part of another store (like computed state). | -| **`delete(keys)`** | Removes one or more keys from the store. | -| **`clear()`** | Resets store to initial state and removes persisted data. | -| **`persist` (option)** | Automatically saves and restores state from `localStorage`. | - - - --------------------- Example Stores -------------------- -```ts - //* Route store for storing current route path - //* Persisted in localStorage as "routeStore" - //*/ -export const routeStore = createStore( - { path: "/" }, - { persist: true, storageKey: "routeStore" } -); - -/** - * App store for global state - * Tracks path, user info, theme - * Persisted in localStorage as "appStore" - */ -export const appStore = createStore( - { path: "/", user: null as null | { name: string }, theme: "light" }, - { persist: true, storageKey: "appStore" } -); - -``` -Here are extra two simple Hello World store examples for getting started and explain things better. - -### Store without persist (default) -```ts -import { createStore } from "rivra/store" - -// Create a simple store -const helloStore = createStore({ message: "Hello World!" }); - -// Subscribe to changes -helloStore.subscribe(state => { - console.log("Current message:", state.message); -}); - -// Get changes anywhere -const data = helloStore.get(); -console.log(helloStore) // { message: Current message} -console.log(data.message) // Current message - - -// Update the store -helloStore.update({ message: "Hello Ripple!" }); - -// Output: -// Current message: Hello World! -// Current message: Hello Ripple! - - - -``` - -### Store with persist - -```ts -import { createStore } from "rivra/store" -import { track } from "ripple" - -const message = track("") - -// Create a persisted store -const persistentHelloStore = createStore( - { message: "Hello Persistent World!" }, - { persist: true, storageKey: "helloStore" } -); - -// Subscribe to changes -persistentHelloStore.subscribe(state => { - console.log("Current message:", state.message); -}); - - -// Get changes anywhere -const data = helloStore.get(); -console.log(helloStore) // { message: Current message} -console.log(data.message) // Current message - - -// Update the store -persistentHelloStore.update({ message: "Updated and Persisted!" }); - - -// Callback update (safe addition) -persistentHelloStore.update(prev => ({ message: prev.message + " " + @message })); - - -// Reload the page and subscribe again -persistentHelloStore.subscribe(state => { - console.log("After reload:", state.message); -}); - -// Output (after reload): -// After reload: Updated and Persisted! - - - -export const appStore = createStore( - { - user: { name: "Joe", location: "unknown", preferences: [] }, - count: 0, - theme: "light", - }, - { persist: true, storageKey: "appStore" } -); - - - -// Subscribe to entire state -appStore.subscribe(s => console.log("State changed:", s)); - -// Watch a specific value -appStore.watch(s => s.count, (n, o) => console.log(`Count: ${o} → ${n}`)); - -// Use middleware for logging -appStore.use((state, action, payload) => - console.log(`[${action}]`, payload, state) -); - -// Partial update -appStore.update({ count: 1 }); - -// Callback update (safe addition) -appStore.update(prev => ({ count: prev.count + 1 })); - -// Derived store -const themeStore = appStore.derive(s => s.theme); -themeStore.subscribe(theme => console.log("Theme:", theme)); - -// Clear store -appStore.clear(); - - -``` - - -### Here’s a concise side-by-side comparison between Rivra createStore and Zustand: - -| Feature / Aspect | **createStore** (Rivra) | **Zustand** | -| ------------------------ | ------------------------------------ | ---------------------------------------- | -| **Size / Complexity** | Ultra-light (~2 KB) | Larger, includes middleware and devtools | -| **Reactivity Model** | Manual `subscribe` / `derive` | React hooks (`useStore`) | -| **Selectors** | Optional selector argument | Built-in via hooks | -| **Persistence** | Native `persist` option | Needs middleware plugin | -| **DevTools Integration** | Coming soon | Built-in Redux DevTools support | -| **Middleware** | Planned via `use()` | Full middleware API | -| **Callback Updates** | Supported: `update(prev => {...})` | Supported: `set(state => {...})` | -| **Derived Stores** | `derive(selector)` | Selectors or derived state | -| **Performance** | Minimal overhead | Optimized for React, slightly heavier | -| **Framework Support** | Framework-agnostic | React-only | -| **TypeScript** | Fully typed generics | Excellent TS support | -| **Persistence Control** | Built-in localStorage | Plugin required | -| **Use Case Fit** | Libraries & multi-framework projects | React apps needing global state | - ---- - - -## Minimal IndexDB Manager with Zero Dependencies. - - 📘 Example Usage - -------------------------------------------------------------------------- - - ✅ Minimal Example - ```ts - import createIndexDBStore from "rivra/stores" -import IndexDBManager from "rivra/stores" - const userStore = createIndexDBStore({ - storeName: 'users', - }); - await userStore.add({ id: 'u1', name: 'Joseph', age: 22 }); - const all = await userStore.getAll(); - console.log(all); - ``` - - ✅ Full Configuration Example - ```ts - interface User { - id: string; - name: string; - age: number; - } - - const userStore = createIndexDBStore({ - dbName: "MyAppDB", - storeName: "users", - keyPath: "id", - version: 1, - }); - - // ➕ Add record - await userStore.add({ id: 'u1', name: 'Ada', age: 45 }); - - // 🔁 Update record by key or object - await userStore.update('u1', { age: 46 }); - - // 🔍 Get a record by key - const user = await userStore.get('u1'); - console.log('Single user:', user); - - // 📦 Get all records - const allUsers = await userStore.getAll(); - console.log('All users:', allUsers); - - // ❌ Remove record by ID - await userStore.remove('u1'); - - // 🧹 Clear all records - await userStore.clear(); - - // 🔎 Query using a filter function - const adults = await userStore.query(u => u.age > 30); - console.log('Adults:', adults); - - // 👂 Subscribe to store changes (reactive) - const unsubscribe = userStore.subscribe(state => { - console.log('Store changed:', state.items); - }); - - // 👁️ Watch specific property or subset of data - const watchAdults = userStore.deriveQuery(items => items.filter(u => u.age > 30)); - watchAdults.subscribe(adults => console.log('Adults updated:', adults)); - - // 🔦 Filter (where) - const namedJohn = await userStore.where({ name: 'John' }); - console.log('Users named John:', namedJohn); - - // 🥇 Get first matching record - const firstUser = await userStore.first({ age: 46 }); - console.log('First matching user:', firstUser); - - // 🔍 Find by ID (alias for get) - const found = await userStore.find('u1'); - console.log('Found user:', found); - - // 🧩 Put (alias for update) - await userStore.put({ id: 'u1', name: 'Ada', age: 50 }); - - // 💳 Perform custom transaction - await userStore.transaction(tx => { - const store = tx.objectStore('users'); - store.add({ id: 'u2', name: 'Ken', age: 35 }); - }); - - // 🧭 Watch specific user reactively - const watchUser = userStore.deriveQuery(items => items.find(u => u.id === 'u1') || null); - watchUser.subscribe(u => console.log('u1 changed:', u)); - - // 🧹 Unsubscribe from store updates - unsubscribe(); - ``` - - ✅ Multi-Store Example (using IndexDBManager) - ```ts - interface User { - id: string; - name: string; - } - - interface Post { - id: string; - title: string; - } - - // Create manager - const db = new IndexDBManager("MyAppDB"); - - // Create multiple stores - const users = db.createStore("users", "id"); - const posts = db.createStore("posts", "id"); - - // Add data - await users.add({ id: "u1", name: "Joe" }); - await posts.add({ id: "p1", title: "Hello World" }); - - // Query data - const allUsers = await users.getAll(); - const allPosts = await posts.getAll(); - - console.log(allUsers, allPosts); - - // Watch updates - users.subscribe(state => console.log("Users changed:", state.items)); - posts.subscribe(state => console.log("Posts changed:", state.items)); - ``` - - ## IndexDB with offline/online live database synchronization - - This is experimental currently. This api allows you make your apps offline first. - - ```ts -import createIndexDBStore from "rivra/stores" -// import IndexDBManager from "rivra/stores" - - const userStore = createIndexDBStore({ - dbName: "MyAppDB", - storeName: "users", - keyPath: "id", - version: 1, - sync: { - endpoint: "https://api.example.com/users", - async push(item, action) { - // simple example using fetch - if (action === "add") await fetch(this.endpoint!, { method: "POST", body: JSON.stringify(item) }); - if (action === "update") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "PUT", body: JSON.stringify(item) }); - if (action === "remove") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "DELETE" }); - }, - async pull() { - const res = await fetch("https://api.example.com/users"); - return res.json(); - }, - interval: 15000, - autoSync: true, - onOffline: () => console.log("User store offline"), - onOnline: () => console.log("User store online"), - }, - }); - - // Multi-store manager usage with global + per-store callbacks: - - const db = new IndexDBManager("MyAppDB", 1, { - onOffline: () => console.log("Global offline"), - onOnline: () => console.log("Global online"), - }); - - // per-store callbacks override global if provided - const users = db.createStore("users", "id", { - sync: { - onOffline: () => console.log("Users store offline"), - onOnline: () => console.log("Users store online"), - } - }); - - const posts = db.createStore<{ id: string; title: string }>("posts", "id"); - - ``` - - -### Features - -* File-based routing -* Dynamic route segments `[param]` -* URL query support -* Optional per-link router events -* Reactive `Link` component with optional loading UI -* Global progress loader -* Minimal setup—just structure `pages/` -* Minimal indexDB manager with zero dependencies. -* Zustand like global state management available in and outside components - - - - - - -# rivra + + + + + +# Rivra +**(Minimal Ripple tool kit. Not just a router)** + + + +# Router + full-stack description: +Not just a router — Rivra is a complete toolkit combining routing, state management, storage, and full-stack app tooling, all fully accessible + +## +![npm](https://img.shields.io/npm/v/rivra) +![downloads](https://img.shields.io/npm/dt/rivra) +![license](https://img.shields.io/npm/l/rivra) +Ripple Logo + +----- + +> **Update:** + Rivra middleware now applies to the **client side** as well — not just API routes. +> Global Middleware and Plugins are now executed **across all routes and resources**, ensuring unified behavior. +> _(Page-specific middleware or plugin logic isn’t supported yet.)_ + +--- + + +## + +## Creating New Project + +```bash +npx rivra create my-app // wrapper around npm create ripple my-app. + +``` + +###### For existing ripple projects use the installation and init + +## Installation + +```bash +npm install rivra +npx rivra init +``` + +or + +```bash +yarn add rivra +npx rivra init +``` + + + +## Quick Start + +After initiating **Rivra** which has all the rivra routing components, the pages directory, /api (if selected full stack), the routes.ts file for your app modules and the configured App.ripple file will be visible in your project src dir. The App.ripple is optional to overwrite. + +### Directory Structure + +Here's an example `src/pages/` directory: + +```bash +pages/ +├── index.ripple # Home page +├── about.ripple # About page +└── users/ + └── [id]/ + └── user/ + └── [username]/ + └── comments/ +``` + +Here's an `api/` directory: + +```bash +api/ +├── index.ts # Root API entry point (can load all modules) +├── posts.ts # Top-level posts routes +└── users/ + └── [id]/ # Dynamic user ID + └── user/ + └── [username]/ # Dynamic username + └── comments/ + └── index.ts # User comments routes + +``` + +```ts +import type { Reply, Req, App } from "rivra" + +import type {Req, Reply, App} from "mivra" +export default async function postAPi(app: App) { + app.get("/", async (req: Req, reply: Reply) => { + + const param = req.params + const queries = req.query + + + return ({ message: "some dynamic route", params: param, query: queries }) + }) +} + +``` + + +### Rivra middlewares/plugins for fastify + +Rivra allows you to customize the server behaviours by leveraging on Fastify hooks and plugins. There are some times you may want to extend function. + +---- +```sql + +plugins/ + ├── middleware/ + │ ├── cors.ts → global middleware (order=1) + │ └── helmet.ts → global middleware (order=2) + ├── auth.md.ts → middleware only for /auth/* + ├── auth.pg.ts → /auth routes + ├── users/ + │ ├── users.md.ts → middleware only for /users/* + │ └── index.ts → /users routes + └── logger.ts → global plugin +``` + + +* Example of plugin +```ts +export const order = 2; // order from 1-10 gives you ability to prioritize the order which your hooks run. + +export default async function (req: Req, reply: Reply, app: App) { + console.log("global plugin triggered") + reply.header('X-Powered-By', 'Rivra'); + + if (req.url === "/api/users") { + reply.send({ message: "Users root route" }); + } else if (req.url === "/api/users/profile") { + reply.send({ message: "User profile route" }); + } + +} + + +``` + +* Example of middlewre +```ts + +export default function (req: Req, res: Reply,) { + console.log("Protected middleware Incoming:", req.method, req.url); + const truthy = true + if (!truthy) { + res.code(400).send({error: "Bad request"}) + return + } + if (req.url === "/api/blocked") { + res.code(403).send({ error: "Forbidden" }); + return; + } + +} + + +``` + +#### Here’s a simple and clear table that explains the difference between a plugin and a middleware and how Rivra handle them. + +| **Aspect** | **Plugin** | **Middleware** | +| ------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| **Purpose** | Extends or configures the Fastify instance (adds routes, hooks, decorators, schemas, etc.) | Intercepts and processes requests before or during route handling (authentication, logging, validation, etc.) | +| **Function Signature** | `(app: FastifyInstance) => void` | `(req: FastifyRequest, reply: FastifyReply, app: FastifyInstance) => void` | +| **Execution Time** | Runs **once** during server startup to register logic | Runs **on every incoming request** (depending on where it’s applied) | +| **Common Use Cases** | Adding routes, setting up databases, registering third-party plugins, global configurations | Authentication checks, access control, logging, pre-processing requests` | Using hooks like `app.addHook("preHandler", handler)` or route-level middlewares | +| **Scope** | Can be **scoped** to a prefix (e.g., `/api/protected`) | Can be **global** or **route-specific** | +| **Impact** | Modifies or enhances the app’s capabilities | Filters or modifies the request/response cycle | +| **File Naming Convention (in your system)** | `*.pg.ts` (Scoped Plugin) | `*.md.ts` (Middleware) | +| **Example (Plugin)** | `export default async function (app) { app.get('/hello', () => 'Hi');}` | | +| **Example (Middleware)** | | `t export default async function (req, reply, app) { if (!req.headers.auth) reply.code(401).send({ error: 'Unauthorized' });}` | + + +| Type | Example File | Prefix Applied | Loaded As | Order | +| ----------------- | ------------------------------- | -------------- | ---------- | -------------------------- | +| Global middleware | `/plugins/middleware/logger.ts` | none | middleware | before all plugins | +| Route middleware | `/plugins/auth/auth.md.ts` | `/auth` | middleware | after global, before route | +| Prefixed plugin | `/plugins/auth.pg.ts` | `/auth` | plugin | normal | +| Folder plugin | `/plugins/payments/index.ts` | `/payments` | plugin | normal | +| Global plugin | `/plugins/cors.ts` | none | plugin | normal | + + +Dynamic segments use `[param]` notation like `[id]` or `[username]`. + +--- + +### App Component + +```ts +import {PageRoutes} from "rivra/router" +import { modules } from "./routes"; + +export component App() { + +} +``` + +That's it! Your routing is now set up. `PageRoutes` automatically reads your `pages/` folder and matches routes, including dynamic parameters. + +--- + +### Link Component + +Use the `Link` component for navigation: + +```ts +import Link from "rivra/router" + +export component Navigation() { + +} +``` + +#### Props: + +| Prop | Type | Default | Description | +| ------------------ | --------------------- | ------- | --------------------------------------------- | +| `href` | `string` | — | Path to navigate to | +| `children` | `Component` | — | Content to render inside link | +| `onLoading` | `() => void` | — | Callback when navigation starts | +| `emitEvent` | `boolean` | `true` | Whether to trigger route events for this link | +| `loadingComponent` | `Component` | — | Optional component to show while loading | +| `className` | `string` | — | Additional CSS class names for styling | +| `queries` | `Record` | — | Optional query parameters for URLSearch | + + +--- + +### Router And Events + +You can subscribe to router events if you need custom behavior: + +```ts +import { useRouter } from "rivra/router" + +const router = useRouter(); + +router.on("start", path => console.log("Navigating to:", path)); +router.on("complete", path => console.log("Navigation finished:", path)); +router.on("change", path => console.log("Route changed:", path)); + + + +//Guard back navigation +router.beforePopState((url) => { + if (url === "/protected") { + console.log("Navigation blocked:", url); + return false; // Cancel navigation + } +}); + +// Navigate to a new route +router.push("/users/42?tab=posts"); +router.push("/users/42?tab=posts", true, false, {name: "John", age: 20}); // path, emitEvent, shallow (partial url change), queries + + +//Replace URL shallowly (no full sync)// +router.replace("/users/42?tab=profile", true, true); + +// Prefetch a route +router.prefetch("/about"); + +// Resolve href +console.log("Resolved href:", router.resolveHref("/contact?ref=home")); + +// Access reactive properties +console.log("Current path:", router.path); +console.log("Query params:", router.queries); +console.log("Dynamic params:", router.params); +console.log("Full URL:", router.asPath); + +// Access full URL info +console.log(router.host); +console.log(router.hostname); +console.log(router.origin); +console.log(router.protocol); +console.log(router.port); +console.log(router.href); +console.log(router.search); +console.log(router.hash); +``` + +* `start`: triggered before navigation begins +* `complete`: triggered after navigation finishes +* `change`: triggered on every path change + +You can opt out of events per `Link` with `emitEvent={false}`. + +--- + +### Dynamic Route Params + +Access route params and queries in any component: + +```ts +import { useRouter } from "rivra/router" + +export component UserProfile() { + const router = useRouter(); + + const id = router.params.id; // dynamic param + const username = router.params.username; + const queryName = router.queries.name; // URL query ?name=... + // or + const {params, queries} = router; + +
+ {"User ID: " + id} + {"Username: " + username} + {"Query name: " + queryName} +
+} +``` + +--- + +### Global Loading Indicator (Optional) +you can disable it with props ```ts + +``` + +```ts +import {PageRoutes} from "rivra/router" +import { modules } from "./routes"; + +export component App() { + +} + +``` + + +--- + +--- + +### A minimal reactive store that manages shared state across your app with an intuitive API. It provides reactivity, persistence, and derived state — all in under a few lines of code. + +| Feature | Description | +| ------------------------------ | ---------------------------------------------------------------------------------------- | +| **`get()`** | Returns the current store value. | +| **`set(next)`** | Replaces the entire store value. | +| **`update(partialOrFn)`** | Merges new data into the store. Supports both object patching and callback styles. | +| **`subscribe(fn, selector?)`** | Reactively listens for state changes, optionally to a selected portion. | +| **`derive(selector)`** | Creates a new store derived from a specific part of another store (like computed state). | +| **`delete(keys)`** | Removes one or more keys from the store. | +| **`clear()`** | Resets store to initial state and removes persisted data. | +| **`persist` (option)** | Automatically saves and restores state from `localStorage`. | + + + +-------------------- Example Stores -------------------- +```ts + //* Route store for storing current route path + //* Persisted in localStorage as "routeStore" + //*/ +export const routeStore = createStore( + { path: "/" }, + { persist: true, storageKey: "routeStore" } +); + +/** + * App store for global state + * Tracks path, user info, theme + * Persisted in localStorage as "appStore" + */ +export const appStore = createStore( + { path: "/", user: null as null | { name: string }, theme: "light" }, + { persist: true, storageKey: "appStore" } +); + +``` +Here are extra two simple Hello World store examples for getting started and explain things better. + +### Store without persist (default) +```ts +import { createStore } from "rivra/store" + +// Create a simple store +const helloStore = createStore({ message: "Hello World!" }); + +// Subscribe to changes +helloStore.subscribe(state => { + console.log("Current message:", state.message); +}); + +// Get changes anywhere +const data = helloStore.get(); +console.log(helloStore) // { message: Current message} +console.log(data.message) // Current message + + +// Update the store +helloStore.update({ message: "Hello Ripple!" }); + +// Output: +// Current message: Hello World! +// Current message: Hello Ripple! + + + +``` + +### Store with persist + +```ts +import { createStore } from "rivra/store" +import { track } from "ripple" + +const message = track("") + +// Create a persisted store +const persistentHelloStore = createStore( + { message: "Hello Persistent World!" }, + { persist: true, storageKey: "helloStore" } +); + +// Subscribe to changes +persistentHelloStore.subscribe(state => { + console.log("Current message:", state.message); +}); + + +// Get changes anywhere +const data = helloStore.get(); +console.log(helloStore) // { message: Current message} +console.log(data.message) // Current message + + +// Update the store +persistentHelloStore.update({ message: "Updated and Persisted!" }); + + +// Callback update (safe addition) +persistentHelloStore.update(prev => ({ message: prev.message + " " + @message })); + + +// Reload the page and subscribe again +persistentHelloStore.subscribe(state => { + console.log("After reload:", state.message); +}); + +// Output (after reload): +// After reload: Updated and Persisted! + + + +export const appStore = createStore( + { + user: { name: "Joe", location: "unknown", preferences: [] }, + count: 0, + theme: "light", + }, + { persist: true, storageKey: "appStore" } +); + + + +// Subscribe to entire state +appStore.subscribe(s => console.log("State changed:", s)); + +// Watch a specific value +appStore.watch(s => s.count, (n, o) => console.log(`Count: ${o} → ${n}`)); + +// Use middleware for logging +appStore.use((state, action, payload) => + console.log(`[${action}]`, payload, state) +); + +// Partial update +appStore.update({ count: 1 }); + +// Callback update (safe addition) +appStore.update(prev => ({ count: prev.count + 1 })); + +// Derived store +const themeStore = appStore.derive(s => s.theme); +themeStore.subscribe(theme => console.log("Theme:", theme)); + +// Clear store +appStore.clear(); + + +``` + + +### Here’s a concise side-by-side comparison between Rivra createStore and Zustand: + +| Feature / Aspect | **createStore** (Rivra) | **Zustand** | +| ------------------------ | ------------------------------------ | ---------------------------------------- | +| **Size / Complexity** | Ultra-light (~2 KB) | Larger, includes middleware and devtools | +| **Reactivity Model** | Manual `subscribe` / `derive` | React hooks (`useStore`) | +| **Selectors** | Optional selector argument | Built-in via hooks | +| **Persistence** | Native `persist` option | Needs middleware plugin | +| **DevTools Integration** | Coming soon | Built-in Redux DevTools support | +| **Middleware** | Planned via `use()` | Full middleware API | +| **Callback Updates** | Supported: `update(prev => {...})` | Supported: `set(state => {...})` | +| **Derived Stores** | `derive(selector)` | Selectors or derived state | +| **Performance** | Minimal overhead | Optimized for React, slightly heavier | +| **Framework Support** | Framework-agnostic | React-only | +| **TypeScript** | Fully typed generics | Excellent TS support | +| **Persistence Control** | Built-in localStorage | Plugin required | +| **Use Case Fit** | Libraries & multi-framework projects | React apps needing global state | + +--- + + +## Minimal IndexDB Manager with Zero Dependencies. + + 📘 Example Usage + -------------------------------------------------------------------------- + + ✅ Minimal Example + ```ts + import createIndexDBStore from "rivra/stores" +import IndexDBManager from "rivra/stores" + const userStore = createIndexDBStore({ + storeName: 'users', + }); + await userStore.add({ id: 'u1', name: 'Joseph', age: 22 }); + const all = await userStore.getAll(); + console.log(all); + ``` + + ✅ Full Configuration Example + ```ts + interface User { + id: string; + name: string; + age: number; + } + + const userStore = createIndexDBStore({ + dbName: "MyAppDB", + storeName: "users", + keyPath: "id", + version: 1, + }); + + // ➕ Add record + await userStore.add({ id: 'u1', name: 'Ada', age: 45 }); + + // 🔁 Update record by key or object + await userStore.update('u1', { age: 46 }); + + // 🔍 Get a record by key + const user = await userStore.get('u1'); + console.log('Single user:', user); + + // 📦 Get all records + const allUsers = await userStore.getAll(); + console.log('All users:', allUsers); + + // ❌ Remove record by ID + await userStore.remove('u1'); + + // 🧹 Clear all records + await userStore.clear(); + + // 🔎 Query using a filter function + const adults = await userStore.query(u => u.age > 30); + console.log('Adults:', adults); + + // 👂 Subscribe to store changes (reactive) + const unsubscribe = userStore.subscribe(state => { + console.log('Store changed:', state.items); + }); + + // 👁️ Watch specific property or subset of data + const watchAdults = userStore.deriveQuery(items => items.filter(u => u.age > 30)); + watchAdults.subscribe(adults => console.log('Adults updated:', adults)); + + // 🔦 Filter (where) + const namedJohn = await userStore.where({ name: 'John' }); + console.log('Users named John:', namedJohn); + + // 🥇 Get first matching record + const firstUser = await userStore.first({ age: 46 }); + console.log('First matching user:', firstUser); + + // 🔍 Find by ID (alias for get) + const found = await userStore.find('u1'); + console.log('Found user:', found); + + // 🧩 Put (alias for update) + await userStore.put({ id: 'u1', name: 'Ada', age: 50 }); + + // 💳 Perform custom transaction + await userStore.transaction(tx => { + const store = tx.objectStore('users'); + store.add({ id: 'u2', name: 'Ken', age: 35 }); + }); + + // 🧭 Watch specific user reactively + const watchUser = userStore.deriveQuery(items => items.find(u => u.id === 'u1') || null); + watchUser.subscribe(u => console.log('u1 changed:', u)); + + // 🧹 Unsubscribe from store updates + unsubscribe(); + ``` + + ✅ Multi-Store Example (using IndexDBManager) + ```ts + interface User { + id: string; + name: string; + } + + interface Post { + id: string; + title: string; + } + + // Create manager + const db = new IndexDBManager("MyAppDB"); + + // Create multiple stores + const users = db.createStore("users", "id"); + const posts = db.createStore("posts", "id"); + + // Add data + await users.add({ id: "u1", name: "Joe" }); + await posts.add({ id: "p1", title: "Hello World" }); + + // Query data + const allUsers = await users.getAll(); + const allPosts = await posts.getAll(); + + console.log(allUsers, allPosts); + + // Watch updates + users.subscribe(state => console.log("Users changed:", state.items)); + posts.subscribe(state => console.log("Posts changed:", state.items)); + ``` + + ## IndexDB with offline/online live database synchronization + + This is experimental currently. This api allows you make your apps offline first. + + ```ts +import createIndexDBStore from "rivra/stores" +// import IndexDBManager from "rivra/stores" + + const userStore = createIndexDBStore({ + dbName: "MyAppDB", + storeName: "users", + keyPath: "id", + version: 1, + sync: { + endpoint: "https://api.example.com/users", + async push(item, action) { + // simple example using fetch + if (action === "add") await fetch(this.endpoint!, { method: "POST", body: JSON.stringify(item) }); + if (action === "update") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "PUT", body: JSON.stringify(item) }); + if (action === "remove") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "DELETE" }); + }, + async pull() { + const res = await fetch("https://api.example.com/users"); + return res.json(); + }, + interval: 15000, + autoSync: true, + onOffline: () => console.log("User store offline"), + onOnline: () => console.log("User store online"), + }, + }); + + // Multi-store manager usage with global + per-store callbacks: + + const db = new IndexDBManager("MyAppDB", 1, { + onOffline: () => console.log("Global offline"), + onOnline: () => console.log("Global online"), + }); + + // per-store callbacks override global if provided + const users = db.createStore("users", "id", { + sync: { + onOffline: () => console.log("Users store offline"), + onOnline: () => console.log("Users store online"), + } + }); + + const posts = db.createStore<{ id: string; title: string }>("posts", "id"); + + ``` + + +### Features + +* File-based routing +* Dynamic route segments `[param]` +* URL query support +* Optional per-link router events +* Reactive `Link` component with optional loading UI +* Global progress loader +* Minimal setup—just structure `pages/` +* Minimal indexDB manager with zero dependencies. +* Zustand like global state management available in and outside components + + + + + + +# rivra diff --git a/cli.ts b/cli.ts index 6f0821a..38533c4 100644 --- a/cli.ts +++ b/cli.ts @@ -1,471 +1,471 @@ -#!/usr/bin/env node -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; -import readline from "readline"; -import { execSync } from "child_process"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const args = process.argv.slice(2); - -/** Prompt user input */ -function askQuestion(query: string): Promise { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - return new Promise((resolve) => - rl.question(query, (ans) => { - rl.close(); - resolve(ans.trim()); - }) - ); -} - -const file_routing = async () => { - const targetDir = path.join(process.cwd(), "src"); - const pagesDir = path.join(targetDir, "pages"); - - // Ensure src/pages directory exists - fs.mkdirSync(pagesDir, { recursive: true }); - - // --- Write index.ripple --- - const indexContent = ` -import { track } from 'ripple'; - -export default component Home() { - let count = track(0); - -
-
- -

- {'Welcome to Rivra '}{"Ripple!"} -

- -
- - {@count} - -
- -

- {'Get started by editing '}{"src/App.ripple"} -

- - -
- - - - -
-} -`; - - // --- Write notfound.ripple --- - const notfoundContent = ` -import { useRouter, Link } from "rivra/router"; - -export default component NotFound404() { -
-

- {"40"}{"4"} -

-

{"Oops! The page you are looking for does not exist."}

- {"Go Home"} - - -
-} -`; - - // --- Write App.ripple (always overwrite) --- - const appContent = ` -import { PageRoutes } from "rivra/router"; -import { modules } from "./routes"; - -export component App() { - -} -`; - - fs.writeFileSync( - path.join(pagesDir, "index.ripple"), - indexContent.trim(), - "utf8" - ); - fs.writeFileSync( - path.join(pagesDir, "notfound.ripple"), - notfoundContent.trim(), - "utf8" - ); - fs.writeFileSync( - path.join(targetDir, "App.ripple"), - appContent.trim(), - "utf8" - ); - - // --- Write routes.ts --- - const routesFile = path.join(targetDir, "routes.ts"); - const routesContent = ` -// Auto-generated by rivra/router -// @ts-ignore -export const modules = import.meta.glob("/src/pages/**/*.ripple", { eager: true }); -`; - fs.writeFileSync(routesFile, routesContent.trim(), "utf8"); - - // --- Create vercel.json --- - const vercelPath = path.join(process.cwd(), "vercel.json"); - const vercelConfig = { - rewrites: [{ source: "/(.*)", destination: "/index.html" }], - }; - fs.writeFileSync(vercelPath, JSON.stringify(vercelConfig, null, 2)); - - console.log( - "✅ Created src/pages/index.ripple, notfound.ripple, App.ripple, routes.ts, and vercel.json" - ); -}; - -/** CREATE COMMAND */ -async function createApp() { - let appName = args[1] || ""; - if (!appName) { - appName = await askQuestion("Enter project name: "); - } - - console.log(`Creating Rivra app ${appName}...`); - try { - execSync(`npm create ripple --y ${appName}`, { stdio: "inherit" }); - process.chdir(appName); - - console.log("Installing dependencies..."); - execSync("npm install", { stdio: "inherit" }); - execSync("npm i rivra@latest", { stdio: "inherit" }); - - await initApp(); - } catch (err: any) { - console.error("Failed to create Rivra app:", err.message); - process.exit(1); - } -} - -/** DEV COMMAND */ -async function startDev() { - console.log("Starting Rivra development server..."); - try { - execSync( - `npx cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS="--no-deprecation" npx nodemon --watch api --watch plugins --ext ts,js --exec "node --loader ts-node/esm server.ts"`, - { stdio: "inherit" } - ); - } catch (err: any) { - console.error("Dev server failed:", err.message); - process.exit(1); - } -} - -/** BUILD COMMAND */ -async function buildApp() { - console.log("Building Rivra app..."); - try { - execSync("npx vite build && tsc -p tsconfig.build.json", { - stdio: "inherit", - }); - } catch (err: any) { - console.error("Build failed: ", err.message?.toString()); - process.exit(1); - } -} - -/** START COMMAND */ -async function startApp() { - console.log("Starting Rivra app..."); - try { - execSync(`NODE_ENV=production node dist/server/server.js`, { - stdio: "inherit", - }); - } catch (err: any) { - console.error("Failed to start app:", err.message); - process.exit(1); - } -} - -/** INIT COMMAND */ -async function initApp() { - console.log("Initializing Ripple Tools (Full-stack mode)..."); - - try { - await file_routing(); - } catch (err: any) { - console.error("Failed to run complete:", err.message); - process.exit(1); - } - - const projectDir = process.cwd(); - const srcDir = path.join(projectDir, "src"); - fs.mkdirSync(srcDir, { recursive: true }); - - const enableFullStack = await askQuestion( - "Enable full-stack mode (Fastify + Vite)? (y/n): " - ); - - if (enableFullStack.toLowerCase() !== "y") { - console.log("Ripple project initialized (frontend only)."); - return; - } - - console.log("Setting up full-stack mode..."); - - const indexPath = path.join(projectDir, "server.ts"); - const nodemonPath = path.join(projectDir, "nodemon.json"); - const serverDir = path.join(projectDir, "api"); - const apiPath = path.join(serverDir, "index.ts"); - const rootPlugin = path.join(projectDir, "plugins"); - const pluginPath = path.join(rootPlugin, "global.ts"); - const middlewareDir = path.join(rootPlugin, "middleware"); - const middlewarePath = path.join(middlewareDir, "middleware.ts"); - - fs.mkdirSync(serverDir, { recursive: true }); - fs.mkdirSync(rootPlugin, { recursive: true }); - fs.mkdirSync(middlewareDir, { recursive: true }); - - const indexContent = ` -import { StartServer } from "rivra/server" - -(async () => { - const app = await StartServer(); - // app.register(...) // register plugins and add custom instance behaviours. -})();`; - fs.writeFileSync(indexPath, indexContent.trim() + "\n"); - - const apiContent = ` -import type { App, Req, Reply } from "rivra" - -export default async function registerApi(app: App) { - app.get("/", async (req: Req, reply: Reply) => { - return { message: "Hello from Ripple full-stack!" } - }) -}`; - fs.writeFileSync(apiPath, apiContent.trim() + "\n"); - - const nodemonContent = ` -{ - "watch": ["index.ts", "api/**/*"], - "ext": "ts,js", - "ignore": ["node_modules"], - "exec": "ts-node --esm server.ts" -}`; - fs.writeFileSync(nodemonPath, nodemonContent.trim() + "\n"); - - const pluginContent = ` -import type { App, Req, Reply } from "rivra" - -export default async function (req: Req, reply: Reply, app: App) { - console.log("global plugin triggered") - if (req.url === "/api/users") { - reply.send({ message: "Users root route" }); - } else if (req.url === "/api/users/profile") { - reply.send({ message: "User profile route" }); - } - -} - -// plugins/some_plugin.ts -> global plugin (all routes) -// plugins/auth.pg.ts -> plugin -> api/auth -// plugins/auth.md.ts -> middleware -> api/auth -// plugins/users/index.ts -> plugin -> api/users -// plugins/users/users.md.ts -> middleware -> api/users -// You can also access "app" for shared logic - -`; - fs.writeFileSync(pluginPath, pluginContent.trim() + "\n"); - - const middlewareContent = ` -import type {Req, Reply } from "rivra" - - -export default function (req: Req, res: Reply) { - console.log("Incoming:", req.method, req.url); - - const truthy = true - if (!truthy) { - res.code(400).send({error: "Bad request"}) - return - } - if (req.url === "/api/blocked") { - res.code(403).send({ error: "Forbidden" }); - return; - } - -} - -// plugins/middleware/some_middleware.ts -> global middleware (all routes) -// plugins/auth.pg.ts -> plugin -> api/auth -// plugins/auth.md.ts -> middleware -> api/auth -// plugins/users/index.ts -> plugin -> api/users -// plugins/users/users.md.ts -> middleware -> api/users -`; - fs.writeFileSync(middlewarePath, middlewareContent.trim() + "\n"); - - // tsconfig setup - const tsconfig = { - compilerOptions: { - target: "ESNext", - module: "NodeNext", - lib: ["ES2022", "DOM", "DOM.Iterable"], - allowSyntheticDefaultImports: true, - esModuleInterop: true, - moduleResolution: "nodenext", - jsx: "preserve", - jsxImportSource: "ripple", - noEmit: true, - isolatedModules: true, - types: ["node"], - allowImportingTsExtensions: false, - skipLibCheck: true, - noEmitOnError: false, - }, - }; - fs.writeFileSync( - path.join(projectDir, "tsconfig.json"), - JSON.stringify(tsconfig, null, 2) - ); - - const tsBuild = { - extends: "./tsconfig.json", - include: ["index.ts", "api/**/*.ts", "server.ts", "plugins"], - exclude: ["node_modules", "dist", "src/**/*.ripple", "src/pages"], - compilerOptions: { noEmit: false, outDir: "dist/server" }, - }; - fs.writeFileSync( - path.join(projectDir, "tsconfig.build.json"), - JSON.stringify(tsBuild, null, 2) - ); - - // update package.json - const pkgPath = path.join(projectDir, "package.json"); - const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); - pkg.scripts = { - ...pkg.scripts, - dev: "npx rivra dev", - build: "npx rivra build", - start: "npx rivra start", - serve: "vite preview", - }; - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); - - console.log("Installing dependencies..."); - execSync("npm install fastify @fastify/middie get-port vite", { - stdio: "inherit", - }); - execSync("npm install ts-node @types/connect cross-env -D", { stdio: "inherit" }); - - console.log(` -Rivra full-stack initialized successfully! - -Next steps: - npm run dev # start development server - npm run build # build for production - npm start # run production build -`); -} - -/** CLI Router */ -async function main() { - switch (args[0]) { - case "create": - await createApp(); - break; - case "init": - await initApp(); - break; - case "dev": - await startDev(); - break; - case "build": - await buildApp(); - break; - case "start": - await startApp(); - break; - default: - console.log(` -Usage: rivra - -Commands: - create Create a new Rivra app - init Initialize Rivra (full-stack mode) - dev Start development server - build Build project for production - start Run built app -`); - } -} - -main(); +#!/usr/bin/env node +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import readline from "readline"; +import { execSync } from "child_process"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const args = process.argv.slice(2); + +/** Prompt user input */ +function askQuestion(query: string): Promise { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + return new Promise((resolve) => + rl.question(query, (ans) => { + rl.close(); + resolve(ans.trim()); + }) + ); +} + +const file_routing = async () => { + const targetDir = path.join(process.cwd(), "src"); + const pagesDir = path.join(targetDir, "pages"); + + // Ensure src/pages directory exists + fs.mkdirSync(pagesDir, { recursive: true }); + + // --- Write index.ripple --- + const indexContent = ` +import { track } from 'ripple'; + +export default component Home() { + let count = track(0); + +
+
+ +

+ {'Welcome to Rivra '}{"Ripple!"} +

+ +
+ + {@count} + +
+ +

+ {'Get started by editing '}{"src/App.ripple"} +

+ + +
+ + + + +
+} +`; + + // --- Write notfound.ripple --- + const notfoundContent = ` +import { useRouter, Link } from "rivra/router"; + +export default component NotFound404() { +
+

+ {"40"}{"4"} +

+

{"Oops! The page you are looking for does not exist."}

+ {"Go Home"} + + +
+} +`; + + // --- Write App.ripple (always overwrite) --- + const appContent = ` +import { PageRoutes } from "rivra/router"; +import { modules } from "./routes"; + +export component App() { + +} +`; + + fs.writeFileSync( + path.join(pagesDir, "index.ripple"), + indexContent.trim(), + "utf8" + ); + fs.writeFileSync( + path.join(pagesDir, "notfound.ripple"), + notfoundContent.trim(), + "utf8" + ); + fs.writeFileSync( + path.join(targetDir, "App.ripple"), + appContent.trim(), + "utf8" + ); + + // --- Write routes.ts --- + const routesFile = path.join(targetDir, "routes.ts"); + const routesContent = ` +// Auto-generated by rivra/router +// @ts-ignore +export const modules = import.meta.glob("/src/pages/**/*.ripple", { eager: true }); +`; + fs.writeFileSync(routesFile, routesContent.trim(), "utf8"); + + // --- Create vercel.json --- + const vercelPath = path.join(process.cwd(), "vercel.json"); + const vercelConfig = { + rewrites: [{ source: "/(.*)", destination: "/index.html" }], + }; + fs.writeFileSync(vercelPath, JSON.stringify(vercelConfig, null, 2)); + + console.log( + "✅ Created src/pages/index.ripple, notfound.ripple, App.ripple, routes.ts, and vercel.json" + ); +}; + +/** CREATE COMMAND */ +async function createApp() { + let appName = args[1] || ""; + if (!appName) { + appName = await askQuestion("Enter project name: "); + } + + console.log(`Creating Rivra app ${appName}...`); + try { + execSync(`npm create ripple --y ${appName}`, { stdio: "inherit" }); + process.chdir(appName); + + console.log("Installing dependencies..."); + execSync("npm install", { stdio: "inherit" }); + execSync("npm i rivra@latest", { stdio: "inherit" }); + + await initApp(); + } catch (err: any) { + console.error("Failed to create Rivra app:", err.message); + process.exit(1); + } +} + +/** DEV COMMAND */ +async function startDev() { + console.log("Starting Rivra development server..."); + try { + execSync( + `npx cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS="--no-deprecation" npx nodemon --watch api --watch plugins --ext ts,js --exec "node --loader ts-node/esm server.ts"`, + { stdio: "inherit" } + ); + } catch (err: any) { + console.error("Dev server failed:", err.message); + process.exit(1); + } +} + +/** BUILD COMMAND */ +async function buildApp() { + console.log("Building Rivra app..."); + try { + execSync("npx vite build && tsc -p tsconfig.build.json", { + stdio: "inherit", + }); + } catch (err: any) { + console.error("Build failed: ", err.message?.toString()); + process.exit(1); + } +} + +/** START COMMAND */ +async function startApp() { + console.log("Starting Rivra app..."); + try { + execSync(`NODE_ENV=production node dist/server/server.js`, { + stdio: "inherit", + }); + } catch (err: any) { + console.error("Failed to start app:", err.message); + process.exit(1); + } +} + +/** INIT COMMAND */ +async function initApp() { + console.log("Initializing Ripple Tools (Full-stack mode)..."); + + try { + await file_routing(); + } catch (err: any) { + console.error("Failed to run complete:", err.message); + process.exit(1); + } + + const projectDir = process.cwd(); + const srcDir = path.join(projectDir, "src"); + fs.mkdirSync(srcDir, { recursive: true }); + + const enableFullStack = await askQuestion( + "Enable full-stack mode (Fastify + Vite)? (y/n): " + ); + + if (enableFullStack.toLowerCase() !== "y") { + console.log("Ripple project initialized (frontend only)."); + return; + } + + console.log("Setting up full-stack mode..."); + + const indexPath = path.join(projectDir, "server.ts"); + const nodemonPath = path.join(projectDir, "nodemon.json"); + const serverDir = path.join(projectDir, "api"); + const apiPath = path.join(serverDir, "index.ts"); + const rootPlugin = path.join(projectDir, "plugins"); + const pluginPath = path.join(rootPlugin, "global.ts"); + const middlewareDir = path.join(rootPlugin, "middleware"); + const middlewarePath = path.join(middlewareDir, "middleware.ts"); + + fs.mkdirSync(serverDir, { recursive: true }); + fs.mkdirSync(rootPlugin, { recursive: true }); + fs.mkdirSync(middlewareDir, { recursive: true }); + + const indexContent = ` +import { StartServer } from "rivra/server" + +(async () => { + const app = await StartServer(); + // app.register(...) // register plugins and add custom instance behaviours. +})();`; + fs.writeFileSync(indexPath, indexContent.trim() + "\n"); + + const apiContent = ` +import type { App, Req, Reply } from "rivra" + +export default async function registerApi(app: App) { + app.get("/", async (req: Req, reply: Reply) => { + return { message: "Hello from Ripple full-stack!" } + }) +}`; + fs.writeFileSync(apiPath, apiContent.trim() + "\n"); + + const nodemonContent = ` +{ + "watch": ["index.ts", "api/**/*"], + "ext": "ts,js", + "ignore": ["node_modules"], + "exec": "ts-node --esm server.ts" +}`; + fs.writeFileSync(nodemonPath, nodemonContent.trim() + "\n"); + + const pluginContent = ` +import type { App, Req, Reply } from "rivra" + +export default async function (req: Req, reply: Reply, app: App) { + console.log("global plugin triggered") + if (req.url === "/api/users") { + reply.send({ message: "Users root route" }); + } else if (req.url === "/api/users/profile") { + reply.send({ message: "User profile route" }); + } + +} + +// plugins/some_plugin.ts -> global plugin (all routes) +// plugins/auth.pg.ts -> plugin -> api/auth +// plugins/auth.md.ts -> middleware -> api/auth +// plugins/users/index.ts -> plugin -> api/users +// plugins/users/users.md.ts -> middleware -> api/users +// You can also access "app" for shared logic + +`; + fs.writeFileSync(pluginPath, pluginContent.trim() + "\n"); + + const middlewareContent = ` +import type {Req, Reply } from "rivra" + + +export default function (req: Req, res: Reply) { + console.log("Incoming:", req.method, req.url); + + const truthy = true + if (!truthy) { + res.code(400).send({error: "Bad request"}) + return + } + if (req.url === "/api/blocked") { + res.code(403).send({ error: "Forbidden" }); + return; + } + +} + +// plugins/middleware/some_middleware.ts -> global middleware (all routes) +// plugins/auth.pg.ts -> plugin -> api/auth +// plugins/auth.md.ts -> middleware -> api/auth +// plugins/users/index.ts -> plugin -> api/users +// plugins/users/users.md.ts -> middleware -> api/users +`; + fs.writeFileSync(middlewarePath, middlewareContent.trim() + "\n"); + + // tsconfig setup + const tsconfig = { + compilerOptions: { + target: "ESNext", + module: "NodeNext", + lib: ["ES2022", "DOM", "DOM.Iterable"], + allowSyntheticDefaultImports: true, + esModuleInterop: true, + moduleResolution: "nodenext", + jsx: "preserve", + jsxImportSource: "ripple", + noEmit: true, + isolatedModules: true, + types: ["node"], + allowImportingTsExtensions: false, + skipLibCheck: true, + noEmitOnError: false, + }, + }; + fs.writeFileSync( + path.join(projectDir, "tsconfig.json"), + JSON.stringify(tsconfig, null, 2) + ); + + const tsBuild = { + extends: "./tsconfig.json", + include: ["index.ts", "api/**/*.ts", "server.ts", "plugins"], + exclude: ["node_modules", "dist", "src/**/*.ripple", "src/pages"], + compilerOptions: { noEmit: false, outDir: "dist/server" }, + }; + fs.writeFileSync( + path.join(projectDir, "tsconfig.build.json"), + JSON.stringify(tsBuild, null, 2) + ); + + // update package.json + const pkgPath = path.join(projectDir, "package.json"); + const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); + pkg.scripts = { + ...pkg.scripts, + dev: "npx rivra dev", + build: "npx rivra build", + start: "npx rivra start", + serve: "vite preview", + }; + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); + + console.log("Installing dependencies..."); + execSync("npm install fastify @fastify/middie get-port vite", { + stdio: "inherit", + }); + execSync("npm install ts-node @types/connect cross-env -D", { stdio: "inherit" }); + + console.log(` +Rivra full-stack initialized successfully! + +Next steps: + npm run dev # start development server + npm run build # build for production + npm start # run production build +`); +} + +/** CLI Router */ +async function main() { + switch (args[0]) { + case "create": + await createApp(); + break; + case "init": + await initApp(); + break; + case "dev": + await startDev(); + break; + case "build": + await buildApp(); + break; + case "start": + await startApp(); + break; + default: + console.log(` +Usage: rivra + +Commands: + create Create a new Rivra app + init Initialize Rivra (full-stack mode) + dev Start development server + build Build project for production + start Run built app +`); + } +} + +main(); diff --git a/nodemon.json b/nodemon.json index c25b075..0f57799 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,6 +1,6 @@ -{ - "watch": ["index.ts", "api/**/*"], - "ext": "ts,js", - "ignore": ["node_modules"], - "exec": "ts-node --esm index.ts" -} +{ + "watch": ["index.ts", "api/**/*"], + "ext": "ts,js", + "ignore": ["node_modules"], + "exec": "ts-node --esm index.ts" +} diff --git a/package-lock.json b/package-lock.json index 7412fdf..9dc16be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3030 +1,3040 @@ -{ - "name": "rivra", - "version": "0.0.15", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "rivra", - "version": "0.0.15", - "license": "MIT", - "dependencies": { - "@fastify/middie": "^9.0.3", - "@fastify/static": "^8.3.0", - "get-port": "^7.1.0", - "ripple": "^0.2.140", - "vite": "^7.1.6", - "vite-plugin-ripple": "^0.2.56" - }, - "bin": { - "rivra": "dist/cli.js" - }, - "devDependencies": { - "@types/node": "^24.5.2", - "fastify": "^5.6.1", - "nodemon": "^3.1.10", - "ts-node": "^10.9.2", - "typescript": "^5.9.3" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@fastify/accept-negotiator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", - "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@fastify/ajv-compiler": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", - "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0" - } - }, - "node_modules/@fastify/error": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", - "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@fastify/fast-json-stringify-compiler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", - "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "fast-json-stringify": "^6.0.0" - } - }, - "node_modules/@fastify/forwarded": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", - "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@fastify/merge-json-schemas": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", - "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/@fastify/middie": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@fastify/middie/-/middie-9.0.3.tgz", - "integrity": "sha512-7OYovKXp9UKYeVMcjcFLMcSpoMkmcZmfnG+eAvtdiatN35W7c+r9y1dRfpA+pfFVNuHGGqI3W+vDTmjvcfLcMA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/error": "^4.0.0", - "fastify-plugin": "^5.0.0", - "path-to-regexp": "^8.1.0", - "reusify": "^1.0.4" - } - }, - "node_modules/@fastify/proxy-addr": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", - "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/forwarded": "^3.0.0", - "ipaddr.js": "^2.1.0" - } - }, - "node_modules/@fastify/send": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", - "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@lukeed/ms": "^2.0.2", - "escape-html": "~1.0.3", - "fast-decode-uri-component": "^1.0.1", - "http-errors": "^2.0.0", - "mime": "^3" - } - }, - "node_modules/@fastify/static": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.3.0.tgz", - "integrity": "sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/accept-negotiator": "^2.0.0", - "@fastify/send": "^4.0.0", - "content-disposition": "^0.5.4", - "fastify-plugin": "^5.0.0", - "fastq": "^1.17.1", - "glob": "^11.0.0" - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "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==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@lukeed/ms": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", - "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", - "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", - "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", - "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", - "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", - "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", - "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", - "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", - "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", - "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", - "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", - "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", - "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", - "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", - "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", - "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", - "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", - "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", - "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", - "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", - "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", - "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", - "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", - "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "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==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", - "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.14.0" - } - }, - "node_modules/abstract-logging": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", - "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.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": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/avvio": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", - "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@fastify/error": "^4.0.0", - "fastq": "^1.17.1" - } - }, - "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/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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==", - "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==", - "license": "MIT" - }, - "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/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "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/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/devalue": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.4.2.tgz", - "integrity": "sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==", - "license": "MIT" - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.10", - "@esbuild/android-arm": "0.25.10", - "@esbuild/android-arm64": "0.25.10", - "@esbuild/android-x64": "0.25.10", - "@esbuild/darwin-arm64": "0.25.10", - "@esbuild/darwin-x64": "0.25.10", - "@esbuild/freebsd-arm64": "0.25.10", - "@esbuild/freebsd-x64": "0.25.10", - "@esbuild/linux-arm": "0.25.10", - "@esbuild/linux-arm64": "0.25.10", - "@esbuild/linux-ia32": "0.25.10", - "@esbuild/linux-loong64": "0.25.10", - "@esbuild/linux-mips64el": "0.25.10", - "@esbuild/linux-ppc64": "0.25.10", - "@esbuild/linux-riscv64": "0.25.10", - "@esbuild/linux-s390x": "0.25.10", - "@esbuild/linux-x64": "0.25.10", - "@esbuild/netbsd-arm64": "0.25.10", - "@esbuild/netbsd-x64": "0.25.10", - "@esbuild/openbsd-arm64": "0.25.10", - "@esbuild/openbsd-x64": "0.25.10", - "@esbuild/openharmony-arm64": "0.25.10", - "@esbuild/sunos-x64": "0.25.10", - "@esbuild/win32-arm64": "0.25.10", - "@esbuild/win32-ia32": "0.25.10", - "@esbuild/win32-x64": "0.25.10" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "license": "MIT" - }, - "node_modules/esrap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", - "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "node_modules/fast-decode-uri-component": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", - "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", - "license": "MIT" - }, - "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-stringify": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.1.1.tgz", - "integrity": "sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/merge-json-schemas": "^0.2.0", - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0", - "json-schema-ref-resolver": "^3.0.0", - "rfdc": "^1.2.0" - } - }, - "node_modules/fast-querystring": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", - "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-decode-uri-component": "^1.0.1" - } - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastify": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.6.1.tgz", - "integrity": "sha512-WjjlOciBF0K8pDUPZoGPhqhKrQJ02I8DKaDIfO51EL0kbSMwQFl85cRwhOvmSDWoukNOdTo27gLN549pLCcH7Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/ajv-compiler": "^4.0.0", - "@fastify/error": "^4.0.0", - "@fastify/fast-json-stringify-compiler": "^5.0.0", - "@fastify/proxy-addr": "^5.0.0", - "abstract-logging": "^2.0.1", - "avvio": "^9.0.0", - "fast-json-stringify": "^6.0.0", - "find-my-way": "^9.0.0", - "light-my-request": "^6.0.0", - "pino": "^9.0.0", - "process-warning": "^5.0.0", - "rfdc": "^1.3.1", - "secure-json-parse": "^4.0.0", - "semver": "^7.6.0", - "toad-cache": "^3.7.0" - } - }, - "node_modules/fastify-plugin": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", - "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "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==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-my-way": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", - "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-querystring": "^1.0.0", - "safe-regex2": "^5.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-port": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", - "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.0.3", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "license": "ISC", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/json-schema-ref-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", - "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/light-my-request": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", - "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause", - "dependencies": { - "cookie": "^1.0.1", - "process-warning": "^4.0.0", - "set-cookie-parser": "^2.6.0" - } - }, - "node_modules/light-my-request/node_modules/process-warning": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", - "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "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/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "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/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "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/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pino": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", - "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "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", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "dev": true, - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ret": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", - "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ripple": { - "version": "0.2.140", - "resolved": "https://registry.npmjs.org/ripple/-/ripple-0.2.140.tgz", - "integrity": "sha512-ULUd1n+KvbDkL4nMPRUDbd9whJCXNfYONvMApIg7zA+6WwWbewD4lSVW3Q62X14f/i8L/eHgOwVP8weFVa+oyA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5", - "@sveltejs/acorn-typescript": "^1.0.6", - "acorn": "^8.15.0", - "clsx": "^2.1.1", - "devalue": "^5.3.2", - "esm-env": "^1.2.2", - "esrap": "^2.1.0", - "is-reference": "^3.0.3", - "magic-string": "^0.30.18", - "muggle-string": "^0.4.1", - "zimmerframe": "^1.1.2" - }, - "peerDependencies": { - "ripple": "0.2.140" - } - }, - "node_modules/rollup": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", - "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", - "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.52.4", - "@rollup/rollup-android-arm64": "4.52.4", - "@rollup/rollup-darwin-arm64": "4.52.4", - "@rollup/rollup-darwin-x64": "4.52.4", - "@rollup/rollup-freebsd-arm64": "4.52.4", - "@rollup/rollup-freebsd-x64": "4.52.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", - "@rollup/rollup-linux-arm-musleabihf": "4.52.4", - "@rollup/rollup-linux-arm64-gnu": "4.52.4", - "@rollup/rollup-linux-arm64-musl": "4.52.4", - "@rollup/rollup-linux-loong64-gnu": "4.52.4", - "@rollup/rollup-linux-ppc64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-musl": "4.52.4", - "@rollup/rollup-linux-s390x-gnu": "4.52.4", - "@rollup/rollup-linux-x64-gnu": "4.52.4", - "@rollup/rollup-linux-x64-musl": "4.52.4", - "@rollup/rollup-openharmony-arm64": "4.52.4", - "@rollup/rollup-win32-arm64-msvc": "4.52.4", - "@rollup/rollup-win32-ia32-msvc": "4.52.4", - "@rollup/rollup-win32-x64-gnu": "4.52.4", - "@rollup/rollup-win32-x64-msvc": "4.52.4", - "fsevents": "~2.3.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "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/safe-regex2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", - "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "ret": "~0.5.0" - } - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/secure-json-parse": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", - "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "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==", - "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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", - "dev": true, - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "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==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/string-width-cjs/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==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", - "dev": true, - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "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/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toad-cache": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", - "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "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", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-plugin-ripple": { - "version": "0.2.109", - "resolved": "https://registry.npmjs.org/vite-plugin-ripple/-/vite-plugin-ripple-0.2.109.tgz", - "integrity": "sha512-FP6/Vrv6Z9OYPdGkf+84Eo0fbwZH7IwGSevaJySPDWliwtKqxIrVWmY7Vi6rFJvrFq6tRptHb7h3jsz+eoy91g==", - "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==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/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==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/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==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/zimmerframe": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "license": "MIT" - } - } -} +{ + "name": "rivra", + "version": "0.0.25", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rivra", + "version": "0.0.25", + "license": "MIT", + "dependencies": { + "@fastify/middie": "^9.0.3", + "@fastify/static": "^8.3.0", + "commander": "^14.0.2", + "get-port": "^7.1.0", + "ripple": "^0.2.140", + "vite": "^7.1.6", + "vite-plugin-ripple": "^0.2.56" + }, + "bin": { + "rivra": "dist/cli.js" + }, + "devDependencies": { + "@types/node": "^24.5.2", + "fastify": "^5.6.1", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", + "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/middie": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@fastify/middie/-/middie-9.0.3.tgz", + "integrity": "sha512-7OYovKXp9UKYeVMcjcFLMcSpoMkmcZmfnG+eAvtdiatN35W7c+r9y1dRfpA+pfFVNuHGGqI3W+vDTmjvcfLcMA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastify-plugin": "^5.0.0", + "path-to-regexp": "^8.1.0", + "reusify": "^1.0.4" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@fastify/send": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", + "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" + } + }, + "node_modules/@fastify/static": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.3.0.tgz", + "integrity": "sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^4.0.0", + "content-disposition": "^0.5.4", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^11.0.0" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", + "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", + "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", + "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", + "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", + "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", + "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", + "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", + "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", + "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", + "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", + "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", + "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", + "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", + "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", + "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", + "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", + "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", + "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", + "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", + "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", + "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", + "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "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==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", + "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.14.0" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.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": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", + "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "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/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "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/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "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/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devalue": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.4.2.tgz", + "integrity": "sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", + "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "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-stringify": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.1.1.tgz", + "integrity": "sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.6.1.tgz", + "integrity": "sha512-WjjlOciBF0K8pDUPZoGPhqhKrQJ02I8DKaDIfO51EL0kbSMwQFl85cRwhOvmSDWoukNOdTo27gLN549pLCcH7Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.0", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^9.0.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "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==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-my-way": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", + "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ripple": { + "version": "0.2.140", + "resolved": "https://registry.npmjs.org/ripple/-/ripple-0.2.140.tgz", + "integrity": "sha512-ULUd1n+KvbDkL4nMPRUDbd9whJCXNfYONvMApIg7zA+6WwWbewD4lSVW3Q62X14f/i8L/eHgOwVP8weFVa+oyA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5", + "@sveltejs/acorn-typescript": "^1.0.6", + "acorn": "^8.15.0", + "clsx": "^2.1.1", + "devalue": "^5.3.2", + "esm-env": "^1.2.2", + "esrap": "^2.1.0", + "is-reference": "^3.0.3", + "magic-string": "^0.30.18", + "muggle-string": "^0.4.1", + "zimmerframe": "^1.1.2" + }, + "peerDependencies": { + "ripple": "0.2.140" + } + }, + "node_modules/rollup": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", + "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "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.52.4", + "@rollup/rollup-android-arm64": "4.52.4", + "@rollup/rollup-darwin-arm64": "4.52.4", + "@rollup/rollup-darwin-x64": "4.52.4", + "@rollup/rollup-freebsd-arm64": "4.52.4", + "@rollup/rollup-freebsd-x64": "4.52.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", + "@rollup/rollup-linux-arm-musleabihf": "4.52.4", + "@rollup/rollup-linux-arm64-gnu": "4.52.4", + "@rollup/rollup-linux-arm64-musl": "4.52.4", + "@rollup/rollup-linux-loong64-gnu": "4.52.4", + "@rollup/rollup-linux-ppc64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-musl": "4.52.4", + "@rollup/rollup-linux-s390x-gnu": "4.52.4", + "@rollup/rollup-linux-x64-gnu": "4.52.4", + "@rollup/rollup-linux-x64-musl": "4.52.4", + "@rollup/rollup-openharmony-arm64": "4.52.4", + "@rollup/rollup-win32-arm64-msvc": "4.52.4", + "@rollup/rollup-win32-ia32-msvc": "4.52.4", + "@rollup/rollup-win32-x64-gnu": "4.52.4", + "@rollup/rollup-win32-x64-msvc": "4.52.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "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", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.1.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", + "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-ripple": { + "version": "0.2.109", + "resolved": "https://registry.npmjs.org/vite-plugin-ripple/-/vite-plugin-ripple-0.2.109.tgz", + "integrity": "sha512-FP6/Vrv6Z9OYPdGkf+84Eo0fbwZH7IwGSevaJySPDWliwtKqxIrVWmY7Vi6rFJvrFq6tRptHb7h3jsz+eoy91g==", + "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==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index 512bd6d..790fccd 100644 --- a/package.json +++ b/package.json @@ -1,80 +1,81 @@ -{ - "name": "rivra", - "version": "0.0.25", - "description": "Rivra — a lightweight full-stack framework built on Ripple, combining file-based routing, reactive state management, and full front-to-back tooling with zero configuration.", - "keywords": [ - "ripple", - "router", - "spa", - "store", - "routing", - "tools", - "tooling" - ], - "bin": { - "rivra": "./dist/cli.js" - }, - "homepage": "https://github.com/emeraldlinks/rivra#readme", - "bugs": { - "url": "https://github.com/emeraldlinks/rivra/issues" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/emeraldlinks/rivra.git" - }, - "license": "MIT", - "author": "Joseph Christopher", - "type": "module", - "main": "./dist/index.js", - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.js" - }, - "./stores": { - "types": "./dist/src/store.d.ts", - "import": "./dist/src/store.js", - "require": "./dist/src/store.js" - }, - "./server": { - "import": "./dist/packages/server.js", - "types": "./dist/packages/server.d.ts", - "require": "./dist/packages/server.js" - }, - "./router": { - "types": "./dist/src/router/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.js" - } - }, - "types": "./dist/src/index.d.ts", - "files": [ - "cli.js", - "ReadMe.md", - "README.md", - "dist", - "vite-plugin-ripple.d.ts", - "src/pages", - "src/routes.ts" - ], - "scripts": { - "build": "vite build && tsc --outDir dist --declaration --emitDeclarationOnly false && cp src/router/index.d.ts dist/src/router/index.d.ts", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "dependencies": { - "@fastify/middie": "^9.0.3", - "@fastify/static": "^8.3.0", - "get-port": "^7.1.0", - "ripple": "^0.2.140", - "vite": "^7.1.6", - "vite-plugin-ripple": "^0.2.56" - }, - "devDependencies": { - "@types/node": "^24.5.2", - "fastify": "^5.6.1", - "nodemon": "^3.1.10", - "ts-node": "^10.9.2", - "typescript": "^5.9.3" - } -} +{ + "name": "rivra", + "version": "0.0.25", + "description": "Rivra — a lightweight full-stack framework built on Ripple, combining file-based routing, reactive state management, and full front-to-back tooling with zero configuration.", + "keywords": [ + "ripple", + "router", + "spa", + "store", + "routing", + "tools", + "tooling" + ], + "bin": { + "rivra": "./dist/cli.js" + }, + "homepage": "https://github.com/emeraldlinks/rivra#readme", + "bugs": { + "url": "https://github.com/emeraldlinks/rivra/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/emeraldlinks/rivra.git" + }, + "license": "MIT", + "author": "Joseph Christopher", + "type": "module", + "main": "./dist/index.js", + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.js" + }, + "./stores": { + "types": "./dist/src/store.d.ts", + "import": "./dist/src/store.js", + "require": "./dist/src/store.js" + }, + "./server": { + "import": "./dist/packages/server.js", + "types": "./dist/packages/server.d.ts", + "require": "./dist/packages/server.js" + }, + "./router": { + "types": "./dist/src/router/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.js" + } + }, + "types": "./dist/src/index.d.ts", + "files": [ + "cli.js", + "ReadMe.md", + "README.md", + "dist", + "vite-plugin-ripple.d.ts", + "src/pages", + "src/routes.ts" + ], + "scripts": { + "build": "vite build && tsc --declaration false && cp src/router/index.d.ts dist/src/router/index.d.ts", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "@fastify/middie": "^9.0.3", + "@fastify/static": "^8.3.0", + "commander": "^14.0.2", + "get-port": "^7.1.0", + "ripple": "^0.2.140", + "vite": "^7.1.6", + "vite-plugin-ripple": "^0.2.56" + }, + "devDependencies": { + "@types/node": "^24.5.2", + "fastify": "^5.6.1", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + } +} diff --git a/packages/client_middleware.ts b/packages/client_middleware.ts index e618a0e..4224631 100644 --- a/packages/client_middleware.ts +++ b/packages/client_middleware.ts @@ -1,70 +1,70 @@ -import type { FastifyInstance, FastifyRequest, FastifyReply } from "fastify"; -import fs from "fs"; -import path from "path"; -import { pathToFileURL } from "url"; - -interface ClientMiddlewareTask { - handler: (req: FastifyRequest, reply: FastifyReply) => Promise; - route?: string; - fileName: string; - order: number; -} - -/** - * Load and register frontend (client) middleware. - * - * Structures: - * - Global middleware: /client_middleware/*.ts → runs for all non-API routes - * - Route-specific middleware: filename.cm.ts → runs only for / - * - * Execution order is controlled by `export const order = ` (default 10) - */ -export default async function registerClientMiddleware( - app: FastifyInstance, - dir: string -) { - if (!fs.existsSync(dir)) return; - - const tasks: ClientMiddlewareTask[] = []; - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - if (!/\.(ts|js)$/.test(entry.name)) continue; - - const fullPath = path.join(dir, entry.name); - const moduleUrl = pathToFileURL(fullPath).href; - try { - const mod = await import(moduleUrl); - const handler = - mod.default || Object.values(mod).find((v) => typeof v === "function"); - if (typeof handler !== "function") continue; - - let route: string | undefined; - if (/\.cm\.(ts|js)$/.test(entry.name)) { - route = "/" + entry.name.split(".cm.")[0].toLowerCase(); - } - - const order = typeof mod.order === "number" ? mod.order : 10; - - tasks.push({ handler, route, fileName: entry.name, order }); - } catch (err) { - console.error(`Failed to load client middleware "${entry.name}":`, err); - } - } - - // Sort by order - tasks.sort((a, b) => a.order - b.order); - - // Register middleware - app.addHook("preHandler", async (req, reply) => { - if (req.url?.startsWith("/api") || /\.[a-zA-Z0-9]+$/.test(req.url)) return; - - for (const task of tasks) { - if (!task.route || task.route === req.url.replace(/\/$/, "")) { - await task.handler(req, reply); - } - } - }); - - console.log(`Loaded ${tasks.length} client middleware(s) from ${dir}`); -} +import type { FastifyInstance, FastifyRequest, FastifyReply } from "fastify"; +import fs from "fs"; +import path from "path"; +import { pathToFileURL } from "url"; + +interface ClientMiddlewareTask { + handler: (req: FastifyRequest, reply: FastifyReply) => Promise; + route?: string; + fileName: string; + order: number; +} + +/** + * Load and register frontend (client) middleware. + * + * Structures: + * - Global middleware: /client_middleware/*.ts → runs for all non-API routes + * - Route-specific middleware: filename.cm.ts → runs only for / + * + * Execution order is controlled by `export const order = ` (default 10) + */ +export default async function registerClientMiddleware( + app: FastifyInstance, + dir: string +) { + if (!fs.existsSync(dir)) return; + + const tasks: ClientMiddlewareTask[] = []; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + if (!/\.(ts|js)$/.test(entry.name)) continue; + + const fullPath = path.join(dir, entry.name); + const moduleUrl = pathToFileURL(fullPath).href; + try { + const mod = await import(moduleUrl); + const handler = + mod.default || Object.values(mod).find((v) => typeof v === "function"); + if (typeof handler !== "function") continue; + + let route: string | undefined; + if (/\.cm\.(ts|js)$/.test(entry.name)) { + route = "/" + entry.name.split(".cm.")[0].toLowerCase(); + } + + const order = typeof mod.order === "number" ? mod.order : 10; + + tasks.push({ handler, route, fileName: entry.name, order }); + } catch (err) { + console.error(`Failed to load client middleware "${entry.name}":`, err); + } + } + + // Sort by order + tasks.sort((a, b) => a.order - b.order); + + // Register middleware + app.addHook("preHandler", async (req, reply) => { + if (req.url?.startsWith("/api") || /\.[a-zA-Z0-9]+$/.test(req.url)) return; + + for (const task of tasks) { + if (!task.route || task.route === req.url.replace(/\/$/, "")) { + await task.handler(req, reply); + } + } + }); + + console.log(`Loaded ${tasks.length} client middleware(s) from ${dir}`); +} diff --git a/packages/plugin_manager.js b/packages/plugin_manager.js index 938047b..60a3feb 100644 --- a/packages/plugin_manager.js +++ b/packages/plugin_manager.js @@ -1,143 +1,143 @@ -import fs from "fs"; -import path from "path"; -import { pathToFileURL } from "url"; -// import fp from "fastify-plugin"; - -/** - * Dynamically registers Fastify plugins, middleware, and route-specific logic. - * - * Features: - * - Global middleware: /plugins/middleware/ - * - Route middleware: *.md.ts/js → route-specific middleware - * - Prefixed plugins: *.pg.ts/js → /name prefix - * - Folder-based plugins: auto-prefixed by folder name - * - Order control: export const order = - */ -export default async function registerPlugins(app, dir) { - if (!fs.existsSync(dir)) return; - - // All collected tasks (plugins + middleware) - const pluginTasks = []; - - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - // 1. Load global middleware first (if any) - const middlewareDir = path.join(dir, "middleware"); - if (fs.existsSync(middlewareDir) && fs.statSync(middlewareDir).isDirectory()) { - console.log("⚙️ Loading global middleware..."); - await collectPlugins(pluginTasks, middlewareDir, undefined, true); - } - - // 2. Collect all other plugins and route middleware - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.name === "middleware") continue; // Skip global middleware dir - - if (entry.isDirectory()) { - const prefix = "/" + entry.name.toLowerCase(); - await collectPlugins(pluginTasks, fullPath, prefix); - continue; - } - - if (/\.(ts|js)$/.test(entry.name)) { - await collectSinglePlugin(pluginTasks, fullPath); - } - } - - // 3. Sort by `order` and register in sequence - pluginTasks.sort((a, b) => a.order - b.order); - - for (const task of pluginTasks) { - if (task.prefix) { - await app.register(task.handler, { prefix: task.prefix }); - } else { - await app.register(task.handler); - } - - console.log( - `${task.isMiddleware ? "🧩 Middleware" : "✅ Plugin"} registered: ${ - task.fileName - }${task.prefix ? ` → ${task.prefix}` : ""} (order: ${task.order})` - ); - } -} - -/** Recursively collect plugins for ordered registration */ -async function collectPlugins(tasks, dir, prefix, isMiddleware = false) { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - - if (entry.isDirectory()) { - await collectPlugins( - tasks, - fullPath, - prefix ? prefix + "/" + entry.name.toLowerCase() : undefined, - isMiddleware - ); - continue; - } - - if (!/\.(ts|js)$/.test(entry.name)) continue; - - await collectSinglePlugin(tasks, fullPath, prefix, isMiddleware); - } -} - -/** Collect a single plugin or middleware */ -async function collectSinglePlugin(tasks, filePath, prefix, isMiddleware = false) { - const fileName = path.basename(filePath); - - try { - const moduleUrl = pathToFileURL(filePath).href; - const mod = await import(moduleUrl); - - const handler = - mod.default || - mod.plugin || - Object.values(mod).find((v) => typeof v === "function"); - - if (typeof handler !== "function") { - console.warn(`⚠️ Skipped ${fileName}: no valid export.`); - return; - } - - // Detect prefix patterns - if (!prefix && /\.pg\.(ts|js)$/.test(fileName)) { - const name = fileName.split(".pg.")[0]; - prefix = "/" + name.toLowerCase(); - } - - // Detect route-specific middleware - if (/\.md\.(ts|js)$/.test(fileName)) { - const name = fileName.split(".md.")[0]; - prefix = prefix || "/" + name.toLowerCase(); - isMiddleware = true; - } - - // Determine execution order (default = 10) - const order = typeof mod.order === "number" ? mod.order : 10; - - // Add to registration queue - let wrappedHandler = handler; -if (typeof handler === "function" && !handler[Symbol.for('skipFastifyPluginWrap')]) { - try { - const fp = (await import("fastify-plugin")).default; - wrappedHandler = fp(handler, { - name: fileName.replace(/\.(ts|js)$/, ""), - fastify: ">=4", - }); - } catch (e) { - console.warn(`⚠️ Failed to wrap ${fileName} with fastify-plugin:`, e); - } -} - - -// Add to registration queue -tasks.push({ handler: wrappedHandler, prefix, order, isMiddleware, fileName }); - - } catch (err) { - console.error(`❌ Failed to load plugin "${fileName}":`, err); - } -} +import fs from "fs"; +import path from "path"; +import { pathToFileURL } from "url"; +// import fp from "fastify-plugin"; + +/** + * Dynamically registers Fastify plugins, middleware, and route-specific logic. + * + * Features: + * - Global middleware: /plugins/middleware/ + * - Route middleware: *.md.ts/js → route-specific middleware + * - Prefixed plugins: *.pg.ts/js → /name prefix + * - Folder-based plugins: auto-prefixed by folder name + * - Order control: export const order = + */ +export default async function registerPlugins(app, dir) { + if (!fs.existsSync(dir)) return; + + // All collected tasks (plugins + middleware) + const pluginTasks = []; + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + // 1. Load global middleware first (if any) + const middlewareDir = path.join(dir, "middleware"); + if (fs.existsSync(middlewareDir) && fs.statSync(middlewareDir).isDirectory()) { + console.log("⚙️ Loading global middleware..."); + await collectPlugins(pluginTasks, middlewareDir, undefined, true); + } + + // 2. Collect all other plugins and route middleware + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.name === "middleware") continue; // Skip global middleware dir + + if (entry.isDirectory()) { + const prefix = "/" + entry.name.toLowerCase(); + await collectPlugins(pluginTasks, fullPath, prefix); + continue; + } + + if (/\.(ts|js)$/.test(entry.name)) { + await collectSinglePlugin(pluginTasks, fullPath); + } + } + + // 3. Sort by `order` and register in sequence + pluginTasks.sort((a, b) => a.order - b.order); + + for (const task of pluginTasks) { + if (task.prefix) { + await app.register(task.handler, { prefix: task.prefix }); + } else { + await app.register(task.handler); + } + + console.log( + `${task.isMiddleware ? "🧩 Middleware" : "✅ Plugin"} registered: ${ + task.fileName + }${task.prefix ? ` → ${task.prefix}` : ""} (order: ${task.order})` + ); + } +} + +/** Recursively collect plugins for ordered registration */ +async function collectPlugins(tasks, dir, prefix, isMiddleware = false) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + await collectPlugins( + tasks, + fullPath, + prefix ? prefix + "/" + entry.name.toLowerCase() : undefined, + isMiddleware + ); + continue; + } + + if (!/\.(ts|js)$/.test(entry.name)) continue; + + await collectSinglePlugin(tasks, fullPath, prefix, isMiddleware); + } +} + +/** Collect a single plugin or middleware */ +async function collectSinglePlugin(tasks, filePath, prefix, isMiddleware = false) { + const fileName = path.basename(filePath); + + try { + const moduleUrl = pathToFileURL(filePath).href; + const mod = await import(moduleUrl); + + const handler = + mod.default || + mod.plugin || + Object.values(mod).find((v) => typeof v === "function"); + + if (typeof handler !== "function") { + console.warn(`⚠️ Skipped ${fileName}: no valid export.`); + return; + } + + // Detect prefix patterns + if (!prefix && /\.pg\.(ts|js)$/.test(fileName)) { + const name = fileName.split(".pg.")[0]; + prefix = "/" + name.toLowerCase(); + } + + // Detect route-specific middleware + if (/\.md\.(ts|js)$/.test(fileName)) { + const name = fileName.split(".md.")[0]; + prefix = prefix || "/" + name.toLowerCase(); + isMiddleware = true; + } + + // Determine execution order (default = 10) + const order = typeof mod.order === "number" ? mod.order : 10; + + // Add to registration queue + let wrappedHandler = handler; +if (typeof handler === "function" && !handler[Symbol.for('skipFastifyPluginWrap')]) { + try { + const fp = (await import("fastify-plugin")).default; + wrappedHandler = fp(handler, { + name: fileName.replace(/\.(ts|js)$/, ""), + fastify: ">=4", + }); + } catch (e) { + console.warn(`⚠️ Failed to wrap ${fileName} with fastify-plugin:`, e); + } +} + + +// Add to registration queue +tasks.push({ handler: wrappedHandler, prefix, order, isMiddleware, fileName }); + + } catch (err) { + console.error(`❌ Failed to load plugin "${fileName}":`, err); + } +} diff --git a/packages/plugin_manager.ts b/packages/plugin_manager.ts index 3e0ebde..4e3bfb7 100644 --- a/packages/plugin_manager.ts +++ b/packages/plugin_manager.ts @@ -1,235 +1,235 @@ -import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; -import fs from "fs"; -import path from "path"; -import { pathToFileURL } from "url"; -import fp from "fastify-plugin"; - -interface PluginTask { - handler: any; - prefix?: string; - order: number; - isMiddleware: boolean; - fileName: string; - route?: string; -} - -/** - * Dynamically loads and registers Fastify plugins and middleware. - * - * Supported structures: - * - Global middleware: /plugins/middleware/*.ts - * - Route middleware: filename.md.ts → specific to one route (e.g., users.md.ts → /api/users) - * - Scoped plugins: filename.pg.ts → prefixed plugin (e.g., users.pg.ts → /api/users) - * - Directory-based prefixes: each subfolder under /plugins becomes an API prefix - * - Execution order: controlled by `export const order = ` (default is 10) - * - * Middleware design: - * - Global middleware (inside /middleware) run for every route. - * - Route-specific middleware (.md.ts) only run for their target route. - * - Middleware receive `(req, res, app)` arguments. - * - * Plugin design: - * - Plugins receive `(app)` and are automatically wrapped with `fastify-plugin` - * unless they explicitly define `Symbol.for("skipFastifyPluginWrap")`. - * - * @param app Fastify application instance - * @param dir Base directory containing plugin and middleware files - */ -export default async function registerPlugins(app: FastifyInstance, dir: string) { - if (!fs.existsSync(dir)) return; - - const pluginTasks: PluginTask[] = []; - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - // Load global middleware first - const middlewareDir = path.join(dir, "middleware"); - if (fs.existsSync(middlewareDir) && fs.statSync(middlewareDir).isDirectory()) { - console.log("Loading global middleware..."); - await collectPlugins(pluginTasks, middlewareDir, undefined, true); - } - - // Collect plugins and subdirectories recursively - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.name === "middleware") continue; - - if (entry.isDirectory()) { - const prefix = "/api/" + entry.name.toLowerCase(); - await collectPlugins(pluginTasks, fullPath, prefix); - continue; - } - - if (/\.(ts|js)$/.test(entry.name)) { - await collectSinglePlugin(pluginTasks, fullPath); - } - } - - // Sort all tasks based on the `order` property - pluginTasks.sort((a, b) => a.order - b.order); - - // Register each plugin or middleware - for (const task of pluginTasks) { - if (task.isMiddleware && task.route) { - // Route-specific middleware (filename.md.ts) - const route = "/api" + task.route; - app.addHook("preHandler", async (req, res) => { - const path = req.raw.url?.replace(/\/$/, ""); - if (path === route) { - await task.handler(req, res, app); - } - }); - console.log(`Route middleware registered: ${task.fileName} → ${route}`); - } else if (task.isMiddleware && !task.route) { - // Global middleware (inside /middleware) - app.addHook("preHandler", async (req, res) => { - await task.handler(req, res, app); - }); - console.log(`Middleware registered: ${task.fileName}`); - } else if (task.prefix) { - // Scoped plugin (filename.pg.ts or folder plugin) - let basePrefix = ""; - const apiMatch = dir.match(/\/api(\/[a-zA-Z0-9_-]+)?/); - if (apiMatch) basePrefix = apiMatch[0]; - const fullPrefix = basePrefix ? basePrefix + task.prefix : task.prefix; - await app.register(task.handler, { prefix: fullPrefix }); - } else { - // Global plugin (no prefix) - await app.register(task.handler); - console.log(`Plugin registered: ${task.fileName}`); - } - } -} - -/** - * Recursively collects plugins and middleware from a directory. - * - * Each subfolder automatically adds its name as a prefix, - * and any `.md.ts` files are recognized as route-specific middleware. - * - * @param tasks Array to accumulate plugin or middleware tasks - * @param dir Directory path to scan - * @param prefix Optional prefix for plugin routes - * @param isMiddleware Whether to treat all files in this directory as middleware - */ -async function collectPlugins( - tasks: PluginTask[], - dir: string, - prefix?: string, - isMiddleware = false -) { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - - if (entry.isDirectory()) { - await collectPlugins( - tasks, - fullPath, - prefix ? prefix + "/" + entry.name.toLowerCase() : undefined, - isMiddleware - ); - continue; - } - - if (!/\.(ts|js)$/.test(entry.name)) continue; - await collectSinglePlugin(tasks, fullPath, prefix, isMiddleware); - } -} - -/** - * Loads a single plugin or middleware file and determines its type. - * - * File naming rules: - * - *.md.ts → route-specific middleware - * - *.pg.ts → prefixed plugin (scoped to /api/) - * - any other .ts/.js file → treated as a regular plugin - * - * Middleware are not wrapped with `fastify-plugin`, while - * all regular plugins are wrapped unless they specify otherwise. - * - * @param tasks Array to store collected tasks - * @param filePath Path to the plugin or middleware file - * @param prefix Optional prefix (used for directory-based plugins) - * @param isMiddleware Whether this file should be treated as middleware - */ -async function collectSinglePlugin( - tasks: PluginTask[], - filePath: string, - prefix?: string, - isMiddleware = false -) { - const fileName = path.basename(filePath); - - try { - const moduleUrl = pathToFileURL(filePath).href; - const mod = await import(moduleUrl); - - const handler = - mod.default || - mod.plugin || - Object.values(mod).find((v) => typeof v === "function"); - - if (typeof handler !== "function") return; - - let route: string | undefined; - - // Route-specific middleware (*.md.ts → /route) - if (/\.md\.(ts|js)$/.test(fileName)) { - route = "/" + fileName.split(".md.")[0].toLowerCase(); - isMiddleware = true; - } - - // Scoped plugin (*.pg.ts → /prefix) - if (!prefix && /\.pg\.(ts|js)$/.test(fileName)) { - const name = fileName.split(".pg.")[0]; - prefix = "/api/" + name.toLowerCase(); - } - - const order = typeof mod.order === "number" ? mod.order : 10; - - let wrappedHandler: any; - - if (isMiddleware) { - // Middleware: only req, reply - wrappedHandler = async (req: FastifyRequest, reply: FastifyReply) => { - await handler(req, reply); - }; - } else { - // Plugin: inject app - wrappedHandler = fp(async function (app: FastifyInstance) { - - if (prefix) { - // Scoped plugin: only trigger under its prefix - app.addHook("preHandler", async (req, reply) => { - // Match only routes under the prefix - if (req.url.startsWith(prefix!)) { - await handler(req, reply, app); - } - }); - } else { - // Global plugin - app.addHook("preHandler", async (req, reply) => { - await handler(req, reply, app); - }); - } -}, { - name: fileName.replace(/\.(ts|js)$/, ""), - fastify: ">=4", -}); - - - } - - tasks.push({ - handler: wrappedHandler, - prefix, - order, - isMiddleware, - fileName, - route, - }); - } catch (err) { - console.error(`Failed to load plugin "${fileName}":`, err); - } -} +import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; +import fs from "fs"; +import path from "path"; +import { pathToFileURL } from "url"; +import fp from "fastify-plugin"; + +interface PluginTask { + handler: any; + prefix?: string; + order: number; + isMiddleware: boolean; + fileName: string; + route?: string; +} + +/** + * Dynamically loads and registers Fastify plugins and middleware. + * + * Supported structures: + * - Global middleware: /plugins/middleware/*.ts + * - Route middleware: filename.md.ts → specific to one route (e.g., users.md.ts → /api/users) + * - Scoped plugins: filename.pg.ts → prefixed plugin (e.g., users.pg.ts → /api/users) + * - Directory-based prefixes: each subfolder under /plugins becomes an API prefix + * - Execution order: controlled by `export const order = ` (default is 10) + * + * Middleware design: + * - Global middleware (inside /middleware) run for every route. + * - Route-specific middleware (.md.ts) only run for their target route. + * - Middleware receive `(req, res, app)` arguments. + * + * Plugin design: + * - Plugins receive `(app)` and are automatically wrapped with `fastify-plugin` + * unless they explicitly define `Symbol.for("skipFastifyPluginWrap")`. + * + * @param app Fastify application instance + * @param dir Base directory containing plugin and middleware files + */ +export default async function registerPlugins(app: FastifyInstance, dir: string) { + if (!fs.existsSync(dir)) return; + + const pluginTasks: PluginTask[] = []; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + // Load global middleware first + const middlewareDir = path.join(dir, "middleware"); + if (fs.existsSync(middlewareDir) && fs.statSync(middlewareDir).isDirectory()) { + console.log("Loading global middleware..."); + await collectPlugins(pluginTasks, middlewareDir, undefined, true); + } + + // Collect plugins and subdirectories recursively + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.name === "middleware") continue; + + if (entry.isDirectory()) { + const prefix = "/api/" + entry.name.toLowerCase(); + await collectPlugins(pluginTasks, fullPath, prefix); + continue; + } + + if (/\.(ts|js)$/.test(entry.name)) { + await collectSinglePlugin(pluginTasks, fullPath); + } + } + + // Sort all tasks based on the `order` property + pluginTasks.sort((a, b) => a.order - b.order); + + // Register each plugin or middleware + for (const task of pluginTasks) { + if (task.isMiddleware && task.route) { + // Route-specific middleware (filename.md.ts) + const route = "/api" + task.route; + app.addHook("preHandler", async (req, res) => { + const path = req.raw.url?.replace(/\/$/, ""); + if (path === route) { + await task.handler(req, res, app); + } + }); + console.log(`Route middleware registered: ${task.fileName} → ${route}`); + } else if (task.isMiddleware && !task.route) { + // Global middleware (inside /middleware) + app.addHook("preHandler", async (req, res) => { + await task.handler(req, res, app); + }); + console.log(`Middleware registered: ${task.fileName}`); + } else if (task.prefix) { + // Scoped plugin (filename.pg.ts or folder plugin) + let basePrefix = ""; + const apiMatch = dir.match(/\/api(\/[a-zA-Z0-9_-]+)?/); + if (apiMatch) basePrefix = apiMatch[0]; + const fullPrefix = basePrefix ? basePrefix + task.prefix : task.prefix; + await app.register(task.handler, { prefix: fullPrefix }); + } else { + // Global plugin (no prefix) + await app.register(task.handler); + console.log(`Plugin registered: ${task.fileName}`); + } + } +} + +/** + * Recursively collects plugins and middleware from a directory. + * + * Each subfolder automatically adds its name as a prefix, + * and any `.md.ts` files are recognized as route-specific middleware. + * + * @param tasks Array to accumulate plugin or middleware tasks + * @param dir Directory path to scan + * @param prefix Optional prefix for plugin routes + * @param isMiddleware Whether to treat all files in this directory as middleware + */ +async function collectPlugins( + tasks: PluginTask[], + dir: string, + prefix?: string, + isMiddleware = false +) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + await collectPlugins( + tasks, + fullPath, + prefix ? prefix + "/" + entry.name.toLowerCase() : undefined, + isMiddleware + ); + continue; + } + + if (!/\.(ts|js)$/.test(entry.name)) continue; + await collectSinglePlugin(tasks, fullPath, prefix, isMiddleware); + } +} + +/** + * Loads a single plugin or middleware file and determines its type. + * + * File naming rules: + * - *.md.ts → route-specific middleware + * - *.pg.ts → prefixed plugin (scoped to /api/) + * - any other .ts/.js file → treated as a regular plugin + * + * Middleware are not wrapped with `fastify-plugin`, while + * all regular plugins are wrapped unless they specify otherwise. + * + * @param tasks Array to store collected tasks + * @param filePath Path to the plugin or middleware file + * @param prefix Optional prefix (used for directory-based plugins) + * @param isMiddleware Whether this file should be treated as middleware + */ +async function collectSinglePlugin( + tasks: PluginTask[], + filePath: string, + prefix?: string, + isMiddleware = false +) { + const fileName = path.basename(filePath); + + try { + const moduleUrl = pathToFileURL(filePath).href; + const mod = await import(moduleUrl); + + const handler = + mod.default || + mod.plugin || + Object.values(mod).find((v) => typeof v === "function"); + + if (typeof handler !== "function") return; + + let route: string | undefined; + + // Route-specific middleware (*.md.ts → /route) + if (/\.md\.(ts|js)$/.test(fileName)) { + route = "/" + fileName.split(".md.")[0].toLowerCase(); + isMiddleware = true; + } + + // Scoped plugin (*.pg.ts → /prefix) + if (!prefix && /\.pg\.(ts|js)$/.test(fileName)) { + const name = fileName.split(".pg.")[0]; + prefix = "/api/" + name.toLowerCase(); + } + + const order = typeof mod.order === "number" ? mod.order : 10; + + let wrappedHandler: any; + + if (isMiddleware) { + // Middleware: only req, reply + wrappedHandler = async (req: FastifyRequest, reply: FastifyReply) => { + await handler(req, reply); + }; + } else { + // Plugin: inject app + wrappedHandler = fp(async function (app: FastifyInstance) { + + if (prefix) { + // Scoped plugin: only trigger under its prefix + app.addHook("preHandler", async (req, reply) => { + // Match only routes under the prefix + if (req.url.startsWith(prefix!)) { + await handler(req, reply, app); + } + }); + } else { + // Global plugin + app.addHook("preHandler", async (req, reply) => { + await handler(req, reply, app); + }); + } +}, { + name: fileName.replace(/\.(ts|js)$/, ""), + fastify: ">=4", +}); + + + } + + tasks.push({ + handler: wrappedHandler, + prefix, + order, + isMiddleware, + fileName, + route, + }); + } catch (err) { + console.error(`Failed to load plugin "${fileName}":`, err); + } +} diff --git a/packages/server.d.ts b/packages/server.d.ts index 1d602fa..ec9eeef 100644 --- a/packages/server.d.ts +++ b/packages/server.d.ts @@ -1,4 +1,4 @@ -import type{ FastifyInstance } from 'fastify'; - -export declare function StartServer(): Promise; -export default StartServer; +import type{ FastifyInstance } from 'fastify'; + +export declare function StartServer(): Promise; +export default StartServer; diff --git a/packages/server.js b/packages/server.js index a89e20a..d592f2d 100644 --- a/packages/server.js +++ b/packages/server.js @@ -1,147 +1,147 @@ -import Fastify from "fastify"; -import fastifyMiddie from "@fastify/middie"; -import { createServer as createViteServer } from "vite"; -import fs from "fs"; -import { pathToFileURL, fileURLToPath } from "url"; -import path from "path"; -import registerPlugins from "./plugin_manager.js"; -import { render, executeServerFunction } from "ripple/server"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const projectRoot = process.cwd(); -const apiDir = path.join(projectRoot, "api"); -const pluginDir = path.join(projectRoot, "plugins"); -const srcPath = path.join(projectRoot, "src"); -const RippleAppPath = path.join(srcPath, "App.ripple"); - -const rpc_modules = new Map(); - -function getRequestBody(req) { - return new Promise((resolve, reject) => { - let data = ""; - req.on("data", (chunk) => { - data += chunk; - if (data.length > 1e6) { - req.destroy(); - reject(new Error("Request body too large")); - } - }); - req.on("end", () => resolve(data)); - req.on("error", reject); - }); -} - -// --------------------- API ROUTES --------------------- -async function registerApiRoutes(app, dir, prefix = "") { - if (!fs.existsSync(dir)) return; - - const files = fs.readdirSync(dir); - for (const file of files) { - const fullPath = path.join(dir, file); - const stat = fs.statSync(fullPath); - - if (stat.isDirectory()) { - await registerApiRoutes(app, fullPath, `${prefix}/${file}`); - continue; - } - if (!file.endsWith(".ts") && !file.endsWith(".js")) continue; - - let routePath = prefix + "/" + file.replace(/\.(ts|js)$/, ""); - if (file.startsWith("index.")) routePath = prefix || "/"; - routePath = routePath.replace(/\[([^\]]+)\]/g, ":$1"); - - const moduleUrl = pathToFileURL(fullPath).href + `?t=${Date.now()}`; - const mod = await import(moduleUrl); - const handler = mod.default || Object.values(mod)[0]; - - if (typeof handler === "function") { - app.register(async (fastify) => await handler(fastify), { - prefix: `/api${routePath}`, - }); - console.log(`✅ Loaded route: /api${routePath}`); - } - } -} - -// --------------------- START SERVER --------------------- -export async function StartServer() { - const app = Fastify({ routerOptions: { ignoreTrailingSlash: true } }); - - await app.register(fastifyMiddie); - await registerPlugins(app, pluginDir); - await registerApiRoutes(app, apiDir); - - const vite = await createViteServer({ - root: projectRoot, - server: { middlewareMode: true, hmr: { port: 24678 } }, - appType: "custom", - }); - - // Vite dev middleware - app.use((req, res, next) => { - if (req.url?.startsWith("/api")) return next(); - vite.middlewares(req, res, next); - }); - - // SSR + RPC handler - app.all("/*", async (req, reply) => { - try { - if (req.url.startsWith("/api")) return reply.callNotFound(); - - // Handle Ripple RPC - if (req.url.startsWith("/_$_ripple_rpc_$_/")) { - const hash = req.url.slice("/_$_ripple_rpc_$_/".length); - const module_info = rpc_modules.get(hash); - if (!module_info) throw new Error("RPC module not found"); - const [file_path, func_name] = module_info; - const { _$_server_$_: server } = await vite.ssrLoadModule(file_path); - const rpc_args = await getRequestBody(req); - const result = await executeServerFunction(server[func_name], rpc_args); - reply.type("application/json").send(result); - return; - } - - // SSR rendering - let template = fs.readFileSync(path.resolve(projectRoot, "index.html"), "utf-8"); - template = await vite.transformIndexHtml(req.url, template); - - let getCssForHashes; - const previous_rpc = rpc_modules; - try { - globalThis.rpc_modules = new Map(rpc_modules); - ({ get_css_for_hashes: getCssForHashes } = await vite.ssrLoadModule("ripple/server")); - } finally { - globalThis.rpc_modules = previous_rpc; - } - - const { App } = await vite.ssrLoadModule(RippleAppPath); - const { head, body, css } = await render(App); - - let cssTags = ""; - if (css.size > 0 && getCssForHashes) { - const cssContent = getCssForHashes(css); - if (cssContent) cssTags = ``; - } - - const html = template - .replace("", head + cssTags) - .replace("", body); - - reply.type("text/html").send(html); - } catch (err) { - console.error("SSR/RPC Error:", err); - reply.status(500).send(err.stack || String(err)); - } - }); - - const port = 3000; - app.listen({ port }, (err, address) => { - if (err) throw err; - console.log(`🚀 Rivra Full-Stack Dev + SSR running on ${address}`); - console.log(app.printRoutes()); - }); - - return app; -} - -export default StartServer; +import Fastify from "fastify"; +import fastifyMiddie from "@fastify/middie"; +import { createServer as createViteServer } from "vite"; +import fs from "fs"; +import { pathToFileURL, fileURLToPath } from "url"; +import path from "path"; +import registerPlugins from "./plugin_manager.js"; +import { render, executeServerFunction } from "ripple/server"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = process.cwd(); +const apiDir = path.join(projectRoot, "api"); +const pluginDir = path.join(projectRoot, "plugins"); +const srcPath = path.join(projectRoot, "src"); +const RippleAppPath = path.join(srcPath, "App.ripple"); + +const rpc_modules = new Map(); + +function getRequestBody(req) { + return new Promise((resolve, reject) => { + let data = ""; + req.on("data", (chunk) => { + data += chunk; + if (data.length > 1e6) { + req.destroy(); + reject(new Error("Request body too large")); + } + }); + req.on("end", () => resolve(data)); + req.on("error", reject); + }); +} + +// --------------------- API ROUTES --------------------- +async function registerApiRoutes(app, dir, prefix = "") { + if (!fs.existsSync(dir)) return; + + const files = fs.readdirSync(dir); + for (const file of files) { + const fullPath = path.join(dir, file); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + await registerApiRoutes(app, fullPath, `${prefix}/${file}`); + continue; + } + if (!file.endsWith(".ts") && !file.endsWith(".js")) continue; + + let routePath = prefix + "/" + file.replace(/\.(ts|js)$/, ""); + if (file.startsWith("index.")) routePath = prefix || "/"; + routePath = routePath.replace(/\[([^\]]+)\]/g, ":$1"); + + const moduleUrl = pathToFileURL(fullPath).href + `?t=${Date.now()}`; + const mod = await import(moduleUrl); + const handler = mod.default || Object.values(mod)[0]; + + if (typeof handler === "function") { + app.register(async (fastify) => await handler(fastify), { + prefix: `/api${routePath}`, + }); + console.log(`✅ Loaded route: /api${routePath}`); + } + } +} + +// --------------------- START SERVER --------------------- +export async function StartServer() { + const app = Fastify({ routerOptions: { ignoreTrailingSlash: true } }); + + await app.register(fastifyMiddie); + await registerPlugins(app, pluginDir); + await registerApiRoutes(app, apiDir); + + const vite = await createViteServer({ + root: projectRoot, + server: { middlewareMode: true, hmr: { port: 24678 } }, + appType: "custom", + }); + + // Vite dev middleware + app.use((req, res, next) => { + if (req.url?.startsWith("/api")) return next(); + vite.middlewares(req, res, next); + }); + + // SSR + RPC handler + app.all("/*", async (req, reply) => { + try { + if (req.url.startsWith("/api")) return reply.callNotFound(); + + // Handle Ripple RPC + if (req.url.startsWith("/_$_ripple_rpc_$_/")) { + const hash = req.url.slice("/_$_ripple_rpc_$_/".length); + const module_info = rpc_modules.get(hash); + if (!module_info) throw new Error("RPC module not found"); + const [file_path, func_name] = module_info; + const { _$_server_$_: server } = await vite.ssrLoadModule(file_path); + const rpc_args = await getRequestBody(req); + const result = await executeServerFunction(server[func_name], rpc_args); + reply.type("application/json").send(result); + return; + } + + // SSR rendering + let template = fs.readFileSync(path.resolve(projectRoot, "index.html"), "utf-8"); + template = await vite.transformIndexHtml(req.url, template); + + let getCssForHashes; + const previous_rpc = rpc_modules; + try { + globalThis.rpc_modules = new Map(rpc_modules); + ({ get_css_for_hashes: getCssForHashes } = await vite.ssrLoadModule("ripple/server")); + } finally { + globalThis.rpc_modules = previous_rpc; + } + + const { App } = await vite.ssrLoadModule(RippleAppPath); + const { head, body, css } = await render(App); + + let cssTags = ""; + if (css.size > 0 && getCssForHashes) { + const cssContent = getCssForHashes(css); + if (cssContent) cssTags = ``; + } + + const html = template + .replace("", head + cssTags) + .replace("", body); + + reply.type("text/html").send(html); + } catch (err) { + console.error("SSR/RPC Error:", err); + reply.status(500).send(err.stack || String(err)); + } + }); + + const port = 3000; + app.listen({ port }, (err, address) => { + if (err) throw err; + console.log(`🚀 Rivra Full-Stack Dev + SSR running on ${address}`); + console.log(app.printRoutes()); + }); + + return app; +} + +export default StartServer; diff --git a/packages/server.ts b/packages/server.ts index 71beb97..dd5d58b 100644 --- a/packages/server.ts +++ b/packages/server.ts @@ -1,190 +1,190 @@ -/** - * --------------------------------------------------------------- - * RIVRA DEVELOPMENT SERVER - * --------------------------------------------------------------- - * This server runs Vite in middleware mode during development - * so that frontend routes are served through Fastify. - * - * That means all Fastify plugins, hooks, and middlewares - * apply to both backend and frontend requests. - * - * In production, static files are served from /dist - * and API routes are auto-loaded from /api. - * --------------------------------------------------------------- - */ - -import Fastify, { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; -import fastifyMiddie from "@fastify/middie"; -import fastifyStatic from "@fastify/static"; -import { createServer as createViteServer, ViteDevServer } from "vite"; -import fs from "fs"; -import path, { dirname } from "path"; -import { fileURLToPath, pathToFileURL } from "url"; -import registerPlugins from "./plugin_manager.js"; -import getPort from "get-port"; - -// Pick an available port for Vite HMR (hot reload) -const vite_port = await getPort({ - port: [24678, 24679, 23678, 24658, 23178, 23000, 2178, 22278], -}); - -const isProd = process.env.NODE_ENV === "production"; - -// Resolve paths relative to current file -const __dirname = dirname(fileURLToPath(import.meta.url)); -const projectRoot = process.cwd(); -const distDir = path.join(projectRoot, "dist"); -const distServerDir = path.join(distDir, "server"); - -// Dynamic directories based on environment -const apiDir = isProd ? path.join(distServerDir, "api") : path.join(projectRoot, "api"); -const pluginDir = isProd ? path.join(distServerDir, "plugins") : path.join(projectRoot, "plugins"); - -/** - * --------------------------------------------------------------- - * AUTO-REGISTER API ROUTES - * --------------------------------------------------------------- - * Automatically loads all JS/TS files inside /api - * and registers them as Fastify routes. - * - * Supports: - * - Nested directories - * - Dynamic routes like [id].ts → /:id - * - index.ts → root route - * --------------------------------------------------------------- - */ -async function registerApiRoutes(app: FastifyInstance, dir: string, prefix = ""): Promise { - if (!fs.existsSync(dir)) return; - const files = fs.readdirSync(dir); - - for (const file of files) { - const fullPath = path.join(dir, file); - const stat = fs.statSync(fullPath); - - // Recurse into subdirectories - if (stat.isDirectory()) { - await registerApiRoutes(app, fullPath, `${prefix}/${file}`); - continue; - } - - // Skip non-JS/TS files - if (!file.endsWith(".ts") && !file.endsWith(".js")) continue; - - // Convert filenames to route paths - let routePath = prefix + "/" + file.replace(/\.(ts|js)$/, ""); - if (file.startsWith("index.")) routePath = prefix || "/"; - routePath = routePath.replace(/\[([^\]]+)\]/g, ":$1"); // dynamic route params - - // Import the route module - const moduleUrl = pathToFileURL(fullPath).href + `?t=${Date.now()}`; - const mod = await import(moduleUrl); - const handler = mod.default || Object.values(mod)[0]; - - // Register the route if a valid handler is exported - if (typeof handler === "function") { - app.register(async (f: FastifyInstance) => await handler(f), { - prefix: `/api${routePath}`, - }); - console.log(`Loaded route: /api${routePath}`); - } - } -} - -/** - * --------------------------------------------------------------- - * START SERVER - * --------------------------------------------------------------- - * Creates and runs the Fastify + Vite dev server. - * --------------------------------------------------------------- - */ -export async function StartServer(): Promise { - const app = Fastify({ - routerOptions: { ignoreTrailingSlash: true }, - }); - - // Allow Fastify middlewares and custom plugins - await app.register(fastifyMiddie); - - // Register all user-defined plugins from /plugins directory - await registerPlugins(app, pluginDir); - - // Load API routes automatically - await registerApiRoutes(app, apiDir); - - if (!isProd) { - /** - * --------------------------------------------------------------- - * DEVELOPMENT MODE - * --------------------------------------------------------------- - * Runs Vite as middleware. - * All requests (including frontend ones like "/") - * pass through Fastify first — so plugins and hooks apply. - * --------------------------------------------------------------- - */ - const vite: ViteDevServer = await createViteServer({ - root: projectRoot, - server: { middlewareMode: true, hmr: { port: vite_port } }, - }); - - // Global request hook (runs before routes) - app.addHook("onRequest", async (req, reply) => { - console.log("Incoming:", req.url); - }); - - // Catch-all route for frontend requests - app.all("/*", async (req: FastifyRequest, reply: FastifyReply) => { - const url = req.url || "/"; - - // Let /api routes be handled by Fastify - if (url.startsWith("/api")) return reply.callNotFound(); - - // Forward everything else to Vite (frontend) - await new Promise((resolve, reject) => { - vite.middlewares(req.raw, reply.raw, (err: any) => { - if (err) reject(err); - else resolve(); - }); - }); - }); - } else { - /** - * --------------------------------------------------------------- - * PRODUCTION MODE - * --------------------------------------------------------------- - * Serves prebuilt static files from /dist. - * Plugins still apply since Fastify serves everything. - * --------------------------------------------------------------- - */ - await app.register(fastifyStatic, { - root: distDir, - prefix: "/", - index: ["index.html"], - wildcard: false, - }); - - // Frontend fallback route (after /api) - app.get("/*", async (req, reply) => { - if (req.url.startsWith("/api")) return reply.callNotFound(); - const html = fs.readFileSync(path.join(distDir, "index.html"), "utf-8"); - reply.type("text/html").send(html); - }); - } - - /** - * --------------------------------------------------------------- - * SERVER STARTUP - * --------------------------------------------------------------- - * Automatically finds an available port and starts listening. - * --------------------------------------------------------------- - */ - const port = await getPort({ port: [3000, 3001, 3002, 4000] }); - - app.listen({ port, host: "0.0.0.0" }, (err, address) => { - if (err) throw err; - console.log(`Rivra ${isProd ? "Production" : "Development"} running on ${address}`); - }); - - return app; -} - -export default StartServer; +/** + * --------------------------------------------------------------- + * RIVRA DEVELOPMENT SERVER + * --------------------------------------------------------------- + * This server runs Vite in middleware mode during development + * so that frontend routes are served through Fastify. + * + * That means all Fastify plugins, hooks, and middlewares + * apply to both backend and frontend requests. + * + * In production, static files are served from /dist + * and API routes are auto-loaded from /api. + * --------------------------------------------------------------- + */ + +import Fastify, { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; +import fastifyMiddie from "@fastify/middie"; +import fastifyStatic from "@fastify/static"; +import { createServer as createViteServer, ViteDevServer } from "vite"; +import fs from "fs"; +import path, { dirname } from "path"; +import { fileURLToPath, pathToFileURL } from "url"; +import registerPlugins from "./plugin_manager.js"; +import getPort from "get-port"; + +// Pick an available port for Vite HMR (hot reload) +const vite_port = await getPort({ + port: [24678, 24679, 23678, 24658, 23178, 23000, 2178, 22278], +}); + +const isProd = process.env.NODE_ENV === "production"; + +// Resolve paths relative to current file +const __dirname = dirname(fileURLToPath(import.meta.url)); +const projectRoot = process.cwd(); +const distDir = path.join(projectRoot, "dist"); +const distServerDir = path.join(distDir, "server"); + +// Dynamic directories based on environment +const apiDir = isProd ? path.join(distServerDir, "api") : path.join(projectRoot, "api"); +const pluginDir = isProd ? path.join(distServerDir, "plugins") : path.join(projectRoot, "plugins"); + +/** + * --------------------------------------------------------------- + * AUTO-REGISTER API ROUTES + * --------------------------------------------------------------- + * Automatically loads all JS/TS files inside /api + * and registers them as Fastify routes. + * + * Supports: + * - Nested directories + * - Dynamic routes like [id].ts → /:id + * - index.ts → root route + * --------------------------------------------------------------- + */ +async function registerApiRoutes(app: FastifyInstance, dir: string, prefix = ""): Promise { + if (!fs.existsSync(dir)) return; + const files = fs.readdirSync(dir); + + for (const file of files) { + const fullPath = path.join(dir, file); + const stat = fs.statSync(fullPath); + + // Recurse into subdirectories + if (stat.isDirectory()) { + await registerApiRoutes(app, fullPath, `${prefix}/${file}`); + continue; + } + + // Skip non-JS/TS files + if (!file.endsWith(".ts") && !file.endsWith(".js")) continue; + + // Convert filenames to route paths + let routePath = prefix + "/" + file.replace(/\.(ts|js)$/, ""); + if (file.startsWith("index.")) routePath = prefix || "/"; + routePath = routePath.replace(/\[([^\]]+)\]/g, ":$1"); // dynamic route params + + // Import the route module + const moduleUrl = pathToFileURL(fullPath).href + `?t=${Date.now()}`; + const mod = await import(moduleUrl); + const handler = mod.default || Object.values(mod)[0]; + + // Register the route if a valid handler is exported + if (typeof handler === "function") { + app.register(async (f: FastifyInstance) => await handler(f), { + prefix: `/api${routePath}`, + }); + console.log(`Loaded route: /api${routePath}`); + } + } +} + +/** + * --------------------------------------------------------------- + * START SERVER + * --------------------------------------------------------------- + * Creates and runs the Fastify + Vite dev server. + * --------------------------------------------------------------- + */ +export async function StartServer(p0: { dev: boolean; }): Promise { + const app = Fastify({ + routerOptions: { ignoreTrailingSlash: true }, + }); + + // Allow Fastify middlewares and custom plugins + await app.register(fastifyMiddie); + + // Register all user-defined plugins from /plugins directory + await registerPlugins(app, pluginDir); + + // Load API routes automatically + await registerApiRoutes(app, apiDir); + + if (!isProd) { + /** + * --------------------------------------------------------------- + * DEVELOPMENT MODE + * --------------------------------------------------------------- + * Runs Vite as middleware. + * All requests (including frontend ones like "/") + * pass through Fastify first — so plugins and hooks apply. + * --------------------------------------------------------------- + */ + const vite: ViteDevServer = await createViteServer({ + root: projectRoot, + server: { middlewareMode: true, hmr: { port: vite_port } }, + }); + + // Global request hook (runs before routes) + app.addHook("onRequest", async (req, reply) => { + console.log("Incoming:", req.url); + }); + + // Catch-all route for frontend requests + app.all("/*", async (req: FastifyRequest, reply: FastifyReply) => { + const url = req.url || "/"; + + // Let /api routes be handled by Fastify + if (url.startsWith("/api")) return reply.callNotFound(); + + // Forward everything else to Vite (frontend) + await new Promise((resolve, reject) => { + vite.middlewares(req.raw, reply.raw, (err: any) => { + if (err) reject(err); + else resolve(); + }); + }); + }); + } else { + /** + * --------------------------------------------------------------- + * PRODUCTION MODE + * --------------------------------------------------------------- + * Serves prebuilt static files from /dist. + * Plugins still apply since Fastify serves everything. + * --------------------------------------------------------------- + */ + await app.register(fastifyStatic, { + root: distDir, + prefix: "/", + index: ["index.html"], + wildcard: false, + }); + + // Frontend fallback route (after /api) + app.get("/*", async (req, reply) => { + if (req.url.startsWith("/api")) return reply.callNotFound(); + const html = fs.readFileSync(path.join(distDir, "index.html"), "utf-8"); + reply.type("text/html").send(html); + }); + } + + /** + * --------------------------------------------------------------- + * SERVER STARTUP + * --------------------------------------------------------------- + * Automatically finds an available port and starts listening. + * --------------------------------------------------------------- + */ + const port = await getPort({ port: [3000, 3001, 3002, 4000] }); + + app.listen({ port, host: "0.0.0.0" }, (err, address) => { + if (err) throw err; + console.log(`Rivra ${isProd ? "Production" : "Development"} running on ${address}`); + }); + + return app; +} + +export default StartServer; diff --git a/ripple-env.d.ts b/ripple-env.d.ts index 2793237..842324e 100644 --- a/ripple-env.d.ts +++ b/ripple-env.d.ts @@ -1,8 +1,8 @@ - -declare module "*.ripple" { - import type { Component } from "ripple"; - - // mimic a JSX-like component type with prop inference - const component:

>(props: P) => ReturnType; - export default component; -} + +declare module "*.ripple" { + import type { Component } from "ripple"; + + // mimic a JSX-like component type with prop inference + const component:

>(props: P) => ReturnType; + export default component; +} diff --git a/src/App.ripple b/src/App.ripple index 939f92e..9fababd 100644 --- a/src/App.ripple +++ b/src/App.ripple @@ -1,7 +1,7 @@ -import { track } from 'ripple'; -import {PageRoutes} from "ripple-tools-full" -import { modules } from "./routes"; - -export component App() { - -} +import { track } from 'ripple'; +import {PageRoutes} from "ripple-tools-full" +import { modules } from "./routes"; + +export component App() { + +} diff --git a/src/App2.ripple b/src/App2.ripple index 9da8875..7a59256 100644 --- a/src/App2.ripple +++ b/src/App2.ripple @@ -1,9 +1,9 @@ -// Copy this app's content to your app's App.ripple file - -import { track } from 'ripple'; -import {PageRoutes} from "ripple-tools-full" -import { modules } from "./routes"; - -export component App() { - -} +// Copy this app's content to your app's App.ripple file + +import { track } from 'ripple'; +import {PageRoutes} from "ripple-tools-full" +import { modules } from "./routes"; + +export component App() { + +} diff --git a/src/ReadMe.md b/src/ReadMe.md index f7d6961..b0d95da 100644 --- a/src/ReadMe.md +++ b/src/ReadMe.md @@ -1,718 +1,718 @@ - - - - - -# Rivra -**(Minimal Ripple tool kit. Not just a router)** - - - -# Router + full-stack description: -Not just a router — Rivra is a complete toolkit combining routing, state management, storage, and full-stack app tooling, all fully accessible - -## -![npm](https://img.shields.io/npm/v/rivra) -![downloads](https://img.shields.io/npm/dt/rivra) -![license](https://img.shields.io/npm/l/rivra) -Ripple Logo - ------ - -> **Update:** - Rivra middleware now applies to the **client side** as well — not just API routes. -> Global Middleware and Plugins are now executed **across all routes and resources**, ensuring unified behavior. -> _(Page-specific middleware or plugin logic isn’t supported yet.)_ - ---- - - -## - -## Creating New Project - -```bash -npx rivra create my-app // wrapper around npm create ripple my-app. - -``` - -###### For existing ripple projects use the installation and init - -## Installation - -```bash -npm install rivra -npx rivra init -``` - -or - -```bash -yarn add rivra -npx rivra init -``` - - - -## Quick Start - -After initiating **Rivra** which has all the rivra routing components, the pages directory, /api (if selected full stack), the routes.ts file for your app modules and the configured App.ripple file will be visible in your project src dir. The App.ripple is optional to overwrite. - -### Directory Structure - -Here's an example `src/pages/` directory: - -```bash -pages/ -├── index.ripple # Home page -├── about.ripple # About page -└── users/ - └── [id]/ - └── user/ - └── [username]/ - └── comments/ -``` - -Here's an `api/` directory: - -```bash -api/ -├── index.ts # Root API entry point (can load all modules) -├── posts.ts # Top-level posts routes -└── users/ - └── [id]/ # Dynamic user ID - └── user/ - └── [username]/ # Dynamic username - └── comments/ - └── index.ts # User comments routes - -``` - -```ts -import type { Reply, Req, App } from "rivra" - -import type {Req, Reply, App} from "mivra" -export default async function postAPi(app: App) { - app.get("/", async (req: Req, reply: Reply) => { - - const param = req.params - const queries = req.query - - - return ({ message: "some dynamic route", params: param, query: queries }) - }) -} - -``` - - -### Rivra middlewares/plugins for fastify - -Rivra allows you to customize the server behaviours by leveraging on Fastify hooks and plugins. There are some times you may want to extend function. - ----- -```sql - -plugins/ - ├── middleware/ - │ ├── cors.ts → global middleware (order=1) - │ └── helmet.ts → global middleware (order=2) - ├── auth.md.ts → middleware only for /auth/* - ├── auth.pg.ts → /auth routes - ├── users/ - │ ├── users.md.ts → middleware only for /users/* - │ └── index.ts → /users routes - └── logger.ts → global plugin -``` - - -* Example of plugin -```ts -export const order = 2; // order from 1-10 gives you ability to prioritize the order which your hooks run. - -export default async function (req: Req, reply: Reply, app: App) { - console.log("global plugin triggered") - reply.header('X-Powered-By', 'Rivra'); - - if (req.url === "/api/users") { - reply.send({ message: "Users root route" }); - } else if (req.url === "/api/users/profile") { - reply.send({ message: "User profile route" }); - } - -} - - -``` - -* Example of middlewre -```ts - -export default function (req: Req, res: Reply,) { - console.log("Protected middleware Incoming:", req.method, req.url); - const truthy = true - if (!truthy) { - res.code(400).send({error: "Bad request"}) - return - } - if (req.url === "/api/blocked") { - res.code(403).send({ error: "Forbidden" }); - return; - } - -} - - -``` - -#### Here’s a simple and clear table that explains the difference between a plugin and a middleware and how Rivra handle them. - -| **Aspect** | **Plugin** | **Middleware** | -| ------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| **Purpose** | Extends or configures the Fastify instance (adds routes, hooks, decorators, schemas, etc.) | Intercepts and processes requests before or during route handling (authentication, logging, validation, etc.) | -| **Function Signature** | `(app: FastifyInstance) => void` | `(req: FastifyRequest, reply: FastifyReply, app: FastifyInstance) => void` | -| **Execution Time** | Runs **once** during server startup to register logic | Runs **on every incoming request** (depending on where it’s applied) | -| **Common Use Cases** | Adding routes, setting up databases, registering third-party plugins, global configurations | Authentication checks, access control, logging, pre-processing requests` | Using hooks like `app.addHook("preHandler", handler)` or route-level middlewares | -| **Scope** | Can be **scoped** to a prefix (e.g., `/api/protected`) | Can be **global** or **route-specific** | -| **Impact** | Modifies or enhances the app’s capabilities | Filters or modifies the request/response cycle | -| **File Naming Convention (in your system)** | `*.pg.ts` (Scoped Plugin) | `*.md.ts` (Middleware) | -| **Example (Plugin)** | `export default async function (app) { app.get('/hello', () => 'Hi');}` | | -| **Example (Middleware)** | | `t export default async function (req, reply, app) { if (!req.headers.auth) reply.code(401).send({ error: 'Unauthorized' });}` | - - -| Type | Example File | Prefix Applied | Loaded As | Order | -| ----------------- | ------------------------------- | -------------- | ---------- | -------------------------- | -| Global middleware | `/plugins/middleware/logger.ts` | none | middleware | before all plugins | -| Route middleware | `/plugins/auth/auth.md.ts` | `/auth` | middleware | after global, before route | -| Prefixed plugin | `/plugins/auth.pg.ts` | `/auth` | plugin | normal | -| Folder plugin | `/plugins/payments/index.ts` | `/payments` | plugin | normal | -| Global plugin | `/plugins/cors.ts` | none | plugin | normal | - - -Dynamic segments use `[param]` notation like `[id]` or `[username]`. - ---- - -### App Component - -```ts -import {PageRoutes} from "rivra/router" -import { modules } from "./routes"; - -export component App() { - -} -``` - -That's it! Your routing is now set up. `PageRoutes` automatically reads your `pages/` folder and matches routes, including dynamic parameters. - ---- - -### Link Component - -Use the `Link` component for navigation: - -```ts -import Link from "rivra/router" - -export component Navigation() { -

-} -``` - -#### Props: - -| Prop | Type | Default | Description | -| ------------------ | --------------------- | ------- | --------------------------------------------- | -| `href` | `string` | — | Path to navigate to | -| `children` | `Component` | — | Content to render inside link | -| `onLoading` | `() => void` | — | Callback when navigation starts | -| `emitEvent` | `boolean` | `true` | Whether to trigger route events for this link | -| `loadingComponent` | `Component` | — | Optional component to show while loading | -| `className` | `string` | — | Additional CSS class names for styling | -| `queries` | `Record` | — | Optional query parameters for URLSearch | - - ---- - -### Router And Events - -You can subscribe to router events if you need custom behavior: - -```ts -import { useRouter } from "rivra/router" - -const router = useRouter(); - -router.on("start", path => console.log("Navigating to:", path)); -router.on("complete", path => console.log("Navigation finished:", path)); -router.on("change", path => console.log("Route changed:", path)); - - - -//Guard back navigation -router.beforePopState((url) => { - if (url === "/protected") { - console.log("Navigation blocked:", url); - return false; // Cancel navigation - } -}); - -// Navigate to a new route -router.push("/users/42?tab=posts"); -router.push("/users/42?tab=posts", true, false, {name: "John", age: 20}); // path, emitEvent, shallow (partial url change), queries - - -//Replace URL shallowly (no full sync)// -router.replace("/users/42?tab=profile", true, true); - -// Prefetch a route -router.prefetch("/about"); - -// Resolve href -console.log("Resolved href:", router.resolveHref("/contact?ref=home")); - -// Access reactive properties -console.log("Current path:", router.path); -console.log("Query params:", router.queries); -console.log("Dynamic params:", router.params); -console.log("Full URL:", router.asPath); - -// Access full URL info -console.log(router.host); -console.log(router.hostname); -console.log(router.origin); -console.log(router.protocol); -console.log(router.port); -console.log(router.href); -console.log(router.search); -console.log(router.hash); -``` - -* `start`: triggered before navigation begins -* `complete`: triggered after navigation finishes -* `change`: triggered on every path change - -You can opt out of events per `Link` with `emitEvent={false}`. - ---- - -### Dynamic Route Params - -Access route params and queries in any component: - -```ts -import { useRouter } from "rivra/router" - -export component UserProfile() { - const router = useRouter(); - - const id = router.params.id; // dynamic param - const username = router.params.username; - const queryName = router.queries.name; // URL query ?name=... - // or - const {params, queries} = router; - -
- {"User ID: " + id} - {"Username: " + username} - {"Query name: " + queryName} -
-} -``` - ---- - -### Global Loading Indicator (Optional) -you can disable it with props ```ts - -``` - -```ts -import {PageRoutes} from "rivra/router" -import { modules } from "./routes"; - -export component App() { - -} - -``` - - ---- - ---- - -### A minimal reactive store that manages shared state across your app with an intuitive API. It provides reactivity, persistence, and derived state — all in under a few lines of code. - -| Feature | Description | -| ------------------------------ | ---------------------------------------------------------------------------------------- | -| **`get()`** | Returns the current store value. | -| **`set(next)`** | Replaces the entire store value. | -| **`update(partialOrFn)`** | Merges new data into the store. Supports both object patching and callback styles. | -| **`subscribe(fn, selector?)`** | Reactively listens for state changes, optionally to a selected portion. | -| **`derive(selector)`** | Creates a new store derived from a specific part of another store (like computed state). | -| **`delete(keys)`** | Removes one or more keys from the store. | -| **`clear()`** | Resets store to initial state and removes persisted data. | -| **`persist` (option)** | Automatically saves and restores state from `localStorage`. | - - - --------------------- Example Stores -------------------- -```ts - //* Route store for storing current route path - //* Persisted in localStorage as "routeStore" - //*/ -export const routeStore = createStore( - { path: "/" }, - { persist: true, storageKey: "routeStore" } -); - -/** - * App store for global state - * Tracks path, user info, theme - * Persisted in localStorage as "appStore" - */ -export const appStore = createStore( - { path: "/", user: null as null | { name: string }, theme: "light" }, - { persist: true, storageKey: "appStore" } -); - -``` -Here are extra two simple Hello World store examples for getting started and explain things better. - -### Store without persist (default) -```ts -import { createStore } from "rivra/store" - -// Create a simple store -const helloStore = createStore({ message: "Hello World!" }); - -// Subscribe to changes -helloStore.subscribe(state => { - console.log("Current message:", state.message); -}); - -// Get changes anywhere -const data = helloStore.get(); -console.log(helloStore) // { message: Current message} -console.log(data.message) // Current message - - -// Update the store -helloStore.update({ message: "Hello Ripple!" }); - -// Output: -// Current message: Hello World! -// Current message: Hello Ripple! - - - -``` - -### Store with persist - -```ts -import { createStore } from "rivra/store" -import { track } from "ripple" - -const message = track("") - -// Create a persisted store -const persistentHelloStore = createStore( - { message: "Hello Persistent World!" }, - { persist: true, storageKey: "helloStore" } -); - -// Subscribe to changes -persistentHelloStore.subscribe(state => { - console.log("Current message:", state.message); -}); - - -// Get changes anywhere -const data = helloStore.get(); -console.log(helloStore) // { message: Current message} -console.log(data.message) // Current message - - -// Update the store -persistentHelloStore.update({ message: "Updated and Persisted!" }); - - -// Callback update (safe addition) -persistentHelloStore.update(prev => ({ message: prev.message + " " + @message })); - - -// Reload the page and subscribe again -persistentHelloStore.subscribe(state => { - console.log("After reload:", state.message); -}); - -// Output (after reload): -// After reload: Updated and Persisted! - - - -export const appStore = createStore( - { - user: { name: "Joe", location: "unknown", preferences: [] }, - count: 0, - theme: "light", - }, - { persist: true, storageKey: "appStore" } -); - - - -// Subscribe to entire state -appStore.subscribe(s => console.log("State changed:", s)); - -// Watch a specific value -appStore.watch(s => s.count, (n, o) => console.log(`Count: ${o} → ${n}`)); - -// Use middleware for logging -appStore.use((state, action, payload) => - console.log(`[${action}]`, payload, state) -); - -// Partial update -appStore.update({ count: 1 }); - -// Callback update (safe addition) -appStore.update(prev => ({ count: prev.count + 1 })); - -// Derived store -const themeStore = appStore.derive(s => s.theme); -themeStore.subscribe(theme => console.log("Theme:", theme)); - -// Clear store -appStore.clear(); - - -``` - - -### Here’s a concise side-by-side comparison between Rivra createStore and Zustand: - -| Feature / Aspect | **createStore** (Rivra) | **Zustand** | -| ------------------------ | ------------------------------------ | ---------------------------------------- | -| **Size / Complexity** | Ultra-light (~2 KB) | Larger, includes middleware and devtools | -| **Reactivity Model** | Manual `subscribe` / `derive` | React hooks (`useStore`) | -| **Selectors** | Optional selector argument | Built-in via hooks | -| **Persistence** | Native `persist` option | Needs middleware plugin | -| **DevTools Integration** | Coming soon | Built-in Redux DevTools support | -| **Middleware** | Planned via `use()` | Full middleware API | -| **Callback Updates** | Supported: `update(prev => {...})` | Supported: `set(state => {...})` | -| **Derived Stores** | `derive(selector)` | Selectors or derived state | -| **Performance** | Minimal overhead | Optimized for React, slightly heavier | -| **Framework Support** | Framework-agnostic | React-only | -| **TypeScript** | Fully typed generics | Excellent TS support | -| **Persistence Control** | Built-in localStorage | Plugin required | -| **Use Case Fit** | Libraries & multi-framework projects | React apps needing global state | - ---- - - -## Minimal IndexDB Manager with Zero Dependencies. - - 📘 Example Usage - -------------------------------------------------------------------------- - - ✅ Minimal Example - ```ts - import createIndexDBStore from "rivra/stores" -import IndexDBManager from "rivra/stores" - const userStore = createIndexDBStore({ - storeName: 'users', - }); - await userStore.add({ id: 'u1', name: 'Joseph', age: 22 }); - const all = await userStore.getAll(); - console.log(all); - ``` - - ✅ Full Configuration Example - ```ts - interface User { - id: string; - name: string; - age: number; - } - - const userStore = createIndexDBStore({ - dbName: "MyAppDB", - storeName: "users", - keyPath: "id", - version: 1, - }); - - // ➕ Add record - await userStore.add({ id: 'u1', name: 'Ada', age: 45 }); - - // 🔁 Update record by key or object - await userStore.update('u1', { age: 46 }); - - // 🔍 Get a record by key - const user = await userStore.get('u1'); - console.log('Single user:', user); - - // 📦 Get all records - const allUsers = await userStore.getAll(); - console.log('All users:', allUsers); - - // ❌ Remove record by ID - await userStore.remove('u1'); - - // 🧹 Clear all records - await userStore.clear(); - - // 🔎 Query using a filter function - const adults = await userStore.query(u => u.age > 30); - console.log('Adults:', adults); - - // 👂 Subscribe to store changes (reactive) - const unsubscribe = userStore.subscribe(state => { - console.log('Store changed:', state.items); - }); - - // 👁️ Watch specific property or subset of data - const watchAdults = userStore.deriveQuery(items => items.filter(u => u.age > 30)); - watchAdults.subscribe(adults => console.log('Adults updated:', adults)); - - // 🔦 Filter (where) - const namedJohn = await userStore.where({ name: 'John' }); - console.log('Users named John:', namedJohn); - - // 🥇 Get first matching record - const firstUser = await userStore.first({ age: 46 }); - console.log('First matching user:', firstUser); - - // 🔍 Find by ID (alias for get) - const found = await userStore.find('u1'); - console.log('Found user:', found); - - // 🧩 Put (alias for update) - await userStore.put({ id: 'u1', name: 'Ada', age: 50 }); - - // 💳 Perform custom transaction - await userStore.transaction(tx => { - const store = tx.objectStore('users'); - store.add({ id: 'u2', name: 'Ken', age: 35 }); - }); - - // 🧭 Watch specific user reactively - const watchUser = userStore.deriveQuery(items => items.find(u => u.id === 'u1') || null); - watchUser.subscribe(u => console.log('u1 changed:', u)); - - // 🧹 Unsubscribe from store updates - unsubscribe(); - ``` - - ✅ Multi-Store Example (using IndexDBManager) - ```ts - interface User { - id: string; - name: string; - } - - interface Post { - id: string; - title: string; - } - - // Create manager - const db = new IndexDBManager("MyAppDB"); - - // Create multiple stores - const users = db.createStore("users", "id"); - const posts = db.createStore("posts", "id"); - - // Add data - await users.add({ id: "u1", name: "Joe" }); - await posts.add({ id: "p1", title: "Hello World" }); - - // Query data - const allUsers = await users.getAll(); - const allPosts = await posts.getAll(); - - console.log(allUsers, allPosts); - - // Watch updates - users.subscribe(state => console.log("Users changed:", state.items)); - posts.subscribe(state => console.log("Posts changed:", state.items)); - ``` - - ## IndexDB with offline/online live database synchronization - - This is experimental currently. This api allows you make your apps offline first. - - ```ts -import createIndexDBStore from "rivra/stores" -// import IndexDBManager from "rivra/stores" - - const userStore = createIndexDBStore({ - dbName: "MyAppDB", - storeName: "users", - keyPath: "id", - version: 1, - sync: { - endpoint: "https://api.example.com/users", - async push(item, action) { - // simple example using fetch - if (action === "add") await fetch(this.endpoint!, { method: "POST", body: JSON.stringify(item) }); - if (action === "update") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "PUT", body: JSON.stringify(item) }); - if (action === "remove") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "DELETE" }); - }, - async pull() { - const res = await fetch("https://api.example.com/users"); - return res.json(); - }, - interval: 15000, - autoSync: true, - onOffline: () => console.log("User store offline"), - onOnline: () => console.log("User store online"), - }, - }); - - // Multi-store manager usage with global + per-store callbacks: - - const db = new IndexDBManager("MyAppDB", 1, { - onOffline: () => console.log("Global offline"), - onOnline: () => console.log("Global online"), - }); - - // per-store callbacks override global if provided - const users = db.createStore("users", "id", { - sync: { - onOffline: () => console.log("Users store offline"), - onOnline: () => console.log("Users store online"), - } - }); - - const posts = db.createStore<{ id: string; title: string }>("posts", "id"); - - ``` - - -### Features - -* File-based routing -* Dynamic route segments `[param]` -* URL query support -* Optional per-link router events -* Reactive `Link` component with optional loading UI -* Global progress loader -* Minimal setup—just structure `pages/` -* Minimal indexDB manager with zero dependencies. -* Zustand like global state management available in and outside components - - - - - - -# rivra + + + + + +# Rivra +**(Minimal Ripple tool kit. Not just a router)** + + + +# Router + full-stack description: +Not just a router — Rivra is a complete toolkit combining routing, state management, storage, and full-stack app tooling, all fully accessible + +## +![npm](https://img.shields.io/npm/v/rivra) +![downloads](https://img.shields.io/npm/dt/rivra) +![license](https://img.shields.io/npm/l/rivra) +Ripple Logo + +----- + +> **Update:** + Rivra middleware now applies to the **client side** as well — not just API routes. +> Global Middleware and Plugins are now executed **across all routes and resources**, ensuring unified behavior. +> _(Page-specific middleware or plugin logic isn’t supported yet.)_ + +--- + + +## + +## Creating New Project + +```bash +npx rivra create my-app // wrapper around npm create ripple my-app. + +``` + +###### For existing ripple projects use the installation and init + +## Installation + +```bash +npm install rivra +npx rivra init +``` + +or + +```bash +yarn add rivra +npx rivra init +``` + + + +## Quick Start + +After initiating **Rivra** which has all the rivra routing components, the pages directory, /api (if selected full stack), the routes.ts file for your app modules and the configured App.ripple file will be visible in your project src dir. The App.ripple is optional to overwrite. + +### Directory Structure + +Here's an example `src/pages/` directory: + +```bash +pages/ +├── index.ripple # Home page +├── about.ripple # About page +└── users/ + └── [id]/ + └── user/ + └── [username]/ + └── comments/ +``` + +Here's an `api/` directory: + +```bash +api/ +├── index.ts # Root API entry point (can load all modules) +├── posts.ts # Top-level posts routes +└── users/ + └── [id]/ # Dynamic user ID + └── user/ + └── [username]/ # Dynamic username + └── comments/ + └── index.ts # User comments routes + +``` + +```ts +import type { Reply, Req, App } from "rivra" + +import type {Req, Reply, App} from "mivra" +export default async function postAPi(app: App) { + app.get("/", async (req: Req, reply: Reply) => { + + const param = req.params + const queries = req.query + + + return ({ message: "some dynamic route", params: param, query: queries }) + }) +} + +``` + + +### Rivra middlewares/plugins for fastify + +Rivra allows you to customize the server behaviours by leveraging on Fastify hooks and plugins. There are some times you may want to extend function. + +---- +```sql + +plugins/ + ├── middleware/ + │ ├── cors.ts → global middleware (order=1) + │ └── helmet.ts → global middleware (order=2) + ├── auth.md.ts → middleware only for /auth/* + ├── auth.pg.ts → /auth routes + ├── users/ + │ ├── users.md.ts → middleware only for /users/* + │ └── index.ts → /users routes + └── logger.ts → global plugin +``` + + +* Example of plugin +```ts +export const order = 2; // order from 1-10 gives you ability to prioritize the order which your hooks run. + +export default async function (req: Req, reply: Reply, app: App) { + console.log("global plugin triggered") + reply.header('X-Powered-By', 'Rivra'); + + if (req.url === "/api/users") { + reply.send({ message: "Users root route" }); + } else if (req.url === "/api/users/profile") { + reply.send({ message: "User profile route" }); + } + +} + + +``` + +* Example of middlewre +```ts + +export default function (req: Req, res: Reply,) { + console.log("Protected middleware Incoming:", req.method, req.url); + const truthy = true + if (!truthy) { + res.code(400).send({error: "Bad request"}) + return + } + if (req.url === "/api/blocked") { + res.code(403).send({ error: "Forbidden" }); + return; + } + +} + + +``` + +#### Here’s a simple and clear table that explains the difference between a plugin and a middleware and how Rivra handle them. + +| **Aspect** | **Plugin** | **Middleware** | +| ------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| **Purpose** | Extends or configures the Fastify instance (adds routes, hooks, decorators, schemas, etc.) | Intercepts and processes requests before or during route handling (authentication, logging, validation, etc.) | +| **Function Signature** | `(app: FastifyInstance) => void` | `(req: FastifyRequest, reply: FastifyReply, app: FastifyInstance) => void` | +| **Execution Time** | Runs **once** during server startup to register logic | Runs **on every incoming request** (depending on where it’s applied) | +| **Common Use Cases** | Adding routes, setting up databases, registering third-party plugins, global configurations | Authentication checks, access control, logging, pre-processing requests` | Using hooks like `app.addHook("preHandler", handler)` or route-level middlewares | +| **Scope** | Can be **scoped** to a prefix (e.g., `/api/protected`) | Can be **global** or **route-specific** | +| **Impact** | Modifies or enhances the app’s capabilities | Filters or modifies the request/response cycle | +| **File Naming Convention (in your system)** | `*.pg.ts` (Scoped Plugin) | `*.md.ts` (Middleware) | +| **Example (Plugin)** | `export default async function (app) { app.get('/hello', () => 'Hi');}` | | +| **Example (Middleware)** | | `t export default async function (req, reply, app) { if (!req.headers.auth) reply.code(401).send({ error: 'Unauthorized' });}` | + + +| Type | Example File | Prefix Applied | Loaded As | Order | +| ----------------- | ------------------------------- | -------------- | ---------- | -------------------------- | +| Global middleware | `/plugins/middleware/logger.ts` | none | middleware | before all plugins | +| Route middleware | `/plugins/auth/auth.md.ts` | `/auth` | middleware | after global, before route | +| Prefixed plugin | `/plugins/auth.pg.ts` | `/auth` | plugin | normal | +| Folder plugin | `/plugins/payments/index.ts` | `/payments` | plugin | normal | +| Global plugin | `/plugins/cors.ts` | none | plugin | normal | + + +Dynamic segments use `[param]` notation like `[id]` or `[username]`. + +--- + +### App Component + +```ts +import {PageRoutes} from "rivra/router" +import { modules } from "./routes"; + +export component App() { + +} +``` + +That's it! Your routing is now set up. `PageRoutes` automatically reads your `pages/` folder and matches routes, including dynamic parameters. + +--- + +### Link Component + +Use the `Link` component for navigation: + +```ts +import Link from "rivra/router" + +export component Navigation() { + +} +``` + +#### Props: + +| Prop | Type | Default | Description | +| ------------------ | --------------------- | ------- | --------------------------------------------- | +| `href` | `string` | — | Path to navigate to | +| `children` | `Component` | — | Content to render inside link | +| `onLoading` | `() => void` | — | Callback when navigation starts | +| `emitEvent` | `boolean` | `true` | Whether to trigger route events for this link | +| `loadingComponent` | `Component` | — | Optional component to show while loading | +| `className` | `string` | — | Additional CSS class names for styling | +| `queries` | `Record` | — | Optional query parameters for URLSearch | + + +--- + +### Router And Events + +You can subscribe to router events if you need custom behavior: + +```ts +import { useRouter } from "rivra/router" + +const router = useRouter(); + +router.on("start", path => console.log("Navigating to:", path)); +router.on("complete", path => console.log("Navigation finished:", path)); +router.on("change", path => console.log("Route changed:", path)); + + + +//Guard back navigation +router.beforePopState((url) => { + if (url === "/protected") { + console.log("Navigation blocked:", url); + return false; // Cancel navigation + } +}); + +// Navigate to a new route +router.push("/users/42?tab=posts"); +router.push("/users/42?tab=posts", true, false, {name: "John", age: 20}); // path, emitEvent, shallow (partial url change), queries + + +//Replace URL shallowly (no full sync)// +router.replace("/users/42?tab=profile", true, true); + +// Prefetch a route +router.prefetch("/about"); + +// Resolve href +console.log("Resolved href:", router.resolveHref("/contact?ref=home")); + +// Access reactive properties +console.log("Current path:", router.path); +console.log("Query params:", router.queries); +console.log("Dynamic params:", router.params); +console.log("Full URL:", router.asPath); + +// Access full URL info +console.log(router.host); +console.log(router.hostname); +console.log(router.origin); +console.log(router.protocol); +console.log(router.port); +console.log(router.href); +console.log(router.search); +console.log(router.hash); +``` + +* `start`: triggered before navigation begins +* `complete`: triggered after navigation finishes +* `change`: triggered on every path change + +You can opt out of events per `Link` with `emitEvent={false}`. + +--- + +### Dynamic Route Params + +Access route params and queries in any component: + +```ts +import { useRouter } from "rivra/router" + +export component UserProfile() { + const router = useRouter(); + + const id = router.params.id; // dynamic param + const username = router.params.username; + const queryName = router.queries.name; // URL query ?name=... + // or + const {params, queries} = router; + +
+ {"User ID: " + id} + {"Username: " + username} + {"Query name: " + queryName} +
+} +``` + +--- + +### Global Loading Indicator (Optional) +you can disable it with props ```ts + +``` + +```ts +import {PageRoutes} from "rivra/router" +import { modules } from "./routes"; + +export component App() { + +} + +``` + + +--- + +--- + +### A minimal reactive store that manages shared state across your app with an intuitive API. It provides reactivity, persistence, and derived state — all in under a few lines of code. + +| Feature | Description | +| ------------------------------ | ---------------------------------------------------------------------------------------- | +| **`get()`** | Returns the current store value. | +| **`set(next)`** | Replaces the entire store value. | +| **`update(partialOrFn)`** | Merges new data into the store. Supports both object patching and callback styles. | +| **`subscribe(fn, selector?)`** | Reactively listens for state changes, optionally to a selected portion. | +| **`derive(selector)`** | Creates a new store derived from a specific part of another store (like computed state). | +| **`delete(keys)`** | Removes one or more keys from the store. | +| **`clear()`** | Resets store to initial state and removes persisted data. | +| **`persist` (option)** | Automatically saves and restores state from `localStorage`. | + + + +-------------------- Example Stores -------------------- +```ts + //* Route store for storing current route path + //* Persisted in localStorage as "routeStore" + //*/ +export const routeStore = createStore( + { path: "/" }, + { persist: true, storageKey: "routeStore" } +); + +/** + * App store for global state + * Tracks path, user info, theme + * Persisted in localStorage as "appStore" + */ +export const appStore = createStore( + { path: "/", user: null as null | { name: string }, theme: "light" }, + { persist: true, storageKey: "appStore" } +); + +``` +Here are extra two simple Hello World store examples for getting started and explain things better. + +### Store without persist (default) +```ts +import { createStore } from "rivra/store" + +// Create a simple store +const helloStore = createStore({ message: "Hello World!" }); + +// Subscribe to changes +helloStore.subscribe(state => { + console.log("Current message:", state.message); +}); + +// Get changes anywhere +const data = helloStore.get(); +console.log(helloStore) // { message: Current message} +console.log(data.message) // Current message + + +// Update the store +helloStore.update({ message: "Hello Ripple!" }); + +// Output: +// Current message: Hello World! +// Current message: Hello Ripple! + + + +``` + +### Store with persist + +```ts +import { createStore } from "rivra/store" +import { track } from "ripple" + +const message = track("") + +// Create a persisted store +const persistentHelloStore = createStore( + { message: "Hello Persistent World!" }, + { persist: true, storageKey: "helloStore" } +); + +// Subscribe to changes +persistentHelloStore.subscribe(state => { + console.log("Current message:", state.message); +}); + + +// Get changes anywhere +const data = helloStore.get(); +console.log(helloStore) // { message: Current message} +console.log(data.message) // Current message + + +// Update the store +persistentHelloStore.update({ message: "Updated and Persisted!" }); + + +// Callback update (safe addition) +persistentHelloStore.update(prev => ({ message: prev.message + " " + @message })); + + +// Reload the page and subscribe again +persistentHelloStore.subscribe(state => { + console.log("After reload:", state.message); +}); + +// Output (after reload): +// After reload: Updated and Persisted! + + + +export const appStore = createStore( + { + user: { name: "Joe", location: "unknown", preferences: [] }, + count: 0, + theme: "light", + }, + { persist: true, storageKey: "appStore" } +); + + + +// Subscribe to entire state +appStore.subscribe(s => console.log("State changed:", s)); + +// Watch a specific value +appStore.watch(s => s.count, (n, o) => console.log(`Count: ${o} → ${n}`)); + +// Use middleware for logging +appStore.use((state, action, payload) => + console.log(`[${action}]`, payload, state) +); + +// Partial update +appStore.update({ count: 1 }); + +// Callback update (safe addition) +appStore.update(prev => ({ count: prev.count + 1 })); + +// Derived store +const themeStore = appStore.derive(s => s.theme); +themeStore.subscribe(theme => console.log("Theme:", theme)); + +// Clear store +appStore.clear(); + + +``` + + +### Here’s a concise side-by-side comparison between Rivra createStore and Zustand: + +| Feature / Aspect | **createStore** (Rivra) | **Zustand** | +| ------------------------ | ------------------------------------ | ---------------------------------------- | +| **Size / Complexity** | Ultra-light (~2 KB) | Larger, includes middleware and devtools | +| **Reactivity Model** | Manual `subscribe` / `derive` | React hooks (`useStore`) | +| **Selectors** | Optional selector argument | Built-in via hooks | +| **Persistence** | Native `persist` option | Needs middleware plugin | +| **DevTools Integration** | Coming soon | Built-in Redux DevTools support | +| **Middleware** | Planned via `use()` | Full middleware API | +| **Callback Updates** | Supported: `update(prev => {...})` | Supported: `set(state => {...})` | +| **Derived Stores** | `derive(selector)` | Selectors or derived state | +| **Performance** | Minimal overhead | Optimized for React, slightly heavier | +| **Framework Support** | Framework-agnostic | React-only | +| **TypeScript** | Fully typed generics | Excellent TS support | +| **Persistence Control** | Built-in localStorage | Plugin required | +| **Use Case Fit** | Libraries & multi-framework projects | React apps needing global state | + +--- + + +## Minimal IndexDB Manager with Zero Dependencies. + + 📘 Example Usage + -------------------------------------------------------------------------- + + ✅ Minimal Example + ```ts + import createIndexDBStore from "rivra/stores" +import IndexDBManager from "rivra/stores" + const userStore = createIndexDBStore({ + storeName: 'users', + }); + await userStore.add({ id: 'u1', name: 'Joseph', age: 22 }); + const all = await userStore.getAll(); + console.log(all); + ``` + + ✅ Full Configuration Example + ```ts + interface User { + id: string; + name: string; + age: number; + } + + const userStore = createIndexDBStore({ + dbName: "MyAppDB", + storeName: "users", + keyPath: "id", + version: 1, + }); + + // ➕ Add record + await userStore.add({ id: 'u1', name: 'Ada', age: 45 }); + + // 🔁 Update record by key or object + await userStore.update('u1', { age: 46 }); + + // 🔍 Get a record by key + const user = await userStore.get('u1'); + console.log('Single user:', user); + + // 📦 Get all records + const allUsers = await userStore.getAll(); + console.log('All users:', allUsers); + + // ❌ Remove record by ID + await userStore.remove('u1'); + + // 🧹 Clear all records + await userStore.clear(); + + // 🔎 Query using a filter function + const adults = await userStore.query(u => u.age > 30); + console.log('Adults:', adults); + + // 👂 Subscribe to store changes (reactive) + const unsubscribe = userStore.subscribe(state => { + console.log('Store changed:', state.items); + }); + + // 👁️ Watch specific property or subset of data + const watchAdults = userStore.deriveQuery(items => items.filter(u => u.age > 30)); + watchAdults.subscribe(adults => console.log('Adults updated:', adults)); + + // 🔦 Filter (where) + const namedJohn = await userStore.where({ name: 'John' }); + console.log('Users named John:', namedJohn); + + // 🥇 Get first matching record + const firstUser = await userStore.first({ age: 46 }); + console.log('First matching user:', firstUser); + + // 🔍 Find by ID (alias for get) + const found = await userStore.find('u1'); + console.log('Found user:', found); + + // 🧩 Put (alias for update) + await userStore.put({ id: 'u1', name: 'Ada', age: 50 }); + + // 💳 Perform custom transaction + await userStore.transaction(tx => { + const store = tx.objectStore('users'); + store.add({ id: 'u2', name: 'Ken', age: 35 }); + }); + + // 🧭 Watch specific user reactively + const watchUser = userStore.deriveQuery(items => items.find(u => u.id === 'u1') || null); + watchUser.subscribe(u => console.log('u1 changed:', u)); + + // 🧹 Unsubscribe from store updates + unsubscribe(); + ``` + + ✅ Multi-Store Example (using IndexDBManager) + ```ts + interface User { + id: string; + name: string; + } + + interface Post { + id: string; + title: string; + } + + // Create manager + const db = new IndexDBManager("MyAppDB"); + + // Create multiple stores + const users = db.createStore("users", "id"); + const posts = db.createStore("posts", "id"); + + // Add data + await users.add({ id: "u1", name: "Joe" }); + await posts.add({ id: "p1", title: "Hello World" }); + + // Query data + const allUsers = await users.getAll(); + const allPosts = await posts.getAll(); + + console.log(allUsers, allPosts); + + // Watch updates + users.subscribe(state => console.log("Users changed:", state.items)); + posts.subscribe(state => console.log("Posts changed:", state.items)); + ``` + + ## IndexDB with offline/online live database synchronization + + This is experimental currently. This api allows you make your apps offline first. + + ```ts +import createIndexDBStore from "rivra/stores" +// import IndexDBManager from "rivra/stores" + + const userStore = createIndexDBStore({ + dbName: "MyAppDB", + storeName: "users", + keyPath: "id", + version: 1, + sync: { + endpoint: "https://api.example.com/users", + async push(item, action) { + // simple example using fetch + if (action === "add") await fetch(this.endpoint!, { method: "POST", body: JSON.stringify(item) }); + if (action === "update") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "PUT", body: JSON.stringify(item) }); + if (action === "remove") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "DELETE" }); + }, + async pull() { + const res = await fetch("https://api.example.com/users"); + return res.json(); + }, + interval: 15000, + autoSync: true, + onOffline: () => console.log("User store offline"), + onOnline: () => console.log("User store online"), + }, + }); + + // Multi-store manager usage with global + per-store callbacks: + + const db = new IndexDBManager("MyAppDB", 1, { + onOffline: () => console.log("Global offline"), + onOnline: () => console.log("Global online"), + }); + + // per-store callbacks override global if provided + const users = db.createStore("users", "id", { + sync: { + onOffline: () => console.log("Users store offline"), + onOnline: () => console.log("Users store online"), + } + }); + + const posts = db.createStore<{ id: string; title: string }>("posts", "id"); + + ``` + + +### Features + +* File-based routing +* Dynamic route segments `[param]` +* URL query support +* Optional per-link router events +* Reactive `Link` component with optional loading UI +* Global progress loader +* Minimal setup—just structure `pages/` +* Minimal indexDB manager with zero dependencies. +* Zustand like global state management available in and outside components + + + + + + +# rivra diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..bd9dc32 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,46 @@ +#!/usr/bin/env node +import { Command } from "commander"; +import { createApp } from "./packages/createApp"; +import { startDevServer } from "./packages/devServer"; +import { buildProject } from "./packages/build"; +import pkg from "../package.json" assert { type: "json" }; + +const program = new Command(); + +program + .name("rivra") + .description("Rivra — Full-stack Ripple framework CLI") + .version(pkg.version); + +// ----------------------- +// rivra create +// ----------------------- +program + .command("create") + .argument("") + .description("Create a new Rivra project") + .action(async (name: string) => { + await createApp(name); + }); + +// ----------------------- +// rivra dev +// ----------------------- +program + .command("dev") + .description("Start Rivra dev server") + .action(async () => { + await startDevServer(); + }); + +// ----------------------- +// rivra build +// ----------------------- +program + .command("build") + .description("Build production bundle") + .action(async () => { + await buildProject(); + }); + +program.parse(process.argv); diff --git a/src/createDBStore.ts b/src/createDBStore.ts index 86e0b35..fa577e5 100644 --- a/src/createDBStore.ts +++ b/src/createDBStore.ts @@ -1,768 +1,768 @@ -/** - * indexdb-store.ts - * - * Full implementation of createIndexDBStore + IndexDBManager with: - * - Full JSDoc - * - Multi-store manager - * - Optional sync adapter hooks - * - Global (manager) + per-store online/offline callbacks - * - SSR-safe checks - * - Preserves all existing methods and signatures - * - * Drop into your project. Typescript friendly. - */ - -import { createStore } from "./store"; - -/* -------------------------------------------------------------------------- - * Types & Options - * -------------------------------------------------------------------------- */ - -/** - * Live sync & network callback config (part of DBStoreOptions.sync) - */ -interface SyncOptions { - /** Base URL or remote DB client (optional helper) */ - endpoint?: string; - /** Push local change to remote */ - push?: (item: any, action: "add" | "update" | "remove") => Promise; - /** Pull data from remote */ - pull?: () => Promise; - /** Auto-sync pull interval (ms) */ - interval?: number; - /** Automatically push on local changes */ - autoSync?: boolean; - /** Called when this store detects it's offline */ - onOffline?: () => void; - /** Called when this store becomes online again */ - onOnline?: () => void; -} - -/** - * Configuration options for IndexedDB-backed reactive store. - */ -interface DBStoreOptions { - /** Database name (defaults to "default_db") */ - dbName?: string; - /** Store name (required) */ - storeName: string; - /** Key path for object store (defaults to "id") */ - keyPath?: string; - /** Database version (defaults to 1) */ - version?: number; - /** Automatically load data into store on init (default: true) */ - autoLoad?: boolean; - /** Live sync configuration (optional) */ - sync?: SyncOptions; -} - -/** Filter type for query operations */ -type QueryFilter = Partial> | ((item: T) => boolean); - -/* -------------------------------------------------------------------------- - * Utility: SSR-safe feature detection - * -------------------------------------------------------------------------- */ -const isBrowser = - typeof window !== "undefined" && typeof navigator !== "undefined"; -const supportsBroadcastChannel = typeof BroadcastChannel !== "undefined"; - -/* -------------------------------------------------------------------------- - * createIndexDBStore - * -------------------------------------------------------------------------- */ - -/** - * Creates a reactive IndexedDB-backed store with ORM-like methods. - * Includes reactivity, syncing across tabs, live sync hooks, and offline detection. - * - * @template T - The record type stored in IndexedDB. - * @param {DBStoreOptions} options - Configuration for the store. - * @returns {object} Reactive data store with CRUD, query, and utility methods. - * @example - * // Create multiple stores - * const userStore = createIndexDBStore({storeName: 'users'}); - */ -export function createIndexDBStore>( - options: DBStoreOptions -) { - const { - dbName = "default_db", - storeName, - keyPath, - version = 1, - autoLoad = true, - sync, - } = options; - - // main reactive store for items - const store = createStore<{ items: T[] }>({ items: [] }); - - // database reference and runtime state - let db: IDBDatabase | null = null; - let resolvedKeyPath: string | null = keyPath || null; - - // cross-tab channel (guarded for SSR / old browsers) - const bc = - isBrowser && supportsBroadcastChannel - ? new BroadcastChannel(`${dbName}_${storeName}_sync`) - : null; - - // network state - let isOffline = isBrowser ? !navigator.onLine : false; - - /* ------------------------------------------------------------------------ - * DB helpers - * ------------------------------------------------------------------------ */ - - /** - * Opens or upgrades the IndexedDB database. - * @returns {Promise} The database instance. - */ - async function openDB(): Promise { - if (!isBrowser) - throw new Error("IndexedDB not available in this environment (SSR)."); - return new Promise((resolve, reject) => { - const req = indexedDB.open(dbName, version); - req.onupgradeneeded = () => { - const _db = req.result; - if (!_db.objectStoreNames.contains(storeName)) { - _db.createObjectStore(storeName, { - keyPath: resolvedKeyPath || "id", - }); - } - }; - req.onsuccess = () => resolve(req.result); - req.onerror = () => reject(req.error); - }); - } - - async function ensureDB() { - if (!db) db = await openDB(); - return db!; - } - - /** Initializes the store and loads all data if autoLoad is true. */ - async function init() { - if (!isBrowser) return; // SSR-safe: do not attempt DB operations on server - db = await openDB(); - if (autoLoad) { - const all = await getAll(); - store.set({ items: all }); - } - setupNetworkListeners(); - setupAutoPull(); - } - - function ensureKeyPath(item: T) { - if (!resolvedKeyPath) { - const firstKey = Object.keys(item)[0]; - resolvedKeyPath = firstKey; - } - return resolvedKeyPath!; - } - - /* ------------------------------------------------------------------------ - * CRUD - * ------------------------------------------------------------------------ */ - - /** - * Adds a new record to the store. - * @param {T} item - The record to add. - * @returns {Promise} - */ - async function add(item: T) { - const dbRef = await ensureDB(); - ensureKeyPath(item); - return new Promise((resolve, reject) => { - const tx = dbRef.transaction(storeName, "readwrite"); - const req = tx.objectStore(storeName).add(item); - req.onsuccess = async () => { - store.update((s) => ({ items: [...s.items, item] })); - bc?.postMessage("sync"); - await maybeSync(item, "add"); - resolve(); - }; - req.onerror = () => reject(req.error); - }); - } - - /** - * Updates an existing record by ID or object. - * @param {IDBValidKey | Partial} idOrItem - Record ID or object containing key. - * @param {Partial} [partial] - Partial data to merge when using ID form. - * @returns {Promise} - */ - async function update( - idOrItem: IDBValidKey | Partial, - partial?: Partial - ) { - const dbRef = await ensureDB(); - - let id: IDBValidKey; - let newData: Partial = {}; - - const isKeyType = - typeof idOrItem === "string" || - typeof idOrItem === "number" || - idOrItem instanceof Date || - idOrItem instanceof ArrayBuffer || - ArrayBuffer.isView(idOrItem) || - Array.isArray(idOrItem); - - if (isKeyType) { - id = idOrItem as IDBValidKey; - newData = partial || {}; - } else if (typeof idOrItem === "object" && idOrItem !== null) { - ensureKeyPath(idOrItem as T); - id = (idOrItem as any)[resolvedKeyPath!]; - newData = idOrItem as Partial; - } else { - throw new Error("Invalid argument passed to update()"); - } - - const existing = await get(id); - if (!existing) return; - - const updated = { ...existing, ...newData } as T; - - return new Promise((resolve, reject) => { - const tx = dbRef.transaction(storeName, "readwrite"); - const req = tx.objectStore(storeName).put(updated); - req.onsuccess = async () => { - store.update((s) => ({ - items: s.items.map((i) => - (i as any)[resolvedKeyPath!] === id ? updated : i - ), - })); - bc?.postMessage("sync"); - await maybeSync(updated, "update"); - resolve(); - }; - req.onerror = () => reject(req.error); - }); - } - - /** - * Retrieves a record by ID. - * @param {IDBValidKey} id - Record key. - * @returns {Promise} The matching record, if found. - */ - async function get(id: IDBValidKey): Promise { - const dbRef = await ensureDB(); - return new Promise((resolve, reject) => { - const tx = dbRef.transaction(storeName, "readonly"); - const req = tx.objectStore(storeName).get(id); - req.onsuccess = () => resolve(req.result as T | undefined); - req.onerror = () => reject(req.error); - }); - } - - /** - * Retrieves all records. - * @returns {Promise} All stored records. - */ - async function getAll(): Promise { - const dbRef = await ensureDB(); - return new Promise((resolve, reject) => { - const tx = dbRef.transaction(storeName, "readonly"); - const req = tx.objectStore(storeName).getAll(); - req.onsuccess = () => resolve(req.result as T[]); - req.onerror = () => reject(req.error); - }); - } - - /** - * Removes a record by ID. - * @param {IDBValidKey} id - Record key. - * @returns {Promise} - */ - async function remove(id: IDBValidKey) { - const dbRef = await ensureDB(); - const key = resolvedKeyPath || "id"; - return new Promise((resolve, reject) => { - const tx = dbRef.transaction(storeName, "readwrite"); - const req = tx.objectStore(storeName).delete(id); - req.onsuccess = async () => { - store.update((s) => ({ - items: s.items.filter((i) => (i as any)[key] !== id), - })); - bc?.postMessage("sync"); - await maybeSync({ [key]: id } as any, "remove"); - resolve(); - }; - req.onerror = () => reject(req.error); - }); - } - - /** - * Clears all data from the store. - * @returns {Promise} - */ - async function clear() { - const dbRef = await ensureDB(); - return new Promise((resolve, reject) => { - const tx = dbRef.transaction(storeName, "readwrite"); - const req = tx.objectStore(storeName).clear(); - req.onsuccess = () => { - store.set({ items: [] }); - bc?.postMessage("sync"); - resolve(); - }; - req.onerror = () => reject(req.error); - }); - } - - /* ------------------------------------------------------------------------ - * Queries & Utilities - * ------------------------------------------------------------------------ */ - - async function where(filter: QueryFilter): Promise { - const all = await getAll(); - if (typeof filter === "function") return all.filter(filter); - return all.filter((item) => - Object.entries(filter).every(([k, v]) => item[k as keyof T] === v) - ); - } - - async function first(filter?: QueryFilter): Promise { - const res = filter ? await where(filter) : await getAll(); - return res[0]; - } - - async function find(id: IDBValidKey): Promise { - return get(id); - } - - /** - * Get the last matching record - * @example - * const lastAdult = await User.last(u => u.age >= 18); - */ - async function last(filter?: QueryFilter): Promise { - const res = filter ? await where(filter) : await getAll(); - return res[res.length - 1]; - } - - /** - * Count items matching filter - * @example - * const adultCount = await User.count(u => u.age >= 18); - */ - async function count(filter?: QueryFilter): Promise { - const res = filter ? await where(filter) : await getAll(); - return res.length; - } - - /** - * Check if any record exists matching filter - * @example - * const exists = await User.exists({ name: "John" }); - */ - async function exists(filter: QueryFilter): Promise { - const res = await where(filter); - return res.length > 0; - } - - /** - * Get a random record - * @example - * const randomUser = await User.random(); - */ - async function random(filter?: QueryFilter): Promise { - const res = filter ? await where(filter) : await getAll(); - if (res.length === 0) return undefined; - return res[Math.floor(Math.random() * res.length)]; - } - - async function below(filter: Partial): Promise { - const all = await getAll(); - return all.filter(item => - Object.entries(filter).every(([k, v]) => { - const val = item[k as keyof T]; - if (typeof val === "number" && typeof v === "number") return (val as number) < (v as number); - if (typeof val === "string" && typeof v === "string") return (val as string) < (v as string); - return false; - }) - ); -} - -async function above(filter: Partial): Promise { - const all = await getAll(); - return all.filter(item => - Object.entries(filter).every(([k, v]) => { - const val = item[k as keyof T]; - if (typeof val === "number" && typeof v === "number") return (val as number) > (v as number); - if (typeof val === "string" && typeof v === "string") return (val as string) > (v as string); - return false; - }) - ); -} - - - - async function query(predicate: (item: T) => boolean): Promise { - const all = await getAll(); - return all.filter(predicate); - } - - /** - * Creates a derived live query that reacts to store changes. - * @template U - * @param {(items: T[]) => U} selector - Function that selects part of the data. - * @returns {{ subscribe(fn: (value: U) => void): () => void; get(): U }} - */ - function deriveQuery(selector: (items: T[]) => U) { - const live = createStore({ value: selector(store.get().items) }); - - store.watch( - (s) => s.items, - (items) => { - const result = selector(items); - live.set({ value: result }); - } - ); - - return { - subscribe: (fn: (v: U) => void) => live.subscribe((s) => fn(s.value)), - get: () => live.get().value, - }; - } - - /* ------------------------------------------------------------------------ - * Live sync & network listeners (per-store) - * ------------------------------------------------------------------------ */ - - async function maybeSync(item: any, action: "add" | "update" | "remove") { - // sync only if configured and online - if ( - sync?.autoSync && - typeof isBrowser !== "undefined" && - navigator.onLine && - sync.push - ) { - try { - await sync.push(item, action); - } catch (err) { - // swallow - caller may implement retry - console.warn( - `[createIndexDBStore:${storeName}] sync push failed:`, - err - ); - } - } - } - - function setupAutoPull() { - if (!isBrowser) return; - if (sync?.interval && sync.pull) { - setInterval(async () => { - if (!navigator.onLine) return; - try { - const remote = await sync.pull!(); - if (remote) { - store.set({ items: remote as T[] }); - } - } catch (err) { - console.warn( - `[createIndexDBStore:${storeName}] sync pull failed:`, - err - ); - } - }, sync.interval); - } - } - - function setupNetworkListeners() { - if (!isBrowser) return; - - // offline - window.addEventListener("offline", () => { - isOffline = true; - try { - sync?.onOffline?.(); - } catch (err) { - console.error("onOffline callback error:", err); - } - // Trigger store subscribers reactively (announce network change) - store.update((s) => ({ items: s.items })); - }); - - // online - window.addEventListener("online", async () => { - const wasOffline = isOffline; - isOffline = false; - try { - sync?.onOnline?.(); - } catch (err) { - console.error("onOnline callback error:", err); - } - // If we were offline and now online, optionally pull latest - if (wasOffline && sync?.pull) { - try { - const remote = await sync.pull(); - if (remote) store.set({ items: remote as T[] }); - } catch (err) { - console.warn( - `[createIndexDBStore:${storeName}] sync pull after online failed:`, - err - ); - } - } - // notify subscribers (reactive) - store.update((s) => ({ items: s.items })); - }); - } - - // cross-tab broadcast listener (keeps store in sync across tabs) - if (bc) { - bc.onmessage = async (msg) => { - if (msg.data === "sync") { - const all = await getAll(); - store.set({ items: all }); - } - }; - } - - /* ------------------------------------------------------------------------ - * Transactions - * ------------------------------------------------------------------------ */ - - /** - * Runs a custom transaction operation. - * @param {(tx: IDBTransaction) => void} fn - Transaction callback. - * @returns {Promise} - */ - async function transaction(fn: (tx: IDBTransaction) => void) { - const dbRef = await ensureDB(); - const tx = dbRef.transaction(storeName, "readwrite"); - fn(tx); - return new Promise((resolve, reject) => { - tx.oncomplete = () => resolve(); - tx.onerror = () => reject(tx.error); - }); - } - - // initialize - init(); - - /* ------------------------------------------------------------------------ - * Public API (do not remove these keys — kept for compatibility) - * ------------------------------------------------------------------------ */ - return { - add, - update, - get, - getAll, - remove, - clear, - query, - subscribe: store.subscribe, - watch: store.watch, - deriveQuery, - where, - first, - find, - put: update, - transaction, - random, - exists, - count, - last, - above, - below - }; -} - -/* -------------------------------------------------------------------------- - * IndexDBManager — multi-store manager with global network callbacks - * -------------------------------------------------------------------------- */ - -/** - * Options for IndexDBManager constructor (global network callbacks). - */ -interface IndexDBManagerOptions { - onOffline?: () => void; - onOnline?: () => void; -} - -/** - * A lightweight IndexedDB manager that can create and manage multiple stores. - * Supports optional global onOnline/onOffline callbacks. Each store also supports - * per-store callbacks via its DBStoreOptions.sync.onOnline/onOffline. - */ -export class IndexDBManager { - private dbName: string; - private version: number; - private stores: Record>> = - {}; - private globalOnOffline?: () => void; - private globalOnOnline?: () => void; - - /** - * Create a manager for a named IndexedDB database. - * @param {string} dbName - database name - * @param {number} [version=1] - db version - * @param {IndexDBManagerOptions} [opts] - global network callbacks - */ - constructor( - dbName: string, - version: number = 1, - opts?: IndexDBManagerOptions - ) { - this.dbName = dbName; - this.version = version; - this.globalOnOffline = opts?.onOffline; - this.globalOnOnline = opts?.onOnline; - - // If running in browser, hook into global online/offline to call manager-level hooks. - if (isBrowser) { - window.addEventListener("offline", () => { - try { - this.globalOnOffline?.(); - } catch (err) { - console.error("IndexDBManager.onOffline error:", err); - } - }); - window.addEventListener("online", () => { - try { - this.globalOnOnline?.(); - } catch (err) { - console.error("IndexDBManager.onOnline error:", err); - } - }); - } - } - - /** - * Creates a new store within the database. - * - * @template T - * @param {string} storeName - The name of the object store. - * @param {string} [keyPath='id'] - The key path used as the primary key. - * @param {{ sync?: SyncOptions }} [opts] - Optional per-store additions (e.g., per-store onOnline/onOffline). - * @returns {ReturnType>} The created store instance. - * - * @example - * // Create multiple stores - * const users = db.createStore("users", "id"); - * const posts = db.createStore("posts", "id"); - */ - createStore>( - storeName: string, - keyPath: string = "id", - opts?: { sync?: SyncOptions } - ) { - // merge global and per-store network callbacks: per-store takes precedence - const mergedSync: SyncOptions | undefined = opts?.sync - ? { - ...opts.sync, - onOffline: opts.sync.onOffline ?? this.globalOnOffline, - onOnline: opts.sync.onOnline ?? this.globalOnOnline, - } - : this.globalOnOffline || this.globalOnOnline - ? { onOffline: this.globalOnOffline, onOnline: this.globalOnOnline } - : undefined; - - const store = createIndexDBStore({ - dbName: this.dbName, - storeName, - keyPath, - version: this.version, - sync: mergedSync, - }); - this.stores[storeName] = store; - return store; - } - - /** - * Retrieves a store by its name. - * @param {string} name - The store name. - * @returns {ReturnType | undefined} The store instance, if found. - */ - getStore(name: string) { - return this.stores[name]; - } -} - -/* -------------------------------------------------------------------------- - * 📘 Example Usage (merged, minimal + full + multi-store) - * -------------------------------------------------------------------------- - * - * Minimal: - * - * const userStore = createIndexDBStore({ - * storeName: 'users', - * }); - * - * await userStore.add({ id: 'u1', name: 'Joseph', age: 22 }); - * const all = await userStore.getAll(); - * console.log(all); - * - * - * Full single-store config: - * - * interface User { - * id: string; - * name: string; - * age: number; - * } - * - * const userStore = createIndexDBStore({ - * dbName: "MyAppDB", - * storeName: "users", - * keyPath: "id", - * version: 1, - * sync: { - * endpoint: "https://api.example.com/users", - * async push(item, action) { - * // simple example using fetch - * if (action === "add") await fetch(this.endpoint!, { method: "POST", body: JSON.stringify(item) }); - * if (action === "update") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "PUT", body: JSON.stringify(item) }); - * if (action === "remove") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "DELETE" }); - * }, - * async pull() { - * const res = await fetch("https://api.example.com/users"); - * return res.json(); - * }, - * interval: 15000, - * autoSync: true, - * onOffline: () => console.log("User store offline"), - * onOnline: () => console.log("User store online"), - * }, - * }); - * - * // Multi-store manager usage with global + per-store callbacks: - * - * const db = new IndexDBManager("MyAppDB", 1, { - * onOffline: () => console.log("Global offline"), - * onOnline: () => console.log("Global online"), - * }); - * - * // per-store callbacks override global if provided - * const users = db.createStore("users", "id", { - * sync: { - * onOffline: () => console.log("Users store offline"), - * onOnline: () => console.log("Users store online"), - * } - * }); - * - * const posts = db.createStore<{ id: string; title: string }>("posts", "id"); - * - * await users.add({ id: "u1", name: "Joe", age: 22 }); - * await posts.add({ id: "p1", title: "Hello World" }); - * - * users.subscribe(state => console.log("Users changed:", state.items)); - * posts.subscribe(state => console.log("Posts changed:", state.items)); - * - * // derived example (watch one user) - * const watchUser = users.deriveQuery(items => items.find(u => u.id === "u1") || null); - * const unsubscribeWatch = watchUser.subscribe(u => console.log("u1 changed:", u)); - * - * // manual transaction example - * await users.transaction(tx => { - * const s = tx.objectStore("users"); - * s.add({ id: "u2", name: "Ada", age: 45 }); - * }); - * - * // Note: When running in SSR environment, store operations that touch IndexedDB are safe-guarded. - * - * -------------------------------------------------------------------------- */ +/** + * indexdb-store.ts + * + * Full implementation of createIndexDBStore + IndexDBManager with: + * - Full JSDoc + * - Multi-store manager + * - Optional sync adapter hooks + * - Global (manager) + per-store online/offline callbacks + * - SSR-safe checks + * - Preserves all existing methods and signatures + * + * Drop into your project. Typescript friendly. + */ + +import { createStore } from "./store"; + +/* -------------------------------------------------------------------------- + * Types & Options + * -------------------------------------------------------------------------- */ + +/** + * Live sync & network callback config (part of DBStoreOptions.sync) + */ +interface SyncOptions { + /** Base URL or remote DB client (optional helper) */ + endpoint?: string; + /** Push local change to remote */ + push?: (item: any, action: "add" | "update" | "remove") => Promise; + /** Pull data from remote */ + pull?: () => Promise; + /** Auto-sync pull interval (ms) */ + interval?: number; + /** Automatically push on local changes */ + autoSync?: boolean; + /** Called when this store detects it's offline */ + onOffline?: () => void; + /** Called when this store becomes online again */ + onOnline?: () => void; +} + +/** + * Configuration options for IndexedDB-backed reactive store. + */ +interface DBStoreOptions { + /** Database name (defaults to "default_db") */ + dbName?: string; + /** Store name (required) */ + storeName: string; + /** Key path for object store (defaults to "id") */ + keyPath?: string; + /** Database version (defaults to 1) */ + version?: number; + /** Automatically load data into store on init (default: true) */ + autoLoad?: boolean; + /** Live sync configuration (optional) */ + sync?: SyncOptions; +} + +/** Filter type for query operations */ +type QueryFilter = Partial> | ((item: T) => boolean); + +/* -------------------------------------------------------------------------- + * Utility: SSR-safe feature detection + * -------------------------------------------------------------------------- */ +const isBrowser = + typeof window !== "undefined" && typeof navigator !== "undefined"; +const supportsBroadcastChannel = typeof BroadcastChannel !== "undefined"; + +/* -------------------------------------------------------------------------- + * createIndexDBStore + * -------------------------------------------------------------------------- */ + +/** + * Creates a reactive IndexedDB-backed store with ORM-like methods. + * Includes reactivity, syncing across tabs, live sync hooks, and offline detection. + * + * @template T - The record type stored in IndexedDB. + * @param {DBStoreOptions} options - Configuration for the store. + * @returns {object} Reactive data store with CRUD, query, and utility methods. + * @example + * // Create multiple stores + * const userStore = createIndexDBStore({storeName: 'users'}); + */ +export function createIndexDBStore>( + options: DBStoreOptions +) { + const { + dbName = "default_db", + storeName, + keyPath, + version = 1, + autoLoad = true, + sync, + } = options; + + // main reactive store for items + const store = createStore<{ items: T[] }>({ items: [] }); + + // database reference and runtime state + let db: IDBDatabase | null = null; + let resolvedKeyPath: string | null = keyPath || null; + + // cross-tab channel (guarded for SSR / old browsers) + const bc = + isBrowser && supportsBroadcastChannel + ? new BroadcastChannel(`${dbName}_${storeName}_sync`) + : null; + + // network state + let isOffline = isBrowser ? !navigator.onLine : false; + + /* ------------------------------------------------------------------------ + * DB helpers + * ------------------------------------------------------------------------ */ + + /** + * Opens or upgrades the IndexedDB database. + * @returns {Promise} The database instance. + */ + async function openDB(): Promise { + if (!isBrowser) + throw new Error("IndexedDB not available in this environment (SSR)."); + return new Promise((resolve, reject) => { + const req = indexedDB.open(dbName, version); + req.onupgradeneeded = () => { + const _db = req.result; + if (!_db.objectStoreNames.contains(storeName)) { + _db.createObjectStore(storeName, { + keyPath: resolvedKeyPath || "id", + }); + } + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } + + async function ensureDB() { + if (!db) db = await openDB(); + return db!; + } + + /** Initializes the store and loads all data if autoLoad is true. */ + async function init() { + if (!isBrowser) return; // SSR-safe: do not attempt DB operations on server + db = await openDB(); + if (autoLoad) { + const all = await getAll(); + store.set({ items: all }); + } + setupNetworkListeners(); + setupAutoPull(); + } + + function ensureKeyPath(item: T) { + if (!resolvedKeyPath) { + const firstKey = Object.keys(item)[0]; + resolvedKeyPath = firstKey; + } + return resolvedKeyPath!; + } + + /* ------------------------------------------------------------------------ + * CRUD + * ------------------------------------------------------------------------ */ + + /** + * Adds a new record to the store. + * @param {T} item - The record to add. + * @returns {Promise} + */ + async function add(item: T) { + const dbRef = await ensureDB(); + ensureKeyPath(item); + return new Promise((resolve, reject) => { + const tx = dbRef.transaction(storeName, "readwrite"); + const req = tx.objectStore(storeName).add(item); + req.onsuccess = async () => { + store.update((s) => ({ items: [...s.items, item] })); + bc?.postMessage("sync"); + await maybeSync(item, "add"); + resolve(); + }; + req.onerror = () => reject(req.error); + }); + } + + /** + * Updates an existing record by ID or object. + * @param {IDBValidKey | Partial} idOrItem - Record ID or object containing key. + * @param {Partial} [partial] - Partial data to merge when using ID form. + * @returns {Promise} + */ + async function update( + idOrItem: IDBValidKey | Partial, + partial?: Partial + ) { + const dbRef = await ensureDB(); + + let id: IDBValidKey; + let newData: Partial = {}; + + const isKeyType = + typeof idOrItem === "string" || + typeof idOrItem === "number" || + idOrItem instanceof Date || + idOrItem instanceof ArrayBuffer || + ArrayBuffer.isView(idOrItem) || + Array.isArray(idOrItem); + + if (isKeyType) { + id = idOrItem as IDBValidKey; + newData = partial || {}; + } else if (typeof idOrItem === "object" && idOrItem !== null) { + ensureKeyPath(idOrItem as T); + id = (idOrItem as any)[resolvedKeyPath!]; + newData = idOrItem as Partial; + } else { + throw new Error("Invalid argument passed to update()"); + } + + const existing = await get(id); + if (!existing) return; + + const updated = { ...existing, ...newData } as T; + + return new Promise((resolve, reject) => { + const tx = dbRef.transaction(storeName, "readwrite"); + const req = tx.objectStore(storeName).put(updated); + req.onsuccess = async () => { + store.update((s) => ({ + items: s.items.map((i) => + (i as any)[resolvedKeyPath!] === id ? updated : i + ), + })); + bc?.postMessage("sync"); + await maybeSync(updated, "update"); + resolve(); + }; + req.onerror = () => reject(req.error); + }); + } + + /** + * Retrieves a record by ID. + * @param {IDBValidKey} id - Record key. + * @returns {Promise} The matching record, if found. + */ + async function get(id: IDBValidKey): Promise { + const dbRef = await ensureDB(); + return new Promise((resolve, reject) => { + const tx = dbRef.transaction(storeName, "readonly"); + const req = tx.objectStore(storeName).get(id); + req.onsuccess = () => resolve(req.result as T | undefined); + req.onerror = () => reject(req.error); + }); + } + + /** + * Retrieves all records. + * @returns {Promise} All stored records. + */ + async function getAll(): Promise { + const dbRef = await ensureDB(); + return new Promise((resolve, reject) => { + const tx = dbRef.transaction(storeName, "readonly"); + const req = tx.objectStore(storeName).getAll(); + req.onsuccess = () => resolve(req.result as T[]); + req.onerror = () => reject(req.error); + }); + } + + /** + * Removes a record by ID. + * @param {IDBValidKey} id - Record key. + * @returns {Promise} + */ + async function remove(id: IDBValidKey) { + const dbRef = await ensureDB(); + const key = resolvedKeyPath || "id"; + return new Promise((resolve, reject) => { + const tx = dbRef.transaction(storeName, "readwrite"); + const req = tx.objectStore(storeName).delete(id); + req.onsuccess = async () => { + store.update((s) => ({ + items: s.items.filter((i) => (i as any)[key] !== id), + })); + bc?.postMessage("sync"); + await maybeSync({ [key]: id } as any, "remove"); + resolve(); + }; + req.onerror = () => reject(req.error); + }); + } + + /** + * Clears all data from the store. + * @returns {Promise} + */ + async function clear() { + const dbRef = await ensureDB(); + return new Promise((resolve, reject) => { + const tx = dbRef.transaction(storeName, "readwrite"); + const req = tx.objectStore(storeName).clear(); + req.onsuccess = () => { + store.set({ items: [] }); + bc?.postMessage("sync"); + resolve(); + }; + req.onerror = () => reject(req.error); + }); + } + + /* ------------------------------------------------------------------------ + * Queries & Utilities + * ------------------------------------------------------------------------ */ + + async function where(filter: QueryFilter): Promise { + const all = await getAll(); + if (typeof filter === "function") return all.filter(filter); + return all.filter((item) => + Object.entries(filter).every(([k, v]) => item[k as keyof T] === v) + ); + } + + async function first(filter?: QueryFilter): Promise { + const res = filter ? await where(filter) : await getAll(); + return res[0]; + } + + async function find(id: IDBValidKey): Promise { + return get(id); + } + + /** + * Get the last matching record + * @example + * const lastAdult = await User.last(u => u.age >= 18); + */ + async function last(filter?: QueryFilter): Promise { + const res = filter ? await where(filter) : await getAll(); + return res[res.length - 1]; + } + + /** + * Count items matching filter + * @example + * const adultCount = await User.count(u => u.age >= 18); + */ + async function count(filter?: QueryFilter): Promise { + const res = filter ? await where(filter) : await getAll(); + return res.length; + } + + /** + * Check if any record exists matching filter + * @example + * const exists = await User.exists({ name: "John" }); + */ + async function exists(filter: QueryFilter): Promise { + const res = await where(filter); + return res.length > 0; + } + + /** + * Get a random record + * @example + * const randomUser = await User.random(); + */ + async function random(filter?: QueryFilter): Promise { + const res = filter ? await where(filter) : await getAll(); + if (res.length === 0) return undefined; + return res[Math.floor(Math.random() * res.length)]; + } + + async function below(filter: Partial): Promise { + const all = await getAll(); + return all.filter(item => + Object.entries(filter).every(([k, v]) => { + const val = item[k as keyof T]; + if (typeof val === "number" && typeof v === "number") return (val as number) < (v as number); + if (typeof val === "string" && typeof v === "string") return (val as string) < (v as string); + return false; + }) + ); +} + +async function above(filter: Partial): Promise { + const all = await getAll(); + return all.filter(item => + Object.entries(filter).every(([k, v]) => { + const val = item[k as keyof T]; + if (typeof val === "number" && typeof v === "number") return (val as number) > (v as number); + if (typeof val === "string" && typeof v === "string") return (val as string) > (v as string); + return false; + }) + ); +} + + + + async function query(predicate: (item: T) => boolean): Promise { + const all = await getAll(); + return all.filter(predicate); + } + + /** + * Creates a derived live query that reacts to store changes. + * @template U + * @param {(items: T[]) => U} selector - Function that selects part of the data. + * @returns {{ subscribe(fn: (value: U) => void): () => void; get(): U }} + */ + function deriveQuery(selector: (items: T[]) => U) { + const live = createStore({ value: selector(store.get().items) }); + + store.watch( + (s) => s.items, + (items) => { + const result = selector(items); + live.set({ value: result }); + } + ); + + return { + subscribe: (fn: (v: U) => void) => live.subscribe((s) => fn(s.value)), + get: () => live.get().value, + }; + } + + /* ------------------------------------------------------------------------ + * Live sync & network listeners (per-store) + * ------------------------------------------------------------------------ */ + + async function maybeSync(item: any, action: "add" | "update" | "remove") { + // sync only if configured and online + if ( + sync?.autoSync && + typeof isBrowser !== "undefined" && + navigator.onLine && + sync.push + ) { + try { + await sync.push(item, action); + } catch (err) { + // swallow - caller may implement retry + console.warn( + `[createIndexDBStore:${storeName}] sync push failed:`, + err + ); + } + } + } + + function setupAutoPull() { + if (!isBrowser) return; + if (sync?.interval && sync.pull) { + setInterval(async () => { + if (!navigator.onLine) return; + try { + const remote = await sync.pull!(); + if (remote) { + store.set({ items: remote as T[] }); + } + } catch (err) { + console.warn( + `[createIndexDBStore:${storeName}] sync pull failed:`, + err + ); + } + }, sync.interval); + } + } + + function setupNetworkListeners() { + if (!isBrowser) return; + + // offline + window.addEventListener("offline", () => { + isOffline = true; + try { + sync?.onOffline?.(); + } catch (err) { + console.error("onOffline callback error:", err); + } + // Trigger store subscribers reactively (announce network change) + store.update((s) => ({ items: s.items })); + }); + + // online + window.addEventListener("online", async () => { + const wasOffline = isOffline; + isOffline = false; + try { + sync?.onOnline?.(); + } catch (err) { + console.error("onOnline callback error:", err); + } + // If we were offline and now online, optionally pull latest + if (wasOffline && sync?.pull) { + try { + const remote = await sync.pull(); + if (remote) store.set({ items: remote as T[] }); + } catch (err) { + console.warn( + `[createIndexDBStore:${storeName}] sync pull after online failed:`, + err + ); + } + } + // notify subscribers (reactive) + store.update((s) => ({ items: s.items })); + }); + } + + // cross-tab broadcast listener (keeps store in sync across tabs) + if (bc) { + bc.onmessage = async (msg) => { + if (msg.data === "sync") { + const all = await getAll(); + store.set({ items: all }); + } + }; + } + + /* ------------------------------------------------------------------------ + * Transactions + * ------------------------------------------------------------------------ */ + + /** + * Runs a custom transaction operation. + * @param {(tx: IDBTransaction) => void} fn - Transaction callback. + * @returns {Promise} + */ + async function transaction(fn: (tx: IDBTransaction) => void) { + const dbRef = await ensureDB(); + const tx = dbRef.transaction(storeName, "readwrite"); + fn(tx); + return new Promise((resolve, reject) => { + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); + } + + // initialize + init(); + + /* ------------------------------------------------------------------------ + * Public API (do not remove these keys — kept for compatibility) + * ------------------------------------------------------------------------ */ + return { + add, + update, + get, + getAll, + remove, + clear, + query, + subscribe: store.subscribe, + watch: store.watch, + deriveQuery, + where, + first, + find, + put: update, + transaction, + random, + exists, + count, + last, + above, + below + }; +} + +/* -------------------------------------------------------------------------- + * IndexDBManager — multi-store manager with global network callbacks + * -------------------------------------------------------------------------- */ + +/** + * Options for IndexDBManager constructor (global network callbacks). + */ +interface IndexDBManagerOptions { + onOffline?: () => void; + onOnline?: () => void; +} + +/** + * A lightweight IndexedDB manager that can create and manage multiple stores. + * Supports optional global onOnline/onOffline callbacks. Each store also supports + * per-store callbacks via its DBStoreOptions.sync.onOnline/onOffline. + */ +export class IndexDBManager { + private dbName: string; + private version: number; + private stores: Record>> = + {}; + private globalOnOffline?: () => void; + private globalOnOnline?: () => void; + + /** + * Create a manager for a named IndexedDB database. + * @param {string} dbName - database name + * @param {number} [version=1] - db version + * @param {IndexDBManagerOptions} [opts] - global network callbacks + */ + constructor( + dbName: string, + version: number = 1, + opts?: IndexDBManagerOptions + ) { + this.dbName = dbName; + this.version = version; + this.globalOnOffline = opts?.onOffline; + this.globalOnOnline = opts?.onOnline; + + // If running in browser, hook into global online/offline to call manager-level hooks. + if (isBrowser) { + window.addEventListener("offline", () => { + try { + this.globalOnOffline?.(); + } catch (err) { + console.error("IndexDBManager.onOffline error:", err); + } + }); + window.addEventListener("online", () => { + try { + this.globalOnOnline?.(); + } catch (err) { + console.error("IndexDBManager.onOnline error:", err); + } + }); + } + } + + /** + * Creates a new store within the database. + * + * @template T + * @param {string} storeName - The name of the object store. + * @param {string} [keyPath='id'] - The key path used as the primary key. + * @param {{ sync?: SyncOptions }} [opts] - Optional per-store additions (e.g., per-store onOnline/onOffline). + * @returns {ReturnType>} The created store instance. + * + * @example + * // Create multiple stores + * const users = db.createStore("users", "id"); + * const posts = db.createStore("posts", "id"); + */ + createStore>( + storeName: string, + keyPath: string = "id", + opts?: { sync?: SyncOptions } + ) { + // merge global and per-store network callbacks: per-store takes precedence + const mergedSync: SyncOptions | undefined = opts?.sync + ? { + ...opts.sync, + onOffline: opts.sync.onOffline ?? this.globalOnOffline, + onOnline: opts.sync.onOnline ?? this.globalOnOnline, + } + : this.globalOnOffline || this.globalOnOnline + ? { onOffline: this.globalOnOffline, onOnline: this.globalOnOnline } + : undefined; + + const store = createIndexDBStore({ + dbName: this.dbName, + storeName, + keyPath, + version: this.version, + sync: mergedSync, + }); + this.stores[storeName] = store; + return store; + } + + /** + * Retrieves a store by its name. + * @param {string} name - The store name. + * @returns {ReturnType | undefined} The store instance, if found. + */ + getStore(name: string) { + return this.stores[name]; + } +} + +/* -------------------------------------------------------------------------- + * 📘 Example Usage (merged, minimal + full + multi-store) + * -------------------------------------------------------------------------- + * + * Minimal: + * + * const userStore = createIndexDBStore({ + * storeName: 'users', + * }); + * + * await userStore.add({ id: 'u1', name: 'Joseph', age: 22 }); + * const all = await userStore.getAll(); + * console.log(all); + * + * + * Full single-store config: + * + * interface User { + * id: string; + * name: string; + * age: number; + * } + * + * const userStore = createIndexDBStore({ + * dbName: "MyAppDB", + * storeName: "users", + * keyPath: "id", + * version: 1, + * sync: { + * endpoint: "https://api.example.com/users", + * async push(item, action) { + * // simple example using fetch + * if (action === "add") await fetch(this.endpoint!, { method: "POST", body: JSON.stringify(item) }); + * if (action === "update") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "PUT", body: JSON.stringify(item) }); + * if (action === "remove") await fetch(`${this.endpoint}/${(item as any).id}`, { method: "DELETE" }); + * }, + * async pull() { + * const res = await fetch("https://api.example.com/users"); + * return res.json(); + * }, + * interval: 15000, + * autoSync: true, + * onOffline: () => console.log("User store offline"), + * onOnline: () => console.log("User store online"), + * }, + * }); + * + * // Multi-store manager usage with global + per-store callbacks: + * + * const db = new IndexDBManager("MyAppDB", 1, { + * onOffline: () => console.log("Global offline"), + * onOnline: () => console.log("Global online"), + * }); + * + * // per-store callbacks override global if provided + * const users = db.createStore("users", "id", { + * sync: { + * onOffline: () => console.log("Users store offline"), + * onOnline: () => console.log("Users store online"), + * } + * }); + * + * const posts = db.createStore<{ id: string; title: string }>("posts", "id"); + * + * await users.add({ id: "u1", name: "Joe", age: 22 }); + * await posts.add({ id: "p1", title: "Hello World" }); + * + * users.subscribe(state => console.log("Users changed:", state.items)); + * posts.subscribe(state => console.log("Posts changed:", state.items)); + * + * // derived example (watch one user) + * const watchUser = users.deriveQuery(items => items.find(u => u.id === "u1") || null); + * const unsubscribeWatch = watchUser.subscribe(u => console.log("u1 changed:", u)); + * + * // manual transaction example + * await users.transaction(tx => { + * const s = tx.objectStore("users"); + * s.add({ id: "u2", name: "Ada", age: 45 }); + * }); + * + * // Note: When running in SSR environment, store operations that touch IndexedDB are safe-guarded. + * + * -------------------------------------------------------------------------- */ diff --git a/src/createFileRoutes.ts b/src/createFileRoutes.ts index a117e02..401bca1 100644 --- a/src/createFileRoutes.ts +++ b/src/createFileRoutes.ts @@ -1,30 +1,30 @@ -import type { Component } from "ripple"; -import type { Route } from "./types"; - - - -let NotFoundComponent: Component; - - -export function createFileRoutes(modules: any[]): Route[] { - // @ts-ignore - ///const modules = import.meta.glob("/src/pages/**/*.ripple", { eager: true }); - NotFoundComponent = modules["/src/pages/notfound.ripple"]?.default - - const routes: Route[] = Object.entries(modules).map(([file, mod]) => { - const path = file - .replace("/src/pages", "") - .replace(/index\.ripple$/, "") - .replace(/\.ripple$/, "") - .replace(/\[(.+?)\]/g, ":$1"); - - return { - path: path || "/", - component: (mod as { default: Component }).default, - }; - }); - - routes.push({ path: "*", component: NotFoundComponent }); - return routes; -} - +import type { Component } from "ripple"; +import type { Route } from "./types"; + + + +let NotFoundComponent: Component; + + +export function createFileRoutes(modules: any[]): Route[] { + // @ts-ignore + ///const modules = import.meta.glob("/src/pages/**/*.ripple", { eager: true }); + NotFoundComponent = modules["/src/pages/notfound.ripple"]?.default + + const routes: Route[] = Object.entries(modules).map(([file, mod]) => { + const path = file + .replace("/src/pages", "") + .replace(/index\.ripple$/, "") + .replace(/\.ripple$/, "") + .replace(/\[(.+?)\]/g, ":$1"); + + return { + path: path || "/", + component: (mod as { default: Component }).default, + }; + }); + + routes.push({ path: "*", component: NotFoundComponent }); + return routes; +} + diff --git a/src/globe.ts b/src/globe.ts index 14066fd..6564186 100644 --- a/src/globe.ts +++ b/src/globe.ts @@ -1,9 +1,9 @@ - - - -export function loadPages(): any { - // @ts-ignore - const modules = import.meta.glob("/src/pages/**/*.ripple", { eager: true }); -return modules - + + + +export function loadPages(): any { + // @ts-ignore + const modules = import.meta.glob("/src/pages/**/*.ripple", { eager: true }); +return modules + } \ No newline at end of file diff --git a/src/index.d.ts b/src/index.d.ts index 81ab558..b7a7741 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,21 +1,21 @@ -declare module "rivra" { -import type { FastifyReply, FastifyRequest, FastifyInstance } from "fastify"; - import type { Component } from "ripple"; - - export interface LinkProps { - href: string; - className?: string; - onLoading?: () => void; - loadingComponent?: Component; - emitEvent?: boolean; - children?: any; - queries?: Record; - } - - export const Link: Component; - export const Router: Component; - export const PageRoutes: Component; - export type Req = FastifyRequest; -export type Reply = FastifyReply; -export type App = FastifyInstance; -} +declare module "rivra" { +import type { FastifyReply, FastifyRequest, FastifyInstance } from "fastify"; + import type { Component } from "ripple"; + + export interface LinkProps { + href: string; + className?: string; + onLoading?: () => void; + loadingComponent?: Component; + emitEvent?: boolean; + children?: any; + queries?: Record; + } + + export const Link: Component; + export const Router: Component; + export const PageRoutes: Component; + export type Req = FastifyRequest; +export type Reply = FastifyReply; +export type App = FastifyInstance; +} diff --git a/src/index.ts b/src/index.ts index 53b3d37..b721316 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,13 @@ -// export * from "./createFileRoutes"; -export * from "./types"; -export * from "./stores"; -export * from "./router" - - - -import type { FastifyReply, FastifyRequest, FastifyInstance } from "fastify"; - -export type Req = FastifyRequest; -export type Reply = FastifyReply; -export type App = FastifyInstance; - +// export * from "./createFileRoutes"; +export * from "./types"; +export * from "./stores"; +export * from "./router" + + + +import type { FastifyReply, FastifyRequest, FastifyInstance } from "fastify"; + +export type Req = FastifyRequest; +export type Reply = FastifyReply; +export type App = FastifyInstance; + diff --git a/src/packages/build.ts b/src/packages/build.ts new file mode 100644 index 0000000..041802a --- /dev/null +++ b/src/packages/build.ts @@ -0,0 +1,7 @@ +import { build } from "vite"; + +export async function buildProject() { + console.log("📦 Building Rivra project..."); + await build(); + console.log("✅ Build completed."); +} diff --git a/src/packages/createApp.ts b/src/packages/createApp.ts new file mode 100644 index 0000000..d29600a --- /dev/null +++ b/src/packages/createApp.ts @@ -0,0 +1,27 @@ +import fs from "fs"; +import path from "path"; + +export async function createApp(name: string) { + const dest = path.resolve(process.cwd(), name); + + if (fs.existsSync(dest)) { + console.log(`❌ Folder '${name}' already exists.`); + return; + } + + fs.mkdirSync(dest); + + // Copy template (you can adjust this) + const templateDir = path.resolve(__dirname, "../../templates/app"); + + if (fs.existsSync(templateDir)) { + for (const file of fs.readdirSync(templateDir)) { + fs.copyFileSync( + path.join(templateDir, file), + path.join(dest, file) + ); + } + } + + console.log(`🎉 Rivra project '${name}' created.`); +} diff --git a/src/packages/devServer.ts b/src/packages/devServer.ts new file mode 100644 index 0000000..5f92351 --- /dev/null +++ b/src/packages/devServer.ts @@ -0,0 +1,6 @@ +import { StartServer } from "../../packages/server"; + +export async function startDevServer() { + console.log("🔧 Starting Rivra dev server..."); + await StartServer({ dev: true }); +} diff --git a/src/rcomponents/link.d.ts b/src/rcomponents/link.d.ts index 202c8ef..0ce8699 100644 --- a/src/rcomponents/link.d.ts +++ b/src/rcomponents/link.d.ts @@ -1,26 +1,26 @@ -import type { Component } from "ripple"; -export * from "../rcomponents/link.ripple" -export *from "../rcomponents/page_routes.ripple" -export * from "../rcomponents/router.ripple" -declare module "./rcomponents/link.ripple" { - export default function Link( - href: string, - children: Component, - emitEvent?: boolean, - onLoading?: () => void, - loadingComponent?: Component, - className?: string, - queries?: Record - ): void; -} - - - export declare function Link( - href: string, - children: Component, - emitEvent?: boolean, - onLoading?: () => void, - loadingComponent?: Component, - className?: string, - queries?: Record +import type { Component } from "ripple"; +export * from "../rcomponents/link.ripple" +export *from "../rcomponents/page_routes.ripple" +export * from "../rcomponents/router.ripple" +declare module "./rcomponents/link.ripple" { + export default function Link( + href: string, + children: Component, + emitEvent?: boolean, + onLoading?: () => void, + loadingComponent?: Component, + className?: string, + queries?: Record + ): void; +} + + + export declare function Link( + href: string, + children: Component, + emitEvent?: boolean, + onLoading?: () => void, + loadingComponent?: Component, + className?: string, + queries?: Record ): void; \ No newline at end of file diff --git a/src/rcomponents/link.ripple b/src/rcomponents/link.ripple index 74b3a2f..b3b85ab 100644 --- a/src/rcomponents/link.ripple +++ b/src/rcomponents/link.ripple @@ -1,78 +1,78 @@ -import type { Component } from 'ripple'; -import { useRouter } from '../userouter'; -import { track, effect } from 'ripple'; -import { LinkProps } from "../types.ts"; - -/** - * @component - * A navigation component for client-side routing. - * - * @param {string} href - The URL path to navigate to. - * @param {Component} children - The content to render inside the link. - * @param {() => void} [onLoading] - Optional callback triggered when navigation starts. - * @param {string} [className] - Additional class names for styling. - * @param {Component} [loadingComponent] - Optional component to display while loading. - * @param {boolean} [emitEvent=true] - Whether to emit route events during navigation. - * @param {Record} [queries] - Optional query parameters for URLSearch. - * - * @example - * console.log('Loading...')}> - * {"Go to Dashboard"} - * - */ -export component Link({ - href, - children, - onLoading, - loadingComponent, - className, - queries, - emitEvent = true, -}: LinkProps) { - const router = useRouter(); - let isLoading = track(false); // reactive per-Link loading state - const currentUrl = track(router.path); // track current path - - - -const handleClick = (e: MouseEvent) => { - if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return; - e.preventDefault(); - - if (!href) return; - - // handle in-page anchor links - if (href.startsWith("#")) { - const el = document.querySelector(href); - if (el) { - // scroll to section smoothly - el.scrollIntoView({ behavior: "smooth" }); - - // update hash in URL without page reload - history.pushState({}, "", href); - } - return; - } - - if (@currentUrl === href) { - window.scrollTo(0, {behavior: "auto" }) - return - }; - if (onLoading) onLoading(); - - window.scrollTo(0, {behavior: "auto" }) - router.push(href, emitEvent, false, queries); -}; - - - - - - - - if (@isLoading && loadingComponent) { - - } -} - -export default Link; +import type { Component } from 'ripple'; +import { useRouter } from '../userouter'; +import { track, effect } from 'ripple'; +import { LinkProps } from "../types.ts"; + +/** + * @component + * A navigation component for client-side routing. + * + * @param {string} href - The URL path to navigate to. + * @param {Component} children - The content to render inside the link. + * @param {() => void} [onLoading] - Optional callback triggered when navigation starts. + * @param {string} [className] - Additional class names for styling. + * @param {Component} [loadingComponent] - Optional component to display while loading. + * @param {boolean} [emitEvent=true] - Whether to emit route events during navigation. + * @param {Record} [queries] - Optional query parameters for URLSearch. + * + * @example + * console.log('Loading...')}> + * {"Go to Dashboard"} + * + */ +export component Link({ + href, + children, + onLoading, + loadingComponent, + className, + queries, + emitEvent = true, +}: LinkProps) { + const router = useRouter(); + let isLoading = track(false); // reactive per-Link loading state + const currentUrl = track(router.path); // track current path + + + +const handleClick = (e: MouseEvent) => { + if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return; + e.preventDefault(); + + if (!href) return; + + // handle in-page anchor links + if (href.startsWith("#")) { + const el = document.querySelector(href); + if (el) { + // scroll to section smoothly + el.scrollIntoView({ behavior: "smooth" }); + + // update hash in URL without page reload + history.pushState({}, "", href); + } + return; + } + + if (@currentUrl === href) { + window.scrollTo(0, {behavior: "auto" }) + return + }; + if (onLoading) onLoading(); + + window.scrollTo(0, {behavior: "auto" }) + router.push(href, emitEvent, false, queries); +}; + + + + + + + + if (@isLoading && loadingComponent) { + + } +} + +export default Link; diff --git a/src/rcomponents/notfound.ripple b/src/rcomponents/notfound.ripple index 37255bd..b2d54f7 100644 --- a/src/rcomponents/notfound.ripple +++ b/src/rcomponents/notfound.ripple @@ -1,67 +1,67 @@ -// import Link from "./link.ripple" -export default component NotFound404() { - -
-

- {"40"}{"4"} -

-

{"Oops! The page you are looking for does not exist."}

- {"Go Home"} - - -
-} +// import Link from "./link.ripple" +export default component NotFound404() { + +
+

+ {"40"}{"4"} +

+

{"Oops! The page you are looking for does not exist."}

+ {"Go Home"} + + +
+} diff --git a/src/rcomponents/page_routes.d.ts b/src/rcomponents/page_routes.d.ts index 426e059..d508e7a 100644 --- a/src/rcomponents/page_routes.d.ts +++ b/src/rcomponents/page_routes.d.ts @@ -1,15 +1,15 @@ -// page_routes.ripple.d.ts -import type { Component } from "ripple"; // if needed - -declare module "./rcomponents/page_routes.ripple" { - export default function PageRoutes( - modules?: any, - enableLoader?: boolean, - ): void; -} - - - export declare function PageRoutes( - modules?: any, - enableLoader?: boolean, +// page_routes.ripple.d.ts +import type { Component } from "ripple"; // if needed + +declare module "./rcomponents/page_routes.ripple" { + export default function PageRoutes( + modules?: any, + enableLoader?: boolean, + ): void; +} + + + export declare function PageRoutes( + modules?: any, + enableLoader?: boolean, ): void; \ No newline at end of file diff --git a/src/rcomponents/page_routes.ripple b/src/rcomponents/page_routes.ripple index 5540bd0..e460be1 100644 --- a/src/rcomponents/page_routes.ripple +++ b/src/rcomponents/page_routes.ripple @@ -1,19 +1,19 @@ -import { useRouter } from '../userouter'; -import { createFileRoutes } from "../createFileRoutes"; -import { Router } from "./router.ripple"; - - -interface Props { - enableLoader?: boolean - modules: any[] -} - -export component PageRoutes({modules, enableLoader = true}: Props){ - const routes = createFileRoutes(modules); - - - -} - - +import { useRouter } from '../userouter'; +import { createFileRoutes } from "../createFileRoutes"; +import { Router } from "./router.ripple"; + + +interface Props { + enableLoader?: boolean + modules: any[] +} + +export component PageRoutes({modules, enableLoader = true}: Props){ + const routes = createFileRoutes(modules); + + + +} + + export default PageRoutes \ No newline at end of file diff --git a/src/rcomponents/router.ripple b/src/rcomponents/router.ripple index de2bf91..ec07492 100644 --- a/src/rcomponents/router.ripple +++ b/src/rcomponents/router.ripple @@ -1,53 +1,53 @@ -import type { Component } from "ripple"; -import { effect, track } from "ripple"; -import { useRouter } from "../userouter"; -import type { Route, RouterProp } from "../types"; - -export component Router({ routes }: RouterProp) { - const router = useRouter(); - - let currentPath = track(null); - let matched = track(null); - - // Client-only logic in an effect - effect(() => { - if (typeof window === "undefined") return; - - @currentPath = window.location.pathname; - - const handler = () => { - @currentPath = window.location.pathname; - @matched = null; - }; - - window.addEventListener("popstate", handler); - window.addEventListener("pushstate", handler); - window.addEventListener("replacestate", handler); - - return () => { - window.removeEventListener("popstate", handler); - window.removeEventListener("pushstate", handler); - window.removeEventListener("replacestate", handler); - }; - }); - - // Recompute matched route when path changes - effect(() => { - if (@currentPath === null) return; - @matched = routes.find(r => router.match(r.path, @currentPath.toString())) || null; - }); - - // Always render a template, SSR-safe - if (@matched) { - const Comp = @matched.component; - - } else { - - } -} - -component DefaultNotFound() { -
{"404 Page not found"}
-} - -export default Router; +import type { Component } from "ripple"; +import { effect, track } from "ripple"; +import { useRouter } from "../userouter"; +import type { Route, RouterProp } from "../types"; + +export component Router({ routes }: RouterProp) { + const router = useRouter(); + + let currentPath = track(null); + let matched = track(null); + + // Client-only logic in an effect + effect(() => { + if (typeof window === "undefined") return; + + @currentPath = window.location.pathname; + + const handler = () => { + @currentPath = window.location.pathname; + @matched = null; + }; + + window.addEventListener("popstate", handler); + window.addEventListener("pushstate", handler); + window.addEventListener("replacestate", handler); + + return () => { + window.removeEventListener("popstate", handler); + window.removeEventListener("pushstate", handler); + window.removeEventListener("replacestate", handler); + }; + }); + + // Recompute matched route when path changes + effect(() => { + if (@currentPath === null) return; + @matched = routes.find(r => router.match(r.path, @currentPath.toString())) || null; + }); + + // Always render a template, SSR-safe + if (@matched) { + const Comp = @matched.component; + + } else { + + } +} + +component DefaultNotFound() { +
{"404 Page not found"}
+} + +export default Router; diff --git a/src/rcomponents/style.css b/src/rcomponents/style.css index be07ea9..3753bdb 100644 --- a/src/rcomponents/style.css +++ b/src/rcomponents/style.css @@ -1,23 +1,23 @@ -.loader { - position: fixed; - top: 0; - left: 0; - height: 3px; - z-index: 9999; - border-radius: 2px; - background: linear-gradient(270deg, #1e90ff, #ff1493, #00ff7f); - background-size: 600% 600%; - transition: width 0.15s linear; - - /* blink/gradient animation */ - animation-name: progress-blink; - animation-duration: 1.5s; - animation-timing-function: ease; - animation-iteration-count: infinite; - - /* opacity animation */ - animation-name: fade-in-out; /* this will override the previous one if not careful */ - animation-duration: 0.8s; - animation-timing-function: ease-in-out; - animation-iteration-count: infinite; -} +.loader { + position: fixed; + top: 0; + left: 0; + height: 3px; + z-index: 9999; + border-radius: 2px; + background: linear-gradient(270deg, #1e90ff, #ff1493, #00ff7f); + background-size: 600% 600%; + transition: width 0.15s linear; + + /* blink/gradient animation */ + animation-name: progress-blink; + animation-duration: 1.5s; + animation-timing-function: ease; + animation-iteration-count: infinite; + + /* opacity animation */ + animation-name: fade-in-out; /* this will override the previous one if not careful */ + animation-duration: 0.8s; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; +} diff --git a/src/ripple.d.ts b/src/ripple.d.ts index 168b1ca..8100320 100644 --- a/src/ripple.d.ts +++ b/src/ripple.d.ts @@ -1,5 +1,5 @@ -declare module "*.ripple" { - import type { Component } from "ripple"; - const component: Component; - export default component; -} +declare module "*.ripple" { + import type { Component } from "ripple"; + const component: Component; + export default component; +} diff --git a/src/router/index.d.ts b/src/router/index.d.ts index c3670a7..43397e1 100644 --- a/src/router/index.d.ts +++ b/src/router/index.d.ts @@ -1,92 +1,92 @@ -import type { Component } from "ripple"; -export declare const Router: (props: { - routes: any; -}) => void; -/** - * A navigation component for client-side routing. - * - * @param {string} href - The URL path to navigate to. - * @param {Component} children - The content to render inside the link. - * @param {() => void} [onLoading] - Optional callback triggered when navigation starts. - * @param {string} [className] - Additional class names for styling. - * @param {Component} [loadingComponent] - Optional component to display while loading. - * @param {boolean} [emitEvent=true] - Whether to emit route events during navigation. - * @param {Record} [queries] - Optional query parameters for URLSearch. - * - * @example - * console.log('Loading...')}> - * {"Go to Dashboard"} - * - */ -declare const Link: (props: { - href: string; - children: Component; - emitEvent?: boolean; - onLoading?: () => void; - loadingComponent?: Component; - className?: string; - queries?: Record; -}) => void; -export declare const PageRoutes: (props: { - modules?: any; - enableLoader?: boolean; -}) => void; -export { Link }; - - - - - - export interface InuseRouter { - readonly path: string; - readonly asPath: string; - readonly queries: Record; - readonly params: Record; - readonly loading: boolean; - - push( - url: string, - announce?: boolean, - shallow?: boolean, - queries?: Record - ): void; - - replace( - url: string, - announce?: boolean, - shallow?: boolean, - queries?: Record - ): void; - - back(announce?: boolean): void; - - match(pattern: string, currentPath: string): true | null; - on(type: "start" | "complete" | "change", cb: (path: string) => void): () => void; - beforePopState(cb: (url: string) => boolean | void): () => void; - prefetch(url: string): Promise; - resolveHref(url: string): string; - isActive(url: string): boolean; - - readonly host: string; - readonly hostname: string; - readonly protocol: string; - readonly origin: string; - readonly port: string; - readonly href: string; - readonly hash: string; - readonly search: string; - } - - /** - * useRouter hook - * - * Provides client-side navigation and routing utilities. - * - * @example - * ```ts - * import { useRouter } from "rivra/router"; - * const router = useRouter(); - * router.push("/about"); - * ``` - */ +import type { Component } from "ripple"; +export declare const Router: (props: { + routes: any; +}) => void; +/** + * A navigation component for client-side routing. + * + * @param {string} href - The URL path to navigate to. + * @param {Component} children - The content to render inside the link. + * @param {() => void} [onLoading] - Optional callback triggered when navigation starts. + * @param {string} [className] - Additional class names for styling. + * @param {Component} [loadingComponent] - Optional component to display while loading. + * @param {boolean} [emitEvent=true] - Whether to emit route events during navigation. + * @param {Record} [queries] - Optional query parameters for URLSearch. + * + * @example + * console.log('Loading...')}> + * {"Go to Dashboard"} + * + */ +declare const Link: (props: { + href: string; + children: Component; + emitEvent?: boolean; + onLoading?: () => void; + loadingComponent?: Component; + className?: string; + queries?: Record; +}) => void; +export declare const PageRoutes: (props: { + modules?: any; + enableLoader?: boolean; +}) => void; +export { Link }; + + + + + + export interface InuseRouter { + readonly path: string; + readonly asPath: string; + readonly queries: Record; + readonly params: Record; + readonly loading: boolean; + + push( + url: string, + announce?: boolean, + shallow?: boolean, + queries?: Record + ): void; + + replace( + url: string, + announce?: boolean, + shallow?: boolean, + queries?: Record + ): void; + + back(announce?: boolean): void; + + match(pattern: string, currentPath: string): true | null; + on(type: "start" | "complete" | "change", cb: (path: string) => void): () => void; + beforePopState(cb: (url: string) => boolean | void): () => void; + prefetch(url: string): Promise; + resolveHref(url: string): string; + isActive(url: string): boolean; + + readonly host: string; + readonly hostname: string; + readonly protocol: string; + readonly origin: string; + readonly port: string; + readonly href: string; + readonly hash: string; + readonly search: string; + } + + /** + * useRouter hook + * + * Provides client-side navigation and routing utilities. + * + * @example + * ```ts + * import { useRouter } from "rivra/router"; + * const router = useRouter(); + * router.push("/about"); + * ``` + */ export function useRouter(): InuseRouter; \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 86fea03..d8eaae1 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,53 +1,53 @@ -import type { Component } from "ripple"; -import _Router from "../rcomponents/router.ripple"; -import _PageRoutes from "../rcomponents/page_routes.ripple" ; -import _Link from "../rcomponents/link.ripple"; - - -export { useRouter } from "../userouter"; - - - - -export const Router = _Router as unknown as ( - props: { - routes: any - } -) => void - -/** - * A navigation component for client-side routing. - * - * @param {string} href - The URL path to navigate to. - * @param {Component} children - The content to render inside the link. - * @param {() => void} [onLoading] - Optional callback triggered when navigation starts. - * @param {string} [className] - Additional class names for styling. - * @param {Component} [loadingComponent] - Optional component to display while loading. - * @param {boolean} [emitEvent=true] - Whether to emit route events during navigation. - * @param {Record} [queries] - Optional query parameters for URLSearch. - * - * @example - * console.log('Loading...')}> - * {"Go to Dashboard"} - * - */ - const Link = _Link as unknown as ( - props: { - href: string; - children: Component; - emitEvent?: boolean; - onLoading?: () => void; - loadingComponent?: Component; - className?: string; - queries?: Record; - } -) => void; - - export const PageRoutes = _PageRoutes as unknown as ( - props: { - modules?: any, - enableLoader?: boolean -} - ) => void ; - -export { Link} +import type { Component } from "ripple"; +import _Router from "../rcomponents/router.ripple"; +import _PageRoutes from "../rcomponents/page_routes.ripple" ; +import _Link from "../rcomponents/link.ripple"; + + +export { useRouter } from "../userouter"; + + + + +export const Router = _Router as unknown as ( + props: { + routes: any + } +) => void + +/** + * A navigation component for client-side routing. + * + * @param {string} href - The URL path to navigate to. + * @param {Component} children - The content to render inside the link. + * @param {() => void} [onLoading] - Optional callback triggered when navigation starts. + * @param {string} [className] - Additional class names for styling. + * @param {Component} [loadingComponent] - Optional component to display while loading. + * @param {boolean} [emitEvent=true] - Whether to emit route events during navigation. + * @param {Record} [queries] - Optional query parameters for URLSearch. + * + * @example + * console.log('Loading...')}> + * {"Go to Dashboard"} + * + */ + const Link = _Link as unknown as ( + props: { + href: string; + children: Component; + emitEvent?: boolean; + onLoading?: () => void; + loadingComponent?: Component; + className?: string; + queries?: Record; + } +) => void; + + export const PageRoutes = _PageRoutes as unknown as ( + props: { + modules?: any, + enableLoader?: boolean +} + ) => void ; + +export { Link} diff --git a/src/routes.ts b/src/routes.ts index bcba773..758d13e 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,2 +1,2 @@ -// @ts-ignore -export const modules = import.meta.glob("./pages/**/*.ripple", { eager: true }); +// @ts-ignore +export const modules = import.meta.glob("./pages/**/*.ripple", { eager: true }); diff --git a/src/store.ts b/src/store.ts index a565608..0d7f90f 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,250 +1,250 @@ -/** - * @typedef Subscriber - * A callback that receives the current store state or derived value. - * @example - * const unsub = store.subscribe(state => console.log(state)); - */ -type Subscriber = (value: T) => void; - -/** - * @typedef StoreOptions - * Options for creating a store. - * @property persist Whether to persist store in localStorage (default: false) - * @property storageKey The key to use in localStorage if persist is true - */ -interface StoreOptions { - persist?: boolean; - storageKey?: string; -} - -/** - * @typedef Middleware - * A function that runs before or after each update. - * @param state Current state of the store - * @param action Optional string describing the event - * @param payload Optional data passed to update/set - */ -type Middleware = (state: T, action?: string, payload?: any) => void; - -/** - * @function createStore - * Creates a reactive store with optional persistence and tools like watch & middleware. - * - * @param initial The initial state object - * @param options Optional store options - * @returns Store API with get, set, update, delete, subscribe, derive, watch, use, clear - * - * @example - * const counterStore = createStore({ count: 0 }); - */ -export function createStore( - initial: T, - options: StoreOptions = {} -) { - const { persist = false, storageKey } = options; - - // Check if running in browser for SSR safety - const canUseStorage = typeof window !== "undefined" && persist && storageKey; - - // Load persisted state if enabled - let value: T = { ...initial }; - if (canUseStorage) { - const stored = localStorage.getItem(storageKey!); - if (stored) { - try { - value = JSON.parse(stored); - } catch {} - } - } - - const subs = new Set>(); - const watchers: { selector: (state: T) => any; cb: (newVal: any, oldVal: any) => void }[] = []; - const middlewares: Middleware[] = []; - - let prevValue = { ...value }; - - /** Notify all subscribers and watchers of a state change */ - function notify(action?: string, payload?: any) { - subs.forEach(fn => fn(value)); - watchers.forEach(({ selector, cb }) => { - const newVal = selector(value); - const oldVal = selector(prevValue); - if (newVal !== oldVal) cb(newVal, oldVal); - }); - middlewares.forEach(fn => fn(value, action, payload)); - } - - /** Persist current state to localStorage if enabled */ - function persistToStorage() { - if (canUseStorage) { - try { - localStorage.setItem(storageKey!, JSON.stringify(value)); - } catch (err) { - console.log("error saving to localStorage: ", err); - } - } - } - - /** @function get — Returns the current store state */ - function get(): T { - return value; - } - - /** @function set — Replaces the entire store state */ - function set(next: T, action = "set") { - prevValue = value; - value = { ...next }; - persistToStorage(); - notify(action, next); - } - - /** - * @function update - * Merges a partial object or uses a callback to modify the current state. - * - * - Object patch: `store.update({ count: 1 })` - * - Callback style: `store.update(prev => ({ count: prev.count + 1 }))` - */ - function update(partial: Partial): void; - function update(partialFn: (prev: T) => Partial): void; - function update( - partialOrFn: Partial | ((prev: T) => Partial), - action = "update" - ) { - prevValue = value; - const patch = - typeof partialOrFn === "function" ? partialOrFn(value) : partialOrFn; - value = { ...value, ...patch }; - persistToStorage(); - notify(action, patch); - } - - /** @function delete — Removes one or multiple keys from the store */ - function deleteKeys(keys: (keyof T) | (keyof T)[], action = "delete") { - prevValue = value; - const kArray = Array.isArray(keys) ? keys : [keys]; - kArray.forEach(k => delete (value as any)[k]); - persistToStorage(); - notify(action, keys); - } - - /** @function clear — Clears both state and its localStorage record */ - function clear() { - prevValue = { ...value }; - value = { ...initial }; - if (canUseStorage) { - try { - localStorage.removeItem(storageKey!); - } catch (err) { - console.warn("Failed to clear persisted store:", err); - } - } - notify("clear"); - } - - /** - * @function subscribe - * Subscribes to all store changes (full state or a selected part). - * - * @param {Function} fn Callback to receive state or selected part - * @param {Function} [selector] Optional selector to receive subset - * - * @example - * store.subscribe(s => console.log(s)); // full state - * store.subscribe(s => console.log(s.theme), s => s.theme); // only theme - */ - function subscribe(fn: Subscriber, selector?: (state: T) => any) { - const wrapper = selector ? (state: T) => fn(selector(state)) : fn; - subs.add(wrapper); - wrapper(value); // initial call - return () => subs.delete(wrapper); - } - - /** - * @function watch - * Watches a specific value in the store and runs callback when it changes. - * Provides both new and old values. - * - * @param {Function} selector Function selecting what to watch - * @param {Function} callback Called with (newValue, oldValue) - * - * @example - * store.watch(s => s.count, (n, o) => console.log(`count: ${o} → ${n}`)); - */ - function watch(selector: (s: T) => any, callback: (n: any, o: any) => void) { - watchers.push({ selector, cb: callback }); - return () => { - const i = watchers.findIndex(w => w.cb === callback); - if (i >= 0) watchers.splice(i, 1); - }; - } - - /** - * @function use - * Adds middleware to run on every state change. - * - * Middleware is for logging, analytics, or side effects. - * - * @param {Function} middleware Function with (state, action?, payload?) - * - * @example - * store.use((s, action) => console.log(`[${action}]`, s)); - */ - function use(middleware: Middleware) { - middlewares.push(middleware); - return () => { - const i = middlewares.indexOf(middleware); - if (i >= 0) middlewares.splice(i, 1); - }; - } - - /** - * @function derive - * Creates a derived store that reacts to changes in selected parts of the state. - * - * @example - * const themeStore = store.derive(s => s.theme); - * themeStore.subscribe(v => console.log("theme:", v)); - */ - function derive(selector: (s: T) => K) { - return { - subscribe(cb: (v: K) => void) { - return subscribe(state => cb(selector(state))); - }, - }; - } - - return { - get, - set, - update, - delete: deleteKeys, - clear, - subscribe, - watch, - use, - derive, - }; -} - -export default createStore; - -// -------------------- Example Stores -------------------- - -/** - * This provides a basic store for your app like theme, user etc. - * It's an example which you can delete; - */ -export const appStore = createStore( - { - user: { name: "Joe", location: "unknown", preferences: [] }, - count: 0, - theme: "dark", - }, - { persist: true, storageKey: "appStore" } -); - -export const routeStore = createStore( - { path: "/" }, - { persist: true, storageKey: "routeStore" } -); +/** + * @typedef Subscriber + * A callback that receives the current store state or derived value. + * @example + * const unsub = store.subscribe(state => console.log(state)); + */ +type Subscriber = (value: T) => void; + +/** + * @typedef StoreOptions + * Options for creating a store. + * @property persist Whether to persist store in localStorage (default: false) + * @property storageKey The key to use in localStorage if persist is true + */ +interface StoreOptions { + persist?: boolean; + storageKey?: string; +} + +/** + * @typedef Middleware + * A function that runs before or after each update. + * @param state Current state of the store + * @param action Optional string describing the event + * @param payload Optional data passed to update/set + */ +type Middleware = (state: T, action?: string, payload?: any) => void; + +/** + * @function createStore + * Creates a reactive store with optional persistence and tools like watch & middleware. + * + * @param initial The initial state object + * @param options Optional store options + * @returns Store API with get, set, update, delete, subscribe, derive, watch, use, clear + * + * @example + * const counterStore = createStore({ count: 0 }); + */ +export function createStore( + initial: T, + options: StoreOptions = {} +) { + const { persist = false, storageKey } = options; + + // Check if running in browser for SSR safety + const canUseStorage = typeof window !== "undefined" && persist && storageKey; + + // Load persisted state if enabled + let value: T = { ...initial }; + if (canUseStorage) { + const stored = localStorage.getItem(storageKey!); + if (stored) { + try { + value = JSON.parse(stored); + } catch {} + } + } + + const subs = new Set>(); + const watchers: { selector: (state: T) => any; cb: (newVal: any, oldVal: any) => void }[] = []; + const middlewares: Middleware[] = []; + + let prevValue = { ...value }; + + /** Notify all subscribers and watchers of a state change */ + function notify(action?: string, payload?: any) { + subs.forEach(fn => fn(value)); + watchers.forEach(({ selector, cb }) => { + const newVal = selector(value); + const oldVal = selector(prevValue); + if (newVal !== oldVal) cb(newVal, oldVal); + }); + middlewares.forEach(fn => fn(value, action, payload)); + } + + /** Persist current state to localStorage if enabled */ + function persistToStorage() { + if (canUseStorage) { + try { + localStorage.setItem(storageKey!, JSON.stringify(value)); + } catch (err) { + console.log("error saving to localStorage: ", err); + } + } + } + + /** @function get — Returns the current store state */ + function get(): T { + return value; + } + + /** @function set — Replaces the entire store state */ + function set(next: T, action = "set") { + prevValue = value; + value = { ...next }; + persistToStorage(); + notify(action, next); + } + + /** + * @function update + * Merges a partial object or uses a callback to modify the current state. + * + * - Object patch: `store.update({ count: 1 })` + * - Callback style: `store.update(prev => ({ count: prev.count + 1 }))` + */ + function update(partial: Partial): void; + function update(partialFn: (prev: T) => Partial): void; + function update( + partialOrFn: Partial | ((prev: T) => Partial), + action = "update" + ) { + prevValue = value; + const patch = + typeof partialOrFn === "function" ? partialOrFn(value) : partialOrFn; + value = { ...value, ...patch }; + persistToStorage(); + notify(action, patch); + } + + /** @function delete — Removes one or multiple keys from the store */ + function deleteKeys(keys: (keyof T) | (keyof T)[], action = "delete") { + prevValue = value; + const kArray = Array.isArray(keys) ? keys : [keys]; + kArray.forEach(k => delete (value as any)[k]); + persistToStorage(); + notify(action, keys); + } + + /** @function clear — Clears both state and its localStorage record */ + function clear() { + prevValue = { ...value }; + value = { ...initial }; + if (canUseStorage) { + try { + localStorage.removeItem(storageKey!); + } catch (err) { + console.warn("Failed to clear persisted store:", err); + } + } + notify("clear"); + } + + /** + * @function subscribe + * Subscribes to all store changes (full state or a selected part). + * + * @param {Function} fn Callback to receive state or selected part + * @param {Function} [selector] Optional selector to receive subset + * + * @example + * store.subscribe(s => console.log(s)); // full state + * store.subscribe(s => console.log(s.theme), s => s.theme); // only theme + */ + function subscribe(fn: Subscriber, selector?: (state: T) => any) { + const wrapper = selector ? (state: T) => fn(selector(state)) : fn; + subs.add(wrapper); + wrapper(value); // initial call + return () => subs.delete(wrapper); + } + + /** + * @function watch + * Watches a specific value in the store and runs callback when it changes. + * Provides both new and old values. + * + * @param {Function} selector Function selecting what to watch + * @param {Function} callback Called with (newValue, oldValue) + * + * @example + * store.watch(s => s.count, (n, o) => console.log(`count: ${o} → ${n}`)); + */ + function watch(selector: (s: T) => any, callback: (n: any, o: any) => void) { + watchers.push({ selector, cb: callback }); + return () => { + const i = watchers.findIndex(w => w.cb === callback); + if (i >= 0) watchers.splice(i, 1); + }; + } + + /** + * @function use + * Adds middleware to run on every state change. + * + * Middleware is for logging, analytics, or side effects. + * + * @param {Function} middleware Function with (state, action?, payload?) + * + * @example + * store.use((s, action) => console.log(`[${action}]`, s)); + */ + function use(middleware: Middleware) { + middlewares.push(middleware); + return () => { + const i = middlewares.indexOf(middleware); + if (i >= 0) middlewares.splice(i, 1); + }; + } + + /** + * @function derive + * Creates a derived store that reacts to changes in selected parts of the state. + * + * @example + * const themeStore = store.derive(s => s.theme); + * themeStore.subscribe(v => console.log("theme:", v)); + */ + function derive(selector: (s: T) => K) { + return { + subscribe(cb: (v: K) => void) { + return subscribe(state => cb(selector(state))); + }, + }; + } + + return { + get, + set, + update, + delete: deleteKeys, + clear, + subscribe, + watch, + use, + derive, + }; +} + +export default createStore; + +// -------------------- Example Stores -------------------- + +/** + * This provides a basic store for your app like theme, user etc. + * It's an example which you can delete; + */ +export const appStore = createStore( + { + user: { name: "Joe", location: "unknown", preferences: [] }, + count: 0, + theme: "dark", + }, + { persist: true, storageKey: "appStore" } +); + +export const routeStore = createStore( + { path: "/" }, + { persist: true, storageKey: "routeStore" } +); diff --git a/src/stores/index.ts b/src/stores/index.ts index 638c861..aaee84a 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -1,5 +1,5 @@ -// export * from "../createDBStore"; -// export * from "../store"; -import createStore from "../store"; -import { createIndexDBStore } from "../createDBStore"; -export { createStore, createIndexDBStore }; +// export * from "../createDBStore"; +// export * from "../store"; +import createStore from "../store"; +import { createIndexDBStore } from "../createDBStore"; +export { createStore, createIndexDBStore }; diff --git a/src/types.ts b/src/types.ts index 889e222..24733f0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,27 +1,27 @@ -import { Component } from "ripple"; -import type { FastifyReply, FastifyRequest, FastifyInstance } from "fastify"; - - - -export type Req = FastifyRequest; -export type Reply = FastifyReply; -export type App = FastifyInstance; - -export interface Route { - path: string; - component: Component; -} - -export interface RouterProp { - routes: Route[]; -} - -export interface LinkProps { - href: string; - children: Component; - onLoading?: () => void; - emitEvent?: boolean; - loadingComponent?: Component; - className?: string; - queries?: Record; -} +import { Component } from "ripple"; +import type { FastifyReply, FastifyRequest, FastifyInstance } from "fastify"; + + + +export type Req = FastifyRequest; +export type Reply = FastifyReply; +export type App = FastifyInstance; + +export interface Route { + path: string; + component: Component; +} + +export interface RouterProp { + routes: Route[]; +} + +export interface LinkProps { + href: string; + children: Component; + onLoading?: () => void; + emitEvent?: boolean; + loadingComponent?: Component; + className?: string; + queries?: Record; +} diff --git a/src/userouter.ts b/src/userouter.ts index c7e9bd9..6ed7312 100644 --- a/src/userouter.ts +++ b/src/userouter.ts @@ -1,376 +1,376 @@ -import type { Component } from "ripple"; - -type RouterListener = (path: string) => void; -type BeforePopCallback = (url: string) => boolean | void; - -/** - * RouterStore - * ----------- - * Client-side router for Ripple apps, inspired by Next.js router. - * Features: - * - Path tracking - * - Query parameters - * - Dynamic route parameters (params) - * - Loading state - * - Router events: start, complete, change - * - Shallow routing - * - Back navigation guards - * - Prefetching of routes - * - * Uses HTML5 History API to avoid full page reloads. - */ -class RouterStore { - /** Current pathname (e.g., "/users/42") */ - path: string = ""; - - /** Query parameters (e.g., { tab: "posts" }) */ - queries: Record = {}; - - /** Dynamic route parameters extracted from matched patterns (e.g., { id: "42" }) */ - params: Record = {}; - - /** Loading state, true when navigation starts */ - loading = false; - - /** Internal event listeners: start, complete, change */ - private listeners: { - start: RouterListener[]; - complete: RouterListener[]; - change: RouterListener[]; - } = { start: [], complete: [], change: [] }; - - /** Callbacks that can block back/forward navigation */ - private beforePop: BeforePopCallback[] = []; - - constructor() { - if (typeof window !== "undefined") { - this.syncWithLocation(); - window.addEventListener("popstate", () => this.handlePopState()); - } - } - - /** Handle browser back/forward events */ - private handlePopState() { - if (typeof window === "undefined") return; - for (const fn of this.beforePop) { - const result = fn(this.asPath); - if (result === false) return; // Cancel navigation - } - this.syncWithLocation(); - } - - /** - * Sync router state with window.location - * @param announce Whether to emit events (default: true) - */ - private syncWithLocation(announce = true) { - if (typeof window === "undefined") return; - this.path = window.location.pathname; - this.queries = Object.fromEntries(new URLSearchParams(window.location.search).entries()); - this.params = {}; - this.loading = false; - - if (announce) { - this.emit("change", this.path); - this.emit("complete", this.path); - } - } - - /** Emit event to all listeners */ - private emit(type: keyof RouterStore["listeners"], path: string) { - for (const cb of this.listeners[type]) cb(path); - } - - /** - * Subscribe to router events - * @param type "start" | "complete" | "change" - * @param cb Callback called with path - * @returns Unsubscribe function - */ - on(type: keyof RouterStore["listeners"], cb: RouterListener) { - this.listeners[type].push(cb); - return () => { - this.listeners[type] = this.listeners[type].filter(fn => fn !== cb); - }; - } - - /** Builds a complete URL from path + query object */ - private buildUrl(path: string, queries?: Record): string { - if (!queries || Object.keys(queries).length === 0) return path; - const params = new URLSearchParams(); - for (const [key, value] of Object.entries(queries)) { - if (value !== undefined && value !== null) params.set(key, String(value)); - } - const query = params.toString(); - return query ? `${path}?${query}` : path; - } - - /** - * Register a callback before back/forward navigation - * Return false to cancel navigation - */ - beforePopState(cb: BeforePopCallback) { - this.beforePop.push(cb); - return () => { - this.beforePop = this.beforePop.filter(f => f !== cb); - }; - } - - /** - * Navigate to a new URL (pushState) - * @param url Target URL - * @param announce Emit events? (default: true) - * @param shallow Update URL without full sync? (default: false) - */ - push( - url: string, - announce = true, - shallow = false, - queries?: Record, - ) { - const fullUrl = this.buildUrl(url, queries); - this.navigate(fullUrl, "push", announce, shallow); - } - - /** - * Replace current URL (replaceState) - * @param url Target URL - * @param announce Emit events? (default: true) - * @param shallow Update URL without full sync? (default: false) - */ - replace( - url: string, - announce = true, - shallow = false, - queries?: Record, - ) { - const fullUrl = this.buildUrl(url, queries); - this.navigate(fullUrl, "replace", announce, shallow); - } - - /** Go back in browser history */ - back(announce = true) { - if (typeof window === "undefined") return; - this.loading = true; - if (announce) this.emit("start", this.path); - history.back(); - window.dispatchEvent(new Event("popstate")); - } - - /** Internal navigation helper */ - private navigate(url: string, method: "push" | "replace", announce = true, shallow = false) { - if (typeof window === "undefined") return; - - this.loading = true; - if (announce) this.emit("start", url); - - const [path, search = ""] = url.split("?"); - - // Always update internal state - this.path = path; - this.queries = Object.fromEntries(new URLSearchParams(search).entries()); - - // Update browser history - if (method === "push") window.history.pushState({}, "", url); - else window.history.replaceState({}, "", url); - - // Only run full sync/announce if NOT shallow - if (!shallow) this.syncWithLocation(announce); - - // Trigger popstate so listeners react - window.dispatchEvent(new Event("popstate")); - } - - /** - * Match a route pattern to a path - * @param pathPattern Pattern (e.g., "/users/:id") - * @param currentPath Path to match (e.g., "/users/42") - * @returns true if matches, null if not - * Sets `this.params` with extracted dynamic parameters - */ - matchRoute(pathPattern: string, currentPath: string) { - if (!currentPath) return null; - const normalize = (p: string) => (p === "/" ? "" : p.replace(/\/+$/, "")); - const pattern = normalize(pathPattern); - const path = normalize(currentPath); - - let wildcardIndex = 0; - const regexPattern = pattern - .replace(/:([A-Za-z0-9_]+)/g, (_, key) => `(?<${key}>[^/]+)`) - .replace(/\*/g, () => { - wildcardIndex++; - return `(?.*)`; - }); - - const regex = new RegExp(`^${regexPattern}$`); - const match = path.match(regex); - - if (!match) return null; - this.params = match.groups || {}; - return true; - } - - /** Simulate prefetching a route */ - prefetch(url: string) { - if (typeof window === "undefined") return Promise.resolve(); - console.log("Prefetching route:", url); - return Promise.resolve(); - } - - /** Resolve relative href to absolute path + query */ - resolveHref(href: string) { - if (typeof window === "undefined") return href; - const url = new URL(href, window.location.origin); - return url.pathname + url.search; - } - - /** Full path with query string */ - get asPath() { - return typeof window !== "undefined" - ? window.location.pathname + window.location.search - : this.path; - } -} - -export const routerStore = new RouterStore(); - -export interface Router { - readonly path: string; - readonly asPath: string; - readonly queries: Record; - readonly params: Record; - readonly loading: boolean; - push: ( - url: string, - announce?: boolean, - shallow?: boolean, - queries?: Record - ) => void; - replace: ( - url: string, - announce?: boolean, - shallow?: boolean, - queries?: Record - ) => void; - back: (announce?: boolean) => void; - match: (pattern: string, currentPath: string) => true | null; - on: (type: "start" | "complete" | "change", cb: (path: string) => void) => () => void; - beforePopState: (cb: (url: string) => boolean | void) => () => void; - prefetch: (url: string) => Promise; - resolveHref: (url: string) => string; - isActive: (url: string) => boolean; - readonly host: string; - readonly hostname: string; - readonly protocol: string; - readonly origin: string; - readonly port: string; - readonly href: string; - readonly hash: string; - readonly search: string; -} - -/** - * useRouter hook - * - * Provides full client-side navigation and routing capabilities. - */ -export function useRouter(): Router { - return { - get path() { return routerStore.path; }, - get asPath() { return routerStore.asPath; }, - get queries() { return routerStore.queries; }, - get params() { return routerStore.params; }, - get loading() { return routerStore.loading; }, - push: (...args) => routerStore.push(...args), - replace: (...args) => routerStore.replace(...args), - back: (announce = true) => routerStore.back(announce), - match: (pattern, currentPath) => routerStore.matchRoute(pattern, currentPath), - on: (type, cb) => routerStore.on(type, cb), - beforePopState: (cb) => routerStore.beforePopState(cb), - prefetch: (url) => routerStore.prefetch(url), - resolveHref: (url) => routerStore.resolveHref(url), - isActive: (url) => url === routerStore.path, - get host() { return typeof window !== "undefined" ? window.location.host : ""; }, - get hostname() { return typeof window !== "undefined" ? window.location.hostname : ""; }, - get protocol() { return typeof window !== "undefined" ? window.location.protocol : ""; }, - get origin() { return typeof window !== "undefined" ? window.location.origin : ""; }, - get port() { return typeof window !== "undefined" ? window.location.port : ""; }, - get href() { return typeof window !== "undefined" ? window.location.href : ""; }, - get hash() { return typeof window !== "undefined" ? window.location.hash : ""; }, - get search() { return typeof window !== "undefined" ? window.location.search : ""; } - }; -} - - -/** - * useRouter hook - * - * Provides full client-side navigation and routing capabilities. - * - * @example - * ```ts - * import { useRouter } from "rivra/router"; - * - * const router = useRouter(); - * router.push("/users/42?tab=posts"); - * - * router.replace("/users/42?tab=profile", true, true); - * router.prefetch("/about"); - * - * router.on("start", (path) => console.log("Start navigating to:", path)); - * router.on("change", (path) => console.log("Route changed:", path)); - * router.on("complete", (path) => console.log("Navigation complete:", path)); - * - * router.beforePopState((url) => { - * if (url === "/protected") return false; // Block navigation - * }); - * ``` - */ - - -export default useRouter; - -// -------------------- Example Usage -------------------- - -// const router = useRouter(); - -// // Listen to navigation events -// router.on("start", (path) => console.log("Start navigating to:", path)); -// router.on("change", (path) => console.log("Route changed to:", path)); -// router.on("complete", (path) => console.log("Navigation complete:", path)); -// router.isActive("/about") -// Guard back navigation -// router.beforePopState((url) => { -// if (url === "/protected") { -// console.log("Navigation blocked:", url); -// return false; // Cancel navigation -// } -// }); - -// Navigate to a new route -//router.push("/users/42?tab=posts"); - -// Replace URL shallowly (no full sync)// -//router.replace("/users/42?tab=profile", true, true); - -// Prefetch a route -// router.prefetch("/about"); - -// Resolve href -// console.log("Resolved href:", router.resolveHref("/contact?ref=home")); - -// Access reactive properties -// console.log("Current path:", router.path); -// console.log("Query params:", router.queries); -// console.log("Dynamic params:", router.params); -// console.log("Full URL:", router.asPath); - -// Access full URL info -// console.log(router.host); -// console.log(router.hostname); -// console.log(router.origin); -// console.log(router.protocol); -// console.log(router.port); -// console.log(router.href); -// console.log(router.search); -// console.log(router.hash); +import type { Component } from "ripple"; + +type RouterListener = (path: string) => void; +type BeforePopCallback = (url: string) => boolean | void; + +/** + * RouterStore + * ----------- + * Client-side router for Ripple apps, inspired by Next.js router. + * Features: + * - Path tracking + * - Query parameters + * - Dynamic route parameters (params) + * - Loading state + * - Router events: start, complete, change + * - Shallow routing + * - Back navigation guards + * - Prefetching of routes + * + * Uses HTML5 History API to avoid full page reloads. + */ +class RouterStore { + /** Current pathname (e.g., "/users/42") */ + path: string = ""; + + /** Query parameters (e.g., { tab: "posts" }) */ + queries: Record = {}; + + /** Dynamic route parameters extracted from matched patterns (e.g., { id: "42" }) */ + params: Record = {}; + + /** Loading state, true when navigation starts */ + loading = false; + + /** Internal event listeners: start, complete, change */ + private listeners: { + start: RouterListener[]; + complete: RouterListener[]; + change: RouterListener[]; + } = { start: [], complete: [], change: [] }; + + /** Callbacks that can block back/forward navigation */ + private beforePop: BeforePopCallback[] = []; + + constructor() { + if (typeof window !== "undefined") { + this.syncWithLocation(); + window.addEventListener("popstate", () => this.handlePopState()); + } + } + + /** Handle browser back/forward events */ + private handlePopState() { + if (typeof window === "undefined") return; + for (const fn of this.beforePop) { + const result = fn(this.asPath); + if (result === false) return; // Cancel navigation + } + this.syncWithLocation(); + } + + /** + * Sync router state with window.location + * @param announce Whether to emit events (default: true) + */ + private syncWithLocation(announce = true) { + if (typeof window === "undefined") return; + this.path = window.location.pathname; + this.queries = Object.fromEntries(new URLSearchParams(window.location.search).entries()); + this.params = {}; + this.loading = false; + + if (announce) { + this.emit("change", this.path); + this.emit("complete", this.path); + } + } + + /** Emit event to all listeners */ + private emit(type: keyof RouterStore["listeners"], path: string) { + for (const cb of this.listeners[type]) cb(path); + } + + /** + * Subscribe to router events + * @param type "start" | "complete" | "change" + * @param cb Callback called with path + * @returns Unsubscribe function + */ + on(type: keyof RouterStore["listeners"], cb: RouterListener) { + this.listeners[type].push(cb); + return () => { + this.listeners[type] = this.listeners[type].filter(fn => fn !== cb); + }; + } + + /** Builds a complete URL from path + query object */ + private buildUrl(path: string, queries?: Record): string { + if (!queries || Object.keys(queries).length === 0) return path; + const params = new URLSearchParams(); + for (const [key, value] of Object.entries(queries)) { + if (value !== undefined && value !== null) params.set(key, String(value)); + } + const query = params.toString(); + return query ? `${path}?${query}` : path; + } + + /** + * Register a callback before back/forward navigation + * Return false to cancel navigation + */ + beforePopState(cb: BeforePopCallback) { + this.beforePop.push(cb); + return () => { + this.beforePop = this.beforePop.filter(f => f !== cb); + }; + } + + /** + * Navigate to a new URL (pushState) + * @param url Target URL + * @param announce Emit events? (default: true) + * @param shallow Update URL without full sync? (default: false) + */ + push( + url: string, + announce = true, + shallow = false, + queries?: Record, + ) { + const fullUrl = this.buildUrl(url, queries); + this.navigate(fullUrl, "push", announce, shallow); + } + + /** + * Replace current URL (replaceState) + * @param url Target URL + * @param announce Emit events? (default: true) + * @param shallow Update URL without full sync? (default: false) + */ + replace( + url: string, + announce = true, + shallow = false, + queries?: Record, + ) { + const fullUrl = this.buildUrl(url, queries); + this.navigate(fullUrl, "replace", announce, shallow); + } + + /** Go back in browser history */ + back(announce = true) { + if (typeof window === "undefined") return; + this.loading = true; + if (announce) this.emit("start", this.path); + history.back(); + window.dispatchEvent(new Event("popstate")); + } + + /** Internal navigation helper */ + private navigate(url: string, method: "push" | "replace", announce = true, shallow = false) { + if (typeof window === "undefined") return; + + this.loading = true; + if (announce) this.emit("start", url); + + const [path, search = ""] = url.split("?"); + + // Always update internal state + this.path = path; + this.queries = Object.fromEntries(new URLSearchParams(search).entries()); + + // Update browser history + if (method === "push") window.history.pushState({}, "", url); + else window.history.replaceState({}, "", url); + + // Only run full sync/announce if NOT shallow + if (!shallow) this.syncWithLocation(announce); + + // Trigger popstate so listeners react + window.dispatchEvent(new Event("popstate")); + } + + /** + * Match a route pattern to a path + * @param pathPattern Pattern (e.g., "/users/:id") + * @param currentPath Path to match (e.g., "/users/42") + * @returns true if matches, null if not + * Sets `this.params` with extracted dynamic parameters + */ + matchRoute(pathPattern: string, currentPath: string) { + if (!currentPath) return null; + const normalize = (p: string) => (p === "/" ? "" : p.replace(/\/+$/, "")); + const pattern = normalize(pathPattern); + const path = normalize(currentPath); + + let wildcardIndex = 0; + const regexPattern = pattern + .replace(/:([A-Za-z0-9_]+)/g, (_, key) => `(?<${key}>[^/]+)`) + .replace(/\*/g, () => { + wildcardIndex++; + return `(?.*)`; + }); + + const regex = new RegExp(`^${regexPattern}$`); + const match = path.match(regex); + + if (!match) return null; + this.params = match.groups || {}; + return true; + } + + /** Simulate prefetching a route */ + prefetch(url: string) { + if (typeof window === "undefined") return Promise.resolve(); + console.log("Prefetching route:", url); + return Promise.resolve(); + } + + /** Resolve relative href to absolute path + query */ + resolveHref(href: string) { + if (typeof window === "undefined") return href; + const url = new URL(href, window.location.origin); + return url.pathname + url.search; + } + + /** Full path with query string */ + get asPath() { + return typeof window !== "undefined" + ? window.location.pathname + window.location.search + : this.path; + } +} + +export const routerStore = new RouterStore(); + +export interface Router { + readonly path: string; + readonly asPath: string; + readonly queries: Record; + readonly params: Record; + readonly loading: boolean; + push: ( + url: string, + announce?: boolean, + shallow?: boolean, + queries?: Record + ) => void; + replace: ( + url: string, + announce?: boolean, + shallow?: boolean, + queries?: Record + ) => void; + back: (announce?: boolean) => void; + match: (pattern: string, currentPath: string) => true | null; + on: (type: "start" | "complete" | "change", cb: (path: string) => void) => () => void; + beforePopState: (cb: (url: string) => boolean | void) => () => void; + prefetch: (url: string) => Promise; + resolveHref: (url: string) => string; + isActive: (url: string) => boolean; + readonly host: string; + readonly hostname: string; + readonly protocol: string; + readonly origin: string; + readonly port: string; + readonly href: string; + readonly hash: string; + readonly search: string; +} + +/** + * useRouter hook + * + * Provides full client-side navigation and routing capabilities. + */ +export function useRouter(): Router { + return { + get path() { return routerStore.path; }, + get asPath() { return routerStore.asPath; }, + get queries() { return routerStore.queries; }, + get params() { return routerStore.params; }, + get loading() { return routerStore.loading; }, + push: (...args) => routerStore.push(...args), + replace: (...args) => routerStore.replace(...args), + back: (announce = true) => routerStore.back(announce), + match: (pattern, currentPath) => routerStore.matchRoute(pattern, currentPath), + on: (type, cb) => routerStore.on(type, cb), + beforePopState: (cb) => routerStore.beforePopState(cb), + prefetch: (url) => routerStore.prefetch(url), + resolveHref: (url) => routerStore.resolveHref(url), + isActive: (url) => url === routerStore.path, + get host() { return typeof window !== "undefined" ? window.location.host : ""; }, + get hostname() { return typeof window !== "undefined" ? window.location.hostname : ""; }, + get protocol() { return typeof window !== "undefined" ? window.location.protocol : ""; }, + get origin() { return typeof window !== "undefined" ? window.location.origin : ""; }, + get port() { return typeof window !== "undefined" ? window.location.port : ""; }, + get href() { return typeof window !== "undefined" ? window.location.href : ""; }, + get hash() { return typeof window !== "undefined" ? window.location.hash : ""; }, + get search() { return typeof window !== "undefined" ? window.location.search : ""; } + }; +} + + +/** + * useRouter hook + * + * Provides full client-side navigation and routing capabilities. + * + * @example + * ```ts + * import { useRouter } from "rivra/router"; + * + * const router = useRouter(); + * router.push("/users/42?tab=posts"); + * + * router.replace("/users/42?tab=profile", true, true); + * router.prefetch("/about"); + * + * router.on("start", (path) => console.log("Start navigating to:", path)); + * router.on("change", (path) => console.log("Route changed:", path)); + * router.on("complete", (path) => console.log("Navigation complete:", path)); + * + * router.beforePopState((url) => { + * if (url === "/protected") return false; // Block navigation + * }); + * ``` + */ + + +export default useRouter; + +// -------------------- Example Usage -------------------- + +// const router = useRouter(); + +// // Listen to navigation events +// router.on("start", (path) => console.log("Start navigating to:", path)); +// router.on("change", (path) => console.log("Route changed to:", path)); +// router.on("complete", (path) => console.log("Navigation complete:", path)); +// router.isActive("/about") +// Guard back navigation +// router.beforePopState((url) => { +// if (url === "/protected") { +// console.log("Navigation blocked:", url); +// return false; // Cancel navigation +// } +// }); + +// Navigate to a new route +//router.push("/users/42?tab=posts"); + +// Replace URL shallowly (no full sync)// +//router.replace("/users/42?tab=profile", true, true); + +// Prefetch a route +// router.prefetch("/about"); + +// Resolve href +// console.log("Resolved href:", router.resolveHref("/contact?ref=home")); + +// Access reactive properties +// console.log("Current path:", router.path); +// console.log("Query params:", router.queries); +// console.log("Dynamic params:", router.params); +// console.log("Full URL:", router.asPath); + +// Access full URL info +// console.log(router.host); +// console.log(router.hostname); +// console.log(router.origin); +// console.log(router.protocol); +// console.log(router.port); +// console.log(router.href); +// console.log(router.search); +// console.log(router.hash); diff --git a/src/vite-plugin-add-alias.ts b/src/vite-plugin-add-alias.ts index 8767a21..a1ae971 100644 --- a/src/vite-plugin-add-alias.ts +++ b/src/vite-plugin-add-alias.ts @@ -1,20 +1,20 @@ -import { Plugin } from 'vite'; - -export function addAliasPlugin(): Plugin { - return { - name: 'vite-plugin-add-alias', - config(config) { - // Ensure that the alias section exists - config.resolve = config.resolve || {}; - config.resolve.alias = config.resolve.alias || {}; - - // Dynamically resolve the src directory - // In this case, we avoid using `path` entirely to prevent issues in the browser - const srcPath = `${__dirname}/src`; // Relative path for bundling - - // Add the alias for `@src` - - return config; - }, - }; -} +import { Plugin } from 'vite'; + +export function addAliasPlugin(): Plugin { + return { + name: 'vite-plugin-add-alias', + config(config) { + // Ensure that the alias section exists + config.resolve = config.resolve || {}; + config.resolve.alias = config.resolve.alias || {}; + + // Dynamically resolve the src directory + // In this case, we avoid using `path` entirely to prevent issues in the browser + const srcPath = `${__dirname}/src`; // Relative path for bundling + + // Add the alias for `@src` + + return config; + }, + }; +} diff --git a/test/some.js b/test/some.js index 9d0c393..a6015b1 100644 --- a/test/some.js +++ b/test/some.js @@ -1,98 +1,98 @@ -import Fastify from "fastify"; -import fastifyMiddie from "@fastify/middie"; -import { createServer as createViteServer } from "vite"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; -import { executeServerFunction } from "ripple/server"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const projectRoot = process.cwd(); - -const rpc_modules = new Map(); - -async function getRequestBody(req) { - return new Promise((resolve, reject) => { - let data = ''; - req.on('data', (chunk) => { - data += chunk; - if (data.length > 1e6) { - req.destroy(); - reject(new Error('Request body too large')); - } - }); - req.on('end', () => resolve(data)); - req.on('error', reject); - }); -} - -export async function StartServer() { - const app = Fastify({ logger: true }); - - await app.register(fastifyMiddie); - - const vite = await createViteServer({ - root: projectRoot, - server: { middlewareMode: true }, - appType: 'custom', - }); - - // Use Vite as middleware - app.use((req, res, next) => { - vite.middlewares(req, res, next); - }); - - // SSR route - app.all("/*", async (req, reply) => { - try { - // Handle RPC requests - if (req.raw.url?.startsWith('/_$_ripple_rpc_$_/')) { - const hash = req.raw.url.slice('/_$_ripple_rpc_$_/'.length); - const module_info = rpc_modules.get(hash); - - if (!module_info) { - reply.status(500).send('RPC module not found'); - return; - } - - const file_path = module_info[0]; - const func_name = module_info[1]; - const { _$_server_$_: server } = await vite.ssrLoadModule(file_path); - const rpc_arguments = await getRequestBody(req.raw); - const result = await executeServerFunction(server[func_name], rpc_arguments); - reply.type("application/json").send(result); - return; - } - - // SSR HTML - let template = fs.readFileSync(path.resolve(projectRoot, 'index.html'), 'utf-8'); - template = await vite.transformIndexHtml(req.raw.url, template); - - const { render, get_css_for_hashes } = await vite.ssrLoadModule('ripple/server'); - const { App } = await vite.ssrLoadModule('/src/App.ripple'); - const { head, body, css } = await render(App); - - let css_tags = ''; - if (css.size > 0) { - const css_content = get_css_for_hashes(css); - if (css_content) css_tags = ``; - } - - const html = template - .replace('', head + css_tags) - .replace('', body); - - reply.type('text/html').send(html); - - } catch (err) { - console.error('SSR Error:', err); - // reply.status(500).send(String(err.stack || err)); - } - }); - - const port = 3000; - app.listen({ port }, () => console.log(`🚀 Fastify + Vite SSR running at http://localhost:${port}`)); -} - -export default StartServer; +import Fastify from "fastify"; +import fastifyMiddie from "@fastify/middie"; +import { createServer as createViteServer } from "vite"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { executeServerFunction } from "ripple/server"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = process.cwd(); + +const rpc_modules = new Map(); + +async function getRequestBody(req) { + return new Promise((resolve, reject) => { + let data = ''; + req.on('data', (chunk) => { + data += chunk; + if (data.length > 1e6) { + req.destroy(); + reject(new Error('Request body too large')); + } + }); + req.on('end', () => resolve(data)); + req.on('error', reject); + }); +} + +export async function StartServer() { + const app = Fastify({ logger: true }); + + await app.register(fastifyMiddie); + + const vite = await createViteServer({ + root: projectRoot, + server: { middlewareMode: true }, + appType: 'custom', + }); + + // Use Vite as middleware + app.use((req, res, next) => { + vite.middlewares(req, res, next); + }); + + // SSR route + app.all("/*", async (req, reply) => { + try { + // Handle RPC requests + if (req.raw.url?.startsWith('/_$_ripple_rpc_$_/')) { + const hash = req.raw.url.slice('/_$_ripple_rpc_$_/'.length); + const module_info = rpc_modules.get(hash); + + if (!module_info) { + reply.status(500).send('RPC module not found'); + return; + } + + const file_path = module_info[0]; + const func_name = module_info[1]; + const { _$_server_$_: server } = await vite.ssrLoadModule(file_path); + const rpc_arguments = await getRequestBody(req.raw); + const result = await executeServerFunction(server[func_name], rpc_arguments); + reply.type("application/json").send(result); + return; + } + + // SSR HTML + let template = fs.readFileSync(path.resolve(projectRoot, 'index.html'), 'utf-8'); + template = await vite.transformIndexHtml(req.raw.url, template); + + const { render, get_css_for_hashes } = await vite.ssrLoadModule('ripple/server'); + const { App } = await vite.ssrLoadModule('/src/App.ripple'); + const { head, body, css } = await render(App); + + let css_tags = ''; + if (css.size > 0) { + const css_content = get_css_for_hashes(css); + if (css_content) css_tags = ``; + } + + const html = template + .replace('', head + css_tags) + .replace('', body); + + reply.type('text/html').send(html); + + } catch (err) { + console.error('SSR Error:', err); + // reply.status(500).send(String(err.stack || err)); + } + }); + + const port = 3000; + app.listen({ port }, () => console.log(`🚀 Fastify + Vite SSR running at http://localhost:${port}`)); +} + +export default StartServer; StartServer() \ No newline at end of file diff --git a/tests_.ts b/tests_.ts index adb5fe0..dfbd5e9 100644 --- a/tests_.ts +++ b/tests_.ts @@ -1,98 +1,98 @@ -import Fastify from "fastify"; -import fastifyMiddie from "@fastify/middie"; -import { createServer as createViteServer } from "vite"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; -import { executeServerFunction } from "ripple/server"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const projectRoot = process.cwd(); - -const rpc_modules = new Map(); - -async function getRequestBody(req: any) { - return new Promise((resolve, reject) => { - let data = ''; - req.on('data', (chunk: any) => { - data += chunk; - if (data.length > 1e6) { - req.destroy(); - reject(new Error('Request body too large')); - } - }); - req.on('end', () => resolve(data)); - req.on('error', reject); - }); -} - -export async function StartServer() { - const app = Fastify({ logger: true }); - - await app.register(fastifyMiddie); - - const vite = await createViteServer({ - root: projectRoot, - server: { middlewareMode: true }, - appType: 'custom', - }); - - // Use Vite as middleware - app.use((req, res, next) => { - vite.middlewares(req, res, next); - }); - - // SSR route - app.all("/*", async (req, reply) => { - try { - // Handle RPC requests - if (req.raw.url?.startsWith('/_$_ripple_rpc_$_/')) { - const hash = req.raw.url.slice('/_$_ripple_rpc_$_/'.length); - const module_info = rpc_modules.get(hash); - - if (!module_info) { - reply.status(500).send('RPC module not found'); - return; - } - - const file_path = module_info[0]; - const func_name = module_info[1]; - const { _$_server_$_: server } = await vite.ssrLoadModule(file_path); - const rpc_arguments = await getRequestBody(req.raw); - const result = await executeServerFunction(server[func_name], rpc_arguments); - reply.type("application/json").send(result); - return; - } - - // SSR HTML - let template = fs.readFileSync(path.resolve(projectRoot, 'index.html'), 'utf-8'); - template = await vite.transformIndexHtml(req.raw.url!, template); - - const { render, get_css_for_hashes } = await vite.ssrLoadModule('ripple/server'); - const { App } = await vite.ssrLoadModule('/src/App.ripple'); - const { head, body, css } = await render(App); - - let css_tags = ''; - if (css.size > 0) { - const css_content = get_css_for_hashes(css); - if (css_content) css_tags = ``; - } - - const html = template - .replace('', head + css_tags) - .replace('', body); - - reply.type('text/html').send(html); - - } catch (err) { - console.error('SSR Error:', err); - // reply.status(500).send(String(err.stack || err)); - } - }); - - const port = 3000; - app.listen({ port }, () => console.log(`🚀 Fastify + Vite SSR running at http://localhost:${port}`)); -} - -export default StartServer; +import Fastify from "fastify"; +import fastifyMiddie from "@fastify/middie"; +import { createServer as createViteServer } from "vite"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { executeServerFunction } from "ripple/server"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = process.cwd(); + +const rpc_modules = new Map(); + +async function getRequestBody(req: any) { + return new Promise((resolve, reject) => { + let data = ''; + req.on('data', (chunk: any) => { + data += chunk; + if (data.length > 1e6) { + req.destroy(); + reject(new Error('Request body too large')); + } + }); + req.on('end', () => resolve(data)); + req.on('error', reject); + }); +} + +export async function StartServer() { + const app = Fastify({ logger: true }); + + await app.register(fastifyMiddie); + + const vite = await createViteServer({ + root: projectRoot, + server: { middlewareMode: true }, + appType: 'custom', + }); + + // Use Vite as middleware + app.use((req, res, next) => { + vite.middlewares(req, res, next); + }); + + // SSR route + app.all("/*", async (req, reply) => { + try { + // Handle RPC requests + if (req.raw.url?.startsWith('/_$_ripple_rpc_$_/')) { + const hash = req.raw.url.slice('/_$_ripple_rpc_$_/'.length); + const module_info = rpc_modules.get(hash); + + if (!module_info) { + reply.status(500).send('RPC module not found'); + return; + } + + const file_path = module_info[0]; + const func_name = module_info[1]; + const { _$_server_$_: server } = await vite.ssrLoadModule(file_path); + const rpc_arguments = await getRequestBody(req.raw); + const result = await executeServerFunction(server[func_name], rpc_arguments); + reply.type("application/json").send(result); + return; + } + + // SSR HTML + let template = fs.readFileSync(path.resolve(projectRoot, 'index.html'), 'utf-8'); + template = await vite.transformIndexHtml(req.raw.url!, template); + + const { render, get_css_for_hashes } = await vite.ssrLoadModule('ripple/server'); + const { App } = await vite.ssrLoadModule('/src/App.ripple'); + const { head, body, css } = await render(App); + + let css_tags = ''; + if (css.size > 0) { + const css_content = get_css_for_hashes(css); + if (css_content) css_tags = ``; + } + + const html = template + .replace('', head + css_tags) + .replace('', body); + + reply.type('text/html').send(html); + + } catch (err) { + console.error('SSR Error:', err); + // reply.status(500).send(String(err.stack || err)); + } + }); + + const port = 3000; + app.listen({ port }, () => console.log(`🚀 Fastify + Vite SSR running at http://localhost:${port}`)); +} + +export default StartServer; StartServer() \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 82c75c4..cfc372d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,28 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "rootDir": ".", // keep root as project root - "outDir": "dist", - "declaration": true, - "declarationDir": "dist", // all .d.ts files go to dist - "types": ["node", "./ripple-env.d.ts"], - "jsx": "preserve", - "jsxImportSource": "ripple" - }, - "include": [ - "packages/**/*.ts", - "packages/**/*.d.ts", - "src", - "**/*.ripple", - "cli.ts", - "ripple-env.d.ts", - "src/router/index.d.ts" - ] -} +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "rootDir": ".", // keep root as project root + "outDir": "dist", + "declaration": true, + "declarationDir": "dist", // all .d.ts files go to dist + "types": ["node", "./ripple-env.d.ts"], + "jsx": "preserve", + "jsxImportSource": "ripple" + }, + "include": [ + "packages/**/*.ts", + "packages/**/*.d.ts", + "src", + "**/*.ripple", + "cli.ts", + "ripple-env.d.ts", + "src/cli.ts", + "src/router/index.d.ts" + ] +} diff --git a/vite-plugin-ripple.d.ts b/vite-plugin-ripple.d.ts index 30e3a93..6a0d594 100644 --- a/vite-plugin-ripple.d.ts +++ b/vite-plugin-ripple.d.ts @@ -1,5 +1,5 @@ -declare module "vite-plugin-ripple" { - import { Plugin } from "vite"; - - export function ripple(): Plugin; -} +declare module "vite-plugin-ripple" { + import { Plugin } from "vite"; + + export function ripple(): Plugin; +} diff --git a/vite.config.ts b/vite.config.ts index 313b9bb..27f494c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,21 +1,22 @@ -import { defineConfig, type PluginOption } from 'vite'; -import { ripple } from 'vite-plugin-ripple'; -import path from 'path'; - -export default defineConfig(({ mode }) => ({ - plugins: [...(ripple() as PluginOption[])], - build: { - target: 'esnext', - minify: false, - sourcemap: mode !== 'production', - lib: { - entry: 'src/index.ts', - formats: ['es'], - fileName: 'index', - }, - rollupOptions: { - external: ['ripple', "node_modules", /^ripple\//], - }, - }, - root: path.resolve(__dirname), -})); +import { defineConfig, type PluginOption } from 'vite'; +import { ripple } from 'vite-plugin-ripple'; +import path from 'path'; + +export default defineConfig(({ mode }) => ({ + plugins: [...(ripple() as PluginOption[])], + build: { + target: 'esnext', + minify: false, + sourcemap: mode !== 'production', + lib: { + entry: 'src/index.ts', + formats: ['es'], + fileName: 'index', + }, + rollupOptions: { + external: ['ripple', "node_modules", /^ripple\//], + }, + }, + root: path.resolve(__dirname), +})); +// "build": "vite build && tsc --outDir dist --declaration --emitDeclarationOnly false && cp src/router/index.d.ts dist/src/router/index.d.ts", \ No newline at end of file