diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0d2da9cfc..e551da118 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,9 +7,13 @@ on:
- 4.x
- 4.next
- 5.x
+ paths-ignore:
+ - docs/**
pull_request:
branches:
- '*'
+ paths-ignore:
+ - docs/**
workflow_dispatch:
permissions:
diff --git a/.github/workflows/deploy_docs_5x.yml b/.github/workflows/deploy_docs_5x.yml
index bc6f5ad9c..2f5c1a27e 100644
--- a/.github/workflows/deploy_docs_5x.yml
+++ b/.github/workflows/deploy_docs_5x.yml
@@ -21,3 +21,4 @@ jobs:
with:
git_remote_url: 'ssh://dokku@apps.cakephp.org:22/migrations-docs-5'
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }}
+ branch: '5.x'
diff --git a/Dockerfile b/Dockerfile
index 70ceb413d..832f6bc1c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,25 +1,36 @@
-# Basic docker based environment
-# Necessary to trick dokku into building the documentation
-# using dockerfile instead of herokuish
-FROM ubuntu:22.04
-
-# Add basic tools
-RUN apt-get update && \
- apt-get install -y build-essential \
- software-properties-common \
- curl \
- git \
- libxml2 \
- libffi-dev \
- libssl-dev && \
- LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php && \
- apt-get update && \
- apt-get install -y php8.1-cli php8.1-mbstring php8.1-xml php8.1-zip php8.1-intl php8.1-opcache php8.1-sqlite &&\
- apt-get clean &&\
- rm -rf /var/lib/apt/lists/*
-
-WORKDIR /code
-
-VOLUME ["/code"]
-
-CMD [ '/bin/bash' ]
+# ----------------------
+# 1. Build stage
+# ----------------------
+FROM node:22-alpine AS builder
+
+# Git is required because docs/package.json pulls a dependency from GitHub.
+RUN apk add --no-cache git openssh-client
+
+WORKDIR /app/docs
+
+# Copy dependency manifests first to preserve Docker layer caching.
+COPY docs/ ./
+RUN npm ci
+
+# Increase max-old-space-size to avoid memory issues during build
+ENV NODE_OPTIONS="--max-old-space-size=8192"
+
+# Build the site.
+RUN npm run docs:build
+
+# ----------------------
+# 2. Runtime stage (nginx)
+# ----------------------
+FROM nginx:1.27-alpine AS runner
+
+# Copy built files
+COPY --from=builder /app/docs/.vitepress/dist /usr/share/nginx/html
+
+# Expose port
+EXPOSE 80
+
+# Health check (optional)
+HEALTHCHECK CMD wget --quiet --tries=1 --spider http://localhost:80/ || exit 1
+
+# Start nginx
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/docs.Dockerfile b/docs.Dockerfile
deleted file mode 100644
index 8d5a6570e..000000000
--- a/docs.Dockerfile
+++ /dev/null
@@ -1,26 +0,0 @@
-# Generate the HTML output.
-FROM ghcr.io/cakephp/docs-builder as builder
-
-COPY docs /data/docs
-
-RUN cd /data/docs-builder && \
- # In the future repeat website for each version
- make website LANGS="en" SOURCE=/data/docs DEST=/data/website/
-
-# Build a small nginx container with just the static site in it.
-FROM ghcr.io/cakephp/docs-builder:runtime as runtime
-
-ENV LANGS="en"
-ENV SEARCH_SOURCE="/usr/share/nginx/html"
-ENV SEARCH_URL_PREFIX="/migrations/5"
-
-COPY --from=builder /data/docs /data/docs
-COPY --from=builder /data/website /data/website
-COPY --from=builder /data/docs-builder/nginx.conf /etc/nginx/conf.d/default.conf
-
-
-# Move each version into place
-RUN cp -R /data/website/html/* /usr/share/nginx/html \
- && rm -rf /data/website/
-
-RUN ln -s /usr/share/nginx/html /usr/share/nginx/html/2.x
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 000000000..a1a455b7d
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+*/public/
+.vitepress/cache
diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js
new file mode 100644
index 000000000..b0ede705f
--- /dev/null
+++ b/docs/.vitepress/config.js
@@ -0,0 +1,51 @@
+import baseConfig from '@cakephp/docs-skeleton/config'
+
+import { createRequire } from "module";
+const require = createRequire(import.meta.url);
+const toc_en = require("./toc_en.json");
+
+const versions = {
+ text: "5.x",
+ items: [
+ { text: "5.x (current)", link: "book.cakephp.org/migrations/5.x/", target: '_self' },
+ { text: "4.x", link: "https://book.cakephp.org/migrations/4/", target: '_self' },
+ { text: "3.x", link: "https://book.cakephp.org/migrations/3/", target: '_self' },
+ { text: "2.x", link: "https://book.cakephp.org/migrations/2/", target: '_self' },
+ ],
+};
+
+// This file contains overrides for .vitepress/config.js
+export default {
+ extends: baseConfig,
+ srcDir: 'en',
+ title: 'Migrations plugin',
+ description: 'Migrations - CakePHP migrations Documentation',
+ base: "/5.x/",
+ rewrites: {
+ "en/:slug*": ":slug*",
+ },
+ sitemap: {
+ hostname: "https://book.cakephp.org/migrations/5.x/",
+ },
+ themeConfig: {
+ socialLinks: [
+ { icon: "github", link: "https://github.com/cakephp/cakephp" },
+ ],
+ editLink: {
+ pattern: "https://github.com/cakephp/migrations/edit/5.x/docs/:path",
+ text: "Edit this page on GitHub",
+ },
+ sidebar: toc_en,
+ nav: [
+ { text: "CakePHP Book", link: "https://book.cakephp.org/" },
+ { ...versions },
+ ],
+ },
+ substitutions: {},
+ locales: {
+ root: {
+ label: "English",
+ lang: "en",
+ },
+ },
+};
diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js
new file mode 100644
index 000000000..e33e19ec9
--- /dev/null
+++ b/docs/.vitepress/theme/index.js
@@ -0,0 +1 @@
+export { default } from '@cakephp/docs-skeleton'
diff --git a/docs/.vitepress/toc_en.json b/docs/.vitepress/toc_en.json
new file mode 100644
index 000000000..b7f94176a
--- /dev/null
+++ b/docs/.vitepress/toc_en.json
@@ -0,0 +1,23 @@
+{
+ "/": [
+ {
+ "text": "Migrations",
+ "collapsed": false,
+ "items": [
+ { "text": "Introduction", "link": "/index" },
+ { "text": "Writing Migrations", "link": "/writing-migrations" },
+ { "text": "Using the Query Builder", "link": "/using-the-query-builder" },
+ { "text": "Executing Queries", "link": "/executing-queries" },
+ { "text": "Database Seeding", "link": "/seeding" }
+ ]
+ },
+ {
+ "text": "Upgrade Guides",
+ "collapsed": true,
+ "items": [
+ { "text": "Upgrading from 4.x to 5.x", "link": "/upgrading" },
+ { "text": "Upgrading to the Builtin Backend", "link": "/upgrading-to-builtin-backend" }
+ ]
+ }
+ ]
+}
diff --git a/docs/config/__init__.py b/docs/config/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/docs/config/all.py b/docs/config/all.py
deleted file mode 100644
index a8f5f2f86..000000000
--- a/docs/config/all.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Global configuration information used across all the
-# translations of documentation.
-#
-# Import the base theme configuration
-from cakephpsphinx.config.all import *
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-
-# The full version, including alpha/beta/rc tags.
-release = '5.x'
-
-# The search index version.
-search_version = 'migrations-5'
-
-# The marketing display name for the book.
-version_name = ''
-
-# Project name shown in the black header bar
-project = 'CakePHP Migrations'
-
-# Other versions that display in the version picker menu.
-version_list = [
- {'name': '2.x', 'number': 'migrations/2', 'title': '2.x'},
- {'name': '3.x', 'number': 'migrations/3', 'title': '3.x'},
- {'name': '4.x', 'number': 'migrations/4', 'title': '4.x'},
- {'name': '5.x', 'number': 'migrations/5', 'title': '5.x', 'current': True},
-]
-
-# Languages available.
-languages = ['en', 'fr', 'ja', 'pt', 'ru']
-
-# The GitHub branch name for this version of the docs
-# for edit links to point at.
-branch = '5.x'
-
-# Current version being built
-version = '5.x'
-
-# Language in use for this directory.
-language = 'en'
-
-show_root_link = True
-
-repository = 'cakephp/migrations'
-
-source_path = 'docs/'
-
-hide_page_contents = ('search', '404', 'contents')
-
-is_prerelease = False
diff --git a/docs/en/conf.py b/docs/en/conf.py
deleted file mode 100644
index f638bda22..000000000
--- a/docs/en/conf.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import sys, os
-
-# Append the top level directory of the docs, so we can import from the config dir.
-sys.path.insert(0, os.path.abspath('..'))
-
-# Pull in all the configuration options defined in the global config file..
-from config.all import *
-
-language = 'en'
diff --git a/docs/en/contents.md b/docs/en/contents.md
new file mode 100644
index 000000000..18fe41d4c
--- /dev/null
+++ b/docs/en/contents.md
@@ -0,0 +1,8 @@
+### CakePHP Migrations
+
+- [Migrations 5.x](index)
+- [Writing Migrations](writing-migrations)
+- [Using the Query Builder](using-the-query-builder)
+- [Executing Queries](executing-queries)
+- [Database Seeding](seeding)
+- [Upgrading to the builtin backend](upgrading-to-builtin-backend)
diff --git a/docs/en/contents.rst b/docs/en/contents.rst
deleted file mode 100644
index 88edb891a..000000000
--- a/docs/en/contents.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-.. toctree::
- :maxdepth: 2
- :caption: CakePHP Migrations
-
- /index
- /writing-migrations
- /using-the-query-builder
- /executing-queries
- /seeding
- /upgrading-to-builtin-backend
diff --git a/docs/en/executing-queries.md b/docs/en/executing-queries.md
new file mode 100644
index 000000000..a87c58dd0
--- /dev/null
+++ b/docs/en/executing-queries.md
@@ -0,0 +1,135 @@
+# Executing Queries
+
+Queries can be executed with the `execute()` and `query()` methods. The
+`execute()` method returns the number of affected rows whereas the
+`query()` method returns the result as a
+[CakePHP Statement](https://book.cakephp.org/5/en/orm/database-basics.html#interacting-with-statements). Both methods
+accept an optional second parameter `$params` which is an array of elements,
+and if used will cause the underlying connection to use a prepared statement:
+
+``` php
+execute('DELETE FROM users'); // returns the number of affected rows
+
+ // query()
+ $stmt = $this->query('SELECT * FROM users'); // returns PDOStatement
+ $rows = $stmt->fetchAll(); // returns the result as an array
+
+ // using prepared queries
+ $count = $this->execute('DELETE FROM users WHERE id = ?', [5]);
+ $stmt = $this->query('SELECT * FROM users WHERE id > ?', [5]); // returns PDOStatement
+ $rows = $stmt->fetchAll();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+## Fetching Rows
+
+There are two methods available to fetch rows. The `fetchRow()` method will
+fetch a single row, whilst the `fetchAll()` method will return multiple rows.
+Both methods accept raw SQL as their only parameter:
+
+``` php
+fetchRow('SELECT * FROM users');
+
+ // fetch an array of messages
+ $rows = $this->fetchAll('SELECT * FROM messages');
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+## Inserting Data
+
+Migrations makes it easy to insert data into your tables. Whilst this feature is
+intended for the [seed feature](seeding), you are also free to use the
+insert methods in your migrations:
+
+``` php
+table('status');
+
+ // inserting only one row
+ $singleRow = [
+ 'id' => 1,
+ 'name' => 'In Progress'
+ ];
+
+ $table->insert($singleRow)->saveData();
+
+ // inserting multiple rows
+ $rows = [
+ [
+ 'id' => 2,
+ 'name' => 'Stopped'
+ ],
+ [
+ 'id' => 3,
+ 'name' => 'Queued'
+ ]
+ ];
+
+ $table->insert($rows)->saveData();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ $this->execute('DELETE FROM status');
+ }
+}
+```
+
+> [!NOTE]
+> You cannot use the insert methods inside a change() method. Please use the
+> up() and down() methods.
diff --git a/docs/en/executing-queries.rst b/docs/en/executing-queries.rst
deleted file mode 100644
index de26a5490..000000000
--- a/docs/en/executing-queries.rst
+++ /dev/null
@@ -1,134 +0,0 @@
-Executing Queries
-#################
-
-Queries can be executed with the ``execute()`` and ``query()`` methods. The
-``execute()`` method returns the number of affected rows whereas the
-``query()`` method returns the result as a
-`CakePHP Statement `_. Both methods
-accept an optional second parameter ``$params`` which is an array of elements,
-and if used will cause the underlying connection to use a prepared statement::
-
- execute('DELETE FROM users'); // returns the number of affected rows
-
- // query()
- $stmt = $this->query('SELECT * FROM users'); // returns PDOStatement
- $rows = $stmt->fetchAll(); // returns the result as an array
-
- // using prepared queries
- $count = $this->execute('DELETE FROM users WHERE id = ?', [5]);
- $stmt = $this->query('SELECT * FROM users WHERE id > ?', [5]); // returns PDOStatement
- $rows = $stmt->fetchAll();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Fetching Rows
-=============
-
-There are two methods available to fetch rows. The ``fetchRow()`` method will
-fetch a single row, whilst the ``fetchAll()`` method will return multiple rows.
-Both methods accept raw SQL as their only parameter::
-
- fetchRow('SELECT * FROM users');
-
- // fetch an array of messages
- $rows = $this->fetchAll('SELECT * FROM messages');
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Inserting Data
-==============
-
-Migrations makes it easy to insert data into your tables. Whilst this feature is
-intended for the :doc:`seed feature `, you are also free to use the
-insert methods in your migrations::
-
- table('status');
-
- // inserting only one row
- $singleRow = [
- 'id' => 1,
- 'name' => 'In Progress'
- ];
-
- $table->insert($singleRow)->saveData();
-
- // inserting multiple rows
- $rows = [
- [
- 'id' => 2,
- 'name' => 'Stopped'
- ],
- [
- 'id' => 3,
- 'name' => 'Queued'
- ]
- ];
-
- $table->insert($rows)->saveData();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- $this->execute('DELETE FROM status');
- }
- }
-
-.. note::
-
- You cannot use the insert methods inside a `change()` method. Please use the
- `up()` and `down()` methods.
-
diff --git a/docs/en/index.md b/docs/en/index.md
new file mode 100644
index 000000000..6eb56c665
--- /dev/null
+++ b/docs/en/index.md
@@ -0,0 +1,941 @@
+# Migrations 5.x
+
+Migrations is a plugin that lets you track changes to your database schema over
+time as PHP code that accompanies your application. This lets you ensure each
+environment your application runs in has the appropriate schema by applying
+migrations.
+
+Instead of writing schema modifications in SQL, this plugin allows you to
+define schema changes with a high-level database portable API.
+
+## What's New in 5.x
+
+Migrations 5.x includes several new features:
+
+- **Seed tracking** - Seeds are now tracked in a `cake_seeds` table, preventing
+ accidental re-runs
+- **Check constraints** - Support for database check constraints via `addCheckConstraint()`
+- **Default values in bake** - Specify default values when baking migrations
+ (e.g., `active:boolean:default[true]`)
+- **MySQL ALTER options** - Control `ALGORITHM` and `LOCK` for ALTER TABLE operations
+- **insertOrSkip()** - New method for idempotent seed data insertion
+
+See the [Upgrading from 4.x to 5.x](upgrading) guide for breaking changes and migration steps from 4.x.
+
+## Installation
+
+By default Migrations is installed with the application skeleton. If
+you've removed it and want to re-install it, you can do so by running the
+following from your application's ROOT directory (where **composer.json** file is
+located):
+
+``` bash
+php composer.phar require cakephp/migrations "@stable"
+
+# Or if composer is installed globally
+composer require cakephp/migrations "@stable"
+```
+
+To use the plugin you'll need to load it in your application's
+**config/bootstrap.php** file. You can use [CakePHP's Plugin shell](https://book.cakephp.org/5/en/console-and-shells/plugin-shell.html) to
+load and unload plugins from your **config/bootstrap.php**:
+
+``` bash
+bin/cake plugin load Migrations
+```
+
+Or you can load the plugin by editing your **src/Application.php** file and
+adding the following statement:
+
+``` php
+$this->addPlugin('Migrations');
+```
+
+Additionally, you will need to configure the default database configuration for
+your application in your **config/app.php** file as explained in the [Database
+Configuration section](https://book.cakephp.org/5/en/orm/database-basics.html#database-configuration).
+
+## Upgrading from 4.x
+
+If you are upgrading from Migrations 4.x, please see the [Upgrading from 4.x to 5.x](upgrading) guide
+for breaking changes and migration steps.
+
+## Overview
+
+A migration is a PHP file that describes the changes to apply to your database.
+A migration file can add, change or remove tables, columns, indexes and foreign keys.
+
+If we wanted to create a table, we could use a migration similar to this:
+
+``` php
+table('products');
+ $table->addColumn('name', 'string', [
+ 'default' => null,
+ 'limit' => 255,
+ 'null' => false,
+ ]);
+ $table->addColumn('description', 'text', [
+ 'default' => null,
+ 'null' => false,
+ ]);
+ $table->addColumn('created', 'datetime', [
+ 'default' => null,
+ 'null' => false,
+ ]);
+ $table->addColumn('modified', 'datetime', [
+ 'default' => null,
+ 'null' => false,
+ ]);
+ $table->create();
+ }
+}
+```
+
+When applied, this migration will add a table to your database named
+`products` with the following column definitions:
+
+- `id` column of type `integer` as primary key. This column is added
+ implicitly, but you can customize the name and type if necessary.
+- `name` column of type `string`
+- `description` column of type `text`
+- `created` column of type `datetime`
+- `modified` column of type `datetime`
+
+> [!NOTE]
+> Migrations are not automatically applied, you can apply and rollback
+> migrations with CLI commands.
+
+Once the file has been created in the **config/Migrations** folder, you can
+apply it:
+
+``` bash
+bin/cake migrations migrate
+```
+
+## Creating Migrations
+
+Migration files are stored in the **config/Migrations** directory of your
+application. The name of the migration files are prefixed with the date in
+which they were created, in the format **YYYYMMDDHHMMSS_MigrationName.php**.
+Here are examples of migration filenames:
+
+- **20160121163850_CreateProducts.php**
+- **20160210133047_AddRatingToProducts.php**
+
+The easiest way to create a migrations file is by using `bin/cake bake migration` CLI command:
+
+``` bash
+bin/cake bake migration CreateProducts
+```
+
+This will create an empty migration that you can edit to add any columns,
+indexes and foreign keys you need. See the [Creating A Table](writing-migrations#creating-a-table) section to
+learn more about using migrations to define tables.
+
+> [!NOTE]
+> Migrations need to be applied using `bin/cake migrations migrate` after
+> they have been created.
+
+### Migration file names
+
+When generating a migration, you can follow one of the following patterns
+to have additional skeleton code generated:
+
+- `/^(Create)(.*)/` Creates the specified table.
+- `/^(Drop)(.*)/` Drops the specified table.
+ Ignores specified field arguments
+- `/^(Add).*(?:To)(.*)/` Adds fields to the specified
+ table
+- `/^(Remove).*(?:From)(.*)/` Removes fields from the
+ specified table
+- `/^(Alter)(.*)/` Alters the specified table. An alias
+ for CreateTable and AddField.
+- `/^(Alter).*(?:On)(.*)/` Alters fields from the specified table.
+
+You can also use the `underscore_form` as the name for your migrations i.e.
+`create_products`.
+
+> [!WARNING]
+> Migration names are used as class names, and thus may collide with
+> other migrations if the class names are not unique. In this case, it may be
+> necessary to manually override the name at a later date, or simply change
+> the name you are specifying.
+
+### Creating a table
+
+You can use `bake migration` to create a table:
+
+``` bash
+bin/cake bake migration CreateProducts name:string description:text created modified
+```
+
+The command line above will generate a migration file that resembles:
+
+``` php
+table('products');
+ $table->addColumn('name', 'string', [
+ 'default' => null,
+ 'limit' => 255,
+ 'null' => false,
+ ]);
+ $table->addColumn('description', 'text', [
+ 'default' => null,
+ 'null' => false,
+ ]);
+ $table->addColumn('created', 'datetime', [
+ 'default' => null,
+ 'null' => false,
+ ]);
+ $table->addColumn('modified', 'datetime', [
+ 'default' => null,
+ 'null' => false,
+ ]);
+ $table->create();
+ }
+}
+```
+
+### Column syntax
+
+The `bake migration` command provides a compact syntax to define columns when
+generating a migration:
+
+``` bash
+bin/cake bake migration CreateProducts name:string description:text created modified
+```
+
+You can use the column syntax when creating tables and adding columns. You can
+also edit the migration after generation to add or customize the columns
+
+Columns on the command line follow the following pattern:
+
+ fieldName:fieldType?[length]:default[value]:indexType:indexName
+
+For instance, the following are all valid ways of specifying an email field:
+
+- `email:string?`
+- `email:string:unique`
+- `email:string?[50]`
+- `email:string:unique:EMAIL_INDEX`
+- `email:string[120]:unique:EMAIL_INDEX`
+
+While defining decimal columns, the `length` can be defined to have precision
+and scale, separated by a comma.
+
+- `amount:decimal[5,2]`
+- `amount:decimal?[5,2]`
+
+Columns with a question mark after the fieldType will make the column nullable.
+
+The `length` part is optional and should always be written between bracket.
+
+The `default[value]` part is optional and sets the default value for the column.
+Supported value types include:
+
+- Booleans: `true` or `false` - e.g., `active:boolean:default[true]`
+- Integers: `0`, `123`, `-456` - e.g., `count:integer:default[0]`
+- Floats: `1.5`, `-2.75` - e.g., `rate:decimal:default[1.5]`
+- Strings: `'hello'` or `"world"` (quoted) - e.g., `status:string:default['pending']`
+- Null: `null` or `NULL` - e.g., `description:text?:default[null]`
+- SQL expressions: `CURRENT_TIMESTAMP` - e.g., `created_at:datetime:default[CURRENT_TIMESTAMP]`
+
+Fields named `created` and `modified`, as well as any field with a `_at`
+suffix, will automatically be set to the type `datetime`.
+
+There are some heuristics to choosing fieldtypes when left unspecified or set to
+an invalid value. Default field type is `string`:
+
+- id: integer
+- created, modified, updated: datetime
+- latitude, longitude (or short forms lat, lng): decimal
+
+Additionally you can create an empty migrations file if you want full control
+over what needs to be executed, by omitting to specify a columns definition:
+
+``` bash
+bin/cake migrations create MyCustomMigration
+```
+
+See [Writing Migrations](writing-migrations) for more information on how to use `Table`
+objects to interact with tables and define schema changes.
+
+### Adding columns to an existing table
+
+If the migration name in the command line is of the form "AddXXXToYYY" and is
+followed by a list of column names and types then a migration file containing
+the code for creating the columns will be generated:
+
+``` bash
+bin/cake bake migration AddPriceToProducts price:decimal[5,2]
+```
+
+Executing the command line above will generate:
+
+``` php
+table('products');
+ $table->addColumn('price', 'decimal', [
+ 'default' => null,
+ 'null' => false,
+ 'precision' => 5,
+ 'scale' => 2,
+ ]);
+ $table->update();
+ }
+}
+```
+
+### Adding a column with an index
+
+It is also possible to add indexes to columns:
+
+``` bash
+bin/cake bake migration AddNameIndexToProducts name:string:index
+```
+
+will generate:
+
+``` php
+table('products');
+ $table->addColumn('name', 'string')
+ ->addColumn('email', 'string')
+ ->addIndex(['name'])
+ // add a unique index:
+ ->addIndex('email', ['unique' => true])
+ ->update();
+ }
+}
+```
+
+### Adding a column with a default value
+
+You can specify default values for columns using the `default[value]` syntax:
+
+``` bash
+bin/cake bake migration AddActiveToUsers active:boolean:default[true]
+```
+
+will generate:
+
+``` php
+table('users');
+ $table->addColumn('active', 'boolean', [
+ 'default' => true,
+ 'null' => false,
+ ]);
+ $table->update();
+ }
+}
+```
+
+You can combine default values with other options like nullable and indexes:
+
+``` bash
+bin/cake bake migration AddStatusToOrders status:string:default['pending']:unique
+```
+
+### Altering a column
+
+In the same way, you can generate a migration to alter a column by using the
+command line, if the migration name is of the form "AlterXXXOnYYY":
+
+``` bash
+bin/cake bake migration AlterPriceOnProducts name:float
+```
+
+will generate:
+
+``` php
+table('products');
+ $table->changeColumn('name', 'float');
+ $table->update();
+ }
+}
+```
+
+> [!WARNING]
+> Changing the type of a column can result in data loss if the
+> current and target column type are not compatible. For example converting
+> a varchar to float.
+
+### Removing a column
+
+In the same way, you can generate a migration to remove a column by using the
+command line, if the migration name is of the form "RemoveXXXFromYYY":
+
+``` bash
+bin/cake bake migration RemovePriceFromProducts price
+```
+
+creates the file:
+
+``` php
+table('products');
+ $table->removeColumn('price')
+ ->save();
+ }
+}
+```
+
+> [!NOTE]
+> The removeColumn command is not reversible, so must be called in the
+> up method. A corresponding addColumn call should be added to the
+> down method.
+
+## Generating migration snapshots from an existing database
+
+If you have a pre-existing database and want to start using
+migrations, or to version control the initial schema of your application's
+database, you can run the `bake migration_snapshot` command:
+
+``` bash
+bin/cake bake migration_snapshot Initial
+```
+
+It will generate a migration file called **YYYYMMDDHHMMSS_Initial.php**
+containing all the create statements for all tables in your database.
+
+By default, the snapshot will be created by connecting to the database defined
+in the `default` connection configuration. If you need to bake a snapshot from
+a different datasource, you can use the `--connection` option:
+
+``` bash
+bin/cake bake migration_snapshot Initial --connection my_other_connection
+```
+
+You can also make sure the snapshot includes only the tables for which you have
+defined the corresponding model classes by using the `--require-table` flag:
+
+``` bash
+bin/cake bake migration_snapshot Initial --require-table
+```
+
+When using the `--require-table` flag, the shell will look through your
+application `Table` classes and will only add the model tables in the snapshot.
+
+If you want to generate a snapshot without marking it as migrated (for example,
+for use in unit tests), you can use the `--generate-only` flag:
+
+``` bash
+bin/cake bake migration_snapshot Initial --generate-only
+```
+
+This will create the migration file but will not add an entry to the migrations
+tracking table, allowing you to move the file to a different location without causing
+"MISSING" status issues.
+
+The same logic will be applied implicitly if you wish to bake a snapshot for a
+plugin. To do so, you need to use the `--plugin` option:
+
+``` bash
+bin/cake bake migration_snapshot Initial --plugin MyPlugin
+```
+
+Only the tables which have a `Table` object model class defined will be added
+to the snapshot of your plugin.
+
+> [!NOTE]
+> When baking a snapshot for a plugin, the migration files will be created
+> in your plugin's **config/Migrations** directory.
+
+Be aware that when you bake a snapshot, it is automatically added to the
+migrations log table as migrated.
+
+## Generating a diff
+
+As migrations are applied and rolled back, the migrations plugin will generate
+a 'dump' file of your schema. If you make manual changes to your database schema
+outside of migrations, you can use `bake migration_diff` to generate
+a migration file that captures the difference between the current schema dump
+file and database schema. To do so, you can use the following command:
+
+``` bash
+bin/cake bake migration_diff NameOfTheMigrations
+```
+
+By default, the diff will be created by connecting to the database defined
+in the `default` connection configuration.
+If you need to bake a diff from a different datasource, you can use the
+`--connection` option:
+
+``` bash
+bin/cake bake migration_diff NameOfTheMigrations --connection my_other_connection
+```
+
+If you want to use the diff feature on an application that already has a
+migrations history, you need to manually create the dump file that will be used
+as comparison:
+
+``` bash
+bin/cake migrations dump
+```
+
+The database state must be the same as it would be if you just migrated all
+your migrations before you create a dump file.
+Once the dump file is generated, you can start doing changes in your database
+and use the `bake migration_diff` command whenever you see fit.
+
+> [!NOTE]
+> Migration diff generation can not detect column renamings.
+
+## Applying Migrations
+
+Once you have generated or written your migration file, you need to execute the
+following command to apply the changes to your database:
+
+``` bash
+# Run all the migrations
+bin/cake migrations migrate
+
+# Migrate to a specific version using the ``--target`` option
+# or ``-t`` for short.
+# The value is the timestamp that is prefixed to the migrations file name::
+bin/cake migrations migrate -t 20150103081132
+
+# By default, migration files are looked for in the **config/Migrations**
+# directory. You can specify the directory using the ``--source`` option
+# or ``-s`` for short.
+# The following example will run migrations in the **config/Alternate**
+# directory
+bin/cake migrations migrate -s Alternate
+
+# You can run migrations to a different connection than the ``default`` one
+# using the ``--connection`` option or ``-c`` for short
+bin/cake migrations migrate -c my_custom_connection
+
+# Migrations can also be run for plugins. Simply use the ``--plugin`` option
+# or ``-p`` for short
+bin/cake migrations migrate -p MyAwesomePlugin
+```
+
+## Reverting Migrations
+
+The rollback command is used to undo previous migrations executed by this
+plugin. It is the reverse action of the `migrate` command:
+
+``` bash
+# You can rollback to the previous migration by using the
+# ``rollback`` command::
+bin/cake migrations rollback
+
+# You can also pass a migration version number to rollback
+# to a specific version::
+bin/cake migrations rollback -t 20150103081132
+```
+
+You can also use the `--source`, `--connection` and `--plugin` options
+just like for the `migrate` command.
+
+## View Migrations Status
+
+The Status command prints a list of all migrations, along with their current
+status. You can use this command to determine which migrations have been run:
+
+``` bash
+bin/cake migrations status
+```
+
+You can also output the results as a JSON formatted string using the
+`--format` option (or `-f` for short):
+
+``` bash
+bin/cake migrations status --format json
+```
+
+You can also use the `--source`, `--connection` and `--plugin` options
+just like for the `migrate` command.
+
+### Cleaning up missing migrations
+
+Sometimes migration files may be deleted from the filesystem but still exist
+in the migrations tracking table. These migrations will be marked as **MISSING** in the
+status output. You can remove these entries from the tracking table using the
+`--cleanup` option:
+
+``` bash
+bin/cake migrations status --cleanup
+```
+
+This will remove all migration entries from the tracking table that no longer
+have corresponding migration files in the filesystem.
+
+## Marking a migration as migrated
+
+It can sometimes be useful to mark a set of migrations as migrated without
+actually running them. In order to do this, you can use the `mark_migrated`
+command. The command works seamlessly as the other commands.
+
+You can mark all migrations as migrated using this command:
+
+``` bash
+bin/cake migrations mark_migrated
+```
+
+You can also mark all migrations up to a specific version as migrated using
+the `--target` option:
+
+``` bash
+bin/cake migrations mark_migrated --target=20151016204000
+```
+
+If you do not want the targeted migration to be marked as migrated during the
+process, you can use the `--exclude` flag with it:
+
+``` bash
+bin/cake migrations mark_migrated --target=20151016204000 --exclude
+```
+
+Finally, if you wish to mark only the targeted migration as migrated, you can
+use the `--only` flag:
+
+``` bash
+bin/cake migrations mark_migrated --target=20151016204000 --only
+```
+
+You can also use the `--source`, `--connection` and `--plugin` options
+just like for the `migrate` command.
+
+> [!NOTE]
+> When you bake a snapshot with the `cake bake migration_snapshot`
+> command, the created migration will automatically be marked as migrated.
+> To prevent this behavior (e.g., for unit test migrations), use the
+> `--generate-only` flag.
+
+This command expects the migration version number as argument:
+
+``` bash
+bin/cake migrations mark_migrated 20150420082532
+```
+
+If you wish to mark all migrations as migrated, you can use the `all` special
+value. If you use it, it will mark all found migrations as migrated:
+
+``` bash
+bin/cake migrations mark_migrated all
+```
+
+## Seeding your database
+
+Seed classes are a good way to populate your database with default or starter
+data. They are also a great way to generate data for development environments.
+
+By default, seeds will be looked for in the `config/Seeds/` directory of
+your application. See the [Database Seeding](seeding) for how to build and use seed classes.
+
+## Generating a dump file
+
+The dump command creates a file to be used with the `bake migration_diff`
+command:
+
+``` bash
+bin/cake migrations dump
+```
+
+Each generated dump file is specific to the Connection it is generated from (and
+is suffixed as such). This allows the `bake migration_diff` command to
+properly compute diff in case your application is dealing with multiple database
+possibly from different database vendors.
+
+Dump files are created in the same directory as your migrations files.
+
+You can also use the `--source`, `--connection` and `--plugin` options
+just like for the `migrate` command.
+
+## Using Migrations for Tests
+
+If you are using migrations for your application schema you can also use those
+same migrations to build schema in your tests. In your application's
+`tests/bootstrap.php` file you can use the `Migrator` class to build schema
+when tests are run. The `Migrator` will use existing schema if it is current,
+and if the migration history that is in the database differs from what is in the
+filesystem, all tables will be dropped and migrations will be rerun from the
+beginning:
+
+``` php
+// in tests/bootstrap.php
+use Migrations\TestSuite\Migrator;
+
+$migrator = new Migrator();
+
+// Simple setup for with no plugins
+$migrator->run();
+
+// Run a non 'test' database
+$migrator->run(['connection' => 'test_other']);
+
+// Run migrations for plugins
+$migrator->run(['plugin' => 'Contacts']);
+
+// Run the Documents migrations on the test_docs connection.
+$migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']);
+```
+
+If you need to run multiple sets of migrations, those can be run as follows:
+
+``` php
+// Run migrations for plugin Contacts on the ``test`` connection, and Documents on the ``test_docs`` connection
+$migrator->runMany([
+ ['plugin' => 'Contacts'],
+ ['plugin' => 'Documents', 'connection' => 'test_docs']
+]);
+```
+
+If your database also contains tables that are not managed by your application
+like those created by PostGIS, then you can exclude those tables from the drop
+& truncate behavior using the `skip` option:
+
+``` php
+$migrator->run(['connection' => 'test', 'skip' => ['postgis*']]);
+```
+
+The `skip` option accepts a `fnmatch()` compatible pattern to exclude tables
+from drop & truncate operations.
+
+If you need to see additional debugging output from migrations are being run,
+you can enable a `debug` level logger.
+
+## Using Migrations In Plugins
+
+Plugins can also provide migration files. This makes plugins that are intended
+to be distributed much more portable and easy to install. All commands in the
+Migrations plugin support the `--plugin` or `-p` option that will scope the
+execution to the migrations relative to that plugin:
+
+``` bash
+bin/cake migrations status -p PluginName
+
+bin/cake migrations migrate -p PluginName
+```
+
+## Running Migrations in a non-shell environment
+
+While typical usage of migrations is from the command line, you can also run
+migrations from a non-shell environment, by using
+`Migrations\Migrations` class. This can be handy in case you are developing a plugin
+installer for a CMS for instance. The `Migrations` class allows you to run the
+following commands from the migrations shell:
+
+- migrate
+- rollback
+- markMigrated
+- status
+- seed
+
+Each of these commands has a method defined in the `Migrations` class.
+
+Here is how to use it:
+
+``` php
+use Migrations\Migrations;
+
+$migrations = new Migrations();
+
+// Will return an array of all migrations and their status
+$status = $migrations->status();
+
+// Will return true if success. If an error occurred, an exception will be thrown
+$migrate = $migrations->migrate();
+
+// Will return true if success. If an error occurred, an exception will be thrown
+$rollback = $migrations->rollback();
+
+// Will return true if success. If an error occurred, an exception will be thrown
+$markMigrated = $migrations->markMigrated(20150804222900);
+
+// Will return true if success. If an error occurred, an exception will be thrown
+$seeded = $migrations->seed();
+```
+
+The methods can accept an array of parameters that should match options from
+the commands:
+
+``` php
+use Migrations\Migrations;
+
+$migrations = new Migrations();
+
+// Will return an array of all migrations and their status
+$status = $migrations->status(['connection' => 'custom', 'source' => 'MyMigrationsFolder']);
+```
+
+You can pass any options the shell commands would take.
+The only exception is the `markMigrated` command which is expecting the
+version number of the migrations to mark as migrated as first argument. Pass
+the array of parameters as the second argument for this method.
+
+Optionally, you can pass these parameters in the constructor of the class.
+They will be used as default and this will prevent you from having to pass
+them on each method call:
+
+``` php
+use Migrations\Migrations;
+
+$migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']);
+
+// All the following calls will be done with the parameters passed to the Migrations class constructor
+$status = $migrations->status();
+$migrate = $migrations->migrate();
+```
+
+If you need to override one or more default parameters for one call, you can
+pass them to the method:
+
+``` php
+use Migrations\Migrations;
+
+$migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']);
+
+// This call will be made with the "custom" connection
+$status = $migrations->status();
+// This one with the "default" connection
+$migrate = $migrations->migrate(['connection' => 'default']);
+```
+
+
+
+## Feature Flags
+
+Migrations offers a few feature flags for compatibility. These features are disabled by default but can be enabled if required:
+
+- `unsigned_primary_keys`: Should Migrations create primary keys as unsigned integers? (default: `false`)
+- `unsigned_ints`: Should Migrations create all integer columns as unsigned? (default: `false`)
+- `column_null_default`: Should Migrations create columns as null by default? (default: `false`)
+- `add_timestamps_use_datetime`: Should Migrations use `DATETIME` type
+ columns for the columns added by `addTimestamps()`.
+
+Set them via Configure to enable (e.g. in `config/app.php`):
+
+``` text
+'Migrations' => [
+ 'unsigned_primary_keys' => true,
+ 'unsigned_ints' => true,
+ 'column_null_default' => true,
+],
+```
+
+> [!NOTE]
+> The `unsigned_primary_keys` and `unsigned_ints` options only affect MySQL databases.
+> When generating migrations with `bake migration_snapshot` or `bake migration_diff`,
+> the `signed` attribute will only be included in the output for unsigned columns
+> (as `'signed' => false`). Signed is the default for integer columns in MySQL, so
+> `'signed' => true` is never output.
+
+## Skipping the `schema.lock` file generation
+
+In order for the diff feature to work, a **.lock** file is generated everytime
+you migrate, rollback or bake a snapshot, to keep track of the state of your
+database schema at any given point in time. You can skip this file generation,
+for instance when deploying on your production environment, by using the
+`--no-lock` option for the aforementioned command:
+
+``` bash
+bin/cake migrations migrate --no-lock
+
+bin/cake migrations rollback --no-lock
+
+bin/cake bake migration_snapshot MyMigration --no-lock
+```
+
+## Deployment
+
+You should update your deployment scripts to run migrations when new code is
+deployed. Ideally you want to run migrations after the code is on your servers,
+but before the application code becomes active.
+
+After running migrations remember to clear the ORM cache so it renews the column
+metadata of your tables. Otherwise, you might end up having errors about
+columns not existing when performing operations on those new columns. The
+CakePHP Core includes a [Schema Cache Shell](https://book.cakephp.org/5/en/console-and-shells/schema-cache.html) that you
+can use to perform this operation:
+
+``` bash
+bin/cake migration migrate
+bin/cake schema_cache clear
+```
+
+## Alert of missing migrations
+
+You can use the `Migrations.PendingMigrations` middleware in local development
+to alert developers about new migrations that have not been applied:
+
+``` php
+use Migrations\Middleware\PendingMigrationsMiddleware;
+
+$config = [
+ 'plugins' => [
+ ... // Optionally include a list of plugins with migrations to check.
+ ],
+];
+
+$middlewareQueue
+ ... // ErrorHandler middleware
+ ->add(new PendingMigrationsMiddleware($config))
+ ... // rest
+```
+
+You can add `'app'` config key set to `false` if you are only interested in
+checking plugin migrations.
+
+You can temporarily disable the migration check by adding
+`skip-migration-check=1` to the URL query string
+
+## IDE autocomplete support
+
+The [IdeHelper plugin](https://github.com/dereuromark/cakephp-ide-helper) can help
+you to get more IDE support for the tables, their column names and possible column types.
+Specifically PHPStorm understands the meta information and can help you autocomplete those.
diff --git a/docs/en/seeding.md b/docs/en/seeding.md
new file mode 100644
index 000000000..c2d21c501
--- /dev/null
+++ b/docs/en/seeding.md
@@ -0,0 +1,585 @@
+# Database Seeding
+
+Seed classes are a great way to easily fill your database with data after
+it's created. By default, they are stored in the `config/Seeds` directory.
+
+> [!NOTE]
+> Database seeding is entirely optional, and Migrations does not create a Seeds
+> directory by default.
+
+## Creating a New Seed Class
+
+Migrations includes a command to easily generate a new seed class:
+
+``` bash
+$ bin/cake bake seed MyNewSeed
+```
+
+By default, it generates a traditional seed class with a named class:
+
+``` php
+call('Articles');` or `$this->call('ArticlesSeed');`
+
+Using the short name is recommended for cleaner, more concise code.
+
+### Anonymous Seed Classes
+
+Migrations also supports generating anonymous seed classes, which use PHP's
+anonymous class feature instead of named classes. This style is useful for:
+
+- Avoiding namespace declarations
+- Better PHPCS compatibility (no class name to filename matching required)
+- Simpler file structure without named class constraints
+
+To generate an anonymous seed class, use the `--style anonymous` option:
+
+``` bash
+$ bin/cake bake seed MyNewSeed --style anonymous
+```
+
+This generates a seed file using an anonymous class:
+
+``` php
+ [
+ 'style' => 'anonymous', // or 'traditional'
+],
+```
+
+### Seed Options
+
+``` bash
+# you specify the name of the table the seed files will alter by using the ``--table`` option
+bin/cake bake seed articles --table my_articles_table
+# You can specify a plugin to bake into
+bin/cake bake seed Articles --plugin PluginName
+
+# You can specify an alternative connection when generating a seed.
+bin/cake bake seed Articles --connection connection
+
+# Include data from the Articles table in your seed.
+bin/cake bake seed --data Articles
+```
+
+By default, it will export all the rows found in your table. You can limit the
+number of rows exported by using the `--limit` option:
+
+``` bash
+# Will only export the first 10 rows found
+bin/cake bake seed --data --limit 10 Articles
+```
+
+If you only want to include a selection of fields from the table in your seed
+file, you can use the `--fields` option. It takes the list of fields to
+include as a comma separated value string:
+
+``` bash
+# Will only export the fields `id`, `title` and `excerpt`
+bin/cake bake seed --data --fields id,title,excerpt Articles
+```
+
+> [!TIP]
+> Of course you can use both the `--limit` and `--fields` options in the
+> same command call.
+
+
+
+### Customizing Seed and Migration templates
+
+Because migrations uses [bake](https://book.cakephp.org/bake) under the hood
+you can customize the templates that migrations uses for creating seeds and
+migrations by creating templates in your application. Custom templates for
+migrations should be on one of the following paths:
+
+- `ROOT/templates/plugin/Migrations/bake/`
+- `ROOT/templates/bake/`
+
+For example, the seed templates are:
+
+- Traditional: `Seed/seed.twig` at **ROOT/templates/plugin/Migrations/bake/Seed/seed.twig**
+- Anonymous: `Seed/seed-anonymous.twig` at **ROOT/templates/plugin/Migrations/bake/Seed/seed-anonymous.twig**
+
+## The BaseSeed Class
+
+All Migrations seeds extend from the `BaseSeed` class.
+It provides the necessary support to create your seed classes. Seed
+classes are primarily used to insert test data.
+
+## The Run Method
+
+The run method is automatically invoked by Migrations when you execute the
+`cake seeds run` command. You should use this method to insert your test
+data.
+
+## Seed Execution Tracking
+
+Seeds track their execution state in the `cake_seeds` database table. By default,
+a seed will only run once. If you attempt to run a seed that has already been
+executed, it will be skipped with an "already executed" message.
+
+To re-run a seed that has already been executed, use the `--force` flag:
+
+``` bash
+bin/cake seeds run Users --force
+```
+
+You can check which seeds have been executed using the status command:
+
+``` bash
+bin/cake seeds status
+```
+
+To reset all seeds' execution state (allowing them to run again without `--force`):
+
+``` bash
+bin/cake seeds reset
+```
+
+> [!NOTE]
+> When re-running seeds with `--force`, be careful to ensure your seeds are
+> idempotent (safe to run multiple times) or they may create duplicate data.
+
+### Customizing the Seed Tracking Table
+
+By default, seed execution is tracked in a table named `cake_seeds`. You can
+customize this table name by configuring it in your `config/app.php` or
+`config/app_local.php`:
+
+``` php
+'Migrations' => [
+ 'seed_table' => 'my_custom_seeds_table',
+],
+```
+
+This is useful if you need to avoid table name conflicts or want to follow
+a specific naming convention in your database.
+
+## Idempotent Seeds
+
+Some seeds are designed to be run multiple times safely (idempotent), such as seeds
+that update configuration or reference data. For these seeds, you can override the
+`isIdempotent()` method:
+
+``` php
+execute("
+ INSERT INTO settings (setting_key, setting_value)
+ VALUES ('app_version', '2.0.0')
+ ON DUPLICATE KEY UPDATE setting_value = '2.0.0'
+ ");
+
+ // Or check before inserting
+ $exists = $this->fetchRow(
+ "SELECT COUNT(*) as count FROM settings WHERE setting_key = 'maintenance_mode'"
+ );
+
+ if ($exists['count'] == 0) {
+ $this->table('settings')->insert([
+ 'setting_key' => 'maintenance_mode',
+ 'setting_value' => 'false',
+ ])->save();
+ }
+ }
+}
+```
+
+When `isIdempotent()` returns `true`:
+
+- The seed will run **every time** you execute `seeds run`
+- The last execution time is still tracked in the `cake_seeds` table
+- The `seeds status` command will show the seed as `(idempotent)`
+- You must ensure the seed's `run()` method handles duplicate executions safely
+
+This is useful for:
+
+- Configuration seeds that should always reflect current values
+- Reference data that may need periodic updates
+- Seeds that use `INSERT ... ON DUPLICATE KEY UPDATE` or similar patterns
+- Development/testing seeds that need to run repeatedly
+
+> [!WARNING]
+> Only mark a seed as idempotent if you've verified it's safe to run multiple times.
+> Otherwise, you may create duplicate data or other unexpected behavior.
+
+## The Init Method
+
+The `init()` method is run by Migrations before the run method if it exists. This
+can be used to initialize properties of the Seed class before using run.
+
+## The Should Execute Method
+
+The `shouldExecute()` method is run by Migrations before executing the seed.
+This can be used to prevent the seed from being executed at this time. It always
+returns true by default. You can override it in your custom `BaseSeed`
+implementation.
+
+## Foreign Key Dependencies
+
+Often you'll find that seeds need to run in a particular order, so they don't
+violate foreign key constraints. To define this order, you can implement the
+`getDependencies()` method that returns an array of seeds to run before the
+current seed:
+
+``` php
+ [!NOTE]
+> Dependencies that have already been executed (according to the `cake_seeds`
+> table) will be skipped, unless you use the `--force` flag which will
+> re-execute all seeds including dependencies.
+
+## Calling a Seed from another Seed
+
+Usually when seeding, the order in which to insert the data must be respected
+to not encounter constraint violations. Since seeds are executed in an
+alphabetical order by default, you can use the `\Migrations\BaseSeed::call()`
+method to define your own sequence of seeds execution:
+
+``` php
+call('Another'); // Short name without 'Seed' suffix
+ $this->call('YetAnother'); // Short name without 'Seed' suffix
+
+ // You can use the plugin dot syntax to call seeds from a plugin
+ $this->call('PluginName.FromPlugin');
+ }
+}
+```
+
+You can also use the full seed name including the `Seed` suffix:
+
+``` php
+$this->call('AnotherSeed');
+$this->call('YetAnotherSeed');
+$this->call('PluginName.FromPluginSeed');
+```
+
+Both forms are supported and work identically.
+
+## Inserting Data
+
+Seed classes can also use the familiar `Table` object to insert data. You can
+retrieve an instance of the Table object by calling the `table()` method from
+within your seed class and then use the `insert()` method to insert data:
+
+``` php
+ 'foo',
+ 'created' => date('Y-m-d H:i:s'),
+ ],[
+ 'body' => 'bar',
+ 'created' => date('Y-m-d H:i:s'),
+ ]
+ ];
+
+ $posts = $this->table('posts');
+ $posts->insert($data)
+ ->saveData();
+ }
+}
+```
+
+> [!NOTE]
+> You must call the `saveData()` method to commit your data to the table.
+> Migrations will buffer data until you do so.
+
+## Insert Modes
+
+In addition to the standard `insert()` method, Migrations provides specialized
+insert methods for handling conflicts with existing data.
+
+### Insert or Skip
+
+The `insertOrSkip()` method inserts rows but silently skips any that would
+violate a unique constraint:
+
+``` php
+ 'USD', 'name' => 'US Dollar'],
+ ['code' => 'EUR', 'name' => 'Euro'],
+ ];
+
+ $this->table('currencies')
+ ->insertOrSkip($data)
+ ->saveData();
+ }
+}
+```
+
+### Insert or Update (Upsert)
+
+The `insertOrUpdate()` method performs an "upsert" operation - inserting new
+rows and updating existing rows that conflict on unique columns:
+
+``` php
+ 'USD', 'rate' => 1.0000],
+ ['code' => 'EUR', 'rate' => 0.9234],
+ ];
+
+ $this->table('exchange_rates')
+ ->insertOrUpdate($data, ['rate'], ['code'])
+ ->saveData();
+ }
+}
+```
+
+The method takes three arguments:
+
+- `$data`: The rows to insert (same format as `insert()`)
+- `$updateColumns`: Which columns to update when a conflict occurs
+- `$conflictColumns`: Which columns define uniqueness (must have a unique index)
+
+> [!WARNING]
+> Database-specific behavior differences:
+>
+> **MySQL**: Uses `ON DUPLICATE KEY UPDATE`. The `$conflictColumns` parameter
+> is ignored because MySQL automatically applies the update to *all* unique
+> constraint violations on the table. Passing `$conflictColumns` will trigger
+> a warning. If your table has multiple unique constraints, be aware that a
+> conflict on *any* of them will trigger the update.
+>
+> **PostgreSQL/SQLite**: Uses `ON CONFLICT (...) DO UPDATE SET`. The
+> `$conflictColumns` parameter is required and specifies exactly which unique
+> constraint should trigger the update. A `RuntimeException` will be thrown
+> if this parameter is empty.
+>
+> **SQL Server**: Not currently supported. Use separate insert/update logic.
+
+## Truncating Tables
+
+In addition to inserting data Migrations makes it trivial to empty your tables using the
+SQL TRUNCATE command:
+
+``` php
+ 'foo',
+ 'created' => date('Y-m-d H:i:s'),
+ ],
+ [
+ 'body' => 'bar',
+ 'created' => date('Y-m-d H:i:s'),
+ ]
+ ];
+
+ $posts = $this->table('posts');
+ $posts->insert($data)
+ ->saveData();
+
+ // empty the table
+ $posts->truncate();
+ }
+}
+```
+
+> [!NOTE]
+> SQLite doesn't natively support the `TRUNCATE` command so behind the scenes
+> `DELETE FROM` is used. It is recommended to call the `VACUUM` command
+> after truncating a table. Migrations does not do this automatically.
+
+## Executing Seed Classes
+
+This is the easy part. To seed your database, simply use the `seeds run` command:
+
+``` bash
+$ bin/cake seeds run
+```
+
+By default, Migrations will execute all available seed classes. If you would like to
+run a specific seed, simply pass in the seed name as an argument.
+You can use either the short name (without the `Seed` suffix) or the full name:
+
+``` bash
+$ bin/cake seeds run User
+# or
+$ bin/cake seeds run UserSeed
+```
+
+Both commands work identically.
+
+You can also run multiple seeds by separating them with commas:
+
+``` bash
+$ bin/cake seeds run User,Permission,Log
+# or with full names
+$ bin/cake seeds run UserSeed,PermissionSeed,LogSeed
+```
+
+You can also use the -v parameter for more output verbosity:
+
+``` bash
+$ bin/cake seeds run -v
+```
+
+The Migrations seed functionality provides a simple mechanism to easily and repeatably
+insert test data into your database, this is great for development environment
+sample data or getting state for demos.
diff --git a/docs/en/upgrading-to-builtin-backend.md b/docs/en/upgrading-to-builtin-backend.md
new file mode 100644
index 000000000..59a034c1b
--- /dev/null
+++ b/docs/en/upgrading-to-builtin-backend.md
@@ -0,0 +1,187 @@
+# Upgrading to the builtin backend
+
+As of migrations 4.3 there is a new migrations backend that uses CakePHP's
+database abstractions and ORM. In 4.4, the `builtin` backend became the
+default backend. As of migrations 5.0, phinx has been removed as a dependency
+and only the builtin backend is supported. This greatly reduces the dependency
+footprint of migrations.
+
+## What is the same?
+
+Your migrations shouldn't have to change much to adapt to the new backend.
+The builtin backend provides similar functionality to what was available with
+phinx. If your migrations don't work in a way that could be addressed by the
+changes outlined below, please open an issue.
+
+## What is different?
+
+### Command Structure Changes
+
+As of migrations 5.0, the command structure has changed. The old phinx wrapper
+commands have been removed and replaced with new command names:
+
+**Seeds:**
+
+``` bash
+# Old (4.x and earlier)
+bin/cake migrations seed
+bin/cake migrations seed --seed Articles
+
+# New (5.x and later)
+bin/cake seeds run
+bin/cake seeds run Articles
+```
+
+The new commands are:
+
+- `bin/cake seeds run` - Run seed classes
+- `bin/cake seeds status` - Show seed execution status
+- `bin/cake seeds reset` - Reset seed execution tracking
+- `bin/cake bake seed` - Generate new seed classes
+
+#### Maintaining Backward Compatibility
+
+If you need to maintain the old command structure for existing scripts or CI/CD
+pipelines, you can add command aliases in your application. In your
+`src/Application.php` file, add the following to the `console()` method:
+
+``` php
+public function console(CommandCollection $commands): CommandCollection
+{
+ // Add your application's commands
+ $commands = $this->addConsoleCommands($commands);
+
+ // Add backward compatibility aliases for migrations 4.x commands
+ $commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
+
+ return $commands;
+}
+```
+
+For multiple aliases, you can add them all together:
+
+``` php
+// Add multiple backward compatibility aliases
+$commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
+$commands->add('migrations seed:run', \Migrations\Command\SeedCommand::class);
+$commands->add('migrations seed:status', \Migrations\Command\SeedStatusCommand::class);
+```
+
+This allows gradual migration of scripts and documentation without modifying the
+migrations plugin or creating wrapper command classes.
+
+### API Changes
+
+If your migrations are using the `AdapterInterface` to fetch rows or update
+rows you will need to update your code. If you use `Adapter::query()` to
+execute queries, the return of this method is now
+`Cake\Database\StatementInterface` instead. This impacts `fetchAll()`,
+and `fetch()`:
+
+``` php
+// This
+$stmt = $this->getAdapter()->query('SELECT * FROM articles');
+$rows = $stmt->fetchAll();
+
+// Now needs to be
+$stmt = $this->getAdapter()->query('SELECT * FROM articles');
+$rows = $stmt->fetchAll('assoc');
+```
+
+Similar changes are for fetching a single row:
+
+``` php
+// This
+$stmt = $this->getAdapter()->query('SELECT * FROM articles');
+$rows = $stmt->fetch();
+
+// Now needs to be
+$stmt = $this->getAdapter()->query('SELECT * FROM articles');
+$rows = $stmt->fetch('assoc');
+```
+
+## Unified Migrations Table
+
+As of migrations 5.x, there is a new unified `cake_migrations` table that
+replaces the legacy `phinxlog` tables. This provides several benefits:
+
+- **Single table for all migrations**: Instead of separate `phinxlog` (app)
+ and `{plugin}_phinxlog` (plugins) tables, all migrations are tracked in
+ one `cake_migrations` table with a `plugin` column.
+- **Simpler database schema**: Fewer migration tracking tables to manage.
+- **Better plugin support**: Plugin migrations are properly namespaced.
+
+### Backward Compatibility
+
+For existing applications with `phinxlog` tables:
+
+- **Automatic detection**: If any `phinxlog` table exists, migrations will
+ continue using the legacy tables automatically.
+- **No forced migration**: Existing applications don't need to change anything.
+- **Opt-in upgrade**: You can migrate to the new table when you're ready.
+
+### Configuration
+
+The `Migrations.legacyTables` configuration option controls the behavior:
+
+``` php
+// config/app.php or config/app_local.php
+'Migrations' => [
+ // null (default): Autodetect - use legacy if phinxlog tables exist
+ // false: Force use of new cake_migrations table
+ // true: Force use of legacy phinxlog tables
+ 'legacyTables' => null,
+],
+```
+
+### Upgrading to the Unified Table
+
+To migrate from `phinxlog` tables to the new `cake_migrations` table:
+
+1. **Preview the upgrade** (dry run):
+
+ ``` bash
+ bin/cake migrations upgrade --dry-run
+ ```
+
+2. **Run the upgrade**:
+
+ ``` bash
+ bin/cake migrations upgrade
+ ```
+
+3. **Update your configuration**:
+
+ ``` php
+ // config/app.php
+ 'Migrations' => [
+ 'legacyTables' => false,
+ ],
+ ```
+
+4. **Optionally drop phinx tables**: Your migration history is preserved
+ by default. Use `--drop-tables` to drop the `phinxlog`tables after
+ verifying your migrations run correctly.
+
+ ``` bash
+ bin/cake migrations upgrade --drop-tables
+ ```
+
+### Rolling Back
+
+If you need to revert to phinx tables after upgrading:
+
+1. Set `'legacyTables' => true` in your configuration.
+
+> [!WARNING]
+> You cannot rollback after running `upgrade --drop-tables`.
+
+### New Installations
+
+For new applications without any existing `phinxlog` tables, the unified
+`cake_migrations` table is used automatically. No configuration is needed.
+
+## Problems with the builtin backend?
+
+If your migrations contain errors when run with the builtin backend, please
+open [an issue](https://github.com/cakephp/migrations/issues/new).
diff --git a/docs/en/upgrading-to-builtin-backend.rst b/docs/en/upgrading-to-builtin-backend.rst
deleted file mode 100644
index ea2909433..000000000
--- a/docs/en/upgrading-to-builtin-backend.rst
+++ /dev/null
@@ -1,198 +0,0 @@
-Upgrading to the builtin backend
-################################
-
-As of migrations 4.3 there is a new migrations backend that uses CakePHP's
-database abstractions and ORM. In 4.4, the ``builtin`` backend became the
-default backend. As of migrations 5.0, phinx has been removed as a dependency
-and only the builtin backend is supported. This greatly reduces the dependency
-footprint of migrations.
-
-What is the same?
-=================
-
-Your migrations shouldn't have to change much to adapt to the new backend.
-The builtin backend provides similar functionality to what was available with
-phinx. If your migrations don't work in a way that could be addressed by the
-changes outlined below, please open an issue.
-
-What is different?
-==================
-
-Command Structure Changes
--------------------------
-
-As of migrations 5.0, the command structure has changed. The old phinx wrapper
-commands have been removed and replaced with new command names:
-
-**Seeds:**
-
-.. code-block:: bash
-
- # Old (4.x and earlier)
- bin/cake migrations seed
- bin/cake migrations seed --seed Articles
-
- # New (5.x and later)
- bin/cake seeds run
- bin/cake seeds run Articles
-
-The new commands are:
-
-- ``bin/cake seeds run`` - Run seed classes
-- ``bin/cake seeds status`` - Show seed execution status
-- ``bin/cake seeds reset`` - Reset seed execution tracking
-- ``bin/cake bake seed`` - Generate new seed classes
-
-Maintaining Backward Compatibility
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If you need to maintain the old command structure for existing scripts or CI/CD
-pipelines, you can add command aliases in your application. In your
-``src/Application.php`` file, add the following to the ``console()`` method:
-
-.. code-block:: php
-
- public function console(CommandCollection $commands): CommandCollection
- {
- // Add your application's commands
- $commands = $this->addConsoleCommands($commands);
-
- // Add backward compatibility aliases for migrations 4.x commands
- $commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
-
- return $commands;
- }
-
-For multiple aliases, you can add them all together:
-
-.. code-block:: php
-
- // Add multiple backward compatibility aliases
- $commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
- $commands->add('migrations seed:run', \Migrations\Command\SeedCommand::class);
- $commands->add('migrations seed:status', \Migrations\Command\SeedStatusCommand::class);
-
-This allows gradual migration of scripts and documentation without modifying the
-migrations plugin or creating wrapper command classes.
-
-API Changes
------------
-
-If your migrations are using the ``AdapterInterface`` to fetch rows or update
-rows you will need to update your code. If you use ``Adapter::query()`` to
-execute queries, the return of this method is now
-``Cake\Database\StatementInterface`` instead. This impacts ``fetchAll()``,
-and ``fetch()``::
-
- // This
- $stmt = $this->getAdapter()->query('SELECT * FROM articles');
- $rows = $stmt->fetchAll();
-
- // Now needs to be
- $stmt = $this->getAdapter()->query('SELECT * FROM articles');
- $rows = $stmt->fetchAll('assoc');
-
-Similar changes are for fetching a single row::
-
- // This
- $stmt = $this->getAdapter()->query('SELECT * FROM articles');
- $rows = $stmt->fetch();
-
- // Now needs to be
- $stmt = $this->getAdapter()->query('SELECT * FROM articles');
- $rows = $stmt->fetch('assoc');
-
-Unified Migrations Table
-========================
-
-As of migrations 5.x, there is a new unified ``cake_migrations`` table that
-replaces the legacy ``phinxlog`` tables. This provides several benefits:
-
-- **Single table for all migrations**: Instead of separate ``phinxlog`` (app)
- and ``{plugin}_phinxlog`` (plugins) tables, all migrations are tracked in
- one ``cake_migrations`` table with a ``plugin`` column.
-- **Simpler database schema**: Fewer migration tracking tables to manage.
-- **Better plugin support**: Plugin migrations are properly namespaced.
-
-Backward Compatibility
-----------------------
-
-For existing applications with ``phinxlog`` tables:
-
-- **Automatic detection**: If any ``phinxlog`` table exists, migrations will
- continue using the legacy tables automatically.
-- **No forced migration**: Existing applications don't need to change anything.
-- **Opt-in upgrade**: You can migrate to the new table when you're ready.
-
-Configuration
--------------
-
-The ``Migrations.legacyTables`` configuration option controls the behavior:
-
-.. code-block:: php
-
- // config/app.php or config/app_local.php
- 'Migrations' => [
- // null (default): Autodetect - use legacy if phinxlog tables exist
- // false: Force use of new cake_migrations table
- // true: Force use of legacy phinxlog tables
- 'legacyTables' => null,
- ],
-
-Upgrading to the Unified Table
-------------------------------
-
-To migrate from ``phinxlog`` tables to the new ``cake_migrations`` table:
-
-1. **Preview the upgrade** (dry run):
-
- .. code-block:: bash
-
- bin/cake migrations upgrade --dry-run
-
-2. **Run the upgrade**:
-
- .. code-block:: bash
-
- bin/cake migrations upgrade
-
-3. **Update your configuration**:
-
- .. code-block:: php
-
- // config/app.php
- 'Migrations' => [
- 'legacyTables' => false,
- ],
-
-4. **Optionally drop phinx tables**: Your migration history is preserved
- by default. Use ``--drop-tables`` to drop the ``phinxlog``tables after
- verifying your migrations run correctly.
-
- .. code-block:: bash
-
- bin/cake migrations upgrade --drop-tables
-
-Rolling Back
-------------
-
-If you need to revert to phinx tables after upgrading:
-
-1. Set ``'legacyTables' => true`` in your configuration.
-
-.. warning::
-
- You cannot rollback after running ``upgrade --drop-tables``.
-
-
-New Installations
------------------
-
-For new applications without any existing ``phinxlog`` tables, the unified
-``cake_migrations`` table is used automatically. No configuration is needed.
-
-Problems with the builtin backend?
-==================================
-
-If your migrations contain errors when run with the builtin backend, please
-open `an issue `_.
diff --git a/docs/en/upgrading.md b/docs/en/upgrading.md
new file mode 100644
index 000000000..75b95a539
--- /dev/null
+++ b/docs/en/upgrading.md
@@ -0,0 +1,162 @@
+# Upgrading from 4.x to 5.x
+
+Migrations 5.x includes significant changes from 4.x. This guide outlines
+the breaking changes and what you need to update when upgrading.
+
+## Requirements
+
+- **PHP 8.2+** is now required (was PHP 8.1+)
+- **CakePHP 5.3+** is now required
+- **Phinx has been removed** - The builtin backend is now the only supported backend
+
+If you were already using the builtin backend in 4.x (introduced in 4.3, default in 4.4),
+the upgrade should be straightforward. See [Upgrading to the builtin backend](upgrading-to-builtin-backend) for more
+details on API differences between the phinx and builtin backends.
+
+## Command Changes
+
+The phinx wrapper commands have been removed. The new command structure is:
+
+### Migrations
+
+The migration commands remain unchanged:
+
+``` bash
+bin/cake migrations migrate
+bin/cake migrations rollback
+bin/cake migrations status
+bin/cake migrations mark_migrated
+bin/cake migrations dump
+```
+
+### Seeds
+
+Seed commands have changed:
+
+``` bash
+# 4.x # 5.x
+bin/cake migrations seed bin/cake seeds run
+bin/cake migrations seed --seed X bin/cake seeds run X
+```
+
+The new seed commands are:
+
+- `bin/cake seeds run` - Run seed classes
+- `bin/cake seeds run SeedName` - Run a specific seed
+- `bin/cake seeds status` - Show seed execution status
+- `bin/cake seeds reset` - Reset seed execution tracking
+
+### Maintaining Backward Compatibility
+
+If you need to maintain the old `migrations seed` command for existing scripts or
+CI/CD pipelines, you can add command aliases in your `src/Application.php`:
+
+``` php
+public function console(CommandCollection $commands): CommandCollection
+{
+ $commands = $this->addConsoleCommands($commands);
+
+ // Add backward compatibility alias
+ $commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
+
+ return $commands;
+}
+```
+
+## Removed Classes and Namespaces
+
+The following have been removed in 5.x:
+
+- `Migrations\Command\Phinx\*` - All phinx wrapper commands
+- `Migrations\Command\MigrationsCommand` - Use `bin/cake migrations` entry point
+- `Migrations\Command\MigrationsSeedCommand` - Use `bin/cake seeds run`
+- `Migrations\Command\MigrationsCacheBuildCommand` - Schema cache is managed differently
+- `Migrations\Command\MigrationsCacheClearCommand` - Schema cache is managed differently
+- `Migrations\Command\MigrationsCreateCommand` - Use `bin/cake bake migration`
+
+If you have code that directly references any of these classes, you will need to update it.
+
+## API Changes
+
+### Adapter Query Results
+
+If your migrations use `AdapterInterface::query()` to fetch rows, the return type has
+changed from a phinx result to `Cake\Database\StatementInterface`:
+
+``` php
+// 4.x (phinx)
+$stmt = $this->getAdapter()->query('SELECT * FROM articles');
+$rows = $stmt->fetchAll();
+$row = $stmt->fetch();
+
+// 5.x (builtin)
+$stmt = $this->getAdapter()->query('SELECT * FROM articles');
+$rows = $stmt->fetchAll('assoc');
+$row = $stmt->fetch('assoc');
+```
+
+## New Features in 5.x
+
+5.x includes several new features:
+
+### Seed Tracking
+
+Seeds are now tracked in a `cake_seeds` table by default, preventing accidental re-runs.
+Use `--force` to run a seed again, or `bin/cake seeds reset` to clear tracking.
+See [Database Seeding](seeding) for more details.
+
+### Check Constraints
+
+Support for database check constraints via `addCheckConstraint()`.
+See [Writing Migrations](writing-migrations) for usage details.
+
+### MySQL ALTER Options
+
+Support for `ALGORITHM` and `LOCK` options on MySQL ALTER TABLE operations,
+allowing control over how MySQL performs schema changes.
+
+### insertOrSkip() for Seeds
+
+New `insertOrSkip()` method for seeds to insert records only if they don't already exist,
+making seeds more idempotent.
+
+## Foreign Key Constraint Naming
+
+Starting in 5.x, when you use `addForeignKey()` without providing an explicit constraint
+name, migrations will auto-generate a name using the pattern `{table}_{columns}`.
+
+Previously, MySQL would auto-generate constraint names (like `articles_ibfk_1`), while
+PostgreSQL and SQL Server used migrations-generated names. Now all adapters use the same
+consistent naming pattern.
+
+**Impact on existing migrations:**
+
+If you have existing migrations that use `addForeignKey()` without explicit names, and
+later migrations that reference those constraints by name (e.g., in `dropForeignKey()`),
+the generated names may differ between old and new migrations. This could cause
+`dropForeignKey()` to fail if it's looking for a name that doesn't exist.
+
+**Recommendations:**
+
+1. For new migrations, you can rely on auto-generated names or provide explicit names
+2. If you have rollback issues with existing migrations, you may need to update them
+ to use explicit constraint names
+3. The auto-generated names include conflict resolution - if `{table}_{columns}` already
+ exists, a counter suffix is added (`_2`, `_3`, etc.)
+
+**Name length limits:**
+
+Auto-generated names are truncated to respect database limits:
+
+- MySQL: 61 characters (64 - 3 for counter suffix)
+- PostgreSQL: 60 characters (63 - 3)
+- SQL Server: 125 characters (128 - 3)
+- SQLite: No limit
+
+## Migration File Compatibility
+
+Your existing migration files should work without changes in most cases. The builtin backend
+provides the same API as phinx for common operations.
+
+If you encounter issues with existing migrations, please report them at
+
diff --git a/docs/en/using-the-query-builder.md b/docs/en/using-the-query-builder.md
new file mode 100644
index 000000000..297d11359
--- /dev/null
+++ b/docs/en/using-the-query-builder.md
@@ -0,0 +1,260 @@
+# Using the Query Builder
+
+It is not uncommon to pair database structure changes with data changes. For example, you may want to
+migrate the data in a couple columns from the users to a newly created table. For this type of scenarios,
+Migrations provides access to a Query builder object, that you may use to execute complex `SELECT`, `UPDATE`,
+`INSERT` or `DELETE` statements.
+
+The Query builder is provided by the [cakephp/database](https://github.com/cakephp/database) project, and should
+be easy to work with as it resembles very closely plain SQL. Accesing the query builder is done by calling the
+`getQueryBuilder(string $type)` function. The `string $type` options are 'select', 'insert', 'update' and \`'delete'\`:
+
+``` php
+getQueryBuilder('select');
+ $statement = $builder->select('*')->from('users')->execute();
+ var_dump($statement->fetchAll());
+ }
+}
+```
+
+## Selecting Fields
+
+Adding fields to the SELECT clause:
+
+``` php
+select(['id', 'title', 'body']);
+
+// Results in SELECT id AS pk, title AS aliased_title, body ...
+$builder->select(['pk' => 'id', 'aliased_title' => 'title', 'body']);
+
+// Use a closure
+$builder->select(function ($builder) {
+ return ['id', 'title', 'body'];
+});
+```
+
+## Where Conditions
+
+Generating conditions:
+
+``` php
+// WHERE id = 1
+$builder->where(['id' => 1]);
+
+// WHERE id > 1
+$builder->where(['id >' => 1]);
+```
+
+As you can see you can use any operator by placing it with a space after the field name. Adding multiple conditions is easy as well:
+
+``` php
+where(['id >' => 1])->andWhere(['title' => 'My Title']);
+
+// Equivalent to
+$builder->where(['id >' => 1, 'title' => 'My title']);
+
+// WHERE id > 1 OR title = 'My title'
+$builder->where(['OR' => ['id >' => 1, 'title' => 'My title']]);
+```
+
+For even more complex conditions you can use closures and expression objects:
+
+``` php
+select('*')
+ ->from('articles')
+ ->where(function ($exp) {
+ return $exp
+ ->eq('author_id', 2)
+ ->eq('published', true)
+ ->notEq('spam', true)
+ ->gt('view_count', 10);
+ });
+```
+
+Which results in:
+
+``` sql
+SELECT * FROM articles
+WHERE
+ author_id = 2
+ AND published = 1
+ AND spam != 1
+ AND view_count > 10
+```
+
+Combining expressions is also possible:
+
+``` php
+select('*')
+ ->from('articles')
+ ->where(function ($exp) {
+ $orConditions = $exp->or_(['author_id' => 2])
+ ->eq('author_id', 5);
+ return $exp
+ ->not($orConditions)
+ ->lte('view_count', 10);
+ });
+```
+
+It generates:
+
+``` sql
+SELECT *
+FROM articles
+WHERE
+ NOT (author_id = 2 OR author_id = 5)
+ AND view_count <= 10
+```
+
+When using the expression objects you can use the following methods to create conditions:
+
+- `eq()` Creates an equality condition.
+- `notEq()` Create an inequality condition
+- `like()` Create a condition using the `LIKE` operator.
+- `notLike()` Create a negated `LIKE` condition.
+- `in()` Create a condition using `IN`.
+- `notIn()` Create a negated condition using `IN`.
+- `gt()` Create a `>` condition.
+- `gte()` Create a `>=` condition.
+- `lt()` Create a `<` condition.
+- `lte()` Create a `<=` condition.
+- `isNull()` Create an `IS NULL` condition.
+- `isNotNull()` Create a negated `IS NULL` condition.
+
+## Aggregates and SQL Functions
+
+``` php
+select(['count' => $builder->func()->count('*')]);
+```
+
+A number of commonly used functions can be created with the func() method:
+
+- `sum()` Calculate a sum. The arguments will be treated as literal values.
+- `avg()` Calculate an average. The arguments will be treated as literal values.
+- `min()` Calculate the min of a column. The arguments will be treated as literal values.
+- `max()` Calculate the max of a column. The arguments will be treated as literal values.
+- `count()` Calculate the count. The arguments will be treated as literal values.
+- `concat()` Concatenate two values together. The arguments are treated as bound parameters unless marked as literal.
+- `coalesce()` Coalesce values. The arguments are treated as bound parameters unless marked as literal.
+- `dateDiff()` Get the difference between two dates/times. The arguments are treated as bound parameters unless marked as literal.
+- `now()` Take either 'time' or 'date' as an argument allowing you to get either the current time, or current date.
+
+When providing arguments for SQL functions, there are two kinds of parameters you can use,
+literal arguments and bound parameters. Literal parameters allow you to reference columns or
+other SQL literals. Bound parameters can be used to safely add user data to SQL functions. For example:
+
+``` php
+func()->concat([
+ 'title' => 'literal',
+ ' NEW'
+]);
+$query->select(['title' => $concat]);
+```
+
+## Getting Results out of a Query
+
+Once you’ve made your query, you’ll want to retrieve rows from it. There are a few ways of doing this:
+
+``` php
+execute()->fetchAll('assoc');
+```
+
+## Creating an Insert Query
+
+Creating insert queries is also possible:
+
+``` php
+getQueryBuilder('insert');
+$builder
+ ->insert(['first_name', 'last_name'])
+ ->into('users')
+ ->values(['first_name' => 'Steve', 'last_name' => 'Jobs'])
+ ->values(['first_name' => 'Jon', 'last_name' => 'Snow'])
+ ->execute();
+```
+
+For increased performance, you can use another builder object as the values for an insert query:
+
+``` php
+getQueryBuilder('select');
+$namesQuery
+ ->select(['fname', 'lname'])
+ ->from('users')
+ ->where(['is_active' => true]);
+
+$builder = $this->getQueryBuilder('insert');
+$st = $builder
+ ->insert(['first_name', 'last_name'])
+ ->into('names')
+ ->values($namesQuery)
+ ->execute();
+
+var_dump($st->lastInsertId('names', 'id'));
+```
+
+The above code will generate:
+
+``` sql
+INSERT INTO names (first_name, last_name)
+ (SELECT fname, lname FROM USERS where is_active = 1)
+```
+
+## Creating an update Query
+
+Creating update queries is similar to both inserting and selecting:
+
+``` php
+getQueryBuilder('update');
+$builder
+ ->update('users')
+ ->set('fname', 'Snow')
+ ->where(['fname' => 'Jon'])
+ ->execute();
+```
+
+## Creating a Delete Query
+
+Finally, delete queries:
+
+``` php
+getQueryBuilder('delete');
+$builder
+ ->delete('users')
+ ->where(['accepted_gdpr' => false])
+ ->execute();
+```
diff --git a/docs/en/using-the-query-builder.rst b/docs/en/using-the-query-builder.rst
deleted file mode 100644
index 64a7617cb..000000000
--- a/docs/en/using-the-query-builder.rst
+++ /dev/null
@@ -1,272 +0,0 @@
-Using the Query Builder
-#######################
-
-It is not uncommon to pair database structure changes with data changes. For example, you may want to
-migrate the data in a couple columns from the users to a newly created table. For this type of scenarios,
-Migrations provides access to a Query builder object, that you may use to execute complex ``SELECT``, ``UPDATE``,
-``INSERT`` or ``DELETE`` statements.
-
-The Query builder is provided by the `cakephp/database `_ project, and should
-be easy to work with as it resembles very closely plain SQL. Accesing the query builder is done by calling the
-``getQueryBuilder(string $type)`` function. The ``string $type`` options are `'select'`, `'insert'`, `'update'` and `'delete'`::
-
- getQueryBuilder('select');
- $statement = $builder->select('*')->from('users')->execute();
- var_dump($statement->fetchAll());
- }
- }
-
-Selecting Fields
-----------------
-
-Adding fields to the SELECT clause::
-
- select(['id', 'title', 'body']);
-
- // Results in SELECT id AS pk, title AS aliased_title, body ...
- $builder->select(['pk' => 'id', 'aliased_title' => 'title', 'body']);
-
- // Use a closure
- $builder->select(function ($builder) {
- return ['id', 'title', 'body'];
- });
-
-
-Where Conditions
-----------------
-
-Generating conditions::
-
- // WHERE id = 1
- $builder->where(['id' => 1]);
-
- // WHERE id > 1
- $builder->where(['id >' => 1]);
-
-
-As you can see you can use any operator by placing it with a space after the field name. Adding multiple conditions is easy as well::
-
- where(['id >' => 1])->andWhere(['title' => 'My Title']);
-
- // Equivalent to
- $builder->where(['id >' => 1, 'title' => 'My title']);
-
- // WHERE id > 1 OR title = 'My title'
- $builder->where(['OR' => ['id >' => 1, 'title' => 'My title']]);
-
-
-For even more complex conditions you can use closures and expression objects::
-
- select('*')
- ->from('articles')
- ->where(function ($exp) {
- return $exp
- ->eq('author_id', 2)
- ->eq('published', true)
- ->notEq('spam', true)
- ->gt('view_count', 10);
- });
-
-
-Which results in:
-
-.. code-block:: sql
-
- SELECT * FROM articles
- WHERE
- author_id = 2
- AND published = 1
- AND spam != 1
- AND view_count > 10
-
-
-Combining expressions is also possible::
-
- select('*')
- ->from('articles')
- ->where(function ($exp) {
- $orConditions = $exp->or_(['author_id' => 2])
- ->eq('author_id', 5);
- return $exp
- ->not($orConditions)
- ->lte('view_count', 10);
- });
-
-It generates:
-
-.. code-block:: sql
-
- SELECT *
- FROM articles
- WHERE
- NOT (author_id = 2 OR author_id = 5)
- AND view_count <= 10
-
-
-When using the expression objects you can use the following methods to create conditions:
-
-* ``eq()`` Creates an equality condition.
-* ``notEq()`` Create an inequality condition
-* ``like()`` Create a condition using the ``LIKE`` operator.
-* ``notLike()`` Create a negated ``LIKE`` condition.
-* ``in()`` Create a condition using ``IN``.
-* ``notIn()`` Create a negated condition using ``IN``.
-* ``gt()`` Create a ``>`` condition.
-* ``gte()`` Create a ``>=`` condition.
-* ``lt()`` Create a ``<`` condition.
-* ``lte()`` Create a ``<=`` condition.
-* ``isNull()`` Create an ``IS NULL`` condition.
-* ``isNotNull()`` Create a negated ``IS NULL`` condition.
-
-
-Aggregates and SQL Functions
-----------------------------
-
-.. code-block:: php
-
- select(['count' => $builder->func()->count('*')]);
-
-A number of commonly used functions can be created with the func() method:
-
-* ``sum()`` Calculate a sum. The arguments will be treated as literal values.
-* ``avg()`` Calculate an average. The arguments will be treated as literal values.
-* ``min()`` Calculate the min of a column. The arguments will be treated as literal values.
-* ``max()`` Calculate the max of a column. The arguments will be treated as literal values.
-* ``count()`` Calculate the count. The arguments will be treated as literal values.
-* ``concat()`` Concatenate two values together. The arguments are treated as bound parameters unless marked as literal.
-* ``coalesce()`` Coalesce values. The arguments are treated as bound parameters unless marked as literal.
-* ``dateDiff()`` Get the difference between two dates/times. The arguments are treated as bound parameters unless marked as literal.
-* ``now()`` Take either 'time' or 'date' as an argument allowing you to get either the current time, or current date.
-
-When providing arguments for SQL functions, there are two kinds of parameters you can use,
-literal arguments and bound parameters. Literal parameters allow you to reference columns or
-other SQL literals. Bound parameters can be used to safely add user data to SQL functions. For example:
-
-
-.. code-block:: php
-
- func()->concat([
- 'title' => 'literal',
- ' NEW'
- ]);
- $query->select(['title' => $concat]);
-
-
-Getting Results out of a Query
-------------------------------
-
-Once you’ve made your query, you’ll want to retrieve rows from it. There are a few ways of doing this:
-
-
-.. code-block:: php
-
- execute()->fetchAll('assoc');
-
-
-Creating an Insert Query
-------------------------
-
-Creating insert queries is also possible:
-
-
-.. code-block:: php
-
- getQueryBuilder('insert');
- $builder
- ->insert(['first_name', 'last_name'])
- ->into('users')
- ->values(['first_name' => 'Steve', 'last_name' => 'Jobs'])
- ->values(['first_name' => 'Jon', 'last_name' => 'Snow'])
- ->execute();
-
-
-For increased performance, you can use another builder object as the values for an insert query:
-
-.. code-block:: php
-
- getQueryBuilder('select');
- $namesQuery
- ->select(['fname', 'lname'])
- ->from('users')
- ->where(['is_active' => true]);
-
- $builder = $this->getQueryBuilder('insert');
- $st = $builder
- ->insert(['first_name', 'last_name'])
- ->into('names')
- ->values($namesQuery)
- ->execute();
-
- var_dump($st->lastInsertId('names', 'id'));
-
-
-The above code will generate:
-
-.. code-block:: sql
-
- INSERT INTO names (first_name, last_name)
- (SELECT fname, lname FROM USERS where is_active = 1)
-
-
-Creating an update Query
-------------------------
-
-Creating update queries is similar to both inserting and selecting:
-
-.. code-block:: php
-
- getQueryBuilder('update');
- $builder
- ->update('users')
- ->set('fname', 'Snow')
- ->where(['fname' => 'Jon'])
- ->execute();
-
-
-Creating a Delete Query
------------------------
-
-Finally, delete queries:
-
-.. code-block:: php
-
- getQueryBuilder('delete');
- $builder
- ->delete('users')
- ->where(['accepted_gdpr' => false])
- ->execute();
diff --git a/docs/en/writing-migrations.md b/docs/en/writing-migrations.md
new file mode 100644
index 000000000..162515f1b
--- /dev/null
+++ b/docs/en/writing-migrations.md
@@ -0,0 +1,2527 @@
+# Writing Migrations
+
+Migrations are a declarative API that helps you transform your database. Each migration
+is represented by a PHP class in a unique file. It is preferred that you write
+your migrations using the Migrations API, but raw SQL is also supported.
+
+## Creating a New Migration
+
+Let's start by creating a new migration with `bake`:
+
+``` bash
+$ bin/cake bake migration
+```
+
+This will create a new migration in the format
+`YYYYMMDDHHMMSS_my_new_migration.php`, where the first 14 characters are
+replaced with the current timestamp down to the second.
+
+If you have specified multiple migration paths, you will be asked to select
+which path to create the new migration in.
+
+Bake will automatically creates a skeleton migration file with a single method:
+
+``` php
+ [
+ 'style' => 'anonymous', // or 'traditional'
+],
+```
+
+This configuration also applies to seeds, allowing you to use consistent styling
+across your entire project.
+
+## The Change Method
+
+Migrations supports 'reversible migrations'. In many scenarios, you
+only need to define the `up` logic, and Migrations can figure out how to
+generate the rollback operations for you. For example:
+
+``` php
+table('user_logins');
+ $table->addColumn('user_id', 'integer')
+ ->addColumn('created', 'datetime')
+ ->create();
+ }
+}
+```
+
+When executing this migration, Migrations will create the `user_logins` table on
+the way up and automatically figure out how to drop the table on the way down.
+Please be aware that when a `change` method exists, Migrations will
+ignore the `up` and `down` methods. If you need to use these methods it is
+recommended to create a separate migration file.
+
+> [!NOTE]
+> When creating or updating tables inside a `change()` method you must use
+> the Table `create()` and `update()` methods. Migrations cannot automatically
+> determine whether a `save()` call is creating a new table or modifying an
+> existing one.
+
+The following actions are reversible when done through the Table API in
+Migrations, and will be automatically reversed:
+
+- Creating a table
+- Renaming a table
+- Adding a column
+- Renaming a column
+- Adding an index
+- Adding a foreign key
+- Adding a check constraint
+
+If a command cannot be reversed then Migrations will throw an
+`IrreversibleMigrationException` when it's migrating down. If you wish to
+use a command that cannot be reversed in the change function, you can use an
+if statement with `$this->isMigratingUp()` to only run things in the
+up or down direction. For example:
+
+``` php
+table('user_logins');
+ $table->addColumn('user_id', 'integer')
+ ->addColumn('created', 'datetime')
+ ->create();
+ if ($this->isMigratingUp()) {
+ $table->insert([['user_id' => 1, 'created' => '2020-01-19 03:14:07']])
+ ->save();
+ }
+ }
+}
+```
+
+## The Up Method
+
+The up method is automatically run by Migrations when you are migrating up and it
+detects the given migration hasn't been executed previously. You should use the
+up method to transform the database with your intended changes.
+
+## The Down Method
+
+The down method is automatically run by Migrations when you are migrating down and
+it detects the given migration has been executed in the past. You should use
+the down method to reverse/undo the transformations described in the up method.
+
+## The Init Method
+
+The `init()` method is run by Migrations before the migration methods if it exists.
+This can be used for setting common class properties that are then used within
+the migration methods.
+
+## The Should Execute Method
+
+The `shouldExecute()` method is run by Migrations before executing the migration.
+This can be used to prevent the migration from being executed at this time. It always
+returns true by default. You can override it in your custom `BaseMigration`
+implementation.
+
+## Working With Tables
+
+The Table object enables you to easily manipulate database tables using PHP
+code. You can retrieve an instance of the Table object by calling the
+`table()` method from within your database migration:
+
+``` php
+table('tableName');
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+You can then manipulate this table using the methods provided by the Table
+object.
+
+
+
+## Adding Columns
+
+Column types are specified as strings and can be one of:
+
+- binary
+- boolean
+- char
+- date
+- datetime
+- decimal
+- float
+- double
+- smallinteger
+- integer
+- biginteger
+- string
+- text
+- time
+- timestamp
+- uuid
+- binaryuuid
+- nativeuuid
+
+In addition, the MySQL adapter supports `enum`, `set`, `blob`,
+`tinyblob`, `mediumblob`, `longblob`, `bit` and `json` column types
+(`json` in MySQL 5.7 and above). When providing a limit value and using
+`binary`, `varbinary` or `blob` and its subtypes, the retained column type
+will be based on required length (see [Limit Option and MySQL](#limit-option-and-mysql) for details).
+
+With most adapters, the `uuid` and `nativeuuid` column types are aliases,
+however with the MySQL adapter + MariaDB, the `nativeuuid` type maps to
+a native uuid column instead of `CHAR(36)` like `uuid` does.
+
+In addition, the Postgres adapter supports `interval`, `json`, `jsonb`,
+`uuid`, `cidr`, `inet` and `macaddr` column types (PostgreSQL 9.3 and
+above).
+
+### Valid Column Options
+
+The following are valid column options:
+
+For any column type:
+
+| Option | Description |
+|----|----|
+| limit | set maximum length for strings, also hints column types in adapters (see note below) |
+| length | alias for `limit` |
+| default | set default value or action |
+| null | allow `NULL` values, defaults to `true` (setting `identity` will override default to `false`) |
+| after | specify the column that a new column should be placed after, or use `\Migrations\Db\Adapter\MysqlAdapter::FIRST` to place the column at the start of the table *(only applies to MySQL)* |
+| comment | set a text comment on the column |
+
+For `decimal` and `float` columns:
+
+| Option | Description |
+|----|----|
+| precision | total number of digits (e.g., 10 in `DECIMAL(10,2)`) |
+| scale | number of digits after the decimal point (e.g., 2 in `DECIMAL(10,2)`) |
+| signed | enable or disable the `unsigned` option *(only applies to MySQL)* |
+
+> [!NOTE]
+> **Precision and Scale Terminology**
+>
+> Migrations follows the SQL standard where `precision` represents the total number of digits,
+> and `scale` represents digits after the decimal point. For example, to create `DECIMAL(10,2)`
+> (10 total digits with 2 decimal places):
+>
+> ``` php
+> $table->addColumn('price', 'decimal', [
+> 'precision' => 10, // Total digits
+> 'scale' => 2, // Decimal places
+> ]);
+> ```
+>
+> This differs from CakePHP's TableSchema which uses `length` for total digits and
+> `precision` for decimal places. The migration adapter handles this conversion automatically.
+
+For `enum` and `set` columns:
+
+| Option | Description |
+|--------|-----------------------------------------------------|
+| values | Can be a comma separated list or an array of values |
+
+For `smallinteger`, `integer` and `biginteger` columns:
+
+| Option | Description |
+|----|----|
+| identity | enable or disable automatic incrementing (if enabled, will set `null: false` if `null` option is not set) |
+| signed | enable or disable the `unsigned` option *(only applies to MySQL)* |
+
+For Postgres, when using `identity`, it will utilize the `serial` type
+appropriate for the integer size, so that `smallinteger` will give you
+`smallserial`, `integer` gives `serial`, and `biginteger` gives
+`bigserial`.
+
+For `date` columns:
+
+| Option | Description |
+|---------|---------------------------------------------|
+| default | set default value (use with `CURRENT_DATE`) |
+
+For `time` columns:
+
+| Option | Description |
+|----|----|
+| default | set default value (use with `CURRENT_TIME`) |
+| timezone | enable or disable the `with time zone` option *(only applies to Postgres)* |
+
+For `datetime` columns:
+
+| Option | Description |
+|----|----|
+| default | set default value (use with `CURRENT_TIMESTAMP`) |
+| timezone | enable or disable the `with time zone` option *(only applies to Postgres)* |
+
+For `timestamp` columns:
+
+| Option | Description |
+|----|----|
+| default | set default value (use with `CURRENT_TIMESTAMP`) |
+| update | set an action to be triggered when the row is updated (use with `CURRENT_TIMESTAMP`) *(only applies to MySQL)* |
+| timezone | enable or disable the `with time zone` option for `time` and `timestamp` columns *(only applies to Postgres)* |
+
+You can add `created` and `updated` timestamps to a table using the
+`addTimestamps()` method. This method accepts three arguments, where the first
+two allow setting alternative names for the columns while the third argument
+allows you to enable the `timezone` option for the columns. The defaults for
+these arguments are `created`, `updated`, and `false` respectively. For
+the first and second argument, if you provide `null`, then the default name
+will be used, and if you provide `false`, then that column will not be
+created. Please note that attempting to set both to `false` will throw
+a `\RuntimeException`. Additionally, you can use the
+`addTimestampsWithTimezone()` method, which is an alias to `addTimestamps()`
+that will always set the third argument to `true` (see examples below). The
+`created` column will have a default set to `CURRENT_TIMESTAMP`. For MySQL
+only, `updated` column will have update set to
+`CURRENT_TIMESTAMP`:
+
+``` php
+table('users')->addTimestamps()->create();
+ // Use defaults (with timezones)
+ $table = $this->table('users')->addTimestampsWithTimezone()->create();
+
+ // Override the 'created' column name with 'recorded_at'.
+ $table = $this->table('books')->addTimestamps('recorded_at')->create();
+
+ // Override the 'updated' column name with 'amended_at', preserving timezones.
+ // The two lines below do the same, the second one is simply cleaner.
+ $table = $this->table('books')->addTimestamps(null, 'amended_at', true)->create();
+ $table = $this->table('users')->addTimestampsWithTimezone(null, 'amended_at')->create();
+
+ // Only add the created column to the table
+ $table = $this->table('books')->addTimestamps(null, false);
+ // Only add the updated column to the table
+ $table = $this->table('users')->addTimestamps(false);
+ // Note, setting both false will throw a \RuntimeError
+ }
+}
+```
+
+For `boolean` columns:
+
+| Option | Description |
+|--------|-------------------------------------------------------------------|
+| signed | enable or disable the `unsigned` option *(only applies to MySQL)* |
+
+For `string` and `text` columns:
+
+| Option | Description |
+|----|----|
+| collation | set collation that differs from table defaults *(only applies to MySQL)* |
+| encoding | set character set that differs from table defaults *(only applies to MySQL)* |
+
+### Limit Option and MySQL
+
+When using the MySQL adapter, there are a couple things to consider when working with limits:
+
+- When using a `string` primary key or index on MySQL 5.7 or below, or the
+ MyISAM storage engine, and the default charset of `utf8mb4_unicode_ci`, you
+ must specify a limit less than or equal to 191, or use a different charset.
+- Additional hinting of database column type can be made for `integer`,
+ `text`, `blob`, `tinyblob`, `mediumblob`, `longblob` columns. Using
+ `limit` with one the following options will modify the column type
+ accordingly:
+
+| Limit | Column Type |
+|--------------|-------------|
+| BLOB_TINY | TINYBLOB |
+| BLOB_REGULAR | BLOB |
+| BLOB_MEDIUM | MEDIUMBLOB |
+| BLOB_LONG | LONGBLOB |
+| TEXT_TINY | TINYTEXT |
+| TEXT_REGULAR | TEXT |
+| TEXT_MEDIUM | MEDIUMTEXT |
+| TEXT_LONG | LONGTEXT |
+| INT_TINY | TINYINT |
+| INT_SMALL | SMALLINT |
+| INT_MEDIUM | MEDIUMINT |
+| INT_REGULAR | INT |
+| INT_BIG | BIGINT |
+
+For `binary` or `varbinary` types, if limit is set greater than allowed 255
+bytes, the type will be changed to the best matching blob type given the
+length:
+
+``` php
+table('cart_items');
+$table->addColumn('user_id', 'integer')
+ ->addColumn('product_id', 'integer', ['limit' => MysqlAdapter::INT_BIG])
+ ->addColumn('subtype_id', 'integer', ['limit' => MysqlAdapter::INT_SMALL])
+ ->addColumn('quantity', 'integer', ['limit' => MysqlAdapter::INT_TINY])
+ ->create();
+```
+
+### Default values with expressions
+
+If you need to set a default to an expression, you can use a `Literal` to have
+the column's default value used without any quoting or escaping. This is helpful
+when you want to use a function as a default value:
+
+``` php
+use Migrations\BaseMigration;
+use Migrations\Db\Literal;
+
+class AddSomeColumns extends BaseMigration
+{
+ public function change(): void
+ {
+ $this->table('users')
+ ->addColumn('uniqid', 'uuid', [
+ 'default' => Literal::from('uuid_generate_v4()')
+ ])
+ ->create();
+ }
+}
+```
+
+
+
+### Creating a Table
+
+Creating a table is really easy using the Table object. Let's create a table to
+store a collection of users:
+
+``` php
+table('users');
+ $users->addColumn('username', 'string', ['limit' => 20])
+ ->addColumn('password', 'string', ['limit' => 40])
+ ->addColumn('password_salt', 'string', ['limit' => 40])
+ ->addColumn('email', 'string', ['limit' => 100])
+ ->addColumn('first_name', 'string', ['limit' => 30])
+ ->addColumn('last_name', 'string', ['limit' => 30])
+ ->addColumn('created', 'datetime')
+ ->addColumn('updated', 'datetime', ['null' => true])
+ ->addIndex(['username', 'email'], ['unique' => true])
+ ->create();
+ }
+}
+```
+
+Columns are added using the `addColumn()` method. We create a unique index
+for both the username and email columns using the `addIndex()` method.
+Finally calling `create()` commits the changes to the database.
+
+> [!NOTE]
+> Migrations automatically creates an auto-incrementing primary key column called `id` for every
+> table.
+
+The `id` option sets the name of the automatically created identity field,
+while the `primary_key` option selects the field or fields used for primary
+key. `id` will always override the `primary_key` option unless it's set to
+false. If you don't need a primary key set `id` to false without specifying
+a `primary_key`, and no primary key will be created.
+
+To specify an alternate primary key, you can specify the `primary_key` option
+when accessing the Table object. Let's disable the automatic `id` column and
+create a primary key using two columns instead:
+
+``` php
+table('followers', ['id' => false, 'primary_key' => ['user_id', 'follower_id']]);
+ $table->addColumn('user_id', 'integer')
+ ->addColumn('follower_id', 'integer')
+ ->addColumn('created', 'datetime')
+ ->create();
+ }
+}
+```
+
+Setting a single `primary_key` doesn't enable the `AUTO_INCREMENT` option.
+To simply change the name of the primary key, we need to override the default `id` field name:
+
+``` php
+table('followers', ['id' => 'user_id']);
+ $table->addColumn('follower_id', 'integer')
+ ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
+ ->create();
+ }
+}
+```
+
+In addition, the MySQL adapter supports following options:
+
+| Option | Platform | Description |
+|----|----|----|
+| comment | MySQL, Postgres | set a text comment on the table |
+| collation | MySQL, SqlServer | set the table collation *(defaults to database collation)* |
+| row_format | MySQL | set the table row format |
+| engine | MySQL | define table engine *(defaults to \`\`InnoDB\`\`)* |
+| signed | MySQL | whether the primary key is `signed` *(defaults to \`\`true\`\`)* |
+| limit | MySQL | set the maximum length for the primary key |
+
+By default, the primary key is `signed`.
+To set it to be unsigned, pass the `signed` option with a `false`
+value, or enable the `Migrations.unsigned_primary_keys` and
+`Migrations.unsigned_ints` feature flags (see [Feature Flags](index#feature-flags)).
+Both flags should be used together so that foreign key columns match
+the primary keys they reference:
+
+``` php
+table('followers', ['signed' => false]);
+ $table->addColumn('follower_id', 'integer')
+ ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
+ ->create();
+ }
+}
+```
+
+If you need to create a table with a different collation than the database,
+use:
+
+``` php
+table('categories', [
+ 'collation' => 'latin1_german1_ci'
+ ])
+ ->addColumn('title', 'string')
+ ->create();
+ }
+}
+```
+
+Note however this can only be done on table creation : there is currently no way
+of adding a column to an existing table with a different collation than the
+table or the database. Only `MySQL` and `SqlServer` supports this
+configuration key for the time being.
+
+To view available column types and options, see [Adding Columns](#adding-columns) for details.
+
+### MySQL ALTER TABLE Options
+
+::: info Added in version 5.0.0
+`ALGORITHM` and `LOCK` options were added in 5.0.0.
+:::
+
+When modifying tables in MySQL, you can control how the ALTER TABLE operation is
+performed using the `algorithm` and `lock` options. This is useful for performing
+zero-downtime schema changes on large tables in production environments.
+
+``` php
+table('large_table');
+ $table->addIndex(['status'], [
+ 'name' => 'idx_status',
+ ]);
+ $table->update([
+ 'algorithm' => 'INPLACE',
+ 'lock' => 'NONE',
+ ]);
+ }
+}
+```
+
+Available `algorithm` values:
+
+| Algorithm | Description |
+|----|----|
+| DEFAULT | Let MySQL choose the algorithm (default behavior) |
+| INPLACE | Modify the table in place without copying data (when possible) |
+| COPY | Create a copy of the table with the changes (legacy method) |
+| INSTANT | Only modify metadata, no table rebuild (MySQL 8.0+, limited operations) |
+
+Available `lock` values:
+
+| Lock | Description |
+|-----------|----------------------------------------------------------|
+| DEFAULT | Use minimal locking for the algorithm (default behavior) |
+| NONE | Allow concurrent reads and writes during the operation |
+| SHARED | Allow concurrent reads but block writes |
+| EXCLUSIVE | Block all reads and writes during the operation |
+
+> [!NOTE]
+> Not all operations support all algorithm/lock combinations. MySQL will raise
+> an error if the requested combination is not possible for the operation.
+> The `INSTANT` algorithm is only available in MySQL 8.0+ and only for specific
+> operations like adding columns at the end of a table.
+
+> [!WARNING]
+> Using `ALGORITHM=INPLACE, LOCK=NONE` does not guarantee zero-downtime for
+> all operations. Some operations may still require a table copy or exclusive lock.
+> Always test schema changes on a staging environment first.
+
+### Table Partitioning
+
+Migrations supports table partitioning for MySQL and PostgreSQL. Partitioning helps
+manage large tables by splitting them into smaller, more manageable pieces.
+
+> [!NOTE]
+> Partition columns must be included in the primary key for MySQL. SQLite does
+> not support partitioning. MySQL's `RANGE` and `LIST` types only work with
+> integer columns - use `RANGE COLUMNS` and `LIST COLUMNS` for DATE/STRING columns.
+
+#### RANGE Partitioning
+
+RANGE partitioning is useful when you want to partition by numeric ranges. For MySQL,
+use `TYPE_RANGE` with integer columns or expressions, and `TYPE_RANGE_COLUMNS` for
+DATE/DATETIME/STRING columns:
+
+``` php
+table('orders', [
+ 'id' => false,
+ 'primary_key' => ['id', 'order_date'],
+ ]);
+ $table->addColumn('id', 'integer', ['identity' => true])
+ ->addColumn('order_date', 'date')
+ ->addColumn('amount', 'decimal', ['precision' => 10, 'scale' => 2])
+ ->partitionBy(Partition::TYPE_RANGE_COLUMNS, 'order_date')
+ ->addPartition('p2022', '2023-01-01')
+ ->addPartition('p2023', '2024-01-01')
+ ->addPartition('p2024', '2025-01-01')
+ ->addPartition('pmax', 'MAXVALUE')
+ ->create();
+ }
+}
+```
+
+#### LIST Partitioning
+
+LIST partitioning is useful when you want to partition by discrete values. For MySQL,
+use `TYPE_LIST` with integer columns and `TYPE_LIST_COLUMNS` for STRING columns:
+
+``` php
+table('customers', [
+ 'id' => false,
+ 'primary_key' => ['id', 'region'],
+ ]);
+ $table->addColumn('id', 'integer', ['identity' => true])
+ ->addColumn('region', 'string', ['limit' => 20])
+ ->addColumn('name', 'string')
+ ->partitionBy(Partition::TYPE_LIST_COLUMNS, 'region')
+ ->addPartition('p_americas', ['US', 'CA', 'MX', 'BR'])
+ ->addPartition('p_europe', ['UK', 'DE', 'FR', 'IT'])
+ ->addPartition('p_asia', ['JP', 'CN', 'IN', 'KR'])
+ ->create();
+ }
+}
+```
+
+#### HASH Partitioning
+
+HASH partitioning distributes data evenly across a specified number of partitions:
+
+``` php
+table('sessions');
+ $table->addColumn('user_id', 'integer')
+ ->addColumn('data', 'text')
+ ->partitionBy(Partition::TYPE_HASH, 'user_id', ['count' => 8])
+ ->create();
+ }
+}
+```
+
+#### KEY Partitioning (MySQL only)
+
+KEY partitioning is similar to HASH but uses MySQL's internal hashing function:
+
+``` php
+table('cache', [
+ 'id' => false,
+ 'primary_key' => ['cache_key'],
+ ]);
+ $table->addColumn('cache_key', 'string', ['limit' => 255])
+ ->addColumn('value', 'binary')
+ ->partitionBy(Partition::TYPE_KEY, 'cache_key', ['count' => 16])
+ ->create();
+ }
+}
+```
+
+#### Partitioning with Expressions
+
+You can partition by expressions using the `Literal` class:
+
+``` php
+table('events', [
+ 'id' => false,
+ 'primary_key' => ['id', 'created_at'],
+ ]);
+ $table->addColumn('id', 'integer', ['identity' => true])
+ ->addColumn('created_at', 'datetime')
+ ->partitionBy(Partition::TYPE_RANGE, Literal::from('YEAR(created_at)'))
+ ->addPartition('p2022', 2023)
+ ->addPartition('p2023', 2024)
+ ->addPartition('pmax', 'MAXVALUE')
+ ->create();
+ }
+}
+```
+
+#### Modifying Partitions on Existing Tables
+
+You can add or drop partitions on existing partitioned tables:
+
+``` php
+table('orders')
+ ->addPartitionToExisting('p2025', '2026-01-01')
+ ->update();
+ }
+
+ public function down(): void
+ {
+ // Drop the partition
+ $this->table('orders')
+ ->dropPartition('p2025')
+ ->update();
+ }
+}
+```
+
+### Saving Changes
+
+When working with the Table object, Migrations stores certain operations in a
+pending changes cache. Once you have made the changes you want to the table,
+you must save them. To perform this operation, Migrations provides three methods,
+`create()`, `update()`, and `save()`. `create()` will first create
+the table and then run the pending changes. `update()` will just run the
+pending changes, and should be used when the table already exists. `save()`
+is a helper function that checks first if the table exists and if it does not
+will run `create()`, else it will run `update()`.
+
+As stated above, when using the `change()` migration method, you should always
+use `create()` or `update()`, and never `save()` as otherwise migrating
+and rolling back may result in different states, due to `save()` calling
+`create()` when running migrate and then `update()` on rollback. When
+using the `up()`/`down()` methods, it is safe to use either `save()` or
+the more explicit methods.
+
+When in doubt with working with tables, it is always recommended to call
+the appropriate function and commit any pending changes to the database.
+
+### Renaming a Column
+
+To rename a column, access an instance of the Table object then call the
+`renameColumn()` method:
+
+``` php
+table('users');
+ $table->renameColumn('bio', 'biography')
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ $table = $this->table('users');
+ $table->renameColumn('biography', 'bio')
+ ->save();
+ }
+}
+```
+
+### Adding a Column After Another Column
+
+When adding a column with the MySQL adapter, you can dictate its position using
+the `after` option, where its value is the name of the column to position it
+after:
+
+``` php
+table('users');
+ $table->addColumn('city', 'string', ['after' => 'email'])
+ ->update();
+ }
+}
+```
+
+This would create the new column `city` and position it after the `email`
+column. The `\Migrations\Db\Adapter\MysqlAdapter::FIRST` constant can be used
+to specify that the new column should be created as the first column in that
+table.
+
+### Dropping a Column
+
+To drop a column, use the `removeColumn()` method:
+
+``` php
+table('users');
+ $table->removeColumn('short_name')
+ ->save();
+ }
+}
+```
+
+### Specifying a Column Limit
+
+You can limit the maximum length of a column by using the `limit` option:
+
+``` php
+table('tags');
+ $table->addColumn('short_name', 'string', ['limit' => 30])
+ ->update();
+ }
+}
+```
+
+### Changing Column Attributes
+
+There are two methods for modifying existing columns:
+
+#### Updating Columns (Recommended)
+
+To modify specific column attributes while preserving others, use the `updateColumn()` method.
+This method automatically preserves unspecified attributes like defaults, nullability, limits, etc.:
+
+``` php
+table('users');
+ // Make email nullable, preserving all other attributes
+ $users->updateColumn('email', null, ['null' => true])
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ $users = $this->table('users');
+ $users->updateColumn('email', null, ['null' => false])
+ ->save();
+ }
+}
+```
+
+You can pass `null` as the column type to preserve the existing type, or specify a new type:
+
+``` php
+// Preserve type and other attributes, only change nullability
+$table->updateColumn('email', null, ['null' => true]);
+
+// Change type to biginteger, preserve default and other attributes
+$table->updateColumn('user_id', 'biginteger');
+
+// Change default value, preserve everything else
+$table->updateColumn('status', null, ['default' => 'active']);
+```
+
+The following attributes are automatically preserved by `updateColumn()`:
+
+- Default values
+- NULL/NOT NULL constraint
+- Column limit/length
+- Decimal scale/precision
+- Comments
+- Signed/unsigned (for numeric types)
+- Collation and encoding
+- Enum/set values
+
+#### Changing Columns (Traditional)
+
+To completely replace a column definition, use the `changeColumn()` method.
+This method requires you to specify all desired column attributes.
+See [Valid Column Types](#valid-column-types) and [Valid Column Options](#valid-column-options) for allowed values:
+
+``` php
+table('users');
+ // Must specify all attributes
+ $users->changeColumn('email', 'string', [
+ 'limit' => 255,
+ 'null' => true,
+ 'default' => null,
+ ])
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+You can enable attribute preservation with `changeColumn()` by passing
+`'preserveUnspecified' => true` in the options:
+
+``` php
+$table->changeColumn('email', 'string', [
+ 'null' => true,
+ 'preserveUnspecified' => true,
+]);
+```
+
+> [!NOTE]
+> For most use cases, `updateColumn()` is recommended as it is safer and requires
+> less code. Use `changeColumn()` when you need to completely redefine a column
+> or when working with legacy code that expects the traditional behavior.
+
+### Working With Indexes
+
+To add an index to a table you can simply call the `addIndex()` method on the
+table object:
+
+``` php
+table('users');
+ $table->addColumn('city', 'string')
+ ->addIndex(['city'])
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+By default Migrations instructs the database adapter to create a simple index. We
+can pass an additional parameter `unique` to the `addIndex()` method to
+specify a unique index. We can also explicitly specify a name for the index
+using the `name` parameter, the index columns sort order can also be specified using
+the `order` parameter. The order parameter takes an array of column names and sort order key/value pairs:
+
+``` php
+table('users');
+ $table->addColumn('email', 'string')
+ ->addColumn('username','string')
+ ->addIndex(['email', 'username'], [
+ 'unique' => true,
+ 'name' => 'idx_users_email',
+ 'order' => ['email' => 'DESC', 'username' => 'ASC']]
+ )
+ ->save();
+ }
+}
+```
+
+As of 4.6.0, you can use `BaseMigration::index()` to get a fluent builder to
+define indexes:
+
+``` php
+table('users');
+ $table->addColumn('email', 'string')
+ ->addColumn('username','string')
+ ->addIndex(
+ $this->index(['email', 'username'])
+ ->setType('unique')
+ ->setName('idx_users_email')
+ ->setOrder(['email' => 'DESC', 'username' => 'ASC'])
+ )
+ ->save();
+ }
+}
+```
+
+The MySQL adapter also supports `fulltext` indexes. If you are using a version before 5.6 you must
+ensure the table uses the `MyISAM` engine:
+
+``` php
+table('users', ['engine' => 'MyISAM']);
+ $table->addColumn('email', 'string')
+ ->addIndex('email', ['type' => 'fulltext'])
+ ->create();
+ }
+}
+```
+
+MySQL adapter supports setting the index length defined by limit option.
+When you are using a multi-column index, you are able to define each column index length.
+The single column index can define its index length with or without defining column name in limit option:
+
+``` php
+table('users');
+ $table->addColumn('email', 'string')
+ ->addColumn('username','string')
+ ->addColumn('user_guid', 'string', ['limit' => 36])
+ ->addIndex(['email','username'], ['limit' => ['email' => 5, 'username' => 2]])
+ ->addIndex('user_guid', ['limit' => 6])
+ ->create();
+ }
+}
+```
+
+The SQL Server and PostgreSQL adapters support `include` (non-key) columns on indexes:
+
+``` php
+table('users');
+ $table->addColumn('email', 'string')
+ ->addColumn('firstname','string')
+ ->addColumn('lastname','string')
+ ->addIndex(['email'], ['include' => ['firstname', 'lastname']])
+ ->create();
+ }
+}
+```
+
+PostgreSQL, SQLServer, and SQLite support partial indexes by defining where
+clauses for the index:
+
+``` php
+table('users');
+ $table->addColumn('email', 'string')
+ ->addColumn('is_verified','boolean')
+ ->addIndex(
+ $this->index('email')
+ ->setName('user_email_verified_idx')
+ ->setType('unique')
+ ->setWhere('is_verified = true')
+ )
+ ->create();
+ }
+}
+```
+
+PostgreSQL can create indexes concurrently which avoids taking disruptive locks
+during index creation:
+
+``` php
+table('users');
+ $table->addColumn('email', 'string')
+ ->addIndex(
+ $this->index('email')
+ ->setName('user_email_unique_idx')
+ ->setType('unique')
+ ->setConcurrently(true)
+ )
+ ->create();
+ }
+}
+```
+
+PostgreSQL adapters also supports Generalized Inverted Index `gin` indexes:
+
+``` php
+table('users');
+ $table->addColumn('address', 'string')
+ ->addIndex('address', ['type' => 'gin'])
+ ->create();
+ }
+}
+```
+
+Removing indexes is as easy as calling the `removeIndex()` method. You must
+call this method for each index:
+
+``` php
+table('users');
+ $table->removeIndex(['email'])
+ ->save();
+
+ // alternatively, you can delete an index by its name, ie:
+ $table->removeIndexByName('idx_users_email')
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+::: info Added in version 4.6.0
+`Index::setWhere()`, and `Index::setConcurrently()` were added.
+:::
+
+### Working With Foreign Keys
+
+Migrations has support for creating foreign key constraints on your database tables.
+Let's add a foreign key to an example table:
+
+``` php
+table('tags');
+ $table->addColumn('tag_name', 'string')
+ ->save();
+
+ $refTable = $this->table('tag_relationships');
+ $refTable->addColumn('tag_id', 'integer', ['null' => true])
+ ->addForeignKey(
+ 'tag_id',
+ 'tags',
+ 'id',
+ ['delete'=> 'SET_NULL', 'update'=> 'NO_ACTION'],
+ )
+ ->save();
+
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+The 'delete' and 'update' options allow you to define the `ON UPDATE` and `ON DELETE` behavior. Possibles values are 'SET_NULL', 'NO_ACTION', 'CASCADE' and
+'RESTRICT'. If 'SET_NULL' is used then the column must be created as nullable
+with the option `['null' => true]`.
+
+Foreign keys can be defined with arrays of columns to build constraints between
+tables with composite keys:
+
+``` php
+table('follower_events');
+ $table->addColumn('user_id', 'integer')
+ ->addColumn('follower_id', 'integer')
+ ->addColumn('event_id', 'integer')
+ ->addForeignKey(
+ ['user_id', 'follower_id'],
+ 'followers',
+ ['user_id', 'follower_id'],
+ [
+ 'delete'=> 'NO_ACTION',
+ 'update'=> 'NO_ACTION',
+ 'constraint' => 'user_follower_id',
+ ]
+ )
+ ->save();
+ }
+}
+```
+
+The options parameter of `addForeignKey()` supports the following options:
+
+| Option | Description |
+|------------|--------------------------------------------------------|
+| update | set an action to be triggered when the row is updated |
+| delete | set an action to be triggered when the row is deleted |
+| constraint | set a name to be used by foreign key constraint |
+| deferrable | define deferred constraint application (postgres only) |
+
+Using the `foreignKey()` method provides a fluent builder to define a foreign
+key:
+
+``` php
+table('articles');
+ $table->addForeignKey(
+ $this->foreignKey()
+ ->setColumns('user_id')
+ ->setReferencedTable('users')
+ ->setReferencedColumns('user_id')
+ ->setDelete(ForeignKey::CASCADE)
+ ->setName('article_user_fk')
+ )
+ ->save();
+ }
+}
+```
+
+::: info Added in version 4.6.0
+The `foreignKey` method was added.
+:::
+
+We can also easily check if a foreign key exists:
+
+``` php
+table('tag_relationships');
+ $exists = $table->hasForeignKey('tag_id');
+ if ($exists) {
+ // do something
+ }
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+Finally, to delete a foreign key, use the `dropForeignKey` method.
+
+Note that like other methods in the `Table` class, `dropForeignKey` also
+needs `save()` to be called at the end in order to be executed. This allows
+Migrations to intelligently plan migrations when more than one table is
+involved:
+
+``` php
+table('tag_relationships');
+ $table->dropForeignKey('tag_id')->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+### Working With Check Constraints
+
+::: info Added in version 5.0.0
+Check constraints were added in 5.0.0.
+:::
+
+Check constraints allow you to enforce data validation rules at the database level.
+They are particularly useful for ensuring data integrity across your application.
+
+> [!NOTE]
+> Check constraints are supported by MySQL 8.0.16+, PostgreSQL, and SQLite.
+> SQL Server support is planned for a future release.
+
+#### Adding a Check Constraint
+
+You can add a check constraint to a table using the `addCheckConstraint()` method:
+
+``` php
+table('products');
+ $table->addColumn('price', 'decimal', ['precision' => 10, 'scale' => 2])
+ ->addCheckConstraint('price_positive', 'price > 0')
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ $table = $this->table('products');
+ $table->dropCheckConstraint('price_positive')
+ ->save();
+ }
+}
+```
+
+The first argument is the constraint name, and the second is the SQL expression
+that defines the constraint. The expression should evaluate to a boolean value.
+
+#### Using the CheckConstraint Fluent Builder
+
+For more complex scenarios, you can use the `checkConstraint()` method to get
+a fluent builder:
+
+``` php
+table('users');
+ $table->addColumn('age', 'integer')
+ ->addColumn('status', 'string', ['limit' => 20])
+ ->addCheckConstraint(
+ $this->checkConstraint()
+ ->setName('age_valid')
+ ->setExpression('age >= 18 AND age <= 120')
+ )
+ ->addCheckConstraint(
+ $this->checkConstraint()
+ ->setName('status_valid')
+ ->setExpression("status IN ('active', 'inactive', 'pending')")
+ )
+ ->save();
+ }
+}
+```
+
+#### Auto-Generated Constraint Names
+
+If you don't specify a constraint name, one will be automatically generated based
+on the table name and expression hash:
+
+``` php
+table('inventory');
+ $table->addColumn('quantity', 'integer')
+ // Name will be auto-generated like 'inventory_chk_a1b2c3d4'
+ ->addCheckConstraint(
+ $this->checkConstraint()
+ ->setExpression('quantity >= 0')
+ )
+ ->save();
+ }
+}
+```
+
+#### Complex Check Constraints
+
+Check constraints can reference multiple columns and use complex SQL expressions:
+
+``` php
+table('date_ranges');
+ $table->addColumn('start_date', 'date')
+ ->addColumn('end_date', 'date')
+ ->addColumn('discount', 'decimal', ['precision' => 5, 'scale' => 2])
+ ->addCheckConstraint('valid_date_range', 'end_date >= start_date')
+ ->addCheckConstraint('valid_discount', 'discount BETWEEN 0 AND 100')
+ ->save();
+ }
+}
+```
+
+#### Checking if a Check Constraint Exists
+
+You can verify if a check constraint exists using the `hasCheckConstraint()` method:
+
+``` php
+table('products');
+ $exists = $table->hasCheckConstraint('price_positive');
+ if ($exists) {
+ // do something
+ } else {
+ $table->addCheckConstraint('price_positive', 'price > 0')
+ ->save();
+ }
+ }
+}
+```
+
+#### Dropping a Check Constraint
+
+To remove a check constraint, use the `dropCheckConstraint()` method with the
+constraint name:
+
+``` php
+table('products');
+ $table->dropCheckConstraint('price_positive')
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ $table = $this->table('products');
+ $table->addCheckConstraint('price_positive', 'price > 0')
+ ->save();
+ }
+}
+```
+
+> [!NOTE]
+> Like other table operations, `dropCheckConstraint()` requires `save()`
+> to be called to execute the change.
+
+#### Database-Specific Behavior
+
+**MySQL (8.0.16+)**
+
+Check constraints are fully supported. MySQL stores constraint metadata in the
+`INFORMATION_SCHEMA.CHECK_CONSTRAINTS` table.
+
+**PostgreSQL**
+
+Check constraints are fully supported and stored in the `pg_constraint` catalog.
+PostgreSQL allows the most flexible expressions in check constraints.
+
+**SQLite**
+
+Check constraints are supported but with some limitations. SQLite does not support
+`ALTER TABLE` operations for check constraints, so adding or dropping constraints
+requires recreating the entire table. This is handled automatically by the adapter.
+
+**SQL Server**
+
+Check constraint support for SQL Server is planned for a future release.
+
+### Determining Whether a Table Exists
+
+You can determine whether or not a table exists by using the `hasTable()`
+method:
+
+``` php
+hasTable('users');
+ if ($exists) {
+ // do something
+ }
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+### Dropping a Table
+
+Tables can be dropped quite easily using the `drop()` method. It is a
+good idea to recreate the table again in the `down()` method.
+
+Note that like other methods in the `Table` class, `drop` also needs `save()`
+to be called at the end in order to be executed. This allows Migrations to intelligently
+plan migrations when more than one table is involved:
+
+``` php
+table('users')->drop()->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ $users = $this->table('users');
+ $users->addColumn('username', 'string', ['limit' => 20])
+ ->addColumn('password', 'string', ['limit' => 40])
+ ->addColumn('password_salt', 'string', ['limit' => 40])
+ ->addColumn('email', 'string', ['limit' => 100])
+ ->addColumn('first_name', 'string', ['limit' => 30])
+ ->addColumn('last_name', 'string', ['limit' => 30])
+ ->addColumn('created', 'datetime')
+ ->addColumn('updated', 'datetime', ['null' => true])
+ ->addIndex(['username', 'email'], ['unique' => true])
+ ->save();
+ }
+}
+```
+
+### Renaming a Table
+
+To rename a table access an instance of the Table object then call the
+`rename()` method:
+
+``` php
+table('users');
+ $table
+ ->rename('legacy_users')
+ ->update();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ $table = $this->table('legacy_users');
+ $table
+ ->rename('users')
+ ->update();
+ }
+}
+```
+
+### Changing the Primary Key
+
+To change the primary key on an existing table, use the `changePrimaryKey()`
+method. Pass in a column name or array of columns names to include in the
+primary key, or `null` to drop the primary key. Note that the mentioned
+columns must be added to the table, they will not be added implicitly:
+
+``` php
+table('users');
+ $users
+ ->addColumn('username', 'string', ['limit' => 20, 'null' => false])
+ ->addColumn('password', 'string', ['limit' => 40])
+ ->save();
+
+ $users
+ ->addColumn('new_id', 'integer', ['null' => false])
+ ->changePrimaryKey(['new_id', 'username'])
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+### Creating Custom Primary Keys
+
+You can specify a `autoId` property in the Migration class and set it to
+`false`, which will turn off the automatic `id` column creation. You will
+need to manually create the column that will be used as a primary key and add
+it to the table declaration:
+
+``` php
+table('products');
+ $table
+ ->addColumn('id', 'uuid')
+ ->addPrimaryKey('id')
+ ->addColumn('name', 'string')
+ ->addColumn('description', 'text')
+ ->create();
+ }
+}
+```
+
+The above will create a `CHAR(36)` `id` column that is also the primary key.
+
+When specifying a custom primary key on the command line, you must note
+it as the primary key in the id field, otherwise you may get an error
+regarding duplicate id fields, i.e.:
+
+``` bash
+bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified
+```
+
+All baked migrations and snapshot will use this new way when necessary.
+
+> [!WARNING]
+> Dealing with primary key can only be done on table creation operations.
+> This is due to limitations for some database servers the plugin supports.
+
+### Changing the Table Comment
+
+To change the comment on an existing table, use the `changeComment()` method.
+Pass in a string to set as the new table comment, or `null` to drop the existing comment:
+
+``` php
+table('users');
+ $users
+ ->addColumn('username', 'string', ['limit' => 20])
+ ->addColumn('password', 'string', ['limit' => 40])
+ ->save();
+
+ $users
+ ->changeComment('This is the table with users auth information, password should be encrypted')
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+## Checking Columns
+
+`BaseMigration` also provides methods for introspecting the current schema,
+allowing you to conditionally make changes to schema, or read data.
+Schema is inspected **when the migration is run**.
+
+### Get a column list
+
+To retrieve all table columns, simply create a `table` object and call
+`getColumns()` method. This method will return an array of Column classes with
+basic info. Example below:
+
+``` php
+table('users')->getColumns();
+ ...
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ ...
+ }
+}
+```
+
+### Get a column by name
+
+To retrieve one table column, simply create a `table` object and call the
+`getColumn()` method. This method will return a Column class with basic info
+or NULL when the column doesn't exist. Example below:
+
+``` php
+table('users')->getColumn('email');
+ ...
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ ...
+ }
+}
+```
+
+### Checking whether a column exists
+
+You can check if a table already has a certain column by using the
+`hasColumn()` method:
+
+``` php
+table('user');
+ $column = $table->hasColumn('username');
+
+ if ($column) {
+ // do something
+ }
+
+ }
+}
+
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+### Dropping a Table
+
+Tables can be dropped quite easily using the `drop()` method. It is a
+good idea to recreate the table again in the `down()` method.
+
+Note that like other methods in the `Table` class, `drop` also needs `save()`
+to be called at the end in order to be executed. This allows Migrations to intelligently
+plan migrations when more than one table is involved:
+
+``` php
+table('users')->drop()->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ $users = $this->table('users');
+ $users->addColumn('username', 'string', ['limit' => 20])
+ ->addColumn('password', 'string', ['limit' => 40])
+ ->addColumn('password_salt', 'string', ['limit' => 40])
+ ->addColumn('email', 'string', ['limit' => 100])
+ ->addColumn('first_name', 'string', ['limit' => 30])
+ ->addColumn('last_name', 'string', ['limit' => 30])
+ ->addColumn('created', 'datetime')
+ ->addColumn('updated', 'datetime', ['null' => true])
+ ->addIndex(['username', 'email'], ['unique' => true])
+ ->save();
+ }
+}
+```
+
+### Renaming a Table
+
+To rename a table access an instance of the Table object then call the
+`rename()` method:
+
+``` php
+table('users');
+ $table
+ ->rename('legacy_users')
+ ->update();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ $table = $this->table('legacy_users');
+ $table
+ ->rename('users')
+ ->update();
+ }
+}
+```
+
+### Changing the Primary Key
+
+To change the primary key on an existing table, use the `changePrimaryKey()`
+method. Pass in a column name or array of columns names to include in the
+primary key, or `null` to drop the primary key. Note that the mentioned
+columns must be added to the table, they will not be added implicitly:
+
+``` php
+table('users');
+ $users
+ ->addColumn('username', 'string', ['limit' => 20, 'null' => false])
+ ->addColumn('password', 'string', ['limit' => 40])
+ ->save();
+
+ $users
+ ->addColumn('new_id', 'integer', ['null' => false])
+ ->changePrimaryKey(['new_id', 'username'])
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+### Creating Custom Primary Keys
+
+You can specify a `autoId` property in the Migration class and set it to
+`false`, which will turn off the automatic `id` column creation. You will
+need to manually create the column that will be used as a primary key and add
+it to the table declaration:
+
+``` php
+table('products');
+ $table
+ ->addColumn('id', 'uuid')
+ ->addPrimaryKey('id')
+ ->addColumn('name', 'string')
+ ->addColumn('description', 'text')
+ ->create();
+ }
+}
+```
+
+The above will create a `CHAR(36)` `id` column that is also the primary key.
+
+When specifying a custom primary key on the command line, you must note
+it as the primary key in the id field, otherwise you may get an error
+regarding duplicate id fields, i.e.:
+
+``` bash
+bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified
+```
+
+All baked migrations and snapshot will use this new way when necessary.
+
+> [!WARNING]
+> Dealing with primary key can only be done on table creation operations.
+> This is due to limitations for some database servers the plugin supports.
+
+### Changing the Table Comment
+
+To change the comment on an existing table, use the `changeComment()` method.
+Pass in a string to set as the new table comment, or `null` to drop the existing comment:
+
+``` php
+table('users');
+ $users
+ ->addColumn('username', 'string', ['limit' => 20])
+ ->addColumn('password', 'string', ['limit' => 40])
+ ->save();
+
+ $users
+ ->changeComment('This is the table with users auth information, password should be encrypted')
+ ->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+
+ }
+}
+```
+
+## Checking Columns
+
+`BaseMigration` also provides methods for introspecting the current schema,
+allowing you to conditionally make changes to schema, or read data.
+Schema is inspected **when the migration is run**.
+
+### Get a column list
+
+To retrieve all table columns, simply create a `table` object and call
+`getColumns()` method. This method will return an array of Column classes with
+basic info. Example below:
+
+``` php
+table('users')->getColumns();
+ ...
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ ...
+ }
+}
+```
+
+### Get a column by name
+
+To retrieve one table column, simply create a `table` object and call the
+`getColumn()` method. This method will return a Column class with basic info
+or NULL when the column doesn't exist. Example below:
+
+``` php
+table('users')->getColumn('email');
+ ...
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down(): void
+ {
+ ...
+ }
+}
+```
+
+### Checking whether a column exists
+
+You can check if a table already has a certain column by using the
+`hasColumn()` method:
+
+``` php
+table('user');
+ $column = $table->hasColumn('username');
+
+ if ($column) {
+ // do something
+ }
+
+ }
+}
+```
+
+### Changing templates
+
+See [Custom Seed Migration Templates](seeding#custom-seed-migration-templates) for how to customize the templates
+used to generate migrations.
+
+## Database-Specific Limitations
+
+While Migrations aims to provide a database-agnostic API, some features have
+database-specific limitations or are not available on all platforms.
+
+### SQL Server
+
+The following features are not supported on SQL Server:
+
+**Check Constraints**
+
+Check constraints are not currently implemented for SQL Server. Attempting to
+use `addCheckConstraint()` or `dropCheckConstraint()` will throw a
+`BadMethodCallException`.
+
+**Table Comments**
+
+SQL Server does not support table comments. Attempting to use `changeComment()`
+will throw a `BadMethodCallException`.
+
+**INSERT IGNORE / insertOrSkip()**
+
+SQL Server does not support the `INSERT IGNORE` syntax used by `insertOrSkip()`.
+This method will throw a `RuntimeException` on SQL Server. Use `insertOrUpdate()`
+instead for upsert operations, which uses `MERGE` statements on SQL Server.
+
+### SQLite
+
+**Foreign Key Names**
+
+SQLite does not support named foreign keys. The foreign key constraint name option
+is ignored when creating foreign keys on SQLite.
+
+**Table Comments**
+
+SQLite does not support table comments directly. Comments are stored as metadata
+but not in the database itself.
+
+**Check Constraint Modifications**
+
+SQLite does not support `ALTER TABLE` operations for check constraints. Adding or
+dropping check constraints requires recreating the entire table, which is handled
+automatically by the adapter.
+
+**Table Partitioning**
+
+SQLite does not support table partitioning.
+
+### PostgreSQL
+
+**KEY Partitioning**
+
+PostgreSQL does not support MySQL's `KEY` partitioning type. Use `HASH`
+partitioning instead for similar distribution behavior.
+
+### MySQL/MariaDB
+
+**insertOrUpdate() Conflict Columns**
+
+For MySQL, the `$conflictColumns` parameter in `insertOrUpdate()` is ignored
+because MySQL's `ON DUPLICATE KEY UPDATE` automatically applies to all unique
+constraints. PostgreSQL and SQLite require this parameter to be specified.
+
+**MariaDB GIS/Geometry**
+
+Some geometry column features may not work correctly on MariaDB due to differences
+in GIS implementation compared to MySQL.
diff --git a/docs/en/writing-migrations.rst b/docs/en/writing-migrations.rst
deleted file mode 100644
index 6da4195d1..000000000
--- a/docs/en/writing-migrations.rst
+++ /dev/null
@@ -1,2458 +0,0 @@
-Writing Migrations
-##################
-
-Migrations are a declarative API that helps you transform your database. Each migration
-is represented by a PHP class in a unique file. It is preferred that you write
-your migrations using the Migrations API, but raw SQL is also supported.
-
-Creating a New Migration
-========================
-
-Let's start by creating a new migration with ``bake``:
-
-.. code-block:: bash
-
- $ bin/cake bake migration
-
-This will create a new migration in the format
-``YYYYMMDDHHMMSS_my_new_migration.php``, where the first 14 characters are
-replaced with the current timestamp down to the second.
-
-If you have specified multiple migration paths, you will be asked to select
-which path to create the new migration in.
-
-Bake will automatically creates a skeleton migration file with a single method::
-
- table('user_logins');
- $table->addColumn('user_id', 'integer')
- ->addColumn('created', 'datetime')
- ->create();
- }
- }
-
-When executing this migration, Migrations will create the ``user_logins`` table on
-the way up and automatically figure out how to drop the table on the way down.
-Please be aware that when a ``change`` method exists, Migrations will
-ignore the ``up`` and ``down`` methods. If you need to use these methods it is
-recommended to create a separate migration file.
-
-.. note::
-
- When creating or updating tables inside a ``change()`` method you must use
- the Table ``create()`` and ``update()`` methods. Migrations cannot automatically
- determine whether a ``save()`` call is creating a new table or modifying an
- existing one.
-
-The following actions are reversible when done through the Table API in
-Migrations, and will be automatically reversed:
-
-- Creating a table
-- Renaming a table
-- Adding a column
-- Renaming a column
-- Adding an index
-- Adding a foreign key
-- Adding a check constraint
-
-If a command cannot be reversed then Migrations will throw an
-``IrreversibleMigrationException`` when it's migrating down. If you wish to
-use a command that cannot be reversed in the change function, you can use an
-if statement with ``$this->isMigratingUp()`` to only run things in the
-up or down direction. For example::
-
- table('user_logins');
- $table->addColumn('user_id', 'integer')
- ->addColumn('created', 'datetime')
- ->create();
- if ($this->isMigratingUp()) {
- $table->insert([['user_id' => 1, 'created' => '2020-01-19 03:14:07']])
- ->save();
- }
- }
- }
-
-
-The Up Method
-=============
-
-The up method is automatically run by Migrations when you are migrating up and it
-detects the given migration hasn't been executed previously. You should use the
-up method to transform the database with your intended changes.
-
-The Down Method
-===============
-
-The down method is automatically run by Migrations when you are migrating down and
-it detects the given migration has been executed in the past. You should use
-the down method to reverse/undo the transformations described in the up method.
-
-The Init Method
-===============
-
-The ``init()`` method is run by Migrations before the migration methods if it exists.
-This can be used for setting common class properties that are then used within
-the migration methods.
-
-The Should Execute Method
-=========================
-
-The ``shouldExecute()`` method is run by Migrations before executing the migration.
-This can be used to prevent the migration from being executed at this time. It always
-returns true by default. You can override it in your custom ``BaseMigration``
-implementation.
-
-Working With Tables
-===================
-
-The Table object enables you to easily manipulate database tables using PHP
-code. You can retrieve an instance of the Table object by calling the
-``table()`` method from within your database migration::
-
- table('tableName');
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-You can then manipulate this table using the methods provided by the Table
-object.
-
-.. _adding-columns:
-
-Adding Columns
-==============
-
-Column types are specified as strings and can be one of:
-
-- binary
-- boolean
-- char
-- date
-- datetime
-- decimal
-- float
-- double
-- smallinteger
-- integer
-- biginteger
-- string
-- text
-- time
-- timestamp
-- uuid
-- binaryuuid
-- nativeuuid
-
-In addition, the MySQL adapter supports ``enum``, ``set``, ``blob``,
-``tinyblob``, ``mediumblob``, ``longblob``, ``bit`` and ``json`` column types
-(``json`` in MySQL 5.7 and above). When providing a limit value and using
-``binary``, ``varbinary`` or ``blob`` and its subtypes, the retained column type
-will be based on required length (see `Limit Option and MySQL`_ for details).
-
-With most adapters, the ``uuid`` and ``nativeuuid`` column types are aliases,
-however with the MySQL adapter + MariaDB, the ``nativeuuid`` type maps to
-a native uuid column instead of ``CHAR(36)`` like ``uuid`` does.
-
-In addition, the Postgres adapter supports ``interval``, ``json``, ``jsonb``,
-``uuid``, ``cidr``, ``inet`` and ``macaddr`` column types (PostgreSQL 9.3 and
-above).
-
-Valid Column Options
---------------------
-
-The following are valid column options:
-
-For any column type:
-
-======= ===========
-Option Description
-======= ===========
-limit set maximum length for strings, also hints column types in adapters (see note below)
-length alias for ``limit``
-default set default value or action
-null allow ``NULL`` values, defaults to ``true`` (setting ``identity`` will override default to ``false``)
-after specify the column that a new column should be placed after, or use ``\Migrations\Db\Adapter\MysqlAdapter::FIRST`` to place the column at the start of the table *(only applies to MySQL)*
-comment set a text comment on the column
-======= ===========
-
-For ``decimal`` and ``float`` columns:
-
-========= ===========
-Option Description
-========= ===========
-precision total number of digits (e.g., 10 in ``DECIMAL(10,2)``)
-scale number of digits after the decimal point (e.g., 2 in ``DECIMAL(10,2)``)
-signed enable or disable the ``unsigned`` option *(only applies to MySQL)*
-========= ===========
-
-.. note::
-
- **Precision and Scale Terminology**
-
- Migrations follows the SQL standard where ``precision`` represents the total number of digits,
- and ``scale`` represents digits after the decimal point. For example, to create ``DECIMAL(10,2)``
- (10 total digits with 2 decimal places):
-
- .. code-block:: php
-
- $table->addColumn('price', 'decimal', [
- 'precision' => 10, // Total digits
- 'scale' => 2, // Decimal places
- ]);
-
- This differs from CakePHP's TableSchema which uses ``length`` for total digits and
- ``precision`` for decimal places. The migration adapter handles this conversion automatically.
-
-For ``enum`` and ``set`` columns:
-
-========= ===========
-Option Description
-========= ===========
-values Can be a comma separated list or an array of values
-========= ===========
-
-For ``smallinteger``, ``integer`` and ``biginteger`` columns:
-
-======== ===========
-Option Description
-======== ===========
-identity enable or disable automatic incrementing (if enabled, will set ``null: false`` if ``null`` option is not set)
-signed enable or disable the ``unsigned`` option *(only applies to MySQL)*
-========
-
-For Postgres, when using ``identity``, it will utilize the ``serial`` type
-appropriate for the integer size, so that ``smallinteger`` will give you
-``smallserial``, ``integer`` gives ``serial``, and ``biginteger`` gives
-``bigserial``.
-
-For ``date`` columns:
-
-======== ===========
-Option Description
-======== ===========
-default set default value (use with ``CURRENT_DATE``)
-======== ===========
-
-For ``time`` columns:
-
-======== ===========
-Option Description
-======== ===========
-default set default value (use with ``CURRENT_TIME``)
-timezone enable or disable the ``with time zone`` option *(only applies to Postgres)*
-======== ===========
-
-For ``datetime`` columns:
-
-======== ===========
-Option Description
-======== ===========
-default set default value (use with ``CURRENT_TIMESTAMP``)
-timezone enable or disable the ``with time zone`` option *(only applies to Postgres)*
-======== ===========
-
-For ``timestamp`` columns:
-
-======== ===========
-Option Description
-======== ===========
-default set default value (use with ``CURRENT_TIMESTAMP``)
-update set an action to be triggered when the row is updated (use with ``CURRENT_TIMESTAMP``) *(only applies to MySQL)*
-timezone enable or disable the ``with time zone`` option for ``time`` and ``timestamp`` columns *(only applies to Postgres)*
-======== ===========
-
-You can add ``created`` and ``updated`` timestamps to a table using the
-``addTimestamps()`` method. This method accepts three arguments, where the first
-two allow setting alternative names for the columns while the third argument
-allows you to enable the ``timezone`` option for the columns. The defaults for
-these arguments are ``created``, ``updated``, and ``false`` respectively. For
-the first and second argument, if you provide ``null``, then the default name
-will be used, and if you provide ``false``, then that column will not be
-created. Please note that attempting to set both to ``false`` will throw
-a ``\RuntimeException``. Additionally, you can use the
-``addTimestampsWithTimezone()`` method, which is an alias to ``addTimestamps()``
-that will always set the third argument to ``true`` (see examples below). The
-``created`` column will have a default set to ``CURRENT_TIMESTAMP``. For MySQL
-only, ``updated`` column will have update set to
-``CURRENT_TIMESTAMP``::
-
- table('users')->addTimestamps()->create();
- // Use defaults (with timezones)
- $table = $this->table('users')->addTimestampsWithTimezone()->create();
-
- // Override the 'created' column name with 'recorded_at'.
- $table = $this->table('books')->addTimestamps('recorded_at')->create();
-
- // Override the 'updated' column name with 'amended_at', preserving timezones.
- // The two lines below do the same, the second one is simply cleaner.
- $table = $this->table('books')->addTimestamps(null, 'amended_at', true)->create();
- $table = $this->table('users')->addTimestampsWithTimezone(null, 'amended_at')->create();
-
- // Only add the created column to the table
- $table = $this->table('books')->addTimestamps(null, false);
- // Only add the updated column to the table
- $table = $this->table('users')->addTimestamps(false);
- // Note, setting both false will throw a \RuntimeError
- }
- }
-
-For ``boolean`` columns:
-
-======== ===========
-Option Description
-======== ===========
-signed enable or disable the ``unsigned`` option *(only applies to MySQL)*
-======== ===========
-
-For ``string`` and ``text`` columns:
-
-========= ===========
-Option Description
-========= ===========
-collation set collation that differs from table defaults *(only applies to MySQL)*
-encoding set character set that differs from table defaults *(only applies to MySQL)*
-========= ===========
-
-Limit Option and MySQL
-----------------------
-
-When using the MySQL adapter, there are a couple things to consider when working with limits:
-
-- When using a ``string`` primary key or index on MySQL 5.7 or below, or the
- MyISAM storage engine, and the default charset of ``utf8mb4_unicode_ci``, you
- must specify a limit less than or equal to 191, or use a different charset.
-- Additional hinting of database column type can be made for ``integer``,
- ``text``, ``blob``, ``tinyblob``, ``mediumblob``, ``longblob`` columns. Using
- ``limit`` with one the following options will modify the column type
- accordingly:
-
-============ ==============
-Limit Column Type
-============ ==============
-BLOB_TINY TINYBLOB
-BLOB_REGULAR BLOB
-BLOB_MEDIUM MEDIUMBLOB
-BLOB_LONG LONGBLOB
-TEXT_TINY TINYTEXT
-TEXT_REGULAR TEXT
-TEXT_MEDIUM MEDIUMTEXT
-TEXT_LONG LONGTEXT
-INT_TINY TINYINT
-INT_SMALL SMALLINT
-INT_MEDIUM MEDIUMINT
-INT_REGULAR INT
-INT_BIG BIGINT
-============ ==============
-
-For ``binary`` or ``varbinary`` types, if limit is set greater than allowed 255
-bytes, the type will be changed to the best matching blob type given the
-length::
-
- table('cart_items');
- $table->addColumn('user_id', 'integer')
- ->addColumn('product_id', 'integer', ['limit' => MysqlAdapter::INT_BIG])
- ->addColumn('subtype_id', 'integer', ['limit' => MysqlAdapter::INT_SMALL])
- ->addColumn('quantity', 'integer', ['limit' => MysqlAdapter::INT_TINY])
- ->create();
-
-Default values with expressions
--------------------------------
-
-If you need to set a default to an expression, you can use a ``Literal`` to have
-the column's default value used without any quoting or escaping. This is helpful
-when you want to use a function as a default value::
-
- use Migrations\BaseMigration;
- use Migrations\Db\Literal;
-
- class AddSomeColumns extends BaseMigration
- {
- public function change(): void
- {
- $this->table('users')
- ->addColumn('uniqid', 'uuid', [
- 'default' => Literal::from('uuid_generate_v4()')
- ])
- ->create();
- }
- }
-
-.. _creating-a-table::
-
-Creating a Table
-----------------
-
-Creating a table is really easy using the Table object. Let's create a table to
-store a collection of users::
-
- table('users');
- $users->addColumn('username', 'string', ['limit' => 20])
- ->addColumn('password', 'string', ['limit' => 40])
- ->addColumn('password_salt', 'string', ['limit' => 40])
- ->addColumn('email', 'string', ['limit' => 100])
- ->addColumn('first_name', 'string', ['limit' => 30])
- ->addColumn('last_name', 'string', ['limit' => 30])
- ->addColumn('created', 'datetime')
- ->addColumn('updated', 'datetime', ['null' => true])
- ->addIndex(['username', 'email'], ['unique' => true])
- ->create();
- }
- }
-
-Columns are added using the ``addColumn()`` method. We create a unique index
-for both the username and email columns using the ``addIndex()`` method.
-Finally calling ``create()`` commits the changes to the database.
-
-.. note::
-
- Migrations automatically creates an auto-incrementing primary key column called ``id`` for every
- table.
-
-The ``id`` option sets the name of the automatically created identity field,
-while the ``primary_key`` option selects the field or fields used for primary
-key. ``id`` will always override the ``primary_key`` option unless it's set to
-false. If you don't need a primary key set ``id`` to false without specifying
-a ``primary_key``, and no primary key will be created.
-
-To specify an alternate primary key, you can specify the ``primary_key`` option
-when accessing the Table object. Let's disable the automatic ``id`` column and
-create a primary key using two columns instead::
-
- table('followers', ['id' => false, 'primary_key' => ['user_id', 'follower_id']]);
- $table->addColumn('user_id', 'integer')
- ->addColumn('follower_id', 'integer')
- ->addColumn('created', 'datetime')
- ->create();
- }
- }
-
-Setting a single ``primary_key`` doesn't enable the ``AUTO_INCREMENT`` option.
-To simply change the name of the primary key, we need to override the default ``id`` field name::
-
- table('followers', ['id' => 'user_id']);
- $table->addColumn('follower_id', 'integer')
- ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
- ->create();
- }
- }
-
-In addition, the MySQL adapter supports following options:
-
-========== ================ ===========
-Option Platform Description
-========== ================ ===========
-comment MySQL, Postgres set a text comment on the table
-collation MySQL, SqlServer set the table collation *(defaults to database collation)*
-row_format MySQL set the table row format
-engine MySQL define table engine *(defaults to ``InnoDB``)*
-signed MySQL whether the primary key is ``signed`` *(defaults to ``true``)*
-limit MySQL set the maximum length for the primary key
-========== ================ ===========
-
-By default, the primary key is ``signed``.
-To set it to be unsigned, pass the ``signed`` option with a ``false``
-value, or enable the ``Migrations.unsigned_primary_keys`` and
-``Migrations.unsigned_ints`` feature flags (see :ref:`feature-flags`).
-Both flags should be used together so that foreign key columns match
-the primary keys they reference::
-
- table('followers', ['signed' => false]);
- $table->addColumn('follower_id', 'integer')
- ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
- ->create();
- }
- }
-
-If you need to create a table with a different collation than the database,
-use::
-
- table('categories', [
- 'collation' => 'latin1_german1_ci'
- ])
- ->addColumn('title', 'string')
- ->create();
- }
- }
-
-Note however this can only be done on table creation : there is currently no way
-of adding a column to an existing table with a different collation than the
-table or the database. Only ``MySQL`` and ``SqlServer`` supports this
-configuration key for the time being.
-
-To view available column types and options, see :ref:`adding-columns` for details.
-
-MySQL ALTER TABLE Options
--------------------------
-
-.. versionadded:: 5.0.0
- ``ALGORITHM`` and ``LOCK`` options were added in 5.0.0.
-
-When modifying tables in MySQL, you can control how the ALTER TABLE operation is
-performed using the ``algorithm`` and ``lock`` options. This is useful for performing
-zero-downtime schema changes on large tables in production environments.
-
-.. code-block:: php
-
- table('large_table');
- $table->addIndex(['status'], [
- 'name' => 'idx_status',
- ]);
- $table->update([
- 'algorithm' => 'INPLACE',
- 'lock' => 'NONE',
- ]);
- }
- }
-
-Available ``algorithm`` values:
-
-============ ===========
-Algorithm Description
-============ ===========
-DEFAULT Let MySQL choose the algorithm (default behavior)
-INPLACE Modify the table in place without copying data (when possible)
-COPY Create a copy of the table with the changes (legacy method)
-INSTANT Only modify metadata, no table rebuild (MySQL 8.0+, limited operations)
-============ ===========
-
-Available ``lock`` values:
-
-========= ===========
-Lock Description
-========= ===========
-DEFAULT Use minimal locking for the algorithm (default behavior)
-NONE Allow concurrent reads and writes during the operation
-SHARED Allow concurrent reads but block writes
-EXCLUSIVE Block all reads and writes during the operation
-========= ===========
-
-.. note::
-
- Not all operations support all algorithm/lock combinations. MySQL will raise
- an error if the requested combination is not possible for the operation.
- The ``INSTANT`` algorithm is only available in MySQL 8.0+ and only for specific
- operations like adding columns at the end of a table.
-
-.. warning::
-
- Using ``ALGORITHM=INPLACE, LOCK=NONE`` does not guarantee zero-downtime for
- all operations. Some operations may still require a table copy or exclusive lock.
- Always test schema changes on a staging environment first.
-
-Table Partitioning
-------------------
-
-Migrations supports table partitioning for MySQL and PostgreSQL. Partitioning helps
-manage large tables by splitting them into smaller, more manageable pieces.
-
-.. note::
-
- Partition columns must be included in the primary key for MySQL. SQLite does
- not support partitioning. MySQL's ``RANGE`` and ``LIST`` types only work with
- integer columns - use ``RANGE COLUMNS`` and ``LIST COLUMNS`` for DATE/STRING columns.
-
-RANGE Partitioning
-~~~~~~~~~~~~~~~~~~
-
-RANGE partitioning is useful when you want to partition by numeric ranges. For MySQL,
-use ``TYPE_RANGE`` with integer columns or expressions, and ``TYPE_RANGE_COLUMNS`` for
-DATE/DATETIME/STRING columns::
-
- table('orders', [
- 'id' => false,
- 'primary_key' => ['id', 'order_date'],
- ]);
- $table->addColumn('id', 'integer', ['identity' => true])
- ->addColumn('order_date', 'date')
- ->addColumn('amount', 'decimal', ['precision' => 10, 'scale' => 2])
- ->partitionBy(Partition::TYPE_RANGE_COLUMNS, 'order_date')
- ->addPartition('p2022', '2023-01-01')
- ->addPartition('p2023', '2024-01-01')
- ->addPartition('p2024', '2025-01-01')
- ->addPartition('pmax', 'MAXVALUE')
- ->create();
- }
- }
-
-LIST Partitioning
-~~~~~~~~~~~~~~~~~
-
-LIST partitioning is useful when you want to partition by discrete values. For MySQL,
-use ``TYPE_LIST`` with integer columns and ``TYPE_LIST_COLUMNS`` for STRING columns::
-
- table('customers', [
- 'id' => false,
- 'primary_key' => ['id', 'region'],
- ]);
- $table->addColumn('id', 'integer', ['identity' => true])
- ->addColumn('region', 'string', ['limit' => 20])
- ->addColumn('name', 'string')
- ->partitionBy(Partition::TYPE_LIST_COLUMNS, 'region')
- ->addPartition('p_americas', ['US', 'CA', 'MX', 'BR'])
- ->addPartition('p_europe', ['UK', 'DE', 'FR', 'IT'])
- ->addPartition('p_asia', ['JP', 'CN', 'IN', 'KR'])
- ->create();
- }
- }
-
-HASH Partitioning
-~~~~~~~~~~~~~~~~~
-
-HASH partitioning distributes data evenly across a specified number of partitions::
-
- table('sessions');
- $table->addColumn('user_id', 'integer')
- ->addColumn('data', 'text')
- ->partitionBy(Partition::TYPE_HASH, 'user_id', ['count' => 8])
- ->create();
- }
- }
-
-KEY Partitioning (MySQL only)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-KEY partitioning is similar to HASH but uses MySQL's internal hashing function::
-
- table('cache', [
- 'id' => false,
- 'primary_key' => ['cache_key'],
- ]);
- $table->addColumn('cache_key', 'string', ['limit' => 255])
- ->addColumn('value', 'binary')
- ->partitionBy(Partition::TYPE_KEY, 'cache_key', ['count' => 16])
- ->create();
- }
- }
-
-Partitioning with Expressions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can partition by expressions using the ``Literal`` class::
-
- table('events', [
- 'id' => false,
- 'primary_key' => ['id', 'created_at'],
- ]);
- $table->addColumn('id', 'integer', ['identity' => true])
- ->addColumn('created_at', 'datetime')
- ->partitionBy(Partition::TYPE_RANGE, Literal::from('YEAR(created_at)'))
- ->addPartition('p2022', 2023)
- ->addPartition('p2023', 2024)
- ->addPartition('pmax', 'MAXVALUE')
- ->create();
- }
- }
-
-Modifying Partitions on Existing Tables
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can add or drop partitions on existing partitioned tables::
-
- table('orders')
- ->addPartitionToExisting('p2025', '2026-01-01')
- ->update();
- }
-
- public function down(): void
- {
- // Drop the partition
- $this->table('orders')
- ->dropPartition('p2025')
- ->update();
- }
- }
-
-Saving Changes
---------------
-
-When working with the Table object, Migrations stores certain operations in a
-pending changes cache. Once you have made the changes you want to the table,
-you must save them. To perform this operation, Migrations provides three methods,
-``create()``, ``update()``, and ``save()``. ``create()`` will first create
-the table and then run the pending changes. ``update()`` will just run the
-pending changes, and should be used when the table already exists. ``save()``
-is a helper function that checks first if the table exists and if it does not
-will run ``create()``, else it will run ``update()``.
-
-As stated above, when using the ``change()`` migration method, you should always
-use ``create()`` or ``update()``, and never ``save()`` as otherwise migrating
-and rolling back may result in different states, due to ``save()`` calling
-``create()`` when running migrate and then ``update()`` on rollback. When
-using the ``up()``/``down()`` methods, it is safe to use either ``save()`` or
-the more explicit methods.
-
-When in doubt with working with tables, it is always recommended to call
-the appropriate function and commit any pending changes to the database.
-
-
-Renaming a Column
------------------
-
-To rename a column, access an instance of the Table object then call the
-``renameColumn()`` method::
-
- table('users');
- $table->renameColumn('bio', 'biography')
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- $table = $this->table('users');
- $table->renameColumn('biography', 'bio')
- ->save();
- }
- }
-
-Adding a Column After Another Column
-------------------------------------
-
-When adding a column with the MySQL adapter, you can dictate its position using
-the ``after`` option, where its value is the name of the column to position it
-after::
-
- table('users');
- $table->addColumn('city', 'string', ['after' => 'email'])
- ->update();
- }
- }
-
-This would create the new column ``city`` and position it after the ``email``
-column. The ``\Migrations\Db\Adapter\MysqlAdapter::FIRST`` constant can be used
-to specify that the new column should be created as the first column in that
-table.
-
-Dropping a Column
------------------
-
-To drop a column, use the ``removeColumn()`` method::
-
- table('users');
- $table->removeColumn('short_name')
- ->save();
- }
- }
-
-
-Specifying a Column Limit
--------------------------
-
-You can limit the maximum length of a column by using the ``limit`` option::
-
- table('tags');
- $table->addColumn('short_name', 'string', ['limit' => 30])
- ->update();
- }
- }
-
-Changing Column Attributes
---------------------------
-
-There are two methods for modifying existing columns:
-
-Updating Columns (Recommended)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To modify specific column attributes while preserving others, use the ``updateColumn()`` method.
-This method automatically preserves unspecified attributes like defaults, nullability, limits, etc.::
-
- table('users');
- // Make email nullable, preserving all other attributes
- $users->updateColumn('email', null, ['null' => true])
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- $users = $this->table('users');
- $users->updateColumn('email', null, ['null' => false])
- ->save();
- }
- }
-
-You can pass ``null`` as the column type to preserve the existing type, or specify a new type::
-
- // Preserve type and other attributes, only change nullability
- $table->updateColumn('email', null, ['null' => true]);
-
- // Change type to biginteger, preserve default and other attributes
- $table->updateColumn('user_id', 'biginteger');
-
- // Change default value, preserve everything else
- $table->updateColumn('status', null, ['default' => 'active']);
-
-The following attributes are automatically preserved by ``updateColumn()``:
-
-- Default values
-- NULL/NOT NULL constraint
-- Column limit/length
-- Decimal scale/precision
-- Comments
-- Signed/unsigned (for numeric types)
-- Collation and encoding
-- Enum/set values
-
-Changing Columns (Traditional)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To completely replace a column definition, use the ``changeColumn()`` method.
-This method requires you to specify all desired column attributes.
-See :ref:`valid-column-types` and `Valid Column Options`_ for allowed values::
-
- table('users');
- // Must specify all attributes
- $users->changeColumn('email', 'string', [
- 'limit' => 255,
- 'null' => true,
- 'default' => null,
- ])
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-You can enable attribute preservation with ``changeColumn()`` by passing
-``'preserveUnspecified' => true`` in the options::
-
- $table->changeColumn('email', 'string', [
- 'null' => true,
- 'preserveUnspecified' => true,
- ]);
-
-.. note::
-
- For most use cases, ``updateColumn()`` is recommended as it is safer and requires
- less code. Use ``changeColumn()`` when you need to completely redefine a column
- or when working with legacy code that expects the traditional behavior.
-
-Working With Indexes
---------------------
-
-To add an index to a table you can simply call the ``addIndex()`` method on the
-table object::
-
- table('users');
- $table->addColumn('city', 'string')
- ->addIndex(['city'])
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-By default Migrations instructs the database adapter to create a simple index. We
-can pass an additional parameter ``unique`` to the ``addIndex()`` method to
-specify a unique index. We can also explicitly specify a name for the index
-using the ``name`` parameter, the index columns sort order can also be specified using
-the ``order`` parameter. The order parameter takes an array of column names and sort order key/value pairs::
-
- table('users');
- $table->addColumn('email', 'string')
- ->addColumn('username','string')
- ->addIndex(['email', 'username'], [
- 'unique' => true,
- 'name' => 'idx_users_email',
- 'order' => ['email' => 'DESC', 'username' => 'ASC']]
- )
- ->save();
- }
- }
-
-As of 4.6.0, you can use ``BaseMigration::index()`` to get a fluent builder to
-define indexes::
-
- table('users');
- $table->addColumn('email', 'string')
- ->addColumn('username','string')
- ->addIndex(
- $this->index(['email', 'username'])
- ->setType('unique')
- ->setName('idx_users_email')
- ->setOrder(['email' => 'DESC', 'username' => 'ASC'])
- )
- ->save();
- }
- }
-
-
-The MySQL adapter also supports ``fulltext`` indexes. If you are using a version before 5.6 you must
-ensure the table uses the ``MyISAM`` engine::
-
- table('users', ['engine' => 'MyISAM']);
- $table->addColumn('email', 'string')
- ->addIndex('email', ['type' => 'fulltext'])
- ->create();
- }
- }
-
-MySQL adapter supports setting the index length defined by limit option.
-When you are using a multi-column index, you are able to define each column index length.
-The single column index can define its index length with or without defining column name in limit option::
-
- table('users');
- $table->addColumn('email', 'string')
- ->addColumn('username','string')
- ->addColumn('user_guid', 'string', ['limit' => 36])
- ->addIndex(['email','username'], ['limit' => ['email' => 5, 'username' => 2]])
- ->addIndex('user_guid', ['limit' => 6])
- ->create();
- }
- }
-
-The SQL Server and PostgreSQL adapters support ``include`` (non-key) columns on indexes::
-
- table('users');
- $table->addColumn('email', 'string')
- ->addColumn('firstname','string')
- ->addColumn('lastname','string')
- ->addIndex(['email'], ['include' => ['firstname', 'lastname']])
- ->create();
- }
- }
-
-PostgreSQL, SQLServer, and SQLite support partial indexes by defining where
-clauses for the index::
-
- table('users');
- $table->addColumn('email', 'string')
- ->addColumn('is_verified','boolean')
- ->addIndex(
- $this->index('email')
- ->setName('user_email_verified_idx')
- ->setType('unique')
- ->setWhere('is_verified = true')
- )
- ->create();
- }
- }
-
-PostgreSQL can create indexes concurrently which avoids taking disruptive locks
-during index creation::
-
- table('users');
- $table->addColumn('email', 'string')
- ->addIndex(
- $this->index('email')
- ->setName('user_email_unique_idx')
- ->setType('unique')
- ->setConcurrently(true)
- )
- ->create();
- }
- }
-
-PostgreSQL adapters also supports Generalized Inverted Index ``gin`` indexes::
-
- table('users');
- $table->addColumn('address', 'string')
- ->addIndex('address', ['type' => 'gin'])
- ->create();
- }
- }
-
-Removing indexes is as easy as calling the ``removeIndex()`` method. You must
-call this method for each index::
-
- table('users');
- $table->removeIndex(['email'])
- ->save();
-
- // alternatively, you can delete an index by its name, ie:
- $table->removeIndexByName('idx_users_email')
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-.. versionadded:: 4.6.0
- ``Index::setWhere()``, and ``Index::setConcurrently()`` were added.
-
-
-Working With Foreign Keys
--------------------------
-
-Migrations has support for creating foreign key constraints on your database tables.
-Let's add a foreign key to an example table::
-
- table('tags');
- $table->addColumn('tag_name', 'string')
- ->save();
-
- $refTable = $this->table('tag_relationships');
- $refTable->addColumn('tag_id', 'integer', ['null' => true])
- ->addForeignKey(
- 'tag_id',
- 'tags',
- 'id',
- ['delete'=> 'SET_NULL', 'update'=> 'NO_ACTION'],
- )
- ->save();
-
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-The 'delete' and 'update' options allow you to define the ``ON UPDATE`` and ``ON
-DELETE`` behavior. Possibles values are 'SET_NULL', 'NO_ACTION', 'CASCADE' and
-'RESTRICT'. If 'SET_NULL' is used then the column must be created as nullable
-with the option ``['null' => true]``.
-
-Foreign keys can be defined with arrays of columns to build constraints between
-tables with composite keys::
-
- table('follower_events');
- $table->addColumn('user_id', 'integer')
- ->addColumn('follower_id', 'integer')
- ->addColumn('event_id', 'integer')
- ->addForeignKey(
- ['user_id', 'follower_id'],
- 'followers',
- ['user_id', 'follower_id'],
- [
- 'delete'=> 'NO_ACTION',
- 'update'=> 'NO_ACTION',
- 'constraint' => 'user_follower_id',
- ]
- )
- ->save();
- }
- }
-
-The options parameter of ``addForeignKey()`` supports the following options:
-
-========== ===========
-Option Description
-========== ===========
-update set an action to be triggered when the row is updated
-delete set an action to be triggered when the row is deleted
-constraint set a name to be used by foreign key constraint
-deferrable define deferred constraint application (postgres only)
-========== ===========
-
-Using the ``foreignKey()`` method provides a fluent builder to define a foreign
-key::
-
- table('articles');
- $table->addForeignKey(
- $this->foreignKey()
- ->setColumns('user_id')
- ->setReferencedTable('users')
- ->setReferencedColumns('user_id')
- ->setDelete(ForeignKey::CASCADE)
- ->setName('article_user_fk')
- )
- ->save();
- }
- }
-
-.. versionadded:: 4.6.0
- The ``foreignKey`` method was added.
-
-We can also easily check if a foreign key exists::
-
- table('tag_relationships');
- $exists = $table->hasForeignKey('tag_id');
- if ($exists) {
- // do something
- }
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Finally, to delete a foreign key, use the ``dropForeignKey`` method.
-
-Note that like other methods in the ``Table`` class, ``dropForeignKey`` also
-needs ``save()`` to be called at the end in order to be executed. This allows
-Migrations to intelligently plan migrations when more than one table is
-involved::
-
- table('tag_relationships');
- $table->dropForeignKey('tag_id')->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Working With Check Constraints
--------------------------------
-
-.. versionadded:: 5.0.0
- Check constraints were added in 5.0.0.
-
-Check constraints allow you to enforce data validation rules at the database level.
-They are particularly useful for ensuring data integrity across your application.
-
-.. note::
-
- Check constraints are supported by MySQL 8.0.16+, PostgreSQL, and SQLite.
- SQL Server support is planned for a future release.
-
-Adding a Check Constraint
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can add a check constraint to a table using the ``addCheckConstraint()`` method::
-
- table('products');
- $table->addColumn('price', 'decimal', ['precision' => 10, 'scale' => 2])
- ->addCheckConstraint('price_positive', 'price > 0')
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- $table = $this->table('products');
- $table->dropCheckConstraint('price_positive')
- ->save();
- }
- }
-
-The first argument is the constraint name, and the second is the SQL expression
-that defines the constraint. The expression should evaluate to a boolean value.
-
-Using the CheckConstraint Fluent Builder
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-For more complex scenarios, you can use the ``checkConstraint()`` method to get
-a fluent builder::
-
- table('users');
- $table->addColumn('age', 'integer')
- ->addColumn('status', 'string', ['limit' => 20])
- ->addCheckConstraint(
- $this->checkConstraint()
- ->setName('age_valid')
- ->setExpression('age >= 18 AND age <= 120')
- )
- ->addCheckConstraint(
- $this->checkConstraint()
- ->setName('status_valid')
- ->setExpression("status IN ('active', 'inactive', 'pending')")
- )
- ->save();
- }
- }
-
-Auto-Generated Constraint Names
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you don't specify a constraint name, one will be automatically generated based
-on the table name and expression hash::
-
- table('inventory');
- $table->addColumn('quantity', 'integer')
- // Name will be auto-generated like 'inventory_chk_a1b2c3d4'
- ->addCheckConstraint(
- $this->checkConstraint()
- ->setExpression('quantity >= 0')
- )
- ->save();
- }
- }
-
-Complex Check Constraints
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Check constraints can reference multiple columns and use complex SQL expressions::
-
- table('date_ranges');
- $table->addColumn('start_date', 'date')
- ->addColumn('end_date', 'date')
- ->addColumn('discount', 'decimal', ['precision' => 5, 'scale' => 2])
- ->addCheckConstraint('valid_date_range', 'end_date >= start_date')
- ->addCheckConstraint('valid_discount', 'discount BETWEEN 0 AND 100')
- ->save();
- }
- }
-
-Checking if a Check Constraint Exists
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can verify if a check constraint exists using the ``hasCheckConstraint()`` method::
-
- table('products');
- $exists = $table->hasCheckConstraint('price_positive');
- if ($exists) {
- // do something
- } else {
- $table->addCheckConstraint('price_positive', 'price > 0')
- ->save();
- }
- }
- }
-
-Dropping a Check Constraint
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To remove a check constraint, use the ``dropCheckConstraint()`` method with the
-constraint name::
-
- table('products');
- $table->dropCheckConstraint('price_positive')
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- $table = $this->table('products');
- $table->addCheckConstraint('price_positive', 'price > 0')
- ->save();
- }
- }
-
-.. note::
-
- Like other table operations, ``dropCheckConstraint()`` requires ``save()``
- to be called to execute the change.
-
-Database-Specific Behavior
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-**MySQL (8.0.16+)**
-
-Check constraints are fully supported. MySQL stores constraint metadata in the
-``INFORMATION_SCHEMA.CHECK_CONSTRAINTS`` table.
-
-**PostgreSQL**
-
-Check constraints are fully supported and stored in the ``pg_constraint`` catalog.
-PostgreSQL allows the most flexible expressions in check constraints.
-
-**SQLite**
-
-Check constraints are supported but with some limitations. SQLite does not support
-``ALTER TABLE`` operations for check constraints, so adding or dropping constraints
-requires recreating the entire table. This is handled automatically by the adapter.
-
-**SQL Server**
-
-Check constraint support for SQL Server is planned for a future release.
-
-Determining Whether a Table Exists
-----------------------------------
-
-You can determine whether or not a table exists by using the ``hasTable()``
-method::
-
- hasTable('users');
- if ($exists) {
- // do something
- }
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Dropping a Table
-----------------
-
-Tables can be dropped quite easily using the ``drop()`` method. It is a
-good idea to recreate the table again in the ``down()`` method.
-
-Note that like other methods in the ``Table`` class, ``drop`` also needs ``save()``
-to be called at the end in order to be executed. This allows Migrations to intelligently
-plan migrations when more than one table is involved::
-
- table('users')->drop()->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- $users = $this->table('users');
- $users->addColumn('username', 'string', ['limit' => 20])
- ->addColumn('password', 'string', ['limit' => 40])
- ->addColumn('password_salt', 'string', ['limit' => 40])
- ->addColumn('email', 'string', ['limit' => 100])
- ->addColumn('first_name', 'string', ['limit' => 30])
- ->addColumn('last_name', 'string', ['limit' => 30])
- ->addColumn('created', 'datetime')
- ->addColumn('updated', 'datetime', ['null' => true])
- ->addIndex(['username', 'email'], ['unique' => true])
- ->save();
- }
- }
-
-Renaming a Table
-----------------
-
-To rename a table access an instance of the Table object then call the
-``rename()`` method::
-
- table('users');
- $table
- ->rename('legacy_users')
- ->update();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- $table = $this->table('legacy_users');
- $table
- ->rename('users')
- ->update();
- }
- }
-
-Changing the Primary Key
-------------------------
-
-To change the primary key on an existing table, use the ``changePrimaryKey()``
-method. Pass in a column name or array of columns names to include in the
-primary key, or ``null`` to drop the primary key. Note that the mentioned
-columns must be added to the table, they will not be added implicitly::
-
- table('users');
- $users
- ->addColumn('username', 'string', ['limit' => 20, 'null' => false])
- ->addColumn('password', 'string', ['limit' => 40])
- ->save();
-
- $users
- ->addColumn('new_id', 'integer', ['null' => false])
- ->changePrimaryKey(['new_id', 'username'])
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Creating Custom Primary Keys
-----------------------------
-
-You can specify a ``autoId`` property in the Migration class and set it to
-``false``, which will turn off the automatic ``id`` column creation. You will
-need to manually create the column that will be used as a primary key and add
-it to the table declaration::
-
- table('products');
- $table
- ->addColumn('id', 'uuid')
- ->addPrimaryKey('id')
- ->addColumn('name', 'string')
- ->addColumn('description', 'text')
- ->create();
- }
- }
-
-The above will create a ``CHAR(36)`` ``id`` column that is also the primary key.
-
-When specifying a custom primary key on the command line, you must note
-it as the primary key in the id field, otherwise you may get an error
-regarding duplicate id fields, i.e.:
-
-.. code-block:: bash
-
- bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified
-
-
-All baked migrations and snapshot will use this new way when necessary.
-
-.. warning::
-
- Dealing with primary key can only be done on table creation operations.
- This is due to limitations for some database servers the plugin supports.
-
-Changing the Table Comment
---------------------------
-
-To change the comment on an existing table, use the ``changeComment()`` method.
-Pass in a string to set as the new table comment, or ``null`` to drop the existing comment::
-
- table('users');
- $users
- ->addColumn('username', 'string', ['limit' => 20])
- ->addColumn('password', 'string', ['limit' => 40])
- ->save();
-
- $users
- ->changeComment('This is the table with users auth information, password should be encrypted')
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Checking Columns
-================
-
-``BaseMigration`` also provides methods for introspecting the current schema,
-allowing you to conditionally make changes to schema, or read data.
-Schema is inspected **when the migration is run**.
-
-Get a column list
------------------
-
-To retrieve all table columns, simply create a ``table`` object and call
-``getColumns()`` method. This method will return an array of Column classes with
-basic info. Example below::
-
- table('users')->getColumns();
- ...
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- ...
- }
- }
-
-Get a column by name
---------------------
-
-To retrieve one table column, simply create a ``table`` object and call the
-``getColumn()`` method. This method will return a Column class with basic info
-or NULL when the column doesn't exist. Example below::
-
- table('users')->getColumn('email');
- ...
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- ...
- }
- }
-
-Checking whether a column exists
---------------------------------
-
-You can check if a table already has a certain column by using the
-``hasColumn()`` method::
-
- table('user');
- $column = $table->hasColumn('username');
-
- if ($column) {
- // do something
- }
-
- }
- }
-
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Dropping a Table
-----------------
-
-Tables can be dropped quite easily using the ``drop()`` method. It is a
-good idea to recreate the table again in the ``down()`` method.
-
-Note that like other methods in the ``Table`` class, ``drop`` also needs ``save()``
-to be called at the end in order to be executed. This allows Migrations to intelligently
-plan migrations when more than one table is involved::
-
- table('users')->drop()->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- $users = $this->table('users');
- $users->addColumn('username', 'string', ['limit' => 20])
- ->addColumn('password', 'string', ['limit' => 40])
- ->addColumn('password_salt', 'string', ['limit' => 40])
- ->addColumn('email', 'string', ['limit' => 100])
- ->addColumn('first_name', 'string', ['limit' => 30])
- ->addColumn('last_name', 'string', ['limit' => 30])
- ->addColumn('created', 'datetime')
- ->addColumn('updated', 'datetime', ['null' => true])
- ->addIndex(['username', 'email'], ['unique' => true])
- ->save();
- }
- }
-
-Renaming a Table
-----------------
-
-To rename a table access an instance of the Table object then call the
-``rename()`` method::
-
- table('users');
- $table
- ->rename('legacy_users')
- ->update();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- $table = $this->table('legacy_users');
- $table
- ->rename('users')
- ->update();
- }
- }
-
-Changing the Primary Key
-------------------------
-
-To change the primary key on an existing table, use the ``changePrimaryKey()``
-method. Pass in a column name or array of columns names to include in the
-primary key, or ``null`` to drop the primary key. Note that the mentioned
-columns must be added to the table, they will not be added implicitly::
-
- table('users');
- $users
- ->addColumn('username', 'string', ['limit' => 20, 'null' => false])
- ->addColumn('password', 'string', ['limit' => 40])
- ->save();
-
- $users
- ->addColumn('new_id', 'integer', ['null' => false])
- ->changePrimaryKey(['new_id', 'username'])
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Creating Custom Primary Keys
-----------------------------
-
-You can specify a ``autoId`` property in the Migration class and set it to
-``false``, which will turn off the automatic ``id`` column creation. You will
-need to manually create the column that will be used as a primary key and add
-it to the table declaration::
-
- table('products');
- $table
- ->addColumn('id', 'uuid')
- ->addPrimaryKey('id')
- ->addColumn('name', 'string')
- ->addColumn('description', 'text')
- ->create();
- }
- }
-
-The above will create a ``CHAR(36)`` ``id`` column that is also the primary key.
-
-When specifying a custom primary key on the command line, you must note
-it as the primary key in the id field, otherwise you may get an error
-regarding duplicate id fields, i.e.:
-
-.. code-block:: bash
-
- bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified
-
-
-All baked migrations and snapshot will use this new way when necessary.
-
-.. warning::
-
- Dealing with primary key can only be done on table creation operations.
- This is due to limitations for some database servers the plugin supports.
-
-Changing the Table Comment
---------------------------
-
-To change the comment on an existing table, use the ``changeComment()`` method.
-Pass in a string to set as the new table comment, or ``null`` to drop the existing comment::
-
- table('users');
- $users
- ->addColumn('username', 'string', ['limit' => 20])
- ->addColumn('password', 'string', ['limit' => 40])
- ->save();
-
- $users
- ->changeComment('This is the table with users auth information, password should be encrypted')
- ->save();
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
-
- }
- }
-
-Checking Columns
-================
-
-``BaseMigration`` also provides methods for introspecting the current schema,
-allowing you to conditionally make changes to schema, or read data.
-Schema is inspected **when the migration is run**.
-
-Get a column list
------------------
-
-To retrieve all table columns, simply create a ``table`` object and call
-``getColumns()`` method. This method will return an array of Column classes with
-basic info. Example below::
-
- table('users')->getColumns();
- ...
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- ...
- }
- }
-
-Get a column by name
---------------------
-
-To retrieve one table column, simply create a ``table`` object and call the
-``getColumn()`` method. This method will return a Column class with basic info
-or NULL when the column doesn't exist. Example below::
-
- table('users')->getColumn('email');
- ...
- }
-
- /**
- * Migrate Down.
- */
- public function down(): void
- {
- ...
- }
- }
-
-Checking whether a column exists
---------------------------------
-
-You can check if a table already has a certain column by using the
-``hasColumn()`` method::
-
- table('user');
- $column = $table->hasColumn('username');
-
- if ($column) {
- // do something
- }
-
- }
- }
-
-
-Changing templates
-------------------
-
-See :ref:`custom-seed-migration-templates` for how to customize the templates
-used to generate migrations.
-
-Database-Specific Limitations
-=============================
-
-While Migrations aims to provide a database-agnostic API, some features have
-database-specific limitations or are not available on all platforms.
-
-SQL Server
-----------
-
-The following features are not supported on SQL Server:
-
-**Check Constraints**
-
-Check constraints are not currently implemented for SQL Server. Attempting to
-use ``addCheckConstraint()`` or ``dropCheckConstraint()`` will throw a
-``BadMethodCallException``.
-
-**Table Comments**
-
-SQL Server does not support table comments. Attempting to use ``changeComment()``
-will throw a ``BadMethodCallException``.
-
-**INSERT IGNORE / insertOrSkip()**
-
-SQL Server does not support the ``INSERT IGNORE`` syntax used by ``insertOrSkip()``.
-This method will throw a ``RuntimeException`` on SQL Server. Use ``insertOrUpdate()``
-instead for upsert operations, which uses ``MERGE`` statements on SQL Server.
-
-SQLite
-------
-
-**Foreign Key Names**
-
-SQLite does not support named foreign keys. The foreign key constraint name option
-is ignored when creating foreign keys on SQLite.
-
-**Table Comments**
-
-SQLite does not support table comments directly. Comments are stored as metadata
-but not in the database itself.
-
-**Check Constraint Modifications**
-
-SQLite does not support ``ALTER TABLE`` operations for check constraints. Adding or
-dropping check constraints requires recreating the entire table, which is handled
-automatically by the adapter.
-
-**Table Partitioning**
-
-SQLite does not support table partitioning.
-
-PostgreSQL
-----------
-
-**KEY Partitioning**
-
-PostgreSQL does not support MySQL's ``KEY`` partitioning type. Use ``HASH``
-partitioning instead for similar distribution behavior.
-
-MySQL/MariaDB
--------------
-
-**insertOrUpdate() Conflict Columns**
-
-For MySQL, the ``$conflictColumns`` parameter in ``insertOrUpdate()`` is ignored
-because MySQL's ``ON DUPLICATE KEY UPDATE`` automatically applies to all unique
-constraints. PostgreSQL and SQLite require this parameter to be specified.
-
-**MariaDB GIS/Geometry**
-
-Some geometry column features may not work correctly on MariaDB due to differences
-in GIS implementation compared to MySQL.
diff --git a/docs/package-lock.json b/docs/package-lock.json
new file mode 100644
index 000000000..f22cc50b3
--- /dev/null
+++ b/docs/package-lock.json
@@ -0,0 +1,2104 @@
+{
+ "name": "docs",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "docs",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@cakephp/docs-skeleton": "github:cakephp/docs-skeleton#node-package",
+ "vitepress": "^2.0.0-alpha.16"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@cakephp/docs-skeleton": {
+ "version": "1.0.0",
+ "resolved": "git+ssh://git@github.com/cakephp/docs-skeleton.git#beb4b50177ce8fd81d5113d2e3fc49b107ddf2f2",
+ "bin": {
+ "cakedocs": "bin/cakedocs.js"
+ },
+ "peerDependencies": {
+ "vitepress": "^2.0.0-alpha.15"
+ }
+ },
+ "node_modules/@docsearch/css": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.2.tgz",
+ "integrity": "sha512-fH/cn8BjEEdM2nJdjNMHIvOVYupG6AIDtFVDgIZrNzdCSj4KXr9kd+hsehqsNGYjpUjObeKYKvgy/IwCb1jZYQ==",
+ "license": "MIT"
+ },
+ "node_modules/@docsearch/js": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-4.6.2.tgz",
+ "integrity": "sha512-qj1yoxl3y4GKoK7+VM6fq/rQqPnvUmg3IKzJ9x0VzN14QVzdB/SG/J6VfV1BWT5RcPUFxIcVwoY1fwHM2fSRRw==",
+ "license": "MIT"
+ },
+ "node_modules/@docsearch/sidepanel-js": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/@docsearch/sidepanel-js/-/sidepanel-js-4.6.2.tgz",
+ "integrity": "sha512-Pni85AP/GwRj7fFg8cBJp0U04tzbueBvWSd3gysgnOsVnQVSZwSYncfErUScLE1CAtR+qocPDFjmYR9AMRNJtQ==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
+ "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
+ "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
+ "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
+ "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
+ "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
+ "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
+ "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
+ "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
+ "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
+ "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
+ "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
+ "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
+ "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
+ "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
+ "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
+ "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
+ "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
+ "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
+ "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
+ "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz",
+ "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@iconify-json/simple-icons": {
+ "version": "1.2.75",
+ "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.75.tgz",
+ "integrity": "sha512-KvcCUbvcBWb0sbqLIxHoY8z5/piXY08wcY9gfMhF+ph3AfzGMaSmZFkUY71HSXAljQngXkgs4bdKdekO0HQWvg==",
+ "license": "CC0-1.0",
+ "dependencies": {
+ "@iconify/types": "*"
+ }
+ },
+ "node_modules/@iconify/types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
+ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
+ "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==",
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz",
+ "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz",
+ "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz",
+ "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz",
+ "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz",
+ "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz",
+ "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz",
+ "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz",
+ "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz",
+ "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz",
+ "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz",
+ "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz",
+ "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz",
+ "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz",
+ "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz",
+ "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz",
+ "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz",
+ "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz",
+ "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz",
+ "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz",
+ "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz",
+ "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz",
+ "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz",
+ "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz",
+ "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz",
+ "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@shikijs/core": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz",
+ "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4",
+ "hast-util-to-html": "^9.0.5"
+ }
+ },
+ "node_modules/@shikijs/engine-javascript": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz",
+ "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "oniguruma-to-es": "^4.3.4"
+ }
+ },
+ "node_modules/@shikijs/engine-oniguruma": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz",
+ "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2"
+ }
+ },
+ "node_modules/@shikijs/langs": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz",
+ "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0"
+ }
+ },
+ "node_modules/@shikijs/themes": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz",
+ "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0"
+ }
+ },
+ "node_modules/@shikijs/transformers": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-3.23.0.tgz",
+ "integrity": "sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/core": "3.23.0",
+ "@shikijs/types": "3.23.0"
+ }
+ },
+ "node_modules/@shikijs/types": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz",
+ "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
+ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
+ "license": "MIT"
+ },
+ "node_modules/@types/markdown-it": {
+ "version": "14.1.2",
+ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
+ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/linkify-it": "^5",
+ "@types/mdurl": "^2"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.21",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
+ "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz",
+ "integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==",
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "1.0.0-rc.2"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.31.tgz",
+ "integrity": "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.2",
+ "@vue/shared": "3.5.31",
+ "entities": "^7.0.1",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.31.tgz",
+ "integrity": "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.31",
+ "@vue/shared": "3.5.31"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.31.tgz",
+ "integrity": "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.2",
+ "@vue/compiler-core": "3.5.31",
+ "@vue/compiler-dom": "3.5.31",
+ "@vue/compiler-ssr": "3.5.31",
+ "@vue/shared": "3.5.31",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.8",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.31.tgz",
+ "integrity": "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.31",
+ "@vue/shared": "3.5.31"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.1.tgz",
+ "integrity": "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^8.1.1"
+ }
+ },
+ "node_modules/@vue/devtools-kit": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.1.tgz",
+ "integrity": "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^8.1.1",
+ "birpc": "^2.6.1",
+ "hookable": "^5.5.3",
+ "perfect-debounce": "^2.0.0"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.1.tgz",
+ "integrity": "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.31.tgz",
+ "integrity": "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.31"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.31.tgz",
+ "integrity": "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.31",
+ "@vue/shared": "3.5.31"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.31.tgz",
+ "integrity": "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.31",
+ "@vue/runtime-core": "3.5.31",
+ "@vue/shared": "3.5.31",
+ "csstype": "^3.2.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.31.tgz",
+ "integrity": "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.31",
+ "@vue/shared": "3.5.31"
+ },
+ "peerDependencies": {
+ "vue": "3.5.31"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.31.tgz",
+ "integrity": "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==",
+ "license": "MIT"
+ },
+ "node_modules/@vueuse/core": {
+ "version": "14.2.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz",
+ "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.21",
+ "@vueuse/metadata": "14.2.1",
+ "@vueuse/shared": "14.2.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/@vueuse/integrations": {
+ "version": "14.2.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-14.2.1.tgz",
+ "integrity": "sha512-2LIUpBi/67PoXJGqSDQUF0pgQWpNHh7beiA+KG2AbybcNm+pTGWT6oPGlBgUoDWmYwfeQqM/uzOHqcILpKL7nA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vueuse/core": "14.2.1",
+ "@vueuse/shared": "14.2.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "async-validator": "^4",
+ "axios": "^1",
+ "change-case": "^5",
+ "drauu": "^0.4",
+ "focus-trap": "^7 || ^8",
+ "fuse.js": "^7",
+ "idb-keyval": "^6",
+ "jwt-decode": "^4",
+ "nprogress": "^0.2",
+ "qrcode": "^1.5",
+ "sortablejs": "^1",
+ "universal-cookie": "^7 || ^8",
+ "vue": "^3.5.0"
+ },
+ "peerDependenciesMeta": {
+ "async-validator": {
+ "optional": true
+ },
+ "axios": {
+ "optional": true
+ },
+ "change-case": {
+ "optional": true
+ },
+ "drauu": {
+ "optional": true
+ },
+ "focus-trap": {
+ "optional": true
+ },
+ "fuse.js": {
+ "optional": true
+ },
+ "idb-keyval": {
+ "optional": true
+ },
+ "jwt-decode": {
+ "optional": true
+ },
+ "nprogress": {
+ "optional": true
+ },
+ "qrcode": {
+ "optional": true
+ },
+ "sortablejs": {
+ "optional": true
+ },
+ "universal-cookie": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "14.2.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz",
+ "integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "14.2.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.1.tgz",
+ "integrity": "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/birpc": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
+ "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
+ "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.4",
+ "@esbuild/android-arm": "0.27.4",
+ "@esbuild/android-arm64": "0.27.4",
+ "@esbuild/android-x64": "0.27.4",
+ "@esbuild/darwin-arm64": "0.27.4",
+ "@esbuild/darwin-x64": "0.27.4",
+ "@esbuild/freebsd-arm64": "0.27.4",
+ "@esbuild/freebsd-x64": "0.27.4",
+ "@esbuild/linux-arm": "0.27.4",
+ "@esbuild/linux-arm64": "0.27.4",
+ "@esbuild/linux-ia32": "0.27.4",
+ "@esbuild/linux-loong64": "0.27.4",
+ "@esbuild/linux-mips64el": "0.27.4",
+ "@esbuild/linux-ppc64": "0.27.4",
+ "@esbuild/linux-riscv64": "0.27.4",
+ "@esbuild/linux-s390x": "0.27.4",
+ "@esbuild/linux-x64": "0.27.4",
+ "@esbuild/netbsd-arm64": "0.27.4",
+ "@esbuild/netbsd-x64": "0.27.4",
+ "@esbuild/openbsd-arm64": "0.27.4",
+ "@esbuild/openbsd-x64": "0.27.4",
+ "@esbuild/openharmony-arm64": "0.27.4",
+ "@esbuild/sunos-x64": "0.27.4",
+ "@esbuild/win32-arm64": "0.27.4",
+ "@esbuild/win32-ia32": "0.27.4",
+ "@esbuild/win32-x64": "0.27.4"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/focus-trap": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-8.0.1.tgz",
+ "integrity": "sha512-9ptSG6z51YQOstI/oN4XuVGP/03u2nh0g//qz7L6zX0i6PZiPnkcf3GenXq7N2hZnASXaMxTPpbKwdI+PFvxlw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "tabbable": "^6.4.0"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/hast-util-to-html": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
+ "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "stringify-entities": "^4.0.0",
+ "zwitch": "^2.0.4"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+ "license": "MIT"
+ },
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/mark.js": {
+ "version": "8.11.1",
+ "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz",
+ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==",
+ "license": "MIT"
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/minisearch": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz",
+ "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/oniguruma-parser": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
+ "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==",
+ "license": "MIT"
+ },
+ "node_modules/oniguruma-to-es": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.5.tgz",
+ "integrity": "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==",
+ "license": "MIT",
+ "dependencies": {
+ "oniguruma-parser": "^0.12.1",
+ "regex": "^6.1.0",
+ "regex-recursion": "^6.0.2"
+ }
+ },
+ "node_modules/perfect-debounce": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
+ "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/property-information": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz",
+ "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==",
+ "license": "MIT",
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-recursion": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz",
+ "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
+ "license": "MIT",
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-utilities": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
+ "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
+ "license": "MIT"
+ },
+ "node_modules/rollup": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz",
+ "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.0",
+ "@rollup/rollup-android-arm64": "4.60.0",
+ "@rollup/rollup-darwin-arm64": "4.60.0",
+ "@rollup/rollup-darwin-x64": "4.60.0",
+ "@rollup/rollup-freebsd-arm64": "4.60.0",
+ "@rollup/rollup-freebsd-x64": "4.60.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.0",
+ "@rollup/rollup-linux-arm64-musl": "4.60.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.0",
+ "@rollup/rollup-linux-loong64-musl": "4.60.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.0",
+ "@rollup/rollup-linux-x64-gnu": "4.60.0",
+ "@rollup/rollup-linux-x64-musl": "4.60.0",
+ "@rollup/rollup-openbsd-x64": "4.60.0",
+ "@rollup/rollup-openharmony-arm64": "4.60.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.0",
+ "@rollup/rollup-win32-x64-gnu": "4.60.0",
+ "@rollup/rollup-win32-x64-msvc": "4.60.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/shiki": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz",
+ "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/core": "3.23.0",
+ "@shikijs/engine-javascript": "3.23.0",
+ "@shikijs/engine-oniguruma": "3.23.0",
+ "@shikijs/langs": "3.23.0",
+ "@shikijs/themes": "3.23.0",
+ "@shikijs/types": "3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/tabbable": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
+ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitepress": {
+ "version": "2.0.0-alpha.17",
+ "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-2.0.0-alpha.17.tgz",
+ "integrity": "sha512-Z3VPUpwk/bHYqt1uMVOOK1/4xFiWQov1GNc2FvMdz6kvje4JRXEOngVI9C+bi5jeedMSHiA4dwKkff1NCvbZ9Q==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@docsearch/css": "^4.5.3",
+ "@docsearch/js": "^4.5.3",
+ "@docsearch/sidepanel-js": "^4.5.3",
+ "@iconify-json/simple-icons": "^1.2.69",
+ "@shikijs/core": "^3.22.0",
+ "@shikijs/transformers": "^3.22.0",
+ "@shikijs/types": "^3.22.0",
+ "@types/markdown-it": "^14.1.2",
+ "@vitejs/plugin-vue": "^6.0.4",
+ "@vue/devtools-api": "^8.0.5",
+ "@vue/shared": "^3.5.27",
+ "@vueuse/core": "^14.2.0",
+ "@vueuse/integrations": "^14.2.0",
+ "focus-trap": "^8.0.0",
+ "mark.js": "8.11.1",
+ "minisearch": "^7.2.0",
+ "shiki": "^3.22.0",
+ "vite": "^7.3.1",
+ "vue": "^3.5.27"
+ },
+ "bin": {
+ "vitepress": "bin/vitepress.js"
+ },
+ "peerDependencies": {
+ "markdown-it-mathjax3": "^4",
+ "oxc-minify": "*",
+ "postcss": "^8"
+ },
+ "peerDependenciesMeta": {
+ "markdown-it-mathjax3": {
+ "optional": true
+ },
+ "oxc-minify": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.31",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz",
+ "integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.31",
+ "@vue/compiler-sfc": "3.5.31",
+ "@vue/runtime-dom": "3.5.31",
+ "@vue/server-renderer": "3.5.31",
+ "@vue/shared": "3.5.31"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/docs/package.json b/docs/package.json
new file mode 100644
index 000000000..fb3f3a14b
--- /dev/null
+++ b/docs/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "docs",
+ "version": "1.0.0",
+ "description": "",
+ "main": "config.js",
+ "scripts": {
+ "docs:dev": "vitepress dev",
+ "docs:build": "vitepress build",
+ "docs:preview": "vitepress preview"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "type": "commonjs",
+ "dependencies": {
+ "@cakephp/docs-skeleton": "github:cakephp/docs-skeleton#node-package",
+ "vitepress": "^2.0.0-alpha.16"
+ }
+}