# Veap Documentation > A modular ecosystem of MIT-licensed TypeScript packages for Next.js 16 and React 19: kernel with a service container, an Active Record ORM with migrations, auth with sessions and RBAC, plugins with a virtual router, i18n, mail, settings, storage, shared UI and four ORM trait packages. 15 official plugins, a create-veap scaffolder, and everything pre-1.0 in Early Access. Canonical site: https://veap.pl/docs Complete developer documentation in the order defined by the meta.json navigation files in docs/developer-docs (60 pages). Every page is also available individually at the URL listed below its title. ## Table of contents - [Veap documentation](https://veap.pl/docs) - [Installation and scaffolding](https://veap.pl/docs/getting-started/installation) - [Your first application](https://veap.pl/docs/getting-started/first-application) - [Next.js integration](https://veap.pl/docs/getting-started/nextjs-integration) - [Project structure and configuration](https://veap.pl/docs/getting-started/project-structure) - [IoC container and service providers](https://veap.pl/docs/fundamentals/ioc-and-providers) - [Configuration and error handling](https://veap.pl/docs/fundamentals/configuration-and-errors) - [Events, caching, and logging](https://veap.pl/docs/fundamentals/events-and-cache) - [Console and CLI commands (@veap/kernel/cli)](https://veap.pl/docs/fundamentals/cli) - [Core kernel (@veap/kernel)](https://veap.pl/docs/packages/kernel) - [Database and ORM (@veap/database)](https://veap.pl/docs/packages/database) - [Models, queries, and relationships](https://veap.pl/docs/packages/database/models-and-relations) - [Migrations, transactions, and seeders](https://veap.pl/docs/packages/database/migrations-and-transactions) - [Seeders and model factories (@veap/database)](https://veap.pl/docs/packages/database/seeders-and-factories) - [Polymorphic ORM traits](https://veap.pl/docs/packages/orm-traits) - [Polymorphic translations (@veap/translatable)](https://veap.pl/docs/packages/orm-traits/translatable) - [Polymorphic tagging (@veap/taggable)](https://veap.pl/docs/packages/orm-traits/taggable) - [Hierarchical categories (@veap/categorizable)](https://veap.pl/docs/packages/orm-traits/categorizable) - [Threaded comments (@veap/commentable)](https://veap.pl/docs/packages/orm-traits/commentable) - [Authentication (@veap/auth)](https://veap.pl/docs/packages/auth) - [Server Actions and RBAC](https://veap.pl/docs/packages/auth/actions-and-rbac) - [Verification, cryptography, and 2FA hooks](https://veap.pl/docs/packages/auth/verification-and-security) - [Modular plugin system (@veap/plugins)](https://veap.pl/docs/packages/plugins) - [Virtual routing, extension points, and widgets](https://veap.pl/docs/packages/plugins/routing-and-ui) - [Internationalization (@veap/intl)](https://veap.pl/docs/packages/intl) - [Formatting and components](https://veap.pl/docs/packages/intl/formatting-and-components) - [Email delivery (@veap/mail)](https://veap.pl/docs/packages/mail) - [Transports and custom drivers](https://veap.pl/docs/packages/mail/transports-and-drivers) - [Application settings (@veap/settings)](https://veap.pl/docs/packages/settings) - [Reading, writing, and caching settings](https://veap.pl/docs/packages/settings/reading-and-caching) - [File storage (@veap/storage)](https://veap.pl/docs/packages/storage) - [Uploading files and custom cloud drivers](https://veap.pl/docs/packages/storage/uploads-and-drivers) - [UI component library (@veap/ui)](https://veap.pl/docs/packages/ui) - [Using UI components](https://veap.pl/docs/packages/ui/components) - [Project scaffolding (create-veap)](https://veap.pl/docs/packages/create-veap) - [CLI options and template structure](https://veap.pl/docs/packages/create-veap/templates-and-options) - [Testing Veap applications](https://veap.pl/docs/guides/testing) - [Authoring and customizing plugins](https://veap.pl/docs/guides/custom-plugins) - [Deployment and production readiness](https://veap.pl/docs/guides/deployment-and-production) - [Background tasks, queues, and distributed cache](https://veap.pl/docs/guides/background-jobs-and-cache) - [Troubleshooting and frequently asked questions](https://veap.pl/docs/guides/troubleshooting) - [Building a full-stack multilingual blog CMS](https://veap.pl/docs/tutorials/fullstack-blog-cms) - [Building REST APIs and microservices with Veap](https://veap.pl/docs/tutorials/rest-api-microservices) - [Authoring a custom Veap plugin](https://veap.pl/docs/tutorials/authoring-a-plugin) - [@veap/kernel API reference](https://veap.pl/docs/reference/packages/kernel) - [@veap/database API reference](https://veap.pl/docs/reference/packages/database) - [@veap/auth API reference](https://veap.pl/docs/reference/packages/auth) - [@veap/plugins API reference](https://veap.pl/docs/reference/packages/plugins) - [@veap/intl API reference](https://veap.pl/docs/reference/packages/intl) - [@veap/mail API reference](https://veap.pl/docs/reference/packages/mail) - [@veap/settings API reference](https://veap.pl/docs/reference/packages/settings) - [@veap/storage API reference](https://veap.pl/docs/reference/packages/storage) - [@veap/ui API reference](https://veap.pl/docs/reference/packages/ui) - [@veap/translatable API reference](https://veap.pl/docs/reference/packages/translatable) - [@veap/taggable API reference](https://veap.pl/docs/reference/packages/taggable) - [@veap/categorizable API reference](https://veap.pl/docs/reference/packages/categorizable) - [@veap/commentable API reference](https://veap.pl/docs/reference/packages/commentable) - [create-veap CLI reference](https://veap.pl/docs/reference/packages/create-veap) - [Official plugins catalog](https://veap.pl/docs/reference/plugins/official-plugins) - [Plugin CLI and generated files reference](https://veap.pl/docs/reference/plugins/cli) --- # Veap documentation URL: https://veap.pl/docs Veap is a modular, backend-focused TypeScript framework built for **Next.js** (App Router, React Server Components, and Server Actions). Out of the box, `@veap/kernel` binds default Next.js adapters (`NextCookieStore` and `NextRequestContext`) to its HTTP transport ports (`COOKIE_STORE` and `REQUEST_CONTEXT`). Because all other Veap packages communicate with the HTTP layer strictly through those two IoC ports rather than importing Next.js directly, Veap works with zero transport configuration in Next.js while remaining easy to run in other frameworks (such as **Hono**, **Express**, or standalone Node.js/Bun services) by overriding those ports with custom adapters. ## How Veap works Every Veap application starts from a central composition root (`lib/veap.ts`) powered by `@veap/kernel`. When `app.bootstrap()` runs, it automatically boots `KernelServiceProvider` (which sets up the default Next.js `COOKIE_STORE` and `REQUEST_CONTEXT` adapters, `ConfigService`, `EventBus`, `ConsoleLogger`, and `MemoryCacheProvider`) followed by the feature `ServiceProvider` classes your application registers: ```ts import { Application } from "@veap/kernel"; import { DatabaseServiceProvider } from "@veap/database"; import { AuthServiceProvider } from "@veap/auth"; import { StorageServiceProvider } from "@veap/storage"; export const app = Application.configure() .use(DatabaseServiceProvider) .use(AuthServiceProvider) .use(StorageServiceProvider) .create(); ``` ## Core ecosystem packages Veap is distributed as a set of focused packages. You can adopt the full stack or install individual packages incrementally: | Package | Description | | --- | --- | | `@veap/kernel` | IoC dependency injection container, `Application` lifecycle builder, asynchronous `eventBus`, `ConfigService`, `AppError` / `Result` helpers, and the `veap` CLI runner. | | `@veap/database` | Knex-powered Active Record ORM (`Model`), fluent `QueryBuilder`, relations (`belongsTo`, `hasMany`, polymorphic `morphMany` / `morphToMany`), `AsyncLocalStorage` transactions, and schema migrations. | | `@veap/auth` | Session management, password hashing (`bcrypt`), AES-GCM encryption, RBAC roles and permissions, email verification, and Next.js Server Actions (`signIn`, `signUp`, `signOut`). | | `@veap/plugins` | Modular plugin contract (`IPlugin`), virtual router (`VeapRouter`), UI extension slots (`ExtensionPoint`), customizable widget grids (`WidgetComposer`), and CLI management (`veap add`, `veap eject`). | | `@veap/intl` | Server and client internationalization, ICU message formatting (`intl-messageformat`), and automatic locale negotiation from cookies and HTTP headers. | | `@veap/mail` | Pluggable email delivery (`sendMail`) with SMTP/Nodemailer, console development logging, and custom transport adapters (`CUSTOM_MAILER`). | | `@veap/settings` | Database-persisted key-value application settings (`SettingsService`) with automatic in-memory caching. | | `@veap/storage` | File upload and deletion router (`StorageService`) with a hardened `LocalFileProvider` and custom cloud driver contracts (`IStorageProvider`). | | `@veap/ui` | Accessible React 19 and Tailwind CSS v4 component library built on Radix UI primitives. | | `create-veap` | Project scaffolding CLI (`bun create veap`) for initializing new Next.js + Veap applications. | ## Polymorphic ORM traits In addition to the core infrastructure packages, Veap ships four reusable polymorphic traits for `@veap/database` models: | Trait package | Description | | --- | --- | | `@veap/translatable` | Stores multilingual model attributes in a single polymorphic `translations` table with proxy property access and locale-scoped queries. | | `@veap/taggable` | Attaches polymorphic, translatable tags (`tags` and `taggables` tables) to any model with query scopes (`whereTag`, `whereAnyTag`, `whereAllTags`). | | `@veap/categorizable` | Organizes models into hierarchical parent-child category trees (`categories` and `categorizables` tables) with a `` UI component. | | `@veap/commentable` | Adds threaded polymorphic comments, nested replies, and moderation statuses (`pending`, `approved`, `spam`, `rejected`, `trash`) to any model. | ## Getting started path Follow these four guides in order to set up and structure your first Veap application: 1. [Installation and scaffolding](https://veap.pl/docs/getting-started/installation) — create a new project with `create-veap` or add Veap packages to an existing Next.js application. 2. [Your first application](https://veap.pl/docs/getting-started/first-application) — build the `lib/veap.ts` composition root, configure transport adapters, and resolve services from the container. 3. [Next.js integration](https://veap.pl/docs/getting-started/nextjs-integration) — wire Veap into Next.js `instrumentation.ts`, Server Components, Server Actions, and Route Handlers while respecting server/client boundaries. 4. [Project structure and configuration](https://veap.pl/docs/getting-started/project-structure) — understand the directory layout, `veap.config.ts`, `lib/plugins.gen.ts`, and plugin ejection. ## Kernel fundamentals (`@veap/kernel`) Dive deeper into the core architectural primitives provided by `@veap/kernel`: - [IoC container and service providers](https://veap.pl/docs/fundamentals/ioc-and-providers) — dependency injection tokens, `@Injectable()` and `@Inject()`, `ServiceProvider` lifecycle hooks, and `Application.configure()`. - [Configuration and error handling](https://veap.pl/docs/fundamentals/configuration-and-errors) — `veap.config.ts`, `ConfigService` (`CONFIG_SERVICE`), domain errors with `AppError`, and Server Action `Result` helpers (`ok`, `handleActionError`). - [Events, caching, and logging](https://veap.pl/docs/fundamentals/events-and-cache) — publish-subscribe `EventBus` (`EVENT_BUS`), `CACHE_PROVIDER` (`MemoryCacheProvider`), and structured console logging (`LOGGER`). - [Console and CLI commands](https://veap.pl/docs/fundamentals/cli) — the `veap` console runner (`ConsoleApplication`), built-in commands, and custom `Command` classes (`@veap/kernel/cli`). ## Packages (`@veap/*`) Learn how to configure and use each package in the Veap ecosystem: - [`@veap/database`](https://veap.pl/docs/packages/database) — Active Record [models and relations](https://veap.pl/docs/packages/database/models-and-relations), `AsyncLocalStorage` [transactions and migrations](https://veap.pl/docs/packages/database/migrations-and-transactions), and [seeders and model factories](https://veap.pl/docs/packages/database/seeders-and-factories). - [Polymorphic ORM traits](https://veap.pl/docs/packages/orm-traits) — [Translatable](https://veap.pl/docs/packages/orm-traits/translatable) (`@veap/translatable`), [Taggable](https://veap.pl/docs/packages/orm-traits/taggable) (`@veap/taggable`), [Categorizable](https://veap.pl/docs/packages/orm-traits/categorizable) (`@veap/categorizable`), and [Commentable](https://veap.pl/docs/packages/orm-traits/commentable) (`@veap/commentable`). - [`@veap/auth`](https://veap.pl/docs/packages/auth) — Sessions, [Server Actions and RBAC](https://veap.pl/docs/packages/auth/actions-and-rbac), and [email verification, cryptography, and 2FA hooks](https://veap.pl/docs/packages/auth/verification-and-security). - [`@veap/plugins`](https://veap.pl/docs/packages/plugins) — `IPlugin` contract, [virtual routing (`VeapRouter`), extension slots, and widgets](https://veap.pl/docs/packages/plugins/routing-and-ui). - [`@veap/intl`](https://veap.pl/docs/packages/intl) — Locale negotiation and [ICU formatting in Server and Client Components](https://veap.pl/docs/packages/intl/formatting-and-components). - [`@veap/mail`](https://veap.pl/docs/packages/mail) — Transactional emails and [SMTP or custom mail drivers](https://veap.pl/docs/packages/mail/transports-and-drivers). - [`@veap/settings`](https://veap.pl/docs/packages/settings) — Persisted key-value settings and [cached reads with `SettingsService`](https://veap.pl/docs/packages/settings/reading-and-caching). - [`@veap/storage`](https://veap.pl/docs/packages/storage) — Local file storage and [Server Action uploads with custom cloud drivers](https://veap.pl/docs/packages/storage/uploads-and-drivers). - [`@veap/ui`](https://veap.pl/docs/packages/ui) — Tailwind CSS v4 tokens, `ThemeProvider`, and [accessible Radix UI components](https://veap.pl/docs/packages/ui/components). - [`create-veap`](https://veap.pl/docs/packages/create-veap) — Project scaffolding CLI and [starter template structure](https://veap.pl/docs/packages/create-veap/templates-and-options). ## Guides Explore cross-cutting recipes and production best practices: - [Testing Veap applications](https://veap.pl/docs/guides/testing) — container isolation, SQLite `:memory:` databases, `MemoryCookieStore`, and `BufferedOutput`. - [Authoring and customizing plugins](https://veap.pl/docs/guides/custom-plugins) — scaffolding workspace plugins with `veap make:plugin`, ejecting npm plugins with `veap eject`, and publishing plugins. - [Deployment and production readiness](https://veap.pl/docs/guides/deployment-and-production) — Docker containerization, Next.js `force-dynamic` boundaries, environment variables, CI migrations, and PaaS hosting. - [Background tasks, queues, and distributed cache](https://veap.pl/docs/guides/background-jobs-and-cache) — non-blocking execution with `after()`, `EventBus` topics, BullMQ queue workers, and Redis caching. - [Troubleshooting and frequently asked questions](https://veap.pl/docs/guides/troubleshooting) — solving database connection exhaustion, SQLite locks, migration recovery, CLI headers, and hydration warnings. ## Tutorials Build complete end-to-end applications step by step: - [Building a full-stack multilingual blog CMS](https://veap.pl/docs/tutorials/fullstack-blog-cms) — combine `@veap/database`, `@veap/translatable`, `@veap/taggable`, `@veap/auth`, and `@veap/plugins` into a working CMS. ## API reference Look up subpath exports, IoC tokens, class methods, and CLI commands by package: - **Core & infrastructure packages**: [`@veap/kernel`](https://veap.pl/docs/reference/packages/kernel), [`@veap/database`](https://veap.pl/docs/reference/packages/database), [`@veap/auth`](https://veap.pl/docs/reference/packages/auth), [`@veap/plugins`](https://veap.pl/docs/reference/packages/plugins), [`@veap/intl`](https://veap.pl/docs/reference/packages/intl), [`@veap/mail`](https://veap.pl/docs/reference/packages/mail), [`@veap/settings`](https://veap.pl/docs/reference/packages/settings), [`@veap/storage`](https://veap.pl/docs/reference/packages/storage), [`@veap/ui`](https://veap.pl/docs/reference/packages/ui), and [`create-veap`](https://veap.pl/docs/reference/packages/create-veap). - **Polymorphic ORM traits**: [`@veap/translatable`](https://veap.pl/docs/reference/packages/translatable), [`@veap/taggable`](https://veap.pl/docs/reference/packages/taggable), [`@veap/categorizable`](https://veap.pl/docs/reference/packages/categorizable), and [`@veap/commentable`](https://veap.pl/docs/reference/packages/commentable). - **Plugin & migration CLI tooling**: [Plugin CLI and generated files](https://veap.pl/docs/reference/plugins/cli). --- # Installation and scaffolding URL: https://veap.pl/docs/getting-started/installation Section: Getting Started You can start a new Veap project in seconds using the official `create-veap` scaffolding CLI, or install individual `@veap/*` packages into an existing Next.js application. ## Prerequisites Before installing Veap, verify that your development environment meets the following requirements: - **Node.js 24+** or **Bun 1.2+** (Bun is recommended for workspace scripts and the `veap` CLI). - **Next.js 15 or 16** with the App Router and **React 19** when building full-stack web applications. - For SQLite development (`better-sqlite3`), standard C/C++ build tools if prebuilt binaries are unavailable for your operating system. ## Scaffold a new project with `create-veap` The fastest way to create a new application is with `create-veap`. It initializes a ready-to-run Next.js App Router project preconfigured with `@veap/kernel`, Tailwind CSS v4, TypeScript, `lib/veap.ts`, and `instrumentation.ts`. Run the initializer with your preferred package manager: ```bash # Using Bun (recommended) bun create veap my-app # Using pnpm pnpm create veap my-app # Using npm npm create veap my-app ``` When run in an interactive terminal without flags, `create-veap` prompts you for the project directory name and template selection. ### CLI options Pass command-line flags to `create-veap` for non-interactive setups or custom templates: | Flag | Description | | --- | --- | | `--name ` | Specify the project name up front, skipping the interactive name prompt. | | `--template ` | Select a built-in template (for example, `01-basic`). | | `--example ` | Scaffold from a GitHub repository URL or branch subfolder instead of a built-in template. | | `--choose` | Force interactive template selection even when a default template is available. | | `--skip-install` | Generate project files without running package installation. | | `--help`, `-h` | Print usage information and available flags. | For example, to scaffold a project non-interactively using the `01-basic` template: ```bash bun create veap my-app --template 01-basic ``` Once scaffolding finishes, start the development server: ```bash cd my-app bun dev ``` ## Add Veap to an existing Next.js project If you already have a Next.js App Router project, you can install `@veap/kernel` and only the feature packages you need. ### 1. Install `@veap/kernel` Every Veap package builds on top of `@veap/kernel`: ```bash bun add @veap/kernel ``` ### 2. Install optional feature packages Install additional packages based on your application's requirements: ```bash # Database ORM + SQLite driver (or pg / mysql2) bun add @veap/database better-sqlite3 # Authentication, sessions, and RBAC bun add @veap/auth # Plugin engine, virtual router, and UI extension points bun add @veap/plugins # Internationalization, mail, settings, storage, and UI components bun add @veap/intl @veap/mail @veap/settings @veap/storage @veap/ui ``` > [!NOTE] > `@veap/database` declares database drivers (`better-sqlite3`, `pg`, `mysql2`) as optional peer dependencies. Install the driver that matches your `DB_CLIENT` configuration. ### 3. Enable TypeScript decorator metadata If you use the `@Injectable()` and `@Inject()` decorators from `@veap/kernel`, ensure `experimentalDecorators` is enabled in your `tsconfig.json`: ```json { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } } ``` ## Next steps Now that your packages are installed, configure your application composition root: - [Your first application](https://veap.pl/docs/getting-started/first-application) — create `lib/veap.ts`, register service providers, and boot the container. - [Next.js integration](https://veap.pl/docs/getting-started/nextjs-integration) — connect `lib/veap.ts` to Next.js `instrumentation.ts`, Server Components, and Server Actions. --- # Your first application URL: https://veap.pl/docs/getting-started/first-application Section: Getting Started Every Veap project defines a server-side composition root — by convention at `lib/veap.ts` — that configures the `Application` instance, registers `ServiceProvider` classes, and exports an idempotent `initializeSystem()` bootstrap helper. ## Create the composition root (`lib/veap.ts`) The `Application.configure()` builder from `@veap/kernel` lets you assemble service providers in a deterministic order. When `app.bootstrap()` runs, Veap automatically prepends `KernelServiceProvider`, which registers core kernel services along with the default Next.js transport adapters (`NextCookieStore` for `COOKIE_STORE` and `NextRequestContext` for `REQUEST_CONTEXT`). In a Next.js application, your base `lib/veap.ts` requires no manual HTTP adapter boilerplate: ```ts // lib/veap.ts import { Application } from "@veap/kernel"; export const app = Application.configure().create(); let bootstrapPromise: Promise | null = null; /** * Main system bootstrap function. * Safe for concurrent invocations across layouts, pages, and server actions. */ export async function initializeSystem(): Promise { if (!bootstrapPromise) { bootstrapPromise = app.bootstrap().catch((err) => { bootstrapPromise = null; throw err; }); } return bootstrapPromise; } ``` ### Default Next.js transport ports (`COOKIE_STORE` and `REQUEST_CONTEXT`) Veap is built for Next.js, so `KernelServiceProvider` binds two HTTP transport ports by default: - **`COOKIE_STORE` (`NextCookieStore`):** Dynamically reads and writes cookies via `cookies()` from `next/headers`. - **`REQUEST_CONTEXT` (`NextRequestContext`):** Reads request headers via `headers()` from `next/headers` and triggers redirects via `redirect()` from `next/navigation`. Because feature packages like `@veap/auth` and `@veap/intl` resolve `COOKIE_STORE` and `REQUEST_CONTEXT` from the container instead of importing `next/*` directly, Next.js works out of the box, and you can run the exact same packages in other frameworks (such as Hono or Express) or in unit tests (`MemoryCookieStore`, `MemoryRequestContext`) by overriding those two tokens in a custom `ServiceProvider`. ## Add feature providers As your application grows, chain additional service providers onto `Application.configure()`. You can pass either a `ServiceProvider` class constructor or a factory function `(container) => new Provider(container, options)` when passing configuration options: ```ts // lib/veap.ts import { AuthServiceProvider } from "@veap/auth"; import { DatabaseServiceProvider, MigrationServiceProvider, } from "@veap/database"; import { IntlServiceProvider } from "@veap/intl/server"; import { Application } from "@veap/kernel"; import { MailServiceProvider } from "@veap/mail"; import { SettingsServiceProvider } from "@veap/settings"; import { StorageServiceProvider } from "@veap/storage"; export const app = Application.configure() .use(DatabaseServiceProvider) .use(MigrationServiceProvider) .use((c) => new AuthServiceProvider(c, { autoMigrate: true })) .use((c) => new SettingsServiceProvider(c, { autoMigrate: true })) .use(StorageServiceProvider) .use(MailServiceProvider) .use(IntlServiceProvider) .create(); ``` ## Understand the two-phase bootstrap lifecycle When you call `await app.bootstrap()`, the kernel executes two sequential phases across all registered providers: 1. **Registration phase (`register()`):** Calls `provider.register()` synchronously on every provider in registration order. Providers bind tokens, factories, and classes into the IoC container without performing I/O or resolving other services. 2. **Boot phase (`boot()`):** Calls `await provider.boot()` asynchronously on every provider in registration order. Because every token is already registered, `boot()` hooks can safely resolve dependencies from the container, run database migrations, and subscribe to event bus topics. > [!IMPORTANT] > Always wrap `app.bootstrap()` in a shared promise (such as `initializeSystem()`) or React's `cache()`. If bootstrap fails, reset the cached promise in `.catch()` so subsequent requests can retry initialization. ## Resolve services with `app()` Once the application is booted, use the `app()` helper function from `@veap/kernel` to resolve services by class constructor or token: ```ts import { app, CONFIG_SERVICE, type IConfigService } from "@veap/kernel"; import { SettingsService } from "@veap/settings"; // Resolve by class token const settings = await app(SettingsService); const siteName = await settings.get("site.name", "My Veap App"); // Resolve by Symbol token const config = await app(CONFIG_SERVICE); const isDebug = config.getBoolean("DEBUG", false); ``` ## Write a custom `ServiceProvider` You can encapsulate your own domain services and startup logic (such as ensuring custom tables or seeding initial data) inside a `ServiceProvider`: ```ts import { Injectable, ServiceProvider, app } from "@veap/kernel"; @Injectable() export class BillingService { public calculateTax(amountCents: number): number { return Math.round(amountCents * 0.23); } } export class BillingServiceProvider extends ServiceProvider { register(): void { this.container.register({ token: BillingService, useClass: BillingService, singleton: true, }); } async boot(): Promise { const billing = await app(BillingService); // Perform any async startup checks here } } ``` ## Next steps Learn how to connect your composition root to Next.js rendering and server boundaries: - [Next.js integration](https://veap.pl/docs/getting-started/nextjs-integration) — trigger `initializeSystem()` automatically via `instrumentation.ts` and call Veap services from Server Components and Server Actions. - [Project structure and configuration](https://veap.pl/docs/getting-started/project-structure) — explore how `veap.config.ts`, migrations, and plugins are organized. --- # Next.js integration URL: https://veap.pl/docs/getting-started/nextjs-integration Section: Getting Started Veap complements Next.js rather than replacing it. Next.js owns the HTTP server, React Server Components (RSC), client hydration, and build pipeline (`next dev`, `next build`, `next start`), while Veap provides the backend service container, ORM, authentication, and plugin system around your requests. ## Responsibilities: Next.js vs. Veap The table below summarizes how responsibilities are divided between Next.js and Veap in a full-stack application: | Concern | Native Next.js | Next.js with Veap | | --- | --- | --- | | **Application lifecycle** | No shared IoC container or ordered boot step | `@veap/kernel` registers and boots `ServiceProvider` classes once per server process | | **Routing** | File-system routes in `app/` | File-system routes in `app/` plus dynamic plugin route trees mounted via `app/[...catchAll]/page.tsx` | | **Request state** | Direct `cookies()` and `headers()` calls | `COOKIE_STORE` (`NextCookieStore`) and `REQUEST_CONTEXT` (`NextRequestContext`) provided by `@veap/kernel/next` (`NextTransportServiceProvider`) | | **Database & transactions** | Bring your own client and manual transaction passing | `@veap/database` Active Record models and `AsyncLocalStorage`-backed `transaction()` blocks | | **Authentication & RBAC** | Custom middleware and session logic | `@veap/auth` Server Actions (`signIn`, `signUp`, `signOut`), `getCurrentSession()`, and role/permission guards | | **Error handling** | Unstructured exceptions across actions | `AppError`, `Result`, `ok()`, and `handleActionError()` with Next.js redirect preservation | ## Automatic startup with `instrumentation.ts` Next.js executes the `register()` hook in `instrumentation.ts` once when the server process starts. Calling `initializeSystem()` inside `instrumentation.ts` ensures your IoC container is booted before requests arrive: ```ts // instrumentation.ts export async function register() { if (process.env.NEXT_RUNTIME !== "nodejs") { return; } const { initializeSystem } = await import("./lib/veap"); await initializeSystem(); } ``` Checking `process.env.NEXT_RUNTIME !== "nodejs"` prevents Edge runtime workers from importing Node-only packages (such as `@veap/database` or `better-sqlite3`). ### Build-time safety (`next build`) During `next build`, Next.js imports page modules to collect static metadata. `@veap/kernel` automatically detects the Next.js production build phase (`NEXT_PHASE === "phase-production-build"`) and skips `app.bootstrap()` so your build never requires a live database connection. You can also force this behavior explicitly by setting `SKIP_VEAP_INIT=true` in your build environment. ## Using Veap in Server Components In React Server Components, you can query Active Record models, read translations, or inspect the current user session directly on the server: ```tsx // app/dashboard/page.tsx import { getCurrentSession } from "@veap/auth"; import { getTranslation } from "@veap/intl/server"; import { redirect } from "next/navigation"; import { initializeSystem } from "@/lib/veap"; export default async function DashboardPage() { await initializeSystem(); const { user } = await getCurrentSession(); if (!user) { redirect("/signin"); } const { t } = await getTranslation("common"); return (

{t("dashboard.welcome", { name: user.name })}

); } ``` ## Using Veap in Server Actions Wrap Next.js Server Actions with `ok()`, `AppError`, and `handleActionError()` from `@veap/kernel` to return strongly typed `Result` objects to your client forms: ```ts // app/actions/profile.ts "use server"; import { getCurrentSession } from "@veap/auth"; import { AppError, handleActionError, ok, type Result } from "@veap/kernel"; import { SettingsService } from "@veap/settings"; import { app } from "@veap/kernel"; import { initializeSystem } from "@/lib/veap"; export async function updateSiteTitleAction( newTitle: string, ): Promise> { try { await initializeSystem(); const { user } = await getCurrentSession(); if (!user) { throw AppError.Unauthorized("You must be signed in."); } if (!newTitle.trim()) { throw AppError.Validation("Site title cannot be empty."); } const settings = await app(SettingsService); await settings.set("site.title", newTitle.trim()); return ok({ title: newTitle.trim() }); } catch (error) { return handleActionError(error, "Settings:UpdateSiteTitle"); } } ``` > [!TIP] > `handleActionError()` automatically inspects the caught error for Next.js `NEXT_REDIRECT` and `NEXT_NOT_FOUND` digests and rethrows them untouched, so calling `redirect()` or `notFound()` inside a `try` block works as expected. ## Using Veap in Route Handlers Call `initializeSystem()` at the start of your `app/api/*/route.ts` handlers before resolving services or querying models: ```ts // app/api/health/route.ts import { NextResponse } from "next/server"; import { initializeSystem } from "@/lib/veap"; export async function GET() { await initializeSystem(); return NextResponse.json({ status: "ok" }); } ``` ## Respecting server and client boundaries Because Veap includes both Node.js backend services and React client hooks, always import from the subpath matching your component environment: | Package | Server-only imports (RSC, Server Actions, Route Handlers) | Client Component imports (`"use client"`) | | --- | --- | --- | | `@veap/kernel` | `@veap/kernel`, `@veap/kernel/server`, `@veap/kernel/cli` | Types and error helpers (`type Result`) | | `@veap/database` | `@veap/database`, `@veap/database/migrations` (never import in client components) | None | | `@veap/auth` | `@veap/auth`, `@veap/auth/server`, `@veap/auth/actions`, `@veap/auth/models` | `@veap/auth/client` (`AuthProvider`, `useUser`, `useAuthRoutes`) | | `@veap/intl` | `@veap/intl/server` (`getTranslation`, `getCurrentLocale`, `I18nProvider`) | `@veap/intl/client` (`useTranslation`, `I18nProvider`) | | `@veap/plugins` | `@veap/plugins`, `@veap/plugins/server`, `@veap/plugins/router`, `@veap/plugins/models` | `@veap/plugins/client` (`ExtensionPointClient`, `WidgetAreaClient`, `useConfirmAction`) | | `@veap/ui` | `@veap/ui` (presentational components, `cn`) | `@veap/ui`, `@veap/ui/providers` (`ThemeProvider`) | > [!WARNING] > Never import `lib/veap.ts`, `@veap/database`, or `@veap/auth/models` inside a file marked with `"use client"`. Client components must interact with backend services through Next.js Server Actions or API Route Handlers. ## Running Veap in other frameworks While Veap is built primarily for Next.js, its hexagonal port architecture lets you run the container, ORM, authentication, localization, mail, settings, and storage packages in any other framework or HTTP server (such as **Hono**, **Express**, **Fastify**, or standalone Node.js/Bun services). `KernelServiceProvider` defaults to in-memory fallback adapters (`MemoryCookieStore` and `MemoryRequestContext`), while Next.js applications register `NextTransportServiceProvider` from `@veap/kernel/next`. In other frameworks, you can override those two ports by binding your own custom implementations of `ICookieStore` and `IHttpRequestContext`: ```ts // src/lib/veap.ts (Custom HTTP framework example) import { AuthServiceProvider } from "@veap/auth"; import { DatabaseServiceProvider } from "@veap/database"; import { Application, COOKIE_STORE, type CookieOptions, type ICookieStore, type IHttpRequestContext, REQUEST_CONTEXT, ServiceProvider, } from "@veap/kernel"; class CustomCookieStore implements ICookieStore { async get(name: string): Promise { // Read cookie from your framework's active request context return null; } async set(name: string, value: string, options?: CookieOptions): Promise { // Append Set-Cookie header to your framework's active response context } async delete(name: string): Promise { // Expire cookie in your framework's active response context } } class CustomRequestContext implements IHttpRequestContext { async getHeader(name: string): Promise { // Read header from your framework's active request context return null; } redirect(url: string): never { // Trigger your framework's HTTP redirect exception throw new Error(`Redirect to ${url}`); } } class CustomTransportServiceProvider extends ServiceProvider { register(): void { this.container.register({ token: COOKIE_STORE, useClass: CustomCookieStore, singleton: true, }); this.container.register({ token: REQUEST_CONTEXT, useClass: CustomRequestContext, singleton: true, }); } } export const app = Application.configure() .use(CustomTransportServiceProvider) .use(DatabaseServiceProvider) .use(AuthServiceProvider) .create(); ``` For unit and integration tests, `@veap/kernel` also provides in-memory implementations (`MemoryCookieStore` and `MemoryRequestContext`). For stateless REST APIs (such as a standalone Hono or Express service using `@veap/database`), you can initialize the database or container directly without calling cookie or redirect adapters. ## Next steps Explore how files, configuration, and plugins are laid out in a Veap project: - [Project structure and configuration](https://veap.pl/docs/getting-started/project-structure) — understand `veap.config.ts`, `lib/plugins.gen.ts`, catch-all plugin routes, and `veap eject`. --- # Project structure and configuration URL: https://veap.pl/docs/getting-started/project-structure Section: Getting Started Veap does not force a rigid directory layout, but scaffolded applications and official plugins follow a predictable convention so the `veap` CLI, migrations, and virtual router work together with zero boilerplate. ## Typical directory layout A full-stack Next.js and Veap application is organized as follows: ```text my-app/ ├── app/ │ ├── (site)/ # Application-owned public routes and layouts │ ├── [...catchAll]/ │ │ └── page.tsx # Mounts @veap/plugins Virtual Router pages │ ├── api/ │ │ └── [...catchAll]/ │ │ └── route.ts # Mounts @veap/plugins Virtual Router API routes │ ├── globals.css # Tailwind CSS v4 + @veap/ui/globals.css │ └── layout.tsx # Root HTML shell, ThemeProvider, I18nProvider ├── components/ # Application React components and layouts ├── lib/ │ ├── plugins.gen.ts # Auto-generated plugin imports (managed by CLI) │ └── veap.ts # Application composition root and initializeSystem() ├── locales/ │ ├── en.json # English ICU translation dictionary │ └── de.json # German ICU translation dictionary ├── migrations/ │ └── index.ts # Application-level database migrations ├── plugins/ # Local or ejected plugins (created by veap eject) ├── instrumentation.ts # Next.js server startup hook ├── next.config.ts # Next.js configuration ├── veap.config.ts # Veap framework configuration └── package.json ``` ## Framework configuration (`veap.config.ts`) Place a `veap.config.ts` file at the root of your project to configure framework-wide defaults such as the private admin path prefix and supported locales: ```ts // veap.config.ts import type { VeapConfig } from "@veap/kernel"; const config: VeapConfig = { privatePath: "/app", intl: { cookie: "VEAP_LOCALE", default: "en", locales: ["en", "de"], timeZone: "UTC", }, debug: false, }; export default config; ``` ### Configuration properties `VeapConfig` supports the following settings: | Property | Type | Default | Description | | --- | --- | --- | --- | | `privatePath` | `string` | `"/app"` | URL prefix used by panel and admin plugins for protected routes. | | `intl.cookie` | `string` | `"NEXT_LOCALE"` | Cookie name used by `@veap/intl` to persist the active user locale. | | `intl.default` | `string` | `"en"` | Fallback locale when no cookie or `Accept-Language` match is found. | | `intl.locales` | `string[]` | `["en"]` | List of locale codes supported by the application. | | `intl.timeZone` | `string` | `"UTC"` | Default IANA time zone for date and time formatting. | | `debug` | `boolean` | `false` | Enables verbose diagnostic logging across kernel services. | ## Environment variables (`.env`) Runtime secrets and infrastructure connection strings belong in `.env` (or your deployment platform's environment settings) and are read through `ConfigService`: ```ini # Database connection (better-sqlite3, pg, or mysql2) DB_CLIENT=better-sqlite3 DATABASE_URL=./data/app.sqlite # Application URL NEXT_PUBLIC_APP_URL=http://localhost:3000 # Mail transport (console, smtp, or custom) MAIL_TRANSPORT=console MAIL_FROM_ADDRESS=noreply@example.com # Local file storage directory FILE_STORAGE_FOLDER=public/uploads ``` ## Generated plugin registry (`lib/plugins.gen.ts`) When you install or remove plugins using the `veap` CLI (`bun veap add ` or `bun veap remove `), Veap automatically updates `lib/plugins.gen.ts`: ```ts // lib/plugins.gen.ts // This file is auto-generated by Veap CLI. Do not edit manually. import type { IPlugin } from "@veap/plugins"; import authPlugin from "@veap/auth-plugin"; import panelPlugin from "@veap/panel-plugin"; export const plugins: IPlugin[] = [authPlugin, panelPlugin]; ``` Import `plugins` in `lib/veap.ts` and pass it to `PluginsServiceProvider` and `.withPlugins(plugins)` so newly installed plugins register their routes, migrations, translations, and widgets automatically. ## Catch-all routes for plugins If your application uses `@veap/plugins` to render virtual plugin pages (such as `/signin` from `@veap/auth-plugin` or `/app` from `@veap/panel-plugin`), mount the virtual router in `app/[...catchAll]/page.tsx`: ```tsx // app/[...catchAll]/page.tsx import { buildRouteTree, VeapRouter } from "@veap/plugins/router"; import type { Metadata } from "next"; import { initializeSystem } from "@/lib/veap"; export const dynamic = "force-dynamic"; export default async function CatchAllPage({ params, searchParams, }: { params: Promise<{ catchAll?: string[] }>; searchParams: Promise>; }) { const { catchAll } = await params; const resolvedSearchParams = await searchParams; const path = catchAll && catchAll.length > 0 ? `/${catchAll.join("/")}` : "/"; await initializeSystem(); const tree = await buildRouteTree(true); return VeapRouter({ tree, path, searchParams: resolvedSearchParams, }); } ``` Because Next.js resolves explicit file-system routes in `app/` before falling back to `app/[...catchAll]/page.tsx`, any physical page you create in `app/` naturally overrides a plugin route at the same URL. ## Customizing plugins with `veap eject` When you need full ownership over an installed plugin's UI, routes, or business logic, you can eject it into your application's local `plugins/` directory: ```bash bun veap eject @veap/blog-plugin ``` This command clones the plugin's source repository into `plugins/blog-plugin/`, configures `"workspace:*"` in `package.json`, and links it via your package manager so you can customize every component and model locally without changing any package import specifiers. ## Next steps Now that you know how a Veap project is structured, explore the `@veap/kernel` fundamentals: - [IoC container and service providers](https://veap.pl/docs/fundamentals/ioc-and-providers) — Learn how tokens, constructor injection, `ServiceProvider` classes, and `Application` work under the hood. --- # IoC container and service providers URL: https://veap.pl/docs/fundamentals/ioc-and-providers Section: Fundamentals At the heart of `@veap/kernel` is an asynchronous Inversion of Control (IoC) container paired with a two-phase `ServiceProvider` lifecycle. Every Veap package registers its capabilities as abstract contracts (tokens) in the container and resolves its dependencies asynchronously at runtime. ## Why Veap uses an IoC container Modern Next.js applications frequently outgrow direct module imports when services require asynchronous initialization, test isolation, or swappable infrastructure drivers. The `@veap/kernel` container solves these challenges with three design mechanisms: - **Hexagonal ports and adapters**: Packages depend on `Symbol.for(...)` tokens (ports) such as `COOKIE_STORE`, `CACHE_PROVIDER`, or `MAIL_DRIVER` rather than concrete classes, allowing you to swap adapters without editing business logic. - **Hot Module Replacement (HMR) survival**: Both the global `container` instance (`globalThis.__VEAP_CONTAINER__`) and class tokens (normalized via `Symbol.for("veap:ioc:")` in development) survive Next.js Fast Refresh reloads without duplicating connections or losing singleton state. - **Concurrent async resolution**: When multiple React Server Components resolve the same asynchronous singleton simultaneously, the container deduplicates in-flight promises so the factory or constructor runs only once. ## The `Container` and `app()` helper `@veap/kernel` exports a global `container` instance alongside the `app()` helper function. Calling `app()` with no arguments returns the `Container` instance, while calling `app(token)` resolves and returns a `Promise` for the requested token. ```ts import { app, CONFIG_SERVICE, container } from "@veap/kernel"; // Resolve a service via the app(token) shorthand const config = await app(CONFIG_SERVICE); // Access the container directly const hasConfig = container.has(CONFIG_SERVICE); const sameConfig = await container.resolve(CONFIG_SERVICE); ``` ### Container methods The `Container` class provides six methods for registering, inspecting, resolving, and resetting bindings across your application and test suites: | Method | Signature | Description | | :--- | :--- | :--- | | `register` | `register(def: ProviderDef): void` | Registers a provider definition (`useClass`, `useValue`, or `useFactory`) for a token. | | `resolve` | `resolve(token: Token): Promise` | Resolves and returns the bound instance for `token`, deduplicating concurrent in-flight resolutions. | | `make` | `make(token: Token): Promise` | Alias for `resolve(token)`. | | `has` | `has(token: Token): boolean` | Returns `true` if a provider definition or instantiated singleton is registered for `token`. | | `clear` | `clear(): void` | Clears cached singleton instances and in-flight promises while preserving registered provider definitions. | | `reset` | `reset(): void` | Clears all provider definitions, cached singleton instances, and in-flight promises. | ## Registering bindings When you call `container.register(def)`, you pass a `ProviderDef` object describing how the container should construct the dependency. By default, all bindings are singletons (`singleton: true`) unless you explicitly pass `singleton: false`. ### Binding strategies You can register a token using one of three strategies: a class constructor (`useClass`), a static value (`useValue`), or a synchronous/asynchronous factory function (`useFactory`). ```ts import { container, CONFIG_SERVICE, type IConfigService, type Token } from "@veap/kernel"; export interface IPaymentGateway { charge(amountCents: number): Promise<{ transactionId: string }>; } export const PAYMENT_GATEWAY: Token = Symbol.for( "app:payment-gateway", ); // 1. useValue: Bind a constant or pre-instantiated object container.register({ token: "APP_VERSION", useValue: "1.0.0", }); // 2. useFactory: Bind a sync or async factory with injected dependencies container.register({ token: PAYMENT_GATEWAY, inject: [CONFIG_SERVICE], useFactory: async (config: IConfigService): Promise => { const apiKey = config.get("STRIPE_SECRET_KEY", "sk_test_mock"); return { async charge(amountCents: number) { return { transactionId: `tx_${apiKey.slice(-4)}_${amountCents}` }; }, }; }, singleton: true, }); ``` ## Constructor injection with `@Injectable` and `@Inject` When registering a class with `useClass` (or resolving an unregistered class constructor directly), the container inspects TypeScript decorator metadata to resolve constructor parameters automatically. ### Decorating classes and token parameters Mark your service classes with `@Injectable()` so TypeScript emits `design:paramtypes` metadata, and use `@Inject(token)` on constructor parameters that reference `Symbol` or string tokens rather than class constructors: ```ts import { CONFIG_SERVICE, type IConfigService, type ILogger, Inject, Injectable, LOGGER, } from "@veap/kernel"; @Injectable() export class OrderService { constructor( @Inject(CONFIG_SERVICE) private readonly config: IConfigService, @Inject(LOGGER) private readonly logger: ILogger, ) {} async placeOrder(orderId: string): Promise { const env = this.config.get("NODE_ENV", "development"); this.logger.info("app:orders", `Order ${orderId} placed in ${env} mode.`); } } ``` > [!WARNING] > When injecting another class via constructor metadata (without an explicit `@Inject(token)` decorator), never import that class using `import type`. TypeScript erases type-only imports from `design:paramtypes` metadata and replaces them with `Object`, causing the container to throw an error at runtime. Always use a value import for class constructor tokens, or annotate the parameter with `@Inject(TOKEN)`. ## Method injection with `container.call()` In addition to constructor injection, `Container` supports method-level dependency injection through `container.call(target, method, ...extraArgs)`. The container inspects method parameters decorated with `@Inject(token)` or class tokens from `design:paramtypes`, resolves each dependency asynchronously from the container, and supplies any remaining non-injected parameters sequentially from `extraArgs`: ```ts import { app, Inject, type ILogger, LOGGER } from "@veap/kernel"; export class ReportGenerator { async generate( reportType: string, @Inject(LOGGER) logger: ILogger, format: "pdf" | "csv" = "pdf", ): Promise { logger.info("reports", `Generating ${reportType} report as ${format}`); return `report-${reportType}.${format}`; } } // Invocations resolve @Inject(LOGGER) automatically: const generator = new ReportGenerator(); const result = await app().call(generator, "generate", "sales", "pdf"); ``` This pattern is especially useful in plugin lifecycle hooks (such as `init`, `onEnable`, and `onMigrate`) and controller action dispatchers where parameters combine container-managed services and runtime arguments. ## Writing a `ServiceProvider` Instead of scattering `container.register()` calls across your project, Veap organizes bindings into modular `ServiceProvider` classes. Every `@veap/*` package exports a `ServiceProvider`, and you can write custom providers for your own application domains. ### Lifecycle methods A `ServiceProvider` subclass can implement up to four lifecycle hooks executed in a deterministic order by the `Application`: 1. **`register(): void | Promise` (required)**: Binds tokens into `this.container`. Never resolve other services inside `register()`, because other providers may not have registered their bindings yet. 2. **`commands?(): CommandEntry[] | Promise` (optional)**: Returns an array of CLI command classes or instances contributed by this provider. Collected after all providers have finished `register()` and before `boot()`. 3. **`boot?(): void | Promise` (optional)**: Executes after every provider has registered. You can safely resolve other services from `this.container`, subscribe to `EventBus` topics, or warm up caches here. 4. **`shutdown?(): void | Promise` (optional)**: Releases persistent resources (such as database connection pools, timers, or sockets) when `app.shutdown()` is called. Executed in reverse registration order. ```ts import { EVENT_BUS, type IEventBus, type ILogger, LOGGER, ServiceProvider, } from "@veap/kernel"; import { OrderService } from "./order.service"; export class OrderServiceProvider extends ServiceProvider { register(): void { this.container.register({ token: OrderService, useClass: OrderService, singleton: true, }); } async boot(): Promise { const eventBus = await this.container.resolve(EVENT_BUS); const logger = await this.container.resolve(LOGGER); eventBus.subscribe("order:created", "OrderServiceProvider", async (event) => { logger.info("app:orders", `Handled order:created for ${event.payload.id}`); }); } async shutdown(): Promise { const eventBus = await this.container.resolve(EVENT_BUS); eventBus.unsubscribe("order:created", "OrderServiceProvider"); } } ``` ## Composing the `Application` The `Application` class orchestrates your `ServiceProvider` list in your composition root (`lib/veap.ts`). Use `Application.configure()` to chain providers via `.use(...)` (or `.with(...)` / `.withProviders(...)`) and finalize the instance with `.create()`. ```ts // lib/veap.ts import { DatabaseServiceProvider } from "@veap/database"; import { Application } from "@veap/kernel"; import { SettingsServiceProvider } from "@veap/settings"; import { OrderServiceProvider } from "./orders/order.provider"; export const app = Application.configure() .use(DatabaseServiceProvider) // Pass a factory function when a provider accepts options .use((c) => new SettingsServiceProvider(c, { cacheTtlSeconds: 120 })) .use(OrderServiceProvider) .create(); ``` ### What happens during `app.bootstrap()` Calling `await app.bootstrap()` runs the initialization sequence with built-in safeguards for Next.js: 1. **Build and browser guards**: If called in the browser (`typeof window !== "undefined"`), during `next build` (`process.env.NEXT_PHASE === "phase-production-build"`), or when `process.env.SKIP_VEAP_INIT === "true"`, `bootstrap()` returns immediately as a no-op. 2. **Idempotency**: If the application is already bootstrapped (`globalThis.__VEAP_BOOTSTRAPPED__`) or currently bootstrapping (`globalThis.__VEAP_BOOTSTRAPPING_PROMISE__`), `bootstrap()` returns the existing promise without re-running providers. 3. **Kernel defaults**: `Application` automatically prepends `KernelServiceProvider` before your custom providers, registering `EventBus` (`EVENT_BUS`), `ConsoleLogger` (`LOGGER`), `ConfigService` (`CONFIG_SERVICE`), `MemoryCacheProvider` (`CACHE_PROVIDER`), `VeapConfigProvider` (`VEAP_CONFIG`), `commands` (`COMMANDS`), and default Next.js HTTP adapters (`NextCookieStore` for `COOKIE_STORE` and `NextRequestContext` for `REQUEST_CONTEXT` if not overridden). 4. **Deduplication**: Provider classes are deduplicated by constructor reference so each provider runs once even if registered multiple times. 5. **Two-phase execution**: Every provider's `register()` runs sequentially, then `commands()` are collected into the CLI registry, and finally every provider's `boot()` runs sequentially before publishing the `"system:ready"` event on the `EventBus`. > [!TIP] > If a provider throws Next.js control-flow exceptions (`NEXT_REDIRECT` or `NEXT_NOT_FOUND`) during `boot()`, `app.bootstrap()` re-throws them cleanly without logging a false critical error. ## Next steps Learn how `@veap/kernel` loads configuration files, manages environment variables, and models typed domain errors: - [Configuration and error handling](https://veap.pl/docs/fundamentals/configuration-and-errors) — `veap.config.ts`, `ConfigService`, `AppError`, and `Result`. - [Events, caching, and logging](https://veap.pl/docs/fundamentals/events-and-cache) — `EventBus`, `CACHE_PROVIDER`, and `LOGGER`. --- # Configuration and error handling URL: https://veap.pl/docs/fundamentals/configuration-and-errors Section: Fundamentals `@veap/kernel` provides a unified configuration system that merges environment variables with a typed `veap.config.ts` file, alongside a transport-agnostic domain error taxonomy (`AppError`) and Server Action result helpers (`Result`, `ok`, and `handleActionError`). ## Configuration management Veap separates configuration into two complementary mechanisms: static project configuration defined in `veap.config.ts` and runtime configuration accessed through the `CONFIG_SERVICE` (`ConfigService`) IoC port. ### Project configuration with `veap.config.ts` At the root of your application, `veap.config.ts` exports a configuration object satisfying the `VeapConfig` interface. During `KernelServiceProvider.boot()`, `@veap/kernel` loads `veap.config.ts` on the server using `jiti` (via the `VEAP_CONFIG` port) and merges all defined keys into `ConfigService`. ```ts // veap.config.ts import type { VeapConfig } from "@veap/kernel"; const config: VeapConfig = { debug: process.env.NODE_ENV !== "production", appName: "Acme Portal", pagination: { defaultPageSize: 20, }, }; export default config; ``` `@veap/kernel` looks for the configuration file in the current working directory in the following order: 1. `veap.config.ts` 2. `veap.config.mjs` 3. `veap.config.js` 4. `veap.config` In development, `getVeapConfig()` reloads `veap.config.ts` on demand with cache busting so changes take effect immediately. In production (`NODE_ENV === "production"`), the parsed object is cached in memory after the first read. > [!NOTE] > `getVeapConfig()` is strictly a server-only function (`@veap/kernel/server` or `@veap/kernel`). Calling it in a browser environment throws an `INTERNAL_SERVER_ERROR` `AppError`. ### Reading configuration via `CONFIG_SERVICE` In your services, Server Components, or Server Actions, resolve `CONFIG_SERVICE` (or `ConfigService`) from the container. `ConfigService` initializes with a snapshot of `process.env` and is enriched with keys from `veap.config.ts` when `KernelServiceProvider` boots. ```ts import { app, CONFIG_SERVICE } from "@veap/kernel"; export async function getMailConfiguration() { const config = await app(CONFIG_SERVICE); // Read with an optional fallback value const driver = config.get("MAIL_DRIVER", "log"); const fromAddress = config.get("MAIL_FROM", "noreply@example.com"); // Check if a key is defined if (config.has("SMTP_HOST")) { const smtpHost = config.get("SMTP_HOST"); return { driver, fromAddress, smtpHost }; } return { driver, fromAddress }; } ``` ### `IConfigService` API summary The `IConfigService` contract exposes four methods for reading and mutating runtime configuration: | Method | Signature | Description | | :--- | :--- | :--- | | `get` | `get(key: string, defaultValue?: T): T` | Returns the configuration value for `key`, or `defaultValue` if `undefined`. | | `has` | `has(key: string): boolean` | Returns `true` if `key` exists in the configuration store and is not `undefined`. | | `set` | `set(key: string, value: any): void` | Sets or overrides a configuration value in memory at runtime. | | `getAll` | `getAll(): Record` | Returns a shallow copy of all configuration key-value pairs. | ## Domain error handling with `AppError` Instead of throwing generic `Error` instances or coupling business services to HTTP status codes, Veap models failures with the `AppError` class. Every `AppError` carries a transport-agnostic `ErrorCode`, a human-readable `message`, and an optional `details` payload (such as field validation errors). ```ts import { AppError } from "@veap/kernel"; export async function publishArticle(articleId: number, userId: number | null) { if (!userId) { throw AppError.Unauthorized("You must be signed in to publish articles."); } const article = await findArticleById(articleId); if (!article) { throw AppError.NotFound(`Article #${articleId} does not exist.`); } if (article.authorId !== userId) { throw AppError.Forbidden("You can only publish your own articles."); } if (!article.title.trim()) { throw AppError.Validation("Article title cannot be empty.", { field: "title", }); } } ``` ### Static factory methods and error codes `AppError` provides eight static factory methods that map directly to standardized `ErrorCode` values and HTTP status codes (via `httpStatusForErrorCode(code)`): | Factory Method | `ErrorCode` | Default Message | HTTP Status (`httpStatusForErrorCode`) | | :--- | :--- | :--- | :--- | | `AppError.BadRequest(message, details?)` | `"BAD_REQUEST"` | *(required)* | `400` | | `AppError.Unauthorized(message?, details?)` | `"UNAUTHORIZED"` | `"Unauthorized"` | `401` | | `AppError.Forbidden(message?, details?)` | `"FORBIDDEN"` | `"Forbidden"` | `403` | | `AppError.NotFound(message?, details?)` | `"NOT_FOUND"` | `"Not Found"` | `404` | | `AppError.Conflict(message, details?)` | `"CONFLICT"` | *(required)* | `409` | | `AppError.Validation(message, details?)` | `"VALIDATION_ERROR"` | *(required)* | `422` | | `AppError.RateLimited(message?, details?)` | `"RATE_LIMITED"` | `"Too Many Requests"` | `429` | | `AppError.Internal(message?, details?)` | `"INTERNAL_SERVER_ERROR"` | `"Internal Server Error"` | `500` | Calling `error.toJSON()` serializes any `AppError` into a plain object safe for crossing the Next.js Server Action boundary: ```ts { success: false, error: { code: "VALIDATION_ERROR", message: "Article title cannot be empty.", details: { field: "title" }, }, } ``` ## Typed Server Action results (`Result`) Next.js Server Actions serialize return values across the network to Client Components, and unhandled exceptions strip custom properties in production builds. To preserve typed error codes and validation details without try/catch boilerplate on the client, `@veap/kernel` provides the `Result` discriminated union along with `ok()` and `handleActionError()`. ```ts export type AppErrorJson = { code: ErrorCode; message: string; details?: unknown; }; export type Result = | { success: true; data: T; error?: never } | { success: false; error: AppErrorJson; data?: never }; ``` ### Using `ok()` and `handleActionError()` in Server Actions Wrap your Server Action body in a `try / catch` block, return `ok(data)` on success, and delegate any caught error to `handleActionError(e)`: ```ts // app/actions/articles.ts "use server"; import { AppError, handleActionError, ok, type Result } from "@veap/kernel"; import { initializeSystem } from "@/lib/veap"; export async function createTagAction( name: string, ): Promise> { try { await initializeSystem(); const trimmed = name.trim(); if (trimmed.length < 2) { throw AppError.Validation("Tag name must be at least 2 characters.", { field: "name", }); } const slug = trimmed.toLowerCase().replace(/\s+/g, "-"); return ok({ id: 1, slug }); } catch (e) { return handleActionError(e, "createTagAction"); } } ``` > [!IMPORTANT] > `handleActionError(e)` automatically detects Next.js internal control-flow exceptions (`NEXT_REDIRECT` and `NEXT_NOT_FOUND` digests) and re-throws them so `redirect()` and `notFound()` calls inside your Server Actions continue to work as expected. ### Mapping `AppError` in REST Route Handlers When building JSON Route Handlers (`app/api/.../route.ts`), combine `handleActionError` with `httpStatusForErrorCode` to return proper HTTP status codes alongside the standardized error payload: ```ts // app/api/tags/route.ts import { handleActionError, httpStatusForErrorCode, ok } from "@veap/kernel"; import { NextResponse } from "next/server"; export async function POST(request: Request) { try { const body = await request.json(); return NextResponse.json(ok({ created: body.name }), { status: 201 }); } catch (e) { const failure = handleActionError(e); return NextResponse.json(failure, { status: httpStatusForErrorCode(failure.error.code), }); } } ``` ## Next steps Continue exploring `@veap/kernel` fundamentals: - [Events, caching, and logging](https://veap.pl/docs/fundamentals/events-and-cache) — Decouple domain side effects with `EventBus`, cache expensive queries with `CACHE_PROVIDER`, and output structured logs via `LOGGER`. --- # Events, caching, and logging URL: https://veap.pl/docs/fundamentals/events-and-cache Section: Fundamentals `@veap/kernel` includes three built-in infrastructure ports registered automatically by `KernelServiceProvider`: an asynchronous publish-subscribe `EventBus` (`EVENT_BUS`), a key-value cache abstraction (`CACHE_PROVIDER`), and a group-colored logger (`LOGGER`). ## Domain events with `EventBus` The `EventBus` decouples modules and plugins so a package (such as `@veap/auth` or `@veap/plugins`) can broadcast domain events without knowing which listeners will react to them. Every subscriber is keyed by a unique `subscriberId` string, making subscriptions idempotent across Next.js Hot Module Replacement (HMR) reloads. ### Subscribing and publishing events Resolve `EVENT_BUS` (or import the `eventBus` singleton) to subscribe handlers or publish events. `subscribe` takes three arguments (`eventType`, `subscriberId`, and `handler`) and returns an unsubscribe function: ```ts import { app, EVENT_BUS } from "@veap/kernel"; const eventBus = await app(EVENT_BUS); // 1. Register an idempotent subscriber by subscriberId const unsubscribe = eventBus.subscribe( "user:registered", "welcome-email-listener", async (event) => { console.log( `[${event.source}] User ${event.payload.email} registered at ${event.timestamp}`, ); }, ); // 2. Publish an event (runs all registered handlers concurrently via Promise.all) await eventBus.publish( "user:registered", { userId: 42, email: "ada@example.com" }, "auth-service", ); // 3. Remove the listener when no longer needed unsubscribe(); ``` ### `IEventBus` methods The `IEventBus` interface supports persistent subscriptions, one-time listeners, and both fault-tolerant and strict publishing modes: | Method | Signature | Description | | :--- | :--- | :--- | | `subscribe` | `subscribe(eventType, subscriberId, handler): () => void` | Registers `handler` under `subscriberId` for `eventType`. If `subscriberId` is already registered for that event, it is a no-op and returns the unsubscribe function. | | `once` | `once(eventType, handler, subscriberIdPrefix?): () => void` | Subscribes a handler that automatically unsubscribes itself before executing on the first matching event. | | `unsubscribe` | `unsubscribe(eventType, subscriberId): void` | Removes the handler registered under `subscriberId` for `eventType`. | | `publish` | `publish(eventType, payload, source?, options?): Promise` | Dispatches `{ type, payload, timestamp, source }` to all subscribers concurrently. By default (`strict: false`), handler errors are logged via `LOGGER` without aborting other handlers. | | `publishStrict` | `publishStrict(eventType, payload, source?): Promise` | Equivalent to `publish(..., { strict: true })`. Rethrows the handler error if one subscriber fails, or an `AggregateError` if multiple subscribers fail. | | `clearAll` | `clearAll(): void` | Removes all event listeners across all topics (useful in test teardown). | > [!NOTE] > Built-in system lifecycle events include `"system:ready"` (published at the end of `app.bootstrap()`) and `"system:stop"` (published during `app.shutdown()`). ### Typing custom events with `SystemEventsMap` You can augment the `SystemEventsMap` interface from `@veap/kernel` using TypeScript declaration merging so `eventBus.subscribe` and `eventBus.publish` infer payload types automatically: ```ts // types/events.d.ts import "@veap/kernel"; declare module "@veap/kernel" { interface SystemEventsMap { "user:registered": { userId: number; email: string; }; "order:paid": { orderId: string; totalCents: number; }; } } ``` ## Caching with `CACHE_PROVIDER` The `CACHE_PROVIDER` port (`ICacheProvider`) provides an asynchronous key-value store with optional Time-To-Live (`ttlSeconds`) expiration. Packages such as `@veap/settings` use `CACHE_PROVIDER` to cache database lookups transparently. By default, `KernelServiceProvider` binds `CACHE_PROVIDER` to `MemoryCacheProvider`, an in-memory `Map`-backed store with lazy TTL expiration on read. ```ts import { app, CACHE_PROVIDER } from "@veap/kernel"; export async function getExchangeRates(): Promise> { const cache = await app(CACHE_PROVIDER); const cacheKey = "rates:usd"; const cached = await cache.get>(cacheKey); if (cached) { return cached; } const freshRates = { EUR: 0.92, CAD: 1.36, GBP: 0.79 }; // Cache for 300 seconds (5 minutes) await cache.set(cacheKey, freshRates, 300); return freshRates; } ``` ### `ICacheProvider` contract and custom adapters To replace the default in-memory cache with Redis or Upstash in a multi-instance deployment, implement `ICacheProvider` and register it under the `CACHE_PROVIDER` token in your composition root: ```ts import { CACHE_PROVIDER, type ICacheProvider, ServiceProvider, } from "@veap/kernel"; export class RedisCacheProvider implements ICacheProvider { async get(key: string): Promise { // Fetch and JSON.parse from Redis return null; } async set(key: string, value: T, ttlSeconds?: number): Promise { // Serialize and SET (with EX ttlSeconds) in Redis } async delete(key: string): Promise { // DEL key in Redis } async clear(): Promise { // Flush namespace in Redis } } export class CustomCacheServiceProvider extends ServiceProvider { register(): void { this.container.register({ token: CACHE_PROVIDER, useClass: RedisCacheProvider, singleton: true, }); } } ``` ## Structured logging with `LOGGER` `@veap/kernel` provides a `LOGGER` port (`ILogger`) bound by default to `ConsoleLogger` (also exported as the `logger` singleton and standalone `debug`, `info`, `warn`, and `error` functions). Every log call takes a `group` namespace as its first argument, which `ConsoleLogger` hashes to assign a consistent ANSI terminal color (or CSS color in browser devtools). ```ts import { app, AppError, LOGGER } from "@veap/kernel"; const log = await app(LOGGER); log.debug("app:billing", "Verifying webhook signature..."); log.info("app:billing", "Invoice #1042 marked as paid."); log.warn("app:billing", "Webhook retry attempt 2 of 3."); log.error( "app:billing", "Payment capture failed", AppError.BadRequest("Card declined"), ); ``` ### Log filtering and error formatting `ConsoleLogger` adapts its output automatically based on runtime environment variables and arguments: - **Production debug suppression**: `logger.debug(...)` is suppressed when `process.env.NODE_ENV === "production"` unless `process.env.DEBUG` is set. - **CLI quiet mode**: When running CLI commands (`process.env.VEAP_CLI === "1"`), `debug` and `info` logs are silenced unless `process.env.DEBUG === "1"` so command output stays clean. - **`AppError` code formatting**: When an `AppError` instance is passed as the first extra argument to `logger.error(group, message, appError)`, `ConsoleLogger` automatically appends `[]` to the formatted message. ## Next steps Continue exploring the `@veap/kernel` core subsystems: - [Console and CLI commands](https://veap.pl/docs/fundamentals/cli) — Run built-in commands and author custom `Command` classes with `@veap/kernel/cli`. --- # Console and CLI commands (@veap/kernel/cli) URL: https://veap.pl/docs/fundamentals/cli Section: Fundamentals `@veap/kernel` ships with a built-in console command runner (`@veap/kernel/cli`) and the `veap` binary (`bun veap `). Every Veap application includes the console out of the box as a core kernel subsystem rather than an optional add-on. ## How the `veap` console works When you run `bun veap ` from your project directory, `ConsoleApplication` executes a deterministic single-command lifecycle: 1. **Resolves the project root and environment**: Walks up from the current working directory (or `--cwd `) to locate your project's `package.json`, sets `process.env.VEAP_CLI = "1"`, and loads `.env` and `.env.` files before importing application code. 2. **Binds in-memory transport fallbacks**: Registers `MemoryCookieStore` (`COOKIE_STORE`) and `MemoryRequestContext` (`REQUEST_CONTEXT`) in the IoC container before bootstrap so services can be resolved outside an HTTP request scope without triggering `next/headers` errors. 3. **Discovers and boots your composition root**: Loads `lib/veap.ts` (or a dedicated console entry file) via `jiti` with JSX and `tsconfig.json` path alias support, calls `await app.bootstrap()`, and harvests `Command` definitions from every registered `ServiceProvider.commands()` method. 4. **Parses arguments and runs the command**: Matches CLI arguments and flags against the target command's `static signature`, injects `CommandContext` (`this.context`), and invokes `handle(input, output)`. 5. **Shuts down cleanly**: Always calls `await app.shutdown()` in a `finally` block so database connection pools and background listeners close cleanly before the process exits. ## Built-in and ecosystem CLI commands Run `bun veap list` to inspect all commands registered in your application. `@veap/kernel` provides five built-in commands, and ecosystem providers (`MigrationServiceProvider` in `@veap/database` and `PluginsServiceProvider` in `@veap/plugins`) automatically register their own commands when added to `lib/veap.ts`: | Command | Source package | Description | | :--- | :--- | :--- | | `veap list` | `@veap/kernel` | Lists all registered console commands grouped by namespace. | | `veap help [command]` | `@veap/kernel` | Displays usage signature, arguments, and options for a command. | | `veap about` | `@veap/kernel` | Prints environment, runtime, and registered provider diagnostics. | | `veap config:show [key]` | `@veap/kernel` | Displays merged `ConfigService` values with sensitive secrets redacted. | | `veap make:command ` | `@veap/kernel` | Scaffolds a new CLI command class in your project. | | `veap make:migration ` | `@veap/database` | Creates a timestamped database migration file in `migrations/`. | | `veap migrate` | `@veap/database` | Executes pending database migrations. | | `veap migrate:status` | `@veap/database` | Shows applied and pending migrations. | | `veap migrate:rollback` | `@veap/database` | Rolls back the last migration batch (`--force` skips confirmation). | | `veap list-plugins` | `@veap/plugins` | Lists registered plugins and their runtime status. | | `veap add ` | `@veap/plugins` | Installs a plugin (`--local`, `--skip-install`) and regenerates `lib/plugins.gen.ts`. | | `veap remove ` | `@veap/plugins` | Removes a plugin (`--skip-install`) and updates `lib/plugins.gen.ts`. | | `veap eject ` | `@veap/plugins` | Ejects an installed plugin or template into `plugins//` or `templates//`. | | `veap make:plugin ` | `@veap/plugins` | Scaffolds a new local plugin in `plugins/-plugin/`. | | `veap plugins:sync` | `@veap/plugins` | Scans `package.json` and regenerates `lib/plugins.gen.ts`. | ### Global console flags The `veap` runner recognizes global flags anywhere in `argv` before handing remaining tokens to the command: | Flag | Shorthand | Description | | :--- | :--- | :--- | | `--cwd ` | — | Runs the console against a specific project root directory. | | `--env ` | `-e` | Overrides `NODE_ENV` and loads `.env.`. | | `--force` | — | Waives interactive confirmation prompts (`this.context.flags.force = true`) for CI runs. | | `--no-interaction` | `-n` | Disables interactive prompts (`ask()` uses default values or fails fast). | | `--quiet` | `-q` | Suppresses informational and table output while preserving warnings and errors. | | `--verbose` | `-v` | Enables `DEBUG=1` and prints full error stack traces. | | `--help` | `-h` | Prints usage information for the target command. | | `--version` | `-V` | Prints the installed `@veap/kernel` version. | ## Writing a custom CLI command Scaffold a new command with `bun veap make:command SeedDemo` or create a class extending `Command` from `@veap/kernel/cli`. Declare `static signature` and `static description`, and implement `handle(input, output)`: ```ts // lib/commands/seed-demo.command.ts import { Command, type CommandInput, type Output } from "@veap/kernel/cli"; export class SeedDemoCommand extends Command { static override signature = "app:seed {--count=10 : Number of records} {--f|force : Skip confirmation prompt}"; static override description = "Seed sample records into the database"; async handle(input: CommandInput, output: Output): Promise { const count = Number(input.option("count") ?? 10); if (!input.flag("force") && !this.context.flags.force) { const confirmed = await output.confirm(`Seed ${count} sample records?`); if (!confirmed) { output.warn("Seeding cancelled."); return 0; } } output.info( `Seeding ${count} records in ${this.context.env} environment...`, ); output.success("Sample records created."); return 0; } } ``` ### Registering commands You can register custom commands either directly on your `ApplicationBuilder` in `lib/veap.ts` using `.withCommands([...])`, or inside a `ServiceProvider` by implementing the `commands()` method: ```ts // lib/veap.ts import { Application } from "@veap/kernel"; import { SeedDemoCommand } from "./commands/seed-demo.command"; export const app = Application.configure() .withCommands([SeedDemoCommand]) .create(); ``` If you package commands inside a reusable module or plugin `ServiceProvider`, return them from `commands()`: ```ts import { ServiceProvider } from "@veap/kernel"; import type { CommandEntry } from "@veap/kernel/cli"; import { SeedDemoCommand } from "./commands/seed-demo.command"; export class DemoServiceProvider extends ServiceProvider { commands(): CommandEntry[] { return [SeedDemoCommand]; } register(): void {} } ``` ## Next steps Explore the Veap ecosystem packages to build database models, authentication flows, and modular plugins: - [Database and ORM (`@veap/database`)](https://veap.pl/docs/packages/database) — Define `Model` classes, relations, `AsyncLocalStorage` transactions, and migrations with `@veap/database`. --- # Core kernel (@veap/kernel) URL: https://veap.pl/docs/packages/kernel Section: Packages (@veap/*) / @veap/kernel `@veap/kernel` is the foundational package of the Veap framework. It provides the asynchronous Inversion of Control (IoC) dependency injection container, the `Application` and `ServiceProvider` lifecycle engine, configuration loading, event dispatching, transport-agnostic error handling with `Result`, the `veap` CLI console runner, and HTTP adapters for Next.js and Node.js runtimes. Every framework package in Veap depends on `@veap/kernel`. ## Installation and setup `@veap/kernel` is installed by default in every application scaffolded with `create-veap`. If you add Veap to an existing project, install the package directly: ```bash bun add @veap/kernel ``` > [!NOTE] > Enable `experimentalDecorators` and `emitDecoratorMetadata` in your `tsconfig.json` compiler options to use `@Injectable()` and `@Inject()` decorators with the IoC container. ## The composition root (`lib/veap.ts`) Applications configure and boot their service providers through the fluent `Application.configure()` builder: ```ts // lib/veap.ts import { Application } from "@veap/kernel"; import { DatabaseServiceProvider } from "@veap/database"; import { AuthServiceProvider } from "@veap/auth"; export const app = Application.configure() .use(DatabaseServiceProvider) .use(AuthServiceProvider) .create(); let bootstrapPromise: Promise | null = null; export async function initializeSystem(): Promise { if (!bootstrapPromise) { bootstrapPromise = app.bootstrap().catch((error) => { bootstrapPromise = null; throw error; }); } return bootstrapPromise; } ``` ## Core subsystems overview `@veap/kernel` packages several coordinated subsystems: ### 1. Inversion of Control (IoC) container The asynchronous DI container (`Container`) supports constructor injection, property injection, singleton and transient lifetimes, in-flight promise deduplication, and method-level injection with `container.call()`: ```ts import { app, Injectable, Inject, Token } from "@veap/kernel"; export interface IAnalyticsService { track(event: string, properties?: Record): Promise; } export const ANALYTICS: Token = Symbol.for("app:analytics"); @Injectable() export class OrderProcessor { constructor(@Inject(ANALYTICS) private analytics: IAnalyticsService) {} async processOrder(orderId: string): Promise { await this.analytics.track("order_processed", { orderId }); } } // Resolve anywhere asynchronously const processor = await app(OrderProcessor); ``` ### 2. Service providers and lifecycle `ServiceProvider` is the modular unit of packaging in Veap. Providers define four deterministic lifecycle hooks: - **`register()`**: Binds tokens, factories, and singletons into the container. No database or network I/O should run here. - **`commands?()`**: Discovers console commands contributed by this module for `bun veap `. - **`boot?()`**: Performs asynchronous initialization (verifying connections, running bootstrap routines, or subscribing to events). - **`shutdown?()`**: Closes connection pools and releases resources cleanly when the process exits. ### 3. Events and cache ports Hexagonal ports decouple application logic from concrete drivers: - **`EventBus` (`EVENT_BUS`)**: In-memory pub/sub broker for asynchronous event-driven architectures. - **`ICacheProvider` (`CACHE_PROVIDER`)**: Low-latency cache contract (`get`, `set`, `delete`, `remember`, `clear`). ### 4. Configuration and typed errors - **`ConfigService` (`CONFIG_SERVICE`)**: Reads typed environment configuration with nested dot notation (`config.get("database.host", "localhost")`). - **`AppError` and `Result`**: Structured domain errors (`AppError.NotFound()`, `AppError.Unauthorized()`, `AppError.Validation()`) returned through the discriminated union `Result` (`ok(data)` vs `{ success: false, error }`). ### 5. Console and CLI command engine (`@veap/kernel/cli`) Built-in command runner for terminal tasks (`bun veap list`, `bun veap about`, `bun veap config:show`, `bun veap make:command`). ### 6. Next.js transport adapters (`@veap/kernel/next`) Binds `NextCookieStore` and `NextRequestContext` to decouple authentication and request headers from direct `next/headers` calls, enabling seamless fallback to in-memory mocks during CLI commands and unit tests. ## Next steps Explore each kernel subsystem in detail: - [IoC container and service providers](https://veap.pl/docs/fundamentals/ioc-and-providers) — Dependency injection, registration styles, and method injection. - [Configuration and error handling](https://veap.pl/docs/fundamentals/configuration-and-errors) — Environment variables, `veap.config.ts`, and `Result` pattern. - [Events and cache](https://veap.pl/docs/fundamentals/events-and-cache) — Subscribing to domain events and using cache providers. - [Console and CLI commands](https://veap.pl/docs/fundamentals/cli) — The `veap` runner, built-in commands, and writing custom commands. - [Kernel API reference](https://veap.pl/docs/reference/packages/kernel) — Detailed list of exported classes, interfaces, and tokens. --- # Database and ORM (@veap/database) URL: https://veap.pl/docs/packages/database Section: Packages (@veap/*) / @veap/database `@veap/database` is an Active Record ORM built on top of Knex.js for PostgreSQL, MySQL, and SQLite. It provides declarative `Model` classes, a chainable `QueryBuilder`, relational and polymorphic associations, `AsyncLocalStorage` transaction propagation, and a reversible schema migration runner. ## Connecting to the database Register `DatabaseServiceProvider` and `MigrationServiceProvider` in your application composition root (`lib/veap.ts`), or chain `.withMigrations(appMigrations)` to register your migration list and `MigrationServiceProvider` in a single step: ```ts // lib/veap.ts import { DatabaseServiceProvider } from "@veap/database"; import { Application } from "@veap/kernel"; import { appMigrations } from "../migrations"; export const app = Application.configure() .use(DatabaseServiceProvider) .withMigrations(appMigrations) .create(); ``` When `DatabaseServiceProvider.boot()` runs, it reads `DATABASE_URL` (or `DATABASE_FILE`) from `ConfigService` / `process.env` and automatically selects `better-sqlite3` for SQLite file paths or `pg` for PostgreSQL connection strings. > [!TIP] > If you use `@veap/database` in a standalone script or service without `Application.configure()`, call `initDatabase({ client: "better-sqlite3", connection: { filename: "./dev.sqlite" }, useNullAsDefault: true })` directly. ## PostgreSQL SSL configuration When `DATABASE_URL` points to a PostgreSQL instance in production (`NODE_ENV === "production"`), `DatabaseServiceProvider` configures TLS verification automatically. You can customize certificate verification through two environment variables: - `DATABASE_SSL_REJECT_UNAUTHORIZED`: Set to `"false"` when connecting to managed PostgreSQL providers with self-signed certificates. - `DATABASE_SSL_CA`: Provide a custom PEM-encoded CA certificate string for strict verification. ## SQLite on serverless platforms (Vercel and AWS Lambda) When deploying to serverless platforms such as Vercel or AWS Lambda, the root deployment directory (`/var/task`) is read-only. `@veap/database` automatically detects serverless environments (`VERCEL === "1"` or `AWS_LAMBDA_FUNCTION_NAME`) and redirects relative SQLite file paths to the writable `/tmp` directory (for example, `/tmp/veap.sqlite`). > [!WARNING] > Storage on serverless functions is ephemeral and wiped across function cold starts. For persistent production deployments on serverless platforms, configure a PostgreSQL database URL in your environment variables. ## Checking database initialization Use `isDatabaseInitialized()` to check whether the global Knex instance or an active transaction is ready: ```ts import { isDatabaseInitialized } from "@veap/database"; if (isDatabaseInitialized()) { // Safe to query models } ``` Continue exploring `@veap/database` features: - [Models, queries, and relationships](https://veap.pl/docs/packages/database/models-and-relations) — Define `Model` subclasses, casts, scopes, lifecycle hooks, and eager loaded associations. - [Migrations and transactions](https://veap.pl/docs/packages/database/migrations-and-transactions) — Manage schema changes with `Schema` and run `AsyncLocalStorage` transactions. - [Seeders and model factories](https://veap.pl/docs/packages/database/seeders-and-factories) — Populate test data with `defineFactory`, create `Seeder` classes, and run seeds via the console CLI. --- # Models, queries, and relationships URL: https://veap.pl/docs/packages/database/models-and-relations Section: Packages (@veap/*) / @veap/database Extend the base `Model` class from `@veap/database` to map a database table to a strongly typed Active Record class with fluent querying, attribute casting, relations, lifecycle hooks, and soft deletes. ## Defining Active Record models Set `static override table` on your `Model` subclass to the underlying database table name. By default, models use `id` as the primary key and manage `created_at` and `updated_at` timestamps automatically: ```ts // lib/models/article.ts import { type BelongsTo, type HasMany, Model } from "@veap/database"; import { Author } from "./author"; import { Comment } from "./comment"; export class Article extends Model { static override table = "articles"; declare id: number; declare author_id: number; declare title: string; declare slug: string; declare content: string; declare is_published: boolean; declare metadata: Record; declare created_at: string; declare updated_at: string; protected static override casts = { is_published: "boolean", metadata: "json", } as const; public author(): BelongsTo { return this.belongsTo(Author, "author_id"); } public comments(): HasMany { return this.hasMany(Comment, "article_id"); } } ``` ### Attribute casting Define `protected static override casts` on a model to convert database column values automatically when reading and writing attributes. Supported cast types include `"boolean"`, `"number"`, `"int"`, `"integer"`, `"float"`, `"string"`, `"json"`, `"array"`, `"object"`, and `"date"`. ## Querying and CRUD operations Call `Model.query()` to start a fluent `QueryBuilder` chain, or use static convenience methods such as `create`, `find`, `findOrFail`, and `all`: ```ts import { Article } from "@/lib/models/article"; // 1. Create and persist a new record const article = await Article.create({ author_id: 1, title: "Getting Started with Veap ORM", slug: "getting-started-with-veap-orm", content: "Active Record modeling in TypeScript...", is_published: true, metadata: { readingTimeMinutes: 4 }, }); // 2. Query with filtering, ordering, and eager loading const published = await Article.query() .with("author", "comments") .where("is_published", true) .orderBy("created_at", "desc") .limit(10) .get(); // 3. Update an existing instance article.title = "Mastering Veap ORM"; await article.save(); // 4. Paginate results const page = await Article.query() .where("is_published", true) .paginate(1, 20); // page => { data: Article[], total, perPage: 20, currentPage: 1, lastPage } ``` ## Relationships `@veap/database` supports standard relational associations (`belongsTo`, `hasOne`, `hasMany`, `belongsToMany`) as well as polymorphic associations (`morphTo`, `morphOne`, `morphMany`, `morphToMany`) used by Veap's ORM traits: | Relation method | Signature | Description | | :--- | :--- | :--- | | `this.belongsTo` | `belongsTo(RelatedModel, foreignKey?, ownerKey?)` | Inverse one-to-one or one-to-many relation where the current model holds the foreign key. | | `this.hasOne` | `hasOne(RelatedModel, foreignKey?, localKey?)` | One-to-one relation where `RelatedModel` holds the foreign key. | | `this.hasMany` | `hasMany(RelatedModel, foreignKey?, localKey?)` | One-to-many relation where `RelatedModel` holds the foreign key. | | `this.belongsToMany` | `belongsToMany(RelatedModel, pivotTable?, foreignPivotKey?, relatedPivotKey?)` | Many-to-many relation over a join table (supports `.attach()`, `.detach()`, and `.sync()`). | | `this.morphMany` | `morphMany(RelatedModel, morphName)` | Polymorphic one-to-many relation using `_type` and `_id` columns. | | `this.morphToMany` | `morphToMany(RelatedModel, morphName, pivotTable?)` | Polymorphic many-to-many relation over a shared pivot table. | ## Model lifecycle hooks and events Every `Model` subclass can define instance lifecycle hook methods that run automatically before and after persistence operations. Returning `false` from any `"before*"` (or `creating` / `updating` / `saving` / `deleting` / `restoring`) hook cancels the database operation cleanly: | Lifecycle stage | Hook methods invoked on `this` | Dispatched `EventBus` topics | | :--- | :--- | :--- | | Before insert or update | `saving()` / `beforeSave()` | `model:saving:`, `model:saving` | | After insert or update | `saved()` / `afterSave()` | `model:saved:`, `model:saved` | | Before insert | `creating()` / `beforeCreate()` | `model:creating:`, `model:creating` | | After insert | `created()` / `afterCreate()` | `model:created:`, `model:created` | | Before update | `updating()` / `beforeUpdate()` | `model:updating:`, `model:updating` | | After update | `updated()` / `afterUpdate()` | `model:updated:`, `model:updated` | | Before delete | `deleting()` / `beforeDelete()` | `model:deleting:`, `model:deleting` | | After delete | `deleted()` / `afterDelete()` | `model:deleted:`, `model:deleted` | | Before restore | `restoring()` / `beforeRestore()` | `model:restoring:`, `model:restoring` | | After restore | `restored()` / `afterRestore()` | `model:restored:`, `model:restored` | ```ts import { Model } from "@veap/database"; export class Article extends Model { static override table = "articles"; declare title: string; declare slug: string; protected beforeSave(): void { if (!this.slug && this.title) { this.slug = this.title.toLowerCase().trim().replace(/\s+/g, "-"); } } } ``` ## Soft deletes and global scopes Enable soft deletes on a model by setting `static override softDeletes = true` (which uses the `deleted_at` column by default, configurable via `static override deletedAtColumn`). Calling `await model.delete()` sets `deleted_at` to the current timestamp, and standard `Model.query()` calls automatically exclude soft-deleted rows: ```ts import { Model } from "@veap/database"; export class Invoice extends Model { static override table = "invoices"; static override softDeletes = true; } // Soft-delete an instance await invoice.delete(); console.log(invoice.trashed()); // true // Include or isolate soft-deleted records in queries const allInvoices = await Invoice.withTrashed().get(); const archivedOnly = await Invoice.onlyTrashed().get(); // Restore or permanently delete await invoice.restore(); await invoice.forceDelete(); ``` ### Registering global scopes Use `Model.addGlobalScope(name, callback)` to apply automatic constraints to every query on a model unless explicitly removed with `.withoutGlobalScope(name)`: ```ts Article.addGlobalScope("publishedOnly", (builder) => { builder.where("is_published", true); }); ``` ### Local query scopes Local scopes allow you to define common, reusable sets of query constraints directly on your model class. Define methods prefixed with `scope`: ```ts import { Model, type QueryBuilder } from "@veap/database"; export class Article extends Model { static override table = "articles"; // Define local scopes with the scope prefix static scopeActive(query: QueryBuilder
): QueryBuilder
{ return query.where("is_published", true); } static scopePopular( query: QueryBuilder
, minViews = 1000, ): QueryBuilder
{ return query.where("views_count", ">=", minViews); } static scopeCategory( query: QueryBuilder
, categoryId: number, ): QueryBuilder
{ return query.where("category_id", categoryId); } } ``` You can invoke local scopes dynamically on `Model.query()` by calling the method name without the `scope` prefix, or explicitly via `.scope(name, ...args)`: ```ts // Dynamic invocation via Proxy dispatch const trending = await Article.query() .active() .popular(5000) .orderBy("created_at", "desc") .get(); // Explicit invocation via .scope() const categoryArticles = await Article.query() .scope("active") .scope("category", 4) .get(); ``` ## Eager loading and preventing N+1 queries (`.with`) When rendering lists of models and their associated relations, lazy loading each relation inside a loop triggers the N+1 query problem. Use `.with()` to eager load relations in bulk queries: ```ts // Eager load single or multiple relations const articles = await Article.query() .with("author", "tags") .where("is_published", true) .get(); // Eager load nested relations (such as author and their profile) const posts = await Article.query() .with("author.profile", "comments.author") .get(); ``` ### Filtering queries by relation existence (`whereHas` and `whereDoesntHave`) Use `whereHas` or `whereDoesntHave` to filter models based on whether they have matching child or pivot records: ```ts // Find articles that have at least one approved comment const commentedArticles = await Article.query() .whereHas("comments", (query) => { query.where("status", "approved"); }) .get(); // Find users who do not have any orders const inactiveUsers = await User.query() .whereDoesntHave("orders") .get(); // Count related models as an extra attribute (such as comments_count) const articlesWithCounts = await Article.query() .withCount("comments") .get(); ``` ## Advanced queries and raw Knex (`.toKnex`) When you need specialized SQL features not covered by Active Record (such as row-level locking with `forUpdate()`, Common Table Expressions, or window functions), call `.toKnex()` to access the underlying Knex query builder directly: ```ts import { transaction } from "@veap/database"; await transaction(async () => { // Acquire an exclusive lock on the row until the transaction commits const account = await BankAccount.query() .where("id", accountId) .toKnex() .forUpdate() .first(); // Perform balance updates securely }); ``` --- # Migrations, transactions, and seeders URL: https://veap.pl/docs/packages/database/migrations-and-transactions Section: Packages (@veap/*) / @veap/database `@veap/database` manages schema versions through reversible migrations, propagates database transactions across async call stacks using `AsyncLocalStorage`, and includes model factories and seeders for test fixtures and development data. ## Schema migrations and CLI commands Migrations live in your project's `migrations/` folder and use the `Schema` facade from `@veap/database` (or `@veap/database/migrations`). Each migration exports a unique `name`, an `up()` function, and an optional `down()` function: ```ts // migrations/20261001_create_articles.ts import { type Migration, Schema } from "@veap/database"; export const createArticlesMigration: Migration = { name: "20261001_create_articles", async up() { await Schema.create("articles", (table) => { table.increments("id").primary(); table.integer("author_id").notNullable().index(); table.string("title").notNullable(); table.string("slug").notNullable().unique(); table.text("content").notNullable(); table.boolean("is_published").notNullable().defaultTo(false); table.json("metadata").nullable(); table.timestamps(true, true); }); }, async down() { await Schema.dropIfExists("articles"); }, }; ``` ### Managing migrations from the CLI `MigrationServiceProvider` registers four console commands in the `veap` CLI: - `bun veap make:migration ` — Scaffolds a timestamped migration file in `migrations/` and adds it to `migrations/index.ts`. - `bun veap migrate` — Runs all pending migrations. - `bun veap migrate:status` — Displays applied and pending migrations with their batch numbers. - `bun veap migrate:rollback` — Rolls back the most recent migration batch (pass `--force` in non-interactive CI environments). ## Transactions with `AsyncLocalStorage` Wrap multi-step database writes in `transaction(async () => { ... })`. Because `@veap/database` stores the active Knex transaction in Node's `AsyncLocalStorage`, every `Model.query()`, `model.save()`, and `dbClient` call inside the callback automatically participates in the transaction without passing a transaction object through function arguments: ```ts import { transaction } from "@veap/database"; import { Article } from "@/lib/models/article"; import { Author } from "@/lib/models/author"; await transaction(async () => { const author = await Author.create({ name: "Ada Lovelace", email: "ada@example.com", }); await Article.create({ author_id: author.id, title: "Notes on the Analytical Engine", slug: "notes-on-the-analytical-engine", content: "...", is_published: true, }); // Throws anywhere inside this block roll back both inserts automatically. }); ``` ## Model factories and database seeders `@veap/database` includes `defineFactory` (and the `Factory` base class) along with `Seeder` and `runSeeders` for generating test fixtures and initial development data: ```ts import { defineFactory, runSeeders, Seeder } from "@veap/database"; import { Article } from "./models/article"; export const ArticleFactory = defineFactory(Article, () => ({ title: "Sample Article", slug: `sample-article-${Math.random().toString(36).slice(2, 8)}`, content: "Generated by ArticleFactory", is_published: true, })); export class DatabaseSeeder extends Seeder { async run(): Promise { // Create and persist 5 articles with rotating sequence states await ArticleFactory.count(5) .sequence({ is_published: true }, { is_published: false }) .create(); } } // Run seeders wrapped in an AsyncLocalStorage database transaction await runSeeders(DatabaseSeeder, { inTransaction: true }); ``` --- # Seeders and model factories (@veap/database) URL: https://veap.pl/docs/packages/database/seeders-and-factories Section: Packages (@veap/*) / @veap/database `@veap/database` includes a fluent model factory engine and database seeder runner. These tools let you populate development databases with realistic fake data, generate fixtures for automated tests, and execute reproducible initial data seeds. ## Defining model factories (`defineFactory`) Model factories generate mock records with default attributes, state modifications, and sequential values. Use `defineFactory(ModelClass, definitionFn)` to declare a factory: ```ts // lib/database/factories/user.factory.ts import { defineFactory } from "@veap/database"; import { User } from "../../models/user"; export const UserFactory = defineFactory(User, () => ({ username: `user_${Math.random().toString(36).slice(2, 8)}`, email: `user_${Math.random().toString(36).slice(2, 8)}@example.com`, role: "member", isActive: true, })); ``` You can also use libraries such as `@faker-js/faker` inside the definition function for realistic names, addresses, and timestamps. ### Making and creating instances Factories support both in-memory generation (`make`, `makeOne`) and database persistence (`create`, `createOne`): ```ts // Instantiate in memory without persisting to the database const unsavedUser = await UserFactory.makeOne(); const unsavedBatch = await UserFactory(5).make(); // Create and persist directly to the database const savedUser = await UserFactory.createOne({ role: "admin" }); const savedUsers = await UserFactory.count(10).create(); ``` ### Factory states and sequences Use `.state()` to apply attribute transformations for specific scenarios, and `.sequence()` to cycle through a list of values: ```ts // Apply state overrides const adminUsers = await UserFactory.count(3) .state({ role: "admin" }) .create(); // Apply dynamic attribute states const verifiedUsers = await UserFactory.count(5) .state((attributes) => ({ emailVerifiedAt: new Date(), email: `verified_${attributes.username}@example.com`, })) .create(); // Cycle through alternating values const mixedRoleUsers = await UserFactory.count(4) .sequence({ role: "member" }, { role: "editor" }, { role: "admin" }) .create(); ``` ## Writing database seeders (`Seeder`) Seeders populate your tables with initial system records (such as default roles, permissions, or administrative accounts) and test fixtures. Extend the `Seeder` base class and implement `run()`: ```ts // lib/database/seeders/roles.seeder.ts import { Seeder } from "@veap/database"; import { Role } from "../../models/role"; export class RolesSeeder extends Seeder { async run(): Promise { const roles = ["admin", "editor", "member"]; for (const name of roles) { await Role.query().insert({ name, createdAt: new Date(), updatedAt: new Date(), }); } } } ``` ### Composing seeders (`this.call`) In larger applications, create a master `DatabaseSeeder` that invokes other seeders sequentially using `this.call()`: ```ts // lib/database/seeders/database.seeder.ts import { Seeder } from "@veap/database"; import { UserFactory } from "../factories/user.factory"; import { PermissionsSeeder } from "./permissions.seeder"; import { RolesSeeder } from "./roles.seeder"; export class DatabaseSeeder extends Seeder { async run(): Promise { // 1. Run role and permission seeders in order await this.call([RolesSeeder, PermissionsSeeder]); // 2. Seed test users with factories await UserFactory.count(20).create(); } } ``` ### Executing seeders programmatically (`runSeeders`) Call `runSeeders` to execute one or more seeder classes. Pass `inTransaction: true` to wrap all seeders in a single rollbackable database transaction: ```ts import { runSeeders } from "@veap/database"; import { DatabaseSeeder } from "./database/seeders/database.seeder"; await runSeeders(DatabaseSeeder, { inTransaction: true, }); ``` ## Running seeds with custom CLI commands To run seeders from your terminal via `bun veap seed`, create a custom console command extending `Command` from `@veap/kernel/cli`: ```ts // lib/commands/seed.command.ts import { runSeeders } from "@veap/database"; import { Command, type CommandInput, type Output } from "@veap/kernel/cli"; import { DatabaseSeeder } from "../database/seeders/database.seeder"; export class SeedCommand extends Command { static override signature = "db:seed {--f|force : Run without confirmation prompt}"; static override description = "Seed the database with records"; async handle(input: CommandInput, output: Output): Promise { if (!input.flag("force") && !this.context.flags.force) { const confirmed = await output.confirm( `Seed database in ${this.context.env} environment?`, ); if (!confirmed) { output.warn("Seeding aborted."); return 0; } } output.info("Seeding database records..."); await runSeeders(DatabaseSeeder, { inTransaction: true }); output.success("Database seeded successfully."); return 0; } } ``` Register `SeedCommand` in `lib/veap.ts` with `.withCommands([SeedCommand])`. You can then run `bun veap db:seed` or `bun veap db:seed --force` during automated testing or container initialization. --- # Polymorphic ORM traits URL: https://veap.pl/docs/packages/orm-traits Section: Packages (@veap/*) / ORM traits Veap provides four reusable polymorphic ORM traits that extend any `@veap/database` `Model` subclass through TypeScript class mixins and shared polymorphic database tables—without adding extra columns to your domain tables. ## How Veap ORM traits work Each trait package exports: 1. **A class mixin function** (`Translatable(Model)`, `Taggable(Model)`, `Categorizable(Model)`, `Commentable(Model)`) that attaches typed relation methods, query scopes, and lifecycle hooks (`afterSave`, `beforeDelete`) to your model. 2. **Polymorphic Active Record models** (`Translation`, `Tag`, `Category`, `Comment`) backed by shared `_type` and `_id` columns so multiple models (`Article`, `Product`, `Course`) share a single set of trait tables. 3. **Reversible migrations** exported from `/migrations` that you spread into your application's `migrations/index.ts` array. ```ts // lib/models/article.ts import { Model } from "@veap/database"; import { Categorizable } from "@veap/categorizable"; import { Commentable } from "@veap/commentable"; import { Taggable } from "@veap/taggable"; import { Translatable } from "@veap/translatable"; export class Article extends Commentable( Categorizable(Taggable(Translatable(Model))), ) { static override table = "articles"; static translatable = ["title", "content"]; } ``` ## Available ORM trait packages Explore each trait package in detail: - [`@veap/translatable`](https://veap.pl/docs/packages/orm-traits/translatable) — Multilingual model attributes stored in a polymorphic `translations` table with automatic proxy access and locale-scoped queries. - [`@veap/taggable`](https://veap.pl/docs/packages/orm-traits/taggable) — Polymorphic, translatable tags (`tags` and `taggables` tables) with `syncTags`, `attachTags`, and `whereTag` / `whereAnyTag` / `whereAllTags` query scopes. - [`@veap/categorizable`](https://veap.pl/docs/packages/orm-traits/categorizable) — Hierarchical parent-child categories (`categories` and `categorizables` tables) with tree queries and ``. - [`@veap/commentable`](https://veap.pl/docs/packages/orm-traits/commentable) — Threaded polymorphic comments, nested replies, ratings, and moderation statuses (`pending`, `approved`, `spam`, `rejected`, `trash`). --- # Polymorphic translations (@veap/translatable) URL: https://veap.pl/docs/packages/orm-traits/translatable Section: Packages (@veap/*) / ORM traits `@veap/translatable` adds multilingual attribute storage to any `@veap/database` model without altering your model tables. All translated field values across all models (`BlogPost`, `Product`, `Category`, `Tag`) are stored in a single polymorphic `translations` table using `morphMany` relations. ## Installation and migration Install `@veap/translatable` alongside `@veap/database`: ```bash bun add @veap/translatable @veap/database ``` Register or run the package migrations so the `translations` table is created in your database: ```ts import { runMigrations } from "@veap/database"; import { migrations as translatableMigrations } from "@veap/translatable/migrations"; await runMigrations("@veap/translatable", translatableMigrations); ``` ## Making a model translatable You can either extend `TranslatableModel` directly or wrap an existing base class with the `Translatable(...)` mixin. Declare the list of localized field names in `static override translatable`: ```ts import { Model } from "@veap/database"; import { Translatable } from "@veap/translatable"; export class Article extends Translatable(Model) { static override table = "articles"; static override translatable = ["title", "slug", "excerpt", "content"]; declare id: string; declare status: "draft" | "published"; declare title: string; declare slug: string; declare excerpt: string; declare content: string; } ``` ## Reading and writing translations `Translatable` wraps model instances in a `Proxy` so reading or assigning a translatable property (`article.title`) automatically targets the active locale, while explicit methods let you read or populate multiple locales at once. ```ts // 1. Set translations for multiple locales at once const article = new Article({ status: "published" }); article.fillTranslations({ en: { title: "Hello World", slug: "hello-world", content: "Welcome to Veap.", }, de: { title: "Hallo Welt", slug: "hallo-welt", content: "Willkommen bei Veap.", }, }); // Saving the model automatically persists all staged translations await article.save(); // 2. Read for a specific locale or active locale const deTitle = article.getTranslation("title", "de"); // "Hallo Welt" const activeTitle = article.title; // Resolves active locale (falls back to fallbackLocale) // 3. Inspect all locales for a field or for the entire model const titleMap = article.getTranslations("title"); // => { en: "Hello World", de: "Hallo Welt" } ``` ## Querying translated attributes Calling `Article.query()` on a translatable model returns a `TranslatableQueryBuilder` equipped with locale-aware filtering, sorting, and eager-loading scopes: ```ts // Filter by exact translation in a specific locale const post = await Article.query() .whereTranslation("slug", "hallo-welt", "de") .first(); // Search translated fields using SQL LIKE in the current locale const matches = await Article.query() .whereTranslationLike("title", "%Veap%") .get(); // Eager-load translations for specific locales (or all locales) to avoid N+1 queries const feed = await Article.query() .withTranslations(["en", "de"]) .orderByTranslation("title", "asc", "de") .get(); ``` ## Scoping the active locale Use `setLocale`, `setFallbackLocale`, or the `AsyncLocalStorage`-backed `withLocale` helper to control which language `model.` resolves during a request: ```ts import { setFallbackLocale, setLocale, withLocale } from "@veap/translatable"; setLocale("en"); setFallbackLocale("en"); // Scope the active locale for a specific async execution flow await withLocale("de", async () => { const articles = await Article.query().withTranslations(["de", "en"]).get(); console.log(articles[0]?.title); // "Hallo Welt" }); ``` --- # Polymorphic tagging (@veap/taggable) URL: https://veap.pl/docs/packages/orm-traits/taggable Section: Packages (@veap/*) / ORM traits `@veap/taggable` lets you attach, sync, detach, and query tags on any `@veap/database` model (`Article`, `Product`, `MediaFile`) through shared `tags` and `taggables` tables. Because `Tag` itself uses `@veap/translatable`, tag names and slugs are automatically multilingual. ## Installation and migration Install `@veap/taggable` alongside `@veap/database` and `@veap/translatable`: ```bash bun add @veap/taggable @veap/database @veap/translatable ``` Run the migrations for both `@veap/translatable` and `@veap/taggable`: ```ts import { runMigrations } from "@veap/database"; import { migrations as taggableMigrations } from "@veap/taggable/migrations"; import { migrations as translatableMigrations } from "@veap/translatable/migrations"; await runMigrations("@veap/translatable", translatableMigrations); await runMigrations("@veap/taggable", taggableMigrations); ``` ## Adding `Taggable` to a model Apply the `Taggable` mixin to any `Model` (or extend `TaggableModel`). You can compose `Taggable` with other ORM traits such as `Translatable` and `Categorizable`: ```ts import { Model } from "@veap/database"; import { Taggable } from "@veap/taggable"; import { Translatable } from "@veap/translatable"; export class Article extends Taggable(Translatable(Model)) { static override table = "articles"; static override translatable = ["title", "content"]; } ``` ## Attaching, syncing, and detaching tags Tag mutation methods accept tag names, slugs, UUIDs, or `Tag` model instances. When you pass a tag name that does not exist yet, `@veap/taggable` creates the `Tag` record automatically via `Tag.findOrCreateByName`. ```ts // Attach additional tags without removing existing ones await article.attachTags(["TypeScript", "Next.js"]); // Replace all attached tags with the provided list await article.syncTags(["Architecture", "ORM", "TypeScript"]); // Detach a single tag or clear all tags on the model await article.detachTags("ORM"); await article.detachTags(); // Detaches all tags ``` ## Querying models by tags `Taggable` extends your model's query builder with four expressive scopes for filtering and eager-loading tags: ```ts // 1. Models tagged with a specific tag (by name, slug, or ID) const tsArticles = await Article.query().whereTag("typescript").get(); // 2. Models matching ANY of the given tags const webArticles = await Article.query() .whereAnyTag(["typescript", "nextjs"]) .get(); // 3. Models matching ALL of the given tags const strictMatches = await Article.query() .whereAllTags(["architecture", "typescript"]) .get(); // 4. Eager-load attached tags and their localized names const articlesWithTags = await Article.query().withTags().get(); ``` ## Using the `` UI component `@veap/taggable/ui` exports a client-side `` component with autocomplete suggestions and keyboard chip creation: ```tsx "use client"; import { TagInput } from "@veap/taggable/ui"; import { useState } from "react"; export function ArticleTagsField({ availableTags }: { availableTags: string[] }) { const [tags, setTags] = useState(["TypeScript"]); return ( ); } ``` --- # Hierarchical categories (@veap/categorizable) URL: https://veap.pl/docs/packages/orm-traits/categorizable Section: Packages (@veap/*) / ORM traits `@veap/categorizable` organizes `@veap/database` models into nested parent-child category trees using polymorphic `categories` and `categorizables` tables. Categories support both single primary assignment (`category()`) and multi-category assignment (`categories()`), with localized names and slugs powered by `@veap/translatable`. ## Installation and migration Install `@veap/categorizable` alongside `@veap/database` and `@veap/translatable`: ```bash bun add @veap/categorizable @veap/database @veap/translatable ``` Run the migrations to create the `translations`, `categories`, and `categorizables` tables: ```ts import { migrations as categorizableMigrations } from "@veap/categorizable/migrations"; import { runMigrations } from "@veap/database"; import { migrations as translatableMigrations } from "@veap/translatable/migrations"; await runMigrations("@veap/translatable", translatableMigrations); await runMigrations("@veap/categorizable", categorizableMigrations); ``` ## Adding `Categorizable` to a model Wrap your model with the `Categorizable` mixin or extend `CategorizableModel`: ```ts import { Categorizable } from "@veap/categorizable"; import { Model } from "@veap/database"; export class Product extends Categorizable(Model) { static override table = "products"; declare id: string; declare sku: string; } ``` ## Assigning and syncing categories You can pass category names, slugs, UUIDs, or `Category` instances. Passing a name that does not exist yet creates the category automatically. ```ts import { Category } from "@veap/categorizable"; // Create a parent-child hierarchy explicitly const hardware = await Category.findOrCreateByName("Hardware"); const laptops = await Category.create({ parentId: hardware.id, name: "Laptops", slug: "laptops", }); // Assign a single primary category await product.assignCategory(laptops); // Or sync multiple categories at once await product.syncCategories(["Hardware", "Laptops"]); // Remove all category associations from this model await product.detachCategories(); ``` ## Querying by category Filter models by category name, slug, or ID using `whereCategory` and `whereInCategories`, and eager-load attached categories with `withCategories()`: ```ts // Find products in a single category const laptopsList = await Product.query().whereCategory("laptops").get(); // Find products belonging to any of the listed categories const electronics = await Product.query() .whereInCategories(["laptops", "workstations"]) .withCategories() .get(); ``` ## Using `` `@veap/categorizable/ui` provides a collapsible `` component for selecting single or multiple categories from a nested hierarchy: ```tsx "use client"; import { CategoryTreeSelect, type CategoryTreeNode } from "@veap/categorizable/ui"; import { useState } from "react"; export function ProductCategoryPicker({ tree }: { tree: CategoryTreeNode[] }) { const [selectedIds, setSelectedIds] = useState([]); return ( ); } ``` --- # Threaded comments (@veap/commentable) URL: https://veap.pl/docs/packages/orm-traits/commentable Section: Packages (@veap/*) / ORM traits `@veap/commentable` attaches polymorphic, threaded comments and moderation workflows to any `@veap/database` model (`BlogPost`, `Product`, `IssueTicket`). All comments live in a shared `comments` table with parent-child reply nesting (`parentId`) and status tracking (`pending`, `approved`, `spam`, `rejected`, `trash`). ## Installation and migration Install `@veap/commentable` alongside `@veap/database`: ```bash bun add @veap/commentable @veap/database ``` Run the package migrations to create the `comments` table: ```ts import { migrations as commentableMigrations } from "@veap/commentable/migrations"; import { runMigrations } from "@veap/database"; await runMigrations("@veap/commentable", commentableMigrations); ``` ## Adding `Commentable` to a model Apply the `Commentable` mixin to your model class or extend `CommentableModel`: ```ts import { Commentable } from "@veap/commentable"; import { Model } from "@veap/database"; export class BlogPost extends Commentable(Model) { static override table = "blog_posts"; static morphAlias = "post"; declare id: string; declare title: string; } ``` ## Creating replies and moderating comments Use `model.addComment(...)` to attach a top-level comment or a nested reply (via `parentId`), and use `approvedComments()` or the `Comment` model's moderation helpers to manage visibility: ```ts // 1. Add a comment from an authenticated user const rootComment = await post.addComment({ content: "Clear walkthrough of the IoC lifecycle!", authorId: currentUser.id, status: "approved", }); // 2. Add a guest comment (pending moderation by default) const guestComment = await post.addComment({ content: "Does this work with SQLite and PostgreSQL?", authorName: "Alex Rivera", authorEmail: "alex@example.com", status: "pending", }); // 3. Reply to an existing comment await post.addComment({ content: "Yes, both SQLite and PostgreSQL are supported out of the box.", authorId: adminUser.id, parentId: guestComment.id, status: "approved", }); // 4. Fetch approved comments for display const visibleComments = await post.approvedComments(); ``` ## Query scopes and UI components Filter models that have comments with `whereHasComments()` and eager-load comments via `withComments()`: ```ts const discussedPosts = await BlogPost.query() .whereHasComments() .withComments() .get(); ``` ### Rendering `` `@veap/commentable/ui` provides `` and `` components for rendering nested discussion threads and handling new submissions: ```tsx import { CommentSection } from "@veap/commentable/ui"; export function ArticleDiscussion({ postId, comments, currentUserId, }: { postId: string; comments: any[]; currentUserId?: string; }) { return ( { "use server"; // Persist comment via post.addComment(payload) }} /> ); } ``` --- # Authentication (@veap/auth) URL: https://veap.pl/docs/packages/auth Section: Packages (@veap/*) / @veap/auth `@veap/auth` provides database-backed session authentication, password hashing (`bcrypt`), AES-GCM secret encryption, Role-Based Access Control (RBAC), email verification, password reset flows, and ready-to-use Next.js Server Actions. ## Registering `AuthServiceProvider` Install `@veap/auth` alongside `@veap/kernel` and `@veap/database`, then register `AuthServiceProvider` in your `lib/veap.ts` composition root: ```ts // lib/veap.ts import { AuthServiceProvider } from "@veap/auth"; import { DatabaseServiceProvider } from "@veap/database"; import { Application } from "@veap/kernel"; export const app = Application.configure() .use(DatabaseServiceProvider) .use( (c) => new AuthServiceProvider(c, { autoMigrate: true, }), ) .create(); ``` When `autoMigrate: true` is enabled (the default), `AuthServiceProvider.boot()` automatically runs the `@veap/auth` schema migrations (`users`, `sessions`, `roles`, `permissions`, `user_roles`, `role_permissions`, `email_verification_requests`, and `password_reset_sessions`). ## Reading the active user and session on the server In React Server Components, Server Actions, or Route Handlers, call `getCurrentSession()` to inspect the active `user` and `session`: ```tsx // app/dashboard/page.tsx import { getCurrentSession } from "@veap/auth"; import { redirect } from "next/navigation"; import { initializeSystem } from "@/lib/veap"; export default async function DashboardPage() { await initializeSystem(); const session = await getCurrentSession(); if (!session?.user) { redirect("/login"); } return (

Welcome back, {session.user.name}

{session.user.email}

); } ``` `getCurrentSession()` supports both web browsers and API or mobile clients: 1. **Bearer token header**: It checks for an `Authorization: Bearer ` header in the incoming HTTP request first. 2. **Session cookie fallback**: If no Bearer header is present, it falls back to the HTTP-only `session` cookie managed by the kernel cookie store. 3. **Automatic augmentation**: Once the token is verified against the database, user roles, permissions, and session metadata are attached automatically. ## Bearer token authentication (mobile and API clients) Mobile applications (for example built with React Native or Expo) and external REST API clients cannot always rely on browser cookies. `@veap/auth` natively supports Bearer token authentication so both web and mobile applications share the same session database and access control rules. ### Obtaining the session token When an account logs in or registers via `AuthService`, the returned `AuthResponse` contains a `token` property with the raw session token: ```ts // app/api/auth/login/route.ts import { AuthService } from "@veap/auth"; import { app } from "@veap/kernel"; import { NextResponse } from "next/server"; import { initializeSystem } from "@/lib/veap"; export async function POST(req: Request) { await initializeSystem(); const body = await req.json(); const auth = await app(AuthService); const response = await auth.signIn({ email: body.email, password: body.password, }); if (response.status !== "SUCCESS") { return NextResponse.json( { error: response.message || "Invalid credentials" }, { status: 401 }, ); } return NextResponse.json({ token: response.token, user: response.user, }); } ``` > [!NOTE] > Web applications that use Server Actions can ignore the `token` property > because `AuthService` also sets the HTTP-only cookie automatically. Mobile and > external API clients use this returned `token` string for subsequent requests. ### Sending the Bearer token Store the token securely on the device (for example using `expo-secure-store` in Expo or an encrypted device keychain), then send it in the `Authorization` header on subsequent requests: ```ts // mobile-client/api.ts import * as SecureStore from "expo-secure-store"; export async function fetchProfile() { const token = await SecureStore.getItemAsync("session_token"); const response = await fetch("https://api.example.com/api/profile", { headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); return await response.json(); } ``` In your Next.js Route Handlers, Server Components, and plugin actions, `getCurrentSession()` reads the Bearer token automatically and authenticates the user with full roles and permissions. ## Client context and `useUser()` hook To read the authenticated user in Client Components (`"use client"`), wrap your layout in `` from `@veap/auth/client` and call `useUser()`: ```tsx // app/providers.tsx "use client"; import { AuthProvider } from "@veap/auth/client"; export function Providers({ children }: { children: React.ReactNode }) { // sessionEndpoint defaults to "/api/auth/me" return {children}; } ``` Expose the session endpoint using `handleAuthMe` from `@veap/auth/server`: ```ts // app/api/auth/me/route.ts export { handleAuthMe as GET } from "@veap/auth/server"; ``` Then read user state anywhere in client component trees: ```tsx // components/user-menu.tsx "use client"; import { useUser } from "@veap/auth/client"; export function UserMenu() { const { user, isLoading } = useUser(); if (isLoading) { return Loading session...; } if (!user) { return Sign in; } return Signed in as {user.name}; } ``` ## Next steps Continue exploring `@veap/auth`: - [Server Actions and RBAC](https://veap.pl/docs/packages/auth/actions-and-rbac) — Authenticate users with `signIn`, `signUp`, and `signOut`, and manage `Role` and `Permission` assignments. - [Verification, cryptography, and 2FA hooks](https://veap.pl/docs/packages/auth/verification-and-security) — Configure `ENCRYPTION_KEY`, run email verification and password reset flows, and register 2FA challenge validators. --- # Server Actions and RBAC URL: https://veap.pl/docs/packages/auth/actions-and-rbac Section: Packages (@veap/*) / @veap/auth `@veap/auth` provides ready-to-use Next.js Server Actions for login, registration, and session management alongside a database-backed Role-Based Access Control (RBAC) system. ## Server Actions (`signIn`, `signUp`, `signOut`) `@veap/auth` (and `@veap/auth/actions`) exports Next.js Server Actions returning typed `Result` payloads. Because `@veap/auth` communicates with cookies via `@veap/kernel`'s `COOKIE_STORE` port, session cookies are read and written automatically in Next.js: ```ts // app/actions/auth.ts "use server"; import { getCurrentSession, signIn, signOut, signUp } from "@veap/auth"; import { initializeSystem } from "@/lib/veap"; export async function loginAction(email: string, password: string) { await initializeSystem(); const result = await signIn({ email, password }); if (!result.success) { return result; } // Handle two-factor authentication (2FA) challenge if enabled on the account if (result.data.status === "CHALLENGE_REQUIRED") { return result; } return result; } export async function registerAction( username: string, email: string, password: string, ) { await initializeSystem(); return await signUp({ username, email, password }); } export async function logoutAction() { await initializeSystem(); return await signOut(); } export async function fetchSessionAction() { await initializeSystem(); return await getCurrentSession(); } ``` When `signIn` or `signUp` succeeds, the returned `AuthResponse` (`result.data`) contains: - `status: "SUCCESS"` - `user`: Authenticated `FullUser` object with augmented roles and permissions. - `session`: Active database `Session` record. - `token?: string`: Raw session token string. Web browsers store the session token in an HTTP-only cookie automatically. For mobile applications and external API clients, extract `result.data.token` and store it securely on the device, then pass it in the `Authorization` header (`Authorization: Bearer `) on subsequent requests. ### Authenticating API routes with Bearer tokens `getCurrentSession()` automatically inspects the `Authorization: Bearer ` header before falling back to cookies: ```ts // app/api/auth/me/route.ts // You can also simply re-export: export { handleAuthMe as GET } from "@veap/auth/server"; import { NextResponse } from "next/server"; import { getCurrentSession } from "@veap/auth"; import { initializeSystem } from "@/lib/veap"; export async function GET() { await initializeSystem(); const { user, session } = await getCurrentSession(); if (!user || !session) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } return NextResponse.json({ user: { id: user.id, name: user.name, email: user.email, roles: user.roles ?? [], }, }); } ``` ## Role-Based Access Control (RBAC) `@veap/auth/models` provides `User`, `Role`, and `Permission` Active Record models linked by many-to-many pivot tables (`users_to_roles` and `roles_to_permissions`). You can also use the RBAC Server Actions (`assignRoleToUser`, `assignPermissionToRole`, `createRole`, `createPermission`) from `@veap/auth`: ```ts import { Permission, Role, User } from "@veap/auth/models"; import { hashPassword } from "@veap/auth/server"; // Create a role and permission const editorRole = await Role.create({ name: "editor", description: "Can create and publish articles", }); const publishPerm = await Permission.create({ name: "articles:publish", description: "Publish articles", }); await editorRole.permissions().attach(publishPerm.id); // Create a user and attach the role const user = await User.create({ name: "ada", email: "ada@example.com", password: await hashPassword("StrongPassword!123"), recovery_code: Buffer.from("recovery-secret-bytes"), }); await user.roles().attach(editorRole.id); ``` ## Shared Zod validation schemas (`@veap/auth/validation`) Import client-safe Zod schemas from `@veap/auth/validation` (or `@veap/auth`) to validate forms on both the client and server without pulling database dependencies into client bundles: - `loginSchema` (`email`, `password`, `remember?`) - `registerSchema` (`username`, `email`, `password`, `terms`) - `forgotPasswordSchema` (`email`) - `resetPasswordSchema` (`password`, `confirm`) - `verifyEmailSchema` (`code`) - `totpSetupSchema`, `totpVerifySchema`, and `backupCodeSchema` --- # Verification, cryptography, and 2FA hooks URL: https://veap.pl/docs/packages/auth/verification-and-security Section: Packages (@veap/*) / @veap/auth `@veap/auth` includes AES-GCM encryption and `bcrypt` password hashing helpers, email verification and password reset workflows, and extensible hooks for Multi-Factor Authentication (2FA) and session augmentation. ## Cryptography and `ENCRYPTION_KEY` `@veap/auth/server` uses AES-GCM to encrypt sensitive user secrets (such as TOTP keys and recovery codes) and `bcrypt` to hash passwords. On startup, `@veap/auth` validates that `process.env.ENCRYPTION_KEY` is set to a base64-encoded 16, 24, or 32-byte key. Generate a valid 16-byte (128-bit) key for your `.env` file using OpenSSL: ```bash openssl rand -base64 16 ``` You can also use the cryptographic helpers from `@veap/auth/server` directly in your services: ```ts import { decryptToString, encryptString, hashPassword, verifyPasswordHash, } from "@veap/auth/server"; const passwordHash = await hashPassword("MySecretPassword123!"); const isValid = await verifyPasswordHash(passwordHash, "MySecretPassword123!"); const ciphertextBytes = encryptString("totp-secret-seed"); const plaintext = decryptToString(ciphertextBytes); ``` ## Email verification and password reset flows `@veap/auth` ships server facades and database tables (`email_verification_requests` and `password_reset_sessions`) for verifying user email addresses and handling forgot-password flows. Delivery is handled through the `IAuthNotificationService` port, which dispatches via `@veap/mail` when configured: ```ts import { createEmailVerificationRequest, createPasswordResetSession, sendResetPassword, sendVerifyEmail, setEmailVerificationRequestCookie, setPasswordResetSessionTokenCookie, verifyResetCode, } from "@veap/auth"; import { generateSessionToken } from "@veap/auth/server"; // 1. Start email verification for a user const verificationReq = await createEmailVerificationRequest( user.id, user.email, ); await sendVerifyEmail(verificationReq.email, verificationReq.code); await setEmailVerificationRequestCookie(verificationReq); // 2. Start a password reset session const resetToken = generateSessionToken(); const resetSession = await createPasswordResetSession( resetToken, user.id, user.email, ); await sendResetPassword(resetSession.email, resetSession.code); await setPasswordResetSessionTokenCookie(resetToken, resetSession.expiresAt); // 3. Verify the 6-digit reset code submitted by the user const check = await verifyResetCode(resetSession.id, submittedCode); ``` ### Customizing notifications (`AUTH_NOTIFICATION_SERVICE`) To route authentication emails or SMS codes through a custom provider, implement `IAuthNotificationService` and bind it to `AUTH_NOTIFICATION_SERVICE`: ```ts import { AUTH_NOTIFICATION_SERVICE, type IAuthNotificationService, } from "@veap/auth"; import { ServiceProvider } from "@veap/kernel"; export class CustomAuthNotifier implements IAuthNotificationService { async sendVerificationEmail(email: string, code: string): Promise { // Deliver verification code via custom API or SMS } async sendPasswordReset(email: string, code: string): Promise { // Deliver password reset code via custom API } async sendRecoveryCode(email: string, code: string): Promise { // Deliver recovery code via custom API } } export class CustomNotificationProvider extends ServiceProvider { register(): void { this.container.register({ token: AUTH_NOTIFICATION_SERVICE, useClass: CustomAuthNotifier, singleton: true, }); } } ``` ## Identity augmentation and 2FA challenge hooks Plugins and application modules can enrich the authenticated `user` and `session` objects or intercept login to require Multi-Factor Authentication (2FA) challenges using extensible registry hooks: ### 1. Enriching user and session data (`registerIdentityAugmenter` and `registerSessionAugmenter`) Register an identity augmenter in a `ServiceProvider.boot()` method to attach custom properties (such as billing tier, active organization, or team permissions) to the `FullUser` object returned by `getCurrentSession()`: ```ts import { registerIdentityAugmenter, registerSessionAugmenter } from "@veap/auth"; // Append extra profile data to the authenticated user registerIdentityAugmenter("organization-data", async (user) => { // Query custom organization table return { organizationId: "org_123", plan: "pro", }; }); // Append custom flags to the active session registerSessionAugmenter("device-info", async (session) => { return { isTrustedDevice: true, }; }); ``` ### 2. Intercepting authentication (`registerAuthValidator`) Run custom validation during `signIn()`. If a validator returns `CHALLENGE_REQUIRED`, `signIn` halts before issuing a session cookie, enabling your UI to present a 2FA prompt: ```ts import { registerAuthValidator } from "@veap/auth"; registerAuthValidator("totp-check", async (userId) => { const user = await User.find(userId); if (user?.totp_key) { return { status: "CHALLENGE_REQUIRED", challengeType: "totp", userId, }; } return { status: "SUCCESS" }; }); ``` Once the user provides a valid 6-digit TOTP code, call `finalizeLogin(userId, { twoFactorVerified: true })` from your 2FA verification action to create the authenticated session cookie. ### 3. Enforcing route security gates (`registerSecurityRequirement`) Enforce prerequisite conditions (such as mandatory onboarding or email verification) before granting access to protected routes: ```ts import { registerSecurityRequirement } from "@veap/auth"; registerSecurityRequirement("verified-email-required", async (session, user, path) => { if (path?.startsWith("/dashboard") && !user.emailVerifiedAt) { return { satisfied: false, redirect: "/verify-email", }; } return { satisfied: true }; }); ``` --- # Modular plugin system (@veap/plugins) URL: https://veap.pl/docs/packages/plugins Section: Packages (@veap/*) / @veap/plugins `@veap/plugins` enables Veap applications to be extended with self-contained feature modules. A single plugin (`IPlugin`) can contribute virtual pages and API routes, UI extension slots, drag-and-drop dashboard widgets, navigation links, database migrations, translations, and event listeners. ## Registering `PluginsServiceProvider` In your composition root (`lib/veap.ts`), register `PluginsServiceProvider` and pass your `siteLayout` component along with the plugin array (typically combining generated registry plugins from `lib/plugins.gen.ts` with any local host plugins): ```ts // lib/veap.ts import { DatabaseServiceProvider } from "@veap/database"; import { Application } from "@veap/kernel"; import { PluginsServiceProvider } from "@veap/plugins"; import { SiteLayout } from "../components/site-layout"; import { plugins } from "./plugins.gen"; export const app = Application.configure() .use(DatabaseServiceProvider) .use( (c) => new PluginsServiceProvider(c, { siteLayout: SiteLayout, plugins, }), ) .withPlugins(plugins) .create(); ``` ## Authoring a plugin (`IPlugin`) Every plugin implements the `IPlugin` interface from `@veap/plugins`, declaring a `manifest` (typically built via `createManifestFromPackageJson(pkg)` or defined inline for local host plugins) along with optional lifecycle hooks (`init`, `onMigrate`, `onEnable`, `onDisable`) and feature definitions (`routeTree`, `migrations`, `hooks`, `locales`, and nested `plugins`). Navigation items, dashboard widgets, and UI extension points are registered programmatically via `MenuService`, `WidgetService`, and `ExtensionService` inside `init()`: ```tsx // lib/announcements-plugin.tsx import { Inject } from "@veap/kernel"; import { ExtensionService, type IPlugin, MenuService, WidgetService, } from "@veap/plugins"; export class AnnouncementsPlugin implements IPlugin { manifest = { id: "announcements", name: "Site Announcements", version: "1.0.0", description: "Adds a public navigation link, a header banner slot, and a dashboard widget", system: true, enabled: true, }; async init( @Inject(MenuService) menuService: MenuService, @Inject(WidgetService) widgetService: WidgetService, @Inject(ExtensionService) extensionService: ExtensionService, ) { menuService.addPublicItem({ id: "nav-announcements", title: "Announcements", url: "/announcements", priority: 20, }); widgetService.addWidget({ id: "announcements-summary", name: "Latest Announcements", area: "dashboard-main", component: () =>
No active alerts.
, priority: 15, }); extensionService.addExtension({ id: "announcements-banner", target: "site:header", point: "banner", priority: 10, component: () => (
Veap 0.2 is now live!
), }); } } export const announcementsPlugin = new AnnouncementsPlugin(); ``` ## Next steps Continue exploring `@veap/plugins`: - [Virtual routing, extension points, and widgets](https://veap.pl/docs/packages/plugins/routing-and-ui) — Mount `VeapRouter` catch-all routes, render `` and `` slots, and manage plugins with the `veap` CLI. --- # Virtual routing, extension points, and widgets URL: https://veap.pl/docs/packages/plugins/routing-and-ui Section: Packages (@veap/*) / @veap/plugins `@veap/plugins` provides `VeapRouter` for serving virtual page and API routes, `` and `` components for injecting plugin UI into host layouts, and CLI commands for installing, scaffolding, and ejecting plugins. ## Virtual routing (`VeapRouter`) To let active plugins serve pages and REST endpoints without modifying your `app/` directory every time a plugin is added or toggled, mount two thin catch-all forwarders in Next.js: 1. **Page forwarder (`app/[...catchAll]/page.tsx`)**: Builds the route tree with `buildRouteTree(true)` and renders `await VeapRouter({ tree, path, searchParams })`. 2. **API forwarder (`app/api/[...catchAll]/route.ts`)**: Builds the route tree and dispatches incoming requests with `handleVeapApiRequest(request, { tree, path, searchParams })` or custom `runApiPipeline()`. Any physical route file you place in `app/` takes precedence over virtual plugin routes at the same path. ## UI extension points and widget areas Host layouts and pages expose named slots where enabled plugins can inject React components or customizable dashboard widgets: ```tsx // Server Component slot rendering import { ExtensionPoint, WidgetArea } from "@veap/plugins/server"; export async function AdminDashboardLayout({ children, }: { children: React.ReactNode; }) { return (
{children}
); } ``` ### Client-side widgets and interactive extension points In interactive Client Components (`"use client"`), import `` and `` from `@veap/plugins/client`: ```tsx "use client"; import { ExtensionPointClient, WidgetAreaClient } from "@veap/plugins/client"; export function ClientDashboard() { return (
); } ``` ### Dynamic breadcrumbs (`getPluginBreadcrumbs`) The plugin router automatically generates breadcrumb hierarchies by traversing route nodes and navigation definitions: ```tsx import { getPluginBreadcrumbs } from "@veap/plugins"; export default async function BreadcrumbsBar({ path }: { path: string }) { const crumbs = await getPluginBreadcrumbs(path); return ( ); } ``` ### Confirming sensitive actions (`useConfirmAction`) The `useConfirmAction()` hook from `@veap/plugins/client` provides a standard confirmation prompt before executing high-risk Server Actions (such as deleting databases or revoking tokens): ```tsx "use client"; import { useConfirmAction } from "@veap/plugins/client"; import { deleteWorkspaceAction } from "@/app/actions/workspace"; export function DeleteWorkspaceButton({ id }: { id: string }) { const confirm = useConfirmAction(); const handleDelete = async () => { const result = await confirm({ title: "Delete Workspace", description: "This will permanently delete all records. Are you sure?", }); if (result.confirmed) { await deleteWorkspaceAction(id); } }; return ; } ``` If `@veap/action-confirm-plugin` is enabled, it presents a modal password/biometric challenge. If the plugin is disabled, it falls back to native browser confirmation. ## Managing plugins from the CLI `PluginsServiceProvider` registers six CLI commands in `@veap/kernel/cli` for managing plugins and regenerating `lib/plugins.gen.ts`: ```bash # List all registered plugins and their status bun veap list-plugins # Install a plugin package and regenerate lib/plugins.gen.ts bun veap add @veap/blog-plugin # Scaffold a new local plugin in plugins/-plugin bun veap make:plugin newsletter # Eject an npm plugin into plugins/blog-plugin as a local workspace package bun veap eject @veap/blog-plugin # Rescan package.json and regenerate lib/plugins.gen.ts bun veap plugins:sync ``` --- # Internationalization (@veap/intl) URL: https://veap.pl/docs/packages/intl Section: Packages (@veap/*) / @veap/intl `@veap/intl` provides server and client localization for Next.js App Router applications. It supports ICU message formatting (`intl-messageformat`), automatic locale detection from cookies and `Accept-Language` headers (`COOKIE_STORE` and `REQUEST_CONTEXT`), and priority-ordered translation loaders so plugins and host applications can merge dictionaries seamlessly. ## Registering `IntlServiceProvider` Register `IntlServiceProvider` from `@veap/intl/server` in your composition root (`lib/veap.ts`). You can pass custom `loaders` with a `priority` number so your application dictionaries override default package or plugin translations: ```ts // lib/veap.ts import { IntlServiceProvider } from "@veap/intl/server"; import { Application } from "@veap/kernel"; export const app = Application.configure() .use( (c) => new IntlServiceProvider(c, { loaders: [ { id: "app:locales", priority: 200, load: async (locale: string) => { if (locale === "de") { return (await import("../locales/de.json")).default; } return (await import("../locales/en.json")).default; }, }, ], }), ) .create(); ``` ## Next steps Continue exploring `@veap/intl`: - [Formatting and components](https://veap.pl/docs/packages/intl/formatting-and-components) — Translate strings in React Server Components and Client Components using ICU message syntax. --- # Formatting and components URL: https://veap.pl/docs/packages/intl/formatting-and-components Section: Packages (@veap/*) / @veap/intl `@veap/intl` separates server-side translation resolution (`@veap/intl/server`) from client-side hooks (`@veap/intl/client`) while sharing the same ICU message syntax across both environments. ## Translating in React Server Components In Server Components, Server Actions, or Route Handlers, call `getTranslation(namespace?)` or `getCurrentLocale()` from `@veap/intl/server`: ```tsx // app/page.tsx import { getTranslation } from "@veap/intl/server"; import { initializeSystem } from "@/lib/veap"; export default async function HomePage() { await initializeSystem(); const { t, locale } = await getTranslation("common"); return (

{t("hero.title", { framework: "Veap" })}

Active locale: {locale}

); } ``` ## Translating in Client Components Wrap your root `app/layout.tsx` with `` from `@veap/intl/server`. Inside Client Components (`"use client"`), call `useTranslation()` from `@veap/intl/client` to access formatting helpers, or `useLocale()` and `useSupportedLocales()` to inspect the active locale configuration: ```tsx // components/greeting-card.tsx "use client"; import { useLocale, useTranslation } from "@veap/intl/client"; export function GreetingCard({ count }: { count: number }) { const { t, date } = useTranslation(); const locale = useLocale(); return (

{t("common.notifications.unread_count", { count })}

Locale: {locale} ({date(new Date(), { dateStyle: "short" })})
); } ``` To switch languages, set the `VEAP_LOCALE` cookie (or your custom `intl.cookie` name configured in `veap.config.ts`) and refresh or trigger router navigation. ## ICU message syntax Because `@veap/intl` uses `intl-messageformat`, dictionary strings support named placeholders, plural rules, and select expressions: ```json { "common": { "hero": { "title": "Build modular apps with {framework}" }, "notifications": { "unread_count": "{count, plural, =0 {No unread messages} one {# unread message} other {# unread messages}}" } } } ``` --- # Email delivery (@veap/mail) URL: https://veap.pl/docs/packages/mail Section: Packages (@veap/*) / @veap/mail `@veap/mail` decouples transactional email sending from transport protocols. In local development (when no SMTP credentials are set), it defaults automatically to `ConsoleMailService`, printing formatted email previews to the terminal. ## Configuring `MailServiceProvider` and sending mail Register `MailServiceProvider` in `lib/veap.ts` and call `sendMail(message)` in your Server Actions or event listeners: ```ts // lib/veap.ts import { Application } from "@veap/kernel"; import { MailServiceProvider } from "@veap/mail"; export const app = Application.configure() .use(MailServiceProvider) .create(); ``` Send an email from any Server Action or domain service using `sendMail`: ```ts import { sendMail } from "@veap/mail"; await sendMail({ to: "ada@example.com", subject: "Your invoice is ready", text: "View your invoice at https://example.com/invoices/1042", html: "

View your invoice at #1042.

", }); ``` ## Next steps Continue exploring `@veap/mail`: - [Transports and custom drivers](https://veap.pl/docs/packages/mail/transports-and-drivers) — Configure SMTP environment variables or bind a custom `IMailer` adapter (`CUSTOM_MAILER`). --- # Transports and custom drivers URL: https://veap.pl/docs/packages/mail/transports-and-drivers Section: Packages (@veap/*) / @veap/mail `@veap/mail` selects its active mail driver (`IMailer`) based on environment variables and container bindings registered before `MailServiceProvider.boot()`. ## Environment variables Configure SMTP delivery through environment variables in your `.env` file: | Variable | Description | | :--- | :--- | | `MAIL_TRANSPORT` | Transport mode: `"smtp"`, `"console"`, or `"custom"`. | | `MAIL_SERVICE` | Nodemailer service name (such as `"gmail"`). | | `MAIL_USERNAME` / `MAIL_PASSWORD` | SMTP authentication credentials. | | `MAIL_FROM_ADDRESS` | Default sender `From` address. | ## Registering a custom mail driver (`CUSTOM_MAILER`) To send transactional emails through an HTTP provider (such as Resend, Postmark, or AWS SES), implement `IMailer`, bind it to the `CUSTOM_MAILER` token in your composition root, and set `MAIL_TRANSPORT=custom`: ```ts import { ServiceProvider } from "@veap/kernel"; import { CUSTOM_MAILER, type IMailer, type MailMessage } from "@veap/mail"; export class ResendMailer implements IMailer { async sendMail(message: MailMessage): Promise { // Dispatch message via Resend HTTP API } } export class CustomMailProvider extends ServiceProvider { register(): void { this.container.register({ token: CUSTOM_MAILER, useClass: ResendMailer, singleton: true, }); } } ``` --- # Application settings (@veap/settings) URL: https://veap.pl/docs/packages/settings Section: Packages (@veap/*) / @veap/settings `@veap/settings` persists runtime-editable key-value settings in a `settings` database table (`Setting` model) while caching reads automatically through `@veap/kernel`'s `CACHE_PROVIDER`. ## Registering `SettingsServiceProvider` Install `@veap/settings` alongside `@veap/database`, then register `SettingsServiceProvider` in your `lib/veap.ts` composition root: ```ts // lib/veap.ts import { DatabaseServiceProvider } from "@veap/database"; import { Application } from "@veap/kernel"; import { SettingsServiceProvider } from "@veap/settings"; export const app = Application.configure() .use(DatabaseServiceProvider) .use(SettingsServiceProvider) .create(); ``` When `SettingsServiceProvider.boot()` runs, it automatically applies the `settings` table migration unless `autoMigrate: false` is passed. ## Next steps Continue exploring `@veap/settings`: - [Reading, writing, and caching settings](https://veap.pl/docs/packages/settings/reading-and-caching) — Use `SettingsService` to read typed settings with defaults, update values, and invalidate cached entries. --- # Reading, writing, and caching settings URL: https://veap.pl/docs/packages/settings/reading-and-caching Section: Packages (@veap/*) / @veap/settings Resolve `SettingsService` from the IoC container to read, update, or remove persisted application settings. ## Using `SettingsService` `SettingsService` serializes values as JSON in the `settings` database table and caches them under the `veap:settings:` namespace in `CACHE_PROVIDER`: ```ts import { app } from "@veap/kernel"; import { SettingsService } from "@veap/settings"; const settings = await app(SettingsService); // Read a setting with a typed fallback default const siteTitle = await settings.get("site.title", "Veap App"); const maintenanceMode = await settings.get("site.maintenance", false); // Create or update a setting (automatically invalidates cache) await settings.set("site.title", "Acme Developer Portal"); // Delete a setting key await settings.remove("site.legacy_banner"); ``` ## Updating settings in a Server Action Wrap `settings.set()` inside a Next.js Server Action using `@veap/kernel` `Result` helpers so admin forms receive structured responses: ```ts "use server"; import { app, handleActionError, ok } from "@veap/kernel"; import { SettingsService } from "@veap/settings"; import { initializeSystem } from "@/lib/veap"; export async function updateSiteTitleAction(title: string) { try { await initializeSystem(); const settings = await app(SettingsService); await settings.set("site.title", title.trim()); return ok({ title: title.trim() }); } catch (e) { return handleActionError(e, "updateSiteTitleAction"); } } ``` --- # File storage (@veap/storage) URL: https://veap.pl/docs/packages/storage Section: Packages (@veap/*) / @veap/storage `@veap/storage` routes web `File` uploads to named storage providers (`IStorageProvider`). Out of the box, `StorageServiceProvider` registers `LocalFileProvider` (`id: "local"`), which validates MIME types and extensions, sanitizes filenames, prevents path traversal, and writes files to `FILE_STORAGE_FOLDER` (defaulting to `public/uploads`). ## Registering `StorageServiceProvider` Register `StorageServiceProvider` in your `lib/veap.ts` composition root: ```ts // lib/veap.ts import { Application } from "@veap/kernel"; import { StorageServiceProvider } from "@veap/storage"; export const app = Application.configure() .use(StorageServiceProvider) .create(); ``` ## Next steps Continue exploring `@veap/storage`: - [Uploading files and custom cloud drivers](https://veap.pl/docs/packages/storage/uploads-and-drivers) — Handle `FormData` file uploads in Server Actions and register custom S3, R2, or Supabase storage providers. --- # Uploading files and custom cloud drivers URL: https://veap.pl/docs/packages/storage/uploads-and-drivers Section: Packages (@veap/*) / @veap/storage Use `StorageService` to validate, store, and delete uploaded files across local disk or custom cloud storage adapters. ## Uploading and deleting files in Server Actions Resolve `StorageService` from the container inside a Next.js Server Action or Route Handler: ```ts "use server"; import { app, AppError, handleActionError, ok } from "@veap/kernel"; import { StorageService } from "@veap/storage"; import { initializeSystem } from "@/lib/veap"; export async function uploadDocumentAction(formData: FormData) { try { await initializeSystem(); const file = formData.get("file"); if (!(file instanceof File)) { throw AppError.BadRequest("Attach a valid file."); } const storage = await app(StorageService); const result = await storage.upload(file, "local"); if ("error" in result) { throw AppError.Validation(result.error); } return ok({ url: result.url, serviceId: result.serviceId, size: result.size, }); } catch (e) { return handleActionError(e, "uploadDocumentAction"); } } ``` ## Registering a custom cloud storage provider To store uploads in AWS S3, Cloudflare R2, or Supabase Storage, implement `IStorageProvider` (`id`, `upload(file)`, and `delete(serviceId)`) and register it with `StorageService` during provider boot: ```ts import { ServiceProvider } from "@veap/kernel"; import { type IStorageProvider, StorageService, type UploadResult, } from "@veap/storage"; export class S3StorageProvider implements IStorageProvider { readonly id = "s3"; async upload(file: File): Promise { return { url: `https://cdn.example.com/${file.name}`, serviceId: file.name, size: file.size, mimeType: file.type, }; } async delete(_serviceId: string): Promise { return true; } } export class CloudStorageServiceProvider extends ServiceProvider { register(): void {} async boot(): Promise { const storage = await this.container.resolve(StorageService); storage.registerProvider(new S3StorageProvider()); } } ``` --- # UI component library (@veap/ui) URL: https://veap.pl/docs/packages/ui Section: Packages (@veap/*) / @veap/ui `@veap/ui` provides over 40 accessible React 19 components built on Radix UI primitives and styled with Tailwind CSS v4 design tokens and OKLCH color variables. ## Importing stylesheet tokens and `ThemeProvider` Import `@veap/ui/globals.css` in your root `app/globals.css` stylesheet so Tailwind CSS v4 loads the shared design tokens and theme variables: ```css /* app/globals.css */ @import "tailwindcss"; @import "@veap/ui/globals.css"; ``` Wrap your root `app/layout.tsx` with `ThemeProvider` from `@veap/ui` (or `@veap/ui/providers`) to support light, dark, and system color modes: ```tsx // app/layout.tsx import { Toaster } from "@veap/ui/components/sonner"; import { ThemeProvider } from "@veap/ui/providers"; import "./globals.css"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` ## Next steps Continue exploring `@veap/ui`: - [Using UI components](https://veap.pl/docs/packages/ui/components) — Import cards, badges, buttons, forms, dialogs, and toast notifications from `@veap/ui`. --- # Using UI components URL: https://veap.pl/docs/packages/ui/components Section: Packages (@veap/*) / @veap/ui `@veap/ui` exports components both from the root barrel (`@veap/ui`) and from individual component subpaths (`@veap/ui/components/*`). ## Cards, badges, and buttons Import layout and action primitives from `@veap/ui`: ```tsx import { Badge, Button, Card, CardContent, CardHeader, CardTitle, } from "@veap/ui"; export function StatusCard() { return ( Database Connection Connected ); } ``` ## Subpath imports for dialogs, forms, and notifications For granular imports, reference `@veap/ui/components/` directly: - `@veap/ui/components/button`, `@veap/ui/components/input`, `@veap/ui/components/label`, `@veap/ui/components/textarea` - `@veap/ui/components/dialog`, `@veap/ui/components/sheet`, `@veap/ui/components/dropdown-menu` - `@veap/ui/components/table`, `@veap/ui/components/tabs`, `@veap/ui/components/badge` - `@veap/ui/components/sonner` (`Toaster` component for toast notifications) - `@veap/ui/components/spinner`, `@veap/ui/components/separator`, `@veap/ui/components/alert` --- # Project scaffolding (create-veap) URL: https://veap.pl/docs/packages/create-veap Section: Packages (@veap/*) / create-veap `create-veap` is the official command-line initializer for scaffolding new Veap + Next.js App Router applications. It copies a verified project template, configures `package.json` for your project name, and installs dependencies using your active package manager. ## Quick start Run `create-veap` with Bun, pnpm, npm, or Yarn: ```bash # Using Bun (recommended) bun create veap my-app # Using pnpm pnpm create veap my-app # Using npm npm create veap@latest my-app ``` If you omit the project directory name (`bun create veap`), the CLI launches an interactive prompt asking for the target folder and template. ## Next steps Continue exploring `create-veap`: - [CLI options and template structure](https://veap.pl/docs/packages/create-veap/templates-and-options) — Pass non-interactive flags for CI workflows and inspect the files generated by the `01-basic` starter template. --- # CLI options and template structure URL: https://veap.pl/docs/packages/create-veap/templates-and-options Section: Packages (@veap/*) / create-veap `create-veap` supports non-interactive CLI flags for automated pipelines and generates a pre-configured Next.js 16 App Router workspace. ## Command-line options Pass flags directly to `create-veap` for non-interactive or CI workflows: | Option | Shorthand | Description | | :--- | :--- | :--- | | `--template ` | `-t ` | Selects the starter template (defaults to `01-basic`). | | `--skip-install` | — | Copies and configures project files without running package installation. | | `--help` | `-h` | Prints CLI usage instructions and available options. | ```bash bun create veap my-app --template 01-basic --skip-install ``` ## Package manager detection `create-veap` inspects the `npm_config_user_agent` environment variable to detect whether you invoked it via `bun`, `pnpm`, `npm`, or `yarn`, and runs ` install` automatically using the matching tool. ## Built-in templates `create-veap` provides built-in starter templates for different application architectures: ### `01-basic` (Next.js App Router) The default template sets up a ready-to-run Next.js 16 App Router workspace with React 19, TypeScript, and Tailwind CSS v4: - **`lib/veap.ts`**: Application composition root (`Application.configure()...create()`) and the `initializeSystem()` bootstrap helper. - **`instrumentation.ts`**: Next.js server startup hook (`register()`) guarded for the `nodejs` runtime. - **`app/`**: Next.js App Router layout (`app/layout.tsx`), global Tailwind CSS v4 styles (`app/globals.css`), and starter landing page (`app/page.tsx`). - **`.env`**: Pre-populated environment variable placeholders (`DATABASE_URL`, `NODE_ENV`). - **`AGENTS.md`**: Contextual instructions for AI coding assistants working inside your project. ### `02-hono` (Hono microservice and REST API) A lightweight API service starter pairing [Hono](https://hono.dev) with `@veap/kernel`: - **`src/veap.ts`**: Veap composition root with Hono cookie and header transport adapters and bootstrap lifecycle. - **`src/providers/app.provider.ts`**: Modular `ServiceProvider` binding application services into the IoC container. - **`src/services/greeting.service.ts`**: Example domain service resolved from the IoC container. - **`src/index.ts`**: Hono routing with `contextStorage()` middleware and universal runtime support (Node.js and Bun). ## Scaffolding from remote Git repositories (`--example`) You can scaffold a new project using any public GitHub repository or monorepo subfolder as the starter template by passing `--example `: ```bash # Scaffold from a specific repository branch or directory bun create veap my-app --example https://github.com/veapjs/veap/tree/main/templates/01-basic # Scaffold from a community starter kit bun create veap my-app --example https://github.com/username/my-custom-veap-starter ``` `create-veap` fetches the repository archive, extracts the directory contents into your target folder, updates the `package.json` project name, and runs your active package manager's install process. ## Authoring custom project templates When creating reusable team starters or enterprise boilerplate templates: 1. **Structure standard files**: Include `package.json`, `tsconfig.json`, `next.config.ts`, `lib/veap.ts`, and an `.env.example` file. 2. **Exclude build artifacts and secrets**: Do not commit `node_modules/`, `.next/`, `bun.lock`, or private `.env` files to template repositories. 3. **Use standard peer dependencies**: Declare exact semver versions for `@veap/*` framework dependencies matching the current release. 4. **Provide onboarding instructions**: Include an `AGENTS.md` file and a `README.md` explaining environment variable requirements and initial migration steps (`bun veap migrate`). --- # Testing Veap applications URL: https://veap.pl/docs/guides/testing Section: Guides Because Veap is built around an Inversion of Control (IoC) container, hexagonal transport ports (`COOKIE_STORE` and `REQUEST_CONTEXT`), and SQLite-compatible Active Record models, you can test services, Server Actions, database queries, and CLI commands in fast, isolated unit and integration tests without booting a Next.js HTTP server. ## Isolating the container and database In test suites (using `bun:test` or `vitest`), reset or clear the global container between tests and always shut down the application in `afterAll` (or `afterEach`) so database connection pools and event listeners do not leak across test files. | Method | Behavior | When to use | | :--- | :--- | :--- | | `container.clear()` | Clears instantiated singletons and in-flight promises while keeping registered provider definitions. | In `beforeEach` when all tests share the same `ServiceProvider` registrations. | | `container.reset()` | Clears all provider definitions, singletons, and in-flight promises. | When each test registers a different set of providers or mock bindings. | | `await app.shutdown()` | Calls `shutdown()` on all active providers in reverse order (closing Knex connections) and resets bootstrap flags. | In `afterAll` or `afterEach` after running `await app.bootstrap()`. | ```ts // tests/articles.test.ts import { afterAll, beforeAll, beforeEach, describe, expect, it } from "bun:test"; import { DatabaseServiceProvider, runMigrations, Schema } from "@veap/database"; import { Application, container } from "@veap/kernel"; import { Article } from "../lib/models/article"; const testApp = Application.configure() .use( (c) => new DatabaseServiceProvider(c, { config: { client: "better-sqlite3", connection: { filename: ":memory:" }, useNullAsDefault: true, }, }), ) .create(); describe("Article model", () => { beforeAll(async () => { await testApp.bootstrap(); await runMigrations("test", [ { name: "create_articles", async up() { await Schema.create("articles", (table) => { table.uuid("id").primary(); table.string("title").notNullable(); table.string("slug").notNullable().unique(); table.timestamps(true, true); }); }, }, ]); }); beforeEach(async () => { await Article.query().delete(); }); afterAll(async () => { await testApp.shutdown(); container.reset(); }); it("creates and queries an article", async () => { await Article.create({ title: "Testing Veap", slug: "testing-veap" }); const found = await Article.query().where("slug", "testing-veap").first(); expect(found?.title).toBe("Testing Veap"); }); }); ``` ## Mocking HTTP cookies and headers (`MemoryCookieStore` & `MemoryRequestContext`) Next.js functions like `cookies()` and `headers()` throw an error when called outside a Next.js request scope. Because `@veap/auth` and `@veap/intl` read cookies and headers through `@veap/kernel`'s `COOKIE_STORE` and `REQUEST_CONTEXT` tokens, you can bind the built-in `MemoryCookieStore` and `MemoryRequestContext` adapters in your tests: ```ts import { beforeEach, describe, expect, it } from "bun:test"; import { app, container, COOKIE_STORE, MemoryCookieStore, MemoryRequestContext, REQUEST_CONTEXT, } from "@veap/kernel"; describe("HTTP transport isolation", () => { let cookieStore: MemoryCookieStore; let requestContext: MemoryRequestContext; beforeEach(() => { container.reset(); cookieStore = new MemoryCookieStore(); requestContext = new MemoryRequestContext(); container.register({ token: COOKIE_STORE, useValue: cookieStore }); container.register({ token: REQUEST_CONTEXT, useValue: requestContext }); }); it("reads cookies and headers from memory adapters", async () => { await cookieStore.set("NEXT_LOCALE", "de"); requestContext.setHeader("accept-language", "de-DE,de;q=0.9,en;q=0.8"); const resolvedCookies = await app(COOKIE_STORE); const resolvedRequest = await app(REQUEST_CONTEXT); expect(await resolvedCookies.get("NEXT_LOCALE")).toBe("de"); expect(await resolvedRequest.getHeader("Accept-Language")).toContain("de-DE"); }); }); ``` ## Testing CLI commands with `BufferedOutput` `@veap/kernel/cli` provides `BufferedOutput`, an in-memory implementation of the `Output` port that captures every line written by a command and replays pre-queued answers for interactive `output.ask()` and `output.confirm()` prompts: ```ts import { describe, expect, it } from "bun:test"; import { Application, container } from "@veap/kernel"; import { BufferedOutput, CommandInput, commands, matchArgs, parseSignature, } from "@veap/kernel/cli"; import { SeedDemoCommand } from "../lib/commands/seed-demo.command"; describe("SeedDemoCommand", () => { it("prompts for confirmation and seeds records", async () => { const sig = parseSignature(SeedDemoCommand.signature); const matched = matchArgs(sig, ["--count=5"]); const input = new CommandInput(sig, matched, ["--count=5"]); // Queue "yes" for the output.confirm() prompt const output = new BufferedOutput().queueAnswer("yes"); const cmd = new SeedDemoCommand({ root: process.cwd(), env: "test", application: new Application([]), container, registry: commands, flags: { force: false }, }); const exitCode = await cmd.handle(input, output); expect(exitCode).toBe(0); expect(output.find("success")).toContain("Sample records created."); }); }); ``` --- # Authoring and customizing plugins URL: https://veap.pl/docs/guides/custom-plugins Section: Guides Veap lets you extend your application with modular plugins in two ways: scaffolding a new local plugin from scratch with `bun veap make:plugin`, or taking local ownership of an installed npm plugin with `bun veap eject`. ## Scaffolding a local plugin (`veap make:plugin`) Run `bun veap make:plugin ` from your project root to scaffold a new workspace plugin inside `plugins/-plugin/`: ```bash bun veap make:plugin newsletter ``` The command creates three files (`package.json`, `tsconfig.json`, and `src/index.ts`), adds `"@veap/newsletter-plugin": "workspace:*"` to your root `package.json`, ensures `"plugins/*"` is listed in `workspaces`, and regenerates `lib/plugins.gen.ts`: ```ts // plugins/newsletter-plugin/src/index.ts import type { IPlugin } from "@veap/plugins"; import { createManifestFromPackageJson } from "@veap/plugins"; import pkg from "../package.json" with { type: "json" }; export class NewsletterPlugin implements IPlugin { manifest = createManifestFromPackageJson(pkg); async init(): Promise { // Initialize plugin when the registry boots } async onEnable(): Promise { // Called when the plugin is enabled } async onDisable(): Promise { // Called when the plugin is disabled } } export default new NewsletterPlugin(); ``` Because the generated plugin is a class, you can inject any service registered in the IoC container—such as `MenuService`, `WidgetService`, `ExtensionService`, or `AuthService`—directly into its lifecycle methods (`init`, `onMigrate`, `onEnable`, `onDisable`) using the `@Inject` parameter decorator from `@veap/kernel`: ```ts import { AuthService } from "@veap/auth"; import { Inject } from "@veap/kernel"; import { createManifestFromPackageJson, ExtensionService, type IPlugin, MenuService, WidgetService, } from "@veap/plugins"; import pkg from "../package.json" with { type: "json" }; export class NewsletterPlugin implements IPlugin { manifest = createManifestFromPackageJson(pkg); async init( @Inject(MenuService) menuService: MenuService, @Inject(WidgetService) widgetService: WidgetService, @Inject(ExtensionService) extensionService: ExtensionService, @Inject(AuthService) authService: AuthService, ): Promise { menuService.addPublicItem({ id: "newsletter-link", title: "Newsletter", url: "/newsletter", priority: 30, }); } } export default new NewsletterPlugin(); ``` ## Contributing routes, extensions, and migrations A single `IPlugin` implementation can bundle database schema migrations, virtual App Router routes (`routeTree` with `discoverRoutes`), UI extension slots (registered via `ExtensionService`), data hooks (`hooks`), and dynamic locale loaders (`locales`): ```tsx // plugins/newsletter-plugin/src/index.ts import * as path from "node:path"; import { fileURLToPath } from "node:url"; import { type Migration, Schema } from "@veap/database"; import { Inject } from "@veap/kernel"; import { createManifestFromPackageJson, ExtensionService, type IPlugin, } from "@veap/plugins"; import { discoverRoutes } from "@veap/plugins/router"; import pkg from "../package.json" with { type: "json" }; const createSubscribersMigration: Migration = { name: "20261015_create_newsletter_subscribers", async up() { await Schema.create("newsletter_subscribers", (table) => { table.increments("id").primary(); table.string("email").notNullable().unique(); table.timestamps(true, true); }); }, async down() { await Schema.dropIfExists("newsletter_subscribers"); }, }; export class NewsletterPlugin implements IPlugin { manifest = createManifestFromPackageJson(pkg); migrations = [createSubscribersMigration]; async init( @Inject(ExtensionService) extensionService: ExtensionService, ): Promise { extensionService.addExtension({ id: "newsletter-footer-cta", target: "site:footer", point: "cta", priority: 50, component: () => ( Join the newsletter ), }); } routeTree = async () => { const appDir = path.join( path.dirname(fileURLToPath(import.meta.url)), "app", ); return discoverRoutes(appDir, (relPath) => import(`./app/${relPath}`)); }; hooks = [ { point: "newsletter:subscriber:before_create", priority: 10, handler: async (data) => ({ ...data, email: data.email.trim().toLowerCase(), }), }, ]; } export default new NewsletterPlugin(); ``` ## Taking ownership of an npm plugin (`veap eject`) When you install an official or community plugin with `bun veap add @veap/blog-plugin` and later need to customize its React components, database models, or route handlers, run `bun veap eject`: ```bash bun veap eject @veap/blog-plugin ``` `veap eject` clones the plugin's Git repository into `plugins/blog-plugin`, rewrites your `package.json` dependency to `"@veap/blog-plugin": "workspace:*"`, links the workspace package, and regenerates `lib/plugins.gen.ts` without changing any package import specifiers in your application. ## Packaging and publishing plugins When your local plugin is ready to be shared with the community or published to npm: ### 1. Configure `package.json` Ensure your plugin's `package.json` exports its main entry file and declares required framework dependencies: ```json { "name": "@my-org/newsletter-plugin", "version": "1.0.0", "type": "module", "main": "./src/index.ts", "exports": { ".": "./src/index.ts" }, "peerDependencies": { "@veap/kernel": "^0.1.0", "@veap/plugins": "^0.1.0" }, "devDependencies": { "typescript": "^5.0.0" } } ``` ### 2. Publish to npm Log in to npm and publish your package: ```bash # Verify contents before publishing npm pack --dry-run # Publish public package npm publish --access public ``` ### 3. Registering in the official Veap registry To list your published plugin in the official Veap Marketplace (`veap.pl/plugins`), open a pull request on GitHub adding your plugin metadata to `apps/veap-docs/veap-registry.json`: ```json { "id": "newsletter-plugin", "slug": "newsletter-plugin", "name": "Veap Newsletter", "package": "@my-org/newsletter-plugin", "version": "1.0.0", "category": "content", "type": "plugin", "description": "Email newsletter subscription and campaign management plugin.", "purpose": "Collects subscriber emails, manages double opt-in verification, and sends newsletters.", "installation": { "command": "bun veap add @my-org/newsletter-plugin", "steps": [ "Run: `bun veap add @my-org/newsletter-plugin`", "The CLI registers the plugin in `lib/plugins.gen.ts`" ] }, "repository": "https://github.com/my-org/newsletter-plugin" } ``` --- # Deployment and production readiness URL: https://veap.pl/docs/guides/deployment-and-production Section: Guides Preparing a Veap + Next.js App Router application for production involves configuring dynamic route rendering so build workers do not require live database connections, verifying required secrets, and swapping in-memory development adapters for multi-instance production drivers where needed. ## Next.js build boundaries and `force-dynamic` During `next build`, Next.js attempts to prerender static routes at build time. Any layout or page that calls `await initializeSystem()` and queries the database or session store must opt into dynamic rendering so the kernel boots at request time rather than during container image builds: ```tsx // app/layout.tsx import { initializeSystem } from "@/lib/veap"; export const dynamic = "force-dynamic"; export default async function RootLayout({ children, }: { children: React.ReactNode; }) { await initializeSystem(); return ( {children} ); } ``` Ensure your root `instrumentation.ts` also guards initialization behind the Node.js runtime check: ```ts // instrumentation.ts export async function register() { if (process.env.NEXT_RUNTIME === "nodejs") { const { initializeSystem } = await import("./lib/veap"); await initializeSystem(); } } ``` ## Production environment variables checklist Before starting your application in production (`NODE_ENV=production`), configure the following environment variables: | Variable | Package | Requirement | | :--- | :--- | :--- | | `NODE_ENV` | `@veap/kernel` | Set to `"production"`. Suppresses debug logs and enables strict security defaults. | | `DATABASE_URL` | `@veap/database` | Connection URI (such as `postgres://user:pass@host:5432/db`) or SQLite file path. | | `DATABASE_SSL_REJECT_UNAUTHORIZED` | `@veap/database` | Optional (`"false"` when using managed PostgreSQL with self-signed certificates). | | `ENCRYPTION_KEY` | `@veap/auth` | Required when `@veap/auth` is used. Base64-encoded 16, 24, or 32-byte key (`openssl rand -base64 16`). | | `MAIL_TRANSPORT` | `@veap/mail` | Set to `"smtp"` or `"custom"` in production (`"console"` is for local development). | ## Running migrations in CI/CD pipelines By default, `MigrationServiceProvider` and `PluginsServiceProvider` apply pending migrations on startup during `app.bootstrap()`. In multi-replica deployments where you prefer running migrations as a dedicated release step before rolling out new containers: 1. Run `bun veap migrate --force` in your deployment pipeline. 2. Check migration status at any time with `bun veap migrate:status`. 3. Roll back the last batch if needed with `bun veap migrate:rollback --force`. ## Multi-instance cache and cloud storage adapters Out of the box, Veap registers single-node adapters (`MemoryCacheProvider` for `CACHE_PROVIDER` and `LocalFileProvider` for `StorageService`). When scaling horizontally across multiple stateless containers: - **Cache (`CACHE_PROVIDER`)**: Implement `ICacheProvider` backed by Redis or Upstash and bind it to `CACHE_PROVIDER` in `lib/veap.ts` so `@veap/settings` and custom services share cache invalidation across instances. - **File storage (`StorageService`)**: Implement `IStorageProvider` backed by S3, Cloudflare R2, or Supabase Storage and register it on `StorageService` so user uploads are stored in durable object storage rather than container-local disk. ## Containerizing with Docker (`Dockerfile`) Use a multi-stage `Dockerfile` to produce minimal, secure container images based on Next.js standalone output. Configure `output: "standalone"` in `next.config.ts`: ```dockerfile # Dockerfile FROM oven/bun:1.2-alpine AS base # Stage 1: Install dependencies FROM base AS deps WORKDIR /app COPY package.json bun.lock* ./ RUN bun install --frozen-lockfile # Stage 2: Build application FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NODE_ENV=production RUN bun run build # Stage 3: Production runner FROM node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV PORT=3000 # Create non-root user RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs # Copy standalone build and static assets COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 CMD ["node", "server.js"] ``` ### Docker Compose for local staging Run your application alongside PostgreSQL and Redis using `docker-compose.yml`: ```yaml services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=postgres://veap:secret@postgres:5432/veap_prod - REDIS_URL=redis://redis:6379 - ENCRYPTION_KEY=dGhpc2lzYTE2Ynl0ZWtleTEyMzQ= depends_on: postgres: condition: service_healthy redis: condition: service_started postgres: image: postgres:17-alpine environment: POSTGRES_USER: veap POSTGRES_PASSWORD: secret POSTGRES_DB: veap_prod volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U veap"] interval: 5s timeout: 5s retries: 5 redis: image: redis:7-alpine volumes: - redisdata:/data volumes: pgdata: redisdata: ``` ## Continuous deployment with GitHub Actions Automate linting, testing, image builds, and zero-downtime database migrations in `.github/workflows/deploy.yml`: ```yaml name: Deploy on: push: branches: [main] jobs: verify-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Install dependencies run: bun install --frozen-lockfile - name: Run test suite run: bun run test - name: Run database migrations env: DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }} ENCRYPTION_KEY: ${{ secrets.PROD_ENCRYPTION_KEY }} run: bun veap migrate --force - name: Deploy container to production run: | echo "Triggering deployment webhook or registry push..." ``` ### Zero-downtime migration strategy In continuous deployment pipelines where old and new container instances run simultaneously during rolling updates, follow the expand and contract pattern for database schema changes: 1. **Phase 1 (Expand)**: Add new columns as nullable, or create new tables. Deploy the code that writes to both old and new columns. 2. **Phase 2 (Backfill)**: Run a background migration or script to backfill existing records. 3. **Phase 3 (Contract)**: Deploy code reading exclusively from the new column. Once all old containers are replaced, apply a migration dropping unused columns or constraints. ## PaaS and self-hosted deployment guides Deploy your Veap container image or repository to modern hosting platforms: - **Coolify / Railway / Render**: Connect your Git repository, configure environment variables (`DATABASE_URL`, `ENCRYPTION_KEY`, `NODE_ENV=production`), set the build command to `bun install && bun run build`, and start command to `node .next/standalone/server.js` or `bun run start`. - **Vercel**: Deploy directly using the official Next.js integration. Because serverless runtimes have a read-only root filesystem (`/var/task`), `@veap/database` redirects SQLite database files to `/tmp/veap.sqlite`. Because `/tmp` on serverless functions is ephemeral and reset between cold starts, configure a persistent PostgreSQL database (such as Neon, Supabase, or AWS RDS) using connection pooler endpoints (with `sslmode=require`) to prevent connection exhaustion. - **Ubuntu VPS with PM2 and Nginx**: Run your standalone application using PM2 (`pm2 start server.js --name my-app`) behind an Nginx reverse proxy with automated Let's Encrypt SSL certificates. --- # Background tasks, queues, and distributed cache URL: https://veap.pl/docs/guides/background-jobs-and-cache Section: Guides In high-traffic applications, deferring heavy computations, external API calls, and email delivery outside the HTTP request lifecycle keeps response times fast and reliable. This guide covers asynchronous task execution, event-driven decoupling, worker queue architectures, and distributed caching in Veap. ## Request lifecycle and asynchronous tasks In Next.js App Router applications, long-running asynchronous operations executed inside Server Actions or Route Handlers can delay HTTP responses or terminate prematurely when serverless functions exit. Next.js provides the `after()` API to schedule work that runs after the response is sent: ```ts // app/actions/orders.ts "use server"; import { after } from "next/server"; import { app } from "@/lib/veap"; import { sendMail } from "@veap/mail"; export async function placeOrder(orderData: FormData) { // 1. Process order within the active request const order = await saveOrderToDatabase(orderData); // 2. Schedule non-blocking work to run after response finishes after(async () => { await sendMail({ to: order.customerEmail, subject: `Order Confirmation #${order.id}`, text: `Thank you for your order! Your confirmation code is ${order.code}.`, }); }); return { success: true, orderId: order.id }; } ``` ## Decoupling with the kernel `EventBus` `@veap/kernel` provides an in-memory `EventBus` (`Token`) that lets different subsystems react to domain events without coupling business logic to infrastructure delivery: ```ts // lib/services/order.service.ts import { EVENT_BUS, type IEventBus, Inject } from "@veap/kernel"; export class OrderService { constructor(@Inject(EVENT_BUS) private bus: IEventBus) {} async completeOrder(order: any): Promise { // Save order await order.save(); // Publish domain event await this.bus.publish("order:completed", { orderId: order.id, amount: order.total, customerId: order.customerId, }); } } ``` Listeners subscribe to domain topics in a `ServiceProvider.boot()` method or plugin initializer: ```ts // lib/providers/notification.provider.ts import { EVENT_BUS, type IEventBus, ServiceProvider } from "@veap/kernel"; import { sendMail } from "@veap/mail"; export class NotificationServiceProvider extends ServiceProvider { register(): void {} async boot(): Promise { const bus = this.container.resolve(EVENT_BUS); bus.subscribe("order:completed", async (payload: any) => { await sendMail({ to: "sales@example.com", subject: `New Order Received #${payload.orderId}`, text: `Order total: $${payload.amount}`, }); }); } } ``` ## Production queue architecture (BullMQ and Redis) When your application scales beyond a single server or handles spikes of intensive jobs (such as image resizing, PDF invoice rendering, or batch webhook dispatching), we recommend delegating tasks to a dedicated Redis-backed queue like BullMQ. ### Defining a queue service provider Encapsulate your queue client in a custom `ServiceProvider` and register it in `lib/veap.ts`: ```ts // lib/providers/queue.provider.ts import { ServiceProvider, type Token } from "@veap/kernel"; import { Queue } from "bullmq"; export interface IJobQueue { add(name: string, data: Record): Promise; } export const JOB_QUEUE: Token = Symbol.for("app:job-queue"); export class QueueServiceProvider extends ServiceProvider { private queue?: Queue; register(): void { const connection = { host: process.env.REDIS_HOST ?? "127.0.0.1", port: Number(process.env.REDIS_PORT ?? 6379), }; this.queue = new Queue("veap-jobs", { connection }); this.container.singleton(JOB_QUEUE, { add: async (name, data) => { await this.queue?.add(name, data, { attempts: 3, backoff: { type: "exponential", delay: 2000 }, }); }, }); } async shutdown(): Promise { await this.queue?.close(); } } ``` ### Running dedicated worker processes Create a dedicated worker entry point in `workers/index.ts` that runs as a separate Node.js or Bun process: ```ts // workers/index.ts import { Worker } from "bullmq"; import { app } from "../lib/veap"; import { sendMail } from "@veap/mail"; await app.bootstrap(); const worker = new Worker( "veap-jobs", async (job) => { switch (job.name) { case "send-welcome-email": await sendMail({ to: job.data.email, subject: "Welcome to Veap", text: `Welcome, ${job.data.username}!`, }); break; default: console.warn(`Unknown job name: ${job.name}`); } }, { connection: { host: process.env.REDIS_HOST ?? "127.0.0.1", port: Number(process.env.REDIS_PORT ?? 6379), }, }, ); console.log("Worker listening for background jobs..."); ``` Run this process alongside your Next.js application using Docker, PM2, or a Kubernetes worker deployment. ## Distributed caching with Redis or Upstash By default, `@veap/kernel` registers `MemoryCacheProvider`, storing cached entries in process memory. In serverless or multi-container deployments where multiple replicas serve incoming requests, bind a distributed `ICacheProvider` to `CACHE_PROVIDER` so cached entries and invalidations (such as `@veap/settings`) synchronize across instances. ### Implementing `ICacheProvider` Implement the `ICacheProvider` contract using your preferred Redis client (such as `ioredis` or `@upstash/redis`): ```ts // lib/cache/redis-cache.provider.ts import { CACHE_PROVIDER, type ICacheProvider, ServiceProvider } from "@veap/kernel"; import Redis from "ioredis"; export class RedisCacheAdapter implements ICacheProvider { constructor(private client: Redis) {} async get(key: string): Promise { const data = await this.client.get(key); if (!data) return null; try { return JSON.parse(data) as T; } catch { return data as unknown as T; } } async set(key: string, value: T, ttlSeconds?: number): Promise { const raw = typeof value === "string" ? value : JSON.stringify(value); if (ttlSeconds && ttlSeconds > 0) { await this.client.set(key, raw, "EX", ttlSeconds); } else { await this.client.set(key, raw); } } async delete(key: string): Promise { await this.client.del(key); } async clear(): Promise { await this.client.flushdb(); } } export class DistributedCacheServiceProvider extends ServiceProvider { private redisClient?: Redis; register(): void { const redisUrl = process.env.REDIS_URL ?? "redis://localhost:6379"; this.redisClient = new Redis(redisUrl); this.container.singleton( CACHE_PROVIDER, new RedisCacheAdapter(this.redisClient), ); } async shutdown(): Promise { await this.redisClient?.quit(); } } ``` ### Registering in `lib/veap.ts` Register `DistributedCacheServiceProvider` in your composition root before services that consume cache: ```ts // lib/veap.ts import { Application } from "@veap/kernel"; import { DatabaseServiceProvider } from "@veap/database"; import { SettingsServiceProvider } from "@veap/settings"; import { DistributedCacheServiceProvider } from "./cache/redis-cache.provider"; export const app = Application.configure() .use(new DistributedCacheServiceProvider()) .use(DatabaseServiceProvider) .use(SettingsServiceProvider) .create(); ``` Because `@veap/settings` resolves `CACHE_PROVIDER` from the container, all settings updates automatically invalidate across every connected instance. --- # Troubleshooting and frequently asked questions URL: https://veap.pl/docs/guides/troubleshooting Section: Guides This guide provides solutions for common issues encountered during development, testing, and production deployment of Veap applications. ## Database connection issues When running applications against managed databases, local file stores, or serverless platforms, unexpected connection drops and lock contention can occur. ### PostgreSQL connection exhaustion in serverless environments In serverless hosting environments (such as Vercel, AWS Lambda, or Cloudflare Pages), each serverless function invocation can instantiate a new database connection pool. Under load, this exhausts the database connection limit and produces `remaining connection slots are reserved for non-replication superuser connections` or `timeout acquiring connection` errors. To prevent connection exhaustion: 1. **Use a managed connection pooler**: Connect to your database using a connection pooler endpoint (such as PgBouncer, Neon pooled connections, or Supabase connection pooler on port `6543`). 2. **Configure connection pool limits**: In your database configuration, configure a low pool maximum (such as `max: 2` or `max: 1`) for serverless functions: ```ts // lib/veap.ts export const app = Application.configure() .use( (c) => new DatabaseServiceProvider(c, { pool: { min: 0, max: 1 }, }), ) .create(); ``` ### SQLite locks (`SQLITE_BUSY: database is locked`) When running concurrent writes or parallel test suites against a local SQLite file, SQLite can return `SQLITE_BUSY` errors because default rollback journals lock the entire database file during writes. To resolve SQLite locking: 1. **Enable Write-Ahead Logging (WAL)**: In WAL mode, readers do not block writers and writers do not block readers. Configure WAL mode in your connection options: ```ts // veap.config.ts or database provider options { client: "better-sqlite3", connection: { filename: "./data/app.sqlite", options: { timeout: 5000, // Wait up to 5 seconds before throwing SQLITE_BUSY }, }, } ``` 2. **Use isolated databases for parallel tests**: When running tests with Vitest or Jest in multi-worker mode, allocate a unique database file per worker using `process.env.VITEST_POOL_ID` or use in-memory SQLite (`:memory:`). ### Recovering from migration locks (`knex_migrations_lock`) If a migration process terminates abruptly (such as a container crash or forced SIGKILL), Knex may leave the migration lock active, causing subsequent `veap migrate` commands to hang indefinitely. To resolve a migration lock: 1. Inspect the migration status: ```bash bun veap migrate:status ``` 2. If migrations report a persistent lock, clear the lock in your database client: ```sql UPDATE knex_migrations_lock SET is_locked = 0; ``` 3. Re-run `bun veap migrate`. ## CLI and environment issues Console commands and automated scripts run in a standalone Node.js or Bun process without an active HTTP server context. ### `Dynamic server usage: cookies() cannot be called outside request scope` Next.js App Router throws a dynamic server usage error if code calls `cookies()` or `headers()` from `next/headers` inside CLI commands or background workers where no HTTP request exists. In Veap, the console runner automatically sets `process.env.VEAP_CLI = "1"` and registers in-memory transport fallbacks `MemoryCookieStore` (`COOKIE_STORE`) and `MemoryRequestContext` (`REQUEST_CONTEXT`) before bootstrapping services. If you encounter this error in custom services: - Do not call `cookies()` from `next/headers` directly inside reusable service classes or domain models. - Instead, inject the `COOKIE_STORE` token from `@veap/kernel`: ```ts import { COOKIE_STORE, type ICookieStore, Inject } from "@veap/kernel"; export class SessionService { constructor(@Inject(COOKIE_STORE) private cookies: ICookieStore) {} async getSessionToken(): Promise { return this.cookies.get("session_token"); } } ``` In HTTP requests, `COOKIE_STORE` automatically delegates to Next.js `cookies()`. In CLI commands, it reads from in-memory fallback storage without throwing errors. ### Invalid `ENCRYPTION_KEY` in `@veap/auth` On startup, `@veap/auth` validates that `ENCRYPTION_KEY` is a base64-encoded string representing exactly 16, 24, or 32 bytes (128, 192, or 256 bits for AES-GCM encryption). If the key is missing or has an invalid length, startup halts with a descriptive error. Generate a valid 16-byte key using OpenSSL: ```bash openssl rand -base64 16 ``` Copy the generated output to your `.env` file: ```ini ENCRYPTION_KEY=dGhpc2lzYTE2Ynl0ZWtleTEyMzQ= ``` ## React and UI hydration mismatches Next.js App Router renders pages on the server before hydrating interactive components on the client, which can trigger hydration mismatches if states diverge. ### Hydration warning with theme and language attributes If you inspect the console and observe `Warning: Extra attributes from the server: class, style` on the `` or `` element, this occurs when client-side theme toggling or locale negotiation modifies attributes before hydration completes. Add `suppressHydrationWarning` to the root `` tag in `app/layout.tsx`: ```tsx // app/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` ### Static prerendering failure during `next build` If `bun run build` or `next build` fails with an error indicating that a database query or secret environment variable was requested during build time: 1. Ensure your root `app/layout.tsx` or dynamic routes export `export const dynamic = "force-dynamic";`. 2. Guard background database calls in `instrumentation.ts` behind `if (process.env.NEXT_RUNTIME === "nodejs")`. --- # Building a full-stack multilingual blog CMS URL: https://veap.pl/docs/tutorials/fullstack-blog-cms Section: Tutorials This tutorial walks you step by step through building a complete multilingual blog and Content Management System (CMS) with Next.js App Router, `@veap/kernel`, `@veap/database`, `@veap/translatable`, `@veap/taggable`, `@veap/auth`, and `@veap/plugins`. ## Step 1: Scaffold the project and install packages Initialize a new Veap application with `create-veap` and install the database, authentication, and polymorphic ORM trait packages: ```bash bun create veap acme-blog cd acme-blog bun add @veap/database @veap/auth @veap/plugins @veap/translatable @veap/taggable ``` Generate an encryption key for `@veap/auth` and add it to `.env`: ```bash echo "ENCRYPTION_KEY=$(openssl rand -base64 16)" >> .env echo "DATABASE_URL=./dev.sqlite" >> .env ``` ## Step 2: Create the `posts` migration and register trait migrations Scaffold a database migration for the `posts` table: ```bash bun veap make:migration create_posts_table ``` Update the generated migration file in `migrations/` so `posts` stores the non-translatable fields (`id`, `author_id`, `is_published`, and timestamps), while localized `title`, `slug`, and `content` values live in `@veap/translatable`: ```ts // migrations/20261017000000_create_posts_table.ts import { type Migration, Schema } from "@veap/database"; export const createPostsTable: Migration = { name: "20261017000000_create_posts_table", async up() { await Schema.create("posts", (table) => { table.increments("id").primary(); table.integer("author_id").notNullable().index(); table.boolean("is_published").notNullable().defaultTo(false); table.timestamps(true, true); }); }, async down() { await Schema.dropIfExists("posts"); }, }; ``` In `migrations/index.ts`, combine your `createPostsTable` migration with the migrations from `@veap/translatable/migrations` and `@veap/taggable/migrations`: ```ts // migrations/index.ts import { migrations as taggableMigrations } from "@veap/taggable/migrations"; import { migrations as translatableMigrations } from "@veap/translatable/migrations"; import { createPostsTable } from "./20261017000000_create_posts_table"; export const appMigrations = [ ...translatableMigrations, ...taggableMigrations, createPostsTable, ]; ``` ## Step 3: Define the `Post` model with `Translatable` and `Taggable` Create `lib/models/post.ts` and compose `Translatable` and `Taggable` around `@veap/database`'s `Model`: ```ts // lib/models/post.ts import { User } from "@veap/auth/models"; import { type BelongsTo, Model } from "@veap/database"; import { Taggable } from "@veap/taggable"; import { Translatable } from "@veap/translatable"; export class Post extends Taggable(Translatable(Model)) { static override table = "posts"; static override translatable = ["title", "slug", "content"]; declare id: number; declare author_id: number; declare is_published: boolean; declare title: string; declare slug: string; declare content: string; protected static override casts = { is_published: "boolean", } as const; public author(): BelongsTo { return this.belongsTo(User, "author_id"); } } ``` ## Step 4: Wire the composition root (`lib/veap.ts`) Register `DatabaseServiceProvider`, `AuthServiceProvider`, `PluginsServiceProvider`, and `.withMigrations(appMigrations)` in `lib/veap.ts`: ```ts // lib/veap.ts import { AuthServiceProvider } from "@veap/auth"; import { DatabaseServiceProvider } from "@veap/database"; import { Application } from "@veap/kernel"; import { PluginsServiceProvider } from "@veap/plugins"; import { appMigrations } from "../migrations"; export const app = Application.configure() .use(DatabaseServiceProvider) .use(AuthServiceProvider) .use(PluginsServiceProvider) .withMigrations(appMigrations) .create(); export async function initializeSystem(): Promise { await app.bootstrap(); } ``` ## Step 5: Create a Server Action to publish multilingual posts Write a Server Action in `app/actions/posts.ts` that verifies the authenticated session via `getCurrentSession()`, creates a `Post` with English and German translations, and attaches tags inside a single database transaction: ```ts // app/actions/posts.ts "use server"; import { getCurrentSession } from "@veap/auth"; import { transaction } from "@veap/database"; import { AppError, handleActionError, ok } from "@veap/kernel"; import { initializeSystem } from "@/lib/veap"; import { Post } from "@/lib/models/post"; export interface CreatePostInput { translations: { en: { title: string; slug: string; content: string }; de: { title: string; slug: string; content: string }; }; tags: string[]; } export async function createPostAction(input: CreatePostInput) { try { await initializeSystem(); const session = await getCurrentSession(); if (!session?.user) { throw AppError.Unauthorized("You must sign in before publishing a post."); } const post = await transaction(async () => { const created = new Post({ author_id: session.user.id, is_published: true, }); created.fillTranslations(input.translations); await created.save(); await created.syncTags(input.tags); return created; }); return ok({ id: post.id, slug: post.slug }); } catch (e) { return handleActionError(e, "createPostAction"); } } ``` ## Step 6: Render the localized blog feed in a Server Component In `app/page.tsx`, eager-load translations (`withTranslations`), author (`with("author")`), and tags (`with("tags")`) to render the published blog feed with zero N+1 queries: ```tsx // app/page.tsx import { withLocale } from "@veap/translatable"; import { initializeSystem } from "@/lib/veap"; import { Post } from "@/lib/models/post"; export const dynamic = "force-dynamic"; export default async function BlogIndexPage() { await initializeSystem(); const posts = await withLocale("en", async () => Post.query() .where("is_published", true) .withTranslations(["en", "de"]) .with("author", "tags") .orderBy("created_at", "desc") .get(), ); return (

Latest Articles

{posts.map((post) => (

{post.title}

{post.content}

))}
); } ``` --- # Building REST APIs and microservices with Veap URL: https://veap.pl/docs/tutorials/rest-api-microservices Section: Tutorials This tutorial demonstrates how to build RESTful web services with Veap across two primary deployment patterns: 1. **Next.js Route Handlers (`app/api/`)**: High-performance API endpoints co-located with your Next.js application, sharing database models and authentication. 2. **Standalone Hono microservices (`02-hono`)**: Ultra-lightweight REST API microservices pairing [Hono](https://hono.dev) with `@veap/kernel`'s IoC container and Active Record ORM—without Next.js or React. --- ## Pattern 1: REST endpoints with Next.js Route Handlers Next.js App Router defines API endpoints in `app/api/**/route.ts` files. By integrating `@veap/kernel` and `@veap/database`, your route handlers benefit from Active Record models, transactions, typed error handling, and session authentication. ### Step 1: Define the model Create an Active Record model in `lib/models/article.ts`: ```ts // lib/models/article.ts import { Model } from "@veap/database"; export class Article extends Model { static override table = "articles"; declare id: number; declare title: string; declare slug: string; declare content: string; declare is_published: boolean; protected static override casts = { is_published: "boolean", } as const; } ``` ### Step 2: Create a collection Route Handler (`app/api/articles/route.ts`) Implement `GET` (list with pagination) and `POST` (create with Bearer token authentication and Zod validation): ```ts // app/api/articles/route.ts import { NextResponse } from "next/server"; import { getCurrentSession } from "@veap/auth"; import { AppError, httpStatusForErrorCode } from "@veap/kernel"; import { Article } from "@/lib/models/article"; import { initializeSystem } from "@/lib/veap"; import { z } from "zod"; const createArticleSchema = z.object({ title: z.string().min(3), slug: z.string().min(3), content: z.string().min(10), is_published: z.boolean().default(false), }); export async function GET(request: Request) { await initializeSystem(); const url = new URL(request.url); const page = Number(url.searchParams.get("page") ?? 1); const perPage = Number(url.searchParams.get("perPage") ?? 10); const paginated = await Article.query() .where("is_published", true) .orderBy("created_at", "desc") .paginate(page, perPage); return NextResponse.json(paginated); } export async function POST(request: Request) { try { await initializeSystem(); // 1. Authenticate via session cookie or Authorization: Bearer const { user } = await getCurrentSession(); if (!user) { throw AppError.Unauthorized("Authentication required."); } // 2. Validate request payload const body = await request.json(); const parsed = createArticleSchema.safeParse(body); if (!parsed.success) { throw AppError.Validation("Invalid input", parsed.error.format()); } // 3. Create record using Active Record ORM const article = await Article.create(parsed.data); return NextResponse.json(article, { status: 201 }); } catch (error) { if (error instanceof AppError) { return NextResponse.json( error.toJSON(), { status: httpStatusForErrorCode(error.code) }, ); } return NextResponse.json( { error: "Internal Server Error" }, { status: 500 }, ); } } ``` ### Step 3: Create an item Route Handler (`app/api/articles/[id]/route.ts`) Handle single-resource `GET`, `PUT`, and `DELETE` requests: ```ts // app/api/articles/[id]/route.ts import { NextResponse } from "next/server"; import { getCurrentSession } from "@veap/auth"; import { AppError, httpStatusForErrorCode } from "@veap/kernel"; import { Article } from "@/lib/models/article"; import { initializeSystem } from "@/lib/veap"; export async function GET( _request: Request, { params }: { params: Promise<{ id: string }> }, ) { await initializeSystem(); const { id } = await params; const article = await Article.find(Number(id)); if (!article) { return NextResponse.json( AppError.NotFound("Article not found").toJSON(), { status: 404 }, ); } return NextResponse.json(article); } export async function DELETE( _request: Request, { params }: { params: Promise<{ id: string }> }, ) { try { await initializeSystem(); const { user } = await getCurrentSession(); if (!user) { throw AppError.Unauthorized(); } const { id } = await params; const article = await Article.find(Number(id)); if (!article) { throw AppError.NotFound(); } await article.delete(); return new NextResponse(null, { status: 204 }); } catch (error) { if (error instanceof AppError) { return NextResponse.json( error.toJSON(), { status: httpStatusForErrorCode(error.code) }, ); } return NextResponse.json({ error: "Server error" }, { status: 500 }); } } ``` --- ## Pattern 2: Standalone API microservices with Hono (`02-hono`) When you do not need Next.js or React, pair [Hono](https://hono.dev) with `@veap/kernel` for sub-millisecond response times, edge portability, and full access to the Veap IoC container. ### Step 1: Scaffold using the `02-hono` template Run `create-veap` with the `--template 02-hono` flag: ```bash bun create veap my-api --template 02-hono cd my-api ``` ### Step 2: Bridge Hono request context with `@veap/kernel` In `src/veap.ts`, adapt Hono's `contextStorage` to satisfy `@veap/kernel`'s `COOKIE_STORE` and `REQUEST_CONTEXT` ports: ```ts // src/veap.ts import { Application, COOKIE_STORE, type ICookieStore, type IHttpRequestContext, REQUEST_CONTEXT, ServiceProvider, } from "@veap/kernel"; import { tryGetContext } from "hono/context-storage"; import { deleteCookie, getCookie, setCookie } from "hono/cookie"; class HonoTransportServiceProvider extends ServiceProvider { register(): void { // Adapter for COOKIE_STORE backed by Hono context this.container.register({ token: COOKIE_STORE, useValue: { async get(name: string) { const c = tryGetContext(); return c ? getCookie(c, name) : undefined; }, async set(name: string, value: string, options?: any) { const c = tryGetContext(); if (c) setCookie(c, name, value, options); }, async delete(name: string) { const c = tryGetContext(); if (c) deleteCookie(c, name); }, } satisfies ICookieStore, }); // Adapter for REQUEST_CONTEXT backed by Hono headers this.container.register({ token: REQUEST_CONTEXT, useValue: { async getHeader(name: string) { const c = tryGetContext(); return c ? c.req.header(name) : undefined; }, async getIp() { const c = tryGetContext(); return c ? c.req.header("x-forwarded-for") : undefined; }, } satisfies IHttpRequestContext, }); } } export const app = Application.configure() .use(HonoTransportServiceProvider) .create(); ``` ### Step 3: Run the Hono server (`src/index.ts`) Mount the Hono router with `contextStorage` middleware, boot the Veap application on startup, and graceful shutdown on termination: ```ts // src/index.ts import { Hono } from "hono"; import { contextStorage } from "hono/context-storage"; import { app } from "./veap"; const server = new Hono(); // Enable async request context storage server.use("*", contextStorage()); server.get("/health", (c) => { return c.json({ status: "healthy", timestamp: new Date().toISOString() }); }); server.get("/api/v1/info", async (c) => { return c.json({ framework: "Veap + Hono", runtime: "bun" }); }); // Boot Veap lifecycle before listening await app.bootstrap(); export default server; ``` Start the service with Bun: ```bash bun run dev ``` Visit `http://localhost:3000/health` to verify that the service is running. --- # Authoring a custom Veap plugin URL: https://veap.pl/docs/tutorials/authoring-a-plugin Section: Tutorials This tutorial guides you through creating, testing, and packaging a custom plugin for the Veap ecosystem. We will build `@acme/newsletter-plugin`—a modular feature that contributes: 1. An email subscriber database table with scoped migrations. 2. A public virtual subscription route (`/newsletter`). 3. An administrative dashboard widget displaying subscriber metrics. 4. An admin sidebar navigation link under `/admin`. --- ## Step 1: Scaffold the plugin directory Use the `veap make:plugin` command from your application root to generate a standard plugin skeleton: ```bash bun veap make:plugin newsletter ``` This creates `plugins/newsletter-plugin/` with the recommended layout and configures `"plugins/*"` in your root `package.json` workspaces. ``` plugins/newsletter-plugin/ ├── src/ │ ├── index.ts # Primary entry point implementing IPlugin │ ├── client.ts # Client-safe exports and React components │ ├── migrations/ # Scoped database migrations │ ├── models/ # Active Record models │ └── ui/ # Dashboard widgets and pages ├── package.json └── tsconfig.json ``` --- ## Step 2: Define the `Subscriber` model and migration ### 1. Database migration Create a reversible schema migration in `plugins/newsletter-plugin/src/migrations/0001_create_subscribers_table.ts`: ```ts // src/migrations/0001_create_subscribers_table.ts import { type Migration, Schema } from "@veap/database"; export const createSubscribersTable: Migration = { name: "20261018000000_create_subscribers_table", async up() { await Schema.create("newsletter_subscribers", (table) => { table.increments("id").primary(); table.string("email").notNullable().unique(); table.string("status").notNullable().defaultTo("active"); table.timestamps(true, true); }); }, async down() { await Schema.dropIfExists("newsletter_subscribers"); }, }; ``` Export the migration list in `src/migrations/index.ts`: ```ts // src/migrations/index.ts import { createSubscribersTable } from "./0001_create_subscribers_table"; export const newsletterMigrations = [createSubscribersTable]; ``` ### 2. Active Record model Create the `Subscriber` model in `src/models/subscriber.ts`: ```ts // src/models/subscriber.ts import { Model } from "@veap/database"; export class Subscriber extends Model { static override table = "newsletter_subscribers"; declare id: number; declare email: string; declare status: string; declare created_at: string; declare updated_at: string; } ``` --- ## Step 3: Create the dashboard widget Create an analytics card component in `src/ui/newsletter-stats-widget.tsx`: ```tsx // src/ui/newsletter-stats-widget.tsx import { Subscriber } from "../models/subscriber"; export async function NewsletterStatsWidget() { const count = await Subscriber.query() .where("status", "active") .count(); return (

Newsletter Subscribers

{count}

Active and subscribed
); } ``` --- ## Step 4: Implement the `IPlugin` entry point (`src/index.ts`) Tie everything together in `src/index.ts`. Implement the `IPlugin` interface and use dependency injection in the `init` hook to register menu items and dashboard widgets: ```ts // src/index.ts import { type IPlugin, type MenuService, type PluginManifest, type WidgetService, } from "@veap/plugins"; import { Inject } from "@veap/kernel"; import { newsletterMigrations } from "./migrations/index"; import { NewsletterStatsWidget } from "./ui/newsletter-stats-widget"; import pkg from "../package.json"; export class NewsletterPlugin implements IPlugin { manifest: PluginManifest = { id: "newsletter-plugin", name: "Newsletter", version: pkg.version ?? "0.1.0", description: "Email newsletter management and subscriber widgets.", }; migrations = newsletterMigrations; async init( @Inject("MenuService") menuService: MenuService, @Inject("WidgetService") widgetService: WidgetService, ): Promise { // 1. Register admin sidebar navigation menuService.addAdminItem("marketing", { id: "newsletter-subscribers", label: "Subscribers", url: "/admin/newsletter", priority: 20, }); // 2. Register dashboard widget widgetService.addWidget({ id: "newsletter:stats", name: "Newsletter Stats", area: "admin:dashboard", component: NewsletterStatsWidget, defaultColSpan: 1, defaultRowSpan: 1, priority: 50, }); } async onEnable(): Promise { // Lifecycle hook called when administrator enables the plugin } async onDisable(): Promise { // Lifecycle hook called when administrator disables the plugin } } export default new NewsletterPlugin(); ``` --- ## Step 5: Test and enable the plugin Regenerate your application's static plugin registry: ```bash bun veap plugins:sync ``` Start the development server: ```bash bun dev ``` 1. The plugin database migrations execute automatically during startup. 2. Visit `/admin` to verify that `NewsletterStatsWidget` renders in the admin dashboard widget area. 3. Check the admin navigation sidebar to confirm the `Subscribers` menu link appears under `Marketing`. --- ## Step 6: Distribute and eject - **Distributing**: Publish your plugin to npm as `@acme/newsletter-plugin`. Other developers install it with `bun veap add @acme/newsletter-plugin`. - **Ejecting**: If your application consumes a third-party plugin from npm and you need full source-level customization, run `bun veap eject ` to clone its source code directly into your local `plugins/` workspace. --- # @veap/kernel API reference URL: https://veap.pl/docs/reference/packages/kernel Section: API reference / Packages `@veap/kernel` is the foundational layer of the Veap framework. It provides the asynchronous Inversion of Control (IoC) container, `ServiceProvider` and `Application` lifecycle orchestration, `ConfigService`, `EventBus`, `AppError` / `Result` error handling, `ConsoleLogger`, `MemoryCacheProvider`, Next.js HTTP transport adapters (`NextCookieStore`, `NextRequestContext`), and the `veap` CLI command runner. ## Subpath exports `@veap/kernel` exposes eleven entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/kernel` | Core barrel exporting IoC container, `Application`, `ServiceProvider`, `ConfigService`, `EventBus`, `AppError`, `Result`, `MemoryCacheProvider`, `ConsoleLogger`, and HTTP transport ports (`COOKIE_STORE`, `REQUEST_CONTEXT`, `ICookieStore`, `IHttpRequestContext`, `MemoryCookieStore`, `MemoryRequestContext`). | | `@veap/kernel/server` | Server-only entry point exporting `KernelServiceProvider`, `getVeapConfig`, `clearVeapConfigCache`, and all core symbols. | | `@veap/kernel/cli` | CLI framework (`Command`, `CommandInput`, `ConsoleApplication`, `ConsoleOutput`, `BufferedOutput`, `commands`, `COMMANDS`). | | `@veap/kernel/ioc` | `Container`, `container`, `app`, `ServiceProvider`, `Injectable`, `Inject`, and `Token`. | | `@veap/kernel/config` | `ConfigService`, `CONFIG_SERVICE`, `VEAP_CONFIG`, `getVeapConfig`, `clearVeapConfigCache`, and `VeapConfig`. | | `@veap/kernel/errors` | `AppError`, `Result`, `ok`, `handleActionError`, `httpStatusForErrorCode`, and `ErrorCode`. | | `@veap/kernel/events` | `EventBus`, `eventBus`, `EVENT_BUS`, `IEventBus`, `SystemEvent`, and `SystemEventsMap`. | | `@veap/kernel/cache` | `CACHE_PROVIDER`, `ICacheProvider`, and `MemoryCacheProvider`. | | `@veap/kernel/http` | `COOKIE_STORE`, `REQUEST_CONTEXT`, `ICookieStore`, `IHttpRequestContext`, `MemoryCookieStore`, `MemoryRequestContext`, and security headers. | | `@veap/kernel/next` | Next.js HTTP transport adapters (`NextCookieStore`, `NextRequestContext`, `NextTransportServiceProvider`). | | `@veap/kernel/logging` | `LOGGER`, `ILogger`, `ConsoleLogger`, `logger`, `debug`, `info`, `warn`, and `error`. | ## IoC tokens registered by `KernelServiceProvider` When `app.bootstrap()` runs, `KernelServiceProvider` binds the following tokens in the global container: | Token | Default Binding | Interface / Type | | :--- | :--- | :--- | | `EVENT_BUS` / `EventBus` | `eventBus` (`EventBus`) | `IEventBus` | | `COMMANDS` | `commands` (`CommandRegistry`) | `CommandRegistry` | | `LOGGER` / `ConsoleLogger` | `logger` (`ConsoleLogger`) | `ILogger` | | `CONFIG_SERVICE` / `ConfigService` | `ConfigService` | `IConfigService` | | `CACHE_PROVIDER` | `MemoryCacheProvider` | `ICacheProvider` | | `VEAP_CONFIG` | `VeapConfigProvider` | `IVeapConfigProvider` | | `COOKIE_STORE` | `MemoryCookieStore` *(fallback, overridden by `NextCookieStore` in Next.js apps)* | `ICookieStore` | | `REQUEST_CONTEXT` | `MemoryRequestContext` *(fallback, overridden by `NextRequestContext` in Next.js apps)* | `IHttpRequestContext` | ## Core classes and functions The table below summarizes the primary classes and helper functions exported by `@veap/kernel`: | Symbol | Signature / Members | Description | | :--- | :--- | :--- | | `Application` | `Application.configure()`, `bootstrap()`, `shutdown()`, `providers()` | Orchestrates registration, CLI command discovery, booting, and shutdown across `ServiceProvider` entries. | | `ApplicationBuilder` | `.use(provider)`, `.with(provider)`, `.withProviders(list)`, `.create()` | Fluent builder returned by `Application.configure()`. | | `Container` | `register(def)`, `resolve(token)`, `make(token)`, `call(target, method, ...extraArgs)`, `has(token)`, `clear()`, `reset()` | Asynchronous dependency injection container with method injection (`call`), HMR persistence, and in-flight promise deduplication. | | `app` | `app(): Container` / `app(token: Token): Promise` | Global helper returning the `Container` or resolving a token directly. | | `ServiceProvider` | `register()`, `commands?()`, `boot?()`, `shutdown?()` | Abstract base class for modular service providers. | | `AppError` | `BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `Conflict`, `Validation`, `RateLimited`, `Internal`, `.toJSON()` | Transport-agnostic domain error class carrying an `ErrorCode` and optional `details`. | | `ok` | `ok(data: T): Result` | Wraps a value into `{ success: true, data }`. | | `handleActionError` | `handleActionError(e: unknown, context?: string): Result` | Formats caught errors into `{ success: false, error }` while re-throwing Next.js `NEXT_REDIRECT` and `NEXT_NOT_FOUND` digests. | | `httpStatusForErrorCode` | `httpStatusForErrorCode(code: ErrorCode): number` | Maps an `ErrorCode` to its HTTP status code (`400`–`500`). | --- # @veap/database API reference URL: https://veap.pl/docs/reference/packages/database Section: API reference / Packages `@veap/database` provides the Knex-powered Active Record ORM (`Model`), fluent `QueryBuilder`, relational and polymorphic associations, `AsyncLocalStorage` transactions (`transaction`), test factories (`Factory`), and schema migration runner (`Schema`, `runMigrations`). ## Subpath exports `@veap/database` defines three entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/database` | Main barrel exporting `Model`, `QueryBuilder`, `DatabaseServiceProvider`, `MigrationServiceProvider`, `Schema`, `runMigrations`, `transaction`, `initDatabase`, `getKnex`, `dbClient`, `Factory`, and relations. | | `@veap/database/migrations` | `Schema`, `runMigrations`, `rollbackMigrations`, `getMigrationStatus`, and `Migration` types. | | `@veap/database/relations` | `BelongsTo`, `HasOne`, `HasMany`, `BelongsToMany`, `MorphTo`, `MorphOne`, `MorphMany`, and `MorphToMany`. | ## Service providers and IoC tokens `@veap/database` integrates with `@veap/kernel` through two providers and two container tokens: | Symbol | Kind | Description | | :--- | :--- | :--- | | `DatabaseServiceProvider` | `ServiceProvider` | Initializes Knex from `DATABASE_URL` / `DATABASE_FILE` (auto-selecting `better-sqlite3` or `pg`), binds `DATABASE` (`Symbol.for("veap:database:connection")`) and `"Knex"`, and destroys the connection pool on `shutdown()`. | | `MigrationServiceProvider` | `ServiceProvider` | Registers CLI migration commands (`make:migration`, `migrate`, `migrate:status`, `migrate:rollback`) and runs `APP_MIGRATIONS` automatically on server boot (skipped when `VEAP_CLI === "1"`). | | `DATABASE` | `Token` | Injection token for the active Knex instance. | | `APP_MIGRATIONS` | `Token` | Injection token populated by `ApplicationBuilder.withMigrations(migrations)`. | ## `Model` static and instance API Every Active Record model extends `Model` and overrides `static override table`: | Member | Signature | Description | | :--- | :--- | :--- | | `static table` | `string` | Database table name for the model. | | `static primaryKey` | `string` (default `"id"`) | Primary key column name. | | `static timestamps` | `boolean` (default `true`) | Automatically manages `created_at` and `updated_at` columns. | | `protected static casts` | `Record` | Automatic attribute casting (`"boolean"`, `"number"`, `"int"`, `"float"`, `"string"`, `"json"`, `"array"`, `"object"`, `"date"`). | | `Model.query()` | `QueryBuilder` | Starts a new fluent query builder for the model. | | `Model.create(attrs)` | `Promise` | Instantiates and inserts a new record into the database. | | `Model.find(id)` | `Promise` | Finds a record by primary key or returns `null`. | | `Model.findOrFail(id)` | `Promise` | Finds a record by primary key or throws `NotFoundError`. | | `model.save()` | `Promise` | Inserts a new record or updates dirty attributes on an existing record. | | `model.delete()` | `Promise` | Deletes the record from the database. | ## `QueryBuilder` methods `Model.query()` returns a chainable `QueryBuilder`: | Method | Signature | Description | | :--- | :--- | :--- | | `where` | `where(column, operatorOrValue, value?)` | Adds a `WHERE` clause (also supports `whereIn`, `whereNull`, `whereNotNull`, `orWhere`). | | `with` | `with(...relations: string[])` | Eager-loads declared model relations to prevent N+1 queries. | | `orderBy` | `orderBy(column, direction?: "asc" \| "desc")` | Sorts query results. | | `limit` / `offset` | `limit(n)` / `offset(n)` | Restricts result count and offset. | | `get` | `get(): Promise` | Executes the query and returns hydrated model instances. | | `first` | `first(): Promise` | Limits to 1 row and returns the model instance or `null`. | | `count` | `count(): Promise` | Returns the total number of matching rows. | | `paginate` | `paginate(page?: number, perPage?: number)` | Returns `{ data, total, perPage, currentPage, lastPage }`. | --- # @veap/auth API reference URL: https://veap.pl/docs/reference/packages/auth Section: API reference / Packages `@veap/auth` provides authentication, database-backed session management, password hashing (`bcrypt`), AES-GCM encryption, Role-Based Access Control (RBAC), email verification, password reset flows, and Next.js Server Actions. ## Subpath exports `@veap/auth` provides seven entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/auth` | Main barrel exporting `AuthServiceProvider`, Server Actions (`signIn`, `signUp`, `signOut`, `getCurrentSession`), notification helpers (`sendVerifyEmail`, `sendResetPassword`, `sendRecoveryCode`, `send2FACode`), RBAC helpers, and validation schemas. | | `@veap/auth/server` | Server-only cryptographic utilities (`hashPassword`, `verifyPasswordHash`, `encrypt`, `decrypt`, `encryptionKey`), `SessionService`, `UserService`, `RbacService`, and route handler helpers (`handleAuthMe`). | | `@veap/auth/client` | Client React components and hooks (``, `useUser`, `useAuthRoutes`). | | `@veap/auth/actions` | Next.js Server Actions (`signIn`, `signUp`, `signOut`, `getCurrentSession`, `requestPasswordReset`, `resetPassword`, `verifyEmail`, RBAC actions). | | `@veap/auth/models` | Active Record models (`User`, `Session`, `Role`, `Permission`, `EmailVerificationRequest`, `PasswordResetSession`). | | `@veap/auth/migrations` | Knex schema migrations (`authMigrations`) for authentication and RBAC tables. | | `@veap/auth/validation` | Zod validation schemas (`loginSchema`, `registerSchema`, `forgotPasswordSchema`, `resetPasswordSchema`). | ## `AuthServiceProvider` options Register `AuthServiceProvider` in `lib/veap.ts`: ```ts .use((c) => new AuthServiceProvider(c, { autoMigrate: true })) ``` | Option | Type | Default | Description | | :--- | :--- | :--- | :--- | | `autoMigrate` | `boolean` | `true` | Automatically applies `@veap/auth` database migrations during `boot()`. | ## Server Actions and session functions All mutation actions return `@veap/kernel`'s `Result` discriminated union: | Function | Signature | Description | | :--- | :--- | :--- | | `signIn` | `signIn(input: { email: string; password: string })` | Validates credentials, sets a session cookie, returns raw `token` in `result.data.token` on success, or returns `{ status: "CHALLENGE_REQUIRED", ... }` when a 2FA hook is active. | | `signUp` | `signUp(input: { username: string; email: string; password: string })` | Creates a new user account, hashes the password, initiates a session, and returns raw `token` in `result.data.token`. | | `signOut` | `signOut()` | Invalidates the active database session and deletes the session cookie. | | `getCurrentSession` | `getCurrentSession()` | Resolves the active `session` and `user` by checking `Authorization: Bearer ` first, then falling back to the session cookie (`null` if unauthenticated). | ## Active Record models (`@veap/auth/models`) `@veap/auth/models` exports the following `@veap/database` models: | Model | Table | Key Relations & Columns | | :--- | :--- | :--- | | `User` | `users` | `id`, `name`, `email`, `password`, `image`, `recovery_code`, `email_verified_at`, `roles()`, `sessions()` | | `Session` | `sessions` | `id`, `user_id`, `expires_at`, `two_factor_verified`, `user()` | | `Role` | `roles` | `id`, `name`, `description`, `permissions()`, `users()` | | `Permission` | `permissions` | `id`, `name`, `description`, `roles()` | ## Authentication notifications port (`IAuthNotificationService`) `@veap/auth` defines a delivery port for authentication messages such as verification codes, password reset tokens, and recovery codes. Applications can register a custom notification service with the `AUTH_NOTIFICATION_SERVICE` token or rely on the default implementation that dispatches emails through `@veap/mail`. | Symbol | Kind | Signature / Type | Description | | :--- | :--- | :--- | :--- | | `AUTH_NOTIFICATION_SERVICE` | `Token` | `Symbol.for("veap:auth:notification-service")` | IoC token for binding a custom authentication notification service. | | `IAuthNotificationService` | Interface | `{ sendVerificationEmail, sendPasswordReset, sendRecoveryCode, send2FACode? }` | Port contract implemented by notification services. | | `DefaultAuthNotificationService` | Class | `new DefaultAuthNotificationService(mailerResolver?, eventBusResolver?)` | Default implementation delivering emails via `@veap/mail` when bound or logging codes to the console in development. | | `sendVerifyEmail` | Function | `sendVerifyEmail(email: string, code: string): Promise` | Dispatches email verification notification through the active service. | | `sendResetPassword` | Function | `sendResetPassword(email: string, code: string): Promise` | Dispatches password reset notification through the active service. | | `sendRecoveryCode` | Function | `sendRecoveryCode(email: string, code: string): Promise` | Dispatches recovery code notification through the active service. | | `send2FACode` | Function | `send2FACode(email: string, code: string): Promise` | Dispatches 2FA challenge code through the active service if supported. | --- # @veap/plugins API reference URL: https://veap.pl/docs/reference/packages/plugins Section: API reference / Packages `@veap/plugins` provides the modular plugin contract (`IPlugin`), virtual page and API routing (`VeapRouter`), server and client UI extension slots (`ExtensionPoint`, `ExtensionPointClient`), customizable dashboard widgets (`WidgetArea`, `WidgetAreaClient`), and CLI plugin management commands. ## Subpath exports `@veap/plugins` exposes eight entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/plugins` | Core plugin types (`IPlugin`, `PluginManifest`), `PluginsServiceProvider`, extension and widget services, and `.withPlugins()` builder extension. | | `@veap/plugins/server` | Server-side React components (`ExtensionPoint`, `WidgetArea`) and plugin registry services. | | `@veap/plugins/client` | Client-side React components and hooks (`ExtensionPointClient`, `WidgetAreaClient`, `WidgetComposer`, `useConfirmAction`). | | `@veap/plugins/actions` | Server Actions for enabling/disabling plugins and saving user widget layouts. | | `@veap/plugins/router` | `VeapRouter` virtual page and API route dispatcher. | | `@veap/plugins/models` | Active Record models (`SystemPlugin`, `SystemUserWidget`). | | `@veap/plugins/migrations` | Knex migrations for `system_plugins` and `system_user_widgets` tables. | | `@veap/plugins/cli` | CLI command classes (`ListPluginsCommand`, `AddPluginCommand`, `RemovePluginCommand`, `EjectPluginCommand`, `MakePluginCommand`, `PluginsSyncCommand`) and programmatic helpers (`addPlugin`, `removePlugin`, `ejectPlugin`, `generatePlugin`, `regeneratePluginsRegistry`). | ## The `IPlugin` contract Every Veap plugin exports an object implementing `IPlugin`: | Property / Hook | Type | Description | | :--- | :--- | :--- | | `manifest` | `PluginManifest` | Required metadata (`id`, `name`, `version`, `description?`, `enabled?`, `system?`, `hasSetup?`, `dependencies?`, `extends?`, `author?`), typically created via `createManifestFromPackageJson(pkg)`. | | `init?(...args)` | `(...args: any[]) => Promise \| void` | Lifecycle hook called every time the plugin registry initializes for an enabled plugin. Supports `@Inject(token)` parameter injection on class plugins. | | `onMigrate?(...args)` | `(...args: any[]) => Promise \| void` | Lifecycle hook called after migrations complete during initial plugin installation. Supports `@Inject(token)` parameter injection. | | `onEnable?(...args)` | `(...args: any[]) => Promise \| void` | Lifecycle hook called when the plugin is enabled (receives `context` and supports `@Inject(token)` parameter injection). | | `onDisable?(...args)` | `(...args: any[]) => Promise \| void` | Lifecycle hook called when the plugin is disabled. Supports `@Inject(token)` parameter injection. | | `migrations?` | `Migration[]` | Database migrations executed automatically when the plugin is enabled and rolled back when disabled. | | `routeTree?` | `RouteNode \| (() => Promise \| RouteNode)` | App Router-style route tree definition, typically built via `discoverRoutes(appDir, loader)`. | | `hooks?` | `PluginHook[]` | Filter/transform hooks (`{ point, handler, priority? }`) invoked via `applyPluginFilters(point, data, context)`. | | `locales?` | `Record Promise<{ default: any }>>` | Dynamic locale dictionary loaders merged into `@veap/intl`. | | `plugins?` | `IPlugin[]` | Nested sub-plugins registered recursively alongside the parent plugin. | ## Navigation management (`MenuService`) Plugins register navigation links programmatically via `MenuService` (typically injected into `init(@Inject(MenuService) menuService: MenuService)`): - **`menuService.addPublicItem(item, pluginId?)`**: Registers a public site navigation item. - **`menuService.addAdminItem(groupId, item, pluginId?)`**: Registers a navigation item in an admin panel sidebar group. - **`menuService.addSettingsItem(groupId, item, pluginId?)`**: Registers a navigation item in a settings sidebar group. - **`menuService.addGroup(section, group, pluginId?)`**: Registers or updates an admin or settings navigation group (`title`, `priority`, `items`). - **`menuService.register(navigation, pluginId?)`**: Bulk-registers `public`, `admin`, and `settings` navigation definitions. ## Dashboard widgets (`WidgetService`) Plugins register dashboard widgets programmatically via `WidgetService` (typically injected into `init(@Inject(WidgetService) widgetService: WidgetService)`): - **`widgetService.addWidget(widget, pluginId?)`**: Registers a single `PluginWidget` (`{ id, name, area, component, priority?, defaultColSpan?, defaultRowSpan?, roles?, permissions? }`). - **`widgetService.addWidgets(widgets, pluginId?)`**: Registers multiple `PluginWidget` definitions. - **`widgetService.register(widgetOrWidgets, pluginId?)`**: Registers a single widget or an array of widgets. - **`widgetService.getWidgets(area, context?)`**: Resolves enabled widgets for `area`, filtered by roles and permissions and sorted by priority. ## UI extension points (`ExtensionService`) Plugins register UI extension components programmatically via `ExtensionService` (typically injected into `init(@Inject(ExtensionService) extensionService: ExtensionService)`): - **`extensionService.addExtension(extension, pluginId?)`**: Registers a single `PluginExtension` (`{ id, target, point, component, priority?, roles?, permissions? }`) injected into `` slots. - **`extensionService.addExtensions(extensions, pluginId?)`**: Registers multiple `PluginExtension` definitions. - **`extensionService.register(extensionOrExtensions, pluginId?)`**: Registers a single extension or an array of extensions. - **`extensionService.getExtensions(target, point, includeDisabled?, context?)`**: Resolves extensions for `(target, point)`, filtered by roles and permissions and sorted by priority. - **`extensionService.hasExtension(target, point?)`**: Checks whether any enabled plugin registers an extension matching `target` (and optional `point`). ## Virtual routing (`VeapRouter`) `@veap/plugins/router` dispatches incoming Next.js catch-all requests to matching plugin routes: - **`discoverRoutes(appDir, importFn)`**: Scans a plugin's `src/app/` directory and constructs a `RouteNode` tree for `IPlugin.routeTree`. - **`VeapRouter({ tree, path, searchParams })`**: Async React Server Component used in `app/[...catchAll]/page.tsx` to match and render plugin pages nested within matching layout chains. - **`handleVeapApiRequest(request, { tree, path, searchParams })`**: Dispatches API requests in `app/api/[...catchAll]/route.ts`, resolving plugin route handlers, running middleware pipelines, and enforcing RBAC permissions. - **`buildRouteTree(includeDynamic?: boolean)`**: Scans and constructs the unified hierarchical `RouteTree` from all active plugins. --- # @veap/intl API reference URL: https://veap.pl/docs/reference/packages/intl Section: API reference / Packages `@veap/intl` provides internationalization, ICU message formatting (`intl-messageformat`), and automatic request locale negotiation for React Server Components and Client Components. ## Subpath exports `@veap/intl` defines three entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/intl` | Core localization types, constants, and `IntlServiceProvider`. | | `@veap/intl/server` | Server-only entry point exporting `IntlServiceProvider`, `getTranslation`, `getCurrentLocale`, and the server ``. | | `@veap/intl/client` | Client-only entry point exporting `useTranslation` and the client `` context. | ## Server and client APIs The table below lists the primary functions and components exported by `@veap/intl`: | Symbol | Entry point | Signature / Props | Description | | :--- | :--- | :--- | :--- | | `IntlServiceProvider` | `@veap/intl/server` | `new IntlServiceProvider(container, options?)` | Registers translation loaders and locale negotiation services in the IoC container. | | `getTranslation` | `@veap/intl/server` | `getTranslation(localeOverride?, timeZoneOverride?): Promise<{ t, text, date, relativeTime, locale, timeZone }>` | Resolves the active locale and returns formatters for messages (`t`), dates (`date`), and relative time (`relativeTime`). | | `getCurrentLocale` | `@veap/intl/server` | `getCurrentLocale(): Promise` | Negotiates the current request locale from `COOKIE_STORE` (`VEAP_LOCALE` by default) and `REQUEST_CONTEXT` (`Accept-Language`). | | `I18nProvider` | `@veap/intl/server` | `{children}` | Async Server Component that loads the active locale's dictionary and hydrates client context. | | `useTranslation` | `@veap/intl/client` | `useTranslation(): { t, text, date, relativeTime, locale, timeZone }` | Client hook returning ICU message, date, and relative time formatters for the active locale. | | `useLocale` | `@veap/intl/client` | `useLocale(): string` | Client hook returning the active locale string. | | `useSupportedLocales` | `@veap/intl/client` | `useSupportedLocales(): string[]` | Client hook returning the configured array of supported locales. | --- # @veap/mail API reference URL: https://veap.pl/docs/reference/packages/mail Section: API reference / Packages `@veap/mail` provides a unified email transport abstraction (`sendMail`, `IMailer`) supporting SMTP via Nodemailer, local terminal previews via `ConsoleMailService`, and custom third-party drivers via the `CUSTOM_MAILER` IoC token. ## Subpath exports `@veap/mail` exports all symbols from its root entry point: | Subpath | Description | | :--- | :--- | | `@veap/mail` | `MailServiceProvider`, `sendMail`, `CUSTOM_MAILER`, `MAILER`, `IMailer`, `MailMessage`, `ConsoleMailService`, and `NodemailerMailService`. | ## Core APIs and tokens The table below summarizes the providers, tokens, functions, and interfaces exported by `@veap/mail`: | Symbol | Kind | Signature / Type | Description | | :--- | :--- | :--- | :--- | | `MailServiceProvider` | `ServiceProvider` | `new MailServiceProvider(container)` | Inspects `ConfigService` / environment variables and binds the active mail driver (`NodemailerMailService`, `ConsoleMailService`, or `CUSTOM_MAILER`). | | `sendMail` | Function | `sendMail(message: MailMessage): Promise` | Sends an email message (`{ to, subject, text?, html? }`) through the active mail driver. | | `CUSTOM_MAILER` | `Token` | `Symbol.for("veap:communication:custom-mailer")` | IoC token for registering a custom email provider (such as Resend, Postmark, or AWS SES). | | `MAILER` | `Token` | `Symbol.for("veap:communication:mailer")` | Primary IoC token for resolving the active email transport. | | `IMailer` | Interface | `{ sendMail(message: MailMessage): Promise }` | Contract implemented by all mail drivers. | --- # @veap/settings API reference URL: https://veap.pl/docs/reference/packages/settings Section: API reference / Packages `@veap/settings` manages database-persisted key-value application settings (`Setting` model) backed by `@veap/kernel`'s `CACHE_PROVIDER` for low-latency reads and automatic cache invalidation on writes. ## Subpath exports `@veap/settings` exposes four entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/settings` | `SettingsService`, `SettingsServiceProvider`, `SETTINGS_REPOSITORY`, and `ISettingsRepository`. | | `@veap/settings/server` | Server-only barrel re-exporting `SettingsService`, `SettingsServiceProvider`, and repository interfaces. | | `@veap/settings/models` | Active Record `Setting` model (`settings` table). | | `@veap/settings/migrations` | Knex migrations (`settingsMigrations`) for the `settings` table. | ## `SettingsService` API Resolve `SettingsService` from the container (`await app(SettingsService)`) to read and mutate runtime settings: | Method | Signature | Description | | :--- | :--- | :--- | | `get` | `get(key: string, defaultValue?: T): Promise` | Reads a setting from cache or database, returning `defaultValue` if the key does not exist. | | `set` | `set(key: string, value: T, group?: string): Promise` | Inserts or updates a setting in the database and invalidates its cached entry. | | `remove` | `remove(key: string): Promise` | Deletes a setting key from the database and removes its cached entry. | | `getGroup` | `getGroup(group: string): Promise>` | Returns all key-value pairs belonging to the specified settings group. | | `all` | `all(): Promise>` | Returns all persisted settings as a key-value record. | --- # @veap/storage API reference URL: https://veap.pl/docs/reference/packages/storage Section: API reference / Packages `@veap/storage` provides a multi-driver file upload and deletion service (`StorageService`) with a built-in `LocalFileProvider` (`id: "local"`) and a pluggable `IStorageProvider` contract for cloud object storage adapters. ## Subpath exports `@veap/storage` exposes three entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/storage` | `StorageService`, `StorageServiceProvider`, `LocalFileProvider`, and storage types. | | `@veap/storage/providers/local` | `LocalFileProvider` implementation writing sanitized uploads to `FILE_STORAGE_FOLDER` (default `public/uploads`). | | `@veap/storage/types` | `IStorageProvider`, `StorageData`, `StorageError`, and `StorageResult`. | ## `StorageService` and `IStorageProvider` API Resolve `StorageService` from the container (`await app(StorageService)`) to upload or delete files, or register additional cloud storage providers: | Member | Signature | Description | | :--- | :--- | :--- | | `storage.upload` | `upload(file: File, providerId?: string): Promise` | Uploads a web `File` using the specified provider (defaults to `"local"`). Returns `StorageData` (`{ name, url, type, size, service, serviceId }`) or `StorageError` (`{ error: string }`). | | `storage.delete` | `delete(serviceId: string, providerId?: string): Promise` | Deletes a stored file by its `serviceId` from the specified provider. | | `storage.registerProvider` | `registerProvider(provider: IStorageProvider): void` | Registers a custom storage driver (such as `"s3"` or `"r2"`) under `provider.id`. | | `storage.getProvider` | `getProvider(id: string): IStorageProvider \| undefined` | Retrieves a registered storage provider by its `id`. | --- # @veap/ui API reference URL: https://veap.pl/docs/reference/packages/ui Section: API reference / Packages `@veap/ui` is the shared React 19 + Tailwind CSS v4 component library and design system for Veap applications, built on Radix UI primitives and Class Variance Authority (CVA). ## Subpath exports `@veap/ui` exposes individual component subpaths alongside its main entry points: | Subpath | Description | | :--- | :--- | | `@veap/ui` | Barrel export of all UI components, hooks (`useIsMobile`), `ThemeProvider`, `Logo`, and the `cn` class-merging helper. | | `@veap/ui/globals.css` | Tailwind CSS v4 design tokens, OKLCH light and dark theme variables, and base layer styles. | | `@veap/ui/providers` | `ThemeProvider` wrapper around `next-themes`. | | `@veap/ui/logo` | `Logo` vector component with theme-aware variants. | | `@veap/ui/shared/*` | Shared layout feedback components (`@veap/ui/shared/page-loader`, `@veap/ui/shared/access-denied`, `@veap/ui/shared/scroll-fade-effect`). | | `@veap/ui/components/*` | Direct imports for individual components (such as `@veap/ui/components/button`, `@veap/ui/components/dialog`, `@veap/ui/components/sonner`). | | `@veap/ui/mdx` | Markdown and MDX component overrides (`useMDXComponents`). | ## Component catalog The table below lists all components shipped in `@veap/ui` by category: | Category | Components | | :--- | :--- | | **Actions & triggers** | `Button`, `ButtonGroup`, `Toggle`, `ToggleGroup` | | **Data display** | `Avatar`, `Badge`, `Card`, `Table`, `Kbd`, `Separator`, `Empty` | | **Feedback & status** | `Alert`, `AlertDialog`, `Progress`, `Skeleton`, `Spinner`, `Toaster` (`sonner`) | | **Forms & inputs** | `Checkbox`, `Field`, `Form`, `Input`, `InputGroup`, `InputOTP`, `Label`, `RadioGroup`, `Select`, `Slider`, `Switch`, `Textarea` | | **Navigation & menus** | `Breadcrumb`, `Command`, `ContextMenu`, `DropdownMenu`, `Menubar`, `NavigationMenu`, `Pagination`, `Sidebar`, `Tabs` | | **Overlays & popups** | `Dialog`, `Drawer`, `HoverCard`, `Popover`, `Sheet`, `Tooltip` | | **Layout & brand** | `PageLoader`, `AccessDenied`, `ScrollFadeEffect`, `Logo` | --- # @veap/translatable API reference URL: https://veap.pl/docs/reference/packages/translatable Section: API reference / Packages `@veap/translatable` provides polymorphic multilingual translation storage for `@veap/database` Active Record models. All translated attributes across your application share a single centralized `translations` table using `morphMany` relations. ## Subpath exports `@veap/translatable` exposes three entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/translatable` | Core model classes (`TranslatableModel`), mixin (`Translatable`), query builder (`TranslatableQueryBuilder`), and locale manager. | | `@veap/translatable/migrations` | Knex migration defining the polymorphic `translations` table. | | `@veap/translatable/ui` | React UI components (`TranslatableTabs`, `TranslatableField`, `TranslatableFormCard`) for multilingual forms. | ## Core APIs and classes The table below summarizes the primary classes, mixins, and utilities exported by `@veap/translatable`: | Symbol | Kind | Signature / Props | Description | | :--- | :--- | :--- | :--- | | `Translatable` | Function | `Translatable>(Base: T)` | Class mixin adding multilingual attributes and query scopes to any model. | | `TranslatableModel` | Class | `class TranslatableModel extends Model` | Base Active Record class with built-in polymorphic translation support. | | `Translation` | Class | `class Translation extends Model` | Morph target model mapping to the `translations` table. | | `TranslatableQueryBuilder` | Class | `class TranslatableQueryBuilder extends QueryBuilder` | Extends query builder with translation scopes and locale filters. | | `getLocale` | Function | `getLocale(): string` | Returns the active application locale for translation resolution. | | `setLocale` | Function | `setLocale(locale: string): void` | Sets the global fallback locale for translatable models. | ## Model instance methods Models that extend `TranslatableModel` or use `Translatable(Base)` expose the following methods on each instance: | Method | Signature | Description | | :--- | :--- | :--- | | `getTranslation` | `getTranslation(key: string, locale?: string, useFallback?: boolean): string \| null` | Resolves the translated value for a field, falling back to default locale if needed. | | `setTranslation` | `setTranslation(key: string, locale: string, value: string \| null): this` | Stages a translation for a specific locale in memory. | | `fillTranslations` | `fillTranslations(data: Record>): this` | Stages multi-attribute translations across multiple locales simultaneously. | | `getTranslations` | `getTranslations(key?: string): Record` | Returns all loaded translations indexed by locale. | | `hasTranslation` | `hasTranslation(key: string, locale?: string): boolean` | Checks whether a translation exists for the given attribute and locale. | | `forgetTranslation` | `forgetTranslation(key: string, locale?: string): this` | Marks a translation for deletion upon next `save()`. | ## Query builder scopes `TranslatableQueryBuilder` extends database queries with translation-aware filtering and preloading: | Method | Signature | Description | | :--- | :--- | :--- | | `whereTranslation` | `whereTranslation(key: string, value: any, locale?: string): this` | Filters records where a translated attribute equals the specified value. | | `whereTranslationLike` | `whereTranslationLike(key: string, pattern: string, locale?: string): this` | Filters records using a SQL `LIKE` expression against a translated attribute. | | `orderByTranslation` | `orderByTranslation(key: string, direction?: "asc" \| "desc", locale?: string): this` | Joins the translation table and sorts records by localized field values. | | `withTranslations` | `withTranslations(locales?: string[]): this` | Eager-loads translations into the in-memory cache to prevent N+1 queries. | ## Multilingual UI components (`@veap/translatable/ui`) `@veap/translatable/ui` provides client-side React components for managing multilingual input forms: | Component | Props | Description | | :--- | :--- | :--- | | `TranslatableTabs` | `TranslatableTabsProps` | Tab bar switching the active editing locale with visual flags and completion indicators. | | `TranslatableField` | `TranslatableFieldProps` | Single localized field supporting text, textarea, or rich text with locale switching. | | `TranslatableFormCard` | `TranslatableFormCardProps` | Complete form card managing a set of localized fields across multiple languages. | --- # @veap/taggable API reference URL: https://veap.pl/docs/reference/packages/taggable Section: API reference / Packages `@veap/taggable` provides polymorphic tagging for `@veap/database` Active Record models. It includes a multilingual `Tag` model backed by `@veap/translatable`, a pivot relationship (`taggables`), querying scopes, factories, seeders, and ready-to-use UI badge components. ## Subpath exports `@veap/taggable` exposes four entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/taggable` | Core mixin (`Taggable`), base model (`TaggableModel`), `Tag` entity, and query builder (`TaggableQueryBuilder`). | | `@veap/taggable/migrations` | Knex migrations creating the `tags` table and `taggables` polymorphic pivot table. | | `@veap/taggable/ui` | React UI badge and selector components (`TagBadge`, `TagInput`) for forms and display. | | `@veap/taggable/testing` | Test utilities, model factory (`TagFactory`), and seeder (`TagSeeder`) for testing environments. | ## Core APIs and classes The table below summarizes the primary classes, mixins, and utilities exported by `@veap/taggable`: | Symbol | Kind | Signature / Props | Description | | :--- | :--- | :--- | :--- | | `Taggable` | Function | `Taggable>(Base: T)` | Class mixin adding tag relationships and management methods to any model. | | `TaggableModel` | Class | `class TaggableModel extends Model` | Base Active Record class with built-in polymorphic tagging capabilities. | | `Tag` | Class | `class Tag extends TranslatableModel` | Multilingual tag entity storing localized name, slug, and description. | | `TaggableQueryBuilder` | Class | `class TaggableQueryBuilder extends ModelQueryBuilder` | Extends query builder with tag-filtering scopes (`whereTag`, `whereAnyTag`, `whereAllTags`). | | `TagFactory` | Class | `defineFactory(Tag, ...)` | Model factory for generating realistic fake tag data in tests (exported from `@veap/taggable/testing`). | | `TagSeeder` | Class | `class TagSeeder extends Seeder` | Database seeder populating standard application tags (exported from `@veap/taggable/testing`). | ## Model instance methods Models that extend `TaggableModel` or use `Taggable(Base)` expose the following tagging methods on each instance: | Method | Signature | Description | | :--- | :--- | :--- | | `tags` | `tags(): MorphToMany` | Defines the polymorphic many-to-many relationship to the `tags` table through `taggables`. | | `attachTags` | `attachTags(tags: (string \| Tag)[] \| string \| Tag, locale?: string): Promise` | Attaches tags by ID, name (finding or creating automatically), or model instance. | | `detachTags` | `detachTags(tags?: (string \| Tag)[] \| string \| Tag, locale?: string): Promise` | Detaches specified tags, or detaches all tags if called without arguments. | | `syncTags` | `syncTags(tags: (string \| Tag)[] \| string \| Tag, locale?: string): Promise` | Synchronizes the model tags so only the specified list remains attached. | ## Query builder scopes `TaggableQueryBuilder` provides fluent scopes for querying tagged records: | Method | Signature | Description | | :--- | :--- | :--- | | `whereTag` | `whereTag(nameOrSlug: string, locale?: string): this` | Filters models that have a tag matching the given name or slug. | | `whereAnyTag` | `whereAnyTag(namesOrSlugs: string[], locale?: string): this` | Filters models having at least one of the specified tags. | | `whereAllTags` | `whereAllTags(namesOrSlugs: string[], locale?: string): this` | Filters models having all of the specified tags attached simultaneously. | | `withTags` | `withTags(): this` | Preloads attached tags and their translations in a single optimized query. | --- # @veap/categorizable API reference URL: https://veap.pl/docs/reference/packages/categorizable Section: API reference / Packages `@veap/categorizable` provides hierarchical polymorphic categorization for `@veap/database` Active Record models. It features a recursive `Category` model with parent-child nesting, multilingual translations via `@veap/translatable`, query builder scopes, factories, seeders, and tree navigation UI components. ## Subpath exports `@veap/categorizable` exposes four entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/categorizable` | Core mixin (`Categorizable`), base model (`CategorizableModel`), `Category` entity, and query builder (`CategorizableQueryBuilder`). | | `@veap/categorizable/migrations` | Knex migrations creating the `categories` table and `categorizables` polymorphic pivot table. | | `@veap/categorizable/ui` | React UI tree view and category selector components (`CategoryTree`, `CategorySelect`) for forms and admin navigation. | | `@veap/categorizable/testing` | Test utilities, model factory (`CategoryFactory`), and seeder (`CategorySeeder`) for testing environments. | ## Core APIs and classes The table below summarizes the primary classes, mixins, and utilities exported by `@veap/categorizable`: | Symbol | Kind | Signature / Props | Description | | :--- | :--- | :--- | :--- | | `Categorizable` | Function | `Categorizable>(Base: T)` | Class mixin adding category relationships and management methods to any model. | | `CategorizableModel` | Class | `class CategorizableModel extends Model` | Base Active Record class with built-in polymorphic categorization. | | `Category` | Class | `class Category extends TranslatableModel` | Multilingual hierarchical category entity supporting recursive parent-child trees. | | `CategorizableQueryBuilder` | Class | `class CategorizableQueryBuilder extends ModelQueryBuilder` | Extends query builder with category scopes (`whereCategory`, `whereCategoryIn`). | | `CategoryFactory` | Class | `defineFactory(Category, ...)` | Model factory for generating test category hierarchies (exported from `@veap/categorizable/testing`). | | `CategorySeeder` | Class | `class CategorySeeder extends Seeder` | Database seeder generating baseline taxonomic categories (exported from `@veap/categorizable/testing`). | ## Model instance methods Models extending `CategorizableModel` or using `Categorizable(Base)` provide the following instance methods: | Method | Signature | Description | | :--- | :--- | :--- | | `categories` | `categories(): MorphToMany` | Defines the polymorphic many-to-many relationship through `categorizables`. | | `category` | `category(): MorphOneCategory` | Resolves the primary or first category associated with the model. | | `assignCategory` | `assignCategory(category: string \| Category, locale?: string): Promise` | Assigns a single category by UUID, name (finds or creates), or instance. | | `syncCategories` | `syncCategories(categories: (string \| Category)[] \| string \| Category, locale?: string): Promise` | Replaces currently assigned categories with the provided collection. | | `detachCategories` | `detachCategories(categories?: (string \| Category)[] \| string \| Category, locale?: string): Promise` | Detaches specific categories or clears all category assignments. | ## Query builder scopes `CategorizableQueryBuilder` enables filtering models by single or grouped categories: | Method | Signature | Description | | :--- | :--- | :--- | | `whereCategory` | `whereCategory(nameOrSlugOrId: string, locale?: string): this` | Filters models belonging to a category identified by UUID, name, or slug. | | `whereCategoryIn` | `whereCategoryIn(namesOrSlugsOrIds: string[], locale?: string): this` | Filters models matching any category within the specified array. | | `withCategories` | `withCategories(): this` | Preloads attached categories along with their multilingual translations. | | `withCategory` | `withCategory(): this` | Eager-loads the primary category relation. | --- # @veap/commentable API reference URL: https://veap.pl/docs/reference/packages/commentable Section: API reference / Packages `@veap/commentable` provides polymorphic threaded discussion systems for `@veap/database` Active Record models. It supports nested parent-child replies, optional authenticated user links (`@veap/auth`), moderation statuses, query builder scopes, factories, and UI conversation components. ## Subpath exports `@veap/commentable` exposes four entry points in `package.json`: | Subpath | Description | | :--- | :--- | | `@veap/commentable` | Core mixin (`Commentable`), base model (`CommentableModel`), `Comment` entity, and query builder (`CommentableQueryBuilder`). | | `@veap/commentable/migrations` | Knex migrations creating the polymorphic `comments` table with moderation statuses. | | `@veap/commentable/ui` | React UI discussion list and comment submission form components (`CommentSection`, `CommentForm`). | | `@veap/commentable/testing` | Test utilities and model factory (`CommentFactory`) for testing environments. | ## Core APIs and classes The table below summarizes the primary classes, mixins, and utilities exported by `@veap/commentable`: | Symbol | Kind | Signature / Props | Description | | :--- | :--- | :--- | :--- | | `Commentable` | Function | `Commentable>(Base: T)` | Class mixin adding polymorphic comment relationships and submission helpers. | | `CommentableModel` | Class | `class CommentableModel extends Model` | Base Active Record class with built-in polymorphic commenting capabilities. | | `Comment` | Class | `class Comment extends Model` | Entity representing a threaded comment with optional parent and user links. | | `CommentableQueryBuilder` | Class | `class CommentableQueryBuilder extends ModelQueryBuilder` | Extends query builder with comment scopes (`whereHasComments`, `withComments`). | | `CommentFactory` | Class | `defineFactory(Comment, ...)` | Model factory for generating realistic fake comments and replies in tests (exported from `@veap/commentable/testing`). | ## Model instance methods Models extending `CommentableModel` or using `Commentable(Base)` expose the following instance methods: | Method | Signature | Description | | :--- | :--- | :--- | | `comments` | `comments(): MorphMany` | Defines the polymorphic one-to-many relationship to the `comments` table. | | `approvedComments` | `approvedComments(): Promise` | Returns only comments with `status: "approved"`, sorted chronologically. | | `addComment` | `addComment(attributes: CommentInput): Promise` | Creates and associates a new comment (or nested reply via `parentId`) with the model. | ## `Comment` entity methods and relations The `Comment` Active Record model represents individual comments and supports nested conversation threads: | Method / Property | Signature / Type | Description | | :--- | :--- | :--- | | `author` | `author(): Relation` | Belongs-to relation to the authenticated `User` model, if provided. | | `parent` | `parent(): Relation` | Belongs-to relation pointing to the parent comment for nested reply threads. | | `replies` | `replies(): Relation` | Has-many relation returning direct child replies to this comment. | | `status` | `"pending" \| "approved" \| "spam" \| "rejected" \| "trash"` | Current moderation status governing public visibility. | ## Query builder scopes `CommentableQueryBuilder` adds convenient filtering and eager-loading scopes: | Method | Signature | Description | | :--- | :--- | :--- | | `whereHasComments` | `whereHasComments(): this` | Filters models that have at least one associated comment. | | `withComments` | `withComments(): this` | Preloads attached comments in a single database query. | --- # create-veap CLI reference URL: https://veap.pl/docs/reference/packages/create-veap Section: API reference / Packages `create-veap` is the standalone CLI initializer package invoked by `bun create veap`, `pnpm create veap`, `npm create veap`, and `yarn create veap` to scaffold new Veap + Next.js App Router projects. ## Binary and invocation The package exposes a single binary entry point (`create-veap`) and accepts an optional positional project directory argument followed by CLI flags: ```bash bun create veap [project-directory] [options] ``` ## CLI options All command-line flags parsed by `create-veap`: | Flag | Type | Default | Description | | :--- | :--- | :--- | :--- | | `[project-directory]` | Positional string | `"veap-project"` | Target directory where the project is scaffolded. | | `--name ` | `string` | `"veap-project"` | Explicit project name (overrides positional directory argument). | | `--template ` | `string` | `"01-basic"` | Built-in starter template to copy from `templates/`. | | `--choose` | `boolean` | `false` | Forces an interactive prompt to select from available built-in templates. | | `--example ` | `string` | — | Clones and extracts a starter project directly from a GitHub repository URL or branch path. | | `--skip-install` | `boolean` | `false` | Skips running ` install` after copying template files. | | `--help`, `-h` | `boolean` | `false` | Prints usage instructions and exits. | ## Environment detection and behavior When executed, `create-veap` performs the following sequence: 1. **Package manager detection**: Reads `process.env.npm_config_user_agent` to detect `bun`, `pnpm`, `yarn`, or `npm`. 2. **Interactive fallback**: If no project name or `--name` is passed, prompts for the project directory using `@clack/prompts` and asks for confirmation before overwriting a non-empty directory. 3. **Package name normalization**: Validates and normalizes the target directory name into a valid `package.json` `name` field. 4. **Template or example extraction**: Copies the selected built-in template (such as `01-basic`) or downloads and extracts the GitHub repository specified via `--example`. 5. **Dependency installation**: Unless `--skip-install` is passed, spawns ` install` in the newly created project directory and checks for newer versions of `create-veap`. --- # Official plugins catalog URL: https://veap.pl/docs/reference/plugins/official-plugins Section: API reference / Plugins The Veap ecosystem features a collection of officially maintained, modular plugins that provide user-facing capabilities, administrative interfaces, and third-party integrations. Plugins are installed through the `veap` CLI runner, which configures dependencies and registers the plugin in `lib/plugins.gen.ts`: ```bash bun veap add @veap/ ``` > [!NOTE] > When installed, plugins automatically apply their database migrations on startup and mount virtual routes and UI extension points into your host application. --- ## Content and publishing ### `@veap/blog-plugin` A comprehensive blog and content publishing plugin featuring Markdown and MDX editing, drafts, categories, tags, comments, and media integration. - **Installation**: `bun veap add @veap/blog-plugin` - **Dependencies**: `@veap/media-plugin`, `@veap/categorizable`, `@veap/taggable`, `@veap/commentable`, `@veap/translatable` - **Virtual routes**: - Public blog: `/blog`, `/blog/[slug]`, `/blog/category/[slug]`, `/blog/tag/[slug]` - Administration: `/admin/blog` (post editor, list view, publishing controls) - **Extension slots**: Injects navigation items into the admin sidebar and dashboard widgets into `/admin`. --- ## Security and authentication ### `@veap/auth-plugin` Enterprise-grade multi-strategy authentication, session management, and identity orchestrator with pre-styled client forms and type-safe Server Actions. - **Installation**: `bun veap add @veap/auth-plugin` - **Required environment**: `ENCRYPTION_KEY` (base64-encoded key for credential encryption) - **Virtual routes**: `/signin`, `/signup`, `/forgot-password`, `/reset-password`, `/verify-email` - **Client components**: `LoginForm`, `SignUpForm`, `ForgotPasswordForm` exported from `@veap/auth-plugin/client` - **Sub-plugins**: Orchestrates `@veap/auth-google-plugin`, `@veap/auth-passkey-plugin`, and `@veap/auth-totp-plugin` via pluggable form slots. ### `@veap/auth-google-plugin` Google OAuth 2.0 and OpenID Connect single sign-on (SSO) sub-plugin for `@veap/auth-plugin`. - **Installation**: `bun veap add @veap/auth-google-plugin` - **Required environment**: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` - **Integration**: Injects a "Sign in with Google" button slot into `@veap/auth-plugin`'s login form. ### `@veap/auth-passkey-plugin` Biometric WebAuthn Passkeys authentication supporting Apple Touch ID, Face ID, Windows Hello, and YubiKeys. - **Installation**: `bun veap add @veap/auth-passkey-plugin` - **Integration**: Injects biometric sign-in buttons and passkey management settings in user profiles. ### `@veap/auth-totp-plugin` Time-based One-Time Password (TOTP) two-factor authentication compatible with Google Authenticator, 1Password, and Authy. - **Installation**: `bun veap add @veap/auth-totp-plugin` - **Integration**: QR code generator for 2FA enrollment and challenge dialog during sign-in. ### `@veap/rbac-plugin` Visual Role-Based Access Control matrix and permissions manager for administrators. - **Installation**: `bun veap add @veap/rbac-plugin` - **Virtual routes**: `/admin/rbac` (roles, permissions, and assignment matrix) --- ## Administration and dashboard ### `@veap/panel-plugin` The core administration panel shell for Veap applications. - **Installation**: `bun veap add @veap/panel-plugin` - **Features**: Responsive admin layout, collapsible sidebar, breadcrumb integration, dark/light theme switcher, and dynamic widget dashboard. - **Virtual routes**: `/admin` - **Extension points**: Provides `admin:sidebar`, `admin:dashboard`, `admin:header` extension slots consumed by other plugins. ### `@veap/manager-plugin` Visual plugin and extension manager embedded in the admin panel. - **Installation**: `bun veap add @veap/manager-plugin` - **Features**: In-browser plugin gallery, one-click plugin enablement/disablement, and version checking against the registry. - **Virtual routes**: `/admin/plugins` ### `@veap/action-confirm-plugin` Biometric and password re-authentication challenges for high-risk operations (such as data export, account deletion, or permission updates). - **Installation**: `bun veap add @veap/action-confirm-plugin` - **Integration**: Implements the backend handler for the `useConfirmAction()` hook from `@veap/plugins/client`. --- ## Media and storage ### `@veap/media-plugin` Centralized media library with drag-and-drop file uploads, image preview, folder organization, and metadata tagging. - **Installation**: `bun veap add @veap/media-plugin` - **Dependencies**: `@veap/storage` - **Virtual routes**: `/admin/media` (media gallery modal and file picker dialog) - **Extension points**: Provides the `MediaPicker` dialog slot for post editors and profile avatars. ### `@veap/vercel-blob-plugin` Cloud object storage provider integrating Vercel Blob with `@veap/storage`. - **Installation**: `bun veap add @veap/vercel-blob-plugin` - **Required environment**: `BLOB_READ_WRITE_TOKEN` - **Provider ID**: Registers as `"vercel-blob"` on `StorageService`. --- ## System and developer tools ### `@veap/notifications-plugin` In-app notification center with real-time bell badge, dropdown feed, and mark-as-read workflows. - **Installation**: `bun veap add @veap/notifications-plugin` - **Extension points**: Injects the `` widget into `site:header` and `admin:header`. ### `@veap/activity-plugin` Audit log tracker recording administrative mutations, user logins, and critical entity changes. - **Installation**: `bun veap add @veap/activity-plugin` - **Virtual routes**: `/admin/activity` (filterable audit log table) ### `@veap/installer-plugin` Initial setup wizard for new application deployments. - **Installation**: `bun veap add @veap/installer-plugin` - **Virtual routes**: `/install` (database connection verification, administrator account creation, site title setup) ### `@veap/devtools-plugin` In-browser development toolbar providing real-time database query inspection, IoC container exploration, and route debugging during development. - **Installation**: `bun veap add @veap/devtools-plugin` - **Features**: Automatically disabled in production (`NODE_ENV === "production"`). --- # Plugin CLI and generated files reference URL: https://veap.pl/docs/reference/plugins/cli Section: API reference / Plugins `@veap/plugins` and `@veap/database` integrate with the `veap` console runner to manage installed plugins, regenerate static bundler registries (`lib/plugins.gen.ts`), scaffold database migrations (`migrations/index.ts`), and eject npm plugins into local workspace packages (`plugins/`). ## CLI commands for plugins and migrations Run these commands from your application root using `bun veap `: | Command | Package | Description | | :--- | :--- | :--- | | `veap list-plugins` | `@veap/plugins` | Lists all plugins registered in the application along with their version and enabled state. | | `veap add ` | `@veap/plugins` | Installs a plugin package (`--local`, `--skip-install`), prompts for required `.env` variables, installs `veap.dependencies`, and regenerates `lib/plugins.gen.ts`. | | `veap remove ` | `@veap/plugins` | Uninstalls a plugin package (`--skip-install`) and removes its import from `lib/plugins.gen.ts`. | | `veap eject ` | `@veap/plugins` | Clones the plugin's source repository into `plugins/`, configures `"workspaces": ["plugins/*"]` in `package.json`, pins the dependency to `"workspace:*"`, links it via your package manager, and regenerates `lib/plugins.gen.ts`. | | `veap make:plugin ` | `@veap/plugins` | Scaffolds a new local plugin in `plugins/-plugin` (`--dir`, `--skip-register`), registers it as `"workspace:*"` in `package.json`, and regenerates `lib/plugins.gen.ts`. | | `veap plugins:sync` | `@veap/plugins` | Scans `package.json` for installed Veap plugins and regenerates `lib/plugins.gen.ts`. | | `veap make:migration ` | `@veap/database` | Creates a new timestamped migration file in `migrations/` and exports it from `migrations/index.ts`. | | `veap migrate` | `@veap/database` | Applies all pending application and plugin migrations. | | `veap migrate:status` | `@veap/database` | Prints the status and batch number of every registered migration. | | `veap migrate:rollback` | `@veap/database` | Rolls back the most recent migration batch (`--force` bypasses interactive confirmation). | ## Generated project files Two files in a Veap application are maintained automatically by CLI tooling so Next.js and Turbopack can statically analyze imports without dynamic `fs` scanning at runtime: ### `lib/plugins.gen.ts` Generated and updated by `veap add`, `veap remove`, `veap eject`, `veap make:plugin`, and `veap plugins:sync`. It statically imports every installed plugin and exports the `plugins: IPlugin[]` array consumed by `lib/veap.ts`: ```ts // Auto-generated by @veap/plugins CLI — do not edit manually import type { IPlugin } from "@veap/plugins"; import blogPlugin from "@veap/blog-plugin"; export const plugins: IPlugin[] = [blogPlugin]; ``` ### `migrations/index.ts` Aggregates your host application's reversible migration definitions into the `appMigrations` array passed to `.withMigrations(appMigrations)` in `lib/veap.ts`: ```ts import { createArticlesMigration } from "./20261001_create_articles"; export const appMigrations = [createArticlesMigration]; ``` ## How `veap eject` transitions ownership When you run `bun veap eject @veap/blog-plugin`, the CLI performs five deterministic steps: 1. Clones the plugin's git repository into `plugins/blog-plugin`. 2. Adds `"plugins/*"` to the `workspaces` array in your root `package.json`. 3. Rewrites the dependency in `package.json` to `"@veap/blog-plugin": "workspace:*"`. 4. Runs your package manager's install step so `node_modules/@veap/blog-plugin` symlinks to `plugins/blog-plugin`. 5. Regenerates `lib/plugins.gen.ts`. Because the package specifier (`@veap/blog-plugin`) stays identical while its resolution moves to your local `plugins/blog-plugin` workspace folder, your application code requires zero import changes and you gain full edit ownership over the plugin's components, routes, and models.