diff --git a/.babelrc b/.babelrc
deleted file mode 100644
index 746b6ab..0000000
--- a/.babelrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "presets": ["env"],
- "env": {
- "production": {
- "presets": ["minify"]
- }
- }
-}
diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 2a1dce4..0000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "env": {
- "browser": true,
- "es6": true,
- "node": true,
- "mocha": true
- },
- "extends": "airbnb-base",
- "rules": {
- "func-names": ["error", "never"],
- "no-param-reassign": [2, { "props": false }],
- "max-len": "off",
- "valid-typeof": ["error", { "requireStringLiterals": false }]
- }
-}
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..aaf3357
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "singleQuote": true,
+ "semi": false,
+ "trailingComma": "all"
+}
diff --git a/.travis.yml b/.travis.yml
index 2430d05..1b22c1b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,3 @@
-
language: node_js
node_js:
- node
@@ -7,10 +6,9 @@ before_deploy:
after_success:
- 'bash <(curl -s https://codecov.io/bash)'
deploy:
- skip_cleanup: true
provider: npm
- email: "$NPM_EMAIL"
- api_key: "$NPM_TOKEN"
+ email: '$NPM_EMAIL'
+ api_key: '$NPM_TOKEN'
skip_cleanup: true
on:
tags: true
diff --git a/README.md b/README.md
index 6504d0f..965d525 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# Maizal
+

> Pronounced like /'māisɔːl/
@@ -19,6 +20,7 @@ $ npm install maizal
```
In the browser
+
```
```
@@ -28,40 +30,42 @@ In the browser
Perform a `Breadth-first` search
```js
-const maizal = require('maizal');
+const maizal = require('maizal')
-maizal.bfs({
- initial: {
- position: 1,
- },
- goals: {
- position: 4,
- },
- actions: [
- {
- name: 'right',
- expand: (state) => {
- if (state.position + 1 > 5) return undefined;
- return { position: state.position + 1 };
- },
+maizal
+ .bfs({
+ initial: {
+ position: 1,
},
- {
- name: 'left',
- expand: (state) => {
- if (state.position - 1 < 0) return undefined;
- return { position: state.position - 1 };
- },
+ goals: {
+ position: 4,
},
- ],
- hash: 'position',
-})
-.then(results => console.log(results))
-.catch(error => console.log(error));
+ actions: [
+ {
+ name: 'right',
+ expand: (state) => {
+ if (state.position + 1 > 5) return undefined
+ return { position: state.position + 1 }
+ },
+ },
+ {
+ name: 'left',
+ expand: (state) => {
+ if (state.position - 1 < 0) return undefined
+ return { position: state.position - 1 }
+ },
+ },
+ ],
+ hash: 'position',
+ })
+ .then((results) => console.log(results))
+ .catch((error) => console.log(error))
```
Perform a `Dijkstra` search
+
```js
-const maizal = require('maizal');
+const maizal = require('maizal')
const config = {
initial: {
@@ -75,15 +79,15 @@ const config = {
name: 'right',
cost: 50,
expand: (state) => {
- if (state.position + 1 > 5) return undefined;
- return { position: state.position + 1 };
+ if (state.position + 1 > 5) return undefined
+ return { position: state.position + 1 }
},
},
{
name: 'left',
expand: (state) => {
- if (state.position - 1 < 0) return undefined;
- return { position: state.position - 1 };
+ if (state.position - 1 < 0) return undefined
+ return { position: state.position - 1 }
},
},
],
@@ -92,10 +96,10 @@ const config = {
async function solveCorridor() {
try {
- const results = await maizal.dijkstra(config);
- console.log(results);
+ const results = await maizal.dijkstra(config)
+ console.log(results)
} catch (error) {
- console.error(error);
+ console.error(error)
}
}
```
@@ -103,7 +107,6 @@ async function solveCorridor() {
> **NOTE:** `async/await` is part of ECMAScript 2017 and is not supported in Internet
> Explorer and older browsers, so use with caution.
-
## Documentation
The config object
@@ -111,27 +114,29 @@ The config object
### Initial state
| Type | Defaults | Optional | Description |
-|--------|----------|----------|-----------------------------|
+| ------ | -------- | -------- | --------------------------- |
| Object | | false | Initial state of the search |
Examples
+
```js
const initial = {
position: [4, 5],
-};
+}
const initial = {
name: 'myFancyName',
-};
+}
```
### Goals
-| Type | Defaults | Optional | Description |
-|--------|----------|----------|-----------------------------|
+| Type | Defaults | Optional | Description |
+| --------------- | -------- | -------- | ------------------- |
| Object \| Array | | false | Set of goals states |
Examples
+
```js
const goals = [
{
@@ -139,70 +144,71 @@ const goals = [
},
{
position: [4, 2],
- }
-];
+ },
+]
const goals = {
height: 200,
-};
+}
```
### Actions
-| Type | Defaults | Optional | Description |
-|--------|----------|----------|-----------------------------|
+| Type | Defaults | Optional | Description |
+| --------------- | -------- | -------- | ----------------------------------------- |
| Object \| Array | | false | Set of actions to take for each new state |
-|Key | Type | Defaults | Optional | Description |
-|-------|-----------|----------|----------|-----------------------------|
-|name | String | 'expand' | true | Action name |
-|cost | Int | 1 | true | Action cost |
-|expand | Function | | false | Function that takes a state as argument and returns the data for the following states |
+| Key | Type | Defaults | Optional | Description |
+| ------ | -------- | -------- | -------- | ------------------------------------------------------------------------------------- |
+| name | String | 'expand' | true | Action name |
+| cost | Int | 1 | true | Action cost |
+| expand | Function | | false | Function that takes a state as argument and returns the data for the following states |
Examples
+
```js
const actions = [
{
expand: (state) => {
- return { position: state.position + 1 };
- }
+ return { position: state.position + 1 }
+ },
},
{
expand: (state) => {
- return Promise.resolve({ position: state.position - 1});
- }
+ return Promise.resolve({ position: state.position - 1 })
+ },
},
{
name: 'myFancyAction',
cost: 80,
expand: (state) => {
- if(state.height > 90) {
- return;
+ if (state.height > 90) {
+ return
}
- return { height: state.height + 10 };
- }
+ return { height: state.height + 10 }
+ },
},
-];
-
+]
```
+
> **NOTE:** Remember to return `undefined` or simply `return` on forbidden actions to avoid generating infinite states
### Hash
Used to establish when two newly generated states are essentially the same , for example, in a maze going to the left one cell and the returning to the same represents essentially the same state and we do not want that
-| Type | Defaults | Optional | Description |
-|--------|----------|----------|-----------------------------|
-| String\|Function| | false | Field or function to determine the equality of two states |
+| Type | Defaults | Optional | Description |
+| ---------------- | -------- | -------- | --------------------------------------------------------- |
+| String\|Function | | false | Field or function to determine the equality of two states |
Examples
```js
-const hash = 'position';
+const hash = 'position'
-const hash = 'height';
+const hash = 'height'
-const hash = (state) => `${state.x},${state.y}`;
+const hash = (state) => `${state.x},${state.y}`
```
### Heuristics
@@ -211,24 +217,24 @@ Used to give a state a 'sense of approaching to the goal'.
It is a function that returns decreasing values as the closer we get to the goal.
It depends purely on your search state representation and you must ensure to provide logical and decreasing values the closer the solution is
-| Type | Defaults | Optional | Description |
-|--------|----------|----------|-----------------------------|
-|Function| | true | Function to determine the 'proximity' to a goal |
+| Type | Defaults | Optional | Description |
+| -------- | -------- | -------- | ----------------------------------------------- |
+| Function | | true | Function to determine the 'proximity' to a goal |
Examples
```js
const heuristics = ({ x, y }) => {
// Euclidean distance
- return Math.sqrt(((x - goal.x) ** 2) + ((y - goal.y) ** 2));
-};
+ return Math.sqrt((x - goal.x) ** 2 + (y - goal.y) ** 2)
+}
const heuristics = ({ x, y }) => {
// Manhattan distance
- return Math.abs((x - goal.x) + (y - goal.y));
-};
+ return Math.abs(x - goal.x + (y - goal.y))
+}
-const heuristics = state => 90 - state.position;
+const heuristics = (state) => 90 - state.position
```
A better documentation its on the way.
@@ -238,20 +244,21 @@ A better documentation its on the way.
Available engines
### Uninformed
+
| Engine | API |
-|---------------|----------|
+| ------------- | -------- |
| Breadth-first | bfs |
| Dijkstra | dijkstra |
| Random-search | random |
| Depth-first | dfs |
### Informed
-| Engine | API |
-|-------------------|----------|
-| Best-first search | bestfs |
-| A* | astar |
-| Weighted-A* | weightedastar |
+| Engine | API |
+| ----------------- | ------------- |
+| Best-first search | bestfs |
+| A\* | astar |
+| Weighted-A\* | weightedastar |
## Promises
diff --git a/bun.lock b/bun.lock
new file mode 100644
index 0000000..7286422
--- /dev/null
+++ b/bun.lock
@@ -0,0 +1,616 @@
+{
+ "lockfileVersion": 1,
+ "configVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "maizal",
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@eslint/markdown": "^7.5.1",
+ "@tsconfig/strictest": "^2.0.8",
+ "@typescript-eslint/eslint-plugin": "^8.46.4",
+ "@typescript-eslint/parser": "^8.46.4",
+ "@vitest/coverage-v8": "^4.0.9",
+ "eslint": "^9.39.1",
+ "globals": "^16.5.0",
+ "prettier": "^3.6.2",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.46.4",
+ "vitest": "^4.0.9",
+ },
+ },
+ },
+ "packages": {
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
+
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
+
+ "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
+
+ "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
+
+ "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
+
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+
+ "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
+
+ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
+
+ "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
+
+ "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
+
+ "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
+
+ "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
+
+ "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
+
+ "@eslint/markdown": ["@eslint/markdown@7.5.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "@eslint/plugin-kit": "^0.4.1", "github-slugger": "^2.0.0", "mdast-util-from-markdown": "^2.0.2", "mdast-util-frontmatter": "^2.0.1", "mdast-util-gfm": "^3.1.0", "micromark-extension-frontmatter": "^2.0.0", "micromark-extension-gfm": "^3.0.0", "micromark-util-normalize-identifier": "^2.0.1" } }, "sha512-R8uZemG9dKTbru/DQRPblbJyXpObwKzo8rv1KYGGuPUPtjM4LXBYM9q5CIZAComzZupws3tWbDwam5AFpPLyJQ=="],
+
+ "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
+
+ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
+
+ "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
+
+ "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
+
+ "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
+
+ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
+
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+ "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
+
+ "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
+
+ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
+
+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.2", "", { "os": "android", "cpu": "arm" }, "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA=="],
+
+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.2", "", { "os": "android", "cpu": "arm64" }, "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g=="],
+
+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ=="],
+
+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw=="],
+
+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA=="],
+
+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA=="],
+
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg=="],
+
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q=="],
+
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA=="],
+
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ=="],
+
+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ=="],
+
+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g=="],
+
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA=="],
+
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ=="],
+
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w=="],
+
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw=="],
+
+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA=="],
+
+ "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.2", "", { "os": "none", "cpu": "arm64" }, "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A=="],
+
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA=="],
+
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg=="],
+
+ "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw=="],
+
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA=="],
+
+ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
+
+ "@tsconfig/strictest": ["@tsconfig/strictest@2.0.8", "", {}, "sha512-XnQ7vNz5HRN0r88GYf1J9JJjqtZPiHt2woGJOo2dYqyHGGcd6OLGqSlBB6p1j9mpzja6Oe5BoPqWmeDx6X9rLw=="],
+
+ "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
+
+ "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
+
+ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
+
+ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
+ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
+
+ "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
+
+ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
+
+ "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
+
+ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+
+ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.46.4", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/type-utils": "8.46.4", "@typescript-eslint/utils": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.46.4", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg=="],
+
+ "@typescript-eslint/parser": ["@typescript-eslint/parser@8.46.4", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/types": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w=="],
+
+ "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.46.4", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.4", "@typescript-eslint/types": "^8.46.4", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ=="],
+
+ "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4" } }, "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA=="],
+
+ "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.46.4", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A=="],
+
+ "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4", "@typescript-eslint/utils": "8.46.4", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ=="],
+
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.46.4", "", {}, "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w=="],
+
+ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.4", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.4", "@typescript-eslint/tsconfig-utils": "8.46.4", "@typescript-eslint/types": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA=="],
+
+ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.46.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/types": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg=="],
+
+ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "eslint-visitor-keys": "^4.2.1" } }, "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw=="],
+
+ "@vitest/coverage-v8": ["@vitest/coverage-v8@4.0.9", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.0.9", "ast-v8-to-istanbul": "^0.3.8", "debug": "^4.4.3", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.2.0", "magicast": "^0.5.1", "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "peerDependencies": { "@vitest/browser": "4.0.9", "vitest": "4.0.9" }, "optionalPeers": ["@vitest/browser"] }, "sha512-70oyhP+Q0HlWBIeGSP74YBw5KSjYhNgSCQjvmuQFciMqnyF36WL2cIkcT7XD85G4JPmBQitEMUsx+XMFv2AzQA=="],
+
+ "@vitest/expect": ["@vitest/expect@4.0.9", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.9", "@vitest/utils": "4.0.9", "chai": "^6.2.0", "tinyrainbow": "^3.0.3" } }, "sha512-C2vyXf5/Jfj1vl4DQYxjib3jzyuswMi/KHHVN2z+H4v16hdJ7jMZ0OGe3uOVIt6LyJsAofDdaJNIFEpQcrSTFw=="],
+
+ "@vitest/mocker": ["@vitest/mocker@4.0.9", "", { "dependencies": { "@vitest/spy": "4.0.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-PUyaowQFHW+9FKb4dsvvBM4o025rWMlEDXdWRxIOilGaHREYTi5Q2Rt9VCgXgPy/hHZu1LeuXtrA/GdzOatP2g=="],
+
+ "@vitest/pretty-format": ["@vitest/pretty-format@4.0.9", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-Hor0IBTwEi/uZqB7pvGepyElaM8J75pYjrrqbC8ZYMB9/4n5QA63KC15xhT+sqHpdGWfdnPo96E8lQUxs2YzSQ=="],
+
+ "@vitest/runner": ["@vitest/runner@4.0.9", "", { "dependencies": { "@vitest/utils": "4.0.9", "pathe": "^2.0.3" } }, "sha512-aF77tsXdEvIJRkj9uJZnHtovsVIx22Ambft9HudC+XuG/on1NY/bf5dlDti1N35eJT+QZLb4RF/5dTIG18s98w=="],
+
+ "@vitest/snapshot": ["@vitest/snapshot@4.0.9", "", { "dependencies": { "@vitest/pretty-format": "4.0.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-r1qR4oYstPbnOjg0Vgd3E8ADJbi4ditCzqr+Z9foUrRhIy778BleNyZMeAJ2EjV+r4ASAaDsdciC9ryMy8xMMg=="],
+
+ "@vitest/spy": ["@vitest/spy@4.0.9", "", {}, "sha512-J9Ttsq0hDXmxmT8CUOWUr1cqqAj2FJRGTdyEjSR+NjoOGKEqkEWj+09yC0HhI8t1W6t4Ctqawl1onHgipJve1A=="],
+
+ "@vitest/utils": ["@vitest/utils@4.0.9", "", { "dependencies": { "@vitest/pretty-format": "4.0.9", "tinyrainbow": "^3.0.3" } }, "sha512-cEol6ygTzY4rUPvNZM19sDf7zGa35IYTm9wfzkHoT/f5jX10IOY7QleWSOh5T0e3I3WVozwK5Asom79qW8DiuQ=="],
+
+ "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
+
+ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
+
+ "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
+
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
+
+ "ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.8", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } }, "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ=="],
+
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
+
+ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+
+ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
+
+ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
+
+ "chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="],
+
+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
+
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+ "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
+
+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
+
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="],
+
+ "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
+
+ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
+
+ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
+
+ "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
+
+ "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+
+ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
+
+ "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
+
+ "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
+
+ "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
+
+ "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
+
+ "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
+
+ "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
+
+ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
+
+ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
+
+ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+
+ "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="],
+
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
+
+ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
+
+ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
+
+ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
+
+ "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
+
+ "fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="],
+
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
+
+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
+
+ "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
+
+ "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
+
+ "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
+
+ "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="],
+
+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
+
+ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
+
+ "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
+
+ "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
+
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
+
+ "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+
+ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
+
+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
+
+ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
+
+ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
+
+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
+
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+ "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
+
+ "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
+
+ "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="],
+
+ "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="],
+
+ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
+ "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
+
+ "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
+
+ "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
+
+ "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
+
+ "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
+
+ "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
+
+ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
+
+ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
+
+ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
+
+ "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
+
+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+
+ "magicast": ["magicast@0.5.1", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "source-map-js": "^1.2.1" } }, "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw=="],
+
+ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
+
+ "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
+
+ "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
+
+ "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
+
+ "mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="],
+
+ "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
+
+ "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
+
+ "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
+
+ "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
+
+ "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
+
+ "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
+
+ "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
+
+ "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
+
+ "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
+
+ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
+
+ "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
+
+ "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
+
+ "micromark-extension-frontmatter": ["micromark-extension-frontmatter@2.0.0", "", { "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg=="],
+
+ "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
+
+ "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
+
+ "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
+
+ "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
+
+ "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
+
+ "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
+
+ "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
+
+ "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
+
+ "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
+
+ "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
+
+ "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
+
+ "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
+
+ "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
+
+ "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
+
+ "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
+
+ "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
+
+ "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
+
+ "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
+
+ "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
+
+ "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
+
+ "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
+
+ "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
+
+ "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
+
+ "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
+
+ "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
+
+ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
+
+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
+
+ "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
+
+ "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
+
+ "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
+
+ "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
+
+ "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
+
+ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
+
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+
+ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
+
+ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
+
+ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
+
+ "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
+
+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+
+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
+
+ "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
+
+ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
+
+ "rollup": ["rollup@4.53.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.2", "@rollup/rollup-android-arm64": "4.53.2", "@rollup/rollup-darwin-arm64": "4.53.2", "@rollup/rollup-darwin-x64": "4.53.2", "@rollup/rollup-freebsd-arm64": "4.53.2", "@rollup/rollup-freebsd-x64": "4.53.2", "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", "@rollup/rollup-linux-arm-musleabihf": "4.53.2", "@rollup/rollup-linux-arm64-gnu": "4.53.2", "@rollup/rollup-linux-arm64-musl": "4.53.2", "@rollup/rollup-linux-loong64-gnu": "4.53.2", "@rollup/rollup-linux-ppc64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-musl": "4.53.2", "@rollup/rollup-linux-s390x-gnu": "4.53.2", "@rollup/rollup-linux-x64-gnu": "4.53.2", "@rollup/rollup-linux-x64-musl": "4.53.2", "@rollup/rollup-openharmony-arm64": "4.53.2", "@rollup/rollup-win32-arm64-msvc": "4.53.2", "@rollup/rollup-win32-ia32-msvc": "4.53.2", "@rollup/rollup-win32-x64-gnu": "4.53.2", "@rollup/rollup-win32-x64-msvc": "4.53.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g=="],
+
+ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
+
+ "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
+
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
+
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+
+ "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
+
+ "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
+
+ "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
+
+ "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
+
+ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
+
+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "terser": ["terser@3.17.0", "", { "dependencies": { "commander": "^2.19.0", "source-map": "~0.6.1", "source-map-support": "~0.5.10" }, "bin": { "terser": "bin/uglifyjs" } }, "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ=="],
+
+ "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
+
+ "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
+
+ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
+
+ "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
+
+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
+
+ "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
+
+ "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
+
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+
+ "typescript-eslint": ["typescript-eslint@8.46.4", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.46.4", "@typescript-eslint/parser": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4", "@typescript-eslint/utils": "8.46.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg=="],
+
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
+
+ "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
+
+ "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
+
+ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
+
+ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+
+ "vite": ["vite@7.2.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "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" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ=="],
+
+ "vitest": ["vitest@4.0.9", "", { "dependencies": { "@vitest/expect": "4.0.9", "@vitest/mocker": "4.0.9", "@vitest/pretty-format": "4.0.9", "@vitest/runner": "4.0.9", "@vitest/snapshot": "4.0.9", "@vitest/spy": "4.0.9", "@vitest/utils": "4.0.9", "debug": "^4.4.3", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.9", "@vitest/browser-preview": "4.0.9", "@vitest/browser-webdriverio": "4.0.9", "@vitest/ui": "4.0.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-E0Ja2AX4th+CG33yAFRC+d1wFx2pzU5r6HtG6LiPSE04flaE0qB6YyjSw9ZcpJAtVPfsvZGtJlKWZpuW7EHRxg=="],
+
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+ "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
+
+ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
+
+ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
+
+ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
+
+ "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
+
+ "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
+
+ "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
+
+ "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+ "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
+
+ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+
+ "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+
+ "mdast-util-frontmatter/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+
+ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+ "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+ }
+}
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..ce75430
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,21 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import tseslint from 'typescript-eslint'
+import markdown from '@eslint/markdown'
+import { defineConfig } from 'eslint/config'
+
+export default defineConfig([
+ {
+ files: ['**/*.{js,mjs,cjs,ts,mts,cts}'],
+ plugins: { js },
+ extends: ['js/recommended'],
+ languageOptions: { globals: { ...globals.browser, ...globals.node } },
+ },
+ tseslint.configs.recommended,
+ {
+ files: ['**/*.md'],
+ plugins: { markdown },
+ language: 'markdown/gfm',
+ extends: ['markdown/recommended'],
+ },
+])
diff --git a/package.json b/package.json
index cd44c58..aa4594b 100644
--- a/package.json
+++ b/package.json
@@ -2,70 +2,37 @@
"name": "maizal",
"version": "0.4.0",
"description": "Search algorithm framework to find and expand trees.",
- "main": "lib/maizal.js",
"scripts": {
- "build": "npm run lint && npm run transpile && npm run bundle && size-limit",
- "transpile": "babel --delete-dir-on-start -d lib/ src/ ",
- "bundle": "rollup -c",
- "preversion": "npm test",
- "postversion": "git push && git push --tags",
- "prepublishOnly": "npm run build",
- "lint": "eslint ./src",
- "test": "nyc mocha test/unit/*/*.js --reporter spec",
- "clean": "rm -r ./lib && rm -r ./dist"
+ "lint": "eslint ./src ./tests",
+ "lint:fix": "eslint ./src ./tests --fix",
+ "format": "prettier --check .",
+ "format:fix": "prettier --write .",
+ "test": "vitest --run",
+ "test:watch": "vitest --watch"
},
- "files": [
- "src",
- "lib",
- "dist/*.js"
- ],
"repository": {
"type": "git",
"url": "https://github.com/benjides/Maizal"
},
"keywords": [
- "js"
+ "js",
+ "ts"
],
"author": "Benjamin Hernandez",
"license": "MIT",
- "dependencies": {},
"devDependencies": {
- "await-to-js": "^2.1.1",
- "babel-cli": "^6.26.0",
- "babel-preset-env": "^1.7.0",
- "babel-preset-minify": "^0.5.0",
- "chai": "^4.2.0",
- "eslint": "^5.8.0",
- "eslint-config-airbnb-base": "^13.1.0",
- "eslint-plugin-import": "^2.14.0",
- "husky": "^1.3.1",
- "mocha": "^5.2.0",
- "nyc": "^13.1.0",
- "rollup": "^0.67.3",
- "rollup-plugin-commonjs": "^9.2.0",
- "rollup-plugin-terser": "^3.0.0",
- "sinon": "^7.1.1",
- "size-limit": "^0.21.0"
- },
- "nyc": {
- "reporter": [
- "lcov",
- "text"
- ]
+ "@eslint/js": "^9.39.1",
+ "@eslint/markdown": "^7.5.1",
+ "@tsconfig/strictest": "^2.0.8",
+ "@typescript-eslint/eslint-plugin": "^8.46.4",
+ "@typescript-eslint/parser": "^8.46.4",
+ "@vitest/coverage-v8": "^4.0.9",
+ "eslint": "^9.39.1",
+ "globals": "^16.5.0",
+ "prettier": "^3.6.2",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.46.4",
+ "vitest": "^4.0.9"
},
- "size-limit": [
- {
- "path": "lib/maizal.js",
- "limit": "2 KB"
- },
- {
- "path": "dist/maizal.min.js",
- "limit": "2 KB"
- }
- ],
- "husky": {
- "hooks": {
- "pre-commit": "npm test"
- }
- }
+ "type": "module"
}
diff --git a/rollup.config.js b/rollup.config.js
deleted file mode 100644
index a52d164..0000000
--- a/rollup.config.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const commonjs = require('rollup-plugin-commonjs');
-const { terser } = require('rollup-plugin-terser');
-
-module.exports = {
- input: 'lib/maizal.js',
- output: {
- file: './dist/maizal.min.js',
- name: 'maizal',
- format: 'umd',
- },
- plugins: [commonjs(), terser()],
-};
diff --git a/src/AStar.ts b/src/AStar.ts
new file mode 100644
index 0000000..9a2ecbb
--- /dev/null
+++ b/src/AStar.ts
@@ -0,0 +1,14 @@
+import { Node } from './Node'
+import { Evaluate, Search, solver } from './Search'
+
+const aStarEvaluate: (heuristics: Heuristics) => Evaluate =
+ (heuristics: Heuristics) =>
+ (node: Node) =>
+ node.depth + heuristics(node.state)
+
+export type Heuristics = (currentState: S) => number
+
+export const aStar: (
+ heuristics: Heuristics,
+) => (search: Search) => Promise = (heuristics: Heuristics) =>
+ solver(aStarEvaluate(heuristics))
diff --git a/src/BestFirstSearch.ts b/src/BestFirstSearch.ts
new file mode 100644
index 0000000..0407a81
--- /dev/null
+++ b/src/BestFirstSearch.ts
@@ -0,0 +1,11 @@
+import { Node } from './Node'
+import { Evaluate, Search, solver } from './Search'
+
+const bestFirstSearchEvaluate: () => Evaluate =
+ () =>
+ (node: Node) =>
+ node.depth
+
+export const bestFirstSearch: (search: Search) => Promise = solver(
+ bestFirstSearchEvaluate(),
+)
diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts
new file mode 100644
index 0000000..48ffbbc
--- /dev/null
+++ b/src/DepthFirstSearch.ts
@@ -0,0 +1,11 @@
+import { Node } from './Node'
+import { Evaluate, Search, solver } from './Search'
+
+const depthFirstSearchEvaluate: () => Evaluate =
+ () =>
+ (node: Node) =>
+ -node.depth
+
+export const depthFirstSearch: (search: Search) => Promise = solver(
+ depthFirstSearchEvaluate(),
+)
diff --git a/src/Graph.ts b/src/Graph.ts
new file mode 100644
index 0000000..8438ad2
--- /dev/null
+++ b/src/Graph.ts
@@ -0,0 +1,83 @@
+export type Graph = Vertex[]
+
+export type Vertex = {
+ data: T
+ adjacentVertexes: number[]
+}
+export type Eq = (x: T, y: T) => boolean
+
+export const graph: () => Graph = (): Graph => []
+
+export const vertex: (vertex: S) => (graph: Graph) => Graph =
+ (vertex: S) =>
+ (graph: Graph) => [
+ ...graph,
+ {
+ data: vertex,
+ adjacentVertexes: [],
+ },
+ ]
+
+export const hasVertex: (
+ eq: Eq,
+ vertex: S,
+) => (graph: Graph) => boolean =
+ (eq: Eq, vertex: S) =>
+ (graph: Graph) =>
+ getVertex(eq, vertex)(graph) !== null
+
+const getVertex: (
+ eq: Eq,
+ x: S,
+) => (graph: Graph) => Vertex | null =
+ (eq: Eq, x: S) =>
+ (graph: Graph) =>
+ graph.find((vertex: Vertex) => eq(vertex.data, x)) ?? null
+
+const vertexIndex: (eq: Eq, x: S) => (graph: Graph) => number =
+ (eq: Eq, x: S) =>
+ (graph: Graph) =>
+ graph.findIndex((vertex: Vertex) => eq(vertex.data, x))
+
+export const edge =
+ (eq: Eq, x: S, y: S) =>
+ (graph: Graph): Graph =>
+ graph.map((vertex: Vertex) => {
+ if (eq(vertex.data, x)) {
+ return {
+ data: vertex.data,
+ adjacentVertexes: [
+ ...vertex.adjacentVertexes,
+ vertexIndex(eq, y)(graph),
+ ],
+ }
+ }
+
+ if (eq(vertex.data, y)) {
+ return {
+ data: vertex.data,
+ adjacentVertexes: [
+ ...vertex.adjacentVertexes,
+ vertexIndex(eq, x)(graph),
+ ],
+ }
+ }
+ return vertex
+ })
+
+export const hasEdge =
+ (eq: Eq, x: S, y: S) =>
+ (graph: Graph) =>
+ getVertex(
+ eq,
+ x,
+ )(graph)?.adjacentVertexes.includes(vertexIndex(eq, y)(graph))
+
+export const adjacent =
+ (eq: Eq, x: S, adjacent: S[]) =>
+ (graph: Graph): Graph =>
+ adjacent.reduce(
+ (accumulatedGraph: Graph, v: S) =>
+ edge(eq, x, v)(vertex(v)(accumulatedGraph)),
+ graph,
+ )
diff --git a/src/HashSet.ts b/src/HashSet.ts
new file mode 100644
index 0000000..35ae6e0
--- /dev/null
+++ b/src/HashSet.ts
@@ -0,0 +1,65 @@
+/**
+ * HashSet containing arbitrary data
+ *
+ * @category model
+ */
+export type HashSet = T[]
+
+/**
+ * Given two elements of the same type, checks if they are essentially the same
+ *
+ * @category model
+ */
+export type Eq = (x: T, y: T) => boolean
+
+/**
+ * Creates an empty HashSet
+ *
+ * @category constructor
+ *
+ * @example
+ * ```ts
+ * assert.deepStrictEqual(hashSet(), [2])
+ * ```
+ */
+export const hashSet: () => HashSet = () => []
+
+/**
+ * Inserts an element for a given HashSet. Does not check if the element is already present
+ *
+ * @example
+ * ```ts
+ * assert.deepStrictEqual(insert(2)(hashSet()), [2])
+ * ```
+ */
+export const insert: (element: T) => (hashSet: HashSet) => HashSet =
+ (element: T) =>
+ (hashSet: HashSet) => [...hashSet, element]
+
+/**
+ * Checks if an element is present for a given Eq and a given HashSet
+ *
+ * @example
+ * ```ts
+ * const hashSet : hashSet = insert(2)(hashSet())
+ * const eq : Eq = (x: number, y: number) => x === y
+ *
+ * assert.isTrue(has(eq)(2)(hashSet))
+ * ```
+ * @example
+ * ```ts
+ * const hashSet : hashSet = insert(2)(hashSet())
+ * const eq : Eq = (x: number, y: number) => x === y
+ *
+ * assert.isFalse(has(eq)(87557)(hashSet))
+ * ```
+ *
+ * @see Eq
+ */
+export const has: (
+ eq: Eq,
+) => (element: T) => (hashSet: HashSet) => boolean =
+ (eq: Eq) =>
+ (element: T) =>
+ (hashSet: HashSet): boolean =>
+ hashSet.findIndex((a: T) => eq(a, element)) !== -1
diff --git a/src/Node.ts b/src/Node.ts
new file mode 100644
index 0000000..9586c67
--- /dev/null
+++ b/src/Node.ts
@@ -0,0 +1,31 @@
+import { Ord } from './PriorityQueue'
+
+import { Evaluate } from './Search'
+
+export type Node = {
+ state: S
+ parent: Node | null
+ depth: number
+}
+
+export const node =
+ (node: Node) =>
+ (state: S): Node => ({
+ state: state,
+ parent: node,
+ depth: node.depth + 1,
+ })
+
+export const initial: (state: S) => Node = (state: S): Node => ({
+ state: state,
+ parent: null,
+ depth: 0,
+})
+
+export const toArray: (node: Node) => S[] = (node: Node) =>
+ node.parent === null ? [node.state] : [...toArray(node.parent), node.state]
+
+export const nodeOrd: (evaluate: Evaluate) => Ord> =
+ (evaluate: Evaluate) =>
+ (a: Node, b: Node) =>
+ evaluate(b) - evaluate(a)
diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts
new file mode 100644
index 0000000..858947c
--- /dev/null
+++ b/src/PriorityQueue.ts
@@ -0,0 +1,117 @@
+/**
+ * Naive implementation of a PriorityQueue using sorted array as internal data structure
+ */
+
+/**
+ * Queue containing arbitrary data sorted by ascending priority
+ *
+ * @category model
+ */
+export type PriorityQueue = T[]
+
+/**
+ * Defines a total ordering over values of the same type
+ * If `a` precedes `b` -1 shall be returned
+ * If `b` precedes `c` 1 shall be returned
+ * If `a` and `b` are in same position 0 shall be returned
+ *
+ * @category model
+ */
+export type Ord = (a: T, b: T) => number
+
+/**
+ * Creates an empty PriorityQueue
+ *
+ * @category constructors
+ *
+ * @example
+ * ```ts
+ * assert.deepStrictEqual(values(empty()), [])
+ * ```
+ */
+export const empty: () => PriorityQueue = () => []
+
+/**
+ * Creates a PriorityQueue for a given element
+ *
+ * @category constructors
+ *
+ * @example
+ * ```ts
+ * assert.deepStrictEqual(values(priorityQueue('Hello World!')), ['Hello World!'])
+ * ```
+ */
+export const priorityQueue: (data: T) => PriorityQueue = (data: T) => [
+ data,
+]
+
+/**
+ * Converts a given PriorityQueue into an array containing current values maintaining priority order
+ *
+ * @category conversions
+ *
+ * @example
+ * ```ts
+ * assert.deepStrictEqual(values(priorityQueue('Hello World!')), ['Hello World!'])
+ * ```
+ */
+export const values = (priorityQueue: PriorityQueue) => priorityQueue
+
+/**
+ * Inserts an element for a given PriorityQueue
+ *
+ * @example
+ * ```ts
+ * const ord : Ord = (a: string, b: string) => b.length - a .length
+ *
+ * const priorityQueue : PriorityQueue = insert(ord)('Hello World!')(empty())
+ *
+ * assert.deepStrictEqual(values(priorityQueue), ['Hello World!'])
+ * ```
+ */
+export const insert: (
+ ord: Ord,
+) => (data: T) => (priorityQueue: PriorityQueue) => PriorityQueue =
+ (ord: Ord) =>
+ (data: T) =>
+ (priorityQueue: PriorityQueue) => {
+ const index: number = priorityQueue.findIndex(
+ (node: T) => ord(data, node) > 0,
+ )
+
+ if (index === -1) {
+ return [...priorityQueue, data]
+ }
+
+ return [
+ ...priorityQueue.slice(0, index),
+ data,
+ ...priorityQueue.slice(index),
+ ]
+ }
+
+/**
+ * Returns the next element in the queue and the resulting PriorityQueue if possible
+ *
+ * @example
+ * ```ts
+ * const [element, queue] = poll(priorityQueue('Hello World!'))
+ *
+ * assert.deepStrictEqual(element, 'Hello World!')
+ * assert.deepStrictEqual(values(queue), [])
+ * ```
+ *
+ * @example
+ * ```ts
+ * const [element, priorityQueue] = poll(empty())
+ *
+ * assert.isNull(element)
+ * assert.deepStrictEqual(values(priorityQueue), [])
+ * ```
+ */
+export const poll: (
+ priorityQueue: PriorityQueue,
+) => [T | null, PriorityQueue] = (priorityQueue: PriorityQueue) => [
+ priorityQueue[0] ?? null,
+ priorityQueue.slice(1),
+]
diff --git a/src/Search.ts b/src/Search.ts
new file mode 100644
index 0000000..f443151
--- /dev/null
+++ b/src/Search.ts
@@ -0,0 +1,108 @@
+import { initial, node, Node, nodeOrd, toArray } from './Node'
+import {
+ insert as priorityQueueInsert,
+ poll,
+ priorityQueue,
+ PriorityQueue,
+} from './PriorityQueue'
+import { has, hashSet, HashSet, insert as hashSetInsert } from './HashSet'
+
+export type Eq = (a: S, b: S) => boolean
+
+export type Expand = (s: S) => Promise[]
+
+export type Search = {
+ initial: S
+ goal: S
+ eq: Eq
+ expand: Expand
+}
+
+export const search: (
+ initial: S,
+ goal: S,
+ eq: Eq,
+ expand: Expand,
+) => Search = (initial: S, goal: S, eq: Eq, expand: Expand) => ({
+ initial: initial,
+ goal: goal,
+ eq: eq,
+ expand: expand,
+})
+
+type OpenSet = PriorityQueue>
+
+const openSet: (state: S) => OpenSet = (state: S) =>
+ priorityQueue(initial(state))
+
+type ClosedSet = HashSet
+
+const closedSet: () => ClosedSet = () => hashSet()
+
+export type Evaluate = (node: Node) => number
+
+export const solver: (
+ evaluate: Evaluate,
+) => (search: Search) => Promise =
+ (evaluate: Evaluate) =>
+ (search: Search) =>
+ expandRecursively(
+ openSet(search.initial),
+ closedSet(),
+ nodeInsert(evaluate),
+ isDone(search.eq, search.goal),
+ search.eq,
+ search.expand,
+ )
+
+const isDone: (eq: Eq, node: S) => (state: S) => boolean =
+ (eq: Eq, node: T) =>
+ (state: T) =>
+ eq(node, state)
+
+const nodeInsert: (
+ evaluate: Evaluate,
+) => (node: Node) => (openSet: OpenSet) => OpenSet =
+ (evaluate: Evaluate) =>
+ (node: Node) =>
+ (openSet: OpenSet) =>
+ priorityQueueInsert(nodeOrd(evaluate))(node)(openSet)
+
+const hasBeenVisited: (
+ eq: Eq,
+ closedSet: ClosedSet,
+) => (state: S) => boolean =
+ (eq: Eq, closedSet: ClosedSet) =>
+ (state: S) =>
+ has(eq)(state)(closedSet)
+
+const expandRecursively = async (
+ openSet: OpenSet,
+ closedSet: ClosedSet,
+ openSetInsert: (node: Node) => (openSet: OpenSet) => OpenSet,
+ isGoal: (state: S) => boolean,
+ eq: Eq,
+ expand: Expand,
+): Promise => {
+ const [n, priorityQueue] = poll(openSet)
+
+ if (n === null) {
+ return []
+ }
+
+ if (isGoal(n.state)) {
+ return toArray(n)
+ }
+
+ const closed: ClosedSet = hashSetInsert(n.state)(closedSet)
+
+ const newStates: OpenSet = (await Promise.all(expand(n.state)))
+ .filter((state: S) => !hasBeenVisited(eq, closed)(state))
+ .map(node(n))
+ .reduce(
+ (openSet: OpenSet, node: Node) => openSetInsert(node)(openSet),
+ priorityQueue,
+ )
+
+ return expandRecursively(newStates, closed, openSetInsert, isGoal, eq, expand)
+}
diff --git a/src/Tree.ts b/src/Tree.ts
new file mode 100644
index 0000000..4bf655d
--- /dev/null
+++ b/src/Tree.ts
@@ -0,0 +1,66 @@
+/**
+ * Tree main Node data containing arbitrary key and value and optionally a parent Node
+ *
+ * @see Node
+ */
+export type Node = {
+ key: K
+ value: V
+ parent: Node | null
+}
+
+/**
+ * Tree containing arbitrary key and values for each Node
+ *
+ * @see Node
+ */
+export type Tree = Node
+
+/**
+ * Creates a Tree for a given Root
+ *
+ * @example
+ * ```ts
+ * assert.deepStrictEqual(toArray(fromRoot(1, 1)), [1])
+ * ```
+ */
+export const fromRoot: (key: K, value: V) => Tree = (
+ key: K,
+ value: V,
+): Tree => ({
+ key: key,
+ value: value,
+ parent: null,
+})
+
+/**
+ * Inserts a child for a given Tree and returns a Tree pointing to the inserted child
+ *
+ * @example
+ * ```ts
+ * assert.deepStrictEqual(toArray(insert(2, 2)(fromRoot(1, 1))), [1, 2])
+ * ```
+ */
+export const insert: (
+ key: K,
+ value: V,
+) => (tree: Tree) => Tree =
+ (key: K, value: V) =>
+ (tree: Tree) => ({
+ key: key,
+ value: value,
+ parent: tree,
+ })
+
+/**
+ * Returns an ordered list of values from Root for a given Tree iterating over its parents recursively
+ *
+ * @example
+ * ```ts
+ * assert.deepStrictEqual(toArray(fromRoot(1, 1)), [1])
+ * ```
+ */
+export const toArray: (tree: Tree) => V[] = (
+ tree: Tree,
+) =>
+ tree.parent === null ? [tree.value] : [...toArray(tree.parent), tree.value]
diff --git a/src/core/Maizal.js b/src/core/Maizal.js
deleted file mode 100644
index f3238ee..0000000
--- a/src/core/Maizal.js
+++ /dev/null
@@ -1,23 +0,0 @@
-const init = require('./init/init');
-const solve = require('./solver/solver');
-
-/**
- * Creates a Maizal instance
- *
- * @constructor
- * @param {Object} config object containing the necesarry configuation to start a search
- */
-function Maizal(config) {
- init(this, config);
-}
-
-/**
- * Solves a search problem
- *
- * @returns {Promise} Promise including the search results
- */
-Maizal.prototype.solve = function () {
- return solve(this);
-};
-
-module.exports = Maizal;
diff --git a/src/core/init/init.js b/src/core/init/init.js
deleted file mode 100644
index 2690596..0000000
--- a/src/core/init/init.js
+++ /dev/null
@@ -1,99 +0,0 @@
-const PriorityQueue = require('../../util/PriorityQueue');
-const HashSet = require('../../util/HashSet');
-
-/**
- * Defines the poll of yet unexplored states
- *
- * @param {HashSet} goals HashSet containing the user defined goals
- * @param {Object} config User defined search configuration
- * @returns {PriorityQueue}
- */
-function initPoll(goals, config) {
- if (!config.initial) {
- throw new Error('Missing initial state');
- }
-
- const evaluationFn = state => config.engine.evaluationFn(state, config);
-
- const poll = new PriorityQueue(evaluationFn);
- poll.enqueue({
- final: goals.has(config.initial),
- data: config.initial,
- depth: 0,
- cost: 0,
- });
- return poll;
-}
-
-/**
- * Defines a closed set to introduce already visited states
- *
- * @param {String|Int|Function} hash HashFn to determine whether two states are essentially the same
- * @returns {HashSet}
- */
-function initClosed({ hash }) {
- return new HashSet(hash);
-}
-
-/**
- * Defines the user created actions
- *
- * @param {Array|Object} actions Array or Object containing the user defined actions
- * @returns {Array} Actions array
- */
-function initActions({ actions }) {
- return (Array.isArray(actions) ? actions : [actions]).map((action) => {
- if (!action.expand) throw new Error(`Every action needs to have its expand functionno on action ${JSON.stringify(action)}`);
- return {
- name: action.name || 'expand',
- cost: action.cost || 1,
- expand: action.expand,
- };
- });
-}
-
-/**
- * Defines the user created goal states
- *
- * @param {Array|Object} goals Array containing the goal states
- * @param {String|Int|Function} hash HashFn to quickly recognize a goal state when is created
- * @returns {HashSet}
- */
-function initGoals({ goals, hash }) {
- if (!hash) {
- throw new Error('Missing Hash function');
- }
- if (!goals) {
- throw new Error('Missing Goals states');
- }
- const g = new HashSet(hash);
- g.add(goals);
- return g;
-}
-
-function setup(config) {
- if (!config.engine.requires) {
- return;
- }
- Object.keys(config.engine.requires).forEach((requirement) => {
- if (typeof config[requirement] !== config.engine.requires[requirement]) {
- throw new Error(`The search requires the field ${requirement} to be a ${config.engine.requires[requirement]}`);
- }
- });
-}
-
-/**
- * Sets the configuartion for the Maizal search
- *
- * @param {Maizal} maizal Maizal instance
- * @param {Object} config
- */
-function init(maizal, config) {
- setup(config);
- maizal.goals = initGoals(config);
- maizal.poll = initPoll(maizal.goals, config);
- maizal.closed = initClosed(config);
- maizal.actions = initActions(config);
-}
-
-module.exports = init;
diff --git a/src/core/node.js b/src/core/node.js
deleted file mode 100644
index 98f0317..0000000
--- a/src/core/node.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Creates a new node
- *
- * @param {Object} parent state
- * @param {Object} data information about the state
- * @param {boolean} final
- * @param {Object} action taken action
- */
-const node = ({
- parent, data, final, action,
-}) => ({
- parent,
- data,
- final,
- action: action.name,
- cost: parent.cost + action.cost,
- depth: parent.depth + 1,
-});
-
-module.exports = node;
diff --git a/src/core/solver/rebuild.js b/src/core/solver/rebuild.js
deleted file mode 100644
index 217f453..0000000
--- a/src/core/solver/rebuild.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Rebuild the tree once the solution is found
- *
- * @param {Object} goal state of the search
- * @returns {Array} ordered array containing the path from the initial state to the goal one.
- */
-function rebuild(goal) {
- const tree = [];
- let state = goal;
- while (state) {
- const {
- parent, data, action, cost, depth,
- } = state;
- const s = {
- data, cost, depth,
- };
- if (action) {
- s.action = action;
- }
- tree.unshift(s);
- state = parent;
- }
- return tree;
-}
-
-module.exports = rebuild;
diff --git a/src/core/solver/solver.js b/src/core/solver/solver.js
deleted file mode 100644
index d8b0da7..0000000
--- a/src/core/solver/solver.js
+++ /dev/null
@@ -1,29 +0,0 @@
-const node = require('../node');
-const rebuild = require('./rebuild');
-const stats = require('./stats');
-
-/**
- * Expands recursively the most promising node acording to the priorityQueue
- *
- * @param {Maizal} maizal instance
- * @returns {Promise} the results of the search
- */
-function expand(maizal) {
- const state = maizal.poll.dequeue();
- if (!state) {
- return Promise.reject(new Error('The goal states could not be reached. Are you sure they are accesible?'));
- }
- if (state.final) {
- return Promise.resolve().then(() => ({ stats: stats(maizal, state), solution: rebuild(state) }));
- }
- const promises = maizal.actions.map(action => new Promise(resolve => resolve(action.expand(state.data)))
- .then(states => (Array.isArray(states) ? states : [states]))
- .then(states => states.filter(data => (data && !maizal.closed.has(data))))
- .then(states => states.map(data => node({
- parent: state, data, final: maizal.goals.has(data), action,
- })))
- .then(states => maizal.poll.enqueue(states)));
- return Promise.all(promises).then(() => maizal.closed.add(state.data)).then(() => expand(maizal));
-}
-
-module.exports = maizal => expand(maizal);
diff --git a/src/core/solver/stats.js b/src/core/solver/stats.js
deleted file mode 100644
index be82365..0000000
--- a/src/core/solver/stats.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Get statistics from the search
- *
- * @param {Maizal} maizal solved instance
- * @param {State} state reached goal state
- * @returns {Object}
- */
-function stats(maizal, state) {
- return {
- cost: state.cost,
- depth: state.depth,
- nodes: maizal.closed.size(),
- };
-}
-
-module.exports = stats;
diff --git a/src/engine/engines.js b/src/engine/engines.js
deleted file mode 100644
index cbfdb86..0000000
--- a/src/engine/engines.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// Uninformed
-const bfs = require('./uninformed/bfs');
-const dijkstra = require('./uninformed/dijkstra');
-const random = require('./uninformed/random');
-const dfs = require('./uninformed/dfs');
-
-// Informed
-const bestfs = require('./informed/bestfs');
-const astar = require('./informed/astar');
-const weightedastar = require('./informed/weightedastar');
-
-module.exports = {
- bfs, dijkstra, random, dfs, bestfs, astar, weightedastar,
-};
diff --git a/src/engine/informed/astar.js b/src/engine/informed/astar.js
deleted file mode 100644
index b9285e3..0000000
--- a/src/engine/informed/astar.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * A* search
- *
- * Expands based on cost and heuristics
- *
- * completness: (depending on the heuristics)
- * admissability: (depending on the heuristics)
- */
-module.exports = {
- requires: {
- heuristics: 'function',
- },
- evaluationFn: ({ cost, data }, { heuristics }) => cost + heuristics(data),
-};
diff --git a/src/engine/informed/bestfs.js b/src/engine/informed/bestfs.js
deleted file mode 100644
index d23db8d..0000000
--- a/src/engine/informed/bestfs.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Best-first search
- *
- * Expands based purely on heuristics
- * note that the 1 is added to ensure we arte not getting 0 values on reaching goal states.
- *
- * completness: (depending on the heuristics)
- * admissability: (depending on the heuristics)
- */
-module.exports = {
- requires: {
- heuristics: 'function',
- },
- evaluationFn: ({ data }, { heuristics }) => 1 + heuristics(data),
-};
diff --git a/src/engine/informed/weightedastar.js b/src/engine/informed/weightedastar.js
deleted file mode 100644
index e6bdaf4..0000000
--- a/src/engine/informed/weightedastar.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Weighted A* search
- *
- * Expands based on cost and heuristics giving a weight value to the heuristics
- *
- * completness: (depending on the heuristics)
- * admissability: (depending on the heuristics)
- */
-module.exports = {
- requires: {
- heuristics: 'function',
- epsilon: 'number',
- },
- evaluationFn: ({ cost, data }, { heuristics, epsilon }) => cost + epsilon * heuristics(data),
-};
diff --git a/src/engine/uninformed/bfs.js b/src/engine/uninformed/bfs.js
deleted file mode 100644
index f65194c..0000000
--- a/src/engine/uninformed/bfs.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Breadth-first search
- *
- * Expands nodes at depth "d" before going to the next level
- *
- * completness: true
- * admissability: true
- */
-module.exports = {
- evaluationFn: ({ depth }) => depth,
-};
diff --git a/src/engine/uninformed/dfs.js b/src/engine/uninformed/dfs.js
deleted file mode 100644
index 82db494..0000000
--- a/src/engine/uninformed/dfs.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Depth-first Search
- *
- * Expands nodes on depth
- *
- * completness: true
- * admissability: false
- */
-module.exports = {
- evaluationFn: ({ depth }) => -depth,
-};
diff --git a/src/engine/uninformed/dijkstra.js b/src/engine/uninformed/dijkstra.js
deleted file mode 100644
index 5142f82..0000000
--- a/src/engine/uninformed/dijkstra.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Dijkstra search
- *
- * Espands nodes with least cost
- *
- * completness: true
- * admissability: false
- */
-module.exports = {
- evaluationFn: ({ cost }) => cost,
-};
diff --git a/src/engine/uninformed/random.js b/src/engine/uninformed/random.js
deleted file mode 100644
index c34d81e..0000000
--- a/src/engine/uninformed/random.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Random search
- *
- * Expands nodes randomly
- *
- * completness: true
- * admissability: false
- */
-module.exports = {
- evaluationFn: () => Math.round(Math.random()) + 1,
-};
diff --git a/src/maizal.js b/src/maizal.js
deleted file mode 100644
index 48cf074..0000000
--- a/src/maizal.js
+++ /dev/null
@@ -1,20 +0,0 @@
-const Maizal = require('./core/Maizal');
-const engines = require('./engine/engines');
-
-const maizal = {};
-
-/**
- * Expose maizal.engine
- */
-Object.keys(engines).forEach((engine) => {
- maizal[engine] = (config) => {
- config.engine = engines[engine];
- try {
- return new Maizal(config).solve();
- } catch (error) {
- return Promise.reject(error);
- }
- };
-});
-
-module.exports = maizal;
diff --git a/src/util/HashSet.js b/src/util/HashSet.js
deleted file mode 100644
index ced8ac5..0000000
--- a/src/util/HashSet.js
+++ /dev/null
@@ -1,133 +0,0 @@
-/**
- * Creates a new instance of a HashSet
- *
- * @param {Function|String} hashFunction to check whether two elements are essentially the same
- * @constructor
- */
-function HashSet(hashFunction) {
- if (hashFunction === undefined) {
- throw new TypeError('hashFunction is required argument');
- }
- if (typeof hashFunction === 'string') {
- this.hashFn = function (item) {
- if (item[hashFunction] !== undefined) {
- return item[hashFunction];
- }
- throw new Error(`Undefined was returned by hash function on object ${JSON.stringify(item)}`);
- };
- } else {
- this.hashFn = hashFunction;
- }
- this.hashObject = {};
- this.length = 0;
-}
-
-/**
- * Inserts the element into the set if it is not already present.
- *
- * @param {*} value Value to add
- * @returns {boolean} True if the item was added
- */
-HashSet.prototype.add = function (value) {
- if (Array.isArray(value)) {
- return this.addArray(value);
- }
- const r = !this.has(value);
- if (r) {
- this.hashObject[this.hashFn(value)] = value;
- this.length += 1;
- }
- return r;
-};
-
-/**
- * Adds an Array of elements
- *
- * @param {array} values Array of values to be added
- * @return {array} Boolean array specifying if the value was added
- */
-HashSet.prototype.addArray = function (values) {
- return values.map(value => this.add(value));
-};
-
-/**
- * Get the key/hashes of the set.
- *
- * @returns {Array} Array of keys
- */
-HashSet.prototype.keys = function () {
- return Object.keys(this.hashObject);
-};
-
-/**
- * Clear the set and keep the Hash Function
- */
-HashSet.prototype.clear = function () {
- this.hashObject = {};
- this.length = 0;
-};
-
-/**
- * Get the value given a hash
- *
- * @param {String} hash
- * @returns {*} stored value or undefined if the hash did not found anything
- */
-HashSet.prototype.getValue = function (hash) {
- return this.hashObject[hash];
-};
-
-/**
- * Get the hash given a value
- *
- * @param {*} value
- * @returns {string} stored hash or undefined if the hash did not found anything
- */
-HashSet.prototype.getHash = function (value) {
- return this.hashFn(value);
-};
-
-/**
- * Removes a value from the set given its hash or value
- *
- * @param {*} valueOrHash either hash or an object
- * @returns {boolean} whether it removed the item
- */
-HashSet.prototype.remove = function (valueOrHash) {
- if (this.has(valueOrHash)) {
- if (typeof valueOrHash === 'object') {
- delete this.hashObject[this.hashFn(valueOrHash)];
- } else {
- delete this.hashObject[valueOrHash];
- }
- this.length -= 1;
- return true;
- }
- return false;
-};
-
-/**
- * Check if the set contains the given value or hash
- *
- * @param {*} valueOrHash either hash or an object
- * @returns {boolean}
- */
-HashSet.prototype.has = function (valueOrHash) {
- if (typeof valueOrHash === 'object') {
- return this.hashObject[this.hashFn(valueOrHash)] !== undefined;
- }
- return this.hashObject[valueOrHash] !== undefined;
-};
-
-/**
- * Size of the Set
- *
- * @returns {number}
- */
-HashSet.prototype.size = function () {
- return this.length;
-};
-
-HashSet.prototype.hashes = HashSet.prototype.keys;
-
-module.exports = HashSet;
diff --git a/src/util/PriorityQueue.js b/src/util/PriorityQueue.js
deleted file mode 100644
index 38a680b..0000000
--- a/src/util/PriorityQueue.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * Creates a new PriorityQueue
- *
- * @param {Function} priorityFunction Function over a element to state its integer priority
- * @constructor
- */
-function PriorityQueue(priorityFunction) {
- if (!priorityFunction) {
- throw new Error('The priority function for an element must be provided');
- }
- this.priorityFn = (current, insert) => {
- if (!priorityFunction(insert)) {
- return undefined;
- }
- return priorityFunction(current) - priorityFunction(insert) > 0;
- };
- this.queue = [];
-}
-
-/**
- * Enqueues an element
- *
- * @param {*} element Enqueues an element in the proper position using the comparator function
- * @return {number} Position where the element was placed
- */
-PriorityQueue.prototype.enqueue = function (element) {
- if (Array.isArray(element)) {
- return this.enqueueArray(element);
- }
- for (let i = 0; i < this.queue.length; i += 1) {
- switch (this.priorityFn(this.queue[i], element)) {
- case true:
- this.queue.splice(i, 0, element);
- return i;
- case undefined:
- return undefined;
- // no default
- }
- }
- return this.queue.push(element) - 1;
-};
-
-/**
- * Enqueues an Array of elements
- *
- * @param {array} array Array of elements to be enqueued
- * @return {array} Position array where the elements were placed
- */
-PriorityQueue.prototype.enqueueArray = function (elements) {
- return elements.map(element => this.enqueue(element));
-};
-
-/**
- * Dequeues an element
- *
- * @return {*} Gets the first element of the queue and removes from the list
- */
-PriorityQueue.prototype.dequeue = function () {
- return this.queue.shift();
-};
-
-/**
- * Clears the queue
- */
-PriorityQueue.prototype.clear = function () {
- this.queue = [];
-};
-
-/**
- * Gets the queue size
- *
- * @returns {number}
- */
-PriorityQueue.prototype.size = function () {
- return this.queue.length;
-};
-
-/**
- * Checks if the queue is empty
- *
- * @returns {boolean}
- */
-PriorityQueue.prototype.empty = function () {
- return this.size() === 0;
-};
-
-module.exports = PriorityQueue;
diff --git a/test/corridor.js b/test/corridor.js
deleted file mode 100644
index db35e5c..0000000
--- a/test/corridor.js
+++ /dev/null
@@ -1,26 +0,0 @@
-module.exports = {
- initial: {
- position: 1,
- },
- goals: {
- position: 4,
- },
- actions: [
- {
- name: 'right',
- expand: (state) => {
- if (state.position + 1 > 4) return undefined;
- return { position: state.position + 1 };
- },
- },
- {
- name: 'left',
- expand: (state) => {
- if (state.position - 1 < 0) return undefined;
- return { position: state.position - 1 };
- },
- },
- ],
- hash: 'position',
- heuristics: state => 4 - state.position,
-};
diff --git a/test/months.js b/test/months.js
deleted file mode 100644
index 5db8c49..0000000
--- a/test/months.js
+++ /dev/null
@@ -1,50 +0,0 @@
-module.exports = [
- {
- index: 1,
- name: 'January',
- },
- {
- index: 2,
- name: 'February',
- },
- {
- index: 3,
- name: 'March',
- },
- {
- index: 4,
- name: 'April',
- },
- {
- index: 5,
- name: 'May',
- },
- {
- index: 6,
- name: 'June',
- },
- {
- index: 7,
- name: 'July',
- },
- {
- index: 8,
- name: 'August',
- },
- {
- index: 9,
- name: 'September',
- },
- {
- index: 10,
- name: 'October',
- },
- {
- index: 11,
- name: 'November',
- },
- {
- index: 12,
- name: 'December',
- },
-];
diff --git a/test/unit/core/core.js b/test/unit/core/core.js
deleted file mode 100644
index 183a7dd..0000000
--- a/test/unit/core/core.js
+++ /dev/null
@@ -1,284 +0,0 @@
-const { expect } = require('chai');
-const sinon = require('sinon');
-const { to } = require('await-to-js');
-const maizal = require('../../../src/maizal');
-const Maizal = require('../../../src/core/Maizal');
-const { bfs } = require('../../../src/engine/engines');
-const corridor = require('../../corridor');
-
-afterEach(() => {
- sinon.restore();
-});
-
-let c = {};
-
-// All the core functionalities of maizal goes here
-
-// For specific engine funcitonalty go to the dedicated test file
-
-describe('Simple core base corridor search testing', () => {
- it('A simple search should reach the last position in the corridor', async () => {
- const res = await maizal.bfs(corridor);
- it('The search should provide the solution found and the stats', () => {
- expect(res).to.have.all.keys('solution', 'stats');
- });
- const { solution, stats } = res;
- it('The solution must be and ordered array with the found nodes from initial to last', () => {
- expect(solution).to.be.an('array');
- });
- it('Checks of the initial state of the solution', () => {
- const initial = solution[0];
- expect(initial.action).to.be.an('undefined');
- expect(initial.depth).to.be(0);
- expect(initial.cost).to.be(0);
- });
- it('Checks of the goal state found', () => {
- const final = solution.pop();
- expect(final.position).to.eq(corridor.goals.position);
- expect(final.action).to.eq('right');
- expect(final.cost).to.eq(3);
- expect(final.depth).to.eq(3);
- });
- it('The statistics must be an object with search results info', () => {
- expect(stats).to.be.an('object');
- expect(stats.nodes).to.eq(4);
- expect(stats.depth).to.eq(3);
- expect(stats.nodes).to.eq(3);
- });
- });
- it('If the final states are not reachable the search should be rejected', async () => {
- c = Object.assign({}, corridor);
- c.actions = {
- expand: ({ position }) => Promise.resolve({ position }),
- };
- const [err, data] = await to(maizal.bfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- it('Chain two maizal searches', async () => {
- await maizal.bfs(corridor);
- c = Object.assign({}, corridor);
- c.goals = {
- position: 2,
- };
- const { solution, stats } = await maizal.bfs(c);
- expect(stats.nodes).to.eq(1);
- expect(solution.pop().data.position).to.eq(c.goals.position);
- });
-
- describe('Checking hashing function call assertions', () => {
- it('Checking called assertions on the closed set hash function', async () => {
- c = Object.assign({}, corridor);
- c.engine = bfs;
- // Remove limitations to better calculate function callings
- c.actions = [
- {
- name: 'right',
- expand: ({ position }) => ({ position: position + 1 }),
- },
- {
- name: 'left',
- expand: ({ position }) => ({ position: position - 1 }),
- },
- ];
- const m = new Maizal(c);
- const hash = sinon.spy(m.closed, 'hashFn');
- const { stats } = await m.solve();
-
- // The hash function is called twice per closed state (has, add)
- // The hash function is called once per iteration (state closed) per action (each action generates a new state)
- sinon.assert.callCount(hash, stats.nodes * 2 * c.actions.length);
- });
- it('The hash function is called for each new state to determine if it is a goal state including the initial', async () => {
- c = Object.assign({}, corridor);
- c.engine = bfs;
- const m = new Maizal(c);
- const hash = sinon.spy(m.goals, 'hashFn');
- await m.solve();
- // Being BFS every single state must be checked and therefore called
- sinon.assert.callCount(hash, 4);
- });
- });
-
- describe('Checking priority function call assertions', () => {
- it('The evaluation function is called only once to reach the next cell', async () => {
- c = Object.assign({}, corridor);
- c.engine = bfs;
- c.goals = {
- position: 2,
- };
- const m = new Maizal(c);
- const priority = sinon.spy(m.poll, 'priorityFn');
- await m.solve();
- // This is called once, the first element is inserted at the beggining (no call) and the next is placed after (1 call)
- sinon.assert.calledOnce(priority);
- });
- it('The evaluation function is called twice to reach the end of the corridor', async () => {
- c = Object.assign({}, corridor);
- c.engine = bfs;
- const m = new Maizal(c);
- const priority = sinon.spy(m.poll, 'priorityFn');
- await m.solve();
- // This is tricky. As elements are removed there is no need to call the priority function for every new state
- sinon.assert.callCount(priority, 2);
- });
- });
-
- describe('Initial set testing', () => {
- it('If the initial is the goal should not expand any action', async () => {
- c = Object.assign({}, corridor);
- c.goals = { position: 1 };
- const { solution, stats } = await maizal.bfs(c);
- expect(solution).to.have.length(1);
- expect(solution[0].data.position).to.eq(c.goals.position);
- expect(stats.nodes).to.eq(0);
- expect(stats.depth).to.eq(0);
- expect(stats.cost).to.eq(0);
- });
- it('A search cannot start without an initial state', async () => {
- c = Object.assign({}, corridor);
- delete c.initial;
- const [err, data] = await to(maizal.bfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- it('A search cannot start with an empty initial state', async () => {
- c = Object.assign({}, corridor);
- c.initial = {};
- const [err, data] = await to(maizal.bfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- });
- describe('Action testing', () => {
- it('Ensure the actions expand functions are called', async () => {
- c = Object.assign({}, corridor);
- c.engine = bfs;
- const right = sinon.spy(c.actions[0], 'expand');
- const left = sinon.spy(c.actions[1], 'expand');
- const m = new Maizal(c);
- await m.solve();
- sinon.assert.called(right);
- sinon.assert.called(left);
- });
- it('The expand actions should be called in order', async () => {
- c = Object.assign({}, corridor);
- c.engine = bfs;
- const right = sinon.spy(c.actions[0], 'expand');
- const left = sinon.spy(c.actions[1], 'expand');
- const m = new Maizal(c);
- await m.solve();
- sinon.assert.callOrder(right, left);
- });
- it('Promise actions are supported and the robot shall reach the end of the corridor', async () => {
- c = Object.assign({}, corridor);
- c.actions = {
- expand: ({ position }) => Promise.resolve({ position: position + 1 }),
- };
- const { solution, stats } = await maizal.bfs(c);
- const final = solution.pop();
- expect(final.data.position).to.eq(c.goals.position);
- expect(final.action).to.eq('expand');
- expect(stats.nodes).to.eq(3);
- });
- it('A search with no actions should be rejected', async () => {
- c = Object.assign({}, corridor);
- c.actions = {};
- const [err, data] = await to(maizal.bfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- it('An empty expand function should be rejected', async () => {
- c = Object.assign({}, corridor);
- c.actions = {
- name: 'IdoNotHaveExpand',
- };
- const [err, data] = await to(maizal.bfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- it('Newly generated states should be able to be hashed', async () => {
- c = Object.assign({}, corridor);
- c.actions = {
- expand: () => ({ notPosition: 3 }),
- };
- const [err, data] = await to(maizal.bfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- it('Actions returning several states are allowed', async () => {
- c = Object.assign({}, corridor);
- c.actions = {
- expand: ({ position }) => {
- if (position > 5 || position < 0) {
- return undefined;
- }
- return [{ position: position + 1 }, { position: position - 1 }];
- },
- };
- const { solution, stats } = await maizal.bfs(c);
- expect(solution.pop().data.position).to.be.eq(c.goals.position);
- expect(solution.pop().action).to.be.eq('expand');
- expect(stats.nodes).to.be.eq(5);
- });
- });
- describe('Hash testing', () => {
- it('Function hashes are allowed', async () => {
- c = Object.assign({}, corridor);
- c.hash = ({ position }) => position + 15;
- const hash = sinon.spy(c, 'hash');
- const { solution } = await maizal.bfs(c);
- expect(solution).to.have.length(4);
- sinon.assert.called(hash);
- });
- it('A search with no hash should be rejected', async () => {
- c = Object.assign({}, corridor);
- delete c.hash;
- const [err, data] = await to(maizal.bfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- it('A non existing hash should be rejected', async () => {
- c = Object.assign({}, corridor);
- c.hash = 'IdoNotExist';
- const [err, data] = await to(maizal.bfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- });
- describe('Goals testing', () => {
- it('A search with no goals should be rejected', async () => {
- c = Object.assign({}, corridor);
- delete c.goals;
- const [err, data] = await to(maizal.bfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- });
-
- describe('Informed search testing', () => {
- it('Perform a simple Best-first search to reach the end of the corridor', async () => {
- c = Object.assign({}, corridor);
- const heuristics = sinon.spy(c, 'heuristics');
- const { solution } = await maizal.bestfs(c);
- expect(solution).to.have.length(4);
- sinon.assert.called(heuristics);
- });
-
- it('Ensure the requirements of the search are fullfilled', async () => {
- c = Object.assign({}, corridor);
- delete c.heuristics;
- const [err, data] = await to(maizal.bestfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
-
- it('Ensure the requirements match the expected datatype', async () => {
- c = Object.assign({}, corridor);
- c.heuristics = 'iAmNotAFunction';
- const [err, data] = await to(maizal.bestfs(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- });
-});
diff --git a/test/unit/informed/astar.js b/test/unit/informed/astar.js
deleted file mode 100644
index b55ce72..0000000
--- a/test/unit/informed/astar.js
+++ /dev/null
@@ -1,69 +0,0 @@
-const { expect } = require('chai');
-const maizal = require('../../../src/maizal');
-const corridor = require('../../corridor');
-
-let c = {};
-
-describe('Simple A* corridor search', () => {
- it('A simple search should reach the last position in the corridor', async () => {
- const { solution } = await maizal.astar(corridor);
- expect(solution.pop().data.position).to.eq(corridor.goals.position);
- });
- it('A better solution must prioritize over a worse one if the heuristics point to them', async () => {
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 2,
- }, {
- position: 4,
- }];
- const { solution } = await maizal.astar(c);
- expect(solution.pop().data.position).to.eq(c.goals[0].position);
- });
- it('The solution is found at the least cost even if the heuristics point otherwise (incresing cost weight)', async () => {
- c = Object.assign({}, corridor);
- c.actions = [
- {
- name: 'right',
- cost: 50,
- expand: (state) => {
- if (state.position + 1 > 4) return undefined;
- return { position: state.position + 1 };
- },
- },
- {
- name: 'left',
- cost: 50,
- expand: (state) => {
- if (state.position - 1 < 0) return undefined;
- return { position: state.position - 1 };
- },
- },
- ];
- c.goals = [{
- position: 4,
- }, {
- position: 0,
- }];
- const { solution } = await maizal.astar(c);
- expect(solution.pop().data.position).to.eq(c.goals[1].position);
- });
- it('Force the search to find the solution on the left', async () => {
- c = Object.assign({}, corridor);
- c.heuristics = ({ position }) => position;
- c.goals = {
- position: 0,
- };
- const { solution } = await maizal.astar(c);
- expect(solution.pop().data.position).to.eq(c.goals.position);
- });
- it('Forcing the heuristics wrong must provide a solution if any but more costly (computer wise)', async () => {
- c = Object.assign({}, corridor);
- c.goals = {
- position: 0,
- };
- const { stats, solution } = await maizal.astar(c);
- expect(solution.pop().data.position).to.eq(c.goals.position);
- expect(stats.nodes).to.be.eq(4);
- expect(stats.depth).to.be.eq(1);
- });
-});
diff --git a/test/unit/informed/bestfs.js b/test/unit/informed/bestfs.js
deleted file mode 100644
index 3cbfc29..0000000
--- a/test/unit/informed/bestfs.js
+++ /dev/null
@@ -1,50 +0,0 @@
-const { expect } = require('chai');
-const maizal = require('../../../src/maizal');
-const corridor = require('../../corridor');
-
-let c = {};
-
-describe('Simple Best-first corridor search', () => {
- it('A simple search should reach the last position in the corridor', async () => {
- const { solution } = await maizal.bestfs(corridor);
- expect(solution.pop().data.position).to.eq(corridor.goals.position);
- });
- it('A better solution must prioritize over a worse one if the heuristics point to them', async () => {
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 2,
- }, {
- position: 4,
- }];
- const { solution } = await maizal.bestfs(c);
- expect(solution.pop().data.position).to.eq(c.goals[0].position);
- });
- it('The solution is found at the heuristics even if there is a better solution', async () => {
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 4,
- }, {
- position: 0,
- }];
- const { solution } = await maizal.bestfs(c);
- expect(solution.pop().data.position).to.eq(c.goals[0].position);
- });
- it('Force the search to find the solution on the left', async () => {
- c = Object.assign({}, corridor);
- c.heuristics = ({ position }) => position;
- c.goals = {
- position: 0,
- };
- const { solution } = await maizal.bestfs(c);
- expect(solution.pop().data.position).to.eq(c.goals.position);
- });
- it('Forcing the heuristics wrong must provide a solution if any but more costly (computer wise)', async () => {
- c = Object.assign({}, corridor);
- c.goals = {
- position: 0,
- };
- const { stats, solution } = await maizal.bestfs(c);
- expect(solution.pop().data.position).to.eq(c.goals.position);
- expect(stats.nodes).to.be.eq(4);
- });
-});
diff --git a/test/unit/informed/weightedastar.js b/test/unit/informed/weightedastar.js
deleted file mode 100644
index ad59ac6..0000000
--- a/test/unit/informed/weightedastar.js
+++ /dev/null
@@ -1,65 +0,0 @@
-const { expect } = require('chai');
-const { to } = require('await-to-js');
-const maizal = require('../../../src/maizal');
-const corridor = require('../../corridor');
-
-let c = {};
-
-describe('Simple Weighted-A* corridor search', () => {
- it('A simple search should reach the last position in the corridor', async () => {
- c = Object.assign({}, corridor);
- c.epsilon = 1.2;
- const { solution } = await maizal.weightedastar(c);
- expect(solution.pop().data.position).to.eq(corridor.goals.position);
- });
- it('A zero epsilon should behave as a dijkstra search', async () => {
- c = Object.assign({}, corridor);
- c.epsilon = 0;
- c.initial = {
- position: 3,
- };
- c.actions = [{
- name: 'right',
- cost: 50,
- expand: (state) => {
- if (state.position + 1 > 4) return undefined;
- return { position: state.position + 1 };
- },
- },
- {
- name: 'left',
- expand: (state) => {
- if (state.position - 1 < 0) return undefined;
- return { position: state.position - 1 };
- },
- }];
- const { solution, stats } = await maizal.weightedastar(c);
- expect(solution.pop().data.position).to.eq(c.goals.position);
- expect(stats.nodes).to.eq(4);
- expect(stats.depth).to.eq(1);
- expect(stats.cost).to.eq(50);
- });
- it('A really high epsilon should behave as a best first search', async () => {
- c = Object.assign({}, corridor);
- c.epsilon = 999;
- c.goals = [{
- position: 4,
- }, {
- position: 0,
- }];
- const { solution } = await maizal.weightedastar(c);
- expect(solution.pop().data.position).to.eq(c.goals[0].position);
- });
- it('A Weighted-A* cannot start without an epsilon value', async () => {
- const [err, data] = await to(maizal.weightedastar(corridor));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
- it('The epsilon value must be a number', async () => {
- c = Object.assign({}, corridor);
- c.epsilon = "String";
- const [err, data] = await to(maizal.weightedastar(c));
- expect(err).to.be.an('error');
- expect(data).to.be.an('undefined');
- });
-});
diff --git a/test/unit/uninformed/bfs.js b/test/unit/uninformed/bfs.js
deleted file mode 100644
index 413a6af..0000000
--- a/test/unit/uninformed/bfs.js
+++ /dev/null
@@ -1,50 +0,0 @@
-const { expect } = require('chai');
-const maizal = require('../../../src/maizal');
-const corridor = require('../../corridor');
-
-let c = {};
-
-describe('Simple BFS corridor search', () => {
- it('A simple search should reach the last position in the corridor', async () => {
- const { solution } = await maizal.bfs(corridor);
- expect(solution.pop().data.position).to.eq(corridor.goals.position);
- });
- it('A better solution must prioritize over a worse one', async () => {
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 2,
- }, {
- position: 4,
- }];
- const { solution } = await maizal.bfs(c);
- expect(solution.pop().data.position).to.eq(c.goals[0].position);
- });
- it('A better solution must prioritize over a worse one', async () => {
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 4,
- }, {
- position: 0,
- }];
- const { solution } = await maizal.bfs(c);
- expect(solution.pop().data.position).to.eq(c.goals[1].position);
- });
- it('In case there a two optimal solutions the first defined action should prioritize', async () => {
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 0,
- }, {
- position: 2,
- }];
- const { solution } = await maizal.bfs(c);
- expect(solution.pop().action).to.eq(c.actions[0].name);
- });
- it('Find the best solution on the left instead', async () => {
- c = Object.assign({}, corridor);
- c.goals = {
- position: 0,
- };
- const { solution } = await maizal.bfs(c);
- expect(solution.pop().data.position).to.eq(c.goals.position);
- });
-});
diff --git a/test/unit/uninformed/dfs.js b/test/unit/uninformed/dfs.js
deleted file mode 100644
index 20f381c..0000000
--- a/test/unit/uninformed/dfs.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const { expect } = require('chai');
-const maizal = require('../../../src/maizal');
-const corridor = require('../../corridor');
-
-let c = {};
-
-describe('Simple Depth-First corridor search', () => {
- it('A simple search should reach the last position in the corridor', async () => {
- const { solution } = await maizal.dfs(corridor);
- expect(solution.pop().data.position).to.eq(corridor.goals.position);
- });
- it('A better solution may not prioritize over a worse one', async () => {
- // DFS expands on depth using the right as primary action losing the left
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 0,
- }, {
- position: 4,
- }];
- const { solution, stats } = await maizal.dfs(c);
- expect(solution.pop().data.position).to.eq(c.goals[1].position);
- expect(stats.nodes).to.eq(3);
- });
- it('The search shall expand on right until the maximun depth is reached and retreat', async () => {
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 0,
- }];
- const { solution, stats } = await maizal.dfs(c);
- expect(solution.pop().data.position).to.eq(c.goals[0].position);
- expect(stats.nodes).to.eq(4);
- });
-});
diff --git a/test/unit/uninformed/dijkstra.js b/test/unit/uninformed/dijkstra.js
deleted file mode 100644
index e0f7f95..0000000
--- a/test/unit/uninformed/dijkstra.js
+++ /dev/null
@@ -1,59 +0,0 @@
-const { expect } = require('chai');
-const maizal = require('../../../src/maizal');
-const corridor = require('../../corridor');
-
-let c = {};
-
-describe('Simple Dijkstra corridor search', () => {
- it('A simple search should reach the last position in the corridor', async () => {
- const { solution } = await maizal.dijkstra(corridor);
- expect(solution.pop().data.position).to.eq(corridor.goals.position);
- });
- it('A better solution must prioritize over a worse one', async () => {
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 2,
- }, {
- position: 4,
- }];
- const { solution } = await maizal.dijkstra(c);
- expect(solution.pop().data.position).to.eq(c.goals[0].position);
- });
- it('A better solution must prioritize over a worse one, Dijkstra with action cost = 0 means a BFS so it should act as so', async () => {
- // Dijkstra with action cost = 0 means a BFS so it should act as so
- c = Object.assign({}, corridor);
- c.goals = [{
- position: 0,
- }, {
- position: 4,
- }];
- const { solution } = await maizal.dijkstra(c);
- expect(solution.pop().data.position).to.eq(c.goals[0].position);
- });
- it('Smaller cost nodes should be expanded first', async () => {
- c = Object.assign({}, corridor);
- c.initial = {
- position: 3,
- };
- c.actions = [{
- name: 'right',
- cost: 50,
- expand: (state) => {
- if (state.position + 1 > 4) return undefined;
- return { position: state.position + 1 };
- },
- },
- {
- name: 'left',
- expand: (state) => {
- if (state.position - 1 < 0) return undefined;
- return { position: state.position - 1 };
- },
- }];
- const { solution, stats } = await maizal.dijkstra(c);
- expect(solution.pop().data.position).to.eq(c.goals.position);
- expect(stats.nodes).to.eq(4);
- expect(stats.depth).to.eq(1);
- expect(stats.cost).to.eq(50);
- });
-});
diff --git a/test/unit/uninformed/random.js b/test/unit/uninformed/random.js
deleted file mode 100644
index 280d414..0000000
--- a/test/unit/uninformed/random.js
+++ /dev/null
@@ -1,23 +0,0 @@
-const { expect } = require('chai');
-const maizal = require('../../../src/maizal');
-const corridor = require('../../corridor');
-
-let c = {};
-
-describe('Simple Random corridor search', () => {
- it('A simple search should reach the last position in the corridor', async () => {
- const { solution } = await maizal.random(corridor);
- expect(solution.pop().data.position).to.eq(corridor.goals.position);
- });
- it('A random search with several solutions must return a solution', async () => {
- c = Object.assign({}, corridor);
- c.initial = { position: 3 };
- c.goals = [{
- position: 2,
- }, {
- position: 4,
- }];
- const { solution } = await maizal.random(c);
- expect(solution.pop().data.position).to.be.oneOf([2, 4]);
- });
-});
diff --git a/test/unit/util/HashSet.js b/test/unit/util/HashSet.js
deleted file mode 100644
index 224807e..0000000
--- a/test/unit/util/HashSet.js
+++ /dev/null
@@ -1,150 +0,0 @@
-
-const { expect } = require('chai');
-const HashSet = require('../../../src/util/HashSet');
-const months = require('../../months');
-
-describe('HashSet string Constructor using an integer field', () => {
- const set = new HashSet('index');
- it('An elment not present should be inserted', () => {
- expect(set.add(months[3])).to.eq(true);
- });
- it('The same reference element should not be inserted', () => {
- expect(set.add(months[3])).to.eq(false);
- });
- it('The same new element should not be inserted', () => {
- expect(set.add({ index: 4, name: 'April' })).to.eq(false);
- });
- it('An element without field should not be inserted and an error returned', () => {
- expect(() => set.add({ name: 'Blue' })).to.throw();
- });
- it('An element previously added should be present', () => {
- expect(set.has(months[3])).to.eq(true);
- expect(set.getHash(months[3])).to.eq(4);
- });
- it('An element not previously added should not be present', () => {
- expect(set.has(months[7])).to.eq(false);
- });
- it('An element can be removed if its present', () => {
- expect(set.remove(months[3])).to.eq(true);
- expect(set.add(months[3])).to.eq(true);
- expect(set.remove(4)).to.eq(true);
- });
- it('Cannot remove an element if it is not present', () => {
- expect(set.remove(months[9])).to.eq(false);
- });
- it('Can get an element if its present', () => {
- set.add(months[3]);
- expect(set.getValue(4).name).to.eq('April');
- });
- it('Cannot get an element if it is not present', () => {
- expect(set.getValue(6)).to.be.undefined;
- });
- it('An array of elements can be added', () => {
- expect(set.add(months).filter(value => value)).to.have.length(11);
- });
- it('Clear a HashSet and maintaing the hash key', () => {
- set.clear();
- expect(set.size()).to.eq(0);
- expect(set.add(months[3])).to.eq(true);
- });
-});
-
-describe('HashSet string Constructor using a String field', () => {
- const set = new HashSet('name');
- it('An elment not present should be inserted', () => {
- expect(set.add(months[3])).to.eq(true);
- });
- it('The same reference element should not be inserted', () => {
- expect(set.add(months[3])).to.eq(false);
- });
- it('The same new element should not be inserted', () => {
- expect(set.add({ index: 4, name: 'April' })).to.eq(false);
- });
- it('An element without field should not be inserted and an error returned', () => {
- expect(() => set.add({ id: 5 })).to.throw();
- });
- it('An element previously added should be present', () => {
- expect(set.has(months[3])).to.eq(true);
- expect(set.getHash(months[3])).to.eq('April');
- });
- it('An element not previously added should not be present', () => {
- expect(set.has(months[7])).to.eq(false);
- });
- it('An element can be removed if its present', () => {
- expect(set.remove(months[3])).to.eq(true);
- expect(set.add(months[3])).to.eq(true);
- expect(set.remove('April')).to.eq(true);
- });
- it('Cannot remove an element if it is not present', () => {
- expect(set.remove(months[9])).to.eq(false);
- });
- it('Can get an element if its present', () => {
- set.add(months[3]);
- expect(set.getValue('April').id).to.eq(months[3].id);
- });
- it('Cannot get an element if it is not present', () => {
- expect(set.getValue(6)).to.be.undefined;
- });
- it('An array of elements can be added', () => {
- expect(set.add(months).filter(value => value)).to.have.length(11);
- });
- it('Get the keys of a HashSet', () => {
- set.clear();
- set.add(months);
- expect(set.keys()).to.deep.eq(months.map(({ name }) => name));
- });
-});
-
-describe('HashSet string Constructor using a not existent field', () => {
- const set = new HashSet('iDoNotExist');
- it('An element cannot be inserted and error thrown', () => {
- expect(() => set.add(months[3])).to.throw();
- });
- it('An array cannot be inserted an error thrown', () => {
- expect(() => set.add(months)).to.throw();
- });
-});
-
-describe('HashSet using a function', () => {
- const set = new HashSet(value => value.index + 15);
- it('An elment not present should be inserted', () => {
- expect(set.add(months[3])).to.eq(true);
- });
- it('The same reference element should not be inserted', () => {
- expect(set.add(months[3])).to.eq(false);
- });
- it('The same new element should not be inserted', () => {
- expect(set.add({ index: 4, name: 'April' })).to.eq(false);
- });
- it('An element without field may be added depending on how the function deals with hash asignment', () => {
- expect(set.add({ name: 'Blue' })).to.eq(true);
- });
- it('An element previously added should be present', () => {
- expect(set.has(months[3])).to.eq(true);
- });
- it('An element not previously added should not be present', () => {
- expect(set.has(months[7])).to.eq(false);
- });
- it('An element can be removed if its present', () => {
- expect(set.remove(months[3])).to.eq(true);
- });
- it('Cannot remove an element if it is not present', () => {
- expect(set.remove(months[9])).to.eq(false);
- });
- it('Can get an element if its present', () => {
- set.add(months[3]);
- expect(set.getValue(19).name).to.eq('April');
- });
- it('Cannot get an element if it is not present', () => {
- expect(set.getValue(6)).to.be.undefined;
- });
- it('An array of elements can be added', () => {
- expect(set.add(months).filter(value => value)).to.have.length(11);
- });
-});
-
-describe('HashSet without providing a function', () => {
- it('The set cannot be created and error thrown', () => {
- expect(() => new HashSet()).to.throw();
- });
-});
diff --git a/test/unit/util/PriorityQueue.js b/test/unit/util/PriorityQueue.js
deleted file mode 100644
index 29c953c..0000000
--- a/test/unit/util/PriorityQueue.js
+++ /dev/null
@@ -1,77 +0,0 @@
-const { expect } = require('chai');
-const PriorityQueue = require('../../../src/util/PriorityQueue');
-const months = require('../../months');
-
-describe('PriorityQueue ascending order elements', () => {
- const q = new PriorityQueue(({ index }) => index);
- it('An element should be inserted', () => {
- expect(q.enqueue(months[5])).to.eq(0);
- });
- it('An element with higer priority should be first', () => {
- expect(q.enqueue(months[0])).to.eq(0);
- });
- it('The top of the queue should be the first element', () => {
- expect(q.dequeue().name).to.eq('January');
- });
- it('An empty queue cannot dequeue its elements', () => {
- q.dequeue();
- expect(q.dequeue()).to.be.an('undefined');
- });
- it('An empty queue should not contains elements', () => {
- expect(q.size()).to.be.eq(0);
- });
- it('An array of elements can be inserted in their porper positions', () => {
- expect(q.enqueue(months)).to.have.lengthOf(12);
- });
- it('The queue size should be increased', () => {
- expect(q.size()).to.eq(12);
- });
- it('A cleared queue should not have size', () => {
- q.clear();
- expect(q.size()).to.be.eq(0);
- });
- it('An emptied queue should be empty', () => {
- expect(q.empty()).to.be.eq(true);
- });
- it('Adding an array in a random order should not affect the priorities', () => {
- const rev = months.slice(0);
- q.enqueue(rev.reverse());
- expect(q.dequeue().name).to.eq('January');
- });
-});
-
-describe('PriorityQueue reverse order', () => {
- const q = new PriorityQueue(toInsert => -toInsert.index);
- it('An element should be inserted first if the queue is empty', () => {
- expect(q.enqueue(months[5])).to.eq(0);
- });
- it('An element should be enqueued first if its priority is higher', () => {
- expect(q.enqueue(months[8])).to.eq(0);
- });
- it('An element with same priority will be inserted after', () => {
- expect(q.enqueue({ index: 9, name: 'NoJanuary' })).to.eq(1);
- });
- it('Dequeueing should return the top element', () => {
- expect(q.dequeue().name).to.eq('September');
- });
- it('Any array should be added according to their priority regarding the order', () => {
- q.enqueue(months);
- expect(q.dequeue().name).to.eq('December');
- });
-});
-
-describe('PriorityQueue no constructor', () => {
- it('A Queue cannot be created if no argument is provided', () => {
- expect(() => new PriorityQueue()).to.throw();
- });
-});
-
-describe('PriorityQueue undefined priority', () => {
- const q = new PriorityQueue(() => undefined);
- it('Undefined priorities can only be inserted on top', () => {
- expect(q.enqueue(months[2])).to.be.eq(0);
- });
- it('Undefined priorities must not be inserted', () => {
- expect(q.enqueue(months[5])).to.be.an('undefined');
- });
-});
diff --git a/tests/AStar.test.ts b/tests/AStar.test.ts
new file mode 100644
index 0000000..0aef0db
--- /dev/null
+++ b/tests/AStar.test.ts
@@ -0,0 +1,214 @@
+import { describe, it, assert } from 'vitest'
+import {
+ down,
+ expand,
+ type Grid,
+ left,
+ type Position,
+ positionEquality,
+ right,
+ stall,
+ up,
+} from './Grid'
+import { aStar, Heuristics } from '../src/AStar'
+import { search } from '../src/Search'
+
+const euclideanDistance: (goal: Position) => Heuristics =
+ (goal: Position) => (position: Position) =>
+ Math.sqrt(Math.pow(goal.x - position.x, 2) + Math.pow(goal.y - goal.y, 2))
+
+describe('AStar', () => {
+ it('solves when initial and goal are equals', async () => {
+ const grid: Grid = { rows: 1, columns: 1 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 0,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await aStar(euclideanDistance(goal))(
+ search(initial, goal, positionEquality, expand(grid)([])),
+ )
+
+ const expectedSolution: Position[] = [{ x: 0, y: 0 }]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves after expanding once', async () => {
+ const corridor: Grid = { rows: 1, columns: 2 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 1,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await aStar(euclideanDistance(goal))(
+ search(initial, goal, positionEquality, expand(corridor)([right])),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves after expanding twice', async () => {
+ const corridor: Grid = { rows: 1, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await aStar(euclideanDistance(goal))(
+ search(initial, goal, positionEquality, expand(corridor)([right])),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves after expanding n times', async () => {
+ const grid: Grid = { rows: 3, columns: 2 }
+ const initial: Position = {
+ x: 0,
+ y: 1,
+ }
+ const goal: Position = {
+ x: 1,
+ y: 2,
+ }
+
+ const actualSolution: Position[] = await aStar(euclideanDistance(goal))(
+ search(
+ initial,
+ goal,
+ positionEquality,
+ expand(grid)([up, down, right, left]),
+ ),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 1 },
+ { x: 1, y: 1 },
+ { x: 1, y: 2 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves best solution', async () => {
+ const grid: Grid = { rows: 3, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 2,
+ }
+
+ const actualSolution: Position[] = await aStar(euclideanDistance(goal))(
+ search(
+ initial,
+ goal,
+ positionEquality,
+ expand(grid)([up, down, right, left]),
+ ),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ { x: 2, y: 1 },
+ { x: 2, y: 2 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves worst solution', async () => {
+ const grid: Grid = { rows: 3, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 2,
+ }
+
+ const actualSolution: Position[] = await aStar(euclideanDistance(goal))(
+ search(
+ initial,
+ goal,
+ positionEquality,
+ expand(grid)([up, down, right, left]),
+ ),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ { x: 2, y: 1 },
+ { x: 2, y: 2 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('return empty array for unsolvable searches', async () => {
+ const grid: Grid = { rows: 2, columns: 2 }
+ const initial: Position = {
+ x: 0,
+ y: 1,
+ }
+ const goal: Position = {
+ x: 100,
+ y: 100,
+ }
+
+ const actualSolution: Position[] = await aStar(euclideanDistance(goal))(
+ search(initial, goal, positionEquality, expand(grid)([up])),
+ )
+
+ const expectedSolution: Position[] = []
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('filters already visited states on expanding', async () => {
+ const corridor: Grid = { rows: 1, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await aStar(euclideanDistance(goal))(
+ search(initial, goal, positionEquality, expand(corridor)([stall, right])),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+})
diff --git a/tests/BestFirstSearch.test.ts b/tests/BestFirstSearch.test.ts
new file mode 100644
index 0000000..681d32a
--- /dev/null
+++ b/tests/BestFirstSearch.test.ts
@@ -0,0 +1,210 @@
+import { describe, it, assert } from 'vitest'
+import { bestFirstSearch } from '../src/BestFirstSearch'
+import {
+ down,
+ expand,
+ type Grid,
+ left,
+ type Position,
+ positionEquality,
+ right,
+ stall,
+ up,
+} from './Grid'
+import { search } from '../src/Search'
+
+describe('BestFirstSearch', () => {
+ it('solves when initial and goal are equals', async () => {
+ const grid: Grid = { rows: 1, columns: 1 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 0,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await bestFirstSearch(
+ search(initial, goal, positionEquality, expand(grid)([])),
+ )
+
+ const expectedSolution: Position[] = [{ x: 0, y: 0 }]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves after expanding once', async () => {
+ const corridor: Grid = { rows: 1, columns: 2 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 1,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await bestFirstSearch(
+ search(initial, goal, positionEquality, expand(corridor)([right])),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves after expanding twice', async () => {
+ const corridor: Grid = { rows: 1, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await bestFirstSearch(
+ search(initial, goal, positionEquality, expand(corridor)([right])),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves after expanding n times', async () => {
+ const grid: Grid = { rows: 3, columns: 2 }
+ const initial: Position = {
+ x: 0,
+ y: 1,
+ }
+ const goal: Position = {
+ x: 1,
+ y: 2,
+ }
+
+ const actualSolution: Position[] = await bestFirstSearch(
+ search(
+ initial,
+ goal,
+ positionEquality,
+ expand(grid)([up, down, right, left]),
+ ),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 1 },
+ { x: 0, y: 2 },
+ { x: 1, y: 2 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves best solution', async () => {
+ const grid: Grid = { rows: 3, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 2,
+ }
+
+ const actualSolution: Position[] = await bestFirstSearch(
+ search(
+ initial,
+ goal,
+ positionEquality,
+ expand(grid)([up, down, right, left]),
+ ),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 0, y: 1 },
+ { x: 0, y: 2 },
+ { x: 1, y: 2 },
+ { x: 2, y: 2 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves worst solution', async () => {
+ const grid: Grid = { rows: 3, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 2,
+ }
+
+ const actualSolution: Position[] = await bestFirstSearch(
+ search(
+ initial,
+ goal,
+ positionEquality,
+ expand(grid)([up, down, right, left]),
+ ),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 0, y: 1 },
+ { x: 0, y: 2 },
+ { x: 1, y: 2 },
+ { x: 2, y: 2 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('return empty array for unsolvable searches', async () => {
+ const grid: Grid = { rows: 2, columns: 2 }
+ const initial: Position = {
+ x: 0,
+ y: 1,
+ }
+ const goal: Position = {
+ x: 100,
+ y: 100,
+ }
+
+ const actualSolution: Position[] = await bestFirstSearch(
+ search(initial, goal, positionEquality, expand(grid)([up])),
+ )
+
+ const expectedSolution: Position[] = []
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('filters already visited states on expanding', async () => {
+ const corridor: Grid = { rows: 1, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await bestFirstSearch(
+ search(initial, goal, positionEquality, expand(corridor)([stall, right])),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+})
diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts
new file mode 100644
index 0000000..49b7830
--- /dev/null
+++ b/tests/DepthFirstSearch.test.ts
@@ -0,0 +1,184 @@
+import { describe, it, assert } from 'vitest'
+import { depthFirstSearch } from '../src/DepthFirstSearch'
+import {
+ down,
+ expand,
+ type Grid,
+ left,
+ type Position,
+ positionEquality,
+ right,
+ stall,
+ up,
+} from './Grid'
+import { search } from '../src/Search'
+
+describe('DepthFirstSearch', () => {
+ it('solves when initial and goal are equals', async () => {
+ const grid: Grid = { rows: 1, columns: 1 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 0,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await depthFirstSearch(
+ search(initial, goal, positionEquality, expand(grid)([])),
+ )
+
+ const expectedSolution: Position[] = [{ x: 0, y: 0 }]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves after expanding once', async () => {
+ const corridor: Grid = { rows: 1, columns: 2 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 1,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await depthFirstSearch(
+ search(initial, goal, positionEquality, expand(corridor)([right])),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves after expanding twice', async () => {
+ const corridor: Grid = { rows: 1, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await depthFirstSearch(
+ search(initial, goal, positionEquality, expand(corridor)([right])),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves after expanding n times', async () => {
+ const grid: Grid = { rows: 3, columns: 2 }
+ const initial: Position = {
+ x: 0,
+ y: 1,
+ }
+ const goal: Position = {
+ x: 1,
+ y: 2,
+ }
+
+ const actualSolution: Position[] = await depthFirstSearch(
+ search(
+ initial,
+ goal,
+ positionEquality,
+ expand(grid)([up, down, right, left]),
+ ),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 1 },
+ { x: 0, y: 2 },
+ { x: 1, y: 2 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('solves worst solution', async () => {
+ const grid: Grid = { rows: 3, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 2,
+ }
+
+ const actualSolution: Position[] = await depthFirstSearch(
+ search(
+ initial,
+ goal,
+ positionEquality,
+ expand(grid)([up, down, right, left]),
+ ),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 0, y: 1 },
+ { x: 0, y: 2 },
+ { x: 1, y: 2 },
+ { x: 1, y: 1 },
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ { x: 2, y: 1 },
+ { x: 2, y: 2 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('return empty array for unsolvable searches', async () => {
+ const grid: Grid = { rows: 2, columns: 2 }
+ const initial: Position = {
+ x: 0,
+ y: 1,
+ }
+ const goal: Position = {
+ x: 100,
+ y: 100,
+ }
+
+ const actualSolution: Position[] = await depthFirstSearch(
+ search(initial, goal, positionEquality, expand(grid)([up])),
+ )
+
+ const expectedSolution: Position[] = []
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+
+ it('filters already visited states on expanding', async () => {
+ const corridor: Grid = { rows: 1, columns: 3 }
+ const initial: Position = {
+ x: 0,
+ y: 0,
+ }
+ const goal: Position = {
+ x: 2,
+ y: 0,
+ }
+
+ const actualSolution: Position[] = await depthFirstSearch(
+ search(initial, goal, positionEquality, expand(corridor)([stall, right])),
+ )
+
+ const expectedSolution: Position[] = [
+ { x: 0, y: 0 },
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ ]
+ assert.deepStrictEqual(actualSolution, expectedSolution)
+ })
+})
diff --git a/tests/Graph.test.ts b/tests/Graph.test.ts
new file mode 100644
index 0000000..bd0e070
--- /dev/null
+++ b/tests/Graph.test.ts
@@ -0,0 +1,69 @@
+import { assert, describe, it } from 'vitest'
+import {
+ adjacent,
+ edge,
+ Eq,
+ graph,
+ Graph,
+ hasEdge,
+ hasVertex,
+ vertex,
+} from '../src/Graph'
+
+type Person = string
+
+const personEq: Eq = (a: Person, b: Person): boolean => a === b
+
+const addFriends: (
+ a: Person,
+ b: Person,
+) => (graph: Graph) => Graph = (a: Person, b: Person) =>
+ edge(personEq, a, b)
+
+const areFriends = (a: Person, b: Person) => (graph: Graph) =>
+ hasEdge(personEq, a, b)(graph)
+
+describe('Graph', () => {
+ describe('vertex', () => {
+ it('adds given vertex', () => {
+ const john: Person = 'John'
+
+ const networkGraph = vertex(john)(graph())
+
+ assert.isTrue(hasVertex(personEq, john)(networkGraph))
+ const sally: Person = 'sally'
+ assert.isFalse(hasVertex(personEq, sally)(networkGraph))
+ })
+ })
+
+ describe('edge', () => {
+ it('adds edge for two vertexes', () => {
+ const john: Person = 'John'
+ const jane: Person = 'Jane'
+
+ const networkGraph = addFriends(
+ john,
+ jane,
+ )(vertex(jane)(vertex(john)(graph())))
+
+ assert.isTrue(areFriends(jane, john)(networkGraph))
+ assert.isTrue(areFriends(john, jane)(networkGraph))
+ })
+ })
+
+ describe('adjacent', () => {
+ it('adds all adjacent vertex for a given vertex', () => {
+ const john: Person = 'John'
+ const jane: Person = 'Jane'
+ const sally: Person = 'Sally'
+
+ const networkGraph = adjacent(personEq, john, [jane, sally])(
+ vertex(john)(graph()),
+ )
+
+ assert.isTrue(areFriends(john, jane)(networkGraph))
+ assert.isTrue(areFriends(john, sally)(networkGraph))
+ assert.isFalse(areFriends(jane, sally)(networkGraph))
+ })
+ })
+})
diff --git a/tests/Grid.ts b/tests/Grid.ts
new file mode 100644
index 0000000..5cf92ea
--- /dev/null
+++ b/tests/Grid.ts
@@ -0,0 +1,75 @@
+import { Eq, Expand } from '../src/Search'
+
+export type Grid = {
+ rows: number
+ columns: number
+}
+
+export type Position = {
+ x: number
+ y: number
+}
+
+export const positionEquality: Eq = (a: Position, b: Position) =>
+ a.y === b.y && a.x === b.x
+
+type Movement = (position: Position) => Position | null
+
+export const up: (grid: Grid) => Movement =
+ (grid: Grid) => (position: Position) => {
+ if (position.y === grid.rows - 1) {
+ return null
+ }
+ return {
+ x: position.x,
+ y: position.y + 1,
+ }
+ }
+export const down: (grid: Grid) => Movement = () => (position: Position) => {
+ if (position.y === 0) {
+ return null
+ }
+ return {
+ x: position.x,
+ y: position.y - 1,
+ }
+}
+export const right: (grid: Grid) => Movement =
+ (grid: Grid) => (position: Position) => {
+ if (position.x === grid.columns - 1) {
+ return null
+ }
+
+ return {
+ x: position.x + 1,
+ y: position.y,
+ }
+ }
+
+export const left: (grid: Grid) => Movement = () => (position: Position) => {
+ if (position.x === 0) {
+ return null
+ }
+
+ return {
+ x: position.x - 1,
+ y: position.y,
+ }
+}
+
+export const stall: () => Movement = () => (position: Position) => ({
+ x: position.x,
+ y: position.y,
+})
+
+export const expand: (
+ grid: Grid,
+) => (allowedMoves: ((grid: Grid) => Movement)[]) => Expand =
+ (grid: Grid) =>
+ (allowedMoves: ((grid: Grid) => Movement)[]) =>
+ (position: Position) =>
+ allowedMoves
+ .map((gridMovement: (grid: Grid) => Movement) => gridMovement(grid))
+ .map((movement: Movement) => movement(position))
+ .filter((position: Position | null) => position !== null)
+ .map((position: Position) => Promise.resolve(position))
diff --git a/tests/HashSet.test.ts b/tests/HashSet.test.ts
new file mode 100644
index 0000000..a4d21c8
--- /dev/null
+++ b/tests/HashSet.test.ts
@@ -0,0 +1,100 @@
+import { assert, describe, it } from 'vitest'
+
+import { hashSet, has, insert } from '../src/HashSet'
+
+type Vector = {
+ x: number
+ y: number
+}
+
+describe('HashSet', () => {
+ describe('constructor', () => {
+ it('creates empty', () => {
+ assert.deepStrictEqual(hashSet(), [])
+ })
+ })
+
+ describe('insert', () => {
+ it('inserts value', () => {
+ const vector: Vector = {
+ x: -3,
+ y: 8,
+ }
+
+ const vectorHashSet = insert(vector)(hashSet())
+
+ const expectedHashSetValues: Vector[] = [
+ {
+ x: -3,
+ y: 8,
+ },
+ ]
+ assert.deepStrictEqual(vectorHashSet, expectedHashSetValues)
+ })
+ })
+
+ describe('has', () => {
+ it('does not have element for an empty HashSet', () => {
+ const vectorHashSet = hashSet()
+
+ const hasVector = has(
+ (a: Vector, b: Vector) => a.x === b.x && b.y === b.y,
+ )
+
+ const vector: Vector = {
+ x: -3,
+ y: 8,
+ }
+ assert.isFalse(hasVector(vector)(vectorHashSet))
+ })
+
+ it('has element for an element present in the HashSet', () => {
+ const vector: Vector = {
+ x: -3,
+ y: 8,
+ }
+
+ const vectorHashSet = insert(vector)(hashSet())
+
+ const hasVector = has(
+ (a: Vector, b: Vector) => a.x === b.x && b.y === b.y,
+ )
+
+ assert.isTrue(hasVector(vector)(vectorHashSet))
+ })
+
+ it('has element for an element present in the HashSet with swapped properties', () => {
+ const vector: Vector = {
+ x: -3,
+ y: 8,
+ }
+
+ const vectorHashSet = insert(vector)(hashSet())
+
+ const hasVector = has(
+ (a: Vector, b: Vector) => a.x === b.x && b.y === b.y,
+ )
+
+ const swappedPropertiesVector: Vector = {
+ y: 8,
+ x: -3,
+ }
+ assert.isTrue(hasVector(swappedPropertiesVector)(vectorHashSet))
+ })
+
+ it('not present HashSet', () => {
+ const vector: Vector = {
+ x: -3,
+ y: 8,
+ }
+
+ const vectorHashSet = insert(vector)(hashSet())
+
+ const hasVector = has(
+ (a: Vector, b: Vector) => a.x === b.x && b.y === b.y,
+ )
+
+ assert.isFalse(hasVector({ x: 1, y: -18 })(vectorHashSet))
+ })
+ })
+})
diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts
new file mode 100644
index 0000000..a9e30b4
--- /dev/null
+++ b/tests/PriorityQueue.test.ts
@@ -0,0 +1,133 @@
+import { assert, describe, it } from 'vitest'
+import {
+ empty,
+ insert,
+ priorityQueue,
+ Ord,
+ poll,
+ type PriorityQueue,
+ values,
+} from '../src/PriorityQueue'
+
+type Person = {
+ age: number
+ name: string
+}
+
+describe('PriorityQueue', () => {
+ const jane: Person = {
+ age: 27,
+ name: 'Jane',
+ }
+ const john: Person = {
+ age: 34,
+ name: 'John',
+ }
+ const sally: Person = {
+ age: 28,
+ name: 'Sally',
+ }
+ const jasmine: Person = {
+ age: 27,
+ name: 'Jasmine',
+ }
+
+ const ageOrd: Ord = (a: Person, b) => b.age - a.age
+ const insertPerson: (
+ person: Person,
+ ) => (priorityQueue: PriorityQueue) => PriorityQueue = (
+ person: Person,
+ ) => insert(ageOrd)(person)
+
+ describe('constructor', () => {
+ it('creates empty', () => {
+ const priorityQueueValues = values(empty())
+
+ assert.deepStrictEqual(priorityQueueValues, [])
+ })
+
+ it('creates for element', () => {
+ const queue: PriorityQueue = priorityQueue(jane)
+
+ const expectedPriorityQueueValues: Person[] = [
+ {
+ age: 27,
+ name: 'Jane',
+ },
+ ]
+ assert.deepStrictEqual(values(queue), expectedPriorityQueueValues)
+ })
+ })
+ describe('insert', () => {
+ it('inserts value at last position', () => {
+ const personsPriorityQueue = insertPerson(jane)(empty())
+
+ const priorityQueueValues = insertPerson(john)(personsPriorityQueue)
+
+ const expectedPriorityQueueValues: Person[] = [jane, john]
+ assert.deepStrictEqual(
+ values(priorityQueueValues),
+ expectedPriorityQueueValues,
+ )
+ })
+
+ it('inserts value at first position', () => {
+ const personsPriorityQueue = insertPerson(john)(empty())
+
+ const priorityQueueValues = insertPerson(jane)(personsPriorityQueue)
+
+ const expectedPriorityQueueValues: Person[] = [jane, john]
+ assert.deepStrictEqual(
+ values(priorityQueueValues),
+ expectedPriorityQueueValues,
+ )
+ })
+
+ it('inserts value at the middle position', () => {
+ const personsPriorityQueue = insertPerson(jane)(
+ insertPerson(john)(empty()),
+ )
+
+ const priorityQueueValues = insertPerson(sally)(personsPriorityQueue)
+
+ const expectedPriorityQueueValues: Person[] = [jane, sally, john]
+ assert.deepStrictEqual(
+ values(priorityQueueValues),
+ expectedPriorityQueueValues,
+ )
+ })
+
+ it('inserts after in case of same priority', () => {
+ const personsPriorityQueue = insertPerson(jane)(empty())
+
+ const priorityQueueValues = insertPerson(jasmine)(personsPriorityQueue)
+
+ const expectedPriorityQueueValues: Person[] = [jane, jasmine]
+ assert.deepStrictEqual(
+ values(priorityQueueValues),
+ expectedPriorityQueueValues,
+ )
+ })
+ })
+ describe('poll', () => {
+ it('polls non empty PriorityQueue', () => {
+ const personsPriorityQueue = insertPerson(jane)(
+ insertPerson(john)(empty()),
+ )
+
+ const [person, modifiedQueue] = poll(personsPriorityQueue)
+
+ const expectedPerson: Person = jane
+ const expectedPriorityQueueValues: Person[] = [john]
+ assert.deepStrictEqual(person, expectedPerson)
+ assert.deepStrictEqual(values(modifiedQueue), expectedPriorityQueueValues)
+ })
+
+ it('polls empty PriorityQueue', () => {
+ const [person, modifiedQueue] = poll(empty())
+
+ assert.isNull(person)
+ assert.deepStrictEqual(modifiedQueue, [])
+ })
+ })
+})
diff --git a/tests/Tree.test.ts b/tests/Tree.test.ts
new file mode 100644
index 0000000..3a3c99e
--- /dev/null
+++ b/tests/Tree.test.ts
@@ -0,0 +1,33 @@
+import { assert, describe, it } from 'vitest'
+import { insert, toArray, type Tree, fromRoot } from '../src/Tree'
+
+describe('Tree', () => {
+ describe('constructor', () => {
+ it('creates for Tree Root', () => {
+ const t: Tree = fromRoot(1, 1)
+
+ const expectedValues = [1]
+ assert.deepStrictEqual(toArray(t), expectedValues)
+ })
+ })
+
+ describe('insert', () => {
+ it('inserts value', () => {
+ const childInsert = insert(2, 2)
+
+ const t: Tree = childInsert(fromRoot(1, 1))
+
+ const expectedValues = [1, 2]
+ assert.deepStrictEqual(toArray(t), expectedValues)
+ })
+
+ it('inserts multiple values', () => {
+ const childInsert = insert(2, 2)
+
+ const t: Tree = insert(3, 3)(childInsert(fromRoot(1, 1)))
+
+ const expectedValues = [1, 2, 3]
+ assert.deepStrictEqual(toArray(t), expectedValues)
+ })
+ })
+})
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..905c65b
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,45 @@
+{
+ // Visit https://aka.ms/tsconfig to read more about this file
+ "compilerOptions": {
+ // File Layout
+ // "rootDir": "./src",
+ // "outDir": "./dist",
+
+ // Environment Settings
+ // See also https://aka.ms/tsconfig/module
+ "module": "commonjs",
+ "moduleResolution": "bundler",
+ "target": "esnext",
+ "types": [],
+ // For nodejs:
+ // "lib": ["esnext"],
+ // "types": ["node"],
+ // and npm install -D @types/node
+
+ // Other Outputs
+ "sourceMap": true,
+ "declaration": true,
+ "declarationMap": true,
+
+ // Stricter Typechecking Options
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+
+ // Style Options
+ // "noImplicitReturns": true,
+ // "noImplicitOverride": true,
+ // "noUnusedLocals": true,
+ // "noUnusedParameters": true,
+ // "noFallthroughCasesInSwitch": true,
+ // "noPropertyAccessFromIndexSignature": true,
+
+ // Recommended Options
+ "strict": true,
+ "jsx": "react-jsx",
+ "verbatimModuleSyntax": false,
+ "isolatedModules": true,
+ "noUncheckedSideEffectImports": true,
+ "moduleDetection": "force",
+ "skipLibCheck": true
+ }
+}