[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve Vania\ntitle: \"[BUG] - Short Description of Issue\"\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Environment (please complete the following information):** \n- Dart Version: [e.g. 2.10.5]\n- Vania Version: [e.g. 1.0.3]\n- Operating System: [e.g. Ubuntu 20.04, Windows 10]\n- Database: [e.g. PostgreSQL version, MySQL version]\n- Any other relevant backend services or middleware\n\n**Additional context**\nAdd any other context about the problem here.\n\n**Logs**\nIf applicable, include logs to help diagnose the issue.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation-issue.md",
    "content": "---\nname: Documentation Issue\nabout: Report errors, improvements, or suggestions for the documentation.\ntitle: \"[Documentation] - Brief description of the issue\"\nlabels: documentation\nassignees: ''\n\n---\n\n## Description\nProvide a clear and concise description of the issue or suggestion with the documentation.\n\n## Location\nLink to the documentation page or specify the section where the issue occurs.\n\n## Type of Issue\n- [ ] Typo\n- [ ] Inaccuracy\n- [ ] Enhancement\n- [ ] Other\n\n## Suggested Resolution\nDescribe your suggested resolution, if you have one, or what you would expect to see.\n\n## Additional Context\nAdd any other context or screenshots about the documentation issue here.\n\n## Would you like to work on this issue?\n- [ ] Yes\n- [ ] No\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[Feature Request] - Brief Description of Feature\"\nlabels: feature request\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "daysUntilStale: 60\ndaysUntilClose: 7\nstaleLabel: wontfix\nexemptLabels:\n  - bug\n  - enhancement\n  - feature request\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/vania-dart.yml",
    "content": "name: Vania Dart\n\non:\n  push:\n    branches: [ \"main\", \"dev\"]\n  pull_request:\n    branches: [ \"main\", \"dev\"]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Setup Dart\n        uses: dart-lang/setup-dart@v1.2\n        with:\n          sdk: stable\n\n      # Get Flutter packages\n      - name: Install dependencies\n        run: dart pub get\n\n      # Check formatting\n      - name: Format check\n        run: dart format --output=none --set-exit-if-changed .\n        \n      # Analyze the source code\n      - name: Analyze project source\n        run: dart analyze\n\n      # Run tests\n      - name: Run tests\n        run: dart test\n"
  },
  {
    "path": ".gitignore",
    "content": "# https://dart.dev/guides/libraries/private-files\n# Created by `dart pub`\n.dart_tool/\n.idea/\n.vscode/\n\n# Avoid committing pubspec.lock for library packages; see\n# https://dart.dev/guides/libraries/private-files#pubspeclock.\npubspec.lock\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 1.1.1\n\n- Fix(db): switch from mysql_dart to mysql_client to fix UTF-8 encoding issues\n- Chore update dependencies\n\n## 1.1.0\n\n- Add(FormValidation): add support for request validation using FormValidation\n- Add(RequestHelper): introduce helper methods for accessing query strings, IP, and request data\n- Add(Database): add database health check for non-pooled connections\n- Add(Validation): implement builder pattern for defining validation rules (e.g. `field('name').required().string()`)\n- Update(Pagination): use `getParam()` for retrieving `page` parameter in pagination\n- Fix(RequestFile): handle `List<int>` stream in RequestFile.store\n- Improve(Exception): enhance exception handling and error messages\n\n## 1.0.2+2\n\n- Fix(querybuilder): fixed `postgres` params name\n\n## 1.0.2+1\n\n- Fix(querybuilder): remove unnecessary `update_` prefix from update bindings\n\n## 1.0.2\n\n- Fix(Cache): fixed cache issue\n- Fix(Request): fix `hasFile()` and `has()` method behavior\n- Add(Request): add `asList()` method to get the data as a List\n- Add(CSRF): add `CSRF_PROTECTION_ENABLED` config to toggle CSRF protection\n\n## 1.0.1\n\n- Refactor(ORM): fix eager-loading for polymorphic relations (`MorphTo`, `MorphToMany`, `MorphedByMany`) \n- Refactor(ORM): enhance eager-loading for core relations (`hasOne`, `hasMany`, `belongsTo`, `belongsToMany`)\n- Fix(Database): decode byte arrays to `UTF-8` strings in query results\n\n## 1.0.0\n\n- Release stable version 1.0.0  \n- Refactor and improve `QueryBuilder`: remove Eloquent package and implement core version  \n- Implement full ORM functionality in core  \n- Optimize routing system  \n- Enhance request validation  \n- Remove extraneous code and optimize core performance  \n\n## 0.8.2\n\n- Fix (Session): session file locking issue on Linux.  \n- Fix (Static File): Automatically load `index.html` if the `/` route is not defined in web routes.  \n\n## 0.8.1\n\n- Fix(Request Validation): Custom validation rule Future\n- fix(upload): prevent \"Exhausted heap space\" error on large file uploads\n\n## 0.8.0\n\n- Fix session bug\n- Add `RedirectIfAuthenticated` Middleware\n- Add(Response): Back with input\n- Add(Request Validation): `RegExp` rule [#167](https://github.com/vania-dart/framework/issues/167)\n- Add(Request Validation): Custom Validation Rule\n- Add lockFile method for atomic session file handling\n- Remove Isolate from code\n- Refactor HttpRequest handler method to class-based structure\n\n## 0.7.4+1\n\n- chore: Upgraded project dependencies\n\n## 0.7.4\n\n- Add Route name\n- Add(Template engine): Route `{@ route('home') @}` , `{@ route('home', {\"id\":1}) @}`\n- Add(Template engine): assets `{@ asset('css/style.css') @}`\n- Fix `csrf` url excluded\n\n## 0.7.3\n\n- Fix Authentication bug\n- Refactor vania hash\n- Fix incoming requests bug\n- Fix csrf and session issue\n- Fix Authentication issue\n- Add(Template engine): `comment` tag to the template engine `{@# Comments here #@}`\n- Add(Template engine): translate tag to the template engine `{@ trans('welcome', {\"name\": \"Vania\"}) @}`\n\n## 0.7.2\n\n- Add(Template engine): error handler `hasError('email')` , `{@ error('email') @}`\n- Add(Template engine): session message handler `hasSession('email')` , `{@ session('success') @}`\n- Add(Template engine): Cross-Site Request Forgery (`CSRF`) `{@ CSRF @}` , `{@ csrf_token() @}`\n- Add `back()` to the response\n- Refactor exception handling\n- Refactor and sanitize route\n- Add Basic Auth with session to the `Authenticate`\n- chore: Upgraded project dependencies\n\n## 0.7.1\n\n- Add delete Session to the helper\n- Add `async-await` for all Session methods\n\n## 0.7.0\n\n- Feat(Session Management): Session handling capabilities to manage user sessions effectively.\n- Feat(Template engine): Support for rendering HTML templates using a template engine. To handle dynamic HTML rendering with support for control structures.\n- chore: Upgraded project dependencies\n\n## 0.6.2\n\n- Add redirect method [#144](https://github.com/vania-dart/framework/issues/144)\n- Add custom 404 error handling via HTML file [#145](https://github.com/vania-dart/framework/issues/145)\n\n## 0.6.1\n\n- Fix get language path\n\n## 0.6.0\n\n- Refactor incoming route log\n- Remove unnecessary library name\n- Add Multi-language support [#141](https://github.com/vania-dart/framework/issues/141) [Localization](https://vdart.dev/docs/the-basics/localization)\n\n## 0.5.1\n\n- Add support for unique constraints in migrations. Thanks to [WellingtonNico](https://github.com/WellingtonNico) for the contribution.\n- Refactor configuration initialization by moving database setup before service provider registration, ensuring a more reliable startup sequence.\n- Fix Resolved an issue with WebSocket middleware that caused unexpected behavior. See [#132](https://github.com/vania-dart/framework/issues/132) for details.\n- Chore Upgraded dependencies to their latest versions.\n\n## 0.5.0\n\n- Feat: Gate feature for defining user permissions\n- Add WebSocket connect, disconnect, and error handling on the server side (#126)\n- Add user getter method to Request class\n\n## 0.4.3\n\n- Fix nested JSON [#128](https://github.com/vania-dart/framework/issues/128)\n- Add JSON to the request `request.json()`\n\n## 0.4.2\n\n- Fix id auto-increment for PostgreSQL compatibility [#127](https://github.com/vania-dart/framework/issues/118)\n\n## 0.4.1\n\n- Refactor validation rule customErrorMessage to message\n- Fix JSON response for API\n- Fix PostgreSQL sslmode [#118](https://github.com/vania-dart/framework/issues/118)\n- Add enable support for list item submission `form/data` request\n- chore: upgrade dependencies\n\n## 0.4.0\n\n- Feat: a new field validation mechanism by [alirezat66](https://github.com/alirezat66) - [PR 99](https://github.com/vania-dart/framework/pull/99)\n- Fix nested route group [#98](https://github.com/vania-dart/framework/issues/98)\n- Fix middleware issue\n\n## 0.3.5+1\n\n- Fix send message to room\n\n## 0.3.5\n\n- Fix WebSocket session id\n- Add get room members\n- Add is active session\n- Add get active room\n- Add get active sessions\n\n## 0.3.4\n\n- Fix route camel-case issue\n- Add get cookie from the request\n\n## 0.3.3+1\n\n- Fix encoding char-set for form input handling\n\n## 0.3.3\n\n- Fix group route issue\n- Fix uuid issue (#88)\n- Add `Server-Sent Events (SSE)` response (#89) Thank you [Dartly](https://github.com/Dartly)\n\n## 0.3.2\n\n- Refactor Response class\n- Add jsonWithHeader response\n- Add QueryException to model class\n- Add Databse helper\n- Add Create and InsertMany to the ORM\n- Add DB Transaction\n- Add Cookies,Integer,asDouble to request class\n- Fix request body int fields\n- Fix PostgreSQL typo\n- Fix drop table issue when table has foreign key\n\n## 0.3.1\n\n- Fix Refresh token bug([#83](https://github.com/vania-dart/framework/issues/83))\n- Fix WebSocket connect event\n\n## 0.3.0\n\n- Add Parameter validation conditions for the router([#79](https://github.com/vania-dart/framework/issues/79))\n- Add Resource and Any route ([#80](https://github.com/vania-dart/framework/issues/80))\n- Refactor Router, Route Handler\n- Refactor Controller handler for increasing RPS and decreasing latency\n- Refactor Request handler for increase RPS\n- Fix Null params ([#81](https://github.com/vania-dart/framework/issues/81))\n\n## 0.2.7\n\n- Optimize PRS\n- Refactor Controller handler\n- Refactor Request handler\n- Refactor Request class\n- Add none to response type and await for res close\n- Refactor route handler\n- Export Database client\n- Add URL assets to helper\n\n## 0.2.6\n\n- Refactor Local storage class\n- Refactor Cache class\n- Refactor Storage class\n- Refactor Response class\n- Add AWS S3 storage driver\n- Add Storage env config\n\n```env\n    STORAGE=s3\n    STORAGE_S3_BUCKET=''\n    STORAGE_S3_SECRET_KEY=''\n    STORAGE_S3_ACCESS_KEY=''\n    STORAGE_S3_REGION=''\n```\n\n## 0.2.5\n\n- Fix Authentication middleware issue\n- Fix static file url encoding\n- Add Domian to router\n- Add JWT env config\n\n```env\n    JWT_SECRET_KEY\n    JWT_AUDIENCE\n    JWT_ID\n    JWT_ISSUER\n    JWT_SUBJECT\n ```\n\n## 0.2.4\n\n- Fix Route bug\n- Add WebSocket middleware\n- Refactor Auth middleware\n\n## 0.2.3\n\n- Fix Database connection issue with Isolate\n\n## 0.2.2\n\n- Fix Websocket Join and Left room issue([#63](https://github.com/vania-dart/framework/issues/63))\n- Refactor Migration and model\n- Add DatabseClient class\n\n## 0.2.1\n\n- Fix Postgresql bug\n\n## 0.2.0\n\n- Add Redis (base code from dedis dart package)\n- Add Redis Cache Driver\n\n## 0.1.9\n\n- Fix Isolate bug\n\n## 0.1.8\n\n- Fix public and storage file path\n- Refactor Mailable Config to env\n- Refactor Migration class, created migration timestamp Add by [S.M. SHAHi](https://github.com/shahi5472)\n- Refactor Local cache class name to File cache\n\n## 0.1.7+5\n\n- Add pool and poolsize to DatabaseConfig\n\n## 0.1.7+4\n\n- Fix pgsql bug\n- Add alter column to the migration\n\n## 0.1.7+3\n\n- Refactor HttpException to HttpResponseException\n- Add abort method to the helper file\n\n## 0.1.7+2\n\n- Fix route group bug\n\n## 0.1.7+1\n\n- Fix env issue\n\n## 0.1.7\n\n- Add deleteTokens and deleteCurrentToken Auth class\n- Refactor group routing to use a callback function instead of a list\n- Refactor websocket data to payload\n- Add Middleware Handler\n- Fix Webscoket Route bug\n- Update Dependencies\n- Add secure bind\n\n## 0.1.6+1\n\n- Fix Storage issues\n\n## 0.1.6\n\n- Fix Websocket bugs\n- Refactor Storage Converted Instance Methods to Static Methods\n- Refactor Cache Converted Instance Methods to Static Methods\n\n## 0.1.5+1\n\n- Fix env issues\n\n## 0.1.5\n\n- Add Logger\n- Add env file\n\n## 0.1.4\n\n- Add Throttle middleware\n- Add move for upload file in custom folder\n- Add paginate and simplePagination in Eloquent\n\n## 0.1.3\n\n- Add mail\n\n## 0.1.2\n\n- Add multi-isolate server\n\n## 0.1.1+4\n\n- Fix Validation issue on non-required fields\n\n## 0.1.1+3\n\n- Add Singleton base route preFix   to static\n- Readme file\n\n## 0.1.1+2\n\n- Fix bug: Cors file and class name\n\n## 0.1.1+1\n\n- Fix bug: http method options and cors error\n\n- ## 0.1.1\n\n- Add Hash class\n\n## 0.1.0\n\n- Initial beta release\n- Fix a bug related to WebSocket data events\n- Fix authentication check functionality\n- Add `isAuthorized` feature\n- Add `query_builder` from Eloquent package for enhanced functionality\n\n## 0.0.4\n\n- Fix bug: Authentication refresh token\n\n## 0.0.3+1\n\n- Fix bug: migration columns length\n- Add sslmode to the MySqldriver\n\n## 0.0.3\n\n- Fix Bug: Resolved issue with table creation in PostgreSQL\n\n## 0.0.2+1\n\n- Add bigIncrements and  softDeletes columns\n\n## 0.0.2\n\n- Add column index to vania file\n- Code formatted\n\n## 0.0.1\n\n- Initial version.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Vania\n\n## Creating a Pull Request\n\nBefore creating a pull request, please follow these steps:\n\n1. Fork the repository and create your branch from `dev`.\n2. Install all dependencies (`dart pub get`).\n3. Squash your commits and ensure you have a meaningful, [semantic](https://www.conventionalcommits.org/en/v1.0.0/) commit message.\n4. Add tests! Pull Requests without 100% test coverage will not be approved.\n5. Ensure the existing test suite passes locally.\n6. Format your code (`dart format .`).\n7. Analyze your code (`dart analyze --fatal-infos --fatal-warnings .`).\n8. Create the Pull Request targeting the `dev` branch.\n9. Verify that all status checks are passing.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Vania\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Introduction\n\n[Vania Dart Documentation](https://vdart.dev)\n\n[Contributing to Vania](https://github.com/vania-dart/framework/blob/main/CONTRIBUTING.md)\n\nVania is a robust backend framework designed for building high-performance web applications using Dart. With its straightforward approach and powerful features, Vania streamlines the development process for both beginners and experienced developers alike.\n\n## Features\n\n✅ ***Scalability***: Built to handle high traffic, Vania effortlessly scales alongside your application's growth.\n\n✅ ***Developer-Friendly***: Enjoy an intuitive API and clear documentation, making web application development a breeze.\n\n✅ ***Simple Routing***: Define and manage routes effortlessly with Vania's efficient routing system, ensuring robust application architecture.\n\n✅ ***ORM Support***: Interact seamlessly with databases using Vania's powerful ORM system, simplifying data management.\n\n✅ ***Request Data Validation***: Easily validate incoming request data to maintain data integrity and enhance security.\n\n✅ ***Database Migration***: Manage and apply schema changes with ease using Vania's built-in database migration support.\n\n✅ ***WebSocket***: Enable real-time communication between server and clients with WebSocket support, enhancing user experience.\n\n✅ ***Command-Line Interface (CLI)***: Streamline development tasks with Vania's simple CLI, offering commands for creating migrations, generating models, and more.\n\nExperience the simplicity and power of Vania for your next web application project\n\n# Quick Start 🚀\n\nEnsure that you have the [Dart SDK](https://dart.dev) installed on your machine.\n\nYouTube Video [Quick Start](https://www.youtube.com/watch?v=k8ol0F4bDKs)\n\nYouTube Video [Dart & flutter Fullstack with Vania](https://youtu.be/1tfqpusIXwQ)\n\n[![Quick Start](https://img.youtube.com/vi/k8ol0F4bDKs/0.jpg)](https://www.youtube.com/watch?v=k8ol0F4bDKs \"Quick Start\")\n\n## Installing 🧑‍💻\n\n```shell\n# 📦 Install the vania cli from pub.dev\ndart pub global activate vania_cli\n```\n\n## Creating a Project ✨\n\nUse the `vania create` command to create a new project.\n\n```shell\n# 🚀 Create a new project called \"blog\"\nvania create blog\n```\n\n## Start the Dev Server 🏁\n\nOpen the newly created project and start the development server.\n\n```shell\n# 🏁 Start the dev server\nvania serve\n```\n\nYou can also include the `--vm` flag to enable VM service.\n\n## Create a Production Build 📦\n\nCreate a production build:\n\n```shell\n# 📦 Create a production build\nvania build\n```\n\nFor production use, deploy using the provided `Dockerfile` and `docker-compose.yml` files to deploy anywhere.\n\nExample CRUD API Project [Github](https://github.com/vania-dart/example)\n"
  },
  {
    "path": "README.tr.md",
    "content": "\n# Introduction\n\nVania, Dart kullanarak yüksek performanslı web uygulamaları oluşturmak için tasarlanmış sağlam bir backend çerçevesidir. Basit yaklaşımı ve güçlü özellikleriyle Vania, hem yeni başlayanlar hem de deneyimli geliştiriciler için geliştirme sürecini kolaylaştırır.\n\n## Features\n\n✅ ***Ölçeklenebilirlik***: Yüksek trafikle başa çıkmak için tasarlanan Vania, uygulamanızın büyümesiyle birlikte zahmetsizce kendini ölçekler.\n\n✅ ***Geliştici Dostu***: Web uygulaması geliştirmeyi çocuk oyuncağı haline getiren bir API ve açık kaynağın keyfini çıkarın.\n\n✅ ***Kolay Rota Oluşturma***: Vania'nın verimli yönlendirme sistemi ile rotaları zahmetsizce tanımlayın ve yönetin, sağlam bir uygulama mimarisi elde edin.\n\n✅ ***ORM Desteği***: Vania'nın güçlü ORM sistemini kullanarak veritabanlarıyla sorunsuz bir şekilde etkileşim kurun ve veri yönetimini basitleştirin.\n\n✅ ***İstek Verisi Doğrulama***: Veri bütünlüğünü korumak ve güvenliği artırmak için gelen talep verilerini kolayca doğrulayarak kontrol altında tutun.\n\n✅ ***Veritabanı Yönetimi***: Vania'nın yerleşik veritabanı taşıma desteğini kullanarak şema değişikliklerini kolaylıkla yönetin ve uygulayın.\n\n✅ ***WebSocket***: WebSocket desteği ile sunucu ve istemciler arasında gerçek zamanlı iletişim sağlayarak kullanıcı deneyimini geliştirin.\n\n✅ ***Komut Satırı Arayüzü (CLI)***: Vania'nın veritabanı oluşturma, model oluşturma ve daha fazlası için komutlar sunan basit CLI'si ile geliştirme işlemlerini kolaylaştırın.\n\nBir sonraki web uygulaması projeniz için Vania'nın basitliğini ve gücünü deneyimleyin\n\nDokümantasyona buradan göz atın: [Vania Dart Dokümantasyonu](https://vdart.dev)\n\n# Hızlı Başlangıç 🚀\n\n[Dart SDK](https://dart.dev) 'in makinenizde kurulu olduğundan emin olun.\n\nYouTube Video [Hızlı Başlangıç](https://www.youtube.com/watch?v=5LiDQlqhNto)\n\n[![Quick Start](http://img.youtube.com/vi/5LiDQlqhNto/0.jpg)](https://www.youtube.com/watch?v=5LiDQlqhNto \"Hızlı Başlangıç\")\n\n## Kurulum 🧑‍💻\n\n```shell\n# 📦 pub.dev adresinden Vania CLI kurulumunu gerçekleştirin\ndart pub global activate vania_cli\n```\n\n## Proje Oluşturma ✨\n\nOluşturmak için `vania create` komutunu kullanın.\n\n```shell\n# 🚀 \"blog\" isminde yeni bir proje oluşturun\nvania create blog\n```\n\n## Geliştirme Sunucusunu Başlatın 🏁\n\nYeni oluşturulan projeyi açın ve geliştirme sunucusunu başlatın.\n\n```shell\n# 🏁 Sunucuyu başlat\nvania serve\n```\n\nSanal Makine (VM) hizmetini etkinleştirmek için `--vm` bayrağını da ekleyebilirsiniz.\n\n## Projeyi Derleyin 📦\n\nHazırladığınız projeyi derleyin\n\n```shell\n# 📦 Projeyi derleyin\nvania build\n```\n\nProje kullanımı için, herhangi bir yere dağıtmak üzere sağlanan `Dockerfile` ve `docker-compose.yml` dosyalarını kullanarak dağıtın.\n\nÖrnek CRUD API Projesi [Github](https://github.com/vania-dart/example)\n"
  },
  {
    "path": "analysis_options.yaml",
    "content": "# This file configures the static analysis results for your project (errors,\n# warnings, and lints).\n#\n# This enables the 'recommended' set of lints from `package:lints`.\n# This set helps identify many issues that may lead to problems when running\n# or consuming Dart code, and enforces writing Dart using a single, idiomatic\n# style and format.\n#\n# If you want a smaller set of lints you can change this to specify\n# 'package:lints/core.yaml'. These are just the most critical lints\n# (the recommended set includes the core lints).\n# The core lints are also what is used by pub.dev for scoring packages.\n\ninclude: package:lints/recommended.yaml\n\n# Uncomment the following section to specify additional rules.\n\n# linter:\n#   rules:\n#     - camel_case_types\n\n# analyzer:\n#   exclude:\n#     - path/to/excluded/files/**\n\n# For more information about the core and recommended set of lints, see\n# https://dart.dev/go/core-lints\n\n# For additional information about configuring this file, see\n# https://dart.dev/guides/language/analysis-options\n"
  },
  {
    "path": "example/app_exception_handler.dart",
    "content": "import 'package:vania/src/exception/database_exception.dart';\nimport 'package:vania/src/exception/exception_handler.dart';\nimport 'package:vania/src/exception/not_found_exception.dart';\nimport 'package:vania/src/exception/query_exception.dart';\nimport 'package:vania/src/exception/validation_exception.dart';\nimport 'package:vania/src/http/request/request.dart';\nimport 'package:vania/src/http/response/response.dart';\nimport 'package:vania/src/service/service_provider.dart';\nimport 'package:vania/application.dart';\n\nclass DatabaseExceptionHandler extends ExceptionHandler<DatabaseException> {\n  @override\n  Response handle(DatabaseException exception, Request? request) {\n    return Response.json({\n      'success': false,\n      'message': 'Database error occurred',\n      'error': exception.message,\n    }, 500);\n  }\n}\n\nclass QueryExceptionHandler extends ExceptionHandler<QueryException> {\n  @override\n  Response handle(QueryException exception, Request? request) {\n    return Response.json({\n      'success': false,\n      'message': 'Query error occurred',\n      'error': exception.cause,\n    }, 500);\n  }\n}\n\nclass NotFoundExceptionHandler extends ExceptionHandler<NotFoundException> {\n  @override\n  Response handle(NotFoundException exception, Request? request) {\n    return Response.json({\n      'success': false,\n      'message': exception.message,\n    }, 404);\n  }\n}\n\nclass ValidationExceptionHandler extends ExceptionHandler<ValidationException> {\n  @override\n  Response handle(ValidationException exception, Request? request) {\n    return Response.json({\n      'success': false,\n      'message': 'Validation failed',\n      'errors': exception.message,\n    }, 422);\n  }\n}\n\nclass ThirdPartyExceptionHandler extends GeneralExceptionHandler {\n  @override\n  Response? handle(dynamic exception, Request? request) {\n    return Response.json({\n      'success': false,\n      'message': 'An unexpected error occurred',\n      'error': exception.toString(),\n    }, 500);\n  }\n}\n\nclass AppExceptionServiceProvider extends ServiceProvider {\n  @override\n  Future<void> boot() async {}\n\n  @override\n  Future<void> register() async {\n    Application().addExceptionHandlers({\n      DatabaseException: DatabaseExceptionHandler(),\n      QueryException: QueryExceptionHandler(),\n      NotFoundException: NotFoundExceptionHandler(),\n      ValidationException: ValidationExceptionHandler(),\n    });\n    Application().setGeneralExceptionHandler(\n      ThirdPartyExceptionHandler(),\n    );\n  }\n}\n"
  },
  {
    "path": "example/main.dart",
    "content": "import 'package:vania/vania.dart';\nimport 'app_exception_handler.dart';\n\nvoid main() async {\n  Application().initialize(\n    config: {\n      'providers': [AppExceptionServiceProvider()],\n    },\n  );\n}\n"
  },
  {
    "path": "lib/application.dart",
    "content": "import 'src/container.dart';\nimport 'src/exception/exception_handler.dart';\nimport 'src/ioc_container.dart';\nimport 'src/localization_handler/localization.dart';\nimport 'src/server/base_http_server.dart';\nimport 'src/env_handler/env.dart';\nimport 'src/http/request/request_handler.dart';\nimport 'src/http/session/session_manager.dart';\nimport 'src/utils/helper.dart' show env;\n\nclass Application extends Container {\n  static Application? _singleton;\n\n  final Map<Type, ExceptionHandler> _exceptionHandlers = {};\n  GeneralExceptionHandler? _generalExceptionHandler;\n\n  factory Application() {\n    if (_singleton == null) {\n      _singleton = Application._internal();\n      Env().load();\n      Localization().init();\n    }\n    return _singleton!;\n  }\n\n  Application._internal();\n\n  void addExceptionHandler<T>(ExceptionHandler<T> handler) {\n    _exceptionHandlers[T] = handler;\n  }\n\n  void addExceptionHandlers(Map<Type, ExceptionHandler> handlers) {\n    _exceptionHandlers.addAll(handlers);\n  }\n\n  void setGeneralExceptionHandler(GeneralExceptionHandler handler) {\n    _generalExceptionHandler = handler;\n  }\n\n  ExceptionHandler? getExceptionHandler(Type type) {\n    return _exceptionHandlers[type];\n  }\n\n  GeneralExceptionHandler? getGeneralExceptionHandler() {\n    return _generalExceptionHandler;\n  }\n\n  late BaseHttpServer _server;\n\n  Future<void> initialize({\n    required Map<String, dynamic> config,\n    List<String> args = const [],\n  }) async {\n    IoCContainer().register<RequestHandler>(() => RequestHandler());\n    IoCContainer().register<SessionManager>(\n      () => SessionManager(),\n      singleton: true,\n    );\n    if (env('APP_KEY') == '' || env('APP_KEY') == null) {\n      throw Exception('Key not found');\n    }\n\n    _server = BaseHttpServer(config: config, args: args);\n    _server.startServer();\n  }\n\n  Future<void> close() async {\n    _server.httpServer?.close();\n  }\n}\n"
  },
  {
    "path": "lib/authentication.dart",
    "content": "export 'src/authentication/authentication.dart';\nexport 'src/authentication/authenticate.dart';\nexport 'src/authentication/redirect_if_authenticated.dart';\nexport 'src/authentication/has_api_tokens.dart';\nexport 'src/authentication/gate/gate.dart';\n"
  },
  {
    "path": "lib/database.dart",
    "content": "export 'src/database/seeder/seeder.dart';\nexport 'src/database/seeder/seeder_runner.dart';\nexport 'src/database/seeder/seeder_factory.dart';\nexport 'src/enum/column_index.dart';\n"
  },
  {
    "path": "lib/http/controller.dart",
    "content": "export '../src/http/controller/controller.dart';\n"
  },
  {
    "path": "lib/http/form_validation.dart",
    "content": "export '../src/contract/http/request/form_validation.dart';\n"
  },
  {
    "path": "lib/http/middleware.dart",
    "content": "export '../src/route/middleware/throttle.dart';\nexport '../src/http/middleware/middleware.dart';\n"
  },
  {
    "path": "lib/http/request.dart",
    "content": "export '../src/http/request/request.dart';\nexport '../src/http/request/request_file.dart';\nexport '../src/http/validation/validation_chain/export_chain_validation.dart';\nexport '../src/http/validation/custom_validation_rule.dart';\nexport '../src/exception/base_http_exception.dart';\nexport '../src/exception/http_exception.dart';\nexport '../src/exception/redirect_exception.dart';\nexport '../src/utils/request_helper.dart';\n"
  },
  {
    "path": "lib/http/response.dart",
    "content": "export '../src/http/response/response.dart';\nexport '../src/view_engine/helper.dart';\n"
  },
  {
    "path": "lib/mail.dart",
    "content": "export 'src/mail/mailable.dart';\nexport 'src/mail/content.dart';\nexport 'src/mail/envelope.dart';\nexport 'src/mail/mail_view.dart';\nexport 'package:mailer/src/entities/address.dart';\nexport 'package:mailer/src/entities/attachment.dart';\n"
  },
  {
    "path": "lib/migration.dart",
    "content": "export 'src/database/migration/runners/migration_runner.dart';\nexport 'src/database/migration/migration.dart';\nexport 'src/database/migration/builders/column_types.dart';\nexport 'src/database/migration/builders/schema.dart';\nexport 'src/database/migration/migration_connection.dart';\n"
  },
  {
    "path": "lib/orm/model.dart",
    "content": "export '../src/database/orm/model.dart';\n"
  },
  {
    "path": "lib/query_builder.dart",
    "content": "export 'src/database/db.dart';\nexport 'src/contract/database/query_builder/query_builder.dart';\nexport 'src/database/monitoring/database_monitor.dart';\nexport 'src/exception/database_exception.dart';\nexport 'src/exception/query_exception.dart';\nexport 'src/database/isolate_db.dart';\n"
  },
  {
    "path": "lib/route.dart",
    "content": "export 'src/route/router.dart';\nexport 'src/route/route.dart';\n"
  },
  {
    "path": "lib/service_provider.dart",
    "content": "export 'package:vania/src/service/service_provider.dart';\n"
  },
  {
    "path": "lib/src/authentication/authenticate.dart",
    "content": "import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';\nimport 'package:meta/meta.dart';\nimport 'package:vania/src/authentication/authentication.dart';\nimport 'package:vania/src/exception/unauthenticated.dart';\nimport 'package:vania/src/http/middleware/middleware.dart';\nimport 'package:vania/src/http/request/request.dart';\nimport 'package:vania/src/http/response/response.dart';\nimport 'package:vania/src/utils/helper.dart';\n\nclass Authenticate extends Middleware {\n  final String? guard;\n  final bool basic;\n  final String loginPath;\n  Authenticate({this.guard, this.basic = false, this.loginPath = '/login'});\n\n  @mustCallSuper\n  @override\n  handle(Request req) async {\n    if (basic) {\n      bool loggedIn = await getSession<bool?>('logged_in') ?? false;\n      String guard = await getSession<String?>('auth_guard') ?? '';\n      if (loggedIn && guard.isNotEmpty) {\n        Map<String, dynamic> user =\n            await getSession<Map<String, dynamic>?>('auth_user') ?? {};\n        Auth().guard(guard).login(user[guard]);\n      } else {\n        throw Unauthenticated(\n          message: loginPath,\n          responseType: ResponseType.html,\n        );\n      }\n    } else {\n      String? token = req.header('authorization')?.replaceFirst('Bearer ', '');\n      try {\n        if (guard == null) {\n          await Auth().check(token ?? '');\n        } else {\n          await Auth().guard(guard!).check(token ?? '');\n        }\n      } on JWTExpiredException {\n        throw Unauthenticated(message: 'Token expired');\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/authentication/authentication.dart",
    "content": "import 'dart:convert';\n\nimport 'package:crypto/crypto.dart';\nimport 'package:vania/src/config/config.dart';\nimport 'package:vania/src/database/orm/model.dart';\nimport 'package:vania/src/exception/invalid_argument_exception.dart';\nimport 'package:vania/src/exception/unauthenticated.dart';\nimport 'package:vania/src/utils/helper.dart';\n\nimport 'has_api_tokens.dart';\nimport 'model/personal_access_token.dart';\n\nclass Auth {\n  static final Auth _singleton = Auth._internal();\n  factory Auth() => _singleton;\n  Auth._internal();\n\n  String _userGuard = 'default';\n\n  bool _loggedIn = false;\n\n  final Map<String, dynamic> _user = {};\n\n  bool get loggedIn => _loggedIn;\n\n  Map<String, dynamic> user() => _user[_userGuard];\n\n  dynamic id() => _user[_userGuard]['id'] ?? _user[_userGuard]['_id'];\n\n  dynamic get(String filed) => _user[_userGuard][filed];\n\n  /// Sets the authentication guard to the specified guard name.\n  ///\n  /// This method changes the current user guard to the specified guard, which\n  /// determines the user provider and authentication logic to use. If the\n  /// specified guard is not defined in the configuration, an\n  /// [InvalidArgumentException] is thrown.\n  ///\n  /// Returns the current instance of the `Auth` class.\n  ///\n  /// Throws:\n  /// - [InvalidArgumentException] if the specified guard is not defined.\n  ///\n  Auth guard(String guard) {\n    if (Config().get('auth')['guards'][guard] == null) {\n      throw InvalidArgumentException('Auth guard [$guard] is not defined.');\n    }\n\n    _userGuard = guard;\n    return this;\n  }\n\n  /// Set the current user from a given user object.\n  ///\n  /// The object is expected to contain at least the `id` key.\n  ///\n  /// The user object will be stored in the `_user` map with the key being the\n  /// current guard.\n  ///\n  /// Returns the current instance of the `Auth` class.\n  Auth login(Map<String, dynamic> user, [bool basic = false]) {\n    _user[_userGuard] = user;\n    if (basic) {\n      _updateSession();\n    }\n    return this;\n  }\n\n  Future<void> logout() async {\n    await deleteSession('logged_in');\n    await deleteSession('auth_guard');\n    await deleteSession('auth_user');\n    _loggedIn = false;\n  }\n\n  /// Updates the current session with the given user and guard.\n  ///\n  /// The function sets the `logged_in` session key to true, and updates the\n  /// `auth_user` and `auth_guard` session keys if they are not already set or\n  /// have changed. The function also sets the `_isAuthorized` flag to true.\n  ///\n  /// The session is only updated if the user and guard have changed, and the\n  /// function does not return anything.\n  Future<void> _updateSession() async {\n    await setSession('logged_in', true);\n    await setSession('auth_guard', _userGuard);\n    await setSession('auth_user', _user);\n    _loggedIn = true;\n  }\n\n  /// Create new token for the given user.\n  ///\n  /// The token created is a JWT token that contains the user's ID and the\n  /// guard's name. The token is then signed with the secret key from the\n  /// environment variable `JWT_SECRET_KEY`.\n  ///\n  /// If `withRefreshToken` is true, a refresh token is also created and\n  /// returned in the `refresh_token` key of the map.\n  ///\n  /// If `customToken` is true, the token is not stored in the database and\n  /// is returned as is.\n  ///\n  /// The `expiresIn` parameter is the duration after which the token will\n  /// expire. If not provided, the token will expire after 1 hour.\n  ///\n  /// Returns a map containing the following keys:\n  ///\n  /// * `access_token`: the JWT token\n  /// * `refresh_token`: the refresh token if `withRefreshToken` is true\n  /// * `expires_in`: the duration after which the token will expire in seconds\n  Future<Map<String, dynamic>> createToken({\n    Duration? expiresIn,\n    bool withRefreshToken = false,\n    bool customToken = false,\n  }) async {\n    Map<String, dynamic> token = HasApiTokens()\n        .setPayload(_user[_userGuard])\n        .createToken(_userGuard, expiresIn, withRefreshToken);\n\n    if (!customToken) {\n      await PersonalAccessToken().query.insert({\n        'name': _userGuard,\n        'tokenable_id': _user[_userGuard]['id'],\n        'token': md5.convert(utf8.encode(token['access_token'])).toString(),\n        'created_at': DateTime.now(),\n      });\n    }\n\n    return token;\n  }\n\n  /// Create a new token by given refresh token.\n  //\n  /// The given token must be a valid refresh token.\n  //\n  /// The `expiresIn` parameter is the duration after which the token will\n  /// expire. If not provided, the token will expire after 1 hour.\n  //\n  /// The `customToken` parameter determines if the token should be stored in\n  /// the database or not. If `customToken` is true, the token is not stored\n  /// in the database.\n  //\n  /// Returns a map containing the following keys:\n  //\n  /// * `access_token`: the JWT token\n  /// * `refresh_token`: the refresh token\n  /// * `expires_in`: the duration after which the token will expire in seconds\n  Future<Map<String, dynamic>> createTokenByRefreshToken(\n    String token, {\n    Duration? expiresIn,\n    bool customToken = false,\n  }) async {\n    final newToken = HasApiTokens().refreshToken(\n      token.replaceFirst('Bearer ', ''),\n      _userGuard,\n      expiresIn,\n    );\n\n    if (!customToken) {\n      Map<String, dynamic> payload = HasApiTokens().verify(\n        token.replaceFirst('Bearer ', ''),\n        _userGuard,\n        'refresh_token',\n      );\n\n      Model? authenticatable = Config().get(\n        'auth',\n      )['guards'][_userGuard]['provider'];\n\n      if (authenticatable == null) {\n        throw InvalidArgumentException('Authenticatable class not found');\n      }\n\n      Map? user = await authenticatable.query\n          .where('id', '=', payload['id'])\n          .first();\n\n      if (user == null) {\n        throw Unauthenticated(message: 'Invalid token');\n      }\n\n      _user[_userGuard] = user;\n      await PersonalAccessToken().query.insert({\n        'name': _userGuard,\n        'tokenable_id': user['id'],\n        'token': md5.convert(utf8.encode(newToken['access_token'])).toString(),\n        'created_at': DateTime.now(),\n      });\n    }\n\n    return newToken;\n  }\n\n  /// Delete all the tokens for the user that is currently logged in.\n  ///\n  /// This is useful when a user logs out and you want to delete all of their\n  /// tokens.\n  ///\n  /// Returns true if the operation was successful.\n  Future<bool> deleteTokens(dynamic userId) async {\n    await PersonalAccessToken().query.where('tokenable_id', '=', userId).update(\n      {'deleted_at': DateTime.now()},\n    );\n\n    return true;\n  }\n\n  /// Delete the current token for the user that is currently logged in.\n  ///\n  /// This function marks the current token as deleted by setting the `deleted_at`\n  /// field to the current time in the database. This operation helps to effectively\n  /// invalidate the token.\n  ///\n  /// Returns a Future that resolves to true if the operation was successful.\n  ///\n  Future<bool> deleteCurrentToken(String token) async {\n    await PersonalAccessToken().query\n        .where('token', '=', md5.convert(utf8.encode(token)).toString())\n        .update({'deleted_at': DateTime.now()});\n    return true;\n  }\n\n  /// Validates and checks the provided token for authentication.\n  ///\n  /// This function verifies the provided JWT access token and checks its validity\n  /// against stored personal access tokens. If the token is valid, it updates the\n  /// token's last used timestamp and sets the current user context, marking them\n  /// as authorized.\n  ///\n  /// The function handles both custom and stored tokens. For custom tokens, it\n  /// sets the user payload directly. For stored tokens, it ensures the token exists\n  /// and is not marked as deleted, then retrieves the associated user.\n  ///\n  /// Throws:\n  /// - [Unauthenticated] if the token is invalid or not found.\n  /// - [InvalidArgumentException] if the authenticatable provider class is not found.\n  ///\n  /// Returns a Future that resolves to true if the token is valid and the user is successfully authenticated.\n  ///\n  Future<bool> check(\n    String token, {\n    Map<String, dynamic>? user,\n    bool isCustomToken = false,\n  }) async {\n    Map<String, dynamic> payload = HasApiTokens().verify(\n      token.replaceFirst('Bearer ', ''),\n      _userGuard,\n      'access_token',\n    );\n\n    if (isCustomToken) {\n      _user[_userGuard] = payload;\n      _loggedIn = true;\n      return true;\n    } else {\n      Map<String, dynamic>? exists = await PersonalAccessToken().query\n          .where('token', '=', md5.convert(utf8.encode(token)).toString())\n          .whereNull('deleted_at')\n          .first(['id']);\n      // Throw 401 Error if token not found\n      if (exists == null) {\n        throw Unauthenticated(message: 'Invalid token');\n      }\n\n      await PersonalAccessToken().query\n          .where('token', '=', md5.convert(utf8.encode(token)).toString())\n          .update({'last_used_at': DateTime.now()});\n\n      if (user == null) {\n        Model? authenticatable = Config().get(\n          'auth',\n        )['guards'][_userGuard]['provider'];\n\n        if (authenticatable == null) {\n          throw InvalidArgumentException('Authenticatable class not found');\n        }\n        user = await authenticatable.query\n            .where('id', '=', payload['id'])\n            .first();\n      }\n\n      if (user != null) {\n        _user[_userGuard] = user;\n        _loggedIn = true;\n        return true;\n      } else {\n        throw Unauthenticated(message: 'Invalid token');\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/authentication/gate/gate.dart",
    "content": "class Gate {\n  static final Gate _instance = Gate._internal();\n\n  factory Gate() {\n    return _instance;\n  }\n\n  Gate._internal();\n\n  /// All of the defined abilities.\n  final Map<String, Function> _abilities = {};\n\n  /// Define a new ability.\n  /// Gate().define('canDeletePost', () {\n  ///   return user.id == post.user_id;\n  /// });\n  void define(String ability, Function callback) {\n    _abilities[ability] = callback;\n  }\n\n  /// Determine if all of the given abilities should be granted for the current user.\n  /// Gate().allows('canDeletePost');\n  bool allows(String ability) {\n    final gate = _abilities[ability];\n    if (gate != null) {\n      return gate();\n    }\n    return false;\n  }\n\n  /// Determine if a given ability has been defined.\n  bool has(String ability) {\n    return _abilities.containsKey(ability);\n  }\n\n  /// Determine if any of the given abilities should be denied for the current user.\n  bool denies(String ability) {\n    return !allows(ability);\n  }\n}\n"
  },
  {
    "path": "lib/src/authentication/has_api_tokens.dart",
    "content": "import 'dart:convert';\n\nimport 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';\nimport 'package:vania/src/exception/unauthenticated.dart';\nimport 'package:vania/src/utils/helper.dart' show env;\n\nclass HasApiTokens {\n  static final HasApiTokens _singleton = HasApiTokens._internal();\n  factory HasApiTokens() => _singleton;\n  HasApiTokens._internal();\n\n  Map<String, dynamic>? _userPayload = {};\n\n  HasApiTokens setPayload(Map<String, dynamic> payload) {\n    _userPayload = payload;\n    return this;\n  }\n\n  /// Create new token for the given user.\n  ///\n  /// The token created is a JWT token that contains the user's ID, the\n  /// guard's name, and the user's payload. The token is then signed with\n  /// the secret key from the environment variable `JWT_SECRET_KEY`.\n  ///\n  /// If `withRefreshToken` is true, a refresh token is also created and\n  /// returned in the `refresh_token` key of the map.\n  ///\n  /// The `expiresIn` parameter is the duration after which the token will\n  /// expire. If not provided, the token will expire after 1 hour.\n  ///\n  /// Returns a map containing the following keys:\n  ///\n  /// * `access_token`: the JWT token\n  /// * `refresh_token`: the refresh token if `withRefreshToken` is true\n  /// * `expires_in`: the duration after which the token will expire in seconds\n  Map<String, dynamic> createToken([\n    String guard = '',\n    Duration? expiresIn,\n    bool withRefreshToken = false,\n  ]) {\n    String secretKey = env('JWT_SECRET_KEY') ?? env<String>('APP_KEY');\n    Map<String, dynamic> userId = {'id': _userPayload?['id']};\n    if (_userPayload?['id'] == null) {\n      userId = {'_id': _userPayload?['_id']};\n    }\n\n    final jwt = JWT(\n      {'user': jsonEncode(_userPayload), 'type': 'access_token', ...userId},\n      audience: env('JWT_AUDIENCE') == null\n          ? null\n          : Audience.one(env<String>('JWT_AUDIENCE')),\n      jwtId: env<String?>('JWT_ID'),\n      issuer: env<String?>('JWT_ISSUER'),\n      subject: env<String?>('JWT_SUBJECT'),\n    );\n    Map<String, dynamic> payload = {};\n    Duration expirationTime = expiresIn ?? const Duration(hours: 1);\n\n    String accessToken = jwt.sign(\n      SecretKey('$secretKey$guard'),\n      expiresIn: expirationTime,\n    );\n\n    payload['access_token'] = accessToken;\n\n    if (withRefreshToken) {\n      final jwtRefresh = JWT({...userId, 'type': 'refresh_token'});\n      String refreshToken = jwtRefresh.sign(\n        SecretKey('$secretKey$guard'),\n        expiresIn: const Duration(days: 120),\n      );\n      payload['refresh_token'] = refreshToken;\n    }\n\n    payload['expires_in'] = DateTime.now()\n        .add(expirationTime)\n        .toIso8601String();\n\n    return payload;\n  }\n\n  /// Creates a new token from a given refresh token.\n  ///\n  /// This function verifies the given refresh token and if it is valid, creates a\n  /// new token using the `createToken` method.\n  ///\n  /// The `expiresIn` parameter is the duration after which the new token will\n  /// expire. If not provided, the token will expire after 1 hour.\n  ///\n  /// Returns a map containing the following keys:\n  ///\n  /// * `access_token`: the new JWT token\n  /// * `refresh_token`: the new refresh token\n  /// * `expires_in`: the duration after which the new token will expire in seconds\n  Map<String, dynamic> refreshToken(\n    String token, [\n    String guard = '',\n    Duration? expiresIn,\n  ]) {\n    final jwt = verify(token, guard, 'refresh_token');\n    _userPayload = jwt;\n    return createToken(guard, expiresIn, true);\n  }\n\n  /// Verifies a given JWT token and returns the payload if it is valid.\n  ///\n  /// The `expectedType` parameter is the expected type of the token. If the\n  /// token is not of this type, an `Unauthenticated` exception will be thrown.\n  ///\n  /// The `guard` parameter is the guard to use when verifying the token. The\n  /// secret key will be concatenated with the guard before verifying the\n  /// token.\n  ///\n  /// Returns a map containing the payload of the token if it is valid.\n  ///\n  /// Throws an `Unauthenticated` exception if the token is invalid or expired.\n  Map<String, dynamic> verify(String token, String guard, String expectedType) {\n    String secretKey = env('JWT_SECRET_KEY') ?? env<String>('APP_KEY');\n    try {\n      final jwt = JWT.verify(\n        token,\n        SecretKey('$secretKey$guard'),\n        audience: env('JWT_AUDIENCE') == null\n            ? null\n            : Audience.one(env<String>('JWT_AUDIENCE')),\n        jwtId: env<String?>('JWT_ID'),\n        issuer: env<String?>('JWT_ISSUER'),\n        subject: env<String?>('JWT_SUBJECT'),\n      );\n\n      final payload = jwt.payload;\n      if (payload is! Map<String, dynamic>) {\n        throw Unauthenticated(message: 'Invalid JWT payload type');\n      }\n\n      if (payload['type'] != expectedType) {\n        throw Unauthenticated(message: 'Invalid token type');\n      }\n\n      return payload;\n    } on JWTExpiredException {\n      rethrow;\n    } on JWTException {\n      throw Unauthenticated(message: 'Invalid token');\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/authentication/model/personal_access_token.dart",
    "content": "import 'package:vania/src/database/orm/model.dart';\n\nclass PersonalAccessToken extends Model {\n  @override\n  List<String> guarded = ['id'];\n}\n"
  },
  {
    "path": "lib/src/authentication/redirect_if_authenticated.dart",
    "content": "import 'package:vania/src/exception/redirect_exception.dart';\nimport 'package:vania/src/http/middleware/middleware.dart';\nimport 'package:vania/src/http/request/request.dart';\nimport 'package:vania/src/http/response/response.dart';\nimport 'package:vania/src/utils/helper.dart' show getSession;\n\nclass RedirectIfAuthenticated extends Middleware {\n  final String path;\n  RedirectIfAuthenticated({required this.path});\n\n  @override\n  Future handle(Request req) async {\n    bool loggedIn = await getSession<bool?>('logged_in') ?? false;\n    if (loggedIn) {\n      throw RedirectException(message: path, responseType: ResponseType.html);\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/aws/s3_client.dart",
    "content": "import 'dart:convert';\nimport 'dart:typed_data';\n\nimport 'package:crypto/crypto.dart';\nimport 'package:vania/src/extensions/date_time_extension.dart';\nimport 'package:vania/src/utils/helper.dart';\n\nclass S3Client {\n  static final S3Client _singleton = S3Client._internal();\n  factory S3Client() => _singleton;\n  S3Client._internal();\n\n  final String _region = env<String>('STORAGE_S3_REGION', '');\n  final String _bucket = env<String>('STORAGE_S3_BUCKET', '');\n  final String _secretKey = env<String>('STORAGE_S3_SECRET_KEY', '');\n  final String _accessKey = env<String>('STORAGE_S3_ACCESS_KEY', '');\n\n  Uri buildUri(String key) {\n    return Uri.https('$_bucket.s3.$_region.amazonaws.com', '/$key');\n  }\n\n  Uint8List _hmacSha256(Uint8List key, String data) {\n    var hmac = Hmac(sha256, key);\n    return Uint8List.fromList(hmac.convert(utf8.encode(data)).bytes);\n  }\n\n  Uint8List _getSignatureKey(\n    String key,\n    String date,\n    String regionName,\n    String serviceName,\n  ) {\n    var kDate = _hmacSha256(Uint8List.fromList(utf8.encode('AWS4$key')), date);\n    var kRegion = _hmacSha256(kDate, regionName);\n    var kService = _hmacSha256(kRegion, serviceName);\n    var kSigning = _hmacSha256(kService, 'aws4_request');\n    return kSigning;\n  }\n\n  Map<String, String> generateS3Headers(\n    String method,\n    String key, {\n    String? hash,\n  }) {\n    final algorithm = 'AWS4-HMAC-SHA256';\n    final service = 's3';\n    final dateTime = DateTime.now().toUtc().toAwsFormat();\n    final date = dateTime.substring(0, 8).toString();\n    final scope = '$date/$_region/$service/aws4_request';\n\n    final signedHeaders = 'host;x-amz-content-sha256;x-amz-date';\n    hash ??= sha256.convert(utf8.encode('')).toString();\n    final canonicalRequest = [\n      method,\n      '/$key',\n      '',\n      'host:$_bucket.s3.$_region.amazonaws.com',\n      'x-amz-content-sha256:$hash',\n      'x-amz-date:$dateTime',\n      '',\n      signedHeaders,\n      hash,\n    ].join('\\n');\n\n    final stringToSign = [\n      algorithm,\n      dateTime,\n      scope,\n      sha256.convert(utf8.encode(canonicalRequest)).toString(),\n    ].join('\\n');\n\n    final signingKey = _getSignatureKey(_secretKey, date, _region, service);\n    final signature = _hmacSha256(\n      signingKey,\n      stringToSign,\n    ).map((e) => e.toRadixString(16).padLeft(2, '0')).join();\n\n    final authorizationHeader = [\n      '$algorithm Credential=$_accessKey/$scope',\n      'SignedHeaders=$signedHeaders',\n      'Signature=$signature',\n    ].join(', ');\n\n    return {\n      'Authorization': authorizationHeader,\n      'x-amz-content-sha256': hash,\n      'x-amz-date': dateTime,\n    };\n  }\n}\n"
  },
  {
    "path": "lib/src/cache/cache.dart",
    "content": "import 'package:vania/src/cache/file_cache_driver.dart';\nimport 'package:vania/src/utils/helper.dart' show env;\n\nimport 'cache_driver.dart';\nimport 'redis_cache_driver.dart';\n\nclass Cache {\n  static final Cache _singleton = Cache._internal();\n  factory Cache() => _singleton;\n  Cache._internal();\n\n  final CacheDriver _driver = switch (env<String>('CACHE_DRIVER', 'file')) {\n    'file' => FileCacheDriver(),\n    'redis' => RedisCacheDriver(),\n    /*case 'memcached':\n      case 'database':\n      case 'memcache':\n      break;*/\n    _ => FileCacheDriver(),\n  };\n\n  /// set key => value to cache\n  /// default duration is 1 hour\n  /// ```\n  /// await Cache.put('foo', 'bar');\n  /// await Cache.put('foo', 'bar', duration: Duration(hours: 24));\n  /// ```\n  static Future<void> put(\n    String key,\n    dynamic value, {\n    Duration duration = const Duration(hours: 1),\n  }) async {\n    if (value == null) {\n      throw Exception(\"Value can't be null\");\n    }\n    await Cache()._driver.put(key, value, duration: duration);\n  }\n\n  /// set key => value to cache forever\n  /// ```\n  /// await Cache.forever('foo', 'bar');\n  /// ```\n  static Future<void> forever(String key, String value) async {\n    await Cache()._driver.forever(key, value);\n  }\n\n  /// remove a key from cache\n  /// ```\n  /// await Cache.delete('foo');\n  ///\n  static Future<void> delete(String key) async {\n    await Cache()._driver.delete(key);\n  }\n\n  /// get a value from cache\n  /// ```\n  /// String? value = await Cache.get('foo');\n  ///\n  static Future<dynamic> get(String key, [dynamic defaultValue]) async {\n    return await Cache()._driver.get(key, defaultValue);\n  }\n\n  /// get a value exist\n  /// ```\n  /// bool has = await Cache.has('foo');\n  ///\n  static Future<bool> has(String key) async {\n    return await Cache()._driver.has(key);\n  }\n}\n"
  },
  {
    "path": "lib/src/cache/cache_driver.dart",
    "content": "abstract class CacheDriver {\n  Future<void> put(String key, dynamic value, {Duration duration});\n\n  Future<void> forever(String key, dynamic value);\n\n  Future<void> delete(String key);\n\n  Future<dynamic> get(String key, [dynamic defaultValue]);\n\n  Future<bool> has(String key);\n}\n"
  },
  {
    "path": "lib/src/cache/file_cache_driver.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'package:path/path.dart' as path;\nimport '../utils/helper.dart';\nimport 'cache_driver.dart';\n\nclass FileCacheDriver implements CacheDriver {\n  static final FileCacheDriver _instance = FileCacheDriver._internal();\n  factory FileCacheDriver() => _instance;\n  FileCacheDriver._internal();\n\n  final String _cacheDir = storagePath('framework/cache');\n\n  @override\n  Future<dynamic> get(String key, [dynamic defaultValue]) async {\n    final file = _getCacheFile(key);\n    if (!await file.exists()) return defaultValue;\n\n    try {\n      final data = await file.readAsString();\n      final cache = jsonDecode(data);\n\n      if (cache['expiration'] != null) {\n        final expiration = DateTime.parse(cache['expiration']);\n        if (DateTime.now().isAfter(expiration)) {\n          await file.delete();\n          return defaultValue;\n        }\n      }\n\n      return cache['value'] ?? defaultValue;\n    } catch (e) {\n      await file.delete();\n      return defaultValue;\n    }\n  }\n\n  @override\n  Future<void> put(\n    String key,\n    dynamic value, {\n    Duration duration = const Duration(hours: 1),\n  }) async {\n    final file = _getCacheFile(key);\n    await _ensureCacheDirectory();\n\n    final cache = {\n      'value': value,\n      'expiration': DateTime.now().add(duration).toIso8601String(),\n    };\n    await file.writeAsString(jsonEncode(cache));\n  }\n\n  @override\n  Future<void> forever(String key, dynamic value) async {\n    if (value == null) {\n      throw Exception(\"Value can't be null\");\n    }\n\n    final file = _getCacheFile(key);\n    await _ensureCacheDirectory();\n\n    final cache = {'value': value, 'expiration': null};\n\n    await file.writeAsString(jsonEncode(cache));\n  }\n\n  @override\n  Future<bool> has(String key) async {\n    final value = await get(key);\n    return value != null;\n  }\n\n  @override\n  Future<bool> delete(String key) async {\n    final file = _getCacheFile(key);\n    if (!await file.exists()) return false;\n\n    try {\n      await file.delete();\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  File _getCacheFile(String key) {\n    final fileName = base64Url.encode(utf8.encode(key));\n    return File(path.join(_cacheDir, fileName));\n  }\n\n  Future<void> _ensureCacheDirectory() async {\n    final dir = Directory(_cacheDir);\n    if (!await dir.exists()) {\n      await dir.create(recursive: true);\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/cache/redis_cache_driver.dart",
    "content": "import 'package:vania/src/redis/vania_redis.dart';\nimport 'package:vania/src/utils/helper.dart' show env;\n\nimport 'cache_driver.dart';\n\nclass RedisCacheDriver extends CacheDriver {\n  String prefix = env('REDIS_PREFIX', '${env('APP_NAME', 'vania')}_database_');\n\n  @override\n  Future<void> delete(String key) async {\n    await Redis().initialized;\n    await Redis().command.del('$prefix$key');\n  }\n\n  @override\n  Future<void> forever(String key, value) async {\n    await Redis().initialized;\n    await Redis().command.set('$prefix$key', value);\n  }\n\n  @override\n  Future get(String key, [defaultValue]) async {\n    await Redis().initialized;\n    return await Redis().command.get('$prefix$key') ?? defaultValue;\n  }\n\n  @override\n  Future<bool> has(String key) async {\n    await Redis().initialized;\n    return await Redis().command.exists('$prefix$key');\n  }\n\n  @override\n  Future<void> put(String key, value, {Duration? duration}) async {\n    duration ??= Duration(hours: 24);\n    await Redis().initialized;\n    await Redis().command.setEx('$prefix$key', duration.inSeconds, value);\n  }\n}\n"
  },
  {
    "path": "lib/src/config/config.dart",
    "content": "class Config {\n  static final Config _singleton = Config._internal();\n  factory Config() => _singleton;\n  Config._internal();\n\n  Map<String, dynamic> _config = {};\n  set setApplicationConfig(Map<String, dynamic> conf) => _config = conf;\n\n  dynamic get(String key) => _config[key];\n}\n\nclass CORSConfig {\n  final bool enabled;\n  final dynamic origin;\n  final dynamic methods;\n  final dynamic headers;\n  final dynamic exposeHeaders;\n  final bool? credentials;\n  final num? maxAge;\n\n  const CORSConfig({\n    this.enabled = true,\n    this.origin,\n    this.methods,\n    this.headers,\n    this.exposeHeaders,\n    this.credentials,\n    this.maxAge,\n  });\n}\n"
  },
  {
    "path": "lib/src/config/defined_regexp.dart",
    "content": "final ipRegExp = RegExp(\n  r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',\n);\nfinal alphaDashRegExp = RegExp(r'^[a-zA-Z-_]+$');\n\nfinal alphaNumericRegExp = RegExp(r'^[a-zA-Z0-9]+$');\n\nfinal alphaRegExp = RegExp(r'^[a-zA-Z]+$');\n\nfinal emailRegExp = RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$');\n\nfinal uuidRegExp = RegExp(\n  r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$',\n);\n"
  },
  {
    "path": "lib/src/config/http_cors.dart",
    "content": "import 'dart:io';\n\nimport 'config.dart';\n\nclass HttpCors {\n  HttpCors(HttpRequest req) {\n    CORSConfig? cors = Config().get('cors');\n    if (cors != null && cors.enabled) {\n      Map<String, dynamic> headers = <String, dynamic>{\n        HttpHeaders.accessControlAllowOriginHeader: cors.origin,\n        HttpHeaders.accessControlAllowMethodsHeader: cors.methods,\n        HttpHeaders.accessControlAllowHeadersHeader: cors.headers,\n        HttpHeaders.accessControlExposeHeadersHeader: cors.exposeHeaders,\n        HttpHeaders.accessControlAllowCredentialsHeader: cors.credentials,\n        HttpHeaders.accessControlMaxAgeHeader: cors.maxAge,\n      };\n\n      headers.forEach((String key, dynamic value) {\n        _setCorsValue(req.response, key, value);\n      });\n    }\n  }\n\n  // set cors in header\n  void _setCorsValue(HttpResponse res, String key, dynamic data) {\n    if (data == null) {\n      return;\n    }\n\n    /// when data is list of string, eg. ['GET', 'POST']\n    if (data is List<String> && data.isNotEmpty) {\n      res.headers.add(key, data.join(','));\n      return;\n    }\n\n    /// when data is string, eg. 'GET'\n    if (data is String && data.isNotEmpty) {\n      res.headers.add(key, data);\n      return;\n    }\n\n    /// when data is other type and has value, just convert to string\n    if (data != null) {\n      res.headers.add(key, data.toString());\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/container.dart",
    "content": "class Container {}\n"
  },
  {
    "path": "lib/src/contract/database/_connectors/_database_connection.dart",
    "content": "abstract interface class DatabaseConnection {\n  Future<void> connect();\n  Future<List<Map<String, dynamic>>> select(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]);\n  Future insert(String query, [Map<String, dynamic> bindings = const {}]);\n  Future<bool> execute(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]);\n  Future<T> transaction<T>(Future<T> Function() action);\n\n  Future<void> close();\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_bulk_operations_builder.dart",
    "content": "part of 'query_builder.dart';\n\nenum ConflictAction { update, ignore, replace, delete }\n\nabstract class BulkOperationsBuilder {\n  Future<bool> merge(\n    List<Map<String, dynamic>> sourceData, {\n    required List<String> matchOn,\n    ConflictAction whenMatched = ConflictAction.update,\n    ConflictAction whenNotMatched = ConflictAction.ignore,\n    ConflictAction? whenNotMatchedBySource,\n    List<String>? updateColumns,\n    List<String>? insertColumns,\n    Map<String, dynamic>? additionalValues,\n  });\n\n  Future<bool> bulkInsert(\n    List<Map<String, dynamic>> data, {\n    ConflictAction conflictAction = ConflictAction.ignore,\n    List<String>? conflictColumns,\n    List<String>? updateColumns,\n    int batchSize = 1000,\n    bool returnIds = false,\n  });\n\n  Future<bool> bulkUpdate(\n    List<Map<String, dynamic>> updates, {\n    required String matchColumn,\n    List<String>? updateColumns,\n    int batchSize = 500,\n    Map<String, dynamic>? additionalValues,\n  });\n\n  Future<bool> bulkDelete({\n    String? column,\n    List<dynamic>? values,\n    int batchSize = 1000,\n  });\n\n  Future<bool> bulkDeleteWhere(\n    List<Map<String, dynamic>> conditions, {\n    int batchSize = 500,\n  });\n\n  Future<void> batchProcess({\n    required int batchSize,\n    required Future<void> Function(\n      List<Map<String, dynamic>> batch,\n      int batchNumber,\n    )\n    processor,\n    List<String> columns = const ['*'],\n  });\n\n  Future<void> chunkedProcess({\n    required int chunkSize,\n    required Future<List<Map<String, dynamic>>> Function(\n      List<Map<String, dynamic>> chunk,\n    )\n    processor,\n    String? destination,\n    List<String> columns = const ['*'],\n  });\n\n  Future<bool> parallelBulkInsert(\n    List<Map<String, dynamic>> data, {\n    int parallelism = 2,\n    int batchSize = 1000,\n    ConflictAction conflictAction = ConflictAction.ignore,\n    List<String>? conflictColumns,\n  });\n\n  Future<bool> transactionalBulkOperation(Future<bool> Function() operations);\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_cte_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract class CteBuilder {\n  QueryBuilder withCte(\n    String name,\n    QueryBuilder subQuery, {\n    List<String>? columns,\n  });\n\n  QueryBuilder withMultiple(\n    Map<String, QueryBuilder> ctes, {\n    Map<String, List<String>>? columnsMap,\n  });\n\n  QueryBuilder withRecursive(\n    String name,\n    QueryBuilder baseCase,\n    QueryBuilder recursiveCase, {\n    List<String>? columns,\n  });\n\n  QueryBuilder withMaterialized(\n    String name,\n    QueryBuilder subQuery, {\n    List<String>? columns,\n  });\n\n  QueryBuilder withNotMaterialized(\n    String name,\n    QueryBuilder subQuery, {\n    List<String>? columns,\n  });\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_delete_query_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract interface class DeleteQueryBuilder {\n  Future<bool> delete();\n  Future<bool> truncate({bool force = false});\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_insert_query_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract interface class InsertQueryBuilder {\n  Future<bool> insert(Map<String, dynamic> values);\n  Future insertGetId(Map<String, dynamic> values, [String? sequence]);\n  Future<bool> insertMany(List<Map<String, dynamic>> values);\n  Future<bool> insertOrIgnore(Map<String, dynamic> values);\n  Future<bool> insertUsing(List<String> columns, QueryBuilder subQuery);\n  Future<bool> upsert(\n    Map<String, dynamic> values,\n    List<String> uniqueBy, [\n    Map<String, dynamic>? update,\n  ]);\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_join_clause_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract interface class JoinClauseBuilder {\n  QueryBuilder crossJoin(String table, [List<dynamic> bindings = const []]);\n\n  QueryBuilder join(\n    String table,\n    String firstColumn, [\n    String? operator,\n    String? secondColumn,\n    String type = 'inner',\n    bool where = false,\n  ]);\n\n  QueryBuilder joinSub(\n    QueryBuilder subQuery,\n    String as,\n    String firstColumn, [\n    String? operator,\n    String? secondColumn,\n    String type = 'inner',\n  ]);\n\n  QueryBuilder leftJoin(\n    String table,\n    String firstColumn, [\n    String? operator,\n    String? secondColumn,\n    bool where = false,\n  ]);\n\n  QueryBuilder leftJoinSub(\n    QueryBuilder subQuery,\n    String as,\n    String firColumnst, [\n    String? operator,\n    String? secondColumn,\n  ]);\n\n  QueryBuilder rightJoin(\n    String table,\n    String firstColumn, [\n    String? operator,\n    String? secondColumn,\n  ]);\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_query_executor_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract class QueryExecutorBuilder {\n  Future<num> avg(String column);\n  Future<void> chunk(\n    int chunk,\n    void Function(List<Map<String, dynamic>> data) callback,\n  );\n  Future<void> chunkById(\n    int chunk,\n    void Function(List<Map<String, dynamic>> data) callback, [\n    String column,\n  ]);\n  Future<int> count([String columns = '*']);\n  Future<bool> doesntExist();\n  Future<void> each(void Function(Map<String, dynamic>) callback);\n  Future<bool> exists();\n  Future<Map<String, dynamic>?> find(\n    dynamic id, {\n    String byColumnName = 'id',\n    List<String> columns = const ['*'],\n  });\n\n  Future<Map<String, dynamic>?> findOrFail(\n    dynamic id, {\n    String byColumnName = 'id',\n    List<String> columns = const ['*'],\n  });\n  Future<Map<String, dynamic>?> first([List<String> columns]);\n  Future<Map<String, dynamic>?> firstOrFail([\n    List<String> columns = const ['*'],\n  ]);\n  Future<Map<String, dynamic>?> firstWhere(\n    String column, [\n    String? operator,\n    dynamic value,\n    List<String> columns = const ['*'],\n  ]);\n  Future<List<Map<String, dynamic>>> get([List<String> columns]);\n\n  Stream<Iterable<Map<String, dynamic>>> lazy([\n    int chunk = 1000,\n    String column,\n  ]);\n  Stream<Map<String, dynamic>> cursor([int chunk = 1000]);\n  Future<dynamic> max(String column);\n  Future<dynamic> min(String column);\n  Future<Map<String, dynamic>> paginate({\n    int perPage = 15,\n    List<String> columns = const ['*'],\n    String? pageName,\n    int? page,\n  });\n  Future<dynamic> pluck(String column, [String? key]);\n  Future<Map<String, dynamic>> simplePaginate([\n    int perPage,\n    List<String> columns,\n    String pageName,\n    int? page,\n  ]);\n  Future<num> sum(String column);\n\n  Future<dynamic> value(String column);\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_select_query_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract interface class SelectQueryBuilder {\n  QueryBuilder addSelect(List<String> columns);\n\n  QueryBuilder select([List<String> columns]);\n\n  QueryBuilder selectRaw(String query, [List bindings = const []]);\n  QueryBuilder selectSub(QueryBuilder subQuery, String as);\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_union_clause_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract interface class UnionClauseBuilder {\n  QueryBuilder union(QueryBuilder query);\n  QueryBuilder unionAll(QueryBuilder query);\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_update_query_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract interface class UpdateQueryBuilder {\n  Future<bool> decrement(\n    String column, [\n    int amount = 1,\n    Map<String, dynamic> extra = const {},\n  ]);\n  Future<bool> increment(\n    String column, [\n    int amount = 1,\n    Map<String, dynamic> extra = const {},\n  ]);\n  Future<bool> incrementEach(\n    Map<String, int> increments, [\n    Map<String, dynamic> extra = const {},\n  ]);\n  Future<bool> update(Map<String, dynamic> values);\n\n  Future<bool> updateMany(List<Map<String, dynamic>> updates, String column);\n\n  Future<bool> updateOrInsert(\n    Map<String, dynamic> search,\n    Map<String, dynamic> update,\n  );\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_where_clauses_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract interface class WhereClausesBuilder {\n  QueryBuilder orWhere(\n    dynamic condition, [\n    String operator = '=',\n    dynamic value,\n    String boolean = 'and',\n  ]);\n  QueryBuilder orWhereBetween(String column, List values, {bool not = false});\n\n  QueryBuilder orWhereColumn(\n    String first,\n    String operator,\n    String secondColumn,\n  );\n\n  QueryBuilder orWhereDate(String column, String operator, dynamic value);\n\n  QueryBuilder orWhereDay(String column, String operator, dynamic value);\n\n  QueryBuilder orWhereExists(QueryCallback callback, {bool not = false});\n\n  QueryBuilder orWhereFullText(\n    dynamic columns,\n    dynamic query, [\n    Map<String, dynamic> options = const {},\n  ]);\n\n  QueryBuilder orWhereHour(String column, String operator, dynamic value);\n\n  QueryBuilder orWhereIn(String column, List values, {bool not = false});\n  QueryBuilder orWhereJsonContains(\n    String column,\n    dynamic value, {\n    bool not = false,\n  });\n  QueryBuilder orWhereJsonDoesntContain(String column, dynamic value);\n  QueryBuilder orWhereJsonLength(String column, String operator, dynamic value);\n\n  QueryBuilder orWhereLike(\n    String column,\n    dynamic value, {\n    bool caseSensitive = false,\n  });\n  QueryBuilder orWhereMonth(String column, String operator, dynamic value);\n  QueryBuilder orWhereNotBetween(String column, List values);\n  QueryBuilder orWhereNotExists(QueryCallback callback);\n\n  QueryBuilder orWhereNotIn(String column, List values);\n  QueryBuilder orWhereNotLike(\n    String column,\n    dynamic value, {\n    bool caseSensitive = false,\n    String boolean = 'and',\n  });\n  QueryBuilder orWhereNotNull(String column);\n  QueryBuilder orWhereNull(String column);\n\n  QueryBuilder orWhereRaw(String sql, [List<dynamic> bindings = const []]);\n  QueryBuilder orWhereRowValues(\n    List<String> columns,\n    String operator,\n    List<dynamic> values,\n  );\n\n  QueryBuilder orWhereTime(String column, String operator, dynamic value);\n  QueryBuilder orWhereYear(String column, String operator, dynamic value);\n\n  QueryBuilder where(\n    dynamic condition, [\n    String operator = '=',\n    dynamic value,\n    String boolean = 'and',\n  ]);\n  QueryBuilder whereAfterToday(String column, {String boolean = 'and'});\n\n  QueryBuilder whereAll(\n    String column,\n    List<dynamic> values, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereAny(\n    String column,\n    List<dynamic> values, {\n    String boolean = 'and',\n  });\n\n  QueryBuilder whereBeforeToday(String column, {String boolean = 'and'});\n  QueryBuilder whereBetween(\n    String column,\n    List values, {\n    String boolean = 'and',\n    bool not = false,\n  });\n\n  QueryBuilder whereBetweenColumns(\n    String column,\n    List<String> columns, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereColumn(\n    String firstColumn,\n    String operator,\n    String secondColumn, [\n    String boolean = 'and',\n  ]);\n\n  QueryBuilder whereDate(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereDay(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  });\n\n  QueryBuilder whereEqualTo(\n    dynamic condition, [\n    dynamic value,\n    String boolean = 'and',\n  ]);\n  QueryBuilder whereExists(\n    QueryCallback callback, {\n    String boolean = 'and',\n    bool not = false,\n  });\n\n  QueryBuilder whereFullText(\n    dynamic columns,\n    dynamic query, [\n    Map<String, dynamic> options = const {},\n  ]);\n  QueryBuilder whereFuture(String column, {String boolean = 'and'});\n\n  QueryBuilder whereGreaterThan(\n    dynamic condition, [\n    dynamic value,\n    String boolean = 'and',\n  ]);\n  QueryBuilder whereGreaterThanOrEqualTo(\n    dynamic condition, [\n    dynamic value,\n    String boolean = 'and',\n  ]);\n\n  QueryBuilder whereHour(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereIn(\n    String column,\n    List values, {\n    String boolean = 'and',\n    bool not = false,\n  });\n\n  QueryBuilder whereJsonContains(\n    String column,\n    dynamic value, {\n    String boolean = 'and',\n    bool not = false,\n  });\n  QueryBuilder whereJsonDoesntContain(\n    String column,\n    dynamic value, {\n    String boolean = 'and',\n  });\n\n  QueryBuilder whereJsonLength(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereLessThan(\n    dynamic condition, [\n    dynamic value,\n    String boolean = 'and',\n  ]);\n\n  QueryBuilder whereLessThanOrEqualTo(\n    dynamic condition, [\n    dynamic value,\n    String boolean = 'and',\n  ]);\n  QueryBuilder whereLike(\n    String column,\n    dynamic value, {\n    bool caseSensitive = false,\n    String boolean = 'and',\n  });\n\n  QueryBuilder whereMonth(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereNone(\n    String column,\n    List<dynamic> values, {\n    String boolean = 'and',\n  });\n\n  QueryBuilder whereNotBetween(\n    String column,\n    List values, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereNotBetweenColumns(\n    String column,\n    List<String> columns, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereNotEqualTo(\n    dynamic condition, [\n    dynamic value,\n    String boolean = 'and',\n  ]);\n  QueryBuilder whereNotExists(QueryCallback callback, {String boolean = 'and'});\n\n  QueryBuilder whereNotIn(String column, List values, {String boolean = 'and'});\n\n  QueryBuilder whereNotLike(\n    String column,\n    dynamic value, {\n    bool caseSensitive = false,\n    String boolean = 'and',\n  });\n\n  QueryBuilder whereNotNull(String column, {String boolean = 'and'});\n\n  QueryBuilder whereNowOrFuture(String column, {String boolean = 'and'});\n  QueryBuilder whereNowOrPast(String column, {String boolean = 'and'});\n  QueryBuilder whereNull(\n    String column, {\n    String boolean = 'and',\n    bool not = false,\n  });\n  QueryBuilder wherePast(String column, {String boolean = 'and'});\n  QueryBuilder whereRaw(\n    String sql, [\n    List<dynamic> bindings = const [],\n    String boolean = 'and',\n  ]);\n  QueryBuilder whereRowValues(\n    List<String> columns,\n    String operator,\n    List<dynamic> values, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereTime(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  });\n  QueryBuilder whereToday(String column, {String boolean = 'and'});\n  QueryBuilder whereTodayOrAfter(String column, {String boolean = 'and'});\n\n  QueryBuilder whereTodayOrBefore(String column, {String boolean = 'and'});\n  QueryBuilder whereYear(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  });\n\n  QueryBuilder whereHas(\n    String relation,\n    QueryCallback callback, {\n    String boolean = 'and',\n  });\n\n  QueryBuilder orWhereHas(String relation, QueryCallback callback);\n\n  QueryBuilder whereDoesntHave(\n    String relation,\n    QueryCallback callback, {\n    String boolean = 'and',\n  });\n\n  QueryBuilder orWhereDoesntHave(String relation, QueryCallback callback);\n\n  QueryBuilder withSoftDeletes([String column = 'deleted_at']);\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/_window_functions_builder.dart",
    "content": "part of 'query_builder.dart';\n\nabstract interface class WindowFunctionsBuilder {\n  QueryBuilder rowNumber({String? partitionBy, String? orderBy, String? as});\n\n  QueryBuilder rank({String? partitionBy, String? orderBy, String? as});\n\n  QueryBuilder denseRank({String? partitionBy, String? orderBy, String? as});\n\n  QueryBuilder lag(\n    String column, {\n    int offset = 1,\n    dynamic defaultValue,\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n\n  QueryBuilder lead(\n    String column, {\n    int offset = 1,\n    dynamic defaultValue,\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n\n  QueryBuilder firstValue(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n\n  QueryBuilder lastValue(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n\n  QueryBuilder ntile(\n    int buckets, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n\n  QueryBuilder percentRank({String? partitionBy, String? orderBy, String? as});\n\n  QueryBuilder cumeDist({String? partitionBy, String? orderBy, String? as});\n\n  QueryBuilder windowSum(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n\n  QueryBuilder windowAvg(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n\n  QueryBuilder windowCount(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n\n  QueryBuilder windowMax(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n\n  QueryBuilder windowMin(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  });\n}\n"
  },
  {
    "path": "lib/src/contract/database/query_builder/query_builder.dart",
    "content": "import 'package:meta/meta.dart';\nimport '../../../database/_connection_manager.dart';\nimport '../../../database/monitoring/database_monitor.dart';\nimport '../../../exception/invalid_argument_exception.dart';\nimport '../_connectors/_database_connection.dart';\n\npart '../../../database/_database_utils/_paginated_result.dart';\npart '../../../database/_database_utils/_raw_expression.dart';\npart '_bulk_operations_builder.dart';\npart '_cte_builder.dart';\npart '_delete_query_builder.dart';\npart '_insert_query_builder.dart';\npart '_join_clause_builder.dart';\npart '_query_executor_builder.dart';\npart '_select_query_builder.dart';\npart '_union_clause_builder.dart';\npart '_update_query_builder.dart';\npart '_where_clauses_builder.dart';\npart '_window_functions_builder.dart';\n\ntypedef QueryCallback = QueryBuilder Function(QueryBuilder qb);\n\nabstract class QueryBuilder\n    implements\n        InsertQueryBuilder,\n        UpdateQueryBuilder,\n        WhereClausesBuilder,\n        SelectQueryBuilder,\n        DeleteQueryBuilder,\n        UnionClauseBuilder,\n        JoinClauseBuilder,\n        QueryExecutorBuilder,\n        WindowFunctionsBuilder,\n        CteBuilder,\n        BulkOperationsBuilder {\n  @protected\n  final List<String> conditions = [];\n  @protected\n  final Map<String, dynamic> bindings = {};\n  @protected\n  List<String> selectColumns = [];\n  @protected\n  List<String> joins = [];\n  @protected\n  List<String> unions = [];\n\n  @protected\n  String build({String? aggregateFunction, String? aggregateColumn});\n  String toSql();\n  String toRawSql();\n\n  @protected\n  DatabaseConnection? get dbConnection =>\n      ConnectionManager().connection(connectionName);\n\n  @protected\n  String get getTable => '';\n\n  String? get connectionName;\n\n  Future<DatabaseConnection> getConnection() async {\n    if (!ConnectionManager().isConnected) {\n      throw InvalidArgumentException('No database connection found.');\n    }\n\n    return dbConnection!;\n  }\n\n  QueryBuilder connection([String? connection]);\n\n  QueryBuilder table(String table, [String? as]);\n\n  RawExpression raw(String value);\n\n  Future<bool> transaction(\n    Future<bool> Function() action, [\n    String? conditionName,\n  ]);\n\n  Stream<DatabaseAlert> alerts();\n  Map<String, PerformanceStats> getPerformanceStats();\n\n  Map<String, dynamic> getBindings() {\n    return bindings;\n  }\n\n  @protected\n  String buildJoins() {\n    return joins.isNotEmpty ? \" ${joins.join(\" \")}\" : \"\";\n  }\n\n  @protected\n  String buildWhereClause() {\n    return conditions.isNotEmpty ? \"WHERE ${conditions.join(\" \")}\" : \"\";\n  }\n\n  @protected\n  String formatValue(dynamic value) {\n    if (value is num) return value.toString();\n    return \"'$value'\";\n  }\n\n  QueryBuilder groupBy(List<String> groups);\n  QueryBuilder having(\n    String column, [\n    String? operator,\n    dynamic value,\n    String boolean = 'and',\n  ]);\n\n  QueryBuilder havingBetween(\n    String column,\n    List<dynamic> values, {\n    String boolean = 'and',\n    bool not = false,\n  });\n  QueryBuilder inRandomOrder([dynamic seed]);\n  QueryBuilder latest([String column = 'created_at']);\n\n  QueryBuilder limit(int value);\n  QueryBuilder offset(int value);\n\n  QueryBuilder orderBy(String column, [String direction = 'ASC']);\n\n  QueryBuilder orderByAsc(String column);\n\n  QueryBuilder orderByDesc(String column);\n  QueryBuilder reorder([String? column, String? direction]);\n\n  QueryBuilder skip(int value);\n\n  QueryBuilder take(int value);\n}\n"
  },
  {
    "path": "lib/src/contract/http/request/form_validation.dart",
    "content": "import '../../../http/validation/custom_validation_rule.dart';\n\nabstract class FormValidation {\n  dynamic rules();\n  List<CustomValidationRule> customRule() => [];\n  Map<String, String> messages() => {};\n  bool authorize() => true;\n}\n"
  },
  {
    "path": "lib/src/contract/orm/morph_relation.dart",
    "content": "import 'package:vania/src/contract/orm/relation.dart';\n\nabstract class MorphRelation extends Relation {\n  final String morphKey;\n  final String morphType;\n  final String? type;\n  final String? pivotTable;\n  final String? relatedMorphKey;\n  MorphRelation({\n    required super.related,\n    required super.parent,\n    required this.morphKey,\n    required this.morphType,\n    this.pivotTable,\n    this.relatedMorphKey,\n    super.localKey = 'id',\n    this.type,\n  });\n}\n"
  },
  {
    "path": "lib/src/contract/orm/relation.dart",
    "content": "import '../../database/orm/model.dart';\n\nabstract class Relation {\n  final Model related;\n  final Model parent;\n  String? foreignKey;\n  String localKey;\n\n  Relation({\n    required this.related,\n    required this.parent,\n    this.foreignKey,\n    this.localKey = 'id',\n  });\n\n  Map<String, List<Map<String, dynamic>>> buildDictionary(\n    List<Map<String, dynamic>> results,\n    String key,\n  ) {\n    Map<String, List<Map<String, dynamic>>> dictionary = {};\n\n    for (var result in results) {\n      String modelKey = result[key].toString();\n      if (!dictionary.containsKey(modelKey)) {\n        dictionary[modelKey] = [];\n      }\n      dictionary[modelKey]!.add(result);\n    }\n\n    return dictionary;\n  }\n\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n  );\n\n  List<Map<String, dynamic>> matchOneOrMany(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n    String parentKey,\n    String relatedKey,\n  ) {\n    Map<String, Map<String, dynamic>> dictionary = {};\n\n    for (var result in results) {\n      String key = result[relatedKey].toString();\n      dictionary[key] = result;\n    }\n\n    models = models.map((model) {\n      String key = model[parentKey].toString();\n      Map<String, dynamic> cloneModel = Map.from(model);\n      if (dictionary.containsKey(key)) {\n        cloneModel[relation] = dictionary[key];\n      } else {\n        cloneModel[relation] = null;\n      }\n      return cloneModel;\n    }).toList();\n\n    return models;\n  }\n\n  List<Map<String, dynamic>> matchToMany(\n    List<Map<String, dynamic>> parents,\n    List<Map<String, dynamic>> results,\n    String relation,\n    String parentLocalKey,\n    String parentPivotKey,\n    String relatedPivotKey, {\n    List<String> pivotFields = const [],\n  }) {\n    final lookup = <dynamic, List<Map<String, dynamic>>>{};\n    for (var row in results) {\n      final pivotVal = row[parentPivotKey];\n      lookup.putIfAbsent(pivotVal, () => []).add(row);\n    }\n    return parents.map((parent) {\n      final cloned = Map<String, dynamic>.from(parent);\n      final primaryVal = parent[parentLocalKey];\n      final joinedRows = lookup[primaryVal] ?? [];\n\n      final relatedList = joinedRows.map((row) {\n        final relatedData = Map<String, dynamic>.from(row)\n          ..remove(parentPivotKey)\n          ..remove(relatedPivotKey)\n          ..removeWhere((k, _) => pivotFields.contains(k));\n        return relatedData;\n      }).toList();\n\n      cloned[relation] = relatedList;\n      return cloned;\n    }).toList();\n  }\n\n  /// Match many related models to parents (for normal relations)\n  List<Map<String, dynamic>> matchMany(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n    String parentKey,\n    String relatedKey,\n  ) {\n    if (results.isEmpty || models.isEmpty) {\n      return models;\n    }\n    Map<dynamic, List<Map<String, dynamic>>> dictionary = {};\n\n    for (var result in results) {\n      final key = result[relatedKey];\n      if (!dictionary.containsKey(key)) {\n        dictionary[key] = [];\n      }\n      dictionary[key]!.add(result);\n    }\n\n    models = models.map<Map<String, dynamic>>((Map<String, dynamic> model) {\n      final key = model[parentKey];\n      Map<String, dynamic> cloneModel = Map.from(model);\n      if (dictionary.containsKey(key)) {\n        cloneModel[relation] = dictionary[key];\n      } else {\n        cloneModel[relation] = [];\n      }\n      return cloneModel;\n    }).toList();\n\n    return models;\n  }\n\n  /// Match many related morph models to parents.\n  /// Only includes results where the morph type (at column [typeKey]) equals [expectedType].\n  List<Map<String, dynamic>> matchMorphMany(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n    String parentKey,\n    String relatedIdKey,\n    String typeKey,\n    String expectedType,\n  ) {\n    Map<String, List<Map<String, dynamic>>> dictionary = {};\n    for (var result in results) {\n      if (result[typeKey]?.toString() == expectedType) {\n        String key = result[relatedIdKey].toString();\n        if (!dictionary.containsKey(key)) {\n          dictionary[key] = [];\n        }\n        dictionary[key]!.add(result);\n      }\n    }\n\n    models = models.map((model) {\n      String key = model[parentKey].toString();\n      Map<String, dynamic> cloneModel = Map.from(model);\n      if (dictionary.containsKey(key)) {\n        cloneModel[relation] = dictionary[key];\n      } else {\n        cloneModel[relation] = <Map<String, dynamic>>[];\n      }\n      return cloneModel;\n    }).toList();\n\n    return models;\n  }\n\n  List<Map<String, dynamic>> matchMorphToOne(\n    List<Map<String, dynamic>> parents,\n    List<Map<String, dynamic>> results,\n    String relation,\n    String morphKey,\n    String relatedKey,\n  ) {\n    final dict = <String, Map<String, dynamic>>{};\n    for (var row in results) {\n      final key = row[relatedKey].toString();\n      dict[key] = row;\n    }\n    return parents.map((parent) {\n      final clone = Map<String, dynamic>.from(parent);\n      final lookupKey = parent[morphKey]?.toString();\n      clone[relation] = lookupKey != null ? dict[lookupKey] : null;\n      return clone;\n    }).toList();\n  }\n\n  /// Match one related morph model to parents (for hasOne polymorphic relation).\n  /// Only includes results where [typeKey] equals [expectedType] and returns only the first matching result.\n  List<Map<String, dynamic>> matchMorphOneOrMany(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n    String parentKey,\n    String relatedIdKey,\n    String typeKey,\n    String expectedType,\n  ) {\n    Map<String, Map<String, dynamic>> dictionary = {};\n    for (var result in results) {\n      if (result[typeKey]?.toString().toLowerCase() == expectedType) {\n        String key = result[relatedIdKey].toString();\n        if (!dictionary.containsKey(key)) {\n          dictionary[key] = result;\n        }\n      }\n    }\n    models = models.map((model) {\n      String key = model[parentKey].toString();\n      Map<String, dynamic> cloneModel = Map.from(model);\n      if (dictionary.containsKey(key)) {\n        cloneModel[relation] = dictionary[key];\n      } else {\n        cloneModel[relation] = null;\n      }\n      return cloneModel;\n    }).toList();\n\n    return models;\n  }\n}\n"
  },
  {
    "path": "lib/src/cryptographic/hash.dart",
    "content": "import 'dart:convert';\nimport 'dart:math';\nimport 'package:crypto/crypto.dart';\nimport 'package:vania/src/utils/helper.dart' show env;\n\nclass Hash {\n  static final Hash _singleton = Hash._internal();\n  factory Hash() => _singleton;\n  Hash._internal();\n\n  String? _hashKey;\n\n  Hash setHashKey(String hashKey) {\n    _hashKey = hashKey;\n    return this;\n  }\n\n  /// Generates a hashed password using PBKDF2.\n  ///\n  /// This method creates a unique salt and uses it along with the given\n  /// password to generate a hash using the PBKDF2 algorithm. The resulting\n  /// hashed password is a concatenation of the salt and the hash.\n  ///\n  /// Returns a string containing the salt followed by the hash.\n\n  String make(String password) {\n    String salt = _generateSalt();\n    String hash = _hashPbkdf2(password, salt);\n    String hashedPassword = salt + hash;\n    return hashedPassword;\n  }\n\n  /// Verifies that the given [plainPassword] matches the given [hashedPassword].\n  ///\n  /// This method works by first extracting the salt from the given [hashedPassword]\n  /// and then using the extracted salt and the given [plainPassword] to generate\n  /// a hash using the PBKDF2 algorithm. The resulting hash is then compared to\n  /// the given [hashedPassword] to check if it matches.\n  ///\n  /// Returns true if the given [plainPassword] matches the given [hashedPassword],\n  /// false otherwise.\n  bool verify(String plainPassword, String hashedPassword) {\n    int saltLength = 4;\n    String salt = hashedPassword.substring(0, saltLength);\n    String hash = _hashPbkdf2(plainPassword, salt);\n    String saltHash = salt + hash;\n    return _hashEquals(saltHash, hashedPassword);\n  }\n\n  String _generateSalt() {\n    const charset =\n        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n    final random = Random();\n    return String.fromCharCodes(\n      Iterable.generate(\n        4,\n        (_) => charset.codeUnitAt(random.nextInt(charset.length)),\n      ),\n    );\n  }\n\n  /// Hashes the given [password] using the given [salt] and the APP_KEY or\n  /// the given hash key.\n  ///\n  /// The method works by first encoding the given [salt] and [password] into bytes.\n  /// These bytes are then used to compute a SHA-512 HMAC using the bytes of\n  /// the hash key or the APP_KEY if no hash key is given.\n  ///\n  /// The resulting HMAC bytes are then encoded using Base64 and returned as a\n  /// string.\n  String _hashPbkdf2(String password, String salt) {\n    var bytes = utf8.encode(salt + password);\n    var hmac = Hmac(sha512, utf8.encode(_hashKey ?? env('APP_KEY')));\n    return base64.encode(hmac.convert(bytes).bytes);\n  }\n\n  /// Compares two strings in a timing-safe manner to prevent timing attacks.\n  ///\n  /// This method works by first checking if the lengths of the two strings\n  /// are equal. If they are not, the method immediately returns false.\n  ///\n  /// If the lengths are equal, the method then compares the individual characters\n  /// of the two strings. If any of the characters are not equal, the method\n  /// immediately returns false.\n  ///\n  /// If all characters are equal, the method returns true.\n  bool _hashEquals(String salt, String hashedPassword) {\n    if (salt.length != hashedPassword.length) {\n      return false;\n    }\n    var result = 0;\n    for (int i = 0; i < salt.length; i++) {\n      result |= salt.codeUnitAt(i) ^ hashedPassword.codeUnitAt(i);\n    }\n    return result == 0;\n  }\n}\n"
  },
  {
    "path": "lib/src/cryptographic/vania_encryption.dart",
    "content": "import 'dart:convert';\n\nimport 'package:cryptography/cryptography.dart';\n\nclass VaniaEncryption {\n  static final List<int> _fixedNonce = List<int>.filled(12, 0);\n\n  /// Encrypts the given [plainText] using the provided [passphrase].\n  ///\n  /// This method first encodes the [plainText] using Base64 and `UTF-8` encoding.\n  /// Then, it creates a cryptographic key from the [passphrase] and uses the\n  /// AES encryption algorithm to encrypt the text with a predefined initialization\n  /// vector (IV). The result is an encrypted string returned in Base64 format.\n  ///\n  /// Parameters:\n  /// - [plainText]: The text to be encrypted.\n  /// - [passphrase]: The passphrase used to generate the encryption key.\n  ///\n  /// Returns:\n  /// A Base64 encoded string representing the encrypted text.\n  static Future<String> encryptString(\n    String plainText,\n    String passphrase,\n  ) async {\n    try {\n      plainText = base64.encode(utf8.encode(plainText));\n\n      final keyBytes = utf8.encode(\n        passphrase.padRight(32, '0').substring(0, 32),\n      );\n      final secretKey = SecretKey(keyBytes);\n\n      final plainBytes = utf8.encode(plainText);\n\n      final aesGcm = AesGcm.with256bits();\n      final secretBox = await aesGcm.encrypt(\n        plainBytes,\n        secretKey: secretKey,\n        nonce: _fixedNonce,\n      );\n\n      final combined = <int>[];\n      combined.addAll(secretBox.nonce);\n      combined.addAll(secretBox.cipherText);\n      combined.addAll(secretBox.mac.bytes);\n\n      return base64.encode(combined);\n    } catch (error) {\n      return '';\n    }\n  }\n\n  /// Decrypts the given [encryptedText] using the provided [passphrase].\n  ///\n  /// This method first creates a cryptographic key from the [passphrase].\n  /// It then uses the AES encryption algorithm to decrypt the [encryptedText]\n  /// with a predefined initialization vector (IV). The decrypted text is\n  /// decoded from Base64 and `UTF-8` encoding to return the original plaintext.\n  ///\n  /// Parameters:\n  /// - [encryptedText]: The text to be decrypted, in Base64 format.\n  /// - [passphrase]: The passphrase used to generate the decryption key.\n  ///\n  /// Returns:\n  /// The original plaintext if decryption is successful, or an empty\n  /// string if decryption fails.\n  static Future<String> decryptString(\n    String encryptedText,\n    String passphrase,\n  ) async {\n    try {\n      final keyBytes = utf8.encode(\n        passphrase.padRight(32, '0').substring(0, 32),\n      );\n      final secretKey = SecretKey(keyBytes);\n\n      // Decode the base64 encrypted text\n      final encryptedBytes = base64.decode(encryptedText);\n\n      final nonce = encryptedBytes.sublist(0, 12);\n      final mac = encryptedBytes.sublist(encryptedBytes.length - 16);\n      final cipherText = encryptedBytes.sublist(12, encryptedBytes.length - 16);\n\n      final secretBox = SecretBox(cipherText, nonce: nonce, mac: Mac(mac));\n\n      final aesGcm = AesGcm.with256bits();\n      final decryptedBytes = await aesGcm.decrypt(\n        secretBox,\n        secretKey: secretKey,\n      );\n      final decryptedText = utf8.decode(decryptedBytes);\n      return utf8.decode(base64.decode(decryptedText));\n    } catch (error) {\n      return '';\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/_connection_manager.dart",
    "content": "import 'dart:async';\n\nimport 'package:vania/src/exception/database_exception.dart';\n\nimport '../contract/database/_connectors/_database_connection.dart';\nimport '../exception/invalid_argument_exception.dart';\nimport '../logger/logger.dart';\nimport '_connectors/_database_connection_factory.dart';\nimport '_connectors/_database_connection_proxy.dart';\nimport '_database_utils/_db_config.dart';\nimport 'monitoring/database_monitor.dart';\n\nclass ConnectionManager {\n  static ConnectionManager? _singleton;\n  final DatabaseMonitor _monitor = DatabaseMonitor();\n\n  factory ConnectionManager() {\n    _singleton ??= ConnectionManager._internal();\n    return _singleton!;\n  }\n\n  ConnectionManager._internal();\n\n  Map<String, DatabaseConnection> connectionMap = {};\n  String? defaultConnection;\n\n  bool get isConnected => connectionMap.isNotEmpty;\n\n  DatabaseConnection? connection([String? connectionName]) {\n    if (connectionName == null || connectionName.isEmpty) {\n      connectionName = defaultConnection;\n    }\n    return connectionMap[connectionName];\n  }\n\n  Future<void> connect(DBConfig config, String connectionName) async {\n    try {\n      final connection = DatabaseConnectionFactory.createConnection(config);\n      await connection.connect();\n\n      final monitoredConnection = DatabaseConnectionProxy(\n        connection,\n        connectionName,\n        _monitor,\n      );\n      connectionMap[connectionName] = monitoredConnection;\n\n      if (!config.pool) {\n        await _checkDatabaseHealth(monitoredConnection);\n      }\n    } on InvalidArgumentException catch (e) {\n      Logger.log(e.message, type: Logger.ERROR);\n      throw DatabaseException(\"Failed to connect to the database\", e);\n    }\n  }\n\n  Future<void> _checkDatabaseHealth(DatabaseConnection connection) async {\n    Timer.periodic(Duration(minutes: 5), (timer) async {\n      try {\n        await connection.execute('SELECT 1;');\n      } catch (_) {}\n    });\n  }\n\n  Future<bool> transaction(\n    Future<bool> Function() action, [\n    String? connectionName,\n  ]) async {\n    final conn = connectionName ?? defaultConnection;\n    if (conn == null) {\n      throw DatabaseException(\"No connection specified for transaction\");\n    }\n\n    DatabaseConnection? transactionConnection;\n\n    transactionConnection = connection(connectionName);\n\n    if (transactionConnection == null) {\n      throw DatabaseException(\"Connection not found for transaction\");\n    }\n\n    return await transactionConnection.transaction(action);\n  }\n\n  Stream<DatabaseAlert> get alerts => _monitor.alerts;\n  Map<String, PerformanceStats> getPerformanceStats() =>\n      _monitor.getPerformanceStats();\n}\n"
  },
  {
    "path": "lib/src/database/_connectors/_database_connection_factory.dart",
    "content": "import '../../exception/invalid_argument_exception.dart';\nimport '../_database_utils/_db_config.dart';\nimport '../../contract/database/_connectors/_database_connection.dart';\nimport '../adapters/_mysql_connector.dart';\nimport '../adapters/_postgres_connector.dart';\nimport '../adapters/_sqlite_connector.dart';\n\nclass DatabaseConnectionFactory {\n  static DatabaseConnection createConnection(DBConfig config) {\n    return switch (config.driver) {\n      'mysql' => MySqlConnector(config),\n      'pgsql' => PostgresConnector(config),\n      'sqlite' => SQLiteConnector(config),\n      _ => throw InvalidArgumentException(\n        \"Unsupported driver [${config.driver}].\",\n      ),\n    };\n  }\n}\n"
  },
  {
    "path": "lib/src/database/_connectors/_database_connection_proxy.dart",
    "content": "import 'package:vania/src/contract/database/_connectors/_database_connection.dart';\nimport 'package:vania/src/exception/database_exception.dart'\n    show DatabaseException;\n\nimport '../../logger/logger.dart';\nimport '../monitoring/database_monitor.dart';\n\nclass DatabaseConnectionProxy implements DatabaseConnection {\n  final DatabaseConnection _connection;\n  final DatabaseMonitor _monitor;\n  final String _connectionId;\n\n  DatabaseConnectionProxy(this._connection, this._connectionId, this._monitor);\n\n  // Expose the underlying connection for pool management\n  DatabaseConnection get underlyingConnection => _connection;\n\n  @override\n  Future<void> connect() => _connection.connect();\n\n  @override\n  Future<void> close() => _connection.close();\n\n  String _formatQuery(String? query, Map<String, dynamic> bindings) {\n    if (query == null) {\n      Logger.log(\n        'Warning: Null query received in _formatQuery',\n        type: Logger.WARNING,\n      );\n      return 'INVALID QUERY: NULL';\n    }\n\n    try {\n      var formattedQuery = query;\n\n      if (bindings.isEmpty) {\n        return formattedQuery;\n      }\n\n      final sortedBindings = bindings.entries.toList()\n        ..sort((a, b) => b.key.length.compareTo(a.key.length));\n\n      for (var entry in sortedBindings) {\n        final placeholder = ':${entry.key}';\n        final value = entry.value;\n        final formattedValue = _formatValue(value);\n        formattedQuery = formattedQuery.replaceAll(placeholder, formattedValue);\n      }\n\n      return formattedQuery;\n    } catch (e) {\n      Logger.log('Error formatting query: $e', type: Logger.ERROR);\n      throw DatabaseException('Error formatting query: $query', e);\n    }\n  }\n\n  String _formatValue(dynamic value) {\n    if (value == null) return 'NULL';\n    if (value is DateTime) return \"'${value.toIso8601String()}'\";\n    if (value is bool) return value ? '1' : '0';\n    if (value is List) return value.map(_formatValue).join(', ');\n    return value.toString();\n  }\n\n  @override\n  Future<bool> execute(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    final startTime = DateTime.now();\n    try {\n      final formattedQuery = _formatQuery(query, bindings);\n      final result = await _connection.execute(query, bindings);\n      final duration = DateTime.now().difference(startTime);\n      _monitor.recordQuery(_connectionId, formattedQuery, duration);\n      return result;\n    } catch (e) {\n      final duration = DateTime.now().difference(startTime);\n      _monitor.recordQuery(\n        _connectionId,\n        'Failed: ${_formatQuery(query, bindings)} - Error: $e',\n        duration,\n      );\n      rethrow;\n    }\n  }\n\n  @override\n  Future<List<Map<String, dynamic>>> select(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    final startTime = DateTime.now();\n    try {\n      final formattedQuery = _formatQuery(query, bindings);\n      final result = await _connection.select(query, bindings);\n      final duration = DateTime.now().difference(startTime);\n      _monitor.recordQuery(_connectionId, formattedQuery, duration);\n      return result;\n    } catch (e) {\n      final duration = DateTime.now().difference(startTime);\n      _monitor.recordQuery(\n        _connectionId,\n        'Failed: ${_formatQuery(query, bindings)} - Error: $e',\n        duration,\n      );\n      rethrow;\n    }\n  }\n\n  @override\n  Future<dynamic> insert(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    final startTime = DateTime.now();\n    try {\n      final formattedQuery = _formatQuery(query, bindings);\n      final result = await _connection.insert(query, bindings);\n      final duration = DateTime.now().difference(startTime);\n      _monitor.recordQuery(_connectionId, formattedQuery, duration);\n      return result;\n    } catch (e) {\n      final duration = DateTime.now().difference(startTime);\n      _monitor.recordQuery(\n        _connectionId,\n        'Failed: ${_formatQuery(query, bindings)} - Error: $e',\n        duration,\n      );\n      rethrow;\n    }\n  }\n\n  @override\n  Future<T> transaction<T>(Future<T> Function() action) async =>\n      _connection.transaction(action);\n}\n"
  },
  {
    "path": "lib/src/database/_database_utils/_db_config.dart",
    "content": "class DBConfig {\n  final String driver;\n  final String host;\n  final int port;\n  final String username;\n  final String password;\n  final String database;\n  final String collation;\n  final bool sslMode;\n  final bool openInMemorySQLite;\n  final String? filePath;\n  final String? schema;\n  String? timezone;\n  final bool pool;\n  int poolSize;\n\n  DBConfig({\n    required this.driver,\n    this.host = 'mysql',\n    this.port = 3306,\n    this.username = 'root',\n    this.password = '',\n    this.database = 'vania',\n    this.timezone,\n    this.filePath,\n    this.sslMode = false,\n    this.openInMemorySQLite = false,\n    this.collation = 'utf8mb4_general_ci',\n    this.schema,\n    this.pool = false,\n    this.poolSize = 2,\n  });\n}\n"
  },
  {
    "path": "lib/src/database/_database_utils/_paginated_result.dart",
    "content": "part of '../../contract/database/query_builder/query_builder.dart';\n\nclass PaginatedResult {\n  final List<Map<String, dynamic>> data;\n  final int currentPage;\n  final int perPage;\n  final int total;\n  final int lastPage;\n  final bool isFirst;\n  final bool isLast;\n  final bool hasMore;\n\n  PaginatedResult({\n    required this.data,\n    required this.currentPage,\n    required this.perPage,\n    required this.total,\n    required this.lastPage,\n    required this.isFirst,\n    required this.isLast,\n    required this.hasMore,\n  });\n\n  Map<String, dynamic> toMap() => {\n    'data': data,\n    'current_page': currentPage,\n    'per_page': perPage,\n    'total': total,\n    'last_page': lastPage,\n    'is_first': isFirst,\n    'is_last': isLast,\n    'has_more': hasMore,\n  };\n\n  @override\n  String toString() {\n    return 'PaginatedResult(currentPage: $currentPage, perPage: $perPage, total: $total, lastPage: $lastPage, data: $data)';\n  }\n}\n"
  },
  {
    "path": "lib/src/database/_database_utils/_raw_expression.dart",
    "content": "part of '../../contract/database/query_builder/query_builder.dart';\n\nclass RawExpression {\n  final String expression;\n  RawExpression(this.expression);\n\n  @override\n  String toString() => expression;\n}\n"
  },
  {
    "path": "lib/src/database/_database_utils/_singularize.dart",
    "content": "class Singularize {\n  static String make(String tableName) {\n    if (tableName.isEmpty) return tableName;\n\n    final name = tableName.toLowerCase();\n\n    const irregularPlurals = {\n      'children': 'child',\n      'people': 'person',\n      'men': 'man',\n      'women': 'woman',\n      'feet': 'foot',\n      'teeth': 'tooth',\n      'geese': 'goose',\n      'mice': 'mouse',\n      'oxen': 'ox',\n      'sheep': 'sheep',\n      'deer': 'deer',\n      'fish': 'fish',\n      'series': 'series',\n      'species': 'species',\n      'data': 'datum',\n      'media': 'medium',\n      'criteria': 'criterion',\n      'phenomena': 'phenomenon',\n      'alumni': 'alumnus',\n      'cacti': 'cactus',\n      'foci': 'focus',\n      'fungi': 'fungus',\n      'nuclei': 'nucleus',\n      'radii': 'radius',\n      'stimuli': 'stimulus',\n      'syllabi': 'syllabus',\n      'analyses': 'analysis',\n      'bases': 'basis',\n      'crises': 'crisis',\n      'diagnoses': 'diagnosis',\n      'hypotheses': 'hypothesis',\n      'oases': 'oasis',\n      'parentheses': 'parenthesis',\n      'synopses': 'synopsis',\n      'theses': 'thesis',\n    };\n\n    if (irregularPlurals.containsKey(name)) {\n      return _preserveCase(tableName, irregularPlurals[name]!);\n    }\n\n    if (name.endsWith('ies') && name.length > 3) {\n      return _preserveCase(tableName, '${name.substring(0, name.length - 3)}y');\n    }\n\n    if (name.endsWith('ves') && name.length > 3) {\n      final stem = name.substring(0, name.length - 3);\n      if (stem.endsWith('l') || stem.endsWith('r')) {\n        return _preserveCase(tableName, '${stem}f');\n      } else {\n        return _preserveCase(tableName, '${stem}fe');\n      }\n    }\n\n    if (name.endsWith('ses') && name.length > 3) {\n      return _preserveCase(tableName, name.substring(0, name.length - 2));\n    }\n    if ((name.endsWith('ches') ||\n            name.endsWith('shes') ||\n            name.endsWith('xes') ||\n            name.endsWith('zes')) &&\n        name.length > 3) {\n      return _preserveCase(tableName, name.substring(0, name.length - 2));\n    }\n\n    if (name.endsWith('oes') && name.length > 3) {\n      return _preserveCase(tableName, name.substring(0, name.length - 2));\n    }\n\n    if (name.endsWith('i') && name.length > 2) {\n      final stem = name.substring(0, name.length - 1);\n      if (stem.endsWith('cact') ||\n          stem.endsWith('fung') ||\n          stem.endsWith('nucle') ||\n          stem.endsWith('radi') ||\n          stem.endsWith('stimul') ||\n          stem.endsWith('syllab')) {\n        return _preserveCase(tableName, '${stem}us');\n      }\n    }\n\n    if (name.endsWith('a') && name.length > 2) {\n      final stem = name.substring(0, name.length - 1);\n      if (stem.endsWith('dat')) {\n        return _preserveCase(tableName, '${stem}um');\n      } else if (stem.endsWith('criteri') || stem.endsWith('phenomen')) {\n        return _preserveCase(tableName, '${stem}on');\n      }\n    }\n\n    if (name.endsWith('s') &&\n        name.length > 1 &&\n        !name.endsWith('ss') &&\n        !name.endsWith('us') &&\n        !name.endsWith('is')) {\n      return _preserveCase(tableName, name.substring(0, name.length - 1));\n    }\n\n    return tableName;\n  }\n\n  static String pluralize(String word) {\n    if (word.isEmpty) return word;\n\n    final name = word.toLowerCase();\n\n    const irregularSingulars = {\n      'child': 'children',\n      'person': 'people',\n      'man': 'men',\n      'woman': 'women',\n      'foot': 'feet',\n      'tooth': 'teeth',\n      'goose': 'geese',\n      'mouse': 'mice',\n      'ox': 'oxen',\n      'sheep': 'sheep',\n      'deer': 'deer',\n      'fish': 'fish',\n      'series': 'series',\n      'species': 'species',\n      'datum': 'data',\n      'medium': 'media',\n      'criterion': 'criteria',\n      'phenomenon': 'phenomena',\n      'alumnus': 'alumni',\n      'cactus': 'cacti',\n      'focus': 'foci',\n      'fungus': 'fungi',\n      'nucleus': 'nuclei',\n      'radius': 'radii',\n      'stimulus': 'stimuli',\n      'syllabus': 'syllabi',\n      'analysis': 'analyses',\n      'basis': 'bases',\n      'crisis': 'crises',\n      'diagnosis': 'diagnoses',\n      'hypothesis': 'hypotheses',\n      'oasis': 'oases',\n      'parenthesis': 'parentheses',\n      'synopsis': 'synopses',\n      'thesis': 'theses',\n    };\n\n    if (irregularSingulars.containsKey(name)) {\n      return _preserveCase(word, irregularSingulars[name]!);\n    }\n\n    if (name.endsWith('y') && name.length > 1) {\n      final precedingChar = name[name.length - 2];\n      if (!'aeiou'.contains(precedingChar)) {\n        return _preserveCase(word, '${name.substring(0, name.length - 1)}ies');\n      }\n    }\n\n    if (name.endsWith('f') && name.length > 1) {\n      return _preserveCase(word, '${name.substring(0, name.length - 1)}ves');\n    }\n    if (name.endsWith('fe') && name.length > 2) {\n      return _preserveCase(word, '${name.substring(0, name.length - 2)}ves');\n    }\n\n    if (name.endsWith('s') ||\n        name.endsWith('ss') ||\n        name.endsWith('sh') ||\n        name.endsWith('ch') ||\n        name.endsWith('x') ||\n        name.endsWith('z')) {\n      return _preserveCase(word, '${name}es');\n    }\n\n    if (name.endsWith('o') && name.length > 1) {\n      final precedingChar = name[name.length - 2];\n      if (!'aeiou'.contains(precedingChar)) {\n        return _preserveCase(word, '${name}es');\n      }\n    }\n\n    if (name.endsWith('us') && name.length > 2) {\n      final stem = name.substring(0, name.length - 2);\n      if (stem.endsWith('cact') ||\n          stem.endsWith('foc') ||\n          stem.endsWith('fung') ||\n          stem.endsWith('nucle') ||\n          stem.endsWith('radi') ||\n          stem.endsWith('stimul') ||\n          stem.endsWith('syllab')) {\n        return _preserveCase(word, '${stem}i');\n      }\n    }\n\n    if (name.endsWith('um') && name.length > 2) {\n      final stem = name.substring(0, name.length - 2);\n      if (stem.endsWith('dat')) {\n        return _preserveCase(word, '${stem}a');\n      }\n    }\n\n    if (name.endsWith('on') && name.length > 2) {\n      final stem = name.substring(0, name.length - 2);\n      if (stem.endsWith('criteri') || stem.endsWith('phenomen')) {\n        return _preserveCase(word, '${stem}a');\n      }\n    }\n\n    return _preserveCase(word, '${name}s');\n  }\n\n  static String _preserveCase(String original, String converted) {\n    if (original.isEmpty || converted.isEmpty) return converted;\n\n    if (original == original.toUpperCase()) {\n      return converted.toUpperCase();\n    }\n\n    if (original[0] == original[0].toUpperCase()) {\n      return '${converted[0].toUpperCase()}${converted.substring(1)}';\n    }\n\n    return converted;\n  }\n\n  static bool isPlural(String word) {\n    if (word.isEmpty) return false;\n\n    final name = word.toLowerCase();\n\n    const irregularPlurals = {\n      'children',\n      'people',\n      'men',\n      'women',\n      'feet',\n      'teeth',\n      'geese',\n      'mice',\n      'oxen',\n      'data',\n      'media',\n      'criteria',\n      'phenomena',\n      'alumni',\n      'cacti',\n      'foci',\n      'fungi',\n      'nuclei',\n      'radii',\n      'stimuli',\n      'syllabi',\n      'analyses',\n      'bases',\n      'crises',\n      'diagnoses',\n      'hypotheses',\n      'oases',\n      'parentheses',\n      'synopses',\n      'theses',\n    };\n\n    if (irregularPlurals.contains(name)) return true;\n\n    if (name.endsWith('ies') && name.length > 3) return true;\n    if (name.endsWith('ves') && name.length > 3) return true;\n    if (name.endsWith('ses') && name.length > 3) return true;\n    if ((name.endsWith('ches') ||\n            name.endsWith('shes') ||\n            name.endsWith('xes') ||\n            name.endsWith('zes')) &&\n        name.length > 3) {\n      return true;\n    }\n    if (name.endsWith('oes') && name.length > 3) return true;\n    if (name.endsWith('s') &&\n        name.length > 1 &&\n        !name.endsWith('ss') &&\n        !name.endsWith('us') &&\n        !name.endsWith('is')) {\n      return true;\n    }\n\n    return false;\n  }\n\n  static bool isSingular(String word) {\n    return !isPlural(word);\n  }\n}\n"
  },
  {
    "path": "lib/src/database/adapters/_mysql_connector.dart",
    "content": "import 'dart:convert';\n\nimport 'package:mysql_client/mysql_client.dart';\n\nimport '../../exception/database_exception.dart';\nimport '../_database_utils/_db_config.dart';\nimport '../../contract/database/_connectors/_database_connection.dart';\n\nclass MySqlConnector implements DatabaseConnection {\n  final DBConfig config;\n  late dynamic _connection;\n  bool _isPool = false;\n\n  MySqlConnector(this.config);\n\n  @override\n  Future<void> close() async {\n    await _connection.close();\n  }\n\n  @override\n  Future<void> connect() async {\n    try {\n      if (config.pool) {\n        _isPool = true;\n        _connection = MySQLConnectionPool(\n          host: config.host,\n          port: config.port,\n          userName: config.username,\n          password: config.password,\n          databaseName: config.database,\n          collation: config.collation,\n          secure: config.sslMode,\n          maxConnections: config.poolSize,\n        );\n      } else {\n        _isPool = false;\n        _connection = await MySQLConnection.createConnection(\n          host: config.host,\n          port: config.port,\n          userName: config.username,\n          password: config.password,\n          databaseName: config.database,\n          collation: config.collation,\n          secure: config.sslMode,\n        );\n        await _connection.connect();\n      }\n    } catch (e) {\n      throw DatabaseException('Database connection failed', e);\n    }\n  }\n\n  @override\n  Future<bool> execute(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    try {\n      await _connection.execute(query, bindings);\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<T> transaction<T>(Future<T> Function() action) async {\n    try {\n      if (_isPool) {\n        return await (_connection as MySQLConnectionPool).transactional<T>((\n          txConn,\n        ) async {\n          final previous = _connection;\n          _connection = txConn;\n          _isPool = false;\n          try {\n            return await action();\n          } finally {\n            _isPool = true;\n            _connection = previous;\n          }\n        });\n      } else {\n        return await (_connection as MySQLConnection).transactional<T>((\n          txConn,\n        ) async {\n          final previous = _connection;\n          _connection = txConn;\n          try {\n            return await action();\n          } finally {\n            _connection = previous;\n          }\n        });\n      }\n    } catch (e) {\n      throw DatabaseException('Transaction failed', e);\n    }\n  }\n\n  @override\n  Future<List<Map<String, dynamic>>> select(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    try {\n      final IResultSet results;\n\n      if (_isPool) {\n        results = await (_connection as MySQLConnectionPool).execute(\n          query,\n          bindings,\n        );\n      } else {\n        results = await (_connection as MySQLConnection).execute(\n          query,\n          bindings,\n        );\n      }\n      if (results.rows.isEmpty) {\n        return [];\n      }\n\n      return results.rows.map((item) => item.assoc()).toList().map((row) {\n        final newRow = Map<String, dynamic>.from(row);\n        newRow.forEach((key, value) {\n          if (value is List<int>) {\n            try {\n              newRow[key] = utf8.decode(value);\n            } catch (e) {\n              newRow[key] = value;\n            }\n          }\n        });\n        return newRow;\n      }).toList();\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future insert(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    try {\n      final IResultSet results;\n      if (_isPool) {\n        results = await (_connection as MySQLConnectionPool).execute(\n          query,\n          bindings,\n        );\n      } else {\n        results = await (_connection as MySQLConnection).execute(\n          query,\n          bindings,\n        );\n      }\n      return results.lastInsertID;\n    } catch (e) {\n      rethrow;\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/adapters/_postgres_connector.dart",
    "content": "import 'dart:convert';\n\nimport 'package:postgres/postgres.dart';\nimport 'package:vania/src/exception/database_exception.dart';\nimport '../_database_utils/_db_config.dart';\n\nimport '../../contract/database/_connectors/_database_connection.dart';\n\nclass PostgresConnector implements DatabaseConnection {\n  final DBConfig config;\n  late dynamic _connection;\n\n  PostgresConnector(this.config);\n\n  @override\n  Future<void> close() async {\n    await _connection.close();\n  }\n\n  Encoding _getEncoding(String encoding) {\n    switch (encoding.toLowerCase()) {\n      case 'utf8':\n        return Utf8Codec();\n      case 'ascii':\n        return AsciiCodec();\n      case 'latin1':\n        return Latin1Codec();\n      case 'iso-8859-1':\n        return Latin1Codec();\n      default:\n        return Utf8Codec(allowMalformed: true);\n    }\n  }\n\n  Future<void> _onOpen(Connection conn, DBConfig configParser) async {\n    await conn.execute(\"SET client_encoding = '${configParser.collation}'\");\n    if (configParser.schema != null) {\n      await conn.execute(\"SET search_path TO ${configParser.schema}\");\n    }\n    if (configParser.timezone != null) {\n      await conn.execute(\"SET timezone TO '${configParser.timezone}'\");\n    }\n  }\n\n  @override\n  Future<void> connect() async {\n    final endpoint = Endpoint(\n      host: config.host,\n      port: config.port,\n      database: config.database,\n      username: config.username,\n      password: config.password,\n    );\n\n    final sslMode = config.sslMode ? SslMode.require : SslMode.disable;\n\n    try {\n      if (config.pool == true) {\n        _connection = Pool.withEndpoints(\n          [endpoint],\n          settings: PoolSettings(\n            timeZone: config.timezone,\n            maxConnectionCount: config.poolSize,\n            encoding: _getEncoding(config.collation),\n            onOpen: (conn) async {\n              await _onOpen(conn, config);\n            },\n            sslMode: sslMode,\n          ),\n        );\n      } else {\n        _connection = await Connection.open(\n          endpoint,\n          settings: ConnectionSettings(\n            timeZone: config.timezone,\n            encoding: _getEncoding(config.collation),\n            sslMode: sslMode,\n            onOpen: (conn) async {\n              await _onOpen(conn, config);\n            },\n          ),\n        );\n      }\n    } catch (e) {\n      throw DatabaseException('Database connection failed', e);\n    }\n  }\n\n  @override\n  Future<bool> execute(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    try {\n      final result = await _connection.execute(\n        Sql.named(query.replaceAll(':p', '@p')),\n        parameters: bindings,\n      );\n      return result.affectedRows > 0;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<List<Map<String, dynamic>>> select(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    try {\n      final sql = Sql.named(query.replaceAll(':p', '@p'));\n\n      final result = await _connection.execute(sql, parameters: bindings);\n      final rows = result.map((row) => row.toColumnMap()).toList();\n      final maps = <Map<String, dynamic>>[];\n      if (rows.isNotEmpty) {\n        for (final row in rows) {\n          final map = <String, dynamic>{};\n          for (final col in row.entries) {\n            final key = col.key;\n            final value = col.value is UndecodedBytes\n                ? col.value.asString\n                : col.value;\n            map.addAll({key: value});\n          }\n          maps.add(map);\n        }\n      }\n      return maps;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future insert(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    try {\n      final result = await _connection.execute(\n        Sql.named(query.replaceAll(':p', '@p')),\n        parameters: bindings,\n      );\n      return result.affectedRows;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<T> transaction<T>(Future<T> Function() action) async {\n    return await _connection.runTx<T>((ctx) async {\n      final prev = _connection;\n      _connection = ctx;\n      try {\n        return await action();\n      } finally {\n        _connection = prev;\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/database/adapters/_sqlite_connector.dart",
    "content": "import 'dart:ffi';\nimport 'dart:io';\nimport 'package:path/path.dart';\nimport 'package:sqlite3/open.dart';\nimport 'package:sqlite3/sqlite3.dart';\nimport 'package:vania/src/contract/database/_connectors/_database_connection.dart';\nimport 'package:vania/src/exception/database_exception.dart';\nimport '../../utils/helper.dart' show env;\nimport '../_database_utils/_db_config.dart';\n\nclass SQLiteConnector implements DatabaseConnection {\n  final DBConfig config;\n  late Database _connection;\n\n  SQLiteConnector(this.config);\n\n  @override\n  Future<void> close() async {\n    _connection.dispose();\n  }\n\n  @override\n  Future<void> connect() async {\n    try {\n      open.overrideFor(OperatingSystem.linux, _openOnLinux);\n\n      if (config.openInMemorySQLite) {\n        _connection = sqlite3.openInMemory();\n      } else {\n        _connection = sqlite3.open(\n          config.filePath ?? '${env<String?>('APP_NAME', 'Vania')}.db',\n        );\n      }\n    } catch (e) {\n      throw DatabaseException('Database connection failed', e);\n    }\n  }\n\n  DynamicLibrary _openOnLinux() {\n    final scriptDir = File(Platform.script.toFilePath()).parent;\n\n    final libraryNextToScript = File(join(scriptDir.path, 'sqlite3.so'));\n\n    return DynamicLibrary.open(libraryNextToScript.path);\n  }\n\n  List<dynamic> _convertBindingsToList(\n    Map<String, dynamic> bindings,\n    String query,\n  ) {\n    if (bindings.isEmpty) return [];\n\n    final List<dynamic> result = [];\n    final parameterRegex = RegExp(r':(\\w+)');\n\n    final matches = parameterRegex.allMatches(query);\n    for (final match in matches) {\n      final paramName = match.group(1)!;\n      if (bindings.containsKey(paramName)) {\n        result.add(bindings[paramName]);\n      }\n    }\n\n    return result;\n  }\n\n  String _convertNamedParamsToPositional(String query) {\n    return query.replaceAllMapped(RegExp(r':(\\w+)'), (match) => '?');\n  }\n\n  @override\n  Future<bool> execute(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    try {\n      final positionalQuery = _convertNamedParamsToPositional(query);\n      final params = _convertBindingsToList(bindings, query);\n\n      final stmt = _connection.prepare(positionalQuery);\n      stmt.execute(params);\n      stmt.dispose();\n\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<List<Map<String, dynamic>>> select(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    try {\n      final positionalQuery = _convertNamedParamsToPositional(query);\n      final params = _convertBindingsToList(bindings, query);\n\n      final stmt = _connection.prepare(positionalQuery);\n      final results = stmt.select(params);\n\n      final List<Map<String, dynamic>> rows = [];\n      final columns = results.isEmpty ? [] : results.first.keys.toList();\n\n      for (final row in results) {\n        final map = <String, dynamic>{};\n        for (var i = 0; i < columns.length; i++) {\n          map[columns[i]] = row[i.toString()];\n        }\n        rows.add(map);\n      }\n\n      stmt.dispose();\n      return rows;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future insert(\n    String query, [\n    Map<String, dynamic> bindings = const {},\n  ]) async {\n    try {\n      final positionalQuery = _convertNamedParamsToPositional(query);\n      final params = _convertBindingsToList(bindings, query);\n\n      final stmt = _connection.prepare(positionalQuery);\n      stmt.execute(params);\n      final id = _connection.lastInsertRowId;\n      stmt.dispose();\n\n      return id;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<T> transaction<T>(Future<T> Function() action) {\n    throw UnimplementedError();\n  }\n}\n"
  },
  {
    "path": "lib/src/database/db.dart",
    "content": "// ignore_for_file: non_constant_identifier_names\n\nimport '../contract/database/query_builder/query_builder.dart';\nimport 'query_builder/_query_builder_impl.dart' show QueryBuilderImpl;\n\nQueryBuilder get DB => QueryBuilderImpl();\n"
  },
  {
    "path": "lib/src/database/isolate_db.dart",
    "content": "import 'dart:isolate';\n\nimport '_connection_manager.dart';\nimport '_database_utils/_db_config.dart';\n\nclass IsolateDB {\n  static Future<T> run<T>(\n    Future<T> Function() callback,\n    Map<String, dynamic> dbConfig,\n  ) async {\n    return await Isolate.run(() async {\n      ConnectionManager().defaultConnection = dbConfig['driver'];\n      final config = DBConfig(\n        driver: dbConfig['driver'] ?? '',\n        host: dbConfig['host'] ?? '',\n        port: dbConfig['port'] ?? 3306,\n        database: dbConfig['database'] ?? '',\n        username: dbConfig['username'] ?? '',\n        password: dbConfig['password'] ?? '',\n        sslMode: dbConfig['sslmode'] ?? false,\n        collation: dbConfig['collation'] ?? 'utf8mb4_general_ci',\n        pool: false,\n        poolSize: 0,\n        filePath: dbConfig['file_path'],\n        openInMemorySQLite: dbConfig['openInMemorySQLite'] ?? false,\n      );\n      try {\n        await ConnectionManager().connect(config, dbConfig['driver']);\n        return await callback();\n      } finally {\n        await ConnectionManager().connection(dbConfig['driver'])?.close();\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/adapters/grammar/mysql_grammar.dart",
    "content": "import 'sql_grammar.dart';\n\nclass MySqlGrammar extends BaseGrammar {\n  @override\n  String get identifierQuote => '`';\n\n  @override\n  Map<String, String> get dataTypeMappings => {};\n\n  @override\n  Map<String, String> get keywordReplacements => {};\n\n  @override\n  Map<String, String Function(Match)> get regexTransformations => {};\n\n  @override\n  String convertQuery(String query) {\n    return _cleanupQuery(query);\n  }\n\n  String _cleanupQuery(String query) {\n    return query\n        .replaceAll(RegExp(r'\\s+'), ' ')\n        .replaceAll(',,', ',')\n        .replaceAll(RegExp(r',\\s?\\)'), ')')\n        .trim();\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/adapters/grammar/postgresql_grammar.dart",
    "content": "import 'sql_grammar.dart';\n\n/// PostgreSQL-specific SQL grammar (Single Responsibility Principle)\nclass PostgreSqlGrammar extends BaseGrammar {\n  @override\n  String get identifierQuote => '\"';\n\n  @override\n  Map<String, String> get dataTypeMappings => {\n    // Integer types\n    r'BIGINT\\(\\d+\\)': 'BIGINT',\n    r'MEDIUMINT\\(\\d+\\)': 'INTEGER',\n    r'SMALLINT\\(\\d+\\)': 'SMALLINT',\n    r'TINYINT\\(\\d+\\)': 'SMALLINT',\n\n    // Binary types\n    r'BINARY\\(\\d+\\)': 'BYTEA',\n    r'VARBINARY\\(\\d+\\)': 'BYTEA',\n    r'BIT\\(\\d+\\)': 'BOOLEAN',\n\n    // Date/time types\n    r'DATETIME\\(\\d+\\)': 'TIMESTAMP',\n    r'TIME\\(\\d+\\)': 'TIME',\n\n    // Floating point types\n    r'DOUBLE\\(\\d+\\)': 'DOUBLE PRECISION',\n\n    // BLOB types\n    'TINYBLOB': 'BYTEA',\n    'BLOB': 'BYTEA',\n    'MEDIUMBLOB': 'BYTEA',\n    'LONGBLOB': 'BYTEA',\n    'VARBYTEA': 'BYTEA',\n    'MEDIUMBYTEA': 'BYTEA',\n    'LONGBYTEA': 'BYTEA',\n\n    // Text types\n    'TINYTEXT': 'TEXT',\n    'MEDIUMTEXT': 'TEXT',\n    r'LONGTEXT\\(\\d+\\)': 'TEXT',\n\n    // Geometry types\n    'LINESTRING': 'LINE',\n  };\n\n  @override\n  Map<String, String> get keywordReplacements => {\n    'UNSIGNED': '',\n    'ZEROFILL': '',\n    'AUTO_INCREMENT': '',\n    r\"COLLATE '[\\w\\d_-]+'\": '',\n    r\"ENGINE\\s*=\\s*\\w+\": '',\n    r\"COMMENT\\s+'[^']*'\": '',\n  };\n\n  @override\n  Map<String, String Function(Match)> get regexTransformations => {\n    // Auto-increment primary key transformation - handle table.id() pattern\n    r'[`\"](\\w+)[`\"]\\s+BIGINT(?:\\(\\d+\\))?\\s+(?:UNSIGNED\\s+)?NOT\\s+NULL\\s+AUTO_INCREMENT':\n        (match) => '\"${match[1]}\" SERIAL NOT NULL PRIMARY KEY',\n\n    r'\\s+ON\\s+UPDATE\\s+CURRENT_TIMESTAMP': (match) => '',\n    r'ON\\s+UPDATE\\s+CURRENT_TIMESTAMP': (match) => '',\n\n    // Handle `BIGINT` NOT NULL without AUTO_INCREMENT but with separate PRIMARY KEY\n    r'[`\"](\\w+)[`\"]\\s+BIGINT(?:\\(\\d+\\))?\\s+(?:UNSIGNED\\s+)?NOT\\s+NULL(?!\\s+AUTO_INCREMENT)':\n        (match) => '\"${match[1]}\" BIGINT NOT NULL',\n\n    // Remove primary key declarations when SERIAL is used\n    r',\\s*PRIMARY KEY \\([`\"][^`\"]+[`\"]\\)': (match) => '',\n    r'PRIMARY KEY \\([`\"][^`\"]+[`\"]\\)\\s*,?': (match) => '',\n\n    // Remove INDEX declarations - more comprehensive patterns\n    r',\\s*INDEX\\s+[`\"][^`\"]*[`\"]\\s*\\([^)]*\\)': (match) => '',\n    r'INDEX\\s+[`\"][^`\"]*[`\"]\\s*\\([^)]*\\)\\s*,': (match) => '',\n    r'INDEX\\s+[`\"][^`\"]*[`\"]\\s*\\([^)]*\\)': (match) => '',\n\n    // Remove CONSTRAINT UNIQUE (will be handled by adapter)\n    r',\\s*CONSTRAINT\\s+[`\"][^`\"]*[`\"]\\s+UNIQUE\\s*\\([^)]*\\)': (match) => '',\n    r'CONSTRAINT\\s+[`\"][^`\"]*[`\"]\\s+UNIQUE\\s*\\([^)]*\\)\\s*,': (match) => '',\n    r'CONSTRAINT\\s+[`\"][^`\"]*[`\"]\\s+UNIQUE\\s*\\([^)]*\\)': (match) => '',\n\n    // Integer type with length\n    r'(^|\\s|,)INT\\((\\d+)\\)': (match) => '${match[1]}INTEGER',\n    r'(^|\\s|,)INTEGER\\((\\d+)\\)': (match) => '${match[1]}INTEGER',\n\n    // VARCHAR with length preservation\n    r'VARCHAR\\((\\d+)\\)': (match) => 'VARCHAR(${match[1]})',\n    r'VARCHARACTER\\((\\d+)\\)': (match) => 'CHARACTER(${match[1]})',\n\n    // FLOAT conversion\n    r'FLOAT\\((\\d+)\\)': (match) => 'REAL',\n\n    // ENUM to VARCHAR conversion\n    r\"ENUM\\((?:'[^']*'(?:\\s*,\\s*'[^']*')*)\\)\": (match) => 'VARCHAR',\n\n    // Clean up empty spaces and commas that result from removals\n    r',\\s*,+': (match) => ',',\n    r'^\\s*,': (match) => '',\n    r',\\s*\\)': (match) => ')',\n    r'\\(\\s*,': (match) => '(',\n  };\n\n  @override\n  String convertQuery(String query) {\n    String result = super.convertQuery(query);\n\n    // Additional PostgreSQL-specific cleanup\n    result = _postgresqlSpecificCleanup(result);\n\n    return result;\n  }\n\n  /// PostgreSQL-specific cleanup operations\n  String _postgresqlSpecificCleanup(String query) {\n    return query\n        // Remove any remaining double commas\n        .replaceAll(RegExp(r',\\s*,+'), ',')\n        // Remove leading commas\n        .replaceAll(RegExp(r'^\\s*,'), '')\n        // Remove trailing commas before closing parenthesis\n        .replaceAll(RegExp(r',\\s*\\)'), ')')\n        // Remove extra spaces\n        .replaceAll(RegExp(r'\\s+'), ' ')\n        // Clean up any remaining issues\n        .trim();\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/adapters/grammar/sql_grammar.dart",
    "content": "abstract class SqlGrammar {\n  String convertQuery(String query);\n\n  String get identifierQuote;\n\n  Map<String, String> get dataTypeMappings;\n\n  Map<String, String> get keywordReplacements;\n\n  Map<String, String Function(Match)> get regexTransformations;\n}\n\nabstract class BaseGrammar implements SqlGrammar {\n  @override\n  String convertQuery(String query) {\n    String result = query;\n\n    for (final entry in regexTransformations.entries) {\n      result = result.replaceAllMapped(\n        RegExp(entry.key, caseSensitive: false),\n        entry.value,\n      );\n    }\n\n    for (final entry in keywordReplacements.entries) {\n      result = result.replaceAll(\n        RegExp(entry.key, caseSensitive: false),\n        entry.value,\n      );\n    }\n\n    for (final entry in dataTypeMappings.entries) {\n      result = result.replaceAll(\n        RegExp(entry.key, caseSensitive: false),\n        entry.value,\n      );\n    }\n\n    result = result.replaceAll('`', identifierQuote);\n\n    result = _cleanupQuery(result);\n\n    return result;\n  }\n\n  String _cleanupQuery(String query) {\n    return query\n        .replaceAll(RegExp(r'\\s+'), ' ')\n        .replaceAll(',,', ',')\n        .replaceAll(RegExp(r',\\s?\\)'), ')')\n        .trim();\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/adapters/grammar/sqlite_grammar.dart",
    "content": "import 'sql_grammar.dart';\n\nclass SqliteGrammar extends BaseGrammar {\n  @override\n  String get identifierQuote => '\"';\n\n  @override\n  Map<String, String> get dataTypeMappings => {\n    // Integer types - SQLite uses INTEGER for all integer types\n    r'BIGINT\\(\\d+\\)': 'INTEGER',\n    r'MEDIUMINT\\(\\d+\\)': 'INTEGER',\n    r'SMALLINT\\(\\d+\\)': 'INTEGER',\n    r'TINYINT\\(\\d+\\)': 'INTEGER',\n\n    // Binary and bit types\n    r'BINARY\\(\\d+\\)': 'BLOB',\n    r'VARBINARY\\(\\d+\\)': 'BLOB',\n    r'BIT\\(\\d+\\)': 'INTEGER',\n\n    // String types - SQLite uses TEXT for all string types\n    r'VARCHAR\\(\\d+\\)': 'TEXT',\n    r'CHAR\\(\\d+\\)': 'TEXT',\n\n    // Date/time types - SQLite stores as TEXT or INTEGER\n    'DATETIME': 'TEXT',\n    'TIMESTAMP': 'TEXT',\n    'DATE': 'TEXT',\n    'TIME': 'TEXT',\n    'YEAR': 'INTEGER',\n\n    // BLOB types\n    'TINYBLOB': 'BLOB',\n    'BLOB': 'BLOB',\n    'MEDIUMBLOB': 'BLOB',\n    'LONGBLOB': 'BLOB',\n\n    // Text types\n    'TINYTEXT': 'TEXT',\n    'MEDIUMTEXT': 'TEXT',\n    'LONGTEXT': 'TEXT',\n\n    // JSON - SQLite stores as TEXT\n    'JSON': 'TEXT',\n  };\n\n  @override\n  Map<String, String> get keywordReplacements => {\n    'UNSIGNED': '',\n    'ZEROFILL': '',\n    'AUTO_INCREMENT': '',\n    r\"COLLATE\\s+'[^']+'\": '',\n    r\"ENGINE\\s*=\\s*\\w+\": '',\n    r\"COLLATE\\s*=\\s*'[^']+'\": '',\n  };\n\n  @override\n  Map<String, String Function(Match)> get regexTransformations => {\n    // Auto-increment primary key transformation\n    r'`(\\w+)`\\s+BIGINT\\(\\d+\\)\\s+UNSIGNED\\s+NOT\\s+NULL\\s+AUTO_INCREMENT':\n        (match) => '\"${match[1]}\" INTEGER PRIMARY KEY AUTOINCREMENT',\n\n    // Remove primary key declarations (handled by AUTOINCREMENT)\n    r'PRIMARY KEY \\(`.*?`\\) USING BTREE': (match) => '',\n    r'PRIMARY KEY \\(`.*?`\\)': (match) => '',\n\n    // Integer type with length\n    r'(^|\\s|,)INT\\((\\d+)\\)': (match) => '${match[1]}INTEGER',\n    r'(^|\\s|,)INTEGER\\((\\d+)\\)': (match) => '${match[1]}INTEGER',\n\n    // Floating point types\n    r'FLOAT\\((\\d+),(\\d+)\\)': (match) => 'REAL',\n    r'DOUBLE\\((\\d+),(\\d+)\\)': (match) => 'REAL',\n    r'DECIMAL\\((\\d+),(\\d+)\\)': (match) => 'REAL',\n\n    // ENUM - SQLite doesn't have ENUM, use TEXT with CHECK constraint\n    r'ENUM\\(([^)]+)\\)': (match) =>\n        'TEXT CHECK (${_extractColumnName()} IN (${match[1]}))',\n  };\n\n  String _extractColumnName() {\n    return 'column_name';\n  }\n\n  @override\n  String convertQuery(String query) {\n    String result = super.convertQuery(query);\n\n    if (result.contains('TEXT CHECK (column_name IN')) {\n      result = _handleEnumConstraints(query, result);\n    }\n\n    return result;\n  }\n\n  String _handleEnumConstraints(String originalQuery, String convertedQuery) {\n    final enumMatch = RegExp(\n      r'\"?(\\w+)\"?\\s+ENUM\\(([^)]+)\\)',\n      caseSensitive: false,\n    ).firstMatch(originalQuery);\n\n    if (enumMatch != null) {\n      final columnName = enumMatch.group(1);\n      final enumValues = enumMatch.group(2);\n\n      return convertedQuery.replaceAll(\n        'TEXT CHECK (column_name IN ($enumValues))',\n        'TEXT CHECK (\"$columnName\" IN ($enumValues))',\n      );\n    }\n\n    return convertedQuery;\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/adapters/mysql_adapter.dart",
    "content": "import '../contracts/database_adapter_interface.dart';\nimport 'grammar/mysql_grammar.dart';\n\nclass MySqlAdapter implements DatabaseAdapterInterface {\n  late final MySqlGrammar _grammar;\n\n  MySqlAdapter() {\n    _grammar = MySqlGrammar();\n  }\n\n  @override\n  String get driverName => 'mysql';\n\n  @override\n  String adaptQuery(String query) {\n    return _grammar.convertQuery(query);\n  }\n\n  @override\n  String getMigrationsTableSql() {\n    return '''\nCREATE TABLE IF NOT EXISTS `migrations` (\n\t`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,\n\t`migration` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_unicode_ci',\n\t`batch` INT(10) UNSIGNED NOT NULL DEFAULT 1,\n\tPRIMARY KEY (`id`) USING BTREE\n)\nCOLLATE='utf8mb4_unicode_ci'\nENGINE=InnoDB\n;\n''';\n  }\n\n  @override\n  bool supports(String driver) {\n    return driver.toLowerCase() == 'mysql';\n  }\n\n  @override\n  String escapeIdentifier(String identifier) {\n    return '`$identifier`';\n  }\n\n  @override\n  String formatValue(dynamic value) {\n    if (value == null) return 'NULL';\n    if (value is String) return \"'${value.replaceAll(\"'\", \"''\")}'\";\n    if (value is num) return value.toString();\n    if (value is bool) return value ? '1' : '0';\n    return \"'$value'\";\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/adapters/postgresql_adapter.dart",
    "content": "import '../contracts/database_adapter_interface.dart';\nimport 'grammar/postgresql_grammar.dart';\n\nclass PostgreSqlAdapter implements DatabaseAdapterInterface {\n  late final PostgreSqlGrammar _grammar;\n  final List<String> _extractedIndexes = [];\n  String? _currentTableName;\n\n  PostgreSqlAdapter() {\n    _grammar = PostgreSqlGrammar();\n  }\n\n  @override\n  String get driverName => 'pgsql';\n\n  String _cleanedQuery = '';\n\n  @override\n  String adaptQuery(String query) {\n    List<String> statements = adaptQueryToStatements(query);\n    return statements.join(';\\n');\n  }\n\n  List<String> adaptQueryToStatements(String query) {\n    _extractTableNameAndIndexes(query);\n\n    String result = _grammar.convertQuery(_cleanedQuery);\n\n    List<String> statements = [result];\n\n    if (_extractedIndexes.isNotEmpty && _currentTableName != null) {\n      List<String> indexStatements = _generateIndexStatements();\n      statements.addAll(indexStatements);\n    }\n\n    return statements;\n  }\n\n  Future<void> executeStatements(\n    String query,\n    Future<void> Function(List<String>) executor,\n  ) async {\n    List<String> statements = adaptQueryToStatements(query);\n    await executor(statements);\n  }\n\n  void _extractTableNameAndIndexes(String query) {\n    _extractedIndexes.clear();\n    _currentTableName = null;\n\n    final tableNameRegex = RegExp(\n      r'CREATE TABLE (?:IF NOT EXISTS )?[`\"]?([^`\"]+)[`\"]?\\s*\\(',\n      caseSensitive: false,\n    );\n    final tableMatch = tableNameRegex.firstMatch(query);\n    if (tableMatch != null) {\n      _currentTableName = tableMatch.group(1);\n    }\n\n    final indexRegex = RegExp(\n      r'((?:SPATIAL|FULLTEXT|UNIQUE)\\s+)?INDEX\\s+[`\"]([^`\"]+)[`\"]\\s*\\(([^)]+)\\)',\n      caseSensitive: false,\n    );\n    final rawIndexes = indexRegex.allMatches(query).toList();\n    for (final m in rawIndexes) {\n      final typeKey = m.group(1)?.trim().toUpperCase() ?? '';\n      final name = m.group(2)!;\n      final cols = m.group(3)!;\n      final cleanCols = cols\n          .replaceAll(RegExp(r'[`\"]'), '')\n          .replaceAll(RegExp(r'\\s+'), ' ')\n          .trim();\n      _extractedIndexes.add('$name:$cleanCols:$typeKey');\n    }\n    query = query.replaceAll(indexRegex, '');\n    query = query.replaceAll(RegExp(r',\\s*\\)'), ')');\n    final constraintRegex = RegExp(\n      r'CONSTRAINT\\s+[`\"]([^`\"]+)[`\"]\\s+UNIQUE\\s*\\(([^)]+)\\)',\n      caseSensitive: false,\n    );\n    for (final m in constraintRegex.allMatches(query)) {\n      final name = m.group(1)!;\n      final cols = m.group(2)!;\n      final cleanCols = cols\n          .replaceAll(RegExp(r'[`\"]'), '')\n          .replaceAll(RegExp(r'\\s+'), ' ')\n          .trim();\n      _extractedIndexes.add('$name:$cleanCols:UNIQUE');\n    }\n\n    _cleanedQuery = query;\n  }\n\n  List<String> _generateIndexStatements() {\n    final statements = <String>[];\n\n    for (final info in _extractedIndexes) {\n      final parts = info.split(':');\n      final name = parts[0];\n      final cols = parts[1];\n      final typeKey = parts.length > 2 ? parts[2] : '';\n\n      final colList = cols\n          .split(',')\n          .map((c) => '\"${c.trim()}\"')\n          .toList()\n          .join(', ');\n\n      final prefix = typeKey.isNotEmpty ? '$typeKey ' : '';\n\n      final stmt =\n          'CREATE ${prefix}INDEX IF NOT EXISTS \"$name\" '\n          'ON \"$_currentTableName\" ($colList)';\n      statements.add(stmt);\n    }\n\n    return statements;\n  }\n\n  @override\n  String getMigrationsTableSql() {\n    return '''\nCREATE TABLE IF NOT EXISTS \"migrations\" (\n\t\"id\" SERIAL NOT NULL PRIMARY KEY,\n\t\"migration\" VARCHAR(255) NOT NULL,\n\t\"batch\" INTEGER NOT NULL DEFAULT 1\n);\n''';\n  }\n\n  @override\n  bool supports(String driver) {\n    final normalizedDriver = driver.toLowerCase();\n    return normalizedDriver == 'pgsql' ||\n        normalizedDriver == 'postgresql' ||\n        normalizedDriver == 'postgres';\n  }\n\n  @override\n  String escapeIdentifier(String identifier) {\n    return '\"$identifier\"';\n  }\n\n  @override\n  String formatValue(dynamic value) {\n    if (value == null) return 'NULL';\n    if (value is String) return \"'${value.replaceAll(\"'\", \"''\")}'\";\n    if (value is num) return value.toString();\n    if (value is bool) return value ? 'TRUE' : 'FALSE';\n    return \"'$value'\";\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/adapters/sqlite_adapter.dart",
    "content": "import '../contracts/database_adapter_interface.dart';\nimport 'grammar/sqlite_grammar.dart';\n\nclass SqliteAdapter implements DatabaseAdapterInterface {\n  late final SqliteGrammar _grammar;\n\n  SqliteAdapter() {\n    _grammar = SqliteGrammar();\n  }\n\n  @override\n  String get driverName => 'sqlite';\n\n  @override\n  String adaptQuery(String query) {\n    return _grammar.convertQuery(query);\n  }\n\n  @override\n  String getMigrationsTableSql() {\n    return '''\nCREATE TABLE IF NOT EXISTS \"migrations\" (\n\t\"id\" INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\"migration\" TEXT NOT NULL,\n\t\"batch\" INTEGER NOT NULL DEFAULT 1\n);\n''';\n  }\n\n  @override\n  bool supports(String driver) {\n    final normalizedDriver = driver.toLowerCase();\n    return normalizedDriver == 'sqlite' || normalizedDriver == 'sqlite3';\n  }\n\n  @override\n  String escapeIdentifier(String identifier) {\n    return '\"$identifier\"';\n  }\n\n  @override\n  String formatValue(dynamic value) {\n    if (value == null) return 'NULL';\n    if (value is String) return \"'${value.replaceAll(\"'\", \"''\")}'\";\n    if (value is num) return value.toString();\n    if (value is bool) return value ? '1' : '0';\n    return \"'$value'\";\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/builders/column_definition.dart",
    "content": "import '../../../enum/column_index.dart';\nimport 'schema.dart';\n\nclass ColumnDefinition {\n  final Schema _schema;\n  final String _name;\n  final String _type;\n\n  bool _nullable = true;\n  dynamic _length;\n  bool _unsigned = false;\n  bool _zeroFill = false;\n  dynamic _defaultValue;\n  String? _comment;\n  String? _collation;\n  String? _charset;\n  String? _expression;\n  String? _virtuality;\n  bool _increment = false;\n  bool _unique = false;\n  String? _uniqueConstraintName;\n  String? _indexName;\n  ColumnIndex _indexType = ColumnIndex.indexKey;\n\n  String? _foreignTable;\n  String? _foreignColumn;\n  String? _onUpdate;\n  String? _onDelete;\n\n  bool _isFinalized = false;\n\n  ColumnDefinition(this._schema, this._name, this._type) {\n    _schema.registerColumnDefinition(this);\n  }\n\n  /// Set column as NOT NULL\n  ColumnDefinition notNull() {\n    _nullable = false;\n    return this;\n  }\n\n  /// Set column as NULLABLE\n  ColumnDefinition nullable() {\n    _nullable = true;\n    return this;\n  }\n\n  /// Set column length/size\n  ColumnDefinition length(int length) {\n    _length = length;\n    return this;\n  }\n\n  /// Set column as UNSIGNED (for numeric types)\n  ColumnDefinition unsigned() {\n    _unsigned = true;\n    return this;\n  }\n\n  /// Set column as ZEROFILL\n  ColumnDefinition zeroFill() {\n    _zeroFill = true;\n    return this;\n  }\n\n  /// Set default value\n  ColumnDefinition defaultTo(dynamic value) {\n    _defaultValue = value;\n    return this;\n  }\n\n  /// Set default to CURRENT_TIMESTAMP\n  ColumnDefinition defaultToCurrent() {\n    _defaultValue = 'CURRENT_TIMESTAMP';\n    return this;\n  }\n\n  /// Set column comment\n  ColumnDefinition comment(String comment) {\n    _comment = comment;\n    return this;\n  }\n\n  /// Set column collation\n  ColumnDefinition collate(String collation) {\n    _collation = collation;\n    return this;\n  }\n\n  /// Set column charset - NOW PROPERLY USED!\n  ColumnDefinition charset(String charset) {\n    _charset = charset;\n    return this;\n  }\n\n  /// Set column as AUTO_INCREMENT\n  ColumnDefinition autoIncrement() {\n    _increment = true;\n    return this;\n  }\n\n  /// Set column as UNIQUE\n  ColumnDefinition unique([String? constraintName]) {\n    if (constraintName != null) {\n      _unique = false;\n      _uniqueConstraintName = constraintName;\n      _schema.addCompositeUniqueConstraint(constraintName, [_name]);\n    } else {\n      _unique = true;\n      _uniqueConstraintName = null;\n    }\n    return this;\n  }\n\n  /// Add index to column - NOW PROPERLY USED!\n  ColumnDefinition index([\n    String? indexName,\n    ColumnIndex type = ColumnIndex.indexKey,\n  ]) {\n    _indexName = indexName ?? 'idx_${_schema.tableName}_$_name';\n    _indexType = type;\n\n    _schema.addCompositeIndex(_indexName!, _name, _indexType);\n    return this;\n  }\n\n  ColumnDefinition foreignKey(\n    String referencesTable,\n    String referencesColumn, {\n    String onUpdate = 'CASCADE',\n    String onDelete = 'CASCADE',\n  }) {\n    _foreignTable = referencesTable;\n    _foreignColumn = referencesColumn;\n    _onUpdate = onUpdate;\n    _onDelete = onDelete;\n\n    _schema.foreign(\n      _name,\n      _foreignTable!,\n      _foreignColumn!,\n      onUpdate: _onUpdate ?? 'CASCADE',\n      onDelete: _onDelete ?? 'CASCADE',\n    );\n    return this;\n  }\n\n  ColumnDefinition generated(\n    String expression, {\n    String virtuality = 'VIRTUAL',\n  }) {\n    _expression = expression;\n    _virtuality = virtuality;\n    return this;\n  }\n\n  void finalize() {\n    if (!_isFinalized) {\n      _isFinalized = true;\n      _addColumnToSchema();\n    }\n  }\n\n  void _addColumnToSchema() {\n    String columnType = _type;\n    if (_length != null) {\n      columnType = '$_type($_length)';\n    }\n\n    String? finalComment = _comment;\n    if (_charset != null) {\n      String charsetInfo = 'charset: $_charset';\n      finalComment = _comment != null\n          ? '$_comment ($charsetInfo)'\n          : charsetInfo;\n    }\n\n    bool shouldBeUnique = _unique && _uniqueConstraintName == null;\n\n    _schema.addColumn(\n      _name,\n      columnType,\n      nullable: _nullable,\n      unsigned: _unsigned,\n      zeroFill: _zeroFill,\n      defaultValue: _defaultValue,\n      comment: finalComment,\n      collation: _collation,\n      expression: _expression,\n      virtuality: _virtuality,\n      increment: _increment,\n      unique: shouldBeUnique,\n    );\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/builders/column_types.dart",
    "content": "import 'column_definition.dart';\nimport 'schema.dart';\n\nexport 'column_definition.dart';\nexport 'table_definition.dart';\n\nextension ColumnTypes on Schema {\n  /// Add an auto-incrementing primary key column\n  ColumnDefinition id([String name = 'id']) {\n    final definition = ColumnDefinition(\n      this,\n      name,\n      'BIGINT',\n    ).length(20).unsigned().autoIncrement().notNull();\n    primary(name);\n    return definition;\n  }\n\n  /// Add a big auto-incrementing column\n  ColumnDefinition bigIncrements(String name) {\n    final definition = ColumnDefinition(\n      this,\n      name,\n      'BIGINT',\n    ).length(20).unsigned().autoIncrement().notNull();\n    return definition;\n  }\n\n  /// Create an integer column\n  ColumnDefinition integer(String name) {\n    return ColumnDefinition(this, name, 'INT').length(10);\n  }\n\n  /// Create a tiny integer column\n  ColumnDefinition tinyInt(String name) {\n    return ColumnDefinition(this, name, 'TINYINT').length(1);\n  }\n\n  /// Create a small integer column\n  ColumnDefinition smallInt(String name) {\n    return ColumnDefinition(this, name, 'SMALLINT').length(5);\n  }\n\n  /// Create a medium integer column\n  ColumnDefinition mediumInt(String name) {\n    return ColumnDefinition(this, name, 'MEDIUMINT').length(8);\n  }\n\n  /// Create a big integer column\n  ColumnDefinition bigInt(String name) {\n    return ColumnDefinition(this, name, 'BIGINT').length(20);\n  }\n\n  /// Create a bit column\n  ColumnDefinition bit(String name) {\n    return ColumnDefinition(this, name, 'BIT').length(1);\n  }\n\n  /// Create a float column\n  ColumnDefinition float(String name, {int? precision, int? scale}) {\n    String type = 'FLOAT';\n    if (precision != null && scale != null) {\n      type = 'FLOAT($precision,$scale)';\n    }\n    return ColumnDefinition(this, name, type);\n  }\n\n  /// Create a double column\n  ColumnDefinition double(String name, {int? precision, int? scale}) {\n    String type = 'DOUBLE';\n    if (precision != null && scale != null) {\n      type = 'DOUBLE($precision,$scale)';\n    }\n    return ColumnDefinition(this, name, type);\n  }\n\n  /// Create a decimal column\n  ColumnDefinition decimal(String name, {int? precision, int? scale}) {\n    String type = 'DECIMAL';\n    if (precision != null && scale != null) {\n      type = 'DECIMAL($precision,$scale)';\n    }\n    return ColumnDefinition(this, name, type);\n  }\n\n  /// Create a string/varchar column\n  ColumnDefinition string(String name) {\n    return ColumnDefinition(this, name, 'VARCHAR').length(255);\n  }\n\n  /// Create a char column\n  ColumnDefinition char(String name) {\n    return ColumnDefinition(this, name, 'CHAR').length(50);\n  }\n\n  /// Create a tiny text column\n  ColumnDefinition tinyText(String name) {\n    return ColumnDefinition(this, name, 'TINYTEXT');\n  }\n\n  /// Create a text column\n  ColumnDefinition text(String name) {\n    return ColumnDefinition(this, name, 'TEXT');\n  }\n\n  /// Create a medium text column\n  ColumnDefinition mediumText(String name) {\n    return ColumnDefinition(this, name, 'MEDIUMTEXT');\n  }\n\n  /// Create a long text column\n  ColumnDefinition longText(String name) {\n    return ColumnDefinition(this, name, 'LONGTEXT');\n  }\n\n  /// Create a JSON column\n  ColumnDefinition json(String name) {\n    return ColumnDefinition(this, name, 'JSON');\n  }\n\n  /// Create a UUID column\n  ColumnDefinition uuid(String name) {\n    return ColumnDefinition(this, name, 'CHAR').length(36);\n  }\n\n  /// Create a binary column\n  ColumnDefinition binary(String name) {\n    return ColumnDefinition(this, name, 'BINARY').length(50);\n  }\n\n  /// Create a variable binary column\n  ColumnDefinition varBinary(String name) {\n    return ColumnDefinition(this, name, 'VARBINARY').length(50);\n  }\n\n  /// Create a tiny blob column\n  ColumnDefinition tinyBlob(String name) {\n    return ColumnDefinition(this, name, 'TINYBLOB');\n  }\n\n  /// Create a blob column\n  ColumnDefinition blob(String name) {\n    return ColumnDefinition(this, name, 'BLOB');\n  }\n\n  /// Create a medium blob column\n  ColumnDefinition mediumBlob(String name) {\n    return ColumnDefinition(this, name, 'MEDIUMBLOB');\n  }\n\n  /// Create a long blob column\n  ColumnDefinition longBlob(String name) {\n    return ColumnDefinition(this, name, 'LONGBLOB');\n  }\n\n  /// Create a date column\n  ColumnDefinition date(String name) {\n    return ColumnDefinition(this, name, 'DATE');\n  }\n\n  /// Create a time column\n  ColumnDefinition time(String name) {\n    return ColumnDefinition(this, name, 'TIME');\n  }\n\n  /// Create a year column\n  ColumnDefinition year(String name) {\n    return ColumnDefinition(this, name, 'YEAR');\n  }\n\n  /// Create a datetime column\n  ColumnDefinition dateTime(String name) {\n    return ColumnDefinition(this, name, 'DATETIME');\n  }\n\n  /// Create a timestamp column\n  ColumnDefinition timeStamp(String name) {\n    return ColumnDefinition(this, name, 'TIMESTAMP');\n  }\n\n  /// Create standard timestamps (created_at, updated_at)\n  void timeStamps() {\n    timeStamp('created_at').nullable();\n    timeStamp('updated_at').nullable();\n  }\n\n  /// Create soft delete timestamp\n  ColumnDefinition softDeletes([String name = 'deleted_at']) {\n    return timeStamp(name).nullable();\n  }\n\n  /// Create a point geometry column\n  ColumnDefinition point(String name) {\n    return ColumnDefinition(this, name, 'POINT');\n  }\n\n  /// Create a line string geometry column\n  ColumnDefinition lineString(String name) {\n    return ColumnDefinition(this, name, 'LINESTRING');\n  }\n\n  /// Create a polygon geometry column\n  ColumnDefinition polygon(String name) {\n    return ColumnDefinition(this, name, 'POLYGON');\n  }\n\n  /// Create a geometry column\n  ColumnDefinition geometry(String name) {\n    return ColumnDefinition(this, name, 'GEOMETRY');\n  }\n\n  /// Create a multi-point geometry column\n  ColumnDefinition multiPoint(String name) {\n    return ColumnDefinition(this, name, 'MULTIPOINT');\n  }\n\n  /// Create a multi-line string geometry column\n  ColumnDefinition multiLineString(String name) {\n    return ColumnDefinition(this, name, 'MULTILINESTRING');\n  }\n\n  /// Create a multi-polygon geometry column\n  ColumnDefinition multiPolygon(String name) {\n    return ColumnDefinition(this, name, 'MULTIPOLYGON');\n  }\n\n  /// Create a geometry collection column\n  ColumnDefinition geometryCollection(String name) {\n    return ColumnDefinition(this, name, 'GEOMETRYCOLLECTION');\n  }\n\n  /// Create an enum column\n  ColumnDefinition enumType(String name, List<String> values) {\n    final enumValuesString = values.map((value) => \"'$value'\").join(', ');\n    return ColumnDefinition(this, name, 'ENUM($enumValuesString)');\n  }\n\n  /// Create a set column\n  ColumnDefinition setType(String name, List<String> values) {\n    final setValuesString = values.map((value) => \"'$value'\").join(', ');\n    return ColumnDefinition(this, name, 'SET($setValuesString)');\n  }\n\n  /// Create a boolean column\n  ColumnDefinition boolean(String name) {\n    return ColumnDefinition(this, name, 'TINYINT').length(1);\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/builders/schema.dart",
    "content": "import '../../../enum/column_index.dart';\nimport '../contracts/schema_interface.dart';\n\nclass Schema implements SchemaInterface {\n  final List<String> _queries = [];\n  final List<String> _foreignKeys = [];\n  final List<String> _indexes = [];\n  final List<dynamic> _columnDefinitions = [];\n  final Map<String, List<String>> _compositeUniqueConstraints = {};\n  final Map<String, Map<String, dynamic>> _compositeIndexes = {};\n  String _primaryField = '';\n  String _primaryAlgorithm = '';\n  String _tableName = '';\n\n  void setTableName(String tableName) {\n    _tableName = tableName;\n  }\n\n  void registerColumnDefinition(dynamic columnDefinition) {\n    _columnDefinitions.add(columnDefinition);\n  }\n\n  void _finalizeColumnDefinitions() {\n    for (final columnDef in _columnDefinitions) {\n      columnDef.finalize();\n    }\n    _columnDefinitions.clear();\n\n    _addCompositeUniqueConstraints();\n\n    _addCompositeIndexes();\n  }\n\n  void _addCompositeUniqueConstraints() {\n    _compositeUniqueConstraints.forEach((constraintName, columns) {\n      final constraint =\n          'CONSTRAINT `$constraintName` UNIQUE (${columns.map((col) => '`$col`').join(', ')})';\n      _indexes.add(constraint);\n    });\n  }\n\n  void _addCompositeIndexes() {\n    _compositeIndexes.forEach((indexName, indexData) {\n      List<String> columns = indexData['columns'];\n      ColumnIndex type = indexData['type'];\n\n      if (type == ColumnIndex.indexKey) {\n        _indexes.add(\n          'INDEX `$indexName` (${columns.map((e) => \"`$e`\").join(', ')})',\n        );\n      } else {\n        _indexes.add(\n          '${type.name} INDEX `$indexName` (${columns.map((e) => \"`$e`\").join(', ')})',\n        );\n      }\n    });\n  }\n\n  @override\n  void addColumn(\n    String name,\n    String type, {\n    bool nullable = false,\n    dynamic length,\n    bool unsigned = false,\n    bool zeroFill = false,\n    dynamic defaultValue,\n    String? comment,\n    String? collation,\n    String? expression,\n    String? virtuality,\n    bool increment = false,\n    bool unique = false,\n  }) {\n    final columnDefinition = StringBuffer('  `$name` $type');\n\n    if (length != null) {\n      columnDefinition.write('($length)');\n    }\n\n    if (unsigned) {\n      columnDefinition.write(' UNSIGNED');\n    }\n\n    if (zeroFill) {\n      columnDefinition.write(' ZEROFILL');\n    }\n\n    String nullableStr = nullable ? 'NULL' : 'NOT NULL';\n    columnDefinition.write(\n      ' ' * (20 - columnDefinition.length % 20) + nullableStr,\n    );\n\n    if (unique) {\n      columnDefinition.write(' UNIQUE');\n    }\n\n    if (defaultValue != null) {\n      RegExp funcRegex = RegExp(\n        r'^(CURRENT_TIMESTAMP|NOW\\(\\)|UUID\\(\\)|RAND\\(\\))$',\n        caseSensitive: false,\n      );\n      if (funcRegex.hasMatch(defaultValue.toString())) {\n        columnDefinition.write(\" DEFAULT $defaultValue\");\n\n        if (name == 'updated_at' &&\n            defaultValue.toString().toUpperCase() == 'CURRENT_TIMESTAMP') {\n          columnDefinition.write(\" ON UPDATE CURRENT_TIMESTAMP\");\n        }\n      } else {\n        if (defaultValue is int || defaultValue is bool) {\n          columnDefinition.write(\" DEFAULT $defaultValue\");\n        } else {\n          columnDefinition.write(\" DEFAULT '$defaultValue'\");\n        }\n      }\n    }\n\n    if (comment != null) {\n      columnDefinition.write(\" COMMENT '$comment'\");\n    }\n\n    if (collation != null) {\n      columnDefinition.write(\" COLLATE $collation\");\n    }\n\n    if (expression != null) {\n      columnDefinition.write(' GENERATED ALWAYS AS ($expression)');\n    }\n\n    if (virtuality != null) {\n      columnDefinition.write(' $virtuality');\n    }\n\n    if (increment) {\n      columnDefinition.write(' AUTO_INCREMENT');\n    }\n\n    _queries.add(columnDefinition.toString());\n  }\n\n  @override\n  void primary(String columnName, [String algorithm = 'BTREE']) {\n    _primaryField = columnName;\n    _primaryAlgorithm = algorithm;\n  }\n\n  @override\n  void index(ColumnIndex type, String name, List<String> columns) {\n    if (type == ColumnIndex.indexKey) {\n      _indexes.add('INDEX `$name` (${columns.map((e) => \"`$e`\").join(',')})');\n    } else {\n      _indexes.add(\n        '${type.name.toUpperCase()} INDEX `$name` (${columns.map((e) => \"`$e`\").join(',')})',\n      );\n    }\n  }\n\n  @override\n  void foreign(\n    String columnName,\n    String referencesTable,\n    String referencesColumn, {\n    bool constrained = true,\n    String onUpdate = 'CASCADE',\n    String onDelete = 'CASCADE',\n  }) {\n    String constraint = '';\n    if (constrained) {\n      constraint = 'CONSTRAINT FK_${_tableName}_$referencesTable ';\n    }\n\n    final fk =\n        '${constraint}FOREIGN KEY (`$columnName`) REFERENCES `$referencesTable` (`$referencesColumn`) ON UPDATE $onUpdate ON DELETE $onDelete';\n    _foreignKeys.add(fk);\n  }\n\n  @override\n  List<String> get queries => List.unmodifiable(_queries);\n\n  @override\n  List<String> get foreignKeys => List.unmodifiable(_foreignKeys);\n\n  @override\n  List<String> get indexes => List.unmodifiable(_indexes);\n\n  @override\n  String get primaryField => _primaryField;\n\n  @override\n  String get primaryAlgorithm => _primaryAlgorithm;\n\n  String get tableName => _tableName;\n\n  @override\n  void reset() {\n    _queries.clear();\n    _foreignKeys.clear();\n    _indexes.clear();\n    _columnDefinitions.clear();\n    _compositeUniqueConstraints.clear();\n    _compositeIndexes.clear();\n    _primaryField = '';\n    _primaryAlgorithm = '';\n    _tableName = '';\n  }\n\n  void addCompositeUniqueConstraint(\n    String constraintName,\n    List<String> columns,\n  ) {\n    if (_compositeUniqueConstraints.containsKey(constraintName)) {\n      _compositeUniqueConstraints[constraintName]!.addAll(columns);\n    } else {\n      _compositeUniqueConstraints[constraintName] = List.from(columns);\n    }\n  }\n\n  void addCompositeIndex(\n    String indexName,\n    String columnName,\n    ColumnIndex type,\n  ) {\n    if (_compositeIndexes.containsKey(indexName)) {\n      _compositeIndexes[indexName]!['columns'].add(columnName);\n    } else {\n      _compositeIndexes[indexName] = {\n        'columns': [columnName],\n        'type': type,\n      };\n    }\n  }\n\n  String generateCreateTableSql(String tableName, {bool ifNotExists = false}) {\n    _finalizeColumnDefinitions();\n\n    final query = StringBuffer();\n    String createClause = ifNotExists\n        ? 'CREATE TABLE IF NOT EXISTS'\n        : 'CREATE TABLE';\n\n    query.writeln('$createClause `$tableName` (');\n\n    query.write(_queries.join(',\\n'));\n\n    if (_primaryField.isNotEmpty) {\n      query.write(',\\n  PRIMARY KEY (`$_primaryField`)');\n    }\n\n    if (_indexes.isNotEmpty) {\n      for (String index in _indexes) {\n        query.write(',\\n  $index');\n      }\n    }\n\n    if (_foreignKeys.isNotEmpty) {\n      for (String fk in _foreignKeys) {\n        query.write(',\\n  $fk');\n      }\n    }\n\n    query.write('\\n)');\n\n    return query.toString();\n  }\n\n  String generateCreateAlterSql(\n    String tableName, {\n    bool ifNotExists = false,\n    String beforeColumn = '',\n    String afterColumn = '',\n  }) {\n    _finalizeColumnDefinitions();\n\n    final clauses = <String>[];\n\n    for (final colDef in _queries) {\n      var clause = 'ADD COLUMN ${colDef.trim()}';\n      if (beforeColumn.isNotEmpty) clause += ' BEFORE `$beforeColumn`';\n      if (afterColumn.isNotEmpty) clause += ' AFTER `$afterColumn`';\n      clauses.add(clause);\n    }\n\n    if (_primaryField.isNotEmpty) {\n      clauses.add('ADD PRIMARY KEY (`$_primaryField`)');\n    }\n\n    for (final idx in _indexes) {\n      clauses.add('ADD $idx');\n    }\n\n    for (final fk in _foreignKeys) {\n      clauses.add('ADD $fk');\n    }\n\n    final buffer = StringBuffer();\n    buffer.writeln('ALTER TABLE `$tableName`');\n    for (var i = 0; i < clauses.length; i++) {\n      final sep = i == clauses.length - 1 ? '' : ',';\n      buffer.writeln('  ${clauses[i]}$sep');\n    }\n\n    return buffer.toString();\n  }\n\n  String generateDropTableSql(String tableName, {bool ifExists = false}) {\n    String dropClause = ifExists ? 'DROP TABLE IF EXISTS' : 'DROP TABLE';\n    return '$dropClause `$tableName`';\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/builders/table_definition.dart",
    "content": "import 'dart:async';\n\nimport '../contracts/database_adapter_interface.dart';\nimport '../migration_connection.dart';\n\nclass TableDefinition implements Future<void> {\n  final String _tableName;\n  final Future<void> Function() _createFunction;\n  final MigrationConnection? _connection;\n  final DatabaseAdapterInterface? _adapter;\n\n  String? _engine;\n  String? _comment;\n  String? _charset;\n  String? _collation;\n  int? _autoIncrement;\n\n  TableDefinition(\n    this._tableName,\n    this._createFunction, {\n    MigrationConnection? connection,\n    DatabaseAdapterInterface? adapter,\n  }) : _connection = connection,\n       _adapter = adapter;\n\n  TableDefinition engine(String engine) {\n    _engine = engine;\n    return this;\n  }\n\n  TableDefinition comment(String comment) {\n    _comment = comment;\n    return this;\n  }\n\n  TableDefinition charset(String charset) {\n    _charset = charset;\n    return this;\n  }\n\n  TableDefinition collate(String collation) {\n    _collation = collation;\n    return this;\n  }\n\n  TableDefinition autoIncrement(int startValue) {\n    _autoIncrement = startValue;\n    return this;\n  }\n\n  Future<void> _getExecutionFuture() async {\n    await _createFunction();\n    await _applyTableOptions();\n  }\n\n  @override\n  Future<R> then<R>(\n    FutureOr<R> Function(void value) onValue, {\n    Function? onError,\n  }) {\n    return _getExecutionFuture().then(onValue, onError: onError);\n  }\n\n  @override\n  Future<void> catchError(\n    Function onError, {\n    bool Function(Object error)? test,\n  }) {\n    return _getExecutionFuture().catchError(onError, test: test);\n  }\n\n  @override\n  Future<void> whenComplete(FutureOr<void> Function() action) {\n    return _getExecutionFuture().whenComplete(action);\n  }\n\n  @override\n  Future<void> timeout(\n    Duration timeLimit, {\n    FutureOr<void> Function()? onTimeout,\n  }) {\n    return _getExecutionFuture().timeout(timeLimit, onTimeout: onTimeout);\n  }\n\n  @override\n  Stream<void> asStream() {\n    return _getExecutionFuture().asStream();\n  }\n\n  Future<void> _applyTableOptions() async {\n    if (_engine == null &&\n        _comment == null &&\n        _charset == null &&\n        _collation == null &&\n        _autoIncrement == null) {\n      return; // No options to apply\n    }\n\n    final options = <String>[];\n\n    if (_engine != null) {\n      options.add('ENGINE=$_engine');\n    }\n\n    if (_charset != null) {\n      options.add('DEFAULT CHARSET=$_charset');\n    }\n\n    if (_collation != null) {\n      options.add('COLLATE=$_collation');\n    }\n\n    if (_comment != null) {\n      options.add(\"COMMENT='$_comment'\");\n    }\n\n    if (_autoIncrement != null) {\n      options.add('AUTO_INCREMENT=$_autoIncrement');\n    }\n\n    if (options.isNotEmpty && _connection?.connection != null) {\n      String alterSql = 'ALTER TABLE `$_tableName` ${options.join(', ')}';\n\n      if (_adapter != null) {\n        alterSql = _adapter.adaptQuery(alterSql);\n      }\n\n      await _connection!.connection!.execute(alterSql);\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/contracts/database_adapter_interface.dart",
    "content": "abstract class DatabaseAdapterInterface {\n  String get driverName;\n\n  String adaptQuery(String query);\n\n  String getMigrationsTableSql();\n\n  bool supports(String driver);\n\n  String escapeIdentifier(String identifier);\n\n  String formatValue(dynamic value);\n}\n"
  },
  {
    "path": "lib/src/database/migration/contracts/migration_connection_interface.dart",
    "content": "import '../../../contract/database/_connectors/_database_connection.dart';\n\nabstract class MigrationConnectionInterface {\n  DatabaseConnection? get connection;\n\n  String? get driver;\n\n  Future<void> setup(Map<String, dynamic> databaseConfig);\n\n  Future<void> closeConnection();\n\n  Future<void> truncateMigration();\n}\n"
  },
  {
    "path": "lib/src/database/migration/contracts/schema_interface.dart",
    "content": "import '../../../enum/column_index.dart';\n\nabstract class SchemaInterface {\n  void addColumn(\n    String name,\n    String type, {\n    bool nullable = false,\n    dynamic length,\n    bool unsigned = false,\n    bool zeroFill = false,\n    dynamic defaultValue,\n    String? comment,\n    String? collation,\n    String? expression,\n    String? virtuality,\n    bool increment = false,\n    bool unique = false,\n  });\n\n  void primary(String columnName, [String algorithm = 'BTREE']);\n\n  void index(ColumnIndex type, String name, List<String> columns);\n\n  void foreign(\n    String columnName,\n    String referencesTable,\n    String referencesColumn, {\n    bool constrained = true,\n    String onUpdate = 'CASCADE',\n    String onDelete = 'CASCADE',\n  });\n\n  List<String> get queries;\n\n  List<String> get foreignKeys;\n\n  List<String> get indexes;\n\n  String get primaryField;\n\n  String get primaryAlgorithm;\n\n  void reset();\n}\n"
  },
  {
    "path": "lib/src/database/migration/migration.dart",
    "content": "import 'dart:io';\n\nimport 'package:meta/meta.dart';\nimport 'package:vania/src/exception/query_exception.dart';\nimport 'package:vania/src/utils/functions.dart' show toSnakeCase;\nimport 'builders/schema.dart';\nimport 'builders/table_definition.dart';\nimport 'contracts/database_adapter_interface.dart';\nimport 'migration_connection.dart';\n\nabstract class Migration {\n  late final MigrationConnection _connection;\n  late final Schema _schemaBuilder;\n  late final DatabaseAdapterInterface? _adapter;\n\n  String get migrationName => toSnakeCase(runtimeType.toString());\n\n  Migration() {\n    _connection = MigrationConnection();\n    _schemaBuilder = Schema();\n    _adapter = _connection.adapter;\n  }\n\n  @mustBeOverridden\n  Future<void> up();\n\n  @mustBeOverridden\n  Future<void> down();\n\n  TableDefinition create(\n    String tableName,\n    Function(Schema) callback, [\n    bool ifNotExists = false,\n  ]) {\n    _schemaBuilder.reset();\n    _schemaBuilder.setTableName(tableName);\n\n    Future<void> createFunction() async {\n      callback(_schemaBuilder);\n\n      String sql = _schemaBuilder.generateCreateTableSql(\n        tableName,\n        ifNotExists: ifNotExists,\n      );\n\n      if (_adapter != null && _adapter.driverName == 'pgsql') {\n        final postgresAdapter = _adapter as dynamic;\n        if (postgresAdapter.executeStatements != null) {\n          await postgresAdapter.executeStatements(sql, (\n            List<String> statements,\n          ) async {\n            for (String statement in statements) {\n              await _connection.connection!.execute(statement);\n            }\n          });\n          return;\n        }\n      }\n\n      if (_adapter != null) {\n        sql = _adapter.adaptQuery(sql);\n      }\n      try {\n        await _connection.connection!.execute(sql);\n      } on QueryException catch (e) {\n        stderr.writeln('Error executing statement: $sql\\nError: ${e.cause}');\n        exit(0);\n      }\n    }\n\n    return TableDefinition(\n      tableName,\n      createFunction,\n      connection: _connection,\n      adapter: _adapter,\n    );\n  }\n\n  @Deprecated('createTableIfNotExists will be deprecated in version 1.1.0')\n  TableDefinition createTableIfNotExists(\n    String tableName,\n    Function(Schema) callback,\n  ) {\n    _schemaBuilder.reset();\n    _schemaBuilder.setTableName(tableName);\n\n    Future<void> createFunction() async {\n      callback(_schemaBuilder);\n\n      String sql = _schemaBuilder.generateCreateTableSql(\n        tableName,\n        ifNotExists: true,\n      );\n      if (_adapter != null && _adapter.driverName == 'pgsql') {\n        final postgresAdapter = _adapter as dynamic;\n        if (postgresAdapter.executeStatements != null) {\n          await postgresAdapter.executeStatements(sql, (\n            String statement,\n          ) async {\n            try {\n              await _connection.connection!.execute(statement);\n            } on QueryException catch (e) {\n              stderr.writeln(\n                'Error executing statement: $statement\\nError: ${e.cause}',\n              );\n              exit(0);\n            }\n          });\n          return;\n        }\n      }\n\n      if (_adapter != null) {\n        sql = _adapter.adaptQuery(sql);\n      }\n      try {\n        await _connection.connection!.execute(sql);\n      } on QueryException catch (e) {\n        stderr.writeln('Error executing statement: $sql\\nError: ${e.cause}');\n        exit(0);\n      }\n    }\n\n    return TableDefinition(\n      tableName,\n      createFunction,\n      connection: _connection,\n      adapter: _adapter,\n    );\n  }\n\n  TableDefinition alterColumn(\n    String table,\n    Function(Schema) callback, {\n    String beforeColumn = '',\n    String afterColumn = '',\n  }) {\n    _schemaBuilder.reset();\n    _schemaBuilder.setTableName(table);\n    Future<void> createFunction() async {\n      callback(_schemaBuilder);\n\n      try {\n        String sql = _schemaBuilder.generateCreateAlterSql(\n          table,\n          afterColumn: afterColumn,\n          beforeColumn: beforeColumn,\n        );\n        if (_adapter != null) {\n          sql = _adapter.adaptQuery(sql);\n        }\n        await _connection.connection!.execute(sql);\n      } on QueryException catch (e) {\n        stderr.writeln('${e.cause}');\n        exit(0);\n      }\n    }\n\n    return TableDefinition(\n      table,\n      createFunction,\n      connection: _connection,\n      adapter: _adapter,\n    );\n  }\n\n  Future<void> drop(String tableName) async {\n    String sql = _schemaBuilder.generateDropTableSql(tableName, ifExists: true);\n\n    if (_adapter?.driverName == 'mysql') {\n      sql =\n          'SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;$sql;SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;';\n    }\n\n    if (_adapter?.driverName == 'pgsql') {\n      sql = '$sql CASCADE';\n    }\n\n    if (_adapter != null) {\n      sql = _adapter.adaptQuery(sql);\n    }\n\n    try {\n      await _connection.connection!.execute(sql);\n    } on QueryException catch (e) {\n      stderr.writeln('Error executing statement: $sql\\nError: ${e.cause}');\n      exit(0);\n    }\n  }\n\n  @Deprecated('dropTableIfExists will be deprecated in version 1.1.0')\n  Future<void> dropTableIfExists(String tableName) async {\n    String sql = _schemaBuilder.generateDropTableSql(tableName, ifExists: true);\n\n    if (_adapter?.driverName == 'mysql') {\n      sql =\n          'SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;${sql}SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;';\n    }\n\n    if (_adapter != null) {\n      sql = _adapter.adaptQuery(sql);\n    }\n\n    try {\n      await _connection.connection!.execute(sql);\n    } on QueryException catch (e) {\n      stderr.writeln('Error executing statement: $sql\\nError: ${e.cause}');\n      exit(0);\n    }\n  }\n\n  Future<void> execute(String sql) async {\n    if (_adapter != null) {\n      sql = _adapter.adaptQuery(sql);\n    }\n\n    try {\n      await _connection.connection!.execute(sql);\n    } on QueryException catch (e) {\n      stderr.writeln('Error executing statement: $sql\\nError: ${e.cause}');\n      exit(0);\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/migration_connection.dart",
    "content": "import 'dart:io';\n\nimport '../../contract/database/_connectors/_database_connection.dart';\nimport '../../env_handler/env.dart';\nimport '../../exception/database_exception.dart';\nimport '../../exception/invalid_argument_exception.dart';\nimport '../_connection_manager.dart';\nimport '../_database_utils/_db_config.dart';\nimport 'adapters/mysql_adapter.dart';\nimport 'adapters/postgresql_adapter.dart';\nimport 'adapters/sqlite_adapter.dart';\nimport 'contracts/database_adapter_interface.dart';\nimport 'contracts/migration_connection_interface.dart';\n\nclass MigrationConnection implements MigrationConnectionInterface {\n  static final MigrationConnection _singleton = MigrationConnection._internal();\n\n  DatabaseConnection? _dbConnection;\n  String? _driver;\n  DatabaseAdapterInterface? _adapter;\n\n  factory MigrationConnection() {\n    Env().load();\n    return _singleton;\n  }\n\n  MigrationConnection._internal();\n\n  @override\n  DatabaseConnection? get connection => _dbConnection;\n\n  @override\n  String? get driver => _driver;\n\n  DatabaseAdapterInterface? get adapter => _adapter;\n\n  @override\n  Future<void> setup(Map<String, dynamic> databaseConfig) async {\n    try {\n      final connectionManager = ConnectionManager();\n\n      connectionManager.defaultConnection = databaseConfig['default'];\n\n      Map<String, dynamic> connections = databaseConfig['connections'];\n\n      _driver = databaseConfig['default'];\n      await connectionManager.connect(\n        _createDBConfig(connections[_driver]),\n        _driver!,\n      );\n\n      _dbConnection = connectionManager.connection(_driver);\n\n      if (_dbConnection == null) {\n        stderr.writeln('A database must be specified.');\n        exit(1);\n      }\n\n      _adapter = _createAdapter(_driver!);\n\n      String migrationSql = _adapter!.getMigrationsTableSql();\n      await _dbConnection!.execute(migrationSql);\n    } on InvalidArgumentException catch (e) {\n      stderr.writeln(e.message);\n      exit(1);\n    } on DatabaseException catch (e) {\n      stderr.writeln(e.message);\n      stderr.writeln(e.cause);\n      exit(1);\n    } catch (e) {\n      stderr.write(e.toString());\n      exit(1);\n    }\n  }\n\n  @override\n  Future<void> truncateMigration() async {\n    if (_dbConnection == null) {\n      stderr.writeln('Database connection not established');\n      exit(1);\n    }\n\n    try {\n      if (_adapter?.supports('pgsql') == true) {\n        await _dbConnection!.execute('TRUNCATE \"migrations\"');\n      } else if (_adapter?.supports('sqlite') == true) {\n        await _dbConnection!.execute('DELETE FROM \"migrations\"');\n      } else {\n        await _dbConnection!.execute('TRUNCATE `migrations`');\n      }\n    } catch (e) {\n      stderr.writeln('Failed to truncate migrations table: $e');\n      exit(1);\n    }\n  }\n\n  @override\n  Future<void> closeConnection() async {\n    try {\n      await _dbConnection?.close();\n      _dbConnection = null;\n      _driver = null;\n      _adapter = null;\n    } catch (e) {\n      stderr.writeln('Failed to close connection: $e');\n      exit(1);\n    }\n  }\n\n  DBConfig _createDBConfig(Map<String, dynamic> config) {\n    return DBConfig(\n      driver: config['driver'] ?? '',\n      host: config['host'] ?? '',\n      port: config['port'] ?? 0,\n      database: config['database'] ?? '',\n      username: config['username'] ?? '',\n      password: config['password'] ?? '',\n      sslMode: config['sslmode'] ?? false,\n      collation: config['collation'] ?? '',\n      pool: false,\n      poolSize: 0,\n      filePath: config['file_path'] ?? '',\n      openInMemorySQLite: config['openInMemorySQLite'] ?? false,\n    );\n  }\n\n  DatabaseAdapterInterface _createAdapter(String driver) {\n    final normalizedDriver = driver.toLowerCase();\n\n    if (normalizedDriver == 'mysql') {\n      return MySqlAdapter();\n    } else if (normalizedDriver == 'pgsql' ||\n        normalizedDriver == 'postgresql' ||\n        normalizedDriver == 'postgres') {\n      return PostgreSqlAdapter();\n    } else if (normalizedDriver == 'sqlite' || normalizedDriver == 'sqlite3') {\n      return SqliteAdapter();\n    }\n\n    return MySqlAdapter();\n  }\n}\n"
  },
  {
    "path": "lib/src/database/migration/runners/migration_runner.dart",
    "content": "import 'dart:io';\n\nimport 'package:vania/query_builder.dart';\n\nimport '../../../utils/functions.dart';\nimport '../migration.dart';\nimport '../migration_connection.dart';\n\nclass MigrationRunner {\n  int? _currentBatch;\n  final Map<String, Migration> _migrations = {};\n\n  Future<void> _runUp(String migrationName, Function migrationCallback) async {\n    final isExecuted = await _isMigrationExecuted(migrationName);\n    if (isExecuted) {\n      stderr.writeln('Migration $migrationName already executed, skipping...');\n      return;\n    }\n\n    final stopwatch = Stopwatch()..start();\n\n    try {\n      await migrationCallback();\n      final batchNumber = await _getNextBatchNumber();\n      await _recordMigrationWithBatch(migrationName, batchNumber);\n\n      stopwatch.stop();\n      stderr.writeln(\n        ' Migration $migrationName executed ....................................\\x1B[32m ${stopwatch.elapsedMilliseconds}ms DONE\\x1B[0m',\n      );\n    } catch (e) {\n      stopwatch.stop();\n      if (e is QueryException) {\n        stderr.writeln(e.cause);\n      } else {\n        stderr.writeln(e);\n      }\n      stderr.writeln(\n        '❌ Migration $migrationName failed ......................................\\x1B[31m ${stopwatch.elapsedMilliseconds}ms FAILED\\x1B[0m',\n      );\n      exit(1);\n    }\n  }\n\n  MigrationRunner migrationRegister(List<Migration> migrations) {\n    _migrations.clear();\n    for (var migration in migrations) {\n      String name = migration.migrationName;\n      if (!_migrations.containsKey(name)) {\n        _migrations[name] = migration;\n      }\n    }\n    return this;\n  }\n\n  Future<void> run(List<String> args) async {\n    if (MigrationConnection().connection == null) {\n      stderr.writeln('Database connection not established');\n      exit(1);\n    }\n    if (args.contains('--fresh')) {\n      await _fresh(_migrations.values.toList());\n    } else if (args.contains('--install')) {\n      await _install();\n    } else if (args.contains('--refresh')) {\n      await _refresh(_migrations);\n    } else if (args.contains('--reset')) {\n      await _reset(_migrations);\n    } else if (args.contains('--rollback')) {\n      int? steps = args.contains('--steps')\n          ? int.tryParse(args[args.indexOf('--steps') + 1])\n          : null;\n      int? batch = args.contains('--batch')\n          ? int.tryParse(args[args.indexOf('--batch') + 1])\n          : null;\n      await _rollback(_migrations, steps: steps, batch: batch);\n    } else {\n      for (final migration in _migrations.values) {\n        await _runUp(migration.migrationName, migration.up);\n      }\n      stderr.writeln('✅ All migrations executed successfully!');\n    }\n  }\n\n  Future<void> _runDown(\n    String migrationName,\n    Function migrationCallback,\n  ) async {\n    final stopwatch = Stopwatch()..start();\n\n    try {\n      await Future.delayed(Duration(milliseconds: 30));\n      await migrationCallback();\n\n      stopwatch.stop();\n      stderr.writeln(\n        ' Migration $migrationName rolled back....................................\\x1B[32m ${stopwatch.elapsedMilliseconds}ms DONE\\x1B[0m',\n      );\n    } catch (e) {\n      stopwatch.stop();\n      stderr.writeln(\n        ' Migration $migrationName failed ......................................\\x1B[31m ${stopwatch.elapsedMilliseconds}ms FAILED\\x1B[0m',\n      );\n      exit(1);\n    }\n  }\n\n  Future<bool> _isMigrationExecuted(String migrationName) async {\n    if (MigrationConnection().connection == null) {\n      stderr.writeln('Database connection not established');\n      exit(1);\n    }\n\n    try {\n      final snakeCaseName = toSnakeCase(migrationName);\n      final result = await MigrationConnection().connection!.select(\n        \"SELECT id FROM migrations WHERE migration='$snakeCaseName'\",\n      );\n\n      return result.isNotEmpty;\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln(\n          '❌ Failed to check if migration is executed: ${e.cause}',\n        );\n      } else {\n        stderr.writeln('❌ Failed to check if migration is executed: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<void> _recordMigrationWithBatch(\n    String migrationName,\n    int batch,\n  ) async {\n    if (MigrationConnection().connection == null) {\n      stderr.writeln('Database connection not established');\n      exit(1);\n    }\n\n    try {\n      final snakeCaseName = toSnakeCase(migrationName);\n\n      String sql;\n      if (MigrationConnection().adapter?.driverName == 'mysql') {\n        sql =\n            'INSERT INTO `migrations` (`migration`, `batch`) VALUES (\\'$snakeCaseName\\', $batch)';\n      } else {\n        sql =\n            'INSERT INTO \"migrations\" (\"migration\", \"batch\") VALUES (\\'$snakeCaseName\\', $batch)';\n      }\n\n      await Future.delayed(Duration(milliseconds: 100));\n      await MigrationConnection().connection!.execute(sql);\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to record migration with batch: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to record migration with batch: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<void> _removeMigrationRecord(String migrationName) async {\n    if (MigrationConnection().connection == null) {\n      stderr.writeln('Database connection not established');\n      exit(1);\n    }\n\n    try {\n      final snakeCaseName = toSnakeCase(migrationName);\n      String sql;\n      if (MigrationConnection().adapter?.driverName == 'mysql') {\n        sql = 'DELETE FROM `migrations` WHERE `migration`=\\'$snakeCaseName\\'';\n      } else {\n        sql = 'DELETE FROM \"migrations\" WHERE \"migration\"=\\'$snakeCaseName\\'';\n      }\n      await Future.delayed(Duration(milliseconds: 50));\n      await MigrationConnection().connection!.execute(sql);\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to remove migration record: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to remove migration record: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<int> _getNextBatchNumber() async {\n    if (_currentBatch != null) {\n      return _currentBatch!;\n    }\n\n    final currentBatch = await _getCurrentBatchNumber();\n    _currentBatch = currentBatch + 1;\n    return _currentBatch!;\n  }\n\n  Future<int> _getCurrentBatchNumber() async {\n    if (MigrationConnection().connection == null) {\n      stderr.writeln('Database connection not established');\n      exit(1);\n    }\n\n    try {\n      String sql;\n      if (MigrationConnection().adapter?.driverName == 'mysql') {\n        sql = 'SELECT COALESCE(MAX(`batch`), 0) as max_batch FROM `migrations`';\n      } else {\n        sql = 'SELECT COALESCE(MAX(\"batch\"), 0) as max_batch FROM \"migrations\"';\n      }\n      await Future.delayed(Duration(milliseconds: 10));\n      final result = await MigrationConnection().connection!.select(sql);\n      if (result.isNotEmpty) {\n        return int.parse(result.first['max_batch'].toString());\n      }\n      return 0;\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to get current batch number: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to get current batch number: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<List<String>> _getMigrationsFromBatch(int batch) async {\n    if (MigrationConnection().connection == null) {\n      stderr.writeln('Database connection not established');\n      exit(1);\n    }\n\n    try {\n      String sql;\n      if (MigrationConnection().adapter?.driverName == 'mysql') {\n        sql =\n            'SELECT `migration` FROM `migrations` WHERE `batch`=$batch  ORDER BY \"id\" DESC';\n      } else {\n        sql =\n            'SELECT \"migration\" FROM \"migrations\" WHERE \"batch\"=$batch  ORDER BY \"id\" DESC';\n      }\n\n      final result = await MigrationConnection().connection!.select(sql);\n\n      return result.map((row) => row['migration'] as String).toList();\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to get migrations from batch: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to get migrations from batch: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<void> _fresh(List<Migration> migrations) async {\n    stderr.writeln('🔄 Running fresh migration...');\n\n    try {\n      await MigrationConnection().truncateMigration();\n      _currentBatch = null;\n\n      for (final migration in _migrations.values) {\n        await _runDown(migration.migrationName, migration.down);\n      }\n\n      stderr.writeln('📦 Running all migrations...');\n      for (final migration in migrations) {\n        await _runUp(migration.migrationName, migration.up);\n      }\n      stderr.writeln('✅ Fresh migration completed successfully!');\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to run fresh migration: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to run fresh migration: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<void> _install() async {\n    stderr.writeln('📋 Installing migration repository...');\n\n    try {\n      if (MigrationConnection().adapter != null) {\n        String migrationSql = MigrationConnection().adapter!\n            .getMigrationsTableSql();\n        await MigrationConnection().connection!.execute(migrationSql);\n        stderr.writeln('✅ Migration repository installed successfully!');\n      } else {\n        stderr.writeln('❌ Migration repository installation failed!');\n        exit(1);\n      }\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to install migration repository: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to install migration repository: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<void> _refresh(Map<String, Migration> migrations) async {\n    stderr.writeln('🔄 Refreshing migrations...');\n\n    try {\n      await _reset(migrations);\n      _currentBatch = null;\n      stderr.writeln('📦 Re-running all migrations...');\n      for (final migration in migrations.values) {\n        await _runUp(migration.migrationName, migration.up);\n      }\n\n      stderr.writeln('✅ Migration refresh completed successfully!');\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to refresh migrations: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to refresh migrations: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<void> _reset(Map<String, Migration> migrations) async {\n    stderr.writeln('⏪ Resetting all migrations...');\n\n    try {\n      final allMigrations = await _getAllMigrationsInReverseOrder();\n\n      if (allMigrations.isEmpty) {\n        stderr.writeln('ℹ️ No migrations to reset.');\n        return;\n      }\n      for (final migrationName in allMigrations) {\n        stderr.writeln('⏪ Rolling back: $migrationName');\n        final migration = migrations[migrationName];\n        if (migration != null) {\n          await _runDown(migrationName, migration.down);\n        }\n\n        await _removeMigrationRecord(migrationName);\n      }\n\n      stderr.writeln('✅ All migrations reset successfully!');\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to reset migrations: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to reset migrations: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<void> _rollback(\n    Map<String, Migration> migrations, {\n    int? steps,\n    int? batch,\n  }) async {\n    stderr.writeln('⏪ Rolling back migrations...');\n\n    try {\n      List<String> migrationsToRollback = [];\n\n      if (batch != null) {\n        migrationsToRollback = await _getMigrationsFromBatch(batch);\n        stderr.writeln('⏪ Rolling back batch $batch...');\n      } else if (steps != null) {\n        migrationsToRollback = await _getLastNMigrations(steps);\n        stderr.writeln('⏪ Rolling back last $steps migration(s)...');\n      } else {\n        final currentBatch = await _getCurrentBatchNumber();\n        if (currentBatch > 0) {\n          migrationsToRollback = await _getMigrationsFromBatch(currentBatch);\n          stderr.writeln('⏪ Rolling back last batch ($currentBatch)...');\n        }\n      }\n\n      if (migrationsToRollback.isEmpty) {\n        stderr.writeln('ℹ️ No migrations to rollback.');\n        return;\n      }\n\n      for (final migrationName in migrationsToRollback) {\n        stderr.writeln('⏪ Rolling back: $migrationName');\n\n        final migration = migrations[migrationName];\n        if (migration != null) {\n          await _runDown(migrationName, migration.down);\n        }\n        await _removeMigrationRecord(migrationName);\n      }\n\n      stderr.writeln('✅ Rollback completed successfully!');\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to rollback migrations: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to rollback migrations: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<List<String>> _getAllMigrationsInReverseOrder() async {\n    if (MigrationConnection().connection == null) {\n      stderr.writeln('Database connection not established');\n      exit(1);\n    }\n\n    try {\n      String sql;\n      if (MigrationConnection().adapter?.driverName == 'mysql') {\n        sql = 'SELECT `migration` FROM `migrations` ORDER BY `id` DESC';\n      } else {\n        sql = 'SELECT \"migration\" FROM \"migrations\" ORDER BY \"id\" DESC';\n      }\n\n      final result = await MigrationConnection().connection!.select(sql);\n      return result.map((row) => row['migration'] as String).toList();\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to get all migrations: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to get all migrations: $e');\n      }\n      exit(1);\n    }\n  }\n\n  Future<List<String>> _getLastNMigrations(int n) async {\n    if (MigrationConnection().connection == null) {\n      stderr.writeln('Database connection not established');\n      exit(1);\n    }\n\n    try {\n      String sql;\n      if (MigrationConnection().adapter?.driverName == 'mysql') {\n        sql =\n            'SELECT `migration` FROM `migrations` ORDER BY `id` DESC LIMIT $n';\n      } else {\n        sql =\n            'SELECT \"migration\" FROM \"migrations\" ORDER BY \"id\" DESC LIMIT $n';\n      }\n\n      final result = await MigrationConnection().connection!.select(sql);\n      return result.map((row) => row['migration'] as String).toList();\n    } catch (e) {\n      if (e is QueryException) {\n        stderr.writeln('❌ Failed to get last $n migrations: ${e.cause}');\n      } else {\n        stderr.writeln('❌ Failed to get last $n migrations: $e');\n      }\n      exit(1);\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/monitoring/database_monitor.dart",
    "content": "import 'dart:async';\nimport 'package:meta/meta.dart';\n\nenum AlertType { slowQuery, highConnectionUsage, connectionError, deadlock }\n\nclass DatabaseAlert {\n  final AlertType type;\n  final String message;\n  final Map<String, dynamic> details;\n\n  DatabaseAlert({\n    required this.type,\n    required this.message,\n    required this.details,\n  });\n}\n\nclass QueryMetrics {\n  final String sql;\n  final Duration executionTime;\n  final DateTime timestamp;\n\n  QueryMetrics({\n    required this.sql,\n    required this.executionTime,\n    required this.timestamp,\n  });\n}\n\nclass PerformanceStats {\n  final Duration averageQueryTime;\n  final int totalQueries;\n  final int slowQueries;\n  final Duration peakExecutionTime;\n\n  PerformanceStats({\n    required this.averageQueryTime,\n    required this.totalQueries,\n    required this.slowQueries,\n    required this.peakExecutionTime,\n  });\n}\n\nclass DatabaseMonitor {\n  static final DatabaseMonitor _instance = DatabaseMonitor._internal();\n  factory DatabaseMonitor() => _instance;\n  DatabaseMonitor._internal();\n\n  final Map<String, List<QueryMetrics>> _queryMetrics = {};\n  final Map<String, ConnectionMetrics> _connectionMetrics = {};\n  final StreamController<DatabaseAlert> _alertController =\n      StreamController.broadcast();\n\n  Stream<DatabaseAlert> get alerts => _alertController.stream;\n\n  // Performance thresholds\n  static const Duration slowQueryThreshold = Duration(milliseconds: 100);\n  static const int highConnectionUsageThreshold = 80;\n\n  void recordQuery(String connectionId, String sql, Duration executionTime) {\n    final metrics = QueryMetrics(\n      sql: sql,\n      executionTime: executionTime,\n      timestamp: DateTime.now(),\n    );\n\n    _queryMetrics.putIfAbsent(connectionId, () => []).add(metrics);\n\n    // Check for slow queries\n    if (executionTime > slowQueryThreshold) {\n      _alertController.add(\n        DatabaseAlert(\n          type: AlertType.slowQuery,\n          message: 'Slow query detected',\n          details: {\n            'sql': sql,\n            'execution_time': executionTime.inMilliseconds,\n            'threshold': slowQueryThreshold.inMilliseconds,\n          },\n        ),\n      );\n    }\n\n    if (_queryMetrics[connectionId]!.length > 1000) {\n      _queryMetrics[connectionId]!.removeAt(0);\n    }\n  }\n\n  void updateConnectionMetrics(String connectionId, ConnectionMetrics metrics) {\n    _connectionMetrics[connectionId] = metrics;\n\n    if (metrics.usagePercentage > highConnectionUsageThreshold) {\n      _alertController.add(\n        DatabaseAlert(\n          type: AlertType.highConnectionUsage,\n          message: 'High connection pool usage detected',\n          details: {\n            'usage_percentage': metrics.usagePercentage,\n            'active_connections': metrics.activeConnections,\n            'max_connections': metrics.maxConnections,\n          },\n        ),\n      );\n    }\n  }\n\n  List<QueryMetrics> getSlowQueries(String connectionId) {\n    return _queryMetrics[connectionId]\n            ?.where((m) => m.executionTime > slowQueryThreshold)\n            .toList() ??\n        [];\n  }\n\n  Map<String, PerformanceStats> getPerformanceStats() {\n    final stats = <String, PerformanceStats>{};\n\n    for (final entry in _queryMetrics.entries) {\n      final queries = entry.value;\n      if (queries.isEmpty) continue;\n\n      final totalTime = queries.fold<Duration>(\n        Duration.zero,\n        (sum, metric) => sum + metric.executionTime,\n      );\n\n      final peakTime = queries\n          .map((m) => m.executionTime)\n          .reduce((max, time) => time > max ? time : max);\n\n      final slowCount = queries\n          .where((m) => m.executionTime > slowQueryThreshold)\n          .length;\n\n      stats[entry.key] = PerformanceStats(\n        averageQueryTime: totalTime ~/ queries.length,\n        totalQueries: queries.length,\n        slowQueries: slowCount,\n        peakExecutionTime: peakTime,\n      );\n    }\n\n    return stats;\n  }\n\n  void clearMetrics(String connectionId) {\n    _queryMetrics.remove(connectionId);\n    _connectionMetrics.remove(connectionId);\n  }\n\n  @visibleForTesting\n  void reset() {\n    _queryMetrics.clear();\n    _connectionMetrics.clear();\n  }\n}\n\nclass ConnectionMetrics {\n  final int activeConnections;\n  final int maxConnections;\n  final int usagePercentage;\n\n  ConnectionMetrics({\n    required this.activeConnections,\n    required this.maxConnections,\n    required this.usagePercentage,\n  });\n}\n"
  },
  {
    "path": "lib/src/database/orm/belongs_to.dart",
    "content": "import 'package:vania/src/contract/orm/relation.dart';\n\nclass BelongsTo extends Relation {\n  BelongsTo({\n    required super.related,\n    required super.parent,\n    super.foreignKey,\n    super.localKey,\n  });\n\n  @override\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n  ) => matchOneOrMany(\n    models,\n    results,\n    relation,\n    foreignKey ?? '${related.runtimeType.toString()}_id',\n    localKey,\n  );\n}\n"
  },
  {
    "path": "lib/src/database/orm/belongs_to_many.dart",
    "content": "import 'package:vania/src/contract/orm/relation.dart';\n\nclass BelongsToMany extends Relation {\n  final String pivotTable;\n  final String parentPivotKey;\n  final String relatedPivotKey;\n  final String parentLocalKey;\n  final String relatedLocalKey;\n  final List<String> pivotFields;\n\n  BelongsToMany({\n    required super.parent,\n    required super.related,\n    required this.pivotTable,\n    required this.parentPivotKey,\n    required this.relatedPivotKey,\n    this.parentLocalKey = 'id',\n    this.relatedLocalKey = 'id',\n    this.pivotFields = const [],\n  });\n\n  @override\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> parents,\n    List<Map<String, dynamic>> rows,\n    String relationName,\n  ) {\n    return matchToMany(\n      parents,\n      rows,\n      relationName,\n      parentLocalKey,\n      parentPivotKey,\n      relatedPivotKey,\n      pivotFields: pivotFields,\n    );\n  }\n}\n"
  },
  {
    "path": "lib/src/database/orm/has_many.dart",
    "content": "import 'package:vania/src/contract/orm/relation.dart';\n\nclass HasMany extends Relation {\n  HasMany({\n    required super.related,\n    required super.parent,\n    super.foreignKey,\n    super.localKey,\n  });\n  @override\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n  ) => matchMany(\n    models,\n    results,\n    relation,\n    localKey,\n    foreignKey ?? '${related.runtimeType.toString()}_id',\n  );\n}\n"
  },
  {
    "path": "lib/src/database/orm/has_one.dart",
    "content": "import 'package:vania/src/contract/orm/relation.dart';\n\nclass HasOne extends Relation {\n  HasOne({\n    required super.related,\n    required super.parent,\n    super.foreignKey,\n    required super.localKey,\n  });\n\n  @override\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n  ) => matchOneOrMany(\n    models,\n    results,\n    relation,\n    localKey,\n    foreignKey ?? '${related.runtimeType.toString().toLowerCase()}_id',\n  );\n}\n"
  },
  {
    "path": "lib/src/database/orm/model.dart",
    "content": "import 'package:meta/meta.dart';\nimport 'package:vania/query_builder.dart';\nimport 'package:vania/src/contract/database/query_builder/query_builder.dart';\nimport 'package:vania/src/contract/orm/morph_relation.dart';\nimport 'package:vania/src/contract/orm/relation.dart';\nimport 'package:vania/src/database/_database_utils/_singularize.dart';\nimport 'package:vania/src/database/orm/polymorphic/morphed_by_many.dart';\nimport 'package:vania/src/database/query_builder/_query_builder_impl.dart';\nimport 'package:vania/src/exception/invalid_argument_exception.dart';\nimport 'package:vania/src/utils/_pluralize.dart';\nimport 'package:vania/src/utils/functions.dart';\n\nimport '../../utils/request_helper.dart' show getParam;\nimport 'belongs_to.dart';\nimport 'belongs_to_many.dart';\nimport 'has_many.dart';\nimport 'has_one.dart';\nimport 'polymorphic/morph_many.dart';\nimport 'polymorphic/morph_one.dart';\nimport 'polymorphic/morph_to.dart';\nimport 'polymorphic/morph_to_many.dart';\n\nabstract class Model extends QueryBuilderImpl {\n  @protected\n  Map<String, dynamic> attributes = {};\n  final Map<String, Relation> _relations = {};\n  List<_RelationQuery> _withRelation = [];\n  String get defaultConnection => _connection;\n\n  String _connection = 'mysql';\n  @protected\n  String get createdAt => 'created_at';\n  @protected\n  String get deletedAt => 'deleted_at';\n  @protected\n  String get updatedAt => 'updated_at';\n  @protected\n  List<String> get fillable => [];\n  @protected\n  List<String> get guarded => [];\n  @protected\n  List<String> get hidden => [];\n  @protected\n  bool get incrementing => true;\n  @protected\n  String get keyType => 'int';\n  @protected\n  String get primaryKey => 'id';\n\n  @protected\n  String? _table;\n\n  Model get query =>\n      connection(defaultConnection).table('$tablePrefix$tableName') as Model;\n\n  @protected\n  bool get softDeletes => false;\n\n  @protected\n  String get tablePrefix => '';\n\n  @protected\n  @override\n  String get getTable => _table ?? tableName;\n\n  @protected\n  String get tableName =>\n      toSnakeCase(Pluralize().make(runtimeType.toString())).toLowerCase();\n\n  set tableName(String table) {\n    _table = table;\n  }\n\n  @protected\n  bool get timestamps => true;\n\n  bool _relationsRegistered = false;\n\n  /// Override this method to define model relationships\n  /// This method is called automatically when include() is used\n  void registerRelations() {}\n\n  @override\n  Future<num> avg(String column) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    return super.avg(column);\n  }\n\n  void belongsTo(\n    String name,\n    Model model, {\n    String? foreignKey,\n    String localKey = 'id',\n  }) {\n    _relations.addEntries([\n      MapEntry(\n        name,\n        BelongsTo(\n          related: model,\n          parent: this,\n          foreignKey:\n              foreignKey ?? '${model.runtimeType.toString()}_id'.toLowerCase(),\n          localKey: localKey,\n        ),\n      ),\n    ]);\n  }\n\n  void belongsToMany(\n    String name,\n    Model model, {\n    String? pivotTable,\n    required parentPivotKey,\n    required relatedPivotKey,\n    parentLocalKey = 'id',\n    relatedLocalKey = 'id',\n  }) {\n    _relations.addEntries([\n      MapEntry(\n        name,\n        BelongsToMany(\n          related: model,\n          parent: this,\n          pivotTable:\n              pivotTable ??\n              '${Singularize.make(model.runtimeType.toString())}_${Singularize.make(runtimeType.toString())}'\n                  .toLowerCase(),\n          parentPivotKey: parentPivotKey,\n          relatedPivotKey: relatedPivotKey,\n          parentLocalKey: parentLocalKey,\n          relatedLocalKey: relatedLocalKey,\n        ),\n      ),\n    ]);\n  }\n\n  @override\n  Future<void> chunk(\n    int chunk,\n    void Function(List<Map<String, dynamic>> data) callback,\n  ) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    super.chunk(chunk, (List<Map<String, dynamic>> data) async {\n      for (Map<String, dynamic> map in data) {\n        map.removeWhere((key, _) => hidden.contains(key));\n      }\n      final result = await _loadRelations(data);\n      callback(result);\n    });\n  }\n\n  @override\n  Future<void> chunkById(\n    int chunk,\n    void Function(List<Map<String, dynamic>> data) callback, [\n    String column = 'id',\n  ]) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    super.chunkById(chunk, (List<Map<String, dynamic>> data) async {\n      for (Map<String, dynamic> map in data) {\n        map.removeWhere((key, _) => hidden.contains(key));\n      }\n      final result = await _loadRelations(data);\n      callback(result);\n    }, column);\n  }\n\n  @override\n  Model connection([String? connection]) {\n    _setdefaultConnection(connection ?? 'mysql');\n    return this;\n  }\n\n  @override\n  Future<int> count([String columns = '*']) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    return super.count(columns);\n  }\n\n  Future<Map<String, dynamic>> create(Map<String, dynamic> values) async {\n    if (timestamps) {\n      values[createdAt] = DateTime.now();\n      values[updatedAt] = DateTime.now();\n    }\n\n    final id = await insertGetId(values);\n    final result = await find(id);\n    return result!;\n  }\n\n  @override\n  Future<bool> delete() async {\n    try {\n      if (softDeletes) {\n        final now = DateTime.now();\n        super.update({deletedAt: now, updatedAt: now});\n      } else {\n        super.delete();\n      }\n      return true;\n    } catch (_) {\n      return false;\n    }\n  }\n\n  @override\n  Future<bool> doesntExist() async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    return super.doesntExist();\n  }\n\n  @override\n  Future<bool> exists() async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    return super.exists();\n  }\n\n  Model fill() {\n    for (var key in attributes.keys) {\n      if (fillable.contains(key) && !guarded.contains(key)) {\n        setAttribute(key, attributes[key]);\n      }\n    }\n    return this;\n  }\n\n  @override\n  Future<Map<String, dynamic>?> find(\n    dynamic id, {\n    String? byColumnName,\n    List<String> columns = const ['*'],\n  }) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    Map<String, dynamic>? result = await super.find(\n      id,\n      byColumnName: byColumnName ?? primaryKey,\n      columns: columns,\n    );\n    attributes = Map.from(result ?? {});\n\n    if (result != null) {\n      List<Map<String, dynamic>> data = [result];\n      result = (await _loadRelations(data)).first;\n    }\n\n    return result;\n  }\n\n  @override\n  Future<Map<String, dynamic>?> findOrFail(\n    id, {\n    String? byColumnName,\n    List<String> columns = const ['*'],\n  }) async {\n    var result = await find(\n      id,\n      byColumnName: byColumnName ?? primaryKey,\n      columns: columns,\n    );\n    if (result == null) {\n      throw InvalidArgumentException(\"Record with id $id not found.\");\n    }\n    return result;\n  }\n\n  @override\n  Future<Map<String, dynamic>?> first([\n    List<String> columns = const ['*'],\n  ]) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n\n    super.limit(1).toSql();\n    final result = await super.first(columns);\n    attributes = Map.from(result ?? {});\n\n    if (result == null) {\n      return null;\n    }\n    return (await _loadRelations([result])).first;\n  }\n\n  @override\n  Future<Map<String, dynamic>?> firstWhere(\n    String column, [\n    String? operator = '=',\n    value,\n    List<String> columns = const ['*'],\n  ]) async {\n    if (value == null) {\n      throw InvalidArgumentException(\n        \"Invalid input: Value cannot be null. A valid value must be provided for the firstWhere method.\",\n      );\n    }\n    where(column, operator ?? '=', value);\n    return await first(columns);\n  }\n\n  @override\n  Future<List<Map<String, dynamic>>> get([\n    List<String> columns = const ['*'],\n  ]) async {\n    try {\n      if (softDeletes) {\n        whereNull(deletedAt);\n      }\n      List<Map<String, dynamic>> result = await super.get(columns);\n\n      for (Map<String, dynamic> map in result) {\n        map.removeWhere((key, _) => hidden.contains(key));\n      }\n\n      return _loadRelations(result);\n    } catch (e) {\n      throw Exception(e);\n    }\n  }\n\n  dynamic getAttribute(String key) {\n    return attributes[key];\n  }\n\n  dynamic getKey() {\n    return attributes[primaryKey];\n  }\n\n  bool hasAttribute(String key) {\n    return attributes.containsKey(key);\n  }\n\n  void hasMany(\n    String name,\n    Model model, {\n    String? foreignKey,\n    String localKey = 'id',\n  }) {\n    _relations.addEntries([\n      MapEntry(\n        name,\n        HasMany(\n          related: model,\n          parent: this,\n          foreignKey:\n              foreignKey ?? '${runtimeType.toString()}_id'.toLowerCase(),\n          localKey: localKey,\n        ),\n      ),\n    ]);\n  }\n\n  void hasOne(\n    String name,\n    Model model, {\n    String? foreignKey,\n    String localKey = 'id',\n  }) {\n    _relations.addEntries([\n      MapEntry(\n        name,\n        HasOne(\n          related: model,\n          parent: this,\n          foreignKey:\n              foreignKey ?? '${runtimeType.toString()}_id'.toLowerCase(),\n          localKey: localKey,\n        ),\n      ),\n    ]);\n  }\n\n  @override\n  Future<bool> insert(Map<String, dynamic> values) async {\n    _validateFieldsForAssignment(values);\n\n    await super.insert(values);\n    return Future.value(true);\n  }\n\n  @override\n  Future insertGetId(Map<String, dynamic> values, [String? sequence]) async {\n    _validateFieldsForAssignment(values);\n\n    final id = await super.insertGetId(values, sequence);\n    attributes[primaryKey] = id;\n\n    return id;\n  }\n\n  bool is_(Model? model) {\n    if (model == null) return false;\n    return model.getKey() == getKey() && model.getTable == getTable;\n  }\n\n  @override\n  Future max(String column) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    return super.max(column);\n  }\n\n  @override\n  Future min(String column) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    return super.min(column);\n  }\n\n  void morphedByMany(\n    String name,\n    Model model, {\n    required String morphKey,\n    required String morphType,\n    required String type,\n    required String pivotTable,\n    required String relatedMorphKey,\n    String localKey = 'id',\n  }) {\n    _relations.addEntries([\n      MapEntry(\n        name,\n        MorphedByMany(\n          parent: this,\n          related: model,\n          morphKey: morphKey,\n          morphType: morphType,\n          pivotTable: pivotTable,\n          relatedMorphKey: relatedMorphKey,\n          type: type,\n          localKey: localKey,\n        ),\n      ),\n    ]);\n  }\n\n  void morphMany(\n    String name,\n    Model model, {\n    required String morphKey,\n    required String morphType,\n    required String type,\n    String localKey = 'id',\n  }) {\n    _relations.addEntries([\n      MapEntry(\n        name,\n        MorphMany(\n          parent: this,\n          related: model,\n          morphKey: morphKey,\n          morphType: morphType,\n          type: type,\n          localKey: localKey,\n        ),\n      ),\n    ]);\n  }\n\n  void morphOne(\n    String name,\n    Model model, {\n    required String morphKey,\n    required String morphType,\n    required String type,\n    String localKey = 'id',\n  }) {\n    _relations.addEntries([\n      MapEntry(\n        name,\n        MorphOne(\n          parent: this,\n          related: model,\n          morphKey: morphKey,\n          morphType: morphType,\n          type: type,\n          localKey: localKey,\n        ),\n      ),\n    ]);\n  }\n\n  void morphTo(\n    String name,\n    Model model, {\n    required String morphKey,\n    required String morphType,\n    required String type,\n    String localKey = 'id',\n  }) {\n    _relations.addEntries([\n      MapEntry(\n        name,\n        MorphTo(\n          parent: this,\n          related: model,\n          morphKey: morphKey,\n          morphType: morphType,\n          type: type,\n          localKey: localKey,\n        ),\n      ),\n    ]);\n  }\n\n  void morphToMany(\n    String name,\n    Model model, {\n    required String morphKey,\n    required String morphType,\n    required String type,\n    required String pivotTable,\n    required String relatedMorphKey,\n    String localKey = 'id',\n  }) {\n    _relations.addEntries([\n      MapEntry(\n        name,\n        MorphToMany(\n          parent: this,\n          related: model,\n          morphKey: morphKey,\n          morphType: morphType,\n          pivotTable: pivotTable,\n          relatedMorphKey: relatedMorphKey,\n          type: type,\n          localKey: localKey,\n        ),\n      ),\n    ]);\n  }\n\n  Model newInstance() {\n    return (runtimeType as dynamic)();\n  }\n\n  @override\n  Future<Map<String, dynamic>> paginate({\n    int perPage = 15,\n    List<String> columns = const ['*'],\n    String? pageName,\n    int? page,\n  }) async {\n    int currentPage = page ?? getParam<int>('page', 1)!;\n    int total = await count();\n    final lastPage = (total / perPage).ceil();\n    final offset = (currentPage - 1) * perPage;\n    super.take(perPage).skip(offset);\n    final pageData = await get();\n    final isFirst = currentPage == 1;\n    final isLast = currentPage == lastPage;\n    final hasMore = currentPage < lastPage;\n\n    return PaginatedResult(\n      data: pageData,\n      currentPage: currentPage,\n      perPage: perPage,\n      total: total,\n      lastPage: lastPage,\n      isFirst: isFirst,\n      isLast: isLast,\n      hasMore: hasMore,\n    ).toMap();\n  }\n\n  @override\n  Future pluck(String column, [String? key]) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    return super.pluck(column);\n  }\n\n  void setAttribute(String key, dynamic value) {\n    attributes[key] = value;\n  }\n\n  @override\n  Future<Map<String, dynamic>> simplePaginate([\n    int perPage = 15,\n    List<String> columns = const ['*'],\n    String? pageName,\n    int? page,\n  ]) async {\n    int currentPage = page ?? getParam<int>('page', 1)!;\n    int total = await count();\n    final lastPage = (total / perPage).ceil();\n    final offset = (currentPage - 1) * perPage;\n    super.take(perPage).skip(offset);\n    final pageData = await get();\n\n    return {\n      'data': pageData,\n      'current_page': currentPage,\n      'per_page': perPage,\n      'total': total,\n      'last_page': lastPage,\n    };\n  }\n\n  @override\n  Future<num> sum(String column) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    return super.sum(column);\n  }\n\n  Map<String, dynamic> toJson() {\n    final Map<String, dynamic> result = Map.from(attributes);\n    for (var key in hidden) {\n      result.remove(key);\n    }\n    _relations.forEach((key, relation) {\n      if (relation is! Future<dynamic>) {\n        result[key] = relation;\n      }\n    });\n    return result;\n  }\n\n  @override\n  Future<bool> update(Map<String, dynamic> values) async {\n    if (values.isEmpty) {\n      throw InvalidArgumentException('Update values cannot be empty');\n    }\n\n    _validateFieldsForAssignment(values);\n    if (timestamps) {\n      values[updatedAt] = DateTime.now();\n    }\n    return super.update(values);\n  }\n\n  @override\n  Future<bool> updateMany(\n    List<Map<String, dynamic>> updates,\n    String column,\n  ) async {\n    if (updates.isEmpty) return false;\n\n    if (timestamps) {\n      for (var row in updates) {\n        _validateFieldsForAssignment(row);\n        row.addEntries([MapEntry(updatedAt, DateTime.now())]);\n      }\n    }\n\n    return super.updateMany(updates, column);\n  }\n\n  @override\n  Future<bool> updateOrInsert(\n    Map<String, dynamic> search,\n    Map<String, dynamic> update,\n  ) async {\n    Map<String, dynamic> data = {}\n      ..addAll(search)\n      ..addAll(update);\n    return upsert(data, search.keys.toList(), update);\n  }\n\n  @override\n  Future<bool> upsert(\n    Map<String, dynamic> values,\n    List<String> uniqueBy, [\n    Map<String, dynamic>? update,\n  ]) async {\n    _validateFieldsForAssignment(values);\n\n    if (update != null) {\n      _validateFieldsForAssignment(update);\n    }\n    return super.upsert(values, uniqueBy);\n  }\n\n  @override\n  Future value(String column) async {\n    if (softDeletes) {\n      whereNull(deletedAt);\n    }\n    return super.value(column);\n  }\n\n  Model include(String relation, [Function(Model qb)? callback]) {\n    if (!_relationsRegistered) {\n      registerRelations();\n      _relationsRegistered = true;\n    }\n\n    final rela = _relations[relation];\n    if (rela != null && rela is MorphTo) {\n      where(rela.morphType, '=', rela.type!);\n    }\n\n    _withRelation.add(_RelationQuery(relation, callback));\n    return this;\n  }\n\n  Future<void> _eagerLoadRelation(\n    List<Map<String, dynamic>> models,\n    _RelationQuery rq,\n    Function(dynamic data) callBack,\n  ) async {\n    String relation = rq.relation;\n    List<String> wr = relation.split('.');\n\n    String primaryRelation = wr.first;\n\n    List<String> getColumns = ['*'];\n    final relationParts = primaryRelation.split(':');\n\n    if (relationParts.length > 1) {\n      primaryRelation = relationParts.first.trim();\n\n      final columnsString = relationParts.last.trim();\n\n      if (columnsString.isNotEmpty) {\n        getColumns = columnsString\n            .split(',')\n            .map((col) => col.trim())\n            .where((col) => col.isNotEmpty)\n            .toList();\n      }\n    }\n\n    if (!_relations.containsKey(primaryRelation)) {\n      throw InvalidArgumentException(\n        'Relation $relation not found in $runtimeType',\n      );\n    }\n\n    Relation rela = _relations[primaryRelation] as Relation;\n    Model qb = rela.related;\n\n    if (rq.callback != null) {\n      qb = rq.callback!(qb) as Model;\n    }\n\n    if (rela is MorphRelation) {\n      if (rela is MorphTo) {\n        Set ids = models.map((m) => m[rela.morphKey]).toSet();\n\n        // Early return if no IDs to query\n        if (ids.isEmpty) {\n          callBack(rela.match(models, [], primaryRelation));\n          return;\n        }\n\n        qb = qb.whereIn(rela.localKey, ids.toList()) as Model;\n      } else {\n        Set ids = models.map((m) => m[rela.localKey]).toSet();\n\n        // Early return if no IDs to query\n        if (ids.isEmpty) {\n          callBack(rela.match(models, [], primaryRelation));\n          return;\n        }\n\n        if (rela is MorphToMany || rela is MorphedByMany) {\n          qb =\n              qb\n                      .whereIn(rela.morphKey, ids.toList())\n                      .whereEqualTo(rela.morphType, rela.type)\n                      .join(\n                        rela.related.tableName,\n                        '${rela.pivotTable}.${rela.relatedMorphKey}',\n                        '=',\n                        '${rela.related.tableName}.${rela.localKey}',\n                      )\n                  as Model;\n          qb.tableName = rela.pivotTable!;\n        } else {\n          qb =\n              qb\n                      .whereIn(rela.morphKey, ids.toList())\n                      .whereEqualTo(rela.morphType, rela.type)\n                  as Model;\n        }\n      }\n    } else {\n      late final String getLocalKey;\n      if (rela is BelongsTo) {\n        getLocalKey =\n            rela.foreignKey ??\n            '${rela.related.runtimeType.toString()}_id'.toLowerCase();\n      } else {\n        getLocalKey = rela.localKey;\n      }\n\n      Set ids = models.map((m) => m[getLocalKey]).toSet();\n\n      // Early return if no IDs to query\n      if (ids.isEmpty) {\n        callBack(rela.match(models, [], primaryRelation));\n        return;\n      }\n\n      if (rela is BelongsToMany) {\n        qb =\n            qb\n                    .whereIn(\n                      '${rela.pivotTable}.${rela.parentPivotKey}',\n                      ids.toList(),\n                    )\n                    .join(\n                      rela.pivotTable,\n                      '${rela.pivotTable}.${rela.relatedPivotKey}',\n                      '=',\n                      '${rela.related.tableName}.${rela.relatedLocalKey}',\n                    )\n                as Model;\n      } else if (rela is BelongsTo) {\n        qb = qb.whereIn(rela.localKey, ids.toList()) as Model;\n      } else {\n        qb = qb.whereIn(rela.foreignKey!, ids.toList()) as Model;\n      }\n    }\n\n    late final List<Map<String, dynamic>> results;\n\n    if (wr.length > 1) {\n      wr.removeAt(0);\n      results = await qb.include(wr.join('.')).get(getColumns);\n    } else {\n      results = await qb.get(getColumns);\n    }\n\n    callBack(rela.match(models, results, primaryRelation));\n  }\n\n  Future<List<Map<String, dynamic>>> _loadRelations(\n    List<Map<String, dynamic>> result,\n  ) async {\n    if (_withRelation.isNotEmpty) {\n      // Create an immutable copy of relations to load and clear the original list\n      // This prevents concurrent modification when iterating\n      final relationsToLoad = List<_RelationQuery>.unmodifiable(_withRelation);\n      _withRelation = [];\n\n      for (_RelationQuery relation in relationsToLoad) {\n        await _eagerLoadRelation(result, relation, (callBackResult) {\n          result = callBackResult;\n        });\n      }\n    }\n\n    return result;\n  }\n\n  void _setdefaultConnection(String value) => _connection = value;\n\n  /// Validates field names for mass assignment according to fillable and guarded rules\n  /// Throws [InvalidArgumentException] if validation fails\n  void _validateFieldsForAssignment(Map<String, dynamic> values) {\n    List<String> keys = values.keys.toList();\n\n    if (fillable.isNotEmpty) {\n      fillable.add(createdAt);\n      fillable.add(updatedAt);\n      fillable.add(deletedAt);\n    }\n\n    for (String key in keys) {\n      if (guarded.contains(key)) {\n        throw InvalidArgumentException(\n          'Column $key is not allowed to be filled by guarded',\n        );\n      }\n\n      if (guarded.isEmpty && fillable.isEmpty) {\n        throw InvalidArgumentException(\n          'Column $key is not allowed to be filled',\n        );\n      }\n\n      if (!guarded.contains(key) &&\n          fillable.isNotEmpty &&\n          !fillable.contains(key)) {\n        throw InvalidArgumentException(\n          'Column $key is not allowed to be filled',\n        );\n      }\n    }\n  }\n}\n\nclass _RelationQuery {\n  final String relation;\n  final Function(Model qb)? callback;\n  _RelationQuery(this.relation, [this.callback]);\n}\n"
  },
  {
    "path": "lib/src/database/orm/polymorphic/morph_many.dart",
    "content": "import 'package:vania/src/contract/orm/morph_relation.dart';\n\nclass MorphMany extends MorphRelation {\n  MorphMany({\n    required super.parent,\n    required super.related,\n    required super.morphKey,\n    required super.morphType,\n    super.type,\n    super.localKey = 'id',\n  });\n\n  @override\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n  ) => matchMorphMany(\n    models,\n    results,\n    relation,\n    localKey,\n    morphKey,\n    morphType,\n    type ?? related.runtimeType.toString().toLowerCase(),\n  );\n}\n"
  },
  {
    "path": "lib/src/database/orm/polymorphic/morph_one.dart",
    "content": "import 'package:vania/src/contract/orm/morph_relation.dart';\n\nclass MorphOne extends MorphRelation {\n  MorphOne({\n    required super.parent,\n    required super.related,\n    required super.morphKey,\n    required super.morphType,\n    super.type,\n    super.localKey = 'id',\n  });\n\n  @override\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n  ) => matchMorphOneOrMany(\n    models,\n    results,\n    relation,\n    localKey,\n    morphKey,\n    morphType,\n    type ?? related.runtimeType.toString().toLowerCase(),\n  );\n}\n"
  },
  {
    "path": "lib/src/database/orm/polymorphic/morph_to.dart",
    "content": "import 'package:vania/src/contract/orm/morph_relation.dart';\n\nclass MorphTo extends MorphRelation {\n  MorphTo({\n    required super.parent,\n    required super.related,\n    required super.morphKey,\n    required super.morphType,\n    super.type,\n    super.localKey = 'id',\n  });\n\n  @override\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n  ) {\n    return matchMorphToOne(models, results, relation, morphKey, localKey);\n  }\n}\n"
  },
  {
    "path": "lib/src/database/orm/polymorphic/morph_to_many.dart",
    "content": "import 'package:vania/src/contract/orm/morph_relation.dart';\n\nclass MorphToMany extends MorphRelation {\n  MorphToMany({\n    required super.parent,\n    required super.related,\n    required super.morphKey,\n    required super.morphType,\n    required super.pivotTable,\n    required super.relatedMorphKey,\n    super.type,\n    super.localKey = 'id',\n  });\n\n  @override\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n  ) {\n    return matchMorphMany(\n      models,\n      results,\n      relation,\n      localKey,\n      morphKey,\n      morphType,\n      type ?? related.runtimeType.toString().toLowerCase(),\n    );\n  }\n}\n"
  },
  {
    "path": "lib/src/database/orm/polymorphic/morphed_by_many.dart",
    "content": "import 'package:vania/src/contract/orm/morph_relation.dart';\n\nclass MorphedByMany extends MorphRelation {\n  MorphedByMany({\n    required super.parent,\n    required super.related,\n    required super.morphKey,\n    required super.morphType,\n    required super.pivotTable,\n    required super.relatedMorphKey,\n    super.type,\n    super.localKey = 'id',\n  });\n\n  @override\n  List<Map<String, dynamic>> match(\n    List<Map<String, dynamic>> models,\n    List<Map<String, dynamic>> results,\n    String relation,\n  ) {\n    return matchMorphMany(\n      models,\n      results,\n      relation,\n      localKey,\n      morphKey,\n      morphType,\n      type ?? related.runtimeType.toString().toLowerCase(),\n    );\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_bulk_operations_builder_impl.dart",
    "content": "import 'dart:async';\n\nimport 'package:meta/meta.dart';\n\nimport '../../contract/database/query_builder/query_builder.dart'\n    show QueryBuilder, ConflictAction;\nimport '../../exception/invalid_argument_exception.dart';\nimport '_query_builder_impl.dart';\n\nabstract mixin class BulkOperationsBuilderImpl implements QueryBuilder {\n  @protected\n  int _paramCounter = 0;\n\n  String _nextParamName() {\n    _paramCounter++;\n    return 'p$_paramCounter';\n  }\n\n  @override\n  Future<bool> merge(\n    List<Map<String, dynamic>> sourceData, {\n    required List<String> matchOn,\n    ConflictAction whenMatched = ConflictAction.update,\n    ConflictAction whenNotMatched = ConflictAction.ignore,\n    ConflictAction? whenNotMatchedBySource,\n    List<String>? updateColumns,\n    List<String>? insertColumns,\n    Map<String, dynamic>? additionalValues,\n  }) async {\n    if (sourceData.isEmpty) {\n      throw InvalidArgumentException(\n        'Source data cannot be empty for merge operation',\n      );\n    }\n\n    if (matchOn.isEmpty) {\n      throw InvalidArgumentException(\n        'Match columns cannot be empty for merge operation',\n      );\n    }\n\n    try {\n      final conn = await getConnection();\n      final paramBindings = <String, dynamic>{};\n\n      final sourceColumns = sourceData.first.keys.toList();\n      final valueGroups = <String>[];\n\n      for (var row in sourceData) {\n        final placeholders = sourceColumns\n            .map((column) {\n              final paramName = _nextParamName();\n              paramBindings[paramName] = row[column];\n              return \":$paramName\";\n            })\n            .join(\", \");\n        valueGroups.add(\"($placeholders)\");\n      }\n\n      final sourceClause =\n          \"(VALUES ${valueGroups.join(', ')}) AS source(${sourceColumns.join(', ')})\";\n\n      final matchConditions = matchOn\n          .map((col) => \"$getTable.$col = source.$col\")\n          .join(' AND ');\n\n      String mergeSQL =\n          \"MERGE INTO $getTable USING $sourceClause ON $matchConditions\";\n\n      if (whenMatched == ConflictAction.update) {\n        final columnsToUpdate =\n            updateColumns ??\n            sourceColumns.where((col) => !matchOn.contains(col)).toList();\n        final updateSets = columnsToUpdate\n            .map((col) => \"$col = source.$col\")\n            .join(', ');\n        mergeSQL += \" WHEN MATCHED THEN UPDATE SET $updateSets\";\n      } else if (whenMatched == ConflictAction.delete) {\n        mergeSQL += \" WHEN MATCHED THEN DELETE\";\n      }\n\n      if (whenNotMatched != ConflictAction.ignore) {\n        final columnsToInsert = insertColumns ?? sourceColumns;\n        final insertValues = columnsToInsert\n            .map((col) => \"source.$col\")\n            .join(', ');\n        mergeSQL +=\n            \" WHEN NOT MATCHED THEN INSERT (${columnsToInsert.join(', ')}) VALUES ($insertValues)\";\n      }\n\n      if (whenNotMatchedBySource == ConflictAction.delete) {\n        mergeSQL += \" WHEN NOT MATCHED BY SOURCE THEN DELETE\";\n      }\n\n      await conn.execute(mergeSQL, paramBindings);\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> bulkInsert(\n    List<Map<String, dynamic>> data, {\n    ConflictAction conflictAction = ConflictAction.ignore,\n    List<String>? conflictColumns,\n    List<String>? updateColumns,\n    int batchSize = 1000,\n    bool returnIds = false,\n  }) async {\n    if (data.isEmpty) {\n      throw InvalidArgumentException(\n        'Data cannot be empty for bulk insert operation',\n      );\n    }\n\n    try {\n      final conn = await getConnection();\n      for (int i = 0; i < data.length; i += batchSize) {\n        final batch = data.skip(i).take(batchSize).toList();\n        final paramBindings = <String, dynamic>{};\n\n        final columns = batch.first.keys.toList();\n        final valueGroups = <String>[];\n\n        for (var row in batch) {\n          final placeholders = columns\n              .map((column) {\n                final paramName = _nextParamName();\n                paramBindings[paramName] = row[column];\n                return \":$paramName\";\n              })\n              .join(\", \");\n          valueGroups.add(\"($placeholders)\");\n        }\n\n        String sql =\n            \"INSERT INTO $getTable (${columns.join(', ')}) VALUES ${valueGroups.join(', ')}\";\n\n        if (conflictAction == ConflictAction.ignore) {\n          sql =\n              \"INSERT IGNORE INTO $getTable (${columns.join(', ')}) VALUES ${valueGroups.join(', ')}\";\n        } else if (conflictAction == ConflictAction.update &&\n            conflictColumns != null) {\n          final updateCols =\n              updateColumns ??\n              columns.where((col) => !conflictColumns.contains(col)).toList();\n          final updateSets = updateCols\n              .map((col) => \"$col = VALUES($col)\")\n              .join(', ');\n          sql += \" ON DUPLICATE KEY UPDATE $updateSets\";\n        } else if (conflictAction == ConflictAction.replace) {\n          sql =\n              \"REPLACE INTO $getTable (${columns.join(', ')}) VALUES ${valueGroups.join(', ')}\";\n        }\n\n        await conn.execute(sql, paramBindings);\n      }\n\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> bulkUpdate(\n    List<Map<String, dynamic>> updates, {\n    required String matchColumn,\n    List<String>? updateColumns,\n    int batchSize = 500,\n    Map<String, dynamic>? additionalValues,\n  }) async {\n    if (updates.isEmpty) {\n      throw InvalidArgumentException(\n        'Updates cannot be empty for bulk update operation',\n      );\n    }\n\n    try {\n      final conn = await getConnection();\n\n      for (int i = 0; i < updates.length; i += batchSize) {\n        final batch = updates.skip(i).take(batchSize).toList();\n\n        final columns =\n            updateColumns ??\n            batch.first.keys.where((key) => key != matchColumn).toList();\n        final paramBindings = <String, dynamic>{};\n\n        final matchValues = <String>[];\n        final caseClauses = <String, List<String>>{};\n\n        for (var column in columns) {\n          caseClauses[column] = [];\n        }\n\n        for (var row in batch) {\n          final matchParamName = _nextParamName();\n          paramBindings[matchParamName] = row[matchColumn];\n          matchValues.add(\":$matchParamName\");\n\n          for (var column in columns) {\n            final valueParamName = _nextParamName();\n            paramBindings[valueParamName] = row[column];\n            caseClauses[column]!.add(\n              \"WHEN :$matchParamName THEN :$valueParamName\",\n            );\n          }\n        }\n\n        final setClauses = caseClauses.entries.map((entry) {\n          final caseStatement =\n              \"CASE $matchColumn ${entry.value.join(' ')} END\";\n          return \"${entry.key} = $caseStatement\";\n        }).toList();\n\n        if (additionalValues != null) {\n          for (var entry in additionalValues.entries) {\n            final paramName = _nextParamName();\n            paramBindings[paramName] = entry.value;\n            setClauses.add(\"${entry.key} = :$paramName\");\n          }\n        }\n\n        final sql =\n            \"UPDATE $getTable SET ${setClauses.join(', ')} WHERE $matchColumn IN (${matchValues.join(', ')})\";\n\n        await conn.execute(sql, paramBindings);\n      }\n\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> bulkDelete({\n    String? column,\n    List<dynamic>? values,\n    int batchSize = 1000,\n  }) async {\n    if (column == null || values == null || values.isEmpty) {\n      throw InvalidArgumentException(\n        'Column and values must be provided for bulk delete operation',\n      );\n    }\n\n    try {\n      final conn = await getConnection();\n\n      for (int i = 0; i < values.length; i += batchSize) {\n        final batch = values.skip(i).take(batchSize).toList();\n        final paramBindings = <String, dynamic>{};\n\n        final placeholders = batch\n            .map((value) {\n              final paramName = _nextParamName();\n              paramBindings[paramName] = value;\n              return \":$paramName\";\n            })\n            .join(', ');\n\n        final sql = \"DELETE FROM $getTable WHERE $column IN ($placeholders)\";\n\n        await conn.execute(sql, paramBindings);\n      }\n\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> bulkDeleteWhere(\n    List<Map<String, dynamic>> conditions, {\n    int batchSize = 500,\n  }) async {\n    if (conditions.isEmpty) {\n      throw InvalidArgumentException(\n        'Conditions cannot be empty for bulk delete operation',\n      );\n    }\n\n    try {\n      final conn = await getConnection();\n      for (int i = 0; i < conditions.length; i += batchSize) {\n        final batch = conditions.skip(i).take(batchSize).toList();\n        final paramBindings = <String, dynamic>{};\n\n        final orConditions = <String>[];\n\n        for (var conditionMap in batch) {\n          final andConditions = <String>[];\n\n          for (var entry in conditionMap.entries) {\n            final paramName = _nextParamName();\n            paramBindings[paramName] = entry.value;\n            andConditions.add(\"${entry.key} = :$paramName\");\n          }\n\n          orConditions.add(\"(${andConditions.join(' AND ')})\");\n        }\n\n        final sql = \"DELETE FROM $getTable WHERE ${orConditions.join(' OR ')}\";\n\n        await conn.execute(sql, paramBindings);\n      }\n\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<void> batchProcess({\n    required int batchSize,\n    required Future<void> Function(\n      List<Map<String, dynamic>> batch,\n      int batchNumber,\n    )\n    processor,\n    List<String> columns = const ['*'],\n  }) async {\n    int batchNumber = 1;\n    int offset = 0;\n\n    while (true) {\n      final batchQuery = QueryBuilderImpl()\n        ..table(getTable)\n        ..select(columns)\n        ..limit(batchSize)\n        ..offset(offset);\n\n      final batch = await batchQuery.get();\n\n      if (batch.isEmpty) break;\n\n      await processor(batch, batchNumber);\n\n      if (batch.length < batchSize) break;\n\n      offset += batchSize;\n      batchNumber++;\n    }\n  }\n\n  @override\n  Future<void> chunkedProcess({\n    required int chunkSize,\n    required Future<List<Map<String, dynamic>>> Function(\n      List<Map<String, dynamic>> chunk,\n    )\n    processor,\n    String? destination,\n    List<String> columns = const ['*'],\n  }) async {\n    int offset = 0;\n\n    while (true) {\n      final chunkQuery = QueryBuilderImpl()\n        ..table(getTable)\n        ..select(columns)\n        ..limit(chunkSize)\n        ..offset(offset);\n\n      final chunk = await chunkQuery.get();\n\n      if (chunk.isEmpty) break;\n\n      final processedChunk = await processor(chunk);\n\n      if (destination != null && processedChunk.isNotEmpty) {\n        final insertQuery = QueryBuilderImpl()..table(destination);\n        await insertQuery.insertMany(processedChunk);\n      }\n\n      if (chunk.length < chunkSize) break;\n\n      offset += chunkSize;\n    }\n  }\n\n  @override\n  Future<bool> parallelBulkInsert(\n    List<Map<String, dynamic>> data, {\n    int parallelism = 2,\n    int batchSize = 1000,\n    ConflictAction conflictAction = ConflictAction.ignore,\n    List<String>? conflictColumns,\n  }) async {\n    if (data.isEmpty) {\n      throw InvalidArgumentException(\n        'Data cannot be empty for parallel bulk insert operation',\n      );\n    }\n\n    try {\n      final chunkSize = (data.length / parallelism).ceil();\n      final futures = <Future<bool>>[];\n\n      for (int i = 0; i < parallelism; i++) {\n        final start = i * chunkSize;\n        final end = (start + chunkSize).clamp(0, data.length);\n\n        if (start >= data.length) break;\n\n        final chunk = data.sublist(start, end);\n\n        final future = bulkInsert(\n          chunk,\n          conflictAction: conflictAction,\n          conflictColumns: conflictColumns,\n          batchSize: batchSize,\n        );\n\n        futures.add(future);\n      }\n\n      final results = await Future.wait(futures);\n      return results.every((result) => result);\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> transactionalBulkOperation(\n    Future<bool> Function() action,\n  ) async {\n    try {\n      return await transaction(() async => await action());\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @protected\n  void clearBulkOperations() {\n    _paramCounter = 0;\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_cte_cache.dart",
    "content": "class CteCache {\n  String? _cachedWithClause;\n  Map<String, dynamic>? _cachedBindings;\n  int _cteHashCode = 0;\n  bool _isDirty = true;\n\n  void markDirty() {\n    _isDirty = true;\n    _cachedWithClause = null;\n    _cachedBindings = null;\n  }\n\n  bool needsUpdate(int currentHashCode) {\n    return _isDirty || _cteHashCode != currentHashCode;\n  }\n\n  void updateCache(\n    String withClause,\n    Map<String, dynamic> bindings,\n    int hashCode,\n  ) {\n    _cachedWithClause = withClause;\n    _cachedBindings = Map.from(bindings);\n    _cteHashCode = hashCode;\n    _isDirty = false;\n  }\n\n  String? get cachedWithClause => _cachedWithClause;\n\n  Map<String, dynamic>? get cachedBindings =>\n      _cachedBindings != null ? Map.from(_cachedBindings!) : null;\n\n  void clear() {\n    _cachedWithClause = null;\n    _cachedBindings = null;\n    _cteHashCode = 0;\n    _isDirty = true;\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_cte_configuration.dart",
    "content": "import '_cte_feature.dart';\nimport '_database_type.dart';\n\nclass CteConfiguration {\n  final String quoteChar;\n\n  final DatabaseType databaseType;\n\n  final bool enableCaching;\n\n  final int maxCteDepth;\n\n  final bool allowMaterialized;\n\n  final bool allowNotMaterialized;\n\n  final bool allowRecursive;\n\n  const CteConfiguration({\n    this.quoteChar = '\"',\n    this.databaseType = DatabaseType.postgresql,\n    this.enableCaching = true,\n    this.maxCteDepth = 100,\n    this.allowMaterialized = true,\n    this.allowNotMaterialized = true,\n    this.allowRecursive = true,\n  });\n\n  factory CteConfiguration.forDatabase(DatabaseType type) {\n    switch (type) {\n      case DatabaseType.postgresql:\n        return const CteConfiguration(\n          quoteChar: '\"',\n          databaseType: DatabaseType.postgresql,\n          allowMaterialized: true,\n          allowNotMaterialized: true,\n          allowRecursive: true,\n        );\n\n      case DatabaseType.mysql:\n        return const CteConfiguration(\n          quoteChar: '`',\n          databaseType: DatabaseType.mysql,\n          allowMaterialized: false,\n          allowNotMaterialized: false,\n          allowRecursive: true,\n        );\n\n      case DatabaseType.sqlite:\n        return const CteConfiguration(\n          quoteChar: '\"',\n          databaseType: DatabaseType.sqlite,\n          allowMaterialized: false,\n          allowNotMaterialized: false,\n          allowRecursive: true,\n        );\n    }\n  }\n\n  bool supportsFeature(CteFeature feature) {\n    switch (feature) {\n      case CteFeature.materialized:\n        return allowMaterialized;\n      case CteFeature.notMaterialized:\n        return allowNotMaterialized;\n      case CteFeature.recursive:\n        return allowRecursive;\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_cte_definition.dart",
    "content": "import 'package:vania/src/contract/database/query_builder/query_builder.dart';\n\nimport '../_cte_builder_impl.dart';\nimport '_cte_configuration.dart';\nimport '_cte_feature.dart';\nimport '_invalid_cte_configuration_exception.dart';\nimport '_sql_identifier_escaper.dart';\nimport '_standard_escaping_strategy.dart';\nimport '_unsupported_cte_feature_exception.dart';\n\nclass CteDefinition {\n  final String name;\n\n  final QueryBuilder query;\n\n  final bool isRecursive;\n\n  final bool isMaterialized;\n\n  final bool isNotMaterialized;\n\n  final QueryBuilder? recursiveQuery;\n\n  final List<String>? columns;\n\n  const CteDefinition({\n    required this.name,\n    required this.query,\n    this.isRecursive = false,\n    this.isMaterialized = false,\n    this.isNotMaterialized = false,\n    this.recursiveQuery,\n    this.columns,\n  });\n\n  void validate(CteConfiguration config) {\n    SqlIdentifierEscaper.validateIdentifier(name, context: 'CTE name');\n\n    if (isRecursive && !config.supportsFeature(CteFeature.recursive)) {\n      throw UnsupportedCteFeatureException(\n        CteFeature.recursive,\n        config.databaseType,\n        name,\n      );\n    }\n\n    if (isMaterialized && !config.supportsFeature(CteFeature.materialized)) {\n      throw UnsupportedCteFeatureException(\n        CteFeature.materialized,\n        config.databaseType,\n        name,\n      );\n    }\n\n    if (isNotMaterialized &&\n        !config.supportsFeature(CteFeature.notMaterialized)) {\n      throw UnsupportedCteFeatureException(\n        CteFeature.notMaterialized,\n        config.databaseType,\n        name,\n      );\n    }\n\n    if (isRecursive && recursiveQuery == null) {\n      throw InvalidCteConfigurationException(\n        'Recursive CTE must have recursive query',\n        name,\n      );\n    }\n\n    if (isMaterialized && isNotMaterialized) {\n      throw InvalidCteConfigurationException(\n        'CTE cannot be both materialized and not materialized',\n        name,\n      );\n    }\n\n    if (isRecursive && (isMaterialized || isNotMaterialized)) {\n      throw InvalidCteConfigurationException(\n        'Recursive CTE cannot have materialization options',\n        name,\n      );\n    }\n\n    if (columns != null && columns!.isNotEmpty) {\n      for (String column in columns!) {\n        SqlIdentifierEscaper.validateIdentifier(column, context: 'Column name');\n      }\n\n      Set<String> uniqueColumns = columns!.map((c) => c.toLowerCase()).toSet();\n      if (uniqueColumns.length != columns!.length) {\n        throw InvalidCteConfigurationException(\n          'CTE columns must be unique',\n          name,\n        );\n      }\n    }\n  }\n\n  String toSql(\n    IdentifierEscapingStrategy escapingStrategy,\n    CteConfiguration config,\n  ) {\n    validate(config);\n\n    String escapedName = escapingStrategy.escape(name);\n    String sql = escapedName;\n\n    if (columns != null && columns!.isNotEmpty) {\n      List<String> escapedColumns = columns!\n          .map((col) => escapingStrategy.escape(col))\n          .toList();\n      sql += ' (${escapedColumns.join(', ')})';\n    }\n\n    if (isRecursive && recursiveQuery != null) {\n      sql += ' AS (${query.toSql()} UNION ALL ${recursiveQuery!.toSql()})';\n    } else if (isMaterialized &&\n        config.supportsFeature(CteFeature.materialized)) {\n      sql += ' AS MATERIALIZED (${query.toSql()})';\n    } else if (isNotMaterialized &&\n        config.supportsFeature(CteFeature.notMaterialized)) {\n      sql += ' AS NOT MATERIALIZED (${query.toSql()})';\n    } else {\n      sql += ' AS (${query.toSql()})';\n    }\n\n    return sql;\n  }\n\n  Map<String, dynamic> getBindings() {\n    Map<String, dynamic> allBindings = {};\n\n    if (query is QueryBuilderAccessor) {\n      Map<String, dynamic> queryBindings = (query as QueryBuilderAccessor)\n          .accessBindings();\n      allBindings.addAll(queryBindings);\n    }\n\n    if (recursiveQuery != null && recursiveQuery is QueryBuilderAccessor) {\n      Map<String, dynamic> recursiveBindings =\n          (recursiveQuery as QueryBuilderAccessor).accessBindings();\n\n      for (var entry in recursiveBindings.entries) {\n        String key = entry.key;\n        if (allBindings.containsKey(key)) {\n          int counter = 1;\n          String newKey;\n          do {\n            newKey = '${key}_rec_$counter';\n            counter++;\n          } while (allBindings.containsKey(newKey));\n          allBindings[newKey] = entry.value;\n        } else {\n          allBindings[key] = entry.value;\n        }\n      }\n    }\n\n    return allBindings;\n  }\n\n  @override\n  int get hashCode {\n    return Object.hash(\n      name,\n      query.hashCode,\n      isRecursive,\n      isMaterialized,\n      isNotMaterialized,\n      recursiveQuery?.hashCode,\n      columns?.join(','),\n    );\n  }\n\n  @override\n  bool operator ==(Object other) {\n    if (identical(this, other)) return true;\n    return other is CteDefinition &&\n        other.name == name &&\n        other.query == query &&\n        other.isRecursive == isRecursive &&\n        other.isMaterialized == isMaterialized &&\n        other.isNotMaterialized == isNotMaterialized &&\n        other.recursiveQuery == recursiveQuery &&\n        _listEquals(other.columns, columns);\n  }\n\n  bool _listEquals<T>(List<T>? a, List<T>? b) {\n    if (a == null) return b == null;\n    if (b == null || a.length != b.length) return false;\n    for (int index = 0; index < a.length; index += 1) {\n      if (a[index] != b[index]) return false;\n    }\n    return true;\n  }\n\n  @override\n  String toString() {\n    return 'CTE($name, recursive: $isRecursive, materialized: $isMaterialized)';\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_cte_exception.dart",
    "content": "class CteException implements Exception {\n  final String message;\n\n  final String? cteName;\n\n  CteException(this.message, [this.cteName]);\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_cte_feature.dart",
    "content": "enum CteFeature { materialized, notMaterialized, recursive }\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_database_type.dart",
    "content": "enum DatabaseType { postgresql, mysql, sqlite }\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_duplicate_cte_name_exception.dart",
    "content": "import '_cte_exception.dart';\n\nclass DuplicateCteNameException extends CteException {\n  DuplicateCteNameException(String name)\n    : super('CTE with name \"$name\" already exists', name);\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_invalid_cte_configuration_exception.dart",
    "content": "import '_cte_exception.dart';\n\nclass InvalidCteConfigurationException extends CteException {\n  InvalidCteConfigurationException(super.message, [super.cteName]);\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_sql_identifier_escaper.dart",
    "content": "import 'package:vania/src/exception/invalid_argument_exception.dart';\n\nclass SqlIdentifierEscaper {\n  static const Set<String> _reservedWords = {\n    'select',\n    'from',\n    'where',\n    'insert',\n    'update',\n    'delete',\n    'create',\n    'drop',\n    'alter',\n    'table',\n    'view',\n    'index',\n    'database',\n    'schema',\n    'order',\n    'group',\n    'having',\n    'limit',\n    'offset',\n    'join',\n    'inner',\n    'left',\n    'right',\n    'full',\n    'outer',\n    'cross',\n    'on',\n    'using',\n    'as',\n    'in',\n    'exists',\n    'count',\n    'sum',\n    'avg',\n    'min',\n    'max',\n    'case',\n    'when',\n    'then',\n    'else',\n    'end',\n    'and',\n    'or',\n    'like',\n    'between',\n    'true',\n    'false',\n    'is',\n    'begin',\n    'commit',\n    'rollback',\n    'with',\n    'recursive',\n    'union',\n    'all',\n    'distinct',\n  };\n\n  static bool needsEscaping(String identifier) {\n    if (identifier.isEmpty) return false;\n\n    if (_hasInvalidPattern(identifier)) return true;\n\n    if (_reservedWords.contains(identifier.toLowerCase())) return true;\n\n    if (_looksLikeSqlKeyword(identifier)) return true;\n\n    return false;\n  }\n\n  static bool _hasInvalidPattern(String identifier) {\n    if (RegExp(r'^\\d').hasMatch(identifier)) return true;\n\n    if (!RegExp(r'^[a-zA-Z_][a-zA-Z0-9_]*$').hasMatch(identifier)) return true;\n\n    return false;\n  }\n\n  static bool _looksLikeSqlKeyword(String identifier) {\n    final lower = identifier.toLowerCase();\n\n    if (identifier.length <= 10 &&\n        identifier == identifier.toUpperCase() &&\n        identifier.isNotEmpty &&\n        RegExp(r'^[A-Z]+$').hasMatch(identifier)) {\n      if (RegExp(\n        r'^(ID|URL|API|UUID|JSON|XML|HTML|CSS|JS)$',\n      ).hasMatch(identifier)) {\n        return false;\n      }\n      if (RegExp(r'(ID|NAME|CODE|TYPE|FLAG|DATA)$').hasMatch(identifier)) {\n        return false;\n      }\n      return true;\n    }\n\n    if (RegExp(\n      r'^(select|insert|update|delete|create|drop|alter|grant|revoke)',\n    ).hasMatch(lower)) {\n      return true;\n    }\n\n    if (RegExp(\n      r'^(count|sum|avg|min|max|concat|substr|trim|upper|lower)$',\n    ).hasMatch(lower)) {\n      return true;\n    }\n\n    if (RegExp(\n      r'^(now|today|current_date|current_time|current_timestamp)$',\n    ).hasMatch(lower)) {\n      return true;\n    }\n\n    if (RegExp(r'^(date_|time_)').hasMatch(lower) &&\n        !RegExp(r'_(at|on|by|for|from|to)$').hasMatch(lower)) {\n      return true;\n    }\n\n    return false;\n  }\n\n  static void validateIdentifier(\n    String identifier, {\n    String context = 'Identifier',\n  }) {\n    if (identifier.trim().isEmpty) {\n      throw InvalidArgumentException('$context cannot be empty or whitespace');\n    }\n    if (identifier.length > 63) {\n      throw InvalidArgumentException(\n        '$context name is too long (max 63 characters): ${identifier.length}',\n      );\n    }\n    if (identifier.contains('0')) {\n      throw InvalidArgumentException('$context cannot contain null character');\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_standard_escaping_strategy.dart",
    "content": "import '_sql_identifier_escaper.dart';\n\nabstract class IdentifierEscapingStrategy {\n  String escape(String identifier);\n\n  bool needsEscaping(String identifier);\n}\n\nclass StandardEscapingStrategy implements IdentifierEscapingStrategy {\n  final String quoteChar;\n\n  final String? closingQuoteChar;\n\n  const StandardEscapingStrategy(this.quoteChar, [this.closingQuoteChar]);\n\n  @override\n  String escape(String identifier) {\n    if (!needsEscaping(identifier)) {\n      return identifier;\n    }\n\n    String closing = closingQuoteChar ?? quoteChar;\n    String escaped = identifier.replaceAll(quoteChar, '$quoteChar$quoteChar');\n\n    if (quoteChar == '[') {\n      escaped = identifier.replaceAll(']', ']]');\n      return '[$escaped]';\n    }\n\n    return '$quoteChar$escaped$closing';\n  }\n\n  @override\n  bool needsEscaping(String identifier) {\n    return SqlIdentifierEscaper.needsEscaping(identifier);\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte/_unsupported_cte_feature_exception.dart",
    "content": "import '_cte_exception.dart';\nimport '_cte_feature.dart';\nimport '_database_type.dart';\n\nclass UnsupportedCteFeatureException extends CteException {\n  final CteFeature feature;\n\n  final DatabaseType databaseType;\n\n  UnsupportedCteFeatureException(\n    this.feature,\n    this.databaseType, [\n    String? cteName,\n  ]) : super(\n         'Feature ${feature.name} is not supported in ${databaseType.name}',\n         cteName,\n       );\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_cte_builder_impl.dart",
    "content": "import 'package:meta/meta.dart';\n\nimport '../../contract/database/query_builder/query_builder.dart';\nimport '../../exception/invalid_argument_exception.dart';\nimport '_cte/_cte_cache.dart';\nimport '_cte/_cte_configuration.dart';\nimport '_cte/_cte_definition.dart';\nimport '_cte/_cte_feature.dart';\nimport '_cte/_duplicate_cte_name_exception.dart';\nimport '_cte/_invalid_cte_configuration_exception.dart';\nimport '_cte/_sql_identifier_escaper.dart';\nimport '_cte/_standard_escaping_strategy.dart';\nimport '_cte/_unsupported_cte_feature_exception.dart';\n\nmixin QueryBuilderAccessor on QueryBuilder {\n  Map<String, dynamic> accessBindings() => getBindings();\n}\n\nabstract mixin class CteBuilderImpl implements QueryBuilder {\n  @protected\n  final List<CteDefinition> _ctes = [];\n\n  final Set<String> _cteNames = <String>{};\n\n  CteConfiguration _config = const CteConfiguration();\n  final CteCache _cache = CteCache();\n  late final IdentifierEscapingStrategy _escapingStrategy;\n\n  void configureCte(CteConfiguration config) {\n    if (_config != config) {\n      _config = config;\n      _cache.markDirty();\n      _escapingStrategy = StandardEscapingStrategy(config.quoteChar);\n    }\n  }\n\n  CteConfiguration get cteConfiguration => _config;\n\n  @override\n  QueryBuilder withCte(\n    String name,\n    QueryBuilder subQuery, {\n    List<String>? columns,\n  }) {\n    _validateAndAddCte(name, subQuery, columns: columns);\n    return this;\n  }\n\n  @override\n  QueryBuilder withMultiple(\n    Map<String, QueryBuilder> ctes, {\n    Map<String, List<String>>? columnsMap,\n  }) {\n    if (ctes.isEmpty) {\n      throw InvalidArgumentException('CTEs map cannot be empty');\n    }\n\n    for (String name in ctes.keys) {\n      SqlIdentifierEscaper.validateIdentifier(name, context: 'CTE name');\n      if (_cteNames.contains(name.toLowerCase())) {\n        throw DuplicateCteNameException(name);\n      }\n    }\n\n    for (var entry in ctes.entries) {\n      List<String>? columns = columnsMap?[entry.key];\n      _addCteDefinition(entry.key, entry.value, columns: columns);\n    }\n\n    return this;\n  }\n\n  @override\n  QueryBuilder withRecursive(\n    String name,\n    QueryBuilder baseCase,\n    QueryBuilder recursiveCase, {\n    List<String>? columns,\n  }) {\n    if (!_config.supportsFeature(CteFeature.recursive)) {\n      throw UnsupportedCteFeatureException(\n        CteFeature.recursive,\n        _config.databaseType,\n        name,\n      );\n    }\n\n    _validateAndAddCte(\n      name,\n      baseCase,\n      isRecursive: true,\n      recursiveQuery: recursiveCase,\n      columns: columns,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder withMaterialized(\n    String name,\n    QueryBuilder subQuery, {\n    List<String>? columns,\n  }) {\n    if (!_config.supportsFeature(CteFeature.materialized)) {\n      throw UnsupportedCteFeatureException(\n        CteFeature.materialized,\n        _config.databaseType,\n        name,\n      );\n    }\n\n    _validateAndAddCte(name, subQuery, isMaterialized: true, columns: columns);\n    return this;\n  }\n\n  @override\n  QueryBuilder withNotMaterialized(\n    String name,\n    QueryBuilder subQuery, {\n    List<String>? columns,\n  }) {\n    if (!_config.supportsFeature(CteFeature.notMaterialized)) {\n      throw UnsupportedCteFeatureException(\n        CteFeature.notMaterialized,\n        _config.databaseType,\n        name,\n      );\n    }\n\n    _validateAndAddCte(\n      name,\n      subQuery,\n      isNotMaterialized: true,\n      columns: columns,\n    );\n    return this;\n  }\n\n  void _validateAndAddCte(\n    String name,\n    QueryBuilder query, {\n    bool isRecursive = false,\n    bool isMaterialized = false,\n    bool isNotMaterialized = false,\n    QueryBuilder? recursiveQuery,\n    List<String>? columns,\n  }) {\n    SqlIdentifierEscaper.validateIdentifier(name, context: 'CTE name');\n\n    String lowerName = name.toLowerCase();\n    if (_cteNames.contains(lowerName)) {\n      throw DuplicateCteNameException(name);\n    }\n\n    _addCteDefinition(\n      name,\n      query,\n      isRecursive: isRecursive,\n      isMaterialized: isMaterialized,\n      isNotMaterialized: isNotMaterialized,\n      recursiveQuery: recursiveQuery,\n      columns: columns,\n    );\n  }\n\n  void _addCteDefinition(\n    String name,\n    QueryBuilder query, {\n    bool isRecursive = false,\n    bool isMaterialized = false,\n    bool isNotMaterialized = false,\n    QueryBuilder? recursiveQuery,\n    List<String>? columns,\n  }) {\n    final cte = CteDefinition(\n      name: name,\n      query: query,\n      isRecursive: isRecursive,\n      isMaterialized: isMaterialized,\n      isNotMaterialized: isNotMaterialized,\n      recursiveQuery: recursiveQuery,\n      columns: columns,\n    );\n\n    cte.validate(_config);\n\n    _ctes.add(cte);\n    _cteNames.add(name.toLowerCase());\n    _cache.markDirty();\n  }\n\n  @protected\n  void validateAllCtes() {\n    for (var cte in _ctes) {\n      cte.validate(_config);\n    }\n\n    int recursiveCount = _ctes.where((cte) => cte.isRecursive).length;\n    if (recursiveCount > _config.maxCteDepth) {\n      throw InvalidCteConfigurationException(\n        'Too many recursive CTEs: $recursiveCount (max: ${_config.maxCteDepth})',\n      );\n    }\n  }\n\n  @protected\n  String buildWithClause() {\n    if (_ctes.isEmpty) return '';\n\n    int currentHashCode = _calculateCteHashCode();\n\n    if (_config.enableCaching && !_cache.needsUpdate(currentHashCode)) {\n      return _cache.cachedWithClause ?? '';\n    }\n\n    validateAllCtes();\n\n    List<String> cteStrings = [];\n    bool hasRecursive = _ctes.any((cte) => cte.isRecursive);\n\n    _escapingStrategy = StandardEscapingStrategy(_config.quoteChar);\n\n    for (var cte in _ctes) {\n      cteStrings.add(cte.toSql(_escapingStrategy, _config));\n    }\n\n    String withClause = hasRecursive ? 'WITH RECURSIVE ' : 'WITH ';\n    withClause += cteStrings.join(', ');\n\n    if (_config.enableCaching) {\n      Map<String, dynamic> bindings = _calculateCteBindings();\n      _cache.updateCache(withClause, bindings, currentHashCode);\n    }\n\n    return withClause;\n  }\n\n  @protected\n  Map<String, dynamic> getCteBindings() {\n    if (_ctes.isEmpty) return {};\n\n    int currentHashCode = _calculateCteHashCode();\n\n    if (_config.enableCaching && !_cache.needsUpdate(currentHashCode)) {\n      return _cache.cachedBindings ?? {};\n    }\n\n    return _calculateCteBindings();\n  }\n\n  Map<String, dynamic> _calculateCteBindings() {\n    Map<String, dynamic> allBindings = {};\n\n    for (int i = 0; i < _ctes.length; i++) {\n      var cte = _ctes[i];\n      Map<String, dynamic> cteBindings = cte.getBindings();\n\n      for (var entry in cteBindings.entries) {\n        String key = entry.key;\n        if (allBindings.containsKey(key)) {\n          int counter = 1;\n          String newKey;\n          do {\n            newKey = '${key}_cte${i}_$counter';\n            counter++;\n          } while (allBindings.containsKey(newKey));\n          allBindings[newKey] = entry.value;\n        } else {\n          allBindings[key] = entry.value;\n        }\n      }\n    }\n\n    return allBindings;\n  }\n\n  int _calculateCteHashCode() {\n    return Object.hash(\n      _ctes.map((cte) => cte.hashCode).toList(),\n      _config.hashCode,\n    );\n  }\n\n  @protected\n  void clearCtes() {\n    _ctes.clear();\n    _cteNames.clear();\n    _cache.clear();\n  }\n\n  @protected\n  bool get hasCtes => _ctes.isNotEmpty;\n\n  @protected\n  List<String> get cteNames => _cteNames.toList();\n\n  @protected\n  int get cteCount => _ctes.length;\n\n  @protected\n  bool hasCte(String name) => _cteNames.contains(name.toLowerCase());\n\n  @protected\n  bool removeCte(String name) {\n    String lowerName = name.toLowerCase();\n    final index = _ctes.indexWhere(\n      (cte) => cte.name.toLowerCase() == lowerName,\n    );\n    if (index != -1) {\n      _ctes.removeAt(index);\n      _cteNames.remove(lowerName);\n      _cache.markDirty();\n      return true;\n    }\n    return false;\n  }\n\n  @protected\n  List<Map<String, dynamic>> getCteInfo() {\n    return _ctes\n        .map(\n          (cte) => {\n            'name': cte.name,\n            'isRecursive': cte.isRecursive,\n            'isMaterialized': cte.isMaterialized,\n            'isNotMaterialized': cte.isNotMaterialized,\n            'hasColumns': cte.columns != null && cte.columns!.isNotEmpty,\n            'columnCount': cte.columns?.length ?? 0,\n            'columns': cte.columns,\n            'hashCode': cte.hashCode,\n          },\n        )\n        .toList();\n  }\n\n  @protected\n  Map<String, dynamic> getCacheStats() {\n    return {\n      'isCacheEnabled': _config.enableCaching,\n      'isCacheValid': !_cache.needsUpdate(_calculateCteHashCode()),\n      'hasCachedWithClause': _cache.cachedWithClause != null,\n      'hasCachedBindings': _cache.cachedBindings != null,\n      'currentHashCode': _calculateCteHashCode(),\n    };\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_delete_query_builder_impl.dart",
    "content": "import '../../contract/database/_connectors/_database_connection.dart';\nimport '../../contract/database/query_builder/query_builder.dart'\n    show QueryBuilder;\n\nabstract mixin class DeleteQueryBuilderImpl implements QueryBuilder {\n  late DatabaseConnection conn;\n  @override\n  Future<bool> delete() async {\n    try {\n      final sql = \"DELETE FROM $getTable${buildWhereClause()}\";\n      conn = await getConnection();\n      return await conn.execute(sql, bindings);\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> truncate({bool force = false}) async {\n    try {\n      String sql = \"TRUNCATE TABLE $getTable\";\n      if (force) {\n        sql = \"SET FOREIGN_KEY_CHECKS=0; $sql; SET FOREIGN_KEY_CHECKS=1;\";\n      }\n      conn = await getConnection();\n      await conn.execute(sql);\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_insert_query_builder_impl.dart",
    "content": "import 'package:meta/meta.dart';\n\nimport '../../contract/database/_connectors/_database_connection.dart';\nimport '../../contract/database/query_builder/query_builder.dart'\n    show QueryBuilder;\nimport '../../exception/invalid_argument_exception.dart';\n\nabstract mixin class InsertQueryBuilderImpl implements QueryBuilder {\n  late DatabaseConnection conn;\n\n  @protected\n  @override\n  final Map<String, dynamic> bindings = {};\n\n  int _paramCounter = 0;\n\n  String _nextParamName() {\n    _paramCounter++;\n    return 'p$_paramCounter';\n  }\n\n  @override\n  Future<bool> insert(Map<String, dynamic> values) async {\n    try {\n      if (values.isEmpty) {\n        throw InvalidArgumentException(\n          \"Values map cannot be empty for insert operation.\",\n        );\n      }\n\n      conn = await getConnection();\n      final columns = values.keys.toList();\n      final paramBindings = <String, dynamic>{};\n\n      // Create parameter placeholders\n      final placeholders = values.keys\n          .map((key) {\n            final paramName = _nextParamName();\n            paramBindings[paramName] = values[key];\n            return \":$paramName\";\n          })\n          .join(\", \");\n\n      final query =\n          \"INSERT INTO $getTable (${columns.join(', ')}) VALUES ($placeholders)\";\n      await conn.insert(query, paramBindings);\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future insertGetId(Map<String, dynamic> values, [String? sequence]) async {\n    try {\n      if (values.isEmpty) {\n        throw InvalidArgumentException(\n          \"Values map cannot be empty for insertGetId operation.\",\n        );\n      }\n\n      conn = await getConnection();\n      final columns = values.keys.toList();\n      final paramBindings = <String, dynamic>{};\n\n      final placeholders = values.keys\n          .map((key) {\n            final paramName = _nextParamName();\n            paramBindings[paramName] = values[key];\n            return \":$paramName\";\n          })\n          .join(\", \");\n\n      final query =\n          \"INSERT INTO $getTable (${columns.join(', ')}) VALUES ($placeholders)\";\n      final id = await conn.insert(query, paramBindings);\n      return id;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> insertMany(List<Map<String, dynamic>> valuesList) async {\n    try {\n      if (valuesList.isEmpty) {\n        throw InvalidArgumentException(\n          \"Values list cannot be empty for insertMany operation.\",\n        );\n      }\n\n      // Ensure all maps have the same keys\n      final firstItem = valuesList.first;\n      final columns = firstItem.keys.toList();\n\n      for (var values in valuesList) {\n        if (!_haveSameKeys(values, firstItem)) {\n          throw InvalidArgumentException(\n            \"All items in the values list must have the same structure.\",\n          );\n        }\n      }\n\n      conn = await getConnection();\n      final paramBindings = <String, dynamic>{};\n      final valueGroups = <String>[];\n\n      // Create parameter placeholders for each row\n      for (var values in valuesList) {\n        final placeholders = columns\n            .map((column) {\n              final paramName = _nextParamName();\n              paramBindings[paramName] = values[column];\n              return \":$paramName\";\n            })\n            .join(\", \");\n\n        valueGroups.add(\"($placeholders)\");\n      }\n\n      final query =\n          \"INSERT INTO $getTable (${columns.join(', ')}) VALUES ${valueGroups.join(', ')}\";\n\n      await conn.execute(query, paramBindings);\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  bool _haveSameKeys(Map<String, dynamic> map1, Map<String, dynamic> map2) {\n    if (map1.keys.length != map2.keys.length) return false;\n\n    for (var key in map1.keys) {\n      if (!map2.containsKey(key)) return false;\n    }\n\n    return true;\n  }\n\n  @override\n  Future<bool> insertOrIgnore(Map<String, dynamic> values) async {\n    try {\n      if (values.isEmpty) {\n        throw InvalidArgumentException(\n          \"Values map cannot be empty for insertOrIgnore operation.\",\n        );\n      }\n      conn = await getConnection();\n      final columns = values.keys.toList();\n      final paramBindings = <String, dynamic>{};\n      final placeholders = values.keys\n          .map((key) {\n            final paramName = _nextParamName();\n            paramBindings[paramName] = values[key];\n            return \":$paramName\";\n          })\n          .join(\", \");\n\n      final query =\n          \"INSERT IGNORE INTO $getTable (${columns.join(', ')}) VALUES ($placeholders)\";\n      await conn.execute(query, paramBindings);\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> insertUsing(List<String> columns, QueryBuilder subQuery) async {\n    try {\n      String cols = columns.join(\", \");\n      String subSql = subQuery.toRawSql();\n      String sql = \"INSERT INTO $getTable ($cols) $subSql\";\n      conn = await getConnection();\n      await conn.execute(sql);\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> upsert(\n    Map<String, dynamic> values,\n    List<String> uniqueBy, [\n    Map<String, dynamic>? update,\n  ]) async {\n    try {\n      if (values.isEmpty) {\n        throw InvalidArgumentException(\n          \"Values map cannot be empty for upsert operation.\",\n        );\n      }\n\n      conn = await getConnection();\n      final columns = values.keys.toList();\n      final paramBindings = <String, dynamic>{};\n\n      final placeholders = values.keys\n          .map((key) {\n            final paramName = _nextParamName();\n            paramBindings[paramName] = values[key];\n            return \":$paramName\";\n          })\n          .join(\", \");\n\n      String sql =\n          \"INSERT INTO $getTable (${columns.join(', ')}) VALUES ($placeholders)\";\n\n      if (update == null) {\n        update = Map.from(values);\n        for (var col in uniqueBy) {\n          update.remove(col);\n        }\n      }\n\n      if (update.isNotEmpty) {\n        final updateClauses = update.entries\n            .map((entry) {\n              final paramName = _nextParamName();\n              paramBindings[paramName] = entry.value;\n              return \"${entry.key} = :$paramName\";\n            })\n            .join(\", \");\n\n        sql += \" ON DUPLICATE KEY UPDATE $updateClauses\";\n      }\n\n      await conn.execute(sql, paramBindings);\n      return true;\n    } catch (e) {\n      rethrow;\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_join_clause_builder_impl.dart",
    "content": "import '../../contract/database/query_builder/query_builder.dart'\n    show QueryBuilder;\n\nabstract mixin class JoinClauseBuilderImpl implements QueryBuilder {\n  @override\n  QueryBuilder crossJoin(String table, [List<dynamic> bindings = const []]) {\n    String clause = \"CROSS JOIN $table\";\n    joins.add(clause);\n    return this;\n  }\n\n  @override\n  QueryBuilder join(\n    String table,\n    String firstColumn, [\n    String? operator,\n    String? secondColumn,\n    String type = 'INNER',\n    bool where = false,\n  ]) {\n    String clause = \"$type JOIN $table\";\n    if (operator != null && secondColumn != null) {\n      clause += \" ON $firstColumn $operator $secondColumn\";\n    }\n    joins.add(clause);\n    return this;\n  }\n\n  @override\n  QueryBuilder joinSub(\n    QueryBuilder subQuery,\n    String as,\n    String firstColumn, [\n    String? operator,\n    String? secondColumn,\n    String type = 'inner',\n  ]) {\n    String subSql = \"(${subQuery.toSql()}) AS $as\";\n    String clause = \"$type JOIN $subSql\";\n    if (operator != null && secondColumn != null) {\n      clause += \" ON $firstColumn $operator $secondColumn\";\n    }\n    joins.add(clause);\n    return this;\n  }\n\n  @override\n  QueryBuilder leftJoin(\n    String table,\n    String firstColumn, [\n    String? operator,\n    String? secondColumn,\n    bool where = false,\n  ]) {\n    return join(table, firstColumn, operator, secondColumn, \"LEFT\", where);\n  }\n\n  @override\n  QueryBuilder leftJoinSub(\n    QueryBuilder subQuery,\n    String as,\n    String firstColumn, [\n    String? operator,\n    String? secondColumn,\n  ]) {\n    return joinSub(subQuery, as, firstColumn, operator, secondColumn, \"LEFT\");\n  }\n\n  @override\n  QueryBuilder rightJoin(\n    String table,\n    String firstColumn, [\n    String? operator,\n    String? secondColumn,\n  ]) {\n    return join(table, firstColumn, operator, secondColumn, \"RIGHT\");\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_query_builder_impl.dart",
    "content": "import 'package:meta/meta.dart';\nimport '../../contract/database/query_builder/query_builder.dart';\nimport '../../exception/invalid_argument_exception.dart';\nimport '../../utils/helper.dart' show env;\nimport '../_connection_manager.dart';\nimport '../monitoring/database_monitor.dart';\nimport '_bulk_operations_builder_impl.dart';\nimport '_cte_builder_impl.dart';\nimport '_delete_query_builder_impl.dart';\nimport '_insert_query_builder_impl.dart';\nimport '_join_clause_builder_impl.dart';\nimport '_where_clauses_builder_impl.dart';\nimport '_query_executor_builder_impl.dart';\nimport '_select_query_builder_impl.dart';\nimport '_union_clause_builder_impl.dart';\nimport '_update_query_builder_impl.dart';\nimport '_window_functions_builder_impl.dart';\n\nclass QueryBuilderImpl extends QueryBuilder\n    with\n        QueryExecutorBuilderImpl,\n        InsertQueryBuilderImpl,\n        UpdateQueryBuilderImpl,\n        WhereClausesBuilderImpl,\n        DeleteQueryBuilderImpl,\n        SelectQueryBuilderImpl,\n        JoinClauseBuilderImpl,\n        UnionClauseBuilderImpl,\n        WindowFunctionsBuilderImpl,\n        BulkOperationsBuilderImpl,\n        CteBuilderImpl {\n  String _connectionName = env<String>('DB_CONNECTION', '');\n  final List<String> _orderBy = [];\n  final List<String> _groupBy = [];\n  final List<String> _having = [];\n\n  String? _table;\n  String? _tableAlias;\n  int? _limit;\n  int? _offset;\n  int _paramCounter = 0;\n\n  @override\n  String get connectionName => _connectionName;\n  set connectionName(String value) => _connectionName = value;\n\n  @override\n  String get getTable {\n    String tableClause = (_table != null) ? \"$_table\" : \"\";\n    if (_tableAlias != null && _tableAlias!.isNotEmpty) {\n      tableClause += \" AS $_tableAlias\";\n    }\n    return tableClause;\n  }\n\n  @override\n  RawExpression raw(value) => RawExpression(value);\n\n  @override\n  Future<bool> transaction(\n    Future<bool> Function() action, [\n    String? conditionName,\n  ]) => ConnectionManager().transaction(action, conditionName);\n\n  @override\n  Stream<DatabaseAlert> alerts() => ConnectionManager().alerts;\n\n  @override\n  Map<String, PerformanceStats> getPerformanceStats() =>\n      ConnectionManager().getPerformanceStats();\n\n  @protected\n  @override\n  String build({String? aggregateFunction, String? aggregateColumn}) {\n    String sql = '';\n\n    if (selectColumns.length > 1) {\n      selectColumns.remove('*');\n    }\n\n    String withClause = buildWithClause();\n    if (withClause.isNotEmpty) {\n      sql = '$withClause ';\n    }\n\n    if (getTable.isNotEmpty) {\n      if (aggregateFunction != null && aggregateColumn != null) {\n        sql += \"SELECT $aggregateFunction($aggregateColumn) FROM $getTable\";\n      } else {\n        sql +=\n            \"SELECT ${selectColumns.isEmpty ? \"*\" : selectColumns.join(\", \")} FROM $getTable\";\n      }\n\n      if (joins.isNotEmpty) {\n        sql += \" ${joins.join(\" \")}\";\n      }\n      sql += conditions.isNotEmpty ? \" WHERE ${conditions.join(\" \")}\" : \"\";\n\n      if (unions.isNotEmpty) {\n        sql += \" ${unions.join(\" \")}\";\n      }\n\n      if (aggregateFunction == null && aggregateColumn == null) {\n        if (_groupBy.isNotEmpty) {\n          sql += \" GROUP BY ${_groupBy.join(\", \")}\";\n        }\n        if (_having.isNotEmpty) {\n          sql += \" HAVING ${_having.join(\" \")}\";\n        }\n        if (_orderBy.isNotEmpty) {\n          sql += \" ORDER BY ${_orderBy.join(\", \")}\";\n        }\n\n        sql += (_limit != null) ? \" LIMIT $_limit\" : \"\";\n        sql += (_offset != null) ? \" OFFSET $_offset\" : \"\";\n      }\n    } else if (conditions.isNotEmpty) {\n      sql += conditions.join(\" \");\n    } else {\n      sql += '';\n    }\n\n    return sql.trim();\n  }\n\n  @override\n  QueryBuilder connection([String? connection]) {\n    connectionName = connection ?? _connectionName;\n    return this;\n  }\n\n  @override\n  QueryBuilder groupBy(List<String> groups) {\n    _groupBy.addAll(groups);\n    return this;\n  }\n\n  @override\n  QueryBuilder having(\n    String column, [\n    String? operator,\n    dynamic value,\n    String boolean = 'and',\n  ]) {\n    String clause;\n    if (operator != null && value != null) {\n      final paramName = _nextParamName();\n      bindings[paramName] = value;\n      clause = \"$column $operator :$paramName\";\n    } else {\n      clause = column;\n    }\n    if (_having.isEmpty) {\n      _having.add(clause);\n    } else {\n      _having.add(\" $boolean $clause\");\n    }\n    return this;\n  }\n\n  @override\n  QueryBuilder havingBetween(\n    String column,\n    List<dynamic> values, {\n    String boolean = 'and',\n    bool not = false,\n  }) {\n    if (values.length < 2) {\n      throw InvalidArgumentException(\n        'The list of values must contain at least two items.',\n      );\n    }\n\n    final paramName1 = _nextParamName();\n    final paramName2 = _nextParamName();\n    bindings[paramName1] = values[0];\n    bindings[paramName2] = values[1];\n\n    String clause =\n        \"$column ${not ? \"NOT BETWEEN\" : \"BETWEEN\"} :$paramName1 AND :$paramName2\";\n    if (_having.isEmpty) {\n      _having.add(clause);\n    } else {\n      _having.add(\" $boolean $clause\");\n    }\n    return this;\n  }\n\n  @override\n  QueryBuilder inRandomOrder([dynamic seed]) {\n    if (seed != null) {\n      _orderBy.add(\"RAND($seed)\");\n    } else {\n      _orderBy.add(\"RAND()\");\n    }\n    return this;\n  }\n\n  @override\n  QueryBuilder latest([String column = 'created_at']) {\n    return orderByDesc(column);\n  }\n\n  @override\n  QueryBuilder limit(int value) {\n    _limit = value;\n    return this;\n  }\n\n  @override\n  QueryBuilder offset(int value) {\n    _offset = value;\n    return this;\n  }\n\n  @override\n  QueryBuilder orderBy(String column, [String direction = 'ASC']) {\n    _orderBy.add(\"$column $direction\");\n    return this;\n  }\n\n  @override\n  QueryBuilder orderByAsc(String column) {\n    return orderBy(column, \"ASC\");\n  }\n\n  @override\n  QueryBuilder orderByDesc(String column) {\n    return orderBy(column, \"DESC\");\n  }\n\n  @override\n  QueryBuilder reorder([String? column, String? direction]) {\n    _orderBy.clear();\n    if (column != null) {\n      _orderBy.add(\"$column ${direction ?? 'asc'}\");\n    }\n    return this;\n  }\n\n  @override\n  QueryBuilder table(String table, [String? as]) {\n    _table = table;\n    _tableAlias = as;\n    return this;\n  }\n\n  @override\n  QueryBuilder skip(int value) => offset(value);\n\n  @override\n  QueryBuilder take(int value) => limit(value);\n\n  @override\n  String toSql() => build();\n\n  @override\n  String toRawSql() {\n    String sql = build();\n    final bindings = getBindings();\n\n    bindings.forEach((key, value) {\n      String placeholder = ':$key';\n      String formattedValue = _formatValueForRawSql(value);\n      sql = sql.replaceAll(placeholder, formattedValue);\n    });\n\n    return sql;\n  }\n\n  String _formatValueForRawSql(dynamic value) {\n    if (value == null) {\n      return 'NULL';\n    } else if (value is RawExpression) {\n      return value.toString();\n    } else if (value is String) {\n      return \"'${value.replaceAll(\"'\", \"''\")}'\";\n    } else if (value is num) {\n      return value.toString();\n    } else if (value is bool) {\n      return value.toString();\n    } else if (value is DateTime) {\n      return \"'${value.toIso8601String()}'\";\n    } else if (value is List) {\n      return '(${value.map(_formatValueForRawSql).join(', ')})';\n    } else {\n      return \"'${value.toString().replaceAll(\"'\", \"''\")}'\";\n    }\n  }\n\n  @override\n  Map<String, dynamic> getBindings() {\n    Map<String, dynamic> allBindings = {};\n\n    allBindings.addAll((this as WhereClausesBuilderImpl).bindings);\n\n    allBindings.addAll(getCteBindings());\n    (this as WhereClausesBuilderImpl).paramCounter = 0;\n    return allBindings;\n  }\n\n  String _nextParamName() {\n    _paramCounter++;\n    return 'p$_paramCounter';\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_query_executor_builder_impl.dart",
    "content": "import 'package:vania/src/utils/request_helper.dart' show getParam;\n\nimport '../../contract/database/_connectors/_database_connection.dart';\nimport '../../exception/invalid_argument_exception.dart';\n\nimport '../../contract/database/query_builder/query_builder.dart'\n    show PaginatedResult, QueryBuilder;\n\nabstract mixin class QueryExecutorBuilderImpl implements QueryBuilder {\n  late DatabaseConnection conn;\n  @override\n  Future<num> avg(String column) async {\n    try {\n      String sql = build(aggregateFunction: \"AVG\", aggregateColumn: column);\n      final bindings = getBindings();\n      conn = await getConnection();\n      var result = await conn.select(sql, bindings);\n      return num.tryParse(result.first.values.first.toString()) ?? 0;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<void> chunk(\n    int chunk,\n    void Function(List<Map<String, dynamic>> data) callback,\n  ) async {\n    try {\n      int offset = 0;\n      while (true) {\n        limit(chunk).offset(offset);\n        final result = await get();\n        if (result.isEmpty) {\n          break;\n        }\n        callback(result);\n        offset += chunk;\n        if (result.length < chunk) {\n          break;\n        }\n      }\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<void> chunkById(\n    int chunk,\n    void Function(List<Map<String, dynamic>> data) callback, [\n    String column = 'id',\n  ]) async {\n    try {\n      int lastId = 0;\n\n      while (true) {\n        whereGreaterThan(column, lastId).orderByAsc(column).limit(chunk);\n        final result = await get();\n        if (result.isEmpty) {\n          break;\n        }\n        callback(result);\n        lastId = int.parse(result.last[column].toString());\n\n        if (result.length < chunk) {\n          break;\n        }\n      }\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<int> count([String columns = '*']) async {\n    try {\n      final bindings = getBindings();\n      String sql = build(aggregateFunction: \"COUNT\", aggregateColumn: columns);\n      conn = await getConnection();\n      var result = await conn.select(sql, bindings);\n      return int.tryParse(result.first.values.first.toString()) ?? 0;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> doesntExist() async {\n    try {\n      String sql = \"SELECT NOT EXISTS(SELECT 1 FROM $getTable\";\n      if (conditions.isNotEmpty) {\n        sql += \" WHERE ${conditions.join('')}\";\n      }\n      sql += \") as `exists`\";\n      final bindings = getBindings();\n      var result = await dbConnection!.select(sql, bindings);\n\n      return (int.tryParse(result.first[\"exists\"].toString()) == 1);\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<void> each(void Function(Map<String, dynamic> q) callback) async {\n    var results = await get();\n    for (var row in results) {\n      callback(row);\n    }\n  }\n\n  @override\n  Future<bool> exists() async {\n    try {\n      String sql = \"SELECT EXISTS(SELECT 1 FROM $getTable\";\n      if (conditions.isNotEmpty) {\n        sql += \" WHERE ${conditions.join('')}\";\n      }\n      sql += \") as `exists`\";\n      final bindings = getBindings();\n      var result = await dbConnection!.select(sql, bindings);\n      return (int.tryParse(result.first[\"exists\"].toString()) == 1);\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<Map<String, dynamic>?> find(\n    dynamic id, {\n    String byColumnName = 'id',\n    List<String> columns = const [],\n  }) async {\n    try {\n      String sql = whereEqualTo('$getTable.$byColumnName', id).limit(1).toSql();\n      if (columns.isNotEmpty) {\n        for (String column in columns) {\n          selectColumns.remove(column);\n        }\n        selectColumns.addAll(columns);\n      }\n      final bindings = getBindings();\n      final result = await dbConnection!.select(sql, bindings);\n      if (result.isEmpty) {\n        return null;\n      }\n      return result.first;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<Map<String, dynamic>?> findOrFail(\n    id, {\n    String byColumnName = 'id',\n    List<String> columns = const [],\n  }) async {\n    var result = await find(id, byColumnName: byColumnName, columns: columns);\n    if (result == null) {\n      throw InvalidArgumentException(\"Record with id $id not found.\");\n    }\n    return result;\n  }\n\n  @override\n  Future<Map<String, dynamic>?> first([List<String> columns = const []]) async {\n    try {\n      if (columns.isNotEmpty) {\n        for (String column in columns) {\n          selectColumns.remove(column);\n        }\n        selectColumns.addAll(columns);\n      }\n      final bindings = getBindings();\n      String sql = limit(1).toSql();\n      conn = await getConnection();\n      final result = await conn.select(sql, bindings);\n      if (result.isEmpty) {\n        return null;\n      }\n      return result.first;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<Map<String, dynamic>?> firstOrFail([\n    List<String> columns = const [],\n  ]) async {\n    var result = await first(columns);\n    if (result == null) {\n      throw InvalidArgumentException(\"No records found.\");\n    }\n    return result;\n  }\n\n  @override\n  Future<Map<String, dynamic>?> firstWhere(\n    String column, [\n    String? operator = '=',\n    value,\n    List<String> columns = const [],\n  ]) async {\n    if (value == null) {\n      throw InvalidArgumentException(\n        \"Invalid input: Value cannot be null. A valid value must be provided for the firstWhere method.\",\n      );\n    }\n    where(column, operator ?? '=', value);\n    return await first(columns);\n  }\n\n  @override\n  Future<List<Map<String, dynamic>>> get([\n    List<String> columns = const [],\n  ]) async {\n    try {\n      final bindings = getBindings();\n      if (columns.isNotEmpty) {\n        for (String column in columns) {\n          selectColumns.remove(column);\n        }\n        selectColumns.addAll(columns);\n      }\n      final sql = toSql();\n      conn = await getConnection();\n      return await conn.select(sql, bindings);\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Stream<Iterable<Map<String, dynamic>>> lazy([\n    int chunk = 100,\n    String column = 'id',\n  ]) async* {\n    int offset = 0;\n    while (true) {\n      orderByAsc(column).limit(chunk).offset(offset);\n      final result = await get();\n      if (result.isEmpty) {\n        break;\n      }\n      yield result;\n      offset += chunk;\n      if (result.length < chunk) {\n        break;\n      }\n    }\n  }\n\n  @override\n  Stream<Map<String, dynamic>> cursor([int chunk = 1000]) async* {\n    try {\n      int offset = 0;\n\n      while (true) {\n        limit(chunk).offset(offset);\n\n        final result = await get();\n        if (result.isEmpty) {\n          break;\n        }\n\n        for (final row in result) {\n          yield row;\n        }\n\n        offset += chunk;\n\n        if (result.length < chunk) {\n          break;\n        }\n      }\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future max(String column) async {\n    try {\n      final bindings = getBindings();\n      String sql = build(aggregateFunction: \"MAX\", aggregateColumn: column);\n      conn = await getConnection();\n      var result = await conn.select(sql, bindings);\n      return result.first.values.first;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future min(String column) async {\n    try {\n      final bindings = getBindings();\n      String sql = build(aggregateFunction: \"MIN\", aggregateColumn: column);\n      conn = await getConnection();\n      var result = await conn.select(sql, bindings);\n      return result.first.values.first;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<Map<String, dynamic>> paginate({\n    int perPage = 15,\n    List<String> columns = const [],\n    String? pageName,\n    int? page,\n  }) async {\n    int currentPage = page ?? getParam<int>('page', 1)!;\n    int total = await count();\n    final lastPage = (total / perPage).ceil();\n    final offset = (currentPage - 1) * perPage;\n    final pageData = await take(perPage).skip(offset).get(columns);\n    final isFirst = currentPage == 1;\n    final isLast = currentPage == lastPage;\n    final hasMore = currentPage < lastPage;\n\n    return PaginatedResult(\n      data: pageData,\n      currentPage: currentPage,\n      perPage: perPage,\n      total: total,\n      lastPage: lastPage,\n      isFirst: isFirst,\n      isLast: isLast,\n      hasMore: hasMore,\n    ).toMap();\n  }\n\n  @override\n  Future pluck(String column, [String? key]) async {\n    var results = await get();\n    if (key == null) {\n      return results.map((row) => row[column]).toList();\n    } else {\n      Map<dynamic, dynamic> resultMap = {};\n      for (var row in results) {\n        resultMap[row[key]] = row[column];\n      }\n      return resultMap;\n    }\n  }\n\n  @override\n  Future<Map<String, dynamic>> simplePaginate([\n    int perPage = 15,\n    List<String> columns = const [],\n    String? pageName,\n    int? page,\n  ]) async {\n    int currentPage = page ?? getParam<int>('page', 1)!;\n    int total = await count();\n    final lastPage = (total / perPage).ceil();\n    final offset = (currentPage - 1) * perPage;\n    final pageData = await take(perPage).skip(offset).get(columns);\n\n    return {\n      'data': pageData,\n      'current_page': currentPage,\n      'per_page': perPage,\n      'total': total,\n      'last_page': lastPage,\n    };\n  }\n\n  @override\n  Future<num> sum(String column) async {\n    try {\n      final bindings = getBindings();\n      String sql = build(aggregateFunction: \"SUM\", aggregateColumn: column);\n      conn = await getConnection();\n      var result = await conn.select(sql, bindings);\n      return num.tryParse(result.first.values.first.toString()) ?? 0;\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future value(String column) async {\n    final response = await first();\n    if (response != null && response.containsKey(column)) {\n      return response[column];\n    }\n\n    return null;\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_select_query_builder_impl.dart",
    "content": "import '../../contract/database/query_builder/query_builder.dart'\n    show QueryBuilder;\n\nabstract mixin class SelectQueryBuilderImpl implements QueryBuilder {\n  @override\n  QueryBuilder addSelect(List<String> columns) {\n    selectColumns.addAll(columns);\n    return this;\n  }\n\n  @override\n  QueryBuilder select([List<String> columns = const ['*']]) {\n    selectColumns = List.from(columns);\n    return this;\n  }\n\n  @override\n  QueryBuilder selectRaw(String query, [List bindings = const []]) {\n    for (var i = 0; i < bindings.length; i++) {\n      final paramName = 'p${i + 1}';\n      this.bindings[paramName] = bindings[i];\n      query = query.replaceFirst('?', ':$paramName');\n    }\n\n    selectColumns.add(query);\n    return this;\n  }\n\n  @override\n  QueryBuilder selectSub(QueryBuilder subQuery, String as) {\n    String sub = \"(${subQuery.toRawSql()}) AS $as\";\n    selectColumns.add(sub);\n    return this;\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_union_clause_builder_impl.dart",
    "content": "import '../../contract/database/query_builder/query_builder.dart'\n    show QueryBuilder;\n\nabstract mixin class UnionClauseBuilderImpl implements QueryBuilder {\n  @override\n  QueryBuilder union(QueryBuilder query) {\n    unions.add(\"UNION ${toSql()}\");\n    return this;\n  }\n\n  @override\n  QueryBuilder unionAll(QueryBuilder query) {\n    unions.add(\"UNION ALL ${toSql()}\");\n    return this;\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_update_query_builder_impl.dart",
    "content": "import '../../contract/database/_connectors/_database_connection.dart';\nimport '../../contract/database/query_builder/query_builder.dart'\n    show QueryBuilder;\nimport '../../exception/invalid_argument_exception.dart';\n\nabstract mixin class UpdateQueryBuilderImpl implements QueryBuilder {\n  late DatabaseConnection conn;\n  @override\n  Future<bool> update(Map<String, dynamic> values) async {\n    try {\n      if (values.isEmpty) {\n        throw InvalidArgumentException('Update values cannot be empty');\n      }\n\n      List<String> setStatements = [];\n      for (var entry in values.entries) {\n        final paramName = 'p${entry.key}';\n        bindings[paramName] = entry.value;\n        setStatements.add(\"${entry.key} = :$paramName\");\n      }\n\n      String sql =\n          \"UPDATE $getTable${buildJoins()} SET ${setStatements.join(\", \")}${buildWhereClause()}\";\n      conn = await getConnection();\n      return await conn.execute(sql, bindings);\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> updateMany(\n    List<Map<String, dynamic>> updates,\n    String column,\n  ) async {\n    try {\n      if (updates.isEmpty) return false;\n\n      Set<String> columns = {};\n      for (var row in updates) {\n        columns.addAll(row.keys.where((key) => key != column));\n      }\n      List<String> setClauses = [];\n      var caseCounter = 0;\n\n      for (var col in columns) {\n        List<String> cases = [];\n        for (var row in updates) {\n          if (row.containsKey(col)) {\n            final keyParamName = 'p$caseCounter';\n            final valueParamName = 'p$caseCounter';\n            bindings[keyParamName] = row[column];\n            bindings[valueParamName] = row[col];\n            cases.add(\"WHEN $column = :$keyParamName THEN :$valueParamName\");\n            caseCounter++;\n          }\n        }\n        setClauses.add(\"$col = CASE ${cases.join(\" \")} ELSE $col END\");\n      }\n\n      List<String> whereValues = [];\n      for (var i = 0; i < updates.length; i++) {\n        final paramName = 'p$i';\n        bindings[paramName] = updates[i][column];\n        whereValues.add(\":$paramName\");\n      }\n\n      String sql =\n          \"UPDATE $getTable${buildJoins()} SET ${setClauses.join(\", \")} WHERE $column IN (${whereValues.join(\", \")})\";\n      conn = await getConnection();\n      return await conn.execute(sql, bindings);\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> updateOrInsert(\n    Map<String, dynamic> search,\n    Map<String, dynamic> update,\n  ) async {\n    Map<String, dynamic> data = {}\n      ..addAll(search)\n      ..addAll(update);\n\n    return upsert(data, search.keys.toList(), update);\n  }\n\n  @override\n  Future<bool> increment(\n    String column, [\n    int amount = 1,\n    Map<String, dynamic> extra = const {},\n  ]) async {\n    try {\n      final paramName = 'pamount';\n      bindings[paramName] = amount;\n\n      String setClause = \"$column = $column + :$paramName\";\n      if (extra.isNotEmpty) {\n        var extraCounter = 0;\n        List<String> extraClauses = [];\n\n        for (var entry in extra.entries) {\n          final extraParamName = 'p${extraCounter++}';\n          bindings[extraParamName] = entry.value;\n          extraClauses.add(\"${entry.key} = :$extraParamName\");\n        }\n        setClause += \", ${extraClauses.join(\", \")}\";\n      }\n\n      String sql =\n          \"UPDATE $getTable${buildJoins()} SET $setClause${buildWhereClause()}\";\n      conn = await getConnection();\n      return await conn.execute(sql, bindings);\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> decrement(\n    String column, [\n    int amount = 1,\n    Map<String, dynamic> extra = const {},\n  ]) async {\n    try {\n      final paramName = 'pamount';\n      bindings[paramName] = amount;\n\n      String setClause = \"$column = $column - :$paramName\";\n      if (extra.isNotEmpty) {\n        var extraCounter = 0;\n        List<String> extraClauses = [];\n\n        for (var entry in extra.entries) {\n          final extraParamName = 'p${extraCounter++}';\n          bindings[extraParamName] = entry.value;\n          extraClauses.add(\"${entry.key} = :$extraParamName\");\n        }\n        setClause += \", ${extraClauses.join(\", \")}\";\n      }\n\n      String sql =\n          \"UPDATE $getTable${buildJoins()} SET $setClause${buildWhereClause()}\";\n      conn = await getConnection();\n      return await conn.execute(sql, bindings);\n    } catch (e) {\n      rethrow;\n    }\n  }\n\n  @override\n  Future<bool> incrementEach(\n    Map<String, int> increments, [\n    Map<String, dynamic> extra = const {},\n  ]) async {\n    try {\n      List<String> setClauses = [];\n      var counter = 0;\n\n      for (var entry in increments.entries) {\n        final paramName = 'p${counter++}';\n        bindings[paramName] = entry.value;\n        setClauses.add(\"${entry.key} = ${entry.key} + :$paramName\");\n      }\n\n      if (extra.isNotEmpty) {\n        for (var entry in extra.entries) {\n          final paramName = 'p${counter++}';\n          bindings[paramName] = entry.value;\n          setClauses.add(\"${entry.key} = :$paramName\");\n        }\n      }\n\n      String sql =\n          \"UPDATE $getTable${buildJoins()} SET ${setClauses.join(\", \")}${buildWhereClause()}\";\n      conn = await getConnection();\n      return await conn.execute(sql, bindings);\n    } catch (e) {\n      rethrow;\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_where_clauses_builder_impl.dart",
    "content": "import '../../exception/invalid_argument_exception.dart';\nimport '../../contract/database/query_builder/query_builder.dart'\n    show QueryBuilder, QueryCallback;\nimport '../_database_utils/_singularize.dart';\nimport '_query_builder_impl.dart';\n\nint _paramCounter = 0;\n\nabstract mixin class WhereClausesBuilderImpl implements QueryBuilder {\n  /// Valid SQL comparison operators to prevent SQL injection.\n  /// Only these operators are allowed in where clauses.\n  static const Set<String> _validOperators = {\n    '=',\n    '<>',\n    '!=',\n    '<',\n    '>',\n    '<=',\n    '>=',\n    'LIKE',\n    'NOT LIKE',\n    'ILIKE', // PostgreSQL case-insensitive LIKE\n    'NOT ILIKE',\n    'REGEXP',\n    'NOT REGEXP',\n    'RLIKE', // MySQL alias for REGEXP\n    'SIMILAR TO', // PostgreSQL\n  };\n\n  set paramCounter(int paramN) {\n    _paramCounter = paramN;\n  }\n\n  /// Returns the current parameter counter value.\n  /// Useful for synchronizing nested queries.\n  int get currentParamCounter => _paramCounter;\n\n  String _nextParamName() {\n    _paramCounter++;\n    return 'p$_paramCounter';\n  }\n\n  /// Validates that the given operator is a valid SQL comparison operator.\n  /// Throws [InvalidArgumentException] if the operator is not valid.\n  /// This prevents SQL injection attacks through malicious operators.\n  void _validateOperator(String operator) {\n    if (!_validOperators.contains(operator.toUpperCase())) {\n      throw InvalidArgumentException(\n        'Invalid SQL operator: \"$operator\". '\n        'Allowed operators: ${_validOperators.join(\", \")}',\n      );\n    }\n  }\n\n  @override\n  String buildWhereClause() {\n    return conditions.isNotEmpty ? \" WHERE ${conditions.join(\" \")}\" : \"\";\n  }\n\n  @override\n  String build({String? aggregateFunction, String? aggregateColumn}) {\n    throw UnimplementedError(\n      'build() should be implemented by the concrete class',\n    );\n  }\n\n  @override\n  Map<String, dynamic> getBindings() {\n    return bindings;\n  }\n\n  @override\n  QueryBuilder orWhere(\n    dynamic condition, [\n    String operator = '=',\n    dynamic value,\n    String boolean = 'and',\n  ]) {\n    if (condition is String) {\n      _validateOperator(operator);\n      final paramName = _nextParamName();\n      bindings[paramName] = value;\n      _appendCondition(\"$condition $operator :$paramName\", isOr: true);\n    } else if (condition is QueryCallback) {\n      QueryBuilderImpl nested = QueryBuilderImpl()\n        ..paramCounter = _paramCounter;\n      condition(nested);\n      _appendCondition(\"(${nested.toSql()})\", isOr: true);\n      bindings.addAll(nested.getBindings());\n    } else {\n      throw InvalidArgumentException(\n        'Invalid argument type for condition. Expected either a String or a QueryBuilder instance',\n      );\n    }\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereBetween(String column, List values, {bool not = false}) {\n    _appendCondition(_createBetweenCondition(column, values, not), isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereColumn(\n    String first,\n    String operator,\n    String secondColumn,\n  ) {\n    String condition = \"$first $operator $secondColumn\";\n    _appendCondition(condition, isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereDate(String column, String operator, dynamic value) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"DATE\"),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereDay(String column, String operator, dynamic value) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"DAY\"),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereExists(QueryCallback callback, {bool not = false}) {\n    QueryBuilder subQuery = QueryBuilderImpl();\n    callback(subQuery);\n    String condition = \"${not ? 'NOT EXISTS' : 'EXISTS'} (${subQuery.toSql()})\";\n    _appendCondition(condition, isOr: true);\n    bindings.addAll(subQuery.getBindings());\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereFullText(\n    dynamic columns,\n    dynamic query, [\n    Map<String, dynamic> options = const {},\n  ]) {\n    _appendCondition(\n      _createFullTextCondition(columns, query, options),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereHour(String column, String operator, dynamic value) {\n    _appendCondition(_createHourCondition(column, operator, value), isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereIn(String column, List values, {bool not = false}) {\n    _appendCondition(_createInCondition(column, values, not), isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereJsonContains(\n    String column,\n    dynamic value, {\n    bool not = false,\n  }) {\n    _appendCondition(\n      _createJsonContainsCondition(column, value, not),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereJsonDoesntContain(String column, dynamic value) {\n    _appendCondition(\n      _createJsonContainsCondition(column, value, true),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereJsonLength(\n    String column,\n    String operator,\n    dynamic value,\n  ) {\n    _appendCondition(\n      _createJsonLengthCondition(column, operator, value),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereLike(\n    String column,\n    dynamic value, {\n    bool caseSensitive = false,\n  }) {\n    _appendCondition(\n      _createLikeCondition(\n        column,\n        value,\n        not: false,\n        caseSensitive: caseSensitive,\n      ),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereMonth(String column, String operator, dynamic value) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"MONTH\"),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereNotBetween(String column, List values) {\n    _appendCondition(_createBetweenCondition(column, values, true), isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereNotExists(QueryCallback callback) {\n    return orWhereExists(callback, not: true);\n  }\n\n  @override\n  QueryBuilder orWhereNotIn(String column, dynamic values) {\n    _appendCondition(_createInCondition(column, values, true), isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereNotLike(\n    String column,\n    dynamic value, {\n    bool caseSensitive = false,\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createLikeCondition(\n        column,\n        value,\n        not: true,\n        caseSensitive: caseSensitive,\n      ),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereNotNull(String column) {\n    _appendCondition(_createNullCondition(column, true), isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereNull(String column) {\n    _appendCondition(_createNullCondition(column, false), isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereRaw(String sql, [List<dynamic> rawBindings = const []]) {\n    String processedSQL = _processRawSQL(sql, rawBindings);\n    _appendCondition(processedSQL, isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereRowValues(\n    List<String> columns,\n    String operator,\n    List<dynamic> values,\n  ) {\n    _appendCondition(\n      _createRowValuesCondition(columns, operator, values),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereTime(String column, String operator, dynamic value) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"TIME\"),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereYear(String column, String operator, dynamic value) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"YEAR\"),\n      isOr: true,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder where(\n    dynamic condition, [\n    String operator = '=',\n    dynamic value,\n    String boolean = 'and',\n  ]) {\n    if (condition is String) {\n      _validateOperator(operator);\n      final paramName = _nextParamName();\n      bindings[paramName] = value;\n      _appendCondition(\n        \"$condition $operator :$paramName\",\n        isOr: (boolean.toLowerCase() == 'or'),\n      );\n    } else if (condition is QueryCallback) {\n      QueryBuilderImpl nested = QueryBuilderImpl()\n        ..paramCounter = _paramCounter;\n      condition(nested);\n      _appendCondition(\n        \"(${nested.toSql()})\",\n        isOr: (boolean.toLowerCase() == 'or'),\n      );\n      bindings.addAll(nested.getBindings());\n    } else {\n      throw InvalidArgumentException(\n        'Invalid argument type for condition. Expected either a String or a QueryBuilder instance',\n      );\n    }\n    return this;\n  }\n\n  @override\n  QueryBuilder whereAfterToday(String column, {String boolean = 'and'}) {\n    _appendCondition(\n      \"DATE($column) > CURDATE()\",\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereAll(\n    String column,\n    List<dynamic> values, {\n    String boolean = 'and',\n  }) {\n    if (values.isEmpty) {\n      throw InvalidArgumentException(\"The list of values must not be empty.\");\n    }\n\n    List<String> conditions = [];\n    for (var value in values) {\n      final paramName = _nextParamName();\n      bindings[paramName] = value;\n      conditions.add(\"$column = :$paramName\");\n    }\n\n    _appendCondition(\n      conditions.join(\" AND \"),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereAny(\n    String column,\n    List<dynamic> values, {\n    String boolean = 'and',\n  }) {\n    if (values.isEmpty) {\n      throw InvalidArgumentException(\"The list of values must not be empty.\");\n    }\n\n    _appendCondition(\n      _createInCondition(column, values, false),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereBeforeToday(String column, {String boolean = 'and'}) {\n    _appendCondition(\n      \"DATE($column) < CURDATE()\",\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereBetween(\n    String column,\n    List values, {\n    String boolean = 'and',\n    bool not = false,\n  }) {\n    _appendCondition(\n      _createBetweenCondition(column, values, not),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereBetweenColumns(\n    String column,\n    List<String> columns, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createBetweenColumnsCondition(column, columns),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereColumn(\n    String firstColumn,\n    String operator,\n    String secondColumn, [\n    String boolean = 'and',\n  ]) {\n    String condition = \"$firstColumn $operator $secondColumn\";\n    _appendCondition(condition, isOr: (boolean.toLowerCase() == 'or'));\n    return this;\n  }\n\n  @override\n  QueryBuilder whereDate(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"DATE\"),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereDay(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"DAY\"),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereEqualTo(condition, [value, String boolean = 'and']) =>\n      where(condition, '=', value, boolean);\n\n  @override\n  QueryBuilder whereExists(\n    QueryCallback callback, {\n    String boolean = 'and',\n    bool not = false,\n  }) {\n    QueryBuilder subQuery = QueryBuilderImpl();\n    callback(subQuery);\n    String condition = \"${not ? 'NOT EXISTS' : 'EXISTS'} (${subQuery.toSql()})\";\n    _appendCondition(condition, isOr: (boolean.toLowerCase() == 'or'));\n    bindings.addAll(subQuery.getBindings());\n    return this;\n  }\n\n  @override\n  QueryBuilder whereFullText(\n    dynamic columns,\n    dynamic query, [\n    Map<String, dynamic> options = const {},\n  ]) {\n    _appendCondition(\n      _createFullTextCondition(columns, query, options),\n      isOr: false,\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereFuture(String column, {String boolean = 'and'}) {\n    _appendCondition(\"$column > NOW()\", isOr: (boolean.toLowerCase() == 'or'));\n    return this;\n  }\n\n  @override\n  QueryBuilder whereGreaterThan(condition, [value, String boolean = 'and']) =>\n      where(condition, '>', value, boolean);\n\n  @override\n  QueryBuilder whereGreaterThanOrEqualTo(\n    condition, [\n    value,\n    String boolean = 'and',\n  ]) => where(condition, '>=', value, boolean);\n\n  @override\n  QueryBuilder whereHour(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createHourCondition(column, operator, value),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereIn(\n    String column,\n    List values, {\n    String boolean = 'and',\n    bool not = false,\n  }) {\n    _appendCondition(\n      _createInCondition(column, values, not),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereJsonContains(\n    String column,\n    dynamic value, {\n    String boolean = 'and',\n    bool not = false,\n  }) {\n    _appendCondition(\n      _createJsonContainsCondition(column, value, not),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereJsonDoesntContain(\n    String column,\n    dynamic value, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createJsonContainsCondition(column, value, true),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereJsonLength(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createJsonLengthCondition(column, operator, value),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereLessThan(condition, [value, String boolean = 'and']) =>\n      where(condition, '<', value, boolean);\n\n  @override\n  QueryBuilder whereLessThanOrEqualTo(\n    condition, [\n    value,\n    String boolean = 'and',\n  ]) => where(condition, '<=', value, boolean);\n\n  @override\n  QueryBuilder whereLike(\n    String column,\n    dynamic value, {\n    bool caseSensitive = false,\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createLikeCondition(\n        column,\n        value,\n        not: false,\n        caseSensitive: caseSensitive,\n      ),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereMonth(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"MONTH\"),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereNone(\n    String column,\n    List<dynamic> values, {\n    String boolean = 'and',\n  }) {\n    if (values.isEmpty) {\n      throw InvalidArgumentException(\"The list of values must not be empty.\");\n    }\n\n    _appendCondition(\n      _createInCondition(column, values, true),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereNotBetween(\n    String column,\n    List values, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createBetweenCondition(column, values, true),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereNotBetweenColumns(\n    String column,\n    List<String> columns, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createBetweenColumnsCondition(column, columns, not: true),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereNotEqualTo(condition, [value, String boolean = 'and']) =>\n      where(condition, '<>', value, boolean);\n\n  @override\n  QueryBuilder whereNotExists(\n    QueryCallback callback, {\n    String boolean = 'and',\n  }) {\n    return whereExists(callback, boolean: boolean, not: true);\n  }\n\n  @override\n  QueryBuilder whereNotIn(\n    String column,\n    dynamic values, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createInCondition(column, values, true),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereNotLike(\n    String column,\n    dynamic value, {\n    bool caseSensitive = false,\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createLikeCondition(\n        column,\n        value,\n        not: true,\n        caseSensitive: caseSensitive,\n      ),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereNotNull(String column, {String boolean = 'and'}) {\n    _appendCondition(\n      _createNullCondition(column, true),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereNowOrFuture(String column, {String boolean = 'and'}) {\n    _appendCondition(\"$column >= NOW()\", isOr: (boolean.toLowerCase() == 'or'));\n    return this;\n  }\n\n  @override\n  QueryBuilder whereNowOrPast(String column, {String boolean = 'and'}) {\n    _appendCondition(\"$column <= NOW()\", isOr: (boolean.toLowerCase() == 'or'));\n    return this;\n  }\n\n  @override\n  QueryBuilder whereNull(\n    String column, {\n    String boolean = 'and',\n    bool not = false,\n  }) {\n    _appendCondition(\n      _createNullCondition(column, not),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder wherePast(String column, {String boolean = 'and'}) {\n    _appendCondition(\"$column < NOW()\", isOr: (boolean.toLowerCase() == 'or'));\n    return this;\n  }\n\n  @override\n  QueryBuilder whereRaw(\n    String sql, [\n    List<dynamic> rawBindings = const [],\n    String boolean = 'and',\n  ]) {\n    String processedSQL = _processRawSQL(sql, rawBindings);\n    _appendCondition(processedSQL, isOr: (boolean.toLowerCase() == 'or'));\n    return this;\n  }\n\n  @override\n  QueryBuilder whereRowValues(\n    List<String> columns,\n    String operator,\n    List<dynamic> values, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createRowValuesCondition(columns, operator, values),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereTime(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"TIME\"),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereToday(String column, {String boolean = 'and'}) {\n    _appendCondition(\n      \"DATE($column) = CURDATE()\",\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereTodayOrAfter(String column, {String boolean = 'and'}) {\n    _appendCondition(\n      \"DATE($column) >= CURDATE()\",\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereTodayOrBefore(String column, {String boolean = 'and'}) {\n    _appendCondition(\n      \"DATE($column) <= CURDATE()\",\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereYear(\n    String column,\n    String operator,\n    dynamic value, {\n    String boolean = 'and',\n  }) {\n    _appendCondition(\n      _createDateCondition(column, operator, value, \"YEAR\"),\n      isOr: (boolean.toLowerCase() == 'or'),\n    );\n    return this;\n  }\n\n  @override\n  QueryBuilder whereHas(\n    String relation,\n    QueryCallback callback, {\n    String boolean = 'and',\n  }) {\n    String condition = _createRelationshipCondition(relation, callback, false);\n    _appendCondition(condition, isOr: (boolean.toLowerCase() == 'or'));\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereHas(String relation, QueryCallback callback) {\n    String condition = _createRelationshipCondition(relation, callback, false);\n    _appendCondition(condition, isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder whereDoesntHave(\n    String relation,\n    QueryCallback callback, {\n    String boolean = 'and',\n  }) {\n    String condition = _createRelationshipCondition(relation, callback, true);\n    _appendCondition(condition, isOr: (boolean.toLowerCase() == 'or'));\n    return this;\n  }\n\n  @override\n  QueryBuilder orWhereDoesntHave(String relation, QueryCallback callback) {\n    String condition = _createRelationshipCondition(relation, callback, true);\n    _appendCondition(condition, isOr: true);\n    return this;\n  }\n\n  @override\n  QueryBuilder withSoftDeletes([String column = 'deleted_at']) =>\n      whereNull(column);\n\n  String _createRelationshipCondition(\n    String relation,\n    QueryCallback callback,\n    bool not,\n  ) {\n    if (relation.isEmpty) {\n      throw InvalidArgumentException(\n        'Relation name cannot be empty for relationship queries.',\n      );\n    }\n\n    QueryBuilder subQuery = QueryBuilderImpl();\n\n    subQuery.table(relation);\n\n    callback(subQuery);\n\n    String currentTable = getTable.split(' ').first;\n    String foreignKey = '${Singularize.make(currentTable)}_id';\n\n    subQuery.whereColumn('$relation.$foreignKey', '=', '$currentTable.id');\n\n    String subQuerySQL = subQuery.toSql();\n    bindings.addAll(subQuery.getBindings());\n\n    String existsClause = not ? 'NOT EXISTS' : 'EXISTS';\n    return \"$existsClause (SELECT 1 FROM $relation WHERE $relation.$foreignKey = $currentTable.id AND (${_extractWhereFromSubQuery(subQuerySQL)}))\";\n  }\n\n  String _extractWhereFromSubQuery(String sql) {\n    int whereIndex = sql.indexOf('WHERE');\n    if (whereIndex != -1) {\n      return sql.substring(whereIndex + 5).trim();\n    }\n    return '1=1';\n  }\n\n  void _appendCondition(String condition, {bool isOr = false}) {\n    if (conditions.isEmpty) {\n      conditions.add(condition);\n    } else {\n      String joiner = isOr ? \"OR\" : \"AND\";\n      conditions.add(\"$joiner $condition\");\n    }\n  }\n\n  String _createBetweenColumnsCondition(\n    String column,\n    List<String> columns, {\n    bool not = false,\n  }) {\n    if (columns.length < 2) {\n      throw InvalidArgumentException(\n        'At least two columns must be provided for whereBetweenColumns.',\n      );\n    }\n    String op = not ? \"NOT BETWEEN\" : \"BETWEEN\";\n    return \"$column $op ${columns[0]} AND ${columns[1]}\";\n  }\n\n  String _createBetweenCondition(String column, List values, bool not) {\n    if (values.length < 2) {\n      throw InvalidArgumentException(\n        'The list of values must contain at least two items.',\n      );\n    }\n\n    final paramName1 = _nextParamName();\n    final paramName2 = _nextParamName();\n    bindings[paramName1] = values[0];\n    bindings[paramName2] = values[1];\n\n    return \"$column ${not ? 'NOT BETWEEN' : 'BETWEEN'} :$paramName1 AND :$paramName2\";\n  }\n\n  String _createDateCondition(\n    String column,\n    String operator,\n    dynamic value,\n    String function,\n  ) {\n    if (value == null) {\n      throw InvalidArgumentException(\n        'The value for the function $function must not be null.',\n      );\n    }\n\n    final paramName = _nextParamName();\n    bindings[paramName] = value;\n\n    return \"$function($column) $operator :$paramName\";\n  }\n\n  String _createFullTextCondition(\n    dynamic columns,\n    dynamic query,\n    Map<String, dynamic> options,\n  ) {\n    String colStr;\n    if (columns is List) {\n      colStr = columns.join(\", \");\n    } else {\n      colStr = columns.toString();\n    }\n\n    final paramName = _nextParamName();\n    bindings[paramName] = query;\n\n    String mode = \"\";\n    if (options.containsKey('mode')) {\n      mode = \" IN ${options['mode']} MODE\";\n    }\n\n    return \"MATCH($colStr) AGAINST(:$paramName$mode)\";\n  }\n\n  String _createHourCondition(String column, String operator, dynamic value) {\n    if (value == null) {\n      throw InvalidArgumentException(\n        'The value for whereHour must not be null.',\n      );\n    }\n\n    final paramName = _nextParamName();\n    bindings[paramName] = value;\n\n    return \"HOUR($column) $operator :$paramName\";\n  }\n\n  String _createInCondition(String column, dynamic values, bool not) {\n    String clause = not ? \"NOT IN\" : \"IN\";\n\n    if (values is List) {\n      List<String> paramNames = [];\n      for (var i = 0; i < values.length; i++) {\n        final paramName = _nextParamName();\n        bindings[paramName] = values[i];\n        paramNames.add(\":$paramName\");\n      }\n\n      return \"$column $clause (${paramNames.join(\", \")})\";\n    } else if (values is QueryBuilder) {\n      final subQuery = values.toSql();\n      bindings.addAll((values as dynamic).getBindings());\n      return \"$column $clause ($subQuery)\";\n    } else {\n      throw InvalidArgumentException(\n        \"The value for 'values' must be of type List or QueryBuilder.\",\n      );\n    }\n  }\n\n  String _createJsonContainsCondition(String column, dynamic value, bool not) {\n    final paramName = _nextParamName();\n    bindings[paramName] = value;\n\n    String condition = \"JSON_CONTAINS($column, :$paramName)\";\n    if (not) {\n      condition = \"NOT $condition\";\n    }\n    return condition;\n  }\n\n  String _createJsonLengthCondition(\n    String column,\n    String operator,\n    dynamic value,\n  ) {\n    final paramName = _nextParamName();\n    bindings[paramName] = value;\n\n    return \"JSON_LENGTH($column) $operator :$paramName\";\n  }\n\n  String _createLikeCondition(\n    String column,\n    dynamic value, {\n    bool not = false,\n    bool caseSensitive = false,\n  }) {\n    final paramName = _nextParamName();\n    bindings[paramName] = value;\n\n    String operator = not ? \"NOT LIKE\" : \"LIKE\";\n    if (!caseSensitive) {\n      return \"LOWER($column) $operator LOWER(:$paramName)\";\n    }\n    return \"$column $operator :$paramName\";\n  }\n\n  String _createNullCondition(String column, bool not) {\n    return \"$column IS ${not ? 'NOT ' : ''}NULL\";\n  }\n\n  String _createRowValuesCondition(\n    List<String> columns,\n    String operator,\n    List<dynamic> values,\n  ) {\n    if (columns.length != values.length) {\n      throw InvalidArgumentException(\n        \"The number of columns and values must be equal.\",\n      );\n    }\n\n    List<String> paramNames = [];\n    for (var i = 0; i < values.length; i++) {\n      final paramName = _nextParamName();\n      bindings[paramName] = values[i];\n      paramNames.add(\":$paramName\");\n    }\n\n    String cols = \"(${columns.join(\", \")})\";\n    String vals = \"(${paramNames.join(\", \")})\";\n\n    return \"$cols $operator $vals\";\n  }\n\n  String _processRawSQL(String sql, List<dynamic> rawBindings) {\n    String processed = sql;\n    for (var binding in rawBindings) {\n      final paramName = _nextParamName();\n      bindings[paramName] = binding;\n      processed = processed.replaceFirst('?', ':$paramName');\n    }\n\n    return processed;\n  }\n}\n"
  },
  {
    "path": "lib/src/database/query_builder/_window_functions_builder_impl.dart",
    "content": "import 'package:meta/meta.dart';\n\nimport '../../contract/database/query_builder/query_builder.dart'\n    show QueryBuilder;\nimport '../../exception/invalid_argument_exception.dart';\n\nabstract mixin class WindowFunctionsBuilderImpl implements QueryBuilder {\n  @protected\n  final List<String> _windowFunctions = [];\n\n  String _buildOverClause({String? partitionBy, String? orderBy}) {\n    List<String> overParts = [];\n\n    if (partitionBy != null && partitionBy.isNotEmpty) {\n      overParts.add(\"PARTITION BY $partitionBy\");\n    }\n\n    if (orderBy != null && orderBy.isNotEmpty) {\n      overParts.add(\"ORDER BY $orderBy\");\n    }\n\n    return \"OVER (${overParts.join(' ')})\";\n  }\n\n  void _addWindowFunction(\n    String functionName,\n    String overClause,\n    String? alias,\n  ) {\n    String windowFunc = \"$functionName $overClause\";\n    if (alias != null && alias.isNotEmpty) {\n      windowFunc += \" AS $alias\";\n    }\n\n    selectColumns.add(windowFunc);\n    _windowFunctions.add(windowFunc);\n  }\n\n  @override\n  QueryBuilder rowNumber({String? partitionBy, String? orderBy, String? as}) {\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"ROW_NUMBER()\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder rank({String? partitionBy, String? orderBy, String? as}) {\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"RANK()\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder denseRank({String? partitionBy, String? orderBy, String? as}) {\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"DENSE_RANK()\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder lag(\n    String column, {\n    int offset = 1,\n    dynamic defaultValue,\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (column.isEmpty) {\n      throw InvalidArgumentException(\n        'Column name cannot be empty for LAG function',\n      );\n    }\n\n    String lagFunc = \"LAG($column, $offset\";\n    if (defaultValue != null) {\n      if (defaultValue is String) {\n        lagFunc += \", '$defaultValue'\";\n      } else {\n        lagFunc += \", $defaultValue\";\n      }\n    }\n    lagFunc += \")\";\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(lagFunc, overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder lead(\n    String column, {\n    int offset = 1,\n    dynamic defaultValue,\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (column.isEmpty) {\n      throw InvalidArgumentException(\n        'Column name cannot be empty for LEAD function',\n      );\n    }\n\n    String leadFunc = \"LEAD($column, $offset\";\n    if (defaultValue != null) {\n      if (defaultValue is String) {\n        leadFunc += \", '$defaultValue'\";\n      } else {\n        leadFunc += \", $defaultValue\";\n      }\n    }\n    leadFunc += \")\";\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(leadFunc, overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder firstValue(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (column.isEmpty) {\n      throw InvalidArgumentException(\n        'Column name cannot be empty for FIRST_VALUE function',\n      );\n    }\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"FIRST_VALUE($column)\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder lastValue(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (column.isEmpty) {\n      throw InvalidArgumentException(\n        'Column name cannot be empty for LAST_VALUE function',\n      );\n    }\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"LAST_VALUE($column)\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder ntile(\n    int buckets, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (buckets <= 0) {\n      throw InvalidArgumentException(\n        'Number of buckets must be greater than 0',\n      );\n    }\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"NTILE($buckets)\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder percentRank({String? partitionBy, String? orderBy, String? as}) {\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"PERCENT_RANK()\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder cumeDist({String? partitionBy, String? orderBy, String? as}) {\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"CUME_DIST()\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder windowSum(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (column.isEmpty) {\n      throw InvalidArgumentException(\n        'Column name cannot be empty for SUM window function',\n      );\n    }\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"SUM($column)\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder windowAvg(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (column.isEmpty) {\n      throw InvalidArgumentException(\n        'Column name cannot be empty for AVG window function',\n      );\n    }\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"AVG($column)\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder windowCount(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (column.isEmpty) {\n      throw InvalidArgumentException(\n        'Column name cannot be empty for COUNT window function',\n      );\n    }\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"COUNT($column)\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder windowMax(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (column.isEmpty) {\n      throw InvalidArgumentException(\n        'Column name cannot be empty for MAX window function',\n      );\n    }\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"MAX($column)\", overClause, as);\n    return this;\n  }\n\n  @override\n  QueryBuilder windowMin(\n    String column, {\n    String? partitionBy,\n    String? orderBy,\n    String? as,\n  }) {\n    if (column.isEmpty) {\n      throw InvalidArgumentException(\n        'Column name cannot be empty for MIN window function',\n      );\n    }\n\n    final overClause = _buildOverClause(\n      partitionBy: partitionBy,\n      orderBy: orderBy,\n    );\n    _addWindowFunction(\"MIN($column)\", overClause, as);\n    return this;\n  }\n\n  @protected\n  void clearWindowFunctions() {\n    _windowFunctions.clear();\n  }\n}\n"
  },
  {
    "path": "lib/src/database/seeder/seeder.dart",
    "content": "import 'package:meta/meta.dart';\n\nabstract class Seeder {\n  @mustBeOverridden\n  Future<void> run();\n}\n"
  },
  {
    "path": "lib/src/database/seeder/seeder_factory.dart",
    "content": "import 'dart:io';\nimport 'dart:math';\n\nimport 'package:meta/meta.dart';\n\nabstract class SeederFactory {\n  final Random _random = Random();\n\n  @mustBeOverridden\n  Map<String, dynamic> definition();\n\n  Map<String, dynamic> make([Map<String, dynamic>? attributes]) {\n    final data = definition();\n    if (attributes != null) {\n      data.addAll(data);\n    }\n\n    return data;\n  }\n\n  List<Map<String, dynamic>> makeMany(\n    int count, [\n    Map<String, dynamic>? attributes,\n  ]) {\n    return List.generate(count, (index) => make(attributes));\n  }\n\n  Map<String, dynamic> create([Map<String, dynamic>? attributes]) {\n    return make(attributes);\n  }\n\n  List<Map<String, dynamic>> createMany(\n    int count, [\n    Map<String, dynamic>? attributes,\n  ]) {\n    return makeMany(count, attributes);\n  }\n\n  int randomInt(int min, int max) {\n    return min + _random.nextInt(max - min + 1);\n  }\n\n  double randomDouble(double min, double max) {\n    return min + _random.nextDouble() * (max - min);\n  }\n\n  bool randomBool() {\n    return _random.nextBool();\n  }\n\n  T randomElement<T>(List<T> list) {\n    if (list.isEmpty) {\n      stderr.write('List cannot be empty');\n      exit(0);\n    }\n    return list[_random.nextInt(list.length)];\n  }\n\n  String randomString(\n    int length, {\n    bool includeNumbers = true,\n    bool includeSymbols = false,\n  }) {\n    const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';\n    const numbers = '0123456789';\n    const symbols = '!@#\\$%^&*()_+-=[]{}|;:,.<>?';\n\n    String chars = letters;\n    if (includeNumbers) chars += numbers;\n    if (includeSymbols) chars += symbols;\n\n    return String.fromCharCodes(\n      Iterable.generate(\n        length,\n        (_) => chars.codeUnitAt(_random.nextInt(chars.length)),\n      ),\n    );\n  }\n\n  String randomEmail() {\n    final domains = [\n      'gmail.com',\n      'yahoo.com',\n      'hotmail.com',\n      'outlook.com',\n      'aol.com',\n      'icloud.com',\n      'protonmail.com',\n      'mail.com',\n      'yandex.com',\n      'zoho.com',\n      'gmx.com',\n      'live.com',\n      'msn.com',\n      'fastmail.com',\n      'tutanota.com',\n    ];\n    final username = randomString(\n      8,\n      includeNumbers: true,\n      includeSymbols: false,\n    ).toLowerCase();\n    final domain = randomElement(domains);\n    return '$username@$domain';\n  }\n\n  String randomName() {\n    final firstNames = [\n      'John',\n      'Jane',\n      'Michael',\n      'Sarah',\n      'David',\n      'Emily',\n      'Robert',\n      'Jessica',\n      'William',\n      'Ashley',\n      'James',\n      'Amanda',\n      'Christopher',\n      'Stephanie',\n      'Daniel',\n      'Melissa',\n      'Matthew',\n      'Nicole',\n      'Anthony',\n      'Elizabeth',\n      'Dorothy',\n      'Jerry',\n      'Helen',\n      'Tyler',\n      'Sandra',\n      'Aaron',\n      'Donna',\n      'Jose',\n      'Carol',\n      'Henry',\n      'Ruth',\n      'Douglas',\n      'Sharon',\n      'Zachary',\n      'Michelle',\n      'Nathan',\n      'Laura',\n      'Peter',\n      'Sarah',\n      'Kyle',\n      'Kimberly',\n      'Noah',\n      'Deborah',\n      'Jeremy',\n      'Dorothy',\n      'Carl',\n      'Lisa',\n      'Arthur',\n      'Nancy',\n      'Lawrence',\n      'Karen',\n      'Sean',\n      'Betty',\n      'Christian',\n      'Helen',\n      'Austin',\n      'Sandra',\n      'Wayne',\n      'Donna',\n      'Louis',\n      'Carol',\n      'Philip',\n      'Ruth',\n      'Eugene',\n      'Sharon',\n      'Ralph',\n      'Michelle',\n      'Roy',\n      'Laura',\n      'Billy',\n      'Emily',\n      'Bruce',\n      'Kimberly',\n      'Willie',\n      'Deborah',\n      'Jordan',\n      'Amy',\n      'Mason',\n      'Angela',\n      'Ethan',\n      'Brenda',\n      'Liam',\n      'Emma',\n      'Noah',\n      'Olivia',\n      'Oliver',\n      'Ava',\n      'Elijah',\n      'Sophia',\n      'Lucas',\n      'Isabella',\n      'Logan',\n      'Mia',\n      'Owen',\n      'Charlotte',\n      'Aiden',\n      'Amelia',\n      'Carter',\n      'Harper',\n      'Sebastian',\n      'Evelyn',\n    ];\n    final lastNames = [\n      'Smith',\n      'Johnson',\n      'Williams',\n      'Brown',\n      'Jones',\n      'Garcia',\n      'Miller',\n      'Davis',\n      'Rodriguez',\n      'Martinez',\n      'Hernandez',\n      'Lopez',\n      'Gonzalez',\n      'Wilson',\n      'Anderson',\n      'Thomas',\n      'Taylor',\n      'Moore',\n      'Jackson',\n      'Martin',\n      'Thomas',\n      'Taylor',\n      'Moore',\n      'Jackson',\n      'Martin',\n      'Lee',\n      'Perez',\n      'Thompson',\n      'White',\n      'Harris',\n      'Sanchez',\n      'Clark',\n      'Ramirez',\n      'Lewis',\n      'Robinson',\n      'Walker',\n      'Young',\n      'Allen',\n      'King',\n      'Wright',\n      'Scott',\n      'Torres',\n      'Nguyen',\n      'Hill',\n      'Flores',\n      'Green',\n      'Adams',\n      'Nelson',\n      'Baker',\n      'Hall',\n      'Rivera',\n      'Campbell',\n      'Mitchell',\n      'Carter',\n      'Roberts',\n      'Gomez',\n      'Phillips',\n      'Evans',\n      'Turner',\n      'Diaz',\n      'Parker',\n      'Cruz',\n      'Edwards',\n      'Collins',\n      'Reyes',\n      'Stewart',\n      'Morris',\n      'Morales',\n      'Murphy',\n      'Cook',\n      'Rogers',\n      'Gutierrez',\n      'Ortiz',\n      'Morgan',\n      'Cooper',\n      'Peterson',\n      'Bailey',\n      'Reed',\n      'Kelly',\n      'Howard',\n      'Ramos',\n      'Kim',\n      'Cox',\n      'Ward',\n      'Richardson',\n      'Watson',\n      'Brooks',\n      'Chavez',\n      'Wood',\n      'James',\n      'Bennett',\n      'Gray',\n      'Mendoza',\n      'Ruiz',\n      'Hughes',\n      'Price',\n      'Alvarez',\n      'Castillo',\n      'Sanders',\n      'Patel',\n      'Myers',\n      'Long',\n      'Ross',\n      'Foster',\n      'Jimenez',\n      'Powell',\n      'Jenkins',\n      'Perry',\n      'Russell',\n      'Sullivan',\n      'Bell',\n      'Coleman',\n      'Butler',\n      'Henderson',\n      'Barnes',\n      'Gonzales',\n      'Fisher',\n      'Vasquez',\n      'Simmons',\n      'Romero',\n      'Jordan',\n      'Patterson',\n      'Alexander',\n      'Hamilton',\n      'Graham',\n      'Reynolds',\n    ];\n\n    return '${randomElement(firstNames)} ${randomElement(lastNames)}';\n  }\n\n  String randomPhone() {\n    return '+1${randomInt(100, 999)}${randomInt(100, 999)}${randomInt(1000, 9999)}';\n  }\n\n  DateTime randomDate(DateTime start, DateTime end) {\n    final diff = end.difference(start).inDays;\n    final randomDays = _random.nextInt(diff + 1);\n    return start.add(Duration(days: randomDays));\n  }\n\n  DateTime randomPastDate([int maxDaysAgo = 365]) {\n    final now = DateTime.now();\n    final daysAgo = _random.nextInt(maxDaysAgo + 1);\n    return now.subtract(Duration(days: daysAgo));\n  }\n\n  DateTime randomFutureDate([int maxDaysFromNow = 365]) {\n    final now = DateTime.now();\n    final daysFromNow = _random.nextInt(maxDaysFromNow + 1);\n    return now.add(Duration(days: daysFromNow));\n  }\n\n  String randomUuid() {\n    return '${randomString(8)}-${randomString(4)}-${randomString(4)}-${randomString(4)}-${randomString(12)}';\n  }\n\n  String randomText([int sentences = 3]) {\n    final words = [\n      'lorem',\n      'ipsum',\n      'dolor',\n      'sit',\n      'amet',\n      'consectetur',\n      'adipiscing',\n      'elit',\n      'sed',\n      'do',\n      'eiusmod',\n      'tempor',\n      'incididunt',\n      'ut',\n      'labore',\n      'et',\n      'dolore',\n      'magna',\n      'aliqua',\n      'enim',\n      'ad',\n      'minim',\n      'veniam',\n      'quis',\n      'nostrud',\n      'exercitation',\n      'ullamco',\n      'laboris',\n      'nisi',\n      'aliquip',\n      'ex',\n      'ea',\n      'commodo',\n      'consequat',\n      'duis',\n      'aute',\n      'irure',\n      'in',\n      'reprehenderit',\n      'voluptate',\n      'velit',\n      'esse',\n      'cillum',\n      'fugiat',\n      'nulla',\n      'pariatur',\n      'excepteur',\n      'sint',\n      'occaecat',\n      'cupidatat',\n      'non',\n      'proident',\n      'sunt',\n      'culpa',\n      'qui',\n      'officia',\n      'deserunt',\n      'mollit',\n      'anim',\n      'id',\n      'est',\n      'laborum',\n    ];\n\n    final result = <String>[];\n    for (int i = 0; i < sentences; i++) {\n      final sentenceLength = randomInt(5, 15);\n      final sentence = List.generate(\n        sentenceLength,\n        (_) => randomElement(words),\n      );\n      sentence[0] = sentence[0][0].toUpperCase() + sentence[0].substring(1);\n      result.add('${sentence.join(' ')}.');\n    }\n\n    return result.join(' ');\n  }\n\n  double randomPrice([\n    double min = 1.0,\n    double max = 1000.0,\n    int decimals = 2,\n  ]) {\n    return double.parse(randomDouble(min, max).toStringAsFixed(decimals));\n  }\n\n  String randomStatus([List<String>? statuses]) {\n    statuses ??= ['active', 'inactive', 'pending', 'completed'];\n    return randomElement(statuses);\n  }\n}\n"
  },
  {
    "path": "lib/src/database/seeder/seeder_runner.dart",
    "content": "import 'dart:io';\n\nimport '../../env_handler/env.dart';\nimport '../../exception/query_exception.dart';\nimport '../../utils/functions.dart';\nimport '../_connection_manager.dart';\nimport '../_database_utils/_db_config.dart';\nimport 'seeder.dart';\n\nclass SeederRunner {\n  static final SeederRunner _singleton = SeederRunner._internal();\n\n  factory SeederRunner() {\n    Env().load();\n    return _singleton;\n  }\n\n  SeederRunner._internal();\n\n  DBConfig _config(Map<String, dynamic> database) => DBConfig(\n    driver: database['driver'] ?? '',\n    host: database['host'] ?? '',\n    port: database['port'] ?? '',\n    database: database['database'] ?? '',\n    username: database['username'] ?? '',\n    password: database['password'] ?? '',\n    sslMode: database['sslmode'] ?? '',\n    collation: database['collation'] ?? '',\n    pool: database['pool'] ?? false,\n    poolSize: database['poolsize'] ?? 0,\n    filePath: database['file_path'] ?? '',\n    openInMemorySQLite: database['openInMemorySQLite'] ?? false,\n  );\n\n  Future<void> setup({\n    required Map<String, dynamic> database,\n    required List<Seeder> seeders,\n  }) async {\n    if (database['default'] == null) {\n      stderr.write('❌ Database config not valid');\n      exit(1);\n    }\n    try {\n      ConnectionManager().defaultConnection = database['default'];\n      Map<String, dynamic> connections = database['connections'];\n      await ConnectionManager().connect(\n        _config(connections[ConnectionManager().defaultConnection]),\n        database['default'],\n      );\n    } catch (e) {\n      stderr.write(e);\n      exit(1);\n    } finally {\n      for (Seeder seeder in seeders) {\n        final stopwatch = Stopwatch()..start();\n        try {\n          await seeder.run();\n          stopwatch.stop();\n          stderr.writeln(\n            ' Seeder ${toSnakeCase(seeder.runtimeType.toString())} executed ....................................\\x1B[32m ${stopwatch.elapsedMilliseconds}ms DONE\\x1B[0m',\n          );\n        } on QueryException catch (e) {\n          stopwatch.stop();\n          stderr.write(e.cause);\n          await ConnectionManager().connection(database['default'])!.close();\n          exit(1);\n        }\n      }\n      await ConnectionManager().connection(database['default'])!.close();\n      stderr.write(\n        '\\x1B[32m All database seeders executed successfully \\x1B[0m',\n      );\n      exit(0);\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/enum/column_index.dart",
    "content": "enum ColumnIndex { unique, indexKey, fulltext, spatial }\n"
  },
  {
    "path": "lib/src/enum/http_request_method.dart",
    "content": "enum HttpRequestMethod {\n  get,\n  post,\n  put,\n  patch,\n  delete,\n  purge,\n  options,\n  copy,\n  view,\n  link,\n  unlink,\n  lock,\n  unlock,\n  propfind,\n}\n"
  },
  {
    "path": "lib/src/env_handler/env.dart",
    "content": "import 'dart:io';\n\nclass Env {\n  static final Env _singleton = Env._internal();\n\n  factory Env() {\n    return _singleton;\n  }\n\n  Map<String, String> env = <String, String>{};\n\n  Env._internal();\n\n  void load({File? file}) {\n    if (env.isEmpty) {\n      env = _loadEnvFile(file: file);\n    }\n  }\n\n  /// get env value\n  /// ```\n  /// Evn.get('APP_KEY');\n  /// Evn.get('APP_KEY', 'Default Value');\n  /// Evn.get<int>('PORT', 3000);\n  /// Evn.get<num>('PORT', 3000);\n  /// Evn.get<String>('APP_KEY');\n  /// ```\n  static T get<T>(String key, [dynamic defaultValue]) {\n    dynamic value = Env().env[key];\n    value ??= Platform.environment[key];\n    value ??= defaultValue;\n    if (T.toString() == 'int') {\n      return int.parse(value.toString()) as T;\n    }\n\n    if (T.toString() == 'double') {\n      return double.parse(value.toString()) as T;\n    }\n\n    if (T.toString() == 'num') {\n      return num.parse(value.toString()) as T;\n    }\n\n    if (T.toString() == 'bool') {\n      return bool.parse(value.toString()) as T;\n    }\n\n    return value;\n  }\n\n  /// load env from .env of project directory\n  Map<String, String> _loadEnvFile({File? file}) {\n    Map<String, String> data = <String, String>{};\n\n    File envFile = file ?? File('.env');\n    if (!envFile.existsSync()) return data;\n    String contents = envFile.readAsStringSync();\n    // splitting with new line for each variables\n    List<String> list = contents.split('\\n');\n\n    for (String d in list) {\n      // splitting with equal sign to get key and value\n      List<String> keyValue = d.toString().split('=');\n      if (keyValue.first.isNotEmpty) {\n        data[keyValue.first.trim()] = _getValue(keyValue);\n      }\n    }\n\n    return data;\n  }\n\n  String _getValue(List<String> elements) {\n    if (elements.length > 1) {\n      List<String> elementsExceptFirst = elements.sublist(1);\n      String value = elementsExceptFirst.join('=');\n      return value\n          .replaceAll('\"', '')\n          .replaceAll(\"'\", '')\n          .replaceAll('`', '')\n          .trim();\n    }\n    return '';\n  }\n}\n"
  },
  {
    "path": "lib/src/exception/base_http_exception.dart",
    "content": "import 'package:vania/src/http/response/response.dart';\n\nclass BaseHttpResponseException {\n  final dynamic message;\n  final ResponseType responseType;\n  final int code;\n\n  const BaseHttpResponseException({\n    required this.message,\n    required this.code,\n    this.responseType = ResponseType.json,\n  });\n\n  Response response(bool isHtml) => isHtml\n      ? Response.html(message)\n      : Response.json(message is Map ? message : {'message': message}, code);\n}\n"
  },
  {
    "path": "lib/src/exception/database_exception.dart",
    "content": "class DatabaseException implements Exception {\n  final String message;\n  final dynamic cause;\n\n  DatabaseException(this.message, [this.cause]);\n}\n"
  },
  {
    "path": "lib/src/exception/exception_handler.dart",
    "content": "import 'package:vania/src/http/request/request.dart';\nimport 'package:vania/src/http/response/response.dart';\n\nabstract class ExceptionHandler<T> {\n  const ExceptionHandler();\n\n  Response handle(T exception, Request? request);\n}\n\nabstract class GeneralExceptionHandler {\n  const GeneralExceptionHandler();\n\n  Response? handle(dynamic exception, Request? request);\n}\n"
  },
  {
    "path": "lib/src/exception/forbidden_exception.dart",
    "content": "import 'dart:io';\n\nimport 'package:vania/src/exception/base_http_exception.dart';\nimport 'package:vania/src/http/response/response.dart';\n\nclass ForbiddenException extends BaseHttpResponseException {\n  ForbiddenException({\n    super.message = 'Forbidden',\n    super.code = HttpStatus.forbidden,\n    super.responseType = ResponseType.json,\n  });\n}\n"
  },
  {
    "path": "lib/src/exception/http_exception.dart",
    "content": "import 'dart:io';\nimport 'base_http_exception.dart';\n\nclass HttpResponseException extends BaseHttpResponseException {\n  HttpResponseException({super.message, super.code = HttpStatus.found});\n}\n"
  },
  {
    "path": "lib/src/exception/internal_server_error.dart",
    "content": "import 'dart:io';\nimport 'base_http_exception.dart';\n\nclass InternalServerError extends BaseHttpResponseException {\n  InternalServerError({\n    required super.message,\n    super.code = HttpStatus.internalServerError,\n  });\n}\n"
  },
  {
    "path": "lib/src/exception/invalid_argument_exception.dart",
    "content": "class InvalidArgumentException {\n  final String message;\n  const InvalidArgumentException(this.message);\n}\n"
  },
  {
    "path": "lib/src/exception/not_found_exception.dart",
    "content": "import 'dart:io';\nimport '../http/response/response.dart';\nimport 'base_http_exception.dart';\n\nclass NotFoundException extends BaseHttpResponseException {\n  NotFoundException({\n    super.message = 'Not Fount 404',\n    super.code = HttpStatus.notFound,\n    super.responseType = ResponseType.html,\n  });\n}\n"
  },
  {
    "path": "lib/src/exception/page_expired_exception.dart",
    "content": "import 'package:vania/src/http/response/response.dart';\n\nimport 'base_http_exception.dart';\n\nclass PageExpiredException extends BaseHttpResponseException {\n  const PageExpiredException({\n    super.message = '<center><h1>Page Expired (419)</h1></center>',\n    super.code = 419,\n    super.responseType = ResponseType.html,\n  });\n}\n"
  },
  {
    "path": "lib/src/exception/query_exception.dart",
    "content": "class QueryException implements Exception {\n  final String? cause;\n  QueryException([this.cause]);\n}\n"
  },
  {
    "path": "lib/src/exception/redirect_exception.dart",
    "content": "import 'dart:io';\n\nimport '../http/response/response.dart';\nimport 'base_http_exception.dart';\n\nclass RedirectException extends BaseHttpResponseException {\n  RedirectException({\n    super.message,\n    super.code = HttpStatus.found,\n    super.responseType = ResponseType.html,\n  });\n}\n"
  },
  {
    "path": "lib/src/exception/throttle_exception.dart",
    "content": "import '../http/response/response.dart';\nimport 'base_http_exception.dart';\n\nclass ThrottleException extends BaseHttpResponseException {\n  final Map<String, String>? headers;\n\n  ThrottleException({\n    required String super.message,\n    required super.code,\n    this.headers,\n  });\n\n  @override\n  Response response(bool isHtml) {\n    if (isHtml) {\n      return Response.html(message);\n    }\n\n    final Map<String, dynamic> responseData = message is Map\n        ? Map<String, dynamic>.from(message as Map)\n        : {'message': message};\n\n    if (headers != null) {\n      return Response.jsonWithHeader(\n        responseData,\n        statusCode: code,\n        headers: headers!,\n      );\n    }\n\n    return Response.json(responseData, code);\n  }\n}\n"
  },
  {
    "path": "lib/src/exception/unauthenticated.dart",
    "content": "import 'dart:io';\n\nimport 'base_http_exception.dart';\n\nclass Unauthenticated extends BaseHttpResponseException {\n  Unauthenticated({\n    required super.message,\n    super.code = HttpStatus.unauthorized,\n    super.responseType,\n  });\n}\n"
  },
  {
    "path": "lib/src/exception/unauthorized_exception.dart",
    "content": "import 'base_http_exception.dart';\n\nclass UnauthorizedException extends BaseHttpResponseException {\n  UnauthorizedException({required super.message, required super.code});\n}\n"
  },
  {
    "path": "lib/src/exception/validation_exception.dart",
    "content": "import 'dart:io';\n\nimport 'base_http_exception.dart';\n\nclass ValidationException extends BaseHttpResponseException {\n  ValidationException({\n    required super.message,\n    super.code = HttpStatus.unprocessableEntity,\n  });\n}\n"
  },
  {
    "path": "lib/src/extensions/date_time_extension.dart",
    "content": "extension DateTimeExtension on DateTime {\n  /// Converts a DateTime object to a string formatted according to AWS datetime standards.\n  ///\n  /// This method formats the DateTime object into a compact string suitable for AWS services,\n  /// such as those requiring a timestamp in ISO 8601 format but without hyphens, colons,\n  /// or spaces. It ends with a 'Z' to indicate Zulu time (UTC).\n  ///\n  /// **Returns:**\n  /// A string representing the DateTime in AWS format: `YYYYMMDDTHHMMSSZ`\n  ///\n  /// **Example Usage:**\n  /// ```dart\n  /// var now = DateTime.now();\n  /// var awsFormatted = now.toAwsFormat();\n  /// print(awsFormatted); // Outputs: '20230803T142530Z' (varies based on current time)\n  /// ```\n  String toAwsFormat() {\n    String zeroPad(int number) => number.toString().padLeft(2, '0');\n\n    return '${zeroPad(year)}${zeroPad(month)}${zeroPad(day)}T'\n        '${zeroPad(hour)}${zeroPad(minute)}${zeroPad(second)}Z';\n  }\n\n  /// Formats a DateTime object into a human-readable string.\n  ///\n  /// This method converts the DateTime object into a standard datetime string format\n  /// commonly used in various systems for displaying date and time.\n  ///\n  /// **Returns:**\n  /// A string in the format `YYYY-MM-DD HH:MM:SS`, which is more readable than the compact AWS format.\n  ///\n  /// **Example Usage:**\n  /// ```dart\n  /// var now = DateTime.now();\n  /// var formatted = now.format();\n  /// print(formatted); // Outputs: '2023-08-03 14:25:30' (varies based on current time)\n  /// ```\n  String format() {\n    return '${year.toString().padLeft(4, '0')}-'\n        '${month.toString().padLeft(2, '0')}-'\n        '${day.toString().padLeft(2, '0')} '\n        '${hour.toString().padLeft(2, '0')}:'\n        '${minute.toString().padLeft(2, '0')}:'\n        '${second.toString().padLeft(2, '0')}';\n  }\n}\n"
  },
  {
    "path": "lib/src/extensions/extensions.dart",
    "content": "export 'date_time_extension.dart';\nexport 'map_extension.dart';\nexport 'number_extension.dart';\nexport 'string_extension.dart';\nexport 'string_list_extension.dart';\nexport 'localization_extension.dart';\n"
  },
  {
    "path": "lib/src/extensions/localization_extension.dart",
    "content": "import 'package:vania/src/localization_handler/localization.dart';\n\nextension LocalizationExtension on String {\n  String trans({Map<String, dynamic>? args, String? locale}) {\n    return Localization().trans(this, args, locale);\n  }\n}\n"
  },
  {
    "path": "lib/src/extensions/map_extension.dart",
    "content": "extension MapExtensions on Map<String, dynamic> {\n  /// Removes a parameter from a nested map structure based on a dot-separated key path.\n  ///\n  /// This method allows removing a value from a deeply nested map using a path described by a string of keys separated by dots.\n  ///\n  /// **Parameters:**\n  /// - [keys]: A string representing the path to the value to remove, separated by dots. For example, 'user.profile.name'.\n  ///\n  /// **Returns:**\n  /// The original map with the specified parameter removed if found. If any part of the path is invalid (not a map or non-existent key), the original map is returned unchanged.\n  ///\n  /// **Example Usage:**\n  /// ```dart\n  /// var myMap = {\n  ///   'user': {\n  ///     'profile': {\n  ///       'name': 'John',\n  ///       'age': 30\n  ///     }\n  ///   }\n  /// };\n  /// myMap.removeParam('user.profile.name');\n  /// print(myMap); // Outputs: {user: {profile: {age: 30}}}\n  /// myMap.removeParam('user.profile.nonExistentKey'); // Does nothing if key does not exist\n  /// print(myMap); // Outputs: {user: {profile: {age: 30}}}\n  /// ```\n  Map<dynamic, dynamic> removeParam(String keys) {\n    List<String> parts = keys.split('.');\n    Map<dynamic, dynamic> data = this;\n    for (int i = 0; i < parts.length - 1; i++) {\n      if (data[parts[i]] is Map) {\n        data = data[parts[i]];\n      } else {\n        return this; // Early exit if path is invalid\n      }\n    }\n    data.remove(parts.last);\n    return this;\n  }\n\n  /// Retrieves a parameter from a nested map structure based on a dot-separated key path.\n  ///\n  /// This method facilitates accessing values in a deeply nested map using a path described by a string of keys separated by dots.\n  ///\n  /// **Parameters:**\n  /// - [keys]: A string representing the path to the value, separated by dots. For example, 'user.profile.age'.\n  ///\n  /// **Returns:**\n  /// The value at the specified path if it exists and the path is valid. If the path is broken at any point (not a map or key does not exist), returns `null`.\n  ///\n  /// **Example Usage:**\n  /// ```dart\n  /// var myMap = {\n  ///   'user': {\n  ///     'profile': {\n  ///       'name': 'John',\n  ///       'age': 30\n  ///     }\n  ///   }\n  /// };\n  /// var name = myMap.getParam('user.profile.name');\n  /// print(name); // Outputs: John\n  /// var salary = myMap.getParam('user.profile.salary'); // Path does not exist\n  /// print(salary); // Outputs: null\n  /// ```\n  dynamic getParam(String keys) {\n    List<String> parts = keys.split('.');\n    Map<String, dynamic> data = this;\n    for (int i = 0; i < parts.length - 1; i++) {\n      if (data[parts[i]] is Map) {\n        data = data[parts[i]];\n      } else {\n        return [];\n      }\n    }\n    if (data[parts.last] is List) {\n      List<Map<String, dynamic>> list =\n          List.castFrom<dynamic, Map<String, dynamic>>(data[parts.last]);\n      return list;\n    }\n    return data[parts.last];\n  }\n}\n"
  },
  {
    "path": "lib/src/extensions/number_extension.dart",
    "content": "extension NumberExtension on num {\n  /// Rounds the number to a fixed number of decimal places.\n  ///\n  /// This method rounds the calling number to the specified number of decimal places\n  /// and returns it as a `num`. If the number is an integer, it will return the same integer\n  /// if `decimal` is zero; otherwise, it will return a decimal with the specified number of places.\n  ///\n  /// **Parameters:**\n  /// - [decimal]: The number of decimal places to round the number to. This must be a non-negative integer.\n  ///\n  /// **Returns:**\n  /// A new number rounded to the specified number of decimal places.\n  ///\n  /// **Example Usage:**\n  /// ```dart\n  /// var number = 123.4567;\n  /// var rounded = number.toFixed(2);\n  /// print(rounded); // Outputs: 123.46\n  ///\n  /// var integer = 123;\n  /// var roundedInt = integer.toFixed(0);\n  /// print(roundedInt); // Outputs: 123\n  ///\n  /// var negative = -123.456;\n  /// var roundedNeg = negative.toFixed(3);\n  /// print(roundedNeg); // Outputs: -123.456\n  /// ```\n  ///\n  /// **Note:**\n  /// The method uses `toStringAsFixed` and `num.parse` to perform the rounding, which means\n  /// the method handles rounding similarly to how JavaScript handles number rounding.\n  num toFixed(int decimal) {\n    return num.parse(toStringAsFixed(decimal));\n  }\n}\n"
  },
  {
    "path": "lib/src/extensions/string_extension.dart",
    "content": "extension StringExtension on String {\n  /// Converts a string to an integer.\n  ///\n  /// Attempts to parse the string as an integer using `int.tryParse`. If the string\n  /// cannot be parsed into an integer (e.g., because it contains letters or special characters),\n  /// this method returns `null`.\n  ///\n  /// **Examples:**\n  /// - For a valid integer string:\n  /// ```dart\n  /// var number = '123'.toInt(); // 123\n  /// ```\n  /// - For a string containing non-digit characters:\n  /// ```dart\n  /// var number = '123abc'.toInt(); // null\n  /// ```\n  /// - For a string representing a floating-point number:\n  /// ```dart\n  /// var number = '123.45'.toInt(); // null\n  /// ```\n  /// - For an empty string:\n  /// ```dart\n  /// var number = ''.toInt(); // null\n  /// ```\n  ///\n  /// **Parameters:**\n  /// None.\n  ///\n  /// **Returns:**\n  int? toInt() {\n    return int.tryParse(this);\n  }\n}\n"
  },
  {
    "path": "lib/src/extensions/string_list_extension.dart",
    "content": "extension StringListExtension on List<String> {\n  /// Joins a list of strings into a single string with a custom separator and a final conjunction word.\n  ///\n  /// This method concatenates each element of the list into a string, separated by [separator],\n  /// and uses [lastJoinText] before the final element to format the output naturally in English.\n  ///\n  /// **Examples:**\n  /// - For an empty list: returns an empty string.\n  /// - For a list with two elements: returns the two elements separated by [lastJoinText].\n  /// - For a list with more than two elements: returns all elements separated by [separator] and\n  ///   the last two elements are joined with [lastJoinText].\n  ///\n  /// **Code Examples:**\n  /// ```dart\n  /// var value = [].joinWithAnd(); // ''\n  /// value = ['apple'].joinWithAnd(); // 'apple'\n  /// value = ['apple', 'orange'].joinWithAnd(); // 'apple and orange'\n  /// value = ['apple', 'orange', 'banana'].joinWithAnd(); // 'apple, orange and banana'\n  /// ```\n  /// Parameters:\n  /// - [separator] (optional): the string to use between each element except before the last element. Defaults to ', '.\n  /// - [lastJoinText] (optional): the string to use before the last element. Defaults to 'and'.\n  ///\n  /// Returns:\n  /// The concatenated string of the list elements formatted according to the rules described.\n\n  String joinWithAnd([String separator = ', ', String lastJoinText = 'and']) {\n    if (isEmpty) return '';\n    if (length == 1) return first;\n    if (length == 2) return '$first $lastJoinText $last';\n    return '${sublist(0, length - 1).join(separator)} $lastJoinText $last';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/controller/controller.dart",
    "content": "abstract class Controller {\n  const Controller();\n}\n"
  },
  {
    "path": "lib/src/http/controller/controller_handler.dart",
    "content": "import 'package:vania/src/exception/database_exception.dart';\nimport 'package:vania/src/exception/exception_handler.dart';\nimport 'package:vania/src/exception/query_exception.dart';\nimport 'package:vania/src/exception/validation_exception.dart';\nimport 'package:vania/src/http/request/request.dart';\nimport 'package:vania/src/http/response/response.dart';\nimport 'package:vania/src/route/route_data.dart';\nimport 'package:vania/src/route/route_history.dart';\nimport 'package:vania/application.dart';\n\nimport '../../exception/base_http_exception.dart';\nimport '../../exception/invalid_argument_exception.dart';\n\nclass ControllerHandler {\n  dynamic _getParamValue(String param) {\n    if (int.tryParse(param) != null) {\n      return int.parse(param);\n    } else if (num.tryParse(param) != null) {\n      return double.parse(param);\n    } else if (double.tryParse(param) != null) {\n      return num.parse(param);\n    } else if (param.toLowerCase() == 'true') {\n      return true;\n    } else if (param.toLowerCase() == 'false') {\n      return false;\n    } else {\n      return param;\n    }\n  }\n\n  void create({required RouteData route, required Request request}) async {\n    List<dynamic> positionalArguments = [];\n    if (route.params != null) {\n      try {\n        positionalArguments = route.params!.values\n            .map((param) => _getParamValue(param.toString()))\n            .toList();\n      } on FormatException catch (e) {\n        _response(request, e.message, 500);\n      }\n    }\n\n    if (route.hasRequest) {\n      positionalArguments.insert(0, request);\n    }\n\n    try {\n      Response response = await Function.apply(\n        route.action,\n        positionalArguments,\n        {},\n      );\n\n      response.makeResponse(request.response);\n    } on ValidationException catch (error) {\n      Response? customResponse = _handleException(error, request);\n      if (customResponse != null) {\n        return customResponse.makeResponse(request.response);\n      }\n      bool isHtml = request.request.headers\n          .value('accept')\n          .toString()\n          .contains('html');\n      if (isHtml) {\n        Response.redirect(\n          RouteHistory().previousRoute,\n        ).makeResponse(request.response);\n      } else {\n        error.response(false).makeResponse(request.response);\n      }\n    } on InvalidArgumentException catch (error) {\n      Response? customResponse = _handleException(error, request);\n      if (customResponse != null) {\n        return customResponse.makeResponse(request.response);\n      }\n      _response(request, error.message);\n    } on DatabaseException catch (error) {\n      Response? customResponse = _handleException(error, request);\n      if (customResponse != null) {\n        return customResponse.makeResponse(request.response);\n      }\n      _response(request, error.message, 500);\n    } on QueryException catch (error) {\n      Response? customResponse = _handleException(error, request);\n      if (customResponse != null) {\n        return customResponse.makeResponse(request.response);\n      }\n      _response(request, error.cause ?? '', 500);\n    } on BaseHttpResponseException catch (error) {\n      Response? customResponse = _handleException(error, request);\n      if (customResponse != null) {\n        return customResponse.makeResponse(request.response);\n      }\n      _response(request, error.message, error.code);\n    } catch (error) {\n      Response? customResponse = _handleException(error, request);\n      if (customResponse != null) {\n        return customResponse.makeResponse(request.response);\n      }\n      _response(request, error.toString());\n    }\n  }\n\n  Response? _handleException(dynamic exception, Request request) {\n    try {\n      ExceptionHandler? handler =\n          Application().getExceptionHandler(exception.runtimeType);\n      if (handler != null) {\n        return handler.handle(exception, request);\n      }\n      GeneralExceptionHandler? generalHandler =\n          Application().getGeneralExceptionHandler();\n      if (generalHandler != null) {\n        return generalHandler.handle(exception, request);\n      }\n    } catch (_) {}\n    return null;\n  }\n}\n\nvoid _response(Request req, message, [statusCode = 500]) {\n  if (req.headers['accept'].toString().contains('html')) {\n    Response.html(message).makeResponse(req.response);\n  } else {\n    Response.json({\"message\": message}, statusCode).makeResponse(req.response);\n  }\n}\n"
  },
  {
    "path": "lib/src/http/middleware/middleware.dart",
    "content": "import 'dart:io';\n\nimport 'package:vania/src/http/request/request.dart';\n\nabstract class Middleware {\n  Future handle(Request req);\n}\n\nabstract class WebSocketMiddleware {\n  Future handle(HttpRequest req);\n}\n"
  },
  {
    "path": "lib/src/http/middleware/middleware_handler.dart",
    "content": "import 'package:vania/src/http/request/request.dart';\nimport 'middleware.dart';\n\nFuture<void> middlewareHandler(\n  List<Middleware> middlewares,\n  Request request,\n) async {\n  for (Middleware middleware in middlewares) {\n    await middleware.handle(request);\n  }\n}\n"
  },
  {
    "path": "lib/src/http/middleware/web_socket_middleware_handler.dart",
    "content": "import 'dart:io';\nimport 'middleware.dart';\n\nFuture<void> webSocketMiddlewareHandler(\n  List<WebSocketMiddleware> middlewares,\n  HttpRequest request,\n) async {\n  for (WebSocketMiddleware middleware in middlewares) {\n    await middleware.handle(request);\n  }\n}\n"
  },
  {
    "path": "lib/src/http/request/request.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'package:vania/src/authentication/authentication.dart';\nimport 'package:vania/src/contract/http/request/form_validation.dart';\nimport 'package:vania/src/exception/validation_exception.dart';\nimport 'package:vania/src/http/request/request_body.dart';\nimport 'package:vania/src/http/request/request_file.dart';\nimport 'package:vania/src/http/validation/custom_validation_rule.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart'\n    show ValidationRule;\nimport 'package:vania/src/http/validation/validator.dart';\nimport 'package:vania/src/route/route_data.dart';\nimport 'package:vania/src/view_engine/template_engine.dart';\n\nimport '../../exception/unauthorized_exception.dart';\nimport '../validation/field_validation.dart';\n\nclass Request {\n  late HttpRequest request;\n  RouteData? route;\n\n  Request from({required HttpRequest request, RouteData? route}) {\n    this.request = request;\n    this.route = route;\n\n    return this;\n  }\n\n  Map? get user => Auth().user();\n\n  String? get ip => request.connectionInfo?.remoteAddress.address;\n\n  HttpHeaders get _httpHeaders => request.headers;\n\n  ContentType? get contentType => request.headers.contentType;\n\n  Uri get uri => request.uri;\n\n  String? get path => route?.path;\n\n  String get url => \"$host$uri\";\n\n  String get host => header(HttpHeaders.hostHeader) ?? 'unknown';\n\n  String? get method => route?.method;\n\n  HttpResponse get response => request.response;\n\n  Map<String, dynamic> get _all => all();\n\n  Map<String, dynamic> get _query => uri.queryParameters;\n\n  Map<String, dynamic> body = <String, dynamic>{};\n  final Map<String, dynamic> _cookies = <String, dynamic>{};\n\n  List<CustomValidationRule>? _customRules;\n\n  Request setCustomRule(List<CustomValidationRule> customRule) {\n    _customRules = customRule;\n    return this;\n  }\n\n  /// Gets a cookie by name and casts it to type [T].\n  ///\n  /// If the cookie doesn't exist, it returns `null`.\n  ///\n  /// If [T] is [String], it's casted to a string.\n  /// If [T] is [bool], it's parsed from a string.\n  /// If [T] is [int], it's parsed from a string.\n  /// If [T] is [double], it's parsed from a string.\n  /// Otherwise, it's casted to [T].\n  ///\n  T? cookie<T>(String key) {\n    if (_cookies[key] == null) return null;\n    return switch (T.toString()) {\n      'String' => _cookies[key].toString(),\n      'bool' => bool.parse(_cookies[key]) as T,\n      'int' => int.parse(_cookies[key]) as T,\n      'double' => double.parse(_cookies[key]) as T,\n      (_) => _cookies[key],\n    };\n  }\n\n  /// Extracts the cookies from the headers and stores them in [_cookies].\n  ///\n  /// The format of the [HttpHeaders.cookieHeader] is:\n  void _extractCookies() {\n    List<String>? cookies = _httpHeaders[HttpHeaders.cookieHeader];\n    if (cookies == null) {\n      return;\n    }\n    for (String cookie in cookies) {\n      List cookies = cookie.split(';');\n      for (String cookie in cookies) {\n        List cookieList = cookie.split('=');\n        _cookies[cookieList[0].trim()] = cookieList[1].trim();\n      }\n    }\n  }\n\n  Future<Request> extractBody() async {\n    _extractCookies();\n    final whereMethod = [\n      'post',\n      'patch',\n      'put',\n      'delete',\n    ].where((method) => method == request.method.toLowerCase()).toList();\n    if (whereMethod.isNotEmpty) {\n      body = await RequestBody.extractBody(request: request);\n    }\n    return this;\n  }\n\n  Map<String, dynamic> all() {\n    return {...body, ..._query, ...params()};\n  }\n\n  Map<String, dynamic> params() {\n    final vParams = route?.params ?? {};\n    vParams.removeWhere((key, value) => value is Request);\n    return vParams;\n  }\n\n  bool isMethod(String method) {\n    return route?.method.toLowerCase() == method.toLowerCase();\n  }\n\n  Map<String, dynamic> only(List<String> keys) {\n    Map<String, dynamic> ret = <String, dynamic>{};\n    for (String key in keys) {\n      ret[key] = _all[key];\n    }\n    return ret;\n  }\n\n  bool has(dynamic keys) {\n    if (keys is String) {\n      String? val = _all[keys];\n      if (val == null) {\n        return false;\n      }\n      return val.toString().isNotEmpty ? true : false;\n    }\n\n    if (keys is List<String>) {\n      bool hasKey = true;\n      for (String key in keys) {\n        if (_all[key] == null) {\n          hasKey = false;\n        }\n      }\n      return hasKey;\n    }\n\n    return (_all[keys] != null && _all[keys].toString().isNotEmpty);\n  }\n\n  bool hasAny(List<String> keys) {\n    bool hasKey = false;\n    for (String key in keys) {\n      if (_all[key] != null && _all[key].toString().isNotEmpty) {\n        hasKey = true;\n      }\n    }\n    return hasKey;\n  }\n\n  Future whenHas(String key) async {\n    if (_all[key] != null) {\n      return Future.value(_all[key]);\n    } else {\n      return Future.error(\"\");\n    }\n  }\n\n  Map<String, dynamic> except(dynamic key) {\n    Map<String, dynamic> requestItems = _all;\n\n    if (key is List<String>) {\n      for (String vKey in key) {\n        requestItems.removeWhere((iKey, value) => iKey == vKey);\n      }\n    }\n\n    if (key is String) {\n      requestItems.removeWhere((vkey, value) => vkey == key);\n    }\n\n    return requestItems;\n  }\n\n  Map json(String key) {\n    if (_all[key] != null && _all[key] is String) {\n      return jsonDecode(_all[key]);\n    }\n\n    if (_all[key] != null && _all[key] is Map) {\n      return _all[key];\n    }\n\n    return {};\n  }\n\n  dynamic input([String? key, dynamic defaultVal]) {\n    if (key == null) {\n      return _all;\n    }\n\n    if (_all[key] != null) {\n      return int.tryParse(_all[key].toString()) ?? _all[key];\n    }\n\n    if (defaultVal != null) {\n      return defaultVal;\n    }\n\n    return null;\n  }\n\n  RequestFile? file(String key) {\n    if (_all[key] == null) {\n      return null;\n    }\n\n    if (_all[key] is! RequestFile) {\n      return (_all[key] as List<RequestFile>).first;\n    }\n\n    return _all[key];\n  }\n\n  bool hasFile(String key) =>\n      (_all[key].toString().isNotEmpty &&\n      (file(key) != null || files(key) != null));\n\n  List<RequestFile>? files(String key) {\n    if (_all[key] == null) {\n      return null;\n    }\n\n    var files = _all[key];\n\n    if (files is! List) {\n      return [files];\n    }\n    return files as List<RequestFile>;\n  }\n\n  String string(String key) {\n    return _all[key].toString();\n  }\n\n  List asList(String key) {\n    return List.from(_all[key]);\n  }\n\n  int? integer(String key) {\n    return int.tryParse(_all[key].toString());\n  }\n\n  double? asDouble(String key) {\n    return double.tryParse(_all[key].toString());\n  }\n\n  bool boolean(String key) {\n    try {\n      return bool.parse(_all[key].toString());\n    } catch (_) {\n      return false;\n    }\n  }\n\n  DateTime? date(String key) {\n    try {\n      return DateTime.parse(_all[key].toString());\n    } catch (_) {\n      return null;\n    }\n  }\n\n  dynamic query([String? key, String? defaultVal]) {\n    if (key == null) {\n      return _query.values;\n    }\n\n    if (_query[key] != null) {\n      return _query[key];\n    }\n\n    if (defaultVal != null) {\n      return defaultVal;\n    }\n\n    return null;\n  }\n\n  void merge(Map<String, dynamic> values) {\n    _all.addAll(values);\n    body = <String, dynamic>{...body, ...values};\n  }\n\n  void mergeIfMissing(Map<String, dynamic> values) {\n    for (var vKey in values.keys) {\n      if (!_all.keys.contains(vKey)) {\n        _all.addEntries(values[vKey]);\n      }\n    }\n  }\n\n  String? header(String key, [String? defaultHeader]) {\n    return _httpHeaders.value(key) ?? defaultHeader;\n  }\n\n  Map<String, dynamic> get headers {\n    Map<String, dynamic> ret = <String, dynamic>{};\n    _httpHeaders.forEach((String name, List<String> values) {\n      ret[name] = values.join();\n    });\n    return ret;\n  }\n\n  bool isFormData() {\n    return RequestBody.isFormData(contentType);\n  }\n\n  bool isJson() {\n    return RequestBody.isJson(contentType);\n  }\n\n  bool isUrlencoded() {\n    return RequestBody.isUrlencoded(contentType);\n  }\n\n  String? userAgent() {\n    return header(HttpHeaders.userAgentHeader);\n  }\n\n  String? origin() {\n    return header('origin');\n  }\n\n  String? referer() {\n    return header(HttpHeaders.refererHeader);\n  }\n\n  Future<void> validate(\n    dynamic rules, [\n    Map<String, String> messages = const <String, String>{},\n  ]) async {\n    assert(\n      rules is Map<String, String> ||\n          rules is List<FieldValidation> ||\n          rules is List<Validation> ||\n          rules is FormValidation,\n      'Rules must be either Map<String, String> or List<Validation>. or FormRequest',\n    );\n    TemplateEngine().sessionErrors.clear();\n    if (rules is Map<String, String>) {\n      await _validate(rules, messages);\n    } else if (rules is List<FieldValidation>) {\n      Map<String, String> ruleMessages = Map.from(messages);\n      final rulesMap = Map.fromEntries(\n        rules.map((rule) {\n          ruleMessages.addAll(rule.toMapMessages);\n          return MapEntry(rule.fieldName, rule.toString());\n        }),\n      );\n      await _validate(rulesMap, ruleMessages);\n    } else if (rules is FormValidation) {\n      await _formRequestValidate(rules);\n    } else {\n      _validateChain(rules as List<Validation>);\n    }\n  }\n\n  Future<void> _formRequestValidate(FormValidation formRequest) async {\n    if (!formRequest.authorize()) {\n      throw UnauthorizedException(message: 'Access denied', code: 403);\n    }\n\n    final rules = formRequest.rules();\n    final Map<String, String> messages = formRequest.messages();\n\n    Validator validator = Validator(data: body);\n\n    if (formRequest.customRule().isNotEmpty) {\n      validator.customRule(formRequest.customRule());\n    }\n\n    Map<String, String> rulesMap = {};\n    if (rules is List<FieldValidation>) {\n      rulesMap = Map.fromEntries(\n        rules.map((rule) {\n          messages.addAll(rule.toMapMessages);\n          return MapEntry(rule.fieldName, rule.toString());\n        }),\n      );\n    } else {\n      rulesMap = formRequest.rules();\n    }\n\n    if (messages.isNotEmpty) {\n      validator.setNewMessages(messages);\n    }\n\n    await validator.validate(rulesMap);\n    if (validator.hasError) {\n      bool isHtml = request.headers.value('accept').toString().contains('html');\n      if (isHtml) {\n        TemplateEngine().sessionErrors.addAll(validator.errors);\n      }\n      throw ValidationException(message: validator.errors);\n    }\n  }\n\n  Future<void> _validate(\n    Map<String, String> rules, [\n    Map<String, String> messages = const <String, String>{},\n  ]) async {\n    Validator validator = Validator(data: body);\n\n    if (_customRules != null) {\n      validator.customRule(_customRules!);\n    }\n\n    if (messages.isNotEmpty) {\n      validator.setNewMessages(messages);\n    }\n    await validator.validate(rules);\n    if (validator.hasError) {\n      bool isHtml = request.headers.value('accept').toString().contains('html');\n      if (isHtml) {\n        TemplateEngine().sessionErrors.addAll(validator.errors);\n      }\n      throw ValidationException(message: validator.errors);\n    }\n  }\n\n  void _validateChain(List<Validation> validations) {\n    Map<String, String> errors = {};\n    final data = all();\n    for (Validation validation in validations) {\n      dynamic fieldValue = data.containsKey(validation.field)\n          ? data[validation.field]\n          : null;\n      for (ValidationRule rule in validation.rules) {\n        if (!rule.validate(fieldValue, data)) {\n          errors[validation.field] = rule.errorMessage;\n          break;\n        }\n      }\n    }\n    if (errors.isNotEmpty) {\n      bool isHtml = request.headers.value('accept').toString().contains('html');\n      if (isHtml) {\n        TemplateEngine().sessionErrors.addAll(errors);\n      }\n      throw ValidationException(message: errors);\n    }\n  }\n\n  Map<String, dynamic> toJson() {\n    Map<String, dynamic> ret = <String, dynamic>{};\n    _all.forEach((String key, dynamic value) {\n      if (value is RequestFile) {\n        ret[key] = value.filename;\n      } else {\n        ret[key] = value;\n      }\n    });\n    return ret;\n  }\n}\n"
  },
  {
    "path": "lib/src/http/request/request_body.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'dart:typed_data';\n\nimport 'package:vania/src/http/request/request_form_data.dart';\nimport 'package:vania/src/utils/helper.dart' show abort;\nimport 'package:vania/vania.dart' show env;\n\nclass RequestBody {\n  const RequestBody();\n\n  static final int _maxBodySizeBytes = env<int>(\n    'MAX_BODY_SIZE',\n    10 * 1024 * 1024,\n  );\n\n  static Future<Map<String, dynamic>> extractBody({\n    required HttpRequest request,\n  }) async {\n    final headers = request.headers;\n    final contentLength = headers.contentLength;\n\n    if (contentLength > _maxBodySizeBytes) {\n      abort(\n        413,\n        'Request body too large:  $contentLength bytes (max $_maxBodySizeBytes bytes)',\n      );\n    }\n\n    if (isFormData(request.headers.contentType)) {\n      final formData = RequestFormData(request: request);\n      await formData.extractData();\n      return formData.inputs;\n    }\n\n    final bytesBuilder = BytesBuilder();\n    await for (final chunk in request) {\n      bytesBuilder.add(chunk);\n    }\n    final bodyBytes = bytesBuilder.takeBytes();\n    final bodyString = utf8.decode(bodyBytes);\n\n    if (isJson(request.headers.contentType)) {\n      try {\n        final decoded = jsonDecode(bodyString);\n        if (decoded is Map<String, dynamic>) {\n          return decoded;\n        }\n      } catch (_) {}\n      return <String, dynamic>{};\n    }\n\n    if (isUrlencoded(request.headers.contentType)) {\n      try {\n        return Uri.splitQueryString(bodyString);\n      } catch (_) {\n        return <String, dynamic>{};\n      }\n    }\n\n    return <String, dynamic>{};\n  }\n\n  static bool isUrlencoded(ContentType? contentType) {\n    return contentType?.mimeType.toLowerCase().contains('urlencoded') == true;\n  }\n\n  static bool isFormData(ContentType? contentType) {\n    return contentType?.mimeType.toLowerCase().contains('form-data') == true;\n  }\n\n  static bool isJson(ContentType? contentType) {\n    return contentType?.mimeType.toLowerCase().contains('json') == true;\n  }\n}\n"
  },
  {
    "path": "lib/src/http/request/request_file.dart",
    "content": "import 'dart:io';\nimport 'dart:typed_data';\n\nimport 'package:mime/mime.dart';\nimport 'package:vania/src/storage/storage.dart';\nimport 'package:vania/src/utils/functions.dart';\n\n/// Represents an uploaded file part from multipart/form-data.\n/// Provides lazy access to bytes, size, and easy storage/move.\nclass RequestFile {\n  final String filename;\n  final String filetype;\n  final MimeMultipart stream;\n  Uint8List? _bytes;\n\n  RequestFile({\n    required this.filename,\n    required this.filetype,\n    required this.stream,\n  });\n\n  /// File extension without the dot (e.g. \"png\", \"jpg\", \"pdf\").\n  String get extension {\n    final idx = filename.lastIndexOf('.');\n    return (idx >= 0 && idx < filename.length - 1)\n        ? filename.substring(idx + 1).toLowerCase()\n        : '';\n  }\n\n  /// Lazily reads all bytes from the multipart stream.\n  Future<Uint8List> get bytes async {\n    if (_bytes != null) return _bytes!;\n    final builder = BytesBuilder();\n    await for (final chunk in stream) {\n      builder.add(chunk);\n    }\n    _bytes = builder.takeBytes();\n    return _bytes!;\n  }\n\n  /// Returns the file size in bytes.\n  Future<int> get size async {\n    final b = await bytes;\n    return b.length;\n  }\n\n  /// Original client‐provided file name.\n  String get clientOriginalName => filename;\n\n  /// Original client‐provided file extension.\n  String get clientOriginalExtension => extension;\n\n  /// Original client‐provided MIME type.\n  String get clientMimeType => filetype;\n\n  /// Store the file via your Storage layer.\n  /// - `destPath` should include trailing slash if desired.\n  Future<String> store({String path = '', required String name}) async {\n    try {\n      final content = await bytes;\n      return await Storage.put(path, name, content.toList());\n    } catch (e) {\n      throw FileSystemException('Failed to store file as $name: $e');\n    }\n  }\n\n  /// Move the file into a local path on disk.\n  /// Creates directories as needed.\n  Future<String> move({required String toPath, required String name}) async {\n    final fullPath = sanitizeRoutePath('$toPath/$name');\n    final file = File(fullPath);\n    await file.parent.create(recursive: true);\n    final sink = file.openWrite();\n    await for (final chunk in stream) {\n      sink.add(chunk);\n    }\n    await sink.close();\n\n    // strip leading /public if used\n    return fullPath.replaceFirst(RegExp(r'^/?public'), '');\n  }\n}\n"
  },
  {
    "path": "lib/src/http/request/request_form_data.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\n\nimport 'package:mime/mime.dart';\n\nimport 'request_file.dart';\n\n/// Handles extraction of multipart/form-data requests into a simple\n/// Map of field names to either String/int or RequestFile instances.\nclass RequestFormData {\n  final HttpRequest request;\n  final Map<String, dynamic> inputs = <String, dynamic>{};\n\n  RequestFormData({required this.request});\n\n  /// Parse and collect all form-data parts from the request.\n  /// - For text fields: decodes UTF-8 and converts to int if possible.\n  /// - For file fields: wraps them in a RequestFile.\n  Future<RequestFormData> extractData() async {\n    final boundary = request.headers.contentType?.parameters['boundary'];\n    if (boundary == null) {\n      throw HttpException('Missing multipart boundary', uri: request.uri);\n    }\n\n    // split request stream into MimeMultipart parts\n    final transformer = MimeMultipartTransformer(boundary);\n    final parts = await request\n        .cast<List<int>>()\n        .transform(transformer)\n        .toList();\n\n    for (final part in parts) {\n      final disposition = part.headers['content-disposition'];\n      if (disposition == null) continue;\n\n      // Simple regex to capture name=\"...\" and filename=\"...\" pairs\n      final params = <String, String>{};\n      final regExp = RegExp(r'([\\w\\-]+)=\"([^\"]*)\"');\n      for (final m in regExp.allMatches(disposition)) {\n        params[m.group(1)!] = m.group(2)!;\n      }\n\n      final name = params['name'];\n      if (name == null) continue;\n\n      final filename = params['filename'];\n      if (filename == null || filename.isEmpty) {\n        // text field\n        final raw = utf8.decode(\n          await part.fold<List<int>>(<int>[], (buf, b) => buf..addAll(b)),\n        );\n        final value = int.tryParse(raw) ?? raw;\n        if (name.endsWith('[]')) {\n          final key = name.substring(0, name.length - 2);\n          inputs.putIfAbsent(key, () => <dynamic>[]);\n          (inputs[key] as List).add(value);\n        } else {\n          inputs[name] = value;\n        }\n      } else {\n        // file field\n        final file = RequestFile(\n          filename: filename,\n          filetype: part.headers['content-type'] ?? 'application/octet-stream',\n          stream: part,\n        );\n        if (name.endsWith('[]')) {\n          final key = name.substring(0, name.length - 2);\n          inputs.putIfAbsent(key, () => <RequestFile>[]);\n          (inputs[key] as List<RequestFile>).add(file);\n        } else {\n          inputs[name] = file;\n        }\n      }\n    }\n\n    return this;\n  }\n}\n"
  },
  {
    "path": "lib/src/http/request/request_handler.dart",
    "content": "import 'dart:io';\nimport 'package:vania/src/exception/database_exception.dart';\nimport 'package:vania/src/exception/exception_handler.dart';\nimport 'package:vania/src/exception/query_exception.dart';\nimport 'package:vania/src/extensions/extensions.dart';\nimport 'package:vania/src/http/response/response.dart';\nimport 'package:vania/src/http/session/session_manager.dart';\nimport 'package:vania/src/route/middleware/csrf_middleware.dart';\nimport 'package:vania/src/view_engine/helper.dart';\n\nimport 'package:vania/src/config/http_cors.dart';\nimport 'package:vania/src/exception/internal_server_error.dart';\nimport 'package:vania/src/exception/invalid_argument_exception.dart';\nimport 'package:vania/src/exception/page_expired_exception.dart';\nimport 'package:vania/src/exception/not_found_exception.dart';\nimport 'package:vania/src/exception/unauthenticated.dart';\nimport 'package:vania/src/http/controller/controller_handler.dart';\nimport 'package:vania/src/http/middleware/middleware_handler.dart';\nimport 'package:vania/src/ioc_container.dart';\nimport 'package:vania/src/route/route_data.dart';\nimport 'package:vania/src/route/route_handler.dart';\nimport 'package:vania/src/route/route_history.dart';\nimport 'package:vania/src/view_engine/template_engine.dart';\nimport 'package:vania/src/websocket/web_socket_handler.dart';\nimport 'package:vania/src/exception/base_http_exception.dart';\nimport 'package:vania/src/logger/logger.dart';\nimport 'package:vania/src/utils/helper.dart';\nimport 'package:vania/application.dart';\nimport 'request.dart';\n\nHttpRequest? globalHttpRequest;\n\nclass RequestHandler {\n  /// Handles HTTP requests, determining if the request is a WebSocket upgrade or\n  /// a standard HTTP request. If it's a WebSocket request, it delegates handling\n  /// to the WebSocketHandler; otherwise, it processes the request by checking\n  /// CORS, handling routes, invoking middleware, and executing the appropriate\n  /// controller action. The function also manages session initiation and logs\n  /// request details in debug mode.\n  ///\n  /// Throws:\n  /// - [BaseHttpResponseException] if there is an issue with the HTTP response.\n  /// - [InvalidArgumentException] if an invalid argument is encountered.\n  Future handle(HttpRequest req) async {\n    globalHttpRequest = req;\n\n    /// Check the incoming request is web socket or not\n    if (env<bool>('APP_WEBSOCKET', false) &&\n        WebSocketTransformer.isUpgradeRequest(req)) {\n      WebSocketHandler().handler(req);\n    } else {\n      bool isHtml = req.headers.value('accept').toString().contains('html');\n      Request? request;\n      try {\n        HttpCors(req);\n        RouteData? route = httpRouteHandler(req);\n        DateTime startTime = DateTime.now();\n        String requestUri = req.uri.path;\n        String starteRequest = startTime.format();\n        String requestMethod = req.method.toUpperCase();\n\n        if (route != null) {\n          request = Request().from(request: req, route: route);\n          await request.extractBody();\n\n          if (isHtml) {\n            TemplateEngine().formData.addAll(request.all());\n            await IoCContainer().resolve<SessionManager>().sessionStart(\n              req,\n              req.response,\n            );\n            RouteHistory().updateRouteHistory(req);\n          }\n          if (env<bool>('CSRF_PROTECTION_ENABLED', false)) {\n            route.preMiddleware.add(CsrfMiddleware());\n          }\n\n          /// Check if pre middleware exist and call it\n          if (route.preMiddleware.isNotEmpty) {\n            await middlewareHandler(route.preMiddleware, request);\n          }\n\n          /// Controller and method handler\n          ControllerHandler().create(route: route, request: request);\n\n          if (env<bool>('APP_DEBUG')) {\n            var endTime = DateTime.now();\n            var duration = endTime.difference(startTime).inMilliseconds;\n            var requestedPath = requestUri.isNotEmpty\n                ? requestUri.padRight(118 - requestUri.length, '.')\n                : ''.padRight(118, '.');\n            stderr.writeln(\n              '$starteRequest $requestMethod $requestedPath ~ ${duration}ms',\n            );\n          }\n        }\n      } on BaseHttpResponseException catch (error) {\n        Response? customResponse = _handleException(error, request);\n        if (customResponse != null) {\n          return customResponse.makeResponse(req.response);\n        }\n\n        if (error is NotFoundException && isHtml) {\n          if (File('lib/resources/view/errors/404.html').existsSync()) {\n            return view('errors/404').makeResponse(req.response);\n          }\n        }\n\n        if (error is InternalServerError && isHtml) {\n          if (File('lib/resources/view/errors/500.html').existsSync()) {\n            return view('errors/500').makeResponse(req.response);\n          }\n        }\n\n        if (error is PageExpiredException && isHtml) {\n          if (File('lib/resources/view/errors/419.html').existsSync()) {\n            return view('errors/419').makeResponse(req.response);\n          }\n        }\n\n        if (error is Unauthenticated && isHtml) {\n          return Response.redirect(error.message).makeResponse(req.response);\n        }\n\n        if (error is RedirectException && isHtml) {\n          return Response.redirect(error.message).makeResponse(req.response);\n        }\n\n        error.response(isHtml).makeResponse(req.response);\n      } on InvalidArgumentException catch (e) {\n        Response? customResponse = _handleException(e, request);\n        if (customResponse != null) {\n          return customResponse.makeResponse(req.response);\n        }\n        Logger.log(e.message, type: Logger.ERROR);\n        _response(req, e.message);\n      } on DatabaseException catch (error) {\n        Response? customResponse = _handleException(error, request);\n        if (customResponse != null) {\n          return customResponse.makeResponse(req.response);\n        }\n        _response(req, error.message);\n      } on QueryException catch (error) {\n        Response? customResponse = _handleException(error, request);\n        if (customResponse != null) {\n          return customResponse.makeResponse(req.response);\n        }\n        _response(req, error.cause);\n      } catch (e) {\n        Response? customResponse = _handleException(e, request);\n        if (customResponse != null) {\n          return customResponse.makeResponse(req.response);\n        }\n        Logger.log(e.toString(), type: Logger.ERROR);\n        _response(req, e.toString());\n      }\n    }\n  }\n\n  Response? _handleException(dynamic exception, Request? request) {\n    try {\n      ExceptionHandler? handler =\n          Application().getExceptionHandler(exception.runtimeType);\n      if (handler != null) {\n        return handler.handle(exception, request);\n      }\n      GeneralExceptionHandler? generalHandler =\n          Application().getGeneralExceptionHandler();\n      if (generalHandler != null) {\n        return generalHandler.handle(exception, request);\n      }\n    } catch (_) {}\n    return null;\n  }\n\n  void _response(HttpRequest req, dynamic message, {int statusCode = 500}) {\n    if (req.headers.value('accept').toString().contains('html')) {\n      Response.html(message).makeResponse(req.response);\n    } else {\n      Response.json({\n        \"message\": message,\n      }, statusCode).makeResponse(req.response);\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/http/response/response.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'dart:typed_data';\nimport 'package:meta/meta.dart';\nimport 'package:vania/src/http/response/stream_file.dart';\nimport 'package:vania/src/route/route_history.dart';\nimport 'package:vania/src/view_engine/template_engine.dart';\n\nenum ResponseType { json, none, redirect, html, sse, streamFile, download }\n\nclass Response {\n  @protected\n  final ResponseType responseType;\n  @protected\n  final dynamic data;\n  @protected\n  final int httpStatusCode;\n  @protected\n  final Map<String, String> headers;\n\n  Response({\n    this.data,\n    this.responseType = ResponseType.none,\n    this.httpStatusCode = HttpStatus.ok,\n    this.headers = const {},\n  });\n\n  @protected\n  Future<void> sseHandler(HttpResponse res) async {\n    res.headers.contentType = ContentType.parse('text/event-stream');\n    res.headers.add(HttpHeaders.cacheControlHeader, 'no-cache');\n    res.headers.add(HttpHeaders.connectionHeader, 'keep-alive');\n    res.headers.add(HttpHeaders.transferEncodingHeader, 'chunked');\n\n    void writeSSE(String data) {\n      res.add(utf8.encode('data: $data\\n\\n'));\n    }\n\n    await for (var event in data) {\n      writeSSE(jsonEncode(event));\n      await res.flush();\n    }\n\n    await res.close();\n  }\n\n  void makeResponse(HttpResponse res) async {\n    res.statusCode = httpStatusCode;\n    if (headers.isNotEmpty) {\n      headers.forEach((key, value) {\n        res.headers.set(key, value);\n      });\n    }\n    switch (responseType) {\n      case ResponseType.json:\n        res.headers.contentType = ContentType.json;\n        try {\n          res.write(jsonEncode(data));\n        } catch (e) {\n          res.write('jsonEncode Error: $e');\n        }\n        await res.close();\n        break;\n      case ResponseType.html:\n        res.headers.contentType = ContentType.html;\n        res.write(data);\n        await res.close();\n        break;\n      case ResponseType.sse:\n        await sseHandler(res);\n        break;\n      case ResponseType.streamFile:\n        StreamFile? stream = StreamFile(\n          fileName: data['fileName'],\n          bytes: data['bytes'],\n        ).call();\n        if (stream == null) {\n          res.headers.contentType = ContentType.json;\n          res.write(jsonEncode({\"message\": \"File not found\"}));\n          await res.close();\n          break;\n        }\n        res.headers.contentType = stream.contentType;\n        res.headers.contentLength = stream.length;\n        res.addStream(stream.stream!).then((_) => res.close());\n        break;\n      case ResponseType.download:\n        StreamFile? stream = StreamFile(\n          fileName: data['fileName'],\n          bytes: data['bytes'],\n        ).call();\n        if (stream == null) {\n          res.headers.contentType = ContentType.json;\n          res.write(jsonEncode({\"message\": \"File not found\"}));\n          await res.close();\n          break;\n        }\n        res.headers.contentType = stream.contentType;\n        res.headers.contentLength = stream.length;\n        res.headers.add(\"Content-Disposition\", stream.contentDisposition);\n        res.addStream(stream.stream!).then((_) => res.close());\n        break;\n      case ResponseType.redirect:\n        res.headers.set(HttpHeaders.locationHeader, data);\n        await res.close();\n      default:\n        res.write(data);\n        await res.close();\n    }\n  }\n\n  static Response redirect(String location) => Response(\n    responseType: ResponseType.redirect,\n    data: location,\n    httpStatusCode: HttpStatus.found,\n  );\n\n  static Response json(dynamic jsonData, [int statusCode = HttpStatus.ok]) =>\n      Response(\n        data: jsonData,\n        responseType: ResponseType.json,\n        httpStatusCode: statusCode,\n      );\n\n  static Response jsonWithHeader(\n    dynamic jsonData, {\n    int statusCode = HttpStatus.ok,\n    Map<String, String> headers = const {},\n  }) => Response(\n    data: jsonData,\n    responseType: ResponseType.json,\n    httpStatusCode: statusCode,\n    headers: headers,\n  );\n\n  static Response html(\n    dynamic htmlData, {\n    Map<String, String> headers = const {},\n  }) => Response(\n    data: htmlData,\n    responseType: ResponseType.html,\n    headers: headers,\n  );\n\n  static Response file(\n    String fileName,\n    Uint8List bytes, {\n    Map<String, String> headers = const {},\n  }) => Response(\n    data: {\"fileName\": fileName, \"bytes\": bytes},\n    responseType: ResponseType.streamFile,\n    headers: headers,\n  );\n\n  static Response sse(\n    Stream<dynamic> eventStream, {\n    int statusCode = HttpStatus.ok,\n    Map<String, String> headers = const {},\n  }) => Response(\n    data: eventStream,\n    responseType: ResponseType.sse,\n    httpStatusCode: statusCode,\n    headers: headers,\n  );\n\n  static Response download(\n    String fileName,\n    Uint8List bytes, {\n    Map<String, String> headers = const {},\n  }) => Response(\n    data: {\"fileName\": fileName, \"bytes\": bytes},\n    responseType: ResponseType.download,\n    headers: headers,\n  );\n\n  static Response back([String? key, String? message]) {\n    String previousRoute = RouteHistory().previousRoute;\n    if (key != null && message != null) {\n      TemplateEngine().sessions[key] = message;\n    }\n    if (previousRoute.isNotEmpty) {\n      return Response(\n        responseType: ResponseType.redirect,\n        data: previousRoute,\n        httpStatusCode: HttpStatus.found,\n      );\n    }\n    return Response(\n      responseType: ResponseType.redirect,\n      data: RouteHistory().currentRoute,\n      httpStatusCode: HttpStatus.found,\n    );\n  }\n\n  static Response backWithInput([String? input, String? message]) {\n    String previousRoute = RouteHistory().previousRoute;\n    if (input != null && message != null) {\n      TemplateEngine().sessionErrors[input] = message;\n    }\n    if (previousRoute.isNotEmpty) {\n      return Response(\n        responseType: ResponseType.redirect,\n        data: previousRoute,\n        httpStatusCode: HttpStatus.found,\n      );\n    }\n    return Response(\n      responseType: ResponseType.redirect,\n      data: RouteHistory().currentRoute,\n      httpStatusCode: HttpStatus.found,\n    );\n  }\n}\n"
  },
  {
    "path": "lib/src/http/response/stream_file.dart",
    "content": "import 'dart:io';\nimport 'dart:typed_data';\nimport 'package:path/path.dart' as path;\nimport 'package:mime/mime.dart';\n\nclass StreamFile {\n  final String fileName;\n  final Uint8List bytes;\n  StreamFile({required this.fileName, required this.bytes});\n\n  ContentType? _contentType;\n  Stream<List<int>>? _stream;\n  int _length = 0;\n\n  ContentType get contentType =>\n      _contentType ?? ContentType('application', 'octet-stream');\n\n  Stream<List<int>>? get stream => _stream;\n  int get length => _length;\n\n  String get contentDisposition =>\n      'attachment; filename=\"${path.basename(fileName)}\"';\n\n  StreamFile? call() {\n    String mimeType =\n        lookupMimeType(path.basename(fileName), headerBytes: bytes) ?? \"\";\n\n    String primaryType = mimeType.split('/').first;\n    String subType = mimeType.split('/').last;\n\n    _contentType = ContentType(primaryType, subType);\n\n    _stream = Stream<List<int>>.fromIterable([bytes]);\n\n    _length = bytes.length;\n\n    return this;\n  }\n}\n"
  },
  {
    "path": "lib/src/http/session/session_file_store.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'package:crypto/crypto.dart';\n\nimport '../../cryptographic/vania_encryption.dart';\n\nimport 'package:vania/src/utils/helper.dart' show env;\n\nclass SessionFileStore {\n  static final SessionFileStore _singleton = SessionFileStore._internal();\n  factory SessionFileStore() => _singleton;\n  SessionFileStore._internal();\n\n  final String _secretKey = env('APP_KEY');\n  final String sessionPath = 'storage/framework/sessions';\n\n  /// Stores session data in a file with the given session ID.\n  ///\n  /// This method creates or overwrites a file in the session path to store\n  /// session data. The data is serialized to JSON, encrypted, and then written\n  /// to the file. An expiration timestamp is added to the session data, which\n  /// is set to the current time plus the specified duration. The file is locked\n  /// during the write operation to ensure data integrity.\n  ///\n  /// Parameters:\n  /// - [sessionId]: The unique identifier for the session.\n  /// - [data]: A map containing the session data to be stored.\n  /// - [duration]: (Optional) The duration for which the session is valid.\n  ///   Defaults to one hour.\n  ///\n  Future<void> storeSession(\n    String sessionId,\n    Map<String, dynamic> data, {\n    Duration duration = const Duration(hours: 1),\n  }) async {\n    sessionId = _makeHash(sessionId).toString();\n    final file = File('$sessionPath/$sessionId');\n    if (!await file.exists()) {\n      await file.create(recursive: true);\n    }\n    int expiration =\n        DateTime.now().toUtc().millisecondsSinceEpoch + duration.inMilliseconds;\n    final Map<String, dynamic> sessionData = {\n      \"data\": data,\n      \"expiration\": expiration,\n    };\n    final String content = await VaniaEncryption.encryptString(\n      json.encode(sessionData),\n      _secretKey,\n    );\n\n    final raf = await file.open(mode: FileMode.write);\n    try {\n      await raf.writeFrom(utf8.encode(content));\n    } finally {\n      try {\n        await raf.unlock();\n      } catch (_) {}\n      await raf.close();\n    }\n  }\n\n  /// Retrieves the session data associated with the given session ID.\n  ///\n  /// This function checks if a session file exists for the given session ID,\n  /// reads its contents, and decrypts the data. If the session data is valid\n  /// and not expired, it returns the session data as a map. If the session\n  /// does not exist, is expired, or decryption fails, it returns null.\n  ///\n  /// Parameters:\n  /// - [sessionId]: The unique identifier for the session to be retrieved.\n  ///\n  /// Returns:\n  /// A map containing the session data, or null if the session does not exist,\n  /// is expired, or if there is an error in reading or decrypting the file.\n  ///\n  Future<Map<String, dynamic>?> retrieveSession(String sessionId) async {\n    sessionId = _makeHash(sessionId).toString();\n    final file = File('$sessionPath/$sessionId');\n    if (!await file.exists()) {\n      return null;\n    }\n    final raf = await file.open(mode: FileMode.read);\n    String fileContent = '';\n    try {\n      final int length = await raf.length();\n      final List<int> bytes = await raf.read(length);\n      fileContent = utf8.decode(bytes);\n    } finally {\n      try {\n        await raf.unlock();\n      } catch (_) {}\n      await raf.close();\n    }\n\n    final String decrypted = await VaniaEncryption.decryptString(\n      fileContent,\n      _secretKey,\n    );\n    if (decrypted.isEmpty) {\n      return null;\n    }\n    final Map<String, dynamic> data = json.decode(decrypted);\n    int expiration = int.tryParse(data['expiration'].toString()) ?? 0;\n\n    if (!DateTime.now().toUtc().isBefore(\n      DateTime.fromMillisecondsSinceEpoch(expiration),\n    )) {\n      await file.delete();\n      return null;\n    }\n    return data['data'];\n  }\n\n  Future<bool> hasSession(String sessionId) async {\n    Map<String, dynamic>? data = await retrieveSession(sessionId);\n    return data != null;\n  }\n\n  /// Deletes a specific session from the session storage.\n  ///\n  /// This function first checks if a session exists for the given session ID.\n  /// If the session exists, it deletes the session by setting the expiration\n  /// time to the current time minus 1 millisecond, and encrypts the new data\n  /// using the `VaniaEncryption` class. The encrypted data is then written\n  /// to the session file. If the session does not exist, this function does\n  /// nothing.\n  ///\n  /// Parameters:\n  /// - [sessionId]: The unique identifier for the session to be deleted.\n  ///\n  /// Returns:\n  /// A Future that resolves to `null`, indicating that the session was\n  /// successfully deleted.\n  ///\n  Future<void> deleteSession(String sessionId) async {\n    sessionId = _makeHash(sessionId).toString();\n    final file = File('$sessionPath/$sessionId');\n    if (await file.exists()) {\n      final raf = await file.open(mode: FileMode.write);\n      try {\n        int expiration = DateTime.now().toUtc().millisecondsSinceEpoch - 1;\n        final String content = await VaniaEncryption.encryptString(\n          json.encode({\"data\": {}, \"expiration\": expiration}),\n          _secretKey,\n        );\n        await raf.writeFrom(utf8.encode(content));\n      } finally {\n        try {\n          await raf.unlock();\n        } catch (_) {}\n        await raf.close();\n      }\n    }\n  }\n\n  Digest _makeHash(String key) {\n    var secKey = utf8.encode(_secretKey);\n    var bytes = utf8.encode(key);\n    var hmacSha256 = Hmac(sha256, secKey);\n    return hmacSha256.convert(bytes);\n  }\n}\n"
  },
  {
    "path": "lib/src/http/session/session_manager.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'package:crypto/crypto.dart';\nimport 'package:vania/src/utils/functions.dart';\nimport 'package:vania/src/utils/helper.dart' show env;\nimport 'session_file_store.dart';\n\nclass SessionManager {\n  HttpRequest? _request;\n\n  String sessionKey = '${env<String>('APP_NAME', 'Vania')}_session';\n\n  String _csrfToken = '';\n\n  String get csrfToken => _csrfToken;\n\n  Map<String, dynamic> _allSessions = {};\n\n  Map<String, dynamic> get allSessions => _allSessions;\n\n  final Duration _sessionLifeTime = Duration(\n    seconds: env<int>('SESSION_LIFETIME', 9000),\n  );\n  bool secureSession = env<bool>('SECURE_SESSION', true);\n\n  /// Generates a new session ID.\n  ///\n  /// This method creates a 64-byte random key using a secure random number generator,\n  /// and encodes it using base64 URL encoding to ensure a unique and safe session ID.\n  ///\n  /// Returns:\n  /// A base64 URL encoded string representing the session ID.\n  String _generateSessionId() {\n    final keyBytes = randomString(length: 64, numbers: true);\n    return base64Url.encode(utf8.encode(keyBytes));\n  }\n\n  /// Creates a CSRF token and sets it as a secure cookie in the HTTP response.\n  ///\n  /// This function checks if an 'XSRF-TOKEN' cookie is already present in the\n  /// request. If not, it generates a new random CSRF token along with an\n  /// initialization vector (IV), and sets them as cookies in the response. The\n  /// token and IV are also stored in the session for future validation.\n  ///\n  /// Parameters:\n  /// - `request`: The incoming HTTP request containing the cookies.\n  /// - `response`: The HTTP response where the CSRF token cookie will be added.\n  ///\n  /// The generated CSRF token is URL-safe and securely stored in the session with\n  /// the specified session lifetime. The cookie is configured with security\n  /// attributes such as domain, expiration, SameSite policy, and HTTP-only flag\n  /// to mitigate CSRF attacks.\n  Future<void> createXsrfToken(\n    HttpRequest request,\n    HttpResponse response,\n  ) async {\n    Cookie requestCookie = request.cookies.firstWhere(\n      (cookie) => cookie.name == 'XSRF-TOKEN',\n      orElse: () => Cookie('XSRF-TOKEN', ''),\n    );\n    if (requestCookie.value.isEmpty) {\n      await _generateNewCsrfToken(response);\n    } else {\n      String? storedToken = _allSessions['x_csrf_token'];\n      if (storedToken == null || storedToken.isEmpty) {\n        await _generateNewCsrfToken(response);\n      } else {\n        _csrfToken = storedToken;\n      }\n    }\n  }\n\n  /// Generates a new CSRF token and stores it in the session and a secure cookie.\n  ///\n  /// This method generates a random CSRF token and initialization vector (IV),\n  /// stores them in the session and a secure cookie, and sets the cookie in the\n  /// response. The cookie is configured with security attributes such as\n  /// expiration, SameSite policy, and HTTP-only flag to mitigate CSRF attacks.\n  ///\n  /// Parameters:\n  /// - `response`: The HTTP response where the CSRF token cookie will be added.\n  ///\n  /// The generated CSRF token is URL-safe and securely stored in the session with\n  /// the specified session lifetime.\n  Future<void> _generateNewCsrfToken(HttpResponse response) async {\n    String token = randomString(length: 40, numbers: true);\n    String iv = randomString(length: 32, numbers: true);\n    await setSession('x_csrf_token', token);\n    await setSession('x_csrf_iv', iv);\n    _csrfToken = token;\n    String cookieValue = _computeCsrfCookieValue(token, iv);\n    Cookie cookie = Cookie('XSRF-TOKEN', cookieValue)\n      ..expires = DateTime.now().add(Duration(seconds: 9000))\n      ..sameSite = SameSite.lax\n      ..secure = secureSession\n      ..path = '/'\n      ..httpOnly = true;\n    response.cookies.add(cookie);\n  }\n\n  /// Computes the value of the CSRF cookie for the given CSRF token and\n  /// initialization vector (IV).\n\n  /// The method uses the HMAC algorithm with SHA-512 to create a digest from\n  /// the given token and IV. The digest is then encoded in Base64 and stored\n  /// in the CSRF cookie in the response. The cookie is configured with\n  /// security attributes such as expiration, SameSite policy, and HTTP-only flag\n  /// to mitigate CSRF attacks.\n  String _computeCsrfCookieValue(String token, String iv) {\n    var hmac = Hmac(sha512, utf8.encode(iv));\n    final Digest digest = hmac.convert(utf8.encode(token));\n    return base64.encode(\n      utf8.encode(jsonEncode({'token': base64.encode(digest.bytes)})),\n    );\n  }\n\n  /// Starts a new session or retrieves an existing session from the request.\n  ///\n  /// This method initializes a session for the given HTTP request and response.\n  /// If a sessionKey cookie is already present in the request, its value is\n  /// used as the session . Otherwise, a new session is generated and set\n  /// as a cookie in the response.\n  ///\n  /// The session  is stored in a cookie with properties configured for HTTP\n  /// only access, insecure transmission (consider changing to true for secure\n  /// transmission), a path set to '/', and an expiration set to the session\n  /// timeout duration.\n  ///\n  /// Parameters:\n  /// - [request]: The incoming HTTP request containing cookies.\n  /// - [response]: The HTTP response where the session cookie will be added.\n  ///\n  /// Returns:\n  /// A string representing the session.\n  Future<void> sessionStart(HttpRequest request, HttpResponse response) async {\n    _request = null;\n    _request ??= request;\n    final cookie = request.cookies.firstWhere(\n      (c) => c.name == sessionKey,\n      orElse: () => Cookie(sessionKey, _generateSessionId()),\n    );\n    String sessionId = cookie.value;\n\n    response.cookies.add(\n      Cookie(sessionKey, sessionId)\n        ..httpOnly = true\n        ..secure = secureSession\n        ..path = '/'\n        ..sameSite = SameSite.lax\n        ..expires = DateTime.now().add(_sessionLifeTime),\n    );\n\n    await _featchAllSessions(sessionId);\n\n    await createXsrfToken(request, response);\n  }\n\n  String? getSessionId() {\n    final cookie = _request?.cookies.firstWhere(\n      (c) => c.name == sessionKey,\n      orElse: () => Cookie(sessionKey, ''),\n    );\n    return cookie?.value;\n  }\n\n  Future<void> _featchAllSessions(String sessionId) async {\n    _allSessions = await SessionFileStore().retrieveSession(sessionId) ?? {};\n  }\n\n  Future<T> getSession<T>(String key) async {\n    if (_allSessions.isEmpty) {\n      final sessionId = getSessionId();\n      if (sessionId != null) {\n        _allSessions =\n            await SessionFileStore().retrieveSession(sessionId) ?? {};\n      }\n    }\n\n    if (_allSessions[key] == null) {\n      return null as T;\n    }\n\n    if (T.toString() == 'int') {\n      return int.tryParse(_allSessions[key].toString()) as T;\n    }\n\n    if (T.toString() == 'double') {\n      return double.tryParse(_allSessions[key].toString()) as T;\n    }\n\n    if (T.toString() == 'bool') {\n      return bool.tryParse(_allSessions[key].toString()) as T;\n    }\n\n    return _allSessions[key];\n  }\n\n  /// Stores a value in the session data associated with the current session ID.\n  ///\n  /// If a session is found, it verifies the existence and validity of the session.\n  /// If the session exists, it updates the session data by adding the given key-value pair,\n  /// and saves the updated session data. If the session does not exist or is invalid,\n  /// it does not store the value.\n  ///\n  Future<void> setSession(String key, dynamic value) async {\n    final sessionId = getSessionId();\n    if (sessionId != null) {\n      Map<String, dynamic>? session = await SessionFileStore().retrieveSession(\n        sessionId,\n      );\n      if (session != null) {\n        session.addAll({key: value});\n      } else {\n        session = {key: value};\n      }\n      _allSessions = session;\n      await SessionFileStore().storeSession(sessionId, session);\n    }\n  }\n\n  /// Deletes a specific key from the current session data.\n  ///\n  /// If a session is found, it verifies the existence and validity of the session.\n  /// If the session exists, it removes the given key from the session data, and saves the\n  /// updated session data. If the session does not exist or is invalid, it does not delete\n  /// the key.\n  ///\n  /// Parameters:\n  /// - [key]: The key to be deleted from the session data.\n  Future<void> deleteSession(String key) async {\n    final String? sessionId = getSessionId();\n    if (sessionId != null) {\n      Map<String, dynamic>? session = await SessionFileStore().retrieveSession(\n        sessionId,\n      );\n      if (session != null) {\n        session.remove(key);\n        _allSessions = session;\n        await SessionFileStore().storeSession(sessionId, session);\n      }\n    }\n  }\n\n  Future<void> destroyAllSessions() async {\n    final sessionId = getSessionId();\n    if (sessionId != null) {\n      _allSessions = {};\n      await SessionFileStore().storeSession(sessionId, {});\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/custom_validation_rule.dart",
    "content": "class CustomValidationRule {\n  final String ruleName;\n  final String message;\n  final Future<bool> Function(Map<String, dynamic>, dynamic, String?) fn;\n\n  CustomValidationRule({\n    required this.ruleName,\n    required this.message,\n    required this.fn,\n  });\n}\n"
  },
  {
    "path": "lib/src/http/validation/field_validation.dart",
    "content": "class FieldValidation {\n  final String fieldName;\n  final List<String> _rules = [];\n  final Map<String, String> _messages = {};\n\n  FieldValidation(this.fieldName);\n\n  Map<String, String> get toMapMessages => _messages.map((key, message) {\n    final parts = key.split('.*.');\n    final ruleName = parts.isNotEmpty ? parts.last : key;\n    return MapEntry(ruleName, message);\n  });\n\n  FieldValidation alpha({String? messages}) {\n    _rules.add('alpha');\n    if (messages != null) {\n      _messages['$fieldName.alpha'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation alphaDash({String? messages}) {\n    _rules.add('alpha_dash');\n    if (messages != null) {\n      _messages['$fieldName.alpha_dash'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation alphaNumeric({String? messages}) {\n    _rules.add('alpha_numeric');\n    if (messages != null) {\n      _messages['$fieldName.alpha_numeric'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation between(int first, int second, {String? messages}) {\n    _rules.add('between:$first,$second');\n    if (messages != null) {\n      _messages['$fieldName.between'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation boolean({String? messages}) {\n    _rules.add('boolean');\n    if (messages != null) {\n      _messages['$fieldName.boolean'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation confirmed({String? messages}) {\n    _rules.add('confirmed');\n    if (messages != null) {\n      _messages['$fieldName.confirmed'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation date({String? messages}) {\n    _rules.add('date');\n    if (messages != null) {\n      _messages['$fieldName.date'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation dateTime({String? messages}) {\n    _rules.add('date_time');\n    if (messages != null) {\n      _messages['$fieldName.date_time'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation email({String? messages}) {\n    _rules.add('email');\n    if (messages != null) {\n      _messages['$fieldName.email'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation endWith(String value, {String? messages}) {\n    _rules.add('end_with:$value');\n    if (messages != null) {\n      _messages['$fieldName.end_with'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation file(List<String> types, {String? messages}) {\n    final formatted = 'file:${types.join(',')}';\n    _rules.add(formatted);\n    if (messages != null) {\n      _messages['$fieldName.file'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation greaterThan(int value, {String? messages}) {\n    _rules.add('greater_than:$value');\n    if (messages != null) {\n      _messages['$fieldName.greater_than'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation image({String? messages}) {\n    _rules.add('image');\n    if (messages != null) {\n      _messages['$fieldName.image'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation integer({String? messages}) {\n    _rules.add('integer');\n    if (messages != null) {\n      _messages['$fieldName.integer'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation ip({String? messages}) {\n    _rules.add('ip');\n    if (messages != null) {\n      _messages['$fieldName.ip'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation isDouble({String? messages}) {\n    _rules.add('double');\n    if (messages != null) {\n      _messages['$fieldName.double'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation isIn(List<String> value, {String? messages}) {\n    _rules.add('in:${value.join(',')}');\n    if (messages != null) {\n      _messages['$fieldName.in'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation isList({String? messages}) {\n    _rules.add('array');\n    if (messages != null) {\n      _messages['$fieldName.array'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation json({String? messages}) {\n    _rules.add('json');\n    if (messages != null) {\n      _messages['$fieldName.json'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation lengthBetween(int first, int second, {String? messages}) {\n    _rules.add('length_between:$first,$second');\n    if (messages != null) {\n      _messages['$fieldName.length_between'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation lessThan(int value, {String? messages}) {\n    _rules.add('less_than:$value');\n    if (messages != null) {\n      _messages['$fieldName.less_than'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation max(int value, {String? messages}) {\n    _rules.add('max:$value');\n    if (messages != null) {\n      _messages['$fieldName.max'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation maxLength(int value, {String? messages}) {\n    _rules.add('max_length:$value');\n    if (messages != null) {\n      _messages['$fieldName.max_length'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation min(int value, {String? messages}) {\n    _rules.add('min:$value');\n    if (messages != null) {\n      _messages['$fieldName.min'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation minLength(int value, {String? messages}) {\n    _rules.add('min_length:$value');\n    if (messages != null) {\n      _messages['$fieldName.min_length'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation notIn(List<String> value, {String? messages}) {\n    _rules.add('not_in:${value.join(',')}');\n    if (messages != null) {\n      _messages['$fieldName.not_in'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation numeric({String? messages}) {\n    _rules.add('numeric');\n    if (messages != null) {\n      _messages['$fieldName.numeric'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation regExp(String rule, {String? messages}) {\n    _rules.add('reg_exp:$rule');\n    if (messages != null) {\n      _messages['$fieldName.reg_exp'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation required({String? messages}) {\n    _rules.add('required');\n    if (messages != null) {\n      _messages['$fieldName.required'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation requiredIf(List<String> value, {String? messages}) {\n    _rules.add('required_if:${value.join(',')}');\n    if (messages != null) {\n      _messages['$fieldName.required_if'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation requiredIfNot(List<String> value, {String? messages}) {\n    _rules.add('required_if_not:${value.join(',')}');\n    if (messages != null) {\n      _messages['$fieldName.required_if_not'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation startWith(String value, {String? messages}) {\n    _rules.add('start_with:$value');\n    if (messages != null) {\n      _messages['$fieldName.start_with'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation string({String? messages}) {\n    _rules.add('string');\n    if (messages != null) {\n      _messages['$fieldName.string'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation unique(String table, {String? messages}) {\n    _rules.add('unique:$table,$fieldName');\n    if (messages != null) {\n      _messages['$fieldName.unique'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation url({String? messages}) {\n    _rules.add('url');\n    if (messages != null) {\n      _messages['$fieldName.url'] = messages;\n    }\n    return this;\n  }\n\n  FieldValidation uuid({String? messages}) {\n    _rules.add('uuid');\n    if (messages != null) {\n      _messages['$fieldName.uuid'] = messages;\n    }\n    return this;\n  }\n\n  @override\n  String toString() {\n    return _rules.join('|');\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/nested_validation.dart",
    "content": "import 'package:vania/src/extensions/map_extension.dart';\n\nimport 'validation_item.dart';\n\nclass NestedValidation {\n  final String field;\n  final Map<String, dynamic> data;\n  final String rule;\n\n  final List<ValidationItem> fieldsToValidate = <ValidationItem>[];\n\n  NestedValidation({\n    required this.data,\n    required this.field,\n    required this.rule,\n  }) {\n    _process();\n  }\n\n  void _process() {\n    List<String> parts = field.split('.*.');\n\n    /// eg. from products.*.{field} to products\n    String mainField = parts[0];\n\n    /// getting list of products from data.\n    List<Map<String, dynamic>> list = data.getParam(mainField);\n\n    /// remove main filed to loop and get final field to validate\n    List<String> fieldsExceptMainField = parts.sublist(1);\n\n    _processNestedField(mainField, fieldsExceptMainField, rule, list);\n  }\n\n  void _processNestedField(\n    String mainField,\n    List<String> fields,\n    String rule,\n    List<Map<String, dynamic>> items,\n  ) {\n    items.asMap().forEach((int index, Map<String, dynamic> item) {\n      String field = fields.first;\n      String fieldNameWithPositionIndex = '$mainField.$index.$field';\n\n      /// this mean we already get field value to validate\n      if (fields.length == 1) {\n        dynamic value = item.getParam(field);\n\n        fieldsToValidate.add(\n          ValidationItem(\n            field: fieldNameWithPositionIndex,\n            name: field.split('.').last,\n            value: value,\n            rule: rule,\n          ),\n        );\n      }\n      /// this mean we still need to get the actual field value\n      else if (fields.length >= 2) {\n        List<String> fieldsExceptMainField = fields.sublist(1);\n        List<Map<String, dynamic>> newItems = item.getParam(field);\n\n        _processNestedField(\n          fieldNameWithPositionIndex,\n          fieldsExceptMainField,\n          rule,\n          newItems,\n        );\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/rules.dart",
    "content": "import 'package:vania/src/database/db.dart';\nimport 'package:vania/src/http/request/request_file.dart';\n\nclass Rules {\n  /// Check field is required\n  static bool isRequired(\n    Map<String, dynamic> data,\n    dynamic value,\n    String args,\n  ) {\n    if (value == null) {\n      return false;\n    }\n    if (value is List) {\n      return value.isNotEmpty;\n    }\n    return value.toString().isNotEmpty;\n  }\n\n  /// Check field is email\n  static bool isEmail(Map<String, dynamic> data, dynamic value, String args) {\n    RegExp emailRegex = RegExp(\n      r'^[\\w-]+(\\.[\\w-]+)*@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*(\\.[a-zA-Z]{2,})$',\n    );\n    return emailRegex.hasMatch(value.toString());\n  }\n\n  /// Check field is string\n  static bool isString(Map<String, dynamic> data, dynamic value, String args) {\n    return value is String;\n  }\n\n  /// Check field is number\n  static bool isNumeric(Map<String, dynamic> data, dynamic value, String args) {\n    try {\n      num.parse(value.toString());\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  /// Check field is ip address\n  static bool isIp(Map<String, dynamic> data, dynamic value, String args) {\n    RegExp ipAddressRegex = RegExp(\n      r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',\n    );\n    return ipAddressRegex.hasMatch(value.toString());\n  }\n\n  /// Check field is boolean\n  static bool isBoolean(Map<String, dynamic> data, dynamic value, String args) {\n    try {\n      bool.parse(value.toString());\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  /// Check field is integer\n  static bool isInteger(Map<String, dynamic> data, dynamic value, String args) {\n    try {\n      int.parse(value.toString());\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  /// Check field is double\n  static bool isDouble(Map<String, dynamic> data, dynamic value, String args) {\n    try {\n      double.parse(value.toString());\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  /// Check field is array\n  static bool isArray(Map<String, dynamic> data, dynamic value, String args) {\n    return value is List;\n  }\n\n  /// Check field is map or json\n  static bool isJson(Map<String, dynamic> data, dynamic value, String args) {\n    return value is Map;\n  }\n\n  /// Check field is alphabetic\n  static bool isAlpha(Map<String, dynamic> data, dynamic value, String args) {\n    RegExp alphabeticRegex = RegExp(r'^[a-zA-Z]+$');\n    return alphabeticRegex.hasMatch(value.toString());\n  }\n\n  /// Check field is only with alphabetic, dash or underscore\n  static bool isAlphaDash(\n    Map<String, dynamic> data,\n    dynamic value,\n    String args,\n  ) {\n    RegExp alphaDashRegex = RegExp(r'^[a-zA-Z-_]+$');\n    return alphaDashRegex.hasMatch(value.toString());\n  }\n\n  /// Check field is only with alphabetic, number\n  static bool isAlphaNumeric(\n    Map<String, dynamic> data,\n    dynamic value,\n    String args,\n  ) {\n    RegExp alphaNumericRegex = RegExp(r'^[a-zA-Z0-9]+$');\n    return alphaNumericRegex.hasMatch(value.toString());\n  }\n\n  /// Check field is a date or date time\n  static bool isDate(Map<String, dynamic> data, dynamic value, String args) {\n    try {\n      DateTime? dateTime = DateTime.tryParse(value.toString());\n      return dateTime != null;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  /// Check field is a valid url\n  static bool isUrl(Map<String, dynamic> data, dynamic value, String args) {\n    try {\n      Uri? uri = Uri.tryParse(value);\n      return uri != null && uri.hasScheme && uri.hasAuthority;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  /// Check field is a valid uuid\n  static bool isUUID(Map<String, dynamic> data, dynamic value, String args) {\n    RegExp uuidRegex = RegExp(\n      r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$',\n    );\n    return uuidRegex.hasMatch(value.toString());\n  }\n\n  /// Check field character is given max length\n  static bool maxLength(Map<String, dynamic> data, dynamic value, String max) {\n    return value.toString().length <= num.parse(max.toString());\n  }\n\n  /// Check field character is given min length\n  static bool minLength(Map<String, dynamic> data, dynamic value, String min) {\n    return value.toString().length >= num.parse(min.toString());\n  }\n\n  /// Check field character is between given length\n  static bool lengthBetween(\n    Map<String, dynamic> data,\n    dynamic value,\n    String values,\n  ) {\n    List<String> parts = values.toString().split(',');\n    num value1 = num.parse(parts[0]);\n    num value2 = num.parse(parts[1]);\n    value = value.toString().length;\n    if (value1 < value2) {\n      return value >= value1 && value <= value2;\n    }\n    return value >= value2 && value <= value1;\n  }\n\n  /// Check field is unique\n  static Future<bool> unique(\n    Map<String, dynamic> data,\n    dynamic value,\n    String values,\n  ) async {\n    List<String> parts = values.toString().split(',');\n    String table = parts[0];\n    String column = parts[1];\n    final exists = await DB\n        .table(table)\n        .whereEqualTo(column, value)\n        .doesntExist();\n    return exists;\n  }\n\n  /// Check field is between given value\n  static bool between(Map<String, dynamic> data, dynamic value, String values) {\n    List<String> parts = values.toString().split(',');\n    num value1 = num.parse(parts[0]);\n    num value2 = num.parse(parts[1]);\n    value = num.parse(value.toString());\n    if (value1 < value2) {\n      return value >= value1 && value <= value2;\n    }\n    return value >= value2 && value <= value1;\n  }\n\n  /// Check field is greater than given value\n  static bool greaterThan(\n    Map<String, dynamic> data,\n    dynamic value,\n    String compare,\n  ) {\n    value = num.parse(value.toString());\n    return value > num.parse(compare.toString());\n  }\n\n  /// Check field is less than given value\n  static bool lessThan(\n    Map<String, dynamic> data,\n    dynamic value,\n    String compare,\n  ) {\n    value = num.parse(value.toString());\n    return value < num.parse(compare.toString());\n  }\n\n  /// Check field is reach min value\n  static bool min(Map<String, dynamic> data, dynamic value, String compare) {\n    value = num.parse(value.toString());\n    return value >= num.parse(compare.toString());\n  }\n\n  /// Check field is not over max value\n  static bool max(Map<String, dynamic> data, dynamic value, String compare) {\n    value = num.parse(value.toString());\n    return value <= num.parse(compare.toString());\n  }\n\n  /// Check field is in given array\n  static bool inArray(Map<String, dynamic> data, dynamic value, String arr) {\n    List<String> array = arr.toString().split(',');\n    return array.contains(value.toString());\n  }\n\n  /// Check field is not in given array\n  static bool notInArray(Map<String, dynamic> data, dynamic value, String arr) {\n    List<String> array = arr.toString().split(',');\n    return !array.contains(value.toString());\n  }\n\n  /// Check field start with given text\n  static bool startWith(\n    Map<String, dynamic> data,\n    dynamic value,\n    String start,\n  ) {\n    return value.toString().startsWith(start.toString());\n  }\n\n  /// Check field end with given text\n  static bool endWith(Map<String, dynamic> data, dynamic value, String end) {\n    return value.toString().endsWith(end.toString());\n  }\n\n  /// Check 2 password are matched\n  static bool confirmed(Map<String, dynamic> data, dynamic value, String key) {\n    key = key.toString().isEmpty ? 'password_confirmation' : key;\n    dynamic confirmValue = data[key];\n    return confirmValue == value;\n  }\n\n  /// Check field is required when condition is matched\n  static bool requiredIf(\n    Map<String, dynamic> data,\n    dynamic value,\n    String payload,\n  ) {\n    List<String> parts = payload.toString().split(',');\n    String secondField = parts[0];\n    String secondFieldValueFromRule = parts[1].toString();\n    String? secondFieldValueFromRequest = data[secondField].toString();\n\n    /// Check only when req value and rule value are same\n    if (secondFieldValueFromRule == secondFieldValueFromRequest) {\n      return isRequired(data, value, '');\n    }\n    return true;\n  }\n\n  /// Check field is required when condition is not matched\n  static bool requiredIfNot(\n    Map<String, dynamic> data,\n    dynamic value,\n    String payload,\n  ) {\n    List<String> parts = payload.toString().split(',');\n    String secondField = parts[0];\n    String secondFieldValueFromRule = parts[1].toString();\n    String? secondFieldValueFromRequest = data[secondField].toString();\n\n    /// Check only when req value and rule value are same\n    if (secondFieldValueFromRule != secondFieldValueFromRequest) {\n      return isRequired(data, value, '');\n    }\n    return true;\n  }\n\n  /// Check field is valid image\n  static bool isImage(Map<String, dynamic> data, dynamic value, String args) {\n    if (value is! RequestFile) {\n      return false;\n    }\n    List<String> extensions = <String>[\n      'jpg',\n      'jpeg',\n      'png',\n      'gif',\n      'bmp',\n      'svg',\n      'webp',\n      'tiff',\n      'ico',\n    ];\n    if (args.toString().isNotEmpty) {\n      extensions = args.toString().split(',');\n    }\n    if (extensions.contains(value.extension)) {\n      return true;\n    }\n    return false;\n  }\n\n  /// Check field is a file\n  /// not a file => false\n  /// if added supported extension in validation, check with extension\n  /// return true\n  static bool isFile(Map<String, dynamic> data, dynamic value, String args) {\n    if (value is! RequestFile && value is! List<RequestFile>) {\n      return false;\n    }\n\n    if (args.isEmpty) {\n      return true;\n    }\n\n    List<String> validExtensions = args.split(',');\n\n    bool hasValidExtension(RequestFile file) {\n      return validExtensions.contains(file.extension);\n    }\n\n    if (value is List<RequestFile>) {\n      return value.every(hasValidExtension);\n    } else {\n      return hasValidExtension(value);\n    }\n  }\n\n  static bool regExp(Map<String, dynamic> data, dynamic value, String args) {\n    RegExp regExp = RegExp(args);\n    return regExp.hasMatch(value.toString());\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/export_chain_validation.dart",
    "content": "export 'rules/between.dart';\nexport 'rules/confirmed.dart';\nexport 'rules/end_width.dart';\nexport 'rules/greater_than.dart';\nexport 'rules/in_array.dart';\nexport 'rules/is_alpha.dart';\nexport 'rules/is_alpha_dash.dart';\nexport 'rules/is_alpha_numeric.dart';\nexport 'rules/is_array.dart';\nexport 'rules/is_boolean.dart';\nexport 'rules/is_date.dart';\nexport 'rules/is_double.dart';\nexport 'rules/is_email.dart';\nexport 'rules/is_file.dart';\nexport 'rules/is_image.dart';\nexport 'rules/is_integer.dart';\nexport 'rules/is_ip.dart';\nexport 'rules/is_json.dart';\nexport 'rules/is_numeric.dart';\nexport 'rules/is_required.dart';\nexport 'rules/is_string.dart';\nexport 'rules/is_url.dart';\nexport 'rules/is_uuid.dart';\nexport 'rules/lenght_between.dart';\nexport 'rules/less_than.dart';\nexport 'rules/max.dart';\nexport 'rules/max_lenght.dart';\nexport 'rules/min_lenght.dart';\nexport 'rules/not_in_array.dart';\nexport 'rules/required_if.dart';\nexport 'rules/required_if_not.dart';\nexport 'rules/start_with.dart';\nexport 'validation.dart';\nexport 'validation_rule.dart';\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/between.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass Between extends ValidationRule {\n  final num lowerBoundary;\n  final num higherBoundary;\n  Between({\n    required this.lowerBoundary,\n    required this.higherBoundary,\n    super.message,\n  });\n\n  @override\n  bool validate(value, data) {\n    value = num.parse(value.toString());\n    return value >= lowerBoundary && value <= higherBoundary;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field should be between $lowerBoundary and $higherBoundary';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/confirmed.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass Confirmed extends ValidationRule {\n  Confirmed({super.message});\n\n  @override\n  bool validate(value, data) {\n    dynamic confirmValue = data['password_confirmation'];\n    return confirmValue == value;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'Both passwords should match';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/end_width.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass EndWith extends ValidationRule {\n  final String end;\n  EndWith({required this.end, super.message});\n\n  @override\n  bool validate(value, data) {\n    return value.toString().endsWith(end.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must end with $end';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/greater_than.dart",
    "content": "import 'dart:convert';\n\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass GreaterThan extends ValidationRule {\n  final num compare;\n  GreaterThan({required this.compare, super.message});\n  @override\n  bool validate(value, data) {\n    value = num.parse(value.toString());\n    return value > num.parse(compare.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field be greater than $compare';\n  }\n\n  GreaterThan copyWith({num? compare}) {\n    return GreaterThan(compare: compare ?? this.compare);\n  }\n\n  Map<String, dynamic> toMap() {\n    final result = <String, dynamic>{};\n\n    result.addAll({'compare': compare});\n\n    return result;\n  }\n\n  factory GreaterThan.fromMap(Map<String, dynamic> map) {\n    return GreaterThan(compare: map['compare'] ?? 0);\n  }\n\n  String toJson() => json.encode(toMap());\n\n  factory GreaterThan.fromJson(String source) =>\n      GreaterThan.fromMap(json.decode(source));\n\n  @override\n  String toString() => 'GreaterThan(compare: $compare)';\n\n  @override\n  bool operator ==(Object other) {\n    if (identical(this, other)) return true;\n\n    return other is GreaterThan && other.compare == compare;\n  }\n\n  @override\n  int get hashCode => compare.hashCode;\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/in_array.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass InArray<T> extends ValidationRule {\n  final List<T> array;\n  InArray({required this.array, super.message});\n\n  @override\n  bool validate(value, data) {\n    return array.contains(value.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field field have to a part of ${array.toString()}';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_alpha.dart",
    "content": "import 'package:vania/src/config/defined_regexp.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsAlpha extends ValidationRule {\n  IsAlpha({super.message});\n\n  @override\n  bool validate(value, data) {\n    return value is String && alphaRegExp.hasMatch(value);\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must contains just alphabet words';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_alpha_dash.dart",
    "content": "import 'package:vania/src/config/defined_regexp.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsAlphaDash extends ValidationRule {\n  IsAlphaDash({super.message});\n\n  @override\n  bool validate(value, data) {\n    return alphaDashRegExp.hasMatch(value.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field can just contains alphabet and also dash';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_alpha_numeric.dart",
    "content": "import 'package:vania/src/config/defined_regexp.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsAlphaNumeric extends ValidationRule {\n  IsAlphaNumeric({super.message});\n\n  @override\n  bool validate(value, data) {\n    return alphaNumericRegExp.hasMatch(value.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field can just contains alphabet and also numbers';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_array.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsArray extends ValidationRule {\n  IsArray({super.message});\n\n  @override\n  bool validate(value, data) {\n    return value is List;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be an array';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_boolean.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsBoolean extends ValidationRule {\n  IsBoolean({super.message});\n\n  @override\n  bool validate(value, data) {\n    return value is bool;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be a boolean';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_date.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsDate extends ValidationRule {\n  IsDate({super.message});\n\n  @override\n  bool validate(value, data) {\n    try {\n      DateTime.parse(value.toString());\n      return true;\n    } catch (_) {\n      return false;\n    }\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The {field} must be a valid DateTime';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_double.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsDouble extends ValidationRule {\n  IsDouble({super.message});\n\n  @override\n  bool validate(value, data) {\n    try {\n      double.parse(value.toString());\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be a double';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_email.dart",
    "content": "import 'package:vania/src/config/defined_regexp.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsEmail extends ValidationRule {\n  IsEmail({super.message});\n\n  @override\n  bool validate(dynamic value, data) {\n    return value != null && emailRegExp.hasMatch(value.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be a valid email';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_file.dart",
    "content": "import 'package:vania/src/http/request/request_file.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsFile extends ValidationRule {\n  final String args;\n  IsFile({required this.args, super.message});\n\n  @override\n  bool validate(value, data) {\n    if (value is! RequestFile && value is! List<RequestFile>) {\n      return false;\n    }\n\n    if (args.isEmpty) {\n      return true;\n    }\n\n    List<String> validExtensions = args.split(',');\n\n    bool hasValidExtension(RequestFile file) {\n      return validExtensions.contains(file.extension);\n    }\n\n    if (value is List<RequestFile>) {\n      return value.every(hasValidExtension);\n    } else {\n      return hasValidExtension(value);\n    }\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be a file';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_image.dart",
    "content": "import 'package:vania/src/http/request/request_file.dart';\nimport 'package:vania/src/http/validation/validation_chain/export_chain_validation.dart';\n\nclass IsImage extends ValidationRule {\n  final String args;\n  IsImage({required this.args, super.message});\n\n  @override\n  bool validate(value, data) {\n    if (value is RequestFile) {\n      return false;\n    }\n    if (args.toString().isNotEmpty) {\n      extensions = args.toString().split(',');\n    }\n    if (extensions.contains(value.extension)) {\n      return true;\n    }\n    return false;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be a file';\n  }\n}\n\nList<String> extensions = <String>[\n  'jpg',\n  'jpeg',\n  'png',\n  'gif',\n  'bmp',\n  'svg',\n  'webp',\n  'tiff',\n  'ico',\n];\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_integer.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsInteger extends ValidationRule {\n  IsInteger({super.message});\n\n  @override\n  bool validate(value, data) {\n    try {\n      int.parse(value.toString());\n      return true;\n    } catch (_) {\n      return false;\n    }\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be an integer';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_ip.dart",
    "content": "import 'package:vania/src/config/defined_regexp.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsIp extends ValidationRule {\n  IsIp({super.message});\n\n  @override\n  bool validate(value, data) {\n    return ipRegExp.hasMatch(value.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be a valid IP address';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_json.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsJson extends ValidationRule {\n  IsJson({super.message});\n\n  @override\n  bool validate(value, data) {\n    return value is Map;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be a Json';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_numeric.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsNumeric extends ValidationRule {\n  IsNumeric({super.message});\n\n  @override\n  bool validate(value, data) {\n    try {\n      num.parse(value.toString());\n      return true;\n    } catch (_) {\n      return false;\n    }\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be a number';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_required.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsRequired extends ValidationRule {\n  IsRequired({super.message});\n\n  @override\n  bool validate(dynamic value, data) {\n    return value != null && value.toString().isNotEmpty;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field is required';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_string.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsString extends ValidationRule {\n  IsString({super.message});\n\n  @override\n  bool validate(value, data) {\n    return value != null && value is String;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be a string';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_url.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsURL extends ValidationRule {\n  IsURL({super.message});\n\n  @override\n  bool validate(value, data) {\n    try {\n      Uri? uri = Uri.tryParse(value);\n      return uri != null && uri.hasScheme && uri.hasAuthority;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The {field} must be a valid UUID';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/is_uuid.dart",
    "content": "import 'package:vania/src/config/defined_regexp.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass IsUUID extends ValidationRule {\n  IsUUID({super.message});\n\n  @override\n  bool validate(value, data) {\n    return uuidRegExp.hasMatch(value.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The {field} must be a valid UUID';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/lenght_between.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass LengthBetween extends ValidationRule {\n  final num lowerBoundary;\n  final num higherBoundary;\n  LengthBetween({\n    super.message,\n    required this.lowerBoundary,\n    required this.higherBoundary,\n  });\n\n  @override\n  bool validate(value, data) {\n    value = value.toString().length;\n    return value >= lowerBoundary && value <= higherBoundary;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field should between $lowerBoundary and $higherBoundary';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/less_than.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass LessThan extends ValidationRule {\n  final num compare;\n  LessThan({required this.compare, super.message});\n\n  @override\n  bool validate(value, data) {\n    value = num.parse(value.toString());\n    return value < num.parse(compare.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field be less than $compare';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/max.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass Max extends ValidationRule {\n  final num maxValue;\n  Max({required this.maxValue, super.message});\n\n  @override\n  bool validate(value, data) {\n    value = num.parse(value.toString());\n    return value <= num.parse(maxValue.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must be less than or equal $maxValue';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/max_lenght.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass MaxLength extends ValidationRule {\n  int maxLength;\n  MaxLength({required this.maxLength, super.message});\n\n  @override\n  bool validate(value, data) {\n    value = value.toString().length;\n    return value.toString().length <= maxLength;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The {field} should not be greater than $maxLength character';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/min.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass Min extends ValidationRule {\n  final num minValue;\n  Min({required this.minValue, super.message});\n\n  @override\n  bool validate(value, data) {\n    value = num.parse(value.toString());\n    return value >= num.parse(minValue.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field be greater than or equal $minValue';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/min_lenght.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass MinLength extends ValidationRule {\n  int minLength;\n  MinLength({required this.minLength, super.message});\n\n  @override\n  bool validate(value, data) {\n    value = value.toString().length;\n    return value.toString().length >= minLength;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The {field} should not be less than $minLength character';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/not_in_array.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass NotInArray<T> extends ValidationRule {\n  final List<T> array;\n  NotInArray({required this.array, super.message});\n\n  @override\n  bool validate(value, data) {\n    return !array.contains(value.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field field cannot be in ${array.toString()}';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/required_if.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/rules/is_required.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass RequiredIf extends ValidationRule {\n  final String payload;\n  RequiredIf({required this.payload, super.message});\n\n  @override\n  bool validate(value, data) {\n    List<String> parts = payload.toString().split(',');\n    String secondField = parts[0];\n    String secondFieldValueFromRule = parts[1].toString();\n    String? secondFieldValueFromRequest = data[secondField].toString();\n\n    /// check only when req value and rule value are same\n    if (secondFieldValueFromRule != secondFieldValueFromRequest) {\n      return IsRequired().validate(value, data);\n    }\n    return true;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field is Required';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/required_if_not.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/rules/is_required.dart';\nimport 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass RequiredIfNot extends ValidationRule {\n  final String payload;\n  RequiredIfNot({required this.payload, super.message});\n\n  @override\n  bool validate(value, data) {\n    List<String> parts = payload.toString().split(',');\n    String secondField = parts[0];\n    String secondFieldValueFromRule = parts[1].toString();\n    String? secondFieldValueFromRequest = data[secondField].toString();\n\n    /// check only when req value and rule value are same\n    if (secondFieldValueFromRule != secondFieldValueFromRequest) {\n      return IsRequired().validate(value, data);\n    }\n    return true;\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field is Required';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/rules/start_with.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass StartWith extends ValidationRule {\n  final String start;\n  StartWith({required this.start, super.message});\n\n  @override\n  bool validate(value, data) {\n    return value.toString().startsWith(start.toString());\n  }\n\n  @override\n  String getDefaultErrorMessage(String field) {\n    return 'The $field must start with $start';\n  }\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/validation.dart",
    "content": "import 'package:vania/src/http/validation/validation_chain/validation_rule.dart';\n\nclass Validation {\n  final String field;\n  final List<ValidationRule> rules;\n  const Validation({required this.field, required this.rules});\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_chain/validation_rule.dart",
    "content": "abstract class ValidationRule {\n  final String? message;\n  ValidationRule({this.message});\n  bool validate(dynamic value, Map<String, dynamic> data);\n  String get errorMessage => message ?? getDefaultErrorMessage('');\n  String getDefaultErrorMessage(String field);\n}\n"
  },
  {
    "path": "lib/src/http/validation/validation_item.dart",
    "content": "class ValidationItem {\n  final String field;\n  final String name;\n  final String rule;\n  final dynamic value;\n\n  const ValidationItem({\n    required this.field,\n    required this.name,\n    required this.rule,\n    this.value,\n  });\n}\n"
  },
  {
    "path": "lib/src/http/validation/validator.dart",
    "content": "import 'package:sprintf/sprintf.dart';\nimport 'package:vania/src/extensions/string_list_extension.dart';\nimport 'package:vania/src/extensions/map_extension.dart';\n\nimport 'custom_validation_rule.dart';\nimport 'nested_validation.dart';\nimport 'rules.dart';\nimport 'validation_item.dart';\n\nclass Validator {\n  /// request data\n  final Map<String, dynamic> data;\n\n  Validator({required this.data});\n\n  final Map<String, String> _errors = <String, String>{};\n  List<String> get _methodNoNeedToSplitArguments => <String>['in'];\n\n  /// check validation has errors\n  bool get hasError => _errors.isNotEmpty;\n\n  /// get list of error messages\n  Map<String, String> get errors => _errors;\n\n  /// Add custom validation rules to the validator.\n  ///\n  /// This method allows the addition of custom validation rules to the\n  /// validator. Each rule is represented by a [CustomValidationRule] object,\n  /// which includes the rule's name, error message, and validation function.\n  ///\n  /// The rule's name is used as a key in the internal [_matchingRules] map,\n  /// and the rule's message and function are stored as values. This allows\n  /// for custom validation logic to be applied during the validation process.\n  ///\n  /// [rules] is a list of [CustomValidationRule] objects to be added.\n  ///\n  void customRule(List<CustomValidationRule> rules) {\n    for (CustomValidationRule rule in rules) {\n      _matchingRules[rule.ruleName] = <String, dynamic>{\n        'message': rule.message,\n        'function': rule.fn,\n      };\n    }\n  }\n\n  /// set custom validator messages\n  /// ```\n  /// validator.setNewMessages({'required': 'The {field} is required});\n  /// validator.setNewMessages({'name.required': 'The name is required});\n  /// ```\n  void setNewMessages(Map<String, String> messages) {\n    messages.forEach((String key, String value) {\n      List splitedKey = key.split('.');\n      if (splitedKey.length > 1) {\n        Function function = _matchingRules[splitedKey[1]]?['function'];\n        _matchingRules[key] = <String, dynamic>{\n          'message': value,\n          'function': function,\n        };\n      } else if (_matchingRules[key] != null) {\n        _matchingRules[key]?['message'] = value;\n      }\n    });\n  }\n\n  bool _isNestedValidation(String field) {\n    return field.contains('.*.');\n  }\n\n  /// validate your data\n  /// ```\n  /// validator.validate({'field' : 'required|string'});\n  /// ```\n  Future<void> validate(Map<String, String> rules) async {\n    for (var entry in rules.entries) {\n      String field = entry.key;\n      String rule = entry.value;\n\n      if (_isNestedValidation(field)) {\n        NestedValidation v = NestedValidation(\n          data: data,\n          field: field,\n          rule: rule,\n        );\n        for (ValidationItem item in v.fieldsToValidate) {\n          await _validateItem(item);\n        }\n      } else {\n        await _validateItem(\n          ValidationItem(\n            field: field,\n            name: field.split('.').last,\n            value: data.getParam(field),\n            rule: rule,\n          ),\n        );\n      }\n    }\n  }\n\n  Future<void> _validateItem(ValidationItem item) async {\n    if (item.value == null && !item.rule.contains('required')) {\n      return;\n    }\n\n    List<String> rulesForEachName = item.rule.split('|');\n    for (String rule in rulesForEachName) {\n      String? error = await _applyMatchingRule(\n        item.field,\n        item.name,\n        item.value,\n        rule,\n      );\n      if (error != null) {\n        _errors[item.field] = error;\n        break;\n      }\n    }\n  }\n\n  Future<String?> _applyMatchingRule(\n    String field,\n    String name,\n    dynamic value,\n    String rule,\n  ) async {\n    List<String> parts = rule.split(':');\n    String ruleKey = parts.first.toString().toLowerCase();\n    String args = parts.length >= 2 ? parts[1] : '';\n    Map<String, dynamic>? match =\n        _matchingRules[\"$name.$ruleKey\"] ?? _matchingRules[ruleKey];\n    if (match == null) {\n      return null;\n    }\n\n    var result = Function.apply(match['function'], <dynamic>[\n      data,\n      value,\n      args,\n    ]);\n\n    if (result is Future<bool>) {\n      result = await result;\n    }\n    if (result == true) {\n      return null;\n    }\n    String error = match['message']\n        .toString()\n        .replaceAll('{field}', name)\n        .replaceAll('{value}', value == null ? '' : value.toString());\n\n    if (args.isNotEmpty) {\n      List<String> arguments = _methodNoNeedToSplitArguments.contains(ruleKey)\n          ? <String>[args.split(',').joinWithAnd()]\n          : args.split(',');\n      return sprintf(error, arguments);\n    }\n\n    return error;\n  }\n\n  final Map<String, Map<String, dynamic>> _matchingRules =\n      <String, Map<String, dynamic>>{\n        'required': <String, dynamic>{\n          'message': 'The {field} is required',\n          'function': Rules.isRequired,\n        },\n        'email': <String, dynamic>{\n          'message': 'The {field} is not a valid email',\n          'function': Rules.isEmail,\n        },\n        'string': <String, dynamic>{\n          'message': 'The {field} must be a string',\n          'function': Rules.isString,\n        },\n        'numeric': <String, dynamic>{\n          'message': 'The {field} must be a number',\n          'function': Rules.isNumeric,\n        },\n        'ip': <String, dynamic>{\n          'message': 'The {field} must be an ip address',\n          'function': Rules.isIp,\n        },\n        'boolean': <String, dynamic>{\n          'message': 'The {field} must be a boolean',\n          'function': Rules.isBoolean,\n        },\n        'integer': <String, dynamic>{\n          'message': 'The {field} must be an integer',\n          'function': Rules.isInteger,\n        },\n        'double': <String, dynamic>{\n          'message': 'The {field} must be a double',\n          'function': Rules.isDouble,\n        },\n        'array': <String, dynamic>{\n          'message': 'The {field} must be an array',\n          'function': Rules.isArray,\n        },\n        'json': <String, dynamic>{\n          'message': 'The {field} is not a valid json',\n          'function': Rules.isJson,\n        },\n        'alpha': <String, dynamic>{\n          'message': 'The {field} must be an alphabetic',\n          'function': Rules.isAlpha,\n        },\n        'alpha_dash': <String, dynamic>{\n          'message': 'The {field} must be only alphabetic and dash',\n          'function': Rules.isAlphaDash,\n        },\n        'alpha_numeric': <String, dynamic>{\n          'message': 'The {field} must be only alphabetic and number',\n          'function': Rules.isAlphaNumeric,\n        },\n        'date': <String, dynamic>{\n          'message': 'The {field} must be a date',\n          'function': Rules.isDate,\n        },\n        'url': <String, dynamic>{\n          'message': 'The {field} must be a url',\n          'function': Rules.isUrl,\n        },\n        'uuid': <String, dynamic>{\n          'message': 'The {field} is invalid uuid',\n          'function': Rules.isUUID,\n        },\n        'min_length': <String, dynamic>{\n          'message': 'The {field} must be at least %s character',\n          'function': Rules.minLength,\n        },\n        'max_length': <String, dynamic>{\n          'message': 'The {field} may not be greater than %s character',\n          'function': Rules.maxLength,\n        },\n        'length_between': <String, dynamic>{\n          'message': 'The {field} must be between %s and %s character',\n          'function': Rules.lengthBetween,\n        },\n        'between': <String, dynamic>{\n          'message': 'The {field} must be between %s and %s',\n          'function': Rules.between,\n        },\n        'in': <String, dynamic>{\n          'message': 'The selected {field} is invalid. Valid options are %s',\n          'function': Rules.inArray,\n        },\n        'not_in': <String, dynamic>{\n          'message': 'The {field} field cannot be {value}',\n          'function': Rules.notInArray,\n        },\n        'start_with': <String, dynamic>{\n          'message': 'The {field} must start with %s',\n          'function': Rules.startWith,\n        },\n        'end_with': <String, dynamic>{\n          'message': 'The {field} must end with %s',\n          'function': Rules.endWith,\n        },\n        'greater_than': <String, dynamic>{\n          'message': 'The {field} must be greater than %s',\n          'function': Rules.greaterThan,\n        },\n        'less_than': <String, dynamic>{\n          'message': 'The {field} must be less than %s',\n          'function': Rules.lessThan,\n        },\n        'min': <String, dynamic>{\n          'message': 'The {field} must be greater than or equal %s',\n          'function': Rules.min,\n        },\n        'max': <String, dynamic>{\n          'message': 'The {field} must be less than or equal %s',\n          'function': Rules.max,\n        },\n        'confirmed': <String, dynamic>{\n          'message': 'The two password did not match',\n          'function': Rules.confirmed,\n        },\n        'required_if': <String, dynamic>{\n          'message': 'The {field} is required',\n          'function': Rules.requiredIf,\n        },\n        'required_if_not': <String, dynamic>{\n          'message': 'The {field} is required',\n          'function': Rules.requiredIfNot,\n        },\n        'image': <String, dynamic>{\n          'message': 'The {field} is either invalid or unsupported extension',\n          'function': Rules.isImage,\n        },\n        'file': <String, dynamic>{\n          'message': 'The {field} is either invalid or unsupported extension',\n          'function': Rules.isFile,\n        },\n        'reg_exp': <String, dynamic>{\n          'message': 'The {field} is either invalid or unsupported extension',\n          'function': Rules.regExp,\n        },\n        'unique': <String, dynamic>{\n          'message': 'This {field} is exist',\n          'function': Rules.unique,\n        },\n      };\n}\n"
  },
  {
    "path": "lib/src/ioc_container.dart",
    "content": "typedef FactoryFunc<T> = T Function();\n\nclass IoCContainer {\n  static final IoCContainer _instance = IoCContainer._internal();\n  factory IoCContainer() => _instance;\n  IoCContainer._internal();\n\n  final Map<Type, dynamic> _singletons = {};\n  final Map<Type, FactoryFunc<dynamic>> _factories = {};\n\n  void register<T>(FactoryFunc<T> factory, {bool singleton = false}) {\n    if (singleton) {\n      _singletons[T] = factory();\n    } else {\n      _factories[T] = factory;\n    }\n  }\n\n  T resolve<T>() {\n    if (_singletons.containsKey(T)) {\n      return _singletons[T];\n    } else if (_factories.containsKey(T)) {\n      return _factories[T]!() as T;\n    }\n    throw Exception(\n      'Service of type $T is not registered in the IoC container.',\n    );\n  }\n}\n"
  },
  {
    "path": "lib/src/localization_handler/localization.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'package:vania/src/utils/helper.dart' show env;\n\nclass Localization {\n  static final Localization _singleton = Localization._internal();\n  factory Localization() {\n    return _singleton;\n  }\n  Localization._internal();\n\n  String? _locale = env('APP_LOCALE');\n\n  final Map<String, dynamic> _language = {};\n\n  void setLocale(String locale) => _locale = locale;\n\n  bool isLocale(String locale) => _locale == locale;\n\n  /// Initializes the language data by loading all `.json` language files from `lib/lang` directory.\n  /// - `LANG_PATH` specifies the directory where the language files are stored (defaults to `lib/lang/` if not set).\n  /// - `LOCALE` specifies the language/locale to load (defaults to `en` if not set).\n  void init() async {\n    Directory languagePath = Directory(env('APP_LANG_PATH', 'lib/lang'));\n\n    if (languagePath.existsSync()) {\n      for (var entity in languagePath.listSync(\n        recursive: true,\n        followLinks: false,\n      )) {\n        if (entity is Directory) {\n          final segments = entity.uri.pathSegments.where((s) => s.isNotEmpty);\n          final subdirName = segments.last.toLowerCase();\n          final fileMap = <String, dynamic>{};\n          for (var file\n              in entity\n                  .listSync(recursive: false)\n                  .whereType<File>()\n                  .where((f) => f.path.toLowerCase().endsWith('.json'))) {\n            try {\n              final content = file.readAsStringSync();\n              final decoded = json.decode(content);\n              fileMap.addAll(decoded);\n            } catch (e) {\n              stderr.writeln('⚠️ Failed to parse ${file.path}: $e');\n            }\n          }\n          _language[subdirName] = fileMap;\n        }\n      }\n    }\n  }\n\n  /// Translates a string based on the provided key and optional arguments.\n  String trans(String key, [Map<String, dynamic>? args, String? locale]) {\n    if (!_language[locale ?? _locale].containsKey(key)) {\n      return 'Translation not found for key: $key';\n    }\n\n    String tmp = _language[locale ?? _locale][key];\n\n    if (args == null || args.isEmpty) {\n      return tmp;\n    }\n\n    args.forEach((placeholder, value) {\n      tmp = tmp.replaceAll('{$placeholder}', value.toString());\n    });\n\n    return tmp;\n  }\n}\n"
  },
  {
    "path": "lib/src/logger/logger.dart",
    "content": "// ignore_for_file: constant_identifier_names\nimport 'dart:io';\nimport '../utils/helper.dart';\n\nclass Logger {\n  static const EMERGENCY = 'EMERGENCY';\n  static const ALERT = 'ALERT';\n  static const CRITICAL = 'CRITICAL';\n  static const ERROR = 'ERROR';\n  static const SUCCESS = 'SUCCESS';\n  static const WARNING = 'WARNING';\n  static const NOTICE = 'NOTICE';\n  static const INFO = 'INFO';\n  static const DEBUG = 'DEBUG';\n\n  static void log(\n    String content, {\n    String type = INFO,\n    String fileName = 'vania',\n  }) {\n    final now = DateTime.now();\n\n    final directory = Directory(storagePath('logs'));\n    if (!directory.existsSync()) {\n      directory.createSync(recursive: true);\n    }\n\n    final logFile = File('${directory.path}/$fileName.log');\n    final fsSink = logFile.openWrite(mode: FileMode.append);\n\n    var text =\n        \"[${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')} ${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}]\";\n    text += ' $type: ';\n    text += content;\n    text += '\\n';\n\n    fsSink.write(text);\n    fsSink.close();\n  }\n}\n"
  },
  {
    "path": "lib/src/mail/content.dart",
    "content": "class Content {\n  /// The Blade view that represents the text version of the message.\n  final String? text;\n\n  /// The Blade view that should be rendered for the mailable.\n  final String? html;\n\n  const Content({this.text, this.html});\n}\n"
  },
  {
    "path": "lib/src/mail/envelope.dart",
    "content": "import 'package:mailer/mailer.dart';\n\nclass Envelope {\n  ///The address sending the message.\n  final Address? from;\n\n  /// The recipients of the message.\n  final List<Address> to;\n\n  /// The recipients receiving a copy of the message.\n  final List<dynamic>? cc;\n\n  /// The recipients receiving a blind copy of the message.\n  final List<dynamic>? bcc;\n\n  /// The subject of the message.\n  final String subject;\n\n  Envelope({\n    this.from,\n    required this.to,\n    required this.subject,\n    this.cc,\n    this.bcc,\n  });\n}\n"
  },
  {
    "path": "lib/src/mail/mail.dart",
    "content": "import 'package:mailer/mailer.dart';\n\nimport 'content.dart';\nimport 'envelope.dart';\nimport 'mail_view.dart';\n\nabstract class Mail {\n  const Mail();\n  Content? content();\n  MailView? view();\n  Envelope envelope();\n  List<Attachment>? attachments();\n}\n"
  },
  {
    "path": "lib/src/mail/mail_view.dart",
    "content": "class MailView {\n  final String view;\n  final Map<String, dynamic>? data;\n\n  const MailView({required this.view, this.data});\n}\n"
  },
  {
    "path": "lib/src/mail/mailable.dart",
    "content": "import 'dart:io';\n\nimport 'package:mailer/mailer.dart' as mailer;\nimport 'package:mailer/mailer.dart';\nimport 'package:mailer/smtp_server.dart';\nimport 'package:meta/meta.dart';\nimport 'package:vania/src/mail/content.dart';\nimport 'package:vania/src/mail/envelope.dart';\nimport 'package:vania/src/mail/mail.dart';\n\nimport 'package:vania/src/utils/helper.dart' show env;\nimport 'package:vania/src/view_engine/template_engine.dart';\n\nimport 'mail_view.dart';\n\n@immutable\nclass Mailable implements Mail {\n  const Mailable();\n\n  SmtpServer _setupSmtpServer() {\n    switch (env<String>('MAIL_MAILER', 'smtp')) {\n      case 'gmail':\n        return gmail(\n          env<String>('MAIL_USERNAME', ''),\n          env<String>('MAIL_PASSWORD', ''),\n        );\n      case 'gmailSaslXoauth2':\n        return gmailSaslXoauth2(\n          env<String>('MAIL_USERNAME', ''),\n          env<String>('MAIL_ACCESS_TOKEN', ''),\n        );\n      case 'gmailRelaySaslXoauth2':\n        return gmail(\n          env<String>('MAIL_USERNAME', ''),\n          env<String>('MAIL_ACCESS_TOKEN', ''),\n        );\n      case 'hotmail':\n        return hotmail(\n          env<String>('MAIL_USERNAME', ''),\n          env<String>('MAIL_PASSWORD', ''),\n        );\n      case 'mailgun':\n        return mailgun(\n          env<String>('MAIL_USERNAME', ''),\n          env<String>('MAIL_PASSWORD', ''),\n        );\n      case 'qq':\n        return qq(\n          env<String>('MAIL_USERNAME', ''),\n          env<String>('MAIL_PASSWORD', ''),\n        );\n      case 'yahoo':\n        return yahoo(\n          env<String>('MAIL_USERNAME', ''),\n          env<String>('MAIL_PASSWORD', ''),\n        );\n      case 'yandex':\n        return yandex(\n          env<String>('MAIL_USERNAME', ''),\n          env<String>('MAIL_PASSWORD', ''),\n        );\n      default:\n        return SmtpServer(\n          env<String>('MAIL_HOST', ''),\n          username: env<String>('MAIL_USERNAME', ''),\n          password: env<String>('MAIL_PASSWORD', ''),\n          port: env<int>('MAIL_PORT', 465),\n          ssl: env<bool>('MAIL_ENCRYPTION', true),\n          ignoreBadCertificate: env<bool>('MAIL_IGNORE_BAD_CERTIFICATE', true),\n        );\n    }\n  }\n\n  Future<mailer.SendReport> send() async {\n    final message = mailer.Message();\n\n    message.from =\n        envelope().from ??\n        Address(\n          env<String>('MAIL_FROM_ADDRESS', ''),\n          env<String>('MAIL_FROM_NAME', ''),\n        );\n    message.recipients.addAll(envelope().to);\n\n    if (envelope().cc != null) {\n      message.ccRecipients.addAll(envelope().cc!);\n    }\n\n    if (envelope().bcc != null) {\n      message.ccRecipients.addAll(envelope().bcc!);\n    }\n\n    message.subject = envelope().subject;\n    MailView? mailView = view();\n    Content? contentData = content();\n\n    if (mailView != null) {\n      message.html = TemplateEngine().render(\n        mailView.view,\n        mailView.data ?? {},\n      );\n    } else if (contentData != null) {\n      message.text = contentData.text;\n      message.html = contentData.html;\n    }\n\n    if (attachments() != null) {\n      message.attachments.addAll(attachments()!);\n    }\n    try {\n      mailer.SendReport sendReport = await mailer.send(\n        message,\n        _setupSmtpServer(),\n      );\n      return sendReport;\n    } on SmtpMessageValidationException catch (e) {\n      stderr.writeln(\n        'Failed to send email:${e.problems.map((error) => {message: error.msg, error: error.code}).toList()}',\n      );\n      throw Exception(\n        e.problems\n            .map((error) => {message: error.msg, error: error.code})\n            .toList(),\n      );\n    } catch (e) {\n      stderr.writeln('Failed to send email: $e');\n      rethrow;\n    }\n  }\n\n  @mustBeOverridden\n  @override\n  List<mailer.Attachment>? attachments() {\n    throw UnimplementedError();\n  }\n\n  @mustBeOverridden\n  @override\n  MailView? view() {\n    throw UnimplementedError();\n  }\n\n  @mustBeOverridden\n  @override\n  Content? content() {\n    throw UnimplementedError();\n  }\n\n  @mustBeOverridden\n  @override\n  Envelope envelope() {\n    throw UnimplementedError();\n  }\n}\n"
  },
  {
    "path": "lib/src/redis/command/client.dart",
    "content": "import 'dart:convert';\n\nimport 'package:vania/src/logger/logger.dart';\nimport 'package:vania/src/redis/exception.dart';\nimport 'package:vania/src/redis/lowlevel/protocol_client.dart';\nimport 'package:vania/src/redis/lowlevel/resp.dart';\nimport 'package:vania/src/redis/vania_redis.dart';\n\nclass MultiCodec {\n  final List<RedisCodec> codecs = [\n    RedisCodec(encoder: StringEncoder(), decoder: StringDecoder()),\n    RedisCodec(encoder: IntEncoder(), decoder: IntDecoder()),\n  ];\n\n  String encode<T>(T value) {\n    for (final e in codecs) {\n      if (e.encoder.isSupporting<T>(value)) {\n        return e.encoder.convert(value);\n      }\n    }\n    throw RedisConvertException('no encoder found');\n  }\n\n  T decode<T>(String value) {\n    for (final e in codecs) {\n      if (e.decoder.isSupporting<T>(value)) {\n        return e.decoder.convert(value);\n      }\n    }\n    throw RedisConvertException('no decoder found');\n  }\n\n  void registerCodec(RedisCodec codec) {\n    codecs.add(codec);\n  }\n}\n\n/// All commands type inherited\nabstract class Commands<K, V>\n    implements\n        KeysCommands<K, V>,\n        ListCommands<K, V>,\n        TransactionCommands<K, V>,\n        PubSubCommands<V> {}\n\n/// Implementation of [Commands]\nclass CommandsClient<K, V> implements Commands<K, V> {\n  final RedisProtocolClient _connection;\n  CommandsClient._(this._connection);\n\n  /// key type codecs\n  final MultiCodec keyCodec = MultiCodec();\n\n  /// value type codecs\n  final MultiCodec valueCodec = MultiCodec();\n\n  @override\n  Future<bool> del(K key) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['DEL', keyString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    return res.isInteger && res.integerValue == 1;\n  }\n\n  // key-value operation(String) commands\n\n  @override\n  Future<bool> exists(K key) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['EXISTS', keyString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    return res.isInteger && res.integerValue == 1;\n  }\n\n  @override\n  Future<bool> expire(K key, Duration duration) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(\n      Resp(['EXPIRE', keyString, '${duration.inSeconds}']),\n    );\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    return res.isInteger && res.integerValue == 1;\n  }\n\n  @override\n  Future<V?> getdel(K key) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['GETDEL', keyString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    final str = res.stringValue;\n    if (str == null) {\n      return null;\n    }\n    return valueCodec.decode<V>(str);\n  }\n\n  @override\n  Future<V?> get(K key) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['GET', keyString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    final str = res.stringValue;\n    if (str == null) {\n      return null;\n    }\n    return valueCodec.decode<V>(str);\n  }\n\n  @override\n  Future<int?> ttl(K key) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['ttl', keyString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<List<String>> keys(String pattern) async {\n    _connection.sendCommand(Resp(['KEYS', pattern]));\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    final l = res.arrayValue;\n    if (l == null) {\n      return [];\n    }\n    return l.map((e) => e.toString()).toList();\n  }\n\n  @override\n  Future<bool> set(K key, V value) async {\n    final keyString = keyCodec.encode<K>(key);\n    final valueString = valueCodec.encode<V>(value);\n    _connection.sendCommand(Resp(['SET', keyString, valueString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    final s = res.stringValue;\n    if (s == null) {\n      return false;\n    }\n    return s == 'OK';\n  }\n\n  @override\n  Future<bool> setEx(K key, int ttl, V value) async {\n    final keyString = keyCodec.encode<K>(key);\n    final valueString = valueCodec.encode<V>(value);\n    _connection.sendCommand(\n      Resp(['SETEX', keyString, ttl.toString(), valueString]),\n    );\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    final s = res.stringValue;\n    if (s == null) {\n      return false;\n    }\n    return s == 'OK';\n  }\n\n  @override\n  Future<int?> append(K key, V value) async {\n    final keyString = keyCodec.encode<K>(key);\n    final valueString = valueCodec.encode<V>(value);\n    _connection.sendCommand(Resp(['APPEND', keyString, valueString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<int?> bitCount(K key, {int? start, int? end}) async {\n    final keyString = keyCodec.encode<K>(key);\n    final command = ['BITCOUNT', keyString];\n    if (start != null && end != null) {\n      command.addAll([start.toString(), end.toString()]);\n    }\n    _connection.sendCommand(Resp(command));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<int?> bitOp(String operation, K destKey, List<K> keys) async {\n    final destKeyString = keyCodec.encode<K>(destKey);\n    final keyStrings = keys.map((k) => keyCodec.encode<K>(k)).toList();\n    final command = ['BITOP', operation, destKeyString, ...keyStrings];\n    _connection.sendCommand(Resp(command));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<int?> bitPos(K key, int bit, {int? start, int? end}) async {\n    final keyString = keyCodec.encode<K>(key);\n    final command = ['BITPOS', keyString, bit.toString()];\n    if (start != null && end != null) {\n      command.addAll([start.toString(), end.toString()]);\n    }\n    _connection.sendCommand(Resp(command));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<int?> decr(K key) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['DECR', keyString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<int?> decrBy(K key, int decrement) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['DECRBY', keyString, decrement.toString()]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<int?> getBit(K key, int offset) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['GETBIT', keyString, offset.toString()]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<V?> getRange(K key, int start, int end) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(\n      Resp(['GETRANGE', keyString, start.toString(), end.toString()]),\n    );\n    final res = await _connection.receive();\n    res.throwIfError();\n    final str = res.stringValue;\n    if (str == null) {\n      return null;\n    }\n    return valueCodec.decode<V>(str);\n  }\n\n  @override\n  Future<V?> getSet(K key, V value) async {\n    final keyString = keyCodec.encode<K>(key);\n    final valueString = valueCodec.encode<V>(value);\n    _connection.sendCommand(Resp(['GETSET', keyString, valueString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    final str = res.stringValue;\n    if (str == null) {\n      return null;\n    }\n    return valueCodec.decode<V>(str);\n  }\n\n  @override\n  Future<int?> incr(K key) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['INCR', keyString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<int?> incrBy(K key, int increment) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['INCRBY', keyString, increment.toString()]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<double?> incrByFloat(K key, double increment) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(\n      Resp(['INCRBYFLOAT', keyString, increment.toString()]),\n    );\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.doubleValue;\n  }\n\n  @override\n  Future<List<V>> mGet(List<K> keys) async {\n    final keyStrings = keys.map((k) => keyCodec.encode<K>(k)).toList();\n    _connection.sendCommand(Resp(['MGET', ...keyStrings]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    final l = res.arrayValue;\n    if (l == null) {\n      return [];\n    }\n    return l.map((e) => valueCodec.decode<V>(e)).toList();\n  }\n\n  @override\n  Future<bool> mSet(Map<K, V> keyValues) async {\n    final command = ['MSET'];\n    keyValues.forEach((k, v) {\n      command.addAll([keyCodec.encode<K>(k), valueCodec.encode<V>(v)]);\n    });\n    _connection.sendCommand(Resp(command));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.stringValue == 'OK';\n  }\n\n  @override\n  Future<bool> mSetNX(Map<K, V> keyValues) async {\n    final command = ['MSETNX'];\n    keyValues.forEach((k, v) {\n      command.addAll([keyCodec.encode<K>(k), valueCodec.encode<V>(v)]);\n    });\n    _connection.sendCommand(Resp(command));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue == 1;\n  }\n\n  @override\n  Future<int?> setBit(K key, int offset, int value) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(\n      Resp(['SETBIT', keyString, offset.toString(), value.toString()]),\n    );\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<bool> pSetEx(K key, int ttl, V value) async {\n    final keyString = keyCodec.encode<K>(key);\n    final valueString = valueCodec.encode<V>(value);\n    _connection.sendCommand(\n      Resp(['PSETEX', keyString, ttl.toString(), valueString]),\n    );\n    final res = await _connection.receive();\n    res.throwIfError();\n    final s = res.stringValue;\n    if (s == null) {\n      return false;\n    }\n    return s == 'OK';\n  }\n\n  @override\n  Future<bool> setNx(K key, V value) async {\n    final keyString = keyCodec.encode<K>(key);\n    final valueString = valueCodec.encode<V>(value);\n    _connection.sendCommand(Resp(['SETNX', keyString, valueString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue == 1;\n  }\n\n  @override\n  Future<int?> setRange(K key, int offset, V value) async {\n    final keyString = keyCodec.encode<K>(key);\n    final valueString = valueCodec.encode<V>(value);\n    _connection.sendCommand(\n      Resp(['SETRANGE', keyString, offset.toString(), valueString]),\n    );\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<int?> strlen(K key) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(Resp(['STRLEN', keyString]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.integerValue;\n  }\n\n  @override\n  Future<bool> setOption(String option) async {\n    _connection.sendCommand(Resp(['CONFIG', 'SET', option]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    return res.stringValue == 'OK';\n  }\n\n  @override\n  Future<String?> getOption(String option) async {\n    _connection.sendCommand(Resp(['CONFIG', 'GET', option]));\n    final res = await _connection.receive();\n    res.throwIfError();\n    final l = res.arrayValue;\n    if (l == null || l.isEmpty) {\n      return null;\n    }\n    return l.last.stringValue;\n  }\n\n  // List operation commands\n\n  @override\n  Future<List<V>> lrange(K key, int startIndex, int endIndex) async {\n    final keyString = keyCodec.encode<K>(key);\n    _connection.sendCommand(\n      Resp(['LRANGE', keyString, startIndex.toString(), endIndex.toString()]),\n    );\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    final l = res.arrayValue;\n    if (l == null) {\n      return [];\n    }\n\n    return l.map((e) => valueCodec.decode<V>(e)).toList();\n  }\n\n  @override\n  Future<bool> rpush(K key, List<V> values) async {\n    final keyString = keyCodec.encode<K>(key);\n    final command = ['RPUSH', keyString];\n    command.addAll(values.map((e) => valueCodec.encode<V>(e)));\n\n    _connection.sendCommand(Resp(command));\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    return res.isInteger;\n  }\n\n  @override\n  Future<bool> lpush(K key, List<V> values) async {\n    final keyString = keyCodec.encode<K>(key);\n    final command = ['LPUSH', keyString];\n    command.addAll(values.map((e) => valueCodec.encode<V>(e)));\n\n    _connection.sendCommand(Resp(command));\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    return res.isInteger;\n  }\n\n  @override\n  Future<bool> lset(K key, int index, V value) async {\n    final keyString = keyCodec.encode<K>(key);\n    final valueString = valueCodec.encode<V>(value);\n    _connection.sendCommand(\n      Resp(['LSET', keyString, index.toString(), valueString]),\n    );\n    final res = await _connection.receive();\n    res.throwIfError();\n\n    return res.stringValue == 'OK';\n  }\n\n  // Transaction Commands\n\n  @override\n  Future<void> exec() async {\n    _connection.sendCommand(Resp(['EXEC']));\n    await _connection.receive();\n  }\n\n  @override\n  Future<void> multi() async {\n    _connection.sendCommand(Resp(['MULTI']));\n    await _connection.receive();\n  }\n\n  @override\n  Future<void> discard() async {\n    _connection.sendCommand(Resp(['DISCARD']));\n    await _connection.receive();\n  }\n\n  // pubsub operation commands\n  @override\n  Stream<V> psubscribe(String pattern) {\n    _connection.sendCommand(Resp(['PSUBSCRIBE', pattern]));\n    return _connection.stream\n        .map((event) => event?.arrayValue)\n        .where((event) => event != null)\n        .map((event) => event!)\n        .where((e) => e.length == 4) // length == 3 is subscribed notification\n        .map((event) => valueCodec.decode(event.last));\n  }\n\n  @override\n  Future<int?> publish(String channel, V message) async {\n    final messageString = valueCodec.encode<V>(message);\n\n    _connection.sendCommand(Resp(['PUBLISH', channel, messageString]));\n    final res = await _connection.receive();\n    return res.integerValue;\n  }\n\n  Future<String?> auth({String? username, required String password}) async {\n    if (username == null) {\n      _connection.sendCommand(Resp(['AUTH', password]));\n    } else {\n      _connection.sendCommand(Resp(['AUTH', username, password]));\n    }\n    final res = await _connection.receive();\n    return res.stringValue;\n  }\n}\n\n/// Redis Client\nclass RedisClient {\n  final RedisProtocolClient _connection;\n\n  RedisClient._(this._connection);\n\n  /// Establish connection to Redis server and send SELECT command\n  static Future<RedisClient> connect(\n    String host,\n    int port, {\n    required int db,\n    String? username,\n    String? password,\n  }) async {\n    final rpc = await RedisProtocolClient.createConnection(\n      host: host,\n      port: port,\n    );\n\n    if (password != null) {\n      await _auth(rpc, username: username, password: password);\n    }\n    rpc.sendCommand(Resp(['SELECT', '$db']));\n    final res = await rpc.receive();\n    res.throwIfError();\n    return RedisClient._(rpc);\n  }\n\n  static Future<String?> _auth(\n    RedisProtocolClient connection, {\n    String? username,\n    required String password,\n  }) async {\n    if (username == null) {\n      connection.sendCommand(Resp(['AUTH', password]));\n    } else {\n      connection.sendCommand(Resp(['AUTH', username, password]));\n    }\n    final res = await connection.receive();\n    try {\n      res.throwIfError();\n    } on RedisException catch (e) {\n      Logger.log(\n        jsonEncode({'error': 'RedisException', 'message': e.message}),\n        type: Logger.ERROR,\n      );\n    }\n    return res.stringValue;\n  }\n\n  /// Get [Commands]\n  /// [K] : key type\n  /// [V] : value type\n  Commands<K, V> getCommands<K, V>() => CommandsClient<K, V>._(_connection);\n\n  /// close connection\n  Future<void> close() => _connection.close();\n}\n"
  },
  {
    "path": "lib/src/redis/command/codec.dart",
    "content": "import 'dart:convert';\n\n/// converter base class\n/// convert [S] to [D]\nabstract class RedisConverter<S, D> extends Converter<S, D> {\n  bool isSupporting<U>(dynamic value) => value is S && U == D;\n}\n\n/// convert to String from [T]\ntypedef RedisEncoder<T> = RedisConverter<T, String>;\n\n/// convert to [T] from String\ntypedef RedisDecoder<T> = RedisConverter<String, T>;\n\n/// encoder and decoder pair\nclass RedisCodec<T> {\n  final RedisEncoder<T> encoder;\n  final RedisDecoder<T> decoder;\n\n  RedisCodec({required this.encoder, required this.decoder});\n}\n\n/// builtin String to String encoder\nclass StringEncoder extends RedisEncoder<String> {\n  @override\n  String convert(String input) => input;\n}\n\n/// builtin String to String decoder\nclass StringDecoder extends RedisDecoder<String> {\n  @override\n  String convert(String input) => input;\n}\n\n/// builtin int to String encoder\nclass IntEncoder extends RedisEncoder<int> {\n  @override\n  String convert(int input) => input.toString();\n}\n\n/// builtin String to int decoder\nclass IntDecoder extends RedisDecoder<int> {\n  @override\n  int convert(String input) => int.parse(input);\n}\n"
  },
  {
    "path": "lib/src/redis/command/commands.dart",
    "content": "/// key-value operation commands\nabstract class KeysCommands<K, V> {\n  /// DEL command (delete item)\n  Future<bool> del(K key);\n\n  /// EXISTS command (check existence)\n  Future<bool> exists(K key);\n\n  /// EXPIRE command (set expire duration)\n  Future<bool> expire(K key, Duration duration);\n\n  /// KEYS command (get keys that match [pattern])\n  Future<List<String>> keys(String pattern);\n\n  /// Returns the remaining time to live of a key  (get value)\n  Future<int?> ttl(K key);\n\n  /// GET command (get value)\n  Future<V?> get(K key);\n\n  /// SET command (set value)\n  Future<bool> set(K key, V value);\n\n  /// SET expire duration command (set value)\n  Future<bool> setEx(K key, int ttl, V value);\n\n  /// GETDEL command (get value and delete value)\n  Future<V?> getdel(K key);\n\n  Future<int?> append(K key, V value);\n\n  Future<int?> bitCount(K key, {int? start, int? end});\n\n  Future<int?> bitOp(String operation, K destKey, List<K> keys);\n\n  Future<int?> bitPos(K key, int bit, {int? start, int? end});\n\n  Future<int?> decr(K key);\n\n  Future<int?> decrBy(K key, int decrement);\n\n  Future<int?> getBit(K key, int offset);\n\n  Future<V?> getSet(K key, V value);\n\n  Future<int?> incr(K key);\n\n  Future<int?> incrBy(K key, int increment);\n\n  Future<double?> incrByFloat(K key, double increment);\n\n  Future<List<V>> mGet(List<K> keys);\n\n  Future<bool> mSet(Map<K, V> keyValues);\n\n  Future<bool> mSetNX(Map<K, V> keyValues);\n\n  Future<int?> setBit(K key, int offset, int value);\n\n  Future<bool> pSetEx(K key, int ttl, V value);\n\n  Future<bool> setNx(K key, V value);\n\n  Future<int?> setRange(K key, int offset, V value);\n\n  Future<V?> getRange(K key, int start, int end);\n\n  Future<int?> strlen(K key);\n\n  Future<bool> setOption(String option);\n\n  Future<String?> getOption(String option);\n}\n\n/// list operation commands\nabstract class ListCommands<K, V> {\n  /// LRANGE command (get range [startIndex] to [endIndex])\n  Future<List<V>> lrange(K key, int startIndex, int endIndex);\n\n  /// RPUSH command (push to right side)\n  Future<bool> rpush(K key, List<V> values);\n\n  /// LPUSH command (push to left side)\n  Future<bool> lpush(K key, List<V> values);\n\n  /// LSET command (set value that placed in [index])\n  Future<bool> lset(K key, int index, V value);\n}\n\n/// transaction operation commands\nabstract class TransactionCommands<K, V> {\n  /// MULTI command (start transaction)\n  Future<void> multi();\n\n  /// EXEC command (apply transaction)\n  Future<void> exec();\n\n  /// DISCARD command (abort transaction)\n  Future<void> discard();\n}\n\n/// pubsub operation commands\nabstract class PubSubCommands<V> {\n  /// PSUBSCRIBE command (pattern subscribe)\n  Stream<V> psubscribe(String pattern);\n\n  /// PUBLISH command (publish [message] to [channel])\n  Future<int?> publish(String channel, V message);\n}\n"
  },
  {
    "path": "lib/src/redis/exception.dart",
    "content": "/// Redis error exception class\n/// All exceptions thrown from this package inherit from this class.\nclass RedisException implements Exception {\n  final String message;\n\n  const RedisException(this.message);\n\n  @override\n  String toString() => 'RedisException: $message';\n}\n\n/// Convert error exception class\nclass RedisConvertException extends RedisException {\n  const RedisConvertException(String message)\n    : super('convert error: $message');\n}\n"
  },
  {
    "path": "lib/src/redis/lowlevel/protocol_client.dart",
    "content": "import 'dart:async';\nimport 'dart:collection';\nimport 'dart:convert';\nimport 'dart:io';\n\nimport 'package:vania/src/redis/exception.dart';\nimport 'package:vania/src/redis/lowlevel/resp.dart';\n\n/// low level Redis Client\nclass RedisProtocolClient {\n  final Socket _socket;\n  final Queue<Completer<Resp>> _waitingCompleter = ListQueue<Completer<Resp>>();\n  final Stream<List<int>> _stream;\n\n  RedisProtocolClient._(this._socket) : _stream = _socket.asBroadcastStream() {\n    _stream.listen(_onData);\n  }\n\n  /// create [RedisProtocolClient]'s instance\n  static Future<RedisProtocolClient> createConnection({\n    required String host,\n    required int port,\n  }) async {\n    final sock = await Socket.connect(host, port)\n      ..setOption(SocketOption.tcpNoDelay, true);\n    return RedisProtocolClient._(sock);\n  }\n\n  /// event handling on data received\n  void _onData(List<int> data) {\n    final str = utf8.decode(data);\n    if (_waitingCompleter.isEmpty) {\n      return;\n    }\n    final f = _waitingCompleter.removeFirst();\n    final resp = Resp.deserialize(str);\n    if (resp == null) {\n      f.completeError(RedisConvertException('failed to convert'));\n      return;\n    }\n    f.complete(resp);\n  }\n\n  /// send Redis command data.\n  /// [resp]: redis command\n  void sendCommand(Resp resp) {\n    _socket.add(utf8.encode(resp.serialize()));\n  }\n\n  /// receive command\n  Future<Resp> receive() {\n    final c = Completer<Resp>();\n    _waitingCompleter.addLast(c);\n    return c.future;\n  }\n\n  /// received data stream\n  Stream<Resp?> get stream => _stream\n      .map((event) => utf8.decode(event))\n      .map((event) => Resp.deserialize(event));\n\n  /// close connection\n  Future<void> close() => _socket.close();\n}\n"
  },
  {
    "path": "lib/src/redis/lowlevel/resp.dart",
    "content": "// ignore_for_file: constant_identifier_names\n\nimport 'package:vania/src/redis/exception.dart';\n\n/// Redis error reply\nclass RedisError {\n  final String prefix;\n  final String message;\n\n  RedisError({required this.prefix, required this.message});\n\n  @override\n  String toString() => '$prefix: $message';\n}\n\n/// Redis protocol type\nenum RespType { STRING, ARRAY, INTEGER, DOUBLE, ERROR, NULL, UNKNOWN }\n\n/// Redis protocol data\nclass Resp {\n  static const _CRLF = '\\u000d\\u000a';\n\n  final dynamic value;\n\n  Resp(this.value);\n\n  /// serialize value implementation\n  String _serializeValue(dynamic value, {bool isBulkString = true}) {\n    if (value is String) {\n      if (!isBulkString && !value.contains(RegExp('s'))) {\n        return '+$value$_CRLF';\n      }\n      return '\\$${value.length}$_CRLF$value$_CRLF';\n    }\n    if (value is int) {\n      return ':$value$_CRLF';\n    }\n\n    if (value is List) {\n      final data = [\n        '*${value.length}$_CRLF',\n        ...value.map((e) => _serializeValue(e, isBulkString: true)),\n      ].join('');\n      return data;\n    }\n    return '';\n  }\n\n  /// serialize value in this instance\n  String serialize() => _serializeValue(value);\n\n  /// deserialize and create [Resp]'s instance.\n  /// [s]: serialized data\n  static Resp? deserialize(String s) =>\n      _deserializeEntry(s.split(_CRLF), 0)?.resp;\n\n  /// type of [value]\n  RespType get type {\n    if (value == null) {\n      return RespType.NULL;\n    }\n    if (value is String) {\n      return RespType.STRING;\n    }\n    if (value is List) {\n      return RespType.ARRAY;\n    }\n    if (value is int) {\n      return RespType.INTEGER;\n    }\n    if (value is double) {\n      return RespType.DOUBLE;\n    }\n    if (value is RedisError) {\n      return RespType.ERROR;\n    }\n    return RespType.UNKNOWN;\n  }\n\n  /// is [type] == [RespType.NULL]\n  bool get isNull => type == RespType.NULL;\n\n  /// is [type] == [RespType.STRING]\n  bool get isString => type == RespType.STRING;\n\n  /// is [type] == [RespType.ARRAY]\n  bool get isList => type == RespType.ARRAY;\n\n  /// is [type] == [RespType.INTEGER]\n  bool get isInteger => type == RespType.INTEGER;\n\n  /// is [type] == [RespType.DOUBLE]\n  bool get isDouble => type == RespType.DOUBLE;\n\n  /// is [type] == [RespType.ERROR]\n  bool get isError => type == RespType.ERROR;\n\n  /// Get String value if [isString] == true\n  String? get stringValue => isString ? value as String : null;\n\n  /// Get List value if [isList] == true\n  List<dynamic>? get arrayValue => isList ? value as List : null;\n\n  /// Get int value if [isInteger] == true\n  int? get integerValue => isInteger ? value as int : null;\n\n  double? get doubleValue => isInteger ? value as double : null;\n\n  /// Get [RedisError] value if [isError] == true\n  RedisError? get errorValue => isError ? value as RedisError : null;\n\n  /// throw exception if [isError] == true\n  void throwIfError() {\n    final err = errorValue;\n    if (err == null) {\n      return;\n    }\n    throw RedisException('$err');\n  }\n\n  /// for debug\n  @override\n  String toString() =>\n      '$type $stringValue $arrayValue $integerValue $doubleValue $errorValue';\n}\n\nclass _DeserializeResult {\n  final int endIndex;\n  final dynamic value;\n\n  _DeserializeResult(this.endIndex, this.value);\n\n  Resp get resp => Resp(value);\n}\n\nextension _SafeAt on List<String> {\n  String? safeAt(int index) => index < length ? this[index] : null;\n}\n\nextension _ToInt on String {\n  int? toInt() => int.tryParse(this);\n}\n\n_DeserializeResult? _deserializeEntry(List<String> s, int startIndex) {\n  final current = s.safeAt(startIndex);\n  if (current == null) {\n    return null;\n  }\n\n  switch (current[0]) {\n    case '+': // simple strings\n      return _deserializeSimpleString(s, startIndex);\n    case '-': // errors\n      return _deserializeError(s, startIndex);\n    case ':': // integers\n      return _deserializeInteger(s, startIndex);\n    case '\\$': // bulk strings\n      return _deserializeBulkString(s, startIndex);\n    case '*': // arrays\n      return _deserializeArray(s, startIndex);\n  }\n\n  return null;\n}\n\n_DeserializeResult? _deserializeSimpleString(List<String> s, int startIndex) {\n  final value = s.safeAt(startIndex)?.substring(1);\n  if (value == null) {\n    return null;\n  }\n  return _DeserializeResult(startIndex + 1, value);\n}\n\n_DeserializeResult? _deserializeBulkString(List<String> s, int startIndex) {\n  final lengthStr = s.safeAt(startIndex);\n  if (lengthStr == null) {\n    return null;\n  }\n\n  final length = lengthStr.substring(1).toInt();\n\n  if (length == null) {\n    return null;\n  }\n  if (length == -1) {\n    return _DeserializeResult(startIndex + 1, null);\n  }\n  if (length < 0) {\n    return null;\n  }\n  final value = s.sublist(startIndex + 1).join(Resp._CRLF).substring(0, length);\n\n  return _DeserializeResult(\n    startIndex + value.split(Resp._CRLF).length + 1,\n    value,\n  );\n}\n\n_DeserializeResult? _deserializeError(List<String> s, int startIndex) {\n  final value = s.safeAt(startIndex)?.substring(1);\n  if (value == null) {\n    return null;\n  }\n  final spl = value.split(' ');\n  final prefix = spl[0];\n  final message = spl.sublist(1).join(' ');\n\n  return _DeserializeResult(\n    startIndex + 1,\n    RedisError(prefix: prefix, message: message),\n  );\n}\n\n_DeserializeResult? _deserializeInteger(List<String> s, int startIndex) {\n  final value = s.safeAt(startIndex)?.substring(1).toInt();\n  if (value == null) {\n    return null;\n  }\n  return _DeserializeResult(startIndex + 1, value);\n}\n\n_DeserializeResult? _deserializeArray(List<String> s, int startIndex) {\n  final lengthStr = s.safeAt(startIndex);\n  if (lengthStr == null) {\n    return null;\n  }\n\n  final length = lengthStr.substring(1).toInt();\n\n  if (length == null) {\n    return null;\n  }\n  if (length == -1) {\n    return _DeserializeResult(startIndex + 1, null);\n  }\n  if (length < 0) {\n    return null;\n  }\n  final list = [];\n  var index = startIndex + 1;\n  for (var i = 0; i < length; ++i) {\n    final res = _deserializeEntry(s, index);\n    if (res == null) {\n      return null;\n    }\n    list.add(res.value);\n    index = res.endIndex;\n  }\n\n  return _DeserializeResult(index, list);\n}\n"
  },
  {
    "path": "lib/src/redis/redis.dart",
    "content": "import 'dart:async';\n\nimport 'package:vania/src/redis/vania_redis.dart';\n\nimport 'package:vania/src/utils/helper.dart' show env;\n\nclass Redis {\n  late Commands<String, String> command;\n\n  static final Completer<void> _completer = Completer<void>();\n\n  Future<void> get initialized => _completer.future;\n\n  Redis._internal();\n  static Redis? _singleton;\n  factory Redis() {\n    if (_singleton == null) {\n      _singleton = Redis._internal();\n      _singleton!._initRedis().then((_) {\n        _completer.complete();\n      });\n    }\n    return _singleton!;\n  }\n\n  Future<void> _initRedis() async {\n    RedisClient cli = await RedisClient.connect(\n      env<String>('REDIS_HOST', '127.0.0.1'),\n      env<int>('REDIS_PORT', 6379),\n      db: env<int>('REDIS_DB', 0),\n      username: env('REDIS_USERNAME'),\n      password: env<String>('REDIS_PASSWORD'),\n    );\n    command = cli.getCommands<String, String>();\n  }\n}\n"
  },
  {
    "path": "lib/src/redis/vania_redis.dart",
    "content": "export 'command/client.dart';\nexport 'command/codec.dart';\nexport 'command/commands.dart';\nexport 'redis.dart';\n"
  },
  {
    "path": "lib/src/route/middleware/csrf_middleware.dart",
    "content": "import 'dart:convert';\nimport 'package:crypto/crypto.dart';\nimport 'package:vania/http/response.dart';\nimport 'package:vania/src/config/config.dart';\nimport 'package:vania/src/exception/page_expired_exception.dart';\nimport 'package:vania/src/http/middleware/middleware.dart';\nimport 'package:vania/src/http/request/request.dart';\nimport 'package:vania/src/http/session/session_manager.dart';\nimport 'package:vania/src/ioc_container.dart';\nimport 'package:vania/src/utils/functions.dart';\n\nimport 'dart:async';\n\nclass CsrfMiddleware extends Middleware {\n  final SessionManager _sessionManager = IoCContainer()\n      .resolve<SessionManager>();\n\n  @override\n  Future<void> handle(Request req) async {\n    if (req.method?.toLowerCase() == 'post' ||\n        req.method?.toLowerCase() == 'put' ||\n        req.method?.toLowerCase() == 'patch') {\n      List<String> csrfExcept = ['api/*'];\n      csrfExcept.addAll(Config().get('csrf_except') ?? []);\n\n      String uri = Uri.parse(\n        sanitizeRoutePath(req.uri.toString()),\n      ).path.toLowerCase();\n      if (!_isUrlExcluded(uri, csrfExcept)) {\n        String requestCookie = req.cookie('XSRF-TOKEN') ?? '';\n        Map<String, dynamic> cookie = {};\n        if (requestCookie.isNotEmpty) {\n          cookie = jsonDecode(\n            utf8.decode(base64.decode(_fixBase64Padding(requestCookie))),\n          );\n        }\n\n        String? token =\n            req.input('_csrf') ??\n            req.input('_token') ??\n            req.header('X-CSRF-TOKEN');\n        if (token == null || token.isEmpty) {\n          if (req.isJson()) {\n            throw PageExpiredException(\n              message: 'Security Error: The CSRF token is missing or incorrect',\n              responseType: ResponseType.json,\n            );\n          }\n          throw PageExpiredException();\n        }\n\n        final storedToken = await _sessionManager.getSession<String?>(\n          'x_csrf_token',\n        );\n        if (storedToken == null || storedToken.isEmpty) {\n          if (req.isJson()) {\n            throw PageExpiredException(\n              message: 'Security Error: The CSRF token is missing or incorrect',\n              responseType: ResponseType.json,\n            );\n          }\n          throw PageExpiredException();\n        }\n\n        if (storedToken != token) {\n          if (req.isJson()) {\n            throw PageExpiredException(\n              message: 'Security Error: The CSRF token is missing or incorrect',\n              responseType: ResponseType.json,\n            );\n          }\n          throw PageExpiredException();\n        }\n\n        String iv = await _sessionManager.getSession<String>('x_csrf_iv');\n\n        String expectedCookie = _computeCsrfCookieValue(storedToken, iv);\n\n        if (expectedCookie != cookie['token']) {\n          if (req.isJson()) {\n            throw PageExpiredException(\n              message: 'Security Error: The CSRF token is missing or incorrect',\n              responseType: ResponseType.json,\n            );\n          }\n          throw PageExpiredException();\n        }\n      }\n    }\n  }\n\n  String _fixBase64Padding(String value) {\n    while (value.length % 4 != 0) {\n      value += '=';\n    }\n    return value;\n  }\n\n  bool _isUrlExcluded(String path, List<String> csrfExcept) {\n    final cleanPath = path.startsWith('/') ? path.substring(1) : path;\n    for (var pattern in csrfExcept) {\n      final cleanPattern = pattern.startsWith('/')\n          ? pattern.substring(1)\n          : pattern;\n      if (cleanPattern.contains('*')) {\n        final regexStr = cleanPattern\n            .replaceAll('*', '.*')\n            .replaceAll('/', '\\\\/');\n        final regex = RegExp('^$regexStr\\$', caseSensitive: false);\n        if (regex.hasMatch(cleanPath)) {\n          return true;\n        }\n      } else if (cleanPath.toLowerCase().startsWith(\n        cleanPattern.toLowerCase(),\n      )) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  String _computeCsrfCookieValue(String token, String iv) {\n    var hmac = Hmac(sha512, utf8.encode(iv));\n    final Digest digest = hmac.convert(utf8.encode(token));\n    return base64.encode(digest.bytes);\n  }\n}\n"
  },
  {
    "path": "lib/src/route/middleware/throttle.dart",
    "content": "import 'dart:io';\n\nimport 'package:vania/src/http/middleware/middleware.dart';\nimport 'package:vania/src/http/request/request.dart';\n\nimport 'package:vania/src/utils/helper.dart' show env;\nimport '../../exception/throttle_exception.dart';\nimport '../throttle_requests.dart';\n\nclass Throttle extends Middleware {\n  final int maxAttempts;\n  final Duration duration;\n  final bool includeUserIdentifier;\n  final String? customMessage;\n  final Map<String, String>? headers;\n  final bool bypassInDevelopment;\n\n  late final ThrottleRequests _throttle;\n\n  Throttle({\n    this.maxAttempts = 60,\n    this.duration = const Duration(minutes: 1),\n    this.includeUserIdentifier = false,\n    this.customMessage,\n    this.headers,\n    this.bypassInDevelopment = true,\n  }) {\n    _throttle = ThrottleRequests(maxAttempts: maxAttempts, duration: duration);\n  }\n\n  @override\n  Future<void> handle(Request req) async {\n    if (bypassInDevelopment &&\n        env<String>('APP_ENV', 'development') == 'development') {\n      return;\n    }\n\n    final String identifier = await _getRequestIdentifier(req);\n    final remaining = _throttle.remainingAttempts(identifier);\n\n    _addRateLimitHeaders(req.response, remaining);\n\n    if (!_throttle.request(identifier)) {\n      final retryAfter = _throttle.retryAfter(identifier);\n      throw ThrottleException(\n        message: customMessage ?? 'Too Many Requests. Please try again later.',\n        code: HttpStatus.tooManyRequests,\n        headers: {'Retry-After': retryAfter.inSeconds.toString(), ...?headers},\n      );\n    }\n  }\n\n  Future<String> _getRequestIdentifier(Request req) async {\n    final List<String> parts = [req.ip ?? 'unknown'];\n\n    if (includeUserIdentifier) {\n      final userMap = req.user;\n      if (userMap != null && userMap['id'] != null) {\n        parts.add(userMap['id'].toString());\n      }\n    }\n\n    return parts.join(':');\n  }\n\n  void _addRateLimitHeaders(HttpResponse response, int remaining) {\n    response.headers.add('X-RateLimit-Limit', maxAttempts.toString());\n    response.headers.add('X-RateLimit-Remaining', remaining.toString());\n    response.headers.add(\n      'X-RateLimit-Reset',\n      _throttle.resetTime().toIso8601String(),\n    );\n  }\n}\n"
  },
  {
    "path": "lib/src/route/route.dart",
    "content": "import 'package:meta/meta.dart';\nimport 'router.dart';\n\nclass Route {\n  String? get prefix => null;\n  @mustBeOverridden\n  @mustCallSuper\n  void register() {\n    Router.basePrefix(prefix);\n  }\n}\n"
  },
  {
    "path": "lib/src/route/route_data.dart",
    "content": "import 'package:vania/src/http/middleware/middleware.dart';\n\nclass RouteData {\n  final String method;\n  String path;\n  final Function action;\n  Map<String, dynamic>? params;\n  List<Middleware> preMiddleware;\n  String? domain;\n  final bool? corsEnabled;\n  final bool hasRequest;\n  final String? prefix;\n  String? name;\n  Map<String, Type>? paramTypes;\n  Map<String, String>? regex;\n\n  RouteData({\n    required this.method,\n    required this.path,\n    required this.action,\n    this.corsEnabled,\n    this.params,\n    this.preMiddleware = const <Middleware>[],\n    this.domain,\n    this.prefix,\n    this.hasRequest = false,\n    this.paramTypes,\n    this.name,\n    this.regex,\n  });\n\n  @override\n  String toString() {\n    return 'RouteData{method: $method, path: $path, name: $name}';\n  }\n}\n"
  },
  {
    "path": "lib/src/route/route_handler.dart",
    "content": "import 'dart:io';\n\nimport 'package:vania/src/enum/http_request_method.dart';\nimport 'package:vania/src/exception/not_found_exception.dart';\nimport 'package:vania/src/http/response/response.dart';\nimport 'package:vania/src/route/route_data.dart';\nimport 'package:vania/src/route/router.dart';\nimport 'package:vania/src/route/set_static_path.dart';\nimport 'package:vania/src/utils/functions.dart';\nimport 'package:vania/src/utils/helper.dart' show env;\n\nfinal Map<String, RegExp> _regexCache = {};\nfinal _lookupCache = _LruCache<_LookupKey, RouteData?>(\n  env<int>(\"ROUTE_LOOK_UP_SIZE\", 2000),\n);\nfinal Map<String, List<RouteData>> _staticRoutes = {};\nfinal List<RouteData> _dynamicRoutes = [];\n\nclass _LookupKey {\n  final String method;\n  final String path;\n  final String domain;\n\n  _LookupKey(this.method, this.path, this.domain);\n\n  @override\n  bool operator ==(Object other) {\n    if (identical(this, other)) return true;\n    if (other is! _LookupKey) return false;\n    return method == other.method &&\n        path == other.path &&\n        domain == other.domain;\n  }\n\n  @override\n  int get hashCode => Object.hash(method, path, domain);\n}\n\nclass _LruCache<K, V> {\n  final int maxSize;\n  final _map = <K, V>{};\n\n  _LruCache(this.maxSize);\n\n  V? get(K key) {\n    if (!_map.containsKey(key)) return null;\n    final value = _map.remove(key) as V;\n    _map[key] = value;\n    return value;\n  }\n\n  void put(K key, V value) {\n    if (_map.containsKey(key)) {\n      _map.remove(key);\n    }\n    _map[key] = value;\n    if (_map.length > maxSize) {\n      _map.remove(_map.keys.first);\n    }\n  }\n\n  void clear() => _map.clear();\n\n  bool contains(K key) => _map.containsKey(key);\n}\n\nvoid initializeRoutes() {\n  final router = Router();\n  for (var route in router.routes) {\n    final method = route.method.toLowerCase();\n    final normalizedPath = _normalizePath(\n      _normalizePrefix(route.prefix) + route.path,\n    );\n    route.regex?.forEach((param, pattern) {\n      _regexCache.putIfAbsent(pattern, () => RegExp(pattern));\n    });\n    if (!normalizedPath.contains('{')) {\n      _staticRoutes.putIfAbsent(method, () => []).add(route);\n    } else {\n      _dynamicRoutes.add(route);\n    }\n  }\n}\n\n/// Main HTTP request handler using LRU cache\nRouteData? httpRouteHandler(HttpRequest req) {\n  final method = req.method.toLowerCase();\n  final rawPath = sanitizeRoutePath(req.uri.toString());\n  final requestPath = Uri.decodeComponent(Uri.parse(rawPath).path);\n  final domain = req.headers.value(HttpHeaders.hostHeader) ?? '';\n\n  if (setStaticPath(req)) {\n    return null;\n  }\n\n  final key = _LookupKey(method, requestPath, domain);\n  final cached = _lookupCache.get(key);\n  if (cached != null) {\n    return cached;\n  }\n\n  final matchedRoute = _findMatchingRoute(requestPath, method, domain);\n  _lookupCache.put(key, matchedRoute);\n\n  if (matchedRoute == null) {\n    return _handleNotFound(req, method);\n  }\n  return matchedRoute;\n}\n\nRouteData? _handleNotFound(HttpRequest req, String method) {\n  if (method == HttpRequestMethod.options.name.toLowerCase()) {\n    req.response\n      ..headers.add('Content-Length', 0)\n      ..close();\n    return null;\n  }\n\n  throw NotFoundException(\n    message: {'message': 'Not found'},\n    responseType: ResponseType.json,\n  );\n}\n\nRouteData? _findMatchingRoute(\n  String requestPath,\n  String method,\n  String domain,\n) {\n  final staticList = _staticRoutes[method] ?? [];\n\n  for (final route in staticList) {\n    String fullPath = _normalizePath(\n      _normalizePrefix(route.prefix) + route.path,\n    );\n    if (fullPath.endsWith('/')) {\n      fullPath = fullPath.substring(0, fullPath.length - 1);\n    }\n    if (fullPath == _normalizePath(requestPath) &&\n        _domainMatches(domain, route.domain)) {\n      return _applyDomainParams(route, domain);\n    }\n  }\n\n  for (final route in _dynamicRoutes.where(\n    (r) => r.method.toLowerCase() == method,\n  )) {\n    if (!_domainMatches(domain, route.domain)) continue;\n    final result = _matchDynamic(requestPath, route, domain);\n    if (result != null) return result;\n  }\n\n  return null;\n}\n\nbool _domainMatches(String requestDomain, String? routeDomain) {\n  if (routeDomain == null) return true;\n  final pattern = routeDomain.toLowerCase();\n\n  if (!pattern.contains('{')) {\n    return requestDomain.toLowerCase() == pattern;\n  }\n\n  final placeholder = RegExp(r'{([^}]+)}').firstMatch(pattern)!.group(1)!;\n  final actual = requestDomain.split('.').first.toLowerCase();\n  final expected = pattern.replaceAll('{$placeholder}', actual);\n  return expected == requestDomain.toLowerCase();\n}\n\nRouteData _applyDomainParams(RouteData route, String domain) {\n  final copy = RouteData(\n    method: route.method,\n    path: route.path,\n    action: route.action,\n    corsEnabled: route.corsEnabled,\n    params: Map.from(route.params ?? {}),\n    preMiddleware: List.from(route.preMiddleware),\n    domain: route.domain,\n    prefix: route.prefix,\n    hasRequest: route.hasRequest,\n    paramTypes: route.paramTypes != null ? Map.from(route.paramTypes!) : null,\n    name: route.name,\n    regex: route.regex != null ? Map.from(route.regex!) : null,\n  );\n\n  if (route.domain != null && route.domain!.contains('{')) {\n    final placeholder = RegExp(\n      r'{([^}]+)}',\n    ).firstMatch(route.domain!)!.group(1)!;\n    copy.params![placeholder] = domain.split('.').first;\n  }\n\n  return copy;\n}\n\n/// Matches dynamic (parameterized) routes and validates params\nRouteData? _matchDynamic(String requestPath, RouteData route, String domain) {\n  final patternPath = _normalizePath(\n    _normalizePrefix(route.prefix) + route.path,\n  );\n  final reqParts = _normalizePath(requestPath).split('/');\n  final patternParts = patternPath.split('/');\n\n  if (reqParts.length != patternParts.length) return null;\n\n  final params = <String, dynamic>{};\n  for (var i = 0; i < patternParts.length; i++) {\n    final partPattern = patternParts[i];\n    final partValue = reqParts[i];\n    if (partPattern.startsWith('{') && partPattern.endsWith('}')) {\n      final name = partPattern.substring(1, partPattern.length - 1);\n      params[name] = partValue;\n    } else if (partPattern != partValue) {\n      return null;\n    }\n  }\n\n  if (!_validateParams(params, route)) return null;\n  final routed = _applyDomainParams(route, domain);\n  routed.params = params;\n  return routed;\n}\n\n/// Validates parameter types and regex constraints\nbool _validateParams(Map<String, dynamic> params, RouteData route) {\n  if (route.paramTypes != null) {\n    for (final entry in route.paramTypes!.entries) {\n      final name = entry.key;\n      final type = entry.value;\n      if (!params.containsKey(name)) return false;\n      if (type == int) {\n        final val = int.tryParse(params[name].toString());\n        if (val == null) return false;\n        params[name] = val;\n      }\n    }\n  }\n\n  if (route.regex != null) {\n    for (final entry in route.regex!.entries) {\n      final name = entry.key;\n      final pattern = entry.value;\n      final regex = _regexCache[pattern]!;\n      if (!regex.hasMatch(params[name].toString())) return false;\n    }\n  }\n\n  return true;\n}\n\nvoid clearRouteCaches() {\n  _regexCache.clear();\n  _lookupCache.clear();\n  _staticRoutes.clear();\n  _dynamicRoutes.clear();\n}\n\n/// Normalizes a route or request path: trims and removes extra slashes\nString _normalizePath(String path) {\n  return path\n      .trim()\n      .replaceAll('//', '/')\n      .replaceAll(RegExp(r'/+\\$'), '')\n      .replaceAll(RegExp(r'^/'), '');\n}\n\n/// Normalizes the route prefix by ensuring leading slash and no trailing slash\nString _normalizePrefix(String? prefix) {\n  if (prefix == null || prefix.isEmpty) return '';\n  final p = prefix.trim();\n  return '/${p.replaceAll(RegExp(r'^/|/\\$'), '')}';\n}\n"
  },
  {
    "path": "lib/src/route/route_history.dart",
    "content": "import 'dart:io';\n\nclass RouteHistory {\n  static final RouteHistory _instance = RouteHistory._internal();\n  factory RouteHistory() => _instance;\n  RouteHistory._internal();\n\n  String _currentRoute = '';\n  String _previousRoute = '';\n\n  String get currentRoute => _currentRoute;\n  String get previousRoute => _previousRoute;\n\n  Future<void> updateRouteHistory(HttpRequest req) async {\n    // Only track HTML responses\n    if (_isHtmlRequest(req)) {\n      _updateRoutes(req.uri.path);\n    }\n  }\n\n  bool _isHtmlRequest(HttpRequest req) {\n    final acceptHeader = req.headers.value('accept');\n    return acceptHeader != null && acceptHeader.toString().contains('html');\n  }\n\n  void _updateRoutes(String path) {\n    if (_currentRoute.isEmpty) {\n      _currentRoute = path;\n    } else {\n      _previousRoute = _currentRoute;\n      _currentRoute = path;\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/route/router.dart",
    "content": "import 'package:vania/src/enum/http_request_method.dart';\nimport 'package:vania/src/http/middleware/middleware.dart';\nimport 'package:vania/src/route/route_data.dart';\nimport 'package:vania/src/websocket/web_socket_handler.dart';\nimport 'package:vania/src/websocket/websocket_event.dart';\n\nimport '../../vania.dart' show env;\n\nclass Router {\n  static final Router _singleton = Router._internal();\n  factory Router() => _singleton;\n  Router._internal();\n\n  String? _prefix;\n  String? _groupPrefix;\n  String? _groupDomain;\n  final List<Middleware> _groupMiddleware = [];\n  final List<RouteData> _routes = [];\n\n  static final RegExp _requestVarRegex = RegExp(r'Closure: \\(([^)]*)\\) =>');\n  static final RegExp _closureStartRegex = RegExp(r'Closure: \\(');\n\n  List<RouteData> get routes => List.unmodifiable(_routes);\n\n  static String url(String name, [Map<String, dynamic>? params]) {\n    RouteData routeData = Router()._routes\n        .where((route) => route.name == name)\n        .first;\n\n    if (params == null) {\n      return '${env<String>('APP_URL')}/${routeData.path}';\n    }\n\n    final reg = RegExp(r'\\{(\\w+)\\}');\n    return routeData.path.replaceAllMapped(reg, (match) {\n      final key = match.group(1)!;\n      if (!params.containsKey(key)) {\n        throw ArgumentError('Missing parameter: $key');\n      }\n      return '${env<String>('APP_URL')}/${params[key].toString()}';\n    });\n  }\n\n  static void basePrefix([String? prefix]) {\n    if (prefix == null) {\n      Router()._prefix = null;\n      return;\n    }\n    Router()._prefix = prefix.endsWith(\"/\")\n        ? prefix.substring(0, prefix.length - 1)\n        : prefix;\n  }\n\n  bool _getRequestVar(String input) {\n    if (!_closureStartRegex.hasMatch(input)) return false;\n\n    final match = _requestVarRegex.firstMatch(input);\n    if (match == null) return false;\n\n    final params = match.group(1)!;\n    final firstParam = params.split(',').firstOrNull;\n    return firstParam == 'Request';\n  }\n\n  Router _addRouteInternal(\n    HttpRequestMethod method,\n    String path,\n    Function action, {\n    Map<String, Type>? paramTypes,\n    Map<String, String>? regex,\n  }) {\n    final bool hasRequest = _getRequestVar(action.toString());\n\n    if (!path.startsWith('/')) {\n      path = '/$path';\n    }\n\n    final normalizedPath = _normalizePath(path);\n    _routes.add(\n      RouteData(\n        method: method.name,\n        path: normalizedPath,\n        action: action,\n        prefix: _prefix,\n        paramTypes: paramTypes,\n        regex: regex,\n        hasRequest: hasRequest,\n      ),\n    );\n    return this;\n  }\n\n  String _normalizePath(String path) {\n    return path.trim();\n  }\n\n  static Router _addRoute(\n    HttpRequestMethod method,\n    String path,\n    Function action,\n  ) {\n    return Router()\n        ._addRouteInternal(method, path, action)\n        .middleware(Router()._groupMiddleware)\n        .domain(Router()._groupDomain)\n        .prefix(Router()._groupPrefix);\n  }\n\n  Router middleware([List<Middleware>? middleware]) {\n    if (middleware != null && _routes.isNotEmpty) {\n      _routes.last.preMiddleware = [\n        ..._routes.last.preMiddleware,\n        ...middleware,\n      ];\n    }\n    return this;\n  }\n\n  Router prefix([String? prefix]) {\n    if (prefix != null && _routes.isNotEmpty) {\n      final route = _routes.last;\n      final basePath = route.path.startsWith('/')\n          ? route.path.substring(1)\n          : route.path;\n\n      route.path = prefix.endsWith(\"/\")\n          ? \"$prefix$basePath\"\n          : \"$prefix/$basePath\";\n    }\n    return this;\n  }\n\n  Router name([String? name]) {\n    if (name != null && _routes.isNotEmpty) {\n      _routes.last.name = name;\n    }\n    return this;\n  }\n\n  Router domain([String? domain]) {\n    if (domain != null && _routes.isNotEmpty) {\n      _routes.last.domain = domain;\n    }\n    return this;\n  }\n\n  Router whereInt(String paramName) {\n    if (_routes.isNotEmpty) {\n      _routes.last.paramTypes ??= {};\n      _routes.last.paramTypes![paramName] = int;\n    }\n    return this;\n  }\n\n  Router whereString(String paramName) {\n    if (_routes.isNotEmpty) {\n      _routes.last.paramTypes ??= {};\n      _routes.last.paramTypes![paramName] = String;\n    }\n    return this;\n  }\n\n  Router whereDouble(String paramName) {\n    if (_routes.isNotEmpty) {\n      _routes.last.paramTypes ??= {};\n      _routes.last.paramTypes![paramName] = double;\n    }\n    return this;\n  }\n\n  Router whereBool(String paramName) {\n    if (_routes.isNotEmpty) {\n      _routes.last.paramTypes ??= {};\n      _routes.last.paramTypes![paramName] = bool;\n    }\n    return this;\n  }\n\n  Router where(String paramName, String regex) {\n    if (_routes.isNotEmpty) {\n      _routes.last.regex ??= {};\n      _routes.last.regex![paramName] = regex;\n    }\n    return this;\n  }\n\n  static Router get(String path, Function action) =>\n      _addRoute(HttpRequestMethod.get, path, action);\n\n  static Router post(String path, Function action) =>\n      _addRoute(HttpRequestMethod.post, path, action);\n\n  static Router put(String path, Function action) =>\n      _addRoute(HttpRequestMethod.put, path, action);\n\n  static Router patch(String path, Function action) =>\n      _addRoute(HttpRequestMethod.patch, path, action);\n\n  static Router delete(String path, Function action) =>\n      _addRoute(HttpRequestMethod.delete, path, action);\n\n  static Router options(String path, Function action) =>\n      _addRoute(HttpRequestMethod.options, path, action);\n\n  static Router purge(String path, Function action) =>\n      _addRoute(HttpRequestMethod.purge, path, action);\n\n  static Router copy(String path, Function action) =>\n      _addRoute(HttpRequestMethod.copy, path, action);\n\n  static Router link(String path, Function action) =>\n      _addRoute(HttpRequestMethod.link, path, action);\n\n  static Router unlink(String path, Function action) =>\n      _addRoute(HttpRequestMethod.unlink, path, action);\n\n  static Router lock(String path, Function action) =>\n      _addRoute(HttpRequestMethod.lock, path, action);\n\n  static Router unlock(String path, Function action) =>\n      _addRoute(HttpRequestMethod.unlock, path, action);\n\n  static Router propfind(String path, Function action) =>\n      _addRoute(HttpRequestMethod.propfind, path, action);\n\n  static Router any(String path, Function action) {\n    final router = Router();\n\n    final currentPrefix = router._prefix;\n\n    for (HttpRequestMethod method in HttpRequestMethod.values) {\n      final routeData = RouteData(\n        method: method.name,\n        path: router._normalizePath(path),\n        action: action,\n        prefix: currentPrefix,\n        hasRequest: router._getRequestVar(action.toString()),\n        preMiddleware: List.from(router._groupMiddleware),\n        domain: router._groupDomain,\n      );\n\n      router._routes.add(routeData);\n    }\n\n    return router;\n  }\n\n  static void resource(\n    String path,\n    dynamic controller, {\n    String? prefix,\n    List<Middleware>? middleware,\n    String? domain,\n    String regex = r'\\d+(.\\d+)?',\n  }) {\n    Router.get(\n      path,\n      controller.index,\n    ).middleware(middleware).domain(domain).prefix(prefix);\n\n    Router.get(\n      \"$path/create\",\n      controller.create,\n    ).middleware(middleware).domain(domain).prefix(prefix);\n\n    Router.post(\n      path,\n      controller.store,\n    ).middleware(middleware).domain(domain).prefix(prefix);\n\n    Router.get(\n      \"$path/{id}\",\n      controller.show,\n    ).middleware(middleware).domain(domain).prefix(prefix).where('id', regex);\n\n    Router.get(\n      \"$path/{id}/edit\",\n      controller.edit,\n    ).middleware(middleware).domain(domain).prefix(prefix).where('id', regex);\n\n    Router.put(\n      \"$path/{id}\",\n      controller.update,\n    ).middleware(middleware).domain(domain).prefix(prefix).where('id', regex);\n\n    Router.delete(\n      \"$path/{id}\",\n      controller.destroy,\n    ).middleware(middleware).domain(domain).prefix(prefix).where('id', regex);\n  }\n\n  static void websocket(\n    String path,\n    Function(WebSocketEvent) eventCallback, {\n    List<WebSocketMiddleware>? middleware,\n  }) {\n    final currentPrefix = Router()._prefix;\n\n    String fullPath = path;\n    if (currentPrefix != null) {\n      fullPath = currentPrefix.endsWith('/')\n          ? \"$currentPrefix$path\"\n          : \"$currentPrefix/$path\";\n    }\n\n    eventCallback(\n      WebSocketHandler().websocketRoute(fullPath, middleware: middleware),\n    );\n  }\n\n  static void group(\n    Function callback, {\n    String? prefix = '',\n    List<Middleware> middleware = const [],\n    String? domain,\n  }) {\n    final router = Router();\n\n    final previousDomain = router._groupDomain;\n    final previousPrefix = router._groupPrefix;\n    final previousMiddleware = List<Middleware>.from(router._groupMiddleware);\n\n    router._groupDomain = domain ?? previousDomain;\n\n    if (router._groupPrefix != null) {\n      if (prefix != null) {\n        if (!prefix.startsWith('/')) {\n          prefix = '/$prefix';\n        }\n        router._groupPrefix = _joinPrefixes(router._groupPrefix!, prefix);\n      }\n    } else {\n      if (prefix != null) {\n        if (!prefix.startsWith('/')) {\n          prefix = '/$prefix';\n        }\n      }\n      router._groupPrefix = prefix;\n    }\n\n    if (middleware.isNotEmpty) {\n      router._groupMiddleware.addAll(middleware);\n    }\n    callback();\n\n    router._groupDomain = previousDomain;\n    router._groupPrefix = previousPrefix;\n\n    router._groupMiddleware\n      ..clear()\n      ..addAll(previousMiddleware);\n  }\n\n  static String _joinPrefixes(String basePrefix, String newPrefix) {\n    final base = basePrefix.endsWith('/') ? basePrefix : '$basePrefix/';\n    final prefix = newPrefix.startsWith('/')\n        ? newPrefix.substring(1)\n        : newPrefix;\n    return '$base$prefix'.replaceAll(RegExp(r'//'), '/');\n  }\n}\n"
  },
  {
    "path": "lib/src/route/set_static_path.dart",
    "content": "import 'dart:io';\n\nimport 'package:vania/src/http/response/response.dart';\nimport 'package:vania/src/utils/functions.dart';\n\nimport 'package:path/path.dart' as path;\n\nbool setStaticPath(HttpRequest req) {\n  String routePath = Uri.decodeComponent(\n    Uri.parse(sanitizeRoutePath(req.uri.toString())).path.toLowerCase(),\n  );\n  if (!routePath.endsWith(\"/\") && req.method.toLowerCase() == 'get') {\n    if (req.uri.path == '/') {\n      routePath = 'index.html';\n    }\n    File file = File(sanitizeRoutePath(\"public/$routePath\"));\n    if (file.existsSync()) {\n      Response response = Response.file(\n        path.basename(file.path),\n        file.readAsBytesSync(),\n      );\n      response.makeResponse(req.response);\n      return true;\n    } else {\n      return false;\n    }\n  } else {\n    return false;\n  }\n}\n"
  },
  {
    "path": "lib/src/route/throttle_requests.dart",
    "content": "class ThrottleRequests {\n  final int maxAttempts;\n  final Duration duration;\n  final Map<String, _ThrottleData> _requests = {};\n\n  ThrottleRequests({required this.maxAttempts, required this.duration});\n\n  bool request(String identifier) {\n    _cleanup();\n\n    final now = DateTime.now();\n    final data =\n        _requests[identifier] ?? _ThrottleData(attempts: 0, firstAttempt: now);\n\n    if (data.attempts >= maxAttempts &&\n        now.difference(data.firstAttempt) < duration) {\n      return false;\n    }\n\n    if (now.difference(data.firstAttempt) >= duration) {\n      data.attempts = 1;\n      data.firstAttempt = now;\n    } else {\n      data.attempts++;\n    }\n\n    _requests[identifier] = data;\n    return true;\n  }\n\n  int remainingAttempts(String identifier) {\n    _cleanup();\n    final data = _requests[identifier];\n    if (data == null) return maxAttempts;\n\n    if (DateTime.now().difference(data.firstAttempt) >= duration) {\n      return maxAttempts;\n    }\n\n    return maxAttempts - data.attempts;\n  }\n\n  Duration retryAfter(String identifier) {\n    final data = _requests[identifier];\n    if (data == null) return Duration.zero;\n\n    final elapsed = DateTime.now().difference(data.firstAttempt);\n    if (elapsed >= duration) return Duration.zero;\n\n    return duration - elapsed;\n  }\n\n  DateTime resetTime() {\n    return DateTime.now().add(duration);\n  }\n\n  void _cleanup() {\n    final now = DateTime.now();\n    _requests.removeWhere(\n      (_, data) => now.difference(data.firstAttempt) >= duration,\n    );\n  }\n}\n\nclass _ThrottleData {\n  DateTime firstAttempt;\n  int attempts;\n\n  _ThrottleData({required this.firstAttempt, required this.attempts});\n}\n"
  },
  {
    "path": "lib/src/server/base_http_server.dart",
    "content": "import 'dart:io';\nimport 'package:args/args.dart';\nimport 'package:vania/src/http/request/request_handler.dart';\n\nimport 'package:vania/src/utils/helper.dart' show env;\nimport '../ioc_container.dart';\nimport 'initialize_config.dart';\n\nclass BaseHttpServer {\n  final Map<String, dynamic> config;\n  final List<String> args;\n\n  BaseHttpServer({required this.config, this.args = const []});\n\n  HttpServer? httpServer;\n\n  /// Starts the HTTP server with the current configuration.\n  ///\n  /// If the application is configured to use a secure connection, the server\n  /// will be started using HTTPS with the provided certificate and private key.\n  /// Otherwise, it will start an HTTP server.\n  ///\n  /// The server listens for incoming HTTP requests using the `httpRequestHandler`.\n  ///\n  /// If the `APP_DEBUG` environment variable is set to true, the server's URL\n  /// will be printed to the console.\n  ///\n  /// An optional [onError] callback can be provided to handle server start errors.\n  ///\n  /// Returns a [Future] that completes with the started [HttpServer] instance.\n  ///\n  /// Throws an error if the server fails to start.\n\n  Future<HttpServer> startServer({Function? onError}) async {\n    String host = env<String>('APP_HOST', InternetAddress.anyIPv6.host);\n    int port = env<int>('APP_PORT', 8000);\n\n    if (args.isNotEmpty) {\n      final parser = ArgParser()\n        ..addOption('host', abbr: 'h')\n        ..addOption('port', abbr: 'p');\n\n      ArgResults results;\n      try {\n        results = parser.parse(args);\n      } on ArgParserException catch (e) {\n        stderr.writeln('Error: ${e.message}\\n');\n        stderr.writeln(parser.usage);\n        exit(64);\n      }\n\n      if (results['host'] != null) {\n        host = results['host'];\n      }\n\n      if (results['port'] != null) {\n        port = int.tryParse(results['port']) ?? 8000;\n      }\n    }\n    try {\n      await initializeConfig(config);\n      if (env<bool>('APP_SECURE', false)) {\n        String certificateChain = env<String>('APP_CERTIFICATE');\n        String serverKey = env<String>('APP_PRIVATE_KEY');\n        String password = env<String>('APP_PRIVATE_KEY_PASSWORD');\n\n        SecurityContext context = SecurityContext()\n          ..useCertificateChain(certificateChain)\n          ..usePrivateKey(serverKey, password: password);\n\n        httpServer = await HttpServer.bindSecure(\n          host,\n          port,\n          context,\n          shared: env<bool>('APP_SHARED', false),\n        );\n      } else {\n        httpServer = await HttpServer.bind(\n          host,\n          port,\n          shared: env<bool>('APP_SHARED', false),\n        );\n      }\n\n      httpServer?.listen(IoCContainer().resolve<RequestHandler>().handle);\n\n      if (env<bool>('APP_DEBUG')) {\n        stderr.writeln('Server started on http://127.0.0.1:$port');\n      }\n      return httpServer!;\n    } catch (e) {\n      stderr.writeln('${DateTime.now().toUtc()} ERROR: $e');\n      rethrow;\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/server/initialize_config.dart",
    "content": "import 'package:vania/src/route/route_handler.dart';\n\nimport '../database/_database_utils/_db_config.dart';\nimport '../config/config.dart';\nimport '../database/_connection_manager.dart';\nimport '../service/service_provider.dart';\nimport '../utils/helper.dart';\n\nFuture<void> initializeConfig(Map<String, dynamic> config) async {\n  Config().setApplicationConfig = config;\n\n  if (env('DB_CONNECTION') != null && config['database'] != null) {\n    final Map<String, dynamic> database = config['database'];\n    ConnectionManager().defaultConnection = database['default'];\n    Map<String, dynamic> connections = database['connections'];\n    await ConnectionManager().connect(\n      _config(connections[ConnectionManager().defaultConnection]),\n      database['default'],\n    );\n\n    // Handle additional connections\n    List<String> additionalConnections =\n        database['additional_connections'] ?? <String>[];\n    if (additionalConnections.isNotEmpty) {\n      for (String connection in additionalConnections) {\n        await ConnectionManager().connect(\n          _config(connections[connection]),\n          connection,\n        );\n      }\n    }\n  }\n\n  List<ServiceProvider> providers = config['providers'];\n  for (ServiceProvider provider in providers) {\n    await provider.register();\n    await provider.boot();\n  }\n\n  initializeRoutes();\n}\n\nDBConfig _config(Map<String, dynamic> database) => DBConfig(\n  driver: database['driver'] ?? '',\n  host: database['host'] ?? '',\n  port: database['port'] ?? '',\n  database: database['database'] ?? '',\n  username: database['username'] ?? '',\n  password: database['password'] ?? '',\n  sslMode: database['sslmode'] ?? '',\n  collation: database['collation'] ?? 'utf8',\n  timezone: database['timezone'] ?? 'UTC',\n  pool: database['pool'],\n  poolSize: database['poolsize'],\n  filePath: database['file_path'] ?? '',\n  schema: database['schema'] ?? 'public',\n  openInMemorySQLite: database['openInMemorySQLite'] ?? false,\n);\n"
  },
  {
    "path": "lib/src/service/service_provider.dart",
    "content": "abstract class ServiceProvider {\n  const ServiceProvider();\n  Future<void> boot();\n  Future<void> register();\n}\n"
  },
  {
    "path": "lib/src/storage/local_storage.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'dart:typed_data';\n\nimport 'package:mime/mime.dart';\nimport 'package:path/path.dart' as path;\nimport '../utils/helper.dart';\nimport 'storage_driver.dart';\n\nclass LocalStorage implements StorageDriver {\n  static final LocalStorage _instance = LocalStorage._internal();\n  factory LocalStorage() => _instance;\n  LocalStorage._internal();\n\n  final String _storageDir = storagePath('app/public');\n\n  @override\n  Future<bool> delete(String file) async {\n    final targetFile = File(path.join(_storageDir, file));\n    if (!await targetFile.exists()) return false;\n\n    try {\n      await targetFile.delete();\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  @override\n  Future<bool> exists(String file) async {\n    return await File(path.join(_storageDir, file)).exists();\n  }\n\n  @override\n  Future<Uint8List?> getAsBytes(String file) async {\n    final targetFile = File(path.join(_storageDir, file));\n    if (!await targetFile.exists()) return null;\n\n    try {\n      return targetFile.readAsBytes();\n    } catch (e) {\n      return null;\n    }\n  }\n\n  @override\n  Future<String?> get(String file) async {\n    final targetFile = File(path.join(_storageDir, file));\n    if (!await targetFile.exists()) return null;\n\n    try {\n      return targetFile.readAsString();\n    } catch (e) {\n      return null;\n    }\n  }\n\n  @override\n  Future<Map<String, dynamic>?> json(String file) async {\n    final content = await get(file);\n    if (content == null) return null;\n\n    try {\n      return jsonDecode(content) as Map<String, dynamic>;\n    } catch (e) {\n      return null;\n    }\n  }\n\n  @override\n  Future<String> put(String path, dynamic content) async {\n    final targetFile = File(_getFullPath(path));\n    await _ensureDirectoryExists(targetFile.parent);\n\n    try {\n      if (content is List<int>) {\n        await targetFile.writeAsBytes(content);\n      } else {\n        await targetFile.writeAsString(content.toString());\n      }\n      return path;\n    } catch (e) {\n      throw Exception('Failed to write file: $e');\n    }\n  }\n\n  @override\n  Future<String?> mimeType(String file) async {\n    final targetFile = File(path.join(_storageDir, file));\n    if (!await targetFile.exists()) return null;\n\n    try {\n      final bytes = await targetFile.readAsBytes();\n      return lookupMimeType(file, headerBytes: bytes);\n    } catch (e) {\n      return null;\n    }\n  }\n\n  @override\n  Future<num?> size(String file) async {\n    final targetFile = File(path.join(_storageDir, file));\n    if (!await targetFile.exists()) return null;\n\n    try {\n      return targetFile.length();\n    } catch (e) {\n      return null;\n    }\n  }\n\n  String _getFullPath(String filePath) {\n    return path.join(_storageDir, filePath);\n  }\n\n  Future<void> _ensureDirectoryExists(Directory directory) async {\n    if (!await directory.exists()) {\n      await directory.create(recursive: true);\n    }\n  }\n\n  @override\n  String fullPath(String file) => path.join(_storageDir, file);\n}\n"
  },
  {
    "path": "lib/src/storage/s3_storage.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'dart:math';\nimport 'dart:typed_data';\nimport 'package:crypto/crypto.dart';\nimport 'package:mime/mime.dart';\nimport 'package:vania/src/aws/s3_client.dart';\nimport 'storage_driver.dart';\n\nclass S3Storage implements StorageDriver {\n  static final S3Storage _instance = S3Storage._internal();\n  factory S3Storage() => _instance;\n  S3Storage._internal();\n\n  final S3Client _s3Client = S3Client();\n  final Map<String, _CachedMetadata> _metadataCache = {};\n  static const Duration _metadataCacheDuration = Duration(minutes: 5);\n\n  String removeLeadingSlash(String file) {\n    return file.startsWith('/') ? file.replaceFirst('/', '') : file;\n  }\n\n  @override\n  String fullPath(String file) {\n    return _s3Client.buildUri(file).toString();\n  }\n\n  @override\n  Future<String> put(String filePath, dynamic content) async {\n    filePath = removeLeadingSlash(filePath);\n    final uri = _s3Client.buildUri(filePath);\n\n    final client = HttpClient();\n    try {\n      final request = await client.putUrl(uri);\n      final contentType =\n          lookupMimeType(filePath) ?? 'application/octet-stream';\n      request.headers.set('Content-Type', contentType);\n      request.headers.set('Content-Length', content.length.toString());\n\n      final payloadHash = sha256.convert(content).toString();\n      _s3Client\n          .generateS3Headers('PUT', filePath, hash: payloadHash)\n          .forEach((key, value) => request.headers.set(key, value));\n\n      request.add(content);\n      final response = await request.close();\n\n      if (response.statusCode == 200) {\n        _invalidateMetadataCache(filePath);\n        return uri.toString();\n      }\n      throw Exception('Failed to upload file: ${response.statusCode}');\n    } finally {\n      client.close();\n    }\n  }\n\n  @override\n  Future<String?> get(String file) async {\n    file = removeLeadingSlash(file);\n    final client = HttpClient();\n    try {\n      final response = await _executeRequest(client, 'GET', file);\n      if (response.statusCode == 200) {\n        return await response.transform(utf8.decoder).join();\n      }\n      return null;\n    } finally {\n      client.close();\n    }\n  }\n\n  @override\n  Future<Uint8List?> getAsBytes(String file) async {\n    file = removeLeadingSlash(file);\n    final client = HttpClient();\n    try {\n      final response = await _executeRequest(client, 'GET', file);\n      if (response.statusCode == 200) {\n        return await response\n            .fold<BytesBuilder>(BytesBuilder(), (b, d) => b..add(d))\n            .then((b) => b.takeBytes());\n      }\n      return null;\n    } finally {\n      client.close();\n    }\n  }\n\n  @override\n  Future<Map<String, dynamic>?> json(String file) async {\n    final content = await get(removeLeadingSlash(file));\n    if (content == null) return null;\n\n    try {\n      return jsonDecode(content) as Map<String, dynamic>;\n    } catch (e) {\n      return null;\n    }\n  }\n\n  @override\n  Future<String?> mimeType(String file) async {\n    final metadata = await _getMetadata(file);\n    if (metadata?.contentType != null) {\n      return metadata!.contentType;\n    }\n\n    final bytes = await getAsBytes(file);\n    if (bytes != null) {\n      return lookupMimeType(\n        file,\n        headerBytes: bytes.sublist(0, min(4096, bytes.length)),\n      );\n    }\n    return null;\n  }\n\n  @override\n  Future<num?> size(String file) async {\n    final metadata = await _getMetadata(file);\n    return metadata?.contentLength;\n  }\n\n  @override\n  Future<bool> exists(String file) async {\n    final metadata = await _getMetadata(file);\n    return metadata != null;\n  }\n\n  @override\n  Future<bool> delete(String file) async {\n    file = removeLeadingSlash(file);\n    final client = HttpClient();\n    try {\n      final response = await _executeRequest(client, 'DELETE', file);\n      final success = response.statusCode == 204;\n      if (success) {\n        _invalidateMetadataCache(file);\n      }\n      return success;\n    } finally {\n      client.close();\n    }\n  }\n\n  Future<HttpClientResponse> _executeRequest(\n    HttpClient client,\n    String method,\n    String file,\n  ) async {\n    final uri = _s3Client.buildUri(file);\n    final request = await _getRequestForMethod(client, method, uri);\n\n    _s3Client\n        .generateS3Headers(method, file)\n        .forEach((key, value) => request.headers.set(key, value));\n\n    return await request.close();\n  }\n\n  Future<HttpClientRequest> _getRequestForMethod(\n    HttpClient client,\n    String method,\n    Uri uri,\n  ) async {\n    switch (method) {\n      case 'GET':\n        return await client.getUrl(uri);\n      case 'PUT':\n        return await client.putUrl(uri);\n      case 'DELETE':\n        return await client.deleteUrl(uri);\n      case 'HEAD':\n        return await client.headUrl(uri);\n      default:\n        throw UnsupportedError('Unsupported HTTP method: $method');\n    }\n  }\n\n  Future<_CachedMetadata?> _getMetadata(String file) async {\n    file = removeLeadingSlash(file);\n\n    // Check cache first\n    final cached = _metadataCache[file];\n    if (cached != null && !cached.isExpired) {\n      return cached;\n    }\n    final client = HttpClient();\n    try {\n      final response = await _executeRequest(client, 'HEAD', file);\n      if (response.statusCode != 200) return null;\n\n      final metadata = _CachedMetadata(\n        contentLength: int.tryParse(\n          response.headers.value('content-length') ?? '',\n        ),\n        contentType: response.headers.value('content-type'),\n        lastModified: response.headers.value('last-modified'),\n      );\n\n      _metadataCache[file] = metadata;\n      return metadata;\n    } finally {\n      client.close();\n    }\n  }\n\n  void _invalidateMetadataCache(String file) {\n    _metadataCache.remove(file);\n  }\n}\n\nclass _CachedMetadata {\n  final num? contentLength;\n  final String? contentType;\n  final String? lastModified;\n  final DateTime cacheTime;\n\n  _CachedMetadata({this.contentLength, this.contentType, this.lastModified})\n    : cacheTime = DateTime.now();\n\n  bool get isExpired =>\n      DateTime.now().difference(cacheTime) > S3Storage._metadataCacheDuration;\n}\n"
  },
  {
    "path": "lib/src/storage/storage.dart",
    "content": "import 'dart:typed_data';\n\nimport 'package:vania/src/storage/local_storage.dart';\nimport 'package:vania/src/storage/s3_storage.dart';\n\nimport 'storage_driver.dart';\n\nimport 'package:vania/src/utils/helper.dart' show env;\nimport 'package:path/path.dart' as path;\n\nclass Storage {\n  static final Storage _singleton = Storage._internal();\n  factory Storage() => _singleton;\n  Storage._internal();\n\n  final StorageDriver _driver = switch (env<String>('STORAGE', 'local')) {\n    'local' => LocalStorage(),\n    's3' => S3Storage(),\n    _ => LocalStorage(),\n  };\n\n  static Future<bool> delete(String file) async {\n    return await Storage()._driver.delete(file);\n  }\n\n  static Future<bool> exists(String file) async {\n    return await Storage()._driver.exists(file);\n  }\n\n  static Future<Uint8List?> getAsBytes(String file) async {\n    return await Storage()._driver.getAsBytes(file);\n  }\n\n  static Future<String?> get(String file) async {\n    return await Storage()._driver.get(file);\n  }\n\n  static Future<Map<String, dynamic>?> json(String file) async {\n    return await Storage()._driver.json(file);\n  }\n\n  static Future<String> put(\n    String directory,\n    String file,\n    dynamic content,\n  ) async {\n    if (content == null) {\n      throw Exception(\"Content can't be null\");\n    }\n\n    String fullPath = path.join(directory, file);\n\n    if (content is List<int>) {\n      return Storage()._driver.put(fullPath, content);\n    } else if (content is String) {\n      return Storage()._driver.put(fullPath, content);\n    } else if (content is Stream<List<int>>) {\n      final data = await content.fold<List<int>>([], (previous, element) {\n        previous.addAll(element);\n        return previous;\n      });\n      return Storage()._driver.put(fullPath, data);\n    } else {\n      throw Exception(\n        'Content must be a list of int, a string, or a Stream<List<int>>.',\n      );\n    }\n  }\n\n  static Future<String?> mimeType(String file) async {\n    return await Storage()._driver.mimeType(file);\n  }\n\n  static Future<num?> size(String file) async {\n    return Storage()._driver.size(file);\n  }\n}\n"
  },
  {
    "path": "lib/src/storage/storage_driver.dart",
    "content": "import 'dart:typed_data';\n\nabstract class StorageDriver {\n  Future<String> put(String filename, dynamic content);\n\n  Future<String?> get(String filename);\n  Future<Uint8List?> getAsBytes(String filename);\n  Future<Map<String, dynamic>?> json(String filename);\n  Future<String?> mimeType(String filename);\n  Future<num?> size(String filename);\n\n  String fullPath(String file);\n\n  Future<bool> exists(String filename);\n\n  Future<bool> delete(String filename);\n}\n"
  },
  {
    "path": "lib/src/utils/_pluralize.dart",
    "content": "class Pluralize {\n  static Pluralize? _singleton;\n\n  factory Pluralize() {\n    _singleton ??= Pluralize._internal();\n    return _singleton!;\n  }\n\n  Pluralize._internal();\n\n  final Map<String, String> irregulars = {\n    'child': 'children',\n    'goose': 'geese',\n    'man': 'men',\n    'woman': 'women',\n    'tooth': 'teeth',\n    'foot': 'feet',\n    'mouse': 'mice',\n    'person': 'people',\n    'ox': 'oxen',\n    'deer': 'deer',\n    'sheep': 'sheep',\n    'fish': 'fish',\n    'series': 'series',\n    'species': 'species',\n    'aircraft': 'aircraft',\n    'cactus': 'cacti',\n    'analysis': 'analyses',\n    'diagnosis': 'diagnoses',\n    'ellipsis': 'ellipses',\n    'phenomenon': 'phenomena',\n    'criterion': 'criteria',\n    'datum': 'data',\n    'index': 'indices',\n    'matrix': 'matrices',\n    'vertex': 'vertices',\n    'axis': 'axes',\n    'die': 'dice',\n    'bacterium': 'bacteria',\n    'memorandum': 'memoranda',\n    'curriculum': 'curricula',\n    'formula': 'formulae',\n    'addendum': 'addenda',\n    'medium': 'media',\n    'stratum': 'strata',\n    'focus': 'foci',\n    'alumnus': 'alumni',\n    'genus': 'genera',\n    'stimulus': 'stimuli',\n    'automaton': 'automata',\n    'thesis': 'theses',\n    'crisis': 'crises',\n    'appendix': 'appendices',\n    'barracks': 'barracks',\n    'headquarters': 'headquarters',\n  };\n\n  String make(String singular) {\n    if (singular.isEmpty) return singular;\n\n    if (singular.endsWith('ies') ||\n        singular.endsWith('ses') ||\n        singular.endsWith('xes') ||\n        singular.endsWith('zes') ||\n        singular.endsWith('ches') ||\n        singular.endsWith('shes') ||\n        singular.endsWith('oes')) {\n      return singular;\n    }\n\n    if (irregulars.containsKey(singular.toLowerCase())) {\n      String plural = irregulars[singular.toLowerCase()]!;\n\n      if (singular[0].toUpperCase() == singular[0]) {\n        return plural[0].toUpperCase() + plural.substring(1);\n      }\n      return plural;\n    }\n\n    if (singular.endsWith('y') &&\n        singular.length > 1 &&\n        !_isVowel(singular[singular.length - 2])) {\n      return '${singular.substring(0, singular.length - 1)}ies';\n    }\n\n    if (singular.endsWith('s') ||\n        singular.endsWith('x') ||\n        singular.endsWith('z') ||\n        singular.endsWith('ch') ||\n        singular.endsWith('sh') ||\n        (singular.endsWith('o') && !_isVowelBeforeO(singular))) {\n      return '${singular}es';\n    }\n\n    return '${singular}s';\n  }\n\n  static bool _isVowel(String char) {\n    return 'aeiou'.contains(char.toLowerCase());\n  }\n\n  static bool _isVowelBeforeO(String word) {\n    if (word.length < 2 || !word.endsWith('o')) return false;\n    return _isVowel(word[word.length - 2]);\n  }\n\n  String pluralizeVariableName(String variableName) {\n    if (variableName.isEmpty) return variableName;\n\n    if (variableName.contains('_')) {\n      List<String> parts = variableName.split('_');\n      parts[parts.length - 1] = make(parts.last);\n      return parts.join('_');\n    }\n\n    RegExp camelCaseRegex = RegExp(r'[A-Z][a-z]*$');\n    Match? match = camelCaseRegex.firstMatch(variableName);\n\n    if (match != null && match.start > 0) {\n      String lastWord = match.group(0)!;\n      String pluralLastWord = make(lastWord);\n      return variableName.substring(0, match.start) + pluralLastWord;\n    } else {\n      return make(variableName);\n    }\n  }\n}\n"
  },
  {
    "path": "lib/src/utils/functions.dart",
    "content": "import 'dart:math';\n\nString toSnakeCase(String input) {\n  if (input.isEmpty) return input;\n  final result = input.replaceAllMapped(\n    RegExp(r'[A-Z]'),\n    (match) => (match.start > 0 ? '_' : '') + match.group(0)!.toLowerCase(),\n  );\n  return result.toLowerCase();\n}\n\n/// Sanitizes a route path by replacing multiple slashes with a single slash and\n/// removing leading and trailing slashes.\nString sanitizeRoutePath(String path) {\n  path = path.replaceAll(RegExp(r'/+'), '/');\n  return path.replaceAll(RegExp('^\\\\/+|\\\\/+\\$'), '');\n}\n\n/// Generates a random string of a given [length] with the given character set.\n///\n/// The default length is 32. The default character set is all letters of the\n/// alphabet, both lowercase and uppercase. If [numbers] or [special] is true,\n/// the character set is extended to include numbers or special characters,\n/// respectively. The generated string is a random permutation of the characters\n/// in the character set.\nString randomString({\n  int length = 32,\n  bool numbers = false,\n  bool special = false,\n}) {\n  List<String> strList = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'\n      .split('');\n\n  if (numbers) {\n    strList.addAll('1234567890'.split(''));\n  }\n\n  if (special) {\n    strList.addAll('!@#%^&*()_'.split(''));\n  }\n\n  strList.shuffle();\n  String chars = strList.join('');\n  Random rnd = Random();\n  return String.fromCharCodes(\n    Iterable.generate(\n      length,\n      (_) => chars.codeUnitAt(rnd.nextInt(chars.length)),\n    ),\n  );\n}\n\n/// Generate a random number as a string of a given length\n///\n/// If T is int, it will be parsed as an integer and returned as a int\n/// otherwise, it will be returned as a string\n///\n/// The generated numbers are all positive\n///\n/// The default length is 6\n///\n/// [length] is the length of the generated number\n///\n/// Returns a random number as a string of [length] length\n///\n/// Example:\n///\n///     var rand = randomInt(); // '123456'\n///     var rand = randomInt(3); // '246'\n///     var rand = randomInt<int>(3); // 246\nT randomInt<T>([int length = 6]) {\n  List<String> strList = '1234567890'.split('');\n  strList.shuffle();\n  String chars = strList.join('');\n  Random rnd = Random();\n  String random = String.fromCharCodes(\n    Iterable.generate(\n      length,\n      (_) => chars.codeUnitAt(rnd.nextInt(chars.length)),\n    ),\n  );\n\n  if (T is int) {\n    return int.parse(random) as T;\n  }\n  return random as T;\n}\n"
  },
  {
    "path": "lib/src/utils/helper.dart",
    "content": "import 'package:vania/src/authentication/gate/gate.dart';\nimport 'package:vania/src/env_handler/env.dart';\nimport 'package:vania/src/exception/http_exception.dart';\n\nimport 'package:vania/src/ioc_container.dart';\nimport 'package:vania/src/localization_handler/localization.dart';\nimport 'package:vania/src/view_engine/template_engine.dart';\n\nimport '../http/session/session_manager.dart';\nimport '../http/validation/field_validation.dart';\n\nString storagePath(String file) => 'storage/$file';\n\nString publicPath(String file) => 'public/$file';\n\nString url(String path) => '${env<String>('APP_URL')}/$path';\n\nString assets(String src) => url(src);\n\nFieldValidation field(String fieldName) => FieldValidation(fieldName);\n\nbool can(String ability) => Gate().allows(ability);\n\nbool cannot(String ability) => Gate().denies(ability);\n\nT env<T>(String key, [dynamic defaultValue]) => Env.get<T>(key, defaultValue);\n\nString trans(String key, {Map<String, dynamic>? args, String? locale}) =>\n    Localization().trans(key, args, locale);\nvoid setLocale(String locale) => Localization().setLocale(locale);\nbool isLocale(String locale) => Localization().isLocale(locale);\n\nvoid abort(int code, String message) {\n  throw HttpResponseException(message: message, code: code);\n}\n\nFuture<void> setSession(String key, dynamic value) async =>\n    await IoCContainer().resolve<SessionManager>().setSession(key, value);\nFuture<T> getSession<T>(String key) async =>\n    TemplateEngine().sessions[key] ??\n    await IoCContainer().resolve<SessionManager>().getSession<T>(key);\nFuture<Map<String, dynamic>?> allSessions() async =>\n    IoCContainer().resolve<SessionManager>().allSessions;\nFuture<void> deleteSession(String key) async =>\n    await IoCContainer().resolve<SessionManager>().deleteSession(key);\nFuture<void> destroyAllSessions() async =>\n    await IoCContainer().resolve<SessionManager>().destroyAllSessions();\n"
  },
  {
    "path": "lib/src/utils/request_helper.dart",
    "content": "import 'dart:io';\n\nimport '../http/request/request_body.dart' show RequestBody;\nimport '../http/request/request_handler.dart' show globalHttpRequest;\n\nT? getParam<T>(String key, [dynamic defualtValue]) {\n  dynamic param = globalHttpRequest!.uri.queryParameters[key];\n\n  if (param == null && defualtValue == null) {\n    return null;\n  }\n\n  param ??= defualtValue;\n\n  if (T.toString() == 'int') {\n    return int.tryParse(param.toString()) as T;\n  }\n\n  if (T.toString() == 'bool') {\n    return bool.tryParse(param.toString()) as T;\n  }\n\n  if (T.toString() == 'num') {\n    return num.tryParse(param.toString()) as T;\n  }\n\n  if (T.toString() == 'double') {\n    return double.tryParse(param.toString()) as T;\n  }\n\n  return param as T;\n}\n\nUri get requestUri => globalHttpRequest!.uri;\nString? get clienIp => globalHttpRequest?.connectionInfo?.remoteAddress.address;\nHttpHeaders? get requestHeaders => globalHttpRequest?.headers;\nContentType? get requestContentType => globalHttpRequest?.headers.contentType;\nString? get method => globalHttpRequest?.method.toUpperCase();\nHttpResponse httpResponse = globalHttpRequest!.response;\nFuture requestBody() async {\n  final whereMethod = ['post', 'patch', 'put', 'delete']\n      .where((method) => method == globalHttpRequest?.method.toLowerCase())\n      .toList();\n  if (whereMethod.isNotEmpty) {\n    return await RequestBody.extractBody(request: globalHttpRequest!);\n  }\n  return {};\n}\n"
  },
  {
    "path": "lib/src/view_engine/helper.dart",
    "content": "import 'package:vania/src/http/response/response.dart';\nimport 'template_engine.dart';\n\nResponse view(String view, [Map<String, dynamic>? context]) =>\n    Response.html(TemplateEngine().render(view, context));\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/abs_processor.dart",
    "content": "/// An interface (abstract class) for replacing placeholders in a template.\nabstract class AbsProcessor {\n  /// Replaces the placeholders in [content] with data from [context].\n  String parse(String content, [Map<String, dynamic>? context]);\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/assets_processor.dart",
    "content": "import 'package:vania/src/utils/helper.dart';\n\nimport 'abs_processor.dart';\n\nclass AssetsProcessor implements AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final assetsPattern = RegExp(\n      r\"(\\/?)\\{@\\s*assets\\(\\s*'([^']*)'\\s*\\)\\s*@\\}\",\n      dotAll: true,\n    );\n\n    return content.replaceAllMapped(assetsPattern, (match) {\n      final assets = match.group(2);\n      return url(assets ?? '');\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/comment_processor.dart",
    "content": "import 'abs_processor.dart';\n\nclass CommentProcessor implements AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final commentPattern = RegExp(r\"\\{\\@\\#.*?\\#\\@\\}\", dotAll: true);\n    return content.replaceAllMapped(commentPattern, (_) {\n      return '';\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/csrf_processor.dart",
    "content": "import 'package:vania/src/http/session/session_manager.dart';\nimport 'package:vania/src/ioc_container.dart';\nimport 'package:vania/src/view_engine/processor_engine/abs_processor.dart';\n\nclass CsrfProcessor implements AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final csrfPattern = RegExp(r\"\\{@\\s*csrf\\s*@\\}\", dotAll: true);\n    return content.replaceAllMapped(csrfPattern, (match) {\n      String csrfToken = IoCContainer().resolve<SessionManager>().csrfToken;\n      return '<input type=\"hidden\" name=\"_csrf\" value=\"$csrfToken\">';\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/csrf_token_processor.dart",
    "content": "import 'package:vania/src/http/session/session_manager.dart';\nimport 'package:vania/src/ioc_container.dart';\nimport 'package:vania/src/view_engine/processor_engine/abs_processor.dart';\n\nclass CsrfTokenProcessor implements AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final csrfMethodPattern = RegExp(\n      r\"\\{@\\s*csrf_token\\(\\)\\s*@\\}\",\n      dotAll: true,\n    );\n\n    return content.replaceAllMapped(csrfMethodPattern, (match) {\n      return IoCContainer().resolve<SessionManager>().csrfToken;\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/error_processor.dart",
    "content": "import 'package:vania/src/view_engine/template_engine.dart';\n\nimport 'abs_processor.dart';\n\nclass ErrorProcessor extends AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final hasErrorPattern = RegExp(\n      r\"hasError\\(\\s*'([^']*)'\\s*\\)\",\n      dotAll: true,\n    );\n    content = content.replaceAllMapped(hasErrorPattern, (match) {\n      final errorKey = match.group(1);\n      return TemplateEngine().sessionErrors.containsKey(errorKey).toString();\n    });\n\n    final errorPattern = RegExp(\n      r\"\\{@\\s*error\\(\\s*'([^']*)'\\s*\\)\\s*@\\}\",\n      dotAll: true,\n    );\n\n    content = content.replaceAllMapped(errorPattern, (error) {\n      final errorKey = error.group(1);\n      return TemplateEngine().sessionErrors[errorKey] ?? '';\n    });\n\n    return content;\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/evaluate_expression.dart",
    "content": "bool evaluateExpression(String expression, Map<String, dynamic> context) {\n  expression = _stripOuterParens(expression.trim());\n\n  // Check for comparison: e.g. \"leftSide == rightSide\"\n  // We separate the string into leftSide, operator, rightSide\n  final comparisonPattern = RegExp(r'(.+?)\\s*(==|!=|>=|<=|>|<)\\s*(.+)');\n  final compMatch = comparisonPattern.firstMatch(expression);\n  if (compMatch != null) {\n    final leftRaw = compMatch.group(1)!.trim();\n    final op = compMatch.group(2)!.trim();\n    final rightRaw = compMatch.group(3)!.trim();\n\n    final leftVal = _evalArithmetic(leftRaw, context);\n    final rightVal = _evalArithmetic(rightRaw, context);\n\n    return _compare(leftVal, rightVal, op);\n  }\n\n  final singleVal = _evalArithmetic(expression, context);\n\n  if (singleVal is bool) return singleVal;\n  if (singleVal is num) return singleVal != 0;\n\n  if (singleVal is String && singleVal.toLowerCase() == 'true') return true;\n\n  final ctxVal = context[expression];\n\n  if (ctxVal is bool) return ctxVal;\n\n  return false;\n}\n\n/// Removes one set of outer parentheses if they exist, e.g. \"(10 % 2)\" => \"10 % 2\"\nString _stripOuterParens(String expr) {\n  final parenPattern = RegExp(r'^\\((.*)\\)$');\n  final match = parenPattern.firstMatch(expr);\n  if (match != null) {\n    return match.group(1)!.trim();\n  }\n  return expr;\n}\n\n/// Evaluates a single arithmetic expression, e.g. \"10 % 2\", \"i + 1\", \"5 * 3\", or just \"i\".\n/// Returns `num`, `bool`, or `String` depending on what we find.\ndynamic _evalArithmetic(String expr, Map<String, dynamic> ctx) {\n  expr = expr.trim();\n\n  final asInt = int.tryParse(expr);\n  if (asInt != null) return asInt;\n  final asDouble = double.tryParse(expr);\n  if (asDouble != null) return asDouble;\n\n  final arithmeticPattern = RegExp(r'(.+?)\\s*([\\+\\-\\*\\/%])\\s*(.+)');\n  final match = arithmeticPattern.firstMatch(expr);\n  if (match != null) {\n    final leftRaw = match.group(1)!.trim();\n    final op = match.group(2)!.trim();\n    final rightRaw = match.group(3)!.trim();\n\n    final leftVal = _evalArithmetic(leftRaw, ctx);\n    final rightVal = _evalArithmetic(rightRaw, ctx);\n\n    return _doArithmetic(leftVal, rightVal, op);\n  }\n\n  final valFromContext = ctx[expr];\n  if (valFromContext != null) return valFromContext;\n\n  if (expr.toLowerCase() == 'true') return true;\n  if (expr.toLowerCase() == 'false') return false;\n\n  return expr;\n}\n\n/// Perform arithmetic on leftVal and rightVal with the single operator (+, -, *, /, %).\ndynamic _doArithmetic(dynamic left, dynamic right, String op) {\n  if (left is num && right is num) {\n    switch (op) {\n      case '+':\n        return left + right;\n      case '-':\n        return left - right;\n      case '*':\n        return left * right;\n      case '/':\n        return (right == 0) ? null : left / right;\n      case '%':\n        return (right == 0) ? null : left % right;\n    }\n  }\n  if (op == '+') {\n    return '${left?.toString() ?? ''}${right?.toString() ?? ''}';\n  }\n  return null;\n}\n\nString removeOuterQuotes(String input) {\n  if ((input.startsWith(\"'\") && input.endsWith(\"'\")) ||\n      (input.startsWith('\"') && input.endsWith('\"'))) {\n    return input.substring(1, input.length - 1);\n  }\n  return input;\n}\n\n/// Compare leftVal and rightVal with the given operator\nbool _compare(dynamic leftVal, dynamic rightVal, String op) {\n  if (leftVal is num && rightVal is num) {\n    switch (op) {\n      case '==':\n        return leftVal == rightVal;\n      case '!=':\n        return leftVal != rightVal;\n      case '>':\n        return leftVal > rightVal;\n      case '>=':\n        return leftVal >= rightVal;\n      case '<':\n        return leftVal < rightVal;\n      case '<=':\n        return leftVal <= rightVal;\n    }\n  }\n  if (op == '==') {\n    return leftVal?.toString() == removeOuterQuotes(rightVal?.toString() ?? \"\");\n  }\n  if (op == '!=') {\n    return leftVal?.toString() != removeOuterQuotes(rightVal?.toString() ?? \"\");\n  }\n  if (op == '>' || op == '>=' || op == '<' || op == '<=') {\n    final lstr = leftVal?.toString() ?? '';\n    final rstr = rightVal?.toString() ?? '';\n    final cmp = lstr.compareTo(rstr);\n    switch (op) {\n      case '>':\n        return cmp > 0;\n      case '>=':\n        return cmp >= 0;\n      case '<':\n        return cmp < 0;\n      case '<=':\n        return cmp <= 0;\n    }\n  }\n  return false;\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/extends_processor.dart",
    "content": "import 'package:vania/src/view_engine/processor_engine/abs_processor.dart';\nimport 'package:vania/src/view_engine/template_reader.dart';\n\nclass ExtendsProcessor implements AbsProcessor {\n  /// Parse `{@ extends('template_path') @}` blocks in [content] and replace them with the content of the parent template.\n  ///\n  /// The parent template path is read from the file system using [FileTemplateReader].\n  ///\n  /// The following example:\n  ///\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final extendsPattern = RegExp(r\"\\{@\\s*extends\\(\\s*'([^']+)'\\s*\\)\\s*@\\}\");\n    final match = extendsPattern.firstMatch(content);\n    if (match == null) {\n      return content;\n    }\n    final parentLayoutPath = match.group(1);\n    if (parentLayoutPath == null) {\n      return content;\n    }\n    content = content.replaceFirst(extendsPattern, '');\n    final parentTemplate = FileTemplateReader().read(parentLayoutPath);\n    return parentTemplate;\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/for_loop_processor.dart",
    "content": "import '../template_engine.dart';\nimport 'abs_processor.dart';\n\n/// processor to handles \"for\" loops, including **nested** loops.\nclass ForLoopProcessor extends AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    context ??= {};\n\n    return _parseForLoops(content, context);\n  }\n\n  /// Parse and replace `{@ for ... @}` blocks in [template] with values from [context].\n  ///\n  /// This implementation supports nested loops.\n  ///\n  /// The replacement is done by expanding the loop content for each iteration of the loop.\n  /// The loop variable is iterated over the iterable returned by the expression.\n  /// Each iteration is replaced with the value of the loop variable.\n  ///\n  /// The following example:\n  ///```html\n  ///   {@ for user in users @}\n  ///     {@ user.name @}\n  ///   {@ endfor @}\n  ///\n  ///  {@ for i=0; i<3; i++ @}\n  ///    Index ->: @{i} - Name: @{users[i].name}\n  ///  {@ endfor @}\n  ///```\n  /// Is replaced with the contents of the loop block, with `user` replaced with each value of the iterable returned by the expression.\n  ///\n  String _parseForLoops(String template, Map<String, dynamic> context) {\n    final buffer = StringBuffer();\n    int index = 0;\n\n    while (true) {\n      final startPos = template.indexOf('{@ for', index);\n      if (startPos == -1) {\n        buffer.write(template.substring(index));\n        break;\n      }\n\n      buffer.write(template.substring(index, startPos));\n\n      final forStartClose = template.indexOf('@}', startPos);\n      if (forStartClose == -1) {\n        buffer.write(template.substring(startPos));\n        break;\n      }\n\n      final forExpression = template\n          .substring(startPos + 6, forStartClose)\n          .trim();\n\n      int loopContentStart = forStartClose + 2;\n      int searchPos = loopContentStart;\n      int nested = 0;\n      int endforPos = -1;\n\n      while (true) {\n        final nextFor = template.indexOf('{@ for', searchPos);\n        final nextEndfor = template.indexOf('{@ endfor @}', searchPos);\n\n        if (nextEndfor == -1) {\n          break;\n        }\n\n        if (nextFor != -1 && nextFor < nextEndfor) {\n          nested++;\n          searchPos = nextFor + 1;\n        } else {\n          if (nested > 0) {\n            nested--;\n            searchPos = nextEndfor + 1;\n          } else {\n            endforPos = nextEndfor;\n            break;\n          }\n        }\n      }\n\n      if (endforPos == -1) {\n        buffer.write(template.substring(loopContentStart));\n        break;\n      }\n      final loopBlock = template.substring(loopContentStart, endforPos);\n\n      final expanded = _expandLoop(forExpression, loopBlock, context);\n\n      buffer.write(expanded);\n\n      final endforClose = endforPos + '{@ endfor @}'.length;\n      index = endforClose;\n    }\n\n    return buffer.toString();\n  }\n\n  /// Expands a loop expression into the rendered output based on the specified loop style.\n  ///\n  /// Handles two types of loop expressions:\n  /// 1. C-style loops (e.g., `i=0; i<10; i++`), where it extracts the loop\n  ///    control variables, start condition, end condition, and increment expression.\n  /// 2. \"Item in list\" style loops (e.g., `item in items`), where it iterates\n  ///    over each element in the list and processes the loop block.\n  ///\n  /// If the expression matches a C-style loop, it delegates execution to\n  /// `_runCStyleLoop`. If it matches an \"item in list\" loop, it delegates to\n  /// `_runItemInListLoop`. Returns the expanded loop content as a string.\n\n  String _expandLoop(\n    String forExpression,\n    String loopBlock,\n    Map<String, dynamic> context,\n  ) {\n    final cStylePattern = RegExp(\n      r'^(\\w+)\\s*=\\s*(.+?);\\s*\\1\\s*([<>]=?|[<>])\\s*(.+?);\\s*(.+)$',\n    );\n    final cMatch = cStylePattern.firstMatch(forExpression);\n    if (cMatch != null) {\n      final varName = cMatch.group(1)!;\n      final startExpr = cMatch.group(2)!;\n      final operator = cMatch.group(3)!;\n      final endExpr = cMatch.group(4)!;\n      final incExpr = cMatch.group(5)!;\n\n      return _runCStyleLoop(\n        loopBlock: loopBlock,\n        varName: varName,\n        startExpr: startExpr,\n        operator: operator,\n        endExpr: endExpr,\n        incExpr: incExpr,\n        context: context,\n      );\n    }\n\n    final itemInListPattern = RegExp(r'^(\\w+)\\s+in\\s+(\\w+)$');\n    final inMatch = itemInListPattern.firstMatch(forExpression);\n    if (inMatch != null) {\n      final itemName = inMatch.group(1)!;\n      final listName = inMatch.group(2)!;\n      return _runItemInListLoop(loopBlock, itemName, listName, context);\n    }\n\n    return '';\n  }\n\n  /// Runs a loop block for each item in a list.\n  ///\n  /// The loop block is rendered for each item in the list, with the item\n  /// assigned to a variable with the name given by [itemName]. The item's\n  /// index in the list is also available as a variable named `'index'`.\n  ///\n  /// The loop block is rendered by calling [TemplateEngine().renderString] with\n  /// the loop block as the template and a new context that includes the current\n  /// item and index, as well as all of the variables from the original context.\n  ///\n  /// The output of the loop block is concatenated together and returned as a\n  /// single string. If the specified list is not a list, an empty string is\n  /// returned.\n  String _runItemInListLoop(\n    String loopBlock,\n    String itemName,\n    String listName,\n    Map<String, dynamic> context,\n  ) {\n    final listObj = context[listName];\n    if (listObj is! List) return '';\n\n    final buffer = StringBuffer();\n    for (var i = 0; i < listObj.length; i++) {\n      final item = listObj[i];\n      final subCtx = {...context, itemName: item, 'index': i};\n\n      buffer.write(TemplateEngine().renderString(loopBlock, subCtx));\n    }\n    return buffer.toString();\n  }\n\n  /// Runs a C-style for loop block for each iteration of the loop.\n  ///\n  /// The loop block is rendered for each iteration of the loop, with the current\n  /// value of the loop variable assigned to a variable with the name given by\n  /// [varName]. The loop block is rendered by calling\n  /// [TemplateEngine().renderString] with the loop block as the template and a\n  /// new context that includes the current value of the loop variable, as well\n  /// as all of the variables from the original context.\n  ///\n  /// The output of the loop block is concatenated together and returned as a\n  /// single string. If the specified loop variables are not valid, an empty\n  /// string is returned.\n  ///\n  /// The loop iterates until the condition specified by [operator] is false.\n  /// The condition is evaluated by calling [TemplateEngine().renderString] with\n  /// the condition expression as the template and the current context.\n  ///\n  String _runCStyleLoop({\n    required String loopBlock,\n    required String varName,\n    required String startExpr,\n    required String operator,\n    required String endExpr,\n    required String incExpr,\n    required Map<String, dynamic> context,\n  }) {\n    int current = _evalToInt(startExpr, context) ?? 0;\n\n    bool checkCondition(int curVal) {\n      final endVal = _evalToInt(endExpr, context) ?? 0;\n      switch (operator) {\n        case '<':\n          return curVal < endVal;\n        case '<=':\n          return curVal <= endVal;\n        case '>':\n          return curVal > endVal;\n        case '>=':\n          return curVal >= endVal;\n      }\n      return false;\n    }\n\n    /// Increments the current value based on the increment expression.\n    ///\n    /// This function processes the increment expression [incExpr] to determine\n    /// how to modify the [curVal]. It supports the following increment patterns:\n    /// - `varName++` or `varName--`: increments or decrements the value by 1.\n    /// - `varName += n` or `varName -= n`: adds or subtracts the specified amount.\n    /// - `varName = varName + n` or `varName = varName - n`: adds or subtracts the specified amount.\n    ///\n    /// If no pattern is matched, the function defaults to incrementing the value by 1.\n    ///\n    /// Returns the new value after applying the increment.\n\n    int increment(int curVal) {\n      final trimmed = incExpr.trim();\n\n      if (trimmed == '$varName++') {\n        return curVal + 1;\n      } else if (trimmed == '$varName--') {\n        return curVal - 1;\n      }\n\n      final addSubPattern = RegExp(r'^' + varName + r'\\s*([\\+\\-]=)\\s*(\\d+)$');\n      final addSubMatch = addSubPattern.firstMatch(trimmed);\n      if (addSubMatch != null) {\n        final op = addSubMatch.group(1)!;\n        final amt = int.parse(addSubMatch.group(2)!);\n        return (op == '+=') ? curVal + amt : curVal - amt;\n      }\n\n      final assignPattern = RegExp(\n        r'^' + varName + r'\\s*=\\s*' + varName + r'\\s*([\\+\\-])\\s*(\\d+)$',\n      );\n      final assignMatch = assignPattern.firstMatch(trimmed);\n      if (assignMatch != null) {\n        final sign = assignMatch.group(1)!;\n        final amt = int.parse(assignMatch.group(2)!);\n        return (sign == '+') ? curVal + amt : curVal - amt;\n      }\n\n      return curVal + 1;\n    }\n\n    final buffer = StringBuffer();\n    while (checkCondition(current)) {\n      final subCtx = {...context, varName: current};\n\n      buffer.write(TemplateEngine().renderString(loopBlock, subCtx));\n      current = increment(current);\n    }\n    return buffer.toString();\n  }\n\n  /// Evaluates a string expression and attempts to convert it to an integer.\n  ///\n  /// This function processes the provided [expr] in the following ways:\n  /// - Tries to parse [expr] directly as an integer.\n  /// - Checks if the expression matches the pattern `<variable>.length`, and if\n  ///   the variable in the [context] is a list, returns its length.\n  /// - If [expr] is a key in the [context] and its value is an integer, returns\n  ///   that integer.\n  ///\n  /// If none of the above conditions are met, the function returns `null`.\n  ///\n  /// **Parameters:**\n  /// - [expr]: A string representing the expression to evaluate.\n  /// - [context]: A map containing variable names and their corresponding values.\n  ///\n  /// **Returns:**\n  /// An integer if the expression can be evaluated to an integer; otherwise, `null`.\n\n  int? _evalToInt(String expr, Map<String, dynamic> context) {\n    expr = expr.trim();\n\n    final maybeInt = int.tryParse(expr);\n    if (maybeInt != null) return maybeInt;\n\n    final dotPattern = RegExp(r'^(\\w+)\\.length$');\n    final dotMatch = dotPattern.firstMatch(expr);\n    if (dotMatch != null) {\n      final varName = dotMatch.group(1)!;\n      final obj = context[varName];\n      if (obj is List) return obj.length;\n      return null;\n    }\n\n    if (context.containsKey(expr) && context[expr] is int) {\n      return context[expr] as int;\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/if_statement_processor.dart",
    "content": "import 'abs_processor.dart';\nimport 'evaluate_expression.dart';\nimport '../template_engine.dart';\n\n/// A processor that handles  `{@ if ... @}` if statements with\n/// optional `{@ elseif ... @}` and `{@ else @}` sections.\n///\n/// Example:\n/// ```html\n///   {@ if is_admin @}\n///     You are admin\n///   {@ else @}\n///     Not admin\n///   {@ endif @}\n/// ```\n/// nested if statements\n/// ```html\n///   {@ if is_admin @}\n///     {@ if name == 'Vania' @}Hello @{name}!{@ else @}Hello other admin{@ endif @}\n///   {@ else @}\n///     Not admin\n///   {@ endif @}\n/// ```\n///\nclass IfStatementProcessor implements AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    context ??= {};\n    return _parseIfBlocks(content, context);\n  }\n\n  /// Parse and replace `{@ if ... @}` blocks in [template] with values from [context].\n  ///\n  /// This implementation supports nested if statements.\n  ///\n  /// The replacement is done by expanding the block content if the condition is true.\n  /// If the condition is false, the block content is not expanded.\n  ///\n  /// The following example:\n  /// ```html\n  ///   {@ if is_admin @}\n  ///     You are admin\n  ///   {@ else @}\n  ///     Not admin\n  ///   {@ endif @}\n  /// ```\n  /// Is replaced with \"You are admin\" if `is_admin` is true in the context,\n  /// and \"Not admin\" if `is_admin` is false.\n  ///\n  String _parseIfBlocks(String template, Map<String, dynamic> context) {\n    final buffer = StringBuffer();\n    int index = 0;\n\n    while (true) {\n      final startPos = template.indexOf('{@ if', index);\n      if (startPos == -1) {\n        buffer.write(template.substring(index));\n        break;\n      }\n      buffer.write(template.substring(index, startPos));\n      final ifStartClose = template.indexOf('@}', startPos);\n      if (ifStartClose == -1) {\n        buffer.write(template.substring(startPos));\n        break;\n      }\n      final ifConditionExpr = template\n          .substring(startPos + 5, ifStartClose)\n          .trim();\n      int blockStart = ifStartClose + 2;\n      int searchPos = blockStart;\n      int nested = 0;\n      int endifPos = -1;\n\n      while (true) {\n        final nextIf = template.indexOf('{@ if', searchPos);\n        final nextEndif = template.indexOf('{@ endif @}', searchPos);\n        if (nextEndif == -1) {\n          break;\n        }\n        if (nextIf != -1 && nextIf < nextEndif) {\n          nested++;\n          searchPos = nextIf + 1;\n        } else {\n          if (nested > 0) {\n            nested--;\n            searchPos = nextEndif + 1;\n          } else {\n            endifPos = nextEndif;\n            break;\n          }\n        }\n      }\n\n      if (endifPos == -1) {\n        buffer.write(template.substring(blockStart));\n        break;\n      }\n\n      final ifBlockContent = template.substring(blockStart, endifPos);\n\n      final expanded = _expandIfBlock(ifConditionExpr, ifBlockContent, context);\n\n      buffer.write(expanded);\n\n      final endifClose = endifPos + '{@ endif @}'.length;\n      index = endifClose;\n    }\n\n    return buffer.toString();\n  }\n\n  /// Expands an if-else block by evaluating conditions and returning the appropriate content.\n  ///\n  /// This function processes a block of content containing `if`, `elseif`, and `else` conditions,\n  /// using the provided context to evaluate each condition. It returns the rendered string\n  /// for the first true condition or the `else` block if no conditions are true.\n  ///\n  /// - Parameters:\n  ///   - ifConditionExpr: The initial 'if' condition expression as a string.\n  ///   - ifBlockContent: The content of the block to be evaluated.\n  ///   - context: A map containing the context variables used for evaluating conditions.\n  ///\n  /// - Returns: A string with the expanded content for the first true condition or the `else` block.\n\n  String _expandIfBlock(\n    String ifConditionExpr,\n    String ifBlockContent,\n    Map<String, dynamic> context,\n  ) {\n    var cursor = 0;\n    var currentCondition = ifConditionExpr;\n    final segments = <_ConditionalSegment>[];\n\n    final elseIfRegex = RegExp(r'\\{@\\s*elseif\\s+(.*?)\\s*@\\}');\n    final elseRegex = RegExp(r'\\{@\\s*else\\s*@\\}');\n\n    while (true) {\n      final matchElseIf = elseIfRegex.firstMatch(\n        ifBlockContent.substring(cursor),\n      );\n      final matchElse = elseRegex.firstMatch(ifBlockContent.substring(cursor));\n\n      final elseIfPos = (matchElseIf == null) ? -1 : cursor + matchElseIf.start;\n      final elsePos = (matchElse == null) ? -1 : cursor + matchElse.start;\n\n      int nextPos = -1;\n      bool isElseIf = false;\n\n      if (elseIfPos == -1 && elsePos == -1) {\n      } else if (elseIfPos == -1) {\n        nextPos = elsePos;\n      } else if (elsePos == -1) {\n        nextPos = elseIfPos;\n        isElseIf = true;\n      } else {\n        if (elseIfPos < elsePos) {\n          nextPos = elseIfPos;\n          isElseIf = true;\n        } else {\n          nextPos = elsePos;\n        }\n      }\n\n      if (nextPos == -1) {\n        final block = ifBlockContent.substring(cursor);\n        segments.add(\n          _ConditionalSegment(\n            condition: currentCondition,\n            content: block,\n            isConditionSegment: true,\n          ),\n        );\n        break;\n      } else {\n        final block = ifBlockContent.substring(cursor, nextPos);\n        segments.add(\n          _ConditionalSegment(\n            condition: currentCondition,\n            content: block,\n            isConditionSegment: true,\n          ),\n        );\n\n        if (isElseIf) {\n          final elseIfMatch = elseIfRegex.firstMatch(\n            ifBlockContent.substring(nextPos),\n          );\n          if (elseIfMatch == null) break;\n          currentCondition = elseIfMatch.group(1)!.trim();\n          cursor = nextPos + elseIfMatch.end;\n        } else {\n          final elseMatch = elseRegex.firstMatch(\n            ifBlockContent.substring(nextPos),\n          );\n          if (elseMatch == null) break;\n          final elseStart = nextPos + elseMatch.end;\n          final elseContent = ifBlockContent.substring(elseStart);\n\n          segments.add(\n            _ConditionalSegment(\n              condition: '',\n              content: elseContent,\n              isConditionSegment: false,\n            ),\n          );\n          break;\n        }\n      }\n    }\n\n    for (final seg in segments) {\n      if (seg.isConditionSegment) {\n        if (evaluateExpression(seg.condition, context)) {\n          return TemplateEngine().renderString(seg.content, context);\n        }\n      } else {\n        return TemplateEngine().renderString(seg.content, context);\n      }\n    }\n\n    return '';\n  }\n}\n\nclass _ConditionalSegment {\n  final String condition;\n  final String content;\n  final bool isConditionSegment;\n  _ConditionalSegment({\n    required this.condition,\n    required this.content,\n    required this.isConditionSegment,\n  });\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/include_processor.dart",
    "content": "import 'dart:convert';\n\nimport 'package:vania/src/view_engine/processor_engine/abs_processor.dart';\nimport 'package:vania/src/view_engine/template_engine.dart';\nimport 'package:vania/src/view_engine/template_reader.dart';\n\nclass IncludeProcessor implements AbsProcessor {\n  /// Replaces `{@ include <file name> [, <json data>] @}` blocks in [content] with the\n  /// rendered content of the included file.\n  ///\n  /// The included file is read from the file system using the [FileTemplateReader]\n  /// and rendered using [TemplateEngine] with a context that includes the\n  /// variables from [context] as well as any additional data passed in the\n  /// include tag.\n  ///\n  /// The additional data is expected to be a JSON object and is merged with\n  /// the context from [context].\n  ///\n  /// The included file's content is then replaced in the original content\n  /// at the location of the include tag.\n  ///\n  /// The following example:\n  ///\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final includePattern = RegExp(\n      r\"\\{@\\s*include\\(\\s*'([^']+)'\\s*(,\\s*(\\{.*?\\}))?\\)\\s*@\\}\",\n      dotAll: true,\n    );\n\n    return content.replaceAllMapped(includePattern, (match) {\n      final filePath = match.group(1) ?? '';\n\n      final rawData = match.group(3) ?? '';\n\n      final childContext = _parseIncludeData(rawData);\n      final mergedContext = {...context ?? {}, ...childContext};\n\n      final includedTemplate = FileTemplateReader().read(filePath);\n      return TemplateEngine().renderString(includedTemplate, mergedContext);\n    });\n  }\n\n  Map<String, dynamic> _parseIncludeData(String dataString) {\n    dataString = dataString.trim();\n    if (dataString.isEmpty) return {};\n\n    try {\n      final decoded = jsonDecode(dataString);\n      if (decoded is Map<String, dynamic>) {\n        return decoded;\n      }\n    } catch (_) {}\n\n    return {};\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/old_processor.dart",
    "content": "import 'package:vania/src/view_engine/template_engine.dart';\n\nimport 'abs_processor.dart';\n\nclass OldProcessor extends AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final oldPattern = RegExp(\n      r\"\\{@\\s*old\\(\\s*'([^']*)'\\s*\\)\\s*@\\}\",\n      dotAll: true,\n    );\n\n    content = content.replaceAllMapped(oldPattern, (oldMatch) {\n      final oldKey = oldMatch.group(1);\n      return TemplateEngine().formData[oldKey] ?? '';\n    });\n\n    return content;\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/route_processor.dart",
    "content": "import 'dart:convert';\n\nimport 'package:vania/src/exception/internal_server_error.dart';\nimport 'package:vania/src/route/router.dart';\n\nimport 'abs_processor.dart';\n\nclass RouteProcessor implements AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final routePattern = RegExp(\n      r\"(\\/?)\\{\\@\\s*route\\(\\s*\\'([^']+)\\'(?:,\\s*({[^}]+}))?\\s*\\)\\s*\\@\\}\",\n      dotAll: true,\n    );\n\n    return content.replaceAllMapped(routePattern, (match) {\n      String? slash = match.group(1);\n      final routeName = match.group(2) ?? '';\n      final jsonParams = match.group(3) ?? '';\n\n      if (slash == null || slash.isEmpty) {\n        slash = '/';\n      }\n\n      List filteredRoute = Router().routes\n          .where((e) => e.name?.toLowerCase() == routeName.toLowerCase())\n          .toList();\n      if (filteredRoute.isEmpty) {\n        throw InternalServerError(\n          message: 'Route $routeName not found',\n          code: 500,\n        );\n      }\n\n      String path = filteredRoute.first.path;\n      Map<String, dynamic> params = {};\n      if (jsonParams.isNotEmpty) {\n        try {\n          params = jsonDecode(jsonParams);\n        } catch (e) {\n          params = {};\n        }\n      }\n\n      if (params.isNotEmpty) {\n        path = injectParams(path, params);\n      }\n\n      return \"$slash$path\";\n    });\n  }\n\n  String injectParams(String template, Map<String, dynamic> params) {\n    final placeholderPattern = RegExp(r'\\{(\\w+)\\}');\n    return template.replaceAllMapped(placeholderPattern, (match) {\n      final key = match.group(1);\n      final value = params[key];\n      return value?.toString() ?? '';\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/section_processor.dart",
    "content": "import 'package:vania/src/view_engine/processor_engine/abs_processor.dart';\n\nclass SectionProcessor implements AbsProcessor {\n  /// Replace placeholders in a template string with values from a context.\n  ///\n  /// Replaces:\n  ///  - `{@ yield('section_name') @}` with the content of the section in [context]\n  ///    with the given name. If the section does not exist, an empty string is\n  ///    used.\n  ///  - `{@ section('section_name') @}...{@ show @}` with the content of the\n  ///    section in [context] with the given name. If the section does not exist,\n  ///    the content inside the section is used.\n  ///\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final yieldPattern = RegExp(\n      r\"\\{@\\s*yield\\(\\s*'([^']+)'\\s*\\)\\s*@\\}\",\n      dotAll: true,\n    );\n    content = content.replaceAllMapped(yieldPattern, (match) {\n      final sectionName = match.group(1) ?? '';\n      return context?[sectionName] ?? '';\n    });\n\n    final parentSectionPattern = RegExp(\n      r\"\\{@\\s*section\\(\\s*'([^']+)'\\s*\\)\\s*@\\}(.*?)\\{@\\s*show\\s*\\}\",\n      dotAll: true,\n    );\n\n    content = content.replaceAllMapped(parentSectionPattern, (match) {\n      final parentSectionName = match.group(1) ?? '';\n      final parentContent = match.group(2) ?? '';\n      final childContent = context?[parentSectionName];\n\n      return childContent ?? parentContent;\n    });\n    return content;\n  }\n\n  /// Parse child sections from a template string.\n  ///\n  /// Given a template string, parse out all sections (both inline and block) and\n  /// return them as a map with the section name as the key and the section content\n  /// as the value.\n  ///\n  /// The following example:\n  ///\n  /// ```html\n  ///   {@ section section('content') @}\n  ///     <h1>content</h1>\n  ///   {@ endsection @}\n  /// ```\n  ///\n  Map<String, String> parseChildSections(String childTemplate) {\n    final sections = <String, String>{};\n\n    final blockSectionPattern = RegExp(\n      r\"\\{@\\s*section\\(\\s*'([^']+)'\\s*\\)\\s*@\\}(.*?)\\{@\\s*endsection\\s*@\\}\",\n      dotAll: true,\n    );\n\n    childTemplate = childTemplate.replaceAllMapped(blockSectionPattern, (\n      match,\n    ) {\n      final sectionName = match.group(1) ?? '';\n      final content = match.group(2) ?? '';\n      sections[sectionName] = content;\n      return '';\n    });\n\n    final inlineSectionPattern = RegExp(\n      r\"\\{@\\s*section\\(\\s*'([^']+)'\\s*,\\s*'(.*?)'\\s*\\)\\s*@\\}\",\n      dotAll: true,\n    );\n\n    childTemplate = childTemplate.replaceAllMapped(inlineSectionPattern, (\n      match,\n    ) {\n      final sectionName = match.group(1) ?? '';\n      final inlineContent = match.group(2) ?? '';\n      sections[sectionName] = inlineContent;\n      return '';\n    });\n\n    return sections;\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/session_processor.dart",
    "content": "import 'package:vania/src/view_engine/processor_engine/abs_processor.dart';\nimport 'package:vania/src/view_engine/template_engine.dart';\n\nclass SessionProcessor implements AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final hasSessionPattern = RegExp(\n      r\"hasSession\\(\\s*'([^']*)'\\s*\\)\",\n      dotAll: true,\n    );\n\n    content = content.replaceAllMapped(hasSessionPattern, (match) {\n      final sessionKey = match.group(1);\n      return TemplateEngine().sessions.containsKey(sessionKey).toString();\n    });\n\n    final sessionPattern = RegExp(\n      r\"\\{@\\s*session\\(\\s*'([^']*)'\\s*\\)\\s*@\\}\",\n      dotAll: true,\n    );\n\n    content = content.replaceAllMapped(sessionPattern, (math) {\n      final sessionKey = math.group(1);\n      return TemplateEngine().sessions[sessionKey] ?? '';\n    });\n\n    return content;\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/switch_cases_processor.dart",
    "content": "import 'package:vania/src/view_engine/processor_engine/abs_processor.dart';\n\n//   {@ switch <variable> @}\n//     {@ case <value(s)> @}<content>{@ endcase @}\n//     {@ default @}<content>{@ enddefault @}   (optional)\n//   {@ endswitch @}\nclass SwitchCasesProcessor implements AbsProcessor {\n  /// Parse `{@ switch <variable> @}` blocks in [content] and replace them with\n  /// values from [context].\n  //\n  /// The replacement is done by expanding the matching case content if the\n  /// condition is true. If no case matches, the default content is expanded if\n  /// it is provided.\n  //\n  /// The following example:\n  ///\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    context ??= {};\n\n    final switchPattern = RegExp(\n      r'\\{@\\s*switch\\s+(.*?)\\s*@\\}(.*?)\\{@\\s*endswitch\\s*@\\}',\n      dotAll: true,\n    );\n\n    return content.replaceAllMapped(switchPattern, (match) {\n      final switchVariable = match.group(1)?.trim() ?? '';\n      final switchContent = match.group(2) ?? '';\n\n      final switchValue = context![switchVariable];\n\n      final casePattern = RegExp(\n        r'\\{@\\s*case\\s+(.*?)\\s*@\\}(.*?)\\{@\\s*endcase\\s*@\\}',\n        dotAll: true,\n      );\n\n      final defaultPattern = RegExp(\n        r'\\{@\\s*default\\s*@\\}(.*?)\\{@\\s*enddefault\\s*@\\}',\n        dotAll: true,\n      );\n\n      for (final caseMatch in casePattern.allMatches(switchContent)) {\n        final caseValueRaw = caseMatch.group(1)?.trim() ?? '';\n        final caseContent = caseMatch.group(2) ?? '';\n\n        final caseValues = caseValueRaw.split(',').map((val) => val.trim());\n\n        for (final value in caseValues) {\n          final parsedCaseValue = num.tryParse(value) ?? value;\n          final parsedSwitchValue = (switchValue != null)\n              ? num.tryParse(switchValue.toString()) ?? switchValue\n              : null;\n\n          if (parsedSwitchValue == parsedCaseValue) {\n            return caseContent;\n          }\n        }\n      }\n\n      final defaultMatch = defaultPattern.firstMatch(switchContent);\n      if (defaultMatch != null) {\n        return defaultMatch.group(1)!;\n      }\n\n      return '';\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/translate_processor.dart",
    "content": "import 'dart:convert';\n\nimport 'package:vania/src/utils/helper.dart';\n\nimport 'abs_processor.dart';\n\nclass TranslateProcessor implements AbsProcessor {\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    final translatePattern = RegExp(\n      r\"\\{\\@\\s*trans\\(\\s*'([^']+)'\\s*\"\n      r\"(?:,\\s*({[^}]+}))?\\s*\"\n      r\"(?:,\\s*([^)]+?))?\\s*\"\n      r\"\\)\\s*\\@\\}\",\n      dotAll: true,\n    );\n    return content.replaceAllMapped(translatePattern, (match) {\n      final key = match.group(1) ?? '';\n      Map<String, dynamic> args = {};\n      final rawArgs = match.group(2);\n      if (rawArgs != null) {\n        try {\n          args = jsonDecode(rawArgs) as Map<String, dynamic>;\n        } catch (_) {}\n      }\n      final stripQuotes = RegExp(r\"\"\"^['\"]|['\"]$\"\"\");\n      String? locale;\n      final rawLocale = match.group(3);\n      if (rawLocale != null) {\n        locale = rawLocale.trim().replaceAll(stripQuotes, '');\n      }\n      return trans(key, args: args, locale: locale);\n    });\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/processor_engine/variables_processor.dart",
    "content": "import 'abs_processor.dart';\nimport 'dart:math';\n\nclass VariablesProcessor implements AbsProcessor {\n  static final _variablePattern = RegExp(r'@\\{(.*?)\\}', dotAll: true);\n\n  /// Replaces placeholders in the form of `@{expression}` with the evaluated value of [expression] in the context of [context].\n  ///\n  /// The following are valid expressions:\n  ///\n  /// - A variable name, e.g. `@{name}`\n  ///\n  /// If the expression evaluates to a non-string value, it is converted to a string.\n  ///\n  /// If the expression is invalid, or if the context does not contain a value for the specified variable,\n  /// an empty string is returned.\n  ///\n  @override\n  String parse(String content, [Map<String, dynamic>? context]) {\n    context = context ?? {};\n\n    return content.replaceAllMapped(_variablePattern, (match) {\n      final rawExpression = match.group(1)?.trim() ?? '';\n      if (rawExpression.isEmpty) return '';\n\n      if (rawExpression.contains('|')) {\n        return _handleVariableWithFilters(rawExpression, context ?? {});\n      }\n\n      if (_looksLikeVariablePath(rawExpression)) {\n        final value = _fetchValueWithBracketNotation(\n          rawExpression,\n          context ?? {},\n        );\n        if (value != null) return value.toString();\n      }\n\n      final exprValue = _evaluateExpression(rawExpression, context ?? {});\n      return exprValue?.toString() ?? '';\n    });\n  }\n\n  /// Processes a variable expression with filters from the template content.\n  ///\n  /// This function splits the [rawExpression] into a variable name and filter\n  /// operations. It fetches the variable value from the [context] using bracket\n  /// notation, then sequentially applies each filter to the value.\n  ///\n  /// The filters are applied in the order they appear in the expression, and each\n  /// filter transforms the value. If no filters are provided, the function returns\n  /// the string representation of the variable's value. If the variable is not found\n  /// in the context or an error occurs in fetching or filtering, an empty string is returned.\n  ///\n  /// - [rawExpression]: The raw expression containing the variable and optional filters.\n  /// - [context]: The context map with variable values available for substitution.\n  ///\n  /// Returns the filtered variable value as a string.\n\n  String _handleVariableWithFilters(\n    String rawExpression,\n    Map<String, dynamic> context,\n  ) {\n    final parts = rawExpression.split('|').map((e) => e.trim()).toList();\n    final variableName = parts.first;\n    final filters = parts.length > 1 ? parts.sublist(1) : <String>[];\n\n    dynamic value = _fetchValueWithBracketNotation(variableName, context);\n\n    for (final filter in filters) {\n      value = _applyFilter(value, filter);\n    }\n    return value?.toString() ?? '';\n  }\n\n  bool _looksLikeVariablePath(String expr) {\n    return expr.contains('.') || expr.contains('[');\n  }\n\n  /// Fetches a value from a [context] given a string expression.\n  ///\n  /// The [expression] can contain bracket notation, e.g. `user.name` or\n  /// `users[0].name`. The value is fetched by splitting the expression into\n  /// segments and resolving each segment on the current value.\n  ///\n  /// If the expression contains bracket index notation, e.g. `users[0]`, the\n  /// bracket index is resolved to its value in the context by calling\n  /// [_resolveBracketIndexVars].\n  ///\n  /// The function returns `null` if any segment in the expression is `null`.\n  ///\n  dynamic _fetchValueWithBracketNotation(\n    String expression,\n    Map<String, dynamic> context,\n  ) {\n    expression = _resolveBracketIndexVars(expression, context);\n\n    final segments = expression.split('.');\n    dynamic currentValue = context;\n\n    for (final segment in segments) {\n      currentValue = _resolveSegment(currentValue, segment);\n      if (currentValue == null) return null;\n    }\n    return currentValue;\n  }\n\n  String _resolveBracketIndexVars(\n    String expression,\n    Map<String, dynamic> context,\n  ) {\n    final bracketVarRegex = RegExp(r'\\[([^\\[\\]]+)\\]');\n    return expression.replaceAllMapped(bracketVarRegex, (m) {\n      final inside = m.group(1)!;\n      final asInt = int.tryParse(inside);\n      if (asInt != null) {\n        return '[$asInt]';\n      }\n      if (context.containsKey(inside) && context[inside] is int) {\n        return '[${context[inside]}]';\n      }\n      return '[$inside]';\n    });\n  }\n\n  /// Resolves a segment of a dot-separated expression on [currentValue].\n  ///\n  /// If [currentValue] is a map, the segment is resolved to a value in the map.\n  /// If the segment is a bracket-index expression, e.g. `users[0]`, the\n  /// expression is resolved to the value at the specified index in the list\n  /// value associated with the key. If the key is not found, or the value is\n  /// not a list, `null` is returned.\n  ///\n  /// If the segment is not a bracket-index expression, the segment is resolved\n  /// to the value associated with the segment key. If the key is not found,\n  /// `null` is returned.\n  ///\n  /// If [currentValue] is not a map, `null` is returned.\n  dynamic _resolveSegment(dynamic currentValue, String segment) {\n    if (currentValue == null) return null;\n\n    if (currentValue is Map) {\n      final bracketRegex = RegExp(r'^(\\w+)\\[(\\d+)\\]$');\n      final match = bracketRegex.firstMatch(segment);\n      if (match != null) {\n        final mapKey = match.group(1)!;\n        final indexStr = match.group(2)!;\n        if (!currentValue.containsKey(mapKey)) return null;\n        final listObj = currentValue[mapKey];\n        if (listObj is List) {\n          final idx = int.parse(indexStr);\n          if (idx < 0 || idx >= listObj.length) return null;\n          return listObj[idx];\n        }\n        return null;\n      } else {\n        if (!currentValue.containsKey(segment)) return null;\n        return currentValue[segment];\n      }\n    }\n    return null;\n  }\n\n  /// Applies a filter to a value, and returns the filtered value.\n  ///\n  /// The available filters are:\n  ///\n  /// - `default:<value>`: If the value is null, returns `<value>`.\n  /// - `join:<delimiter>`: If the value is a list, joins the elements with `<delimiter>`.\n  /// - `uppercase`: If the value is a string, returns its uppercase version.\n  /// - `lowercase`: If the value is a string, returns its lowercase version.\n  ///\n  /// Otherwise, returns the original value.\n  dynamic _applyFilter(dynamic value, String filter) {\n    final defaultPattern = RegExp(r'^default:\\s*(.*)$');\n    if (defaultPattern.hasMatch(filter)) {\n      if (value == null) {\n        final match = defaultPattern.firstMatch(filter);\n        var defaultVal = match?.group(1)?.trim() ?? '';\n        defaultVal = defaultVal.replaceAll(RegExp(r'^\"|\"$'), '');\n        defaultVal = defaultVal.replaceAll(RegExp(r\"^'|'$\"), '');\n        return defaultVal;\n      }\n      return value;\n    }\n\n    final joinPattern = RegExp(r'^join:\\s*(.*)$');\n    if (joinPattern.hasMatch(filter)) {\n      if (value is List) {\n        final match = joinPattern.firstMatch(filter);\n        var delimiter = match?.group(1)?.trim() ?? ',';\n        delimiter = delimiter.replaceAll(RegExp(r'^\"|\"$'), '');\n        delimiter = delimiter.replaceAll(RegExp(r\"^'|'$\"), '');\n        return value.join(delimiter);\n      }\n      return value;\n    }\n\n    if (filter == 'uppercase') {\n      if (value is String) return value.toUpperCase();\n      return value;\n    }\n\n    if (filter == 'lowercase') {\n      if (value is String) return value.toLowerCase();\n      return value;\n    }\n\n    return value;\n  }\n\n  /// Evaluates an expression in the given context.\n  ///\n  /// The expression can contain:\n  ///\n  /// - ternary expressions: `cond ? trueVal : falseVal`\n  /// - comparison operators: `==`, `!=`, `>=`, `<=`, `>`, `<`\n  /// - arithmetic operators: `+`, `-`, `*`, `/`, `%`, `^`\n  /// - any valid Dart expression\n  ///\n  /// The context is used to resolve any variables used in the expression.\n  ///\n  /// Returns the result of the expression, or `null` if the expression is invalid.\n  dynamic _evaluateExpression(String expr, Map<String, dynamic> context) {\n    expr = expr.trim();\n\n    final ternaryPattern = RegExp(r'^(.+?)\\?(.*?)\\:(.*)$');\n    final tMatch = ternaryPattern.firstMatch(expr);\n    if (tMatch != null) {\n      final condRaw = tMatch.group(1)!.trim();\n      final trueRaw = tMatch.group(2)!.trim();\n      final falseRaw = tMatch.group(3)!.trim();\n\n      final condVal = _evaluateExpression(condRaw, context);\n      final boolCond = (condVal is bool) ? condVal : _boolFromAnything(condVal);\n      if (boolCond) {\n        return _evaluateExpression(trueRaw, context);\n      } else {\n        return _evaluateExpression(falseRaw, context);\n      }\n    }\n\n    final comparisonPattern = RegExp(r'(.+?)(==|!=|>=|<=|>|<)(.+)');\n    final compMatch = comparisonPattern.firstMatch(expr);\n    if (compMatch != null) {\n      final leftRaw = compMatch.group(1)!.trim();\n      final op = compMatch.group(2)!.trim();\n      final rightRaw = compMatch.group(3)!.trim();\n\n      final leftVal = _evalOperand(leftRaw, context);\n      final rightVal = _evalOperand(rightRaw, context);\n      return _compareValues(leftVal, rightVal, op);\n    }\n\n    final arithmeticPattern = RegExp(r'(.+?)(\\+|\\-|\\*|\\/|\\%|\\^)(.+)');\n    final arithMatch = arithmeticPattern.firstMatch(expr);\n    if (arithMatch != null) {\n      final leftRaw = arithMatch.group(1)!.trim();\n      final op = arithMatch.group(2)!.trim();\n      final rightRaw = arithMatch.group(3)!.trim();\n\n      final leftVal = _evalOperand(leftRaw, context);\n      final rightVal = _evalOperand(rightRaw, context);\n      return _arithValues(leftVal, rightVal, op);\n    }\n\n    return _evalOperand(expr, context);\n  }\n\n  dynamic _evalOperand(String raw, Map<String, dynamic> context) {\n    final asInt = int.tryParse(raw);\n    if (asInt != null) return asInt;\n\n    final asDouble = double.tryParse(raw);\n    if (asDouble != null) return asDouble;\n\n    if (raw == 'true') return true;\n    if (raw == 'false') return false;\n\n    final val = _fetchValueWithBracketNotation(raw, context);\n    if (val != null) return val;\n\n    return raw;\n  }\n\n  bool _compareValues(dynamic left, dynamic right, String operator) {\n    if (left is num && right is num) {\n      switch (operator) {\n        case '==':\n          return left == right;\n        case '!=':\n          return left != right;\n        case '>':\n          return left > right;\n        case '>=':\n          return left >= right;\n        case '<':\n          return left < right;\n        case '<=':\n          return left <= right;\n      }\n    }\n\n    final lstr = left?.toString() ?? '';\n    final rstr = right?.toString() ?? '';\n    switch (operator) {\n      case '==':\n        return lstr == rstr;\n      case '!=':\n        return lstr != rstr;\n      case '>':\n        return lstr.compareTo(rstr) > 0;\n      case '>=':\n        return lstr.compareTo(rstr) >= 0;\n      case '<':\n        return lstr.compareTo(rstr) < 0;\n      case '<=':\n        return lstr.compareTo(rstr) <= 0;\n    }\n    return false;\n  }\n\n  dynamic _arithValues(dynamic left, dynamic right, String operator) {\n    if (left is num && right is num) {\n      switch (operator) {\n        case '+':\n          return left + right;\n        case '-':\n          return left - right;\n        case '*':\n          return left * right;\n        case '/':\n          return right == 0 ? null : left / right;\n        case '%':\n          return right == 0 ? null : left % right;\n        case '^':\n          return pow(left, right);\n      }\n    }\n    if (operator == '+') {\n      return '${left?.toString() ?? ''}${right?.toString() ?? ''}';\n    }\n    return null;\n  }\n\n  /// Convert any value to a boolean.\n  ///\n  /// If the value is already a boolean, it is returned as is.\n  ///\n  /// If the value is a string, it is converted to lower case and compared to\n  /// \"true\" and \"false\". If it matches one of those, the corresponding boolean\n  /// is returned. Otherwise, `false` is returned.\n  ///\n  /// If the value is a number, it is converted to a boolean by checking if it\n  /// is not equal to 0.\n  ///\n  /// For all other values, `null` is converted to `false`, and all other values\n  /// are converted to `true`.\n  bool _boolFromAnything(dynamic val) {\n    if (val is bool) return val;\n    if (val is String) {\n      final lower = val.toLowerCase();\n      if (lower == 'true') return true;\n      if (lower == 'false') return false;\n    }\n\n    if (val is num) {\n      return val != 0;\n    }\n\n    return val != null;\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/template_engine.dart",
    "content": "import 'package:vania/src/view_engine/processor_engine/abs_processor.dart';\nimport 'package:vania/src/view_engine/processor_engine/variables_processor.dart';\n\nimport 'processor_engine/assets_processor.dart';\nimport 'processor_engine/comment_processor.dart';\nimport 'processor_engine/csrf_processor.dart';\nimport 'processor_engine/csrf_token_processor.dart';\nimport 'processor_engine/error_processor.dart';\nimport 'processor_engine/if_statement_processor.dart';\nimport 'processor_engine/extends_processor.dart';\nimport 'processor_engine/for_loop_processor.dart';\nimport 'processor_engine/include_processor.dart';\nimport 'processor_engine/old_processor.dart';\nimport 'processor_engine/route_processor.dart';\nimport 'processor_engine/section_processor.dart';\nimport 'processor_engine/session_processor.dart';\nimport 'processor_engine/switch_cases_processor.dart';\nimport 'processor_engine/translate_processor.dart';\nimport 'template_reader.dart';\n\nclass _TemplateProcessingPipeline {\n  final List<AbsProcessor> _processors;\n\n  _TemplateProcessingPipeline(this._processors);\n\n  String run(String content, Map<String, dynamic> data) {\n    for (final processor in _processors) {\n      content = processor.parse(content, data);\n    }\n    return content;\n  }\n}\n\nclass TemplateEngine {\n  static final TemplateEngine _singleton = TemplateEngine._internal();\n  factory TemplateEngine() => _singleton;\n  TemplateEngine._internal();\n\n  final SectionProcessor _sectionProcessor = SectionProcessor();\n\n  final Map<String, dynamic> sessionErrors = {};\n  final Map<String, dynamic> formData = {};\n  final Map<String, dynamic> sessions = {};\n\n  String render(String template, [Map<String, dynamic>? data]) {\n    String templateContent = FileTemplateReader().read(template);\n    String renderedTemplate = renderString(templateContent, data);\n    sessionErrors.clear();\n    formData.clear();\n    sessions.clear();\n    return renderedTemplate;\n  }\n\n  /// Renders a template string with the provided data context.\n  ///\n  /// This function processes the template content by running it through a pipeline\n  /// of processors, including extends, include, section, for loop, switch case,\n  /// conditional, and variables processors. Each processor modifies the template\n  /// content based on the provided context data.\n  ///\n  /// The context data is first merged with any child sections parsed from the template content.\n  ///\n  /// Returns the fully rendered content as a string.\n  ///\n  /// Parameters:\n  /// - [templateContent]: The raw template content to be rendered.\n  /// - [data] (optional): A map of context data to be used for rendering the template.\n  ///   If not provided, an empty map is used.\n  ///\n  String renderString(String templateContent, [Map<String, dynamic>? data]) {\n    data = {\n      ...data ?? {},\n      ..._sectionProcessor.parseChildSections(templateContent),\n    };\n\n    final pipeline = _TemplateProcessingPipeline([\n      ExtendsProcessor(),\n      _sectionProcessor,\n      ErrorProcessor(),\n      SessionProcessor(),\n      ForLoopProcessor(),\n      SwitchCasesProcessor(),\n      IfStatementProcessor(),\n      VariablesProcessor(),\n      CsrfProcessor(),\n      CsrfTokenProcessor(),\n      OldProcessor(),\n      TranslateProcessor(),\n      CommentProcessor(),\n      RouteProcessor(),\n      AssetsProcessor(),\n      IncludeProcessor(),\n    ]);\n\n    final renderedContent = pipeline.run(templateContent, data);\n    return renderedContent;\n  }\n}\n"
  },
  {
    "path": "lib/src/view_engine/template_reader.dart",
    "content": "import 'dart:io';\n\n/// An interface (abstract class) for reading template files.\nabstract class TemplateReader {\n  /// Reads the template contents from the given [filePath].\n  String read(String filePath);\n}\n\nclass FileTemplateReader implements TemplateReader {\n  static final FileTemplateReader _singleton = FileTemplateReader._internal();\n  factory FileTemplateReader() => _singleton;\n  FileTemplateReader._internal();\n\n  /// Reads the html template from the given [template] path.\n  ///\n  /// The template path is relative to the `lib/resources/view/` directory.\n  /// The template file must end with `.html`.\n  ///\n  /// Throws a [FileSystemException] if the file does not exist.\n  ///\n  @override\n  String read(String template) {\n    final filePath = 'lib/resources/view/$template.html';\n    File file = File(filePath);\n    if (!file.existsSync()) {\n      file = File('$template.html');\n      if (!file.existsSync()) {\n        throw FileSystemException('Html template not found', filePath);\n      }\n    }\n    return file.readAsStringSync();\n  }\n}\n"
  },
  {
    "path": "lib/src/websocket/web_socket_handler.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\nimport 'package:uuid/v8.dart';\nimport 'package:vania/src/http/middleware/middleware.dart';\nimport 'package:vania/src/http/middleware/web_socket_middleware_handler.dart';\nimport 'websocket_client.dart';\nimport 'websocket_constants.dart';\nimport 'websocket_event.dart';\nimport 'websocket_session.dart';\n\nclass WebSocketHandler implements WebSocketEvent {\n  final WebsocketSession _session = WebsocketSession();\n\n  static final WebSocketHandler _singleton = WebSocketHandler._internal();\n  factory WebSocketHandler() => _singleton;\n  WebSocketHandler._internal();\n\n  final Map<String, List<WebSocketMiddleware>?> _middleware = {};\n\n  late String _websocketRoute;\n  WebSocketHandler websocketRoute(\n    String path, {\n    List<WebSocketMiddleware>? middleware,\n  }) {\n    _websocketRoute = path.replaceFirst('/', '');\n\n    _middleware[_websocketRoute] = middleware;\n\n    return this;\n  }\n\n  final Map<String, dynamic> _events = {};\n\n  Future handler(HttpRequest req) async {\n    String routePath = req.uri.path.replaceFirst('/', '');\n\n    WebSocket websocket = await WebSocketTransformer.upgrade(req);\n\n    String sessionId = 'ws:${UuidV8().generate()}';\n\n    _session.addNewSession(sessionId, websocket);\n\n    final WebSocketClientImpl client = WebSocketClientImpl(\n      session: _session,\n      id: sessionId,\n      routePath: routePath,\n    );\n\n    websocket.add(\n      jsonEncode({\n        'event': 'connected',\n        'payload': {'session_id': sessionId},\n      }),\n    );\n\n    try {\n      if (_middleware[_websocketRoute] != null) {\n        await webSocketMiddlewareHandler(\n          _middleware[_websocketRoute] as List<WebSocketMiddleware>,\n          req,\n        );\n      }\n    } on WebSocketException catch (e) {\n      websocket.add(\n        jsonEncode({\n          'event': 'error',\n          'payload': {'message': e.message},\n        }),\n      );\n      return;\n    }\n\n    Function? openFunction = _events['${routePath}_connect'];\n    if (openFunction != null) {\n      Function.apply(openFunction, <dynamic>[client]);\n    }\n\n    websocket.listen(\n      (data) async {\n        Map<String, dynamic> payload = jsonDecode(data);\n        String event = '${routePath}_${payload[webScoketEventKey]}';\n\n        /// client join the room\n        if (event == '${routePath}_$webSocketJoinRoomEventName') {\n          String? roomId = payload[webSocketRoomKey].toString();\n          if (roomId.isNotEmpty) {\n            _session.joinRoom(sessionId, '${routePath}_$roomId');\n          }\n          return;\n        }\n\n        /// client left the room\n        if (event == webSocketLeftRoomEventName) {\n          String? roomId = payload[webSocketRoomKey].toString();\n          if (roomId.isNotEmpty) {\n            _session.leftRoom(sessionId, '${routePath}_$roomId');\n          }\n          return;\n        }\n\n        /// websocket response\n        /// ```\n        /// event.on('event',function(WebSocketClient client,message){\n        ///   response\n        ///  });\n        /// ```\n        dynamic message = payload[webSocketMessageKey];\n\n        Function? controller = _events[event];\n\n        if (controller == null) {\n          return;\n        }\n        Function.apply(controller, <dynamic>[client, message]);\n      },\n      onDone: () {\n        Function? openFunction = _events['${routePath}_disconnect'];\n        if (openFunction != null) {\n          Function.apply(openFunction, <dynamic>[client]);\n        }\n\n        _session.removeSession(sessionId);\n      },\n      onError: (_) {\n        Function? openFunction = _events['${routePath}_error'];\n        if (openFunction != null) {\n          Function.apply(openFunction, <dynamic>[client]);\n        }\n\n        _session.removeSession(sessionId);\n      },\n    );\n  }\n\n  ///  Listener\n  /// ```\n  /// event.on('event',function(WebSocketClient client,message){\n  ///   response\n  ///  });\n  /// ```\n  @override\n  void on(String event, Function function) {\n    _events['${_websocketRoute}_$event'] = function;\n  }\n}\n"
  },
  {
    "path": "lib/src/websocket/websocket_client.dart",
    "content": "import 'dart:convert';\n\nimport 'websocket_session.dart';\n\nabstract class WebSocketClient {\n  const WebSocketClient();\n  String get clientId;\n  List<String> get activeSessions;\n  List<String> getRoomMembers({String roomId = ''});\n  bool isActiveSession({String sessionId = ''});\n  String get activeRoom;\n  String get previousRoom;\n  void emit(String event, dynamic payload);\n  void toRoom(String event, String room, dynamic payload);\n  void broadcast(String event, dynamic payload);\n  void to(String clientId, String event, dynamic payload);\n}\n\nclass WebSocketClientImpl implements WebSocketClient {\n  final String id;\n  final String routePath;\n  final WebsocketSession session;\n  const WebSocketClientImpl({\n    required this.session,\n    required this.id,\n    required this.routePath,\n  });\n\n  @override\n  String get clientId => id;\n\n  @override\n  List<String> get activeSessions => session.getActiveSessionIds();\n\n  /// emit to self sender\n  /// ```\n  /// event.emit('event',payload)\n  /// ```\n  @override\n  void emit(String event, dynamic payload) {\n    SessionInfo? info = session.getWebSocketInfo(id);\n    if (info != null) {\n      info.websocket.add(jsonEncode({'event': event, 'payload': payload}));\n    }\n  }\n\n  /// emit to room all users in room can see this message\n  ///  exclude sender\n  /// ```\n  /// event.toRoom('event','room',payload)\n  /// ```\n  @override\n  void toRoom(String event, String room, dynamic payload) {\n    String roomId = room.replaceFirst('ws_', '');\n    List<String> members = session.getRoomMembers('${routePath}_$roomId');\n    for (String member in members) {\n      SessionInfo? info = session.getWebSocketInfo(member);\n      if (info != null) {\n        info.websocket.add(jsonEncode({'event': event, 'payload': payload}));\n      }\n    }\n  }\n\n  /// emit to specific seesion id\n  /// ```\n  /// event.to(clientId,'event',payload)\n  /// ```\n  @override\n  void to(String clientId, String event, dynamic payload) {\n    SessionInfo? info = session.getWebSocketInfo(clientId);\n    if (info != null) {\n      info.websocket.add(jsonEncode({'event': event, 'payload': payload}));\n    }\n  }\n\n  /// broadcast to all connected sessions exclude sender\n  ///```\n  /// event.broadcast('event',payload)\n  /// ```\n  @override\n  void broadcast(String event, dynamic payload) {\n    List<SessionInfo> sessions = session.getActiveSessions();\n    sessions.removeWhere((item) => item.sessionId == id);\n    sessions.shuffle();\n    for (SessionInfo session in sessions) {\n      session.websocket.add(jsonEncode({'event': event, 'payload': payload}));\n    }\n  }\n\n  void joinRoom(String roomId) {\n    toRoom(\"join-room\", roomId, \"join room\");\n  }\n\n  void leftRoom(String roomId) {\n    toRoom(\"left-room\", roomId, \"left room\");\n  }\n\n  @override\n  List<String> getRoomMembers({String roomId = ''}) =>\n      session.getRoomMembers(roomId);\n\n  @override\n  String get activeRoom => session.getWebSocketInfo(id)?.activeRoom ?? '';\n\n  @override\n  String get previousRoom => session.getWebSocketInfo(id)?.previousRoom ?? '';\n\n  @override\n  bool isActiveSession({String sessionId = ''}) =>\n      session.isActiveSession(sessionId);\n}\n"
  },
  {
    "path": "lib/src/websocket/websocket_constants.dart",
    "content": "const String webSocketJoinRoomEventName = 'join-room';\n\nconst String webSocketLeftRoomEventName = 'left-room';\n\nconst String webScoketEventKey = 'event';\n\nconst String webSocketMessageKey = 'payload';\n\nconst String webSocketSenderKey = 'sender';\n\nconst String webSocketRoomKey = 'room';\n"
  },
  {
    "path": "lib/src/websocket/websocket_event.dart",
    "content": "abstract class WebSocketEvent {\n  void on(String event, Function function);\n}\n"
  },
  {
    "path": "lib/src/websocket/websocket_session.dart",
    "content": "import 'dart:collection';\nimport 'dart:io';\n\nfinal Map<String, SessionInfo> _activeSessions = HashMap();\nfinal Map<String, List<String>> _rooms = <String, List<String>>{};\n\nclass SessionInfo {\n  final String sessionId;\n  final WebSocket websocket;\n  String? activeRoom;\n  String? previousRoom;\n\n  SessionInfo({\n    required this.sessionId,\n    required this.websocket,\n    this.activeRoom,\n    this.previousRoom,\n  });\n}\n\nclass WebsocketSession {\n  static final WebsocketSession _singleton = WebsocketSession._internal();\n  factory WebsocketSession() {\n    return _singleton;\n  }\n  WebsocketSession._internal();\n\n  /// add new websocket session to the active sessions\n  void addNewSession(String sessionId, WebSocket ws) {\n    _activeSessions.addAll({\n      sessionId: SessionInfo(sessionId: sessionId, websocket: ws),\n    });\n  }\n\n  /// get session of connected socket\n  SessionInfo? getWebSocketInfo(String sessionId) {\n    return _activeSessions[sessionId];\n  }\n\n  /// remove session of connected socket\n  void removeSession(String sessionId) {\n    SessionInfo? info = _activeSessions[sessionId];\n    if (info != null) {\n      leftRoom(sessionId, info.activeRoom);\n      _activeSessions.remove(sessionId);\n      info.websocket.close();\n    }\n  }\n\n  void joinRoom(String sessionId, String roomId) {\n    if (_rooms[roomId] == null) {\n      _rooms[roomId] = <String>[];\n    }\n    _rooms[roomId]?.add(sessionId);\n    SessionInfo? info = _activeSessions[sessionId];\n    if (info != null) {\n      if (info.previousRoom != null) {\n        leftRoom(sessionId, info.previousRoom);\n      }\n      info.activeRoom = roomId;\n      info.activeRoom = roomId;\n    }\n  }\n\n  void leftRoom(String sessionId, String? roomId) {\n    if (_rooms[roomId] != null && roomId != null) {\n      SessionInfo? info = _activeSessions[sessionId];\n      if (info != null) {\n        info.previousRoom = null;\n        info.activeRoom = null;\n        _rooms[roomId]?.remove(sessionId);\n      }\n    }\n  }\n\n  /// get all room members (socket ids)\n  /// for send message to a room\n  List<String> getRoomMembers(String roomId) {\n    return _rooms[roomId] ?? <String>[];\n  }\n\n  bool isRoom(String roomId) {\n    return _rooms[roomId] != null ? true : false;\n  }\n\n  bool isActiveSession(String sessionId) {\n    return _activeSessions[sessionId] != null ? true : false;\n  }\n\n  List<SessionInfo> getActiveSessions() {\n    return _activeSessions.values.toList();\n  }\n\n  List<String> getActiveSessionIds() {\n    return _activeSessions.keys.toList();\n  }\n}\n"
  },
  {
    "path": "lib/vania.dart",
    "content": "export 'src/extensions/extensions.dart';\n\nexport 'src/redis/vania_redis.dart';\nexport 'src/cache/redis_cache_driver.dart';\nexport 'src/cache/cache_driver.dart';\nexport 'src/cache/cache.dart';\n\nexport 'src/storage/storage_driver.dart';\nexport 'src/storage/storage.dart';\n\nexport 'src/utils/helper.dart';\nexport 'src/env_handler/env.dart';\nexport 'src/logger/logger.dart';\nexport 'src/config/config.dart';\n\nexport 'src/cryptographic/hash.dart';\n\nexport 'src/exception/base_http_exception.dart';\nexport 'src/exception/database_exception.dart';\nexport 'src/exception/exception_handler.dart';\nexport 'src/exception/forbidden_exception.dart';\nexport 'src/exception/http_exception.dart';\nexport 'src/exception/internal_server_error.dart';\nexport 'src/exception/invalid_argument_exception.dart';\nexport 'src/exception/not_found_exception.dart';\nexport 'src/exception/page_expired_exception.dart';\nexport 'src/exception/query_exception.dart';\nexport 'src/exception/redirect_exception.dart';\nexport 'src/exception/throttle_exception.dart';\nexport 'src/exception/unauthenticated.dart';\nexport 'src/exception/unauthorized_exception.dart';\nexport 'src/exception/validation_exception.dart';\nexport 'application.dart';\n"
  },
  {
    "path": "lib/websocket.dart",
    "content": "export 'src/websocket/websocket_client.dart';\nexport 'src/websocket/websocket_event.dart';\n"
  },
  {
    "path": "pubspec.yaml",
    "content": "name: vania\ndescription: Fast, simple, and powerful backend framework for Dart built with\nversion: 1.1.1\nhomepage: https://vdart.dev\nrepository: https://github.com/vania-dart/framework\nissue_tracker: https://github.com/vania-dart/framework/issues\ndocumentation: https://vdart.dev/docs/intro\ntopics: [server, backend, httpserver, framework, web]\n\nscreenshots:\n  - description: 'Vania web application'\n    path: assets/logo.png\n\nenvironment:\n   sdk: '>=3.9.0 <4.0.0'\n\ndependencies:\n  args: ^2.7.0\n  crypto: ^3.0.6\n  cryptography: ^2.7.0\n  dart_jsonwebtoken: ^3.3.1\n  mailer: ^6.5.0\n  meta: ^1.17.0\n  mime: ^2.0.0\n  mysql_client: ^0.0.27\n  path: ^1.9.1\n  postgres: ^3.5.9\n  sprintf: ^7.0.0\n  sqlite3: ^2.9.3\n  string_scanner: ^1.4.1\n  uuid: ^4.5.1\ndev_dependencies:\n  lints: ^6.0.0\n  test: ^1.26.2\n  http: ^1.4.0\n  http_parser: ^4.1.2\n  build_runner: ^2.6.0\n  mockito: ^5.4.6\n\n  "
  },
  {
    "path": "test/src/extensions/date_time_extension_test.dart",
    "content": "import 'package:test/test.dart';\nimport 'package:vania/src/extensions/date_time_extension.dart';\n\nvoid main() {\n  group('DateTimeExtension Tests', () {\n    test('toAwsFormat produces correct AWS timestamp format', () {\n      var dateTime = DateTime(2023, 1, 2, 3, 4, 5);\n      expect(dateTime.toAwsFormat(), equals('20230102T030405Z'));\n    });\n\n    test('format produces correctly formatted date-time string', () {\n      var dateTime = DateTime(2023, 1, 2, 3, 4, 5);\n      expect(dateTime.format(), equals('2023-01-02 03:04:05'));\n    });\n\n    test('toAwsFormat handles single-digit and double-digit fields', () {\n      var dateTime = DateTime(2023, 11, 20, 10, 9, 8);\n      expect(dateTime.toAwsFormat(), equals('20231120T100908Z'));\n    });\n\n    test('format handles edge case of leap year date', () {\n      var dateTime = DateTime(2020, 2, 29, 23, 59, 59);\n      expect(dateTime.format(), equals('2020-02-29 23:59:59'));\n    });\n  });\n}\n"
  },
  {
    "path": "test/src/extensions/number_extension_test.dart",
    "content": "import 'package:test/test.dart';\nimport 'package:vania/src/extensions/number_extension.dart';\n\nvoid main() {\n  group('NumberExtension.toFixed', () {\n    test('should round a double to 2 decimal places', () {\n      expect(123.456.toFixed(2), equals(123.46));\n    });\n\n    test('should round a double to 0 decimal places', () {\n      expect(123.456.toFixed(0), equals(123));\n    });\n\n    test('should handle integers correctly', () {\n      expect(123.toFixed(0), equals(123));\n      expect(123.toFixed(2), equals(123.00));\n    });\n\n    test('should handle rounding up correctly', () {\n      expect(123.556.toFixed(2), equals(123.56));\n    });\n\n    test('should handle rounding down correctly', () {\n      expect(123.554.toFixed(2), equals(123.55));\n    });\n\n    test('should process very small decimals', () {\n      expect(0.000123456.toFixed(4), equals(0.0001));\n    });\n\n    test('should return a double when rounding integers with decimals', () {\n      var result = 100.toFixed(2);\n      expect(result, equals(100.00));\n      expect(result, isA<double>());\n    });\n\n    test(\n      'should return an integer when rounding integers with zero decimals',\n      () {\n        var result = 100.toFixed(0);\n        expect(result, equals(100));\n        expect(result, isA<int>());\n      },\n    );\n\n    test('should handle negative numbers correctly', () {\n      expect((-123.456).toFixed(2), equals(-123.46));\n      expect((-123.456).toFixed(0), equals(-123));\n    });\n  });\n}\n"
  },
  {
    "path": "test/src/extensions/string_extension_test.dart",
    "content": "import 'package:test/test.dart';\nimport 'package:vania/src/extensions/string_extension.dart';\n\nvoid main() {\n  group(\"toInt extension test\", () {\n    test(\"should return int value\", () {\n      expect(\"1\".toInt(), 1);\n    });\n\n    test(\"should return null when it is string\", () {\n      expect(\"a\".toInt(), null);\n    });\n    test(\"should return null when it is double\", () {\n      expect(\"1,25\".toInt(), null);\n    });\n  });\n}\n"
  },
  {
    "path": "test/src/extensions/string_list_extension_test.dart",
    "content": "import 'package:test/test.dart';\nimport 'package:vania/src/extensions/string_list_extension.dart';\n\nvoid main() {\n  group('List<String>.joinWithAnd', () {\n    test('joins empty list into empty string', () {\n      expect(<String>[].joinWithAnd(), equals(''));\n    });\n\n    test('handles single element list', () {\n      expect(['apple'].joinWithAnd(), equals('apple'));\n    });\n\n    test('joins two elements list with custom conjunction', () {\n      expect(['apple', 'orange'].joinWithAnd(), equals('apple and orange'));\n    });\n\n    test('joins multiple elements with default separator and conjunction', () {\n      expect(\n        ['apple', 'orange', 'banana'].joinWithAnd(),\n        equals('apple, orange and banana'),\n      );\n    });\n\n    test('allows custom separator and conjunction', () {\n      expect(\n        ['apple', 'orange', 'banana'].joinWithAnd(' / ', 'or'),\n        equals('apple / orange or banana'),\n      );\n    });\n\n    test('considers only the last two elements for conjunction', () {\n      expect(\n        ['apple', 'orange', 'banana', 'mango'].joinWithAnd(),\n        equals('apple, orange, banana and mango'),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "test/unit/hash_test.dart",
    "content": "import 'dart:io';\n\nimport 'package:test/test.dart';\nimport 'package:vania/vania.dart';\n\nvoid main() {\n  group('Hash class test', () {\n    setUp(() {\n      Env().load(file: File('test/.env'));\n    });\n\n    test('Make/Verify correct paswword', () {\n      String password = \"123456789\";\n      String hash = Hash().make(password);\n      expect(Hash().verify(password, hash), true);\n    });\n\n    test('Make/Verify wrong password', () {\n      String password = \"123456789\";\n      String hash = Hash().make(password);\n      expect(Hash().verify(\"12345678\", hash), false);\n    });\n  });\n}\n"
  },
  {
    "path": "test/unit/route_test.dart",
    "content": "import 'package:test/test.dart';\n\nvoid main() {\n  group('Route Test', () {});\n}\n"
  },
  {
    "path": "test/unit/validation_test.dart",
    "content": "import 'package:test/test.dart';\nimport 'package:vania/src/http/validation/validator.dart';\n\nvoid main() {\n  group('Validation Test', () {\n    test('Validation required Test', () {\n      Validator validator = Validator(data: {'name': ''});\n      validator.validate({'name': 'required'}).then((_) {\n        expect(validator.errors['name'], 'The name is required');\n      });\n    });\n\n    test('Validation integer Test', () {\n      Validator validator = Validator(data: {'age': 'String'});\n      validator.validate({'age': 'integer'}).then((_) {\n        expect(validator.errors['age'], 'The age must be an integer');\n      });\n    });\n\n    test('Validation not_in', () {\n      Validator validator = Validator(data: {'status': 'active'});\n      validator.validate({'status': 'not_in:active,pending'}).then((_) {\n        expect(validator.errors['status'], 'The status field cannot be active');\n      });\n    });\n\n    test('Validation start_with', () {\n      Validator validator = Validator(data: {'name': 'Vania'});\n      validator.validate({'name': 'start_with:_va'}).then((_) {\n        expect(validator.errors['name'], 'The name must start with _va');\n      });\n    });\n\n    test('Validation password confirmed', () {\n      Validator validator = Validator(\n        data: {'password': '12345678', 'password_confirmation': '112233445566'},\n      );\n      validator.validate(<String, String>{'password': 'confirmed'}).then((_) {\n        expect(validator.errors['password'], 'The two password did not match');\n      });\n    });\n\n    test('Validation required if', () {\n      Validator validator = Validator(data: {'username': '', 'type': 'login'});\n      validator.validate({'username': 'required_if:type,login'}).then((_) {\n        expect(validator.errors['username'], 'The username is required');\n      });\n    });\n  });\n}\n"
  }
]