diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d838cef57a..83686a3f57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,6 +147,8 @@ jobs: - run: npm ci - name: Build types run: npm run build:types + - name: Test Types + run: npm run test:types check-mongo: strategy: matrix: diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000000..36526782d6 --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +types/tests.ts +types/eslint.config.mjs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27e7e114a5..01c88df10c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,7 @@ - [Good to Know](#good-to-know) - [Troubleshooting](#troubleshooting) - [Please Do's](#please-dos) + - [TypeScript Tests](#typescript-tests) - [Test against Postgres](#test-against-postgres) - [Postgres with Docker](#postgres-with-docker) - [Breaking Changes](#breaking-changes) @@ -239,6 +240,15 @@ Once you have babel running in watch mode, you can start making changes to parse * Mocks belong in the `spec/support` folder. * Please consider if any changes to the [docs](http://docs.parseplatform.org) are needed or add additional sections in the case of an enhancement or feature. +#### TypeScript Tests + +Type tests are located in [/types/tests.ts](/types/tests.ts) and are responsible for ensuring that the type generation for each class is behaving as expected. Types are generated by manually running the script `npm run build:types`. The generated types are `.d.ts` files located in [/types](/types) and must not be manually changed after generation. + +> [!CAUTION] +> An exemption are type changes to `src/Options/index.js` which must be manually updated in `types/Options/index.d.ts`, as these types are not generated via a script. + +When developing type definitions you can run `npm run watch:ts` in order to rebuild your changes automatically upon each save. Use `npm run test:types` in order to run types tests against generated `.d.ts` files. + ### Test against Postgres If your pull request introduces a change that may affect the storage or retrieval of objects, you may want to make sure it plays nice with Postgres. diff --git a/eslint.config.js b/eslint.config.js index e7fae3b833..d1cbac6e5e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,7 +3,7 @@ const babelParser = require("@babel/eslint-parser"); const globals = require("globals"); module.exports = [ { - ignores: ["**/lib/**", "**/coverage/**", "**/out/**"], + ignores: ["**/lib/**", "**/coverage/**", "**/out/**", "**/types/**"], }, js.configs.recommended, { diff --git a/package-lock.json b/package-lock.json index a472db0129..50d044d05f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "cross-env": "7.0.3", "deep-diff": "1.0.2", "eslint": "9.23.0", + "eslint-plugin-expect-type": "0.6.2", "flow-bin": "0.266.1", "form-data": "4.0.2", "globals": "16.0.0", @@ -102,6 +103,7 @@ "prettier": "2.0.5", "semantic-release": "24.2.3", "typescript": "5.8.2", + "typescript-eslint": "8.29.0", "yaml": "2.7.1" }, "engines": { @@ -6315,6 +6317,403 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz", + "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/type-utils": "8.29.0", + "@typescript-eslint/utils": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz", + "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/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, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", + "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/scope-manager/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz", + "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/utils": "8.29.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/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, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/@typescript-eslint/types": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", @@ -6380,6 +6779,133 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@typescript-eslint/utils": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz", + "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/utils/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, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", @@ -9394,6 +9920,25 @@ } } }, + "node_modules/eslint-plugin-expect-type": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-expect-type/-/eslint-plugin-expect-type-0.6.2.tgz", + "integrity": "sha512-XWgtpplzr6GlpPUFG9ZApnSTv7QJXAPNN6hNmrlleVVCkAK23f/3E2BiCoA3Xtb0rIKfVKh7TLe+D1tcGt8/1w==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^6.10.0 || ^7.0.1 || ^8", + "fs-extra": "^11.1.1", + "get-tsconfig": "^4.8.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@typescript-eslint/parser": ">=6", + "eslint": ">=7", + "typescript": ">=4" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -10356,7 +10901,6 @@ "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.266.1.tgz", "integrity": "sha512-c1lg1E8SDcuPSkrOeH0JTcNKMZOzXvqX2DuuXJ0amZec15uyuIi8QlGTO3OzYssMT/kwFdo5vviJqSUI/BNFbw==", "dev": true, - "license": "MIT", "bin": { "flow": "cli.js" }, @@ -10917,6 +11461,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -11259,6 +11815,12 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/graphql": { "version": "16.10.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", @@ -19080,6 +19642,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/responselike": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", @@ -21182,6 +21753,28 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.0.tgz", + "integrity": "sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.29.0", + "@typescript-eslint/parser": "8.29.0", + "@typescript-eslint/utils": "8.29.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -26427,6 +27020,243 @@ "@types/webidl-conversions": "*" } }, + "@typescript-eslint/eslint-plugin": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz", + "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/type-utils": "8.29.0", + "@typescript-eslint/utils": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true + }, + "@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + } + }, + "eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true + }, + "ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "requires": {} + } + } + }, + "@typescript-eslint/parser": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz", + "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "requires": {} + } + } + }, + "@typescript-eslint/scope-manager": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", + "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true + }, + "@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + } + }, + "eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true + } + } + }, + "@typescript-eslint/type-utils": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz", + "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/utils": "8.29.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "requires": {} + } + } + }, "@typescript-eslint/types": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", @@ -26469,6 +27299,83 @@ } } }, + "@typescript-eslint/utils": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz", + "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "requires": {} + } + } + }, "@typescript-eslint/visitor-keys": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", @@ -28743,6 +29650,17 @@ } } }, + "eslint-plugin-expect-type": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-expect-type/-/eslint-plugin-expect-type-0.6.2.tgz", + "integrity": "sha512-XWgtpplzr6GlpPUFG9ZApnSTv7QJXAPNN6hNmrlleVVCkAK23f/3E2BiCoA3Xtb0rIKfVKh7TLe+D1tcGt8/1w==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "^6.10.0 || ^7.0.1 || ^8", + "fs-extra": "^11.1.1", + "get-tsconfig": "^4.8.1" + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -29711,6 +30629,15 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -29970,6 +30897,12 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "graphql": { "version": "16.10.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", @@ -35454,6 +36387,12 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, + "resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true + }, "responselike": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", @@ -36938,6 +37877,17 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true }, + "typescript-eslint": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.0.tgz", + "integrity": "sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==", + "dev": true, + "requires": { + "@typescript-eslint/eslint-plugin": "8.29.0", + "@typescript-eslint/parser": "8.29.0", + "@typescript-eslint/utils": "8.29.0" + } + }, "uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", diff --git a/package.json b/package.json index 9208bf20d4..bc3a479bd2 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "cross-env": "7.0.3", "deep-diff": "1.0.2", "eslint": "9.23.0", + "eslint-plugin-expect-type": "0.6.2", "flow-bin": "0.266.1", "form-data": "4.0.2", "globals": "16.0.0", @@ -109,6 +110,7 @@ "prettier": "2.0.5", "semantic-release": "24.2.3", "typescript": "5.8.2", + "typescript-eslint": "8.29.0", "yaml": "2.7.1" }, "scripts": { @@ -122,6 +124,7 @@ "build": "babel src/ -d lib/ --copy-files --extensions '.ts,.js'", "build:types": "tsc", "watch": "babel --watch src/ -d lib/ --copy-files", + "watch:ts": "tsc --watch", "test:mongodb:runnerstart": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=$npm_config_dbversion} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017", "test:mongodb:testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=$npm_config_dbversion} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine", "test:mongodb": "npm run test:mongodb:runnerstart --dbversion=$npm_config_dbversion && npm run test:mongodb:testonly --dbversion=$npm_config_dbversion", @@ -132,6 +135,7 @@ "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=8.0.4} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017", "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=8.0.4} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine", "test": "npm run testonly", + "test:types": "eslint types/tests.ts -c ./types/eslint.config.mjs", "posttest": "cross-env mongodb-runner stop --all", "coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=8.0.4} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 nyc jasmine", "start": "node ./bin/parse-server", diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.ts similarity index 97% rename from src/LiveQuery/ParseLiveQueryServer.js rename to src/LiveQuery/ParseLiveQueryServer.ts index 5748f7f10f..3e6048c345 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.ts @@ -3,6 +3,7 @@ import Parse from 'parse/node'; import { Subscription } from './Subscription'; import { Client } from './Client'; import { ParseWebSocketServer } from './ParseWebSocketServer'; +// @ts-ignore import logger from '../logger'; import RequestSchema from './RequestSchema'; import { matchesQuery, queryHash } from './QueryTools'; @@ -26,13 +27,17 @@ import { isDeepStrictEqual } from 'util'; import deepcopy from 'deepcopy'; class ParseLiveQueryServer { - clients: Map; + server: any; + config: any; + clients: Map; // className -> (queryHash -> subscription) - subscriptions: Object; - parseWebSocketServer: Object; + subscriptions: Map; + parseWebSocketServer: any; keyPairs: any; // The subscriber we use to get object update from publisher - subscriber: Object; + subscriber: any; + authCache: any; + cacheController: any; constructor(server: any, config: any = {}, parseServerConfig: any = {}) { this.server = server; @@ -168,7 +173,7 @@ class ParseLiveQueryServer { // Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes. // Message.originalParseObject is the original ParseObject. - async _onAfterDelete(message: any): void { + async _onAfterDelete(message: any): Promise { logger.verbose(Parse.applicationId + 'afterDelete is triggered'); let deletedParseObject = message.currentParseObject.toJSON(); @@ -197,7 +202,7 @@ class ParseLiveQueryServer { const acl = message.currentParseObject.getACL(); // Check CLP const op = this._getCLPOperation(subscription.query); - let res = {}; + let res: any = {}; try { await this._matchesCLP( classLevelPermissions, @@ -261,7 +266,7 @@ class ParseLiveQueryServer { // Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes. // Message.originalParseObject is the original ParseObject. - async _onAfterSave(message: any): void { + async _onAfterSave(message: any): Promise { logger.verbose(Parse.applicationId + 'afterSave is triggered'); let originalParseObject = null; @@ -309,7 +314,7 @@ class ParseLiveQueryServer { // Set current ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let currentACLCheckingPromise; - let res = {}; + let res: any = {}; if (!isCurrentSubscriptionMatched) { currentACLCheckingPromise = Promise.resolve(false); } else { @@ -548,7 +553,7 @@ class ParseLiveQueryServer { } } - getAuthForSessionToken(sessionToken: ?string): Promise<{ auth: ?Auth, userId: ?string }> { + getAuthForSessionToken(sessionToken?: string): Promise<{ auth?: Auth, userId?: string }> { if (!sessionToken) { return Promise.resolve({}); } @@ -565,7 +570,7 @@ class ParseLiveQueryServer { }) .catch(error => { // There was an error with the session token - const result = {}; + const result: any = {}; if (error && error.code === Parse.Error.INVALID_SESSION_TOKEN) { result.error = error; this.authCache.set(sessionToken, Promise.resolve(result), this.config.cacheTimeout); @@ -579,12 +584,12 @@ class ParseLiveQueryServer { } async _matchesCLP( - classLevelPermissions: ?any, - object: any, - client: any, - requestId: number, - op: string - ): any { + classLevelPermissions?: any, + object?: any, + client?: any, + requestId?: number, + op?: string + ): Promise { // try to match on user first, less expensive than with roles const subscriptionInfo = client.getSubscriptionInfo(requestId); const aclGroup = ['*']; @@ -621,12 +626,12 @@ class ParseLiveQueryServer { } async _filterSensitiveData( - classLevelPermissions: ?any, - res: any, - client: any, - requestId: number, - op: string, - query: any + classLevelPermissions?: any, + res?: any, + client?: any, + requestId?: number, + op?: string, + query?: any ) { const subscriptionInfo = client.getSubscriptionInfo(requestId); const aclGroup = ['*']; @@ -718,7 +723,7 @@ class ParseLiveQueryServer { }); } - async getAuthFromClient(client: any, requestId: number, sessionToken: string) { + async getAuthFromClient(client: any, requestId: number, sessionToken?: string) { const getSessionFromClient = () => { const subscriptionInfo = client.getSubscriptionInfo(requestId); if (typeof subscriptionInfo === 'undefined') { @@ -772,7 +777,7 @@ class ParseLiveQueryServer { return false; } - async _handleConnect(parseWebsocket: any, request: any): any { + async _handleConnect(parseWebsocket: any, request: any): Promise { if (!this._validateKeys(request, this.keyPairs)) { Client.pushError(parseWebsocket, 4, 'Key in request is not valid'); logger.error('Key in request is not valid'); @@ -796,6 +801,7 @@ class ParseLiveQueryServer { sessionToken: request.sessionToken, useMasterKey: client.hasMasterKey, installationId: request.installationId, + user: undefined, }; const trigger = getTrigger('@Connect', 'beforeConnect', Parse.applicationId); if (trigger) { @@ -845,7 +851,7 @@ class ParseLiveQueryServer { return isValid; } - async _handleSubscribe(parseWebsocket: any, request: any): any { + async _handleSubscribe(parseWebsocket: any, request: any): Promise { // If we can not find this client, return error to client if (!Object.prototype.hasOwnProperty.call(parseWebsocket, 'clientId')) { Client.pushError( @@ -918,7 +924,7 @@ class ParseLiveQueryServer { } // Add subscriptionInfo to client - const subscriptionInfo = { + const subscriptionInfo: any = { subscription: subscription, }; // Add selected fields, sessionToken and installationId for this subscription if necessary diff --git a/src/ParseServer.js b/src/ParseServer.ts similarity index 98% rename from src/ParseServer.js rename to src/ParseServer.ts index 7b09a8c335..c0df4f3431 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.ts @@ -56,6 +56,11 @@ const connections = new Connections(); // ParseServer works like a constructor of an express app. // https://parseplatform.org/parse-server/api/master/ParseServerOptions.html class ParseServer { + _app: any; + config: any; + server: any; + expressApp: any; + liveQueryServer: any; /** * @constructor * @param {ParseServerOptions} options the parse server initialization options @@ -111,7 +116,7 @@ class ParseServer { const diff = validateKeyNames(options, optionsBlueprint); if (diff.length > 0) { - const logger = logging.logger; + const logger = (logging as any).logger; logger.error(`Invalid key(s) found in Parse Server configuration: ${diff.join(', ')}`); } @@ -129,7 +134,7 @@ class ParseServer { Config.validateOptions(options); const allControllers = controllers.getControllers(options); - options.state = 'initialized'; + (options as any).state = 'initialized'; this.config = Config.put(Object.assign({}, options, allControllers)); this.config.masterKeyIpsStore = new Map(); this.config.maintenanceKeyIpsStore = new Map(); @@ -140,7 +145,7 @@ class ParseServer { * Starts Parse Server as an express app; this promise resolves when Parse Server is ready to accept requests. */ - async start() { + async start(): Promise { try { if (this.config.state === 'ok') { return this; @@ -331,7 +336,7 @@ class ParseServer { if (!process.env.TESTING) { //This causes tests to spew some useless warnings, so disable in test /* istanbul ignore next */ - process.on('uncaughtException', err => { + process.on('uncaughtException', (err: any) => { if (err.code === 'EADDRINUSE') { // user-friendly message for this common error process.stderr.write(`Unable to listen on port ${err.port}. The port is already in use.`); @@ -497,7 +502,7 @@ class ParseServer { httpServer, config: LiveQueryServerOptions, options: ParseServerOptions - ) { + ): Promise { if (!httpServer || (config && config.port)) { var app = express(); httpServer = require('http').createServer(app); diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts diff --git a/tsconfig.json b/tsconfig.json index 0e37d93dce..22836c54d2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,16 @@ { "compilerOptions": { + "module": "commonjs", + "target": "es2015", "declaration": true, "emitDeclarationOnly": true, "outDir": "types", "noImplicitAny": false, "allowJs": false, - "skipLibCheck": true + "skipLibCheck": true, + "paths": { + "deepcopy": ["./types/@types/deepcopy"], + } }, "include": [ "src/*.ts" diff --git a/types/@types/@parse/fs-files-adapter/index.d.ts b/types/@types/@parse/fs-files-adapter/index.d.ts new file mode 100644 index 0000000000..ffab73832d --- /dev/null +++ b/types/@types/@parse/fs-files-adapter/index.d.ts @@ -0,0 +1,5 @@ +// TODO: Remove when @parse/fs-files-adapter is typed +declare module '@parse/fs-files-adapter' { + const FileSystemAdapter: any; + export default FileSystemAdapter; +} diff --git a/types/@types/deepcopy/index.d.ts b/types/@types/deepcopy/index.d.ts new file mode 100644 index 0000000000..e5da2a3238 --- /dev/null +++ b/types/@types/deepcopy/index.d.ts @@ -0,0 +1,5 @@ +// TODO: Remove when https://github.com/sasaplus1/deepcopy.js/issues/278 is fixed +declare type Customizer = (value: any, valueType: string) => unknown; +declare type Options = Customizer | { customizer: Customizer }; +declare function deepcopy(value: T, options?: Options): T; +export default deepcopy; diff --git a/types/LiveQuery/ParseLiveQueryServer.d.ts b/types/LiveQuery/ParseLiveQueryServer.d.ts new file mode 100644 index 0000000000..d5966144b5 --- /dev/null +++ b/types/LiveQuery/ParseLiveQueryServer.d.ts @@ -0,0 +1,40 @@ +import { Auth } from '../Auth'; +declare class ParseLiveQueryServer { + server: any; + config: any; + clients: Map; + subscriptions: Map; + parseWebSocketServer: any; + keyPairs: any; + subscriber: any; + authCache: any; + cacheController: any; + constructor(server: any, config?: any, parseServerConfig?: any); + connect(): Promise; + shutdown(): Promise; + _createSubscribers(): void; + _inflateParseObject(message: any): void; + _onAfterDelete(message: any): Promise; + _onAfterSave(message: any): Promise; + _onConnect(parseWebsocket: any): void; + _matchesSubscription(parseObject: any, subscription: any): boolean; + _clearCachedRoles(userId: string): Promise; + getAuthForSessionToken(sessionToken?: string): Promise<{ + auth?: Auth; + userId?: string; + }>; + _matchesCLP(classLevelPermissions?: any, object?: any, client?: any, requestId?: number, op?: string): Promise; + _filterSensitiveData(classLevelPermissions?: any, res?: any, client?: any, requestId?: number, op?: string, query?: any): Promise; + _getCLPOperation(query: any): "get" | "find"; + _verifyACL(acl: any, token: string): Promise; + getAuthFromClient(client: any, requestId: number, sessionToken?: string): Promise; + _checkWatchFields(client: any, requestId: any, message: any): any; + _matchesACL(acl: any, client: any, requestId: number): Promise; + _handleConnect(parseWebsocket: any, request: any): Promise; + _hasMasterKey(request: any, validKeyPairs: any): boolean; + _validateKeys(request: any, validKeyPairs: any): boolean; + _handleSubscribe(parseWebsocket: any, request: any): Promise; + _handleUpdateSubscription(parseWebsocket: any, request: any): any; + _handleUnsubscribe(parseWebsocket: any, request: any, notifyClient?: boolean): any; +} +export { ParseLiveQueryServer }; diff --git a/types/Options/index.d.ts b/types/Options/index.d.ts new file mode 100644 index 0000000000..ac1c71e886 --- /dev/null +++ b/types/Options/index.d.ts @@ -0,0 +1,251 @@ +// This file is manually updated to match src/Options/index.js until typed +import { AnalyticsAdapter } from '../Adapters/Analytics/AnalyticsAdapter'; +import { CacheAdapter } from '../Adapters/Cache/CacheAdapter'; +import { MailAdapter } from '../Adapters/Email/MailAdapter'; +import { FilesAdapter } from '../Adapters/Files/FilesAdapter'; +import { LoggerAdapter } from '../Adapters/Logger/LoggerAdapter'; +import { PubSubAdapter } from '../Adapters/PubSub/PubSubAdapter'; +import { StorageAdapter } from '../Adapters/Storage/StorageAdapter'; +import { WSSAdapter } from '../Adapters/WebSocketServer/WSSAdapter'; +import { CheckGroup } from '../Security/CheckGroup'; +export interface SchemaOptions { + definitions: any; + strict?: boolean; + deleteExtraFields?: boolean; + recreateModifiedFields?: boolean; + lockSchemas?: boolean; + beforeMigration?: () => void | Promise; + afterMigration?: () => void | Promise; +} +type Adapter = string | T; +type NumberOrBoolean = number | boolean; +type NumberOrString = number | string; +type ProtectedFields = any; +type StringOrStringArray = string | string[]; +type RequestKeywordDenylist = { + key: string; + value: any; +}; +export interface ParseServerOptions { + appId: string; + masterKey: (() => void) | string; + masterKeyTtl?: number; + maintenanceKey: string; + serverURL: string; + masterKeyIps?: (string[]); + maintenanceKeyIps?: (string[]); + appName?: string; + allowHeaders?: (string[]); + allowOrigin?: StringOrStringArray; + analyticsAdapter?: Adapter; + filesAdapter?: Adapter; + push?: any; + scheduledPush?: boolean; + loggerAdapter?: Adapter; + jsonLogs?: boolean; + logsFolder?: string; + verbose?: boolean; + logLevel?: string; + logLevels?: LogLevels; + maxLogFiles?: NumberOrString; + silent?: boolean; + databaseURI: string; + databaseOptions?: DatabaseOptions; + databaseAdapter?: Adapter; + enableCollationCaseComparison?: boolean; + convertEmailToLowercase?: boolean; + convertUsernameToLowercase?: boolean; + cloud?: string; + collectionPrefix?: string; + clientKey?: string; + javascriptKey?: string; + dotNetKey?: string; + encryptionKey?: string; + restAPIKey?: string; + readOnlyMasterKey?: string; + webhookKey?: string; + fileKey?: string; + preserveFileName?: boolean; + userSensitiveFields?: (string[]); + protectedFields?: ProtectedFields; + enableAnonymousUsers?: boolean; + allowClientClassCreation?: boolean; + allowCustomObjectId?: boolean; + auth?: Record; + enableInsecureAuthAdapters?: boolean; + maxUploadSize?: string; + verifyUserEmails?: (boolean | void); + preventLoginWithUnverifiedEmail?: boolean; + preventSignupWithUnverifiedEmail?: boolean; + emailVerifyTokenValidityDuration?: number; + emailVerifyTokenReuseIfValid?: boolean; + sendUserEmailVerification?: (boolean | void); + accountLockout?: AccountLockoutOptions; + passwordPolicy?: PasswordPolicyOptions; + cacheAdapter?: Adapter; + emailAdapter?: Adapter; + encodeParseObjectInCloudFunction?: boolean; + publicServerURL?: string; + pages?: PagesOptions; + customPages?: CustomPagesOptions; + liveQuery?: LiveQueryOptions; + sessionLength?: number; + extendSessionOnUse?: boolean; + defaultLimit?: number; + maxLimit?: number; + expireInactiveSessions?: boolean; + revokeSessionOnPasswordReset?: boolean; + cacheTTL?: number; + cacheMaxSize?: number; + directAccess?: boolean; + enableExpressErrorHandler?: boolean; + objectIdSize?: number; + port?: number; + host?: string; + mountPath?: string; + cluster?: NumberOrBoolean; + middleware?: ((() => void) | string); + trustProxy?: any; + startLiveQueryServer?: boolean; + liveQueryServerOptions?: LiveQueryServerOptions; + idempotencyOptions?: IdempotencyOptions; + fileUpload?: FileUploadOptions; + graphQLSchema?: string; + mountGraphQL?: boolean; + graphQLPath?: string; + mountPlayground?: boolean; + playgroundPath?: string; + schema?: SchemaOptions; + serverCloseComplete?: () => void; + security?: SecurityOptions; + enforcePrivateUsers?: boolean; + allowExpiredAuthDataToken?: boolean; + requestKeywordDenylist?: (RequestKeywordDenylist[]); + rateLimit?: (RateLimitOptions[]); +} +export interface RateLimitOptions { + requestPath: string; + requestTimeWindow?: number; + requestCount?: number; + errorResponseMessage?: string; + requestMethods?: (string[]); + includeMasterKey?: boolean; + includeInternalRequests?: boolean; + redisUrl?: string; + zone?: string; +} +export interface SecurityOptions { + enableCheck?: boolean; + enableCheckLog?: boolean; + checkGroups?: (CheckGroup[]); +} +export interface PagesOptions { + enableRouter?: boolean; + enableLocalization?: boolean; + localizationJsonPath?: string; + localizationFallbackLocale?: string; + placeholders?: any; + forceRedirect?: boolean; + pagesPath?: string; + pagesEndpoint?: string; + customUrls?: PagesCustomUrlsOptions; + customRoutes?: (PagesRoute[]); +} +export interface PagesRoute { + path: string; + method: string; + handler: () => void; +} +export interface PagesCustomUrlsOptions { + passwordReset?: string; + passwordResetLinkInvalid?: string; + passwordResetSuccess?: string; + emailVerificationSuccess?: string; + emailVerificationSendFail?: string; + emailVerificationSendSuccess?: string; + emailVerificationLinkInvalid?: string; + emailVerificationLinkExpired?: string; +} +export interface CustomPagesOptions { + invalidLink?: string; + linkSendFail?: string; + choosePassword?: string; + linkSendSuccess?: string; + verifyEmailSuccess?: string; + passwordResetSuccess?: string; + invalidVerificationLink?: string; + expiredVerificationLink?: string; + invalidPasswordResetLink?: string; + parseFrameURL?: string; +} +export interface LiveQueryOptions { + classNames?: (string[]); + redisOptions?: any; + redisURL?: string; + pubSubAdapter?: Adapter; + wssAdapter?: Adapter; +} +export interface LiveQueryServerOptions { + appId?: string; + masterKey?: string; + serverURL?: string; + keyPairs?: any; + websocketTimeout?: number; + cacheTimeout?: number; + logLevel?: string; + port?: number; + redisOptions?: any; + redisURL?: string; + pubSubAdapter?: Adapter; + wssAdapter?: Adapter; +} +export interface IdempotencyOptions { + paths?: (string[]); + ttl?: number; +} +export interface AccountLockoutOptions { + duration?: number; + threshold?: number; + unlockOnPasswordReset?: boolean; +} +export interface PasswordPolicyOptions { + validatorPattern?: string; + validatorCallback?: () => void; + validationError?: string; + doNotAllowUsername?: boolean; + maxPasswordAge?: number; + maxPasswordHistory?: number; + resetTokenValidityDuration?: number; + resetTokenReuseIfValid?: boolean; + resetPasswordSuccessOnInvalidEmail?: boolean; +} +export interface FileUploadOptions { + fileExtensions?: (string[]); + enableForAnonymousUser?: boolean; + enableForAuthenticatedUser?: boolean; + enableForPublic?: boolean; +} +export interface DatabaseOptions { + enableSchemaHooks?: boolean; + schemaCacheTtl?: number; + retryWrites?: boolean; + maxTimeMS?: number; + maxStalenessSeconds?: number; + minPoolSize?: number; + maxPoolSize?: number; + connectTimeoutMS?: number; + socketTimeoutMS?: number; + autoSelectFamily?: boolean; + autoSelectFamilyAttemptTimeout?: number; +} +export interface AuthAdapter { + enabled?: boolean; +} +export interface LogLevels { + triggerAfter?: string; + triggerBeforeSuccess?: string; + triggerBeforeError?: string; + cloudFunctionSuccess?: string; + cloudFunctionError?: string; +} +export {}; diff --git a/types/ParseServer.d.ts b/types/ParseServer.d.ts new file mode 100644 index 0000000000..e504e03114 --- /dev/null +++ b/types/ParseServer.d.ts @@ -0,0 +1,60 @@ +import { ParseServerOptions, LiveQueryServerOptions } from './Options'; +import { ParseLiveQueryServer } from './LiveQuery/ParseLiveQueryServer'; +declare class ParseServer { + _app: any; + config: any; + server: any; + expressApp: any; + liveQueryServer: any; + /** + * @constructor + * @param {ParseServerOptions} options the parse server initialization options + */ + constructor(options: ParseServerOptions); + /** + * Starts Parse Server as an express app; this promise resolves when Parse Server is ready to accept requests. + */ + start(): Promise; + get app(): any; + /** + * Stops the parse server, cancels any ongoing requests and closes all connections. + * + * Currently, express doesn't shut down immediately after receiving SIGINT/SIGTERM + * if it has client connections that haven't timed out. + * (This is a known issue with node - https://github.com/nodejs/node/issues/2642) + * + * @returns {Promise} a promise that resolves when the server is stopped + */ + handleShutdown(): Promise; + /** + * @static + * Create an express app for the parse server + * @param {Object} options let you specify the maxUploadSize when creating the express app */ + static app(options: any): any; + static promiseRouter({ appId }: { + appId: any; + }): any; + /** + * starts the parse server's express app + * @param {ParseServerOptions} options to use to start the server + * @returns {ParseServer} the parse server instance + */ + startApp(options: ParseServerOptions): Promise; + /** + * Creates a new ParseServer and starts it. + * @param {ParseServerOptions} options used to start the server + * @returns {ParseServer} the parse server instance + */ + static startApp(options: ParseServerOptions): Promise; + /** + * Helper method to create a liveQuery server + * @static + * @param {Server} httpServer an optional http server to pass + * @param {LiveQueryServerOptions} config options for the liveQueryServer + * @param {ParseServerOptions} options options for the ParseServer + * @returns {Promise} the live query server instance + */ + static createLiveQueryServer(httpServer: any, config: LiveQueryServerOptions, options: ParseServerOptions): Promise; + static verifyServerUrl(): any; +} +export default ParseServer; diff --git a/types/eslint.config.mjs b/types/eslint.config.mjs new file mode 100644 index 0000000000..f2375e596f --- /dev/null +++ b/types/eslint.config.mjs @@ -0,0 +1,30 @@ +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import expectType from 'eslint-plugin-expect-type/configs/recommended'; + +export default tseslint.config({ + files: ['**/*.js', '**/*.ts'], + extends: [ + expectType, + eslint.configs.recommended, + ...tseslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ], + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-return": "off", + }, + languageOptions: { + parser: tseslint.parser, + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, +}); diff --git a/types/index.d.ts b/types/index.d.ts index e69de29bb2..591044c272 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -0,0 +1,21 @@ +import ParseServer from './ParseServer'; +import FileSystemAdapter from '@parse/fs-files-adapter'; +import InMemoryCacheAdapter from './Adapters/Cache/InMemoryCacheAdapter'; +import NullCacheAdapter from './Adapters/Cache/NullCacheAdapter'; +import RedisCacheAdapter from './Adapters/Cache/RedisCacheAdapter'; +import LRUCacheAdapter from './Adapters/Cache/LRUCache.js'; +import * as TestUtils from './TestUtils'; +import * as SchemaMigrations from './SchemaMigrations/Migrations'; +import AuthAdapter from './Adapters/Auth/AuthAdapter'; +import { PushWorker } from './Push/PushWorker'; +import { ParseServerOptions } from './Options'; +import { ParseGraphQLServer } from './GraphQL/ParseGraphQLServer'; +declare const _ParseServer: { + (options: ParseServerOptions): ParseServer; + createLiveQueryServer: typeof ParseServer.createLiveQueryServer; + startApp: typeof ParseServer.startApp; +}; +declare const S3Adapter: any; +declare const GCSAdapter: any; +export default ParseServer; +export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, NullCacheAdapter, RedisCacheAdapter, LRUCacheAdapter, TestUtils, PushWorker, ParseGraphQLServer, _ParseServer as ParseServer, SchemaMigrations, AuthAdapter, }; diff --git a/types/tests.ts b/types/tests.ts new file mode 100644 index 0000000000..15593963f8 --- /dev/null +++ b/types/tests.ts @@ -0,0 +1,44 @@ +import ParseServer, { FileSystemAdapter } from 'parse-server'; + +async function server() { + // $ExpectType ParseServer + const parseServer = await ParseServer.startApp({}); + + // $ExpectType void + await parseServer.handleShutdown(); + + // $ExpectType any + parseServer.app; + + // $ExpectType any + ParseServer.app({}); + + // $ExpectType any + ParseServer.promiseRouter({ appId: 'appId' }); + + // $ExpectType ParseLiveQueryServer + await ParseServer.createLiveQueryServer({}, {}, {}); + + // $ExpectType any + ParseServer.verifyServerUrl(); + + // $ExpectError + await ParseServer.startApp(); + + // $ExpectError + ParseServer.promiseRouter(); + + // $ExpectError + await ParseServer.createLiveQueryServer(); + + // $ExpectType ParseServer + const parseServer2 = new ParseServer({}); + + // $ExpectType ParseServer + await parseServer2.start(); +} + +function exports() { + // $ExpectType any + FileSystemAdapter; +} diff --git a/types/tsconfig.json b/types/tsconfig.json index 917319a042..0880cb0a81 100644 --- a/types/tsconfig.json +++ b/types/tsconfig.json @@ -13,6 +13,12 @@ // If the library is an external module (uses `export`), this allows your test file to import "mylib" instead of "./index". // If the library is global (cannot be imported via `import` or `require`), leave this out. "baseUrl": ".", - "paths": { "parse": ["."] } - } + "paths": { + "parse-server": ["."], + "@parse/fs-files-adapter": ["./@types/@parse/fs-files-adapter"], + } + }, + "include": [ + "tests.ts" + ] }