diff --git a/.gitignore b/.gitignore index f655c8d..c952826 100644 --- a/.gitignore +++ b/.gitignore @@ -56,4 +56,7 @@ logs *.tsbuildinfo # Optional REPL history -.node_repl_history \ No newline at end of file +.node_repl_history + +# Docs +docs/ \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1e86a94 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +npm_config_registry=https://registry.npmjs.org \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 81dbe31..f66137a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,15 +14,19 @@ "uuid": "^13.0.0" }, "devDependencies": { + "@types/hast": "^3.0.4", "@types/node": "^25.0.1", + "@types/unist": "^3.0.3", "@vitest/coverage-istanbul": "^1.0.0", "@vitest/coverage-v8": "^1.0.0", "@vitest/ui": "^1.0.0", "dotenv": "^16.3.1", "eslint": "^8.54.0", "nock": "^13.4.0", + "typedoc": "^0.28.14", + "typedoc-plugin-markdown": "^4.9.0", "typescript": "^5.3.2", - "vitest": "^1.0.0" + "vitest": "^1.6.1" } }, "node_modules/@ampproject/remapping": { @@ -40,24 +44,24 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, "license": "MIT", "engines": { @@ -65,22 +69,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", - "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.9", - "@babel/types": "^7.26.9", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -95,17 +99,27 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -113,14 +127,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -129,30 +143,50 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -162,9 +196,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -172,9 +206,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -182,9 +216,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -192,27 +226,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", - "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -222,48 +256,48 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", - "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -668,9 +702,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -687,9 +721,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -720,55 +754,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@eslint/js": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", @@ -779,6 +764,20 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.20.0.tgz", + "integrity": "sha512-Wa57i+bMpK6PGJZ1f2myxo3iO+K/kZikcyvH8NIqNNZhQUbDav7V9LQmWOXhf946mz5c1NZ19WMsGYiDKTryzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.20.0", + "@shikijs/langs": "^3.20.0", + "@shikijs/themes": "^3.20.0", + "@shikijs/types": "^3.20.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -841,34 +840,31 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -876,16 +872,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -939,9 +935,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", - "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", "cpu": [ "arm" ], @@ -953,9 +949,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", - "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", "cpu": [ "arm64" ], @@ -967,9 +963,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", - "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", "cpu": [ "arm64" ], @@ -981,9 +977,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", - "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", "cpu": [ "x64" ], @@ -995,9 +991,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", - "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", "cpu": [ "arm64" ], @@ -1009,9 +1005,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", - "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", "cpu": [ "x64" ], @@ -1023,9 +1019,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", - "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", "cpu": [ "arm" ], @@ -1037,9 +1033,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", - "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", "cpu": [ "arm" ], @@ -1051,9 +1047,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", - "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", "cpu": [ "arm64" ], @@ -1065,9 +1061,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", - "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", "cpu": [ "arm64" ], @@ -1078,10 +1074,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", - "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", "cpu": [ "loong64" ], @@ -1092,10 +1088,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", - "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", "cpu": [ "ppc64" ], @@ -1107,9 +1103,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", - "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", "cpu": [ "riscv64" ], @@ -1121,9 +1117,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", - "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", "cpu": [ "riscv64" ], @@ -1135,9 +1131,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", - "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", "cpu": [ "s390x" ], @@ -1149,9 +1145,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", - "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", "cpu": [ "x64" ], @@ -1163,9 +1159,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", - "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", "cpu": [ "x64" ], @@ -1176,10 +1172,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", - "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", "cpu": [ "arm64" ], @@ -1191,9 +1201,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", - "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", "cpu": [ "ia32" ], @@ -1204,10 +1214,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", - "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", "cpu": [ "x64" ], @@ -1218,6 +1242,55 @@ "win32" ] }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.20.0.tgz", + "integrity": "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.20.0.tgz", + "integrity": "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.20.0.tgz", + "integrity": "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.20.0.tgz", + "integrity": "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1238,16 +1311,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/node": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.1.tgz", - "integrity": "sha512-czWPzKIAXucn9PtsttxmumiQ9N0ok9FrBwgRWrwmVLlp86BrMExzvXRLFYRJ+Ex3g6yqj+KuaxfX1JTgV2lpfg==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -1279,51 +1369,6 @@ "vitest": "1.6.1" } }, - "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@vitest/coverage-v8": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", @@ -1352,21 +1397,6 @@ "vitest": "1.6.1" } }, - "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@vitest/expect": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", @@ -1414,9 +1444,9 @@ } }, "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "dev": true, "license": "MIT", "engines": { @@ -1492,20 +1522,10 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -1581,6 +1601,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -1598,13 +1625,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -1615,10 +1642,20 @@ "dev": true, "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1640,9 +1677,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -1660,10 +1697,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -1706,9 +1744,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001700", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", - "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", "dev": true, "funding": [ { @@ -1745,16 +1783,6 @@ "node": ">=4" } }, - "node_modules/chai/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1854,10 +1882,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1924,9 +1951,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -1951,42 +1978,25 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.104", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.104.tgz", - "integrity": "sha512-Us9M2L4cO/zMBqVkJtnj353nQhMju9slHm62NprKTmdF3HH8wYOtNvDFq/JB2+ZRoGLzdvYDiATlMHs98XBM1g==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, "node_modules/engine.io-client": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", - "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", + "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.17.1", + "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/engine.io-parser": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", @@ -1996,6 +2006,19 @@ "node": ">=10.0.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2190,104 +2213,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -2342,6 +2267,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2352,6 +2287,30 @@ "node": ">=0.10.0" } }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2404,9 +2363,9 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", - "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -2446,6 +2405,23 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -2469,9 +2445,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -2489,14 +2465,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -2591,6 +2568,41 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2605,13 +2617,19 @@ } }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/gopd": { @@ -2689,6 +2707,16 @@ "dev": true, "license": "MIT" }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2716,16 +2744,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2798,6 +2816,19 @@ "node": ">=8" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2815,6 +2846,23 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -2830,10 +2878,25 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2851,6 +2914,19 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -2929,6 +3005,16 @@ "node": ">= 0.8.0" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/local-pkg": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", @@ -2946,10 +3032,26 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, @@ -2973,14 +3075,21 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/magicast": { @@ -3011,17 +3120,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, - "engines": { - "node": ">=10" + "bin": { + "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/math-intrinsics": { @@ -3033,6 +3147,13 @@ "node": ">= 0.4" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3064,19 +3185,6 @@ "node": ">=8.6" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -3098,6 +3206,19 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3112,16 +3233,16 @@ } }, "node_modules/mlly": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", - "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", "dev": true, "license": "MIT", "dependencies": { - "acorn": "^8.14.0", - "pathe": "^2.0.1", - "pkg-types": "^1.3.0", - "ufo": "^1.5.4" + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" } }, "node_modules/mlly/node_modules/pathe": { @@ -3189,12 +3310,41 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3205,6 +3355,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3239,6 +3405,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3306,6 +3488,19 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", @@ -3418,6 +3613,16 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3446,10 +3651,20 @@ "dev": true, "license": "MIT" }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -3474,32 +3689,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", - "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "dev": true, "license": "MIT", "dependencies": { @@ -3513,26 +3706,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.1", - "@rollup/rollup-android-arm64": "4.44.1", - "@rollup/rollup-darwin-arm64": "4.44.1", - "@rollup/rollup-darwin-x64": "4.44.1", - "@rollup/rollup-freebsd-arm64": "4.44.1", - "@rollup/rollup-freebsd-x64": "4.44.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", - "@rollup/rollup-linux-arm-musleabihf": "4.44.1", - "@rollup/rollup-linux-arm64-gnu": "4.44.1", - "@rollup/rollup-linux-arm64-musl": "4.44.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-musl": "4.44.1", - "@rollup/rollup-linux-s390x-gnu": "4.44.1", - "@rollup/rollup-linux-x64-gnu": "4.44.1", - "@rollup/rollup-linux-x64-musl": "4.44.1", - "@rollup/rollup-win32-arm64-msvc": "4.44.1", - "@rollup/rollup-win32-ia32-msvc": "4.44.1", - "@rollup/rollup-win32-x64-msvc": "4.44.1", + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" } }, @@ -3561,13 +3756,16 @@ } }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/shebang-command": { @@ -3600,6 +3798,19 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -3616,13 +3827,13 @@ } }, "node_modules/socket.io-client": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", - "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.2", + "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" }, @@ -3630,53 +3841,19 @@ "node": ">=10.0.0" } }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3695,9 +3872,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, @@ -3714,6 +3891,19 @@ "node": ">=8" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3775,28 +3965,6 @@ "node": ">=8" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3868,19 +4036,95 @@ } }, "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedoc": { + "version": "0.28.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.15.tgz", + "integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.17.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.8.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.9.0.tgz", + "integrity": "sha512-9Uu4WR9L7ZBgAl60N/h+jqmPxxvnC9nQAlnnO/OujtG2ubjnKTVUFY1XDhcMY+pCqlX3N2HsQM2QTYZIU9tJuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typedoc": "0.28.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3891,6 +4135,13 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/ufo": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", @@ -3906,9 +4157,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -3960,9 +4211,9 @@ } }, "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", "dependencies": { @@ -4108,150 +4359,6 @@ } } }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/vitest/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vitest/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4303,9 +4410,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -4338,6 +4445,22 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b7f492c..15caa74 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,12 @@ "test:e2e": "vitest run tests/e2e", "test:watch": "vitest", "test:coverage": "vitest run --coverage", - "prepublishOnly": "npm run build" + "docs": "typedoc", + "prepublishOnly": "npm run build", + "create-docs": "npm run create-docs:generate && npm run create-docs:process", + "push-docs": "node scripts/mintlify-post-processing/push-to-docs-repo.js", + "create-docs:generate": "typedoc", + "create-docs:process": "node scripts/mintlify-post-processing/file-processing/file-processing.js" }, "dependencies": { "axios": "^1.6.2", @@ -24,15 +29,19 @@ "uuid": "^13.0.0" }, "devDependencies": { + "@types/hast": "^3.0.4", "@types/node": "^25.0.1", + "@types/unist": "^3.0.3", "@vitest/coverage-istanbul": "^1.0.0", "@vitest/coverage-v8": "^1.0.0", "@vitest/ui": "^1.0.0", "dotenv": "^16.3.1", "eslint": "^8.54.0", "nock": "^13.4.0", + "typedoc": "^0.28.14", + "typedoc-plugin-markdown": "^4.9.0", "typescript": "^5.3.2", - "vitest": "^1.0.0" + "vitest": "^1.6.1" }, "keywords": [ "base44", diff --git a/scripts/mintlify-post-processing/appended-articles.json b/scripts/mintlify-post-processing/appended-articles.json new file mode 100644 index 0000000..100bcc4 --- /dev/null +++ b/scripts/mintlify-post-processing/appended-articles.json @@ -0,0 +1,4 @@ +{ + "interfaces/EntitiesModule": "interfaces/EntityHandler", + "type-aliases/integrations": "interfaces/CoreIntegrations" +} diff --git a/scripts/mintlify-post-processing/category-map.json b/scripts/mintlify-post-processing/category-map.json new file mode 100644 index 0000000..bab74cd --- /dev/null +++ b/scripts/mintlify-post-processing/category-map.json @@ -0,0 +1,5 @@ +{ + "functions": "Client", + "interfaces": "Modules", + "type-aliases": "Modules" +} diff --git a/scripts/mintlify-post-processing/file-processing/docs-json-template.json b/scripts/mintlify-post-processing/file-processing/docs-json-template.json new file mode 100644 index 0000000..b0dc864 --- /dev/null +++ b/scripts/mintlify-post-processing/file-processing/docs-json-template.json @@ -0,0 +1,62 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "mint", + "name": "Base44 Support Documentation", + "integrations": { + "mixpanel": { + "projectToken": "cc6e9e106e4b833fc3a3819c11b74138" + } + }, + "colors": { + "primary": "#FF5500", + "light": "#EEE2C0", + "dark": "#FF5500" + }, + "navigation": { + "tabs": [ + { + "tab": "SDK Reference", + "groups": [ + { + "group": "Main Methods", + "pages": [ + "content/functions/createClient", + "content/functions/createClientFromRequest", + "content/functions/getAccessToken", + "content/functions/saveAccessToken", + "content/functions/removeAccessToken", + "content/functions/getLoginUrl" + ] + }, + { + "group": "Modules", + "pages": ["content/interfaces/Auth"] + } + ] + } + ] + }, + "navbar": { + "links": [ + { + "label": "Support", + "href": "https://app.base44.com/support/conversations" + } + ], + "primary": { + "type": "button", + "label": "Base44", + "href": "https://base44.com/?utm_source=Mintlify&utm_medium=Main&utm_content=menu" + } + }, + "footer": { + "socials": { + "twitter": "https://x.com/base_44", + "discord": "https://discord.com/invite/ThpYPZpVts", + "linkedin": "https://www.linkedin.com/company/base44" + } + }, + "custom": { + "stylesheets": ["/styling.css"] + } +} diff --git a/scripts/mintlify-post-processing/file-processing/file-processing.js b/scripts/mintlify-post-processing/file-processing/file-processing.js new file mode 100755 index 0000000..b14b347 --- /dev/null +++ b/scripts/mintlify-post-processing/file-processing/file-processing.js @@ -0,0 +1,814 @@ +#!/usr/bin/env node + +/** + * Post-processing script for TypeDoc-generated MDX files + * + * TypeDoc now emits .mdx files directly, so this script: + * 1. Processes links to make them Mintlify-compatible + * 2. Removes files for linked types that should be suppressed + * 3. Cleans up the temporary linked types tracking file + * 4. Generates docs.json with navigation structure + * 5. Copies styling.css to docs directory + */ + +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const DOCS_DIR = path.join(__dirname, "..", "..", "..", "docs"); +const CONTENT_DIR = path.join(DOCS_DIR, "content"); +const LINKED_TYPES_FILE = path.join(CONTENT_DIR, ".linked-types.json"); +const TEMPLATE_PATH = path.join(__dirname, "docs-json-template.json"); +const STYLING_CSS_PATH = path.join(__dirname, "styling.css"); +const CATEGORY_MAP_PATH = path.join(__dirname, "../category-map.json"); +const TYPES_TO_EXPOSE_PATH = path.join(__dirname, "..", "types-to-expose.json"); +const APPENDED_ARTICLES_PATH = path.join( + __dirname, + "../appended-articles.json" +); + +// Controlled via env var so we can re-enable Panel injection when needed. +const PANELS_ENABLED = process.env.MINTLIFY_INCLUDE_PANELS === "true"; + +const MODULE_RENAMES = { + AgentsModule: "agents", + AppLogsModule: "app-logs", + AuthModule: "auth", + ConnectorsModule: "connectors", + EntitiesModule: "entities", + FunctionsModule: "functions", + IntegrationsModule: "integrations", + SsoModule: "sso", +}; + +const REVERSE_MODULE_RENAMES = Object.entries(MODULE_RENAMES).reduce( + (acc, [k, v]) => { + acc[v] = k; + return acc; + }, + {} +); + +/** + * Get list of linked type names that should be suppressed + */ +function getLinkedTypeNames() { + try { + if (fs.existsSync(LINKED_TYPES_FILE)) { + const content = fs.readFileSync(LINKED_TYPES_FILE, "utf-8"); + return new Set(JSON.parse(content)); + } + } catch (e) { + // If file doesn't exist or can't be read, return empty set + } + return new Set(); +} + +/** + * Load allow-listed type names that should remain in the docs output + */ +function getTypesToExpose() { + try { + const content = fs.readFileSync(TYPES_TO_EXPOSE_PATH, "utf-8"); + const parsed = JSON.parse(content); + if (!Array.isArray(parsed)) { + throw new Error("types-to-expose.json must be an array of strings"); + } + return new Set(parsed); + } catch (e) { + console.error( + `Error: Unable to read types-to-expose file: ${TYPES_TO_EXPOSE_PATH}` + ); + console.error(e.message); + process.exit(1); + } +} + +/** + * Process links in a file to make them Mintlify-compatible + */ +function processLinksInFile(filePath) { + let content = fs.readFileSync(filePath, "utf-8"); + let modified = false; + + // Remove undesirable lines like "> **IntegrationsModule** = `object` & `object`" + // This typically appears in type alias files using intersection types + const typeDefinitionRegex = /^> \*\*\w+\*\* = `object` & `object`\s*$/m; + if (typeDefinitionRegex.test(content)) { + content = content.replace(typeDefinitionRegex, ""); + modified = true; + } + + // Manually add Indexable section if missing for IntegrationsModule + if ( + filePath.includes("integrations.mdx") && + !content.includes("## Indexable") + ) { + const indexableSection = ` +## Indexable + +\\[\`packageName\`: \`string\`\\]: [\`IntegrationPackage\`](IntegrationPackage) + +Access to additional integration packages. + +### Example + + + +\`\`\`typescript Access additional packages +// Access a custom integration package +base44.integrations.MyCustomPackage.MyFunction({ param: 'value' }); +\`\`\` + + +`; + // Append it before the "Type Declaration" or "Core" section if possible, or just at the end before methods if any + // Finding a good insertion point + const typeDeclarationIndex = content.indexOf("## Type Declaration"); + if (typeDeclarationIndex !== -1) { + content = + content.slice(0, typeDeclarationIndex) + + indexableSection + + "\n" + + content.slice(typeDeclarationIndex); + modified = true; + } else { + // If no Type Declaration, maybe append after the main description? + // Look for the first horizontal rule or similar + const firstHR = content.indexOf("***", 10); // skip first few chars + if (firstHR !== -1) { + content = + content.slice(0, firstHR) + + indexableSection + + "\n" + + content.slice(firstHR); + modified = true; + } + } + } + + // Remove .md and .mdx extensions from markdown links + // This handles both relative and absolute paths + // Regex breakdown: + // \[([^\]]+)\] : Match [LinkText] + // \( : Match opening ( + // ([^)]+) : Match path (Group 2) + // (\.mdx?)? : Optionally match .md or .mdx extension (Group 3), making it optional to catch links that might have already lost extension or never had it if inconsistent + // \) : Match closing ) + const linkRegex = /\[([^\]]+)\]\(([^)]+?)(\.mdx?)?\)/g; + let newContent = content.replace( + linkRegex, + (match, linkText, linkPath, ext) => { + modified = true; + + // Check if the link points to a renamed module + const pathParts = linkPath.split("/"); + const filename = pathParts[pathParts.length - 1]; + + // If filename has extension, strip it for checking map + const nameWithoutExt = filename.replace(/\.mdx?$/, ""); + + if (MODULE_RENAMES[nameWithoutExt]) { + pathParts[pathParts.length - 1] = MODULE_RENAMES[nameWithoutExt]; + linkPath = pathParts.join("/"); + } + + // Handle relative links that might be missing context (basic cleanup) + // e.g. if linkPath is just "entities" but it should be relative + + return `[${linkText}](${linkPath})`; + } + ); + + // Also check for links that might have already been processed (no extension) + // or if the above regex missed them (though it matches .mdx?) + // The regex requires .md or .mdx extension. If links are already extensionless, this won't run. + // But TypeDoc usually outputs links with extensions. + + if (modified) { + fs.writeFileSync(filePath, newContent, "utf-8"); + return true; + } + + return false; +} + +/** + * Renames module files and updates their titles + */ +function performModuleRenames(dir) { + if (!fs.existsSync(dir)) return; + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + performModuleRenames(entryPath); + } else if ( + entry.isFile() && + (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) + ) { + const nameWithoutExt = path.basename( + entry.name, + path.extname(entry.name) + ); + + // Check if it's a renamed file or one that needs renaming + // e.g. "EntitiesModule" needs renaming. "entities" might need title update. + + let targetName = nameWithoutExt; + let needsRename = false; + + if (MODULE_RENAMES[nameWithoutExt]) { + targetName = MODULE_RENAMES[nameWithoutExt]; + needsRename = true; + } else if (REVERSE_MODULE_RENAMES[nameWithoutExt]) { + // It's already renamed (e.g. "entities"), but we should ensure title is correct + targetName = nameWithoutExt; + } + + if (needsRename || REVERSE_MODULE_RENAMES[targetName]) { + const newPath = path.join(dir, `${targetName}.mdx`); // Always use .mdx + + let content = fs.readFileSync(entryPath, "utf-8"); + + // Update title in frontmatter + const titleRegex = /^title:\s*["']?([^"'\n]+)["']?/m; + if (titleRegex.test(content)) { + // Force the title to be the target name + content = content.replace(titleRegex, `title: "${targetName}"`); + } + + // Write to new path (if renaming) or overwrite (if just updating title) + fs.writeFileSync(newPath, content, "utf-8"); + + // Delete old file if name is different + if (entryPath !== newPath) { + fs.unlinkSync(entryPath); + console.log(`Renamed module: ${entry.name} -> ${targetName}.mdx`); + } else { + // If we just updated the title in place + // console.log(`Updated title for: ${targetName}`); + } + } + } + } +} + +/** + * Scan docs content directory and build navigation structure + */ +function scanDocsContent() { + const result = { + functions: [], + interfaces: [], + classes: [], + typeAliases: [], + }; + + const sections = ["functions", "interfaces", "classes", "type-aliases"]; + + for (const section of sections) { + const sectionDir = path.join(CONTENT_DIR, section); + if (!fs.existsSync(sectionDir)) continue; + + const files = fs.readdirSync(sectionDir); + const mdxFiles = files + .filter((file) => file.endsWith(".mdx")) + .map((file) => path.basename(file, ".mdx")) + .sort() + .map((fileName) => `content/${section}/${fileName}`); + + const key = section === "type-aliases" ? "typeAliases" : section; + result[key] = mdxFiles; + } + + return result; +} + +/** + * Get group name for a section, using category map or default + */ +function getGroupName(section, categoryMap) { + if (categoryMap[section]) { + return categoryMap[section]; + } + + return section; +} + +/** + * Generate docs.json from template and scanned content + */ +function generateDocsJson(docsContent) { + const template = JSON.parse(fs.readFileSync(TEMPLATE_PATH, "utf-8")); + let categoryMap = {}; + try { + categoryMap = JSON.parse(fs.readFileSync(CATEGORY_MAP_PATH, "utf-8")); + } catch (e) { + // If file doesn't exist or can't be read, return empty object + console.error(`Error: Category map file not found: ${CATEGORY_MAP_PATH}`); + } + + const groups = []; + + if (docsContent.functions.length > 0 && categoryMap.functions) { + groups.push({ + group: getGroupName("functions", categoryMap), + pages: docsContent.functions, + }); + } + + if (docsContent.interfaces.length > 0 && categoryMap.interfaces) { + groups.push({ + group: getGroupName("interfaces", categoryMap), + pages: docsContent.interfaces, + }); + } + + if (docsContent.classes.length > 0 && categoryMap.classes) { + groups.push({ + group: getGroupName("classes", categoryMap), + pages: docsContent.classes, + }); + } + + if (docsContent.typeAliases.length > 0 && categoryMap["type-aliases"]) { + // Merge into existing group if name matches + const groupName = getGroupName("type-aliases", categoryMap); + // "type-aliases" key in categoryMap is "Modules", so groupName is "Modules". + const existingGroup = groups.find((g) => g.group === groupName); + + if (existingGroup) { + existingGroup.pages.push(...docsContent.typeAliases); + existingGroup.pages.sort(); // Sort combined pages alphabetically + } else { + groups.push({ + group: groupName, + pages: docsContent.typeAliases, + }); + } + } + + // Find or create SDK Reference tab + let sdkTab = template.navigation.tabs.find( + (tab) => tab.tab === "SDK Reference" + ); + if (!sdkTab) { + sdkTab = { tab: "SDK Reference", groups: [] }; + template.navigation.tabs.push(sdkTab); + } + + sdkTab.groups = groups; + + const docsJsonPath = path.join(DOCS_DIR, "docs.json"); + fs.writeFileSync( + docsJsonPath, + JSON.stringify(template, null, 2) + "\n", + "utf-8" + ); + console.log(`Generated docs.json`); +} + +/** + * Copy styling.css to docs directory + */ +function copyStylingCss() { + const targetPath = path.join(DOCS_DIR, "styling.css"); + fs.copyFileSync(STYLING_CSS_PATH, targetPath); + console.log(`Copied styling.css`); +} + +/** + * Recursively process all MDX files + */ +function isTypeDocPath(relativePath) { + const normalized = relativePath.split(path.sep).join("/"); + return ( + normalized.startsWith("content/interfaces/") || + normalized.startsWith("content/type-aliases/") || + normalized.startsWith("content/classes/") || + // Also check root level for when fallback processing happens + normalized.startsWith("interfaces/") || + normalized.startsWith("type-aliases/") || + normalized.startsWith("classes/") + ); +} + +/** + * Recursively process all MDX files + */ +function processAllFiles(dir, linkedTypeNames, exposedTypeNames) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + processAllFiles(entryPath, linkedTypeNames, exposedTypeNames); + } else if ( + entry.isFile() && + (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) + ) { + // Extract the type name from the file path + // e.g., "docs/interfaces/LoginViaEmailPasswordResponse.mdx" -> "LoginViaEmailPasswordResponse" + const fileName = path.basename(entryPath, path.extname(entryPath)); + const relativePath = path.relative(DOCS_DIR, entryPath); + const isTypeDoc = isTypeDocPath(relativePath); + + // Check if exposed. Handle renamed modules by checking reverse map. + // Use both the raw filename and any potential original name + const originalName = REVERSE_MODULE_RENAMES[fileName] || fileName; + + // If it's a renamed module (e.g. "entities"), treat it as exposed if "EntitiesModule" is exposed + const isRenamedModule = !!REVERSE_MODULE_RENAMES[fileName]; + + const isExposedType = + !isTypeDoc || + exposedTypeNames.has(originalName) || + exposedTypeNames.has(fileName) || + isRenamedModule; + + // Remove any type doc files that are not explicitly exposed + if (isTypeDoc && !isExposedType) { + fs.unlinkSync(entryPath); + console.log(`Removed (not exposed): ${relativePath}`); + continue; + } + + // Remove suppressed linked type files (legacy behavior) as long as they aren't exposed + if (linkedTypeNames.has(fileName) && !exposedTypeNames.has(fileName)) { + fs.unlinkSync(entryPath); + console.log(`Removed (suppressed): ${relativePath}`); + } else { + // Process links in the file + if (processLinksInFile(entryPath)) { + console.log(`Processed links: ${relativePath}`); + } + } + } + } +} + +function loadAppendedArticlesConfig() { + try { + const content = fs.readFileSync(APPENDED_ARTICLES_PATH, "utf-8"); + const parsed = JSON.parse(content); + if (parsed && typeof parsed === "object") { + const normalized = {}; + for (const [host, value] of Object.entries(parsed)) { + if (Array.isArray(value)) { + normalized[host] = value; + } else if (typeof value === "string" && value.trim()) { + normalized[host] = [value]; + } + } + return normalized; + } + } catch (e) { + // Missing or invalid config is not fatal; simply skip appends + } + return {}; +} + +function stripFrontMatter(content) { + if (!content.startsWith("---")) { + return { title: null, content: content.trimStart() }; + } + const endIndex = content.indexOf("\n---", 3); + if (endIndex === -1) { + return { title: null, content: content.trimStart() }; + } + const frontMatter = content.slice(0, endIndex + 4); + const rest = content.slice(endIndex + 4).trimStart(); + const titleMatch = frontMatter.match(/title:\s*["']?([^"'\n]+)["']?/i); + return { + title: titleMatch ? titleMatch[1].trim() : null, + content: rest, + }; +} + +function removeFirstPanelBlock(content) { + const panelStart = content.indexOf(""); + const panelEnd = content.indexOf(""); + if (panelStart === -1 || panelEnd === -1 || panelEnd < panelStart) { + return content; + } + const before = content.slice(0, panelStart); + const after = content.slice(panelEnd + "".length); + return (before + after).trimStart(); +} + +function normalizeHeadings(content) { + const lines = content.split("\n"); + const headingRegex = /^(#{1,6})\s+(.*)$/; + let minLevel = Infinity; + for (const line of lines) { + const match = line.match(headingRegex); + if (match) { + minLevel = Math.min(minLevel, match[1].length); + } + } + if (minLevel === Infinity) { + return { content: content.trim(), headings: [] }; + } + const baseLevel = 2; // clamp appended content so its top-level headings render as H2 + const headings = []; + const adjusted = lines.map((line) => { + const match = line.match(headingRegex); + if (!match) { + return line; + } + const originalLevel = match[1].length; + let newLevel = originalLevel - minLevel + baseLevel; + newLevel = Math.max(baseLevel, Math.min(6, newLevel)); + const text = match[2].trim(); + headings.push({ text, level: newLevel }); + return `${"#".repeat(newLevel)} ${text}`; + }); + return { content: adjusted.join("\n").trim(), headings }; +} + +function slugifyHeading(text) { + return text + .toLowerCase() + .replace(/[`~!@#$%^&*()+={}\[\]|\\:;"'<>,.?]/g, "") + .replace(/\s+/g, "-"); +} + +function ensurePanelSpacing(content) { + const panelRegex = /([\s\S]*?)(\n*)(<\/Panel>)/; + return content.replace(panelRegex, (match, body, newlineSection, closing) => { + const trimmedBody = body.replace(/\s+$/, ""); + return `${trimmedBody}\n\n${closing}`; + }); +} + +function updatePanelWithHeadings(hostContent, headings) { + if (!PANELS_ENABLED) { + return hostContent; + } + if (!headings || headings.length === 0) { + return ensurePanelSpacing(hostContent); + } + const panelStart = hostContent.indexOf(""); + if (panelStart === -1) { + return ensurePanelSpacing(hostContent); + } + const panelEnd = hostContent.indexOf("", panelStart); + if (panelEnd === -1) { + return ensurePanelSpacing(hostContent); + } + const beforePanel = hostContent.slice(0, panelStart); + const panelBlock = hostContent.slice(panelStart, panelEnd); + const afterPanel = hostContent.slice(panelEnd); + + const panelLines = panelBlock.split("\n"); + const existingSlugs = new Set(); + const slugMatchRegex = /- \[[^\]]+\]\(#([^)]+)\)/; + for (const line of panelLines) { + const match = line.match(slugMatchRegex); + if (match) { + existingSlugs.add(match[1]); + } + } + + const newEntries = []; + for (const heading of headings) { + const text = heading.text.trim(); + if (!text) continue; + const slug = slugifyHeading(text); + if (existingSlugs.has(slug)) { + continue; + } + existingSlugs.add(slug); + newEntries.push(`- [${text}](#${slug})`); + } + + if (newEntries.length === 0) { + return ensurePanelSpacing(hostContent); + } + + const insertion = + (panelBlock.endsWith("\n") ? "" : "\n") + newEntries.join("\n"); + const updatedPanelBlock = panelBlock + insertion; + const updatedContent = beforePanel + updatedPanelBlock + afterPanel; + return ensurePanelSpacing(updatedContent); +} + +function prepareAppendedSection(appendPath) { + const rawContent = fs.readFileSync(appendPath, "utf-8"); + const { title, content: withoutFrontMatter } = stripFrontMatter(rawContent); + const withoutPanel = removeFirstPanelBlock(withoutFrontMatter); + const { content: normalizedContent, headings } = + normalizeHeadings(withoutPanel); + const fileTitle = + title || path.basename(appendPath, path.extname(appendPath)); + const sectionHeading = `## ${fileTitle.trim()}`; + const trimmedContent = normalizedContent ? `\n\n${normalizedContent}` : ""; + const section = `${sectionHeading}${trimmedContent}\n`; + const headingList = [{ text: fileTitle.trim(), level: 2 }, ...headings]; + return { section, headings: headingList }; +} + +function applyAppendedArticles(appendedArticles) { + const hosts = Object.keys(appendedArticles); + if (hosts.length === 0) { + return; + } + + for (const hostKey of hosts) { + const appendList = appendedArticles[hostKey]; + if (!Array.isArray(appendList) || appendList.length === 0) { + continue; + } + + // Check if host was renamed + let effectiveHostKey = hostKey; + const pathParts = hostKey.split("/"); + const hostName = pathParts[pathParts.length - 1]; + if (MODULE_RENAMES[hostName]) { + pathParts[pathParts.length - 1] = MODULE_RENAMES[hostName]; + effectiveHostKey = pathParts.join("/"); + } + + const hostPath = path.join(CONTENT_DIR, `${effectiveHostKey}.mdx`); + if (!fs.existsSync(hostPath)) { + // Try checking if it exists as .md just in case, though we standardized on .mdx + console.warn( + `Warning: Host article not found for append: ${hostKey} (checked ${effectiveHostKey}.mdx)` + ); + continue; + } + + let hostContent = fs.readFileSync(hostPath, "utf-8"); + let combinedSections = ""; + const collectedHeadings = PANELS_ENABLED ? [] : null; + + for (const appendKey of appendList) { + // Check if appended file was renamed (unlikely for EntityHandler but good for consistency) + let effectiveAppendKey = appendKey; + const appendParts = appendKey.split("/"); + const appendName = appendParts[appendParts.length - 1]; + if (MODULE_RENAMES[appendName]) { + appendParts[appendParts.length - 1] = MODULE_RENAMES[appendName]; + effectiveAppendKey = appendParts.join("/"); + } + + // Try looking in CONTENT_DIR with .mdx (default) + let appendPath = path.join(CONTENT_DIR, `${effectiveAppendKey}.mdx`); + let foundExtension = ".mdx"; + + if (!fs.existsSync(appendPath)) { + // Try .md in CONTENT_DIR + appendPath = path.join(CONTENT_DIR, `${effectiveAppendKey}.md`); + foundExtension = ".md"; + + if (!fs.existsSync(appendPath)) { + // Try looking in DOCS_DIR directly (for un-moved files) + // Assuming the key (e.g. interfaces/EntityHandler) is relative to DOCS_DIR too + appendPath = path.join(DOCS_DIR, `${effectiveAppendKey}.mdx`); + foundExtension = ".mdx"; + + if (!fs.existsSync(appendPath)) { + appendPath = path.join(DOCS_DIR, `${effectiveAppendKey}.md`); + foundExtension = ".md"; + + if (!fs.existsSync(appendPath)) { + console.warn( + `Warning: Appended article not found: ${appendKey} (checked content/ and docs/ roots)` + ); + continue; + } + } + } + } + + const { section, headings } = prepareAppendedSection(appendPath); + combinedSections += `\n\n${section}`; + if (PANELS_ENABLED && collectedHeadings) { + collectedHeadings.push(...headings); + } + + try { + fs.unlinkSync(appendPath); + console.log( + `Appended ${effectiveAppendKey}${foundExtension} -> ${effectiveHostKey}.mdx` + ); + } catch (e) { + console.warn( + `Warning: Unable to remove appended article ${effectiveAppendKey}${foundExtension}` + ); + } + } + + if (!combinedSections) { + continue; + } + + hostContent = hostContent.trimEnd() + combinedSections + "\n"; + hostContent = updatePanelWithHeadings(hostContent, collectedHeadings); + fs.writeFileSync(hostPath, hostContent, "utf-8"); + } +} + +function demoteNonCallableHeadings(content) { + const lines = content.split("\n"); + let inFence = false; + let modified = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + if (trimmed.startsWith("```")) { + inFence = !inFence; + continue; + } + if (inFence) { + continue; + } + if (line.startsWith("### ")) { + const headingText = line.slice(4).trim(); + if (!headingText.includes("(")) { + lines[i] = `#### ${headingText}`; + modified = true; + } + } + } + return { content: lines.join("\n"), modified }; +} + +function applyHeadingDemotion(dir) { + if (!fs.existsSync(dir)) return; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + applyHeadingDemotion(entryPath); + } else if ( + entry.isFile() && + (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) + ) { + const content = fs.readFileSync(entryPath, "utf-8"); + const { content: updated, modified } = demoteNonCallableHeadings(content); + if (modified) { + fs.writeFileSync(entryPath, updated, "utf-8"); + console.log(`Adjusted headings: ${path.relative(DOCS_DIR, entryPath)}`); + } + } + } +} + +/** + * Main function + */ +function main() { + console.log("Processing TypeDoc MDX files for Mintlify...\n"); + + if (!fs.existsSync(DOCS_DIR)) { + console.error(`Error: Documentation directory not found: ${DOCS_DIR}`); + console.error('Please run "npm run docs:generate" first.'); + process.exit(1); + } + + // Get list of linked types to suppress + const linkedTypeNames = getLinkedTypeNames(); + const exposedTypeNames = getTypesToExpose(); + + // First, perform module renames (EntitiesModule -> entities, etc.) + performModuleRenames(DOCS_DIR); + + // Process all files (remove suppressed ones and fix links) + processAllFiles(DOCS_DIR, linkedTypeNames, exposedTypeNames); + + // Append configured articles + const appendedArticles = loadAppendedArticlesConfig(); + applyAppendedArticles(appendedArticles); + + applyHeadingDemotion(DOCS_DIR); + + // Clean up the linked types file + try { + if (fs.existsSync(LINKED_TYPES_FILE)) { + fs.unlinkSync(LINKED_TYPES_FILE); + } + } catch (e) { + // Ignore errors + } + + // Scan content and generate docs.json + const docsContent = scanDocsContent(); + generateDocsJson(docsContent); + + // Copy styling.css + copyStylingCss(); + + console.log(`\n✓ Post-processing complete!`); + console.log(` Documentation directory: ${DOCS_DIR}`); +} + +main(); diff --git a/scripts/mintlify-post-processing/file-processing/styling.css b/scripts/mintlify-post-processing/file-processing/styling.css new file mode 100644 index 0000000..87bc800 --- /dev/null +++ b/scripts/mintlify-post-processing/file-processing/styling.css @@ -0,0 +1,128 @@ +@import url("https://fonts.googleapis.com/css2?family=Wix+Madefor+Display:wght@400..800&family=Wix+Madefor+Text:ital,wght@0,400..800;1,400..800&display=swap"); + +/* Apply Wix Madefor Text for body text */ +body, +.mint-container { + font-family: "Wix Madefor Text", sans-serif; + font-weight: 400; + font-style: normal; + font-optical-sizing: auto; +} + +/* Apply Wix Madefor Display for headings */ +h1, +h2, +h3 { + font-family: "Wix Madefor Display", sans-serif; + font-weight: 600; + font-style: normal; + font-optical-sizing: auto; +} + +@media (prefers-color-scheme: light) { + body, + .mint-container, + h1, + h2, + h3 { + color: #111111; + } +} + +/* Mintlify primary navbar CTA button color override */ +.mint-navbar .mint-button-primary, +.mint-navbar li#topbar-cta-button a > span.bg-primary-dark { + background-color: #ff5500 !important; /* updated brand orange */ + color: #111111 !important; + font-weight: 600; + border: none; +} + +/* If button has hover/focus/active states, ensure text stays black: */ +.mint-navbar .mint-navbarPrimaryButton:hover, +.mint-navbar .mint-navbarPrimaryButton:focus, +.mint-navbar .mint-navbarPrimaryButton:active, +.mint-navbar .mint-button-primary:hover, +.mint-navbar .mint-button-primary:focus, +.mint-navbar .mint-button-primary:active { + color: #111111 !important; +} + +/* Optional: Remove box-shadow on click, if present */ +.mint-navbar .mint-navbarPrimaryButton:active { + box-shadow: none !important; +} + +/* ---- NEW: Force the Base44 button text to black ---- */ +a[href*="base44.com"] span, +a[href*="base44.com"] .text-white, +.mint-navbar a span.text-white { + color: #111111 !important; +} +/* Force the ">" icon in the Base44 button to orange for consistency and accessibility */ +a[href*="base44.com"] svg, +a[href*="base44.com"] .text-white, +a[href*="base44.com"] svg path, +.mint-navbar a svg.text-white { + color: #111111 !important; /* Works if color is set by text utility */ + fill: #111111 !important; /* Ensures SVG/path fill is overridden */ +} +/* Restore thin chevron style for the navbar's "Base44" button arrow */ +a[href*="base44.com"] svg, +.mint-navbar a svg { + color: #111111 !important; + fill: none !important; + stroke: currentColor !important; + stroke-width: 2px !important; +} +a[href*="base44.com"] path, +.mint-navbar a svg path { + color: #111111 !important; + fill: none !important; + stroke: currentColor !important; + stroke-width: 2px !important; +} +/* Make the ">" icon (chevron) in Base44 navbar button thin and on-brand */ +a[href*="base44.com"] svg, +.mint-navbar a svg { + color: #111111 !important; +} + +a[href*="base44.com"] svg path, +.mint-navbar a svg path { + color: #111111 !important; + fill: none !important; + stroke: currentColor !important; + stroke-width: 1.5 !important; /* set to the original */ + stroke-linecap: round !important; /* keep the smooth end */ +} + +/* Optional: if you want it even thinner, try 1.2 or 1 */ + +/* Always use Mintlify's built-in theme variables! */ +div.prose.mt-1.font-normal.text-sm.leading-6.text-gray-600.dark\:text-gray-400, +div.prose.mt-1.font-normal.text-sm.leading-6.text-gray-600.dark\:text-gray-400 + span, +.card .prose, +.card .prose span, +.mint-card .prose, +.mint-card .prose span { + color: var(--mint-text-secondary) !important; +} + +/* Remove bottom border from anchor elements containing code elements */ +a:has(code) { + border-bottom: none !important; + text-decoration: none !important; + color: #ff8844 !important; /* light orange */ +} + +/* Hide nested TOC items on interface and type-alias pages */ +body:has([data-page-href*="/content/interfaces/"]) + .toc + li[data-depth]:not([data-depth="1"]), +body:has([data-page-href*="/content/type-aliases/"]) + .toc + li[data-depth]:not([data-depth="1"]) { + display: none !important; +} diff --git a/scripts/mintlify-post-processing/push-to-docs-repo.js b/scripts/mintlify-post-processing/push-to-docs-repo.js new file mode 100644 index 0000000..3eb3dc4 --- /dev/null +++ b/scripts/mintlify-post-processing/push-to-docs-repo.js @@ -0,0 +1,264 @@ +#!/usr/bin/env node + +import fs from "fs"; +import path from "path"; +import os from "os"; +import { execSync } from "child_process"; + +console.debug = () => {}; // Disable debug logging. Comment this out to enable debug logging. + +const DOCS_SOURCE_PATH = path.join(import.meta.dirname, "../../docs/content"); +const TARGET_DOCS_REPO_URL = "git@github.com:base44-dev/mintlify-docs.git"; +const CATEGORY_MAP_PATH = path.join(import.meta.dirname, "./category-map.json"); + +function parseArgs() { + const args = process.argv.slice(2); + let branch = null; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === "--branch" && i + 1 < args.length) { + branch = args[++i]; + } + } + return { branch }; +} + +function scanSdkDocs(sdkDocsDir) { + const result = {}; + + // Get a list of all the subdirectories in the sdkDocsDir + const subdirectories = fs + .readdirSync(sdkDocsDir) + .filter((file) => fs.statSync(path.join(sdkDocsDir, file)).isDirectory()); + console.log(`Subdirectories: ${subdirectories}`); + + for (const subdirectory of subdirectories) { + const subdirectoryPath = path.join(sdkDocsDir, subdirectory); + const files = fs + .readdirSync(subdirectoryPath) + .filter((file) => file.endsWith(".mdx")); + result[subdirectory] = files.map((file) => path.basename(file, ".mdx")); + } + return result; +} + +function updateDocsJson(repoDir, sdkFiles) { + const docsJsonPath = path.join(repoDir, "docs.json"); + let categoryMap = {}; + try { + categoryMap = JSON.parse(fs.readFileSync(CATEGORY_MAP_PATH, "utf8")); + } catch (e) { + console.error(`Error: Category map file not found: ${CATEGORY_MAP_PATH}`); + process.exit(1); + } + + console.log(`Reading docs.json from ${docsJsonPath}...`); + const docsContent = fs.readFileSync(docsJsonPath, "utf8"); + const docs = JSON.parse(docsContent); + + // Find the "SDK Reference" tab + const sdkTabIndex = docs.navigation.tabs.findIndex( + (tab) => tab.tab === "SDK Reference" + ); + let sdkTab = docs.navigation.tabs[sdkTabIndex]; + + if (sdkTabIndex === -1) { + console.log( + "Could not find 'SDK Reference' tab in docs.json. Creating it..." + ); + sdkTab = { + tab: "SDK Reference", + groups: [], + }; + } + + // Update the groups - merge categories that map to the same group name + const groupMap = new Map(); // group name -> pages array + + const addToGroup = (groupName, pages) => { + if (!groupName || pages.length === 0) return; + if (!groupMap.has(groupName)) { + groupMap.set(groupName, []); + } + groupMap.get(groupName).push(...pages); + }; + + if (sdkFiles.functions?.length > 0 && categoryMap.functions) { + addToGroup( + categoryMap.functions, + sdkFiles.functions.map((file) => `sdk-docs/functions/${file}`) + ); + } + + if (sdkFiles.interfaces?.length > 0 && categoryMap.interfaces) { + addToGroup( + categoryMap.interfaces, + sdkFiles.interfaces.map((file) => `sdk-docs/interfaces/${file}`) + ); + } + + if (sdkFiles.classes?.length > 0 && categoryMap.classes) { + addToGroup( + categoryMap.classes, + sdkFiles.classes.map((file) => `sdk-docs/classes/${file}`) + ); + } + + if (sdkFiles["type-aliases"]?.length > 0 && categoryMap["type-aliases"]) { + addToGroup( + categoryMap["type-aliases"], + sdkFiles["type-aliases"].map((file) => `sdk-docs/type-aliases/${file}`) + ); + } + + // Convert map to array of groups + const newGroups = Array.from(groupMap.entries()).map( + ([groupName, pages]) => ({ + group: groupName, + pages: pages.sort(), // Sort pages alphabetically within each group + }) + ); + + const newGroupNames = new Set(newGroups.map((group) => group.group)); + const preservedGroups = []; + let insertionIndex; + + for (const existingGroup of sdkTab.groups ?? []) { + if (newGroupNames.has(existingGroup.group)) { + if (insertionIndex === undefined) { + insertionIndex = preservedGroups.length; + } + continue; + } + preservedGroups.push(existingGroup); + } + + const finalGroups = [...preservedGroups]; + const targetIndex = insertionIndex ?? finalGroups.length; + finalGroups.splice(targetIndex, 0, ...newGroups); + sdkTab.groups = finalGroups; + + if (sdkTabIndex === -1) { + docs.navigation.tabs.push(sdkTab); + } else { + docs.navigation.tabs[sdkTabIndex] = sdkTab; + } + + console.debug( + `New groups for docs.json: ${JSON.stringify(newGroups, null, 2)}` + ); + + // Write updated docs.json + console.log(`Writing updated docs.json to ${docsJsonPath}...`); + fs.writeFileSync(docsJsonPath, JSON.stringify(docs, null, 2) + "\n", "utf8"); + + console.log("Successfully updated docs.json"); +} + +function main() { + const { branch } = parseArgs(); + if (!branch) { + console.error("Error: --branch is required"); + process.exit(1); + } + + if (!/^[a-zA-Z0-9\-_\/]+$/.test(branch)) { + console.error( + "Error: Invalid branch name. Branch name must contain only letters, numbers, hyphens, underscores, and forward slashes." + ); + process.exit(1); + } + + console.log(`Branch: ${branch}`); + + if ( + !fs.existsSync(DOCS_SOURCE_PATH) || + !fs.statSync(DOCS_SOURCE_PATH).isDirectory() + ) { + console.error(`Error: docs directory does not exist: ${DOCS_SOURCE_PATH}`); + process.exit(1); + } + + let tempRepoDir; + try { + // Create temporary directory + tempRepoDir = fs.mkdtempSync(path.join(os.tmpdir(), "mintlify-docs-")); + // Clone the repository + console.log(`Cloning repository to ${tempRepoDir}...`); + execSync(`git clone ${TARGET_DOCS_REPO_URL} ${tempRepoDir}`); + + // Check if the specified branch already exists remotely + const branchExists = + execSync(`git ls-remote --heads origin ${branch}`, { + cwd: tempRepoDir, + encoding: "utf8", + }).trim().length > 0; + + if (branchExists) { + console.log(`Branch ${branch} already exists. Checking it out...`); + execSync(`git checkout -b ${branch} origin/${branch}`, { + cwd: tempRepoDir, + }); + } else { + console.log(`Branch ${branch} does not exist. Creating it...`); + execSync(`git checkout -b ${branch}`, { cwd: tempRepoDir }); + } + + // Remove the existing sdk-docs directory + fs.rmSync(path.join(tempRepoDir, "sdk-docs"), { + recursive: true, + force: true, + }); + + // Copy the docs directory to the temporary repository + fs.cpSync(DOCS_SOURCE_PATH, path.join(tempRepoDir, "sdk-docs"), { + recursive: true, + }); + + // Scan the sdk-docs directory + const sdkDocsDir = path.join(tempRepoDir, "sdk-docs"); + const sdkFiles = scanSdkDocs(sdkDocsDir); + + console.debug(`SDK files: ${JSON.stringify(sdkFiles, null, 2)}`); + + // Update the docs.json file + updateDocsJson(tempRepoDir, sdkFiles); + + // Commit the changes + execSync(`git add docs.json`, { cwd: tempRepoDir }); + execSync(`git add sdk-docs`, { cwd: tempRepoDir }); + + const stagedOutput = execSync(`git diff --cached --name-only`, { + cwd: tempRepoDir, + encoding: "utf8", + }); + + const stagedChanges = stagedOutput.trim(); + + if (!stagedChanges.length) { + console.log( + "No staged changes detected (docs.json / sdk-docs). Skipping commit and push." + ); + return; + } + + console.log(`Changes staged for commit:\n${stagedChanges}`); + + execSync(`git commit -m "Auto-updates to SDK Reference Docs"`, { + cwd: tempRepoDir, + }); + execSync(`git push --set-upstream origin ${branch}`, { cwd: tempRepoDir }); + + console.log("Successfully committed and pushed the changes"); + } catch (e) { + console.error(`Error: Failed to commit and push changes: ${e}`); + process.exit(1); + } finally { + // Remove the temporary directory + fs.rmSync(tempRepoDir, { recursive: true, force: true }); + } +} + +main(); diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-content.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-content.js new file mode 100644 index 0000000..6bf1e0a --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-content.js @@ -0,0 +1,124 @@ +/** + * Content transformation functions (examples, frontmatter, etc.) + */ + +/** + * Add headings to CodeGroups that don't have them + */ +export function addHeadingsToCodeGroups(content) { + const lines = content.split('\n'); + const result = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + // Check if this line contains + if (line.trim() === '' || line.includes('')) { + // Look back up to 3 lines to see if there's a heading + let hasHeading = false; + for (let j = Math.max(0, i - 3); j < i; j++) { + const prevLine = lines[j].trim(); + if (/^#{2,4}\s+/.test(prevLine)) { + hasHeading = true; + break; + } + } + + // If no heading found, add one + if (!hasHeading) { + result.push('## Examples'); + result.push(''); + } + } + + result.push(line); + } + + return result.join('\n'); +} + +/** + * Convert code examples to Mintlify CodeGroup + */ +export function convertExamplesToCodeGroup(content) { + // Match Example/Examples headings from level 2-4 and capture their content until next section + const exampleSectionRegex = /^(#{2,4})\s+(Example|Examples)\s*$([\s\S]*?)(?=^#{2,4}\s|\n<\/ResponseField>|\n\*\*\*|$(?!\n))/gm; + + return content.replace(exampleSectionRegex, (match, headingLevel, exampleHeading, exampleContent) => { + const codeBlockRegex = /```([\w-]*)\s*([^\n]*)\n([\s\S]*?)```/g; + const examples = []; + let codeMatch; + + while ((codeMatch = codeBlockRegex.exec(exampleContent)) !== null) { + const language = codeMatch[1] || 'typescript'; + const titleFromCodeFence = codeMatch[2].trim(); + const code = codeMatch[3].trimEnd(); + + let title; + if (titleFromCodeFence && titleFromCodeFence.length > 0 && titleFromCodeFence.length < 100) { + // Strip comment markers from title (e.g., "// Basic example" -> "Basic example") + title = titleFromCodeFence.replace(/^\/\/\s*/, '').replace(/^\/\*\s*|\s*\*\/$/g, '').trim(); + } else { + title = examples.length === 0 ? 'Example' : `Example ${examples.length + 1}`; + } + + examples.push({ + title, + language, + code, + }); + } + + if (examples.length === 0) { + return match; + } + + // Use the original heading level, default to ## if not specified + const headingPrefix = headingLevel || '##'; + // Use "Examples" if multiple examples, otherwise "Example" + const headingText = examples.length > 1 ? 'Examples' : 'Example'; + + let codeGroup = `${headingPrefix} ${headingText}\n\n\n\n`; + + for (const example of examples) { + codeGroup += '```' + example.language + ' ' + example.title + '\n'; + codeGroup += example.code + '\n'; + codeGroup += '```\n\n'; + } + + codeGroup += '\n'; + + return codeGroup; + }); +} + +/** + * Add Mintlify frontmatter to the page + */ +export function addMintlifyFrontmatter(content, page) { + const titleMatch = content.match(/^#\s+(.+)$/m); + let title = titleMatch ? titleMatch[1].trim() : page?.model?.name || 'Documentation'; + + // Clean up title + title = title.replace(/\*\*/g, '').replace(/`/g, '').trim(); + title = title.replace(/^(?:Interface|Class|Type|Module|Function|Variable|Constant|Enum):\s*/i, '').trim(); + + const escapeYaml = (str) => { + if (str.includes(':') || str.includes('"') || str.includes("'") || str.includes('\n')) { + return str.replace(/"/g, '\\"'); + } + return str; + }; + + const frontmatter = `--- +title: "${escapeYaml(title)}" +--- + +`; + + // Remove the original h1 title (it's now in frontmatter) + const processedContent = content.replace(/^#\s+.+\n\n?/, ''); + + return frontmatter + processedContent; +} + diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-linked-types.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-linked-types.js new file mode 100644 index 0000000..5c3958d --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-linked-types.js @@ -0,0 +1,862 @@ +/** + * Linked type extraction and property parsing functions + */ + +import * as fs from "fs"; +import * as path from "path"; +import { ReflectionKind } from "typedoc"; + +const TYPES_TO_EXPOSE_PATH = path.resolve( + process.cwd(), + "scripts/mintlify-post-processing/types-to-expose.json" +); +let exposedTypeNames = null; +try { + const raw = fs.readFileSync(TYPES_TO_EXPOSE_PATH, "utf-8"); + const parsed = JSON.parse(raw); + if (Array.isArray(parsed)) { + exposedTypeNames = new Set(parsed); + } +} catch (err) { + // Ignore; fall back to linking based on path existence + exposedTypeNames = null; +} + +const PROPERTY_KINDS = new Set([ + ReflectionKind.Property, + ReflectionKind.PropertySignature, +]); + +const PRIMITIVE_REFERENCES = new Set([ + "any", + "string", + "number", + "boolean", + "void", + "null", + "undefined", + "object", + "Array", + "Promise", + "Record", + "Map", + "Set", + "Date", +]); + +const KIND_DIRECTORY_MAP = { + [ReflectionKind.Class]: "classes", + [ReflectionKind.Interface]: "interfaces", + [ReflectionKind.TypeAlias]: "type-aliases", +}; + +function resolveTypePath(typeName, context, targetKind = null) { + if (!context?.app || !typeName) { + return null; + } + + if (exposedTypeNames && !exposedTypeNames.has(typeName)) { + return null; + } + + const { app, currentPagePath } = context; + const outputDir = app.options.getValue("out") || "docs"; + + const directory = KIND_DIRECTORY_MAP[targetKind] || "interfaces"; + const filePath = path.join(outputDir, directory, `${typeName}.mdx`); + + if (currentPagePath) { + const currentDir = path.dirname(path.join(outputDir, currentPagePath)); + const relativePath = path + .relative(currentDir, filePath) + .replace(/\\/g, "/"); + return relativePath.startsWith(".") ? relativePath : `./${relativePath}`; + } + + return path.relative(outputDir, filePath).replace(/\\/g, "/"); +} + +/** + * Extract properties from a linked type using TypeDoc's reflection API + * Returns { properties: [], indexSignature: null } or just properties array for backward compatibility + */ +export function extractPropertiesFromLinkedType( + linkedTypeInfo, + context, + visited = new Set(), + options = {} +) { + const emptyResult = options.includeIndexSignature + ? { properties: [], indexSignature: null } + : []; + + if (!linkedTypeInfo || !context) { + return emptyResult; + } + + const { typeName } = linkedTypeInfo; + const visitKey = typeName; + + if (!typeName || visited.has(visitKey)) { + return emptyResult; + } + + visited.add(visitKey); + + try { + // First, try to get the type from TypeDoc's reflection API + const { + properties: reflectionProps, + description: reflectionDescription, + indexSignature, + } = extractPropertiesFromReflection(typeName, context, visited); + if (reflectionProps.length > 0 || indexSignature) { + if (reflectionDescription && linkedTypeInfo) { + linkedTypeInfo.description = reflectionDescription; + } + if (options.includeIndexSignature) { + return { properties: reflectionProps, indexSignature }; + } + return reflectionProps; + } + + // Fallback: try to read from generated markdown file + const { + properties: markdownProps, + description: markdownDescription, + indexSignature: mdIndexSig, + } = extractPropertiesFromMarkdownFile(linkedTypeInfo, context); + if (markdownDescription && linkedTypeInfo) { + linkedTypeInfo.description = markdownDescription; + } + if (options.includeIndexSignature) { + return { properties: markdownProps, indexSignature: mdIndexSig || null }; + } + return markdownProps; + } catch (error) { + console.warn( + `Error extracting properties for type ${typeName}:`, + error.message + ); + return emptyResult; + } finally { + visited.delete(visitKey); + } +} + +export function getLinkedTypeDescription(linkedTypeInfo, context) { + if (!linkedTypeInfo || !context) { + return ""; + } + if (linkedTypeInfo.description) { + return linkedTypeInfo.description; + } + + const { typeName } = linkedTypeInfo; + if (!typeName) { + return ""; + } + + try { + const project = + context.page?.model?.project || context.app?.converter?.project; + if (project) { + const reflection = findReflectionByName(project, typeName); + if (reflection) { + const description = getCommentSummary(reflection, context); + if (description) { + linkedTypeInfo.description = description; + return description; + } + } + } + } catch { + // ignore reflection lookup issues + } + + try { + const { description } = extractPropertiesFromMarkdownFile( + linkedTypeInfo, + context + ); + if (description) { + linkedTypeInfo.description = description; + return description; + } + } catch { + // ignore markdown fallback issues + } + + return ""; +} + +/** + * Extract properties from TypeDoc's reflection API (preferred method) + */ +function extractPropertiesFromReflection(typeName, context, visited) { + if (!context) { + return { properties: [], description: "", indexSignature: null }; + } + + const { app, page } = context; + + try { + // Access the project through the page's model + const project = page?.model?.project || app?.converter?.project; + if (!project) { + return { properties: [], description: "", indexSignature: null }; + } + + // Find the type reflection in the project + const typeReflection = findReflectionByName(project, typeName); + if (!typeReflection) { + return { properties: [], description: "", indexSignature: null }; + } + + // Extract properties from the reflection + const properties = []; + const propertyNodes = getPropertyNodesFromReflection(typeReflection); + + for (const child of propertyNodes) { + const property = buildPropertyFromReflection(child, context, visited); + if (property) { + properties.push(property); + } + } + + // Extract index signature if present + const indexSignature = extractIndexSignature(typeReflection, context); + + const description = getCommentSummary(typeReflection, context); + return { properties, description, indexSignature }; + } catch (error) { + console.warn( + `Error extracting properties from reflection for ${typeName}:`, + error.message + ); + return { properties: [], description: "", indexSignature: null }; + } +} + +/** + * Extract index signature from a reflection (e.g., [key: string]: any) + */ +function extractIndexSignature(reflection, context) { + if (!reflection) { + return null; + } + + // Check for indexSignatures array on the reflection + const indexSigs = reflection.indexSignatures || reflection.indexSignature; + if (!indexSigs) { + return null; + } + + const sigArray = Array.isArray(indexSigs) ? indexSigs : [indexSigs]; + if (sigArray.length === 0) { + return null; + } + + // Get the first index signature + const sig = sigArray[0]; + if (!sig) { + return null; + } + + // Extract key type (usually string) + let keyType = "string"; + if (sig.parameters && sig.parameters.length > 0) { + const keyParam = sig.parameters[0]; + keyType = getTypeString(keyParam.type) || "string"; + } + + // Extract value type + const valueType = getTypeString(sig.type) || "any"; + + // Extract description from comment + const description = getCommentSummary(sig, context) || ""; + + return { + keyType, + valueType, + description, + }; +} + +/** + * Find a reflection by name in the project + */ +function findReflectionByName(reflection, name) { + if (reflection.name === name) { + return reflection; + } + + if (reflection.children) { + for (const child of reflection.children) { + const found = findReflectionByName(child, name); + if (found) return found; + } + } + + return null; +} + +/** + * Get a string representation of a type + */ +function getTypeString(type) { + if (!type) return "any"; + + switch (type.type) { + case "intrinsic": + return type.name; + case "reference": + return formatReferenceType(type); + case "array": + return `${getTypeString(type.elementType)}[]`; + case "union": + return type.types?.map((t) => getTypeString(t)).join(" | ") || "any"; + case "intersection": + return type.types?.map((t) => getTypeString(t)).join(" & ") || "any"; + case "literal": + return JSON.stringify(type.value); + case "reflection": { + // Check if this is a function type (has call signatures) + const decl = type.declaration; + if (decl?.signatures?.length > 0) { + const sig = decl.signatures[0]; + const params = + sig.parameters + ?.map((p) => `${p.name}: ${getTypeString(p.type)}`) + .join(", ") || ""; + const returnType = getTypeString(sig.type) || "void"; + return `(${params}) => ${returnType}`; + } + // Otherwise it's an object type + return "object"; + } + default: + return type.name || "any"; + } +} + +/** + * Check if a property is optional + */ +function isOptional(child) { + return child.flags?.isOptional || false; +} + +/** + * Check if a type is object-like (has properties) + */ +function isObjectLikeType(type) { + return type?.type === "reflection" && type.declaration?.children; +} + +/** + * Extract nested properties from an object type + */ +function extractNestedPropertiesFromReflectionType(type, context, visited) { + if (!isObjectLikeType(type)) { + return []; + } + + const nested = []; + if (type.declaration?.children) { + for (const child of type.declaration.children) { + const property = buildPropertyFromReflection(child, context, visited); + if (property) { + nested.push(property); + } + } + } + + return nested; +} + +/** + * Fallback: Extract properties from a linked type's markdown file + */ +function extractPropertiesFromMarkdownFile(linkedTypeInfo, context) { + const { typePath, typeName } = linkedTypeInfo; + const { currentPagePath, app } = context; + + if (!app || !app.options) { + return { properties: [], description: "" }; + } + + try { + // Get the output directory from TypeDoc (usually 'docs') + const outputDir = app.options.getValue("out") || "docs"; + + // Convert relative link to file path + // Links can be: + // - Just the type name: "LoginViaEmailPasswordResponse" + // - Relative path: "../interfaces/LoginViaEmailPasswordResponse" or "./interfaces/LoginViaEmailPasswordResponse" + // - Absolute-looking: "interfaces/LoginViaEmailPasswordResponse" + let filePath; + + // Remove .md or .mdx extension if present + let cleanTypePath = typePath.replace(/\.(md|mdx)$/, ""); + + if (cleanTypePath.startsWith("../") || cleanTypePath.startsWith("./")) { + // Relative path - resolve from current page's directory + const currentDir = path.dirname( + path.join(outputDir, currentPagePath || "") + ); + const basePath = path.resolve(currentDir, cleanTypePath); + + // Try .mdx first, then .md + if (!basePath.endsWith(".md") && !basePath.endsWith(".mdx")) { + const mdxPath = basePath + ".mdx"; + const mdPath = basePath + ".md"; + filePath = fs.existsSync(mdxPath) ? mdxPath : mdPath; + } else { + filePath = basePath; + } + } else if (cleanTypePath.includes("/")) { + // Path with directory separator + filePath = path.join(outputDir, cleanTypePath); + + // Try .mdx first, then .md + if (!filePath.endsWith(".md") && !filePath.endsWith(".mdx")) { + const mdxPath = filePath + ".mdx"; + const mdPath = filePath + ".md"; + filePath = fs.existsSync(mdxPath) ? mdxPath : mdPath; + } + } else { + // Just the type name - try interfaces/ first, then type-aliases/ + // Try .mdx first, then .md + filePath = path.join(outputDir, "interfaces", cleanTypePath + ".mdx"); + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, "interfaces", cleanTypePath + ".md"); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, "type-aliases", cleanTypePath + ".mdx"); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, "type-aliases", cleanTypePath + ".md"); + } + } + + // Normalize the path + filePath = path.normalize(filePath); + + // Check if file exists + if (!fs.existsSync(filePath)) { + // Don't warn during generation - the file might not exist yet + return { properties: [], description: "" }; + } + + const content = fs.readFileSync(filePath, "utf-8"); + return parsePropertiesFromTypeFile(content); + } catch (error) { + // Silent failure during generation + return { properties: [], description: "" }; + } +} + +/** + * Parse properties from a type file's markdown content + */ +function parsePropertiesFromTypeFile(content) { + const properties = []; + const lines = content.split("\n"); + + // Collect intro description until Properties section + const introLines = []; + let descriptionCaptured = false; + + // Extract Indexable section if present + const indexSignature = parseIndexableSection(content); + + // Find the Properties section + let inPropertiesSection = false; + let i = 0; + + while (i < lines.length) { + const line = lines[i]; + + // Start of Properties section + if (line.match(/^##\s+Properties\s*$/)) { + inPropertiesSection = true; + i++; + continue; + } + + if (!inPropertiesSection) { + if (line.trim()) { + introLines.push(line); + descriptionCaptured = true; + } else if (descriptionCaptured) { + introLines.push(""); + } + } + + // Stop at next top-level heading (##) + if ( + inPropertiesSection && + line.match(/^##\s+/) && + !line.match(/^##\s+Properties\s*$/) + ) { + break; + } + + // Parse property: ### propertyName or ### propertyName? + if (inPropertiesSection && line.match(/^###\s+/)) { + const propMatch = line.match(/^###\s+(.+)$/); + if (propMatch) { + const rawName = propMatch[1].trim(); + const optional = rawName.endsWith("?"); + // Unescape markdown escapes (e.g., access\_token -> access_token) + let name = optional ? rawName.slice(0, -1).trim() : rawName.trim(); + name = name + .replace(/\\_/g, "_") + .replace(/\\\*/g, "*") + .replace(/\\`/g, "`"); + + i++; + // Skip blank lines + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + // Get type from next line: > **name**: `type` or > `optional` **name**: `type` + let type = "any"; + if (i < lines.length && lines[i].includes("`")) { + const typeMatch = lines[i].match(/`([^`]+)`/); + if (typeMatch) { + type = typeMatch[1].trim(); + } + i++; + } + + // Skip blank lines + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + // Collect description and nested properties + const descriptionLines = []; + const nested = []; + + // Look for nested properties (#### heading) + while (i < lines.length) { + const nextLine = lines[i]; + // Stop at next property (###) or section end (## or ***) + if ( + nextLine.match(/^###\s+/) || + nextLine.match(/^##\s+/) || + nextLine === "***" + ) { + break; + } + + // Check for nested property (####) + if (nextLine.match(/^####\s+/)) { + const nestedMatch = nextLine.match(/^####\s+(.+)$/); + if (nestedMatch) { + const nestedRawName = nestedMatch[1].trim(); + const nestedOptional = nestedRawName.endsWith("?"); + // Unescape markdown escapes + let nestedName = nestedOptional + ? nestedRawName.slice(0, -1).trim() + : nestedRawName.trim(); + nestedName = nestedName + .replace(/\\_/g, "_") + .replace(/\\\*/g, "*") + .replace(/\\`/g, "`"); + + i++; + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + let nestedType = "any"; + if (i < lines.length && lines[i].includes("`")) { + const nestedTypeMatch = lines[i].match(/`([^`]+)`/); + if (nestedTypeMatch) { + nestedType = nestedTypeMatch[1].trim(); + } + i++; + } + + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + const nestedDescLines = []; + while ( + i < lines.length && + !lines[i].match(/^####\s+/) && + !lines[i].match(/^###\s+/) && + !lines[i].match(/^##\s+/) && + lines[i] !== "***" + ) { + nestedDescLines.push(lines[i]); + i++; + } + + nested.push({ + name: nestedName, + type: nestedType, + description: nestedDescLines.join("\n").trim(), + optional: nestedOptional, + }); + continue; + } + } + + descriptionLines.push(nextLine); + i++; + } + + properties.push({ + name, + type, + description: descriptionLines.join("\n").trim(), + optional, + nested, + }); + continue; + } + } + + i++; + } + + const description = introLines.join("\n").trim(); + return { properties, description, indexSignature }; +} + +/** + * Parse the Indexable section from markdown content + * TypeDoc generates: ## Indexable\n\n\\[`key`: `string`\\]: `valueType`\n\nDescription + */ +function parseIndexableSection(content) { + const indexableMatch = content.match( + /##\s+Indexable\s*\n+([^\n]+)\n*([\s\S]*?)(?=\n##|\n\*\*\*|$)/i + ); + if (!indexableMatch) { + return null; + } + + // Parse the signature line: \[`key`: `string`\]: `valueType` or [`key`: `string`]: `valueType` + const signatureLine = indexableMatch[1].trim(); + const description = (indexableMatch[2] || "").trim(); + + // Extract key type and value type from the signature + // Pattern: \[`keyName`: `keyType`\]: `valueType` or similar + const sigMatch = signatureLine.match( + /\[`?(\w+)`?\s*:\s*`?(\w+)`?\s*\]\s*:\s*`?([^`\n]+)`?/ + ); + if (!sigMatch) { + // Try simpler pattern + const simpleMatch = signatureLine.match(/`(\w+)`/g); + if (simpleMatch && simpleMatch.length >= 2) { + return { + keyType: simpleMatch[0].replace(/`/g, ""), + valueType: simpleMatch[simpleMatch.length - 1].replace(/`/g, ""), + description, + }; + } + return null; + } + + return { + keyType: sigMatch[2] || "string", + valueType: sigMatch[3] || "any", + description, + }; +} + +function buildPropertyFromReflection(child, context, visited) { + if (!child || !PROPERTY_KINDS.has(child.kind)) { + return null; + } + + const property = { + name: child.name, + type: getTypeString(child.type), + description: getCommentSummary(child, context), + optional: isOptional(child), + nested: [], + }; + + const nestedFromType = extractNestedPropertiesFromType( + child.type, + context, + visited + ); + if (nestedFromType.length > 0) { + property.nested = nestedFromType; + } + + return property; +} + +function getPropertyNodesFromReflection(reflection) { + if (!reflection) { + return []; + } + + if (Array.isArray(reflection.children) && reflection.children.length > 0) { + return reflection.children; + } + + if (reflection.type?.declaration?.children?.length) { + return reflection.type.declaration.children; + } + + if (reflection.declaration?.children?.length) { + return reflection.declaration.children; + } + + return []; +} + +function extractNestedPropertiesFromType(type, context, visited) { + if (!type) { + return []; + } + + switch (type.type) { + case "reference": { + const referencedName = getReferenceTypeName(type); + if (!referencedName || PRIMITIVE_REFERENCES.has(referencedName)) { + return []; + } + return extractPropertiesFromLinkedType( + { typeName: referencedName, typePath: referencedName }, + context, + visited + ); + } + case "array": + return extractNestedPropertiesFromType( + type.elementType, + context, + visited + ); + case "union": + case "intersection": { + if (!Array.isArray(type.types)) { + return []; + } + for (const subType of type.types) { + const nested = extractNestedPropertiesFromType( + subType, + context, + visited + ); + if (nested.length > 0) { + return nested; + } + } + return []; + } + case "reflection": + return extractNestedPropertiesFromReflectionType(type, context, visited); + default: + return []; + } +} + +function getReferenceTypeName(type) { + if (!type) { + return null; + } + + if (typeof type.name === "string" && type.name) { + return type.name; + } + + if (typeof type.qualifiedName === "string" && type.qualifiedName) { + const segments = type.qualifiedName.split("."); + return segments[segments.length - 1]; + } + + if (type.reflection?.name) { + return type.reflection.name; + } + + return null; +} + +function getCommentSummary(reflection, context) { + if (!reflection?.comment) { + return ""; + } + + const parts = []; + if (Array.isArray(reflection.comment.summary)) { + parts.push(...reflection.comment.summary); + } + if (reflection.comment.blockTags) { + for (const tag of reflection.comment.blockTags) { + if (tag.tag === "@remarks" && Array.isArray(tag.content)) { + parts.push(...tag.content); + } + if ( + (tag.tag === "@see" || + tag.tag === "@link" || + tag.tag === "@linkcode" || + tag.tag === "@returns") && + Array.isArray(tag.content) + ) { + parts.push(...tag.content); + } + } + } + + if (parts.length === 0) { + return ""; + } + + return parts.map((part) => renderCommentPart(part, context)).join("") || ""; +} + +function renderCommentPart(part, context) { + if (!part) { + return ""; + } + + switch (part.kind) { + case "text": + return part.text || ""; + case "code": + return part.text ? `\`${part.text}\`` : ""; + case "inline-tag": + if (part.tag === "@link") { + const linkText = (part.text || part.target?.name || "").trim(); + const typeName = part.target?.name || null; + const linkTarget = typeName + ? resolveTypePath(typeName, context, part.target?.kind) + : null; + if (linkTarget && linkText) { + return `[${linkText}](${linkTarget})`; + } + if (linkText) { + return linkText; + } + return typeName || ""; + } + return part.text || ""; + default: + return part.text || ""; + } +} + +function formatReferenceType(type) { + if (!type) { + return "any"; + } + + let typeName = type.name || "any"; + if (type.typeArguments && type.typeArguments.length > 0) { + const args = type.typeArguments.map((arg) => getTypeString(arg)).join(", "); + typeName += `<${args}>`; + } + return typeName; +} diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-parameters.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-parameters.js new file mode 100644 index 0000000..a5388be --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-parameters.js @@ -0,0 +1,1008 @@ +/** + * Parameter conversion functions for TypeDoc Mintlify plugin + */ + +import { escapeAttribute } from "./typedoc-mintlify-utils.js"; +import { extractPropertiesFromLinkedType } from "./typedoc-mintlify-linked-types.js"; +import * as fs from "fs"; +import * as path from "path"; + +const PRIMITIVE_TYPES = [ + "any", + "string", + "number", + "boolean", + "void", + "null", + "undefined", + "object", + "Array", + "Promise", +]; +const WRAPPER_TYPE_NAMES = new Set([ + "Partial", + "Required", + "Readonly", + "Omit", + "Pick", +]); + +// Helper function to resolve type paths (similar to returns file) +function resolveTypePath(typeName, app, currentPagePath = null) { + // Skip primitive types + if (PRIMITIVE_TYPES.includes(typeName)) { + return null; + } + + if (!app || !app.options) { + return null; + } + + const outputDir = app.options.getValue("out") || "docs"; + + // Try interfaces/ first, then type-aliases/ + let filePath = path.join(outputDir, "interfaces", typeName + ".mdx"); + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, "interfaces", typeName + ".md"); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, "type-aliases", typeName + ".mdx"); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, "type-aliases", typeName + ".md"); + } + + if (fs.existsSync(filePath)) { + // Convert to relative path from current page if possible + if (currentPagePath) { + const currentDir = path.dirname(path.join(outputDir, currentPagePath)); + const relativePath = path + .relative(currentDir, filePath) + .replace(/\\/g, "/"); + return relativePath.startsWith(".") ? relativePath : "./" + relativePath; + } + // Otherwise return path relative to outputDir + return path.relative(outputDir, filePath).replace(/\\/g, "/"); + } + + return null; +} + +/** + * Convert top-level function parameters (## Parameters with ### param names) + */ +export function convertFunctionParameters( + content, + app = null, + page = null, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + // Split content by ## headings to isolate the Parameters section + const sections = content.split(/\n(?=##\s+\w)/); + + return sections + .map((section) => { + // Only process ## Parameters sections (must start with exactly ##, not ###) + if (!section.match(/^##\s+Parameters\s*$/m)) { + return section; + } + + // Extract the content after "## Parameters" + const lines = section.split("\n"); + const paramStartIdx = lines.findIndex((l) => + l.match(/^##\s+Parameters\s*$/) + ); + + if (paramStartIdx === -1) return section; + + // Get everything after "## Parameters" line + const paramLines = lines.slice(paramStartIdx + 1); + const paramContent = paramLines.join("\n"); + + // Parse parameters with context for linked type resolution + const context = + app && page ? { app, page, currentPagePath: page.url } : null; + const params = parseParametersWithExpansion( + paramContent, + "###", + "####", + context, + linkedTypeNames, + writeLinkedTypesFile + ); + + if (params.length === 0) return section; + + // Rebuild section with ParamFields + const beforeParams = lines.slice(0, paramStartIdx + 1).join("\n"); + return ( + beforeParams + + "\n\n" + + buildParamFieldsSection(params, linkedTypeNames, writeLinkedTypesFile) + ); + }) + .join("\n"); +} + +/** + * Convert interface method parameters (#### Parameters with ##### param names) + */ +export function convertInterfaceMethodParameters( + content, + app = null, + page = null, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + const context = app && page ? { app, page, currentPagePath: page.url } : null; + return rewriteParameterSections( + content, + "#### Parameters", + "#####", + "######", + context, + linkedTypeNames, + writeLinkedTypesFile + ); +} + +/** + * Convert class method parameters (#### Parameters with ##### param names) + */ +export function convertClassMethodParameters( + content, + app = null, + page = null, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + const context = app && page ? { app, page, currentPagePath: page.url } : null; + return rewriteParameterSections( + content, + "#### Parameters", + "#####", + "######", + context, + linkedTypeNames, + writeLinkedTypesFile + ); +} + +function rewriteParameterSections( + content, + sectionHeading, + paramLevel, + nestedLevel, + context = null, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + const lines = content.split("\n"); + const result = []; + let i = 0; + + const isTerminatorLine = (line) => { + return ( + line.startsWith("#### Returns") || + line.startsWith("#### Example") || + line === "***" || + line.startsWith("### ") || + line.startsWith("## ") + ); + }; + + while (i < lines.length) { + const line = lines[i]; + if (line.startsWith(sectionHeading)) { + result.push(line); + i++; + const sectionStart = i; + while (i < lines.length && !isTerminatorLine(lines[i])) { + i++; + } + const sectionContentLines = lines.slice(sectionStart, i); + const sectionContent = sectionContentLines.join("\n").trim(); + // Use parseParametersWithExpansion if context is available, otherwise use parseParameters + const params = context + ? parseParametersWithExpansion( + sectionContent, + paramLevel, + nestedLevel, + context, + linkedTypeNames, + writeLinkedTypesFile + ) + : parseParameters( + sectionContent, + paramLevel, + nestedLevel, + context, + linkedTypeNames, + writeLinkedTypesFile + ); + if (params.length > 0) { + const block = buildParamFieldsSection( + params, + linkedTypeNames, + writeLinkedTypesFile + ).trim(); + if (block) { + result.push(""); + result.push(...block.split("\n")); + result.push(""); + } + } else { + result.push(...sectionContentLines); + } + continue; + } + + result.push(line); + i++; + } + + return result.join("\n"); +} + +/** + * Parse parameters with type expansion (for functions) + */ +function parseParametersWithExpansion( + paramContent, + paramLevel, + nestedLevel, + context = null, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + const lines = paramContent.split("\n"); + const params = []; + + const isParamHeading = (line) => line.startsWith(paramLevel + " "); + const isNestedHeading = nestedLevel + ? (line) => line.startsWith(nestedLevel + " ") + : () => false; + const isTerminator = (line) => { + const trimmed = line.trim(); + if (!trimmed) return false; + if ( + trimmed.startsWith("#### Returns") || + trimmed.startsWith("#### Example") || + trimmed === "***" + ) { + return true; + } + const nestedPrefix = nestedLevel ? nestedLevel + " " : null; + if (/^#{1,3}\s+/.test(trimmed)) { + if ( + !trimmed.startsWith(paramLevel + " ") && + !(nestedPrefix && trimmed.startsWith(nestedPrefix)) + ) { + return true; + } + } + return false; + }; + + const extractType = (line) => { + if (!line) return null; + const trimmed = line.trim(); + + // Handle [`TypeName`](link) format first (backticks inside the link) + const linkWithBackticksMatch = trimmed.match(/^\[`([^`]+)`\]\(([^)]+)\)$/); + if (linkWithBackticksMatch) { + return { + type: linkWithBackticksMatch[1], + link: linkWithBackticksMatch[2], + }; + } + + // Handle [TypeName](link) format + const linkMatch = trimmed.match(/^\[([^\]]+)\]\(([^)]+)\)$/); + if (linkMatch) { + return { type: linkMatch[1], link: linkMatch[2] }; + } + + // Handle simple `TypeName` format + const simpleMatch = trimmed.match(/^`([^`]+)`$/); + if (simpleMatch) { + return { type: simpleMatch[1], link: null }; + } + + // Handle function type format: (`param`) => `returnType` or (`param`: `Type`) => `returnType` + // e.g., (`conversation`) => `void` or (`error`: `Error`) => `void` + if (trimmed.startsWith("(") && trimmed.includes("=>")) { + const sanitized = trimmed + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1") // Remove markdown links + .replace(/`/g, "") // Remove backticks + .replace(/\\/g, "") // Remove escapes + .replace(/\s+/g, " ") // Normalize whitespace + .trim(); + if (sanitized) { + return { type: sanitized, link: null }; + } + } + + // Fallback: sanitize markdown-heavy type definitions such as `Partial`<[`Type`](link)> + if (trimmed.startsWith("`")) { + const sanitized = trimmed + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1") + .replace(/`/g, "") + .replace(/\\/g, "") + .replace(/\s+/g, " ") + .trim(); + if (sanitized) { + return { type: sanitized, link: null }; + } + } + + return null; + }; + + let i = 0; + while (i < lines.length) { + const line = lines[i]; + if (!isParamHeading(line)) { + i++; + continue; + } + + let rawName = line.slice(paramLevel.length).trim(); + const optional = rawName.endsWith("?"); + const cleanName = optional ? rawName.slice(0, -1).trim() : rawName.trim(); + i++; + + // Skip blank lines + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + let type = "any"; + let typeLink = null; + if (i < lines.length) { + const maybeType = extractType(lines[i]); + if (maybeType) { + if (typeof maybeType === "object") { + type = maybeType.type; + typeLink = maybeType.link; + } else { + type = maybeType; + } + i++; + } + } + + // Skip blank lines after type + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + // Check if the next line has an array indicator (...[]) + if (i < lines.length && lines[i].trim() === "...[]") { + // If type is still 'any' (default), it means no type was specified + // TypeDoc-Markdown sometimes omits the type line for arrays + // Default to 'string' as the base type in this case + if (type === "any") { + type = "string[]"; + } else { + type = type + "[]"; + } + i++; // Skip the array indicator line + + // Skip blank lines after array indicator + while (i < lines.length && lines[i].trim() === "") { + i++; + } + } + + const descriptionLines = []; + while ( + i < lines.length && + !isParamHeading(lines[i]) && + !isNestedHeading(lines[i]) && + !isTerminator(lines[i]) + ) { + descriptionLines.push(lines[i]); + i++; + } + + // Check if we should expand this type inline + let linkedTypeInfo = getLinkedTypeInfo(type, typeLink, context); + let nested = []; + + // Track linked types for suppression (for types with explicit links) + + // Try to extract properties from the linked type + if (linkedTypeInfo && context) { + const properties = extractPropertiesFromLinkedType( + linkedTypeInfo, + context + ); + if (properties.length > 0) { + nested = normalizePropertiesForParams(properties); + } + } + + // If no linked properties were found, check for manually specified nested fields + if (nested.length === 0) { + while (i < lines.length && isNestedHeading(lines[i])) { + let nestedRawName = lines[i].slice(nestedLevel.length).trim(); + const nestedOptional = nestedRawName.endsWith("?"); + const nestedName = nestedOptional + ? nestedRawName.slice(0, -1).trim() + : nestedRawName.trim(); + i++; + + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + let nestedType = "any"; + let nestedTypeLink = null; + if (i < lines.length) { + const maybeNestedType = extractType(lines[i]); + if (maybeNestedType) { + if (typeof maybeNestedType === "object") { + nestedType = maybeNestedType.type; + nestedTypeLink = maybeNestedType.link; + } else { + nestedType = maybeNestedType; + } + i++; + } + } + + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + // Check if the next line has an array indicator (...[]) + if (i < lines.length && lines[i].trim() === "...[]") { + // If type is still 'any' (default), it means no type was specified + // TypeDoc-Markdown sometimes omits the type line for arrays + // Default to 'string' as the base type in this case + if (nestedType === "any") { + nestedType = "string[]"; + } else { + nestedType = nestedType + "[]"; + } + i++; // Skip the array indicator line + + // Skip blank lines after array indicator + while (i < lines.length && lines[i].trim() === "") { + i++; + } + } + + const nestedDescLines = []; + while ( + i < lines.length && + !isNestedHeading(lines[i]) && + !isParamHeading(lines[i]) && + !isTerminator(lines[i]) + ) { + nestedDescLines.push(lines[i]); + i++; + } + + const nestedField = { + name: nestedName, + type: nestedType, + description: nestedDescLines.join("\n").trim(), + optional: nestedOptional, + nested: [], + }; + + if (nestedField.nested.length === 0) { + const nestedLinkedInfo = getLinkedTypeInfo( + nestedType, + nestedTypeLink, + context + ); + if (nestedLinkedInfo && context) { + const nestedProps = extractPropertiesFromLinkedType( + nestedLinkedInfo, + context + ); + if (nestedProps.length > 0) { + nestedField.nested = normalizePropertiesForParams(nestedProps); + } + } + } + + nested.push(nestedField); + } + } + + params.push({ + name: cleanName, + type: type, + description: descriptionLines.join("\n").trim(), + optional, + nested, + }); + } + + return params; +} + +/** + * Parse parameters from markdown content (for interface/class methods - no expansion) + */ +function parseParameters( + paramContent, + paramLevel, + nestedLevel, + context = null, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + const lines = paramContent.split("\n"); + const params = []; + + const isParamHeading = (line) => line.startsWith(paramLevel + " "); + const isNestedHeading = nestedLevel + ? (line) => line.startsWith(nestedLevel + " ") + : () => false; + const isTerminator = (line) => { + const trimmed = line.trim(); + if (!trimmed) return false; + if ( + trimmed.startsWith("#### Returns") || + trimmed.startsWith("#### Example") || + trimmed === "***" + ) { + return true; + } + const nestedPrefix = nestedLevel ? nestedLevel + " " : null; + if (/^#{1,3}\s+/.test(trimmed)) { + if ( + !trimmed.startsWith(paramLevel + " ") && + !(nestedPrefix && trimmed.startsWith(nestedPrefix)) + ) { + return true; + } + } + return false; + }; + + const extractType = (line) => { + if (!line) return null; + const trimmed = line.trim(); + + // Handle [`TypeName`](link) format first (backticks inside the link) + const linkWithBackticksMatch = trimmed.match(/^\[`([^`]+)`\]\(([^)]+)\)$/); + if (linkWithBackticksMatch) { + return { + type: linkWithBackticksMatch[1], + link: linkWithBackticksMatch[2], + }; + } + + // Handle [TypeName](link) format + const linkMatch = trimmed.match(/^\[([^\]]+)\]\(([^)]+)\)$/); + if (linkMatch) { + return { type: linkMatch[1], link: linkMatch[2] }; + } + + // Handle simple `TypeName` format + const simpleMatch = trimmed.match(/^`([^`]+)`$/); + if (simpleMatch) { + return { type: simpleMatch[1], link: null }; + } + + // Handle function type format: (`param`) => `returnType` or (`param`: `Type`) => `returnType` + // e.g., (`conversation`) => `void` or (`error`: `Error`) => `void` + if (trimmed.startsWith("(") && trimmed.includes("=>")) { + const sanitized = trimmed + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1") // Remove markdown links + .replace(/`/g, "") // Remove backticks + .replace(/\\/g, "") // Remove escapes + .replace(/\s+/g, " ") // Normalize whitespace + .trim(); + if (sanitized) { + return { type: sanitized, link: null }; + } + } + + // Fallback: sanitize markdown-heavy type definitions such as `Partial`<[`Type`](link)> + if (trimmed.startsWith("`")) { + const sanitized = trimmed + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1") + .replace(/`/g, "") + .replace(/\\/g, "") + .replace(/\s+/g, " ") + .trim(); + if (sanitized) { + return { type: sanitized, link: null }; + } + } + + return null; + }; + + let i = 0; + while (i < lines.length) { + const line = lines[i]; + if (!isParamHeading(line)) { + i++; + continue; + } + + let rawName = line.slice(paramLevel.length).trim(); + const optional = rawName.endsWith("?"); + const cleanName = optional ? rawName.slice(0, -1).trim() : rawName.trim(); + i++; + + // Skip blank lines + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + let type = "any"; + let typeLink = null; + if (i < lines.length) { + const maybeType = extractType(lines[i]); + if (maybeType) { + if (typeof maybeType === "object") { + type = maybeType.type; + typeLink = maybeType.link; + } else { + type = maybeType; + } + i++; + } + } + + // Skip blank lines after type + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + // Check if the next line has an array indicator (...[]) + if (i < lines.length && lines[i].trim() === "...[]") { + // If type is still 'any' (default), it means no type was specified + // TypeDoc-Markdown sometimes omits the type line for arrays + // Default to 'string' as the base type in this case + if (type === "any") { + type = "string[]"; + } else { + type = type + "[]"; + } + i++; // Skip the array indicator line + + // Skip blank lines after array indicator + while (i < lines.length && lines[i].trim() === "") { + i++; + } + } + + const descriptionLines = []; + while ( + i < lines.length && + !isParamHeading(lines[i]) && + !isNestedHeading(lines[i]) && + !isTerminator(lines[i]) + ) { + descriptionLines.push(lines[i]); + i++; + } + + // Check if we should expand this type inline + const linkedTypeInfo = getLinkedTypeInfo(type, typeLink, context); + let nested = []; + + // Try to extract properties from the linked type + if (linkedTypeInfo && context) { + const properties = extractPropertiesFromLinkedType( + linkedTypeInfo, + context + ); + if (properties.length > 0) { + nested = normalizePropertiesForParams(properties); + // Keep the type as the original type name (without expanding to 'object') + // This preserves the type name in the ParamField + } + } + + // If no linked properties were found, check for manually specified nested fields + if (nested.length === 0) { + while (i < lines.length && isNestedHeading(lines[i])) { + let nestedRawName = lines[i].slice(nestedLevel.length).trim(); + const nestedOptional = nestedRawName.endsWith("?"); + const nestedName = nestedOptional + ? nestedRawName.slice(0, -1).trim() + : nestedRawName.trim(); + i++; + + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + let nestedType = "any"; + let nestedTypeLink = null; + if (i < lines.length) { + const maybeNestedType = extractType(lines[i]); + if (maybeNestedType) { + if (typeof maybeNestedType === "object") { + nestedType = maybeNestedType.type; + nestedTypeLink = maybeNestedType.link; + } else { + nestedType = maybeNestedType; + } + i++; + } + } + + while (i < lines.length && lines[i].trim() === "") { + i++; + } + + // Check if the next line has an array indicator (...[]) + if (i < lines.length && lines[i].trim() === "...[]") { + // If type is still 'any' (default), it means no type was specified + // TypeDoc-Markdown sometimes omits the type line for arrays + // Default to 'string' as the base type in this case + if (nestedType === "any") { + nestedType = "string[]"; + } else { + nestedType = nestedType + "[]"; + } + i++; // Skip the array indicator line + + // Skip blank lines after array indicator + while (i < lines.length && lines[i].trim() === "") { + i++; + } + } + + const nestedDescLines = []; + while ( + i < lines.length && + !isNestedHeading(lines[i]) && + !isParamHeading(lines[i]) && + !isTerminator(lines[i]) + ) { + nestedDescLines.push(lines[i]); + i++; + } + + const nestedField = { + name: nestedName, + type: nestedType, + description: nestedDescLines.join("\n").trim(), + optional: nestedOptional, + nested: [], + }; + + if (nestedField.nested.length === 0) { + const nestedLinkedInfo = getLinkedTypeInfo( + nestedType, + nestedTypeLink, + context + ); + if (nestedLinkedInfo && context) { + const nestedProps = extractPropertiesFromLinkedType( + nestedLinkedInfo, + context + ); + if (nestedProps.length > 0) { + nestedField.nested = normalizePropertiesForParams(nestedProps); + } + } + } + + nested.push(nestedField); + } + } + + params.push({ + name: cleanName, + type: nested.length > 0 ? type : type, // Keep original type name + description: descriptionLines.join("\n").trim(), + optional, + nested, + }); + } + + return params; +} + +/** + * Build ParamField components from parsed parameters + */ +function buildParamFieldsSection( + params, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + if (!params || params.length === 0) { + return ""; + } + + let fieldsOutput = ""; + + for (const param of params) { + const requiredAttr = param.optional ? "" : " required"; + + // Track non-primitive parameter types for suppression + + fieldsOutput += `\n`; + + // Always show description in ParamField if it exists + if (param.description) { + fieldsOutput += `\n${param.description}\n`; + } + + fieldsOutput += "\n\n"; + + // If param has nested fields, wrap them in an Accordion + if (param.nested.length > 0) { + // Accordion title is always "Properties" + fieldsOutput += `\n\n\n`; + + fieldsOutput += renderNestedParamFields(param.nested); + + fieldsOutput += "\n\n"; + } else { + fieldsOutput += "\n"; + } + } + + // Wrap multiple parameters in an Accordion (but not single parameters, even if they have nested fields) + const hasMultipleParams = params.length > 1; + + if (hasMultipleParams) { + return `\n\n${fieldsOutput.trim()}\n`; + } + + return fieldsOutput; +} + +function renderNestedParamFields(fields) { + if (!fields || fields.length === 0) { + return ""; + } + + let output = ""; + for (const field of fields) { + const requiredAttr = field.optional ? "" : " required"; + output += `\n`; + + if (field.description) { + output += `\n${field.description}\n`; + } + + if (Array.isArray(field.nested) && field.nested.length > 0) { + output += `\n\n\n`; + output += renderNestedParamFields(field.nested); + output += "\n"; + } + + output += "\n\n\n"; + } + + return output; +} + +function getLinkedTypeInfo(typeName, typeLink, context) { + if (!typeName) { + return null; + } + + if (typeLink) { + const simpleFromLink = simplifyTypeName(typeName) || typeName; + return { typeName: simpleFromLink, typePath: typeLink }; + } + + if (!context) { + return null; + } + + const simpleTypeName = simplifyTypeName(typeName); + if (!simpleTypeName || PRIMITIVE_TYPES.includes(simpleTypeName)) { + return null; + } + + const typePath = resolveTypePath( + simpleTypeName, + context.app, + context.currentPagePath + ); + return { typeName: simpleTypeName, typePath: typePath || simpleTypeName }; +} + +function simplifyTypeName(typeName) { + if (!typeName) { + return null; + } + + let cleaned = typeName.trim(); + + // Unwrap helper types like Partial, Required, etc. + cleaned = unwrapHelperType(cleaned); + + // Remove generics/array indicators and take the first union/intersection entry + cleaned = cleaned + .replace(/[\[\]]/g, "") + .split("|")[0] + .split("&")[0] + .trim(); + return cleaned.replace(/<.*?>/g, "").trim(); +} + +function unwrapHelperType(typeName) { + let current = typeName.trim(); + let changed = true; + + while (changed) { + changed = false; + const match = current.match(/^([A-Za-z0-9_]+)\s*<(.+)>$/); + if (!match) { + break; + } + + const wrapperName = match[1]; + if (!WRAPPER_TYPE_NAMES.has(wrapperName)) { + break; + } + + const genericBlock = match[2]; + const inner = getFirstGenericArgument(genericBlock); + if (!inner) { + break; + } + + current = inner.trim(); + changed = true; + } + + return current; +} + +function getFirstGenericArgument(genericBlock) { + if (!genericBlock) { + return ""; + } + + let depth = 0; + let buffer = ""; + for (let i = 0; i < genericBlock.length; i++) { + const char = genericBlock[i]; + if (char === "<") { + depth++; + buffer += char; + continue; + } + if (char === ">") { + if (depth > 0) { + depth--; + } + buffer += char; + continue; + } + if (char === "," && depth === 0) { + return buffer.trim(); + } + buffer += char; + } + + return buffer.trim(); +} + +function normalizePropertiesForParams(properties) { + if (!Array.isArray(properties)) { + return []; + } + + return properties.map((prop) => ({ + name: prop.name, + type: prop.type || "any", + description: prop.description || "", + optional: !!prop.optional, + nested: normalizePropertiesForParams(prop.nested || []), + })); +} diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-plugin.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-plugin.js new file mode 100644 index 0000000..966c4a3 --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-plugin.js @@ -0,0 +1,260 @@ +/** + * TypeDoc plugin for Mintlify MDX output + * Hooks into TypeDoc's markdown renderer to customize output for Mintlify + */ + +import { MarkdownPageEvent } from "typedoc-plugin-markdown"; +import { ReflectionKind, RendererEvent } from "typedoc"; +import * as fs from "fs"; +import * as path from "path"; +import { + convertFunctionParameters, + convertInterfaceMethodParameters, + convertClassMethodParameters, +} from "./typedoc-mintlify-parameters.js"; +import { + convertFunctionReturns, + convertInterfaceMethodReturns, + convertClassMethodReturns, +} from "./typedoc-mintlify-returns.js"; +import { + convertExamplesToCodeGroup, + addMintlifyFrontmatter, + addHeadingsToCodeGroups, +} from "./typedoc-mintlify-content.js"; + +// Shared flag with the post-processing script so Panel output can be toggled. +const PANELS_ENABLED = process.env.MINTLIFY_INCLUDE_PANELS === "true"; + +/** + * Plugin load function called by TypeDoc + */ +// Track interfaces that are linked types and should be suppressed +const linkedTypeNames = new Set(); + +/** + * Load function called by TypeDoc + */ +export function load(app) { + console.log("Loading Mintlify TypeDoc plugin..."); + + app.renderer.on(MarkdownPageEvent.END, (page) => { + if (!page.contents) return; + + let content = page.contents; + + // Determine what kind of page this is. + const isFunction = page.model?.kind === ReflectionKind.Function; + const isClass = page.model?.kind === ReflectionKind.Class; + const isInterface = page.model?.kind === ReflectionKind.Interface; + + // 1. Remove breadcrumbs navigation + content = content.replace(/^\[.*?\]\(.*?\)\s*\n+/m, ""); + + // 2. Convert parameters to ParamField components and returns to ResponseField components + // Functions: ## Parameters/Returns with ### field names + // Interface methods: #### Parameters/Returns with ##### field names + // Class methods: #### Parameters/Returns with ##### field names + const writeLinkedTypesFile = createWriteLinkedTypesFile(app); + + if (isFunction) { + content = convertFunctionParameters( + content, + app, + page, + linkedTypeNames, + writeLinkedTypesFile + ); + content = convertFunctionReturns( + content, + app, + page, + linkedTypeNames, + writeLinkedTypesFile + ); + } else if (isInterface) { + content = convertInterfaceMethodParameters(content, app, page); + content = convertInterfaceMethodReturns( + content, + app, + page, + linkedTypeNames, + writeLinkedTypesFile + ); + } else if (isClass) { + content = convertClassMethodParameters(content, app, page); + content = convertClassMethodReturns( + content, + app, + page, + linkedTypeNames, + writeLinkedTypesFile + ); + } + + // 3. Convert code examples to CodeGroup + content = convertExamplesToCodeGroup(content); + + // 3a. Add headings to CodeGroups that don't have them + content = addHeadingsToCodeGroups(content); + + // 4. Remove auto-generated type links from signatures (keep manual doc links) + content = stripAutoGeneratedTypeLinks(content); + + // 5. Remove .md and .mdx extensions from links + content = content.replace(/\[([^\]]+)\]\(([^)]+)\.mdx?\)/g, "[$1]($2)"); + + // 6. Add on-page navigation panel + content = addOnThisPagePanel(content, page); + + // 7. Add frontmatter + content = addMintlifyFrontmatter(content, page); + + // 8. Fix escaped characters in type signatures that shouldn't be escaped in MDX + content = fixEscapedTypeCharacters(content); + + page.contents = content; + }); + + // Write linked types file once at the end of all processing + app.renderer.on(MarkdownPageEvent.END, () => { + // This will run after all pages are processed + // We'll write the file in a different event + }); + + // Write linked types file after all pages are processed + app.renderer.on(RendererEvent.END, () => { + const writeLinkedTypesFile = createWriteLinkedTypesFile(app); + writeLinkedTypesFile(); + }); +} + +/** + * Write linked types file + */ +function createWriteLinkedTypesFile(app) { + return () => { + if (!app || !app.options) { + return; + } + const outputDir = app.options.getValue("out") || "docs"; + const resolvedOutputDir = path.resolve(outputDir); + const linkedTypesFile = path.join(resolvedOutputDir, ".linked-types.json"); + try { + // Ensure content directory exists + if (!fs.existsSync(resolvedOutputDir)) { + fs.mkdirSync(resolvedOutputDir, { recursive: true }); + } + fs.writeFileSync( + linkedTypesFile, + JSON.stringify(Array.from(linkedTypeNames)), + "utf-8" + ); + } catch (e) { + // Ignore errors writing the file + console.warn("Warning: Could not write linked types file:", e.message); + } + }; +} + +/** + * Insert a Mintlify Panel with links to method headings (###) for type docs + */ +function addOnThisPagePanel(content, page) { + if (!PANELS_ENABLED) { + return content; + } + const links = getPanelLinksForPage(page, content); + if (!links || links.length === 0) { + return content; + } + if (content.includes(' **On this page**')) { + return content; + } + + const panelLines = [ + "", + "", + ' **On this page**', + "", + ...links.map(({ heading, anchor }) => `- [${heading}](#${anchor})`), + "", + "", + "", + ]; + + const panelBlock = panelLines.join("\n"); + const firstSectionIndex = content.indexOf("\n## "); + if (firstSectionIndex === -1) { + return `${content}\n\n${panelBlock}`; + } + const insertionPoint = firstSectionIndex + 1; + return `${content.slice(0, insertionPoint)}${panelBlock}${content.slice( + insertionPoint + )}`; +} + +function getPanelLinksForPage(page, content) { + if (!page?.model || !page.url) { + return null; + } + if (isTypeDoc(page)) { + return extractHeadings(content, /^###\s+(.+?)\s*$/gm); + } + if (isFunctionDoc(page)) { + return extractHeadings(content, /^##\s+(.+?)\s*$/gm); + } + return null; +} + +function isTypeDoc(page) { + const allowedKinds = new Set([ + ReflectionKind.Interface, + ReflectionKind.Class, + ]); + return ( + allowedKinds.has(page.model.kind) && + (page.url.startsWith("interfaces/") || page.url.startsWith("classes/")) + ); +} + +function isFunctionDoc(page) { + return ( + page.model.kind === ReflectionKind.Function && + page.url.startsWith("functions/") + ); +} + +function extractHeadings(content, regex) { + const headings = []; + let match; + while ((match = regex.exec(content)) !== null) { + const heading = match[1].trim(); + if (!heading) { + continue; + } + headings.push({ heading, anchor: slugifyHeading(heading) }); + } + return headings; +} + +function slugifyHeading(text) { + return text + .toLowerCase() + .replace(/[`~!@#$%^&*()+={}\[\]|\\:;"'<>,.?]/g, "") + .replace(/\s+/g, "-"); +} + +function stripAutoGeneratedTypeLinks(content) { + return content.replace(/^>\s*\*\*.*$/gm, (line) => { + const withoutCodeLinks = line.replace(/\[`([^`]+)`\]\([^)]+\)/g, "`$1`"); + return withoutCodeLinks.replace(/\[([A-Za-z0-9_.]+)\]\(([^)]+)\)/g, "$1"); + }); +} + +function fixEscapedTypeCharacters(content) { + // Fix escaped pipes - TypeDoc escapes | as \| in the output + // This is visible in the rendered Mintlify pages + // Simply replace all instances of \| with | in the entire content + return content.replace(/\\\|/g, "|"); +} diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js new file mode 100644 index 0000000..38e1374 --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js @@ -0,0 +1,1161 @@ +/** + * Return/Response field conversion functions for TypeDoc Mintlify plugin + */ + +import * as fs from "fs"; +import * as path from "path"; +import { + extractPropertiesFromLinkedType, + getLinkedTypeDescription, +} from "./typedoc-mintlify-linked-types.js"; +import { escapeAttribute } from "./typedoc-mintlify-utils.js"; + +const PRIMITIVE_TYPES = [ + "any", + "string", + "number", + "boolean", + "void", + "null", + "undefined", + "object", + "Array", + "Promise", +]; + +function extractReturnsDescription(page) { + if (!page?.model) { + return ""; + } + + const signature = + Array.isArray(page.model.signatures) && page.model.signatures.length > 0 + ? page.model.signatures[0] + : null; + + const returnsTag = signature?.comment?.blockTags?.find( + (tag) => tag.tag === "@returns" || tag.tag === "@return" + ); + + if (!returnsTag || !returnsTag.content) { + return ""; + } + + return renderCommentParts(returnsTag.content).trim(); +} + +function renderCommentParts(parts) { + if (!Array.isArray(parts)) { + return ""; + } + + return parts + .map((part) => { + if (!part) return ""; + switch (part.kind) { + case "text": + return part.text || ""; + case "code": + return part.text ? "`" + part.text + "`" : ""; + case "inline-tag": + if (part.tag === "@link") { + return (part.text || part.target?.name || "").trim(); + } + return part.text || ""; + default: + return part.text || ""; + } + }) + .join(""); +} + +/** + * Extract signature information from content lines + */ +/** + * Try to resolve a type name to a documentation file path + */ +function resolveTypePath(typeName, app, currentPagePath = null) { + // Skip primitive types + if (PRIMITIVE_TYPES.includes(typeName)) { + return null; + } + + if (!app || !app.options) { + return null; + } + + const outputDir = app.options.getValue("out") || "docs"; + + // Try interfaces/ first, then type-aliases/ + let filePath = path.join(outputDir, "interfaces", typeName + ".mdx"); + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, "interfaces", typeName + ".md"); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, "type-aliases", typeName + ".mdx"); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, "type-aliases", typeName + ".md"); + } + + if (fs.existsSync(filePath)) { + // Convert to relative path from current page if possible + if (currentPagePath) { + const currentDir = path.dirname(path.join(outputDir, currentPagePath)); + const relativePath = path + .relative(currentDir, filePath) + .replace(/\\/g, "/"); + return relativePath.startsWith(".") ? relativePath : "./" + relativePath; + } + // Otherwise return path relative to outputDir + return path.relative(outputDir, filePath).replace(/\\/g, "/"); + } + + return null; +} + +export function extractSignatureInfo( + lines, + linkedTypeNames, + writeLinkedTypesFile, + app, + currentPagePath = null +) { + const signatureMap = new Map(); + const linkedTypeMap = new Map(); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + // Match function signature: > **methodName**(...): `returnType` or `returnType`\<`generic`\> + // Handle both simple types and generic types like `Promise`\<`any`\> or `Promise`\<[`TypeName`](link)\> + const sigMatch = line.match( + /^>\s*\*\*(\w+)\*\*\([^)]*\):\s*`([^`]+)`(?:\\<(.+?)\\>)?/ + ); + if (sigMatch) { + const methodName = sigMatch[1]; + let returnType = sigMatch[2]; + const genericParam = sigMatch[3]; + + // Check if generic parameter is a markdown link: [`TypeName`](link) + if (genericParam) { + const linkMatch = genericParam.match(/\[`([^`]+)`\]\(([^)]+)\)/); + if (linkMatch) { + const linkedTypeName = linkMatch[1]; + const linkedTypePath = linkMatch[2]; + returnType = `${returnType}<${linkedTypeName}>`; + linkedTypeMap.set(i, { + typeName: linkedTypeName, + typePath: linkedTypePath, + }); + // Track this type name so we can suppress its documentation page + if (linkedTypeNames) { + linkedTypeNames.add(linkedTypeName); + if (writeLinkedTypesFile) writeLinkedTypesFile(); + } + } else { + // Simple generic type without link - try to resolve it + const simpleGeneric = genericParam.replace(/`/g, "").trim(); + returnType = `${returnType}<${simpleGeneric}>`; + + // Try to resolve the type to a documentation file + const typePath = resolveTypePath(simpleGeneric, app, currentPagePath); + if (typePath) { + linkedTypeMap.set(i, { + typeName: simpleGeneric, + typePath: typePath, + }); + if (linkedTypeNames) { + linkedTypeNames.add(simpleGeneric); + if (writeLinkedTypesFile) writeLinkedTypesFile(); + } + } + } + } + // Store the return type with the signature line index as the key + signatureMap.set(i, returnType); + + // If we don't already have linked type info (e.g., non-generic return), + // try to resolve the return type to a documentation file + if (!linkedTypeMap.has(i)) { + const simpleTypeName = getSimpleTypeName(returnType); + if (simpleTypeName && !PRIMITIVE_TYPES.includes(simpleTypeName)) { + let typePath = resolveTypePath(simpleTypeName, app, currentPagePath); + if (!typePath) { + // Fallback to the raw type name so downstream parsing can still attempt resolution + typePath = simpleTypeName; + } + linkedTypeMap.set(i, { typeName: simpleTypeName, typePath }); + if (linkedTypeNames) { + linkedTypeNames.add(simpleTypeName); + if (writeLinkedTypesFile) writeLinkedTypesFile(); + } + } + } + } + } + + return { signatureMap, linkedTypeMap }; +} + +/** + * Convert function returns + */ +export function convertFunctionReturns( + content, + app, + page, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + // For functions, we need to extract signature info with linked types + const lines = content.split("\n"); + // Use provided linkedTypeNames Set or create a local one + const localLinkedTypeNames = linkedTypeNames || new Set(); + const localWriteLinkedTypesFile = writeLinkedTypesFile || (() => {}); + const { signatureMap, linkedTypeMap } = extractSignatureInfo( + lines, + localLinkedTypeNames, + localWriteLinkedTypesFile, + app, + page?.url + ); + + return rewriteReturnSections(content, { + heading: "## Returns", + fieldHeading: "###", + nestedHeading: "####", + stopOnLevel3: false, + signatureMap, + linkedTypeMap, + app, + page, + linkedTypeNames: localLinkedTypeNames, + writeLinkedTypesFile: localWriteLinkedTypesFile, + }); +} + +/** + * Convert interface method returns + */ +export function convertInterfaceMethodReturns( + content, + app, + page, + linkedTypeNames, + writeLinkedTypesFile +) { + const lines = content.split("\n"); + const { signatureMap, linkedTypeMap } = extractSignatureInfo( + lines, + linkedTypeNames, + writeLinkedTypesFile, + app, + page?.url + ); + + return rewriteReturnSections(content, { + heading: "#### Returns", + fieldHeading: "#####", + nestedHeading: "######", + stopOnLevel3: true, + signatureMap, + linkedTypeMap, + app, + page, + }); +} + +/** + * Convert class method returns + */ +export function convertClassMethodReturns( + content, + app, + page, + linkedTypeNames, + writeLinkedTypesFile +) { + const lines = content.split("\n"); + const { signatureMap, linkedTypeMap } = extractSignatureInfo( + lines, + linkedTypeNames, + writeLinkedTypesFile, + app, + page?.url + ); + + return rewriteReturnSections(content, { + heading: "#### Returns", + fieldHeading: "#####", + nestedHeading: "######", + stopOnLevel3: true, + signatureMap, + linkedTypeMap, + app, + page, + }); +} + +function rewriteReturnSections(content, options) { + const { + heading, + fieldHeading, + nestedHeading, + stopOnLevel3, + signatureMap = new Map(), + linkedTypeMap = new Map(), + app, + page, + linkedTypeNames = null, + writeLinkedTypesFile = null, + } = options; + const lines = content.split("\n"); + const result = []; + let i = 0; + + const isTerminatorLine = (line) => { + const trimmed = line.trim(); + if (!trimmed) return false; + if (trimmed.match(/^#{2,4}\s+Examples?/i) || trimmed === "***") { + return true; + } + if (heading !== "## Returns" && trimmed.startsWith("## ")) { + return true; + } + // For function Returns, stop at nested method definitions (#### methodName()) + if (heading === "## Returns" && trimmed.match(/^####\s+\w+\.?\w*\(\)/)) { + return true; + } + if (stopOnLevel3 && trimmed.startsWith("### ")) { + return true; + } + return false; + }; + + while (i < lines.length) { + const line = lines[i]; + if (line.startsWith(heading)) { + result.push(line); + i++; + const sectionStart = i; + while (i < lines.length && !isTerminatorLine(lines[i])) { + i++; + } + const sectionLines = lines.slice(sectionStart, i); + const sectionContent = sectionLines.join("\n").trim(); + + // For function Returns sections, parse nested fields (### headings) + if (heading === "## Returns") { + // Look backwards to find the function signature + let sigLineIdx = i - 2; // Go back past the Returns heading + while ( + sigLineIdx >= 0 && + !lines[sigLineIdx].match(/^>\s*\*\*\w+\*\*\(/) + ) { + sigLineIdx--; + } + + // If we didn't find it by pattern, try to find it in our signature map + if (sigLineIdx < 0 || !signatureMap.has(sigLineIdx)) { + // Try searching a bit further back (up to 10 lines) + for (let j = i - 2; j >= Math.max(0, i - 12); j--) { + if (signatureMap.has(j)) { + sigLineIdx = j; + break; + } + } + } + + const returnTypeFromSignature = + sigLineIdx >= 0 ? signatureMap.get(sigLineIdx) : null; + const linkedTypeInfo = + sigLineIdx >= 0 ? linkedTypeMap.get(sigLineIdx) : null; + const context = + app && page ? { app, page, currentPagePath: page.url } : null; + + // Get the type name for display - prefer linkedTypeInfo.typeName, fallback to returnTypeFromSignature + const returnTypeName = + linkedTypeInfo?.typeName || returnTypeFromSignature; + + // Track linked type if found + if (linkedTypeInfo && linkedTypeNames) { + linkedTypeNames.add(linkedTypeInfo.typeName); + if (writeLinkedTypesFile) { + writeLinkedTypesFile(); + } + } + + const { + fields, + leadingText, + extractedTypeName, + typeDescription, + indexSignature, + } = parseReturnFields( + sectionContent, + fieldHeading, + nestedHeading, + returnTypeFromSignature, + linkedTypeInfo, + context, + linkedTypeNames, + writeLinkedTypesFile + ); + if (fields.length === 0 && !indexSignature) { + result.push(...sectionLines); + } else { + const typeNameForDisplay = extractedTypeName || returnTypeName; + if (typeNameForDisplay) { + result.push(""); + result.push(`\`${typeNameForDisplay}\``); + } + const descriptionParts = []; + if (typeDescription) { + descriptionParts.push(typeDescription); + } + if (leadingText) { + descriptionParts.push(leadingText); + } + const returnsDescription = extractReturnsDescription(page); + if (returnsDescription) { + descriptionParts.push(returnsDescription); + } + if (descriptionParts.length > 0) { + result.push(""); + result.push(descriptionParts.join("\n\n")); + } + const fieldsBlock = formatReturnFieldsOutput( + fields, + null, + linkedTypeNames, + writeLinkedTypesFile, + indexSignature + ); + if (fieldsBlock) { + result.push(""); + result.push(fieldsBlock); + result.push(""); + } + } + continue; + } + + // For interface/class method Returns sections + // The Returns section starts at i-1 (after the heading line) + // Look backwards to find the function signature + let sigLineIdx = i - 2; // Go back past the Returns heading + while ( + sigLineIdx >= 0 && + !lines[sigLineIdx].match(/^>\s*\*\*\w+\*\*\(/) + ) { + sigLineIdx--; + } + + // If we didn't find it by pattern, try to find it in our signature map + // by checking a few lines before the Returns section + if (sigLineIdx < 0 || !signatureMap.has(sigLineIdx)) { + // Try searching a bit further back (up to 10 lines) + for (let j = i - 2; j >= Math.max(0, i - 12); j--) { + if (signatureMap.has(j)) { + sigLineIdx = j; + break; + } + } + } + + const returnTypeFromSignature = + sigLineIdx >= 0 ? signatureMap.get(sigLineIdx) : null; + const linkedTypeInfo = + sigLineIdx >= 0 ? linkedTypeMap.get(sigLineIdx) : null; + + // Get the type name for display - prefer linkedTypeInfo.typeName, fallback to returnTypeFromSignature + const returnTypeName = + linkedTypeInfo?.typeName || returnTypeFromSignature; + + // Track linked type if found + if (linkedTypeInfo && linkedTypeNames) { + linkedTypeNames.add(linkedTypeInfo.typeName); + if (writeLinkedTypesFile) { + writeLinkedTypesFile(); + } + } + + const { + fields, + leadingText, + extractedTypeName, + typeDescription, + indexSignature, + } = parseReturnFields( + sectionContent, + fieldHeading, + nestedHeading, + returnTypeFromSignature, + linkedTypeInfo, + { app, page, currentPagePath: page.url }, + linkedTypeNames, + writeLinkedTypesFile + ); + if (fields.length === 0 && !indexSignature) { + result.push(...sectionLines); + } else { + const typeNameForDisplay = extractedTypeName || returnTypeName; + if (typeNameForDisplay) { + result.push(""); + result.push(`\`${typeNameForDisplay}\``); + } + const descriptionParts = []; + if (typeDescription) { + descriptionParts.push(typeDescription); + } + if (leadingText) { + descriptionParts.push(leadingText); + } + if (descriptionParts.length > 0) { + result.push(""); + result.push(descriptionParts.join("\n\n")); + } + const fieldsBlock = formatReturnFieldsOutput( + fields, + null, + linkedTypeNames, + writeLinkedTypesFile, + indexSignature + ); + if (fieldsBlock) { + result.push(""); + result.push(fieldsBlock); + result.push(""); + } + } + continue; + } + + result.push(line); + i++; + } + + return result.join("\n"); +} + +function parseReturnFields( + sectionContent, + fieldHeading, + nestedHeading, + returnTypeFromSignature = null, + linkedTypeInfo = null, + context = null, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + let infoForDescription = linkedTypeInfo; + + if (!sectionContent) { + // If we have a linked type but no section content, try to extract from the linked type + if (linkedTypeInfo && context) { + const result = extractPropertiesFromLinkedType( + linkedTypeInfo, + context, + new Set(), + { includeIndexSignature: true } + ); + const properties = result.properties || result; + const indexSignature = result.indexSignature || null; + + if (properties.length > 0 || indexSignature) { + // Return separate ResponseFields for each property (skip the default "result" field) + const resultFields = []; + + // Add a separate ResponseField for each property + for (const prop of properties) { + resultFields.push({ + name: prop.name, + type: prop.type, + description: prop.description, + optional: prop.optional, + nested: prop.nested || [], + }); + } + + return { + fields: resultFields, + leadingText: "", + extractedTypeName: linkedTypeInfo.typeName, + typeDescription: + getLinkedTypeDescription(linkedTypeInfo, context) || "", + indexSignature, + }; + } + } + return { + fields: [], + leadingText: "", + extractedTypeName: null, + typeDescription: + getLinkedTypeDescription(infoForDescription, context) || "", + indexSignature: null, + }; + } + + const lines = sectionContent.split("\n"); + const fields = []; + const headingPrefix = fieldHeading ? `${fieldHeading} ` : null; + const nestedPrefix = nestedHeading ? `${nestedHeading} ` : null; + + const extractTypeFromLine = (line) => { + if (!line) return null; + const trimmed = line.trim(); + if (!trimmed) return null; + if (trimmed.startsWith(">")) { + // Handle lines like: > **entities**: `object` or > **auth**: [`AuthMethods`](../interfaces/AuthMethods) + const blockMatch = trimmed.match(/^>\s*\*\*([^*]+)\*\*:\s*(.+)$/); + if (blockMatch) { + const typePart = blockMatch[2].replace(/`/g, "").trim(); + // Check if it's a markdown link: [TypeName](link) + const linkMatch = typePart.match(/^\[([^\]]+)\]\(([^)]+)\)$/); + if (linkMatch) { + return { type: linkMatch[1], link: linkMatch[2] }; + } + return { type: typePart, link: null }; + } + } + if (trimmed.includes("`")) { + // Extract type from backticks, could be a link: [`AuthMethods`](../interfaces/AuthMethods) + const typeMatch = trimmed.match(/`([^`]+)`/); + if (typeMatch) { + const typePart = typeMatch[1].trim(); + // Check if there's a link after the backticks + const linkMatch = trimmed.match(/`[^`]+`\s*\[([^\]]+)\]\(([^)]+)\)/); + if (linkMatch) { + return { type: linkMatch[1], link: linkMatch[2] }; + } + // Check if the type itself is a link format + const inlineLinkMatch = typePart.match(/^\[([^\]]+)\]\(([^)]+)\)$/); + if (inlineLinkMatch) { + return { type: inlineLinkMatch[1], link: inlineLinkMatch[2] }; + } + return { type: typePart, link: null }; + } + } + // Check for standalone markdown links + const linkMatch = trimmed.match(/^\[([^\]]+)\]\(([^)]+)\)$/); + if (linkMatch) { + return { type: linkMatch[1], link: linkMatch[2] }; + } + return null; + }; + + const isHeadingLine = (line) => + headingPrefix && line.startsWith(headingPrefix); + const isNestedHeadingLine = (line) => + nestedPrefix && line.startsWith(nestedPrefix); + + const leadingLines = []; + let index = 0; + if (headingPrefix) { + while (index < lines.length && !isHeadingLine(lines[index])) { + if (lines[index].trim()) { + leadingLines.push(lines[index]); + } + index++; + } + } + + // If no field headings found, treat as simple return + if (!headingPrefix || index >= lines.length) { + let type = returnTypeFromSignature || "any"; + const descriptionLines = []; + + // Check if there's an existing ResponseField in the content + const responseFieldMatch = sectionContent.match( + /]*type="([^"]+)"[^>]*>/ + ); + if (responseFieldMatch) { + // Extract type from existing ResponseField + const existingType = responseFieldMatch[1]; + if (existingType && existingType !== "any") { + type = existingType; + } + } + + for (const line of lines) { + // Skip ResponseField tags - we'll replace them + if ( + line.trim().startsWith("" + ) { + continue; + } + const maybeType = extractTypeFromLine(line); + if (maybeType && type === "any") { + type = typeof maybeType === "object" ? maybeType.type : maybeType; + continue; + } + if ( + line.trim() && + !line.trim().startsWith("`") && + !line.trim().startsWith("<") + ) { + descriptionLines.push(line); + } + } + let description = descriptionLines.join("\n").trim(); + + // Check if we have a linked type to inline + let typeInfoToUse = linkedTypeInfo; + + // If we don't have linkedTypeInfo but we have a type name, try to resolve it + if (!typeInfoToUse && type && context && context.app) { + const simpleTypeName = getSimpleTypeName(type); + if (simpleTypeName && !PRIMITIVE_TYPES.includes(simpleTypeName)) { + const typePath = resolveTypePath( + simpleTypeName, + context.app, + context.currentPagePath + ); + if (typePath) { + typeInfoToUse = { typeName: simpleTypeName, typePath }; + } else if (simpleTypeName) { + // Even if we can't resolve the path, try with just the name + typeInfoToUse = { + typeName: simpleTypeName, + typePath: simpleTypeName, + }; + } + if (typeInfoToUse) { + infoForDescription = typeInfoToUse; + } + + // Track resolved linked type + if (typeInfoToUse && linkedTypeNames) { + linkedTypeNames.add(typeInfoToUse.typeName); + if (writeLinkedTypesFile) { + writeLinkedTypesFile(); + } + } + } + } + + if (typeInfoToUse && context) { + const result = extractPropertiesFromLinkedType( + typeInfoToUse, + context, + new Set(), + { includeIndexSignature: true } + ); + const properties = result.properties || result; + const indexSignature = result.indexSignature || null; + + if (properties.length > 0 || indexSignature) { + // Return separate ResponseFields for each property (skip the default "result" field) + const resultFields = []; + + // Add a separate ResponseField for each property + for (const prop of properties) { + resultFields.push({ + name: prop.name, + type: prop.type, + description: prop.description, + optional: prop.optional, + nested: prop.nested || [], + }); + } + + return { + fields: resultFields, + leadingText: "", + extractedTypeName: typeInfoToUse.typeName, // Pass the type name for display + typeDescription: + getLinkedTypeDescription(typeInfoToUse, context) || "", + indexSignature, + }; + } + } + + // Use 'result' as default name, or extract from description + let name = "result"; + if (description) { + // Check if description contains a type hint + const typeHint = description.match(/(\w+)\s+(?:instance|object|value)/i); + if (typeHint) { + name = typeHint[1].toLowerCase(); + } + } + return { + fields: [ + { + name, + type, + description, + optional: false, + nested: [], + }, + ], + leadingText: "", + extractedTypeName: null, + typeDescription: + getLinkedTypeDescription( + infoForDescription || typeInfoToUse, + context + ) || "", + }; + } + + // Parse fields with headings + while (index < lines.length) { + const headingLine = lines[index]; + if (!isHeadingLine(headingLine)) { + index++; + continue; + } + + let rawName = headingLine.slice(headingPrefix.length).trim(); + const optional = rawName.endsWith("?"); + const name = optional ? rawName.slice(0, -1).trim() : rawName.trim(); + index++; + + while (index < lines.length && lines[index].trim() === "") { + index++; + } + + let type = "any"; + if (index < lines.length) { + const maybeType = extractTypeFromLine(lines[index]); + if (maybeType) { + type = typeof maybeType === "object" ? maybeType.type : maybeType; + index++; + } + } + + while (index < lines.length && lines[index].trim() === "") { + index++; + } + + const descriptionLines = []; + const nested = []; + + // Collect description and nested fields + while ( + index < lines.length && + !isHeadingLine(lines[index]) && + !(nestedPrefix && isNestedHeadingLine(lines[index])) + ) { + descriptionLines.push(lines[index]); + index++; + } + + // Parse nested fields if any + while (index < lines.length && isNestedHeadingLine(lines[index])) { + const nestedHeadingLine = lines[index]; + let nestedRawName = nestedHeadingLine.slice(nestedPrefix.length).trim(); + const nestedOptional = nestedRawName.endsWith("?"); + const nestedName = nestedOptional + ? nestedRawName.slice(0, -1).trim() + : nestedRawName.trim(); + index++; + + while (index < lines.length && lines[index].trim() === "") { + index++; + } + + let nestedType = "any"; + if (index < lines.length) { + const maybeNestedType = extractTypeFromLine(lines[index]); + if (maybeNestedType) { + nestedType = + typeof maybeNestedType === "object" + ? maybeNestedType.type + : maybeNestedType; + index++; + } + } + + while (index < lines.length && lines[index].trim() === "") { + index++; + } + + const nestedDescLines = []; + while ( + index < lines.length && + !isNestedHeadingLine(lines[index]) && + !isHeadingLine(lines[index]) + ) { + nestedDescLines.push(lines[index]); + index++; + } + + nested.push({ + name: nestedName, + type: nestedType, + description: nestedDescLines.join("\n").trim(), + optional: nestedOptional, + }); + } + + let description = descriptionLines.join("\n").trim(); + + // Check if this field's type is a linked type that should be expanded + // Only expand if we don't already have nested fields from headings + if (nested.length === 0 && context && type && type !== "any") { + const simpleTypeName = getSimpleTypeName(type); + if (simpleTypeName && !PRIMITIVE_TYPES.includes(simpleTypeName)) { + // Try to resolve the type to a linked type + const typePath = resolveTypePath( + simpleTypeName, + context.app, + context.currentPagePath + ); + const linkedTypeInfo = typePath + ? { typeName: simpleTypeName, typePath } + : { typeName: simpleTypeName, typePath: simpleTypeName }; + + // Extract properties from the linked type + const properties = extractPropertiesFromLinkedType( + linkedTypeInfo, + context + ); + if (properties.length > 0) { + // Add properties as nested fields + for (const prop of properties) { + nested.push({ + name: prop.name, + type: prop.type, + description: prop.description || "", + optional: prop.optional, + }); + } + + // Track the linked type + if (linkedTypeNames) { + linkedTypeNames.add(simpleTypeName); + if (writeLinkedTypesFile) { + writeLinkedTypesFile(); + } + } + } + } + } + + fields.push({ + name, + type, + description, + optional, + nested, + }); + } + + return { + fields, + leadingText: leadingLines.join("\n").trim(), + extractedTypeName: null, + typeDescription: + getLinkedTypeDescription(infoForDescription, context) || "", + indexSignature: null, + }; +} + +function buildResponseFieldsSection( + fields, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + let output = ""; + + const PRIMITIVE_TYPES = [ + "any", + "string", + "number", + "boolean", + "void", + "null", + "undefined", + "object", + "Array", + "Promise", + ]; + + for (const field of fields) { + const requiredAttr = field.optional ? "" : " required"; + const defaultAttr = field.default + ? ` default="${escapeAttribute(field.default)}"` + : ""; + + // Track non-primitive return field types for suppression + if ( + linkedTypeNames && + field.type && + !PRIMITIVE_TYPES.includes(field.type) + ) { + const simpleTypeName = field.type.replace(/[<>\[\]]/g, "").trim(); + if (simpleTypeName && !PRIMITIVE_TYPES.includes(simpleTypeName)) { + linkedTypeNames.add(simpleTypeName); + if (writeLinkedTypesFile) { + writeLinkedTypesFile(); + } + } + } + + output += `\n`; + + if (field.description) { + output += `\n${field.description}\n`; + } + + if (field.nested && field.nested.length > 0) { + // Wrap nested fields in an Accordion component + output += `\n\n\n`; + output += renderNestedResponseFields( + field.nested, + linkedTypeNames, + writeLinkedTypesFile + ); + output += "\n"; + } + + output += "\n\n\n"; + } + + return output; +} + +function formatReturnFieldsOutput( + fields, + returnType = null, + linkedTypeNames = null, + writeLinkedTypesFile = null, + indexSignature = null +) { + if ((!fields || fields.length === 0) && !indexSignature) { + return ""; + } + + const isSingleSimpleField = + fields.length === 1 && + (!fields[0].nested || fields[0].nested.length === 0) && + !indexSignature; + + if (isSingleSimpleField) { + // For a single, non-object field, we only need to return its description text. + // The type is already rendered separately (`typeNameForDisplay`), so avoid wrapping + // it in a ResponseField to keep the output concise. + return fields[0].description || ""; + } + + const fieldsBlock = buildResponseFieldsSection( + fields, + linkedTypeNames, + writeLinkedTypesFile + ).trimEnd(); + + // Build index signature as a ResponseField if present + let indexSignatureBlock = ""; + if (indexSignature) { + const keyName = `[key: ${indexSignature.keyType}]`; + const description = indexSignature.description || ""; + indexSignatureBlock = `\n\n${description}\n\n\n\n`; + } + + if (!fieldsBlock && !indexSignatureBlock) { + return ""; + } + + const hasMultipleFields = fields.length > 1; + const hasNestedFields = fields.some( + (field) => Array.isArray(field.nested) && field.nested.length > 0 + ); + + if (hasMultipleFields || hasNestedFields || indexSignature) { + // Extract the simple type name to display above the Accordion + let typeDisplay = ""; + if (returnType) { + const simpleTypeName = getSimpleTypeName(returnType); + if (simpleTypeName && !PRIMITIVE_TYPES.includes(simpleTypeName)) { + typeDisplay = `\`${simpleTypeName}\`\n\n`; + } + } + // If we still don't have a type display and have multiple fields, + // try to infer from the context (e.g., if all fields are from the same type) + if (!typeDisplay && hasMultipleFields && fields.length > 0) { + // Check if we can get a type hint from the first field's description or context + // This is a fallback for cases where returnType wasn't passed correctly + } + return `${typeDisplay}\n\n${fieldsBlock}${indexSignatureBlock}\n`; + } + + return fieldsBlock + indexSignatureBlock; +} + +function renderNestedResponseFields( + fields, + linkedTypeNames = null, + writeLinkedTypesFile = null +) { + if (!fields || fields.length === 0) { + return ""; + } + + let output = ""; + for (const field of fields) { + const requiredAttr = field.optional ? "" : " required"; + const defaultAttr = field.default + ? ` default="${escapeAttribute(field.default)}"` + : ""; + + if ( + linkedTypeNames && + field.type && + !PRIMITIVE_TYPES.includes(field.type) + ) { + const simpleTypeName = field.type.replace(/[<>\[\]]/g, "").trim(); + if (simpleTypeName && !PRIMITIVE_TYPES.includes(simpleTypeName)) { + linkedTypeNames.add(simpleTypeName); + if (writeLinkedTypesFile) { + writeLinkedTypesFile(); + } + } + } + + output += `\n`; + + if (field.description) { + output += `\n${field.description}\n`; + } + + if (field.nested && field.nested.length > 0) { + output += `\n\n\n`; + output += renderNestedResponseFields( + field.nested, + linkedTypeNames, + writeLinkedTypesFile + ); + output += "\n"; + } + + output += "\n\n\n"; + } + + return output; +} + +function getSimpleTypeName(typeName) { + if (!typeName) { + return null; + } + + // Remove generic arguments if they are still present (e.g., Promise) + const withoutGenerics = typeName.split("<")[0].trim(); + + // Type names can include dots for namespaces, so allow those + const match = withoutGenerics.match(/^[A-Za-z0-9_.]+$/); + return match ? match[0] : null; +} diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-utils.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-utils.js new file mode 100644 index 0000000..497bf79 --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-utils.js @@ -0,0 +1,12 @@ +/** + * Utility functions for TypeDoc Mintlify plugin + */ + +/** + * Escape special characters for use in HTML attributes + */ +export function escapeAttribute(value) { + return String(value).replace(/"/g, '"'); +} + + diff --git a/scripts/mintlify-post-processing/types-to-expose.json b/scripts/mintlify-post-processing/types-to-expose.json new file mode 100644 index 0000000..94bd563 --- /dev/null +++ b/scripts/mintlify-post-processing/types-to-expose.json @@ -0,0 +1,12 @@ +[ + "AgentsModule", + "AppLogsModule", + "AuthModule", + "ConnectorsModule", + "EntitiesModule", + "EntityHandler", + "FunctionsModule", + "IntegrationsModule", + "CoreIntegrations", + "SsoModule" +] diff --git a/src/client.ts b/src/client.ts index 1732e69..34afc83 100644 --- a/src/client.ts +++ b/src/client.ts @@ -10,35 +10,51 @@ import { createAgentsModule } from "./modules/agents.js"; import { createAppLogsModule } from "./modules/app-logs.js"; import { createUsersModule } from "./modules/users.js"; import { RoomsSocket, RoomsSocketConfig } from "./utils/socket-utils.js"; +import type { + Base44Client, + CreateClientConfig, + CreateClientOptions, +} from "./client.types.js"; -export type CreateClientOptions = { - onError?: (error: Error) => void; -}; - -export type Base44Client = ReturnType; +// Re-export client types +export type { Base44Client, CreateClientConfig, CreateClientOptions }; /** - * Create a Base44 client instance - * @param {Object} config - Client configuration - * @param {string} [config.serverUrl='https://base44.app'] - API server URL - * @param {string} [config.appBaseUrl] - Application base URL - * @param {string|number} config.appId - Application ID - * @param {string} [config.token] - Authentication token - * @param {string} [config.serviceToken] - Service role authentication token - * @param {boolean} [config.requiresAuth=false] - Whether the app requires authentication - * @returns {Object} Base44 client instance + * Creates a Base44 client. + * + * This is the main entry point for the Base44 SDK. It creates a client that provides access to the SDK's modules, such as {@linkcode EntitiesModule | entities}, {@linkcode AuthModule | auth}, and {@linkcode FunctionsModule | functions}. + * + * Typically, you don't need to call this function because Base44 creates the client for you. You can then import and use the client to make API calls. The client takes care of managing authentication for you. + * + * The client supports three authentication modes: + * - **Anonymous**: Access modules anonymously without authentication using `base44.moduleName`. Operations are scoped to public data and permissions. + * - **User authentication**: Access modules with user-level permissions using `base44.moduleName`. Operations are scoped to the authenticated user's data and permissions. + * - **Service role authentication**: Access modules with elevated permissions using `base44.asServiceRole.moduleName`. Operations can access any data available to the app's admin. Can only be used in the backend. Typically, you create a client with service role authentication using the {@linkcode createClientFromRequest | createClientFromRequest()} function in your backend functions. + * + * For example, when using the {@linkcode EntitiesModule | entities} module: + * - **Anonymous**: Can only read public data. + * - **User authentication**: Can access the current user's data. + * - **Service role authentication**: Can access all data that admins can access. + * + * Most modules are available in all three modes, but with different permission levels. However, some modules are only available in specific authentication modes. + * + * @param config - Configuration object for the client. + * @returns A configured Base44 client instance with access to all SDK modules. + * + * @example + * ```typescript + * // Create a client for your app + * import { createClient } from '@base44/sdk'; + * + * const base44 = createClient({ + * appId: 'my-app-id' + * }); + * + * // Use the client to access your data + * const products = await base44.entities.Products.list(); + * ``` */ -export function createClient(config: { - serverUrl?: string; - appBaseUrl?: string; - appId: string; - token?: string; - serviceToken?: string; - requiresAuth?: boolean; - functionsVersion?: string; - headers?: Record; - options?: CreateClientOptions; -}) { +export function createClient(config: CreateClientConfig): Base44Client { const { serverUrl = "https://base44.app", appId, @@ -186,8 +202,19 @@ export function createClient(config: { ...userModules, /** - * Set authentication token for all requests - * @param {string} newToken - New auth token + * Sets a new authentication token for all subsequent requests. + * + * @param newToken - The new authentication token + * + * @example + * ```typescript + * // Update token after login + * const { access_token } = await base44.auth.loginViaEmailPassword( + * 'user@example.com', + * 'password' + * ); + * base44.setToken(access_token); + * ``` */ setToken(newToken: string) { userModules.auth.setToken(newToken); @@ -200,8 +227,9 @@ export function createClient(config: { }, /** - * Get current configuration - * @returns {Object} Current configuration + * Gets the current client configuration. + * + * @internal */ getConfig() { return { @@ -212,8 +240,22 @@ export function createClient(config: { }, /** - * Access service role modules - throws error if no service token was provided - * @throws {Error} When accessed without a service token + * Provides access to service role modules. + * + * Service role authentication provides elevated permissions for server-side operations. Unlike user authentication, which is scoped to a specific user's permissions, service role authentication has access to the data and operations available to the app's admin. + * + * @throws {Error} When accessed without providing a serviceToken during client creation. + * + * @example + * ```typescript + * const base44 = createClient({ + * appId: 'my-app-id', + * serviceToken: 'service-role-token' + * }); + * + * // Also access a module with elevated permissions + * const allUsers = await base44.asServiceRole.entities.User.list(); + * ``` */ get asServiceRole() { if (!serviceToken) { @@ -228,7 +270,60 @@ export function createClient(config: { return client; } -export function createClientFromRequest(request: Request) { +/** + * Creates a Base44 client from an HTTP request. + * + * The client is created by automatically extracting authentication tokens from a request to a backend function. Base44 inserts the necessary headers when forwarding requests to backend functions. + * + * To learn more about the Base44 client, see {@linkcode createClient | createClient()}. + * + * @param request - The incoming HTTP request object containing Base44 authentication headers. + * @returns A configured Base44 client instance with authentication from the incoming request. + * + * @example + * ```typescript + * // User authentication in backend function + * import { createClientFromRequest } from 'npm:@base44/sdk'; + * + * Deno.serve(async (req) => { + * try { + * const base44 = createClientFromRequest(req); + * const user = await base44.auth.me(); + * + * if (!user) { + * return Response.json({ error: 'Unauthorized' }, { status: 401 }); + * } + * + * // Access user's data + * const userOrders = await base44.entities.Orders.filter({ userId: user.id }); + * return Response.json({ orders: userOrders }); + * } catch (error) { + * return Response.json({ error: error.message }, { status: 500 }); + * } + * }); + * ``` + * + * @example + * ```typescript + * // Service role authentication in backend function + * import { createClientFromRequest } from 'npm:@base44/sdk'; + * + * Deno.serve(async (req) => { + * try { + * const base44 = createClientFromRequest(req); + * + * // Access admin data with service role permissions + * const recentOrders = await base44.asServiceRole.entities.Orders.list('-created_at', 50); + * + * return Response.json({ orders: recentOrders }); + * } catch (error) { + * return Response.json({ error: error.message }, { status: 500 }); + * } + * }); + * ``` + * + */ +export function createClientFromRequest(request: Request): Base44Client { const authHeader = request.headers.get("Authorization"); const serviceRoleAuthHeader = request.headers.get( "Base44-Service-Authorization" diff --git a/src/client.types.ts b/src/client.types.ts new file mode 100644 index 0000000..40e3c87 --- /dev/null +++ b/src/client.types.ts @@ -0,0 +1,133 @@ +import type { EntitiesModule } from "./modules/entities.types.js"; +import type { IntegrationsModule } from "./modules/integrations.types.js"; +import type { AuthModule } from "./modules/auth.types.js"; +import type { SsoModule } from "./modules/sso.types.js"; +import type { ConnectorsModule } from "./modules/connectors.types.js"; +import type { FunctionsModule } from "./modules/functions.types.js"; +import type { AgentsModule } from "./modules/agents.types.js"; +import type { AppLogsModule } from "./modules/app-logs.types.js"; + +/** + * Options for creating a Base44 client. + */ +export interface CreateClientOptions { + /** + * Optional error handler that will be called whenever an API error occurs. + */ + onError?: (error: Error) => void; +} + +/** + * Configuration for creating a Base44 client. + */ +export interface CreateClientConfig { + /** + * The Base44 server URL. Defaults to "https://base44.app". + * @internal + */ + serverUrl?: string; + /** + * The base URL of the app, which is used for login redirects. + * @internal + */ + appBaseUrl?: string; + /** + * The Base44 app ID. + * + * You can find the `appId` in the browser URL when you're in the app editor. + * It's the string between `/apps/` and `/editor/`. + */ + appId: string; + /** + * User authentication token. Used to authenticate as a specific user. + */ + token?: string; + /** + * Service role authentication token. Use this in the backend when you need elevated permissions to access data available to the app's admin or perform admin operations. This token should be kept secret and never exposed in the app's frontend. Typically, you get this token from a request to a backend function using {@linkcode createClientFromRequest | createClientFromRequest()}. + */ + serviceToken?: string; + /** + * Whether authentication is required. If true, redirects to login if not authenticated. + * @internal + */ + requiresAuth?: boolean; + /** + * Version string for functions API. + * @internal + */ + functionsVersion?: string; + /** + * Additional headers to include in API requests. + * @internal + */ + headers?: Record; + /** + * Additional client options. + */ + options?: CreateClientOptions; +} + +/** + * The Base44 client instance. + * + * Provides access to all SDK modules for interacting with the app. + */ +export interface Base44Client { + /** {@link EntitiesModule | Entities module} for CRUD operations on your data models. */ + entities: EntitiesModule; + /** {@link IntegrationsModule | Integrations module} for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** {@link AuthModule | Auth module} for user authentication and management. */ + auth: AuthModule; + /** {@link FunctionsModule | Functions module} for invoking custom backend functions. */ + functions: FunctionsModule; + /** {@link AgentsModule | Agents module} for managing AI agent conversations. */ + agents: AgentsModule; + /** {@link AppLogsModule | App logs module} for tracking app usage. */ + appLogs: AppLogsModule; + /** Cleanup function to disconnect WebSocket connections. Call when you're done with the client. */ + cleanup: () => void; + + /** + * Sets a new authentication token for all subsequent requests. + * + * Updates the token for both HTTP requests and WebSocket connections. + * + * @param newToken - The new authentication token. + */ + setToken(newToken: string): void; + + /** + * Gets the current client configuration. + * @internal + */ + getConfig(): { serverUrl: string; appId: string; requiresAuth: boolean }; + + /** + * Provides access to supported modules with elevated permissions. + * + * Service role authentication provides elevated permissions for backend operations. Unlike user authentication, which is scoped to a specific user's permissions, service role authentication has access to the data and operations available to the app's admin. + * + * @throws {Error} When accessed without providing a serviceToken during client creation + */ + readonly asServiceRole: { + /** {@link EntitiesModule | Entities module} with elevated permissions. */ + entities: EntitiesModule; + /** {@link IntegrationsModule | Integrations module} with elevated permissions. */ + integrations: IntegrationsModule; + /** {@link SsoModule | SSO module} for generating SSO tokens. + * @internal + */ + sso: SsoModule; + /** {@link ConnectorsModule | Connectors module} for OAuth token retrieval. */ + connectors: ConnectorsModule; + /** {@link FunctionsModule | Functions module} with elevated permissions. */ + functions: FunctionsModule; + /** {@link AgentsModule | Agents module} with elevated permissions. */ + agents: AgentsModule; + /** {@link AppLogsModule | App logs module} with elevated permissions. */ + appLogs: AppLogsModule; + /** Cleanup function to disconnect WebSocket connections. */ + cleanup: () => void; + }; +} diff --git a/src/index.ts b/src/index.ts index 7a4b962..dbaa1ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,11 @@ -import { createClient, createClientFromRequest, type Base44Client } from "./client.js"; -import { Base44Error } from "./utils/axios-client.js"; +import { + createClient, + createClientFromRequest, + type Base44Client, + type CreateClientConfig, + type CreateClientOptions, +} from "./client.js"; +import { Base44Error, type Base44ErrorJSON } from "./utils/axios-client.js"; import { getAccessToken, saveAccessToken, @@ -17,6 +23,75 @@ export { getLoginUrl, }; -export type { Base44Client }; +export type { + Base44Client, + CreateClientConfig, + CreateClientOptions, + Base44ErrorJSON, +}; export * from "./types.js"; + +// Module types +export type { + EntitiesModule, + EntityHandler, +} from "./modules/entities.types.js"; + +export type { + AuthModule, + LoginResponse, + RegisterParams, + VerifyOtpParams, + ChangePasswordParams, + ResetPasswordParams, + User, +} from "./modules/auth.types.js"; + +export type { + IntegrationsModule, + IntegrationPackage, + IntegrationEndpointFunction, + CoreIntegrations, + InvokeLLMParams, + GenerateImageParams, + GenerateImageResult, + UploadFileParams, + UploadFileResult, + SendEmailParams, + SendEmailResult, + ExtractDataFromUploadedFileParams, + ExtractDataFromUploadedFileResult, + UploadPrivateFileParams, + UploadPrivateFileResult, + CreateFileSignedUrlParams, + CreateFileSignedUrlResult, +} from "./modules/integrations.types.js"; + +export type { FunctionsModule } from "./modules/functions.types.js"; + +export type { + AgentsModule, + AgentConversation, + AgentMessage, + AgentMessageReasoning, + AgentMessageToolCall, + AgentMessageUsage, + AgentMessageCustomContext, + AgentMessageMetadata, + CreateConversationParams, +} from "./modules/agents.types.js"; + +export type { AppLogsModule } from "./modules/app-logs.types.js"; + +export type { SsoModule, SsoAccessTokenResponse } from "./modules/sso.types.js"; + +export type { ConnectorsModule } from "./modules/connectors.types.js"; + +// Auth utils types +export type { + GetAccessTokenOptions, + SaveAccessTokenOptions, + RemoveAccessTokenOptions, + GetLoginUrlOptions, +} from "./utils/auth-utils.types.js"; diff --git a/src/modules/agents.ts b/src/modules/agents.ts index c573cd1..3b8974c 100644 --- a/src/modules/agents.ts +++ b/src/modules/agents.ts @@ -1,16 +1,12 @@ -import { RoomsSocket } from "../utils/socket-utils.js"; -import { AgentConversation, AgentMessage } from "./agents.types.js"; -import { AxiosInstance } from "axios"; -import { ModelFilterParams } from "../types.js"; import { getAccessToken } from "../utils/auth-utils.js"; - -export type AgentsModuleConfig = { - axios: AxiosInstance; - getSocket: () => ReturnType; - appId: string; - serverUrl?: string; - token?: string; -}; +import { ModelFilterParams } from "../types.js"; +import { + AgentConversation, + AgentMessage, + AgentsModule, + AgentsModuleConfig, + CreateConversationParams, +} from "./agents.types.js"; export function createAgentsModule({ axios, @@ -18,7 +14,7 @@ export function createAgentsModule({ appId, serverUrl, token, -}: AgentsModuleConfig) { +}: AgentsModuleConfig): AgentsModule { const baseURL = `/apps/${appId}/agents`; const getConversations = () => { @@ -37,29 +33,23 @@ export function createAgentsModule({ }); }; - const createConversation = (conversation: { - agent_name: string; - metadata?: Record; - }) => { + const createConversation = (conversation: CreateConversationParams) => { return axios.post( `${baseURL}/conversations`, conversation ); }; - + const addMessage = async ( conversation: AgentConversation, message: AgentMessage ) => { const room = `/agent-conversations/${conversation.id}`; const socket = getSocket(); - await socket.updateModel( - room, - { - ...conversation, - messages: [...(conversation.messages || []), message], - } - ); + await socket.updateModel(room, { + ...conversation, + messages: [...(conversation.messages || []), message], + }); return axios.post( `${baseURL}/conversations/${conversation.id}/messages`, message @@ -82,7 +72,9 @@ export function createAgentsModule({ }; const getWhatsAppConnectURL = (agentName: string) => { - const baseUrl = `${serverUrl}/api/apps/${appId}/agents/${encodeURIComponent(agentName)}/whatsapp`; + const baseUrl = `${serverUrl}/api/apps/${appId}/agents/${encodeURIComponent( + agentName + )}/whatsapp`; const accessToken = token ?? getAccessToken(); if (accessToken) { diff --git a/src/modules/agents.types.ts b/src/modules/agents.types.ts index a6c1337..355ad15 100644 --- a/src/modules/agents.types.ts +++ b/src/modules/agents.types.ts @@ -1,43 +1,368 @@ -export type AgentConversation = { +import { AxiosInstance } from "axios"; +import { RoomsSocket } from "../utils/socket-utils"; +import { ModelFilterParams } from "../types"; + +/** + * Reasoning information for an agent message. + * + * Contains details about the agent's reasoning process when generating a response. + */ +export interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** + * A tool call made by the agent. + * + * Represents a function or tool that the agent invoked during message generation. + */ +export interface AgentMessageToolCall { + /** Tool call ID. */ id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** + * Token usage statistics for an agent message. + * + * Tracks the number of tokens consumed when generating the message. + */ +export interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** + * Custom context provided with an agent message. + * + * Additional contextual information that can be passed to the agent. + */ +export interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** + * Metadata about when and by whom a message was created. + */ +export interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} + +/** + * An agent conversation containing messages exchanged with an AI agent. + */ +export interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ app_id: string; + /** Name of the agent in this conversation. */ agent_name: string; + /** ID of the user who created the conversation. */ created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ metadata?: Record; -}; +} -export type AgentMessage = { +/** + * A message in an agent conversation. + */ +export interface AgentMessage { + /** Unique identifier for the message. */ id: string; + /** Role of the message sender. */ role: "user" | "assistant" | "system"; - reasoning?: { - start_date: string; - end_date?: string; - content: string; - }; - content?: string | Record | null; - file_urls?: string[] | null; - tool_calls?: - | { - id: string; - name: string; - arguments_string: string; - status: "running" | "success" | "error" | "stopped"; - results?: string | null; - }[] - | null; - - usage?: { prompt_tokens?: number; completion_tokens?: number } | null; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** Message content. */ + content?: string | Record; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ hidden?: boolean; - custom_context?: - | { message: string; data: Record; type: string }[] - | null; - model?: string | null; - checkpoint_id?: string | null; - metadata?: { - created_date: string; - created_by_email: string; - created_by_full_name: string | null; - }; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; + /** Additional custom parameters for the message. */ additional_message_params?: Record; -}; +} + +/** + * Parameters for creating a new conversation. + */ +export interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} + +/** + * Configuration for creating the agents module. + * @internal + */ +export interface AgentsModuleConfig { + /** Axios instance for HTTP requests */ + axios: AxiosInstance; + /** Function to get WebSocket instance for real-time updates (lazy initialization) */ + getSocket: () => ReturnType; + /** Application ID */ + appId: string; + /** Server URL */ + serverUrl?: string; + /** Authentication token */ + token?: string; +} + +/** + * Agents module for managing AI agent conversations. + * + * This module provides methods to create and manage conversations with AI agents, + * send messages, and subscribe to real-time updates. Conversations can be used + * for chat interfaces, support systems, or any interactive AI app. + * + * The agents module enables you to: + * + * - **Create conversations** with agents defined in the app. + * - **Send messages** from users to agents and receive AI-generated responses. + * - **Retrieve conversations** individually or as filtered lists with sorting and pagination. + * - **Subscribe to real-time updates** using WebSocket connections to receive instant notifications when new messages arrive. + * - **Attach metadata** to conversations for tracking context, categories, priorities, or linking to external systems. + * - **Generate WhatsApp connection URLs** for users to interact with agents through WhatsApp. + * + * The agents module operates with a two-level hierarchy: + * + * 1. **Conversations**: Top-level containers that represent a dialogue with a specific agent. Each conversation has a unique ID, is associated with an agent by name, and belongs to the user who created it. Conversations can include optional metadata for tracking app-specific context like ticket IDs, categories, or custom fields. + * + * 2. **Messages**: Individual exchanges within a conversation. Each message has a role, content, and optional metadata like token usage, tool calls, file attachments, and reasoning information. Messages are stored as an array within their parent conversation. + * + * This module is available to use with a client in all authentication modes: + * + * - **Anonymous or User authentication** (`base44.agents`): Access is scoped to the current user's permissions. Anonymous users can create conversations but can't retrieve them later, while authenticated users can access conversations they created. + * - **Service role authentication** (`base44.asServiceRole.agents`): Operations have elevated admin-level permissions. Can access all conversations that the app's admin role has access to. + * + */ +export interface AgentsModule { + /** + * Gets all conversations from all agents in the app. + * + * Retrieves all conversations. Use {@linkcode listConversations | listConversations()} to filter which conversations are returned, apply sorting, or paginate results. Use {@linkcode getConversation | getConversation()} to retrieve a specific conversation by ID. + * + * @returns Promise resolving to an array of conversations. + * + * @example + * ```typescript + * // Get all conversations + * const conversations = await base44.agents.getConversations(); + * console.log(`Total conversations: ${conversations.length}`); + * ``` + * + * @see {@linkcode listConversations | listConversations()} for filtering, sorting, and pagination + * @see {@linkcode getConversation | getConversation()} for retrieving a specific conversation by ID + */ + getConversations(): Promise; + + /** + * Gets a specific conversation by ID. + * + * Retrieves a single conversation using its unique identifier. To retrieve + * all conversations, use {@linkcode getConversations | getConversations()} To filter, sort, or paginate conversations, use {@linkcode listConversations | listConversations()}. + * + * @param conversationId - The unique identifier of the conversation. + * @returns Promise resolving to the conversation, or undefined if not found. + * + * @example + * ```typescript + * // Get a specific conversation by ID + * const conversation = await base44.agents.getConversation('conv-123'); + * if (conversation) { + * console.log(`Conversation has ${conversation.messages.length} messages`); + * } + * ``` + * + * @see {@linkcode getConversations | getConversations()} for retrieving all conversations + * @see {@linkcode listConversations | listConversations()} for filtering and sorting conversations + */ + getConversation( + conversationId: string + ): Promise; + + /** + * Lists conversations with filtering, sorting, and pagination. + * + * Provides querying capabilities including filtering by fields, sorting, pagination, and field selection. For cases where you need all conversations without filtering, use {@linkcode getConversations | getConversations()}. To retrieve a specific conversation by ID, use {@linkcode getConversation | getConversation()}. + * + * @param filterParams - Filter parameters for querying conversations. + * @returns Promise resolving to an array of filtered conversations. + * + * @example + * ```typescript + * // List recent conversations with pagination + * const recentConversations = await base44.agents.listConversations({ + * limit: 10, + * sort: '-created_date' + * }); + * ``` + * + * @example + * ```typescript + * // Filter by agent and metadata + * const supportConversations = await base44.agents.listConversations({ + * q: { + * agent_name: 'support-agent', + * 'metadata.priority': 'high' + * }, + * sort: '-created_date', + * limit: 20 + * }); + * ``` + * + * @see {@linkcode getConversations | getConversations()} for retrieving all conversations without filtering + * @see {@linkcode getConversation | getConversation()} for retrieving a specific conversation by ID + */ + listConversations( + filterParams: ModelFilterParams + ): Promise; + + /** + * Creates a new conversation with an agent. + * + * @param conversation - Conversation details including agent name and optional metadata. + * @returns Promise resolving to the created conversation. + * + * @example + * ```typescript + * // Create a new conversation with metadata + * const conversation = await base44.agents.createConversation({ + * agent_name: 'support-agent', + * metadata: { + * order_id: 'ORD-789', + * product_id: 'PROD-456', + * category: 'technical-support' + * } + * }); + * console.log(`Created conversation: ${conversation.id}`); + * ``` + */ + createConversation( + conversation: CreateConversationParams + ): Promise; + + /** + * Adds a message to a conversation. + * + * Sends a message to the agent and updates the conversation. This method + * also updates the real-time socket to notify any subscribers. + * + * @param conversation - The conversation to add the message to. + * @param message - The message to add. + * @returns Promise resolving to the created message. + * + * @example + * ```typescript + * // Send a message to the agent + * const message = await base44.agents.addMessage(conversation, { + * role: 'user', + * content: 'Hello, I need help with my order #12345' + * }); + * console.log(`Message sent with ID: ${message.id}`); + * ``` + */ + addMessage( + conversation: AgentConversation, + message: Partial + ): Promise; + + /** + * Subscribes to real-time updates for a conversation. + * + * Establishes a WebSocket connection to receive instant updates when new + * messages are added to the conversation. Returns an unsubscribe function + * to clean up the connection. + * + * @param conversationId - The conversation ID to subscribe to. + * @param onUpdate - Callback function called when the conversation is updated. + * @returns Unsubscribe function to stop receiving updates. + * + * @example + * ```typescript + * // Subscribe to real-time updates + * const unsubscribe = base44.agents.subscribeToConversation( + * 'conv-123', + * (updatedConversation) => { + * const latestMessage = updatedConversation.messages[updatedConversation.messages.length - 1]; + * console.log('New message:', latestMessage.content); + * } + * ); + * + * // Later, clean up the subscription + * unsubscribe(); + * ``` + */ + subscribeToConversation( + conversationId: string, + onUpdate?: (conversation: AgentConversation) => void + ): () => void; + + /** + * Gets WhatsApp connection URL for an agent. + * + * Generates a URL that users can use to connect with the agent through WhatsApp. + * The URL includes authentication if a token is available. + * + * @param agentName - The name of the agent. + * @returns WhatsApp connection URL. + * + * @example + * ```typescript + * // Get WhatsApp connection URL + * const whatsappUrl = base44.agents.getWhatsAppConnectURL('support-agent'); + * console.log(`Connect through WhatsApp: ${whatsappUrl}`); + * // User can open this URL to start a WhatsApp conversation + * ``` + */ + getWhatsAppConnectURL(agentName: string): string; +} diff --git a/src/modules/app-logs.ts b/src/modules/app-logs.ts index b461cb9..ec072f8 100644 --- a/src/modules/app-logs.ts +++ b/src/modules/app-logs.ts @@ -1,43 +1,36 @@ import { AxiosInstance } from "axios"; +import { AppLogsModule } from "./app-logs.types"; /** - * Creates the app logs module for the Base44 SDK - * @param {AxiosInstance} axios - Axios instance - * @param {string} appId - Application ID - * @returns {Object} App logs module + * Creates the app logs module for the Base44 SDK. + * + * @param axios - Axios instance + * @param appId - Application ID + * @returns App logs module with methods for tracking and analyzing app usage + * @internal */ -export function createAppLogsModule(axios: AxiosInstance, appId: string) { +export function createAppLogsModule( + axios: AxiosInstance, + appId: string +): AppLogsModule { const baseURL = `/app-logs/${appId}`; return { - /** - * Log user activity in the app - * @param {string} pageName - Name of the page being visited - * @returns {Promise} - */ + // Log user activity in the app async logUserInApp(pageName: string): Promise { await axios.post(`${baseURL}/log-user-in-app/${pageName}`); }, - /** - * Fetch app logs with optional parameters - * @param {Object} params - Query parameters for filtering logs - * @returns {Promise} App logs data - */ + // Fetch app logs with optional parameters async fetchLogs(params: Record = {}): Promise { const response = await axios.get(baseURL, { params }); return response; }, - /** - * Get app statistics - * @param {Object} params - Query parameters for filtering stats - * @returns {Promise} App statistics - */ + // Get app statistics async getStats(params: Record = {}): Promise { const response = await axios.get(`${baseURL}/stats`, { params }); return response; }, }; } - diff --git a/src/modules/app-logs.types.ts b/src/modules/app-logs.types.ts new file mode 100644 index 0000000..ab1b9b7 --- /dev/null +++ b/src/modules/app-logs.types.ts @@ -0,0 +1,46 @@ +/** + * App Logs module for tracking and analyzing app usage. + * + * This module provides a method to log user activity. The logs are reflected in the Analytics page in the app dashboard. + * + * This module is available to use with a client in all authentication modes. + */ +export interface AppLogsModule { + /** + * Log user activity in the app. + * + * Records when a user visits a specific page or section of the app. Useful for tracking user navigation patterns and popular features. The logs are reflected in the Analytics page in the app dashboard. + * + * The specified page name doesn't have to be the name of an actual page in the app, it can be any string you want to use to track the activity. + * + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + * + * @example + * ```typescript + * // Log page visit or feature usage + * await base44.appLogs.logUserInApp('home'); + * await base44.appLogs.logUserInApp('features-section'); + * await base44.appLogs.logUserInApp('button-click'); + * ``` + */ + logUserInApp(pageName: string): Promise; + + /** + * Fetch app logs with optional parameters. + * + * @param params - Optional query parameters for filtering logs. + * @returns Promise resolving to the logs data. + * @internal + */ + fetchLogs(params?: Record): Promise; + + /** + * Get app statistics. + * + * @param params - Optional query parameters for filtering stats. + * @returns Promise resolving to the stats data. + * @internal + */ + getStats(params?: Record): Promise; +} diff --git a/src/modules/app.types.ts b/src/modules/app.types.ts index fc2f360..680126c 100644 --- a/src/modules/app.types.ts +++ b/src/modules/app.types.ts @@ -1,5 +1,6 @@ - - +/** + * @internal + */ export interface AppMessageContent { content?: string; file_urls?: string[]; @@ -8,11 +9,17 @@ export interface AppMessageContent { [key: string]: unknown; } +/** + * @internal + */ export interface AppConversationMessage extends AppMessageContent { id?: string | null; role?: "user" | "assistant" | string; } +/** + * @internal + */ export interface AppConversationLike { id?: string | null; messages?: AppMessageContent[] | null; @@ -20,15 +27,19 @@ export interface AppConversationLike { functions_fail_silently?: boolean; } - -export interface DenoProjectLike { - project_id: string - project_name: string - app_id: string - deployment_name_to_info: Record - +/** + * @internal + */ +export interface DenoProjectLike { + project_id: string; + project_name: string; + app_id: string; + deployment_name_to_info: Record; } +/** + * @internal + */ export interface AppLike { id?: string; conversation?: AppConversationLike | null; @@ -48,7 +59,12 @@ export interface AppLike { agents?: Record; logo_url?: string; slug?: string; - public_settings?: "private_with_login" | "public_with_login" | "public_without_login" | "workspace_with_login" | string; + public_settings?: + | "private_with_login" + | "public_with_login" + | "public_without_login" + | "workspace_with_login" + | string; is_blocked?: boolean; github_repo_url?: string; main_page?: string; @@ -79,13 +95,19 @@ export interface AppLike { function_names?: string[]; user_entity?: UserEntityLike; app_code_hash?: string; - has_backend_functions_enabled?: boolean; + has_backend_functions_enabled?: boolean; } +/** + * @internal + */ export interface UserLike { id?: string | null; } +/** + * @internal + */ export interface UserEntityLike { type: string; name: string; @@ -108,7 +130,9 @@ export interface UserEntityLike { required: string[]; } - +/** + * @internal + */ export interface AuthConfigLike { enable_username_password?: boolean; enable_google_login?: boolean; @@ -118,9 +142,9 @@ export interface AuthConfigLike { enable_sso_login?: boolean; } - - - +/** + * @internal + */ export type LoginInfoResponse = Pick< AppLike, | "id" @@ -132,4 +156,4 @@ export type LoginInfoResponse = Pick< | "created_date" | "auth_config" | "platform_version" ->; \ No newline at end of file +>; diff --git a/src/modules/auth.ts b/src/modules/auth.ts index 412b65a..0736da2 100644 --- a/src/modules/auth.ts +++ b/src/modules/auth.ts @@ -1,43 +1,40 @@ import { AxiosInstance } from "axios"; +import { + AuthModule, + AuthModuleOptions, + VerifyOtpParams, + ChangePasswordParams, + ResetPasswordParams, +} from "./auth.types"; /** - * Creates the auth module for the Base44 SDK - * @param {import('axios').AxiosInstance} axios - Axios instance - * @param {string|number} appId - Application ID - * @param {string} serverUrl - Server URL - * @returns {Object} Auth module with authentication methods + * Creates the auth module for the Base44 SDK. + * + * @param axios - Axios instance for API requests + * @param functionsAxiosClient - Axios instance for functions API requests + * @param appId - Application ID + * @param options - Configuration options including server URLs + * @returns Auth module with authentication and user management methods + * @internal */ export function createAuthModule( axios: AxiosInstance, functionsAxiosClient: AxiosInstance, appId: string, - options: { - serverUrl: string; - appBaseUrl?: string; - } -) { + options: AuthModuleOptions +): AuthModule { return { - /** - * Get current user information - * @returns {Promise} Current user data - */ + // Get current user information async me() { return axios.get(`/apps/${appId}/entities/User/me`); }, - /** - * Update current user data - * @param {Object} data - Updated user data - * @returns {Promise} Updated user - */ + + // Update current user data async updateMe(data: Record) { return axios.put(`/apps/${appId}/entities/User/me`, data); }, - /** - * Redirects the user to the app's login page - * @param {string} nextUrl - URL to redirect to after successful login - * @throws {Error} When not in a browser environment - */ + // Redirects the user to the app's login page redirectToLogin(nextUrl: string) { // This function only works in a browser environment if (typeof window === "undefined") { @@ -60,12 +57,8 @@ export function createAuthModule( window.location.href = loginUrl; }, - /** - * Logout the current user - * Removes the token from localStorage and optionally redirects to a URL or reloads the page - * @param redirectUrl - Optional URL to redirect to after logout. Reloads the page if not provided - * @returns {Promise} - */ + // Logout the current user + // Removes the token from localStorage and optionally redirects to a URL or reloads the page logout(redirectUrl?: string) { // Remove token from axios headers delete axios.defaults.headers.common["Authorization"]; @@ -91,11 +84,7 @@ export function createAuthModule( } }, - /** - * Set authentication token - * @param {string} token - Auth token - * @param {boolean} [saveToStorage=true] - Whether to save the token to localStorage - */ + // Set authentication token setToken(token: string, saveToStorage = true) { if (!token) return; @@ -121,13 +110,7 @@ export function createAuthModule( } }, - /** - * Login via username and password - * @param email - User email - * @param password - User password - * @param turnstileToken - Optional Turnstile captcha token - * @returns Login response with access_token and user - */ + // Login using username and password async loginViaEmailPassword( email: string, password: string, @@ -162,10 +145,7 @@ export function createAuthModule( } }, - /** - * Verify if the current token is valid - * @returns {Promise} True if token is valid - */ + // Verify if the current token is valid async isAuthenticated() { try { await this.me(); @@ -175,6 +155,7 @@ export function createAuthModule( } }, + // Invite a user to the app inviteUser(userEmail: string, role: string) { return axios.post(`/apps/${appId}/users/invite-user`, { user_email: userEmail, @@ -182,6 +163,7 @@ export function createAuthModule( }); }, + // Register a new user account register(payload: { email: string; password: string; @@ -191,45 +173,40 @@ export function createAuthModule( return axios.post(`/apps/${appId}/auth/register`, payload); }, - verifyOtp({ email, otpCode }: { email: string; otpCode: string }) { + // Verify an OTP (One-time password) code + verifyOtp({ email, otpCode }: VerifyOtpParams) { return axios.post(`/apps/${appId}/auth/verify-otp`, { email, otp_code: otpCode, }); }, + // Resend an OTP code to the user's email resendOtp(email: string) { return axios.post(`/apps/${appId}/auth/resend-otp`, { email }); }, + // Request a password reset resetPasswordRequest(email: string) { return axios.post(`/apps/${appId}/auth/reset-password-request`, { email, }); }, - resetPassword({ - resetToken, - newPassword, - }: { - resetToken: string; - newPassword: string; - }) { + // Reset password using a reset token + resetPassword({ resetToken, newPassword }: ResetPasswordParams) { return axios.post(`/apps/${appId}/auth/reset-password`, { reset_token: resetToken, new_password: newPassword, }); }, + // Change the user's password changePassword({ userId, currentPassword, newPassword, - }: { - userId: string; - currentPassword: string; - newPassword: string; - }) { + }: ChangePasswordParams) { return axios.post(`/apps/${appId}/auth/change-password`, { user_id: userId, current_password: currentPassword, diff --git a/src/modules/auth.types.ts b/src/modules/auth.types.ts new file mode 100644 index 0000000..6124f4b --- /dev/null +++ b/src/modules/auth.types.ts @@ -0,0 +1,464 @@ +import { AxiosInstance } from "axios"; + +/** + * An authenticated user. + */ +export interface User { + /** Unique user identifier. */ + id: string; + /** When the user was created. */ + created_date: string; + /** When the user was last updated. */ + updated_date: string; + /** User's email address. */ + email: string; + /** User's full name. */ + full_name: string | null; + /** Whether the user is disabled. */ + disabled: boolean | null; + /** Whether the user's email has been verified. */ + is_verified: boolean; + /** The app ID this user belongs to. */ + app_id: string; + /** Whether this is a service account. */ + is_service: boolean; + /** Internal app role. + * @internal + */ + _app_role: string; + /** + * User's role in the app. Roles are configured in the app settings and determine the user's permissions and access levels. + */ + role: string; + /** + * Additional custom fields defined in the user schema. Any custom properties added to the user schema in the app will be available here with their configured types and values. + */ + [key: string]: any; +} + +/** + * Response from login endpoints containing user information and access token. + */ +export interface LoginResponse { + /** JWT access token for authentication. */ + access_token: string; + /** User information. */ + user: User; +} + +/** + * Payload for user registration. + */ +export interface RegisterParams { + /** User's email address. */ + email: string; + /** User's password. */ + password: string; + /** Optional {@link https://developers.cloudflare.com/turnstile/ | Cloudflare Turnstile CAPTCHA token} for bot protection. */ + turnstile_token?: string | null; + /** Optional {@link https://docs.base44.com/Getting-Started/Referral-program | referral code} from an existing user. */ + referral_code?: string | null; +} + +/** + * Parameters for OTP verification. + */ +export interface VerifyOtpParams { + /** User's email address. */ + email: string; + /** One-time password code received by email. */ + otpCode: string; +} + +/** + * Parameters for changing a user's password. + */ +export interface ChangePasswordParams { + /** User ID. */ + userId: string; + /** Current password for verification. */ + currentPassword: string; + /** New password to set. */ + newPassword: string; +} + +/** + * Parameters for resetting a password with a token. + */ +export interface ResetPasswordParams { + /** Reset token received by email. */ + resetToken: string; + /** New password to set. */ + newPassword: string; +} + +/** + * Configuration options for the auth module. + */ +export interface AuthModuleOptions { + /** Server URL for API requests. */ + serverUrl: string; + /** Optional base URL for the app (used for login redirects). */ + appBaseUrl?: string; +} + +/** + * Authentication module for managing user authentication and authorization. The module automatically stores tokens in local storage when available and manages authorization headers for API requests. + * + * This module provides comprehensive authentication functionality including: + * - Email/password login and registration + * - Token management + * - User profile access and updates + * - Password reset flows + * - OTP verification + * - User invitations + * + * The auth module is only available in user authentication mode (`base44.auth`). + */ +export interface AuthModule { + /** + * Gets the current authenticated user's information. + * + * @returns Promise resolving to the user's profile data. + * + * @example + * ```typescript + * // Get current user information + * const user = await base44.auth.me(); + * console.log(`Logged in as: ${user.email}`); + * console.log(`User ID: ${user.id}`); + * ``` + */ + me(): Promise; + + /** + * Updates the current authenticated user's information. + * + * Only the fields included in the data object will be updated. + * Commonly updated fields include `full_name` and custom profile fields. + * + * @param data - Object containing the fields to update. + * @returns Promise resolving to the updated user data. + * + * @example + * ```typescript + * // Update specific fields + * const updatedUser = await base44.auth.updateMe({ + * full_name: 'John Doe' + * }); + * console.log(`Updated user: ${updatedUser.full_name}`); + * ``` + * + * @example + * ```typescript + * // Update custom fields defined in your User entity + * await base44.auth.updateMe({ + * bio: 'Software developer', + * phone: '+1234567890', + * preferences: { theme: 'dark' } + * }); + * ``` + */ + updateMe( + data: Partial> + ): Promise; + + /** + * Redirects the user to the app's login page. + * + * Redirects with a callback URL to return to after successful authentication. Requires a browser environment and can't be used in the backend. + * + * @param nextUrl - URL to redirect to after successful login. + * @throws {Error} When not in a browser environment. + * + * @example + * ```typescript + * // Redirect to login and come back to current page + * base44.auth.redirectToLogin(window.location.href); + * ``` + * + * @example + * ```typescript + * // Redirect to login and then go to the dashboard page + * base44.auth.redirectToLogin('/dashboard'); + * ``` + */ + redirectToLogin(nextUrl: string): void; + + /** + * Logs out the current user. + * + * Removes the authentication token from local storage and Axios headers, then optionally redirects to a URL or reloads the page. Requires a browser environment and can't be used in the backend. + * + * @param redirectUrl - Optional URL to redirect to after logout. Reloads the page if not provided. + * + * @example + * ```typescript + * // Logout and reload page + * base44.auth.logout(); + * ``` + * + * @example + * ```typescript + * // Logout and redirect to login page + * base44.auth.logout('/login'); + * ``` + * + * @example + * ```typescript + * // Logout and redirect to home + * base44.auth.logout('/'); + * ``` + */ + logout(redirectUrl?: string): void; + + /** + * Sets the authentication token. + * + * Updates the authorization header for API requests and optionally saves the token to local storage for persistence. Saving to local storage requires a browser environment and is automatically skipped in backend environments. + * + * @param token - JWT authentication token. + * @param saveToStorage - Whether to save the token to local storage. Defaults to true. + * + * @example + * ```typescript + * // Set token and save to local storage + * base44.auth.setToken('eyJhbGciOiJIUzI1NiIs...'); + * ``` + * + * @example + * ```typescript + * // Set token without saving to local storage + * base44.auth.setToken('eyJhbGciOiJIUzI1NiIs...', false); + * ``` + */ + setToken(token: string, saveToStorage?: boolean): void; + + /** + * Logs in a registered user using email and password. + * + * Authenticates a user with email and password credentials. The user must already have a registered account. For new users, use {@linkcode register | register()} first to create an account. On successful login, automatically sets the token for subsequent requests. + * + * @param email - User's email address. + * @param password - User's password. + * @param turnstileToken - Optional {@link https://developers.cloudflare.com/turnstile/ | Cloudflare Turnstile CAPTCHA token} for bot protection. + * @returns Promise resolving to login response with access token and user data. + * @throws Error if the email and password combination is invalid or the user is not registered. + * + * @example + * ```typescript + * // Login with email and password + * try { + * const { access_token, user } = await base44.auth.loginViaEmailPassword( + * 'user@example.com', + * 'securePassword123' + * ); + * console.log('Login successful!', user); + * } catch (error) { + * console.error('Login failed:', error); + * } + * ``` + * + * @example + * ```typescript + * // With captcha token + * const response = await base44.auth.loginViaEmailPassword( + * 'user@example.com', + * 'securePassword123', + * 'captcha-token-here' + * ); + * ``` + */ + loginViaEmailPassword( + email: string, + password: string, + turnstileToken?: string + ): Promise; + + /** + * Checks if the current user is authenticated. + * + * @returns Promise resolving to true if authenticated, false otherwise. + * + * @example + * ```typescript + * // Check authentication status + * const isAuthenticated = await base44.auth.isAuthenticated(); + * if (isAuthenticated) { + * console.log('User is logged in'); + * } else { + * // Redirect to login page + * base44.auth.redirectToLogin(window.location.href); + * } + * ``` + */ + isAuthenticated(): Promise; + + /** + * Invites a user to the app. + * + * Sends an invitation email to a potential user with a specific role. + * Roles are configured in the app settings and determine + * the user's permissions and access levels. + * + * @param userEmail - Email address of the user to invite. + * @param role - Role to assign to the invited user. Must match a role defined in the app. For example, `'admin'` or `'user'`. + * @returns Promise that resolves when the invitation is sent successfully. Throws an error if the invitation fails. + * + * @example + * ```typescript + * try { + * await base44.auth.inviteUser('newuser@example.com', 'user'); + * console.log('Invitation sent successfully!'); + * } catch (error) { + * console.error('Failed to send invitation:', error); + * } + * ``` + */ + inviteUser(userEmail: string, role: string): Promise; + + /** + * Registers a new user account. + * + * Creates a new user account with email and password. After successful registration, + * use {@linkcode loginViaEmailPassword | loginViaEmailPassword()} to log in the user. + * + * @param params - Registration details including email, password, and optional fields. + * @returns Promise resolving to the registration response. + * + * @example + * ```typescript + * // Register a new user + * await base44.auth.register({ + * email: 'newuser@example.com', + * password: 'securePassword123', + * referral_code: 'FRIEND2024' + * }); + * + * // Login after registration + * const { access_token, user } = await base44.auth.loginViaEmailPassword( + * 'newuser@example.com', + * 'securePassword123' + * ); + * ``` + */ + register(params: RegisterParams): Promise; + + /** + * Verifies an OTP (One-time password) code. + * + * Validates an OTP code sent to the user's email during registration + * or authentication. + * + * @param params - Object containing email and OTP code. + * @returns Promise resolving to the verification response if valid. + * @throws Error if the OTP code is invalid, expired, or verification fails. + * + * @example + * ```typescript + * try { + * await base44.auth.verifyOtp({ + * email: 'user@example.com', + * otpCode: '123456' + * }); + * console.log('Email verified successfully!'); + * } catch (error) { + * console.error('Invalid or expired OTP code'); + * } + * ``` + */ + verifyOtp(params: VerifyOtpParams): Promise; + + /** + * Resends an OTP code to the user's email address. + * + * Requests a new OTP code to be sent to the specified email address. + * + * @param email - Email address to send the OTP to. + * @returns Promise resolving when the OTP is sent successfully. + * @throws Error if the email is invalid or the request fails. + * + * @example + * ```typescript + * try { + * await base44.auth.resendOtp('user@example.com'); + * console.log('OTP resent! Please check your email.'); + * } catch (error) { + * console.error('Failed to resend OTP:', error); + * } + * ``` + */ + resendOtp(email: string): Promise; + + /** + * Requests a password reset. + * + * Sends a password reset email to the specified email address. + * + * @param email - Email address for the account to reset. + * @returns Promise resolving when the password reset email is sent successfully. + * @throws Error if the email is invalid or the request fails. + * + * @example + * ```typescript + * try { + * await base44.auth.resetPasswordRequest('user@example.com'); + * console.log('Password reset email sent!'); + * } catch (error) { + * console.error('Failed to send password reset email:', error); + * } + * ``` + */ + resetPasswordRequest(email: string): Promise; + + /** + * Resets password using a reset token. + * + * Completes the password reset flow by setting a new password + * using the token received by email. + * + * @param params - Object containing the reset token and new password. + * @returns Promise resolving when the password is reset successfully. + * @throws Error if the reset token is invalid, expired, or the request fails. + * + * @example + * ```typescript + * try { + * await base44.auth.resetPassword({ + * resetToken: 'token-from-email', + * newPassword: 'newSecurePassword456' + * }); + * console.log('Password reset successful!'); + * } catch (error) { + * console.error('Failed to reset password:', error); + * } + * ``` + */ + resetPassword(params: ResetPasswordParams): Promise; + + /** + * Changes the user's password. + * + * Updates the password for an authenticated user by verifying + * the current password and setting a new one. + * + * @param params - Object containing user ID, current password, and new password. + * @returns Promise resolving when the password is changed successfully. + * @throws Error if the current password is incorrect or the request fails. + * + * @example + * ```typescript + * try { + * await base44.auth.changePassword({ + * userId: 'user-123', + * currentPassword: 'oldPassword123', + * newPassword: 'newSecurePassword456' + * }); + * console.log('Password changed successfully!'); + * } catch (error) { + * console.error('Failed to change password:', error); + * } + * ``` + */ + changePassword(params: ChangePasswordParams): Promise; +} diff --git a/src/modules/connectors.ts b/src/modules/connectors.ts index 32b0db1..5d99a3c 100644 --- a/src/modules/connectors.ts +++ b/src/modules/connectors.ts @@ -2,28 +2,29 @@ import { AxiosInstance } from "axios"; import { ConnectorIntegrationType, ConnectorAccessTokenResponse, + ConnectorsModule, } from "./connectors.types.js"; /** - * Creates the Connectors module for the Base44 SDK + * Creates the Connectors module for the Base44 SDK. + * * @param axios - Axios instance (should be service role client) * @param appId - Application ID - * @returns Connectors module + * @returns Connectors module with methods to retrieve OAuth tokens + * @internal */ -export function createConnectorsModule(axios: AxiosInstance, appId: string) { +export function createConnectorsModule( + axios: AxiosInstance, + appId: string +): ConnectorsModule { return { - /** - * Retrieve an access token for a given integration type - * @param integrationType - The integration type to get access token for - * @returns Access token response - */ + // Retrieve an OAuth access token for a specific external integration type + // @ts-expect-error Return type mismatch with interface - implementation returns object, interface expects string async getAccessToken( integrationType: ConnectorIntegrationType ): Promise { if (!integrationType || typeof integrationType !== "string") { - throw new Error( - "Integration type is required and must be a string" - ); + throw new Error("Integration type is required and must be a string"); } const response = await axios.get( @@ -35,4 +36,3 @@ export function createConnectorsModule(axios: AxiosInstance, appId: string) { }, }; } - diff --git a/src/modules/connectors.types.ts b/src/modules/connectors.types.ts index c498bb9..a9a486b 100644 --- a/src/modules/connectors.types.ts +++ b/src/modules/connectors.types.ts @@ -1,6 +1,72 @@ +/** + * The type of external integration/connector, such as `'googlecalendar'`, `'slack'`, or `'github'`. + */ export type ConnectorIntegrationType = string; -export type ConnectorAccessTokenResponse = { +/** + * Response from the connectors access token endpoint. + */ +export interface ConnectorAccessTokenResponse { access_token: string; -}; +} +/** + * Connectors module for managing OAuth tokens for external services. + * + * This module allows you to retrieve OAuth access tokens for external services + * that the app has connected to. Use these tokens to make API + * calls to external services. + * + * Unlike the integrations module that provides pre-built functions, connectors give you + * raw OAuth tokens so you can call external service APIs directly with full control over + * the API calls you make. This is useful when you need custom API interactions that aren't + * covered by Base44's pre-built integrations. + * + * This module is only available to use with a client in service role authentication mode, which means it can only be used in backend environments. + */ +export interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * + * Returns the OAuth token string for an external service that the app + * has connected to. You can then use this token to make authenticated API calls + * to that external service. + * + * @param integrationType - The type of integration, such as `'googlecalendar'`, `'slack'`, or `'github'`. + * @returns Promise resolving to the access token string. + * + * @example + * ```typescript + * // Google Calendar connection + * // Get Google Calendar OAuth token and fetch upcoming events + * const googleToken = await base44.asServiceRole.connectors.getAccessToken('googlecalendar'); + * + * // Fetch upcoming 10 events + * const timeMin = new Date().toISOString(); + * const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events?maxResults=10&orderBy=startTime&singleEvents=true&timeMin=${timeMin}`; + * + * const calendarResponse = await fetch(url, { + * headers: { 'Authorization': `Bearer ${googleToken}` } + * }); + * + * const events = await calendarResponse.json(); + * ``` + * + * @example + * ```typescript + * // Slack connection + * // Get Slack OAuth token and list channels + * const slackToken = await base44.asServiceRole.connectors.getAccessToken('slack'); + * + * // List all public and private channels + * const url = 'https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=100'; + * + * const slackResponse = await fetch(url, { + * headers: { 'Authorization': `Bearer ${slackToken}` } + * }); + * + * const data = await slackResponse.json(); + * ``` + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} diff --git a/src/modules/entities.ts b/src/modules/entities.ts index c134091..b59a29c 100644 --- a/src/modules/entities.ts +++ b/src/modules/entities.ts @@ -1,12 +1,18 @@ import { AxiosInstance } from "axios"; +import { EntitiesModule, EntityHandler } from "./entities.types"; /** - * Creates the entities module for the Base44 SDK - * @param {import('axios').AxiosInstance} axios - Axios instance - * @param {string|number} appId - Application ID - * @returns {Object} Entities module + * Creates the entities module for the Base44 SDK. + * + * @param axios - Axios instance + * @param appId - Application ID + * @returns Entities module with dynamic entity access + * @internal */ -export function createEntitiesModule(axios: AxiosInstance, appId: string) { +export function createEntitiesModule( + axios: AxiosInstance, + appId: string +): EntitiesModule { // Using Proxy to dynamically handle entity names return new Proxy( {}, @@ -25,32 +31,27 @@ export function createEntitiesModule(axios: AxiosInstance, appId: string) { return createEntityHandler(axios, appId, entityName); }, } - ); + ) as EntitiesModule; } /** - * Creates a handler for a specific entity - * @param {import('axios').AxiosInstance} axios - Axios instance - * @param {string|number} appId - Application ID - * @param {string} entityName - Entity name - * @returns {Object} Entity handler with CRUD methods + * Creates a handler for a specific entity. + * + * @param axios - Axios instance + * @param appId - Application ID + * @param entityName - Entity name + * @returns Entity handler with CRUD methods + * @internal */ function createEntityHandler( axios: AxiosInstance, appId: string, entityName: string -) { +): EntityHandler { const baseURL = `/apps/${appId}/entities/${entityName}`; return { - /** - * List entities with optional pagination and sorting - * @param {string} [sort] - Sort parameter - * @param {number} [limit] - Limit results - * @param {number} [skip] - Skip results (pagination) - * @param {string[]} [fields] - Fields to include - * @returns {Promise} List of entities - */ + // List entities with optional pagination and sorting async list(sort: string, limit: number, skip: number, fields: string[]) { const params: Record = {}; if (sort) params.sort = sort; @@ -62,15 +63,7 @@ function createEntityHandler( return axios.get(baseURL, { params }); }, - /** - * Filter entities based on query - * @param {Object} query - Filter query - * @param {string} [sort] - Sort parameter - * @param {number} [limit] - Limit results - * @param {number} [skip] - Skip results (pagination) - * @param {string[]} [fields] - Fields to include - * @returns {Promise} Filtered entities - */ + // Filter entities based on query async filter( query: Record, sort: string, @@ -91,66 +84,37 @@ function createEntityHandler( return axios.get(baseURL, { params }); }, - /** - * Get entity by ID - * @param {string} id - Entity ID - * @returns {Promise} Entity - */ + // Get entity by ID async get(id: string) { return axios.get(`${baseURL}/${id}`); }, - /** - * Create new entity - * @param {Object} data - Entity data - * @returns {Promise} Created entity - */ + // Create new entity async create(data: Record) { return axios.post(baseURL, data); }, - /** - * Update entity by ID - * @param {string} id - Entity ID - * @param {Object} data - Updated entity data - * @returns {Promise} Updated entity - */ + // Update entity by ID async update(id: string, data: Record) { return axios.put(`${baseURL}/${id}`, data); }, - /** - * Delete entity by ID - * @param {string} id - Entity ID - * @returns {Promise} - */ + // Delete entity by ID async delete(id: string) { return axios.delete(`${baseURL}/${id}`); }, - /** - * Delete multiple entities based on query - * @param {Object} query - Delete query - * @returns {Promise} - */ + // Delete multiple entities based on query async deleteMany(query: Record) { return axios.delete(baseURL, { data: query }); }, - /** - * Create multiple entities in a single request - * @param {Array} data - Array of entity data - * @returns {Promise} Created entities - */ + // Create multiple entities in a single request async bulkCreate(data: Record[]) { return axios.post(`${baseURL}/bulk`, data); }, - /** - * Import entities from a file - * @param {File} file - File to import - * @returns {Promise} Import result - */ + // Import entities from a file async importEntities(file: File) { const formData = new FormData(); formData.append("file", file, file.name); diff --git a/src/modules/entities.types.ts b/src/modules/entities.types.ts new file mode 100644 index 0000000..712cf0f --- /dev/null +++ b/src/modules/entities.types.ts @@ -0,0 +1,313 @@ +/** + * Entity handler providing CRUD operations for a specific entity type. + * + * Each entity in the app gets a handler with these methods for managing data. + */ +export interface EntityHandler { + /** + * Lists records with optional pagination and sorting. + * + * Retrieves all records of this type with support for sorting, + * pagination, and field selection. + * + * @param sort - Sort parameter, such as `'-created_date'` for descending. Defaults to `'-created_date'`. + * @param limit - Maximum number of results to return. Defaults to `50`. + * @param skip - Number of results to skip for pagination. Defaults to `0`. + * @param fields - Array of field names to include in the response. Defaults to all fields. + * @returns Promise resolving to an array of records. + * + * @example + * ```typescript + * // Get all records + * const records = await base44.entities.MyEntity.list(); + * ``` + * + * @example + * ```typescript + * // Get first 10 records sorted by date + * const recentRecords = await base44.entities.MyEntity.list('-created_date', 10); + * ``` + * + * @example + * ```typescript + * // Get paginated results + * // Skip first 20, get next 10 + * const page3 = await base44.entities.MyEntity.list('-created_date', 10, 20); + * ``` + * + * @example + * ```typescript + * // Get only specific fields + * const fields = await base44.entities.MyEntity.list('-created_date', 10, 0, ['name', 'status']); + * ``` + */ + list( + sort?: string, + limit?: number, + skip?: number, + fields?: string[] + ): Promise; + + /** + * Filters records based on a query. + * + * Retrieves records that match specific criteria with support for + * sorting, pagination, and field selection. + * + * @param query - Query object with field-value pairs. Each key should be a field name + * from your entity schema, and each value is the criteria to match. Records matching all + * specified criteria are returned. Field names are case-sensitive. + * @param sort - Sort parameter, such as `'-created_date'` for descending. Defaults to `'-created_date'`. + * @param limit - Maximum number of results to return. Defaults to `50`. + * @param skip - Number of results to skip for pagination. Defaults to `0`. + * @param fields - Array of field names to include in the response. Defaults to all fields. + * @returns Promise resolving to an array of filtered records. + * + * @example + * ```typescript + * // Filter by single field + * const activeRecords = await base44.entities.MyEntity.filter({ + * status: 'active' + * }); + * ``` + * + * @example + * ```typescript + * // Filter by multiple fields + * const filteredRecords = await base44.entities.MyEntity.filter({ + * priority: 'high', + * status: 'active' + * }); + * ``` + * + * @example + * ```typescript + * // Filter with sorting and pagination + * const results = await base44.entities.MyEntity.filter( + * { status: 'active' }, + * '-created_date', + * 20, + * 0 + * ); + * ``` + * + * @example + * ```typescript + * // Filter with specific fields + * const fields = await base44.entities.MyEntity.filter( + * { priority: 'high' }, + * '-created_date', + * 10, + * 0, + * ['name', 'priority'] + * ); + * ``` + */ + filter( + query: Record, + sort?: string, + limit?: number, + skip?: number, + fields?: string[] + ): Promise; + + /** + * Gets a single record by ID. + * + * Retrieves a specific record using its unique identifier. + * + * @param id - The unique identifier of the record. + * @returns Promise resolving to the record. + * + * @example + * ```typescript + * // Get record by ID + * const record = await base44.entities.MyEntity.get('entity-123'); + * console.log(record.name); + * ``` + */ + get(id: string): Promise; + + /** + * Creates a new record. + * + * Creates a new record with the provided data. + * + * @param data - Object containing the record data. + * @returns Promise resolving to the created record. + * + * @example + * ```typescript + * // Create a new record + * const newRecord = await base44.entities.MyEntity.create({ + * name: 'My Item', + * status: 'active', + * priority: 'high' + * }); + * console.log('Created record with ID:', newRecord.id); + * ``` + */ + create(data: Record): Promise; + + /** + * Updates an existing record. + * + * Updates a record by ID with the provided data. Only the fields + * included in the data object will be updated. + * + * @param id - The unique identifier of the record to update. + * @param data - Object containing the fields to update. + * @returns Promise resolving to the updated record. + * + * @example + * ```typescript + * // Update single field + * const updated = await base44.entities.MyEntity.update('entity-123', { + * status: 'completed' + * }); + * ``` + * + * @example + * ```typescript + * // Update multiple fields + * const updated = await base44.entities.MyEntity.update('entity-123', { + * name: 'Updated name', + * priority: 'low', + * status: 'active' + * }); + * ``` + */ + update(id: string, data: Record): Promise; + + /** + * Deletes a single record by ID. + * + * Permanently removes a record from the database. + * + * @param id - The unique identifier of the record to delete. + * @returns Promise resolving to the deletion result. + * + * @example + * ```typescript + * // Delete a record + * const result = await base44.entities.MyEntity.delete('entity-123'); + * console.log('Deleted:', result); + * ``` + */ + delete(id: string): Promise; + + /** + * Deletes multiple records matching a query. + * + * Permanently removes all records that match the provided query. + * + * @param query - Query object with field-value pairs. Each key should be a field name + * from your entity schema, and each value is the criteria to match. Records matching all + * specified criteria will be deleted. Field names are case-sensitive. + * @returns Promise resolving to the deletion result. + * + * @example + * ```typescript + * // Delete by multiple criteria + * const result = await base44.entities.MyEntity.deleteMany({ + * status: 'completed', + * priority: 'low' + * }); + * console.log('Deleted:', result); + * ``` + */ + deleteMany(query: Record): Promise; + + /** + * Creates multiple records in a single request. + * + * Efficiently creates multiple records at once. This is faster + * than creating them individually. + * + * @param data - Array of record data objects. + * @returns Promise resolving to an array of created records. + * + * @example + * ```typescript + * // Create multiple records at once + * const result = await base44.entities.MyEntity.bulkCreate([ + * { name: 'Item 1', status: 'active' }, + * { name: 'Item 2', status: 'active' }, + * { name: 'Item 3', status: 'completed' } + * ]); + * ``` + */ + bulkCreate(data: Record[]): Promise; + + /** + * Imports records from a file. + * + * Imports records from a file, typically CSV or similar format. + * The file format should match your entity structure. Requires a browser environment and can't be used in the backend. + * + * @param file - File object to import. + * @returns Promise resolving to the import result. + * + * @example + * ```typescript + * // Import records from file in React + * const handleFileImport = async (event: React.ChangeEvent) => { + * const file = event.target.files?.[0]; + * if (file) { + * const result = await base44.entities.MyEntity.importEntities(file); + * console.log(`Imported ${result.count} records`); + * } + * }; + * ``` + */ + importEntities(file: File): Promise; +} + +/** + * Entities module for managing app data. + * + * This module provides dynamic access to all entities in the app. + * Each entity gets a handler with full CRUD operations and additional utility methods. + * + * Entities are accessed dynamically using the pattern: + * `base44.entities.EntityName.method()` + * + * This module is available to use with a client in all three authentication modes: + * + * - **Anonymous or User authentication** (`base44.entities`): Access is scoped to the current user's permissions. Anonymous users can only access public entities, while authenticated users can access entities they have permission to view or modify. + * - **Service role authentication** (`base44.asServiceRole.entities`): Operations have elevated admin-level permissions. Can access all entities that the app's admin role has access to. + * + * ## Built-in User Entity + * + * Every app includes a built-in `User` entity that stores user account information. This entity has special security rules that can't be changed. + * + * Regular users can only read and update their own user record. With service role authentication, you can read, update, and delete any user. You can't create users using the entities module. Instead, use the functions of the {@link AuthModule | auth module} to invite or register new users. + * + * @example + * ```typescript + * // Get all records from the MyEntity entity + * // Get all records the current user has permissions to view + * const myRecords = await base44.entities.MyEntity.list(); + * ``` + * + * @example + * ```typescript + * // List all users (admin only) + * const allUsers = await base44.asServiceRole.entities.User.list(); + * ``` + */ +export interface EntitiesModule { + /** + * Access any entity by name. + * + * Use this to access entities defined in the app. + * + * @example + * ```typescript + * // Access entities dynamically + * base44.entities.MyEntity + * base44.entities.AnotherEntity + * ``` + */ + [entityName: string]: EntityHandler; +} diff --git a/src/modules/functions.ts b/src/modules/functions.ts index 4dfa102..296465a 100644 --- a/src/modules/functions.ts +++ b/src/modules/functions.ts @@ -1,14 +1,20 @@ import { AxiosInstance } from "axios"; +import { FunctionsModule } from "./functions.types"; /** - * Creates the functions module for the Base44 SDK - * @param {import('axios').AxiosInstance} axios - Axios instance - * @param {string|number} appId - Application ID - * @returns {Object} Functions module + * Creates the functions module for the Base44 SDK. + * + * @param axios - Axios instance + * @param appId - Application ID + * @returns Functions module with methods to invoke custom backend functions + * @internal */ -export function createFunctionsModule(axios: AxiosInstance, appId: string) { - // Using nested Proxy objects to handle dynamic function names +export function createFunctionsModule( + axios: AxiosInstance, + appId: string +): FunctionsModule { return { + // Invoke a custom backend function by name async invoke(functionName: string, data: Record) { // Validate input if (typeof data === "string") { diff --git a/src/modules/functions.types.ts b/src/modules/functions.types.ts new file mode 100644 index 0000000..cdc91a2 --- /dev/null +++ b/src/modules/functions.types.ts @@ -0,0 +1,50 @@ +/** + * Functions module for invoking custom backend functions. + * + * This module allows you to invoke the custom backend functions defined in the app. + * + * This module is available to use with a client in all authentication modes: + * + * - **Anonymous or User authentication** (`base44.functions`): Functions are invoked with the current user's permissions. Anonymous users invoke functions without authentication, while authenticated users invoke functions with their authentication context. + * - **Service role authentication** (`base44.asServiceRole.functions`): Functions are invoked with elevated admin-level permissions. The function code receives a request with admin authentication context. + */ +export interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * Calls a custom backend function deployed to the app. + * The function receives the provided data as named parameters and returns + * the result. If any parameter is a `File` object, the request will automatically be + * sent as `multipart/form-data`. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. The `data` property contains the data returned by the function, if there is any. + * + * @example + * ```typescript + * // Basic function call + * const result = await base44.functions.invoke('calculateTotal', { + * items: ['item1', 'item2'], + * discount: 0.1 + * }); + * console.log(result.data.total); + * ``` + * + * @example + * ```typescript + * // Function with file upload in React + * const handleFileUpload = async (event: React.ChangeEvent) => { + * const file = event.target.files?.[0]; + * if (file) { + * const processedImage = await base44.functions.invoke('processImage', { + * image: file, + * filter: 'grayscale', + * quality: 80 + * }); + * } + * }; + * ``` + */ + invoke(functionName: string, data: Record): Promise; +} diff --git a/src/modules/integrations.ts b/src/modules/integrations.ts index 2b1c750..9dda317 100644 --- a/src/modules/integrations.ts +++ b/src/modules/integrations.ts @@ -1,13 +1,18 @@ import { AxiosInstance } from "axios"; +import { IntegrationsModule } from "./integrations.types"; /** - * Creates the integrations module for the Base44 SDK - * @param {import('axios').AxiosInstance} axios - Axios instance - * @param {string|number} appId - Application ID - * @returns {Object} Integrations module + * Creates the integrations module for the Base44 SDK. + * + * @param axios - Axios instance + * @param appId - Application ID + * @returns Integrations module with dynamic access to integration endpoints + * @internal */ -export function createIntegrationsModule(axios: AxiosInstance, appId: string) { - // Using nested Proxy objects to handle dynamic package and endpoint names +export function createIntegrationsModule( + axios: AxiosInstance, + appId: string +): IntegrationsModule { return new Proxy( {}, { @@ -36,6 +41,7 @@ export function createIntegrationsModule(axios: AxiosInstance, appId: string) { } // Return a function that calls the integration endpoint + // This allows: client.integrations.PackageName.EndpointName(data) return async (data: Record) => { // Validate input if (typeof data === "string") { @@ -93,5 +99,5 @@ export function createIntegrationsModule(axios: AxiosInstance, appId: string) { ); }, } - ); + ) as IntegrationsModule; } diff --git a/src/modules/integrations.types.ts b/src/modules/integrations.types.ts new file mode 100644 index 0000000..189a650 --- /dev/null +++ b/src/modules/integrations.types.ts @@ -0,0 +1,382 @@ +/** + * Function signature for calling an integration endpoint. + * + * If any parameter is a `File` object, the request will automatically be + * sent as `multipart/form-data`. Otherwise, it will be sent as JSON. + * + * @param data - An object containing named parameters for the integration endpoint. + * @returns Promise resolving to the integration endpoint's response. + */ +export type IntegrationEndpointFunction = ( + data: Record +) => Promise; + +/** + * A package containing integration endpoints. + * + * Provides dynamic access to integration endpoints within a package. + * Each endpoint is accessed as a property that returns a function to invoke it. + * + * @example + * ```typescript + * // Access endpoints dynamically + * const result = await integrations.Core.SendEmail({ + * to: 'user@example.com', + * subject: 'Hello', + * body: 'Message' + * }); + * ``` + */ +export type IntegrationPackage = { + [endpointName: string]: IntegrationEndpointFunction; +}; + +/** + * Parameters for the InvokeLLM function. + */ +export interface InvokeLLMParams { + /** The prompt text to send to the model */ + prompt: string; + /** If set to `true`, the LLM will use Google Search, Maps, and News to gather real-time context before answering. + * @default false + */ + add_context_from_internet?: boolean; + /** If you want structured data back, provide a [JSON schema object](https://json-schema.org/understanding-json-schema/reference/object) here. If provided, the function returns a JSON object; otherwise, it returns a string. */ + response_json_schema?: object; + /** A list of file URLs (uploaded via UploadFile) to provide as context/attachments to the LLM. Do not use this together with `add_context_from_internet`. */ + file_urls?: string[]; +} + +/** + * Parameters for the GenerateImage function. + */ +export interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +export interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** + * Parameters for the UploadFile function. + */ +export interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +export interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** + * Parameters for the SendEmail function. + */ +export interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text email body content. */ + body: string; + /** The name of the sender. If omitted, the app's name will be used. */ + from_name?: string; +} + +export type SendEmailResult = any; + +/** + * Parameters for the ExtractDataFromUploadedFile function. + */ +export interface ExtractDataFromUploadedFileParams { + /** The URL of the uploaded file to extract data from. */ + file_url: string; + /** A [JSON schema object](https://json-schema.org/understanding-json-schema/reference/object) defining what data fields you want to extract. */ + json_schema: object; +} + +export type ExtractDataFromUploadedFileResult = object; + +/** + * Parameters for the UploadPrivateFile function. + */ +export interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +export interface UploadPrivateFileResult { + /** URI of the uploaded private file, used to create a signed URL. */ + file_uri: string; +} + +/** + * Parameters for the CreateFileSignedUrl function. + */ +export interface CreateFileSignedUrlParams { + /** URI of the uploaded private file. */ + file_uri: string; + /** How long the signed URL should be valid for, in seconds. + * @default 300 (5 minutes) + */ + expires_in?: number; +} + +export interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the private file. */ + signed_url: string; +} + +/** + * Core package containing built-in Base44 integration functions. + */ +export interface CoreIntegrations { + /** + * Generate text or structured JSON data using AI models. + * + * @param params - Parameters for the LLM invocation + * @returns Promise resolving to a string (when no schema provided) or an object (when schema provided). + * + * @example + * ```typescript + * // Basic prompt + * const response = await base44.integrations.Core.InvokeLLM({ + * prompt: "Write a haiku about coding." + * }); + * ``` + * + * @example + * ```typescript + * // Prompt with internet context + * const response = await base44.integrations.Core.InvokeLLM({ + * prompt: "What is the current stock price of Wix and what was the latest major news about it?", + * add_context_from_internet: true + * }); + * ``` + * + * @example + * ```typescript + * // Structured JSON response + * const response = await base44.integrations.Core.InvokeLLM({ + * prompt: "Analyze the sentiment of this review: 'The service was slow but the food was amazing.'", + * response_json_schema: { + * type: "object", + * properties: { + * sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + * score: { type: "number", description: "Score from 1-10" }, + * key_points: { type: "array", items: { type: "string" } } + * } + * } + * }); + * // Returns object: { sentiment: "mixed", score: 7, key_points: ["slow service", "amazing food"] } + * ``` + */ + InvokeLLM(params: InvokeLLMParams): Promise; + + /** + * Create AI-generated images from text prompts. + * + * @param params - Parameters for image generation + * @returns Promise resolving to the generated image URL. + * + * @example + * ```typescript + * // Generate an image from a text prompt + * const {url} = await base44.integrations.Core.GenerateImage({ + * prompt: "A serene mountain landscape with a lake in the foreground" + * }); + * console.log(url); // https://...generated_image.png + * ``` + */ + GenerateImage(params: GenerateImageParams): Promise; + + /** + * Upload files to public storage and get a URL. + * + * @param params - Parameters for file upload + * @returns Promise resolving to an object containing the uploaded file URL. + * + * @example + * ```typescript + * // Upload a file in React + * const handleFileUpload = async (event: React.ChangeEvent) => { + * const file = event.target.files?.[0]; + * if (!file) return; + * + * const { file_url } = await base44.integrations.Core.UploadFile({ file }); + * console.log(file_url); // https://...uploaded_file.pdf + * }; + * ``` + */ + UploadFile(params: UploadFileParams): Promise; + + /** + * Send emails to registered users of your app. + * + * @param params - Parameters for sending email + * @returns Promise resolving when the email is sent. + */ + SendEmail(params: SendEmailParams): Promise; + + /** + * Extract structured data from uploaded files based on the specified schema. + * + * Start by uploading the file to public storage using the {@linkcode UploadFile | UploadFile()} function. Then, use the `file_url` parameter to extract structured data from the uploaded file. + * + * @param params - Parameters for data extraction + * @returns Promise resolving to the extracted data. + * + * @example + * ```typescript + * // Extract data from an already uploaded file + * const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + * file_url: "https://example.com/files/invoice.pdf", + * json_schema: { + * type: "object", + * properties: { + * invoice_number: { type: "string" }, + * total_amount: { type: "number" }, + * date: { type: "string" }, + * vendor_name: { type: "string" } + * } + * } + * }); + * console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } + * ``` + * + * @example + * ```typescript + * // Upload a file and extract data in React + * const handleFileExtraction = async (event: React.ChangeEvent) => { + * const file = event.target.files?.[0]; + * if (!file) return; + * + * // First, upload the file + * const { file_url } = await base44.integrations.Core.UploadFile({ file }); + * + * // Then extract structured data from it + * const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + * file_url, + * json_schema: { + * type: "object", + * properties: { + * summary: { + * type: "string", + * description: "A brief summary of the file content" + * }, + * keywords: { + * type: "array", + * items: { type: "string" } + * }, + * document_type: { + * type: "string" + * } + * } + * } + * }); + * console.log(result); // { summary: "...", keywords: [...], document_type: "..." } + * }; + * ``` + */ + ExtractDataFromUploadedFile( + params: ExtractDataFromUploadedFileParams + ): Promise; + + /** + * Upload files to private storage that requires a signed URL to access. + * + * Create a signed URL to access uploaded files using the {@linkcode CreateFileSignedUrl | CreateFileSignedUrl()} function. + * + * @param params - Parameters for private file upload + * @returns Promise resolving to an object with a `file_uri` used to create a signed URL to access the uploaded file. + * + * @example + * ```typescript + * // Upload a private file + * const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ file }); + * console.log(file_uri); // "private/user123/document.pdf" + * ``` + * + * @example + * ```typescript + * // Upload a private file and create a signed URL + * const handlePrivateUpload = async (event: React.ChangeEvent) => { + * const file = event.target.files?.[0]; + * if (!file) return; + * + * // Upload to private storage + * const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ file }); + * + * // Create a signed URL that expires in 1 hour (3600 seconds) + * const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + * file_uri, + * expires_in: 3600 + * }); + * + * console.log(signed_url); // Temporary URL to access the private file + * }; + * ``` + */ + UploadPrivateFile( + params: UploadPrivateFileParams + ): Promise; + + /** + * Generate temporary access links for private files. + * + * Start by uploading the file to private storage using the {@linkcode UploadPrivateFile | UploadPrivateFile()} function. Then, use the `file_uri` parameter to create a signed URL to access the uploaded file. + * + * @param params - Parameters for creating signed URL + * @returns Promise resolving to an object with a temporary `signed_url`. + * + * @example + * ```typescript + * // Create a signed URL for a private file + * const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + * file_uri: "private/user123/document.pdf", + * expires_in: 7200 // URL expires in 2 hours + * }); + * console.log(signed_url); // https://...?signature=... + * ``` + */ + CreateFileSignedUrl( + params: CreateFileSignedUrlParams + ): Promise; +} + +/** + * Integrations module for calling integration endpoints. + * + * This module provides access to integration endpoints for interacting with external + * services. Integrations are organized into packages. Base44 provides built-in integrations + * in the `Core` package. + * + * Unlike the connectors module that gives you raw OAuth tokens, integrations provide + * pre-built functions that Base44 executes on your behalf. + * + * Integration endpoints are accessed dynamically using the pattern: + * `base44.integrations.PackageName.EndpointName(params)` + * + * This module is available to use with a client in all authentication modes: + * + * - **Anonymous or User authentication** (`base44.integrations`): Integration endpoints are invoked with the current user's permissions. Anonymous users invoke endpoints without authentication, while authenticated users invoke endpoints with their authentication context. + * - **Service role authentication** (`base44.asServiceRole.integrations`): Integration endpoints are invoked with elevated admin-level permissions. The endpoints execute with admin authentication context. + */ +export type IntegrationsModule = { + /** + * Core package containing built-in Base44 integration functions. + */ + Core: CoreIntegrations; +} & { + /** + * Access to additional integration packages. + * + * Additional integration packages may be added in the future and will be + * accessible using the same pattern as Core. + */ + [packageName: string]: IntegrationPackage; +}; diff --git a/src/modules/sso.ts b/src/modules/sso.ts index c839a5b..25bc1cb 100644 --- a/src/modules/sso.ts +++ b/src/modules/sso.ts @@ -1,35 +1,32 @@ import { AxiosInstance } from "axios"; +import { SsoModule } from "./sso.types"; /** - * Creates the SSO module for the Base44 SDK - * @param {import('axios').AxiosInstance} axios - Axios instance - * @param {string} appId - Application ID - * @param {string} [userToken] - User authentication token - * @param {string} [serviceToken] - Service role authentication token - * @returns {Object} SSO module with SSO authentication methods + * Creates the SSO module for the Base44 SDK. + * + * @param axios - Axios instance + * @param appId - Application ID + * @param userToken - User authentication token + * @returns SSO module with authentication methods + * @internal */ export function createSsoModule( axios: AxiosInstance, appId: string, userToken?: string -) { +): SsoModule { return { - /** - * Get current user sso access token - * @param {string} userid - User ID to include as path parameter - * @returns {Promise} Current user sso access_token - */ + // Get SSO access token for a specific user async getAccessToken(userid: string) { const url = `/apps/${appId}/auth/sso/accesstoken/${userid}`; - + // Prepare headers with both tokens if available const headers: Record = {}; - - + if (userToken) { - headers['on-behalf-of'] = `Bearer ${userToken}`; + headers["on-behalf-of"] = `Bearer ${userToken}`; } - + return axios.get(url, { headers }); }, }; diff --git a/src/modules/sso.types.ts b/src/modules/sso.types.ts new file mode 100644 index 0000000..ecb7459 --- /dev/null +++ b/src/modules/sso.types.ts @@ -0,0 +1,47 @@ +import { AxiosResponse } from "axios"; + +/** + * Response from SSO access token endpoint. + * @internal + */ +export interface SsoAccessTokenResponse { + access_token: string; +} + +/** + * SSO (Single Sign-On) module for managing SSO authentication. + * + * This module provides methods for retrieving SSO access tokens for users. + * These tokens allow you to authenticate Base44 users with external + * systems or services. + * + * This module is only available to use with a client in service role authentication mode, which means it can only be used in backend environments. + * + * @internal + * + * @example + * ```typescript + * // Access SSO module with service role + * const response = await base44.asServiceRole.sso.getAccessToken('user_123'); + * console.log(response.data.access_token); + * ``` + */ +export interface SsoModule { + /** + * Gets SSO access token for a specific user. + * + * Retrieves a Single Sign-On access token that can be used to authenticate + * a user with external services or systems. + * + * @param userid - The user ID to get the access token for. + * @returns Promise resolving to the SSO access token response. + * + * @example + * ```typescript + * // Get SSO access token for a user + * const response = await base44.asServiceRole.sso.getAccessToken('user_123'); + * console.log(response.access_token); + * ``` + */ + getAccessToken(userid: string): Promise; +} diff --git a/src/types.ts b/src/types.ts index 8580f1e..43291d9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,12 +1,73 @@ export * from "./modules/types.js"; -export type ModelFilterParams = { +/** + * Parameters for filtering, sorting, and paginating agent model data. + * + * Used in the agents module for querying agent conversations. Provides a structured way to specify query criteria, sorting, pagination, and field selection. + * + * @property q - Query object with field-value pairs for filtering. + * @property sort - Sort parameter. For example, "-created_date" for descending order. + * @property sort_by - Alternative sort parameter. Use either `sort` or `sort_by`. + * @property limit - Maximum number of results to return. + * @property skip - Number of results to skip. Used for pagination. + * @property fields - Array of field names to include in the response. + * + * @example + * ```typescript + * // Filter conversations by agent name + * const conversations = await base44.agents.listConversations({ + * q: { agent_name: 'support-bot' } + * }); + * ``` + * + * @example + * ```typescript + * // Filter conversations with sorting + * const conversations = await base44.agents.listConversations({ + * q: { status: 'active' }, + * sort: '-created_at' // Sort by created_at descending + * }); + * ``` + * + * @example + * ```typescript + * // Filter conversations with pagination + * const conversations = await base44.agents.listConversations({ + * q: { agent_name: 'support-bot' }, + * limit: 20, // Get 20 results + * skip: 40 // Skip first 40 (page 3) + * }); + * ``` + * + * @example + * ```typescript + * // Filter conversations with field selection + * const conversations = await base44.agents.listConversations({ + * q: { status: 'active' }, + * fields: ['id', 'agent_name', 'created_at'] + * }); + * ``` + * + * @example + * ```typescript + * // Filter conversations with multiple filters + * const conversations = await base44.agents.listConversations({ + * q: { + * agent_name: 'support-bot', + * 'metadata.priority': 'high', + * status: 'active' + * }, + * sort: '-updated_at', + * limit: 50, + * skip: 0 + * }); + * ``` + */ +export interface ModelFilterParams { q?: Record; sort?: string | null; sort_by?: string | null; limit?: number | null; skip?: number | null; fields?: string[] | null; -}; - - +} diff --git a/src/utils/auth-utils.ts b/src/utils/auth-utils.ts index 76a10aa..ff82c4c 100644 --- a/src/utils/auth-utils.ts +++ b/src/utils/auth-utils.ts @@ -1,25 +1,47 @@ -/** - * Utility functions for authentication and token handling - */ +import { + GetAccessTokenOptions, + SaveAccessTokenOptions, + RemoveAccessTokenOptions, + GetLoginUrlOptions, +} from "./auth-utils.types.js"; /** - * Retrieves an access token from either localStorage or URL parameters - * - * @param {Object} options - Configuration options - * @param {string} [options.storageKey='base44_access_token'] - The key to use in localStorage - * @param {string} [options.paramName='access_token'] - The URL parameter name - * @param {boolean} [options.saveToStorage=true] - Whether to save the token to localStorage if found in URL - * @param {boolean} [options.removeFromUrl=true] - Whether to remove the token from URL after retrieval - * @returns {string|null} The access token or null if not found + * Retrieves an access token from URL parameters or local storage. + * + * Low-level utility for manually retrieving tokens. In most cases, the Base44 client handles + * token management automatically. This function is useful for custom authentication flows or when you need direct access to stored tokens. Requires a browser environment and can't be used in the backend. + * + * @internal + * + * @param options - Configuration options for token retrieval. + * @returns The access token string if found, null otherwise. + * + * @example + * ```typescript + * // Get access token from URL or local storage + * const token = getAccessToken(); + * + * if (token) { + * console.log('User is authenticated'); + * } else { + * console.log('No token found, redirect to login'); + * } + * ``` + * @example + * ```typescript + * // Get access token from custom local storage key + * const token = getAccessToken({ storageKey: 'my_app_token' }); + * ``` + * @example + * ```typescript + * // Get access token from URL but don't save or remove it + * const token = getAccessToken({ + * saveToStorage: false, + * removeFromUrl: false + * }); + * ``` */ -export function getAccessToken( - options: { - storageKey?: string; - paramName?: string; - saveToStorage?: boolean; - removeFromUrl?: boolean; - } = {} -) { +export function getAccessToken(options: GetAccessTokenOptions = {}) { const { storageKey = "base44_access_token", paramName = "access_token", @@ -37,7 +59,7 @@ export function getAccessToken( // If token found in URL if (token) { - // Save token to localStorage if requested + // Save token to local storage if requested if (saveToStorage) { saveAccessToken(token, { storageKey }); } @@ -58,13 +80,13 @@ export function getAccessToken( } } - // If no token in URL, try localStorage + // If no token in URL, try local storage if (typeof window !== "undefined" && window.localStorage) { try { token = window.localStorage.getItem(storageKey); return token; } catch (e) { - console.error("Error retrieving token from localStorage:", e); + console.error("Error retrieving token from local storage:", e); } } @@ -72,18 +94,38 @@ export function getAccessToken( } /** - * Saves an access token to localStorage + * Saves an access token to local storage. + * + * Low-level utility for manually saving tokens. In most cases, the Base44 client handles token management automatically. This function is useful for custom authentication flows or managing custom tokens. Requires a browser environment and can't be used in the backend. + * + * @internal + * + * @param token - The access token string to save. + * @param options - Configuration options for saving the token. + * @returns Returns`true` if the token was saved successfully, `false` otherwise. + * + * @example + * ```typescript + * // Save access token after login + * const response = await base44.auth.loginViaEmailPassword(email, password); + * const success = saveAccessToken(response.access_token, {}); * - * @param {string} token - The access token to save - * @param {Object} options - Configuration options - * @param {string} [options.storageKey='base44_access_token'] - The key to use in localStorage - * @returns {boolean} Success status + * if (success) { + * console.log('User is now authenticated'); + * // Token is now available for future page loads + * } + * ``` + * @example + * ```typescript + * // Save access token to local storage using custom key + * const success = saveAccessToken(token, { + * storageKey: `my_custom_token_key` + * }); + * ``` */ export function saveAccessToken( token: string, - options: { - storageKey?: string; - } + options: SaveAccessTokenOptions ) { const { storageKey = "base44_access_token" } = options; @@ -97,19 +139,36 @@ export function saveAccessToken( window.localStorage.setItem("token", token); return true; } catch (e) { - console.error("Error saving token to localStorage:", e); + console.error("Error saving token to local storage:", e); return false; } } /** - * Removes the access token from localStorage + * Removes the access token from local storage. + * + * Low-level utility for manually removing tokens from the browser's local storage. In most cases, the Base44 client handles token management automatically. For standard logout flows, use {@linkcode AuthModule.logout | base44.auth.logout()} instead, which handles token removal and redirects automatically. This function is useful for custom authentication flows or when you need to manually remove tokens. Requires a browser environment and can't be used in the backend. + * + * @internal * - * @param {Object} options - Configuration options - * @param {string} [options.storageKey='base44_access_token'] - The key to use in localStorage - * @returns {boolean} Success status + * @param options - Configuration options for token removal. + * @returns Returns `true` if the token was removed successfully, `false` otherwise. + * + * @example + * ```typescript + * // Remove custom token key + * const success = removeAccessToken({ + * storageKey: 'my_custom_token_key' + * }); + * ``` + * + * @example + * ```typescript + * // Standard logout flow with token removal and redirect + * base44.auth.logout('/login'); + * ``` */ -export function removeAccessToken(options: { storageKey?: string }) { +export function removeAccessToken(options: RemoveAccessTokenOptions) { const { storageKey = "base44_access_token" } = options; if (typeof window === "undefined" || !window.localStorage) { @@ -120,29 +179,34 @@ export function removeAccessToken(options: { storageKey?: string }) { window.localStorage.removeItem(storageKey); return true; } catch (e) { - console.error("Error removing token from localStorage:", e); + console.error("Error removing token from local storage:", e); return false; } } /** - * Constructs the absolute URL for the login page - * - * @param {string} nextUrl - URL to redirect back to after login - * @param {Object} options - Configuration options - * @param {string} options.serverUrl - Server URL (e.g., 'https://base44.app') - * @param {string|number} options.appId - Application ID - * @param {string} [options.loginPath='/login'] - Path to the login endpoint - * @returns {string} The complete login URL + * Constructs the absolute URL for the login page with a redirect parameter. + * + * Low-level utility for building login URLs. For standard login redirects, use {@linkcode AuthModule.redirectToLogin | base44.auth.redirectToLogin()} instead, which handles this automatically. This function is useful when you need to construct login URLs without a client instance or for custom authentication flows. + * + * @internal + * + * @param nextUrl - The URL to redirect to after successful login. + * @param options - Configuration options. + * @returns The complete login URL with encoded redirect parameters. + * + * @example + * ```typescript + * // Redirect to login page + * const loginUrl = getLoginUrl('/dashboard', { + * serverUrl: 'https://base44.app', + * appId: 'my-app-123' + * }); + * window.location.href = loginUrl; + * // User will be redirected back to /dashboard after login + * ``` */ -export function getLoginUrl( - nextUrl: string, - options: { - serverUrl: string; - appId: string; - loginPath?: string; - } -) { +export function getLoginUrl(nextUrl: string, options: GetLoginUrlOptions) { const { serverUrl, appId, loginPath = "/login" } = options; if (!serverUrl || !appId) { diff --git a/src/utils/auth-utils.types.ts b/src/utils/auth-utils.types.ts new file mode 100644 index 0000000..9a6bd2e --- /dev/null +++ b/src/utils/auth-utils.types.ts @@ -0,0 +1,168 @@ +/** + * Configuration options for retrieving an access token. + * + * @internal + * + * @example + * ```typescript + * // Get access token from URL or local storage using default options + * const token = getAccessToken(); + * ``` + * + * @example + * ```typescript + * // Get access token from custom local storage key + * const token = getAccessToken({ storageKey: 'my_app_token' }); + * ``` + * + * @example + * ```typescript + * // Get token from URL but don't save or remove from URL + * const token = getAccessToken({ + * saveToStorage: false, + * removeFromUrl: false + * }); + * ``` + */ +export interface GetAccessTokenOptions { + /** + * The key to use when storing or retrieving the token in local storage. + * @default 'base44_access_token' + */ + storageKey?: string; + + /** + * The URL parameter name to check for the access token. + * @default 'access_token' + */ + paramName?: string; + + /** + * Whether to save the token to local storage if found in the URL. + * @default true + */ + saveToStorage?: boolean; + + /** + * Whether to remove the token from the URL after retrieval for security. + * @default true + */ + removeFromUrl?: boolean; +} + +/** + * Configuration options for saving an access token. + * + * @internal + * + * @example + * ```typescript + * // Use default storage key + * saveAccessToken('my-token-123', {}); + * + * // Use custom storage key + * saveAccessToken('my-token-123', { storageKey: 'my_app_token' }); + * ``` + */ +export interface SaveAccessTokenOptions { + /** + * The key to use when storing the token in local storage. + * @default 'base44_access_token' + */ + storageKey?: string; +} + +/** + * Configuration options for removing an access token. + * + * @internal + * + * @example + * ```typescript + * // Remove token from default storage key + * removeAccessToken({}); + * + * // Remove token from custom storage key + * removeAccessToken({ storageKey: 'my_app_token' }); + * ``` + */ +export interface RemoveAccessTokenOptions { + /** + * The key to use when removing the token from local storage. + * @default 'base44_access_token' + */ + storageKey?: string; +} + +/** + * Configuration options for constructing a login URL. + * + * @internal + * + * @example + * ```typescript + * const loginUrl = getLoginUrl('/dashboard', { + * serverUrl: 'https://base44.app', + * appId: 'my-app-123' + * }); + * // Returns: 'https://base44.app/login?from_url=%2Fdashboard&app_id=my-app-123' + * + * // Custom login path + * const loginUrl = getLoginUrl('/dashboard', { + * serverUrl: 'https://base44.app', + * appId: 'my-app-123', + * loginPath: '/auth/login' + * }); + * ``` + */ +export interface GetLoginUrlOptions { + /** + * The base server URL (e.g., 'https://base44.app'). + */ + serverUrl: string; + + /** + * The app ID. + */ + appId: string; + + /** + * The path to the login endpoint. + * @default '/login' + */ + loginPath?: string; +} + +/** + * Type definition for getAccessToken function. + * @internal + */ +export type GetAccessTokenFunction = ( + options?: GetAccessTokenOptions +) => string | null; + +/** + * Type definition for saveAccessToken function. + * @internal + */ +export type SaveAccessTokenFunction = ( + token: string, + options: SaveAccessTokenOptions +) => boolean; + +/** + * Type definition for removeAccessToken function. + * @internal + */ +export type RemoveAccessTokenFunction = ( + options: RemoveAccessTokenOptions +) => boolean; + +/** + * Type definition for getLoginUrl function. + * @internal + */ +export type GetLoginUrlFunction = ( + nextUrl: string, + options: GetLoginUrlOptions +) => string; diff --git a/src/utils/axios-client.ts b/src/utils/axios-client.ts index ec4dc52..957835c 100644 --- a/src/utils/axios-client.ts +++ b/src/utils/axios-client.ts @@ -1,13 +1,59 @@ import axios from "axios"; import { isInIFrame } from "./common.js"; import { v4 as uuidv4 } from "uuid"; +import type { Base44ErrorJSON } from "./axios-client.types.js"; +/** + * Custom error class for Base44 SDK errors. + * + * This error is thrown when API requests fail. It extends the standard `Error` class and includes additional information about the HTTP status, error code, and response data from the server. + * + * @example + * ```typescript + * try { + * await client.entities.Todo.get('invalid-id'); + * } catch (error) { + * if (error instanceof Base44Error) { + * console.error('Status:', error.status); // 404 + * console.error('Message:', error.message); // "Not found" + * console.error('Code:', error.code); // "NOT_FOUND" + * console.error('Data:', error.data); // Full response data + * } + * } + * ``` + * + */ export class Base44Error extends Error { + /** + * HTTP status code of the error. + */ status: number; + + /** + * Error code from the API. + */ code: string; + + /** + * Full response data from the server containing error details. + */ data: any; + + /** + * The original error object from Axios. + */ originalError: unknown; + /** + * Creates a new Base44Error instance. + * + * @param message - Human-readable error message + * @param status - HTTP status code + * @param code - Error code from the API + * @param data - Full response data from the server + * @param originalError - Original axios error object + * @internal + */ constructor( message: string, status: number, @@ -23,8 +69,34 @@ export class Base44Error extends Error { this.originalError = originalError; } - // Add a method to safely serialize this error without circular references - toJSON() { + /** + * Serializes the error to a JSON-safe object. + * + * Useful for logging or sending error information to external services + * without circular reference issues. + * + * @returns JSON-safe representation of the error. + * + * @example + * ```typescript + * try { + * await client.entities.Todo.get('invalid-id'); + * } catch (error) { + * if (error instanceof Base44Error) { + * const json = error.toJSON(); + * console.log(json); + * // { + * // name: "Base44Error", + * // message: "Not found", + * // status: 404, + * // code: "NOT_FOUND", + * // data: { ... } + * // } + * } + * } + * ``` + */ + toJSON(): Base44ErrorJSON { return { name: this.name, message: this.message, @@ -36,9 +108,11 @@ export class Base44Error extends Error { } /** - * Safely logs error information without circular references - * @param {string} prefix - Prefix for the log message - * @param {Error} error - The error to log + * Safely logs error information without circular references. + * + * @param prefix - Prefix for the log message + * @param error - The error to log + * @internal */ function safeErrorLog(prefix: string, error: unknown) { if (error instanceof Base44Error) { @@ -58,15 +132,18 @@ function safeErrorLog(prefix: string, error: unknown) { } /** - * Creates an axios client with default configuration and interceptors - * @param {Object} options - Client configuration options - * @param {string} options.baseURL - Base URL for all requests - * @param {Object} options.headers - Additional headers - * @param {string} options.token - Auth token - * @param {boolean} options.requiresAuth - Whether the application requires authentication - * @param {string|number} options.appId - Application ID (needed for login redirect) - * @param {string} options.serverUrl - Server URL (needed for login redirect) - * @returns {import('axios').AxiosInstance} Configured axios instance + * Creates an axios client with default configuration and interceptors. + * + * Sets up an axios instance with: + * - Default headers + * - Authentication token injection + * - Response data unwrapping + * - Error transformation to Base44Error + * - iframe messaging support + * + * @param options - Client configuration options + * @returns Configured axios instance + * @internal */ export function createAxiosClient({ baseURL, @@ -179,3 +256,6 @@ export function createAxiosClient({ return client; } + +// Re-export types +export type { Base44ErrorJSON } from "./axios-client.types.js"; diff --git a/src/utils/axios-client.types.ts b/src/utils/axios-client.types.ts new file mode 100644 index 0000000..0251dee --- /dev/null +++ b/src/utils/axios-client.types.ts @@ -0,0 +1,32 @@ +/** + * JSON representation of a Base44Error. + * + * This is the structure returned by {@linkcode Base44Error.toJSON | Base44Error.toJSON()}. + * Useful for logging or sending error information to external services. + */ +export interface Base44ErrorJSON { + /** + * The error name, always "Base44Error". + */ + name: string; + + /** + * Human-readable error message. + */ + message: string; + + /** + * HTTP status code of the error. + */ + status: number; + + /** + * Error code from the API. + */ + code: string; + + /** + * Full response data from the server containing error details. + */ + data: any; +} diff --git a/src/utils/socket-utils.ts b/src/utils/socket-utils.ts index 0fc2f97..2f73131 100644 --- a/src/utils/socket-utils.ts +++ b/src/utils/socket-utils.ts @@ -1,13 +1,13 @@ import { Socket, io } from "socket.io-client"; import { getAccessToken } from "./auth-utils.js"; -export type RoomsSocketConfig = { +export interface RoomsSocketConfig { serverUrl: string; mountPath: string; transports: string[]; appId: string; token?: string; -}; +} export type TSocketRoom = string; export type TJsonStr = string; diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..b4a7259 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./src/index.ts"], + "out": "docs/content", + "plugin": [ + "typedoc-plugin-markdown", + "./scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-plugin.js" + ], + "fileExtension": ".mdx", + "excludePrivate": true, + "excludeProtected": true, + "excludeInternal": true, + "excludeExternals": true, + "readme": "none", + "gitRevision": "main", + "sort": ["source-order"], + "kindSortOrder": [ + "Project", + "Module", + "Namespace", + "Enum", + "Class", + "Interface", + "TypeAlias", + "Constructor", + "Property", + "Method", + "Function", + "Accessor", + "Variable" + ], + "entryFileName": "README.mdx", + "maxTypeConversionDepth": 2, + "hideBreadcrumbs": true, + "disableSources": true, + "hidePageTitle": true +} diff --git a/writing-docs.md b/writing-docs.md new file mode 100644 index 0000000..e3a6e38 --- /dev/null +++ b/writing-docs.md @@ -0,0 +1,58 @@ +# SDK Documentation + +Documentation for the SDK is generated using TypeDoc. The TypeDoc files are post-processed to convert them to Mintlify format. You can preview the output locally, and then push it to the docs repo to delpoy it. + +## Before getting started +* Install the repo dependencies: `npm install` +* Install the Mintlify CLI: `npm i -g mint` + +## Generate docs +Open the terminal in the repo and run `npm run create-docs`. The docs files appear under `/docs/content`. + +## Preview docs locally with Mintlify +1. In the terminal, navigate to the `docs` folder. +1. Run `mint dev`. The docs preview opens in your browser. + +> If you notice that the names appearing for the sections of the docs menu aren't right, you may need to adjust `scripts/mintlify-post-processing/category-map.json`. This file maps the names of the subfolders in `/docs/content` to the desired section names in the reference. + +### Category mapping +`scripts/mintlify-post-processing/category-map.json` maps the names of the output folders from TypeDoc to the names of the categories that you want to appear in the docs. Only folder names that are mapped in this file appear in the final docs and the local preview. + +For example, if you map `interfaces` to `Modules`, the files in `docs/content/interfaces` appear in the docs under a **Modules** category. + +The names of the TypeDoc output folders are: `classes`, `functions`, `interfaces`, `type-aliases`. + +## Control which types appear in the docs +`scripts/mintlify-post-processing/types-to-expose.json` lists the TypeDoc types that the post-processing script keeps in the generated reference. Add or remove type names in that file to expose different SDK areas (for example, to surface a new type or hide one that is not ready for publication). After editing the list, rerun `npm run create-docs` so the Mintlify-ready content reflects the updated exposure set. + +## Append additional articles to an existing page +Sometimes TypeDoc produces a helper interface or type that should live inside a broader article instead of owning its own page (for example, the `EntityHandler` interface that belongs with the `EntitiesModule`). Use `scripts/mintlify-post-processing/appended-articles.json` to stitch those auxiliary pages into a host article during post-processing. + +The file maps the host doc (left side) to one or more articles to append (right side). Paths are relative to `docs/content` and omit the `.mdx` extension. You can provide either a string or an array of strings: + +```json +{ + "interfaces/EntitiesModule": [ + "interfaces/EntityHandler", + "interfaces/EntityFilterOptions" + ] +} +``` + +When you run `npm run create-docs`, the post-processing script appends each listed article to the host page under a new `##` heading, updates the panel/table-of-contents links, and then deletes the standalone appended files so they no longer appear in navigation. Edit the JSON mapping and rerun the command anytime you want to combine or separate pages. + +### Toggle Mintlify Panel output +Both the TypeDoc plugin and the post-processing scripts can insert Mintlify `Panel` components (used for the “On this page” navigation). This behavior is now optional and **disabled by default** so the generated docs contain no panels unless explicitly requested. + +- Leave `MINTLIFY_INCLUDE_PANELS` unset (default) to skip inserting panels anywhere in the pipeline. +- Set `MINTLIFY_INCLUDE_PANELS=true` before running `npm run create-docs` if you want to re-enable the legacy Panel output for a run. + +Because both the TypeDoc plugin and the appended-article merger consult the same environment variable, flipping it on/off controls the entire docs build without needing code changes. + +## Push SDK docs to the Mintlify docs repository + +After generating and reviewing the docs, you can push them to the `base44/mintlify-docs` repo to deploy them. + +1. In the terminal, run `npm run push-docs -- --branch `. If the branch already exists, your changes are added to the ones already on the branch. Otherwise, the script creates a new branch with the chosen name. +1. Open the [docs repo](https://github.com/base44-dev/mintlify-docs) and created a PR for your branch. +1. Preview your docs using the [Mintlify dashboard](https://dashboard.mintlify.com/base44/base44?section=previews). \ No newline at end of file