From b0adf7e02ab0beea2cd9b759d0f788c69d291491 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 23 Mar 2024 04:16:50 +0100 Subject: [PATCH 01/32] feat: Add password validation for user with unverified email via `Parse.User.verifyPassword` using master key and option `ignoreEmailVerification: true` (#2076) --- integration/test/ParseUserTest.js | 13 + integration/test/helper.js | 6 + .../support/MockEmailAdapterWithOptions.js | 21 ++ package-lock.json | 290 +++++------------- package.json | 2 +- src/ParseUser.js | 9 +- src/RESTController.js | 5 + 7 files changed, 118 insertions(+), 228 deletions(-) create mode 100644 integration/test/support/MockEmailAdapterWithOptions.js diff --git a/integration/test/ParseUserTest.js b/integration/test/ParseUserTest.js index d67baae91..d05a477fa 100644 --- a/integration/test/ParseUserTest.js +++ b/integration/test/ParseUserTest.js @@ -1057,6 +1057,19 @@ describe('Parse User', () => { } }); + it('can verify user password for user with unverified email', async () => { + await reconfigureServer({ + appName: 'AppName', + publicServerURL: 'http://localhost:1337/', + verifyUserEmails: true, + preventLoginWithUnverifiedEmail: true, + }); + await Parse.User.signUp('asd123', 'xyz123'); + const res = await Parse.User.verifyPassword('asd123', 'xyz123', { useMasterKey: true, ignoreEmailVerification: true }); + expect(typeof res).toBe('object'); + expect(res.username).toBe('asd123'); + }); + it('can encrypt user', async () => { Parse.User.enableUnsafeCurrentUser(); Parse.enableEncryptedUser(); diff --git a/integration/test/helper.js b/integration/test/helper.js index eac377681..c32516c95 100644 --- a/integration/test/helper.js +++ b/integration/test/helper.js @@ -11,6 +11,7 @@ const Parse = require('../../node'); const fs = require('fs'); const path = require('path'); const dns = require('dns'); +const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions'); // Ensure localhost resolves to ipv4 address first on node v17+ if (dns.setDefaultResultOrder) { @@ -83,6 +84,11 @@ const defaultConfiguration = { revokeSessionOnPasswordReset: false, allowCustomObjectId: false, allowClientClassCreation: true, + emailAdapter: MockEmailAdapterWithOptions({ + fromAddress: 'parse@example.com', + apiKey: 'k', + domain: 'd', + }), }; const openConnections = {}; diff --git a/integration/test/support/MockEmailAdapterWithOptions.js b/integration/test/support/MockEmailAdapterWithOptions.js new file mode 100644 index 000000000..71d23892e --- /dev/null +++ b/integration/test/support/MockEmailAdapterWithOptions.js @@ -0,0 +1,21 @@ +module.exports = options => { + if (!options) { + throw 'Options were not provided'; + } + const adapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => Promise.resolve(), + }; + if (options.sendMail) { + adapter.sendMail = options.sendMail; + } + if (options.sendPasswordResetEmail) { + adapter.sendPasswordResetEmail = options.sendPasswordResetEmail; + } + if (options.sendVerificationEmail) { + adapter.sendVerificationEmail = options.sendVerificationEmail; + } + + return adapter; +}; diff --git a/package-lock.json b/package-lock.json index 6507a9a12..00281f547 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "lint-staged": "13.2.2", "metro-react-native-babel-preset": "0.76.4", "mongodb-runner": "5.4.3", - "parse-server": "7.0.0-alpha.26", + "parse-server": "7.1.0-alpha.1", "prettier": "3.0.2", "puppeteer": "20.4.0", "regenerator-runtime": "0.13.11", @@ -140,9 +140,9 @@ } }, "node_modules/@apollo/server": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.10.0.tgz", - "integrity": "sha512-pLx//lZ/pvUfWL9G8Np8+y3ujc0pYc8U7dwD6ztt9FAw8NmCPzPaDzlXLBAjGU6WnkqVBOnz8b3dOwRNjLYSUA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.10.1.tgz", + "integrity": "sha512-XGMOgTyzV4EBHQq0xQVKFry9hZF7AA/6nxxGLamqdxodhdSdGbU9jrlb5/XDveeGuXP3+5JDdrB2HcziVLJcMA==", "dev": true, "dependencies": { "@apollo/cache-control-types": "^1.0.3", @@ -11873,12 +11873,12 @@ } }, "node_modules/express-rate-limit": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", - "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.11.2.tgz", + "integrity": "sha512-a7uwwfNTh1U60ssiIkuLFWHt4hAC5yxlLGU2VP0X4YNlyEDZAqF4tK3GD3NSitVBrCQmQ0++0uOyFOgC2y4DDw==", "dev": true, "engines": { - "node": ">= 12.9.0" + "node": ">= 14" }, "peerDependencies": { "express": "^4 || ^5" @@ -12660,9 +12660,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -23358,23 +23358,23 @@ } }, "node_modules/parse": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-4.1.0.tgz", - "integrity": "sha512-s0Ti+nWrKWj9DlFcmkEE05fGwa/K5ycZSdqCz01F8YL7Hevqv4WLXAmYGOwzq5UJSZ005seKgb20KwVwLdy/Zg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse/-/parse-5.0.0.tgz", + "integrity": "sha512-6gOARZWiHjmGusbTskhC1qlRn527olMEsdt2LLj9cP2GY3n4VFOwFwV8z/vm2+YfzPfPcv8z7qLih1NzmqzO0g==", "dev": true, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "idb-keyval": "6.2.0", + "@babel/runtime-corejs3": "7.23.2", + "idb-keyval": "6.2.1", "react-native-crypto-js": "1.0.0", - "uuid": "9.0.0", - "ws": "8.12.0", + "uuid": "9.0.1", + "ws": "8.16.0", "xmlhttprequest": "1.8.0" }, "engines": { - "node": ">=14.21.0 <17 || >=18 <20" + "node": ">=18 <21" }, "optionalDependencies": { - "crypto-js": "4.1.1" + "crypto-js": "4.2.0" } }, "node_modules/parse-asn1": { @@ -23477,15 +23477,15 @@ } }, "node_modules/parse-server": { - "version": "7.0.0-alpha.26", - "resolved": "https://registry.npmjs.org/parse-server/-/parse-server-7.0.0-alpha.26.tgz", - "integrity": "sha512-6/Xb/yvy8JtCZ1Puy2gwPmgAYzmzbxntJ56wuN1zWzDA716rxUEmzj0d5Zy0ISZTdTufTlXI5LnBOORRK03K4w==", + "version": "7.1.0-alpha.1", + "resolved": "https://registry.npmjs.org/parse-server/-/parse-server-7.1.0-alpha.1.tgz", + "integrity": "sha512-kHK5eHIraVL+kEvhug/hyjJkXxEFRz8py4t+B0yJBUMVajhsU4GYvN7SwY/IU5lRRZGJo8SjR8t+ThfORrHkpw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@apollo/server": "4.10.0", + "@apollo/server": "4.10.1", "@babel/eslint-parser": "7.21.8", - "@graphql-tools/merge": "8.4.1", + "@graphql-tools/merge": "9.0.3", "@graphql-tools/schema": "10.0.3", "@graphql-tools/utils": "8.12.0", "@parse/fs-files-adapter": "2.0.1", @@ -23496,8 +23496,8 @@ "cors": "2.8.5", "deepcopy": "2.1.0", "express": "4.18.2", - "express-rate-limit": "6.7.0", - "follow-redirects": "1.15.5", + "express-rate-limit": "6.11.2", + "follow-redirects": "1.15.6", "graphql": "16.8.1", "graphql-list-fields": "2.0.4", "graphql-relay": "0.10.0", @@ -23513,10 +23513,10 @@ "mongodb": "5.9.0", "mustache": "4.2.0", "otpauth": "9.2.2", - "parse": "4.1.0", + "parse": "5.0.0", "path-to-regexp": "6.2.1", "pg-monitor": "2.0.0", - "pg-promise": "11.5.4", + "pg-promise": "11.5.5", "pluralize": "8.0.0", "rate-limit-redis": "3.0.2", "redis": "4.6.13", @@ -23524,7 +23524,7 @@ "subscriptions-transport-ws": "0.11.0", "tv4": "1.3.0", "uuid": "9.0.1", - "winston": "3.11.0", + "winston": "3.12.0", "winston-daily-rotate-file": "5.0.0", "ws": "8.16.0" }, @@ -23542,32 +23542,6 @@ "@node-rs/bcrypt": "1.1.0" } }, - "node_modules/parse-server/node_modules/@graphql-tools/merge": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.1.tgz", - "integrity": "sha512-hssnPpZ818mxgl5+GfyOOSnnflAxiaTn1A1AojZcIbh4J52sS1Q0gSuBR5VrnUDjuxiqoCotpXdAQl+K+U6KLQ==", - "dev": true, - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/parse-server/node_modules/@graphql-tools/merge/node_modules/@graphql-tools/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", - "dev": true, - "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/parse-server/node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -23673,65 +23647,6 @@ "node": ">=10" } }, - "node_modules/parse/node_modules/@babel/runtime-corejs3": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.21.0.tgz", - "integrity": "sha512-TDD4UJzos3JJtM+tHX+w2Uc+KWj7GV+VKKFdMVd2Rx8sdA19hcc3P3AHFYd5LVOw+pYuSd5lICC3gm52B6Rwxw==", - "dev": true, - "dependencies": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/parse/node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==", - "dev": true, - "optional": true - }, - "node_modules/parse/node_modules/idb-keyval": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.0.tgz", - "integrity": "sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng==", - "dev": true, - "dependencies": { - "safari-14-idb-fix": "^3.0.0" - } - }, - "node_modules/parse/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/parse/node_modules/ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -24005,9 +23920,9 @@ } }, "node_modules/pg-promise": { - "version": "11.5.4", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.5.4.tgz", - "integrity": "sha512-esYSkDt2h6NQOkfotGAm1Ld5OjoITJLpB88Z1PIlcAU/RQ0XQE2PxW0bLJEOMHPGV5iaRnj1Y7ARznXbgN4FNw==", + "version": "11.5.5", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.5.5.tgz", + "integrity": "sha512-DpJkDDH7rG0wUwFRRHimdV6DtG/UTK2SBEKC7KGFR6a5Zuqf9eGThR7dqIaHXnEBDZuWxUfWC5zMRqyk4EP7Lw==", "dev": true, "dependencies": { "assert-options": "0.8.1", @@ -28762,9 +28677,9 @@ } }, "node_modules/winston": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", - "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.12.0.tgz", + "integrity": "sha512-OwbxKaOlESDi01mC9rkM0dQqQt2I8DAUMRLZ/HpbwvDXm85IryEHgoogy5fziQy38PntgZsLlhAYHz//UPHZ5w==", "dev": true, "dependencies": { "@colors/colors": "^1.6.0", @@ -28777,7 +28692,7 @@ "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" + "winston-transport": "^4.7.0" }, "engines": { "node": ">= 12.0.0" @@ -29142,9 +29057,9 @@ } }, "@apollo/server": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.10.0.tgz", - "integrity": "sha512-pLx//lZ/pvUfWL9G8Np8+y3ujc0pYc8U7dwD6ztt9FAw8NmCPzPaDzlXLBAjGU6WnkqVBOnz8b3dOwRNjLYSUA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.10.1.tgz", + "integrity": "sha512-XGMOgTyzV4EBHQq0xQVKFry9hZF7AA/6nxxGLamqdxodhdSdGbU9jrlb5/XDveeGuXP3+5JDdrB2HcziVLJcMA==", "dev": true, "requires": { "@apollo/cache-control-types": "^1.0.3", @@ -38279,9 +38194,9 @@ } }, "express-rate-limit": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", - "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.11.2.tgz", + "integrity": "sha512-a7uwwfNTh1U60ssiIkuLFWHt4hAC5yxlLGU2VP0X4YNlyEDZAqF4tK3GD3NSitVBrCQmQ0++0uOyFOgC2y4DDw==", "dev": true, "requires": {} }, @@ -38907,9 +38822,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true }, "for-each": { @@ -47029,59 +46944,18 @@ } }, "parse": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-4.1.0.tgz", - "integrity": "sha512-s0Ti+nWrKWj9DlFcmkEE05fGwa/K5ycZSdqCz01F8YL7Hevqv4WLXAmYGOwzq5UJSZ005seKgb20KwVwLdy/Zg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse/-/parse-5.0.0.tgz", + "integrity": "sha512-6gOARZWiHjmGusbTskhC1qlRn527olMEsdt2LLj9cP2GY3n4VFOwFwV8z/vm2+YfzPfPcv8z7qLih1NzmqzO0g==", "dev": true, "requires": { - "@babel/runtime-corejs3": "7.21.0", - "crypto-js": "4.1.1", - "idb-keyval": "6.2.0", + "@babel/runtime-corejs3": "7.23.2", + "crypto-js": "4.2.0", + "idb-keyval": "6.2.1", "react-native-crypto-js": "1.0.0", - "uuid": "9.0.0", - "ws": "8.12.0", + "uuid": "9.0.1", + "ws": "8.16.0", "xmlhttprequest": "1.8.0" - }, - "dependencies": { - "@babel/runtime-corejs3": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.21.0.tgz", - "integrity": "sha512-TDD4UJzos3JJtM+tHX+w2Uc+KWj7GV+VKKFdMVd2Rx8sdA19hcc3P3AHFYd5LVOw+pYuSd5lICC3gm52B6Rwxw==", - "dev": true, - "requires": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.11" - } - }, - "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==", - "dev": true, - "optional": true - }, - "idb-keyval": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.0.tgz", - "integrity": "sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng==", - "dev": true, - "requires": { - "safari-14-idb-fix": "^3.0.0" - } - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true - }, - "ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", - "dev": true, - "requires": {} - } } }, "parse-asn1": { @@ -47162,14 +47036,14 @@ "dev": true }, "parse-server": { - "version": "7.0.0-alpha.26", - "resolved": "https://registry.npmjs.org/parse-server/-/parse-server-7.0.0-alpha.26.tgz", - "integrity": "sha512-6/Xb/yvy8JtCZ1Puy2gwPmgAYzmzbxntJ56wuN1zWzDA716rxUEmzj0d5Zy0ISZTdTufTlXI5LnBOORRK03K4w==", + "version": "7.1.0-alpha.1", + "resolved": "https://registry.npmjs.org/parse-server/-/parse-server-7.1.0-alpha.1.tgz", + "integrity": "sha512-kHK5eHIraVL+kEvhug/hyjJkXxEFRz8py4t+B0yJBUMVajhsU4GYvN7SwY/IU5lRRZGJo8SjR8t+ThfORrHkpw==", "dev": true, "requires": { - "@apollo/server": "4.10.0", + "@apollo/server": "4.10.1", "@babel/eslint-parser": "7.21.8", - "@graphql-tools/merge": "8.4.1", + "@graphql-tools/merge": "9.0.3", "@graphql-tools/schema": "10.0.3", "@graphql-tools/utils": "8.12.0", "@node-rs/bcrypt": "1.1.0", @@ -47181,8 +47055,8 @@ "cors": "2.8.5", "deepcopy": "2.1.0", "express": "4.18.2", - "express-rate-limit": "6.7.0", - "follow-redirects": "1.15.5", + "express-rate-limit": "6.11.2", + "follow-redirects": "1.15.6", "graphql": "16.8.1", "graphql-list-fields": "2.0.4", "graphql-relay": "0.10.0", @@ -47198,10 +47072,10 @@ "mongodb": "5.9.0", "mustache": "4.2.0", "otpauth": "9.2.2", - "parse": "4.1.0", + "parse": "5.0.0", "path-to-regexp": "6.2.1", "pg-monitor": "2.0.0", - "pg-promise": "11.5.4", + "pg-promise": "11.5.5", "pluralize": "8.0.0", "rate-limit-redis": "3.0.2", "redis": "4.6.13", @@ -47209,33 +47083,11 @@ "subscriptions-transport-ws": "0.11.0", "tv4": "1.3.0", "uuid": "9.0.1", - "winston": "3.11.0", + "winston": "3.12.0", "winston-daily-rotate-file": "5.0.0", "ws": "8.16.0" }, "dependencies": { - "@graphql-tools/merge": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.1.tgz", - "integrity": "sha512-hssnPpZ818mxgl5+GfyOOSnnflAxiaTn1A1AojZcIbh4J52sS1Q0gSuBR5VrnUDjuxiqoCotpXdAQl+K+U6KLQ==", - "dev": true, - "requires": { - "@graphql-tools/utils": "^9.2.1", - "tslib": "^2.4.0" - }, - "dependencies": { - "@graphql-tools/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", - "dev": true, - "requires": { - "@graphql-typed-document-node/core": "^3.1.1", - "tslib": "^2.4.0" - } - } - } - }, "body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -47528,9 +47380,9 @@ "requires": {} }, "pg-promise": { - "version": "11.5.4", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.5.4.tgz", - "integrity": "sha512-esYSkDt2h6NQOkfotGAm1Ld5OjoITJLpB88Z1PIlcAU/RQ0XQE2PxW0bLJEOMHPGV5iaRnj1Y7ARznXbgN4FNw==", + "version": "11.5.5", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.5.5.tgz", + "integrity": "sha512-DpJkDDH7rG0wUwFRRHimdV6DtG/UTK2SBEKC7KGFR6a5Zuqf9eGThR7dqIaHXnEBDZuWxUfWC5zMRqyk4EP7Lw==", "dev": true, "requires": { "assert-options": "0.8.1", @@ -51386,9 +51238,9 @@ } }, "winston": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", - "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.12.0.tgz", + "integrity": "sha512-OwbxKaOlESDi01mC9rkM0dQqQt2I8DAUMRLZ/HpbwvDXm85IryEHgoogy5fziQy38PntgZsLlhAYHz//UPHZ5w==", "dev": true, "requires": { "@colors/colors": "^1.6.0", @@ -51401,7 +51253,7 @@ "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" + "winston-transport": "^4.7.0" }, "dependencies": { "@colors/colors": { diff --git a/package.json b/package.json index 9d5e65519..56949565c 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "lint-staged": "13.2.2", "metro-react-native-babel-preset": "0.76.4", "mongodb-runner": "5.4.3", - "parse-server": "7.0.0-alpha.26", + "parse-server": "7.1.0-alpha.1", "prettier": "3.0.2", "puppeteer": "20.4.0", "regenerator-runtime": "0.13.11", diff --git a/src/ParseUser.js b/src/ParseUser.js index 8ba9ba8c0..9347d1239 100644 --- a/src/ParseUser.js +++ b/src/ParseUser.js @@ -882,15 +882,8 @@ class ParseUser extends ParseObject { return Promise.reject(new ParseError(ParseError.OTHER_CAUSE, 'Password must be a string.')); } - options = options || {}; - - const verificationOption = {}; - if (options.hasOwnProperty('useMasterKey')) { - verificationOption.useMasterKey = options.useMasterKey; - } - const controller = CoreManager.getUserController(); - return controller.verifyPassword(username, password, verificationOption); + return controller.verifyPassword(username, password, options || {}); } /** diff --git a/src/RESTController.js b/src/RESTController.js index 75b30a759..73c962cff 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -18,6 +18,7 @@ export type RequestOptions = { progress?: any, context?: any, usePost?: boolean, + ignoreEmailVerification?: boolean, }; export type FullOptions = { @@ -249,6 +250,10 @@ const RESTController = { } } + if (options.ignoreEmailVerification !== undefined) { + payload.ignoreEmailVerification = options.ignoreEmailVerification; + } + if (CoreManager.get('FORCE_REVOCABLE_SESSION')) { payload._RevocableSession = '1'; } From 492d84c63535fa0afa0ed1985b5cdc5523f1ad07 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 23 Mar 2024 03:18:10 +0000 Subject: [PATCH 02/32] chore(release): 5.0.0-alpha.4 [skip ci] # [5.0.0-alpha.4](https://github.com/parse-community/Parse-SDK-JS/compare/5.0.0-alpha.3...5.0.0-alpha.4) (2024-03-23) ### Features * Add password validation for user with unverified email via `Parse.User.verifyPassword` using master key and option `ignoreEmailVerification: true` ([#2076](https://github.com/parse-community/Parse-SDK-JS/issues/2076)) ([b0adf7e](https://github.com/parse-community/Parse-SDK-JS/commit/b0adf7e02ab0beea2cd9b759d0f788c69d291491)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index f70bc6d1d..71e82e03f 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.0.0-alpha.4](https://github.com/parse-community/Parse-SDK-JS/compare/5.0.0-alpha.3...5.0.0-alpha.4) (2024-03-23) + + +### Features + +* Add password validation for user with unverified email via `Parse.User.verifyPassword` using master key and option `ignoreEmailVerification: true` ([#2076](https://github.com/parse-community/Parse-SDK-JS/issues/2076)) ([b0adf7e](https://github.com/parse-community/Parse-SDK-JS/commit/b0adf7e02ab0beea2cd9b759d0f788c69d291491)) + # [5.0.0-alpha.3](https://github.com/parse-community/Parse-SDK-JS/compare/5.0.0-alpha.2...5.0.0-alpha.3) (2024-03-11) diff --git a/package-lock.json b/package-lock.json index 00281f547..63d23a5d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.0.0-beta.1", + "version": "5.0.0-alpha.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.0.0-beta.1", + "version": "5.0.0-alpha.4", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index 56949565c..8b3912ba9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.0.0-beta.1", + "version": "5.0.0-alpha.4", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From 72bc9ac3bfb23443a03742fe47a3b1b2713f8c96 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 31 Mar 2024 11:59:11 -0500 Subject: [PATCH 03/32] feat: Add support for setting `Parse.ACL` from json (#2097) --- integration/test/ParseACLTest.js | 14 +++++++ integration/test/ParseEventuallyQueueTest.js | 43 ++++++++++++++++++++ src/ParseObject.js | 14 ++++--- src/__tests__/ParseObject-test.js | 7 ++++ 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/integration/test/ParseACLTest.js b/integration/test/ParseACLTest.js index 4251f059c..d1340cfe7 100644 --- a/integration/test/ParseACLTest.js +++ b/integration/test/ParseACLTest.js @@ -27,6 +27,20 @@ describe('Parse.ACL', () => { assert(o); }); + it('can set ACL from json', async () => { + Parse.User.enableUnsafeCurrentUser(); + const user = new Parse.User(); + const object = new TestObject(); + user.set('username', 'torn'); + user.set('password', 'acl'); + await user.signUp(); + const acl = new Parse.ACL(user); + object.setACL(acl); + const json = object.toJSON(); + await object.save(json); + assert.equal(acl.equals(object.getACL()), true); + }); + it('disables public get access', async () => { const user = new Parse.User(); const object = new TestObject(); diff --git a/integration/test/ParseEventuallyQueueTest.js b/integration/test/ParseEventuallyQueueTest.js index 6ce6358cb..1d8157553 100644 --- a/integration/test/ParseEventuallyQueueTest.js +++ b/integration/test/ParseEventuallyQueueTest.js @@ -220,6 +220,49 @@ describe('Parse EventuallyQueue', () => { assert.strictEqual(results.length, 1); }); + it('can saveEventually on object with ACL', async () => { + Parse.User.enableUnsafeCurrentUser(); + const parseServer = await reconfigureServer(); + const user = new Parse.User(); + user.set('username', 'torn'); + user.set('password', 'acl'); + await user.signUp(); + + const acl = new Parse.ACL(user); + const object = new TestObject({ hash: 'saveSecret' }); + object.setACL(acl); + + await new Promise((resolve) => parseServer.server.close(resolve)); + + await object.saveEventually(); + + let length = await Parse.EventuallyQueue.length(); + assert(Parse.EventuallyQueue.isPolling()); + assert.strictEqual(length, 1); + + await reconfigureServer({}); + + while (Parse.EventuallyQueue.isPolling()) { + await sleep(100); + } + assert.strictEqual(Parse.EventuallyQueue.isPolling(), false); + + length = await Parse.EventuallyQueue.length(); + while (length) { + await sleep(100); + } + length = await Parse.EventuallyQueue.length(); + assert.strictEqual(length, 0); + + const query = new Parse.Query('TestObject'); + query.equalTo('hash', 'saveSecret'); + let results = await query.find(); + while (results.length === 0) { + results = await query.find(); + } + assert.strictEqual(results.length, 1); + }); + it('can destroyEventually', async () => { const parseServer = await reconfigureServer(); const object = new TestObject({ hash: 'deleteSecret' }); diff --git a/src/ParseObject.js b/src/ParseObject.js index 32701191b..eafa7f45d 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -1308,15 +1308,17 @@ class ParseObject { options = arg3; } + options = options || {}; if (attrs) { - const validation = this.validate(attrs); - if (validation) { - return Promise.reject(validation); + let validationError; + options.error = (_, validation) => { + validationError = validation; + }; + const success = this.set(attrs, options); + if (!success) { + return Promise.reject(validationError); } - this.set(attrs, options); } - - options = options || {}; const saveOptions = {}; if (options.hasOwnProperty('useMasterKey')) { saveOptions.useMasterKey = !!options.useMasterKey; diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index c67c4a3e1..2066a8979 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -364,6 +364,13 @@ describe('ParseObject', () => { expect(o.getACL()).toEqual(ACL); }); + it('encodes ACL from json', () => { + const ACL = new ParseACL({ user1: { read: true } }); + const o = new ParseObject('Item'); + o.set({ ACL: ACL.toJSON() }); + expect(o.getACL()).toEqual(ACL); + }); + it('can be rendered to JSON', () => { let o = new ParseObject('Item'); o.set({ From ab237a2151dfb725e82d97ce50e5baeecc2b3eab Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 31 Mar 2024 17:00:37 +0000 Subject: [PATCH 04/32] chore(release): 5.1.0-alpha.1 [skip ci] # [5.1.0-alpha.1](https://github.com/parse-community/Parse-SDK-JS/compare/5.0.0...5.1.0-alpha.1) (2024-03-31) ### Features * Add password validation for user with unverified email via `Parse.User.verifyPassword` using master key and option `ignoreEmailVerification: true` ([#2076](https://github.com/parse-community/Parse-SDK-JS/issues/2076)) ([b0adf7e](https://github.com/parse-community/Parse-SDK-JS/commit/b0adf7e02ab0beea2cd9b759d0f788c69d291491)) * Add support for setting `Parse.ACL` from json ([#2097](https://github.com/parse-community/Parse-SDK-JS/issues/2097)) ([72bc9ac](https://github.com/parse-community/Parse-SDK-JS/commit/72bc9ac3bfb23443a03742fe47a3b1b2713f8c96)) --- changelogs/CHANGELOG_alpha.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 71e82e03f..819d4e3a4 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,11 @@ +# [5.1.0-alpha.1](https://github.com/parse-community/Parse-SDK-JS/compare/5.0.0...5.1.0-alpha.1) (2024-03-31) + + +### Features + +* Add password validation for user with unverified email via `Parse.User.verifyPassword` using master key and option `ignoreEmailVerification: true` ([#2076](https://github.com/parse-community/Parse-SDK-JS/issues/2076)) ([b0adf7e](https://github.com/parse-community/Parse-SDK-JS/commit/b0adf7e02ab0beea2cd9b759d0f788c69d291491)) +* Add support for setting `Parse.ACL` from json ([#2097](https://github.com/parse-community/Parse-SDK-JS/issues/2097)) ([72bc9ac](https://github.com/parse-community/Parse-SDK-JS/commit/72bc9ac3bfb23443a03742fe47a3b1b2713f8c96)) + # [5.0.0-alpha.4](https://github.com/parse-community/Parse-SDK-JS/compare/5.0.0-alpha.3...5.0.0-alpha.4) (2024-03-23) diff --git a/package-lock.json b/package-lock.json index 63d23a5d3..21c51a58d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.0.0-alpha.4", + "version": "5.1.0-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.0.0-alpha.4", + "version": "5.1.0-alpha.1", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index 8b3912ba9..49ab843a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.0.0-alpha.4", + "version": "5.1.0-alpha.1", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From c591895e8eda4a074871950133255d70ba0ca75c Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 13 Apr 2024 16:47:46 +0200 Subject: [PATCH 05/32] ci: Fix Codecov upload (#2105) --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe1875f53..44a2d4a20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,11 @@ jobs: - run: npm run test:mongodb env: CI: true - - run: bash <(curl -s https://codecov.io/bash) + - name: Upload code coverage + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true From a7b2fa030d6f0c0d1902bcef92536b1b4eec066e Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 13 Apr 2024 22:28:45 +0200 Subject: [PATCH 06/32] refactor: Copy `ignoreEmailVerification` from option to data (#2107) --- src/ParseUser.js | 29 ++++++++++++++++++----------- src/RESTController.js | 4 ---- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/ParseUser.js b/src/ParseUser.js index 9347d1239..26dec8820 100644 --- a/src/ParseUser.js +++ b/src/ParseUser.js @@ -546,10 +546,11 @@ class ParseUser extends ParseObject { /** * Verify whether a given password is the password of the current user. * - * @param {string} password A password to be verified - * @param {object} options - * @returns {Promise} A promise that is fulfilled with a user - * when the password is correct. + * @param {string} password The password to be verified. + * @param {object} options The options. + * @param {boolean} [options.ignoreEmailVerification=false] Set to `true` to bypass email verification and verify + * the password regardless of whether the email has been verified. This requires the master key. + * @returns {Promise} A promise that is fulfilled with a user when the password is correct. */ verifyPassword(password: string, options?: RequestOptions): Promise { const username = this.getUsername() || ''; @@ -865,13 +866,14 @@ class ParseUser extends ParseObject { /** * Verify whether a given password is the password of the current user. - * - * @param {string} username A username to be used for identificaiton - * @param {string} password A password to be verified - * @param {object} options * @static - * @returns {Promise} A promise that is fulfilled with a user - * when the password is correct. + * + * @param {string} username The username of the user whose password should be verified. + * @param {string} password The password to be verified. + * @param {object} options The options. + * @param {boolean} [options.ignoreEmailVerification=false] Set to `true` to bypass email verification and verify + * the password regardless of whether the email has been verified. This requires the master key. + * @returns {Promise} A promise that is fulfilled with a user when the password is correct. */ static verifyPassword(username: string, password: string, options?: RequestOptions) { if (typeof username !== 'string') { @@ -1262,7 +1264,12 @@ const DefaultController = { verifyPassword(username: string, password: string, options: RequestOptions) { const RESTController = CoreManager.getRESTController(); - return RESTController.request('GET', 'verifyPassword', { username, password }, options); + const data = { + username, + password, + ...(options.ignoreEmailVerification !== undefined && { ignoreEmailVerification: options.ignoreEmailVerification }), + }; + return RESTController.request('GET', 'verifyPassword', data, options); }, requestEmailVerification(email: string, options: RequestOptions) { diff --git a/src/RESTController.js b/src/RESTController.js index 73c962cff..552c3d058 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -250,10 +250,6 @@ const RESTController = { } } - if (options.ignoreEmailVerification !== undefined) { - payload.ignoreEmailVerification = options.ignoreEmailVerification; - } - if (CoreManager.get('FORCE_REVOCABLE_SESSION')) { payload._RevocableSession = '1'; } From fe29e6e77abe2c7ae4ae7739e908dc0db64ec469 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Apr 2024 16:23:55 -0500 Subject: [PATCH 07/32] test: Fix EventuallyQueue flaky tests (#2108) --- integration/test/ParseEventuallyQueueTest.js | 33 +++----------------- integration/test/helper.js | 1 + 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/integration/test/ParseEventuallyQueueTest.js b/integration/test/ParseEventuallyQueueTest.js index 1d8157553..8d5305915 100644 --- a/integration/test/ParseEventuallyQueueTest.js +++ b/integration/test/ParseEventuallyQueueTest.js @@ -195,7 +195,8 @@ describe('Parse EventuallyQueue', () => { const object = new TestObject({ hash: 'saveSecret' }); await new Promise((resolve) => parseServer.server.close(resolve)); await object.saveEventually(); - let length = await Parse.EventuallyQueue.length(); + + const length = await Parse.EventuallyQueue.length(); assert(Parse.EventuallyQueue.isPolling()); assert.strictEqual(length, 1); @@ -203,14 +204,6 @@ describe('Parse EventuallyQueue', () => { while (Parse.EventuallyQueue.isPolling()) { await sleep(100); } - assert.strictEqual(Parse.EventuallyQueue.isPolling(), false); - - while (await Parse.EventuallyQueue.length()) { - await sleep(100); - } - length = await Parse.EventuallyQueue.length(); - assert.strictEqual(length, 0); - const query = new Parse.Query(TestObject); query.equalTo('hash', 'saveSecret'); let results = await query.find(); @@ -233,10 +226,9 @@ describe('Parse EventuallyQueue', () => { object.setACL(acl); await new Promise((resolve) => parseServer.server.close(resolve)); - await object.saveEventually(); - let length = await Parse.EventuallyQueue.length(); + const length = await Parse.EventuallyQueue.length(); assert(Parse.EventuallyQueue.isPolling()); assert.strictEqual(length, 1); @@ -245,15 +237,6 @@ describe('Parse EventuallyQueue', () => { while (Parse.EventuallyQueue.isPolling()) { await sleep(100); } - assert.strictEqual(Parse.EventuallyQueue.isPolling(), false); - - length = await Parse.EventuallyQueue.length(); - while (length) { - await sleep(100); - } - length = await Parse.EventuallyQueue.length(); - assert.strictEqual(length, 0); - const query = new Parse.Query('TestObject'); query.equalTo('hash', 'saveSecret'); let results = await query.find(); @@ -269,7 +252,8 @@ describe('Parse EventuallyQueue', () => { await object.save(); await new Promise((resolve) => parseServer.server.close(resolve)); await object.destroyEventually(); - let length = await Parse.EventuallyQueue.length(); + const length = await Parse.EventuallyQueue.length(); + assert(Parse.EventuallyQueue.isPolling()); assert.strictEqual(length, 1); @@ -277,13 +261,6 @@ describe('Parse EventuallyQueue', () => { while (Parse.EventuallyQueue.isPolling()) { await sleep(100); } - assert.strictEqual(Parse.EventuallyQueue.isPolling(), false); - while (await Parse.EventuallyQueue.length()) { - await sleep(100); - } - length = await Parse.EventuallyQueue.length(); - assert.strictEqual(length, 0); - const query = new Parse.Query(TestObject); query.equalTo('hash', 'deleteSecret'); let results = await query.find(); diff --git a/integration/test/helper.js b/integration/test/helper.js index c32516c95..ee22aa42a 100644 --- a/integration/test/helper.js +++ b/integration/test/helper.js @@ -84,6 +84,7 @@ const defaultConfiguration = { revokeSessionOnPasswordReset: false, allowCustomObjectId: false, allowClientClassCreation: true, + encodeParseObjectInCloudFunction: true, emailAdapter: MockEmailAdapterWithOptions({ fromAddress: 'parse@example.com', apiKey: 'k', From 6afd32af3517c88b570505d5cb25bd5ab449f039 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Apr 2024 16:47:59 -0500 Subject: [PATCH 08/32] fix: Local datastore throws error when `Parse.Query.notEqualTo` is set to `null` (#2102) --- integration/test/ParseLocalDatastoreTest.js | 10 ++++++++++ src/OfflineQuery.js | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/integration/test/ParseLocalDatastoreTest.js b/integration/test/ParseLocalDatastoreTest.js index 91dd1b96c..4598539b7 100644 --- a/integration/test/ParseLocalDatastoreTest.js +++ b/integration/test/ParseLocalDatastoreTest.js @@ -1376,6 +1376,16 @@ function runTest(controller) { assert.equal(results.length, 9); }); + it(`${controller.name} can perform notEqualTo null queries`, async () => { + const nullObject = new Parse.Object({ className: 'BoxedNumber', number: null }); + await nullObject.save(); + const query = new Parse.Query('BoxedNumber'); + query.notEqualTo('number', null); + query.fromLocalDatastore(); + const results = await query.find(); + assert.equal(results.length, 10); + }); + it(`${controller.name} can perform containedIn queries`, async () => { const query = new Parse.Query('BoxedNumber'); query.containedIn('number', [3, 5, 7, 9, 11]); diff --git a/src/OfflineQuery.js b/src/OfflineQuery.js index efbc200c9..39220c2f8 100644 --- a/src/OfflineQuery.js +++ b/src/OfflineQuery.js @@ -315,11 +315,11 @@ function matchesKeyConstraints(className, object, objects, key, constraints) { for (const condition in constraints) { compareTo = constraints[condition]; - if (compareTo.__type) { + if (compareTo?.__type) { compareTo = decode(compareTo); } // is it a $relativeTime? convert to date - if (compareTo['$relativeTime']) { + if (compareTo?.['$relativeTime']) { const parserResult = relativeTimeToDate(compareTo['$relativeTime']); if (parserResult.status !== 'success') { throw new ParseError( From c58fddafbed37c6e81268fd83a6b44b5dc1852b2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 13 Apr 2024 21:52:33 +0000 Subject: [PATCH 09/32] chore(release): 5.1.0-alpha.2 [skip ci] # [5.1.0-alpha.2](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.1...5.1.0-alpha.2) (2024-04-13) ### Bug Fixes * Local datastore throws error when `Parse.Query.notEqualTo` is set to `null` ([#2102](https://github.com/parse-community/Parse-SDK-JS/issues/2102)) ([6afd32a](https://github.com/parse-community/Parse-SDK-JS/commit/6afd32af3517c88b570505d5cb25bd5ab449f039)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 819d4e3a4..3f7be6049 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.2](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.1...5.1.0-alpha.2) (2024-04-13) + + +### Bug Fixes + +* Local datastore throws error when `Parse.Query.notEqualTo` is set to `null` ([#2102](https://github.com/parse-community/Parse-SDK-JS/issues/2102)) ([6afd32a](https://github.com/parse-community/Parse-SDK-JS/commit/6afd32af3517c88b570505d5cb25bd5ab449f039)) + # [5.1.0-alpha.1](https://github.com/parse-community/Parse-SDK-JS/compare/5.0.0...5.1.0-alpha.1) (2024-03-31) diff --git a/package-lock.json b/package-lock.json index 21c51a58d..50eb3411e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.1.0-alpha.1", + "version": "5.1.0-alpha.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.1.0-alpha.1", + "version": "5.1.0-alpha.2", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index 49ab843a6..849cc05ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.1.0-alpha.1", + "version": "5.1.0-alpha.2", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From fbd0ab1402792e241c4d9d6496b451e4cc268b8b Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Apr 2024 14:53:48 -0500 Subject: [PATCH 10/32] feat: Lazy load `Parse.CoreManager` controllers to add support for swappable `CryptoController`, `LocalDatastoreController`, `StorageController`, `WebSocketController`, `ParseLiveQuery` (#2100) --- README.md | 13 +++ integration/test/ParseReactNativeTest.js | 104 +++++++++++++++++++ src/LiveQueryClient.js | 14 --- src/LocalDatastoreController.default.js | 67 ++++++++++++ src/LocalDatastoreController.js | 72 +------------ src/Parse.ts | 28 +++-- src/Storage.js | 10 -- src/StorageController.js | 9 ++ src/WebSocketController.js | 18 ++++ src/__tests__/InstallationController-test.js | 2 + src/__tests__/LiveQueryClient-test.js | 3 + src/__tests__/Parse-test.js | 13 +++ src/__tests__/ParseConfig-test.js | 2 + src/__tests__/ParseUser-test.js | 2 + src/__tests__/browser-test.js | 5 +- src/__tests__/react-native-test.js | 12 ++- src/__tests__/weapp-test.js | 8 +- 17 files changed, 278 insertions(+), 104 deletions(-) create mode 100644 integration/test/ParseReactNativeTest.js create mode 100644 src/LocalDatastoreController.default.js create mode 100644 src/StorageController.js create mode 100644 src/WebSocketController.js diff --git a/README.md b/README.md index ae223379f..5111d1d52 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ A library that gives you access to the powerful Parse Server backend from your J - [Getting Started](#getting-started) - [Using Parse on Different Platforms](#using-parse-on-different-platforms) + - [Core Manager](#core-manager) - [Compatibility](#compatibility) - [Parse Server](#parse-server) - [Node.js](#nodejs) @@ -89,6 +90,18 @@ $ npm install @types/parse Types are updated manually after every release. If a definition doesn't exist, please submit a pull request to [@types/parse][types-parse] +#### Core Manager + +The SDK has a [Core Manager](src/CoreManager.js) that handles all configurations and controllers. These modules can be swapped out for customization before you initialize the SDK. For full list of all available modules take a look at the [Core Manager Documentation](src/CoreManager.js). + +```js +// Configuration example +Parse.CoreManager.set('REQUEST_ATTEMPT_LIMIT', 1) + +// Controller example +Parse.CoreManager.setRESTController(MyRESTController); +``` + ## Compatibility ### Parse Server diff --git a/integration/test/ParseReactNativeTest.js b/integration/test/ParseReactNativeTest.js new file mode 100644 index 000000000..d7d30c68c --- /dev/null +++ b/integration/test/ParseReactNativeTest.js @@ -0,0 +1,104 @@ +'use strict'; + +const Parse = require('../../react-native'); +const { resolvingPromise } = require('../../lib/react-native/promiseUtils'); +const CryptoController = require('../../lib/react-native/CryptoController'); +const LocalDatastoreController = require('../../lib/react-native/LocalDatastoreController.default'); +const StorageController = require('../../lib/react-native/StorageController.default'); +const RESTController = require('../../lib/react-native/RESTController'); + +RESTController._setXHR(require('xmlhttprequest').XMLHttpRequest); + +describe('Parse React Native', () => { + beforeEach(() => { + // Set up missing controllers and configurations + Parse.CoreManager.setWebSocketController(require('ws')); + Parse.CoreManager.setEventEmitter(require('events').EventEmitter); + Parse.CoreManager.setLocalDatastoreController(LocalDatastoreController); + Parse.CoreManager.setStorageController(StorageController); + Parse.CoreManager.setRESTController(RESTController); + Parse.CoreManager.setCryptoController(CryptoController); + + Parse.initialize('integration'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.CoreManager.set('MASTER_KEY', 'notsosecret'); + Parse.enableLocalDatastore(); + }); + + afterEach(async () => { + await Parse.User.logOut(); + Parse.Storage._clear(); + }); + + it('can log in a user', async () => { + // Handle Storage Controller + await Parse.User.signUp('asdf', 'zxcv') + const user = await Parse.User.logIn('asdf', 'zxcv'); + expect(user.get('username')).toBe('asdf'); + expect(user.existed()).toBe(true); + }); + + it('can encrypt user', async () => { + // Handle Crypto Controller + Parse.User.enableUnsafeCurrentUser(); + Parse.enableEncryptedUser(); + Parse.secret = 'My Secret Key'; + const user = new Parse.User(); + user.setUsername('usernameENC'); + user.setPassword('passwordENC'); + await user.signUp(); + + const path = Parse.Storage.generatePath('currentUser'); + const encryptedUser = Parse.Storage.getItem(path); + + const crypto = Parse.CoreManager.getCryptoController(); + + const decryptedUser = crypto.decrypt(encryptedUser, Parse.CoreManager.get('ENCRYPTED_KEY')); + expect(JSON.parse(decryptedUser).objectId).toBe(user.id); + + const currentUser = Parse.User.current(); + expect(currentUser).toEqual(user); + + const currentUserAsync = await Parse.User.currentAsync(); + expect(currentUserAsync).toEqual(user); + await Parse.User.logOut(); + Parse.CoreManager.set('ENCRYPTED_USER', false); + Parse.CoreManager.set('ENCRYPTED_KEY', null); + }); + + it('can pin saved object LDS', async () => { + // Handle LocalDatastore Controller + function LDS_KEY(object) { + return Parse.LocalDatastore.getKeyForObject(object); + } + const object = new Parse.Object('TestObject'); + object.set('field', 'test'); + await object.save(); + await object.pin(); + const localDatastore = await Parse.LocalDatastore._getAllContents(); + const cachedObject = localDatastore[LDS_KEY(object)][0]; + expect(Object.keys(localDatastore).length).toBe(2); + expect(cachedObject.objectId).toBe(object.id); + expect(cachedObject.field).toBe('test'); + }); + + it('can subscribe to query', async () => { + // Handle WebSocket Controller + const object = new Parse.Object('TestObject'); + await object.save(); + const installationId = await Parse.CoreManager.getInstallationController().currentInstallationId(); + + const query = new Parse.Query('TestObject'); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + const promise = resolvingPromise(); + subscription.on('update', (object, _, response) => { + expect(object.get('foo')).toBe('bar'); + expect(response.installationId).toBe(installationId); + promise.resolve(); + }); + object.set({ foo: 'bar' }); + await object.save(); + await promise; + }); +}); diff --git a/src/LiveQueryClient.js b/src/LiveQueryClient.js index f7d3e5318..b66a353fa 100644 --- a/src/LiveQueryClient.js +++ b/src/LiveQueryClient.js @@ -1,5 +1,3 @@ -/* global WebSocket */ - import CoreManager from './CoreManager'; import ParseObject from './ParseObject'; import LiveQuerySubscription from './LiveQuerySubscription'; @@ -502,16 +500,4 @@ class LiveQueryClient { } } -if (process.env.PARSE_BUILD === 'node') { - CoreManager.setWebSocketController(require('ws')); -} else if (process.env.PARSE_BUILD === 'browser') { - CoreManager.setWebSocketController( - typeof WebSocket === 'function' || typeof WebSocket === 'object' ? WebSocket : null - ); -} else if (process.env.PARSE_BUILD === 'weapp') { - CoreManager.setWebSocketController(require('./Socket.weapp')); -} else if (process.env.PARSE_BUILD === 'react-native') { - CoreManager.setWebSocketController(WebSocket); -} - export default LiveQueryClient; diff --git a/src/LocalDatastoreController.default.js b/src/LocalDatastoreController.default.js new file mode 100644 index 000000000..7116f28fd --- /dev/null +++ b/src/LocalDatastoreController.default.js @@ -0,0 +1,67 @@ +/** + * @flow + */ +import { isLocalDatastoreKey } from './LocalDatastoreUtils'; +import Storage from './Storage'; + +const LocalDatastoreController = { + async fromPinWithName(name: string): Array { + const values = await Storage.getItemAsync(name); + if (!values) { + return []; + } + const objects = JSON.parse(values); + return objects; + }, + + pinWithName(name: string, value: any) { + const values = JSON.stringify(value); + return Storage.setItemAsync(name, values); + }, + + unPinWithName(name: string) { + return Storage.removeItemAsync(name); + }, + + async getAllContents(): Object { + const keys = await Storage.getAllKeysAsync(); + return keys.reduce(async (previousPromise, key) => { + const LDS = await previousPromise; + if (isLocalDatastoreKey(key)) { + const value = await Storage.getItemAsync(key); + try { + LDS[key] = JSON.parse(value); + } catch (error) { + console.error('Error getAllContents: ', error); + } + } + return LDS; + }, Promise.resolve({})); + }, + + // Used for testing + async getRawStorage(): Object { + const keys = await Storage.getAllKeysAsync(); + return keys.reduce(async (previousPromise, key) => { + const LDS = await previousPromise; + const value = await Storage.getItemAsync(key); + LDS[key] = value; + return LDS; + }, Promise.resolve({})); + }, + + async clear(): Promise { + const keys = await Storage.getAllKeysAsync(); + + const toRemove = []; + for (const key of keys) { + if (isLocalDatastoreKey(key)) { + toRemove.push(key); + } + } + const promises = toRemove.map(this.unPinWithName); + return Promise.all(promises); + }, +}; + +module.exports = LocalDatastoreController; diff --git a/src/LocalDatastoreController.js b/src/LocalDatastoreController.js index 7116f28fd..df2e666d9 100644 --- a/src/LocalDatastoreController.js +++ b/src/LocalDatastoreController.js @@ -1,67 +1,5 @@ -/** - * @flow - */ -import { isLocalDatastoreKey } from './LocalDatastoreUtils'; -import Storage from './Storage'; - -const LocalDatastoreController = { - async fromPinWithName(name: string): Array { - const values = await Storage.getItemAsync(name); - if (!values) { - return []; - } - const objects = JSON.parse(values); - return objects; - }, - - pinWithName(name: string, value: any) { - const values = JSON.stringify(value); - return Storage.setItemAsync(name, values); - }, - - unPinWithName(name: string) { - return Storage.removeItemAsync(name); - }, - - async getAllContents(): Object { - const keys = await Storage.getAllKeysAsync(); - return keys.reduce(async (previousPromise, key) => { - const LDS = await previousPromise; - if (isLocalDatastoreKey(key)) { - const value = await Storage.getItemAsync(key); - try { - LDS[key] = JSON.parse(value); - } catch (error) { - console.error('Error getAllContents: ', error); - } - } - return LDS; - }, Promise.resolve({})); - }, - - // Used for testing - async getRawStorage(): Object { - const keys = await Storage.getAllKeysAsync(); - return keys.reduce(async (previousPromise, key) => { - const LDS = await previousPromise; - const value = await Storage.getItemAsync(key); - LDS[key] = value; - return LDS; - }, Promise.resolve({})); - }, - - async clear(): Promise { - const keys = await Storage.getAllKeysAsync(); - - const toRemove = []; - for (const key of keys) { - if (isLocalDatastoreKey(key)) { - toRemove.push(key); - } - } - const promises = toRemove.map(this.unPinWithName); - return Promise.all(promises); - }, -}; - -module.exports = LocalDatastoreController; +if (process.env.PARSE_BUILD === 'react-native') { + module.exports = require('./LocalDatastoreController.react-native'); +} else { + module.exports = require('./LocalDatastoreController.default'); +} diff --git a/src/Parse.ts b/src/Parse.ts index dbf3a0983..6c1c5a9cd 100644 --- a/src/Parse.ts +++ b/src/Parse.ts @@ -30,8 +30,11 @@ import Schema from './ParseSchema' import Session from './ParseSession' import Storage from './Storage' import User from './ParseUser' -import LiveQuery from './ParseLiveQuery' +import ParseLiveQuery from './ParseLiveQuery' import LiveQueryClient from './LiveQueryClient' +import LocalDatastoreController from './LocalDatastoreController'; +import StorageController from './StorageController'; +import WebSocketController from './WebSocketController'; /** * Contains all Parse API classes and functions. @@ -78,7 +81,7 @@ interface ParseType { Session: typeof Session, Storage: typeof Storage, User: typeof User, - LiveQuery?: typeof LiveQuery, + LiveQuery: ParseLiveQuery, LiveQueryClient: typeof LiveQueryClient, initialize(applicationId: string, javaScriptKey: string): void, @@ -146,7 +149,6 @@ const Parse: ParseType = { Storage: Storage, User: User, LiveQueryClient: LiveQueryClient, - LiveQuery: undefined, IndexedDB: undefined, Hooks: undefined, Parse: undefined, @@ -181,9 +183,11 @@ const Parse: ParseType = { CoreManager.set('MASTER_KEY', masterKey); CoreManager.set('USE_MASTER_KEY', false); CoreManager.setIfNeeded('EventEmitter', EventEmitter); - - Parse.LiveQuery = new LiveQuery(); - CoreManager.setIfNeeded('LiveQuery', Parse.LiveQuery); + CoreManager.setIfNeeded('LiveQuery', new ParseLiveQuery()); + CoreManager.setIfNeeded('CryptoController', CryptoController); + CoreManager.setIfNeeded('LocalDatastoreController', LocalDatastoreController); + CoreManager.setIfNeeded('StorageController', StorageController); + CoreManager.setIfNeeded('WebSocketController', WebSocketController); if (process.env.PARSE_BUILD === 'browser') { Parse.IndexedDB = CoreManager.setIfNeeded('IndexedDBStorageController', IndexedDBStorageController); @@ -289,6 +293,17 @@ const Parse: ParseType = { return CoreManager.get('SERVER_AUTH_TYPE'); }, + /** + * @member {ParseLiveQuery} Parse.LiveQuery + * @static + */ + set LiveQuery(liveQuery: ParseLiveQuery) { + CoreManager.setLiveQuery(liveQuery); + }, + get LiveQuery() { + return CoreManager.getLiveQuery(); + }, + /** * @member {string} Parse.liveQueryServerURL * @static @@ -433,7 +448,6 @@ const Parse: ParseType = { }, }; -CoreManager.setCryptoController(CryptoController); CoreManager.setInstallationController(InstallationController); CoreManager.setRESTController(RESTController); diff --git a/src/Storage.js b/src/Storage.js index 67392c249..8f7599ea5 100644 --- a/src/Storage.js +++ b/src/Storage.js @@ -97,13 +97,3 @@ const Storage = { module.exports = Storage; export default Storage; - -if (process.env.PARSE_BUILD === 'react-native') { - CoreManager.setStorageController(require('./StorageController.react-native')); -} else if (process.env.PARSE_BUILD === 'browser') { - CoreManager.setStorageController(require('./StorageController.browser')); -} else if (process.env.PARSE_BUILD === 'weapp') { - CoreManager.setStorageController(require('./StorageController.weapp')); -} else { - CoreManager.setStorageController(require('./StorageController.default')); -} diff --git a/src/StorageController.js b/src/StorageController.js new file mode 100644 index 000000000..33508d75b --- /dev/null +++ b/src/StorageController.js @@ -0,0 +1,9 @@ +if (process.env.PARSE_BUILD === 'react-native') { + module.exports = require('./StorageController.react-native'); +} else if (process.env.PARSE_BUILD === 'browser') { + module.exports = require('./StorageController.browser'); +} else if (process.env.PARSE_BUILD === 'weapp') { + module.exports = require('./StorageController.weapp'); +} else { + module.exports = require('./StorageController.default'); +} diff --git a/src/WebSocketController.js b/src/WebSocketController.js new file mode 100644 index 000000000..c3e1d0ca4 --- /dev/null +++ b/src/WebSocketController.js @@ -0,0 +1,18 @@ +/* global WebSocket */ + +let WebSocketController; + +try { + if (process.env.PARSE_BUILD === 'browser') { + WebSocketController = (typeof WebSocket === 'function' || typeof WebSocket === 'object' ? WebSocket : null); + } else if (process.env.PARSE_BUILD === 'node') { + WebSocketController = require('ws'); + } else if (process.env.PARSE_BUILD === 'weapp') { + WebSocketController = require('./Socket.weapp'); + } else if (process.env.PARSE_BUILD === 'react-native') { + WebSocketController = WebSocket; + } +} catch (_) { + // WebSocket unavailable +} +module.exports = WebSocketController; diff --git a/src/__tests__/InstallationController-test.js b/src/__tests__/InstallationController-test.js index 2085131c4..dcfad0a45 100644 --- a/src/__tests__/InstallationController-test.js +++ b/src/__tests__/InstallationController-test.js @@ -11,6 +11,8 @@ const CoreManager = require('../CoreManager'); const InstallationController = require('../InstallationController'); const Storage = require('../Storage'); +CoreManager.setStorageController(require('../StorageController.default')); + describe('InstallationController', () => { beforeEach(() => { CoreManager.set('APPLICATION_ID', 'A'); diff --git a/src/__tests__/LiveQueryClient-test.js b/src/__tests__/LiveQueryClient-test.js index 9cd4e4f5f..4588cc1cc 100644 --- a/src/__tests__/LiveQueryClient-test.js +++ b/src/__tests__/LiveQueryClient-test.js @@ -25,6 +25,7 @@ jest.dontMock('../ParseACL'); jest.dontMock('../ParseQuery'); jest.dontMock('../LiveQuerySubscription'); jest.dontMock('../LocalDatastore'); +jest.dontMock('../WebSocketController'); jest.useFakeTimers(); @@ -39,10 +40,12 @@ const EventEmitter = require('../EventEmitter'); const LiveQueryClient = require('../LiveQueryClient').default; const ParseObject = require('../ParseObject').default; const ParseQuery = require('../ParseQuery').default; +const WebSocketController = require('../WebSocketController'); const { resolvingPromise } = require('../promiseUtils'); const events = require('events'); CoreManager.setLocalDatastore(mockLocalDatastore); +CoreManager.setWebSocketController(WebSocketController); describe('LiveQueryClient', () => { beforeEach(() => { diff --git a/src/__tests__/Parse-test.js b/src/__tests__/Parse-test.js index 560585230..6a9e19737 100644 --- a/src/__tests__/Parse-test.js +++ b/src/__tests__/Parse-test.js @@ -3,15 +3,20 @@ jest.dontMock('../CryptoController'); jest.dontMock('../decode'); jest.dontMock('../encode'); jest.dontMock('../Parse'); +jest.dontMock('../ParseObject'); +jest.dontMock('../ParseLiveQuery'); jest.dontMock('../LocalDatastore'); jest.dontMock('crypto-js/aes'); jest.setMock('../EventuallyQueue', { poll: jest.fn() }); global.indexedDB = require('./test_helpers/mockIndexedDB'); const CoreManager = require('../CoreManager'); +const ParseLiveQuery = require('../ParseLiveQuery').default; const EventuallyQueue = require('../EventuallyQueue'); const Parse = require('../Parse'); +CoreManager.setEventEmitter(require('events').EventEmitter); + describe('Parse module', () => { it('can be initialized with keys', () => { Parse.initialize('A', 'B'); @@ -173,6 +178,14 @@ describe('Parse module', () => { CoreManager.set('REQUEST_BATCH_SIZE', 20); }); + it('can set and get live query', () => { + const temp = Parse.LiveQuery; + const LiveQuery = new ParseLiveQuery(); + Parse.LiveQuery = LiveQuery + expect(Parse.LiveQuery).toEqual(LiveQuery); + Parse.LiveQuery = temp; + }); + it('can set allowCustomObjectId', () => { expect(Parse.allowCustomObjectId).toBe(false); Parse.allowCustomObjectId = true; diff --git a/src/__tests__/ParseConfig-test.js b/src/__tests__/ParseConfig-test.js index a3aa5546b..e662bd490 100644 --- a/src/__tests__/ParseConfig-test.js +++ b/src/__tests__/ParseConfig-test.js @@ -16,9 +16,11 @@ const CoreManager = require('../CoreManager'); const ParseConfig = require('../ParseConfig').default; const ParseGeoPoint = require('../ParseGeoPoint').default; const Storage = require('../Storage'); +const StorageController = require('../StorageController.default'); CoreManager.set('APPLICATION_ID', 'A'); CoreManager.set('JAVASCRIPT_KEY', 'B'); +CoreManager.set('StorageController', StorageController); describe('ParseConfig', () => { beforeEach(() => { diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js index 631842b8e..16f25e682 100644 --- a/src/__tests__/ParseUser-test.js +++ b/src/__tests__/ParseUser-test.js @@ -34,6 +34,7 @@ const flushPromises = require('./test_helpers/flushPromises'); const mockAsyncStorage = require('./test_helpers/mockAsyncStorage'); const CoreManager = require('../CoreManager'); const CryptoController = require('../CryptoController'); +const StorageController = require('../StorageController.default'); const LocalDatastore = require('../LocalDatastore'); const ParseObject = require('../ParseObject').default; const ParseUser = require('../ParseUser').default; @@ -44,6 +45,7 @@ const AnonymousUtils = require('../AnonymousUtils').default; CoreManager.set('APPLICATION_ID', 'A'); CoreManager.set('JAVASCRIPT_KEY', 'B'); CoreManager.setCryptoController(CryptoController); +CoreManager.setStorageController(StorageController); describe('ParseUser', () => { beforeEach(() => { diff --git a/src/__tests__/browser-test.js b/src/__tests__/browser-test.js index e89714204..80d84ad98 100644 --- a/src/__tests__/browser-test.js +++ b/src/__tests__/browser-test.js @@ -10,6 +10,7 @@ jest.dontMock('../Storage'); jest.dontMock('crypto-js/aes'); jest.setMock('../EventuallyQueue', { poll: jest.fn() }); +const CoreManager = require('../CoreManager'); const ParseError = require('../ParseError').default; const EventuallyQueue = require('../EventuallyQueue'); @@ -61,7 +62,9 @@ describe('Browser', () => { }); it('load StorageController', () => { - const StorageController = require('../StorageController.browser'); + const StorageController = require('../StorageController'); + CoreManager.setStorageController(StorageController); + jest.spyOn(StorageController, 'setItem'); const storage = require('../Storage'); storage.setItem('key', 'value'); diff --git a/src/__tests__/react-native-test.js b/src/__tests__/react-native-test.js index 02c0a1e76..731684760 100644 --- a/src/__tests__/react-native-test.js +++ b/src/__tests__/react-native-test.js @@ -8,7 +8,8 @@ jest.dontMock('../LiveQueryClient'); jest.dontMock('../LocalDatastore'); jest.dontMock('../ParseObject'); jest.dontMock('../Storage'); - +jest.dontMock('../LocalDatastoreController'); +jest.dontMock('../WebSocketController'); jest.mock( 'react-native/Libraries/vendor/emitter/EventEmitter', () => { @@ -54,14 +55,16 @@ describe('React Native', () => { }); it('load LocalDatastoreController', () => { - const LocalDatastoreController = require('../LocalDatastoreController.react-native'); + const LocalDatastoreController = require('../LocalDatastoreController'); require('../LocalDatastore'); const LDC = CoreManager.getLocalDatastoreController(); expect(LocalDatastoreController).toEqual(LDC); }); it('load StorageController', () => { - const StorageController = require('../StorageController.react-native'); + const StorageController = require('../StorageController'); + CoreManager.setStorageController(StorageController); + jest.spyOn(StorageController, 'setItemAsync'); const storage = require('../Storage'); storage.setItemAsync('key', 'value'); @@ -69,6 +72,9 @@ describe('React Native', () => { }); it('load WebSocketController', () => { + const WebSocketController = require('../WebSocketController'); + CoreManager.setWebSocketController(WebSocketController); + jest.mock('../EventEmitter', () => { return require('events').EventEmitter; }); diff --git a/src/__tests__/weapp-test.js b/src/__tests__/weapp-test.js index ca815502c..c693d087e 100644 --- a/src/__tests__/weapp-test.js +++ b/src/__tests__/weapp-test.js @@ -10,6 +10,7 @@ jest.dontMock('../ParseObject'); jest.dontMock('../RESTController'); jest.dontMock('../Socket.weapp'); jest.dontMock('../Storage'); +jest.dontMock('../StorageController.weapp'); jest.dontMock('../uuid'); jest.dontMock('crypto-js/aes'); jest.dontMock('./test_helpers/mockWeChat'); @@ -33,7 +34,8 @@ describe('WeChat', () => { }); it('load StorageController', () => { - const StorageController = require('../StorageController.weapp'); + const StorageController = require('../StorageController'); + CoreManager.setStorageController(StorageController); jest.spyOn(StorageController, 'setItem'); const storage = require('../Storage'); storage.setItem('key', 'value'); @@ -54,7 +56,9 @@ describe('WeChat', () => { }); it('load WebSocketController', () => { - const socket = require('../Socket.weapp'); + const socket = require('../WebSocketController'); + CoreManager.setWebSocketController(socket); + require('../LiveQueryClient'); const websocket = CoreManager.getWebSocketController(); expect(websocket).toEqual(socket); From a82177739e4f938e3ed9b853c957e6887137b5d2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 14 Apr 2024 19:55:13 +0000 Subject: [PATCH 11/32] chore(release): 5.1.0-alpha.3 [skip ci] # [5.1.0-alpha.3](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.2...5.1.0-alpha.3) (2024-04-14) ### Features * Lazy load `Parse.CoreManager` controllers to add support for swappable `CryptoController`, `LocalDatastoreController`, `StorageController`, `WebSocketController`, `ParseLiveQuery` ([#2100](https://github.com/parse-community/Parse-SDK-JS/issues/2100)) ([fbd0ab1](https://github.com/parse-community/Parse-SDK-JS/commit/fbd0ab1402792e241c4d9d6496b451e4cc268b8b)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 3f7be6049..560f31d95 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.3](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.2...5.1.0-alpha.3) (2024-04-14) + + +### Features + +* Lazy load `Parse.CoreManager` controllers to add support for swappable `CryptoController`, `LocalDatastoreController`, `StorageController`, `WebSocketController`, `ParseLiveQuery` ([#2100](https://github.com/parse-community/Parse-SDK-JS/issues/2100)) ([fbd0ab1](https://github.com/parse-community/Parse-SDK-JS/commit/fbd0ab1402792e241c4d9d6496b451e4cc268b8b)) + # [5.1.0-alpha.2](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.1...5.1.0-alpha.2) (2024-04-13) diff --git a/package-lock.json b/package-lock.json index 50eb3411e..efcd2dd91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.1.0-alpha.2", + "version": "5.1.0-alpha.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.1.0-alpha.2", + "version": "5.1.0-alpha.3", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index 849cc05ee..2ab8ffad0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.1.0-alpha.2", + "version": "5.1.0-alpha.3", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From 7a8966522f06efb3f0303b2a3c6fd08f41d8aff9 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Mon, 15 Apr 2024 10:29:46 -0500 Subject: [PATCH 12/32] fix: Live Query not working on Expo React Native (#2109) --- integration/test/ParseLiveQueryTest.js | 38 ++++++++++++++++++++++++++ src/LiveQueryClient.js | 4 +-- src/LiveQuerySubscription.js | 4 +-- src/ParseLiveQuery.js | 5 ++-- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/integration/test/ParseLiveQueryTest.js b/integration/test/ParseLiveQueryTest.js index c33f21a00..06a2c89d9 100644 --- a/integration/test/ParseLiveQueryTest.js +++ b/integration/test/ParseLiveQueryTest.js @@ -4,6 +4,7 @@ const assert = require('assert'); const Parse = require('../../node'); const sleep = require('./sleep'); const { resolvingPromise } = require('../../lib/node/promiseUtils'); +const { EventEmitter } = require('events'); describe('Parse LiveQuery', () => { beforeEach(() => { @@ -367,4 +368,41 @@ describe('Parse LiveQuery', () => { client.state = 'closed'; await client.close(); }); + + it('can subscribe to query with EventEmitter private fields', async () => { + class CustomEmitter { + #privateEmitter; + + constructor() { + this.#privateEmitter = new EventEmitter(); + } + on(event, listener) { + this.#privateEmitter.on(event, listener); + } + emit(event, ...args) { + this.#privateEmitter.emit(event, ...args); + } + } + + const EV = Parse.CoreManager.getEventEmitter(); + + Parse.CoreManager.setEventEmitter(CustomEmitter); + const object = new TestObject(); + await object.save(); + const installationId = await Parse.CoreManager.getInstallationController().currentInstallationId(); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + const promise = resolvingPromise(); + subscription.on('update', (object, original, response) => { + assert.equal(object.get('foo'), 'bar'); + assert.equal(response.installationId, installationId); + promise.resolve(); + }); + object.set({ foo: 'bar' }); + await object.save(); + await promise; + Parse.CoreManager.setEventEmitter(EV); + }); }); diff --git a/src/LiveQueryClient.js b/src/LiveQueryClient.js index b66a353fa..9c65ef74d 100644 --- a/src/LiveQueryClient.js +++ b/src/LiveQueryClient.js @@ -157,8 +157,8 @@ class LiveQueryClient { const EventEmitter = CoreManager.getEventEmitter(); this.emitter = new EventEmitter(); - this.on = this.emitter.on; - this.emit = this.emitter.emit; + this.on = (eventName, listener) => this.emitter.on(eventName, listener); + this.emit = (eventName, ...args) => this.emitter.emit(eventName, ...args); // adding listener so process does not crash // best practice is for developer to register their own listener this.on('error', () => {}); diff --git a/src/LiveQuerySubscription.js b/src/LiveQuerySubscription.js index c70d234c1..8a1368eb8 100644 --- a/src/LiveQuerySubscription.js +++ b/src/LiveQuerySubscription.js @@ -99,8 +99,8 @@ class Subscription { const EventEmitter = CoreManager.getEventEmitter(); this.emitter = new EventEmitter(); - this.on = this.emitter.on; - this.emit = this.emitter.emit; + this.on = (eventName, listener) => this.emitter.on(eventName, listener); + this.emit = (eventName, ...args) => this.emitter.emit(eventName, ...args); // adding listener so process does not crash // best practice is for developer to register their own listener this.on('error', () => {}); diff --git a/src/ParseLiveQuery.js b/src/ParseLiveQuery.js index eea9e59e2..c293133c7 100644 --- a/src/ParseLiveQuery.js +++ b/src/ParseLiveQuery.js @@ -39,9 +39,8 @@ class LiveQuery { constructor() { const EventEmitter = CoreManager.getEventEmitter(); this.emitter = new EventEmitter(); - this.on = this.emitter.on; - this.emit = this.emitter.emit; - + this.on = (eventName, listener) => this.emitter.on(eventName, listener); + this.emit = (eventName, ...args) => this.emitter.emit(eventName, ...args); // adding listener so process does not crash // best practice is for developer to register their own listener this.on('error', () => {}); From c435272052f3a36f02fb42fda6e1299df15b2cfc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 15 Apr 2024 15:31:10 +0000 Subject: [PATCH 13/32] chore(release): 5.1.0-alpha.4 [skip ci] # [5.1.0-alpha.4](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.3...5.1.0-alpha.4) (2024-04-15) ### Bug Fixes * Live Query not working on Expo React Native ([#2109](https://github.com/parse-community/Parse-SDK-JS/issues/2109)) ([7a89665](https://github.com/parse-community/Parse-SDK-JS/commit/7a8966522f06efb3f0303b2a3c6fd08f41d8aff9)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 560f31d95..65f6b441b 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.4](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.3...5.1.0-alpha.4) (2024-04-15) + + +### Bug Fixes + +* Live Query not working on Expo React Native ([#2109](https://github.com/parse-community/Parse-SDK-JS/issues/2109)) ([7a89665](https://github.com/parse-community/Parse-SDK-JS/commit/7a8966522f06efb3f0303b2a3c6fd08f41d8aff9)) + # [5.1.0-alpha.3](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.2...5.1.0-alpha.3) (2024-04-14) diff --git a/package-lock.json b/package-lock.json index efcd2dd91..7a9e72fb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.1.0-alpha.3", + "version": "5.1.0-alpha.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.1.0-alpha.3", + "version": "5.1.0-alpha.4", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index 2ab8ffad0..b7a71aa16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.1.0-alpha.3", + "version": "5.1.0-alpha.4", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From 0576f56785f1363eec7d10eeee325ec377abb85b Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:51:53 +0200 Subject: [PATCH 14/32] ci: Create Lint CI job (#2110) --- .github/workflows/ci.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44a2d4a20..cedf086df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,20 @@ jobs: - run: npm ci - name: Check Docs run: npm run docs + check-lint: + name: Lint + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + cache: npm + - name: Install dependencies + run: npm ci + - name: Lint + run: npm run lint build: runs-on: ubuntu-latest timeout-minutes: 30 @@ -55,7 +69,6 @@ jobs: node-version: ${{ matrix.NODE_VERSION }} cache: npm - run: npm ci - - run: npm run lint - run: npm test -- --maxWorkers=4 - run: npm run test:mongodb env: From 7b73c033eef8977c3e6c7e4af7146ffa74deed0c Mon Sep 17 00:00:00 2001 From: Morten Moeller Date: Mon, 22 Apr 2024 15:02:46 -0500 Subject: [PATCH 15/32] fix: Chrome browser console warning about unsafe header `access-control-expose-headers` when calling Cloud Function (#2095) --- src/RESTController.js | 7 +++- src/__tests__/RESTController-test.js | 63 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/RESTController.js b/src/RESTController.js index 552c3d058..59090e76e 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -111,11 +111,14 @@ const RESTController = { let response; try { response = JSON.parse(xhr.responseText); + const availableHeaders = typeof xhr.getAllResponseHeaders === 'function' ? xhr.getAllResponseHeaders() : ""; headers = {}; - if (typeof xhr.getResponseHeader === 'function' && xhr.getResponseHeader('access-control-expose-headers')) { + if (typeof xhr.getResponseHeader === 'function' && availableHeaders?.indexOf('access-control-expose-headers') >= 0) { const responseHeaders = xhr.getResponseHeader('access-control-expose-headers').split(', '); responseHeaders.forEach(header => { - headers[header] = xhr.getResponseHeader(header.toLowerCase()); + if (availableHeaders.indexOf(header.toLowerCase()) >= 0) { + headers[header] = xhr.getResponseHeader(header.toLowerCase()); + } }); } } catch (e) { diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 02d72e94d..640d83bc5 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -221,6 +221,9 @@ describe('RESTController', () => { getResponseHeader: function (header) { return headers[header]; }, + getAllResponseHeaders: function() { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n'); + }, send: function () { this.status = 200; this.responseText = '{}'; @@ -241,6 +244,9 @@ describe('RESTController', () => { getResponseHeader: function (header) { return headers[header]; }, + getAllResponseHeaders: function() { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n'); + }, send: function () { this.status = 200; this.responseText = '{}'; @@ -253,6 +259,63 @@ describe('RESTController', () => { expect(response._headers['X-Parse-Push-Status-Id']).toBe('5678'); }); + it('does not call getRequestHeader with no headers or no getAllResponseHeaders', async () => { + const XHR = function () {}; + XHR.prototype = { + open: function () {}, + setRequestHeader: function () {}, + getResponseHeader: jest.fn(), + send: function () { + this.status = 200; + this.responseText = '{"result":"hello"}'; + this.readyState = 4; + this.onreadystatechange(); + }, + }; + RESTController._setXHR(XHR); + await RESTController.request('GET', 'classes/MyObject', {}, {}); + expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(0); + + XHR.prototype.getAllResponseHeaders = jest.fn(); + await RESTController.request('GET', 'classes/MyObject', {}, {}); + expect(XHR.prototype.getAllResponseHeaders.mock.calls.length).toBe(1); + expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(0); + }); + + it('does not invoke Chrome browser console error on getResponseHeader', async () => { + const headers = { + 'access-control-expose-headers': 'a, b, c', + 'a' : 'value', + 'b' : 'value', + 'c' : 'value', + } + const XHR = function () {}; + XHR.prototype = { + open: function () {}, + setRequestHeader: function () {}, + getResponseHeader: jest.fn(key => { + if (Object.keys(headers).includes(key)) { + return headers[key]; + } + throw new Error("Chrome creates a console error here."); + }), + getAllResponseHeaders: jest.fn(() => { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\r\n'); + }), + send: function () { + this.status = 200; + this.responseText = '{"result":"hello"}'; + this.readyState = 4; + this.onreadystatechange(); + }, + }; + RESTController._setXHR(XHR); + await RESTController.request('GET', 'classes/MyObject', {}, {}); + expect(XHR.prototype.getAllResponseHeaders.mock.calls.length).toBe(1); + expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(4); + }); + + it('handles invalid header', async () => { const XHR = function () {}; XHR.prototype = { From 94e9948f570c70b983268f7c3daec4298a254e93 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 22 Apr 2024 20:03:55 +0000 Subject: [PATCH 16/32] chore(release): 5.1.0-alpha.5 [skip ci] # [5.1.0-alpha.5](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.4...5.1.0-alpha.5) (2024-04-22) ### Bug Fixes * Chrome browser console warning about unsafe header `access-control-expose-headers` when calling Cloud Function ([#2095](https://github.com/parse-community/Parse-SDK-JS/issues/2095)) ([7b73c03](https://github.com/parse-community/Parse-SDK-JS/commit/7b73c033eef8977c3e6c7e4af7146ffa74deed0c)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 65f6b441b..e010c09a6 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.5](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.4...5.1.0-alpha.5) (2024-04-22) + + +### Bug Fixes + +* Chrome browser console warning about unsafe header `access-control-expose-headers` when calling Cloud Function ([#2095](https://github.com/parse-community/Parse-SDK-JS/issues/2095)) ([7b73c03](https://github.com/parse-community/Parse-SDK-JS/commit/7b73c033eef8977c3e6c7e4af7146ffa74deed0c)) + # [5.1.0-alpha.4](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.3...5.1.0-alpha.4) (2024-04-15) diff --git a/package-lock.json b/package-lock.json index 7a9e72fb7..0eb9bc00b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.1.0-alpha.4", + "version": "5.1.0-alpha.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.1.0-alpha.4", + "version": "5.1.0-alpha.5", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index b7a71aa16..e776f720a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.1.0-alpha.4", + "version": "5.1.0-alpha.5", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From f92e4d42afdc1e55bcfff1ba9d0658d39943f3f0 Mon Sep 17 00:00:00 2001 From: Morten Moeller Date: Thu, 25 Apr 2024 08:24:47 -0500 Subject: [PATCH 17/32] feat: Allow setting custom queue for handling offline operations via `Parse.EventuallyQueue` (#2106) --- src/CoreManager.js | 16 +++++++++++++++- src/Parse.ts | 17 +++++++++++++++-- src/ParseObject.js | 9 ++++----- src/__tests__/Parse-test.js | 20 ++++++++++++++++++++ src/__tests__/ParseObject-test.js | 1 + 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/CoreManager.js b/src/CoreManager.js index 60467e2a6..de5efba95 100644 --- a/src/CoreManager.js +++ b/src/CoreManager.js @@ -6,7 +6,7 @@ import type { AttributeMap, ObjectCache, OpsMap, State } from './ObjectStateMuta import type ParseFile from './ParseFile'; import type { FileSource } from './ParseFile'; import type { Op } from './ParseOp'; -import type ParseObject from './ParseObject'; +import type ParseObject, {SaveOptions} from './ParseObject'; import type { QueryJSON } from './ParseQuery'; import type ParseUser from './ParseUser'; import type { AuthData } from './ParseUser'; @@ -73,6 +73,11 @@ type QueryController = { find: (className: string, params: QueryJSON, options: RequestOptions) => Promise, aggregate: (className: string, params: any, options: RequestOptions) => Promise, }; +type EventuallyQueue = { + save: (object: ParseObject, serverOptions: SaveOptions) => Promise, + destroy: (object: ParseObject, serverOptions: RequestOptions) => Promise, + poll: (ms: number) => void +}; type RESTController = { request: (method: string, path: string, data: mixed, options: RequestOptions) => Promise, ajax: (method: string, url: string, data: any, headers?: any, options: FullOptions) => Promise, @@ -363,6 +368,15 @@ const CoreManager = { return config['RESTController']; }, + setEventuallyQueue(controller: EventuallyQueue) { + requireMethods('EventuallyQueue', ['poll', 'save', 'destroy'], controller); + config['EventuallyQueue'] = controller; + }, + + getEventuallyQueue(): EventuallyQueue { + return config['EventuallyQueue']; + }, + setSchemaController(controller: SchemaController) { requireMethods( 'SchemaController', diff --git a/src/Parse.ts b/src/Parse.ts index 6c1c5a9cd..4aa88dbd1 100644 --- a/src/Parse.ts +++ b/src/Parse.ts @@ -123,7 +123,6 @@ const Parse: ParseType = { CoreManager: CoreManager, Config: Config, Error: ParseError, - EventuallyQueue: EventuallyQueue, FacebookUtils: FacebookUtils, File: File, GeoPoint: GeoPoint, @@ -153,6 +152,18 @@ const Parse: ParseType = { Hooks: undefined, Parse: undefined, + /** + * @member {EventuallyQueue} Parse.EventuallyQueue + * @static + */ + set EventuallyQueue(queue: EventuallyQueue) { + CoreManager.setEventuallyQueue(queue); + }, + + get EventuallyQueue() { + return CoreManager.getEventuallyQueue(); + }, + /** * Call this method first to set up your authentication tokens for Parse. * @@ -189,6 +200,8 @@ const Parse: ParseType = { CoreManager.setIfNeeded('StorageController', StorageController); CoreManager.setIfNeeded('WebSocketController', WebSocketController); + CoreManager.setIfNeeded('EventuallyQueue', EventuallyQueue); + if (process.env.PARSE_BUILD === 'browser') { Parse.IndexedDB = CoreManager.setIfNeeded('IndexedDBStorageController', IndexedDBStorageController); } @@ -395,7 +408,7 @@ const Parse: ParseType = { if (!this.LocalDatastore.isEnabled) { this.LocalDatastore.isEnabled = true; if (polling) { - EventuallyQueue.poll(ms); + CoreManager.getEventuallyQueue().poll(ms); } } }, diff --git a/src/ParseObject.js b/src/ParseObject.js index eafa7f45d..826a2458e 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -7,7 +7,6 @@ import canBeSerialized from './canBeSerialized'; import decode from './decode'; import encode from './encode'; import escape from './escape'; -import EventuallyQueue from './EventuallyQueue'; import ParseACL from './ParseACL'; import parseDate from './parseDate'; import ParseError from './ParseError'; @@ -1220,8 +1219,8 @@ class ParseObject { await this.save(null, options); } catch (e) { if (e.code === ParseError.CONNECTION_FAILED) { - await EventuallyQueue.save(this, options); - EventuallyQueue.poll(); + await CoreManager.getEventuallyQueue().save(this, options); + CoreManager.getEventuallyQueue().poll(); } } return this; @@ -1366,8 +1365,8 @@ class ParseObject { await this.destroy(options); } catch (e) { if (e.code === ParseError.CONNECTION_FAILED) { - await EventuallyQueue.destroy(this, options); - EventuallyQueue.poll(); + await CoreManager.getEventuallyQueue().destroy(this, options); + CoreManager.getEventuallyQueue().poll(); } } return this; diff --git a/src/__tests__/Parse-test.js b/src/__tests__/Parse-test.js index 6a9e19737..d74b76373 100644 --- a/src/__tests__/Parse-test.js +++ b/src/__tests__/Parse-test.js @@ -94,6 +94,14 @@ describe('Parse module', () => { expect(CoreManager.getLocalDatastoreController()).toBe(controller); }); + it('cannot set EventuallyQueue controller with missing functions', () => { + const controller = { + }; + expect(() => Parse.EventuallyQueue = controller).toThrow( + 'EventuallyQueue must implement poll()' + ); + }); + it('can set AsyncStorage', () => { const controller = { getItem: function () {}, @@ -258,4 +266,16 @@ describe('Parse module', () => { process.env.PARSE_BUILD = 'node'; }); }); + + it('can set EventuallyQueue', () => { + const controller = { + poll: function () {}, + save: function () {}, + destroy: function () {}, + }; + + Parse.EventuallyQueue = controller; + expect(CoreManager.getEventuallyQueue()).toBe(controller); + expect(Parse.EventuallyQueue).toBe(controller); + }); }); diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 2066a8979..06a7f3c2f 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -160,6 +160,7 @@ const flushPromises = require('./test_helpers/flushPromises'); CoreManager.setLocalDatastore(mockLocalDatastore); CoreManager.setRESTController(RESTController); +CoreManager.setEventuallyQueue(EventuallyQueue); CoreManager.setInstallationController({ currentInstallationId() { return Promise.resolve('iid'); From 8f30edf2f7d6697e7711eb8fc789d40db6b41011 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 25 Apr 2024 13:26:10 +0000 Subject: [PATCH 18/32] chore(release): 5.1.0-alpha.6 [skip ci] # [5.1.0-alpha.6](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.5...5.1.0-alpha.6) (2024-04-25) ### Features * Allow setting custom queue for handling offline operations via `Parse.EventuallyQueue` ([#2106](https://github.com/parse-community/Parse-SDK-JS/issues/2106)) ([f92e4d4](https://github.com/parse-community/Parse-SDK-JS/commit/f92e4d42afdc1e55bcfff1ba9d0658d39943f3f0)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index e010c09a6..0c119689c 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.6](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.5...5.1.0-alpha.6) (2024-04-25) + + +### Features + +* Allow setting custom queue for handling offline operations via `Parse.EventuallyQueue` ([#2106](https://github.com/parse-community/Parse-SDK-JS/issues/2106)) ([f92e4d4](https://github.com/parse-community/Parse-SDK-JS/commit/f92e4d42afdc1e55bcfff1ba9d0658d39943f3f0)) + # [5.1.0-alpha.5](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.4...5.1.0-alpha.5) (2024-04-22) diff --git a/package-lock.json b/package-lock.json index 0eb9bc00b..25a369781 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.1.0-alpha.5", + "version": "5.1.0-alpha.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.1.0-alpha.5", + "version": "5.1.0-alpha.6", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index e776f720a..e641a35ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.1.0-alpha.5", + "version": "5.1.0-alpha.6", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From fa4341a8c0ce5a9c478435250b4af6ea020a45bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Wed, 1 May 2024 23:05:46 +0200 Subject: [PATCH 19/32] fix: Multiple object updates of nested keys overwrite each other (#1451) --- src/ObjectStateMutations.js | 6 +-- src/__tests__/ParseObject-test.js | 63 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/ObjectStateMutations.js b/src/ObjectStateMutations.js index 271e4f6b5..dfcbcff87 100644 --- a/src/ObjectStateMutations.js +++ b/src/ObjectStateMutations.js @@ -120,14 +120,14 @@ export function estimateAttributes( if (attr.includes('.')) { // convert a.b.c into { a: { b: { c: value } } } const fields = attr.split('.'); - const first = fields[0]; const last = fields[fields.length - 1]; - data[first] = { ...serverData[first] }; - let object = { ...data }; + let object = data; for (let i = 0; i < fields.length - 1; i++) { const key = fields[i]; if (!(key in object)) { object[key] = {}; + } else { + object[key] = { ...object[key] }; } object = object[key]; } diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 06a7f3c2f..bd6643ff2 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -669,6 +669,69 @@ describe('ParseObject', () => { }); }); + it('can set multiple nested fields (regression test for #1450)', () => { + const o = new ParseObject('Person'); + o._finishFetch({ + objectId: 'setNested2_1450', + objectField: { + number: 5, + letter: 'a', + nested: { + number: 0, + letter: 'b', + }, + }, + }); + + expect(o.attributes).toEqual({ + objectField: { number: 5, letter: 'a', nested: { number: 0, letter: 'b' } }, + }); + o.set('objectField.number', 20); + o.set('objectField.letter', 'b'); + o.set('objectField.nested.number', 1); + o.set('objectField.nested.letter', 'c'); + + expect(o.attributes).toEqual({ + objectField: { number: 20, letter: 'b', nested: { number: 1, letter: 'c' } }, + }); + expect(o.op('objectField.number') instanceof SetOp).toBe(true); + expect(o.dirtyKeys()).toEqual([ + 'objectField.number', + 'objectField.letter', + 'objectField.nested.number', + 'objectField.nested.letter', + 'objectField', + ]); + expect(o._getSaveJSON()).toEqual({ + 'objectField.number': 20, + 'objectField.letter': 'b', + 'objectField.nested.number': 1, + 'objectField.nested.letter': 'c', + }); + + o.revert('objectField.nested.number'); + o.revert('objectField.nested.letter'); + expect(o._getSaveJSON()).toEqual({ + 'objectField.number': 20, + 'objectField.letter': 'b', + }); + expect(o.attributes).toEqual({ + objectField: { number: 20, letter: 'b', nested: { number: 0, letter: 'b' } }, + }); + + // Also test setting new root fields using the dot notation + o.set('objectField2.number', 0); + expect(o._getSaveJSON()).toEqual({ + 'objectField.number': 20, + 'objectField.letter': 'b', + 'objectField2.number': 0, + }); + expect(o.attributes).toEqual({ + objectField: { number: 20, letter: 'b', nested: { number: 0, letter: 'b' } }, + objectField2: { number: 0 }, + }); + }); + it('can increment a nested field', () => { const o = new ParseObject('Person'); o._finishFetch({ From 8b7fd1b62c449ddd72c86ce3803f6fcc1ef373bf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 1 May 2024 21:07:08 +0000 Subject: [PATCH 20/32] chore(release): 5.1.0-alpha.7 [skip ci] # [5.1.0-alpha.7](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.6...5.1.0-alpha.7) (2024-05-01) ### Bug Fixes * Multiple object updates of nested keys overwrite each other ([#1451](https://github.com/parse-community/Parse-SDK-JS/issues/1451)) ([fa4341a](https://github.com/parse-community/Parse-SDK-JS/commit/fa4341a8c0ce5a9c478435250b4af6ea020a45bd)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 0c119689c..7a627e978 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.7](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.6...5.1.0-alpha.7) (2024-05-01) + + +### Bug Fixes + +* Multiple object updates of nested keys overwrite each other ([#1451](https://github.com/parse-community/Parse-SDK-JS/issues/1451)) ([fa4341a](https://github.com/parse-community/Parse-SDK-JS/commit/fa4341a8c0ce5a9c478435250b4af6ea020a45bd)) + # [5.1.0-alpha.6](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.5...5.1.0-alpha.6) (2024-04-25) diff --git a/package-lock.json b/package-lock.json index 25a369781..efced8915 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.1.0-alpha.6", + "version": "5.1.0-alpha.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.1.0-alpha.6", + "version": "5.1.0-alpha.7", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index e641a35ef..7b30a5af9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.1.0-alpha.6", + "version": "5.1.0-alpha.7", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From 23cc573ccae9e11288aaeff61f478e59bf9bae0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Fri, 3 May 2024 01:11:29 +0200 Subject: [PATCH 21/32] fix: Pending updates to nested field causes `ParseObject.toJSON()` to return incorrect object (#1453) --- integration/test/ParseObjectTest.js | 12 ++++++++++++ src/ParseObject.js | 4 ---- src/__tests__/ParseObject-test.js | 8 ++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/integration/test/ParseObjectTest.js b/integration/test/ParseObjectTest.js index 8657f1f57..ef3aed26a 100644 --- a/integration/test/ParseObjectTest.js +++ b/integration/test/ParseObjectTest.js @@ -292,6 +292,18 @@ describe('Parse Object', () => { assert.strictEqual(result.get('a').b.c.d, 2); }); + it('can set nested fields without repeating pending operations on toJSON (regression test for #1452)', async () => { + const a = new Parse.Object('MyObject'); + a.set('obj', {}); + await a.save(); + a.set('obj.a', 0); + const json = a.toJSON(); + expect(json.obj).toEqual({ a: 0 }); + expect(new Set(Object.keys(json))).toEqual( + new Set(['objectId', 'createdAt', 'updatedAt', 'obj']) + ); + }); + it('can increment nested field and retain full object', async () => { const obj = new Parse.Object('TestIncrementObject'); obj.set('objectField', { number: 5, letter: 'a' }); diff --git a/src/ParseObject.js b/src/ParseObject.js index 826a2458e..b011ce151 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -470,10 +470,6 @@ class ParseObject { json[attr] = encode(attrs[attr], false, false, seen, offline); } } - const pending = this._getPendingOps(); - for (const attr in pending[0]) { - json[attr] = pending[0][attr].toJSON(offline); - } if (this.id) { json.objectId = this.id; diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index bd6643ff2..890d77ff4 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -667,6 +667,14 @@ describe('ParseObject', () => { 'objectField.number': 20, otherField: { hello: 'world' }, }); + expect(o.toJSON()).toEqual({ + objectField: { + number: 20, + letter: 'a', + }, + otherField: { hello: 'world' }, + objectId: 'setNested', + }); }); it('can set multiple nested fields (regression test for #1450)', () => { From 6b927a382516150216683f93e096ad75f953d809 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 May 2024 23:12:59 +0000 Subject: [PATCH 22/32] chore(release): 5.1.0-alpha.8 [skip ci] # [5.1.0-alpha.8](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.7...5.1.0-alpha.8) (2024-05-02) ### Bug Fixes * Pending updates to nested field causes `ParseObject.toJSON()` to return incorrect object ([#1453](https://github.com/parse-community/Parse-SDK-JS/issues/1453)) ([23cc573](https://github.com/parse-community/Parse-SDK-JS/commit/23cc573ccae9e11288aaeff61f478e59bf9bae0c)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 7a627e978..5042364fb 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.8](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.7...5.1.0-alpha.8) (2024-05-02) + + +### Bug Fixes + +* Pending updates to nested field causes `ParseObject.toJSON()` to return incorrect object ([#1453](https://github.com/parse-community/Parse-SDK-JS/issues/1453)) ([23cc573](https://github.com/parse-community/Parse-SDK-JS/commit/23cc573ccae9e11288aaeff61f478e59bf9bae0c)) + # [5.1.0-alpha.7](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.6...5.1.0-alpha.7) (2024-05-01) diff --git a/package-lock.json b/package-lock.json index efced8915..28b01b9cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.1.0-alpha.7", + "version": "5.1.0-alpha.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.1.0-alpha.7", + "version": "5.1.0-alpha.8", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index 7b30a5af9..e21708471 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.1.0-alpha.7", + "version": "5.1.0-alpha.8", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From 8717ed6d1e42ad94e4cd141e173656901c90a58c Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 3 May 2024 07:25:23 -0500 Subject: [PATCH 23/32] docs: Generate documentation for TypeScript files (#2121) --- jsdoc-conf.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jsdoc-conf.json b/jsdoc-conf.json index 1386c9069..107f9e9a8 100644 --- a/jsdoc-conf.json +++ b/jsdoc-conf.json @@ -1,10 +1,14 @@ { "plugins": ["node_modules/jsdoc-babel", "plugins/markdown"], "babel": { - "plugins": ["@babel/plugin-transform-flow-comments"] + "babelrc": false, + "extensions": ["js", "ts", "jsx", "tsx"], + "plugins": ["@babel/plugin-transform-flow-comments"], + "presets": ["@babel/preset-typescript"] }, "source": { "include": ["./README.md"], + "includePattern": "\\.(jsx|js|ts|tsx)$", "excludePattern": "(^|\\/|\\\\)_" }, "templates": { From 1c50c37e5dc2a964aec10b7b310214199e1a5e9e Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 3 May 2024 14:22:42 -0500 Subject: [PATCH 24/32] refactor: Convert `CoreManager` to TypeScript (#2118) --- README.md | 2 +- src/{CoreManager.js => CoreManager.ts} | 324 +++++++++++++++--------- src/Parse.ts | 79 +++--- src/ParseFile.js | 4 + src/ParseHooks.js | 3 + src/ParseSession.ts | 2 +- tsconfig.json | 5 +- types/CoreManager.d.ts | 331 ++++++++++++++++--------- types/Parse.d.ts | 15 +- 9 files changed, 480 insertions(+), 285 deletions(-) rename src/{CoreManager.js => CoreManager.ts} (53%) diff --git a/README.md b/README.md index 5111d1d52..42af3ed2a 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Types are updated manually after every release. If a definition doesn't exist, p #### Core Manager -The SDK has a [Core Manager](src/CoreManager.js) that handles all configurations and controllers. These modules can be swapped out for customization before you initialize the SDK. For full list of all available modules take a look at the [Core Manager Documentation](src/CoreManager.js). +The SDK has a [Core Manager](src/CoreManager.ts) that handles all configurations and controllers. These modules can be swapped out for customization before you initialize the SDK. For full list of all available modules take a look at the [Core Manager Documentation](src/CoreManager.ts). ```js // Configuration example diff --git a/src/CoreManager.js b/src/CoreManager.ts similarity index 53% rename from src/CoreManager.js rename to src/CoreManager.ts index de5efba95..db639b55d 100644 --- a/src/CoreManager.js +++ b/src/CoreManager.ts @@ -1,155 +1,236 @@ -/* - * @flow - */ - import type { AttributeMap, ObjectCache, OpsMap, State } from './ObjectStateMutations'; import type ParseFile from './ParseFile'; -import type { FileSource } from './ParseFile'; +import type { FileSaveOptions, FileSource } from './ParseFile'; import type { Op } from './ParseOp'; -import type ParseObject, {SaveOptions} from './ParseObject'; +import type ParseObject from './ParseObject'; +import type { SaveOptions } from './ParseObject'; import type { QueryJSON } from './ParseQuery'; import type ParseUser from './ParseUser'; import type { AuthData } from './ParseUser'; import type { PushData } from './Push'; import type { RequestOptions, FullOptions } from './RESTController'; +import type ParseSession from './ParseSession'; +import type { HookDeclaration, HookDeleteArg } from './ParseHooks'; +import type ParseConfig from './ParseConfig'; +import type LiveQueryClient from './LiveQueryClient'; +import type ParseSchema from './ParseSchema'; type AnalyticsController = { - track: (name: string, dimensions: { [key: string]: string }) => Promise, + track: (name: string, dimensions: { [key: string]: string }) => Promise, }; type CloudController = { - run: (name: string, data: mixed, options: RequestOptions) => Promise, - getJobsData: (options: RequestOptions) => Promise, - startJob: (name: string, data: mixed, options: RequestOptions) => Promise, + run: (name: string, data: any, options: RequestOptions) => Promise, + getJobsData: (options: RequestOptions) => Promise, + /** Returns promise which resolves with JobStatusId of the job */ + startJob: (name: string, data: any, options: RequestOptions) => Promise, }; type ConfigController = { - current: () => Promise, - get: () => Promise, - save: (attrs: { [key: string]: any }) => Promise, + current: () => Promise | ParseConfig, + get: (opts?: RequestOptions) => Promise, + save: (attrs: { [key: string]: any }, masterKeyOnlyFlags?: { [key: string]: any }) => Promise, }; type CryptoController = { encrypt: (obj: any, secretKey: string) => string, decrypt: (encryptedText: string, secretKey: any) => string, }; type FileController = { - saveFile: (name: string, source: FileSource, options: FullOptions) => Promise, - saveBase64: (name: string, source: FileSource, options: FullOptions) => Promise, - download: (uri: string) => Promise, + saveFile: (name: string, source: FileSource, options?: FullOptions) => Promise, + saveBase64: (name: string, source: FileSource, options?: FileSaveOptions) => Promise<{ name: string, url: string }>, + download: (uri: string, options?: any) => Promise<{ base64?: string, contentType?: string }>, + deleteFile: (name: string, options?: { useMasterKey?: boolean }) => Promise, }; type InstallationController = { - currentInstallationId: () => Promise, + currentInstallationId: () => Promise, }; type ObjectController = { fetch: ( object: ParseObject | Array, forceFetch: boolean, options: RequestOptions - ) => Promise, - save: (object: ParseObject | Array, options: RequestOptions) => Promise, - destroy: (object: ParseObject | Array, options: RequestOptions) => Promise, + ) => Promise, + save: (object: ParseObject | Array | null, options: RequestOptions) => Promise | ParseFile>, + destroy: (object: ParseObject | Array, options: RequestOptions) => Promise>, }; type ObjectStateController = { - getState: (obj: any) => ?State, + getState: (obj: any) => State | null, initializeState: (obj: any, initial?: State) => State, - removeState: (obj: any) => ?State, + removeState: (obj: any) => State | null, getServerData: (obj: any) => AttributeMap, setServerData: (obj: any, attributes: AttributeMap) => void, getPendingOps: (obj: any) => Array, - setPendingOp: (obj: any, attr: string, op: ?Op) => void, + setPendingOp: (obj: any, attr: string, op?: Op) => void, pushPendingState: (obj: any) => void, - popPendingState: (obj: any) => OpsMap, + popPendingState: (obj: any) => OpsMap | undefined, mergeFirstPendingState: (obj: any) => void, getObjectCache: (obj: any) => ObjectCache, - estimateAttribute: (obj: any, attr: string) => mixed, + estimateAttribute: (obj: any, attr: string) => any, estimateAttributes: (obj: any) => AttributeMap, commitServerChanges: (obj: any, changes: AttributeMap) => void, - enqueueTask: (obj: any, task: () => Promise) => Promise, + enqueueTask: (obj: any, task: () => Promise) => Promise, clearAllState: () => void, duplicateState: (source: any, dest: any) => void, }; type PushController = { - send: (data: PushData) => Promise, + send: (data: PushData, options?: FullOptions) => Promise, }; type QueryController = { - find: (className: string, params: QueryJSON, options: RequestOptions) => Promise, - aggregate: (className: string, params: any, options: RequestOptions) => Promise, + find(className: string, params: QueryJSON, options: RequestOptions): Promise<{ results?: Array, className?: string, count?: number }>; + aggregate(className: string, params: any, options: RequestOptions): Promise<{ results?: Array }>; }; type EventuallyQueue = { - save: (object: ParseObject, serverOptions: SaveOptions) => Promise, - destroy: (object: ParseObject, serverOptions: RequestOptions) => Promise, + save: (object: ParseObject, serverOptions: SaveOptions) => Promise, + destroy: (object: ParseObject, serverOptions: RequestOptions) => Promise, poll: (ms: number) => void }; type RESTController = { - request: (method: string, path: string, data: mixed, options: RequestOptions) => Promise, - ajax: (method: string, url: string, data: any, headers?: any, options: FullOptions) => Promise, + request: (method: string, path: string, data?: any, options?: RequestOptions) => Promise, + ajax: (method: string, url: string, data: any, headers?: any, options?: FullOptions) => Promise, + handleError: (err?: any) => void, }; type SchemaController = { - purge: (className: string) => Promise, - get: (className: string, options: RequestOptions) => Promise, - delete: (className: string, options: RequestOptions) => Promise, - create: (className: string, params: any, options: RequestOptions) => Promise, - update: (className: string, params: any, options: RequestOptions) => Promise, - send(className: string, method: string, params: any, options: RequestOptions): Promise, + purge: (className: string) => Promise, + get: (className: string, options?: RequestOptions) => Promise<{ results: ParseSchema[] }>, + delete: (className: string, options?: RequestOptions) => Promise, + create: (className: string, params: any, options?: RequestOptions) => Promise, + update: (className: string, params: any, options?: RequestOptions) => Promise, + send(className: string, method: string, params: any, options: RequestOptions): Promise, }; type SessionController = { - getSession: (token: RequestOptions) => Promise, + getSession: (token: RequestOptions) => Promise, +}; +type StorageController = { + async: 0, + getItem: (path: string) => string | null, + setItem: (path: string, value: string) => void, + removeItem: (path: string) => void, + getItemAsync?: (path: string) => Promise, + setItemAsync?: (path: string, value: string) => Promise, + removeItemAsync?: (path: string) => Promise, + clear: () => void, + getAllKeys?: () => Array + getAllKeysAsync?: () => Promise> +} | { + async: 1, + getItem?: (path: string) => string | null, + setItem?: (path: string, value: string) => void, + removeItem?: (path: string) => void, + getItemAsync: (path: string) => Promise, + setItemAsync: (path: string, value: string) => Promise, + removeItemAsync: (path: string) => Promise, + clear: () => void, + getAllKeys?: () => Array + getAllKeysAsync?: () => Promise> }; -type StorageController = - | { - async: 0, - getItem: (path: string) => ?string, - setItem: (path: string, value: string) => void, - removeItem: (path: string) => void, - getItemAsync?: (path: string) => Promise, - setItemAsync?: (path: string, value: string) => Promise, - removeItemAsync?: (path: string) => Promise, - clear: () => void, - } - | { - async: 1, - getItem?: (path: string) => ?string, - setItem?: (path: string, value: string) => void, - removeItem?: (path: string) => void, - getItemAsync: (path: string) => Promise, - setItemAsync: (path: string, value: string) => Promise, - removeItemAsync: (path: string) => Promise, - clear: () => void, - }; type LocalDatastoreController = { - fromPinWithName: (name: string) => ?any, + fromPinWithName: (name: string) => any | undefined, pinWithName: (name: string, objects: any) => void, unPinWithName: (name: string) => void, - getAllContents: () => ?any, + getAllContents: () => any | undefined, clear: () => void, + // Use for testing + // getRawStorage(): Promise, }; type UserController = { - setCurrentUser: (user: ParseUser) => Promise, - currentUser: () => ?ParseUser, - currentUserAsync: () => Promise, - signUp: (user: ParseUser, attrs: AttributeMap, options: RequestOptions) => Promise, - logIn: (user: ParseUser, options: RequestOptions) => Promise, - become: (options: RequestOptions) => Promise, - hydrate: (userJSON: AttributeMap) => Promise, - logOut: (options: RequestOptions) => Promise, - me: (options: RequestOptions) => Promise, - requestPasswordReset: (email: string, options: RequestOptions) => Promise, - updateUserOnDisk: (user: ParseUser) => Promise, - upgradeToRevocableSession: (user: ParseUser, options: RequestOptions) => Promise, - linkWith: (user: ParseUser, authData: AuthData) => Promise, - removeUserFromDisk: () => Promise, - verifyPassword: (username: string, password: string, options: RequestOptions) => Promise, - requestEmailVerification: (email: string, options: RequestOptions) => Promise, + setCurrentUser: (user: ParseUser) => Promise, + currentUser: () => ParseUser | null, + currentUserAsync: () => Promise, + signUp: (user: ParseUser, attrs: AttributeMap, options: RequestOptions) => Promise, + logIn: (user: ParseUser, options: RequestOptions) => Promise, + loginAs: (user: ParseUser, userId: string) => Promise, + become: (user: ParseUser, options: RequestOptions) => Promise, + hydrate: (user: ParseUser, userJSON: AttributeMap) => Promise, + logOut: (options: RequestOptions) => Promise, + me: (user: ParseUser, options: RequestOptions) => Promise, + requestPasswordReset: (email: string, options: RequestOptions) => Promise, + updateUserOnDisk: (user: ParseUser) => Promise, + upgradeToRevocableSession: (user: ParseUser, options: RequestOptions) => Promise, + linkWith: (user: ParseUser, authData: AuthData, options?: FullOptions) => Promise, + removeUserFromDisk: () => Promise, + verifyPassword: (username: string, password: string, options: RequestOptions) => Promise, + requestEmailVerification: (email: string, options: RequestOptions) => Promise, }; type HooksController = { - get: (type: string, functionName?: string, triggerName?: string) => Promise, - create: (hook: mixed) => Promise, - delete: (hook: mixed) => Promise, - update: (hook: mixed) => Promise, - send: (method: string, path: string, body?: mixed) => Promise, + get: (type: string, functionName?: string, triggerName?: string) => Promise, + create: (hook: HookDeclaration) => Promise, + remove: (hook: HookDeleteArg) => Promise, + update: (hook: HookDeclaration) => Promise, + // Renamed to sendRequest since ParseHooks file & tests file uses this. (originally declared as just "send") + sendRequest?: (method: string, path: string, body?: any) => Promise, +}; +type LiveQueryControllerType = { + setDefaultLiveQueryClient(liveQueryClient: LiveQueryClient): void; + getDefaultLiveQueryClient(): Promise; + _clearCachedDefaultClient(): void; +} +/** Based on https://github.com/react-native-async-storage/async-storage/blob/main/packages/default-storage-backend/src/types.ts */ +type AsyncStorageType = { + /** Fetches an item for a `key` and invokes a callback upon completion. */ + getItem: ( + key: string, + callback?: (error?: Error | null, result?: string | null) => void + ) => Promise; + /** Sets the value for a `key` and invokes a callback upon completion. */ + setItem: (key: string, value: string, callback?: (error?: Error | null) => void) => Promise; + /** Removes an item for a `key` and invokes a callback upon completion. */ + removeItem: (key: string, callback?: (error?: Error | null) => void) => Promise; + /** Merges an existing `key` value with an input value, assuming both values are stringified JSON. */ + mergeItem: (key: string, value: string, callback?: (error?: Error | null) => void) => Promise; + /** + * Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably + * don't want to call this; use `removeItem` or `multiRemove` to clear only + * your app's keys. + */ + clear: (callback?: (error?: Error | null) => void) => Promise; + /** Gets *all* keys known to your app; for all callers, libraries, etc. */ + getAllKeys: ( + callback?: (error?: Error | null, result?: readonly string[] | null) => void + ) => Promise; + /** + * This allows you to batch the fetching of items given an array of `key` + * inputs. Your callback will be invoked with an array of corresponding + * key-value pairs found. + */ + multiGet: ( + keys: readonly string[], + callback?: (errors?: readonly (Error | null)[] | null, result?: readonly [string, string][]) => void + ) => Promise; + + /** + * Use this as a batch operation for storing multiple key-value pairs. When + * the operation completes you'll get a single callback with any errors. + * + * See https://react-native-async-storage.github.io/async-storage/docs/api#multiset + */ + multiSet: ( + keyValuePairs: [string, string][], + callback?: (errors?: readonly (Error | null)[] | null) => void + ) => Promise; + + /** + * Call this to batch the deletion of all keys in the `keys` array. + * + * See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove + */ + multiRemove: ( + keys: readonly string[], + callback?: (errors?: readonly (Error | null)[] | null) => void + ) => Promise; + + /** + * Batch operation to merge in existing and new values for a given set of + * keys. This assumes that the values are stringified JSON. + * + * See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge + */ + multiMerge: ( + keyValuePairs: [string, string][], + callback?: (errors?: readonly (Error | null)[] | null) => void + ) => Promise; }; -type WebSocketController = { +export type WebSocketController = { onopen: () => void, onmessage: (message: any) => void, - onclose: () => void, + onclose: (arg?: any) => void, onerror: (error: any) => void, send: (data: any) => void, close: () => void, @@ -171,11 +252,12 @@ type Config = { LocalDatastoreController?: LocalDatastoreController, UserController?: UserController, HooksController?: HooksController, - WebSocketController?: WebSocketController, + WebSocketController?: new (url: string | URL, protocols?: string | string[] | undefined) => WebSocketController, + LiveQueryController?: LiveQueryControllerType, + AsyncStorage?: AsyncStorageType }; -const config: Config & { [key: string]: mixed } = { - // Defaults +const config: Config & { [key: string]: any } = { IS_NODE: typeof process !== 'undefined' && !!process.versions && @@ -237,7 +319,7 @@ const CoreManager = { }, getAnalyticsController(): AnalyticsController { - return config['AnalyticsController']; + return config['AnalyticsController']!; }, setCloudController(controller: CloudController) { @@ -246,7 +328,7 @@ const CoreManager = { }, getCloudController(): CloudController { - return config['CloudController']; + return config['CloudController']!; }, setConfigController(controller: ConfigController) { @@ -255,7 +337,7 @@ const CoreManager = { }, getConfigController(): ConfigController { - return config['ConfigController']; + return config['ConfigController']!; }, setCryptoController(controller: CryptoController) { @@ -280,8 +362,17 @@ const CoreManager = { config['FileController'] = controller; }, + setEventuallyQueue(controller: EventuallyQueue) { + requireMethods('EventuallyQueue', ['poll', 'save', 'destroy'], controller); + config['EventuallyQueue'] = controller; + }, + + getEventuallyQueue(): EventuallyQueue { + return config['EventuallyQueue']!; + }, + getFileController(): FileController { - return config['FileController']; + return config['FileController']!; }, setInstallationController(controller: InstallationController) { @@ -290,7 +381,7 @@ const CoreManager = { }, getInstallationController(): InstallationController { - return config['InstallationController']; + return config['InstallationController']!; }, setLiveQuery(liveQuery: any) { @@ -307,7 +398,7 @@ const CoreManager = { }, getObjectController(): ObjectController { - return config['ObjectController']; + return config['ObjectController']!; }, setObjectStateController(controller: ObjectStateController) { @@ -338,7 +429,7 @@ const CoreManager = { }, getObjectStateController(): ObjectStateController { - return config['ObjectStateController']; + return config['ObjectStateController']!; }, setPushController(controller: PushController) { @@ -347,7 +438,7 @@ const CoreManager = { }, getPushController(): PushController { - return config['PushController']; + return config['PushController']!; }, setQueryController(controller: QueryController) { @@ -356,7 +447,7 @@ const CoreManager = { }, getQueryController(): QueryController { - return config['QueryController']; + return config['QueryController']!; }, setRESTController(controller: RESTController) { @@ -365,16 +456,7 @@ const CoreManager = { }, getRESTController(): RESTController { - return config['RESTController']; - }, - - setEventuallyQueue(controller: EventuallyQueue) { - requireMethods('EventuallyQueue', ['poll', 'save', 'destroy'], controller); - config['EventuallyQueue'] = controller; - }, - - getEventuallyQueue(): EventuallyQueue { - return config['EventuallyQueue']; + return config['RESTController']!; }, setSchemaController(controller: SchemaController) { @@ -387,7 +469,7 @@ const CoreManager = { }, getSchemaController(): SchemaController { - return config['SchemaController']; + return config['SchemaController']!; }, setSessionController(controller: SessionController) { @@ -396,7 +478,7 @@ const CoreManager = { }, getSessionController(): SessionController { - return config['SessionController']; + return config['SessionController']!; }, setStorageController(controller: StorageController) { @@ -426,7 +508,7 @@ const CoreManager = { }, getLocalDatastoreController(): LocalDatastoreController { - return config['LocalDatastoreController']; + return config['LocalDatastoreController']!; }, setLocalDatastore(store: any) { @@ -438,10 +520,10 @@ const CoreManager = { }, getStorageController(): StorageController { - return config['StorageController']; + return config['StorageController']!; }, - setAsyncStorage(storage: any) { + setAsyncStorage(storage: AsyncStorageType) { config['AsyncStorage'] = storage; }, @@ -449,12 +531,12 @@ const CoreManager = { return config['AsyncStorage']; }, - setWebSocketController(controller: WebSocketController) { + setWebSocketController(controller: new (url: string | URL, protocols?: string | string[] | undefined) => WebSocketController) { config['WebSocketController'] = controller; }, - getWebSocketController(): WebSocketController { - return config['WebSocketController']; + getWebSocketController(): new (url: string | URL, protocols?: string | string[] | undefined) => WebSocketController { + return config['WebSocketController']!; }, setUserController(controller: UserController) { @@ -481,10 +563,10 @@ const CoreManager = { }, getUserController(): UserController { - return config['UserController']; + return config['UserController']!; }, - setLiveQueryController(controller: any) { + setLiveQueryController(controller: LiveQueryControllerType) { requireMethods( 'LiveQueryController', ['setDefaultLiveQueryClient', 'getDefaultLiveQueryClient', '_clearCachedDefaultClient'], @@ -493,8 +575,8 @@ const CoreManager = { config['LiveQueryController'] = controller; }, - getLiveQueryController(): any { - return config['LiveQueryController']; + getLiveQueryController(): LiveQueryControllerType { + return config['LiveQueryController']!; }, setHooksController(controller: HooksController) { @@ -503,7 +585,7 @@ const CoreManager = { }, getHooksController(): HooksController { - return config['HooksController']; + return config['HooksController']!; }, }; diff --git a/src/Parse.ts b/src/Parse.ts index 4aa88dbd1..6692df3e3 100644 --- a/src/Parse.ts +++ b/src/Parse.ts @@ -21,7 +21,7 @@ import GeoPoint from './ParseGeoPoint' import Polygon from './ParsePolygon' import Installation from './ParseInstallation' import LocalDatastore from './LocalDatastore' -import Object from './ParseObject' +import ParseObject from './ParseObject'; import * as Push from './Push' import Query from './ParseQuery' import Relation from './ParseRelation' @@ -50,7 +50,10 @@ interface ParseType { Parse?: ParseType, Analytics: typeof Analytics, AnonymousUtils: typeof AnonymousUtils, - Cloud: typeof Cloud, + Cloud: typeof Cloud & { + /** only available in server environments */ + useMasterKey?: () => void + }, CLP: typeof CLP, CoreManager: typeof CoreManager, Config: typeof Config, @@ -63,7 +66,7 @@ interface ParseType { Polygon: typeof Polygon, Installation: typeof Installation, LocalDatastore: typeof LocalDatastore, - Object: typeof Object, + Object: typeof ParseObject, Op: { Set: typeof ParseOp.SetOp, Unset: typeof ParseOp.UnsetOp, @@ -81,7 +84,7 @@ interface ParseType { Session: typeof Session, Storage: typeof Storage, User: typeof User, - LiveQuery: ParseLiveQuery, + LiveQuery: typeof ParseLiveQuery, LiveQueryClient: typeof LiveQueryClient, initialize(applicationId: string, javaScriptKey: string): void, @@ -106,7 +109,7 @@ interface ParseType { _ajax(...args: any[]): void, _decode(...args: any[]): void, _encode(...args: any[]): void, - _getInstallationId?(): string, + _getInstallationId?(): Promise, enableLocalDatastore(polling: boolean, ms: number): void, isLocalDatastoreEnabled(): boolean, dumpLocalDatastore(): void, @@ -117,37 +120,37 @@ interface ParseType { const Parse: ParseType = { ACL: ACL, Analytics: Analytics, - AnonymousUtils: AnonymousUtils, + AnonymousUtils: AnonymousUtils, Cloud: Cloud, CLP: CLP, - CoreManager: CoreManager, - Config: Config, - Error: ParseError, + CoreManager: CoreManager, + Config: Config, + Error: ParseError, FacebookUtils: FacebookUtils, - File: File, - GeoPoint: GeoPoint, - Polygon: Polygon, - Installation: Installation, - LocalDatastore: LocalDatastore, - Object: Object, + File: File, + GeoPoint: GeoPoint, + Polygon: Polygon, + Installation: Installation, + LocalDatastore: LocalDatastore, + Object: ParseObject, Op: { - Set: ParseOp.SetOp, - Unset: ParseOp.UnsetOp, - Increment: ParseOp.IncrementOp, - Add: ParseOp.AddOp, - Remove: ParseOp.RemoveOp, - AddUnique: ParseOp.AddUniqueOp, - Relation: ParseOp.RelationOp, - }, - Push: Push, - Query: Query, - Relation: Relation, - Role: Role, - Schema: Schema, - Session: Session, - Storage: Storage, - User: User, - LiveQueryClient: LiveQueryClient, + Set: ParseOp.SetOp, + Unset: ParseOp.UnsetOp, + Increment: ParseOp.IncrementOp, + Add: ParseOp.AddOp, + Remove: ParseOp.RemoveOp, + AddUnique: ParseOp.AddUniqueOp, + Relation: ParseOp.RelationOp, + }, + Push: Push, + Query: Query, + Relation: Relation, + Role: Role, + Schema: Schema, + Session: Session, + Storage: Storage, + User: User, + LiveQueryClient: LiveQueryClient, IndexedDB: undefined, Hooks: undefined, Parse: undefined, @@ -181,7 +184,7 @@ const Parse: ParseType = { /* eslint-disable no-console */ console.log( "It looks like you're using the browser version of the SDK in a " + - "node.js environment. You should require('parse/node') instead." + "node.js environment. You should require('parse/node') instead." ); /* eslint-enable no-console */ } @@ -389,7 +392,7 @@ const Parse: ParseType = { return encode(value, disallowObjects); }, - _getInstallationId () { + _getInstallationId() { return CoreManager.getInstallationController().currentInstallationId(); }, /** @@ -418,7 +421,7 @@ const Parse: ParseType = { * @static * @returns {boolean} */ - isLocalDatastoreEnabled () { + isLocalDatastoreEnabled() { return this.LocalDatastore.isEnabled; }, /** @@ -446,7 +449,7 @@ const Parse: ParseType = { * * @static */ - enableEncryptedUser () { + enableEncryptedUser() { this.encryptedUser = true; }, @@ -456,7 +459,7 @@ const Parse: ParseType = { * @static * @returns {boolean} */ - isEncryptedUserEnabled () { + isEncryptedUserEnabled() { return this.encryptedUser; }, }; @@ -466,7 +469,7 @@ CoreManager.setRESTController(RESTController); if (process.env.PARSE_BUILD === 'node') { Parse.initialize = Parse._initialize; - Parse.Cloud = Parse.Cloud || {}; + Parse.Cloud = Parse.Cloud || {} as any; Parse.Cloud.useMasterKey = function () { CoreManager.set('USE_MASTER_KEY', true); }; diff --git a/src/ParseFile.js b/src/ParseFile.js index 1d7ff6945..491487ca7 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -18,6 +18,10 @@ if (process.env.PARSE_BUILD === 'weapp') { type Base64 = { base64: string }; type Uri = { uri: string }; type FileData = Array | Base64 | Blob | Uri; +export type FileSaveOptions = FullOptions & { + metadata?: { [key: string]: any }, + tags?: { [key: string]: any }, +}; export type FileSource = | { format: 'file', diff --git a/src/ParseHooks.js b/src/ParseHooks.js index 8a5982687..43375ba7a 100644 --- a/src/ParseHooks.js +++ b/src/ParseHooks.js @@ -2,6 +2,9 @@ import CoreManager from './CoreManager'; import decode from './decode'; import ParseError from './ParseError'; +export type HookDeclaration = { functionName: string, url: string } | { className: string, triggerName: string, url: string }; +export type HookDeleteArg = { functionName: string } | { className: string, triggerName: string }; + export function getFunctions() { return CoreManager.getHooksController().get('functions'); } diff --git a/src/ParseSession.ts b/src/ParseSession.ts index 51b001742..68cb8f398 100644 --- a/src/ParseSession.ts +++ b/src/ParseSession.ts @@ -57,7 +57,7 @@ class ParseSession extends ParseObject { options = options || {}; const controller = CoreManager.getSessionController(); - const sessionOptions = {}; + const sessionOptions: FullOptions = {}; if (options.hasOwnProperty('useMasterKey')) { sessionOptions.useMasterKey = options.useMasterKey; } diff --git a/tsconfig.json b/tsconfig.json index 051477c29..b5166cc62 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,8 +8,7 @@ "noImplicitAny": false, "allowJs": false }, - "files": [ - "src/Parse.ts", - "src/ParseSession.ts" + "include": [ + "src/*.ts" ] } diff --git a/types/CoreManager.d.ts b/types/CoreManager.d.ts index c7a350aa4..2fa05b5d3 100644 --- a/types/CoreManager.d.ts +++ b/types/CoreManager.d.ts @@ -1,184 +1,285 @@ -// @ts-nocheck -export default CoreManager; -declare namespace CoreManager { - function get(key: string): any; - function set(key: string, value: any): void; - function setAnalyticsController(controller: AnalyticsController): void; - function getAnalyticsController(): AnalyticsController; - function setCloudController(controller: CloudController): void; - function getCloudController(): CloudController; - function setConfigController(controller: ConfigController): void; - function getConfigController(): ConfigController; - function setCryptoController(controller: CryptoController): void; - function getCryptoController(): CryptoController; - function setFileController(controller: FileController): void; - function getFileController(): FileController; - function setInstallationController(controller: InstallationController): void; - function getInstallationController(): InstallationController; - function setObjectController(controller: ObjectController): void; - function getObjectController(): ObjectController; - function setObjectStateController(controller: ObjectStateController): void; - function getObjectStateController(): ObjectStateController; - function setPushController(controller: PushController): void; - function getPushController(): PushController; - function setQueryController(controller: QueryController): void; - function getQueryController(): QueryController; - function setRESTController(controller: RESTController): void; - function getRESTController(): RESTController; - function setSchemaController(controller: SchemaController): void; - function getSchemaController(): SchemaController; - function setSessionController(controller: SessionController): void; - function getSessionController(): SessionController; - function setStorageController(controller: StorageController): void; - function setLocalDatastoreController(controller: LocalDatastoreController): void; - function getLocalDatastoreController(): LocalDatastoreController; - function setLocalDatastore(store: any): void; - function getLocalDatastore(): mixed; - function getStorageController(): StorageController; - function setAsyncStorage(storage: any): void; - function getAsyncStorage(): mixed; - function setWebSocketController(controller: WebSocketController): void; - function getWebSocketController(): WebSocketController; - function setUserController(controller: UserController): void; - function getUserController(): UserController; - function setLiveQueryController(controller: any): void; - function getLiveQueryController(): any; - function setHooksController(controller: HooksController): void; - function getHooksController(): HooksController; -} +import type { AttributeMap, ObjectCache, OpsMap, State } from './ObjectStateMutations'; +import type ParseFile from './ParseFile'; +import type { FileSaveOptions, FileSource } from './ParseFile'; +import type { Op } from './ParseOp'; +import type ParseObject from './ParseObject'; +import type { SaveOptions } from './ParseObject'; +import type { QueryJSON } from './ParseQuery'; +import type ParseUser from './ParseUser'; +import type { AuthData } from './ParseUser'; +import type { PushData } from './Push'; +import type { RequestOptions, FullOptions } from './RESTController'; +import type ParseSession from './ParseSession'; +import type { HookDeclaration, HookDeleteArg } from './ParseHooks'; +import type ParseConfig from './ParseConfig'; +import type LiveQueryClient from './LiveQueryClient'; +import type ParseSchema from './ParseSchema'; type AnalyticsController = { track: (name: string, dimensions: { [key: string]: string; }) => Promise; }; type CloudController = { - run: (name: string, data: mixed, options: RequestOptions) => Promise; + run: (name: string, data: any, options: RequestOptions) => Promise; getJobsData: (options: RequestOptions) => Promise; - startJob: (name: string, data: mixed, options: RequestOptions) => Promise; + /** Returns promise which resolves with JobStatusId of the job */ + startJob: (name: string, data: any, options: RequestOptions) => Promise; }; type ConfigController = { - current: () => Promise; - get: () => Promise; + current: () => Promise | ParseConfig; + get: (opts?: RequestOptions) => Promise; save: (attrs: { [key: string]: any; - }) => Promise; + }, masterKeyOnlyFlags?: { + [key: string]: any; + }) => Promise; }; type CryptoController = { encrypt: (obj: any, secretKey: string) => string; decrypt: (encryptedText: string, secretKey: any) => string; }; type FileController = { - saveFile: (name: string, source: FileSource, options: FullOptions) => Promise; - saveBase64: (name: string, source: FileSource, options: FullOptions) => Promise; - download: (uri: string) => Promise; + saveFile: (name: string, source: FileSource, options?: FullOptions) => Promise; + saveBase64: (name: string, source: FileSource, options?: FileSaveOptions) => Promise<{ + name: string; + url: string; + }>; + download: (uri: string, options?: any) => Promise<{ + base64?: string; + contentType?: string; + }>; + deleteFile: (name: string, options?: { + useMasterKey?: boolean; + }) => Promise; }; type InstallationController = { - currentInstallationId: () => Promise; + currentInstallationId: () => Promise; }; type ObjectController = { - fetch: (object: ParseObject | ParseObject[], forceFetch: boolean, options: RequestOptions) => Promise; - save: (object: ParseObject | (ParseFile | ParseObject)[], options: RequestOptions) => Promise; - destroy: (object: ParseObject | ParseObject[], options: RequestOptions) => Promise; + fetch: (object: ParseObject | Array, forceFetch: boolean, options: RequestOptions) => Promise; + save: (object: ParseObject | Array | null, options: RequestOptions) => Promise | ParseFile>; + destroy: (object: ParseObject | Array, options: RequestOptions) => Promise>; }; type ObjectStateController = { - getState: (obj: any) => State; + getState: (obj: any) => State | null; initializeState: (obj: any, initial?: State) => State; - removeState: (obj: any) => State; + removeState: (obj: any) => State | null; getServerData: (obj: any) => AttributeMap; setServerData: (obj: any, attributes: AttributeMap) => void; - getPendingOps: (obj: any) => OpsMap[]; - setPendingOp: (obj: any, attr: string, op: Op) => void; + getPendingOps: (obj: any) => Array; + setPendingOp: (obj: any, attr: string, op?: Op) => void; pushPendingState: (obj: any) => void; - popPendingState: (obj: any) => OpsMap; + popPendingState: (obj: any) => OpsMap | undefined; mergeFirstPendingState: (obj: any) => void; getObjectCache: (obj: any) => ObjectCache; - estimateAttribute: (obj: any, attr: string) => mixed; + estimateAttribute: (obj: any, attr: string) => any; estimateAttributes: (obj: any) => AttributeMap; commitServerChanges: (obj: any, changes: AttributeMap) => void; - enqueueTask: (obj: any, task: () => Promise) => Promise; + enqueueTask: (obj: any, task: () => Promise) => Promise; clearAllState: () => void; duplicateState: (source: any, dest: any) => void; }; type PushController = { - send: (data: PushData) => Promise; + send: (data: PushData, options?: FullOptions) => Promise; }; type QueryController = { - find: (className: string, params: QueryJSON, options: RequestOptions) => Promise; - aggregate: (className: string, params: any, options: RequestOptions) => Promise; + find(className: string, params: QueryJSON, options: RequestOptions): Promise<{ + results?: Array; + className?: string; + count?: number; + }>; + aggregate(className: string, params: any, options: RequestOptions): Promise<{ + results?: Array; + }>; +}; +type EventuallyQueue = { + save: (object: ParseObject, serverOptions: SaveOptions) => Promise; + destroy: (object: ParseObject, serverOptions: RequestOptions) => Promise; + poll: (ms: number) => void; }; type RESTController = { - request: (method: string, path: string, data: mixed, options: RequestOptions) => Promise; - ajax: (method: string, url: string, data: any, headers?: any, options: FullOptions) => Promise; + request: (method: string, path: string, data?: any, options?: RequestOptions) => Promise; + ajax: (method: string, url: string, data: any, headers?: any, options?: FullOptions) => Promise; + handleError: (err?: any) => void; }; type SchemaController = { purge: (className: string) => Promise; - get: (className: string, options: RequestOptions) => Promise; - delete: (className: string, options: RequestOptions) => Promise; - create: (className: string, params: any, options: RequestOptions) => Promise; - update: (className: string, params: any, options: RequestOptions) => Promise; + get: (className: string, options?: RequestOptions) => Promise<{ + results: ParseSchema[]; + }>; + delete: (className: string, options?: RequestOptions) => Promise; + create: (className: string, params: any, options?: RequestOptions) => Promise; + update: (className: string, params: any, options?: RequestOptions) => Promise; send(className: string, method: string, params: any, options: RequestOptions): Promise; }; type SessionController = { - getSession: (token: RequestOptions) => Promise; + getSession: (token: RequestOptions) => Promise; }; type StorageController = { async: 0; - getItem: (path: string) => string; + getItem: (path: string) => string | null; setItem: (path: string, value: string) => void; removeItem: (path: string) => void; - getItemAsync?: (path: string) => Promise; - setItemAsync?: (path: string, value: string) => Promise; - removeItemAsync?: (path: string) => Promise; + getItemAsync?: (path: string) => Promise; + setItemAsync?: (path: string, value: string) => Promise; + removeItemAsync?: (path: string) => Promise; clear: () => void; + getAllKeys?: () => Array; + getAllKeysAsync?: () => Promise>; } | { async: 1; - getItem?: (path: string) => string; + getItem?: (path: string) => string | null; setItem?: (path: string, value: string) => void; removeItem?: (path: string) => void; - getItemAsync: (path: string) => Promise; - setItemAsync: (path: string, value: string) => Promise; - removeItemAsync: (path: string) => Promise; + getItemAsync: (path: string) => Promise; + setItemAsync: (path: string, value: string) => Promise; + removeItemAsync: (path: string) => Promise; clear: () => void; + getAllKeys?: () => Array; + getAllKeysAsync?: () => Promise>; }; type LocalDatastoreController = { - fromPinWithName: (name: string) => any; + fromPinWithName: (name: string) => any | undefined; pinWithName: (name: string, objects: any) => void; unPinWithName: (name: string) => void; - getAllContents: () => any; + getAllContents: () => any | undefined; clear: () => void; }; -type WebSocketController = { +type UserController = { + setCurrentUser: (user: ParseUser) => Promise; + currentUser: () => ParseUser | null; + currentUserAsync: () => Promise; + signUp: (user: ParseUser, attrs: AttributeMap, options: RequestOptions) => Promise; + logIn: (user: ParseUser, options: RequestOptions) => Promise; + loginAs: (user: ParseUser, userId: string) => Promise; + become: (user: ParseUser, options: RequestOptions) => Promise; + hydrate: (user: ParseUser, userJSON: AttributeMap) => Promise; + logOut: (options: RequestOptions) => Promise; + me: (user: ParseUser, options: RequestOptions) => Promise; + requestPasswordReset: (email: string, options: RequestOptions) => Promise; + updateUserOnDisk: (user: ParseUser) => Promise; + upgradeToRevocableSession: (user: ParseUser, options: RequestOptions) => Promise; + linkWith: (user: ParseUser, authData: AuthData, options?: FullOptions) => Promise; + removeUserFromDisk: () => Promise; + verifyPassword: (username: string, password: string, options: RequestOptions) => Promise; + requestEmailVerification: (email: string, options: RequestOptions) => Promise; +}; +type HooksController = { + get: (type: string, functionName?: string, triggerName?: string) => Promise; + create: (hook: HookDeclaration) => Promise; + remove: (hook: HookDeleteArg) => Promise; + update: (hook: HookDeclaration) => Promise; + sendRequest?: (method: string, path: string, body?: any) => Promise; +}; +type LiveQueryControllerType = { + setDefaultLiveQueryClient(liveQueryClient: LiveQueryClient): void; + getDefaultLiveQueryClient(): Promise; + _clearCachedDefaultClient(): void; +}; +/** Based on https://github.com/react-native-async-storage/async-storage/blob/main/packages/default-storage-backend/src/types.ts */ +type AsyncStorageType = { + /** Fetches an item for a `key` and invokes a callback upon completion. */ + getItem: (key: string, callback?: (error?: Error | null, result?: string | null) => void) => Promise; + /** Sets the value for a `key` and invokes a callback upon completion. */ + setItem: (key: string, value: string, callback?: (error?: Error | null) => void) => Promise; + /** Removes an item for a `key` and invokes a callback upon completion. */ + removeItem: (key: string, callback?: (error?: Error | null) => void) => Promise; + /** Merges an existing `key` value with an input value, assuming both values are stringified JSON. */ + mergeItem: (key: string, value: string, callback?: (error?: Error | null) => void) => Promise; + /** + * Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably + * don't want to call this; use `removeItem` or `multiRemove` to clear only + * your app's keys. + */ + clear: (callback?: (error?: Error | null) => void) => Promise; + /** Gets *all* keys known to your app; for all callers, libraries, etc. */ + getAllKeys: (callback?: (error?: Error | null, result?: readonly string[] | null) => void) => Promise; + /** + * This allows you to batch the fetching of items given an array of `key` + * inputs. Your callback will be invoked with an array of corresponding + * key-value pairs found. + */ + multiGet: (keys: readonly string[], callback?: (errors?: readonly (Error | null)[] | null, result?: readonly [string, string][]) => void) => Promise; + /** + * Use this as a batch operation for storing multiple key-value pairs. When + * the operation completes you'll get a single callback with any errors. + * + * See https://react-native-async-storage.github.io/async-storage/docs/api#multiset + */ + multiSet: (keyValuePairs: [string, string][], callback?: (errors?: readonly (Error | null)[] | null) => void) => Promise; + /** + * Call this to batch the deletion of all keys in the `keys` array. + * + * See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove + */ + multiRemove: (keys: readonly string[], callback?: (errors?: readonly (Error | null)[] | null) => void) => Promise; + /** + * Batch operation to merge in existing and new values for a given set of + * keys. This assumes that the values are stringified JSON. + * + * See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge + */ + multiMerge: (keyValuePairs: [string, string][], callback?: (errors?: readonly (Error | null)[] | null) => void) => Promise; +}; +export type WebSocketController = { onopen: () => void; onmessage: (message: any) => void; - onclose: () => void; + onclose: (arg?: any) => void; onerror: (error: any) => void; send: (data: any) => void; close: () => void; }; -type UserController = { - setCurrentUser: (user: ParseUser) => Promise; - currentUser: () => ParseUser; - currentUserAsync: () => Promise; - signUp: (user: ParseUser, attrs: AttributeMap, options: RequestOptions) => Promise; - logIn: (user: ParseUser, options: RequestOptions) => Promise; - become: (options: RequestOptions) => Promise; - hydrate: (userJSON: AttributeMap) => Promise; - logOut: (options: RequestOptions) => Promise; - me: (options: RequestOptions) => Promise; - requestPasswordReset: (email: string, options: RequestOptions) => Promise; - updateUserOnDisk: (user: ParseUser) => Promise; - upgradeToRevocableSession: (user: ParseUser, options: RequestOptions) => Promise; - linkWith: (user: ParseUser, authData: { - [key: string]: mixed; - }) => Promise; - removeUserFromDisk: () => Promise; - verifyPassword: (username: string, password: string, options: RequestOptions) => Promise; - requestEmailVerification: (email: string, options: RequestOptions) => Promise; -}; -type HooksController = { - get: (type: string, functionName?: string, triggerName?: string) => Promise; - create: (hook: mixed) => Promise; - delete: (hook: mixed) => Promise; - update: (hook: mixed) => Promise; - send: (method: string, path: string, body?: mixed) => Promise; +declare const CoreManager: { + get: (key: string) => any; + set: (key: string, value: any) => void; + setIfNeeded: (key: string, value: any) => any; + setAnalyticsController(controller: AnalyticsController): void; + getAnalyticsController(): AnalyticsController; + setCloudController(controller: CloudController): void; + getCloudController(): CloudController; + setConfigController(controller: ConfigController): void; + getConfigController(): ConfigController; + setCryptoController(controller: CryptoController): void; + getCryptoController(): CryptoController; + setEventEmitter(eventEmitter: any): void; + getEventEmitter(): any; + setFileController(controller: FileController): void; + setEventuallyQueue(controller: EventuallyQueue): void; + getEventuallyQueue(): EventuallyQueue; + getFileController(): FileController; + setInstallationController(controller: InstallationController): void; + getInstallationController(): InstallationController; + setLiveQuery(liveQuery: any): void; + getLiveQuery(): any; + setObjectController(controller: ObjectController): void; + getObjectController(): ObjectController; + setObjectStateController(controller: ObjectStateController): void; + getObjectStateController(): ObjectStateController; + setPushController(controller: PushController): void; + getPushController(): PushController; + setQueryController(controller: QueryController): void; + getQueryController(): QueryController; + setRESTController(controller: RESTController): void; + getRESTController(): RESTController; + setSchemaController(controller: SchemaController): void; + getSchemaController(): SchemaController; + setSessionController(controller: SessionController): void; + getSessionController(): SessionController; + setStorageController(controller: StorageController): void; + setLocalDatastoreController(controller: LocalDatastoreController): void; + getLocalDatastoreController(): LocalDatastoreController; + setLocalDatastore(store: any): void; + getLocalDatastore(): any; + getStorageController(): StorageController; + setAsyncStorage(storage: AsyncStorageType): void; + getAsyncStorage(): AsyncStorageType; + setWebSocketController(controller: new (url: string | URL, protocols?: string | string[] | undefined) => WebSocketController): void; + getWebSocketController(): new (url: string | URL, protocols?: string | string[] | undefined) => WebSocketController; + setUserController(controller: UserController): void; + getUserController(): UserController; + setLiveQueryController(controller: LiveQueryControllerType): void; + getLiveQueryController(): LiveQueryControllerType; + setHooksController(controller: HooksController): void; + getHooksController(): HooksController; }; +export default CoreManager; diff --git a/types/Parse.d.ts b/types/Parse.d.ts index db3020c86..ecb89e504 100644 --- a/types/Parse.d.ts +++ b/types/Parse.d.ts @@ -14,7 +14,7 @@ import GeoPoint from './ParseGeoPoint'; import Polygon from './ParsePolygon'; import Installation from './ParseInstallation'; import LocalDatastore from './LocalDatastore'; -import Object from './ParseObject'; +import ParseObject from './ParseObject'; import * as Push from './Push'; import Query from './ParseQuery'; import Relation from './ParseRelation'; @@ -23,7 +23,7 @@ import Schema from './ParseSchema'; import Session from './ParseSession'; import Storage from './Storage'; import User from './ParseUser'; -import LiveQuery from './ParseLiveQuery'; +import ParseLiveQuery from './ParseLiveQuery'; import LiveQueryClient from './LiveQueryClient'; /** * Contains all Parse API classes and functions. @@ -38,7 +38,10 @@ interface ParseType { Parse?: ParseType; Analytics: typeof Analytics; AnonymousUtils: typeof AnonymousUtils; - Cloud: typeof Cloud; + Cloud: typeof Cloud & { + /** only available in server environments */ + useMasterKey?: () => void; + }; CLP: typeof CLP; CoreManager: typeof CoreManager; Config: typeof Config; @@ -51,7 +54,7 @@ interface ParseType { Polygon: typeof Polygon; Installation: typeof Installation; LocalDatastore: typeof LocalDatastore; - Object: typeof Object; + Object: typeof ParseObject; Op: { Set: typeof ParseOp.SetOp; Unset: typeof ParseOp.UnsetOp; @@ -69,7 +72,7 @@ interface ParseType { Session: typeof Session; Storage: typeof Storage; User: typeof User; - LiveQuery: typeof LiveQuery; + LiveQuery: typeof ParseLiveQuery; LiveQueryClient: typeof LiveQueryClient; initialize(applicationId: string, javaScriptKey: string): void; _initialize(applicationId: string, javaScriptKey: string, masterKey?: string): void; @@ -92,7 +95,7 @@ interface ParseType { _ajax(...args: any[]): void; _decode(...args: any[]): void; _encode(...args: any[]): void; - _getInstallationId?(): string; + _getInstallationId?(): Promise; enableLocalDatastore(polling: boolean, ms: number): void; isLocalDatastoreEnabled(): boolean; dumpLocalDatastore(): void; From 4fc62cec0c4ea704f48ec501a5f0182836de45d1 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 4 May 2024 12:54:07 -0500 Subject: [PATCH 25/32] feat: Improve installation object `Parse.Installation.currentInstallation` to support web push notifications (#2119) --- integration/test/ParseUserTest.js | 37 ++- src/CoreManager.ts | 9 +- src/InstallationController.js | 38 --- src/InstallationController.ts | 81 +++++++ src/Parse.ts | 5 +- src/ParseInstallation.js | 20 -- src/ParseInstallation.ts | 230 +++++++++++++++++++ src/__tests__/CoreManager-test.js | 18 ++ src/__tests__/EventuallyQueue-test.js | 2 + src/__tests__/InstallationController-test.js | 100 +++++--- src/__tests__/Parse-test.js | 2 + src/__tests__/ParseInstallation-test.js | 77 ++++++- src/__tests__/ParseObject-test.js | 2 + src/__tests__/RESTController-test.js | 2 + types/CoreManager.d.ts | 3 + types/InstallationController.d.ts | 11 +- types/ParseInstallation.d.ts | 160 ++++++++++++- 17 files changed, 690 insertions(+), 107 deletions(-) delete mode 100644 src/InstallationController.js create mode 100644 src/InstallationController.ts delete mode 100644 src/ParseInstallation.js create mode 100644 src/ParseInstallation.ts diff --git a/integration/test/ParseUserTest.js b/integration/test/ParseUserTest.js index d05a477fa..75a216ca0 100644 --- a/integration/test/ParseUserTest.js +++ b/integration/test/ParseUserTest.js @@ -113,7 +113,7 @@ describe('Parse User', () => { it('can login users with installationId', async () => { Parse.User.enableUnsafeCurrentUser(); - const currentInstallation = await Parse.CoreManager.getInstallationController().currentInstallationId(); + const currentInstallationId = await Parse.CoreManager.getInstallationController().currentInstallationId(); const installationId = '12345678'; const user = new Parse.User(); user.set('username', 'parse'); @@ -132,7 +132,7 @@ describe('Parse User', () => { let sessions = await sessionQuery.find({ useMasterKey: true }); expect(sessions.length).toBe(2); expect(sessions[0].get('installationId')).toBe(installationId); - expect(sessions[1].get('installationId')).toBe(currentInstallation); + expect(sessions[1].get('installationId')).toBe(currentInstallationId); expect(sessions[0].get('sessionToken')).toBe(user.getSessionToken()); expect(sessions[1].get('sessionToken')).toBe(loggedUser.getSessionToken()); @@ -142,12 +142,43 @@ describe('Parse User', () => { }); sessions = await sessionQuery.find({ useMasterKey: true }); expect(sessions.length).toBe(2); - expect(sessions[0].get('installationId')).toBe(currentInstallation); + expect(sessions[0].get('installationId')).toBe(currentInstallationId); expect(sessions[1].get('installationId')).toBe(installationId); expect(sessions[0].get('sessionToken')).toBe(loggedUser.getSessionToken()); expect(sessions[1].get('sessionToken')).toBe(installationUser.getSessionToken()); }); + it('can get current installation', async () => { + const currentInstallationId = await Parse.CoreManager.getInstallationController().currentInstallationId(); + const installation = await Parse.Installation.currentInstallation(); + expect(installation.installationId).toBe(currentInstallationId); + expect(installation.deviceType).toBe(Parse.Installation.DEVICE_TYPES.WEB); + await installation.save(); + expect(installation.id).toBeDefined(); + expect(installation.createdAt).toBeDefined(); + expect(installation.updatedAt).toBeDefined(); + const data = { + deviceToken: '1234', + badge: 1, + appIdentifier: 'com.parse.server', + appName: 'Parse JS SDK', + appVersion: '1.0.0', + parseVersion: '1.0.0', + localeIdentifier: 'en-US', + timeZone: 'GMT', + channels: ['test'], + GCMSenderId: '1234', + pushType: 'test', + }; + installation.set(data); + await installation.save(); + const query = new Parse.Query(Parse.Installation); + const result = await query.get(installation.id, { useMasterKey: true }); + Object.keys(data).forEach(key => { + expect(result[key]).toEqual(data[key]); + }); + }); + it('can login with userId', async () => { Parse.User.enableUnsafeCurrentUser(); diff --git a/src/CoreManager.ts b/src/CoreManager.ts index db639b55d..49ed62e91 100644 --- a/src/CoreManager.ts +++ b/src/CoreManager.ts @@ -14,6 +14,7 @@ import type { HookDeclaration, HookDeleteArg } from './ParseHooks'; import type ParseConfig from './ParseConfig'; import type LiveQueryClient from './LiveQueryClient'; import type ParseSchema from './ParseSchema'; +import type ParseInstallation from './ParseInstallation'; type AnalyticsController = { track: (name: string, dimensions: { [key: string]: string }) => Promise, @@ -41,6 +42,8 @@ type FileController = { }; type InstallationController = { currentInstallationId: () => Promise, + currentInstallation: () => Promise, + updateInstallationOnDisk: (installation: ParseInstallation) => Promise, }; type ObjectController = { fetch: ( @@ -376,7 +379,11 @@ const CoreManager = { }, setInstallationController(controller: InstallationController) { - requireMethods('InstallationController', ['currentInstallationId'], controller); + requireMethods( + 'InstallationController', + ['currentInstallationId', 'currentInstallation', 'updateInstallationOnDisk'], + controller + ); config['InstallationController'] = controller; }, diff --git a/src/InstallationController.js b/src/InstallationController.js deleted file mode 100644 index 41d865d10..000000000 --- a/src/InstallationController.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @flow - */ - -import Storage from './Storage'; -const uuidv4 = require('./uuid'); - -let iidCache = null; - -const InstallationController = { - currentInstallationId(): Promise { - if (typeof iidCache === 'string') { - return Promise.resolve(iidCache); - } - const path = Storage.generatePath('installationId'); - return Storage.getItemAsync(path).then(iid => { - if (!iid) { - iid = uuidv4(); - return Storage.setItemAsync(path, iid).then(() => { - iidCache = iid; - return iid; - }); - } - iidCache = iid; - return iid; - }); - }, - - _clearCache() { - iidCache = null; - }, - - _setInstallationIdCache(iid: string) { - iidCache = iid; - }, -}; - -module.exports = InstallationController; diff --git a/src/InstallationController.ts b/src/InstallationController.ts new file mode 100644 index 000000000..bc14e8337 --- /dev/null +++ b/src/InstallationController.ts @@ -0,0 +1,81 @@ +import CoreManager from './CoreManager'; +import Storage from './Storage'; +import ParseInstallation from './ParseInstallation'; +import uuidv4 from './uuid'; + +const CURRENT_INSTALLATION_KEY = 'currentInstallation'; +const CURRENT_INSTALLATION_ID_KEY = 'currentInstallationId'; + +let iidCache: string | null = null; +let currentInstallationCache = null; +let currentInstallationCacheMatchesDisk = false; + +const InstallationController = { + async updateInstallationOnDisk(installation: ParseInstallation): Promise { + const path = Storage.generatePath(CURRENT_INSTALLATION_KEY); + await Storage.setItemAsync(path, JSON.stringify(installation.toJSON())); + this._setCurrentInstallationCache(installation); + }, + + async currentInstallationId(): Promise { + if (typeof iidCache === 'string') { + return iidCache; + } + const path = Storage.generatePath(CURRENT_INSTALLATION_ID_KEY); + let iid = await Storage.getItemAsync(path); + if (!iid) { + iid = uuidv4(); + return Storage.setItemAsync(path, iid).then(() => { + iidCache = iid; + return iid; + }); + } + iidCache = iid; + return iid; + }, + + async currentInstallation(): Promise { + if (currentInstallationCache) { + return currentInstallationCache; + } + if (currentInstallationCacheMatchesDisk) { + return null; + } + const path = Storage.generatePath(CURRENT_INSTALLATION_KEY); + let installationData = await Storage.getItemAsync(path); + currentInstallationCacheMatchesDisk = true; + if (installationData) { + installationData = JSON.parse(installationData); + installationData.className = '_Installation'; + const current = ParseInstallation.fromJSON(installationData); + currentInstallationCache = current; + return current; + } + const installationId = await this.currentInstallationId(); + const installation = new ParseInstallation(); + installation.set('deviceType', ParseInstallation.DEVICE_TYPES.WEB); + installation.set('installationId', installationId); + installation.set('parseVersion', CoreManager.get('VERSION')); + currentInstallationCache = installation; + await Storage.setItemAsync(path, JSON.stringify(installation.toJSON())) + return installation; + }, + + _clearCache() { + iidCache = null; + currentInstallationCache = null; + currentInstallationCacheMatchesDisk = false; + }, + + _setInstallationIdCache(iid: string) { + iidCache = iid; + }, + + _setCurrentInstallationCache(installation: ParseInstallation, matchesDisk: boolean = true) { + currentInstallationCache = installation; + currentInstallationCacheMatchesDisk = matchesDisk; + }, +}; + +module.exports = InstallationController; +export default InstallationController; diff --git a/src/Parse.ts b/src/Parse.ts index 6692df3e3..b6bc8a169 100644 --- a/src/Parse.ts +++ b/src/Parse.ts @@ -199,12 +199,12 @@ const Parse: ParseType = { CoreManager.setIfNeeded('EventEmitter', EventEmitter); CoreManager.setIfNeeded('LiveQuery', new ParseLiveQuery()); CoreManager.setIfNeeded('CryptoController', CryptoController); + CoreManager.setIfNeeded('EventuallyQueue', EventuallyQueue); + CoreManager.setIfNeeded('InstallationController', InstallationController); CoreManager.setIfNeeded('LocalDatastoreController', LocalDatastoreController); CoreManager.setIfNeeded('StorageController', StorageController); CoreManager.setIfNeeded('WebSocketController', WebSocketController); - CoreManager.setIfNeeded('EventuallyQueue', EventuallyQueue); - if (process.env.PARSE_BUILD === 'browser') { Parse.IndexedDB = CoreManager.setIfNeeded('IndexedDBStorageController', IndexedDBStorageController); } @@ -464,7 +464,6 @@ const Parse: ParseType = { }, }; -CoreManager.setInstallationController(InstallationController); CoreManager.setRESTController(RESTController); if (process.env.PARSE_BUILD === 'node') { diff --git a/src/ParseInstallation.js b/src/ParseInstallation.js deleted file mode 100644 index 380b5c8a9..000000000 --- a/src/ParseInstallation.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @flow - */ - -import ParseObject from './ParseObject'; - -import type { AttributeMap } from './ObjectStateMutations'; - -export default class Installation extends ParseObject { - constructor(attributes: ?AttributeMap) { - super('_Installation'); - if (attributes && typeof attributes === 'object') { - if (!this.set(attributes || {})) { - throw new Error("Can't create an invalid Installation"); - } - } - } -} - -ParseObject.registerSubclass('_Installation', Installation); diff --git a/src/ParseInstallation.ts b/src/ParseInstallation.ts new file mode 100644 index 000000000..10f95bbea --- /dev/null +++ b/src/ParseInstallation.ts @@ -0,0 +1,230 @@ +import CoreManager from './CoreManager'; +import ParseObject from './ParseObject'; + +import type { AttributeMap } from './ObjectStateMutations'; + +type DeviceInterface = { + IOS: string; + MACOS: string; + TVOS: string; + FCM: string; + ANDROID: string; + WEB: string; +} + +const DEVICE_TYPES: DeviceInterface = { + IOS: 'ios', + MACOS: 'macos', + TVOS: 'tvos', + FCM: 'fcm', + ANDROID: 'android', + WEB: 'web', +}; + +/** + * Parse.Installation is a local representation of installation data that can be saved and retrieved from the Parse cloud. + * This class is a subclass of a Parse.Object, and retains the same functionality of a Parse.Object, but also extends it with installation-specific features. + * + *

A valid Parse.Installation can only be instantiated via Parse.Installation.currentInstallation() + * + * Parse.Installation objects which have a valid deviceToken and are saved to the Parse cloud can be used to target push notifications. + *

+ * + * @alias Parse.Installation + */ +class ParseInstallation extends ParseObject { + /** + * @param {object} attributes The initial set of data to store in the object. + */ + constructor(attributes?: AttributeMap) { + super('_Installation'); + if (attributes && typeof attributes === 'object') { + if (!this.set(attributes)) { + throw new Error("Can't create an invalid Installation"); + } + } + } + + /** + * A unique identifier for this installation’s client application. In iOS, this is the Bundle Identifier. + * + * @property {string} appIdentifier + * @static + */ + get appIdentifier() { + return this.get('appIdentifier'); + } + + /** + * The version string of the client application to which this installation belongs. + * + * @property {string} appVersion + * @static + */ + get appVersion() { + return this.get('appVersion'); + } + + /** + * The display name of the client application to which this installation belongs. + * + * @property {string} appName + * @static + */ + get appName() { + return this.get('appName'); + } + + /** + * The current value of the icon badge for iOS apps. + * Changes to this value on the server will be used + * for future badge-increment push notifications. + * + * @property {number} badge + * @static + */ + get badge() { + return this.get('badge'); + } + + /** + * An array of the channels to which a device is currently subscribed. + * + * @property {string[]} channels + * @static + */ + get channels() { + return this.get('channels'); + } + + /** + * Token used to deliver push notifications to the device. + * + * @property {string} deviceToken + * @static + */ + get deviceToken() { + return this.get('deviceToken'); + } + + /** + * The type of device, “ios”, “android”, “web”, etc. + * + * @property {string} deviceType + * @static + */ + get deviceType() { + return this.get('deviceType'); + } + + /** + * Gets the GCM sender identifier for this installation + * + * @property {string} GCMSenderId + * @static + */ + get GCMSenderId() { + return this.get('GCMSenderId'); + } + + /** + * Universally Unique Identifier (UUID) for the device used by Parse. It must be unique across all of an app’s installations. + * + * @property {string} installationId + * @static + */ + get installationId() { + return this.get('installationId'); + } + + /** + * Gets the local identifier for this installation + * + * @property {string} localeIdentifier + * @static + */ + get localeIdentifier() { + return this.get('localeIdentifier'); + } + + /** + * Gets the parse server version for this installation + * + * @property {string} parseVersion + * @static + */ + get parseVersion() { + return this.get('parseVersion'); + } + + /** + * This field is reserved for directing Parse to the push delivery network to be used. + * + * @property {string} pushType + * @static + */ + get pushType() { + return this.get('pushType'); + } + + /** + * Gets the time zone for this installation + * + * @property {string} timeZone + * @static + */ + get timeZone() { + return this.get('timeZone'); + } + + /** + * Returns the device types for used for Push Notifications. + * + *
+   * Parse.Installation.DEVICE_TYPES.IOS
+   * Parse.Installation.DEVICE_TYPES.MACOS
+   * Parse.Installation.DEVICE_TYPES.TVOS
+   * Parse.Installation.DEVICE_TYPES.FCM
+   * Parse.Installation.DEVICE_TYPES.ANDROID
+   * Parse.Installation.DEVICE_TYPES.WEB
+   * 
): Promise { + await super.save.apply(this, args); + await CoreManager.getInstallationController().updateInstallationOnDisk(this); + return this; + } + + /** + * Get the current Parse.Installation from disk. If doesn't exists, create an new installation. + * + *
+   * const installation = await Parse.Installation.currentInstallation();
+   * installation.set('deviceToken', '123');
+   * await installation.save();
+   * 
+ * + * @returns {Promise} A promise that resolves to the local installation object. + */ + static currentInstallation(): Promise { + return CoreManager.getInstallationController().currentInstallation(); + } +} + +ParseObject.registerSubclass('_Installation', ParseInstallation); + +module.exports = ParseInstallation; +export default ParseInstallation; diff --git a/src/__tests__/CoreManager-test.js b/src/__tests__/CoreManager-test.js index b08076bbe..d4744ae1e 100644 --- a/src/__tests__/CoreManager-test.js +++ b/src/__tests__/CoreManager-test.js @@ -136,9 +136,25 @@ describe('CoreManager', () => { 'InstallationController must implement currentInstallationId()' ); + expect(CoreManager.setInstallationController.bind(null, { + currentInstallationId: function () {}, + currentInstallation: function () {}, + })).toThrow( + 'InstallationController must implement updateInstallationOnDisk()' + ); + + expect(CoreManager.setInstallationController.bind(null, { + currentInstallationId: function () {}, + updateInstallationOnDisk: function () {}, + })).toThrow( + 'InstallationController must implement currentInstallation()' + ); + expect( CoreManager.setInstallationController.bind(null, { currentInstallationId: function () {}, + currentInstallation: function () {}, + updateInstallationOnDisk: function () {}, }) ).not.toThrow(); }); @@ -146,6 +162,8 @@ describe('CoreManager', () => { it('can set and get InstallationController', () => { const controller = { currentInstallationId: function () {}, + currentInstallation: function () {}, + updateInstallationOnDisk: function () {}, }; CoreManager.setInstallationController(controller); diff --git a/src/__tests__/EventuallyQueue-test.js b/src/__tests__/EventuallyQueue-test.js index 3b98d5022..da4486c42 100644 --- a/src/__tests__/EventuallyQueue-test.js +++ b/src/__tests__/EventuallyQueue-test.js @@ -57,6 +57,8 @@ CoreManager.setInstallationController({ currentInstallationId() { return Promise.resolve('iid'); }, + currentInstallation() {}, + updateInstallationOnDisk() {}, }); describe('EventuallyQueue', () => { diff --git a/src/__tests__/InstallationController-test.js b/src/__tests__/InstallationController-test.js index dcfad0a45..8c6422a2d 100644 --- a/src/__tests__/InstallationController-test.js +++ b/src/__tests__/InstallationController-test.js @@ -1,13 +1,22 @@ jest.dontMock('../CoreManager'); +jest.dontMock('../decode'); +jest.dontMock('../encode'); jest.dontMock('../InstallationController'); +jest.dontMock('../ObjectStateMutations'); +jest.dontMock('../ParseInstallation'); +jest.dontMock('../ParseObject'); +jest.dontMock('../ParseOp'); jest.dontMock('../Storage'); jest.dontMock('../StorageController.default'); +jest.dontMock('../SingleInstanceStateController'); +jest.dontMock('../UniqueInstanceStateController'); jest.mock('../uuid', () => { let value = 0; return () => value++ + ''; }); const CoreManager = require('../CoreManager'); +const ParseInstallation = require('../ParseInstallation'); const InstallationController = require('../InstallationController'); const Storage = require('../Storage'); @@ -21,48 +30,69 @@ describe('InstallationController', () => { InstallationController._clearCache(); }); - it('generates a new installation id when there is none', done => { - InstallationController.currentInstallationId().then(iid => { - expect(typeof iid).toBe('string'); - expect(iid.length).toBeGreaterThan(0); - done(); - }); + it('generates a new installation id when there is none', async () => { + const iid = await InstallationController.currentInstallationId(); + expect(typeof iid).toBe('string'); + expect(iid.length).toBeGreaterThan(0); }); - it('caches the installation id', done => { - let iid = null; - InstallationController.currentInstallationId() - .then(i => { - iid = i; - Storage._clear(); - return InstallationController.currentInstallationId(); - }) - .then(i => { - expect(i).toBe(iid); - done(); - }); + it('caches the installation id', async () => { + const iid = await InstallationController.currentInstallationId(); + Storage._clear(); + const i = await InstallationController.currentInstallationId(); + expect(i).toBe(iid); }); - it('permanently stores the installation id', done => { - let iid = null; - InstallationController.currentInstallationId() - .then(i => { - iid = i; - InstallationController._clearCache(); - return InstallationController.currentInstallationId(); - }) - .then(i => { - expect(i).toBe(iid); - done(); - }); + it('permanently stores the installation id', async () => { + const iid = await InstallationController.currentInstallationId(); + InstallationController._clearCache(); + const i = await InstallationController.currentInstallationId(); + expect(i).toBe(iid); }); - it('can set installation id', done => { + it('can set installation id', async () => { const iid = '12345678'; InstallationController._setInstallationIdCache(iid); - InstallationController.currentInstallationId().then(i => { - expect(i).toBe(iid); - done(); - }); + const i = await InstallationController.currentInstallationId(); + expect(i).toBe(iid); + }); + + it('generates a new installation when there is none', async () => { + const installation = await InstallationController.currentInstallation(); + expect(installation instanceof ParseInstallation).toBe(true); + expect(installation.deviceType).toBe('web'); + expect(installation.installationId).toBeDefined(); + }); + + it('caches the current installation', async () => { + const iid = 'cached-installation-id'; + InstallationController._setInstallationIdCache(iid); + const installation = await InstallationController.currentInstallation(); + Storage._clear(); + const i = await InstallationController.currentInstallation(); + expect(i.installationId).toEqual(installation.installationId); + }); + + it('permanently stores the current installation', async () => { + const iid = 'stored-installation-id'; + InstallationController._setInstallationIdCache(iid); + const installation = await InstallationController.currentInstallation(); + InstallationController._clearCache(); + const i = await InstallationController.currentInstallation(); + expect(i.installationId).toEqual(installation.installationId); + }); + + it('can update installation on disk', async () => { + const installationId = 'new-installation-id'; + const installation = new ParseInstallation({ installationId }); + InstallationController.updateInstallationOnDisk(installation); + const i = await InstallationController.currentInstallation(); + expect(i.installationId).toBe(installationId); + }); + + it('can handle cache not matching disk', async () => { + InstallationController._setCurrentInstallationCache(null, true); + const i = await InstallationController.currentInstallation(); + expect(i).toBeNull(); }); }); diff --git a/src/__tests__/Parse-test.js b/src/__tests__/Parse-test.js index d74b76373..119484592 100644 --- a/src/__tests__/Parse-test.js +++ b/src/__tests__/Parse-test.js @@ -240,6 +240,8 @@ describe('Parse module', () => { it('_getInstallationId', () => { const controller = { currentInstallationId: () => '1234', + currentInstallation: () => {}, + updateInstallationOnDisk: () => {}, }; CoreManager.setInstallationController(controller); expect(Parse._getInstallationId()).toBe('1234'); diff --git a/src/__tests__/ParseInstallation-test.js b/src/__tests__/ParseInstallation-test.js index 20c01d007..3bdc11fce 100644 --- a/src/__tests__/ParseInstallation-test.js +++ b/src/__tests__/ParseInstallation-test.js @@ -1,14 +1,20 @@ jest.dontMock('../CoreManager'); jest.dontMock('../decode'); +jest.dontMock('../LocalDatastore'); jest.dontMock('../ObjectStateMutations'); jest.dontMock('../ParseError'); jest.dontMock('../ParseObject'); jest.dontMock('../ParseOp'); jest.dontMock('../ParseInstallation'); +jest.dontMock('../promiseUtils'); +jest.dontMock('../RESTController'); +jest.dontMock('../TaskQueue'); jest.dontMock('../SingleInstanceStateController'); jest.dontMock('../UniqueInstanceStateController'); -const ParseInstallation = require('../ParseInstallation').default; +const LocalDatastore = require('../LocalDatastore'); +const ParseInstallation = require('../ParseInstallation'); +const CoreManager = require('../CoreManager'); describe('ParseInstallation', () => { it('can create ParseInstallation', () => { @@ -25,4 +31,73 @@ describe('ParseInstallation', () => { new ParseInstallation({ 'invalid#name': 'foo' }); }).toThrow("Can't create an invalid Installation"); }); + + it('can get device types', () => { + expect(ParseInstallation.DEVICE_TYPES.WEB).toEqual('web'); + expect(ParseInstallation.DEVICE_TYPES.IOS).toEqual('ios'); + expect(ParseInstallation.DEVICE_TYPES.MACOS).toEqual('macos'); + expect(ParseInstallation.DEVICE_TYPES.TVOS).toEqual('tvos'); + expect(ParseInstallation.DEVICE_TYPES.FCM).toEqual('fcm'); + expect(ParseInstallation.DEVICE_TYPES.ANDROID).toEqual('android'); + }); + + it('can retrieve getters', () => { + const data = { + deviceType: 'web', + installationId: '1234', + deviceToken: '1234', + badge: 1, + appIdentifier: 'com.parse.server', + appName: 'Parse JS SDK', + appVersion: '1.0.0', + parseVersion: '1.0.0', + localeIdentifier: 'en-US', + timeZone: 'GMT', + channels: ['test'], + GCMSenderId: '1234', + pushType: 'test', + }; + const installation = new ParseInstallation(data); + Object.keys(data).forEach(key => { + expect(installation[key]).toEqual(data[key]); + }); + }); + + it('can save to disk', async () => { + const InstallationController = { + async updateInstallationOnDisk() {}, + async currentInstallationId() {}, + async currentInstallation() {}, + }; + CoreManager.setInstallationController(InstallationController); + CoreManager.setRESTController({ + request() { + return Promise.resolve({}, 200); + }, + ajax() {}, + }); + CoreManager.setLocalDatastore(LocalDatastore); + jest.spyOn(InstallationController, 'updateInstallationOnDisk').mockImplementationOnce(() => {}); + const installation = new ParseInstallation(); + installation.set('deviceToken', '1234'); + await installation.save(); + expect(InstallationController.updateInstallationOnDisk).toHaveBeenCalledTimes(1); + }); + + it('can get current installation', async () => { + const InstallationController = { + async updateInstallationOnDisk() {}, + async currentInstallationId() {}, + async currentInstallation() {}, + }; + CoreManager.setInstallationController(InstallationController); + jest.spyOn(InstallationController, 'currentInstallation').mockImplementationOnce(() => { + const installation = new ParseInstallation({ deviceType: 'web', installationId: '1234' }); + return installation; + }); + const installation = await ParseInstallation.currentInstallation(); + expect(InstallationController.currentInstallation).toHaveBeenCalledTimes(1); + expect(installation.deviceType).toEqual('web'); + expect(installation.installationId).toEqual('1234'); + }); }); diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 890d77ff4..a9474fef5 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -165,6 +165,8 @@ CoreManager.setInstallationController({ currentInstallationId() { return Promise.resolve('iid'); }, + currentInstallation() {}, + updateInstallationOnDisk() {}, }); CoreManager.set('APPLICATION_ID', 'A'); CoreManager.set('JAVASCRIPT_KEY', 'B'); diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 640d83bc5..4c1890379 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -17,6 +17,8 @@ CoreManager.setInstallationController({ currentInstallationId() { return Promise.resolve('iid'); }, + currentInstallation() {}, + updateInstallationOnDisk() {}, }); CoreManager.set('APPLICATION_ID', 'A'); CoreManager.set('JAVASCRIPT_KEY', 'B'); diff --git a/types/CoreManager.d.ts b/types/CoreManager.d.ts index 2fa05b5d3..4c1f949a2 100644 --- a/types/CoreManager.d.ts +++ b/types/CoreManager.d.ts @@ -14,6 +14,7 @@ import type { HookDeclaration, HookDeleteArg } from './ParseHooks'; import type ParseConfig from './ParseConfig'; import type LiveQueryClient from './LiveQueryClient'; import type ParseSchema from './ParseSchema'; +import type ParseInstallation from './ParseInstallation'; type AnalyticsController = { track: (name: string, dimensions: { [key: string]: string; @@ -54,6 +55,8 @@ type FileController = { }; type InstallationController = { currentInstallationId: () => Promise; + currentInstallation: () => Promise; + updateInstallationOnDisk: (installation: ParseInstallation) => Promise; }; type ObjectController = { fetch: (object: ParseObject | Array, forceFetch: boolean, options: RequestOptions) => Promise; diff --git a/types/InstallationController.d.ts b/types/InstallationController.d.ts index cb0ff5c3b..1409c39db 100644 --- a/types/InstallationController.d.ts +++ b/types/InstallationController.d.ts @@ -1 +1,10 @@ -export {}; +import ParseInstallation from './ParseInstallation'; +declare const InstallationController: { + updateInstallationOnDisk(installation: ParseInstallation): Promise; + currentInstallationId(): Promise; + currentInstallation(): Promise; + _clearCache(): void; + _setInstallationIdCache(iid: string): void; + _setCurrentInstallationCache(installation: ParseInstallation, matchesDisk?: boolean): void; +}; +export default InstallationController; diff --git a/types/ParseInstallation.d.ts b/types/ParseInstallation.d.ts index 2a512fc6b..c74a47ceb 100644 --- a/types/ParseInstallation.d.ts +++ b/types/ParseInstallation.d.ts @@ -1,6 +1,156 @@ -// @ts-nocheck -export default class Installation extends ParseObject { - constructor(attributes: AttributeMap | null); -} import ParseObject from './ParseObject'; -import { AttributeMap } from './ObjectStateMutations'; +import type { AttributeMap } from './ObjectStateMutations'; +type DeviceInterface = { + IOS: string; + MACOS: string; + TVOS: string; + FCM: string; + ANDROID: string; + WEB: string; +}; +/** + * Parse.Installation is a local representation of installation data that can be saved and retrieved from the Parse cloud. + * This class is a subclass of a Parse.Object, and retains the same functionality of a Parse.Object, but also extends it with installation-specific features. + * + *

A valid Parse.Installation can only be instantiated via Parse.Installation.currentInstallation() + * + * Parse.Installation objects which have a valid deviceToken and are saved to the Parse cloud can be used to target push notifications. + *

+ * + * @alias Parse.Installation + */ +declare class ParseInstallation extends ParseObject { + /** + * @param {object} attributes The initial set of data to store in the object. + */ + constructor(attributes?: AttributeMap); + /** + * A unique identifier for this installation’s client application. In iOS, this is the Bundle Identifier. + * + * @property {string} appIdentifier + * @static + */ + get appIdentifier(): any; + /** + * The version string of the client application to which this installation belongs. + * + * @property {string} appVersion + * @static + */ + get appVersion(): any; + /** + * The display name of the client application to which this installation belongs. + * + * @property {string} appName + * @static + */ + get appName(): any; + /** + * The current value of the icon badge for iOS apps. + * Changes to this value on the server will be used + * for future badge-increment push notifications. + * + * @property {number} badge + * @static + */ + get badge(): any; + /** + * An array of the channels to which a device is currently subscribed. + * + * @property {string[]} channels + * @static + */ + get channels(): any; + /** + * Token used to deliver push notifications to the device. + * + * @property {string} deviceToken + * @static + */ + get deviceToken(): any; + /** + * The type of device, “ios”, “android”, “web”, etc. + * + * @property {string} deviceType + * @static + */ + get deviceType(): any; + /** + * Gets the GCM sender identifier for this installation + * + * @property {string} GCMSenderId + * @static + */ + get GCMSenderId(): any; + /** + * Universally Unique Identifier (UUID) for the device used by Parse. It must be unique across all of an app’s installations. + * + * @property {string} installationId + * @static + */ + get installationId(): any; + /** + * Gets the local identifier for this installation + * + * @property {string} localeIdentifier + * @static + */ + get localeIdentifier(): any; + /** + * Gets the parse server version for this installation + * + * @property {string} parseVersion + * @static + */ + get parseVersion(): any; + /** + * This field is reserved for directing Parse to the push delivery network to be used. + * + * @property {string} pushType + * @static + */ + get pushType(): any; + /** + * Gets the time zone for this installation + * + * @property {string} timeZone + * @static + */ + get timeZone(): any; + /** + * Returns the device types for used for Push Notifications. + * + *
+     * Parse.Installation.DEVICE_TYPES.IOS
+     * Parse.Installation.DEVICE_TYPES.MACOS
+     * Parse.Installation.DEVICE_TYPES.TVOS
+     * Parse.Installation.DEVICE_TYPES.FCM
+     * Parse.Installation.DEVICE_TYPES.ANDROID
+     * Parse.Installation.DEVICE_TYPES.WEB
+     * 
): Promise; + /** + * Get the current Parse.Installation from disk. If doesn't exists, create an new installation. + * + *
+     * const installation = await Parse.Installation.currentInstallation();
+     * installation.set('deviceToken', '123');
+     * await installation.save();
+     * 
+ * + * @returns {Promise} A promise that resolves to the local installation object. + */ + static currentInstallation(): Promise; +} +export default ParseInstallation; From aff0a00a6f5d507166dbfc14d1b695002399913b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 4 May 2024 17:55:15 +0000 Subject: [PATCH 26/32] chore(release): 5.1.0-alpha.9 [skip ci] # [5.1.0-alpha.9](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.8...5.1.0-alpha.9) (2024-05-04) ### Features * Improve installation object `Parse.Installation.currentInstallation` to support web push notifications ([#2119](https://github.com/parse-community/Parse-SDK-JS/issues/2119)) ([4fc62ce](https://github.com/parse-community/Parse-SDK-JS/commit/4fc62cec0c4ea704f48ec501a5f0182836de45d1)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 5042364fb..b286a8194 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.9](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.8...5.1.0-alpha.9) (2024-05-04) + + +### Features + +* Improve installation object `Parse.Installation.currentInstallation` to support web push notifications ([#2119](https://github.com/parse-community/Parse-SDK-JS/issues/2119)) ([4fc62ce](https://github.com/parse-community/Parse-SDK-JS/commit/4fc62cec0c4ea704f48ec501a5f0182836de45d1)) + # [5.1.0-alpha.8](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.7...5.1.0-alpha.8) (2024-05-02) diff --git a/package-lock.json b/package-lock.json index 28b01b9cb..7bab3f9c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.1.0-alpha.8", + "version": "5.1.0-alpha.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.1.0-alpha.8", + "version": "5.1.0-alpha.9", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index e21708471..1e38cbb32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.1.0-alpha.8", + "version": "5.1.0-alpha.9", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From 01faf7ba9337dcc35d6105336b899e40503d3e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Wed, 15 May 2024 00:27:25 +0200 Subject: [PATCH 27/32] test: Do not mock ParseObject and ParseOp for ParseQuery tests (#2123) --- src/__tests__/ParseQuery-test.js | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js index 41541ba6b..6afcd224f 100644 --- a/src/__tests__/ParseQuery-test.js +++ b/src/__tests__/ParseQuery-test.js @@ -5,6 +5,8 @@ jest.dontMock('../EventEmitter'); jest.dontMock('../ParseError'); jest.dontMock('../ParseGeoPoint'); jest.dontMock('../ParseQuery'); +jest.dontMock('../ParseObject'); +jest.dontMock('../ParseOp'); jest.dontMock('../promiseUtils'); jest.dontMock('../SingleInstanceStateController'); jest.dontMock('../UniqueInstanceStateController'); @@ -17,22 +19,6 @@ jest.mock('../uuid', () => { let value = 0; return () => value++; }); -const mockObject = function (className) { - this.className = className; - this.attributes = {}; -}; -mockObject.registerSubclass = function () {}; -mockObject.fromJSON = function (json) { - const o = new mockObject(json.className); - o.id = json.objectId; - for (const attr in json) { - if (attr !== 'className' && attr !== '__type' && attr !== 'objectId') { - o.attributes[attr] = json[attr]; - } - } - return o; -}; -jest.setMock('../ParseObject', mockObject); const mockLocalDatastore = { _serializeObjectsFromPinName: jest.fn(), @@ -44,7 +30,7 @@ let CoreManager = require('../CoreManager'); const EventEmitter = require('../EventEmitter'); const ParseError = require('../ParseError').default; const ParseGeoPoint = require('../ParseGeoPoint').default; -let ParseObject = require('../ParseObject'); +let ParseObject = require('../ParseObject').default; let ParseQuery = require('../ParseQuery').default; const LiveQuerySubscription = require('../LiveQuerySubscription').default; @@ -2156,7 +2142,7 @@ describe('ParseQuery', () => { let callCount = 0; const callback = (accumulator, object) => { callCount += 1; - accumulator.attributes.number += object.attributes.number; + accumulator.set('number', accumulator.attributes.number + object.attributes.number); return accumulator; }; const q = new ParseQuery('Item'); From b415165486f0328e0f9fb2d949d7b11abf363435 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 15 May 2024 14:56:18 -0500 Subject: [PATCH 28/32] fix: Remove circular dependencies (#2125) --- .github/workflows/ci.yml | 9 + .madgerc | 10 + package-lock.json | 1719 ++++++++++++++++- package.json | 2 + src/CoreManager.ts | 43 + src/ObjectStateMutations.js | 3 +- src/ParseACL.js | 14 +- src/ParseObject.js | 4 +- src/ParseOp.js | 17 +- src/ParseQuery.js | 1 + src/ParseRelation.js | 11 +- src/ParseRole.js | 2 + src/ParseUser.js | 1 + src/SingleInstanceStateController.js | 5 +- src/__tests__/Cloud-test.js | 2 + src/__tests__/Hooks-test.js | 1 + src/__tests__/LocalDatastore-test.js | 8 +- src/__tests__/ObjectStateMutations-test.js | 3 + src/__tests__/Parse-test.js | 1 + src/__tests__/ParseACL-test.js | 4 + src/__tests__/ParseConfig-test.js | 6 +- src/__tests__/ParseObject-test.js | 4 + src/__tests__/ParseOp-test.js | 5 + src/__tests__/ParseRelation-test.js | 5 + .../SingleInstanceStateController-test.js | 3 + src/__tests__/Storage-test.js | 2 - .../UniqueInstanceStateController-test.js | 4 + src/__tests__/arrayContainsObject-test.js | 2 + src/__tests__/canBeSerialized-test.js | 3 + src/__tests__/decode-test.js | 8 +- src/__tests__/encode-test.js | 7 + src/__tests__/equals-test.js | 4 + .../test_helpers/mockStorageInteface.js | 31 - src/__tests__/unique-test.js | 2 + src/__tests__/unsavedChildren-test.js | 3 + src/arrayContainsObject.js | 4 +- src/canBeSerialized.js | 5 +- src/decode.js | 5 +- src/encode.js | 5 +- src/equals.js | 4 +- src/unique.js | 3 +- src/unsavedChildren.js | 4 +- src/{uuid.js => uuid.ts} | 11 +- types/CoreManager.d.ts | 10 + types/uuid.d.ts | 4 +- 45 files changed, 1899 insertions(+), 105 deletions(-) create mode 100644 .madgerc delete mode 100644 src/__tests__/test_helpers/mockStorageInteface.js rename src/{uuid.js => uuid.ts} (64%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cedf086df..22caf3799 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,15 @@ jobs: - run: npm ci - name: Check Docs run: npm run docs + check-circular: + name: Check Circular Dependencies + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: npm ci + - name: Circular Dependencies + run: npm run madge:circular check-lint: name: Lint timeout-minutes: 15 diff --git a/.madgerc b/.madgerc new file mode 100644 index 000000000..940eac961 --- /dev/null +++ b/.madgerc @@ -0,0 +1,10 @@ +{ + "detectiveOptions": { + "ts": { + "skipTypeImports": true + }, + "es6": { + "skipTypeImports": true + } + } +} diff --git a/package-lock.json b/package-lock.json index 7bab3f9c6..ede3b749e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "jsdoc": "4.0.2", "jsdoc-babel": "0.5.0", "lint-staged": "13.2.2", + "madge": "7.0.0", "metro-react-native-babel-preset": "0.76.4", "mongodb-runner": "5.4.3", "parse-server": "7.1.0-alpha.1", @@ -2926,6 +2927,19 @@ "node": ">= 4.0.0" } }, + "node_modules/@dependents/detective-less": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-4.1.0.tgz", + "integrity": "sha512-KrkT6qO5NxqNfy68sBl6CTSoJ4SNDIS5iQArkibhlbGU4LaDukZ3q2HIkh8aUKDio6o4itU4xDR7t82Y2eP1Bg==", + "dev": true, + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^6.0.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.37.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.37.1.tgz", @@ -6569,6 +6583,12 @@ "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", "dev": true }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -6700,6 +6720,12 @@ "node": ">=0.10.0" } }, + "node_modules/app-module-path": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", + "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", + "dev": true + }, "node_modules/append-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", @@ -7048,6 +7074,15 @@ "node": ">=0.10.0" } }, + "node_modules/ast-module-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-5.0.0.tgz", + "integrity": "sha512-JvqziE0Wc0rXQfma0HZC/aY7URXHFuZV84fJRtP8u+lhp0JYCNd5wJzVXP45t0PH0Mej3ynlzvdyITYIu0G4LQ==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -8639,6 +8674,18 @@ "node": ">=8" } }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", @@ -9030,9 +9077,9 @@ "dev": true }, "node_modules/commander": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", - "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "engines": { "node": ">=14" @@ -9047,6 +9094,12 @@ "node": ">= 12.0.0" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -10081,6 +10134,27 @@ "node": ">= 0.10" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -10263,6 +10337,24 @@ "node": ">= 0.8" } }, + "node_modules/dependency-tree": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-10.0.9.tgz", + "integrity": "sha512-dwc59FRIsht+HfnTVM0BCjJaEWxdq2YAvEDy4/Hn6CwS3CBWMtFnL3aZGAkQn3XCYxk/YcTDE4jX2Q7bFTwCjA==", + "dev": true, + "dependencies": { + "commander": "^10.0.1", + "filing-cabinet": "^4.1.6", + "precinct": "^11.0.5", + "typescript": "^5.0.4" + }, + "bin": { + "dependency-tree": "bin/cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", @@ -10528,6 +10620,113 @@ "node": ">=0.8.0" } }, + "node_modules/detective-amd": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-5.0.2.tgz", + "integrity": "sha512-XFd/VEQ76HSpym80zxM68ieB77unNuoMwopU2TFT/ErUk5n4KvUTwW4beafAVUugrjV48l4BmmR0rh2MglBaiA==", + "dev": true, + "dependencies": { + "ast-module-types": "^5.0.0", + "escodegen": "^2.0.0", + "get-amd-module-type": "^5.0.1", + "node-source-walk": "^6.0.1" + }, + "bin": { + "detective-amd": "bin/cli.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/detective-cjs": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-5.0.1.tgz", + "integrity": "sha512-6nTvAZtpomyz/2pmEmGX1sXNjaqgMplhQkskq2MLrar0ZAIkHMrDhLXkRiK2mvbu9wSWr0V5/IfiTrZqAQMrmQ==", + "dev": true, + "dependencies": { + "ast-module-types": "^5.0.0", + "node-source-walk": "^6.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/detective-es6": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-4.0.1.tgz", + "integrity": "sha512-k3Z5tB4LQ8UVHkuMrFOlvb3GgFWdJ9NqAa2YLUU/jTaWJIm+JJnEh4PsMc+6dfT223Y8ACKOaC0qcj7diIhBKw==", + "dev": true, + "dependencies": { + "node-source-walk": "^6.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/detective-postcss": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-6.1.3.tgz", + "integrity": "sha512-7BRVvE5pPEvk2ukUWNQ+H2XOq43xENWbH0LcdCE14mwgTBEAMoAx+Fc1rdp76SmyZ4Sp48HlV7VedUnP6GA1Tw==", + "dev": true, + "dependencies": { + "is-url": "^1.2.4", + "postcss": "^8.4.23", + "postcss-values-parser": "^6.0.2" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/detective-sass": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-5.0.3.tgz", + "integrity": "sha512-YsYT2WuA8YIafp2RVF5CEfGhhyIVdPzlwQgxSjK+TUm3JoHP+Tcorbk3SfG0cNZ7D7+cYWa0ZBcvOaR0O8+LlA==", + "dev": true, + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^6.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/detective-scss": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-4.0.3.tgz", + "integrity": "sha512-VYI6cHcD0fLokwqqPFFtDQhhSnlFWvU614J42eY6G0s8c+MBhi9QAWycLwIOGxlmD8I/XvGSOUV1kIDhJ70ZPg==", + "dev": true, + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^6.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/detective-stylus": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-4.0.0.tgz", + "integrity": "sha512-TfPotjhszKLgFBzBhTOxNHDsutIxx9GTWjrL5Wh7Qx/ydxKhwUrlSFeLIn+ZaHPF+h0siVBkAQSuy6CADyTxgQ==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/detective-typescript": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-11.2.0.tgz", + "integrity": "sha512-ARFxjzizOhPqs1fYC/2NMC3N4jrQ6HvVflnXBTRqNEqJuXwyKLRr9CrJwkRcV/SnZt1sNXgsF6FPm0x57Tq0rw==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "^5.62.0", + "ast-module-types": "^5.0.0", + "node-source-walk": "^6.0.2", + "typescript": "^5.4.4" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + } + }, "node_modules/devtools-protocol": { "version": "0.0.1120988", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", @@ -10836,6 +11035,19 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", + "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -12295,6 +12507,32 @@ "node": ">=0.10.0" } }, + "node_modules/filing-cabinet": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-4.2.0.tgz", + "integrity": "sha512-YZ21ryzRcyqxpyKggdYSoXx//d3sCJzM3lsYoaeg/FyXdADGJrUl+BW1KIglaVLJN5BBcMtWylkygY8zBp2MrQ==", + "dev": true, + "dependencies": { + "app-module-path": "^2.2.0", + "commander": "^10.0.1", + "enhanced-resolve": "^5.14.1", + "is-relative-path": "^1.0.2", + "module-definition": "^5.0.1", + "module-lookup-amd": "^8.0.5", + "resolve": "^1.22.3", + "resolve-dependency-path": "^3.0.2", + "sass-lookup": "^5.0.1", + "stylus-lookup": "^5.0.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.0.4" + }, + "bin": { + "filing-cabinet": "bin/cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -13009,10 +13247,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -13164,6 +13405,19 @@ "node": ">=6.9.0" } }, + "node_modules/get-amd-module-type": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-5.0.1.tgz", + "integrity": "sha512-jb65zDeHyDjFR1loOVk0HQGM5WNwoGB8aLWy3LKCieMKol0/ProHkhO2X1JxojuN10vbz1qNn09MJ7tNp7qMzw==", + "dev": true, + "dependencies": { + "ast-module-types": "^5.0.0", + "node-source-walk": "^6.0.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/get-assigned-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", @@ -13193,6 +13447,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -13588,6 +13848,21 @@ "node": ">= 0.10" } }, + "node_modules/gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "gonzales": "bin/gonzales.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/google-auth-library": { "version": "9.6.3", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.6.3.tgz", @@ -14748,6 +15023,18 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -15273,12 +15560,12 @@ } }, "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15415,6 +15702,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-natural-number": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", @@ -15514,6 +15810,15 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -15526,6 +15831,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-relative-path": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-relative-path/-/is-relative-path-1.0.2.tgz", + "integrity": "sha512-i1h+y50g+0hRbBD+dbnInl3JlJ702aar58snAeX+MxBAPvzXGej7sYoPMhlnykabt0ZzCJNBEyzMlekuQZN7fA==", + "dev": true + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -15587,6 +15898,36 @@ "node": ">=0.10.0" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, + "node_modules/is-url-superb": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", + "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -18792,6 +19133,92 @@ "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", "dev": true }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/log-update": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", @@ -18976,6 +19403,124 @@ "es5-ext": "~0.10.2" } }, + "node_modules/madge": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/madge/-/madge-7.0.0.tgz", + "integrity": "sha512-x9eHkBWoCJ2B8yGesWf8LRucarkbH5P3lazqgvmxe4xn5U2Meyfu906iG9mBB1RnY/f4D+gtELWdiz1k6+jAZA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "commander": "^7.2.0", + "commondir": "^1.0.1", + "debug": "^4.3.4", + "dependency-tree": "^10.0.9", + "ora": "^5.4.1", + "pluralize": "^8.0.0", + "precinct": "^11.0.5", + "pretty-ms": "^7.0.1", + "rc": "^1.2.8", + "stream-to-array": "^2.3.0", + "ts-graphviz": "^1.8.1", + "walkdir": "^0.4.1" + }, + "bin": { + "madge": "bin/cli.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/pahen" + }, + "peerDependencies": { + "typescript": "^3.9.5 || ^4.9.5 || ^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/madge/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/madge/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/madge/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/madge/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/madge/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/madge/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/madge/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -19711,6 +20256,22 @@ "node": ">=0.10.0" } }, + "node_modules/module-definition": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-5.0.1.tgz", + "integrity": "sha512-kvw3B4G19IXk+BOXnYq/D/VeO9qfHaapMeuS7w7sNUqmGaA6hywdFHMi+VWeR9wUScXM7XjoryTffCZ5B0/8IA==", + "dev": true, + "dependencies": { + "ast-module-types": "^5.0.0", + "node-source-walk": "^6.0.1" + }, + "bin": { + "module-definition": "bin/cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/module-deps": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", @@ -19774,6 +20335,24 @@ "xtend": "~4.0.1" } }, + "node_modules/module-lookup-amd": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-8.0.5.tgz", + "integrity": "sha512-vc3rYLjDo5Frjox8NZpiyLXsNWJ5BWshztc/5KSOMzpg9k5cHH652YsJ7VKKmtM4SvaxuE9RkrYGhiSjH3Ehow==", + "dev": true, + "dependencies": { + "commander": "^10.0.1", + "glob": "^7.2.3", + "requirejs": "^2.3.6", + "requirejs-config-file": "^4.0.0" + }, + "bin": { + "lookup-amd": "bin/cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -19995,6 +20574,24 @@ "dev": true, "optional": true }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -20121,6 +20718,18 @@ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "dev": true }, + "node_modules/node-source-walk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-6.0.2.tgz", + "integrity": "sha512-jn9vOIK/nfqoFCcpK89/VCVaLg1IHE6UVfDOzvqmANaJ/rWCTEdH8RZ1V278nv2jr36BJdyQXIAavBLXpzdlag==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.21.8" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -23085,6 +23694,99 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -23458,6 +24160,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", @@ -24168,6 +24879,57 @@ "node": ">=0.10.0" } }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-values-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz", + "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==", + "dev": true, + "dependencies": { + "color-name": "^1.1.4", + "is-url-superb": "^4.0.0", + "quote-unquote": "^1.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.2.9" + } + }, + "node_modules/postcss-values-parser/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -24207,6 +24969,32 @@ "node": ">=0.10.0" } }, + "node_modules/precinct": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/precinct/-/precinct-11.0.5.tgz", + "integrity": "sha512-oHSWLC8cL/0znFhvln26D14KfCQFFn4KOLSw6hmLhd+LQ2SKt9Ljm89but76Pc7flM9Ty1TnXyrA2u16MfRV3w==", + "dev": true, + "dependencies": { + "@dependents/detective-less": "^4.1.0", + "commander": "^10.0.1", + "detective-amd": "^5.0.2", + "detective-cjs": "^5.0.1", + "detective-es6": "^4.0.1", + "detective-postcss": "^6.1.3", + "detective-sass": "^5.0.3", + "detective-scss": "^4.0.3", + "detective-stylus": "^4.0.0", + "detective-typescript": "^11.1.0", + "module-definition": "^5.0.1", + "node-source-walk": "^6.0.2" + }, + "bin": { + "precinct": "bin/cli.js" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + } + }, "node_modules/precond": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", @@ -24284,6 +25072,21 @@ "node": ">= 0.8" } }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -24721,6 +25524,12 @@ "node": ">=8" } }, + "node_modules/quote-unquote": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz", + "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", + "dev": true + }, "node_modules/randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -25376,6 +26185,32 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "node_modules/requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "dev": true, + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/requirejs-config-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz", + "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.0", + "stringify-object": "^3.2.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -25392,12 +26227,12 @@ } }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -25420,6 +26255,15 @@ "node": ">=8" } }, + "node_modules/resolve-dependency-path": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-3.0.2.tgz", + "integrity": "sha512-Tz7zfjhLfsvR39ADOSk9us4421J/1ztVBo4rWUkF38hgHK5m0OCZ3NxFVpqHRkjctnwVa15igEUHFJp8MCS7vA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -25642,6 +26486,21 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/sass-lookup": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-5.0.1.tgz", + "integrity": "sha512-t0X5PaizPc2H4+rCwszAqHZRtr4bugo4pgiCvrBFvIX0XFxnr29g77LJcpyj9A0DcKf7gXMLcgvRjsonYI6x4g==", + "dev": true, + "dependencies": { + "commander": "^10.0.1" + }, + "bin": { + "sass-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -26316,6 +27175,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -26685,6 +27553,15 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==", + "dev": true, + "dependencies": { + "any-promise": "^1.1.0" + } + }, "node_modules/streamqueue": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/streamqueue/-/streamqueue-0.0.6.tgz", @@ -26816,6 +27693,29 @@ "node": ">=8" } }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stringify-object/node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -26927,6 +27827,21 @@ "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "dev": true }, + "node_modules/stylus-lookup": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-5.0.1.tgz", + "integrity": "sha512-tLtJEd5AGvnVy4f9UHQMw4bkJJtaAcmo54N+ovQBjDY3DuWyK9Eltxzr5+KG0q4ew6v2EHyuWWNnHeiw/Eo7rQ==", + "dev": true, + "dependencies": { + "commander": "^10.0.1" + }, + "bin": { + "stylus-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/subarg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", @@ -27072,6 +27987,15 @@ "integrity": "sha512-GQ3gtYFSOAxSMN/apGtDKKkbJf+8izz5YfbGqIsUc7AMiQOapARZ76dhilRY2h39cynYxBFdafQo5HUL5vgkrg==", "dev": true }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tar": { "version": "6.1.15", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", @@ -27561,6 +28485,33 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-graphviz": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-1.8.2.tgz", + "integrity": "sha512-5YhbFoHmjxa7pgQLkB07MtGnGJ/yhvjmc9uhsnDBEICME6gkPf83SBwLDQqGDoCa3XzUMWLk1AU2Wn1u1naDtA==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", @@ -27746,11 +28697,10 @@ "dev": true }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -28545,6 +29495,15 @@ "node": ">=14" } }, + "node_modules/walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -28554,6 +29513,15 @@ "makeerror": "1.0.12" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -31066,6 +32034,16 @@ } } }, + "@dependents/detective-less": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-4.1.0.tgz", + "integrity": "sha512-KrkT6qO5NxqNfy68sBl6CTSoJ4SNDIS5iQArkibhlbGU4LaDukZ3q2HIkh8aUKDio6o4itU4xDR7t82Y2eP1Bg==", + "dev": true, + "requires": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^6.0.1" + } + }, "@es-joy/jsdoccomment": { "version": "0.37.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.37.1.tgz", @@ -33892,6 +34870,12 @@ "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", "dev": true }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -34007,6 +34991,12 @@ } } }, + "app-module-path": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", + "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", + "dev": true + }, "append-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", @@ -34300,6 +35290,12 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "ast-module-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-5.0.0.tgz", + "integrity": "sha512-JvqziE0Wc0rXQfma0HZC/aY7URXHFuZV84fJRtP8u+lhp0JYCNd5wJzVXP45t0PH0Mej3ynlzvdyITYIu0G4LQ==", + "dev": true + }, "ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -35596,6 +36592,12 @@ "restore-cursor": "^3.1.0" } }, + "cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true + }, "cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", @@ -35907,9 +36909,9 @@ "dev": true }, "commander": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", - "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true }, "comment-parser": { @@ -35918,6 +36920,12 @@ "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", "dev": true }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -36736,6 +37744,23 @@ "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", "dev": true }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true + } + } + }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -36873,6 +37898,18 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, + "dependency-tree": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-10.0.9.tgz", + "integrity": "sha512-dwc59FRIsht+HfnTVM0BCjJaEWxdq2YAvEDy4/Hn6CwS3CBWMtFnL3aZGAkQn3XCYxk/YcTDE4jX2Q7bFTwCjA==", + "dev": true, + "requires": { + "commander": "^10.0.1", + "filing-cabinet": "^4.1.6", + "precinct": "^11.0.5", + "typescript": "^5.0.4" + } + }, "deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", @@ -37102,6 +38139,86 @@ "minimist": "^1.2.6" } }, + "detective-amd": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-5.0.2.tgz", + "integrity": "sha512-XFd/VEQ76HSpym80zxM68ieB77unNuoMwopU2TFT/ErUk5n4KvUTwW4beafAVUugrjV48l4BmmR0rh2MglBaiA==", + "dev": true, + "requires": { + "ast-module-types": "^5.0.0", + "escodegen": "^2.0.0", + "get-amd-module-type": "^5.0.1", + "node-source-walk": "^6.0.1" + } + }, + "detective-cjs": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-5.0.1.tgz", + "integrity": "sha512-6nTvAZtpomyz/2pmEmGX1sXNjaqgMplhQkskq2MLrar0ZAIkHMrDhLXkRiK2mvbu9wSWr0V5/IfiTrZqAQMrmQ==", + "dev": true, + "requires": { + "ast-module-types": "^5.0.0", + "node-source-walk": "^6.0.0" + } + }, + "detective-es6": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-4.0.1.tgz", + "integrity": "sha512-k3Z5tB4LQ8UVHkuMrFOlvb3GgFWdJ9NqAa2YLUU/jTaWJIm+JJnEh4PsMc+6dfT223Y8ACKOaC0qcj7diIhBKw==", + "dev": true, + "requires": { + "node-source-walk": "^6.0.1" + } + }, + "detective-postcss": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-6.1.3.tgz", + "integrity": "sha512-7BRVvE5pPEvk2ukUWNQ+H2XOq43xENWbH0LcdCE14mwgTBEAMoAx+Fc1rdp76SmyZ4Sp48HlV7VedUnP6GA1Tw==", + "dev": true, + "requires": { + "is-url": "^1.2.4", + "postcss": "^8.4.23", + "postcss-values-parser": "^6.0.2" + } + }, + "detective-sass": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-5.0.3.tgz", + "integrity": "sha512-YsYT2WuA8YIafp2RVF5CEfGhhyIVdPzlwQgxSjK+TUm3JoHP+Tcorbk3SfG0cNZ7D7+cYWa0ZBcvOaR0O8+LlA==", + "dev": true, + "requires": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^6.0.1" + } + }, + "detective-scss": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-4.0.3.tgz", + "integrity": "sha512-VYI6cHcD0fLokwqqPFFtDQhhSnlFWvU614J42eY6G0s8c+MBhi9QAWycLwIOGxlmD8I/XvGSOUV1kIDhJ70ZPg==", + "dev": true, + "requires": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^6.0.1" + } + }, + "detective-stylus": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-4.0.0.tgz", + "integrity": "sha512-TfPotjhszKLgFBzBhTOxNHDsutIxx9GTWjrL5Wh7Qx/ydxKhwUrlSFeLIn+ZaHPF+h0siVBkAQSuy6CADyTxgQ==", + "dev": true + }, + "detective-typescript": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-11.2.0.tgz", + "integrity": "sha512-ARFxjzizOhPqs1fYC/2NMC3N4jrQ6HvVflnXBTRqNEqJuXwyKLRr9CrJwkRcV/SnZt1sNXgsF6FPm0x57Tq0rw==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "^5.62.0", + "ast-module-types": "^5.0.0", + "node-source-walk": "^6.0.2", + "typescript": "^5.4.4" + } + }, "devtools-protocol": { "version": "0.0.1120988", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", @@ -37382,6 +38499,16 @@ "once": "^1.4.0" } }, + "enhanced-resolve": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", + "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -38503,6 +39630,26 @@ "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", "dev": true }, + "filing-cabinet": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-4.2.0.tgz", + "integrity": "sha512-YZ21ryzRcyqxpyKggdYSoXx//d3sCJzM3lsYoaeg/FyXdADGJrUl+BW1KIglaVLJN5BBcMtWylkygY8zBp2MrQ==", + "dev": true, + "requires": { + "app-module-path": "^2.2.0", + "commander": "^10.0.1", + "enhanced-resolve": "^5.14.1", + "is-relative-path": "^1.0.2", + "module-definition": "^5.0.1", + "module-lookup-amd": "^8.0.5", + "resolve": "^1.22.3", + "resolve-dependency-path": "^3.0.2", + "sass-lookup": "^5.0.1", + "stylus-lookup": "^5.0.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.0.4" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -39088,9 +40235,9 @@ } }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, "functional-red-black-tree": { @@ -39217,6 +40364,16 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, + "get-amd-module-type": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-5.0.1.tgz", + "integrity": "sha512-jb65zDeHyDjFR1loOVk0HQGM5WNwoGB8aLWy3LKCieMKol0/ProHkhO2X1JxojuN10vbz1qNn09MJ7tNp7qMzw==", + "dev": true, + "requires": { + "ast-module-types": "^5.0.0", + "node-source-walk": "^6.0.1" + } + }, "get-assigned-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", @@ -39240,6 +40397,12 @@ "has-symbols": "^1.0.3" } }, + "get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -39575,6 +40738,15 @@ "sparkles": "^1.0.0" } }, + "gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "google-auth-library": { "version": "9.6.3", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.6.3.tgz", @@ -40525,6 +41697,15 @@ "minimalistic-assert": "^1.0.1" } }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -40924,12 +42105,12 @@ "dev": true }, "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "requires": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "is-data-descriptor": { @@ -41028,6 +42209,12 @@ "is-extglob": "^2.1.1" } }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, "is-natural-number": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", @@ -41100,6 +42287,12 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true + }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -41109,6 +42302,12 @@ "is-unc-path": "^1.0.0" } }, + "is-relative-path": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-relative-path/-/is-relative-path-1.0.2.tgz", + "integrity": "sha512-i1h+y50g+0hRbBD+dbnInl3JlJ702aar58snAeX+MxBAPvzXGej7sYoPMhlnykabt0ZzCJNBEyzMlekuQZN7fA==", + "dev": true + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -41152,6 +42351,24 @@ "unc-path-regex": "^0.1.2" } }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, + "is-url-superb": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", + "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true + }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -43575,6 +44792,67 @@ "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", "dev": true }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "log-update": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", @@ -43719,6 +44997,84 @@ "es5-ext": "~0.10.2" } }, + "madge": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/madge/-/madge-7.0.0.tgz", + "integrity": "sha512-x9eHkBWoCJ2B8yGesWf8LRucarkbH5P3lazqgvmxe4xn5U2Meyfu906iG9mBB1RnY/f4D+gtELWdiz1k6+jAZA==", + "dev": true, + "requires": { + "chalk": "^4.1.2", + "commander": "^7.2.0", + "commondir": "^1.0.1", + "debug": "^4.3.4", + "dependency-tree": "^10.0.9", + "ora": "^5.4.1", + "pluralize": "^8.0.0", + "precinct": "^11.0.5", + "pretty-ms": "^7.0.1", + "rc": "^1.2.8", + "stream-to-array": "^2.3.0", + "ts-graphviz": "^1.8.1", + "walkdir": "^0.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -44312,6 +45668,16 @@ "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true }, + "module-definition": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-5.0.1.tgz", + "integrity": "sha512-kvw3B4G19IXk+BOXnYq/D/VeO9qfHaapMeuS7w7sNUqmGaA6hywdFHMi+VWeR9wUScXM7XjoryTffCZ5B0/8IA==", + "dev": true, + "requires": { + "ast-module-types": "^5.0.0", + "node-source-walk": "^6.0.1" + } + }, "module-deps": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", @@ -44371,6 +45737,18 @@ } } }, + "module-lookup-amd": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-8.0.5.tgz", + "integrity": "sha512-vc3rYLjDo5Frjox8NZpiyLXsNWJ5BWshztc/5KSOMzpg9k5cHH652YsJ7VKKmtM4SvaxuE9RkrYGhiSjH3Ehow==", + "dev": true, + "requires": { + "commander": "^10.0.1", + "glob": "^7.2.3", + "requirejs": "^2.3.6", + "requirejs-config-file": "^4.0.0" + } + }, "moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -44527,6 +45905,12 @@ "dev": true, "optional": true }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -44630,6 +46014,15 @@ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "dev": true }, + "node-source-walk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-6.0.2.tgz", + "integrity": "sha512-jn9vOIK/nfqoFCcpK89/VCVaLg1IHE6UVfDOzvqmANaJ/rWCTEdH8RZ1V278nv2jr36BJdyQXIAavBLXpzdlag==", + "dev": true, + "requires": { + "@babel/parser": "^7.21.8" + } + }, "normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -46722,6 +48115,74 @@ "word-wrap": "~1.2.3" } }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -47023,6 +48484,12 @@ "lines-and-columns": "^1.1.6" } }, + "parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true + }, "parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", @@ -47566,6 +49033,36 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + } + }, + "postcss-values-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz", + "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==", + "dev": true, + "requires": { + "color-name": "^1.1.4", + "is-url-superb": "^4.0.0", + "quote-unquote": "^1.0.0" + }, + "dependencies": { + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -47593,6 +49090,26 @@ "xtend": "^4.0.0" } }, + "precinct": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/precinct/-/precinct-11.0.5.tgz", + "integrity": "sha512-oHSWLC8cL/0znFhvln26D14KfCQFFn4KOLSw6hmLhd+LQ2SKt9Ljm89but76Pc7flM9Ty1TnXyrA2u16MfRV3w==", + "dev": true, + "requires": { + "@dependents/detective-less": "^4.1.0", + "commander": "^10.0.1", + "detective-amd": "^5.0.2", + "detective-cjs": "^5.0.1", + "detective-es6": "^4.0.1", + "detective-postcss": "^6.1.3", + "detective-sass": "^5.0.3", + "detective-scss": "^4.0.3", + "detective-stylus": "^4.0.0", + "detective-typescript": "^11.1.0", + "module-definition": "^5.0.1", + "node-source-walk": "^6.0.2" + } + }, "precond": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", @@ -47642,6 +49159,15 @@ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, + "pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "requires": { + "parse-ms": "^2.1.0" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -47978,6 +49504,12 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, + "quote-unquote": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz", + "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", + "dev": true + }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -48541,6 +50073,22 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "dev": true + }, + "requirejs-config-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz", + "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==", + "dev": true, + "requires": { + "esprima": "^4.0.0", + "stringify-object": "^3.2.1" + } + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -48557,12 +50105,12 @@ } }, "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "requires": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -48576,6 +50124,12 @@ "resolve-from": "^5.0.0" } }, + "resolve-dependency-path": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-3.0.2.tgz", + "integrity": "sha512-Tz7zfjhLfsvR39ADOSk9us4421J/1ztVBo4rWUkF38hgHK5m0OCZ3NxFVpqHRkjctnwVa15igEUHFJp8MCS7vA==", + "dev": true + }, "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -48745,6 +50299,15 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "sass-lookup": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-5.0.1.tgz", + "integrity": "sha512-t0X5PaizPc2H4+rCwszAqHZRtr4bugo4pgiCvrBFvIX0XFxnr29g77LJcpyj9A0DcKf7gXMLcgvRjsonYI6x4g==", + "dev": true, + "requires": { + "commander": "^10.0.1" + } + }, "saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -49287,6 +50850,12 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true + }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -49619,6 +51188,15 @@ } } }, + "stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + }, "streamqueue": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/streamqueue/-/streamqueue-0.0.6.tgz", @@ -49721,6 +51299,25 @@ "strip-ansi": "^6.0.1" } }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "dependencies": { + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true + } + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -49809,6 +51406,15 @@ "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "dev": true }, + "stylus-lookup": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-5.0.1.tgz", + "integrity": "sha512-tLtJEd5AGvnVy4f9UHQMw4bkJJtaAcmo54N+ovQBjDY3DuWyK9Eltxzr5+KG0q4ew6v2EHyuWWNnHeiw/Eo7rQ==", + "dev": true, + "requires": { + "commander": "^10.0.1" + } + }, "subarg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", @@ -49919,6 +51525,12 @@ "integrity": "sha512-GQ3gtYFSOAxSMN/apGtDKKkbJf+8izz5YfbGqIsUc7AMiQOapARZ76dhilRY2h39cynYxBFdafQo5HUL5vgkrg==", "dev": true }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, "tar": { "version": "6.1.15", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", @@ -50329,6 +51941,23 @@ "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", "dev": true }, + "ts-graphviz": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-1.8.2.tgz", + "integrity": "sha512-5YhbFoHmjxa7pgQLkB07MtGnGJ/yhvjmc9uhsnDBEICME6gkPf83SBwLDQqGDoCa3XzUMWLk1AU2Wn1u1naDtA==", + "dev": true + }, + "tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "requires": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", @@ -50476,11 +52105,10 @@ "dev": true }, "typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true, - "peer": true + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true }, "uc.micro": { "version": "1.0.6", @@ -51131,6 +52759,12 @@ "xml-name-validator": "^4.0.0" } }, + "walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "dev": true + }, "walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -51140,6 +52774,15 @@ "makeerror": "1.0.12" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 1e38cbb32..72ec42a4b 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "jsdoc": "4.0.2", "jsdoc-babel": "0.5.0", "lint-staged": "13.2.2", + "madge": "7.0.0", "metro-react-native-babel-preset": "0.76.4", "mongodb-runner": "5.4.3", "parse-server": "7.1.0-alpha.1", @@ -112,6 +113,7 @@ "watch:react-native": "cross-env PARSE_BUILD=react-native npm run watch", "integration": "cross-env TESTING=1 jasmine --config=jasmine.json", "docs": "jsdoc -c ./jsdoc-conf.json ./src", + "madge:circular": "madge ./src --extensions js,ts --circular", "prepare": "npm run build", "release_docs": "./release_docs.sh", "gulp": "gulp", diff --git a/src/CoreManager.ts b/src/CoreManager.ts index 49ed62e91..4d610e5fb 100644 --- a/src/CoreManager.ts +++ b/src/CoreManager.ts @@ -15,6 +15,9 @@ import type ParseConfig from './ParseConfig'; import type LiveQueryClient from './LiveQueryClient'; import type ParseSchema from './ParseSchema'; import type ParseInstallation from './ParseInstallation'; +import type ParseQuery from './ParseQuery'; +import type * as ParseOp from './ParseOp'; +import type ParseRole from './ParseRole'; type AnalyticsController = { track: (name: string, dimensions: { [key: string]: string }) => Promise, @@ -594,6 +597,46 @@ const CoreManager = { getHooksController(): HooksController { return config['HooksController']!; }, + + setParseOp(op: ParseOp) { + config['ParseOp'] = op; + }, + + getParseOp() { + return config['ParseOp']!; + }, + + setParseObject(object: ParseObject) { + config['ParseObject'] = object; + }, + + getParseObject(): ParseObject { + return config['ParseObject']!; + }, + + setParseQuery(query: ParseQuery) { + config['ParseQuery'] = query; + }, + + getParseQuery() { + return config['ParseQuery']! + }, + + setParseRole(role: ParseRole) { + config['ParseRole'] = role; + }, + + getParseRole(): ParseRole { + return config['ParseRole']!; + }, + + setParseUser(user: ParseUser) { + config['ParseUser'] = user; + }, + + getParseUser(): ParseUser { + return config['ParseUser']!; + }, }; module.exports = CoreManager; diff --git a/src/ObjectStateMutations.js b/src/ObjectStateMutations.js index dfcbcff87..6fc32e911 100644 --- a/src/ObjectStateMutations.js +++ b/src/ObjectStateMutations.js @@ -3,8 +3,8 @@ */ import encode from './encode'; +import CoreManager from './CoreManager'; import ParseFile from './ParseFile'; -import ParseObject from './ParseObject'; import ParseRelation from './ParseRelation'; import TaskQueue from './TaskQueue'; import { RelationOp } from './ParseOp'; @@ -161,6 +161,7 @@ export function commitServerChanges( objectCache: ObjectCache, changes: AttributeMap ) { + const ParseObject = CoreManager.getParseObject(); for (const attr in changes) { const val = changes[attr]; nestedSet(serverData, attr, val); diff --git a/src/ParseACL.js b/src/ParseACL.js index beedb6719..42f9ffb25 100644 --- a/src/ParseACL.js +++ b/src/ParseACL.js @@ -2,8 +2,9 @@ * @flow */ -import ParseRole from './ParseRole'; -import ParseUser from './ParseUser'; +import CoreManager from './CoreManager'; +import type ParseRole from './ParseRole'; +import type ParseUser from './ParseUser'; type PermissionsMap = { [permission: string]: boolean }; type ByIdMap = { [userId: string]: PermissionsMap }; @@ -33,6 +34,7 @@ class ParseACL { constructor(arg1: ParseUser | ByIdMap) { this.permissionsById = {}; if (arg1 && typeof arg1 === 'object') { + const ParseUser = CoreManager.getParseUser(); if (arg1 instanceof ParseUser) { this.setReadAccess(arg1, true); this.setWriteAccess(arg1, true); @@ -100,6 +102,8 @@ class ParseACL { } _setAccess(accessType: string, userId: ParseUser | ParseRole | string, allowed: boolean) { + const ParseRole = CoreManager.getParseRole(); + const ParseUser = CoreManager.getParseUser(); if (userId instanceof ParseUser) { userId = userId.id; } else if (userId instanceof ParseRole) { @@ -137,6 +141,8 @@ class ParseACL { } _getAccess(accessType: string, userId: ParseUser | ParseRole | string): boolean { + const ParseRole = CoreManager.getParseRole(); + const ParseUser = CoreManager.getParseUser(); if (userId instanceof ParseUser) { userId = userId.id; if (!userId) { @@ -248,6 +254,7 @@ class ParseACL { * @throws {TypeError} If role is neither a Parse.Role nor a String. */ getRoleReadAccess(role: ParseRole | string): boolean { + const ParseRole = CoreManager.getParseRole(); if (role instanceof ParseRole) { // Normalize to the String name role = role.getName(); @@ -268,6 +275,7 @@ class ParseACL { * @throws {TypeError} If role is neither a Parse.Role nor a String. */ getRoleWriteAccess(role: ParseRole | string): boolean { + const ParseRole = CoreManager.getParseRole(); if (role instanceof ParseRole) { // Normalize to the String name role = role.getName(); @@ -287,6 +295,7 @@ class ParseACL { * @throws {TypeError} If role is neither a Parse.Role nor a String. */ setRoleReadAccess(role: ParseRole | string, allowed: boolean) { + const ParseRole = CoreManager.getParseRole(); if (role instanceof ParseRole) { // Normalize to the String name role = role.getName(); @@ -306,6 +315,7 @@ class ParseACL { * @throws {TypeError} If role is neither a Parse.Role nor a String. */ setRoleWriteAccess(role: ParseRole | string, allowed: boolean) { + const ParseRole = CoreManager.getParseRole(); if (role instanceof ParseRole) { // Normalize to the String name role = role.getName(); diff --git a/src/ParseObject.js b/src/ParseObject.js index b011ce151..82b96b569 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -25,7 +25,6 @@ import { RemoveOp, RelationOp, } from './ParseOp'; -import ParseQuery from './ParseQuery'; import ParseRelation from './ParseRelation'; import * as SingleInstanceStateController from './SingleInstanceStateController'; import unique from './unique'; @@ -1007,6 +1006,7 @@ class ParseObject { return false; } try { + const ParseQuery = CoreManager.getParseQuery(); const query = new ParseQuery(this.className); await query.get(this.id, options); return true; @@ -2228,6 +2228,7 @@ const DefaultController = { if (error) { return Promise.reject(error); } + const ParseQuery = CoreManager.getParseQuery(); const query = new ParseQuery(className); query.containedIn('objectId', ids); if (options && options.include) { @@ -2549,6 +2550,7 @@ const DefaultController = { }, }; +CoreManager.setParseObject(ParseObject); CoreManager.setObjectController(DefaultController); export default ParseObject; diff --git a/src/ParseOp.js b/src/ParseOp.js index ed00441da..2a0d4f494 100644 --- a/src/ParseOp.js +++ b/src/ParseOp.js @@ -5,7 +5,8 @@ import arrayContainsObject from './arrayContainsObject'; import decode from './decode'; import encode from './encode'; -import ParseObject from './ParseObject'; +import CoreManager from './CoreManager'; +import type ParseObject from './ParseObject'; import ParseRelation from './ParseRelation'; import unique from './unique'; @@ -190,6 +191,7 @@ export class AddUniqueOp extends Op { return this._value || []; } if (Array.isArray(value)) { + const ParseObject = CoreManager.getParseObject(); const toAdd = []; this._value.forEach(v => { if (v instanceof ParseObject) { @@ -241,6 +243,7 @@ export class RemoveOp extends Op { return []; } if (Array.isArray(value)) { + const ParseObject = CoreManager.getParseObject(); // var i = value.indexOf(this._value); const removed = value.concat([]); for (let i = 0; i < this._value.length; i++) { @@ -274,6 +277,7 @@ export class RemoveOp extends Op { return new UnsetOp(); } if (previous instanceof RemoveOp) { + const ParseObject = CoreManager.getParseObject(); const uniques = previous._value.concat([]); for (let i = 0; i < this._value.length; i++) { if (this._value[i] instanceof ParseObject) { @@ -450,3 +454,14 @@ export class RelationOp extends Op { return adds || removes || {}; } } +CoreManager.setParseOp({ + Op, + opFromJSON, + SetOp, + UnsetOp, + IncrementOp, + AddOp, + RelationOp, + RemoveOp, + AddUniqueOp +}); diff --git a/src/ParseQuery.js b/src/ParseQuery.js index 07aa71fe5..2a26122a7 100644 --- a/src/ParseQuery.js +++ b/src/ParseQuery.js @@ -2156,6 +2156,7 @@ const DefaultController = { }, }; +CoreManager.setParseQuery(ParseQuery); CoreManager.setQueryController(DefaultController); export default ParseQuery; diff --git a/src/ParseRelation.js b/src/ParseRelation.js index 50c8c09f1..fef257f63 100644 --- a/src/ParseRelation.js +++ b/src/ParseRelation.js @@ -2,9 +2,9 @@ * @flow */ -import { RelationOp } from './ParseOp'; -import ParseObject from './ParseObject'; -import ParseQuery from './ParseQuery'; +import type ParseObject from './ParseObject'; +import type ParseQuery from './ParseQuery'; +import CoreManager from './CoreManager'; /** * Creates a new Relation for the given parent object and key. This @@ -68,7 +68,7 @@ class ParseRelation { if (!Array.isArray(objects)) { objects = [objects]; } - + const { RelationOp } = CoreManager.getParseOp(); const change = new RelationOp(objects, []); const parent = this.parent; if (!parent) { @@ -91,7 +91,7 @@ class ParseRelation { if (!Array.isArray(objects)) { objects = [objects]; } - + const { RelationOp } = CoreManager.getParseOp(); const change = new RelationOp([], objects); if (!this.parent) { throw new Error('Cannot remove from a Relation without a parent'); @@ -127,6 +127,7 @@ class ParseRelation { if (!parent) { throw new Error('Cannot construct a query for a Relation without a parent'); } + const ParseQuery = CoreManager.getParseQuery(); if (!this.targetClassName) { query = new ParseQuery(parent.className); query._extraOptions.redirectClassNameForKey = this.key; diff --git a/src/ParseRole.js b/src/ParseRole.js index fee282395..546a28577 100644 --- a/src/ParseRole.js +++ b/src/ParseRole.js @@ -2,6 +2,7 @@ * @flow */ +import CoreManager from './CoreManager'; import ParseACL from './ParseACL'; import ParseError from './ParseError'; import ParseObject from './ParseObject'; @@ -141,6 +142,7 @@ class ParseRole extends ParseObject { } } +CoreManager.setParseRole(ParseRole); ParseObject.registerSubclass('_Role', ParseRole); export default ParseRole; diff --git a/src/ParseUser.js b/src/ParseUser.js index 26dec8820..328512452 100644 --- a/src/ParseUser.js +++ b/src/ParseUser.js @@ -1278,6 +1278,7 @@ const DefaultController = { }, }; +CoreManager.setParseUser(ParseUser); CoreManager.setUserController(DefaultController); export default ParseUser; diff --git a/src/SingleInstanceStateController.js b/src/SingleInstanceStateController.js index 6cc9d2069..01065bcde 100644 --- a/src/SingleInstanceStateController.js +++ b/src/SingleInstanceStateController.js @@ -6,7 +6,6 @@ import * as ObjectStateMutations from './ObjectStateMutations'; import type { Op } from './ParseOp'; import type { AttributeMap, ObjectCache, OpsMap, State } from './ObjectStateMutations'; -import ParseObject from './ParseObject'; type ObjectIdentifier = { className: string, @@ -100,13 +99,13 @@ export function getObjectCache(obj: ObjectIdentifier): ObjectCache { return {}; } -export function estimateAttribute(obj: ParseObject, attr: string): mixed { +export function estimateAttribute(obj: ObjectIdentifier, attr: string): mixed { const serverData = getServerData(obj); const pendingOps = getPendingOps(obj); return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj, attr); } -export function estimateAttributes(obj: ParseObject): AttributeMap { +export function estimateAttributes(obj: ObjectIdentifier): AttributeMap { const serverData = getServerData(obj); const pendingOps = getPendingOps(obj); return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj); diff --git a/src/__tests__/Cloud-test.js b/src/__tests__/Cloud-test.js index 143f1da9a..49e9a01e5 100644 --- a/src/__tests__/Cloud-test.js +++ b/src/__tests__/Cloud-test.js @@ -6,10 +6,12 @@ jest.dontMock('../ParseError'); jest.dontMock('../ParseObject'); jest.dontMock('../ParseQuery'); jest.dontMock('../Push'); +jest.dontMock('../ParseOp'); const Cloud = require('../Cloud'); const CoreManager = require('../CoreManager'); const Push = require('../Push'); +require('../ParseOp'); const defaultController = CoreManager.getCloudController(); diff --git a/src/__tests__/Hooks-test.js b/src/__tests__/Hooks-test.js index 10cd9acde..365070fdd 100644 --- a/src/__tests__/Hooks-test.js +++ b/src/__tests__/Hooks-test.js @@ -3,6 +3,7 @@ jest.dontMock('../CoreManager'); jest.dontMock('../decode'); jest.dontMock('../encode'); jest.dontMock('../ParseError'); +jest.dontMock('../ParseObject'); const Hooks = require('../ParseHooks'); const CoreManager = require('../CoreManager'); diff --git a/src/__tests__/LocalDatastore-test.js b/src/__tests__/LocalDatastore-test.js index 898f7985a..0cc944062 100644 --- a/src/__tests__/LocalDatastore-test.js +++ b/src/__tests__/LocalDatastore-test.js @@ -2,7 +2,8 @@ jest.autoMockOff(); jest.unmock('../LocalDatastoreUtils'); - +jest.dontMock('../ParseOp'); +require('../ParseOp'); const encode = require('../encode').default; let objectCount = 0; @@ -65,6 +66,8 @@ class MockObject { set(key, value) { this.attributes[key] = value; } + + static registerSubclass() {} } const mockAsyncStorage = require('./test_helpers/mockRNStorage'); @@ -96,6 +99,9 @@ const LocalDatastoreController = require('../LocalDatastoreController'); const RNDatastoreController = require('../LocalDatastoreController.react-native'); const BrowserStorageController = require('../StorageController.browser'); const DefaultStorageController = require('../StorageController.default'); +// Register our mocks +jest.spyOn(CoreManager, 'getParseObject').mockImplementation(() => require('../ParseObject')); +jest.spyOn(CoreManager, 'getParseQuery').mockImplementation(() => require('../ParseQuery')); const item1 = new ParseObject('Item'); const item2 = new ParseObject('Item'); diff --git a/src/__tests__/ObjectStateMutations-test.js b/src/__tests__/ObjectStateMutations-test.js index e81fccebf..34f7885a1 100644 --- a/src/__tests__/ObjectStateMutations-test.js +++ b/src/__tests__/ObjectStateMutations-test.js @@ -1,5 +1,6 @@ jest.dontMock('../decode'); jest.dontMock('../encode'); +jest.dontMock('../CoreManager'); jest.dontMock('../ObjectStateMutations'); jest.dontMock('../ParseFile'); jest.dontMock('../ParseGeoPoint'); @@ -12,6 +13,8 @@ const mockObject = function (className) { }; mockObject.registerSubclass = function () {}; jest.setMock('../ParseObject', mockObject); +const CoreManager = require('../CoreManager'); +CoreManager.setParseObject(mockObject); const ObjectStateMutations = require('../ObjectStateMutations'); const ParseOps = require('../ParseOp'); diff --git a/src/__tests__/Parse-test.js b/src/__tests__/Parse-test.js index 119484592..83ebcfd7e 100644 --- a/src/__tests__/Parse-test.js +++ b/src/__tests__/Parse-test.js @@ -4,6 +4,7 @@ jest.dontMock('../decode'); jest.dontMock('../encode'); jest.dontMock('../Parse'); jest.dontMock('../ParseObject'); +jest.dontMock('../ParseOp'); jest.dontMock('../ParseLiveQuery'); jest.dontMock('../LocalDatastore'); jest.dontMock('crypto-js/aes'); diff --git a/src/__tests__/ParseACL-test.js b/src/__tests__/ParseACL-test.js index c5cc6cc71..4b4ee0dc4 100644 --- a/src/__tests__/ParseACL-test.js +++ b/src/__tests__/ParseACL-test.js @@ -1,4 +1,6 @@ jest.dontMock('../ParseACL'); +jest.dontMock('../ParseUser'); +jest.dontMock('../CoreManager'); const mockRole = function (name) { this.name = name; @@ -11,6 +13,8 @@ jest.setMock('../ParseRole', mockRole); const ParseACL = require('../ParseACL').default; const ParseUser = require('../ParseUser').default; const ParseRole = require('../ParseRole'); +const CoreManager = require('../CoreManager'); +CoreManager.setParseRole(require('../ParseRole')); describe('ParseACL', () => { it('can be constructed with no arguments', () => { diff --git a/src/__tests__/ParseConfig-test.js b/src/__tests__/ParseConfig-test.js index e662bd490..135ee91a4 100644 --- a/src/__tests__/ParseConfig-test.js +++ b/src/__tests__/ParseConfig-test.js @@ -6,11 +6,15 @@ jest.dontMock('../ParseConfig'); jest.dontMock('../ParseError'); jest.dontMock('../ParseFile'); jest.dontMock('../ParseGeoPoint'); +jest.dontMock('../ParseObject'); +jest.dontMock('../ParseOp'); jest.dontMock('../RESTController'); jest.dontMock('../Storage'); jest.dontMock('../StorageController.default'); jest.dontMock('./test_helpers/mockAsyncStorage'); - +// Load the classes into CoreManager +require('../ParseObject'); +require('../ParseOp'); const mockAsyncStorage = require('./test_helpers/mockAsyncStorage'); const CoreManager = require('../CoreManager'); const ParseConfig = require('../ParseConfig').default; diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index a9474fef5..e608346a3 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -172,6 +172,10 @@ CoreManager.set('APPLICATION_ID', 'A'); CoreManager.set('JAVASCRIPT_KEY', 'B'); CoreManager.set('MASTER_KEY', 'C'); CoreManager.set('VERSION', 'V'); +// Register our mocks +jest.spyOn(CoreManager, 'getParseQuery').mockImplementation(() => mockQuery); +jest.spyOn(CoreManager, 'getEventuallyQueue').mockImplementation(() => EventuallyQueue); +jest.spyOn(CoreManager, 'getParseUser').mockImplementation(() => require('../ParseUser').default); const { SetOp, UnsetOp, IncrementOp } = require('../ParseOp'); diff --git a/src/__tests__/ParseOp-test.js b/src/__tests__/ParseOp-test.js index af2ea7710..6a7ff78ff 100644 --- a/src/__tests__/ParseOp-test.js +++ b/src/__tests__/ParseOp-test.js @@ -1,6 +1,7 @@ jest.dontMock('../arrayContainsObject'); jest.dontMock('../encode'); jest.dontMock('../decode'); +jest.dontMock('../CoreManager'); jest.dontMock('../ParseOp'); jest.dontMock('../unique'); @@ -30,6 +31,10 @@ jest.setMock('../ParseRelation', mockRelation); const ParseRelation = require('../ParseRelation'); const ParseObject = require('../ParseObject'); const ParseOp = require('../ParseOp'); +const CoreManager = require('../CoreManager'); +jest.spyOn(CoreManager, 'getParseObject').mockImplementation(() => require('../ParseObject')); +jest.spyOn(CoreManager, 'getEventuallyQueue').mockImplementation(() => require('../EventuallyQueue')); + const { Op, SetOp, UnsetOp, IncrementOp, AddOp, AddUniqueOp, RemoveOp, RelationOp, opFromJSON } = ParseOp; diff --git a/src/__tests__/ParseRelation-test.js b/src/__tests__/ParseRelation-test.js index 47fbd8aaa..92a15e03b 100644 --- a/src/__tests__/ParseRelation-test.js +++ b/src/__tests__/ParseRelation-test.js @@ -1,4 +1,5 @@ jest.dontMock('../encode'); +jest.dontMock('../CoreManager'); jest.dontMock('../ParseRelation'); jest.dontMock('../ParseOp'); jest.dontMock('../unique'); @@ -56,6 +57,10 @@ jest.setMock('../ParseQuery', mockQuery); const ParseObject = require('../ParseObject'); const ParseRelation = require('../ParseRelation').default; +const CoreManager = require('../CoreManager'); +CoreManager.setParseObject(mockObject); +CoreManager.setParseQuery(mockQuery); +CoreManager.setParseOp(require('../ParseOp')); describe('ParseRelation', () => { it('can be constructed with a reference parent and key', () => { diff --git a/src/__tests__/SingleInstanceStateController-test.js b/src/__tests__/SingleInstanceStateController-test.js index 5feea909f..171f3b2ac 100644 --- a/src/__tests__/SingleInstanceStateController-test.js +++ b/src/__tests__/SingleInstanceStateController-test.js @@ -1,5 +1,6 @@ jest.dontMock('../decode'); jest.dontMock('../encode'); +jest.dontMock('../CoreManager'); jest.dontMock('../ObjectStateMutations'); jest.dontMock('../ParseFile'); jest.dontMock('../ParseGeoPoint'); @@ -20,6 +21,8 @@ const ParseOps = require('../ParseOp'); const SingleInstanceStateController = require('../SingleInstanceStateController'); const TaskQueue = require('../TaskQueue'); const flushPromises = require('./test_helpers/flushPromises'); +const CoreManager = require('../CoreManager'); +CoreManager.setParseObject(mockObject); describe('SingleInstanceStateController', () => { it('returns null state for an unknown object', () => { diff --git a/src/__tests__/Storage-test.js b/src/__tests__/Storage-test.js index 0b89bb9e5..843d2c57a 100644 --- a/src/__tests__/Storage-test.js +++ b/src/__tests__/Storage-test.js @@ -1,13 +1,11 @@ jest.autoMockOff(); const mockRNStorageInterface = require('./test_helpers/mockRNStorage'); -const mockStorageInterface = require('./test_helpers/mockStorageInteface'); const mockIndexedDB = require('./test_helpers/mockIndexedDB'); const mockWeChat = require('./test_helpers/mockWeChat'); const CoreManager = require('../CoreManager'); global.wx = mockWeChat; -global.localStorage = mockStorageInterface; global.indexedDB = mockIndexedDB; jest.mock('idb-keyval', () => { return mockIndexedDB; diff --git a/src/__tests__/UniqueInstanceStateController-test.js b/src/__tests__/UniqueInstanceStateController-test.js index 7a84a2f6f..1c6c53f38 100644 --- a/src/__tests__/UniqueInstanceStateController-test.js +++ b/src/__tests__/UniqueInstanceStateController-test.js @@ -7,6 +7,8 @@ jest.dontMock('../ParseOp'); jest.dontMock('../UniqueInstanceStateController'); jest.dontMock('../TaskQueue'); jest.dontMock('../promiseUtils'); +jest.dontMock('../CoreManager'); + jest.useFakeTimers(); const mockObject = function (className) { @@ -22,6 +24,8 @@ const ParseOps = require('../ParseOp'); const UniqueInstanceStateController = require('../UniqueInstanceStateController'); const TaskQueue = require('../TaskQueue'); const { resolvingPromise } = require('../promiseUtils'); +const CoreManager = require('../CoreManager'); +CoreManager.setParseObject(mockObject); describe('UniqueInstanceStateController', () => { it('returns null state for an unknown object', () => { diff --git a/src/__tests__/arrayContainsObject-test.js b/src/__tests__/arrayContainsObject-test.js index d36503283..3381d7387 100644 --- a/src/__tests__/arrayContainsObject-test.js +++ b/src/__tests__/arrayContainsObject-test.js @@ -15,6 +15,8 @@ jest.setMock('../ParseObject', mockObject); const arrayContainsObject = require('../arrayContainsObject').default; const ParseObject = require('../ParseObject'); +const CoreManager = require('../CoreManager'); +jest.spyOn(CoreManager, 'getParseObject').mockImplementation(() => require('../ParseObject')); describe('arrayContainsObject', () => { it('detects objects by their id', () => { diff --git a/src/__tests__/canBeSerialized-test.js b/src/__tests__/canBeSerialized-test.js index 24cedf7e3..c65fdddee 100644 --- a/src/__tests__/canBeSerialized-test.js +++ b/src/__tests__/canBeSerialized-test.js @@ -1,4 +1,5 @@ jest.dontMock('../canBeSerialized'); +jest.dontMock('../CoreManager'); function mockObject(id, attributes) { this.id = id; @@ -6,6 +7,8 @@ function mockObject(id, attributes) { } mockObject.registerSubclass = function () {}; jest.setMock('../ParseObject', mockObject); +const CoreManager = require('../CoreManager'); +CoreManager.setParseObject(mockObject); function mockFile(url) { this._url = url; diff --git a/src/__tests__/decode-test.js b/src/__tests__/decode-test.js index 854996e5f..8cc886e9d 100644 --- a/src/__tests__/decode-test.js +++ b/src/__tests__/decode-test.js @@ -1,6 +1,8 @@ jest.dontMock('../decode'); +jest.dontMock('../CoreManager'); jest.dontMock('../ParseFile'); jest.dontMock('../ParseGeoPoint'); +jest.dontMock('../ParseObject'); jest.dontMock('../ParsePolygon'); const decode = require('../decode').default; @@ -76,23 +78,25 @@ describe('decode', () => { }); it('decodes Pointers', () => { + const spy = jest.spyOn(ParseObject, 'fromJSON'); const data = { __type: 'Pointer', className: 'Item', objectId: '1001', }; decode(data); - expect(ParseObject.fromJSON.mock.calls[0][0]).toEqual(data); + expect(spy.mock.calls[0][0]).toEqual(data); }); it('decodes ParseObjects', () => { + const spy = jest.spyOn(ParseObject, 'fromJSON'); const data = { __type: 'Object', className: 'Item', objectId: '1001', }; decode(data); - expect(ParseObject.fromJSON.mock.calls[1][0]).toEqual(data); + expect(spy.mock.calls[1][0]).toEqual(data); }); it('iterates over arrays', () => { diff --git a/src/__tests__/encode-test.js b/src/__tests__/encode-test.js index ff9244c59..6cf1d142b 100644 --- a/src/__tests__/encode-test.js +++ b/src/__tests__/encode-test.js @@ -2,6 +2,9 @@ jest.dontMock('../encode'); jest.dontMock('../ParseACL'); jest.dontMock('../ParseFile'); jest.dontMock('../ParseGeoPoint'); +jest.dontMock('../ParseOp'); +jest.dontMock('../ParseUser'); +jest.dontMock('../CoreManager'); const mockObject = function (className) { this.className = className; @@ -43,6 +46,10 @@ const ParseFile = require('../ParseFile').default; const ParseGeoPoint = require('../ParseGeoPoint').default; const ParseObject = require('../ParseObject'); const ParseRelation = require('../ParseRelation').default; +const CoreManager = require('../CoreManager'); +CoreManager.setParseObject(mockObject); +CoreManager.setParseOp(require('../ParseOp')); +CoreManager.setParseUser(require('../ParseUser').default); describe('encode', () => { it('ignores primitives', () => { diff --git a/src/__tests__/equals-test.js b/src/__tests__/equals-test.js index bec122f05..690bbaf39 100644 --- a/src/__tests__/equals-test.js +++ b/src/__tests__/equals-test.js @@ -6,6 +6,10 @@ const ParseFile = require('../ParseFile').default; const ParseGeoPoint = require('../ParseGeoPoint').default; const ParseObject = require('../ParseObject').default; +// Load into CoreManager +require('../ParseRole'); +require('../ParseUser'); + describe('equals', () => { it('tests equality of primitives', () => { expect(equals(1, 'string')).toBe(false); diff --git a/src/__tests__/test_helpers/mockStorageInteface.js b/src/__tests__/test_helpers/mockStorageInteface.js deleted file mode 100644 index e801445ab..000000000 --- a/src/__tests__/test_helpers/mockStorageInteface.js +++ /dev/null @@ -1,31 +0,0 @@ -let mockStorage = {}; -const mockStorageInterface = { - getItem(path) { - return mockStorage[path] || null; - }, - - getItemAsync(path) { - return Promise.resolve(mockStorageInterface.getItem(path)); - }, - - setItem(path, value) { - mockStorage[path] = value; - }, - - setItemAsync(path, value) { - return Promise.resolve(mockStorageInterface.setItem(path, value)); - }, - - removeItem(path) { - delete mockStorage[path]; - }, - - removeItemAsync(path) { - return Promise.resolve(mockStorageInterface.removeItem(path)); - }, - - clear() { - mockStorage = {}; - }, -}; -module.exports = mockStorageInterface; diff --git a/src/__tests__/unique-test.js b/src/__tests__/unique-test.js index edae1a8f1..2d3855230 100644 --- a/src/__tests__/unique-test.js +++ b/src/__tests__/unique-test.js @@ -16,6 +16,8 @@ jest.setMock('../ParseObject', mockObject); const unique = require('../unique').default; const ParseObject = require('../ParseObject'); +const CoreManager = require('../CoreManager'); +jest.spyOn(CoreManager, 'getParseObject').mockImplementation(() => require('../ParseObject')); describe('unique', () => { it('produces an array with unique elements', () => { diff --git a/src/__tests__/unsavedChildren-test.js b/src/__tests__/unsavedChildren-test.js index df5771f9b..f85a0c7eb 100644 --- a/src/__tests__/unsavedChildren-test.js +++ b/src/__tests__/unsavedChildren-test.js @@ -1,5 +1,6 @@ jest.dontMock('../ParseFile'); jest.dontMock('../unsavedChildren'); +jest.dontMock('../CoreManager'); function mockObject({ className, localId, id, attributes, dirty }) { this.className = className; @@ -18,6 +19,8 @@ mockObject.prototype = { }, }; jest.setMock('../ParseObject', mockObject); +const CoreManager = require('../CoreManager'); +CoreManager.setParseObject(mockObject); const ParseFile = require('../ParseFile').default; const ParseObject = require('../ParseObject'); diff --git a/src/arrayContainsObject.js b/src/arrayContainsObject.js index ca9356849..188d21cfa 100644 --- a/src/arrayContainsObject.js +++ b/src/arrayContainsObject.js @@ -2,12 +2,14 @@ * @flow */ -import ParseObject from './ParseObject'; +import CoreManager from './CoreManager'; +import type ParseObject from './ParseObject'; export default function arrayContainsObject(array: Array, object: ParseObject): boolean { if (array.indexOf(object) > -1) { return true; } + const ParseObject = CoreManager.getParseObject(); for (let i = 0; i < array.length; i++) { if ( array[i] instanceof ParseObject && diff --git a/src/canBeSerialized.js b/src/canBeSerialized.js index 975b1672d..3923a3545 100644 --- a/src/canBeSerialized.js +++ b/src/canBeSerialized.js @@ -2,11 +2,13 @@ * @flow */ +import CoreManager from './CoreManager'; import ParseFile from './ParseFile'; -import ParseObject from './ParseObject'; +import type ParseObject from './ParseObject'; import ParseRelation from './ParseRelation'; export default function canBeSerialized(obj: ParseObject): boolean { + const ParseObject = CoreManager.getParseObject(); if (!(obj instanceof ParseObject)) { return true; } @@ -27,6 +29,7 @@ function canBeSerializedHelper(value: any): boolean { if (value instanceof ParseRelation) { return true; } + const ParseObject = CoreManager.getParseObject(); if (value instanceof ParseObject) { return !!value.id; } diff --git a/src/decode.js b/src/decode.js index 557002015..c358437a0 100644 --- a/src/decode.js +++ b/src/decode.js @@ -1,12 +1,11 @@ /** * @flow */ +import CoreManager from './CoreManager'; import ParseACL from './ParseACL'; // eslint-disable-line no-unused-vars import ParseFile from './ParseFile'; import ParseGeoPoint from './ParseGeoPoint'; import ParsePolygon from './ParsePolygon'; -import ParseObject from './ParseObject'; -import { opFromJSON } from './ParseOp'; import ParseRelation from './ParseRelation'; export default function decode(value: any): any { @@ -21,8 +20,10 @@ export default function decode(value: any): any { return dup; } if (typeof value.__op === 'string') { + const { opFromJSON } = CoreManager.getParseOp(); return opFromJSON(value); } + const ParseObject = CoreManager.getParseObject(); if (value.__type === 'Pointer' && value.className) { return ParseObject.fromJSON(value); } diff --git a/src/encode.js b/src/encode.js index 0214990f5..3bcba74f3 100644 --- a/src/encode.js +++ b/src/encode.js @@ -2,12 +2,11 @@ * @flow */ +import CoreManager from './CoreManager'; import ParseACL from './ParseACL'; import ParseFile from './ParseFile'; import ParseGeoPoint from './ParseGeoPoint'; import ParsePolygon from './ParsePolygon'; -import ParseObject from './ParseObject'; -import { Op } from './ParseOp'; import ParseRelation from './ParseRelation'; function encode( @@ -17,6 +16,7 @@ function encode( seen: Array, offline: boolean ): any { + const ParseObject = CoreManager.getParseObject(); if (value instanceof ParseObject) { if (disallowObjects) { throw new Error('Parse Objects not allowed here'); @@ -37,6 +37,7 @@ function encode( seen = seen.concat(seenEntry); return value._toFullJSON(seen, offline); } + const { Op } = CoreManager.getParseOp(); if ( value instanceof Op || value instanceof ParseACL || diff --git a/src/equals.js b/src/equals.js index 3af927c99..bbc731f04 100644 --- a/src/equals.js +++ b/src/equals.js @@ -1,7 +1,7 @@ +import CoreManager from './CoreManager'; import ParseACL from './ParseACL'; import ParseFile from './ParseFile'; import ParseGeoPoint from './ParseGeoPoint'; -import ParseObject from './ParseObject'; export default function equals(a, b) { const toString = Object.prototype.toString; @@ -34,7 +34,7 @@ export default function equals(a, b) { } return true; } - + const ParseObject = CoreManager.getParseObject(); if ( a instanceof ParseACL || a instanceof ParseFile || diff --git a/src/unique.js b/src/unique.js index 169c288f5..15bf6248b 100644 --- a/src/unique.js +++ b/src/unique.js @@ -3,11 +3,12 @@ */ import arrayContainsObject from './arrayContainsObject'; -import ParseObject from './ParseObject'; +import CoreManager from './CoreManager'; export default function unique(arr: Array): Array { const uniques = []; arr.forEach(value => { + const ParseObject = CoreManager.getParseObject(); if (value instanceof ParseObject) { if (!arrayContainsObject(uniques, value)) { uniques.push(value); diff --git a/src/unsavedChildren.js b/src/unsavedChildren.js index 088bd6084..3386e91c8 100644 --- a/src/unsavedChildren.js +++ b/src/unsavedChildren.js @@ -2,8 +2,9 @@ * @flow */ +import CoreManager from './CoreManager'; import ParseFile from './ParseFile'; -import ParseObject from './ParseObject'; +import type ParseObject from './ParseObject'; import ParseRelation from './ParseRelation'; type EncounterMap = { @@ -50,6 +51,7 @@ function traverse( shouldThrow: boolean, allowDeepUnsaved: boolean ) { + const ParseObject = CoreManager.getParseObject(); if (obj instanceof ParseObject) { if (!obj.id && shouldThrow) { throw new Error('Cannot create a pointer to an unsaved Object.'); diff --git a/src/uuid.js b/src/uuid.ts similarity index 64% rename from src/uuid.js rename to src/uuid.ts index 450d4976c..03be8a07e 100644 --- a/src/uuid.js +++ b/src/uuid.ts @@ -1,8 +1,10 @@ -let uuid = null; +import { v4 } from 'uuid'; + +let uuid: () => string = null as any; if (process.env.PARSE_BUILD === 'weapp') { uuid = function () { - const s = []; + const s: string[] = []; const hexDigits = '0123456789abcdef'; for (let i = 0; i < 36; i++) { @@ -10,14 +12,13 @@ if (process.env.PARSE_BUILD === 'weapp') { } s[14] = '4'; // bits 12-15 of the time_hi_and_version field to 0010 - s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 + s[19] = hexDigits.substr((Number(s[19]) & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 s[8] = s[13] = s[18] = s[23] = '-'; - return s.join(''); }; } else { - const { v4 } = require('uuid'); uuid = v4; } module.exports = uuid; +export default uuid; diff --git a/types/CoreManager.d.ts b/types/CoreManager.d.ts index 4c1f949a2..f5f410d89 100644 --- a/types/CoreManager.d.ts +++ b/types/CoreManager.d.ts @@ -284,5 +284,15 @@ declare const CoreManager: { getLiveQueryController(): LiveQueryControllerType; setHooksController(controller: HooksController): void; getHooksController(): HooksController; + setParseOp(op: ParseOp): void; + getParseOp(): any; + setParseObject(object: ParseObject): void; + getParseObject(): ParseObject; + setParseQuery(query: ParseQuery): void; + getParseQuery(): any; + setParseRole(role: ParseRole): void; + getParseRole(): ParseRole; + setParseUser(user: ParseUser): void; + getParseUser(): ParseUser; }; export default CoreManager; diff --git a/types/uuid.d.ts b/types/uuid.d.ts index f3a50d131..8ce0850a8 100644 --- a/types/uuid.d.ts +++ b/types/uuid.d.ts @@ -1,2 +1,2 @@ -export = uuid; -declare let uuid: any; +declare let uuid: () => string; +export default uuid; From d366dff4df0e9a7ddefcbf838c539ce612e688fe Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 15 May 2024 19:57:46 +0000 Subject: [PATCH 29/32] chore(release): 5.1.0-alpha.10 [skip ci] # [5.1.0-alpha.10](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.9...5.1.0-alpha.10) (2024-05-15) ### Bug Fixes * Remove circular dependencies ([#2125](https://github.com/parse-community/Parse-SDK-JS/issues/2125)) ([b415165](https://github.com/parse-community/Parse-SDK-JS/commit/b415165486f0328e0f9fb2d949d7b11abf363435)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index b286a8194..dbce2573b 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.10](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.9...5.1.0-alpha.10) (2024-05-15) + + +### Bug Fixes + +* Remove circular dependencies ([#2125](https://github.com/parse-community/Parse-SDK-JS/issues/2125)) ([b415165](https://github.com/parse-community/Parse-SDK-JS/commit/b415165486f0328e0f9fb2d949d7b11abf363435)) + # [5.1.0-alpha.9](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.8...5.1.0-alpha.9) (2024-05-04) diff --git a/package-lock.json b/package-lock.json index ede3b749e..95f88186a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse", - "version": "5.1.0-alpha.9", + "version": "5.1.0-alpha.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse", - "version": "5.1.0-alpha.9", + "version": "5.1.0-alpha.10", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "7.23.2", diff --git a/package.json b/package.json index 72ec42a4b..dea55eaee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse", - "version": "5.1.0-alpha.9", + "version": "5.1.0-alpha.10", "description": "Parse JavaScript SDK", "homepage": "https://parseplatform.org", "keywords": [ From 3860535f5257b7b5edbf7ebfd286e2a4a7fd2769 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 15 May 2024 19:46:18 -0500 Subject: [PATCH 30/32] fix: `Parse.GeoPoint.current` returns `undefined` (#2127) --- src/{ParseGeoPoint.js => ParseGeoPoint.ts} | 20 ++++++------ src/__tests__/ParseGeoPoint-test.js | 35 ++++++++++++++------- types/ParseGeoPoint.d.ts | 36 +++++++++++----------- 3 files changed, 53 insertions(+), 38 deletions(-) rename src/{ParseGeoPoint.js => ParseGeoPoint.ts} (77%) diff --git a/src/ParseGeoPoint.js b/src/ParseGeoPoint.ts similarity index 77% rename from src/ParseGeoPoint.js rename to src/ParseGeoPoint.ts index 3af089e98..b821dc838 100644 --- a/src/ParseGeoPoint.js +++ b/src/ParseGeoPoint.ts @@ -1,7 +1,3 @@ -/** - * @flow - */ - /** * Creates a new GeoPoint with any of the following forms:
*
@@ -102,7 +98,7 @@ class ParseGeoPoint {
     };
   }
 
-  equals(other: mixed): boolean {
+  equals(other: any): boolean {
     return (
       other instanceof ParseGeoPoint &&
       this.latitude === other.latitude &&
@@ -183,12 +179,18 @@ class ParseGeoPoint {
   /**
    * Creates a GeoPoint with the user's current location, if available.
    *
+   * @param {object} options The options.
+   * @param {boolean} [options.enableHighAccuracy=false] A boolean value that indicates the application would like to receive the best possible results. If true and if the device is able to provide a more accurate position, it will do so. Note that this can result in slower response times or increased power consumption (with a GPS chip on a mobile device for example). On the other hand, if false, the device can take the liberty to save resources by responding more quickly and/or using less power. Default: false.
+   * @param {number} [options.timeout=Infinity] A positive long value representing the maximum length of time (in milliseconds) the device is allowed to take in order to return a position. The default value is Infinity, meaning that getCurrentPosition() won't return until the position is available.
+   * @param {number} [options.maximumAge=0] A positive long value indicating the maximum age in milliseconds of a possible cached position that is acceptable to return. If set to 0, it means that the device cannot use a cached position and must attempt to retrieve the real current position. If set to Infinity the device must return a cached position regardless of its age. Default: 0.
    * @static
-   * @returns {Parse.GeoPoint} User's current location
+   * @returns {Promise} User's current location
    */
-  static current() {
-    return navigator.geolocation.getCurrentPosition(location => {
-      return new ParseGeoPoint(location.coords.latitude, location.coords.longitude);
+  static current(options): Promise {
+    return new Promise((resolve, reject) => {
+      navigator.geolocation.getCurrentPosition(location => {
+        resolve(new ParseGeoPoint(location.coords.latitude, location.coords.longitude));
+      }, reject, options);
     });
   }
 }
diff --git a/src/__tests__/ParseGeoPoint-test.js b/src/__tests__/ParseGeoPoint-test.js
index 49c1424ed..3bc9346c0 100644
--- a/src/__tests__/ParseGeoPoint-test.js
+++ b/src/__tests__/ParseGeoPoint-test.js
@@ -1,16 +1,6 @@
 jest.autoMockOff();
 
 const ParseGeoPoint = require('../ParseGeoPoint').default;
-global.navigator.geolocation = {
-  getCurrentPosition: cb => {
-    return cb({
-      coords: {
-        latitude: 10,
-        longitude: 20,
-      },
-    });
-  },
-};
 
 describe('GeoPoint', () => {
   it('can be constructed from various inputs', () => {
@@ -217,8 +207,31 @@ describe('GeoPoint', () => {
   });
 
   it('can get current location', async () => {
-    const geoPoint = ParseGeoPoint.current();
+    global.navigator.geolocation = {
+      getCurrentPosition: (success, _, options) => {
+        success({
+          coords: {
+            latitude: 10,
+            longitude: 20,
+          },
+        });
+        expect(options).toEqual({ timeout: 5000 });
+      },
+    };
+    const geoPoint = await ParseGeoPoint.current({ timeout: 5000 });
     expect(geoPoint.latitude).toBe(10);
     expect(geoPoint.longitude).toBe(20);
   });
+
+  it('can get current location error', async () => {
+    global.navigator.geolocation = {
+      getCurrentPosition: (_, error, options) => {
+        error({
+          message: 'PERMISSION_DENIED',
+        });
+        expect(options).toEqual({ timeout: 5000 });
+      },
+    };
+    await expect(ParseGeoPoint.current({ timeout: 5000 })).rejects.toEqual({ message: 'PERMISSION_DENIED' });
+  });
 });
diff --git a/types/ParseGeoPoint.d.ts b/types/ParseGeoPoint.d.ts
index 9dfca7d53..a48a469d9 100644
--- a/types/ParseGeoPoint.d.ts
+++ b/types/ParseGeoPoint.d.ts
@@ -1,8 +1,3 @@
-// @ts-nocheck
-export default ParseGeoPoint;
-/**
- * @flow
- */
 /**
  * Creates a new GeoPoint with any of the following forms:
*
@@ -27,14 +22,8 @@ export default ParseGeoPoint;
  * @alias Parse.GeoPoint
  */
 declare class ParseGeoPoint {
-    static _validate(latitude: number, longitude: number): void;
-    /**
-     * Creates a GeoPoint with the user's current location, if available.
-     *
-     * @static
-     * @returns {Parse.GeoPoint} User's current location
-     */
-    static current(): Parse.GeoPoint;
+    _latitude: number;
+    _longitude: number;
     /**
      * @param {(number[] | object | number)} arg1 Either a list of coordinate pairs, an object with `latitude`, `longitude`, or the latitude or the point.
      * @param {number} arg2 The longitude of the GeoPoint
@@ -43,9 +32,6 @@ declare class ParseGeoPoint {
         latitude: number;
         longitude: number;
     } | number, arg2?: number);
-    _latitude: number;
-    _longitude: number;
-    set latitude(arg: number);
     /**
      * North-south portion of the coordinate, in range [-90, 90].
      * Throws an exception if set out of range in a modern browser.
@@ -54,7 +40,7 @@ declare class ParseGeoPoint {
      * @returns {number}
      */
     get latitude(): number;
-    set longitude(arg: number);
+    set latitude(val: number);
     /**
      * East-west portion of the coordinate, in range [-180, 180].
      * Throws if set out of range in a modern browser.
@@ -63,6 +49,7 @@ declare class ParseGeoPoint {
      * @returns {number}
      */
     get longitude(): number;
+    set longitude(val: number);
     /**
      * Returns a JSON representation of the GeoPoint, suitable for Parse.
      *
@@ -73,7 +60,7 @@ declare class ParseGeoPoint {
         latitude: number;
         longitude: number;
     };
-    equals(other: mixed): boolean;
+    equals(other: any): boolean;
     /**
      * Returns the distance from this GeoPoint to another in radians.
      *
@@ -95,4 +82,17 @@ declare class ParseGeoPoint {
      * @returns {number}
      */
     milesTo(point: ParseGeoPoint): number;
+    static _validate(latitude: number, longitude: number): void;
+    /**
+     * Creates a GeoPoint with the user's current location, if available.
+     *
+     * @param {object} options The options.
+     * @param {boolean} [options.enableHighAccuracy=false] A boolean value that indicates the application would like to receive the best possible results. If true and if the device is able to provide a more accurate position, it will do so. Note that this can result in slower response times or increased power consumption (with a GPS chip on a mobile device for example). On the other hand, if false, the device can take the liberty to save resources by responding more quickly and/or using less power. Default: false.
+     * @param {number} [options.timeout=Infinity] A positive long value representing the maximum length of time (in milliseconds) the device is allowed to take in order to return a position. The default value is Infinity, meaning that getCurrentPosition() won't return until the position is available.
+     * @param {number} [options.maximumAge=0] A positive long value indicating the maximum age in milliseconds of a possible cached position that is acceptable to return. If set to 0, it means that the device cannot use a cached position and must attempt to retrieve the real current position. If set to Infinity the device must return a cached position regardless of its age. Default: 0.
+     * @static
+     * @returns {Promise} User's current location
+     */
+    static current(options: any): Promise;
 }
+export default ParseGeoPoint;

From 3cc0aed124e964b7ed653f71879b0a0f5a03f46c Mon Sep 17 00:00:00 2001
From: semantic-release-bot 
Date: Thu, 16 May 2024 00:47:33 +0000
Subject: [PATCH 31/32] chore(release): 5.1.0-alpha.11 [skip ci]

# [5.1.0-alpha.11](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.10...5.1.0-alpha.11) (2024-05-16)

### Bug Fixes

* `Parse.GeoPoint.current` returns `undefined` ([#2127](https://github.com/parse-community/Parse-SDK-JS/issues/2127)) ([3860535](https://github.com/parse-community/Parse-SDK-JS/commit/3860535f5257b7b5edbf7ebfd286e2a4a7fd2769))
---
 changelogs/CHANGELOG_alpha.md | 7 +++++++
 package-lock.json             | 4 ++--
 package.json                  | 2 +-
 3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md
index dbce2573b..ba4a4c89f 100644
--- a/changelogs/CHANGELOG_alpha.md
+++ b/changelogs/CHANGELOG_alpha.md
@@ -1,3 +1,10 @@
+# [5.1.0-alpha.11](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.10...5.1.0-alpha.11) (2024-05-16)
+
+
+### Bug Fixes
+
+* `Parse.GeoPoint.current` returns `undefined` ([#2127](https://github.com/parse-community/Parse-SDK-JS/issues/2127)) ([3860535](https://github.com/parse-community/Parse-SDK-JS/commit/3860535f5257b7b5edbf7ebfd286e2a4a7fd2769))
+
 # [5.1.0-alpha.10](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.9...5.1.0-alpha.10) (2024-05-15)
 
 
diff --git a/package-lock.json b/package-lock.json
index 95f88186a..51ac0193d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "parse",
-  "version": "5.1.0-alpha.10",
+  "version": "5.1.0-alpha.11",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "parse",
-      "version": "5.1.0-alpha.10",
+      "version": "5.1.0-alpha.11",
       "license": "Apache-2.0",
       "dependencies": {
         "@babel/runtime-corejs3": "7.23.2",
diff --git a/package.json b/package.json
index dea55eaee..524b4b65c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "parse",
-  "version": "5.1.0-alpha.10",
+  "version": "5.1.0-alpha.11",
   "description": "Parse JavaScript SDK",
   "homepage": "https://parseplatform.org",
   "keywords": [

From 81b694499fbfdbad033870279ec15144fdf00aa7 Mon Sep 17 00:00:00 2001
From: Diamond Lewis 
Date: Thu, 16 May 2024 07:01:42 -0500
Subject: [PATCH 32/32] refactor: Convert ParseObject to TypeScript (#2128)

---
 .github/workflows/ci.yml               |    8 +-
 package.json                           |    1 +
 src/CoreManager.ts                     |   18 +-
 src/EventuallyQueue.js                 |    2 +-
 src/InstallationController.ts          |    2 +-
 src/Parse.ts                           |    2 +-
 src/ParseInstallation.ts               |    4 +-
 src/{ParseObject.js => ParseObject.ts} |  162 ++--
 src/ParseSession.ts                    |    1 -
 types/CoreManager.d.ts                 |   16 +-
 types/ParseInstallation.d.ts           |    4 +-
 types/ParseObject.d.ts                 | 1222 ++++++++++++------------
 12 files changed, 733 insertions(+), 709 deletions(-)
 rename src/{ParseObject.js => ParseObject.ts} (95%)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 22caf3799..4c3499115 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,13 +17,17 @@ jobs:
         with:
           version: 2
   check-types:
-    name: Check types
+    name: Check Types
     timeout-minutes: 5
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v3
       - run: npm ci
-      - name: Check types
+      - name: Build Types
+        run: npm run build:types
+      - name: Lint Types
+        run: npm run lint:types
+      - name: Test Types
         run: npm run test:types
   check-docs:
     name: Check Docs
diff --git a/package.json b/package.json
index 524b4b65c..b2e84515c 100644
--- a/package.json
+++ b/package.json
@@ -107,6 +107,7 @@
     "posttest:mongodb": "mongodb-runner stop --all",
     "lint": "eslint --cache src/ integration/",
     "lint:fix": "eslint --fix --cache src/ integration/",
+    "lint:types": "dtslint types",
     "watch": "cross-env PARSE_BUILD=${PARSE_BUILD} gulp watch",
     "watch:browser": "cross-env PARSE_BUILD=browser npm run watch",
     "watch:node": "cross-env PARSE_BUILD=node npm run watch",
diff --git a/src/CoreManager.ts b/src/CoreManager.ts
index 4d610e5fb..9ee9697ba 100644
--- a/src/CoreManager.ts
+++ b/src/CoreManager.ts
@@ -53,7 +53,7 @@ type ObjectController = {
     object: ParseObject | Array,
     forceFetch: boolean,
     options: RequestOptions
-  ) => Promise,
+  ) => Promise | ParseObject | undefined>,
   save: (object: ParseObject | Array | null, options: RequestOptions) => Promise | ParseFile>,
   destroy: (object: ParseObject | Array, options: RequestOptions) => Promise>,
 };
@@ -86,7 +86,7 @@ type QueryController = {
 type EventuallyQueue = {
   save: (object: ParseObject, serverOptions: SaveOptions) => Promise,
   destroy: (object: ParseObject, serverOptions: RequestOptions) => Promise,
-  poll: (ms: number) => void
+  poll: (ms?: number) => void
 };
 type RESTController = {
   request: (method: string, path: string, data?: any, options?: RequestOptions) => Promise,
@@ -598,7 +598,7 @@ const CoreManager = {
     return config['HooksController']!;
   },
 
-  setParseOp(op: ParseOp) {
+  setParseOp(op: typeof ParseOp) {
     config['ParseOp'] = op;
   },
 
@@ -606,7 +606,7 @@ const CoreManager = {
     return config['ParseOp']!;
   },
 
-  setParseObject(object: ParseObject) {
+  setParseObject(object: typeof ParseObject) {
     config['ParseObject'] = object;
   },
 
@@ -614,15 +614,15 @@ const CoreManager = {
     return config['ParseObject']!;
   },
 
-  setParseQuery(query: ParseQuery) {
+  setParseQuery(query: typeof ParseQuery) {
     config['ParseQuery'] = query;
   },
 
-  getParseQuery() {
-    return config['ParseQuery']!
+  getParseQuery(): ParseQuery {
+    return config['ParseQuery']!;
   },
 
-  setParseRole(role: ParseRole) {
+  setParseRole(role: typeof ParseRole) {
     config['ParseRole'] = role;
   },
 
@@ -630,7 +630,7 @@ const CoreManager = {
     return config['ParseRole']!;
   },
 
-  setParseUser(user: ParseUser) {
+  setParseUser(user: typeof ParseUser) {
     config['ParseUser'] = user;
   },
 
diff --git a/src/EventuallyQueue.js b/src/EventuallyQueue.js
index 88df1d3bd..521d1e514 100644
--- a/src/EventuallyQueue.js
+++ b/src/EventuallyQueue.js
@@ -303,7 +303,7 @@ const EventuallyQueue = {
    * @param [ms] Milliseconds to ping the server. Default 2000ms
    * @static
    */
-  poll(ms: number = 2000) {
+  poll(ms?: number = 2000) {
     if (polling) {
       return;
     }
diff --git a/src/InstallationController.ts b/src/InstallationController.ts
index bc14e8337..419e55451 100644
--- a/src/InstallationController.ts
+++ b/src/InstallationController.ts
@@ -49,7 +49,7 @@ const InstallationController = {
       installationData.className = '_Installation';
       const current = ParseInstallation.fromJSON(installationData);
       currentInstallationCache = current;
-      return current;
+      return current as ParseInstallation;
     }
     const installationId = await this.currentInstallationId();
     const installation = new ParseInstallation();
diff --git a/src/Parse.ts b/src/Parse.ts
index b6bc8a169..b271b013e 100644
--- a/src/Parse.ts
+++ b/src/Parse.ts
@@ -91,7 +91,7 @@ interface ParseType {
   _initialize(applicationId: string, javaScriptKey: string, masterKey?: string): void,
   setAsyncStorage(storage: any): void,
   setLocalDatastoreController(controller: any): void,
-  getServerHealth(): Promise
+  getServerHealth(): Promise,
 
   applicationId: string,
   javaScriptKey: string,
diff --git a/src/ParseInstallation.ts b/src/ParseInstallation.ts
index 10f95bbea..d0e0195e8 100644
--- a/src/ParseInstallation.ts
+++ b/src/ParseInstallation.ts
@@ -189,7 +189,7 @@ class ParseInstallation extends ParseObject {
    * Parse.Installation.DEVICE_TYPES.WEB
    * 
): Promise { + async save(...args: Array): Promise { await super.save.apply(this, args); await CoreManager.getInstallationController().updateInstallationOnDisk(this); return this; diff --git a/src/ParseObject.js b/src/ParseObject.ts similarity index 95% rename from src/ParseObject.js rename to src/ParseObject.ts index 82b96b569..23c699524 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.ts @@ -1,7 +1,3 @@ -/** - * @flow - */ - import CoreManager from './CoreManager'; import canBeSerialized from './canBeSerialized'; import decode from './decode'; @@ -13,7 +9,7 @@ import ParseError from './ParseError'; import ParseFile from './ParseFile'; import { when, continueWhile, resolvingPromise } from './promiseUtils'; import { DEFAULT_PIN, PIN_PREFIX } from './LocalDatastoreUtils'; - +import uuidv4 from './uuid'; import { opFromJSON, Op, @@ -34,12 +30,11 @@ import unsavedChildren from './unsavedChildren'; import type { AttributeMap, OpsMap } from './ObjectStateMutations'; import type { RequestOptions, FullOptions } from './RESTController'; -const uuidv4 = require('./uuid'); - export type Pointer = { __type: string, className: string, - objectId: string, + objectId?: string, + _localId?: string, }; type SaveParams = { @@ -51,8 +46,16 @@ type SaveParams = { export type SaveOptions = FullOptions & { cascadeSave?: boolean, context?: AttributeMap, + batchSize?: number, }; +type FetchOptions = { + useMasterKey?: boolean, + sessionToken?: string, + include?: string | string[], + context?: AttributeMap, +} + // Mapping of class names to constructors, so we can populate objects from the // server with appropriate subclasses of ParseObject const classMap = {}; @@ -103,8 +106,8 @@ class ParseObject { * @param {object} options The options for this object instance. */ constructor( - className: ?string | { className: string, [attr: string]: mixed }, - attributes?: { [attr: string]: mixed }, + className?: string | { className: string, [attr: string]: any }, + attributes?: { [attr: string]: any }, options?: { ignoreValidation: boolean } ) { // Enable legacy initializers @@ -128,7 +131,7 @@ class ParseObject { } } if (attributes && typeof attributes === 'object') { - options = attributes; + options = attributes as any; } } if (toSet && !this.set(toSet, options)) { @@ -141,8 +144,8 @@ class ParseObject { * * @property {string} id */ - id: ?string; - _localId: ?string; + id?: string; + _localId?: string; _objCount: number; className: string; @@ -159,7 +162,7 @@ class ParseObject { * @property {Date} createdAt * @returns {Date} */ - get createdAt(): ?Date { + get createdAt(): Date | undefined { return this._getServerData().createdAt; } @@ -169,7 +172,7 @@ class ParseObject { * @property {Date} updatedAt * @returns {Date} */ - get updatedAt(): ?Date { + get updatedAt(): Date | undefined { return this._getServerData().updatedAt; } @@ -278,7 +281,7 @@ class ParseObject { } _toFullJSON(seen?: Array, offline?: boolean): AttributeMap { - const json: { [key: string]: mixed } = this.toJSON(seen, offline); + const json: { [key: string]: any } = this.toJSON(seen, offline); json.__type = 'Object'; json.className = this.className; return json; @@ -344,7 +347,12 @@ class ParseObject { } const stateController = CoreManager.getObjectStateController(); stateController.initializeState(this._getStateIdentifier()); - const decoded = {}; + const decoded: Partial<{ + createdAt?: Date, + updatedAt?: Date, + ACL?: any, + [key: string]: any, + }> = {}; for (const attr in serverData) { if (attr === 'ACL') { decoded[attr] = new ParseACL(serverData[attr]); @@ -393,7 +401,11 @@ class ParseObject { } _handleSaveResponse(response: AttributeMap, status: number) { - const changes = {}; + const changes: Partial<{ + createdAt: string, + updatedAt: string, + [key: string]: any + }> = {}; let attr; const stateController = CoreManager.getObjectStateController(); const pending = stateController.popPendingState(this._getStateIdentifier()); @@ -460,7 +472,7 @@ class ParseObject { toJSON(seen: Array | void, offline?: boolean): AttributeMap { const seenEntry = this.id ? this.className + ':' + this.id : this; seen = seen || [seenEntry]; - const json = {}; + const json: AttributeMap = {}; const attrs = this.attributes; for (const attr in attrs) { if ((attr === 'createdAt' || attr === 'updatedAt') && attrs[attr].toJSON) { @@ -482,7 +494,7 @@ class ParseObject { * @param {object} other - An other object ot compare * @returns {boolean} */ - equals(other: mixed): boolean { + equals(other: any): boolean { if (this === other) { return true; } @@ -596,7 +608,7 @@ class ParseObject { * @param {string} attr The string name of an attribute. * @returns {*} */ - get(attr: string): mixed { + get(attr: string): any { return this.attributes[attr]; } @@ -683,7 +695,7 @@ class ParseObject { * The only supported option is error. * @returns {(ParseObject|boolean)} true if the set succeeded. */ - set(key: mixed, value: mixed, options?: mixed): ParseObject | boolean { + set(key: any, value?: any, options?: any): ParseObject | boolean { let changes = {}; const newOps = {}; if (key && typeof key === 'object') { @@ -697,8 +709,8 @@ class ParseObject { options = options || {}; let readonly = []; - if (typeof this.constructor.readOnlyAttributes === 'function') { - readonly = readonly.concat(this.constructor.readOnlyAttributes()); + if (typeof (this.constructor as any).readOnlyAttributes === 'function') { + readonly = readonly.concat((this.constructor as any).readOnlyAttributes()); } for (const k in changes) { if (k === 'createdAt' || k === 'updatedAt') { @@ -781,7 +793,7 @@ class ParseObject { * @param options * @returns {(ParseObject | boolean)} */ - unset(attr: string, options?: { [opt: string]: mixed }): ParseObject | boolean { + unset(attr: string, options?: { [opt: string]: any }): ParseObject | boolean { options = options || {}; options.unset = true; return this.set(attr, null, options); @@ -831,7 +843,7 @@ class ParseObject { * @param item {} The item to add. * @returns {(ParseObject | boolean)} */ - add(attr: string, item: mixed): ParseObject | boolean { + add(attr: string, item: any): ParseObject | boolean { return this.set(attr, new AddOp([item])); } @@ -843,7 +855,7 @@ class ParseObject { * @param items {Object[]} The items to add. * @returns {(ParseObject | boolean)} */ - addAll(attr: string, items: Array): ParseObject | boolean { + addAll(attr: string, items: Array): ParseObject | boolean { return this.set(attr, new AddOp(items)); } @@ -856,7 +868,7 @@ class ParseObject { * @param item {} The object to add. * @returns {(ParseObject | boolean)} */ - addUnique(attr: string, item: mixed): ParseObject | boolean { + addUnique(attr: string, item: any): ParseObject | boolean { return this.set(attr, new AddUniqueOp([item])); } @@ -869,7 +881,7 @@ class ParseObject { * @param items {Object[]} The objects to add. * @returns {(ParseObject | boolean)} */ - addAllUnique(attr: string, items: Array): ParseObject | boolean { + addAllUnique(attr: string, items: Array): ParseObject | boolean { return this.set(attr, new AddUniqueOp(items)); } @@ -881,7 +893,7 @@ class ParseObject { * @param item {} The object to remove. * @returns {(ParseObject | boolean)} */ - remove(attr: string, item: mixed): ParseObject | boolean { + remove(attr: string, item: any): ParseObject | boolean { return this.set(attr, new RemoveOp([item])); } @@ -893,7 +905,7 @@ class ParseObject { * @param items {Object[]} The object to remove. * @returns {(ParseObject | boolean)} */ - removeAll(attr: string, items: Array): ParseObject | boolean { + removeAll(attr: string, items: Array): ParseObject | boolean { return this.set(attr, new RemoveOp(items)); } @@ -906,7 +918,7 @@ class ParseObject { * @param attr {String} The key. * @returns {Parse.Op | undefined} The operation, or undefined if none. */ - op(attr: string): ?Op { + op(attr: string): Op | undefined { const pending = this._getPendingOps(); for (let i = pending.length; i--;) { if (pending[i][attr]) { @@ -921,10 +933,10 @@ class ParseObject { * @returns {Parse.Object} */ clone(): any { - const clone = new this.constructor(this.className); + const clone = new (this.constructor as new (...args: ConstructorParameters) => this)(this.className); let attributes = this.attributes; - if (typeof this.constructor.readOnlyAttributes === 'function') { - const readonly = this.constructor.readOnlyAttributes() || []; + if (typeof (this.constructor as any).readOnlyAttributes === 'function') { + const readonly = (this.constructor as any).readOnlyAttributes() || []; // Attributes are frozen, so we have to rebuild an object, // rather than delete readonly keys const copy = {}; @@ -947,7 +959,7 @@ class ParseObject { * @returns {Parse.Object} */ newInstance(): any { - const clone = new this.constructor(this.className); + const clone = new (this.constructor as new (...args: ConstructorParameters) => this)(this.className); clone.id = this.id; if (singleInstance) { // Just return an object with the right id @@ -1055,7 +1067,7 @@ class ParseObject { * @returns {Parse.ACL|null} An instance of Parse.ACL. * @see Parse.Object#get */ - getACL(): ?ParseACL { + getACL(): ParseACL | null { const acl = this.get('ACL'); if (acl instanceof ParseACL) { return acl; @@ -1071,7 +1083,7 @@ class ParseObject { * @returns {(ParseObject | boolean)} Whether the set passed validation. * @see Parse.Object#set */ - setACL(acl: ParseACL, options?: mixed): ParseObject | boolean { + setACL(acl: ParseACL, options?: any): ParseObject | boolean { return this.set('ACL', acl, options); } @@ -1104,8 +1116,8 @@ class ParseObject { const attributes = this.attributes; const erasable = {}; let readonly = ['createdAt', 'updatedAt']; - if (typeof this.constructor.readOnlyAttributes === 'function') { - readonly = readonly.concat(this.constructor.readOnlyAttributes()); + if (typeof (this.constructor as any).readOnlyAttributes === 'function') { + readonly = readonly.concat((this.constructor as any).readOnlyAttributes()); } for (const attr in attributes) { if (readonly.indexOf(attr) < 0) { @@ -1132,9 +1144,9 @@ class ParseObject { * @returns {Promise} A promise that is fulfilled when the fetch * completes. */ - fetch(options: RequestOptions): Promise { + fetch(options: FetchOptions): Promise { options = options || {}; - const fetchOptions = {}; + const fetchOptions: FetchOptions = {}; if (options.hasOwnProperty('useMasterKey')) { fetchOptions.useMasterKey = options.useMasterKey; } @@ -1151,7 +1163,7 @@ class ParseObject { if (Array.isArray(key)) { fetchOptions.include = fetchOptions.include.concat(key); } else { - fetchOptions.include.push(key); + (fetchOptions.include as string[]).push(key); } }); } else { @@ -1180,7 +1192,7 @@ class ParseObject { * @returns {Promise} A promise that is fulfilled when the fetch * completes. */ - fetchWithInclude(keys: String | Array>, options: RequestOptions): Promise { + fetchWithInclude(keys: String | Array>, options: RequestOptions): Promise { options = options || {}; options.include = keys; return this.fetch(options); @@ -1210,7 +1222,7 @@ class ParseObject { * @returns {Promise} A promise that is fulfilled when the save * completes. */ - async saveEventually(options: SaveOptions): Promise { + async saveEventually(options: SaveOptions): Promise { try { await this.save(null, options); } catch (e) { @@ -1286,10 +1298,10 @@ class ParseObject { * completes. */ save( - arg1: ?string | { [attr: string]: mixed }, - arg2: SaveOptions | mixed, + arg1: undefined | string | { [attr: string]: any } | null, + arg2: SaveOptions | any, arg3?: SaveOptions - ): Promise { + ): Promise { let attrs; let options; if (typeof arg1 === 'object' || typeof arg1 === 'undefined') { @@ -1314,7 +1326,7 @@ class ParseObject { return Promise.reject(validationError); } } - const saveOptions = {}; + const saveOptions: SaveOptions = {}; if (options.hasOwnProperty('useMasterKey')) { saveOptions.useMasterKey = !!options.useMasterKey; } @@ -1356,7 +1368,7 @@ class ParseObject { * @returns {Promise} A promise that is fulfilled when the destroy * completes. */ - async destroyEventually(options: RequestOptions): Promise { + async destroyEventually(options: RequestOptions): Promise { try { await this.destroy(options); } catch (e) { @@ -1382,9 +1394,9 @@ class ParseObject { * @returns {Promise} A promise that is fulfilled when the destroy * completes. */ - destroy(options: RequestOptions): Promise { + destroy(options: RequestOptions): Promise { options = options || {}; - const destroyOptions = {}; + const destroyOptions: RequestOptions = {}; if (options.hasOwnProperty('useMasterKey')) { destroyOptions.useMasterKey = options.useMasterKey; } @@ -1397,7 +1409,7 @@ class ParseObject { if (!this.id) { return Promise.resolve(); } - return CoreManager.getObjectController().destroy(this, destroyOptions); + return CoreManager.getObjectController().destroy(this, destroyOptions) as Promise; } /** @@ -1549,7 +1561,7 @@ class ParseObject { * @returns {Parse.Object[]} */ static fetchAll(list: Array, options: RequestOptions = {}) { - const queryOptions = {}; + const queryOptions: RequestOptions = {}; if (options.hasOwnProperty('useMasterKey')) { queryOptions.useMasterKey = options.useMasterKey; } @@ -1659,10 +1671,10 @@ class ParseObject { * @static * @returns {Parse.Object[]} */ - static fetchAllIfNeeded(list: Array, options) { + static fetchAllIfNeeded(list: Array, options: FetchOptions) { options = options || {}; - const queryOptions = {}; + const queryOptions: FetchOptions = {}; if (options.hasOwnProperty('useMasterKey')) { queryOptions.useMasterKey = options.useMasterKey; } @@ -1675,7 +1687,7 @@ class ParseObject { return CoreManager.getObjectController().fetch(list, false, queryOptions); } - static handleIncludeOptions(options) { + static handleIncludeOptions(options: { include?: string | string[] }) { let include = []; if (Array.isArray(options.include)) { options.include.forEach(key => { @@ -1686,7 +1698,7 @@ class ParseObject { } }); } else { - include.push(options.include); + include.push(options.include!); } return include; } @@ -1737,8 +1749,8 @@ class ParseObject { * @returns {Promise} A promise that is fulfilled when the destroyAll * completes. */ - static destroyAll(list: Array, options = {}) { - const destroyOptions = {}; + static destroyAll(list: Array, options: SaveOptions = {}) { + const destroyOptions: SaveOptions = {}; if (options.hasOwnProperty('useMasterKey')) { destroyOptions.useMasterKey = options.useMasterKey; } @@ -1772,8 +1784,8 @@ class ParseObject { * @static * @returns {Parse.Object[]} */ - static saveAll(list: Array, options: RequestOptions = {}) { - const saveOptions = {}; + static saveAll(list: Array, options: SaveOptions = {}) { + const saveOptions: SaveOptions = {}; if (options.hasOwnProperty('useMasterKey')) { saveOptions.useMasterKey = options.useMasterKey; } @@ -1803,7 +1815,7 @@ class ParseObject { * @static * @returns {Parse.Object} A Parse.Object reference. */ - static createWithoutData(id: string) { + static createWithoutData(id: string): ParseObject { const obj = new this(); obj.id = id; return obj; @@ -1819,13 +1831,13 @@ class ParseObject { * @static * @returns {Parse.Object} A Parse.Object reference */ - static fromJSON(json: any, override?: boolean, dirty?: boolean) { + static fromJSON(json: any, override?: boolean, dirty?: boolean): ParseObject { if (!json.className) { throw new Error('Cannot create an object without a className'); } const constructor = classMap[json.className]; const o = constructor ? new constructor(json.className) : new ParseObject(json.className); - const otherAttributes = {}; + const otherAttributes: AttributeMap = {}; for (const attr in json) { if (attr !== 'className' && attr !== '__type') { otherAttributes[attr] = json[attr]; @@ -1947,7 +1959,7 @@ class ParseObject { } let parentProto = ParseObject.prototype; - if (this.hasOwnProperty('__super__') && this.__super__) { + if (this.hasOwnProperty('__super__') && (this as any).__super__) { parentProto = this.prototype; } let ParseObjectSubclass = function (attributes, options) { @@ -1973,15 +1985,15 @@ class ParseObject { if (classMap[adjustedClassName]) { ParseObjectSubclass = classMap[adjustedClassName]; } else { - ParseObjectSubclass.extend = function (name, protoProps, classProps) { + (ParseObjectSubclass as any).extend = function (name, protoProps, classProps) { if (typeof name === 'string') { return ParseObject.extend.call(ParseObjectSubclass, name, protoProps, classProps); } return ParseObject.extend.call(ParseObjectSubclass, adjustedClassName, name, protoProps); }; - ParseObjectSubclass.createWithoutData = ParseObject.createWithoutData; - ParseObjectSubclass.className = adjustedClassName; - ParseObjectSubclass.__super__ = parentProto; + (ParseObjectSubclass as any).createWithoutData = ParseObject.createWithoutData; + (ParseObjectSubclass as any).className = adjustedClassName; + (ParseObjectSubclass as any).__super__ = parentProto; ParseObjectSubclass.prototype = Object.create(parentProto, { constructor: { value: ParseObjectSubclass, @@ -2192,7 +2204,7 @@ const DefaultController = { target: ParseObject | Array, forceFetch: boolean, options: RequestOptions - ): Promise | ParseObject> { + ): Promise | ParseObject | undefined> { const localDatastore = CoreManager.getLocalDatastore(); if (Array.isArray(target)) { if (target.length < 1) { @@ -2273,7 +2285,7 @@ const DefaultController = { ); } const RESTController = CoreManager.getRESTController(); - const params = {}; + const params: RequestOptions = {}; if (options && options.include) { params.include = options.include.join(); } @@ -2290,13 +2302,13 @@ const DefaultController = { return target; }); } - return Promise.resolve(); + return Promise.resolve(undefined); }, async destroy( target: ParseObject | Array, options: RequestOptions - ): Promise | ParseObject> { + ): Promise>{ const batchSize = options && options.batchSize ? options.batchSize : CoreManager.get('REQUEST_BATCH_SIZE'); const localDatastore = CoreManager.getLocalDatastore(); @@ -2373,7 +2385,7 @@ const DefaultController = { return Promise.resolve(target); }, - save(target: ParseObject | Array, options: RequestOptions) { + save(target: ParseObject | Array, options: RequestOptions): Promise | ParseFile> { const batchSize = options && options.batchSize ? options.batchSize : CoreManager.get('REQUEST_BATCH_SIZE'); const localDatastore = CoreManager.getLocalDatastore(); diff --git a/src/ParseSession.ts b/src/ParseSession.ts index 68cb8f398..bbeff1896 100644 --- a/src/ParseSession.ts +++ b/src/ParseSession.ts @@ -3,7 +3,6 @@ import isRevocableSession from './isRevocableSession'; import ParseObject from './ParseObject'; import ParseUser from './ParseUser'; -import type { AttributeMap } from './ObjectStateMutations'; import type { RequestOptions, FullOptions } from './RESTController'; /** diff --git a/types/CoreManager.d.ts b/types/CoreManager.d.ts index f5f410d89..3dde6b121 100644 --- a/types/CoreManager.d.ts +++ b/types/CoreManager.d.ts @@ -59,7 +59,7 @@ type InstallationController = { updateInstallationOnDisk: (installation: ParseInstallation) => Promise; }; type ObjectController = { - fetch: (object: ParseObject | Array, forceFetch: boolean, options: RequestOptions) => Promise; + fetch: (object: ParseObject | Array, forceFetch: boolean, options: RequestOptions) => Promise | ParseObject | undefined>; save: (object: ParseObject | Array | null, options: RequestOptions) => Promise | ParseFile>; destroy: (object: ParseObject | Array, options: RequestOptions) => Promise>; }; @@ -98,7 +98,7 @@ type QueryController = { type EventuallyQueue = { save: (object: ParseObject, serverOptions: SaveOptions) => Promise; destroy: (object: ParseObject, serverOptions: RequestOptions) => Promise; - poll: (ms: number) => void; + poll: (ms?: number) => void; }; type RESTController = { request: (method: string, path: string, data?: any, options?: RequestOptions) => Promise; @@ -284,15 +284,15 @@ declare const CoreManager: { getLiveQueryController(): LiveQueryControllerType; setHooksController(controller: HooksController): void; getHooksController(): HooksController; - setParseOp(op: ParseOp): void; + setParseOp(op: any): void; getParseOp(): any; - setParseObject(object: ParseObject): void; + setParseObject(object: typeof ParseObject): void; getParseObject(): ParseObject; - setParseQuery(query: ParseQuery): void; - getParseQuery(): any; - setParseRole(role: ParseRole): void; + setParseQuery(query: any): void; + getParseQuery(): ParseQuery; + setParseRole(role: any): void; getParseRole(): ParseRole; - setParseUser(user: ParseUser): void; + setParseUser(user: any): void; getParseUser(): ParseUser; }; export default CoreManager; diff --git a/types/ParseInstallation.d.ts b/types/ParseInstallation.d.ts index c74a47ceb..a7c599fc3 100644 --- a/types/ParseInstallation.d.ts +++ b/types/ParseInstallation.d.ts @@ -129,7 +129,7 @@ declare class ParseInstallation extends ParseObject { * Parse.Installation.DEVICE_TYPES.WEB *
): Promise; + save(...args: Array): Promise; /** * Get the current Parse.Installation from disk. If doesn't exists, create an new installation. * diff --git a/types/ParseObject.d.ts b/types/ParseObject.d.ts index 09eb14634..56fbcd11c 100644 --- a/types/ParseObject.d.ts +++ b/types/ParseObject.d.ts @@ -1,14 +1,31 @@ -// @ts-nocheck -type Pointer = { +import ParseACL from './ParseACL'; +import ParseError from './ParseError'; +import { Op } from './ParseOp'; +import ParseRelation from './ParseRelation'; +import type { AttributeMap, OpsMap } from './ObjectStateMutations'; +import type { RequestOptions, FullOptions } from './RESTController'; +export type Pointer = { __type: string; className: string; - objectId: string; + objectId?: string; + _localId?: string; +}; +type SaveParams = { + method: string; + path: string; + body: AttributeMap; }; export type SaveOptions = FullOptions & { cascadeSave?: boolean; context?: AttributeMap; + batchSize?: number; +}; +type FetchOptions = { + useMasterKey?: boolean; + sessionToken?: string; + include?: string | string[]; + context?: AttributeMap; }; -export default ParseObject; /** * Creates a new model with defined attributes. * @@ -28,464 +45,103 @@ export default ParseObject; * @alias Parse.Object */ declare class ParseObject { - static _getClassMap(): {}; - static _clearAllState(): void; /** - * Fetches the given list of Parse.Object. - * If any error is encountered, stops and calls the error handler. - * - *
-     *   Parse.Object.fetchAll([object1, object2, ...])
-     *    .then((list) => {
-     *      // All the objects were fetched.
-     *    }, (error) => {
-     *      // An error occurred while fetching one of the objects.
-     *    });
-     * 
- * - * @param {Array} list A list of Parse.Object. - * @param {object} options - * Valid options are:
    - *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to - * be used for this request. - *
  • sessionToken: A valid session token, used for making a request on - * behalf of a specific user. - *
  • include: The name(s) of the key(s) to include. Can be a string, an array of strings, - * or an array of array of strings. - *
- * @static - * @returns {Parse.Object[]} + * @param {string} className The class name for the object + * @param {object} attributes The initial set of data to store in the object. + * @param {object} options The options for this object instance. */ - static fetchAll(list: Array, options?: RequestOptions): Parse.Object[]; + constructor(className?: string | { + className: string; + [attr: string]: any; + }, attributes?: { + [attr: string]: any; + }, options?: { + ignoreValidation: boolean; + }); /** - * Fetches the given list of Parse.Object. - * - * Includes nested Parse.Objects for the provided key. You can use dot - * notation to specify which fields in the included object are also fetched. - * - * If any error is encountered, stops and calls the error handler. - * - *
-     *   Parse.Object.fetchAllWithInclude([object1, object2, ...], [pointer1, pointer2, ...])
-     *    .then((list) => {
-     *      // All the objects were fetched.
-     *    }, (error) => {
-     *      // An error occurred while fetching one of the objects.
-     *    });
-     * 
+ * The ID of this object, unique within its class. * - * @param {Array} list A list of Parse.Object. - * @param {string | Array>} keys The name(s) of the key(s) to include. - * @param {object} options - * Valid options are:
    - *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to - * be used for this request. - *
  • sessionToken: A valid session token, used for making a request on - * behalf of a specific user. - *
- * @static - * @returns {Parse.Object[]} + * @property {string} id */ - static fetchAllWithInclude(list: Array, keys: String | Array>, options: RequestOptions): Parse.Object[]; + id?: string; + _localId?: string; + _objCount: number; + className: string; + get attributes(): AttributeMap; /** - * Fetches the given list of Parse.Object if needed. - * If any error is encountered, stops and calls the error handler. - * - * Includes nested Parse.Objects for the provided key. You can use dot - * notation to specify which fields in the included object are also fetched. - * - * If any error is encountered, stops and calls the error handler. - * - *
-     *   Parse.Object.fetchAllIfNeededWithInclude([object1, object2, ...], [pointer1, pointer2, ...])
-     *    .then((list) => {
-     *      // All the objects were fetched.
-     *    }, (error) => {
-     *      // An error occurred while fetching one of the objects.
-     *    });
-     * 
+ * The first time this object was saved on the server. * - * @param {Array} list A list of Parse.Object. - * @param {string | Array>} keys The name(s) of the key(s) to include. - * @param {object} options - * Valid options are:
    - *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to - * be used for this request. - *
  • sessionToken: A valid session token, used for making a request on - * behalf of a specific user. - *
- * @static - * @returns {Parse.Object[]} + * @property {Date} createdAt + * @returns {Date} */ - static fetchAllIfNeededWithInclude(list: Array, keys: String | Array>, options: RequestOptions): Parse.Object[]; + get createdAt(): Date | undefined; /** - * Fetches the given list of Parse.Object if needed. - * If any error is encountered, stops and calls the error handler. - * - *
-     *   Parse.Object.fetchAllIfNeeded([object1, ...])
-     *    .then((list) => {
-     *      // Objects were fetched and updated.
-     *    }, (error) => {
-     *      // An error occurred while fetching one of the objects.
-     *    });
-     * 
+ * The last time this object was updated on the server. * - * @param {Array} list A list of Parse.Object. - * @param {object} options - * @static - * @returns {Parse.Object[]} + * @property {Date} updatedAt + * @returns {Date} */ - static fetchAllIfNeeded(list: Array, options: object): Parse.Object[]; - static handleIncludeOptions(options: any): any[]; + get updatedAt(): Date | undefined; /** - * Destroy the given list of models on the server if it was already persisted. - * - *

Unlike saveAll, if an error occurs while deleting an individual model, - * this method will continue trying to delete the rest of the models if - * possible, except in the case of a fatal error like a connection error. - * - *

In particular, the Parse.Error object returned in the case of error may - * be one of two types: - * - *

    - *
  • A Parse.Error.AGGREGATE_ERROR. This object's "errors" property is an - * array of other Parse.Error objects. Each error object in this array - * has an "object" property that references the object that could not be - * deleted (for instance, because that object could not be found).
  • - *
  • A non-aggregate Parse.Error. This indicates a serious error that - * caused the delete operation to be aborted partway through (for - * instance, a connection failure in the middle of the delete).
  • - *
- * - *
-     * Parse.Object.destroyAll([object1, object2, ...])
-     * .then((list) => {
-     * // All the objects were deleted.
-     * }, (error) => {
-     * // An error occurred while deleting one or more of the objects.
-     * // If this is an aggregate error, then we can inspect each error
-     * // object individually to determine the reason why a particular
-     * // object was not deleted.
-     * if (error.code === Parse.Error.AGGREGATE_ERROR) {
-     * for (var i = 0; i < error.errors.length; i++) {
-     * console.log("Couldn't delete " + error.errors[i].object.id +
-     * "due to " + error.errors[i].message);
-     * }
-     * } else {
-     * console.log("Delete aborted because of " + error.message);
-     * }
-     * });
-     * 
+ * Returns a local or server Id used uniquely identify this object * - * @param {Array} list A list of Parse.Object. - * @param {object} options - * @static - * @returns {Promise} A promise that is fulfilled when the destroyAll - * completes. + * @returns {string} */ - static destroyAll(list: Array, options?: object): Promise; + _getId(): string; /** - * Saves the given list of Parse.Object. - * If any error is encountered, stops and calls the error handler. - * - *
-     * Parse.Object.saveAll([object1, object2, ...])
-     * .then((list) => {
-     * // All the objects were saved.
-     * }, (error) => {
-     * // An error occurred while saving one of the objects.
-     * });
-     * 
+ * Returns a unique identifier used to pull data from the State Controller. * - * @param {Array} list A list of Parse.Object. - * @param {object} options - * @static - * @returns {Parse.Object[]} + * @returns {Parse.Object|object} */ - static saveAll(list: Array, options?: RequestOptions): Parse.Object[]; + _getStateIdentifier(): ParseObject | { + id: string; + className: string; + }; + _getServerData(): AttributeMap; + _clearServerData(): void; + _getPendingOps(): Array; /** - * Creates a reference to a subclass of Parse.Object with the given id. This - * does not exist on Parse.Object, only on subclasses. - * - *

A shortcut for:

-     *  var Foo = Parse.Object.extend("Foo");
-     *  var pointerToFoo = new Foo();
-     *  pointerToFoo.id = "myObjectId";
-     * 
- * - * @param {string} id The ID of the object to create a reference to. - * @static - * @returns {Parse.Object} A Parse.Object reference. + * @param {Array} [keysToClear] - if specified, only ops matching + * these fields will be cleared */ - static createWithoutData(id: string): Parse.Object; + _clearPendingOps(keysToClear?: Array): void; + _getDirtyObjectAttributes(): AttributeMap; + _toFullJSON(seen?: Array, offline?: boolean): AttributeMap; + _getSaveJSON(): AttributeMap; + _getSaveParams(): SaveParams; + _finishFetch(serverData: AttributeMap): void; + _setExisted(existed: boolean): void; + _migrateId(serverId: string): void; + _handleSaveResponse(response: AttributeMap, status: number): void; + _handleSaveError(): void; + static _getClassMap(): {}; + initialize(): void; /** - * Creates a new instance of a Parse Object from a JSON representation. + * Returns a JSON version of the object suitable for saving to Parse. * - * @param {object} json The JSON map of the Object's data - * @param {boolean} override In single instance mode, all old server data - * is overwritten if this is set to true - * @param {boolean} dirty Whether the Parse.Object should set JSON keys to dirty - * @static - * @returns {Parse.Object} A Parse.Object reference + * @param seen + * @param offline + * @returns {object} */ - static fromJSON(json: any, override?: boolean, dirty?: boolean): Parse.Object; + toJSON(seen: Array | void, offline?: boolean): AttributeMap; /** - * Registers a subclass of Parse.Object with a specific class name. - * When objects of that class are retrieved from a query, they will be - * instantiated with this subclass. - * This is only necessary when using ES6 subclassing. + * Determines whether this ParseObject is equal to another ParseObject * - * @param {string} className The class name of the subclass - * @param {Function} constructor The subclass + * @param {object} other - An other object ot compare + * @returns {boolean} */ - static registerSubclass(className: string, constructor: any): void; + equals(other: any): boolean; /** - * Unegisters a subclass of Parse.Object with a specific class name. + * Returns true if this object has been modified since its last + * save/refresh. If an attribute is specified, it returns true only if that + * particular attribute has been modified since the last save/refresh. * - * @param {string} className The class name of the subclass + * @param {string} attr An attribute name (optional). + * @returns {boolean} */ - static unregisterSubclass(className: string): void; + dirty(attr?: string): boolean; /** - * Creates a new subclass of Parse.Object for the given Parse class name. - * - *

Every extension of a Parse class will inherit from the most recent - * previous extension of that class. When a Parse.Object is automatically - * created by parsing JSON, it will use the most recent extension of that - * class.

- * - *

You should call either:

-     *     var MyClass = Parse.Object.extend("MyClass", {
-     *         Instance methods,
-     *         initialize: function(attrs, options) {
-     *             this.someInstanceProperty = [],
-     *             Other instance properties
-     *         }
-     *     }, {
-     *         Class properties
-     *     });
- * or, for Backbone compatibility:
-     *     var MyClass = Parse.Object.extend({
-     *         className: "MyClass",
-     *         Instance methods,
-     *         initialize: function(attrs, options) {
-     *             this.someInstanceProperty = [],
-     *             Other instance properties
-     *         }
-     *     }, {
-     *         Class properties
-     *     });

- * - * @param {string} className The name of the Parse class backing this model. - * @param {object} protoProps Instance properties to add to instances of the - * class returned from this method. - * @param {object} classProps Class properties to add the class returned from - * this method. - * @returns {Parse.Object} A new subclass of Parse.Object. - */ - static extend(className: any, protoProps: any, classProps: any): Parse.Object; - /** - * Enable single instance objects, where any local objects with the same Id - * share the same attributes, and stay synchronized with each other. - * This is disabled by default in server environments, since it can lead to - * security issues. - * - * @static - */ - static enableSingleInstance(): void; - /** - * Disable single instance objects, where any local objects with the same Id - * share the same attributes, and stay synchronized with each other. - * When disabled, you can have two instances of the same object in memory - * without them sharing attributes. - * - * @static - */ - static disableSingleInstance(): void; - /** - * Asynchronously stores the objects and every object they point to in the local datastore, - * recursively, using a default pin name: _default. - * - * If those other objects have not been fetched from Parse, they will not be stored. - * However, if they have changed data, all the changes will be retained. - * - *
-     * await Parse.Object.pinAll([...]);
-     * 
- * - * To retrieve object: - * query.fromLocalDatastore() or query.fromPin() - * - * @param {Array} objects A list of Parse.Object. - * @returns {Promise} A promise that is fulfilled when the pin completes. - * @static - */ - static pinAll(objects: Array): Promise; - /** - * Asynchronously stores the objects and every object they point to in the local datastore, recursively. - * - * If those other objects have not been fetched from Parse, they will not be stored. - * However, if they have changed data, all the changes will be retained. - * - *
-     * await Parse.Object.pinAllWithName(name, [obj1, obj2, ...]);
-     * 
- * - * To retrieve object: - * query.fromLocalDatastore() or query.fromPinWithName(name) - * - * @param {string} name Name of Pin. - * @param {Array} objects A list of Parse.Object. - * @returns {Promise} A promise that is fulfilled when the pin completes. - * @static - */ - static pinAllWithName(name: string, objects: Array): Promise; - /** - * Asynchronously removes the objects and every object they point to in the local datastore, - * recursively, using a default pin name: _default. - * - *
-     * await Parse.Object.unPinAll([...]);
-     * 
- * - * @param {Array} objects A list of Parse.Object. - * @returns {Promise} A promise that is fulfilled when the unPin completes. - * @static - */ - static unPinAll(objects: Array): Promise; - /** - * Asynchronously removes the objects and every object they point to in the local datastore, recursively. - * - *
-     * await Parse.Object.unPinAllWithName(name, [obj1, obj2, ...]);
-     * 
- * - * @param {string} name Name of Pin. - * @param {Array} objects A list of Parse.Object. - * @returns {Promise} A promise that is fulfilled when the unPin completes. - * @static - */ - static unPinAllWithName(name: string, objects: Array): Promise; - /** - * Asynchronously removes all objects in the local datastore using a default pin name: _default. - * - *
-     * await Parse.Object.unPinAllObjects();
-     * 
- * - * @returns {Promise} A promise that is fulfilled when the unPin completes. - * @static - */ - static unPinAllObjects(): Promise; - /** - * Asynchronously removes all objects with the specified pin name. - * Deletes the pin name also. - * - *
-     * await Parse.Object.unPinAllObjectsWithName(name);
-     * 
- * - * @param {string} name Name of Pin. - * @returns {Promise} A promise that is fulfilled when the unPin completes. - * @static - */ - static unPinAllObjectsWithName(name: string): Promise; - /** - * @param {string} className The class name for the object - * @param {object} attributes The initial set of data to store in the object. - * @param {object} options The options for this object instance. - */ - constructor(className: string | { - [attr: string]: mixed; - className: string; - }, attributes?: { - [attr: string]: mixed; - }, options?: { - ignoreValidation: boolean; - }, ...args: any[]); - _objCount: number; - className: string; - /** - * The ID of this object, unique within its class. - * - * @property {string} id - */ - id: string | null; - _localId: string | null; - get attributes(): AttributeMap; - /** - * The first time this object was saved on the server. - * - * @property {Date} createdAt - * @returns {Date} - */ - get createdAt(): Date; - /** - * The last time this object was updated on the server. - * - * @property {Date} updatedAt - * @returns {Date} - */ - get updatedAt(): Date; - /** - * Returns a local or server Id used uniquely identify this object - * - * @returns {string} - */ - _getId(): string; - /** - * Returns a unique identifier used to pull data from the State Controller. - * - * @returns {Parse.Object|object} - */ - _getStateIdentifier(): ParseObject | { - id: string; - className: string; - }; - _getServerData(): AttributeMap; - _clearServerData(): void; - _getPendingOps(): Array; - /** - * @param {Array} [keysToClear] - if specified, only ops matching - * these fields will be cleared - */ - _clearPendingOps(keysToClear?: Array): void; - _getDirtyObjectAttributes(): AttributeMap; - _toFullJSON(seen?: Array, offline?: boolean): AttributeMap; - _getSaveJSON(): AttributeMap; - _getSaveParams(): SaveParams; - _finishFetch(serverData: AttributeMap): void; - _setExisted(existed: boolean): void; - _migrateId(serverId: string): void; - _handleSaveResponse(response: AttributeMap, status: number): void; - _handleSaveError(): void; - initialize(): void; - /** - * Returns a JSON version of the object suitable for saving to Parse. - * - * @param seen - * @param offline - * @returns {object} - */ - toJSON(seen: Array | void, offline?: boolean): AttributeMap; - /** - * Determines whether this ParseObject is equal to another ParseObject - * - * @param {object} other - An other object ot compare - * @returns {boolean} - */ - equals(other: mixed): boolean; - /** - * Returns true if this object has been modified since its last - * save/refresh. If an attribute is specified, it returns true only if that - * particular attribute has been modified since the last save/refresh. - * - * @param {string} attr An attribute name (optional). - * @returns {boolean} - */ - dirty(attr?: string): boolean; - /** - * Returns an array of keys that have been modified since last save/refresh + * Returns an array of keys that have been modified since last save/refresh * * @returns {string[]} */ @@ -514,7 +170,7 @@ declare class ParseObject { * @param {string} attr The string name of an attribute. * @returns {*} */ - get(attr: string): mixed; + get(attr: string): any; /** * Gets a relation on the given class for the attribute. * @@ -567,7 +223,7 @@ declare class ParseObject { * The only supported option is error. * @returns {(ParseObject|boolean)} true if the set succeeded. */ - set(key: mixed, value: mixed, options?: mixed): ParseObject | boolean; + set(key: any, value?: any, options?: any): ParseObject | boolean; /** * Remove an attribute from the model. This is a noop if the attribute doesn't * exist. @@ -577,7 +233,7 @@ declare class ParseObject { * @returns {(ParseObject | boolean)} */ unset(attr: string, options?: { - [opt: string]: mixed; + [opt: string]: any; }): ParseObject | boolean; /** * Atomically increments the value of the given attribute the next time the @@ -605,7 +261,7 @@ declare class ParseObject { * @param item {} The item to add. * @returns {(ParseObject | boolean)} */ - add(attr: string, item: mixed): ParseObject | boolean; + add(attr: string, item: any): ParseObject | boolean; /** * Atomically add the objects to the end of the array associated with a given * key. @@ -614,7 +270,7 @@ declare class ParseObject { * @param items {Object[]} The items to add. * @returns {(ParseObject | boolean)} */ - addAll(attr: string, items: Array): ParseObject | boolean; + addAll(attr: string, items: Array): ParseObject | boolean; /** * Atomically add an object to the array associated with a given key, only * if it is not already present in the array. The position of the insert is @@ -624,7 +280,7 @@ declare class ParseObject { * @param item {} The object to add. * @returns {(ParseObject | boolean)} */ - addUnique(attr: string, item: mixed): ParseObject | boolean; + addUnique(attr: string, item: any): ParseObject | boolean; /** * Atomically add the objects to the array associated with a given key, only * if it is not already present in the array. The position of the insert is @@ -634,7 +290,7 @@ declare class ParseObject { * @param items {Object[]} The objects to add. * @returns {(ParseObject | boolean)} */ - addAllUnique(attr: string, items: Array): ParseObject | boolean; + addAllUnique(attr: string, items: Array): ParseObject | boolean; /** * Atomically remove all instances of an object from the array associated * with a given key. @@ -643,7 +299,7 @@ declare class ParseObject { * @param item {} The object to remove. * @returns {(ParseObject | boolean)} */ - remove(attr: string, item: mixed): ParseObject | boolean; + remove(attr: string, item: any): ParseObject | boolean; /** * Atomically remove all instances of the objects from the array associated * with a given key. @@ -652,7 +308,7 @@ declare class ParseObject { * @param items {Object[]} The object to remove. * @returns {(ParseObject | boolean)} */ - removeAll(attr: string, items: Array): ParseObject | boolean; + removeAll(attr: string, items: Array): ParseObject | boolean; /** * Returns an instance of a subclass of Parse.Op describing what kind of * modification has been performed on this field since the last time it was @@ -662,7 +318,7 @@ declare class ParseObject { * @param attr {String} The key. * @returns {Parse.Op | undefined} The operation, or undefined if none. */ - op(attr: string): Op | null; + op(attr: string): Op | undefined; /** * Creates a new model with identical attributes to this one. * @@ -722,7 +378,7 @@ declare class ParseObject { /** * Returns the ACL for this object. * - * @returns {Parse.ACL} An instance of Parse.ACL. + * @returns {Parse.ACL|null} An instance of Parse.ACL. * @see Parse.Object#get */ getACL(): ParseACL | null; @@ -734,13 +390,13 @@ declare class ParseObject { * @returns {(ParseObject | boolean)} Whether the set passed validation. * @see Parse.Object#set */ - setACL(acl: ParseACL, options?: mixed): ParseObject | boolean; + setACL(acl: ParseACL, options?: any): ParseObject | boolean; /** * Clears any (or specific) changes to this object made since the last call to save() * * @param {string} [keys] - specify which fields to revert */ - revert(...keys?: Array): void; + revert(...keys: Array): void; /** * Clears all attributes on a model * @@ -764,7 +420,7 @@ declare class ParseObject { * @returns {Promise} A promise that is fulfilled when the fetch * completes. */ - fetch(options: RequestOptions): Promise; + fetch(options: FetchOptions): Promise; /** * Fetch the model from the server. If the server's representation of the * model differs from its current attributes, they will be overriden. @@ -783,225 +439,577 @@ declare class ParseObject { * @returns {Promise} A promise that is fulfilled when the fetch * completes. */ - fetchWithInclude(keys: String | Array>, options: RequestOptions): Promise; + fetchWithInclude(keys: String | Array>, options: RequestOptions): Promise; + /** + * Saves this object to the server at some unspecified time in the future, + * even if Parse is currently inaccessible. + * + * Use this when you may not have a solid network connection, and don't need to know when the save completes. + * If there is some problem with the object such that it can't be saved, it will be silently discarded. + * + * Objects saved with this method will be stored locally in an on-disk cache until they can be delivered to Parse. + * They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection is + * available. Objects saved this way will persist even after the app is closed, in which case they will be sent the + * next time the app is opened. + * + * @param {object} [options] + * Used to pass option parameters to method if arg1 and arg2 were both passed as strings. + * Valid options are: + *
    + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
  • cascadeSave: If `false`, nested objects will not be saved (default is `true`). + *
  • context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers. + *
+ * @returns {Promise} A promise that is fulfilled when the save + * completes. + */ + saveEventually(options: SaveOptions): Promise; + /** + * Set a hash of model attributes, and save the model to the server. + * updatedAt will be updated when the request returns. + * You can either call it as:
+     * object.save();
+ * or
+     * object.save(attrs);
+ * or
+     * object.save(null, options);
+ * or
+     * object.save(attrs, options);
+ * or
+     * object.save(key, value);
+ * or
+     * object.save(key, value, options);
+ * + * Example 1:
+     * gameTurn.save({
+     * player: "Jake Cutter",
+     * diceRoll: 2
+     * }).then(function(gameTurnAgain) {
+     * // The save was successful.
+     * }, function(error) {
+     * // The save failed.  Error is an instance of Parse.Error.
+     * });
+ * + * Example 2:
+     * gameTurn.save("player", "Jake Cutter");
+ * + * @param {string | object | null} [arg1] + * Valid options are:
    + *
  • `Object` - Key/value pairs to update on the object.
  • + *
  • `String` Key - Key of attribute to update (requires arg2 to also be string)
  • + *
  • `null` - Passing null for arg1 allows you to save the object with options passed in arg2.
  • + *
+ * @param {string | object} [arg2] + *
    + *
  • `String` Value - If arg1 was passed as a key, arg2 is the value that should be set on that key.
  • + *
  • `Object` Options - Valid options are: + *
      + *
    • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
    • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
    • cascadeSave: If `false`, nested objects will not be saved (default is `true`). + *
    • context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers. + *
    + *
  • + *
+ * @param {object} [arg3] + * Used to pass option parameters to method if arg1 and arg2 were both passed as strings. + * Valid options are: + *
    + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
  • cascadeSave: If `false`, nested objects will not be saved (default is `true`). + *
  • context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers. + *
+ * @returns {Promise} A promise that is fulfilled when the save + * completes. + */ + save(arg1: undefined | string | { + [attr: string]: any; + } | null, arg2: SaveOptions | any, arg3?: SaveOptions): Promise; + /** + * Deletes this object from the server at some unspecified time in the future, + * even if Parse is currently inaccessible. + * + * Use this when you may not have a solid network connection, + * and don't need to know when the delete completes. If there is some problem with the object + * such that it can't be deleted, the request will be silently discarded. + * + * Delete instructions made with this method will be stored locally in an on-disk cache until they can be transmitted + * to Parse. They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection + * is available. Delete requests will persist even after the app is closed, in which case they will be sent the + * next time the app is opened. + * + * @param {object} [options] + * Valid options are:
    + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
  • context: A dictionary that is accessible in Cloud Code `beforeDelete` and `afterDelete` triggers. + *
+ * @returns {Promise} A promise that is fulfilled when the destroy + * completes. + */ + destroyEventually(options: RequestOptions): Promise; + /** + * Destroy this model on the server if it was already persisted. + * + * @param {object} options + * Valid options are:
    + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
  • context: A dictionary that is accessible in Cloud Code `beforeDelete` and `afterDelete` triggers. + *
+ * @returns {Promise} A promise that is fulfilled when the destroy + * completes. + */ + destroy(options: RequestOptions): Promise; + /** + * Asynchronously stores the object and every object it points to in the local datastore, + * recursively, using a default pin name: _default. + * + * If those other objects have not been fetched from Parse, they will not be stored. + * However, if they have changed data, all the changes will be retained. + * + *
+     * await object.pin();
+     * 
+ * + * To retrieve object: + * query.fromLocalDatastore() or query.fromPin() + * + * @returns {Promise} A promise that is fulfilled when the pin completes. + */ + pin(): Promise; + /** + * Asynchronously removes the object and every object it points to in the local datastore, + * recursively, using a default pin name: _default. + * + *
+     * await object.unPin();
+     * 
+ * + * @returns {Promise} A promise that is fulfilled when the unPin completes. + */ + unPin(): Promise; + /** + * Asynchronously returns if the object is pinned + * + *
+     * const isPinned = await object.isPinned();
+     * 
+ * + * @returns {Promise} A boolean promise that is fulfilled if object is pinned. + */ + isPinned(): Promise; + /** + * Asynchronously stores the objects and every object they point to in the local datastore, recursively. + * + * If those other objects have not been fetched from Parse, they will not be stored. + * However, if they have changed data, all the changes will be retained. + * + *
+     * await object.pinWithName(name);
+     * 
+ * + * To retrieve object: + * query.fromLocalDatastore() or query.fromPinWithName(name) + * + * @param {string} name Name of Pin. + * @returns {Promise} A promise that is fulfilled when the pin completes. + */ + pinWithName(name: string): Promise; + /** + * Asynchronously removes the object and every object it points to in the local datastore, recursively. + * + *
+     * await object.unPinWithName(name);
+     * 
+ * + * @param {string} name Name of Pin. + * @returns {Promise} A promise that is fulfilled when the unPin completes. + */ + unPinWithName(name: string): Promise; + /** + * Asynchronously loads data from the local datastore into this object. + * + *
+     * await object.fetchFromLocalDatastore();
+     * 
+ * + * You can create an unfetched pointer with Parse.Object.createWithoutData() + * and then call fetchFromLocalDatastore() on it. + * + * @returns {Promise} A promise that is fulfilled when the fetch completes. + */ + fetchFromLocalDatastore(): Promise; + static _clearAllState(): void; + /** + * Fetches the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
+     *   Parse.Object.fetchAll([object1, object2, ...])
+     *    .then((list) => {
+     *      // All the objects were fetched.
+     *    }, (error) => {
+     *      // An error occurred while fetching one of the objects.
+     *    });
+     * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {object} options + * Valid options are:
    + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
  • include: The name(s) of the key(s) to include. Can be a string, an array of strings, + * or an array of array of strings. + *
+ * @static + * @returns {Parse.Object[]} + */ + static fetchAll(list: Array, options?: RequestOptions): Promise; + /** + * Fetches the given list of Parse.Object. + * + * Includes nested Parse.Objects for the provided key. You can use dot + * notation to specify which fields in the included object are also fetched. + * + * If any error is encountered, stops and calls the error handler. + * + *
+     *   Parse.Object.fetchAllWithInclude([object1, object2, ...], [pointer1, pointer2, ...])
+     *    .then((list) => {
+     *      // All the objects were fetched.
+     *    }, (error) => {
+     *      // An error occurred while fetching one of the objects.
+     *    });
+     * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {string | Array>} keys The name(s) of the key(s) to include. + * @param {object} options + * Valid options are:
    + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * @static + * @returns {Parse.Object[]} + */ + static fetchAllWithInclude(list: Array, keys: String | Array>, options: RequestOptions): Promise; + /** + * Fetches the given list of Parse.Object if needed. + * If any error is encountered, stops and calls the error handler. + * + * Includes nested Parse.Objects for the provided key. You can use dot + * notation to specify which fields in the included object are also fetched. + * + * If any error is encountered, stops and calls the error handler. + * + *
+     *   Parse.Object.fetchAllIfNeededWithInclude([object1, object2, ...], [pointer1, pointer2, ...])
+     *    .then((list) => {
+     *      // All the objects were fetched.
+     *    }, (error) => {
+     *      // An error occurred while fetching one of the objects.
+     *    });
+     * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {string | Array>} keys The name(s) of the key(s) to include. + * @param {object} options + * Valid options are:
    + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * @static + * @returns {Parse.Object[]} + */ + static fetchAllIfNeededWithInclude(list: Array, keys: String | Array>, options: RequestOptions): Promise; + /** + * Fetches the given list of Parse.Object if needed. + * If any error is encountered, stops and calls the error handler. + * + *
+     *   Parse.Object.fetchAllIfNeeded([object1, ...])
+     *    .then((list) => {
+     *      // Objects were fetched and updated.
+     *    }, (error) => {
+     *      // An error occurred while fetching one of the objects.
+     *    });
+     * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {object} options + * @static + * @returns {Parse.Object[]} + */ + static fetchAllIfNeeded(list: Array, options: FetchOptions): Promise; + static handleIncludeOptions(options: { + include?: string | string[]; + }): any[]; + /** + * Destroy the given list of models on the server if it was already persisted. + * + *

Unlike saveAll, if an error occurs while deleting an individual model, + * this method will continue trying to delete the rest of the models if + * possible, except in the case of a fatal error like a connection error. + * + *

In particular, the Parse.Error object returned in the case of error may + * be one of two types: + * + *

    + *
  • A Parse.Error.AGGREGATE_ERROR. This object's "errors" property is an + * array of other Parse.Error objects. Each error object in this array + * has an "object" property that references the object that could not be + * deleted (for instance, because that object could not be found).
  • + *
  • A non-aggregate Parse.Error. This indicates a serious error that + * caused the delete operation to be aborted partway through (for + * instance, a connection failure in the middle of the delete).
  • + *
+ * + *
+     * Parse.Object.destroyAll([object1, object2, ...])
+     * .then((list) => {
+     * // All the objects were deleted.
+     * }, (error) => {
+     * // An error occurred while deleting one or more of the objects.
+     * // If this is an aggregate error, then we can inspect each error
+     * // object individually to determine the reason why a particular
+     * // object was not deleted.
+     * if (error.code === Parse.Error.AGGREGATE_ERROR) {
+     * for (var i = 0; i < error.errors.length; i++) {
+     * console.log("Couldn't delete " + error.errors[i].object.id +
+     * "due to " + error.errors[i].message);
+     * }
+     * } else {
+     * console.log("Delete aborted because of " + error.message);
+     * }
+     * });
+     * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {object} options + * @static + * @returns {Promise} A promise that is fulfilled when the destroyAll + * completes. + */ + static destroyAll(list: Array, options?: SaveOptions): Promise; + /** + * Saves the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
+     * Parse.Object.saveAll([object1, object2, ...])
+     * .then((list) => {
+     * // All the objects were saved.
+     * }, (error) => {
+     * // An error occurred while saving one of the objects.
+     * });
+     * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {object} options + * @static + * @returns {Parse.Object[]} + */ + static saveAll(list: Array, options?: SaveOptions): Promise; + /** + * Creates a reference to a subclass of Parse.Object with the given id. This + * does not exist on Parse.Object, only on subclasses. + * + *

A shortcut for:

+     *  var Foo = Parse.Object.extend("Foo");
+     *  var pointerToFoo = new Foo();
+     *  pointerToFoo.id = "myObjectId";
+     * 
+ * + * @param {string} id The ID of the object to create a reference to. + * @static + * @returns {Parse.Object} A Parse.Object reference. + */ + static createWithoutData(id: string): ParseObject; + /** + * Creates a new instance of a Parse Object from a JSON representation. + * + * @param {object} json The JSON map of the Object's data + * @param {boolean} override In single instance mode, all old server data + * is overwritten if this is set to true + * @param {boolean} dirty Whether the Parse.Object should set JSON keys to dirty + * @static + * @returns {Parse.Object} A Parse.Object reference + */ + static fromJSON(json: any, override?: boolean, dirty?: boolean): ParseObject; /** - * Saves this object to the server at some unspecified time in the future, - * even if Parse is currently inaccessible. - * - * Use this when you may not have a solid network connection, and don't need to know when the save completes. - * If there is some problem with the object such that it can't be saved, it will be silently discarded. + * Registers a subclass of Parse.Object with a specific class name. + * When objects of that class are retrieved from a query, they will be + * instantiated with this subclass. + * This is only necessary when using ES6 subclassing. * - * Objects saved with this method will be stored locally in an on-disk cache until they can be delivered to Parse. - * They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection is - * available. Objects saved this way will persist even after the app is closed, in which case they will be sent the - * next time the app is opened. + * @param {string} className The class name of the subclass + * @param {Function} constructor The subclass + */ + static registerSubclass(className: string, constructor: any): void; + /** + * Unegisters a subclass of Parse.Object with a specific class name. * - * @param {object} [options] - * Used to pass option parameters to method if arg1 and arg2 were both passed as strings. - * Valid options are: - *
    - *
  • sessionToken: A valid session token, used for making a request on - * behalf of a specific user. - *
  • cascadeSave: If `false`, nested objects will not be saved (default is `true`). - *
  • context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers. - *
- * @returns {Promise} A promise that is fulfilled when the save - * completes. + * @param {string} className The class name of the subclass */ - saveEventually(options?: SaveOptions): Promise; + static unregisterSubclass(className: string): void; /** - * Set a hash of model attributes, and save the model to the server. - * updatedAt will be updated when the request returns. - * You can either call it as:
-     * object.save();
- * or
-     * object.save(attrs);
- * or
-     * object.save(null, options);
- * or
-     * object.save(attrs, options);
- * or
-     * object.save(key, value);
- * or
-     * object.save(key, value, options);
+ * Creates a new subclass of Parse.Object for the given Parse class name. * - * Example 1:
-     * gameTurn.save({
-     * player: "Jake Cutter",
-     * diceRoll: 2
-     * }).then(function(gameTurnAgain) {
-     * // The save was successful.
-     * }, function(error) {
-     * // The save failed.  Error is an instance of Parse.Error.
-     * });
+ *

Every extension of a Parse class will inherit from the most recent + * previous extension of that class. When a Parse.Object is automatically + * created by parsing JSON, it will use the most recent extension of that + * class.

* - * Example 2:
-     * gameTurn.save("player", "Jake Cutter");
+ *

You should call either:

+     *     var MyClass = Parse.Object.extend("MyClass", {
+     *         Instance methods,
+     *         initialize: function(attrs, options) {
+     *             this.someInstanceProperty = [],
+     *             Other instance properties
+     *         }
+     *     }, {
+     *         Class properties
+     *     });
+ * or, for Backbone compatibility:
+     *     var MyClass = Parse.Object.extend({
+     *         className: "MyClass",
+     *         Instance methods,
+     *         initialize: function(attrs, options) {
+     *             this.someInstanceProperty = [],
+     *             Other instance properties
+     *         }
+     *     }, {
+     *         Class properties
+     *     });

* - * @param {string | object | null} [arg1] - * Valid options are:
    - *
  • `Object` - Key/value pairs to update on the object.
  • - *
  • `String` Key - Key of attribute to update (requires arg2 to also be string)
  • - *
  • `null` - Passing null for arg1 allows you to save the object with options passed in arg2.
  • - *
- * @param {string | object} [arg2] - *
    - *
  • `String` Value - If arg1 was passed as a key, arg2 is the value that should be set on that key.
  • - *
  • `Object` Options - Valid options are: - *
      - *
    • useMasterKey: In Cloud Code and Node only, causes the Master Key to - * be used for this request. - *
    • sessionToken: A valid session token, used for making a request on - * behalf of a specific user. - *
    • cascadeSave: If `false`, nested objects will not be saved (default is `true`). - *
    • context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers. - *
    - *
  • - *
- * @param {object} [arg3] - * Used to pass option parameters to method if arg1 and arg2 were both passed as strings. - * Valid options are: - *
    - *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to - * be used for this request. - *
  • sessionToken: A valid session token, used for making a request on - * behalf of a specific user. - *
  • cascadeSave: If `false`, nested objects will not be saved (default is `true`). - *
  • context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers. - *
- * @returns {Promise} A promise that is fulfilled when the save - * completes. + * @param {string} className The name of the Parse class backing this model. + * @param {object} protoProps Instance properties to add to instances of the + * class returned from this method. + * @param {object} classProps Class properties to add the class returned from + * this method. + * @returns {Parse.Object} A new subclass of Parse.Object. */ - save(arg1?: string | { - [attr: string]: mixed; - }, arg2?: SaveOptions | mixed, arg3?: SaveOptions): Promise; + static extend(className: any, protoProps: any, classProps: any): any; /** - * Deletes this object from the server at some unspecified time in the future, - * even if Parse is currently inaccessible. - * - * Use this when you may not have a solid network connection, - * and don't need to know when the delete completes. If there is some problem with the object - * such that it can't be deleted, the request will be silently discarded. - * - * Delete instructions made with this method will be stored locally in an on-disk cache until they can be transmitted - * to Parse. They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection - * is available. Delete requests will persist even after the app is closed, in which case they will be sent the - * next time the app is opened. + * Enable single instance objects, where any local objects with the same Id + * share the same attributes, and stay synchronized with each other. + * This is disabled by default in server environments, since it can lead to + * security issues. * - * @param {object} [options] - * Valid options are:
    - *
  • sessionToken: A valid session token, used for making a request on - * behalf of a specific user. - *
  • context: A dictionary that is accessible in Cloud Code `beforeDelete` and `afterDelete` triggers. - *
- * @returns {Promise} A promise that is fulfilled when the destroy - * completes. + * @static */ - destroyEventually(options?: RequestOptions): Promise; + static enableSingleInstance(): void; /** - * Destroy this model on the server if it was already persisted. + * Disable single instance objects, where any local objects with the same Id + * share the same attributes, and stay synchronized with each other. + * When disabled, you can have two instances of the same object in memory + * without them sharing attributes. * - * @param {object} options - * Valid options are:
    - *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to - * be used for this request. - *
  • sessionToken: A valid session token, used for making a request on - * behalf of a specific user. - *
  • context: A dictionary that is accessible in Cloud Code `beforeDelete` and `afterDelete` triggers. - *
- * @returns {Promise} A promise that is fulfilled when the destroy - * completes. + * @static */ - destroy(options: RequestOptions): Promise; + static disableSingleInstance(): void; /** - * Asynchronously stores the object and every object it points to in the local datastore, + * Asynchronously stores the objects and every object they point to in the local datastore, * recursively, using a default pin name: _default. * * If those other objects have not been fetched from Parse, they will not be stored. * However, if they have changed data, all the changes will be retained. * *
-     * await object.pin();
+     * await Parse.Object.pinAll([...]);
      * 
* * To retrieve object: * query.fromLocalDatastore() or query.fromPin() * + * @param {Array} objects A list of Parse.Object. * @returns {Promise} A promise that is fulfilled when the pin completes. + * @static */ - pin(): Promise; + static pinAll(objects: Array): Promise; /** - * Asynchronously removes the object and every object it points to in the local datastore, - * recursively, using a default pin name: _default. + * Asynchronously stores the objects and every object they point to in the local datastore, recursively. + * + * If those other objects have not been fetched from Parse, they will not be stored. + * However, if they have changed data, all the changes will be retained. * *
-     * await object.unPin();
+     * await Parse.Object.pinAllWithName(name, [obj1, obj2, ...]);
      * 
* - * @returns {Promise} A promise that is fulfilled when the unPin completes. + * To retrieve object: + * query.fromLocalDatastore() or query.fromPinWithName(name) + * + * @param {string} name Name of Pin. + * @param {Array} objects A list of Parse.Object. + * @returns {Promise} A promise that is fulfilled when the pin completes. + * @static */ - unPin(): Promise; + static pinAllWithName(name: string, objects: Array): Promise; /** - * Asynchronously returns if the object is pinned + * Asynchronously removes the objects and every object they point to in the local datastore, + * recursively, using a default pin name: _default. * *
-     * const isPinned = await object.isPinned();
+     * await Parse.Object.unPinAll([...]);
      * 
* - * @returns {Promise} A boolean promise that is fulfilled if object is pinned. + * @param {Array} objects A list of Parse.Object. + * @returns {Promise} A promise that is fulfilled when the unPin completes. + * @static */ - isPinned(): Promise; + static unPinAll(objects: Array): Promise; /** - * Asynchronously stores the objects and every object they point to in the local datastore, recursively. - * - * If those other objects have not been fetched from Parse, they will not be stored. - * However, if they have changed data, all the changes will be retained. + * Asynchronously removes the objects and every object they point to in the local datastore, recursively. * *
-     * await object.pinWithName(name);
+     * await Parse.Object.unPinAllWithName(name, [obj1, obj2, ...]);
      * 
* - * To retrieve object: - * query.fromLocalDatastore() or query.fromPinWithName(name) - * * @param {string} name Name of Pin. - * @returns {Promise} A promise that is fulfilled when the pin completes. + * @param {Array} objects A list of Parse.Object. + * @returns {Promise} A promise that is fulfilled when the unPin completes. + * @static */ - pinWithName(name: string): Promise; + static unPinAllWithName(name: string, objects: Array): Promise; /** - * Asynchronously removes the object and every object it points to in the local datastore, recursively. + * Asynchronously removes all objects in the local datastore using a default pin name: _default. * *
-     * await object.unPinWithName(name);
+     * await Parse.Object.unPinAllObjects();
      * 
* - * @param {string} name Name of Pin. * @returns {Promise} A promise that is fulfilled when the unPin completes. + * @static */ - unPinWithName(name: string): Promise; + static unPinAllObjects(): Promise; /** - * Asynchronously loads data from the local datastore into this object. + * Asynchronously removes all objects with the specified pin name. + * Deletes the pin name also. * *
-     * await object.fetchFromLocalDatastore();
+     * await Parse.Object.unPinAllObjectsWithName(name);
      * 
* - * You can create an unfetched pointer with Parse.Object.createWithoutData() - * and then call fetchFromLocalDatastore() on it. - * - * @returns {Promise} A promise that is fulfilled when the fetch completes. + * @param {string} name Name of Pin. + * @returns {Promise} A promise that is fulfilled when the unPin completes. + * @static */ - fetchFromLocalDatastore(): Promise; + static unPinAllObjectsWithName(name: string): Promise; } -import { AttributeMap as AttributeMap_1 } from './ObjectStateMutations'; -import { OpsMap } from './ObjectStateMutations'; -type SaveParams = { - method: string; - path: string; - body: AttributeMap; -}; -import ParseRelation from './ParseRelation'; -import { Op } from './ParseOp'; -import { RequestOptions } from './RESTController'; -import ParseError from './ParseError'; -import ParseACL from './ParseACL'; +export default ParseObject;