├── .all-contributorsrc ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── FirestoreEmulator ├── .firebaserc ├── firebase.json ├── firestore.indexes.json ├── firestore.rules ├── package-lock.json ├── package.json └── sample_data │ ├── firebase-export-metadata.json │ └── firestore_export │ ├── all_namespaces │ └── all_kinds │ │ ├── all_namespaces_all_kinds.export_metadata │ │ └── output-0 │ └── firestore_export.overall_export_metadata ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── Connection │ ├── class.ts │ ├── index.ts │ └── types │ │ └── ModelPOJO.ts ├── Document │ ├── class.ts │ ├── index.ts │ └── types │ │ ├── error.ts │ │ └── updateOptions.ts ├── Firefly │ ├── class.ts │ ├── index.ts │ └── types │ │ └── error.ts ├── Model │ ├── class.ts │ ├── index.ts │ └── types │ │ └── error.ts ├── Query │ ├── Base │ │ ├── class.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── buildExtQuery.ts │ │ │ ├── config.ts │ │ │ ├── errors.ts │ │ │ └── operators.ts │ ├── DeleteQuery │ │ ├── class.ts │ │ └── index.ts │ ├── MultipleQuery │ │ ├── class.ts │ │ ├── index.ts │ │ ├── types │ │ │ └── extConfig.ts │ │ └── utils │ │ │ └── buildExtQuery.ts │ ├── SingleQuery │ │ ├── class.ts │ │ ├── index.ts │ │ ├── types │ │ │ └── extConfig.ts │ │ └── utils │ │ │ └── buildExtQuery.ts │ ├── UpdateQuery │ │ ├── class.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── updateConfig.ts │ │ │ └── updateOptions.ts │ ├── index.ts │ └── utils │ │ ├── buildQuery.ts │ │ ├── convertDate.ts │ │ ├── operatorsTypeCheck.ts │ │ └── parseOperators.ts ├── SchemaTypes │ ├── Base │ │ ├── class.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── BaseError.ts │ │ │ ├── BaseSchema.ts │ │ │ └── ValidateFn.ts │ ├── Boolean │ │ ├── checks │ │ │ ├── allChecks.ts │ │ │ ├── index.ts │ │ │ └── typeCheck.ts │ │ ├── class.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── BooleanError.ts │ │ │ └── BooleanSchema.ts │ ├── Date │ │ ├── checks │ │ │ ├── allChecks.ts │ │ │ ├── index.ts │ │ │ ├── typeCheck.ts │ │ │ └── valueCheck.ts │ │ ├── class.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── DateError.ts │ │ │ └── DateSchema.ts │ ├── Number │ │ ├── checks │ │ │ ├── allChecks.ts │ │ │ ├── enumCheck.ts │ │ │ ├── index.ts │ │ │ ├── integerCheck.ts │ │ │ ├── typeCheck.ts │ │ │ └── valueCheck.ts │ │ ├── class.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── NumberError.ts │ │ │ └── NumberSchema.ts │ ├── Object │ │ ├── checks │ │ │ ├── allChecks.ts │ │ │ ├── index.ts │ │ │ ├── keyCheck.ts │ │ │ ├── patternCheck.ts │ │ │ ├── type.ts │ │ │ └── typeCheck.ts │ │ ├── class.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── KeyValue.ts │ │ │ ├── ObjectError.ts │ │ │ └── ObjectSchema.ts │ ├── String │ │ ├── checks │ │ │ ├── allChecks.ts │ │ │ ├── caseCheck.ts │ │ │ ├── enumCheck.ts │ │ │ ├── formatCheck.ts │ │ │ ├── index.ts │ │ │ ├── lengthCheck.ts │ │ │ └── typeCheck.ts │ │ ├── class.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── StringError.ts │ │ │ └── StringSchema.ts │ └── index.ts ├── __tests__ │ ├── Connection │ │ └── class.test.ts │ ├── Firefly │ │ └── class.test.ts │ ├── Query │ │ ├── class.test.ts │ │ └── operators.test.ts │ └── SchemaTypes │ │ ├── Boolean │ │ └── class.test.ts │ │ ├── Date │ │ └── class.test.ts │ │ ├── Object │ │ └── class.test.ts │ │ └── String │ │ └── class.test.ts ├── index.ts └── utils │ └── makeError.ts └── tsconfig.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "aniketbiswas21", 10 | "name": "Aniket Biswas", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/51146347?v=4", 12 | "profile": "https://aniket.codes/", 13 | "contributions": [ 14 | "code", 15 | "doc", 16 | "maintenance" 17 | ] 18 | }, 19 | { 20 | "login": "Aryaman1706", 21 | "name": "Aryaman Grover", 22 | "avatar_url": "https://avatars.githubusercontent.com/u/56519273?v=4", 23 | "profile": "https://github.com/Aryaman1706", 24 | "contributions": [ 25 | "code", 26 | "doc", 27 | "maintenance" 28 | ] 29 | } 30 | ], 31 | "contributorsPerLine": 7, 32 | "projectName": "fireflyjs-core", 33 | "projectOwner": "FireflyJS", 34 | "repoType": "github", 35 | "repoHost": "https://github.com", 36 | "skipCi": true 37 | } 38 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | tsconfig.json 3 | jest.config.js 4 | src/__tests__/ 5 | lib/ 6 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["airbnb-typescript/base", "plugin:prettier/recommended"], 3 | parserOptions: { 4 | project: "./tsconfig.json", 5 | }, 6 | plugins: ["prettier"], 7 | rules: { 8 | quotes: "off", 9 | "@typescript-eslint/quotes": [ 10 | "error", 11 | "double", 12 | { allowTemplateLiterals: true }, 13 | ], 14 | "no-console": "off", 15 | "no-underscore-dangle": "off", 16 | "import/prefer-default-export": "off", 17 | "no-useless-escape": "off", 18 | "no-control-regex": "off", 19 | "import/no-cycle": "off", 20 | "max-classes-per-file": "off", 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | on: 3 | push: 4 | branches: 5 | - "develop" 6 | 7 | pull_request: 8 | branches: 9 | - "develop" 10 | 11 | jobs: 12 | prepare: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup Node 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: "14" 22 | 23 | - name: Install typescript 24 | run: npm install -g typescript 25 | 26 | - name: Install dependencies 27 | run: npm clean-install 28 | 29 | - name: Compile typescript 30 | run: npm run compile 31 | 32 | - name: Format code 33 | run: npm run format 34 | 35 | - name: Lint and fix code 36 | run: npm run lint 37 | 38 | test: 39 | runs-on: ubuntu-latest 40 | needs: prepare 41 | steps: 42 | - name: Checkout code 43 | uses: actions/checkout@v2 44 | 45 | - name: Setup Node 46 | uses: actions/setup-node@v2 47 | with: 48 | node-version: "14" 49 | 50 | - name: Install dependencies 51 | run: npm clean-install 52 | 53 | - name: Install firebase 54 | run: sudo npm i -g firebase-tools 55 | 56 | - name: Setup Java 57 | uses: actions/setup-java@v2 58 | with: 59 | java-version: "11" 60 | distribution: "temurin" 61 | 62 | - name: Cache JAR files 63 | uses: actions/cache@v2 64 | with: 65 | path: ~/.cache/firebase/emulators/ 66 | key: emulator-jar-files 67 | 68 | - name: Run tests 69 | run: npm run test:ci 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | **/*.log -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run compile 5 | npm run format 6 | npm run lint 7 | npm run test 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | package-lock.json 3 | node_modules/ 4 | lib/ 5 | docs/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.enable": true, 3 | "eslint.format.enable": true 4 | } 5 | -------------------------------------------------------------------------------- /FirestoreEmulator/.firebaserc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /FirestoreEmulator/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "emulators": { 7 | "firestore": { 8 | "port": 8080 9 | }, 10 | "ui": { 11 | "enabled": true, 12 | "port": 8000 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FirestoreEmulator/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [], 3 | "fieldOverrides": [] 4 | } 5 | -------------------------------------------------------------------------------- /FirestoreEmulator/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | // This rule allows anyone with your database reference to view, edit, 5 | // and delete all data in your database. It is useful for getting 6 | // started, but it is configured to expire after 30 days because it 7 | // leaves your app open to attackers. At that time, all client 8 | // requests to your database will be denied. 9 | // 10 | // Make sure to write security rules for your app before that time, or 11 | // else all client requests to your database will be denied until you 12 | // update your rules. 13 | allow read, write: if request.time < timestamp.date(2021, 8, 22); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FirestoreEmulator/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firestore-emulator", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@firebase/analytics": { 8 | "version": "0.7.1", 9 | "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.7.1.tgz", 10 | "integrity": "sha512-fTUN47UK4obzIJwmgLMJU46dWZ7pzitCEO+80pQZC7mdLlVs/NW0+tMf6rETwMpKjGSgb25cKidpgEuioQtT7w==", 11 | "requires": { 12 | "@firebase/component": "0.5.7", 13 | "@firebase/installations": "0.5.1", 14 | "@firebase/logger": "0.3.0", 15 | "@firebase/util": "1.4.0", 16 | "tslib": "^2.1.0" 17 | } 18 | }, 19 | "@firebase/analytics-compat": { 20 | "version": "0.1.2", 21 | "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.1.2.tgz", 22 | "integrity": "sha512-TpWpz0s8EgVt9aqyOCFktONqVkjyrNRR4esn3cEYrueH+XXSMDLWY9oFHuUJzntcoEOlOBWMvpsJCPG/1kthkg==", 23 | "requires": { 24 | "@firebase/analytics": "0.7.1", 25 | "@firebase/analytics-types": "0.7.0", 26 | "@firebase/component": "0.5.7", 27 | "@firebase/util": "1.4.0", 28 | "tslib": "^2.1.0" 29 | } 30 | }, 31 | "@firebase/analytics-types": { 32 | "version": "0.7.0", 33 | "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.7.0.tgz", 34 | "integrity": "sha512-DNE2Waiwy5+zZnCfintkDtBfaW6MjIG883474v6Z0K1XZIvl76cLND4iv0YUb48leyF+PJK1KO2XrgHb/KpmhQ==" 35 | }, 36 | "@firebase/app": { 37 | "version": "0.7.2", 38 | "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.2.tgz", 39 | "integrity": "sha512-xKO3KWxVqCLijJToaBGvBnXCaVGvIw+rT2Dtd9B2iyOFJieQQ+xx8/zRWgoSqbMBIZ2crQVr0KdsoyP9D2nQfg==", 40 | "requires": { 41 | "@firebase/component": "0.5.7", 42 | "@firebase/logger": "0.3.0", 43 | "@firebase/util": "1.4.0", 44 | "tslib": "^2.1.0" 45 | } 46 | }, 47 | "@firebase/app-check": { 48 | "version": "0.4.1", 49 | "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.4.1.tgz", 50 | "integrity": "sha512-Kpqh0Y2zpx+acTL7eOVYIWBOmAwoconJpqOAlByGNXuxm/ccP00XREo+HsqaC7wapZRXh+h8BK0jZjvdV36qow==", 51 | "requires": { 52 | "@firebase/component": "0.5.7", 53 | "@firebase/logger": "0.3.0", 54 | "@firebase/util": "1.4.0", 55 | "tslib": "^2.1.0" 56 | } 57 | }, 58 | "@firebase/app-check-compat": { 59 | "version": "0.1.2", 60 | "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.1.2.tgz", 61 | "integrity": "sha512-JB+OHk4Cp9ZgT+UfX0A+lwH1AoM5Y2X1rDhmhCsEXcKKwz1w9DpL9PjStciP8UYg1dpqbp5p9OMxmty+21EBsA==", 62 | "requires": { 63 | "@firebase/app-check": "0.4.1", 64 | "@firebase/component": "0.5.7", 65 | "@firebase/logger": "0.3.0", 66 | "@firebase/util": "1.4.0", 67 | "tslib": "^2.1.0" 68 | } 69 | }, 70 | "@firebase/app-check-interop-types": { 71 | "version": "0.1.0", 72 | "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.1.0.tgz", 73 | "integrity": "sha512-uZfn9s4uuRsaX5Lwx+gFP3B6YsyOKUE+Rqa6z9ojT4VSRAsZFko9FRn6OxQUA1z5t5d08fY4pf+/+Dkd5wbdbA==" 74 | }, 75 | "@firebase/app-compat": { 76 | "version": "0.1.3", 77 | "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.3.tgz", 78 | "integrity": "sha512-+/U2RgRLfLznPuluIMW3bsAehTBTVWKxA6l6jjk9noozPuP99xOulReMqf5kCrXVdW1aMHdRuKfntjbTAR8+aw==", 79 | "requires": { 80 | "@firebase/app": "0.7.2", 81 | "@firebase/component": "0.5.7", 82 | "@firebase/logger": "0.3.0", 83 | "@firebase/util": "1.4.0", 84 | "tslib": "^2.1.0" 85 | } 86 | }, 87 | "@firebase/app-types": { 88 | "version": "0.7.0", 89 | "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", 90 | "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" 91 | }, 92 | "@firebase/auth": { 93 | "version": "0.18.1", 94 | "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.18.1.tgz", 95 | "integrity": "sha512-q455ls7Hjug3yGp7htLL/LABqySoXGXL/ADLJPyiSnVl22a5oQWuTKUL6N5PAXHc5LwygFfHYiHrNhpQDaGm3w==", 96 | "requires": { 97 | "@firebase/component": "0.5.7", 98 | "@firebase/logger": "0.3.0", 99 | "@firebase/util": "1.4.0", 100 | "node-fetch": "2.6.5", 101 | "selenium-webdriver": "4.0.0-rc-1", 102 | "tslib": "^2.1.0" 103 | } 104 | }, 105 | "@firebase/auth-compat": { 106 | "version": "0.1.4", 107 | "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.1.4.tgz", 108 | "integrity": "sha512-Vn7Dsxa7B50ihgDAMQAVb/IxU9tcQyR1JDbWjZzf2b1212hBuuwEs1V1u01xoKunMXMSg+P8ztbG7IRxOj2FdQ==", 109 | "requires": { 110 | "@firebase/auth": "0.18.1", 111 | "@firebase/auth-types": "0.11.0", 112 | "@firebase/component": "0.5.7", 113 | "@firebase/util": "1.4.0", 114 | "node-fetch": "2.6.5", 115 | "selenium-webdriver": "^4.0.0-beta.2", 116 | "tslib": "^2.1.0" 117 | } 118 | }, 119 | "@firebase/auth-interop-types": { 120 | "version": "0.1.6", 121 | "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", 122 | "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==" 123 | }, 124 | "@firebase/auth-types": { 125 | "version": "0.11.0", 126 | "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.11.0.tgz", 127 | "integrity": "sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==" 128 | }, 129 | "@firebase/component": { 130 | "version": "0.5.7", 131 | "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.7.tgz", 132 | "integrity": "sha512-CiAHUPXh2hn/lpzMShNmfAxHNQhKQwmQUJSYMPCjf2bCCt4Z2vLGpS+UWEuNFm9Zf8LNmkS+Z+U/s4Obi5carg==", 133 | "requires": { 134 | "@firebase/util": "1.4.0", 135 | "tslib": "^2.1.0" 136 | } 137 | }, 138 | "@firebase/database": { 139 | "version": "0.12.1", 140 | "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.1.tgz", 141 | "integrity": "sha512-Ethk0hc476qnkSKNBa+8Yc7iM8AO69HYWsaD+QUC983FZtnuMyNLHtEeSUbLQYvyHo7cOjcc52slop14WmfZeQ==", 142 | "requires": { 143 | "@firebase/auth-interop-types": "0.1.6", 144 | "@firebase/component": "0.5.7", 145 | "@firebase/logger": "0.3.0", 146 | "@firebase/util": "1.4.0", 147 | "faye-websocket": "0.11.4", 148 | "tslib": "^2.1.0" 149 | } 150 | }, 151 | "@firebase/database-compat": { 152 | "version": "0.1.1", 153 | "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.1.tgz", 154 | "integrity": "sha512-K3DFWiw0YkLZtlfA9TOGPw6zVXKu5dQ1XqIGztUufFVRYW8IizReXVxzSSmJNR4Adr2LiU9j66Wenc6e5UfwaQ==", 155 | "requires": { 156 | "@firebase/component": "0.5.7", 157 | "@firebase/database": "0.12.1", 158 | "@firebase/database-types": "0.9.1", 159 | "@firebase/logger": "0.3.0", 160 | "@firebase/util": "1.4.0", 161 | "tslib": "^2.1.0" 162 | } 163 | }, 164 | "@firebase/database-types": { 165 | "version": "0.9.1", 166 | "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.1.tgz", 167 | "integrity": "sha512-RUixK/YrbpxbfdE+nYP0wMcEsz1xPTnafP0q3UlSS/+fW744OITKtR1J0cMRaXbvY7EH0wUVTNVkrtgxYY8IgQ==", 168 | "requires": { 169 | "@firebase/app-types": "0.7.0", 170 | "@firebase/util": "1.4.0" 171 | } 172 | }, 173 | "@firebase/firestore": { 174 | "version": "3.1.0", 175 | "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.1.0.tgz", 176 | "integrity": "sha512-vOXueHNRjlgBlKVCWuUDhr3dQW2hJwbcqcJaFiIV9V+PamfyhOHzX8pEQkrPort4YQQvoRmY9uiFhfOGj2hbeA==", 177 | "requires": { 178 | "@firebase/component": "0.5.7", 179 | "@firebase/logger": "0.3.0", 180 | "@firebase/util": "1.4.0", 181 | "@firebase/webchannel-wrapper": "0.6.0", 182 | "@grpc/grpc-js": "^1.3.2", 183 | "@grpc/proto-loader": "^0.6.0", 184 | "node-fetch": "2.6.2", 185 | "tslib": "^2.1.0" 186 | }, 187 | "dependencies": { 188 | "node-fetch": { 189 | "version": "2.6.2", 190 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", 191 | "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==" 192 | } 193 | } 194 | }, 195 | "@firebase/firestore-compat": { 196 | "version": "0.1.3", 197 | "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.1.3.tgz", 198 | "integrity": "sha512-tO3uAkIguKeFeKPu99GR7F7v1/Hc8nV1h7B1kdpkVRRBe+NfVYA3qAUictQ3OAA0oy7Ae9z4SfEURO/R1b6YlQ==", 199 | "requires": { 200 | "@firebase/component": "0.5.7", 201 | "@firebase/firestore": "3.1.0", 202 | "@firebase/firestore-types": "2.5.0", 203 | "@firebase/util": "1.4.0", 204 | "tslib": "^2.1.0" 205 | } 206 | }, 207 | "@firebase/firestore-types": { 208 | "version": "2.5.0", 209 | "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.0.tgz", 210 | "integrity": "sha512-I6c2m1zUhZ5SH0cWPmINabDyH5w0PPFHk2UHsjBpKdZllzJZ2TwTkXbDtpHUZNmnc/zAa0WNMNMvcvbb/xJLKA==" 211 | }, 212 | "@firebase/functions": { 213 | "version": "0.7.2", 214 | "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.7.2.tgz", 215 | "integrity": "sha512-B+b57xXtpsRYD3UgVtteeyavXjXfBTtuv+sG8LA0vgJs6bhORswVlKZQqpfW9SDxCMBwzzytzn1m3ZZGfUw2Lg==", 216 | "requires": { 217 | "@firebase/app-check-interop-types": "0.1.0", 218 | "@firebase/auth-interop-types": "0.1.6", 219 | "@firebase/component": "0.5.7", 220 | "@firebase/messaging-interop-types": "0.1.0", 221 | "@firebase/util": "1.4.0", 222 | "node-fetch": "2.6.2", 223 | "tslib": "^2.1.0" 224 | }, 225 | "dependencies": { 226 | "node-fetch": { 227 | "version": "2.6.2", 228 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", 229 | "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==" 230 | } 231 | } 232 | }, 233 | "@firebase/functions-compat": { 234 | "version": "0.1.3", 235 | "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.1.3.tgz", 236 | "integrity": "sha512-NdobePNq5LUHCI1dJHUGlOTw+Qmq/FJre981/ELEMBdEs95kmKwnXB2UaLjAQYWkgkr4YS3lEnNpsiSTaEHFCg==", 237 | "requires": { 238 | "@firebase/component": "0.5.7", 239 | "@firebase/functions": "0.7.2", 240 | "@firebase/functions-types": "0.5.0", 241 | "@firebase/util": "1.4.0", 242 | "tslib": "^2.1.0" 243 | } 244 | }, 245 | "@firebase/functions-types": { 246 | "version": "0.5.0", 247 | "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.5.0.tgz", 248 | "integrity": "sha512-qza0M5EwX+Ocrl1cYI14zoipUX4gI/Shwqv0C1nB864INAD42Dgv4v94BCyxGHBg2kzlWy8PNafdP7zPO8aJQA==" 249 | }, 250 | "@firebase/installations": { 251 | "version": "0.5.1", 252 | "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.5.1.tgz", 253 | "integrity": "sha512-KZ1XHrEPmCx3Z70P9d8mHmYEZXA/uiLIkV0D8R45Q65c0DUxBDm5tSQs56QWofxB/wx16xmO3xAZw4BdJUBnlQ==", 254 | "requires": { 255 | "@firebase/component": "0.5.7", 256 | "@firebase/util": "1.4.0", 257 | "idb": "3.0.2", 258 | "tslib": "^2.1.0" 259 | } 260 | }, 261 | "@firebase/logger": { 262 | "version": "0.3.0", 263 | "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.0.tgz", 264 | "integrity": "sha512-7oQ+TctqekfgZImWkKuda50JZfkmAKMgh5qY4aR4pwRyqZXuJXN1H/BKkHvN1y0S4XWtF0f/wiCLKHhyi1ppPA==", 265 | "requires": { 266 | "tslib": "^2.1.0" 267 | } 268 | }, 269 | "@firebase/messaging": { 270 | "version": "0.9.1", 271 | "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.9.1.tgz", 272 | "integrity": "sha512-0g3JWTfkv0WHnu4xgx1zcChJXU2dLjWT0e2MI13Q7NbP3TgLu5CgQ/H/lad16j4Zb4RNqZbAUJurEAB6v2BJ/w==", 273 | "requires": { 274 | "@firebase/component": "0.5.7", 275 | "@firebase/installations": "0.5.1", 276 | "@firebase/messaging-interop-types": "0.1.0", 277 | "@firebase/util": "1.4.0", 278 | "idb": "3.0.2", 279 | "tslib": "^2.1.0" 280 | } 281 | }, 282 | "@firebase/messaging-compat": { 283 | "version": "0.1.1", 284 | "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.1.1.tgz", 285 | "integrity": "sha512-8FxrQjJCOfP9HibFsymT3qB18rBBmMPxOV+k0n6B7L6KW6Idswq01hMW12d93ZnvlNNKdikCKwUtBNbITBd8FA==", 286 | "requires": { 287 | "@firebase/component": "0.5.7", 288 | "@firebase/messaging": "0.9.1", 289 | "@firebase/util": "1.4.0", 290 | "tslib": "^2.1.0" 291 | } 292 | }, 293 | "@firebase/messaging-interop-types": { 294 | "version": "0.1.0", 295 | "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.1.0.tgz", 296 | "integrity": "sha512-DbvUl/rXAZpQeKBnwz0NYY5OCqr2nFA0Bj28Fmr3NXGqR4PAkfTOHuQlVtLO1Nudo3q0HxAYLa68ZDAcuv2uKQ==" 297 | }, 298 | "@firebase/performance": { 299 | "version": "0.5.1", 300 | "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.5.1.tgz", 301 | "integrity": "sha512-O93Yry8KhAaFrhnmBaMkM0lpgVmpd7CRX0eq1S0IKLdE3MdF+oAtbQiZG/NuRl3Vz8vjoz96R6bPbCWaDuiy8Q==", 302 | "requires": { 303 | "@firebase/component": "0.5.7", 304 | "@firebase/installations": "0.5.1", 305 | "@firebase/logger": "0.3.0", 306 | "@firebase/util": "1.4.0", 307 | "tslib": "^2.1.0" 308 | } 309 | }, 310 | "@firebase/performance-compat": { 311 | "version": "0.1.1", 312 | "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.1.1.tgz", 313 | "integrity": "sha512-xN/TjU0hVNiJshZzrUvPYB+3sPS9SgaWrfxh3p0eGFVdwHp/3Z8HlT772bkHAEKXVc64v19ktpUVd+sF5aoJNQ==", 314 | "requires": { 315 | "@firebase/component": "0.5.7", 316 | "@firebase/logger": "0.3.0", 317 | "@firebase/performance": "0.5.1", 318 | "@firebase/performance-types": "0.1.0", 319 | "@firebase/util": "1.4.0", 320 | "tslib": "^2.1.0" 321 | } 322 | }, 323 | "@firebase/performance-types": { 324 | "version": "0.1.0", 325 | "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.1.0.tgz", 326 | "integrity": "sha512-6p1HxrH0mpx+622Ql6fcxFxfkYSBpE3LSuwM7iTtYU2nw91Hj6THC8Bc8z4nboIq7WvgsT/kOTYVVZzCSlXl8w==" 327 | }, 328 | "@firebase/polyfill": { 329 | "version": "0.3.36", 330 | "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", 331 | "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", 332 | "requires": { 333 | "core-js": "3.6.5", 334 | "promise-polyfill": "8.1.3", 335 | "whatwg-fetch": "2.0.4" 336 | } 337 | }, 338 | "@firebase/remote-config": { 339 | "version": "0.3.0", 340 | "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.3.0.tgz", 341 | "integrity": "sha512-Yf9/iXToC6Kbec1yOQ9mdTc1MP0mR2VCCR/n3Q+Ol3U+PML+ePXfqWiL2VHrUA86BeB2hpXF1BcTxvD4uOiDnA==", 342 | "requires": { 343 | "@firebase/component": "0.5.7", 344 | "@firebase/installations": "0.5.1", 345 | "@firebase/logger": "0.3.0", 346 | "@firebase/util": "1.4.0", 347 | "tslib": "^2.1.0" 348 | } 349 | }, 350 | "@firebase/remote-config-compat": { 351 | "version": "0.1.1", 352 | "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.1.1.tgz", 353 | "integrity": "sha512-ZHRHYTdDztXHxgYXzuuD6Goa6ScmAqtctXl2eC6D8vxA8fIGRQKHN+9AMwxm8b3JHzdVY/5XhAOmKCcFvPOgtw==", 354 | "requires": { 355 | "@firebase/component": "0.5.7", 356 | "@firebase/logger": "0.3.0", 357 | "@firebase/remote-config": "0.3.0", 358 | "@firebase/remote-config-types": "0.2.0", 359 | "@firebase/util": "1.4.0", 360 | "tslib": "^2.1.0" 361 | } 362 | }, 363 | "@firebase/remote-config-types": { 364 | "version": "0.2.0", 365 | "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.2.0.tgz", 366 | "integrity": "sha512-hqK5sCPeZvcHQ1D6VjJZdW6EexLTXNMJfPdTwbD8NrXUw6UjWC4KWhLK/TSlL0QPsQtcKRkaaoP+9QCgKfMFPw==" 367 | }, 368 | "@firebase/storage": { 369 | "version": "0.8.3", 370 | "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.8.3.tgz", 371 | "integrity": "sha512-oraycQ787tEr6xu2Qc4nngLz1YEoEjZ+lrjThx0CJZB7VwdlkIJ24TkzJ9xoeWc+cpo34deg/If4w8AU5/WupQ==", 372 | "requires": { 373 | "@firebase/component": "0.5.7", 374 | "@firebase/util": "1.4.0", 375 | "node-fetch": "2.6.2", 376 | "tslib": "^2.1.0" 377 | }, 378 | "dependencies": { 379 | "node-fetch": { 380 | "version": "2.6.2", 381 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", 382 | "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==" 383 | } 384 | } 385 | }, 386 | "@firebase/storage-compat": { 387 | "version": "0.1.3", 388 | "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.1.3.tgz", 389 | "integrity": "sha512-m2htGJjCFlTONsqYRKXTfzkux3nbhpIpd72RK2iPkRPE69nQ0wiVplIE7bCaq3CSFMbkI3ETOtTTfW1wrOpF2g==", 390 | "requires": { 391 | "@firebase/component": "0.5.7", 392 | "@firebase/storage": "0.8.3", 393 | "@firebase/storage-types": "0.6.0", 394 | "@firebase/util": "1.4.0", 395 | "tslib": "^2.1.0" 396 | } 397 | }, 398 | "@firebase/storage-types": { 399 | "version": "0.6.0", 400 | "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.6.0.tgz", 401 | "integrity": "sha512-1LpWhcCb1ftpkP/akhzjzeFxgVefs6eMD2QeKiJJUGH1qOiows2w5o0sKCUSQrvrRQS1lz3SFGvNR1Ck/gqxeA==" 402 | }, 403 | "@firebase/util": { 404 | "version": "1.4.0", 405 | "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.0.tgz", 406 | "integrity": "sha512-Qn58d+DVi1nGn0bA9RV89zkz0zcbt6aUcRdyiuub/SuEvjKYstWmHcHwh1C0qmE1wPf9a3a+AuaRtduaGaRT7A==", 407 | "requires": { 408 | "tslib": "^2.1.0" 409 | } 410 | }, 411 | "@firebase/webchannel-wrapper": { 412 | "version": "0.6.0", 413 | "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.6.0.tgz", 414 | "integrity": "sha512-Pz4+7HPzKvOFI1ICQ6pyUv/VgStEWq9IGiVaaV1cQLi66NIA1mD5INnY4CDNoVAxlkuZvDEUZ+cVHLQ8iwA2hQ==" 415 | }, 416 | "@grpc/grpc-js": { 417 | "version": "1.3.7", 418 | "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.7.tgz", 419 | "integrity": "sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==", 420 | "requires": { 421 | "@types/node": ">=12.12.47" 422 | } 423 | }, 424 | "@grpc/proto-loader": { 425 | "version": "0.6.5", 426 | "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz", 427 | "integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==", 428 | "requires": { 429 | "@types/long": "^4.0.1", 430 | "lodash.camelcase": "^4.3.0", 431 | "long": "^4.0.0", 432 | "protobufjs": "^6.10.0", 433 | "yargs": "^16.1.1" 434 | } 435 | }, 436 | "@protobufjs/aspromise": { 437 | "version": "1.1.2", 438 | "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", 439 | "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" 440 | }, 441 | "@protobufjs/base64": { 442 | "version": "1.1.2", 443 | "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", 444 | "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" 445 | }, 446 | "@protobufjs/codegen": { 447 | "version": "2.0.4", 448 | "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", 449 | "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" 450 | }, 451 | "@protobufjs/eventemitter": { 452 | "version": "1.1.0", 453 | "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", 454 | "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" 455 | }, 456 | "@protobufjs/fetch": { 457 | "version": "1.1.0", 458 | "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", 459 | "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", 460 | "requires": { 461 | "@protobufjs/aspromise": "^1.1.1", 462 | "@protobufjs/inquire": "^1.1.0" 463 | } 464 | }, 465 | "@protobufjs/float": { 466 | "version": "1.0.2", 467 | "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", 468 | "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" 469 | }, 470 | "@protobufjs/inquire": { 471 | "version": "1.1.0", 472 | "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", 473 | "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" 474 | }, 475 | "@protobufjs/path": { 476 | "version": "1.1.2", 477 | "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", 478 | "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" 479 | }, 480 | "@protobufjs/pool": { 481 | "version": "1.1.0", 482 | "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", 483 | "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" 484 | }, 485 | "@protobufjs/utf8": { 486 | "version": "1.1.0", 487 | "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", 488 | "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" 489 | }, 490 | "@types/long": { 491 | "version": "4.0.1", 492 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", 493 | "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" 494 | }, 495 | "@types/node": { 496 | "version": "16.10.2", 497 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", 498 | "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==" 499 | }, 500 | "ansi-regex": { 501 | "version": "5.0.1", 502 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 503 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 504 | }, 505 | "ansi-styles": { 506 | "version": "4.3.0", 507 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 508 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 509 | "requires": { 510 | "color-convert": "^2.0.1" 511 | } 512 | }, 513 | "balanced-match": { 514 | "version": "1.0.2", 515 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 516 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 517 | }, 518 | "brace-expansion": { 519 | "version": "1.1.11", 520 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 521 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 522 | "requires": { 523 | "balanced-match": "^1.0.0", 524 | "concat-map": "0.0.1" 525 | } 526 | }, 527 | "cliui": { 528 | "version": "7.0.4", 529 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 530 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 531 | "requires": { 532 | "string-width": "^4.2.0", 533 | "strip-ansi": "^6.0.0", 534 | "wrap-ansi": "^7.0.0" 535 | } 536 | }, 537 | "color-convert": { 538 | "version": "2.0.1", 539 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 540 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 541 | "requires": { 542 | "color-name": "~1.1.4" 543 | } 544 | }, 545 | "color-name": { 546 | "version": "1.1.4", 547 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 548 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 549 | }, 550 | "concat-map": { 551 | "version": "0.0.1", 552 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 553 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 554 | }, 555 | "core-js": { 556 | "version": "3.6.5", 557 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", 558 | "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" 559 | }, 560 | "core-util-is": { 561 | "version": "1.0.3", 562 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 563 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 564 | }, 565 | "emoji-regex": { 566 | "version": "8.0.0", 567 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 568 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 569 | }, 570 | "escalade": { 571 | "version": "3.1.1", 572 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 573 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" 574 | }, 575 | "faye-websocket": { 576 | "version": "0.11.4", 577 | "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", 578 | "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", 579 | "requires": { 580 | "websocket-driver": ">=0.5.1" 581 | } 582 | }, 583 | "firebase": { 584 | "version": "9.1.1", 585 | "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.1.1.tgz", 586 | "integrity": "sha512-106PqKLwWo4vINQUwEbk2aU/nAFhRbCBE2IdnQmf7UDaW4wqJGZcgRvy3jSyuZr/dkqnT7ymKX0GGrDSzNLU6g==", 587 | "requires": { 588 | "@firebase/analytics": "0.7.1", 589 | "@firebase/analytics-compat": "0.1.2", 590 | "@firebase/app": "0.7.2", 591 | "@firebase/app-check": "0.4.1", 592 | "@firebase/app-check-compat": "0.1.2", 593 | "@firebase/app-compat": "0.1.3", 594 | "@firebase/app-types": "0.7.0", 595 | "@firebase/auth": "0.18.1", 596 | "@firebase/auth-compat": "0.1.4", 597 | "@firebase/database": "0.12.1", 598 | "@firebase/database-compat": "0.1.1", 599 | "@firebase/firestore": "3.1.0", 600 | "@firebase/firestore-compat": "0.1.3", 601 | "@firebase/functions": "0.7.2", 602 | "@firebase/functions-compat": "0.1.3", 603 | "@firebase/installations": "0.5.1", 604 | "@firebase/messaging": "0.9.1", 605 | "@firebase/messaging-compat": "0.1.1", 606 | "@firebase/performance": "0.5.1", 607 | "@firebase/performance-compat": "0.1.1", 608 | "@firebase/polyfill": "0.3.36", 609 | "@firebase/remote-config": "0.3.0", 610 | "@firebase/remote-config-compat": "0.1.1", 611 | "@firebase/storage": "0.8.3", 612 | "@firebase/storage-compat": "0.1.3", 613 | "@firebase/util": "1.4.0" 614 | } 615 | }, 616 | "fs.realpath": { 617 | "version": "1.0.0", 618 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 619 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 620 | }, 621 | "get-caller-file": { 622 | "version": "2.0.5", 623 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 624 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 625 | }, 626 | "glob": { 627 | "version": "7.2.0", 628 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 629 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 630 | "requires": { 631 | "fs.realpath": "^1.0.0", 632 | "inflight": "^1.0.4", 633 | "inherits": "2", 634 | "minimatch": "^3.0.4", 635 | "once": "^1.3.0", 636 | "path-is-absolute": "^1.0.0" 637 | } 638 | }, 639 | "http-parser-js": { 640 | "version": "0.5.3", 641 | "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", 642 | "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" 643 | }, 644 | "idb": { 645 | "version": "3.0.2", 646 | "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", 647 | "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" 648 | }, 649 | "immediate": { 650 | "version": "3.0.6", 651 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", 652 | "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" 653 | }, 654 | "inflight": { 655 | "version": "1.0.6", 656 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 657 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 658 | "requires": { 659 | "once": "^1.3.0", 660 | "wrappy": "1" 661 | } 662 | }, 663 | "inherits": { 664 | "version": "2.0.4", 665 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 666 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 667 | }, 668 | "is-fullwidth-code-point": { 669 | "version": "3.0.0", 670 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 671 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 672 | }, 673 | "isarray": { 674 | "version": "1.0.0", 675 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 676 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 677 | }, 678 | "jszip": { 679 | "version": "3.7.1", 680 | "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", 681 | "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", 682 | "requires": { 683 | "lie": "~3.3.0", 684 | "pako": "~1.0.2", 685 | "readable-stream": "~2.3.6", 686 | "set-immediate-shim": "~1.0.1" 687 | } 688 | }, 689 | "lie": { 690 | "version": "3.3.0", 691 | "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", 692 | "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", 693 | "requires": { 694 | "immediate": "~3.0.5" 695 | } 696 | }, 697 | "lodash.camelcase": { 698 | "version": "4.3.0", 699 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 700 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" 701 | }, 702 | "long": { 703 | "version": "4.0.0", 704 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 705 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 706 | }, 707 | "minimatch": { 708 | "version": "3.0.4", 709 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 710 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 711 | "requires": { 712 | "brace-expansion": "^1.1.7" 713 | } 714 | }, 715 | "node-fetch": { 716 | "version": "2.6.5", 717 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", 718 | "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", 719 | "requires": { 720 | "whatwg-url": "^5.0.0" 721 | } 722 | }, 723 | "once": { 724 | "version": "1.4.0", 725 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 726 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 727 | "requires": { 728 | "wrappy": "1" 729 | } 730 | }, 731 | "pako": { 732 | "version": "1.0.11", 733 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 734 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" 735 | }, 736 | "path-is-absolute": { 737 | "version": "1.0.1", 738 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 739 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 740 | }, 741 | "process-nextick-args": { 742 | "version": "2.0.1", 743 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 744 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 745 | }, 746 | "promise-polyfill": { 747 | "version": "8.1.3", 748 | "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", 749 | "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" 750 | }, 751 | "protobufjs": { 752 | "version": "6.11.2", 753 | "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", 754 | "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", 755 | "requires": { 756 | "@protobufjs/aspromise": "^1.1.2", 757 | "@protobufjs/base64": "^1.1.2", 758 | "@protobufjs/codegen": "^2.0.4", 759 | "@protobufjs/eventemitter": "^1.1.0", 760 | "@protobufjs/fetch": "^1.1.0", 761 | "@protobufjs/float": "^1.0.2", 762 | "@protobufjs/inquire": "^1.1.0", 763 | "@protobufjs/path": "^1.1.2", 764 | "@protobufjs/pool": "^1.1.0", 765 | "@protobufjs/utf8": "^1.1.0", 766 | "@types/long": "^4.0.1", 767 | "@types/node": ">=13.7.0", 768 | "long": "^4.0.0" 769 | } 770 | }, 771 | "readable-stream": { 772 | "version": "2.3.7", 773 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 774 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 775 | "requires": { 776 | "core-util-is": "~1.0.0", 777 | "inherits": "~2.0.3", 778 | "isarray": "~1.0.0", 779 | "process-nextick-args": "~2.0.0", 780 | "safe-buffer": "~5.1.1", 781 | "string_decoder": "~1.1.1", 782 | "util-deprecate": "~1.0.1" 783 | } 784 | }, 785 | "require-directory": { 786 | "version": "2.1.1", 787 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 788 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 789 | }, 790 | "rimraf": { 791 | "version": "3.0.2", 792 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 793 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 794 | "requires": { 795 | "glob": "^7.1.3" 796 | } 797 | }, 798 | "safe-buffer": { 799 | "version": "5.1.2", 800 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 801 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 802 | }, 803 | "selenium-webdriver": { 804 | "version": "4.0.0-rc-1", 805 | "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-rc-1.tgz", 806 | "integrity": "sha512-bcrwFPRax8fifRP60p7xkWDGSJJoMkPAzufMlk5K2NyLPht/YZzR2WcIk1+3gR8VOCLlst1P2PI+MXACaFzpIw==", 807 | "requires": { 808 | "jszip": "^3.6.0", 809 | "rimraf": "^3.0.2", 810 | "tmp": "^0.2.1", 811 | "ws": ">=7.4.6" 812 | } 813 | }, 814 | "set-immediate-shim": { 815 | "version": "1.0.1", 816 | "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", 817 | "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" 818 | }, 819 | "string-width": { 820 | "version": "4.2.3", 821 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 822 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 823 | "requires": { 824 | "emoji-regex": "^8.0.0", 825 | "is-fullwidth-code-point": "^3.0.0", 826 | "strip-ansi": "^6.0.1" 827 | } 828 | }, 829 | "string_decoder": { 830 | "version": "1.1.1", 831 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 832 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 833 | "requires": { 834 | "safe-buffer": "~5.1.0" 835 | } 836 | }, 837 | "strip-ansi": { 838 | "version": "6.0.1", 839 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 840 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 841 | "requires": { 842 | "ansi-regex": "^5.0.1" 843 | } 844 | }, 845 | "tmp": { 846 | "version": "0.2.1", 847 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", 848 | "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", 849 | "requires": { 850 | "rimraf": "^3.0.0" 851 | } 852 | }, 853 | "tr46": { 854 | "version": "0.0.3", 855 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 856 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 857 | }, 858 | "tslib": { 859 | "version": "2.3.1", 860 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 861 | "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" 862 | }, 863 | "util-deprecate": { 864 | "version": "1.0.2", 865 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 866 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 867 | }, 868 | "webidl-conversions": { 869 | "version": "3.0.1", 870 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 871 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 872 | }, 873 | "websocket-driver": { 874 | "version": "0.7.4", 875 | "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", 876 | "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", 877 | "requires": { 878 | "http-parser-js": ">=0.5.1", 879 | "safe-buffer": ">=5.1.0", 880 | "websocket-extensions": ">=0.1.1" 881 | } 882 | }, 883 | "websocket-extensions": { 884 | "version": "0.1.4", 885 | "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", 886 | "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" 887 | }, 888 | "whatwg-fetch": { 889 | "version": "2.0.4", 890 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", 891 | "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" 892 | }, 893 | "whatwg-url": { 894 | "version": "5.0.0", 895 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 896 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 897 | "requires": { 898 | "tr46": "~0.0.3", 899 | "webidl-conversions": "^3.0.0" 900 | } 901 | }, 902 | "wrap-ansi": { 903 | "version": "7.0.0", 904 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 905 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 906 | "requires": { 907 | "ansi-styles": "^4.0.0", 908 | "string-width": "^4.1.0", 909 | "strip-ansi": "^6.0.0" 910 | } 911 | }, 912 | "wrappy": { 913 | "version": "1.0.2", 914 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 915 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 916 | }, 917 | "ws": { 918 | "version": "8.2.2", 919 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.2.tgz", 920 | "integrity": "sha512-Q6B6H2oc8QY3llc3cB8kVmQ6pnJWVQbP7Q5algTcIxx7YEpc0oU4NBVHlztA7Ekzfhw2r0rPducMUiCGWKQRzw==" 921 | }, 922 | "y18n": { 923 | "version": "5.0.8", 924 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 925 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" 926 | }, 927 | "yargs": { 928 | "version": "16.2.0", 929 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 930 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 931 | "requires": { 932 | "cliui": "^7.0.2", 933 | "escalade": "^3.1.1", 934 | "get-caller-file": "^2.0.5", 935 | "require-directory": "^2.1.1", 936 | "string-width": "^4.2.0", 937 | "y18n": "^5.0.5", 938 | "yargs-parser": "^20.2.2" 939 | } 940 | }, 941 | "yargs-parser": { 942 | "version": "20.2.9", 943 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 944 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" 945 | } 946 | } 947 | } 948 | -------------------------------------------------------------------------------- /FirestoreEmulator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firestore-emulator", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "firebase emulators:start --project demo-firefly --import=./sample_data", 8 | "add-data": "firebase emulators:start --project demo-firefly --export-on-exit=./sample_data" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /FirestoreEmulator/sample_data/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "9.11.0", 3 | "firestore": { 4 | "version": "1.11.15", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /FirestoreEmulator/sample_data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FireflyJS/fireflyjs-core/98e273baf42c3196409575e771f48d5e5568a3b8/FirestoreEmulator/sample_data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /FirestoreEmulator/sample_data/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FireflyJS/fireflyjs-core/98e273baf42c3196409575e771f48d5e5568a3b8/FirestoreEmulator/sample_data/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /FirestoreEmulator/sample_data/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FireflyJS/fireflyjs-core/98e273baf42c3196409575e771f48d5e5568a3b8/FirestoreEmulator/sample_data/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2021 FireflyJS 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

8 | 9 | Logo 10 | 11 | 12 |

FireflyJS

13 | 14 |

15 | The only ODM you will ever need for Firestore. 16 |
17 | 18 |
19 | 20 | 21 | 22 | Report Bug 23 | · 24 | Request Feature 25 |

26 |

27 | 28 | [![GitHub issues](https://img.shields.io/github/issues/FireflyJS/fireflyjs-core?logo=github)](https://github.com/Saurav-Shrivastav/covaccinate/issues) 29 | ![Size](https://github-size-badge.herokuapp.com/FireflyJS/fireflyjs-core.svg) 30 | 31 | 32 | 33 | [![Pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/Saurav-Shrivastav/covaccinate/blob/master/.pre-commit-config.yaml) 34 | [![License](https://img.shields.io/github/license/FireflyJS/fireflyjs-core)](https://github.com/FireflyJS/fireflyjs-core/blob/main/LICENSE) 35 | 36 | 37 | 38 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 39 | 40 | 41 | 42 | 43 |
44 | Table of Contents 45 |
    46 |
  1. 47 | About The Project 48 |
  2. 49 |
  3. 50 | Features 51 |
  4. 52 |
  5. 53 | Getting Started 54 | 58 |
  6. 59 |
  7. Documentation
  8. 60 | 61 |
  9. Contributing
  10. 62 |
  11. Contributors
  12. 63 | 64 | 65 | 66 |
67 |
68 | 69 | 70 | 71 | ## About The Project 72 | 73 | [![Product Name Screen Shot][product-screenshot]](https://example.com) 74 | 75 | FireflyJS is an object document model(ODM) library for Firestore. It aims to ease the process of developing applications using Firestore by providing robust validation of data, exposing a multitude of ways to query, add, etc. 76 | 77 | ### Built With 78 | 79 | - [TypeScript](https://www.typescriptlang.org/) 80 | - [Jest](https://jestjs.io/) 81 | - [Firebase](https://firebase.google.com/) 82 | 83 | 84 | 85 | ## Features 86 | 87 | - Define a schema for your data and add a multitude of operations before adding data to the database. 88 | - Written completely in TypeScript. Get intelligent intellisense and speed up your development workflow. 89 | - Easy to pick up, with a syntax inspired from popular libraries like [Mongoose](https://github.com/Automattic/mongoose) and [Yup](https://github.com/jquense/yup). 90 | - Query data like a pro! With methods like `findById`, `findOne`, etc. 91 | - Add and update data made easy and safe with proper validation, data transformation according to your defined schema and much more! 92 | 93 | 94 | 95 | ## Getting Started 96 | 97 | Coming Soon! 98 | 99 | For now, you can refer [**tests**](https://github.com/FireflyJS/fireflyjs-core/tree/main/src/__tests__) for usage and examples as we are still developing the npm package. 100 | 101 | 102 | 103 | ## Documentation 104 | 105 | Inline documentation for all methods is available in the codebase. Documentation website is in the works. Stay Tuned :) 106 | 107 | 108 | 109 | ## Contributing 110 | 111 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 112 | 113 | 1. Fork the Project 114 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 115 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 116 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 117 | 5. Open a Pull Request 118 | 119 | [contributors-shield]: https://img.shields.io/github/contributors/othneildrew/Best-README-Template.svg?style=for-the-badge 120 | [contributors-url]: https://github.com/FireflyJS/fireflyjs-core/graphs/contributors 121 | [forks-shield]: https://img.shields.io/github/forks/othneildrew/Best-README-Template.svg?style=for-the-badge 122 | [forks-url]: https://github.com/FireflyJS/fireflyjs-core/network/members 123 | [stars-shield]: https://img.shields.io/github/stars/othneildrew/Best-README-Template.svg?style=for-the-badge 124 | [stars-url]: https://github.com/FireflyJS/fireflyjs-core/stargazers 125 | [issues-shield]: https://img.shields.io/github/issues/othneildrew/Best-README-Template.svg?style=for-the-badge 126 | [issues-url]: https://github.com/FireflyJS/fireflyjs-core/issues 127 | [license-shield]: https://img.shields.io/github/license/othneildrew/Best-README-Template.svg?style=for-the-badge 128 | [license-url]: https://github.com/othneildrew/Best-README-Template/blob/master/LICENSE.txt 129 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 130 | 131 | 132 | 133 | [product-screenshot]: https://user-images.githubusercontent.com/51146347/130316345-b68999ff-8eb5-4620-bf04-bc9eef91e2c6.png 134 | 135 | ## Contributors 136 | 137 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 |

Aniket Biswas

💻 📖 🚧

Aryaman Grover

💻 📖 🚧
148 | 149 | 150 | 151 | 152 | 153 | 154 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 155 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | testMatch: ["**/__tests__/**/*.test.[jt]s?(x)"], 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fireflyjs/core", 3 | "version": "0.0.5", 4 | "description": "ODM for FireStore", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib/**/*" 9 | ], 10 | "scripts": { 11 | "prepare": "husky install", 12 | "compile": "tsc", 13 | "format": "npx prettier --write .", 14 | "lint": "npx eslint --fix .", 15 | "test": "jest", 16 | "test:watch": "jest --watch", 17 | "test:verbose": "jest --verbose" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/FireflyJS/fireflyjs-core.git" 22 | }, 23 | "keywords": [], 24 | "author": "", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/FireflyJS/fireflyjs-core/issues" 28 | }, 29 | "homepage": "https://github.com/FireflyJS/fireflyjs-core#readme", 30 | "devDependencies": { 31 | "@types/jest": "^26.0.24", 32 | "@types/node": "^16.3.1", 33 | "@typescript-eslint/eslint-plugin": "^4.28.3", 34 | "@typescript-eslint/parser": "^4.28.3", 35 | "eslint": "^7.30.0", 36 | "eslint-config-airbnb-typescript": "^12.3.1", 37 | "eslint-config-prettier": "^8.3.0", 38 | "eslint-plugin-import": "^2.23.4", 39 | "eslint-plugin-prettier": "^3.4.0", 40 | "husky": "^7.0.0", 41 | "jest": "^27.0.6", 42 | "prettier": "^2.3.2", 43 | "ts-jest": "^27.0.3", 44 | "typescript": "^4.3.5" 45 | }, 46 | "dependencies": { 47 | "firebase-admin": "^9.11.0" 48 | }, 49 | "directories": { 50 | "lib": "lib" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Connection/class.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import Model from "../Model"; 3 | import { ObjectSchema, KeyValueStore } from "../SchemaTypes"; 4 | import { ModelPOJO } from "."; 5 | 6 | /** 7 | * Class for a Firefly connection. 8 | */ 9 | class Connection { 10 | private __db: __firestore.Firestore; 11 | 12 | private __modelMap: Map = new Map(); 13 | 14 | /** 15 | * Initializes a new connection instance. 16 | * @constructor 17 | * @param {__firestore.Firestore} firestore - The [Firestore](https://firebase.google.com/docs/reference/node/firebase.firestore.Firestore) instance to use. 18 | */ 19 | constructor(firestore: __firestore.Firestore) { 20 | this.__db = firestore; 21 | } 22 | 23 | /** 24 | * A getter function that returns all the models registered with this connection. 25 | * @returns {ModelPOJO} A javascript POJO(Plain Old Javascript object) model names and their corresponding model instances. 26 | */ 27 | get models(): ModelPOJO { 28 | const pojo: ModelPOJO = {}; 29 | this.__modelMap.forEach((val, key) => { 30 | pojo[key] = val; 31 | }); 32 | 33 | return pojo; 34 | } 35 | 36 | /** 37 | * Returns all the models registered with this connection. 38 | * @returns {string[]} An array containing all the names of models. 39 | */ 40 | public modelNames = (): string[] => { 41 | const keys: string[] = [...this.__modelMap.keys()]; 42 | 43 | return keys; 44 | }; 45 | 46 | /** 47 | * Returns an instance of the model corresponding to the provided name 48 | * @param {string} name - The name of the model. 49 | * @returns {Model | undefined} Model instance corresponding to `name`. Returns `undefined` if not present. 50 | */ 51 | public getModel = (name: string): Model | undefined => { 52 | const model = this.__modelMap.get(name); 53 | 54 | return model; 55 | }; 56 | 57 | /** 58 | * Registers a new model with the underlying connection. 59 | * @typeParam T - Type defination of the provided schema, must extend {@link KeyValueStore | KeyValueStore} 60 | * @param {string} name - Name for the new model. Overwrites the existing model instance under the same name if present. 61 | * @param {ObjectSchema.Class} schema - The schema for the new model. This schema would be used to validate data before writing to the database. 62 | * @returns {Model} The new model instance. 63 | */ 64 | public model = ( 65 | name: string, 66 | schema: ObjectSchema 67 | ): Model => { 68 | const model = new Model(name, schema, this.__db); 69 | this.__modelMap.set(name, model); 70 | 71 | return model; 72 | }; 73 | } 74 | 75 | export default Connection; 76 | -------------------------------------------------------------------------------- /src/Connection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { ModelPOJO } from "./types/ModelPOJO"; 3 | -------------------------------------------------------------------------------- /src/Connection/types/ModelPOJO.ts: -------------------------------------------------------------------------------- 1 | import Model from "../../Model/class"; 2 | 3 | /** 4 | * [POJO](https://masteringjs.io/tutorials/fundamentals/pojo#:~:text=The%20intuition%20behind%20POJOs%20is,create%20POJOs%20by%20calling%20Object.) containing model name as keys and Model instance as values 5 | */ 6 | type ModelPOJO = { [k: string]: Model }; 7 | 8 | export { ModelPOJO }; 9 | -------------------------------------------------------------------------------- /src/Document/class.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import { ObjectSchema, KeyValueStore } from "../SchemaTypes"; 3 | import { Errors, UpdateOptions } from "."; 4 | import makeError from "../utils/makeError"; 5 | 6 | /** 7 | * Class for Firefly document 8 | */ 9 | class Document { 10 | private __docRef: __firestore.DocumentReference; 11 | 12 | private __schema: ObjectSchema; 13 | 14 | /** 15 | * Initialize a new Document instance. 16 | * @constructor 17 | * @param {__firestore.DocumentReference} docRef - The Firestore document reference. 18 | * @param {ObjectSchema} schema - The schema for the document. 19 | */ 20 | constructor( 21 | docRef: __firestore.DocumentReference, 22 | schema: ObjectSchema 23 | ) { 24 | this.__docRef = docRef; 25 | this.__schema = schema; 26 | } 27 | 28 | /** 29 | * returns the document schema 30 | * @returns {ObjectSchema} Document Schema 31 | */ 32 | get schema(): ObjectSchema { 33 | return this.__schema; 34 | } 35 | 36 | /** 37 | * returns the document id 38 | * @returns {string} Document id 39 | */ 40 | get id(): string { 41 | return this.__docRef.id; 42 | } 43 | 44 | /** 45 | * Fetches the document data from Firestore. 46 | * @returns {Promise} Promise that resolves with the document data. 47 | */ 48 | public data = async (): Promise => { 49 | const docSnap = await this.__docRef.get().catch((err: Error) => { 50 | throw makeError(Errors.Firestore, err.message); 51 | }); 52 | 53 | const docData = docSnap.data(); 54 | if (typeof docData === "undefined" || !docSnap.exists) 55 | throw makeError(Errors.Invalid, "Document is empty or non existant"); 56 | 57 | return docData; 58 | }; 59 | 60 | /** 61 | * Updates the document value in Firestore with the supplied data. 62 | * @param {Partial} data - The new data to update the document with. 63 | * @param {UpdateOptions} [options = {merge: true}] - The update options to be used while updating the document in Firestore. See reference @link https://googleapis.dev/nodejs/firestore/latest/global.html#SetOptions 64 | */ 65 | public update = async ( 66 | data: Partial, 67 | options: UpdateOptions = { merge: true } 68 | ) => { 69 | const { valid, value, errors } = this.__schema.validate(data, undefined, { 70 | onlySupplied: true, 71 | }); 72 | if (!valid) { 73 | throw makeError(Errors.Validation, errors); 74 | } 75 | 76 | await this.__docRef.set(value, options); 77 | }; 78 | 79 | /** 80 | * Deletes the document from Firestore. 81 | */ 82 | public delete = async () => { 83 | await this.__docRef.delete(); 84 | }; 85 | } 86 | 87 | export default Document; 88 | -------------------------------------------------------------------------------- /src/Document/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { DocErrorTypes as Errors } from "./types/error"; 3 | export { UpdateOptions } from "./types/updateOptions"; 4 | -------------------------------------------------------------------------------- /src/Document/types/error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * All possible error types in Firefly Document. 3 | */ 4 | enum DocErrorTypes { 5 | Invalid = "Document/Invalid", 6 | Firestore = "Document/Firestore", 7 | Validation = "Document/Validation", 8 | } 9 | 10 | export { DocErrorTypes }; 11 | -------------------------------------------------------------------------------- /src/Document/types/updateOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The update options to be used while updating the document in Firestore. See reference 3 | * @link https://googleapis.dev/nodejs/firestore/latest/global.html#SetOptions 4 | */ 5 | type UpdateOptions = { 6 | merge: boolean; 7 | mergeFields?: string[]; 8 | }; 9 | 10 | export { UpdateOptions }; 11 | -------------------------------------------------------------------------------- /src/Firefly/class.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import Connection from "../Connection"; 3 | import { Errors } from "."; 4 | import makeError from "../utils/makeError"; 5 | 6 | /** 7 | * The main entrypoint for establishing a Firefly instance. 8 | * */ 9 | class Firefly { 10 | private __dbMap: Map = new Map(); 11 | 12 | /** 13 | * Establishes a connection to the Firestore database. Can be used to create multiple set of connections. 14 | * @param {__firestore.Firestore} firestore - The [Firestore](https://firebase.google.com/docs/reference/node/firebase.firestore.Firestore) instance to use. 15 | * @param {dbName} dbName = default - The name of the connection to use. 16 | * @return {Connection} An instance of Firefly Connection. 17 | */ 18 | public createConnection = ( 19 | firestore: __firestore.Firestore, 20 | dbName: string = "default" 21 | ): Connection => { 22 | const connection: Connection = new Connection(firestore); 23 | this.__dbMap.set(dbName, connection); 24 | return connection; 25 | }; 26 | 27 | /** 28 | * Retrieves an instance of Firefly connection to the Firestore database. 29 | * @param {dbName} dbName - The name of the connection to retrieve. 30 | * @return {Connection} The instance of Firefly Connection corresponding to `dbName`. 31 | */ 32 | public getConnection = (dbName: string): Connection => { 33 | const connection: Connection | undefined = this.__dbMap.get(dbName); 34 | 35 | if (typeof connection === "undefined") { 36 | throw makeError( 37 | Errors.Invalid, 38 | `Firefly Connection with key ${dbName} does not exist` 39 | ); 40 | } 41 | 42 | return connection; 43 | }; 44 | } 45 | 46 | export default Firefly; 47 | -------------------------------------------------------------------------------- /src/Firefly/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { FireflyErrorTypes as Errors } from "./types/error"; 3 | -------------------------------------------------------------------------------- /src/Firefly/types/error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * All possible error types in Firefly class. 3 | */ 4 | enum FireflyErrorTypes { 5 | Invalid = "Firefly/Invalid", 6 | } 7 | 8 | export { FireflyErrorTypes }; 9 | -------------------------------------------------------------------------------- /src/Model/class.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import Document from "../Document"; 3 | import { 4 | DeleteQuery, 5 | MultipleQuery, 6 | SingleQuery, 7 | UpdateQuery, 8 | BaseQueryNs, 9 | UpdateQueryNs, 10 | } from "../Query"; 11 | import { KeyValueStore, ObjectSchema } from "../SchemaTypes"; 12 | import { Errors } from "."; 13 | import makeError from "../utils/makeError"; 14 | 15 | class Model { 16 | private __name: string; 17 | 18 | private __schema: ObjectSchema; 19 | 20 | private __db: __firestore.Firestore; 21 | 22 | constructor( 23 | name: string, 24 | schema: ObjectSchema, 25 | firestore: __firestore.Firestore 26 | ) { 27 | this.__name = name; 28 | this.__schema = schema; 29 | this.__db = firestore; 30 | } 31 | 32 | get _name() { 33 | return this.__name; 34 | } 35 | 36 | get _schema() { 37 | return this.__schema; 38 | } 39 | 40 | get _db() { 41 | return this.__db; 42 | } 43 | 44 | public create = async (data: Partial) => { 45 | const { valid, value, errors } = this.__schema.validate(data, undefined, { 46 | onlyKeys: true, 47 | }); 48 | if (!valid) { 49 | throw makeError(Errors.Validation, errors); 50 | } 51 | 52 | const docRef = await this.__db 53 | .collection(this.__name) 54 | .add(value) 55 | .catch((err: Error) => { 56 | throw makeError(Errors.Firestore, err.message); 57 | }); 58 | 59 | return new Document( 60 | docRef as __firestore.DocumentReference, 61 | this.__schema 62 | ); 63 | }; 64 | 65 | public findById = (id: string) => { 66 | const collectionRef = this.__db.collection(this.__name); 67 | 68 | const config: BaseQueryNs.ConfigPOJOWithId = { 69 | _id: id, 70 | }; 71 | 72 | return new SingleQuery(config, collectionRef, this.__schema, true); 73 | }; 74 | 75 | public find = (query: BaseQueryNs.ConfigPOJO) => { 76 | const collectionRef = this.__db.collection(this.__name); 77 | 78 | return new MultipleQuery(query, collectionRef, this.__schema); 79 | }; 80 | 81 | public findOne = (query: BaseQueryNs.ConfigPOJO) => { 82 | const collectionRef = this.__db.collection(this.__name); 83 | 84 | return new SingleQuery(query, collectionRef, this.__schema, false); 85 | }; 86 | 87 | public findOneAndUpdate = ( 88 | query: BaseQueryNs.ConfigPOJO, 89 | updateQuery: UpdateQueryNs.UpdateConfigPOJO, 90 | updateOptions: UpdateQueryNs.UpdateOptions 91 | ) => { 92 | const collectionRef = this.__db.collection(this.__name); 93 | 94 | return new UpdateQuery( 95 | query, 96 | collectionRef, 97 | this.__schema, 98 | updateQuery, 99 | updateOptions, 100 | false 101 | ); 102 | }; 103 | 104 | public findByIdAndUpdate = ( 105 | id: string, 106 | updateQuery: UpdateQueryNs.UpdateConfigPOJO, 107 | updateOptions: UpdateQueryNs.UpdateOptions 108 | ) => { 109 | const collectionRef = this.__db.collection(this.__name); 110 | 111 | const config: BaseQueryNs.ConfigPOJOWithId = { 112 | _id: id, 113 | }; 114 | 115 | return new UpdateQuery( 116 | config, 117 | collectionRef, 118 | this.__schema, 119 | updateQuery, 120 | updateOptions, 121 | true 122 | ); 123 | }; 124 | 125 | public findByIdAndDelete = (id: string) => { 126 | const collectionRef = this.__db.collection(this.__name); 127 | 128 | const config: BaseQueryNs.ConfigPOJOWithId = { 129 | _id: id, 130 | }; 131 | 132 | return new DeleteQuery(config, collectionRef, this.__schema, true); 133 | }; 134 | 135 | public findOneAndDelete = (query: BaseQueryNs.ConfigPOJO) => { 136 | const collectionRef = this.__db.collection(this.__name); 137 | 138 | return new DeleteQuery(query, collectionRef, this.__schema, false); 139 | }; 140 | } 141 | 142 | export default Model; 143 | -------------------------------------------------------------------------------- /src/Model/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { ModelErrorTypes as Errors } from "./types/error"; 3 | -------------------------------------------------------------------------------- /src/Model/types/error.ts: -------------------------------------------------------------------------------- 1 | enum ModelErrorTypes { 2 | Firestore = "Model/Firestore", 3 | Validation = "Model/SchemaValidation", 4 | Misc = "Model/Misc", 5 | } 6 | 7 | export { ModelErrorTypes }; 8 | -------------------------------------------------------------------------------- /src/Query/Base/class.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import { ObjectSchema, KeyValueStore } from "../../SchemaTypes"; 3 | import Document from "../../Document"; 4 | 5 | /** 6 | * Base Query Class to define the structure of other derived Query classes 7 | */ 8 | abstract class BaseQuery { 9 | protected abstract __config: K; 10 | 11 | protected abstract __collectionRef: __firestore.CollectionReference; 12 | 13 | protected abstract __schema: ObjectSchema; 14 | 15 | /** 16 | * Executes the query and returns the result 17 | * @returns {Promise | Document[]> | undefined | void} The result of the query 18 | */ 19 | abstract exec: () => Promise | Document[] | undefined | void>; 20 | } 21 | 22 | export default BaseQuery; 23 | -------------------------------------------------------------------------------- /src/Query/Base/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { BuildExtQueryFn } from "./types/buildExtQuery"; 3 | export { ConfigPOJO, ConfigPOJOWithId } from "./types/config"; 4 | export { QueryErrorTypes as Errors } from "./types/errors"; 5 | export { Operators } from "./types/operators"; 6 | -------------------------------------------------------------------------------- /src/Query/Base/types/buildExtQuery.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | 3 | type BuildExtQueryFn = ( 4 | query: __firestore.Query | __firestore.CollectionReference, 5 | extConfig: T 6 | ) => __firestore.Query; 7 | 8 | export { BuildExtQueryFn }; 9 | -------------------------------------------------------------------------------- /src/Query/Base/types/config.ts: -------------------------------------------------------------------------------- 1 | import { Operators } from ".."; 2 | import { KeyValueStore } from "../../../SchemaTypes"; 3 | 4 | type ConfigPOJO = { 5 | [k in keyof T]?: Partial | Operators; 6 | }; 7 | 8 | type ConfigPOJOWithId = 9 | | ConfigPOJO 10 | | { 11 | _id: string; 12 | }; 13 | 14 | export { ConfigPOJO, ConfigPOJOWithId }; 15 | -------------------------------------------------------------------------------- /src/Query/Base/types/errors.ts: -------------------------------------------------------------------------------- 1 | enum QueryErrorTypes { 2 | Invalid = "Query/Invalid", 3 | MissingConfig = "Query/MissingConfiguration", 4 | NotFound = "Query/NoDocumentFound", 5 | MissingUpdateConfig = "Query/UpdateConfigMissing", 6 | } 7 | 8 | export { QueryErrorTypes }; 9 | -------------------------------------------------------------------------------- /src/Query/Base/types/operators.ts: -------------------------------------------------------------------------------- 1 | type OperatorValues = string | number | boolean | null | Date; 2 | 3 | type Operators = { 4 | $gt?: OperatorValues; 5 | $gte?: OperatorValues; 6 | $lt?: OperatorValues; 7 | $lte?: OperatorValues; 8 | $eq?: OperatorValues; 9 | $neq?: OperatorValues; 10 | $ctn?: OperatorValues | OperatorValues[]; 11 | $in?: OperatorValues[]; 12 | $nin?: OperatorValues[]; 13 | }; 14 | 15 | export { Operators }; 16 | -------------------------------------------------------------------------------- /src/Query/DeleteQuery/class.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import BaseQuery, { ConfigPOJOWithId, Errors } from "../Base"; 3 | import Document from "../../Document"; 4 | import { ObjectSchema, KeyValueStore } from "../../SchemaTypes"; 5 | import buildQuery from "../utils/buildQuery"; 6 | import makeError from "../../utils/makeError"; 7 | 8 | class DeleteQuery extends BaseQuery< 9 | T, 10 | ConfigPOJOWithId 11 | > { 12 | protected __config: ConfigPOJOWithId; 13 | 14 | protected __collectionRef: __firestore.CollectionReference; 15 | 16 | protected __schema: ObjectSchema; 17 | 18 | private __queryById: boolean = false; 19 | 20 | constructor( 21 | input: ConfigPOJOWithId, 22 | collectionRef: __firestore.CollectionReference, 23 | schema: ObjectSchema, 24 | queryById: boolean = false 25 | ) { 26 | super(); 27 | this.__config = input; 28 | this.__collectionRef = collectionRef; 29 | this.__schema = schema; 30 | this.__queryById = queryById; 31 | } 32 | 33 | /** 34 | * @returns {Promise} void 35 | */ 36 | public exec = async (): Promise => { 37 | if (!this.__config) { 38 | throw makeError(Errors.MissingConfig, "Query is not configured."); 39 | } 40 | 41 | let query: __firestore.CollectionReference | __firestore.Query; 42 | let documentRef: __firestore.DocumentReference | undefined; 43 | 44 | if ( 45 | this.__queryById && 46 | this.__config._id && 47 | typeof this.__config._id === "string" 48 | ) { 49 | documentRef = this.__collectionRef.doc( 50 | this.__config._id 51 | ) as __firestore.DocumentReference; 52 | } else { 53 | query = this.__collectionRef; 54 | 55 | Object.keys(this.__config).forEach((k) => { 56 | const key = k as keyof ConfigPOJOWithId; 57 | 58 | if (key !== "_id") { 59 | query = buildQuery(key, this.__config[key], query); 60 | } 61 | }); 62 | 63 | const querySnapshot = await query.get(); 64 | 65 | documentRef = querySnapshot.docs[0] 66 | ?.ref as __firestore.DocumentReference; 67 | } 68 | 69 | if (typeof documentRef === "undefined") { 70 | throw makeError(Errors.NotFound, "No document found matching the query."); 71 | } 72 | 73 | const document = new Document(documentRef, this.__schema); 74 | 75 | await document.delete(); 76 | }; 77 | } 78 | 79 | export default DeleteQuery; 80 | -------------------------------------------------------------------------------- /src/Query/DeleteQuery/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | -------------------------------------------------------------------------------- /src/Query/MultipleQuery/class.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import BaseQuery, { ConfigPOJO, Errors } from "../Base"; 3 | import Document from "../../Document"; 4 | import { ObjectSchema, KeyValueStore } from "../../SchemaTypes"; 5 | import { ExtConfigPOJO } from "."; 6 | import { buildExtQuery } from "./utils/buildExtQuery"; 7 | import buildQuery from "../utils/buildQuery"; 8 | import makeError from "../../utils/makeError"; 9 | 10 | class MultipleQuery extends BaseQuery< 11 | T, 12 | ConfigPOJO 13 | > { 14 | protected __config: ConfigPOJO; 15 | 16 | protected __collectionRef: __firestore.CollectionReference; 17 | 18 | protected __schema: ObjectSchema; 19 | 20 | private __extConfig: ExtConfigPOJO; 21 | 22 | constructor( 23 | input: ConfigPOJO, 24 | collectionRef: __firestore.CollectionReference, 25 | schema: ObjectSchema 26 | ) { 27 | super(); 28 | this.__config = input; 29 | this.__collectionRef = collectionRef; 30 | this.__schema = schema; 31 | this.__extConfig = {}; 32 | } 33 | 34 | /** 35 | * limits the number of documents to be returned 36 | * @param {number} limit The no of first matching documents to return 37 | * @returns {this} returns Multiple Query instance 38 | */ 39 | public limit = (limit: number) => { 40 | this.__extConfig.limit = limit; 41 | 42 | return this; 43 | }; 44 | 45 | /** 46 | * Specifies the offset of the returned results. 47 | * @param {number} offset Offset Number 48 | * @returns {this} returns Multiple Query instance 49 | */ 50 | public offset = (offset: number) => { 51 | this.__extConfig.offset = offset; 52 | 53 | return this; 54 | }; 55 | 56 | /** 57 | * Starts at the provided set of field values relative to the order of the query. 58 | * @param {number} value No to startAt relative to the order of the query 59 | * @returns {this} returns Multiple Query instance 60 | */ 61 | public startAt = (value: number) => { 62 | this.__extConfig.startAt = value; 63 | 64 | return this; 65 | }; 66 | 67 | /** 68 | * sorts by the specified field, optionally in descending order instead of ascending. 69 | * @param {string[]} fields Comma seperated list of fields to sort by preceded by either '+' or '-' denoting ascending or descending order respectively. If passed none, defaults to ascending order. 70 | * @returns {this} returns Multiple Query instance 71 | */ 72 | public orderBy = (...fields: string[]) => { 73 | this.__extConfig.orderBy = fields; 74 | 75 | return this; 76 | }; 77 | 78 | /** 79 | * Selects the fields to be included in the final result. 80 | * @param fields Comma separated list of fields to include in the final result. 81 | * @returns {this} returns Multiple Query instance 82 | */ 83 | public select = (...fields: string[]) => { 84 | this.__extConfig.select = fields; 85 | 86 | return this; 87 | }; 88 | 89 | /** 90 | * @returns {Promise[]>} returns a promise that resolves to an array of documents 91 | */ 92 | public exec = async (): Promise[]> => { 93 | if (!this.__config) { 94 | throw makeError(Errors.MissingConfig, "Query is not configured."); 95 | } 96 | 97 | let query: __firestore.CollectionReference | __firestore.Query = 98 | this.__collectionRef; 99 | 100 | Object.keys(this.__config).forEach((key: keyof ConfigPOJO) => { 101 | query = buildQuery(key, this.__config[key], query); 102 | }); 103 | 104 | query = buildExtQuery(query, this.__extConfig); 105 | 106 | const querySnapshot = await query.get(); 107 | 108 | const documents: Document[] = []; 109 | 110 | querySnapshot.forEach((docSnap) => { 111 | documents.push( 112 | new Document( 113 | docSnap.ref as __firestore.DocumentReference, 114 | this.__schema 115 | ) 116 | ); 117 | }); 118 | 119 | return documents; 120 | }; 121 | } 122 | 123 | export default MultipleQuery; 124 | -------------------------------------------------------------------------------- /src/Query/MultipleQuery/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { ExtConfigPOJO } from "./types/extConfig"; 3 | export { buildExtQuery } from "./utils/buildExtQuery"; 4 | -------------------------------------------------------------------------------- /src/Query/MultipleQuery/types/extConfig.ts: -------------------------------------------------------------------------------- 1 | type ExtConfigPOJO = { 2 | limit?: number; 3 | offset?: number; 4 | startAt?: number; 5 | orderBy?: string[]; 6 | select?: string[]; 7 | }; 8 | 9 | export { ExtConfigPOJO }; 10 | -------------------------------------------------------------------------------- /src/Query/MultipleQuery/utils/buildExtQuery.ts: -------------------------------------------------------------------------------- 1 | import { ExtConfigPOJO } from ".."; 2 | import { BuildExtQueryFn } from "../../Base"; 3 | 4 | const buildExtQuery: BuildExtQueryFn = (query, extConfig) => { 5 | let extendedQuery = query; 6 | 7 | if (extConfig.startAt) { 8 | extendedQuery = extendedQuery.startAt(extConfig.startAt); 9 | } 10 | 11 | if (extConfig.offset) { 12 | extendedQuery = extendedQuery.offset(extConfig.offset); 13 | } 14 | 15 | if (extConfig.orderBy) { 16 | extConfig.orderBy.forEach((field: string) => { 17 | if (field[0] === "-") { 18 | const key = field.replace("-", ""); 19 | extendedQuery = extendedQuery.orderBy(key, "desc"); 20 | } else if (field[0] === "+") { 21 | const key = field.replace("+", ""); 22 | extendedQuery = extendedQuery.orderBy(key, "asc"); 23 | } else { 24 | extendedQuery = extendedQuery.orderBy(field, "asc"); 25 | } 26 | }); 27 | } 28 | 29 | if (extConfig.select) { 30 | extendedQuery = extendedQuery.select(...extConfig.select); 31 | } 32 | 33 | if (extConfig.limit) { 34 | extendedQuery = extendedQuery.limit(extConfig.limit); 35 | } 36 | 37 | return extendedQuery; 38 | }; 39 | 40 | export { buildExtQuery }; 41 | -------------------------------------------------------------------------------- /src/Query/SingleQuery/class.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import Document from "../../Document"; 3 | import BaseQuery, { ConfigPOJOWithId, Errors } from "../Base"; 4 | import { ExtConfigPOJO } from "."; 5 | import { ObjectSchema, KeyValueStore } from "../../SchemaTypes"; 6 | import { buildExtQuery } from "./utils/buildExtQuery"; 7 | import makeError from "../../utils/makeError"; 8 | import buildQuery from "../utils/buildQuery"; 9 | 10 | class SingleQuery extends BaseQuery< 11 | T, 12 | ConfigPOJOWithId 13 | > { 14 | protected __config: ConfigPOJOWithId; 15 | 16 | protected __collectionRef: __firestore.CollectionReference; 17 | 18 | protected __schema: ObjectSchema; 19 | 20 | private __extConfig: ExtConfigPOJO; 21 | 22 | private __queryById: boolean = false; 23 | 24 | constructor( 25 | input: ConfigPOJOWithId, 26 | collectionRef: __firestore.CollectionReference, 27 | schema: ObjectSchema, 28 | queryById: boolean = false 29 | ) { 30 | super(); 31 | this.__config = input; 32 | this.__collectionRef = collectionRef; 33 | this.__schema = schema; 34 | this.__extConfig = {}; 35 | this.__queryById = queryById; 36 | } 37 | 38 | /** 39 | * Selects the fields to be included in the final result. 40 | * @param fields Comma separated list of fields to include in the final result. 41 | * @returns {this} returns Single Query instance 42 | */ 43 | public select = (...fields: string[]) => { 44 | this.__extConfig.select = fields; 45 | 46 | return this; 47 | }; 48 | 49 | /** 50 | * @returns {Promise>} returns a promise that resolves to a single document 51 | */ 52 | exec = async (): Promise | undefined> => { 53 | if (!this.__config) { 54 | throw makeError(Errors.MissingConfig, "Query is not configured."); 55 | } 56 | 57 | if ( 58 | this.__queryById && 59 | this.__config._id && 60 | typeof this.__config._id === "string" 61 | ) { 62 | const docRef = this.__collectionRef.doc(this.__config._id); 63 | 64 | return new Document( 65 | docRef as __firestore.DocumentReference, 66 | this.__schema 67 | ); 68 | } 69 | 70 | let query: __firestore.CollectionReference | __firestore.Query = 71 | this.__collectionRef; 72 | 73 | Object.keys(this.__config).forEach((k) => { 74 | const key = k as keyof ConfigPOJOWithId; 75 | if (key !== "_id") { 76 | query = buildQuery(key, this.__config[key], query); 77 | } 78 | }); 79 | 80 | query = buildExtQuery(query, this.__extConfig); 81 | 82 | const querySnapshot = await query.get(); 83 | 84 | if (querySnapshot.empty) { 85 | return undefined; 86 | } 87 | const documentRef = querySnapshot.docs[0]; 88 | 89 | if (typeof documentRef === "undefined") { 90 | return undefined; 91 | } 92 | 93 | return new Document( 94 | documentRef.ref as __firestore.DocumentReference, 95 | this.__schema 96 | ); 97 | }; 98 | } 99 | 100 | export default SingleQuery; 101 | -------------------------------------------------------------------------------- /src/Query/SingleQuery/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { ExtConfigPOJO } from "./types/extConfig"; 3 | export { buildExtQuery } from "./utils/buildExtQuery"; 4 | -------------------------------------------------------------------------------- /src/Query/SingleQuery/types/extConfig.ts: -------------------------------------------------------------------------------- 1 | type ExtConfigPOJO = { 2 | select?: string[]; 3 | }; 4 | 5 | export { ExtConfigPOJO }; 6 | -------------------------------------------------------------------------------- /src/Query/SingleQuery/utils/buildExtQuery.ts: -------------------------------------------------------------------------------- 1 | import { ExtConfigPOJO } from ".."; 2 | import { BuildExtQueryFn } from "../../Base"; 3 | 4 | const buildExtQuery: BuildExtQueryFn = (query, extConfig) => { 5 | let extendedQuery = query; 6 | 7 | if (extConfig.select) { 8 | extendedQuery = extendedQuery.select(...extConfig.select); 9 | } 10 | 11 | return extendedQuery; 12 | }; 13 | 14 | export { buildExtQuery }; 15 | -------------------------------------------------------------------------------- /src/Query/UpdateQuery/class.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import BaseQuery, { ConfigPOJOWithId, Errors } from "../Base"; 3 | import Document from "../../Document"; 4 | import { ObjectSchema, KeyValueStore } from "../../SchemaTypes"; 5 | import { UpdateOptions, UpdateConfigPOJO } from "."; 6 | import buildQuery from "../utils/buildQuery"; 7 | import makeError from "../../utils/makeError"; 8 | 9 | class UpdateQuery extends BaseQuery< 10 | T, 11 | ConfigPOJOWithId 12 | > { 13 | protected __config: ConfigPOJOWithId; 14 | 15 | protected __collectionRef: __firestore.CollectionReference; 16 | 17 | protected __schema: ObjectSchema; 18 | 19 | private __updateConfig: UpdateConfigPOJO; 20 | 21 | private __updateOptions: UpdateOptions; 22 | 23 | private __queryById: boolean = false; 24 | 25 | constructor( 26 | input: ConfigPOJOWithId, 27 | collectionRef: __firestore.CollectionReference, 28 | schema: ObjectSchema, 29 | updateConfig: UpdateConfigPOJO, 30 | updateOptions: UpdateOptions, 31 | queryById: boolean = false 32 | ) { 33 | super(); 34 | this.__config = input; 35 | this.__collectionRef = collectionRef; 36 | this.__schema = schema; 37 | this.__updateConfig = updateConfig; 38 | this.__updateOptions = updateOptions; 39 | this.__queryById = queryById; 40 | } 41 | 42 | public exec = async (): Promise> => { 43 | if (!this.__config) { 44 | throw makeError(Errors.MissingConfig, "Query is not configured."); 45 | } 46 | 47 | if (!this.__updateConfig) { 48 | throw makeError( 49 | Errors.MissingUpdateConfig, 50 | "No data is provided to update." 51 | ); 52 | } 53 | 54 | let query: __firestore.CollectionReference | __firestore.Query; 55 | let documentRef: __firestore.DocumentReference; 56 | 57 | if ( 58 | this.__queryById && 59 | this.__config._id && 60 | typeof this.__config._id === "string" 61 | ) { 62 | documentRef = this.__collectionRef.doc( 63 | this.__config._id 64 | ) as __firestore.DocumentReference; 65 | } else { 66 | query = this.__collectionRef; 67 | 68 | Object.keys(this.__config).forEach((k: string) => { 69 | const key = k as keyof ConfigPOJOWithId; 70 | 71 | if (key !== "_id") { 72 | query = buildQuery(key, this.__config[key], query); 73 | } 74 | }); 75 | 76 | const querySnapshot = await query.get(); 77 | 78 | documentRef = querySnapshot.docs[0] 79 | ?.ref as __firestore.DocumentReference; 80 | } 81 | 82 | if (typeof documentRef === "undefined") { 83 | throw makeError(Errors.NotFound, "No document found matching the query."); 84 | } 85 | 86 | const document = new Document(documentRef, this.__schema); 87 | 88 | await document.update(this.__updateConfig, this.__updateOptions); 89 | 90 | return document; 91 | }; 92 | } 93 | 94 | export default UpdateQuery; 95 | -------------------------------------------------------------------------------- /src/Query/UpdateQuery/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { UpdateConfigPOJO } from "./types/updateConfig"; 3 | export { UpdateOptions } from "./types/updateOptions"; 4 | -------------------------------------------------------------------------------- /src/Query/UpdateQuery/types/updateConfig.ts: -------------------------------------------------------------------------------- 1 | import { KeyValueStore } from "../../../SchemaTypes"; 2 | 3 | type UpdateConfigPOJO = { 4 | [k in keyof T]?: T[k]; 5 | }; 6 | 7 | export { UpdateConfigPOJO }; 8 | -------------------------------------------------------------------------------- /src/Query/UpdateQuery/types/updateOptions.ts: -------------------------------------------------------------------------------- 1 | type UpdateOptions = { 2 | merge: boolean; 3 | mergeFields?: string[]; 4 | }; 5 | 6 | export { UpdateOptions }; 7 | -------------------------------------------------------------------------------- /src/Query/index.ts: -------------------------------------------------------------------------------- 1 | import * as BaseQueryNs from "./Base"; 2 | import * as SingleQueryNs from "./SingleQuery"; 3 | import * as MultipleQueryNs from "./MultipleQuery"; 4 | import * as DeleteQueryNs from "./DeleteQuery"; 5 | import * as UpdateQueryNs from "./UpdateQuery"; 6 | import { KeyValueStore } from "../SchemaTypes"; 7 | 8 | export class SingleQuery< 9 | T extends KeyValueStore 10 | > extends SingleQueryNs.default {} 11 | export class MultipleQuery< 12 | T extends KeyValueStore 13 | > extends MultipleQueryNs.default {} 14 | export class DeleteQuery< 15 | T extends KeyValueStore 16 | > extends DeleteQueryNs.default {} 17 | export class UpdateQuery< 18 | T extends KeyValueStore 19 | > extends UpdateQueryNs.default {} 20 | 21 | export { 22 | BaseQueryNs, 23 | SingleQueryNs, 24 | MultipleQueryNs, 25 | DeleteQueryNs, 26 | UpdateQueryNs, 27 | }; 28 | -------------------------------------------------------------------------------- /src/Query/utils/buildQuery.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import { KeyValueStore } from "../../SchemaTypes"; 3 | import { ConfigPOJO, Errors } from "../Base"; 4 | import operatorsTypeCheck from "./operatorsTypeCheck"; 5 | import parseOperators from "./parseOperators"; 6 | import makeError from "../../utils/makeError"; 7 | 8 | const buildQuery = ( 9 | key: keyof ConfigPOJO, 10 | value: ConfigPOJO[keyof T], 11 | query: __firestore.Query | __firestore.CollectionReference 12 | ): __firestore.Query => { 13 | if (typeof value === "object" && value !== null) { 14 | if (Array.isArray(value)) { 15 | throw makeError(Errors.Invalid, "Arrays are not allowed in query."); 16 | } 17 | 18 | if (value instanceof Date) { 19 | return query.where( 20 | key.toString(), 21 | "==", 22 | __firestore.Timestamp.fromDate(value) 23 | ); 24 | } 25 | 26 | if (operatorsTypeCheck(value)) { 27 | return parseOperators(key, value, query); 28 | } 29 | 30 | // Flatten KeyValueStore and buildQuery recursively 31 | } 32 | 33 | return query.where(key.toString(), "==", value); 34 | }; 35 | 36 | export default buildQuery; 37 | -------------------------------------------------------------------------------- /src/Query/utils/convertDate.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import { Operators } from "../Base"; 3 | 4 | const convertDate = (value: Operators[keyof Operators]) => { 5 | if (Array.isArray(value)) { 6 | return value.map((val) => 7 | val instanceof Date ? __firestore.Timestamp.fromDate(val) : val 8 | ); 9 | } 10 | 11 | return value instanceof Date ? __firestore.Timestamp.fromDate(value) : value; 12 | }; 13 | 14 | export default convertDate; 15 | -------------------------------------------------------------------------------- /src/Query/utils/operatorsTypeCheck.ts: -------------------------------------------------------------------------------- 1 | import { Operators } from "../Base"; 2 | 3 | const operatorsTypeCheck = (value: object): value is Operators => { 4 | if (value === null || Array.isArray(value) || value instanceof Date) 5 | return false; 6 | 7 | // eslint-disable-next-line no-restricted-syntax 8 | for (const key in value) { 9 | if (key.trim().charAt(0) !== "$") return false; 10 | } 11 | 12 | return true; 13 | }; 14 | 15 | export default operatorsTypeCheck; 16 | -------------------------------------------------------------------------------- /src/Query/utils/parseOperators.ts: -------------------------------------------------------------------------------- 1 | import { firestore as __firestore } from "firebase-admin"; 2 | import { ConfigPOJO, Operators } from "../Base"; 3 | import { KeyValueStore } from "../../SchemaTypes"; 4 | import convertDate from "./convertDate"; 5 | 6 | const parseOperators = ( 7 | key: keyof ConfigPOJO, 8 | value: Operators, 9 | query: __firestore.Query 10 | ): __firestore.Query => { 11 | let constructedQuery: __firestore.Query = query; 12 | 13 | if (value.$gt) { 14 | constructedQuery = constructedQuery.where( 15 | key.toString(), 16 | ">", 17 | convertDate(value.$gt) 18 | ); 19 | } 20 | 21 | if (value.$lt) { 22 | constructedQuery = constructedQuery.where( 23 | key.toString(), 24 | "<", 25 | convertDate(value.$lt) 26 | ); 27 | } 28 | 29 | if (value.$gte) { 30 | constructedQuery = constructedQuery.where( 31 | key.toString(), 32 | ">=", 33 | convertDate(value.$gte) 34 | ); 35 | } 36 | 37 | if (value.$lte) { 38 | constructedQuery = constructedQuery.where( 39 | key.toString(), 40 | "<=", 41 | convertDate(value.$lte) 42 | ); 43 | } 44 | 45 | if (value.$eq) { 46 | constructedQuery = constructedQuery.where( 47 | key.toString(), 48 | "==", 49 | convertDate(value.$eq) 50 | ); 51 | } 52 | 53 | if (value.$neq) { 54 | constructedQuery = constructedQuery.where( 55 | key.toString(), 56 | "!=", 57 | convertDate(value.$neq) 58 | ); 59 | } 60 | 61 | if (value.$ctn) { 62 | constructedQuery = constructedQuery.where( 63 | key.toString(), 64 | Array.isArray(value.$ctn) ? "array-contains-any" : "array-contains", 65 | convertDate(value.$ctn) 66 | ); 67 | } 68 | 69 | if (value.$in) { 70 | constructedQuery = constructedQuery.where( 71 | key.toString(), 72 | "in", 73 | convertDate(value.$in) 74 | ); 75 | } 76 | 77 | if (value.$nin) { 78 | constructedQuery = constructedQuery.where( 79 | key.toString(), 80 | "not-in", 81 | convertDate(value.$nin) 82 | ); 83 | } 84 | 85 | return constructedQuery; 86 | }; 87 | 88 | export default parseOperators; 89 | -------------------------------------------------------------------------------- /src/SchemaTypes/Base/class.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchemaConfig as Config } from "./types/BaseSchema"; 2 | import { ValidateFn } from "./types/ValidateFn"; 3 | 4 | /** 5 | * Base SchemaType Class to define the structure of other SchemaType Classes 6 | */ 7 | abstract class SchemaType { 8 | protected __config: Config = {}; 9 | 10 | /** 11 | * marks the value as a required value. 12 | * @returns {this} same instance of class with property set as required. 13 | */ 14 | public required = (): this => { 15 | this.__config.required = true; 16 | return this; 17 | }; 18 | 19 | /** 20 | * sets a default value for given property. 21 | * @param {T} x value to be set as default 22 | * @returns {this} same instance of class with property having a default value. 23 | */ 24 | public default = (x: T): this => { 25 | this.__config.default = x; 26 | return this; 27 | }; 28 | 29 | public get __required(): boolean { 30 | return Boolean(this.__config.required); 31 | } 32 | 33 | public get __default(): T | undefined { 34 | return this.__config.default; 35 | } 36 | 37 | abstract validate: ValidateFn; 38 | } 39 | 40 | export default SchemaType; 41 | -------------------------------------------------------------------------------- /src/SchemaTypes/Base/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { BaseError as Errors } from "./types/BaseError"; 3 | export { BaseSchemaConfig as Config } from "./types/BaseSchema"; 4 | export { ValidateFn, Options as ValidationOptions } from "./types/ValidateFn"; 5 | -------------------------------------------------------------------------------- /src/SchemaTypes/Base/types/BaseError.ts: -------------------------------------------------------------------------------- 1 | interface BaseError { 2 | error: string | null; 3 | errorType: T; 4 | } 5 | 6 | export { BaseError }; 7 | -------------------------------------------------------------------------------- /src/SchemaTypes/Base/types/BaseSchema.ts: -------------------------------------------------------------------------------- 1 | interface BaseSchemaConfig { 2 | required?: true; 3 | default?: T; 4 | } 5 | 6 | export { BaseSchemaConfig }; 7 | -------------------------------------------------------------------------------- /src/SchemaTypes/Base/types/ValidateFn.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from "firebase-admin"; 2 | import { BaseError } from "./BaseError"; 3 | 4 | type Options = { 5 | onlySupplied?: boolean; 6 | onlyKeys?: boolean; 7 | }; 8 | 9 | type ValidateFn = ( 10 | x: any, 11 | key: string, 12 | options?: Options 13 | ) => { 14 | value: T | Partial | firestore.Timestamp; 15 | valid: boolean; 16 | errors: BaseError[]; 17 | }; 18 | 19 | export { ValidateFn, Options }; 20 | -------------------------------------------------------------------------------- /src/SchemaTypes/Boolean/checks/allChecks.ts: -------------------------------------------------------------------------------- 1 | export { typeCheck as type } from "./typeCheck"; 2 | -------------------------------------------------------------------------------- /src/SchemaTypes/Boolean/checks/index.ts: -------------------------------------------------------------------------------- 1 | import { Errors as BaseErrors } from "../../Base"; 2 | import { Errors, Config } from ".."; 3 | import * as check from "./allChecks"; 4 | 5 | const checkRunner = ( 6 | x: any, 7 | config: Config, 8 | key: string 9 | ): { 10 | value: boolean; 11 | errors: BaseErrors[]; 12 | } => { 13 | const { strict } = config; 14 | const errors: BaseErrors[] = []; 15 | 16 | let val = x; 17 | if (!strict) val = Boolean(val); 18 | 19 | // type check 20 | if (!check.type(val)) { 21 | errors.push({ 22 | error: `${key} must be a boolean`, 23 | errorType: Errors.Type, 24 | }); 25 | return { value: val, errors }; 26 | } 27 | 28 | return { value: val, errors }; 29 | }; 30 | 31 | export default checkRunner; 32 | -------------------------------------------------------------------------------- /src/SchemaTypes/Boolean/checks/typeCheck.ts: -------------------------------------------------------------------------------- 1 | const typeCheck = (x: any): x is boolean => { 2 | if (typeof x === "boolean") { 3 | return true; 4 | } 5 | return false; 6 | }; 7 | 8 | export { typeCheck }; 9 | -------------------------------------------------------------------------------- /src/SchemaTypes/Boolean/class.ts: -------------------------------------------------------------------------------- 1 | import BaseSchema, { Errors as BaseErrors } from "../Base"; 2 | import { Config, Errors } from "."; 3 | import checkRunner from "./checks"; 4 | 5 | class BooleanSchema extends BaseSchema { 6 | protected override __config: Config = {}; 7 | 8 | public strict(): this { 9 | this.__config.strict = true; 10 | return this; 11 | } 12 | 13 | validate = ( 14 | x: any, 15 | key: string = "value" 16 | ): { 17 | value: boolean; 18 | valid: boolean; 19 | errors: BaseErrors[]; 20 | } => { 21 | const { value, errors } = checkRunner(x, this.__config, key); 22 | 23 | return { 24 | valid: Boolean(errors.length === 0), 25 | value, 26 | errors, 27 | }; 28 | }; 29 | } 30 | 31 | export default BooleanSchema; 32 | -------------------------------------------------------------------------------- /src/SchemaTypes/Boolean/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { BooleanSchemaErrors as Errors } from "./types/BooleanError"; 3 | export { BooleanSchemaConfig as Config } from "./types/BooleanSchema"; 4 | -------------------------------------------------------------------------------- /src/SchemaTypes/Boolean/types/BooleanError.ts: -------------------------------------------------------------------------------- 1 | enum BooleanSchemaErrors { 2 | None = "None", 3 | Type = "Boolean/Type", 4 | } 5 | 6 | export { BooleanSchemaErrors }; 7 | -------------------------------------------------------------------------------- /src/SchemaTypes/Boolean/types/BooleanSchema.ts: -------------------------------------------------------------------------------- 1 | import { Config as BaseConfig } from "../../Base"; 2 | 3 | interface BooleanSchemaConfig extends BaseConfig { 4 | strict?: true; 5 | } 6 | 7 | export { BooleanSchemaConfig }; 8 | -------------------------------------------------------------------------------- /src/SchemaTypes/Date/checks/allChecks.ts: -------------------------------------------------------------------------------- 1 | export { typeCheck as type } from "./typeCheck"; 2 | export { 3 | greaterThanCheck as greaterThan, 4 | greaterThanOrEqualToCheck as greaterThanOrEqual, 5 | lessThanCheck as lessThan, 6 | lessThanOrEqualToCheck as lessThanOrEqual, 7 | equalToCheck as equalTo, 8 | } from "./valueCheck"; 9 | -------------------------------------------------------------------------------- /src/SchemaTypes/Date/checks/index.ts: -------------------------------------------------------------------------------- 1 | import { Errors as BaseErrors } from "../../Base"; 2 | import { Errors, Config } from ".."; 3 | import * as check from "./allChecks"; 4 | 5 | const checkRunner = ( 6 | x: any, 7 | config: Config, 8 | key: string 9 | ): { value: Date; errors: BaseErrors[] } => { 10 | const { 11 | greaterThan, 12 | greaterThanOrEqualTo, 13 | lessThan, 14 | lessThanOrEqualTo, 15 | equalTo, 16 | } = config; 17 | const errors: BaseErrors[] = []; 18 | 19 | // type check 20 | if (!check.type(x)) { 21 | errors.push({ 22 | error: `${key} must be a Date object`, 23 | errorType: Errors.Type, 24 | }); 25 | return { value: x, errors }; 26 | } 27 | 28 | // value check 29 | if (greaterThan && !check.greaterThan(x, greaterThan)) { 30 | errors.push({ 31 | error: `${key} must be greater than ${greaterThan}`, 32 | errorType: Errors.Value, 33 | }); 34 | } 35 | if ( 36 | greaterThanOrEqualTo && 37 | !check.greaterThanOrEqual(x, greaterThanOrEqualTo) 38 | ) { 39 | errors.push({ 40 | error: `${key} must be greater than or equal to ${greaterThanOrEqualTo}`, 41 | errorType: Errors.Value, 42 | }); 43 | } 44 | if (lessThan && !check.lessThan(x, lessThan)) { 45 | errors.push({ 46 | error: `${key} must be less than ${lessThan}`, 47 | errorType: Errors.Value, 48 | }); 49 | } 50 | if (lessThanOrEqualTo && !check.lessThanOrEqual(x, lessThanOrEqualTo)) { 51 | errors.push({ 52 | error: `${key} must be less than or equal to ${lessThanOrEqualTo}`, 53 | errorType: Errors.Value, 54 | }); 55 | } 56 | if (equalTo && !check.equalTo(x, equalTo)) { 57 | errors.push({ 58 | error: `${key} must be equal to ${equalTo}`, 59 | errorType: Errors.Value, 60 | }); 61 | } 62 | 63 | return { value: x, errors }; 64 | }; 65 | 66 | export default checkRunner; 67 | -------------------------------------------------------------------------------- /src/SchemaTypes/Date/checks/typeCheck.ts: -------------------------------------------------------------------------------- 1 | const typeCheck = (x: any): x is Date => { 2 | if (x instanceof Date) return true; 3 | 4 | return false; 5 | }; 6 | 7 | export { typeCheck }; 8 | -------------------------------------------------------------------------------- /src/SchemaTypes/Date/checks/valueCheck.ts: -------------------------------------------------------------------------------- 1 | const greaterThanCheck = (x: Date, threshold: Date): boolean => { 2 | if (x.getTime() > threshold.getTime()) return true; 3 | 4 | return false; 5 | }; 6 | 7 | const greaterThanOrEqualToCheck = (x: Date, threshold: Date): boolean => { 8 | if (x.getTime() >= threshold.getTime()) return true; 9 | 10 | return false; 11 | }; 12 | 13 | const lessThanCheck = (x: Date, threshold: Date): boolean => { 14 | if (x.getTime() < threshold.getTime()) return true; 15 | 16 | return false; 17 | }; 18 | 19 | const lessThanOrEqualToCheck = (x: Date, threshold: Date): boolean => { 20 | if (x.getTime() <= threshold.getTime()) return true; 21 | 22 | return false; 23 | }; 24 | 25 | const equalToCheck = (x: Date, threshold: Date): boolean => { 26 | if (x.getTime() === threshold.getTime()) return true; 27 | 28 | return false; 29 | }; 30 | 31 | export { 32 | greaterThanCheck, 33 | greaterThanOrEqualToCheck, 34 | lessThanCheck, 35 | lessThanOrEqualToCheck, 36 | equalToCheck, 37 | }; 38 | -------------------------------------------------------------------------------- /src/SchemaTypes/Date/class.ts: -------------------------------------------------------------------------------- 1 | import BaseSchema, { Errors as BaseErrors } from "../Base"; 2 | import { Errors, Config } from "."; 3 | import checkRunner from "./checks"; 4 | 5 | class DateSchema extends BaseSchema { 6 | protected override __config: Config = {}; 7 | 8 | public greaterThan = (x: Date): this => { 9 | this.__config.greaterThan = x; 10 | return this; 11 | }; 12 | 13 | public greaterThanOrEqualTo = (x: Date): this => { 14 | this.__config.greaterThanOrEqualTo = x; 15 | return this; 16 | }; 17 | 18 | public lessThan = (x: Date): this => { 19 | this.__config.lessThan = x; 20 | return this; 21 | }; 22 | 23 | public lessThanOrEqualTo = (x: Date): this => { 24 | this.__config.lessThanOrEqualTo = x; 25 | return this; 26 | }; 27 | 28 | public equalTo = (x: Date): this => { 29 | this.__config.equalTo = x; 30 | return this; 31 | }; 32 | 33 | validate = ( 34 | x: any, 35 | key: string = "value" 36 | ): { 37 | value: Date; 38 | valid: boolean; 39 | errors: BaseErrors[]; 40 | } => { 41 | const { value, errors } = checkRunner(x, this.__config, key); 42 | 43 | return { 44 | valid: Boolean(errors.length === 0), 45 | value, 46 | errors, 47 | }; 48 | }; 49 | } 50 | 51 | export default DateSchema; 52 | -------------------------------------------------------------------------------- /src/SchemaTypes/Date/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { DateSchemaErrors as Errors } from "./types/DateError"; 3 | export { DateSchemaConfig as Config } from "./types/DateSchema"; 4 | -------------------------------------------------------------------------------- /src/SchemaTypes/Date/types/DateError.ts: -------------------------------------------------------------------------------- 1 | enum DateSchemaErrors { 2 | None = "None", 3 | Type = "Date/Type", 4 | Value = "Date/InvalidValue", 5 | } 6 | 7 | export { DateSchemaErrors }; 8 | -------------------------------------------------------------------------------- /src/SchemaTypes/Date/types/DateSchema.ts: -------------------------------------------------------------------------------- 1 | import { Config as BaseConfig } from "../../Base"; 2 | 3 | interface DateSchemaConfig extends BaseConfig { 4 | greaterThan?: Date; 5 | greaterThanOrEqualTo?: Date; 6 | lessThan?: Date; 7 | lessThanOrEqualTo?: Date; 8 | equalTo?: Date; 9 | } 10 | 11 | export { DateSchemaConfig }; 12 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/checks/allChecks.ts: -------------------------------------------------------------------------------- 1 | export { typeCheck as type } from "./typeCheck"; 2 | export { integerCheck as integer } from "./integerCheck"; 3 | export { minCheck as min, maxCheck as max } from "./valueCheck"; 4 | export { enumCheck as enum } from "./enumCheck"; 5 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/checks/enumCheck.ts: -------------------------------------------------------------------------------- 1 | const enumCheck = (x: number, allowedVals: number[]): boolean => { 2 | if (allowedVals.includes(x)) return true; 3 | 4 | return false; 5 | }; 6 | 7 | export { enumCheck }; 8 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/checks/index.ts: -------------------------------------------------------------------------------- 1 | import { Errors as BaseErrors } from "../../Base"; 2 | import { Errors, Config } from ".."; 3 | import * as check from "./allChecks"; 4 | 5 | const checkRunner = ( 6 | x: any, 7 | config: Config, 8 | key: string 9 | ): { value: number; errors: BaseErrors[] } => { 10 | const { integer, min, max, enum: cEnum } = config; 11 | const errors: BaseErrors[] = []; 12 | 13 | // type check 14 | if (!check.type(x)) { 15 | errors.push({ 16 | error: `${key} must be a number`, 17 | errorType: Errors.Type, 18 | }); 19 | return { value: x, errors }; 20 | } 21 | 22 | // integer check 23 | if (integer && !check.integer(x)) { 24 | errors.push({ 25 | error: `${key} must be an integer`, 26 | errorType: Errors.Integer, 27 | }); 28 | } 29 | 30 | // value check 31 | if (min && !check.min(x, min)) { 32 | errors.push({ 33 | error: `minimum allowed value for ${key} is ${min}`, 34 | errorType: Errors.Value, 35 | }); 36 | } 37 | if (max && !check.max(x, max)) { 38 | errors.push({ 39 | error: `maximum allowed value for ${key} is ${max}`, 40 | errorType: Errors.Value, 41 | }); 42 | } 43 | 44 | // enum check 45 | if (cEnum && cEnum.length > 0 && !check.enum(x, cEnum)) { 46 | errors.push({ 47 | error: `${key} is not assignable to specified enum`, 48 | errorType: Errors.Enum, 49 | }); 50 | } 51 | 52 | return { value: x, errors }; 53 | }; 54 | 55 | export default checkRunner; 56 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/checks/integerCheck.ts: -------------------------------------------------------------------------------- 1 | const integerCheck = (x: number) => { 2 | if (Number.isInteger(x)) return true; 3 | 4 | return false; 5 | }; 6 | 7 | export { integerCheck }; 8 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/checks/typeCheck.ts: -------------------------------------------------------------------------------- 1 | const typeCheck = (x: any): x is number => { 2 | if (typeof x === "number") return true; 3 | 4 | return false; 5 | }; 6 | 7 | export { typeCheck }; 8 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/checks/valueCheck.ts: -------------------------------------------------------------------------------- 1 | const minCheck = (x: number, val: number): boolean => { 2 | if (x >= val) return true; 3 | 4 | return false; 5 | }; 6 | 7 | const maxCheck = (x: number, val: number): boolean => { 8 | if (x <= val) return true; 9 | 10 | return false; 11 | }; 12 | 13 | export { minCheck, maxCheck }; 14 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/class.ts: -------------------------------------------------------------------------------- 1 | import BaseSchema, { Errors as BaseErrors } from "../Base"; 2 | import { Errors, Config } from "."; 3 | import checkRunner from "./checks"; 4 | 5 | class NumberSchema extends BaseSchema { 6 | protected override __config: Config = {}; 7 | 8 | public integer = (): this => { 9 | this.__config.integer = true; 10 | return this; 11 | }; 12 | 13 | public min = (x: number): this => { 14 | this.__config.min = x; 15 | return this; 16 | }; 17 | 18 | public max = (x: number): this => { 19 | this.__config.max = x; 20 | return this; 21 | }; 22 | 23 | public enum = (x: number[]): this => { 24 | this.__config.enum = x; 25 | return this; 26 | }; 27 | 28 | validate = ( 29 | x: any, 30 | key: string = "value" 31 | ): { 32 | value: number; 33 | valid: boolean; 34 | errors: BaseErrors[]; 35 | } => { 36 | const { value, errors } = checkRunner(x, this.__config, key); 37 | 38 | return { 39 | valid: Boolean(errors.length === 0), 40 | value, 41 | errors, 42 | }; 43 | }; 44 | } 45 | 46 | export default NumberSchema; 47 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { NumberSchemaErrors as Errors } from "./types/NumberError"; 3 | export { NumberSchemaConfig as Config } from "./types/NumberSchema"; 4 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/types/NumberError.ts: -------------------------------------------------------------------------------- 1 | enum NumberSchemaErrors { 2 | None = "None", 3 | Type = "Number/Type", 4 | Integer = "Number/Integer", 5 | Value = "Number/Value", 6 | Enum = "Number/InvalidValue", 7 | } 8 | 9 | export { NumberSchemaErrors }; 10 | -------------------------------------------------------------------------------- /src/SchemaTypes/Number/types/NumberSchema.ts: -------------------------------------------------------------------------------- 1 | import { Config as BaseConfig } from "../../Base"; 2 | 3 | interface NumberSchemaConfig extends BaseConfig { 4 | integer?: true; 5 | min?: number; 6 | max?: number; 7 | enum?: number[]; 8 | } 9 | 10 | export { NumberSchemaConfig }; 11 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/checks/allChecks.ts: -------------------------------------------------------------------------------- 1 | export { typeCheck as type } from "./typeCheck"; 2 | export { keyCheck as key } from "./keyCheck"; 3 | export { patternCheck as pattern } from "./patternCheck"; 4 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/checks/index.ts: -------------------------------------------------------------------------------- 1 | import SchemaType, { 2 | Errors as BaseErrors, 3 | ValidationOptions, 4 | } from "../../Base"; 5 | import { Errors, Config, KeyValueStore } from ".."; 6 | import { CheckReturn } from "./type"; 7 | import * as check from "./allChecks"; 8 | 9 | const checksRunner = ( 10 | x: any, 11 | config: Config, 12 | key: string, 13 | options: NonNullable 14 | ): { 15 | value: Partial; 16 | errors: BaseErrors[]; 17 | } => { 18 | const { keys: configKeys, pattern: configPattern } = config; 19 | 20 | if (!check.type(x)) 21 | return { 22 | value: x, 23 | errors: [ 24 | { 25 | error: `${key} must contain key value pairs`, 26 | errorType: Errors.Type, 27 | }, 28 | ], 29 | }; 30 | 31 | const transformed: Partial = {}; 32 | const errors: BaseErrors[] = []; 33 | 34 | const requiredKeys = new Map(); 35 | const defaultKeys = new Map(); 36 | 37 | if ( 38 | configKeys && 39 | Object.keys(configKeys).length > 0 && 40 | !options.onlySupplied 41 | ) { 42 | Object.keys(configKeys).forEach((k: keyof T) => { 43 | const schema = configKeys[k]; 44 | 45 | if ( 46 | schema && 47 | schema.__required && 48 | typeof schema.__default === "undefined" 49 | ) 50 | requiredKeys.set(k, schema); 51 | if (schema && typeof schema.__default !== "undefined") 52 | defaultKeys.set(k, schema.__default); 53 | }); 54 | } 55 | 56 | Object.keys(x).forEach((k: string) => { 57 | const keyResolution: CheckReturn = { 58 | resolved: false, 59 | valid: false, 60 | value: null, 61 | errors: [], 62 | }; 63 | 64 | if (configKeys && Object.keys(configKeys).length > 0) { 65 | const keyCheckResolution = check.key(k, x[k]!, configKeys, options); 66 | Object.assign(keyResolution, keyCheckResolution); 67 | } 68 | 69 | if ( 70 | !options.onlyKeys && 71 | !keyResolution.resolved && 72 | configPattern && 73 | configPattern.length === 2 74 | ) { 75 | const patternCheckResolution = check.pattern(k, x[k]!, configPattern); 76 | Object.assign(keyResolution, patternCheckResolution); 77 | } 78 | 79 | if (keyResolution.resolved) { 80 | if (!options.onlySupplied) { 81 | if (requiredKeys.has(k)) requiredKeys.delete(k); 82 | if (defaultKeys.has(k)) defaultKeys.delete(k); 83 | } 84 | if (!keyResolution.valid) errors.push(...keyResolution.errors); 85 | 86 | // @ts-ignore 87 | transformed[k] = keyResolution.value; 88 | } else { 89 | errors.push({ 90 | error: `${k} is not allowed`, 91 | errorType: Errors.Keys, 92 | }); 93 | } 94 | }); 95 | 96 | if (!options.onlySupplied) { 97 | defaultKeys.forEach((v, k) => { 98 | transformed[k] = v; 99 | }); 100 | 101 | requiredKeys.forEach((_, k) => { 102 | errors.push({ 103 | error: `${k} is required`, 104 | errorType: Errors.Keys, 105 | }); 106 | }); 107 | } 108 | 109 | return { 110 | value: transformed, 111 | errors, 112 | }; 113 | }; 114 | 115 | export default checksRunner; 116 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/checks/keyCheck.ts: -------------------------------------------------------------------------------- 1 | import { Keys } from "../types/ObjectSchema"; 2 | import { KeyValueStore } from "../types/KeyValue"; 3 | import { CheckReturn } from "./type"; 4 | import { ValidationOptions } from "../../Base"; 5 | 6 | const keyCheck = ( 7 | key: string, 8 | value: KeyValueStore[string], 9 | configKeys: Keys, 10 | options: ValidationOptions 11 | ): CheckReturn => { 12 | const schema = configKeys[key]; 13 | if (!schema) { 14 | return { 15 | resolved: false, 16 | valid: false, 17 | value, 18 | errors: [], 19 | }; 20 | } 21 | 22 | const { 23 | valid, 24 | value: modValue, 25 | errors, 26 | } = schema.validate(value, key.toString(), options); 27 | 28 | return { 29 | resolved: true, 30 | valid, 31 | value: modValue, 32 | errors, 33 | }; 34 | }; 35 | 36 | export { keyCheck }; 37 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/checks/patternCheck.ts: -------------------------------------------------------------------------------- 1 | import { KeyValueStore } from "../types/KeyValue"; 2 | import { Pattern } from "../types/ObjectSchema"; 3 | import { CheckReturn } from "./type"; 4 | 5 | const patternCheck = ( 6 | key: string, 7 | value: KeyValueStore[string], 8 | pattern: Pattern 9 | ): CheckReturn => { 10 | const [keyPattern, valuePattern] = pattern; 11 | 12 | const { valid: keyValid } = keyPattern.validate(key, key.toString()); 13 | if (keyValid) { 14 | const valueValidation = valuePattern.validate(value, key.toString()); 15 | 16 | return { 17 | resolved: true, 18 | ...valueValidation, 19 | }; 20 | } 21 | 22 | return { 23 | resolved: false, 24 | valid: false, 25 | value: null, 26 | errors: [], 27 | }; 28 | }; 29 | 30 | export { patternCheck }; 31 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/checks/type.ts: -------------------------------------------------------------------------------- 1 | import { Errors as BaseErrors } from "../../Base"; 2 | import { KeyValueStore } from "../types/KeyValue"; 3 | 4 | type CheckReturn = { 5 | resolved: boolean; 6 | valid: boolean; 7 | value: KeyValueStore[string]; 8 | errors: BaseErrors[]; 9 | }; 10 | 11 | export { CheckReturn }; 12 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/checks/typeCheck.ts: -------------------------------------------------------------------------------- 1 | import { KeyValueStore } from "../types/KeyValue"; 2 | 3 | const typeCheck = (x: any): x is KeyValueStore => { 4 | if (x !== null && typeof x === "object" && !Array.isArray(x)) return true; 5 | 6 | return false; 7 | }; 8 | 9 | export { typeCheck }; 10 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/class.ts: -------------------------------------------------------------------------------- 1 | import SchemaType, { Errors as BaseErrors, ValidationOptions } from "../Base"; 2 | import { KeyValueStore, Config, Keys, Pattern } from "."; 3 | import checksRunner from "./checks"; 4 | 5 | class ObjectSchema extends SchemaType { 6 | protected override __config: Config = {}; 7 | 8 | get __keys() { 9 | const configKeys = this.__config.keys; 10 | if (configKeys) return Object.keys(configKeys); 11 | return []; 12 | } 13 | 14 | public keys = (x: Keys): this => { 15 | this.__config.keys = x; 16 | return this; 17 | }; 18 | 19 | public pattern = (x: Pattern): this => { 20 | if (x.length === 2) { 21 | this.__config.pattern = x; 22 | } 23 | return this; 24 | }; 25 | 26 | validate = ( 27 | x: any, 28 | key: string = "value", 29 | options: ValidationOptions = { 30 | onlySupplied: false, 31 | onlyKeys: false, 32 | } 33 | ): { 34 | value: Partial; 35 | valid: boolean; 36 | errors: BaseErrors[]; 37 | } => { 38 | const { value, errors } = checksRunner(x, this.__config, key, options); 39 | 40 | return { 41 | value, 42 | valid: Boolean(errors.length === 0), 43 | errors, 44 | }; 45 | }; 46 | } 47 | 48 | export default ObjectSchema; 49 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { ObjectSchemaErrors as Errors } from "./types/ObjectError"; 3 | export { 4 | ObjectSchemaConfig as Config, 5 | Keys, 6 | Pattern, 7 | } from "./types/ObjectSchema"; 8 | export { KeyValueStore } from "./types/KeyValue"; 9 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/types/KeyValue.ts: -------------------------------------------------------------------------------- 1 | type ValidValueTypes = 2 | | string 3 | | number 4 | | boolean 5 | | Date 6 | | null 7 | | KeyValueStore 8 | | ValidValueTypes[]; 9 | 10 | type KeyValueStore = { 11 | [k in keyof T]: ValidValueTypes; 12 | }; 13 | 14 | export { KeyValueStore }; 15 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/types/ObjectError.ts: -------------------------------------------------------------------------------- 1 | enum ObjectSchemaErrors { 2 | None = "None", 3 | Type = "Object/Type", 4 | Keys = "Object/InvalidKeys", 5 | Pattern = "Object/InvalidPattern", 6 | } 7 | 8 | export { ObjectSchemaErrors }; 9 | -------------------------------------------------------------------------------- /src/SchemaTypes/Object/types/ObjectSchema.ts: -------------------------------------------------------------------------------- 1 | import BaseSchema, { Config } from "../../Base"; 2 | import StringSchema from "../../String"; 3 | 4 | // TODO:- convert basic type to custom type 5 | type Keys = { 6 | [k in keyof T]: BaseSchema; 7 | }; 8 | 9 | type Pattern = [StringSchema, BaseSchema]; 10 | 11 | interface ObjectSchemaConfig extends Config { 12 | keys?: Keys; 13 | pattern?: Pattern; 14 | } 15 | 16 | export { ObjectSchemaConfig, Keys, Pattern }; 17 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/checks/allChecks.ts: -------------------------------------------------------------------------------- 1 | export { typeCheck as type } from "./typeCheck"; 2 | export { 3 | minlengthCheck as minLength, 4 | maxlengthCheck as maxLength, 5 | } from "./lengthCheck"; 6 | export { 7 | lowercaseCheck as lowercase, 8 | uppercaseCheck as uppercase, 9 | } from "./caseCheck"; 10 | export { formatCheck as format } from "./formatCheck"; 11 | export { enumCheck as enum } from "./enumCheck"; 12 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/checks/caseCheck.ts: -------------------------------------------------------------------------------- 1 | const uppercaseCheck = (x: string): boolean => { 2 | if (x === x.toUpperCase()) return true; 3 | 4 | return false; 5 | }; 6 | 7 | const lowercaseCheck = (x: string): boolean => { 8 | if (x === x.toLowerCase()) return true; 9 | 10 | return false; 11 | }; 12 | 13 | export { lowercaseCheck, uppercaseCheck }; 14 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/checks/enumCheck.ts: -------------------------------------------------------------------------------- 1 | const enumCheck = (x: string, allowedValues: string[]): boolean => { 2 | if (allowedValues.includes(x)) return true; 3 | 4 | return false; 5 | }; 6 | 7 | export { enumCheck }; 8 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/checks/formatCheck.ts: -------------------------------------------------------------------------------- 1 | const rEmail = 2 | /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; 3 | 4 | const formatCheck = (x: string, regex: RegExp | "email"): boolean => { 5 | const exp = regex === "email" ? rEmail : regex; 6 | 7 | if (exp.test(x) === true) { 8 | return true; 9 | } 10 | 11 | return false; 12 | }; 13 | 14 | export { formatCheck }; 15 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/checks/index.ts: -------------------------------------------------------------------------------- 1 | import { Errors as BaseErrors } from "../../Base"; 2 | import { Errors, Config } from ".."; 3 | import * as check from "./allChecks"; 4 | 5 | const checkRunner = ( 6 | x: any, 7 | config: Config, 8 | key: string 9 | ): { value: string; errors: BaseErrors[] } => { 10 | const { 11 | trim, 12 | minLength, 13 | maxLength, 14 | uppercase, 15 | lowercase, 16 | regex, 17 | enum: cEnum, 18 | } = config; 19 | const errors: BaseErrors[] = []; 20 | 21 | // type check 22 | if (!check.type(x)) { 23 | errors.push({ 24 | error: `${key} must be a string`, 25 | errorType: Errors.Type, 26 | }); 27 | return { value: x, errors }; 28 | } 29 | 30 | // modifications 31 | let str = x; 32 | if (trim) str = str.trim(); 33 | if (uppercase) str = str.toUpperCase(); 34 | if (lowercase) str = str.toLowerCase(); 35 | 36 | // length checks 37 | if (minLength && !check.minLength(str, minLength)) { 38 | errors.push({ 39 | error: `minimum allowed length for ${key} is ${minLength}`, 40 | errorType: Errors.Length, 41 | }); 42 | } 43 | if (maxLength && !check.maxLength(str, maxLength)) { 44 | errors.push({ 45 | error: `maximum allowed length for ${key} is ${maxLength}`, 46 | errorType: Errors.Length, 47 | }); 48 | } 49 | 50 | // case checks 51 | if (uppercase && !check.uppercase(str)) { 52 | errors.push({ 53 | error: `${key} must be all uppercase`, 54 | errorType: Errors.Case, 55 | }); 56 | } 57 | if (lowercase && !check.lowercase(str)) { 58 | errors.push({ 59 | error: `${key} must be all lowercase`, 60 | errorType: Errors.Case, 61 | }); 62 | } 63 | 64 | // format check 65 | if (regex && !check.format(str, regex)) { 66 | const errorErrors = `${key} must be ${ 67 | regex === "email" ? "a valid email" : "of valid format" 68 | }`; 69 | errors.push({ 70 | error: errorErrors, 71 | errorType: Errors.Format, 72 | }); 73 | } 74 | 75 | // enum check 76 | if (cEnum && !check.enum(str, cEnum)) { 77 | errors.push({ 78 | error: `${key} is not assignable to specified enum`, 79 | errorType: Errors.Enum, 80 | }); 81 | } 82 | 83 | return { value: str, errors }; 84 | }; 85 | 86 | export default checkRunner; 87 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/checks/lengthCheck.ts: -------------------------------------------------------------------------------- 1 | const minlengthCheck = (x: string, len: number): boolean => { 2 | if (x.length >= len) return true; 3 | 4 | return false; 5 | }; 6 | 7 | const maxlengthCheck = (x: string, len: number): boolean => { 8 | if (x.length <= len) return true; 9 | 10 | return false; 11 | }; 12 | 13 | export { minlengthCheck, maxlengthCheck }; 14 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/checks/typeCheck.ts: -------------------------------------------------------------------------------- 1 | const typeCheck = (x: any): x is string => { 2 | if (typeof x === "string") return true; 3 | 4 | return false; 5 | }; 6 | 7 | export { typeCheck }; 8 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/class.ts: -------------------------------------------------------------------------------- 1 | import BaseSchema, { Errors as BaseErrors } from "../Base"; 2 | import { Config, Errors } from "."; 3 | import checkRunner from "./checks"; 4 | 5 | class StringSchema extends BaseSchema { 6 | protected override __config: Config = {}; 7 | 8 | public trim = (): this => { 9 | this.__config.trim = true; 10 | return this; 11 | }; 12 | 13 | public minLength = (x: number): this => { 14 | this.__config.minLength = x; 15 | return this; 16 | }; 17 | 18 | public maxLength = (x: number): this => { 19 | this.__config.maxLength = x; 20 | return this; 21 | }; 22 | 23 | public lowercase = (): this => { 24 | this.__config.lowercase = true; 25 | return this; 26 | }; 27 | 28 | public uppercase = (): this => { 29 | this.__config.uppercase = true; 30 | return this; 31 | }; 32 | 33 | public email = (): this => { 34 | this.__config.regex = "email"; 35 | return this; 36 | }; 37 | 38 | public regex = (x: RegExp): this => { 39 | this.__config.regex = x; 40 | return this; 41 | }; 42 | 43 | public enum = (x: string[]): this => { 44 | if (x.length > 0) { 45 | this.__config.enum = x; 46 | } 47 | return this; 48 | }; 49 | 50 | validate = ( 51 | x: any, 52 | key: string = "value" 53 | ): { value: string; valid: boolean; errors: BaseErrors[] } => { 54 | const { value, errors } = checkRunner(x, this.__config, key); 55 | 56 | return { 57 | valid: Boolean(errors.length === 0), 58 | value, 59 | errors, 60 | }; 61 | }; 62 | } 63 | 64 | export default StringSchema; 65 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./class"; 2 | export { StringSchemaErrors as Errors } from "./types/StringError"; 3 | export { StringSchemaConfig as Config } from "./types/StringSchema"; 4 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/types/StringError.ts: -------------------------------------------------------------------------------- 1 | enum StringSchemaErrors { 2 | None = "None", 3 | Type = "String/Type", 4 | Length = "String/Length", 5 | Case = "String/Case", 6 | Format = "String/Format", 7 | Enum = "String/InvalidValue", 8 | } 9 | 10 | export { StringSchemaErrors }; 11 | -------------------------------------------------------------------------------- /src/SchemaTypes/String/types/StringSchema.ts: -------------------------------------------------------------------------------- 1 | import * as BaseSchema from "../../Base"; 2 | 3 | interface StringSchemaConfig extends BaseSchema.Config { 4 | trim?: true; 5 | minLength?: number; 6 | maxLength?: number; 7 | lowercase?: true; 8 | uppercase?: true; 9 | regex?: RegExp | "email"; 10 | enum?: string[]; 11 | } 12 | 13 | export { StringSchemaConfig }; 14 | -------------------------------------------------------------------------------- /src/SchemaTypes/index.ts: -------------------------------------------------------------------------------- 1 | import * as BaseSchemaNs from "./Base"; 2 | import * as StringSchemaNs from "./String"; 3 | import * as NumberSchemaNs from "./Number"; 4 | import * as BooleanSchemaNs from "./Boolean"; 5 | import * as DateSchemaNs from "./Date"; 6 | import * as ObjectSchemaNs from "./Object"; 7 | 8 | export type KeyValueStore = ObjectSchemaNs.KeyValueStore; 9 | 10 | export class StringSchema extends StringSchemaNs.default {} 11 | export class NumberSchema extends NumberSchemaNs.default {} 12 | export class BooleanSchema extends BooleanSchemaNs.default {} 13 | export class DateSchema extends DateSchemaNs.default {} 14 | export class ObjectSchema< 15 | T extends KeyValueStore = any 16 | > extends ObjectSchemaNs.default {} 17 | 18 | export const string = () => new StringSchema(); 19 | export const number = () => new NumberSchema(); 20 | export const boolean = () => new BooleanSchema(); 21 | export const date = () => new DateSchema(); 22 | export const object = () => 23 | new ObjectSchema(); 24 | 25 | export { 26 | BaseSchemaNs, 27 | StringSchemaNs, 28 | NumberSchemaNs, 29 | BooleanSchemaNs, 30 | DateSchemaNs, 31 | ObjectSchemaNs, 32 | }; 33 | -------------------------------------------------------------------------------- /src/__tests__/Connection/class.test.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | import Connection from "../../Connection/class"; 3 | import * as SchemaType from "../../SchemaTypes"; 4 | 5 | let firestore: FirebaseFirestore.Firestore; 6 | 7 | beforeAll(() => { 8 | process.env["FIRESTORE_EMULATOR_HOST"] = "localhost:8080"; 9 | 10 | admin.initializeApp({ 11 | projectId: "demo-firefly", 12 | }); 13 | 14 | firestore = admin.firestore(); 15 | }); 16 | 17 | describe("Connects to local emulators", () => { 18 | it("connects to firestore emulator", async () => { 19 | expect.assertions(1); 20 | 21 | try { 22 | const connection = new Connection(firestore); 23 | const model = connection.model( 24 | "test", 25 | SchemaType.object<{ 26 | hello: string; 27 | }>().keys({ 28 | hello: SchemaType.string().required(), 29 | }) 30 | ); 31 | 32 | const doc = await model.create({}); 33 | 34 | const data = await doc.data(); 35 | 36 | expect(data).toEqual({ 37 | hello: "world", 38 | }); 39 | } catch (error) { 40 | const errorType = JSON.parse(error.message).type; 41 | expect(errorType).toBe("Model/SchemaValidation"); 42 | } 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/__tests__/Firefly/class.test.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | import Connection from "../../Connection/class"; 3 | import Firefly from "../../Firefly/class"; 4 | 5 | let firestore: FirebaseFirestore.Firestore; 6 | 7 | beforeAll(() => { 8 | process.env["FIRESTORE_EMULATOR_HOST"] = "localhost:8080"; 9 | 10 | admin.initializeApp({ 11 | projectId: "demo-firefly", 12 | }); 13 | 14 | firestore = admin.firestore(); 15 | }); 16 | 17 | describe("Test Firefly main class", () => { 18 | it("should create a Connection instance", () => { 19 | const firefly = new Firefly(); 20 | const fireflyConn = firefly.createConnection(firestore); 21 | expect(fireflyConn).toBeDefined(); 22 | expect(fireflyConn instanceof Connection).toBe(true); 23 | }); 24 | 25 | it("should create a Connection instance with custom names", () => { 26 | const firefly = new Firefly(); 27 | const fireflyConn = firefly.createConnection(firestore, "my-db"); 28 | expect(fireflyConn).toBeDefined(); 29 | expect(fireflyConn instanceof Connection).toBe(true); 30 | const dbConn = firefly.getConnection("my-db"); 31 | expect(dbConn).toBeDefined(); 32 | expect(dbConn instanceof Connection).toBe(true); 33 | }); 34 | 35 | it("should throw an error, if passed a non-existent connection key", () => { 36 | try { 37 | const firefly = new Firefly(); 38 | const fireflyConn = firefly.createConnection(firestore); 39 | expect(fireflyConn).toBeDefined(); 40 | const dbConn = firefly.getConnection("bad-key"); 41 | expect(dbConn).toBeUndefined(); 42 | } catch (err) { 43 | expect(err.message).toBe( 44 | '{"type":"Firefly/Invalid","data":"Firefly Connection with key bad-key does not exist"}' 45 | ); 46 | } 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/__tests__/Query/class.test.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | import Firefly from "../../Firefly/class"; 3 | import Model from "../../Model/class"; 4 | import * as SchemaType from "../../SchemaTypes"; 5 | 6 | let firestore: FirebaseFirestore.Firestore; 7 | 8 | let firefly = new Firefly(); 9 | 10 | let user: Model<{ 11 | userName: string; 12 | isAdmin: boolean; 13 | sNO: number; 14 | }>; 15 | 16 | let userId: string; 17 | 18 | beforeAll(async () => { 19 | process.env["FIRESTORE_EMULATOR_HOST"] = "localhost:8080"; 20 | 21 | admin.initializeApp({ 22 | projectId: "demo-firefly", 23 | }); 24 | 25 | firestore = admin.firestore(); 26 | 27 | const fireflyConn = firefly.createConnection(firestore); 28 | 29 | const userSchema = SchemaType.object<{ 30 | userName: string; 31 | isAdmin: boolean; 32 | sNO: number; 33 | }>().keys({ 34 | userName: SchemaType.string().required(), 35 | isAdmin: SchemaType.boolean().default(false), 36 | sNO: SchemaType.number().required(), 37 | }); 38 | 39 | user = fireflyConn.model("User", userSchema); 40 | 41 | const [userDocument] = await Promise.all([ 42 | user.create({ 43 | userName: "lorem", 44 | isAdmin: true, 45 | sNO: 1, 46 | }), 47 | user.create({ 48 | userName: "ipsum", 49 | isAdmin: false, 50 | sNO: 2, 51 | }), 52 | user.create({ 53 | userName: "dolor", 54 | sNO: 3, 55 | }), 56 | user.create({ 57 | userName: "sit", 58 | isAdmin: true, 59 | sNO: 4, 60 | }), 61 | user.create({ 62 | userName: "amet", 63 | isAdmin: true, 64 | sNO: 5, 65 | }), 66 | user.create({ 67 | userName: "consectetur", 68 | isAdmin: false, 69 | sNO: 6, 70 | }), 71 | ]); 72 | 73 | userId = userDocument.id; 74 | }, 10 * 60 * 1000); 75 | 76 | describe("Tests for query", () => { 77 | it("find returns documents", async () => { 78 | const query = await user 79 | .find({ 80 | userName: "lorem", 81 | }) 82 | .exec(); 83 | 84 | if (!query) { 85 | throw new Error("Document returned from query is empty"); 86 | } 87 | 88 | expect(query).toBeDefined(); 89 | expect(query.length).toBeGreaterThan(0); 90 | 91 | const doc = query[0]; 92 | 93 | if (!doc) { 94 | throw new Error("Document returned from query is empty"); 95 | } 96 | 97 | expect(doc).toBeDefined(); 98 | 99 | expect(await doc.data()).toEqual({ 100 | userName: "lorem", 101 | isAdmin: true, 102 | sNO: 1, 103 | }); 104 | }); 105 | 106 | it("findById returns a single document if passed a valid id", async () => { 107 | const doc = await user.findById(userId).exec(); 108 | 109 | if (!doc) { 110 | throw new Error("Document returned from query is empty"); 111 | } 112 | 113 | expect(doc).toBeDefined(); 114 | 115 | expect(await doc.data()).toEqual({ 116 | userName: "lorem", 117 | isAdmin: true, 118 | sNO: 1, 119 | }); 120 | 121 | expect(doc.id).toEqual(userId); 122 | }); 123 | 124 | it("findById returns null if passed an invalid id", async () => { 125 | try { 126 | const doc = await user.findById("invalidId").exec(); 127 | 128 | const docData = await doc.data(); 129 | 130 | expect(docData).toBeNull(); 131 | } catch (err) { 132 | const errObj = JSON.parse(err.message); 133 | expect(errObj.type).toBe("Document/Invalid"); 134 | } 135 | }); 136 | 137 | it("findOne returns a single document if passed a valid condition", async () => { 138 | const doc = await user 139 | .findOne({ 140 | userName: "lorem", 141 | }) 142 | .exec(); 143 | 144 | if (!doc) { 145 | throw new Error("Document returned from query is empty"); 146 | } 147 | 148 | expect(doc).toBeDefined(); 149 | 150 | expect(await doc.data()).toEqual({ 151 | userName: "lorem", 152 | isAdmin: true, 153 | sNO: 1, 154 | }); 155 | }); 156 | 157 | it("findOne returns error if passed invalid condtions", async () => { 158 | try { 159 | const doc = await user 160 | .findOne({ 161 | userName: "invalid", 162 | }) 163 | .exec(); 164 | 165 | const docData = await doc.data(); 166 | 167 | expect(docData).toBeNull(); 168 | } catch (err) { 169 | const errObj = JSON.parse(err.message); 170 | expect(errObj.type).toBe("SingleQuery/NotFound"); 171 | } 172 | }); 173 | 174 | it("limit returns only the specific no of documents", async () => { 175 | const query = await user 176 | .find({ 177 | isAdmin: true, 178 | }) 179 | .limit(1) 180 | .exec(); 181 | 182 | if (!query) { 183 | throw new Error("Document returned from query is empty"); 184 | } 185 | 186 | expect(query).toBeDefined(); 187 | expect(query.length).toBe(1); 188 | 189 | const doc = query[0]; 190 | 191 | if (!doc) { 192 | throw new Error("Document returned from query is empty"); 193 | } 194 | 195 | expect(doc).toBeDefined(); 196 | 197 | const docData = await doc.data(); 198 | 199 | expect(docData.isAdmin).toBe(true); 200 | }); 201 | 202 | it("orderBy +string arranges in ascending order", async () => { 203 | const query = await user 204 | .find({ 205 | isAdmin: true, 206 | }) 207 | .orderBy("+sNO") 208 | .exec(); 209 | 210 | if (!query) { 211 | throw new Error("Document returned from query is empty"); 212 | } 213 | 214 | expect(query).toBeDefined(); 215 | 216 | const doc = query[0]; 217 | 218 | if (!doc) { 219 | throw new Error("Document returned from query is empty"); 220 | } 221 | 222 | expect(doc).toBeDefined(); 223 | 224 | const docData = await doc.data(); 225 | 226 | expect(docData.sNO).toBe(1); 227 | 228 | const lastDoc = query[query.length - 1]; 229 | 230 | if (!lastDoc) { 231 | throw new Error("Document returned from query is empty"); 232 | } 233 | 234 | const lastDocData = await lastDoc.data(); 235 | 236 | expect(lastDocData.sNO).toBe(5); 237 | }); 238 | 239 | it("orderBy -string arranges in descending order", async () => { 240 | const query = await user 241 | .find({ 242 | isAdmin: true, 243 | }) 244 | .orderBy("-sNO") 245 | .exec(); 246 | 247 | if (!query) { 248 | throw new Error("Document returned from query is empty"); 249 | } 250 | 251 | expect(query).toBeDefined(); 252 | 253 | const doc = query[0]; 254 | 255 | if (!doc) { 256 | throw new Error("Document returned from query is empty"); 257 | } 258 | 259 | expect(doc).toBeDefined(); 260 | 261 | const docData = await doc.data(); 262 | 263 | expect(docData.sNO).toBe(5); 264 | 265 | const lastDoc = query[query.length - 1]; 266 | 267 | if (!lastDoc) { 268 | throw new Error("Document returned from query is empty"); 269 | } 270 | 271 | const lastDocData = await lastDoc.data(); 272 | 273 | expect(lastDocData.sNO).toBe(1); 274 | }); 275 | 276 | // ! Needs to be fixed 277 | it("select returns only the selected fields", async () => { 278 | const query = await user 279 | .find({ 280 | userName: "lorem", 281 | }) 282 | .select("userName") 283 | .exec(); 284 | 285 | if (!query) { 286 | throw new Error("Document returned from query is empty"); 287 | } 288 | 289 | expect(query).toBeDefined(); 290 | 291 | const doc = query[0]; 292 | 293 | if (!doc) { 294 | throw new Error("Document returned from query is empty"); 295 | } 296 | 297 | expect(doc).toBeDefined(); 298 | 299 | const docData = await doc.data(); 300 | 301 | expect(docData).toEqual({ 302 | userName: "lorem", 303 | sNO: 1, 304 | isAdmin: true, 305 | }); 306 | }); 307 | 308 | it("findByIdAndUpdate updates the data", async () => { 309 | const doc = await user 310 | .findByIdAndUpdate( 311 | userId, 312 | { 313 | userName: "updated lorem", 314 | }, 315 | { 316 | merge: true, 317 | } 318 | ) 319 | .exec(); 320 | 321 | if (!doc) { 322 | throw new Error("Document returned from query is empty"); 323 | } 324 | 325 | expect(doc).toBeDefined(); 326 | 327 | const docData = await doc.data(); 328 | 329 | expect(docData).toEqual({ 330 | userName: "updated lorem", 331 | sNO: 1, 332 | isAdmin: true, 333 | }); 334 | }); 335 | 336 | it("findByIdAndDelete removes the document", async () => { 337 | try { 338 | await user.findByIdAndDelete(userId).exec(); 339 | 340 | const doc = await user.findById(userId).exec(); 341 | 342 | if (!doc) { 343 | throw new Error("Document returned from query is empty"); 344 | } 345 | 346 | expect(doc).toBeDefined(); 347 | 348 | const docData = await doc.data(); 349 | 350 | expect(docData).toBeUndefined(); 351 | } catch (err) { 352 | const errObj = JSON.parse(err.message); 353 | expect(errObj.type).toBe("Document/Invalid"); 354 | } 355 | }); 356 | 357 | it("findOneAndDelete removes the document", async () => { 358 | try { 359 | await user 360 | .findOneAndDelete({ 361 | userName: "ipsum", 362 | }) 363 | .exec(); 364 | 365 | const doc = await user 366 | .findOne({ 367 | userName: "ipsum", 368 | }) 369 | .exec(); 370 | 371 | if (!doc) { 372 | throw new Error("Document returned from query is empty"); 373 | } 374 | 375 | expect(doc).toBeDefined(); 376 | 377 | const docData = await doc.data(); 378 | 379 | expect(docData).toBeUndefined(); 380 | } catch (err) { 381 | const errObj = JSON.parse(err.message); 382 | expect(errObj.type).toBe("SingleQuery/NotFound"); 383 | } 384 | }); 385 | }); 386 | -------------------------------------------------------------------------------- /src/__tests__/Query/operators.test.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | import Firefly from "../../Firefly/class"; 3 | import Model from "../../Model/class"; 4 | import * as SchemaTypes from "../../SchemaTypes"; 5 | 6 | type User = { 7 | index: number; 8 | name: string; 9 | address: { 10 | houseNo: number; 11 | city: string; 12 | }; 13 | }; 14 | 15 | let User: Model; 16 | 17 | beforeAll(() => { 18 | process.env["FIRESTORE_EMULATOR_HOST"] = "localhost:8080"; 19 | 20 | admin.initializeApp({ 21 | projectId: "demo-firefly", 22 | }); 23 | 24 | const firestore = admin.firestore(); 25 | 26 | const connection = new Firefly().createConnection(firestore); 27 | 28 | const userSchema = SchemaTypes.object().keys({ 29 | index: SchemaTypes.number().required(), 30 | name: SchemaTypes.string().required(), 31 | address: SchemaTypes.object() 32 | .keys({ 33 | houseNo: SchemaTypes.number().required(), 34 | city: SchemaTypes.string().required(), 35 | }) 36 | .required(), 37 | }); 38 | 39 | User = connection.model("users", userSchema); 40 | }); 41 | 42 | describe("Tests for operators in Query", () => { 43 | it("$gt operator", async () => { 44 | expect.assertions(2); 45 | 46 | const documents = await User.find({ 47 | index: { $gt: 40 }, 48 | }).exec(); 49 | 50 | expect(documents.length).toBe(9); 51 | 52 | const docData = await documents[0]?.data(); 53 | 54 | expect(docData!.index).toBe(41); 55 | }); 56 | 57 | it("$gt + $lt operator", async () => { 58 | expect.assertions(2); 59 | 60 | const documents = await User.find({ 61 | index: { $gt: 40, $lt: 45 }, 62 | }).exec(); 63 | 64 | expect(documents.length).toBe(4); 65 | 66 | const docData = await documents[0]?.data(); 67 | 68 | expect(docData!.index).toBe(41); 69 | }); 70 | 71 | it("$gte + $lte operator", async () => { 72 | expect.assertions(2); 73 | 74 | const documents = await User.find({ 75 | index: { $gte: 40, $lte: 45 }, 76 | }).exec(); 77 | 78 | expect(documents.length).toBe(6); 79 | 80 | const docData = await documents[0]?.data(); 81 | 82 | expect(docData!.index).toBe(40); 83 | }); 84 | 85 | it("$eq operator", async () => { 86 | expect.assertions(2); 87 | 88 | const documents = await User.find({ 89 | index: { $eq: 1 }, 90 | }).exec(); 91 | 92 | expect(documents.length).toBe(1); 93 | 94 | const docData = await documents[0]?.data(); 95 | 96 | expect(docData!.index).toBe(1); 97 | }); 98 | 99 | it("$neq operator", async () => { 100 | expect.assertions(1); 101 | 102 | const documents = await User.find({ 103 | index: { $neq: 0 }, 104 | }).exec(); 105 | 106 | expect(documents.length).toBe(50); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /src/__tests__/SchemaTypes/Boolean/class.test.ts: -------------------------------------------------------------------------------- 1 | import * as SchemaType from "../../../SchemaTypes/index"; 2 | 3 | describe("test Boolean Schema Parser", () => { 4 | it("returns no error if passed a boolean", () => { 5 | type IObjectSchema = { 6 | isBoolean: boolean; 7 | }; 8 | 9 | const objectSchema = SchemaType.object().keys({ 10 | isBoolean: SchemaType.boolean(), 11 | }); 12 | 13 | const testObj = { 14 | isBoolean: true, 15 | }; 16 | 17 | const { valid, value, errors } = objectSchema.validate(testObj); 18 | 19 | expect(valid).toBe(true); 20 | expect(value).toEqual(testObj); 21 | expect(errors).toEqual([]); 22 | }); 23 | 24 | it("returns an error if passed a non-boolean value and is in strict mode", () => { 25 | type IObjectSchema = { 26 | isBoolean: boolean; 27 | }; 28 | 29 | const objectSchema = SchemaType.object().keys({ 30 | isBoolean: SchemaType.boolean().strict(), 31 | }); 32 | 33 | const testObj = { 34 | isBoolean: "true", 35 | }; 36 | 37 | const { valid, value, errors } = objectSchema.validate(testObj); 38 | 39 | expect(valid).toBe(false); 40 | expect(value).toEqual(testObj); 41 | expect(errors).toEqual([ 42 | { 43 | error: "isBoolean must be a boolean", 44 | errorType: "Boolean/Type", 45 | }, 46 | ]); 47 | }); 48 | 49 | it("converts the value to boolean if passed a non-boolean value and is not in strict mode", () => { 50 | type IObjectSchema = { 51 | isBoolean: boolean; 52 | }; 53 | 54 | const objectSchema = SchemaType.object().keys({ 55 | isBoolean: SchemaType.boolean(), 56 | }); 57 | 58 | const testObj = { 59 | isBoolean: "true", 60 | }; 61 | 62 | const { valid, value, errors } = objectSchema.validate(testObj); 63 | 64 | expect(valid).toBe(true); 65 | expect(value).toEqual({ 66 | isBoolean: true, 67 | }); 68 | expect(errors).toEqual([]); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/__tests__/SchemaTypes/Date/class.test.ts: -------------------------------------------------------------------------------- 1 | import * as SchemaTypes from "../../../SchemaTypes/index"; 2 | 3 | describe("Test DateSchema type", () => { 4 | it("returns an error if passed a value that is not an instance of Date", () => { 5 | type IObjectSchema = { 6 | date: Date; 7 | }; 8 | 9 | const objectSchema = SchemaTypes.object().keys({ 10 | date: SchemaTypes.date(), 11 | }); 12 | 13 | const testObj = { 14 | date: "test", 15 | }; 16 | 17 | const { valid, value, errors } = objectSchema.validate(testObj); 18 | 19 | expect(valid).toBe(false); 20 | expect(value).toEqual(testObj); 21 | expect(errors).toEqual([ 22 | { 23 | error: "date must be a Date object", 24 | errorType: "Date/Type", 25 | }, 26 | ]); 27 | }); 28 | 29 | it("returns no error if value passed is an instance of Date and also formats the given value to firestore timestamp", () => { 30 | type IObjectSchema = { 31 | date: Date; 32 | }; 33 | 34 | const objectSchema = SchemaTypes.object().keys({ 35 | date: SchemaTypes.date(), 36 | }); 37 | 38 | const testObj = { 39 | date: new Date("1995-12-17T03:24:00"), 40 | }; 41 | 42 | const { valid, value, errors } = objectSchema.validate(testObj); 43 | 44 | expect(valid).toBe(true); 45 | expect(value).toEqual({ 46 | date: new Date("1995-12-17T03:24:00"), 47 | }); 48 | expect(errors).toEqual([]); 49 | }); 50 | 51 | it("returns an error if date is less than the value specified in greaterThan threshold", () => { 52 | type IObjectSchema = { 53 | date: Date; 54 | }; 55 | 56 | const objectSchema = SchemaTypes.object().keys({ 57 | date: SchemaTypes.date().greaterThan(new Date("1995-12-17T03:24:00")), 58 | }); 59 | 60 | const testObj = { 61 | date: new Date("1995-12-11T03:24:00"), 62 | }; 63 | 64 | const { valid, value, errors } = objectSchema.validate(testObj); 65 | 66 | expect(valid).toBe(false); 67 | expect(value).toEqual(testObj); 68 | expect(errors).toEqual([ 69 | { 70 | error: 71 | "date must be greater than Sun Dec 17 1995 03:24:00 GMT+0530 (India Standard Time)", 72 | errorType: "Date/InvalidValue", 73 | }, 74 | ]); 75 | }); 76 | 77 | it("returns an error if date is greater than the value specified in lessThan threshold", () => { 78 | type IObjectSchema = { 79 | date: Date; 80 | }; 81 | 82 | const objectSchema = SchemaTypes.object().keys({ 83 | date: SchemaTypes.date().lessThan(new Date("1995-12-17T03:24:00")), 84 | }); 85 | 86 | const testObj = { 87 | date: new Date("1995-12-18T03:24:00"), 88 | }; 89 | 90 | const { valid, value, errors } = objectSchema.validate(testObj); 91 | 92 | expect(valid).toBe(false); 93 | expect(value).toEqual(testObj); 94 | expect(errors).toEqual([ 95 | { 96 | error: 97 | "date must be less than Sun Dec 17 1995 03:24:00 GMT+0530 (India Standard Time)", 98 | errorType: "Date/InvalidValue", 99 | }, 100 | ]); 101 | }); 102 | 103 | it("returns no error if date is equal to the threshold value provided in equalTo method", () => { 104 | type IObjectSchema = { 105 | date: Date; 106 | }; 107 | 108 | const objectSchema = SchemaTypes.object().keys({ 109 | date: SchemaTypes.date().equalTo(new Date("1995-12-17T03:24:00")), 110 | }); 111 | 112 | const testObj = { 113 | date: new Date("1995-12-17T03:24:00"), 114 | }; 115 | 116 | const { valid, value, errors } = objectSchema.validate(testObj); 117 | 118 | expect(valid).toBe(true); 119 | expect(value).toEqual(testObj); 120 | expect(errors).toEqual([]); 121 | }); 122 | 123 | it("returns no error if the date is equal to or less than the threshold provided in lessThanOrEqualTo method", () => { 124 | type IObjectSchema = { 125 | date: Date; 126 | }; 127 | 128 | const objectSchema = SchemaTypes.object().keys({ 129 | date: SchemaTypes.date().lessThanOrEqualTo( 130 | new Date("1995-12-17T03:24:00") 131 | ), 132 | }); 133 | 134 | const testObj = { 135 | date: new Date("1995-12-11T03:24:00"), 136 | }; 137 | 138 | const { valid, value, errors } = objectSchema.validate(testObj); 139 | 140 | expect(valid).toBe(true); 141 | expect(value).toEqual(testObj); 142 | expect(errors).toEqual([]); 143 | }); 144 | 145 | it("returns no error if the date is equal to or greater than the threshold provided in greaterThanOrEqualTo method", () => { 146 | type IObjectSchema = { 147 | date: Date; 148 | }; 149 | 150 | const objectSchema = SchemaTypes.object().keys({ 151 | date: SchemaTypes.date().greaterThanOrEqualTo( 152 | new Date("1995-12-17T03:24:00") 153 | ), 154 | }); 155 | 156 | const testObj = { 157 | date: new Date("1995-12-18T03:24:00"), 158 | }; 159 | 160 | const { valid, value, errors } = objectSchema.validate(testObj); 161 | 162 | expect(valid).toBe(true); 163 | expect(value).toEqual(testObj); 164 | expect(errors).toEqual([]); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /src/__tests__/SchemaTypes/Object/class.test.ts: -------------------------------------------------------------------------------- 1 | import * as SchemaTypes from "../../../SchemaTypes/index"; 2 | 3 | describe("Test ObjectSchemaType", () => { 4 | it("returns no error if testObj has defined keys", () => { 5 | type IObjectSchema = { 6 | name: string; 7 | age: number; 8 | }; 9 | 10 | const objectSchema = SchemaTypes.object().keys({ 11 | name: SchemaTypes.string().required(), 12 | age: SchemaTypes.number().required(), 13 | }); 14 | 15 | const testObj = { 16 | name: "firefly", 17 | age: 5, 18 | }; 19 | 20 | const { valid, value, errors } = objectSchema.validate(testObj); 21 | 22 | expect(valid).toBe(true); 23 | expect(value).toEqual(testObj); 24 | expect(errors).toEqual([]); 25 | }); 26 | 27 | it("returns error if testObj has missing keys", () => { 28 | type IObjectSchema = { 29 | name: string; 30 | age: number; 31 | }; 32 | 33 | const objectSchema = SchemaTypes.object().keys({ 34 | name: SchemaTypes.string().uppercase().required(), 35 | age: SchemaTypes.number().required(), 36 | }); 37 | 38 | const testObj = { 39 | name: "firefly", 40 | }; 41 | 42 | const { valid, value, errors } = objectSchema.validate(testObj); 43 | 44 | expect(valid).toBe(false); 45 | expect(value).toEqual({ 46 | name: "FIREFLY", 47 | }); 48 | expect(errors[0]).toBeDefined(); 49 | expect(errors[0]!.errorType).toBe("Object/InvalidKeys"); 50 | }); 51 | 52 | it("returns no error if testObj has missing keys that match the specified pattern", () => { 53 | type IObjectSchema = { 54 | name: string; 55 | }; 56 | 57 | const objectSchema = SchemaTypes.object() 58 | .keys({ 59 | name: SchemaTypes.string().required(), 60 | }) 61 | .pattern([SchemaTypes.string(), SchemaTypes.number()]); 62 | 63 | const testObj = { 64 | name: "firefly", 65 | age: 10, 66 | }; 67 | 68 | const { valid, value, errors } = objectSchema.validate(testObj); 69 | 70 | expect(valid).toBe(true); 71 | expect(value).toEqual(testObj); 72 | expect(errors).toEqual([]); 73 | }); 74 | 75 | it("returns error if testObj has missing keys that do not match the specified pattern", () => { 76 | type IObjectSchema = { 77 | name: string; 78 | }; 79 | 80 | const objectSchema = SchemaTypes.object() 81 | .keys({ 82 | name: SchemaTypes.string().required(), 83 | }) 84 | .pattern([SchemaTypes.string().regex(/^age$/), SchemaTypes.number()]); 85 | 86 | const testObj = { 87 | name: "firefly", 88 | username: "firefly", 89 | }; 90 | 91 | const { valid, value, errors } = objectSchema.validate(testObj); 92 | 93 | expect(valid).toBe(false); 94 | expect(value).toEqual({ 95 | name: "firefly", 96 | }); 97 | expect(errors[0]).toBeDefined(); 98 | expect(errors[0]!.errorType).toBe("Object/InvalidKeys"); 99 | }); 100 | 101 | it("returns error if testObj has missing keys whose values do not match the specified pattern", () => { 102 | type IObjectSchema = { 103 | name: string; 104 | }; 105 | 106 | const objectSchema = SchemaTypes.object() 107 | .keys({ 108 | name: SchemaTypes.string().required(), 109 | }) 110 | .pattern([SchemaTypes.string().regex(/^age$/), SchemaTypes.number()]); 111 | 112 | const testObj = { 113 | name: "firefly", 114 | age: "firefly", 115 | }; 116 | 117 | const { valid, value, errors } = objectSchema.validate(testObj); 118 | 119 | expect(valid).toBe(false); 120 | expect(value).toEqual(testObj); 121 | expect(errors[0]).toBeDefined(); 122 | expect(errors[0]!.errorType).toBe("Number/Type"); 123 | }); 124 | 125 | it("returns error if testObj is missing required keys", () => { 126 | type IObjectSchema = { 127 | name: string; 128 | age: string; 129 | }; 130 | 131 | const objectSchema = SchemaTypes.object().keys({ 132 | name: SchemaTypes.string().required(), 133 | age: SchemaTypes.number().required(), 134 | }); 135 | 136 | const testObj = { 137 | age: 10, 138 | }; 139 | 140 | const { valid, value, errors } = objectSchema.validate(testObj); 141 | 142 | expect(valid).toBe(false); 143 | expect(value).toEqual(testObj); 144 | expect(errors[0]).toBeDefined(); 145 | expect(errors[0]!.errorType).toBe("Object/InvalidKeys"); 146 | }); 147 | 148 | it("returns no error if testObj has missing default keys", () => { 149 | type IObjectSchema = { 150 | name: string; 151 | age: string; 152 | }; 153 | 154 | const objectSchema = SchemaTypes.object().keys({ 155 | name: SchemaTypes.string().default("username"), 156 | age: SchemaTypes.number().required(), 157 | }); 158 | 159 | const testObj = { 160 | age: 10, 161 | }; 162 | 163 | const { valid, value, errors } = objectSchema.validate(testObj); 164 | 165 | expect(valid).toBe(true); 166 | expect(value).toEqual({ ...testObj, name: "username" }); 167 | expect(errors).toEqual([]); 168 | }); 169 | 170 | it("returns no error if testObj is a nested object and default value is provided", () => { 171 | type IObjectSchema = { 172 | name: { 173 | firstName: string; 174 | lastName: string; 175 | }; 176 | age: number; 177 | }; 178 | 179 | const objectSchema = SchemaTypes.object().keys({ 180 | name: SchemaTypes.object() 181 | .keys({ 182 | firstName: SchemaTypes.string().required(), 183 | lastName: SchemaTypes.string().default("."), 184 | }) 185 | .default("username"), 186 | age: SchemaTypes.number().required(), 187 | }); 188 | 189 | const testObj = { 190 | age: 10, 191 | }; 192 | 193 | const { valid, value, errors } = objectSchema.validate(testObj); 194 | 195 | expect(valid).toBe(true); 196 | expect(value).toEqual({ ...testObj, name: "username" }); 197 | expect(errors).toEqual([]); 198 | }); 199 | 200 | it("returns error if testObj is a nested object and keys are missing in the nested object", () => { 201 | type IObjectSchema = { 202 | name: { 203 | firstName: string; 204 | lastName: string; 205 | }; 206 | age: number; 207 | }; 208 | 209 | const objectSchema = SchemaTypes.object().keys({ 210 | name: SchemaTypes.object() 211 | .keys({ 212 | firstName: SchemaTypes.string().required(), 213 | lastName: SchemaTypes.string().default("."), 214 | }) 215 | .required(), 216 | age: SchemaTypes.number().required(), 217 | }); 218 | 219 | const testObj = { 220 | name: {}, 221 | age: 10, 222 | }; 223 | 224 | const { valid, value, errors } = objectSchema.validate(testObj); 225 | 226 | expect(valid).toBe(false); 227 | expect(value).toEqual({ 228 | age: 10, 229 | name: { 230 | lastName: ".", 231 | }, 232 | }); 233 | expect(errors[0]).toBeDefined(); 234 | expect(errors[0]!.error).toEqual("firstName is required"); 235 | }); 236 | 237 | it("adds default value for default keys", () => { 238 | type Address = { 239 | city: string; 240 | state: string; 241 | }; 242 | 243 | type User = { 244 | name: string; 245 | address: Address; 246 | }; 247 | 248 | const schema = SchemaTypes.object().keys({ 249 | name: SchemaTypes.string().default("Username"), 250 | address: SchemaTypes.object
().keys({ 251 | city: SchemaTypes.string().required(), 252 | state: SchemaTypes.string().default("Not Mentioned"), 253 | }), 254 | }); 255 | 256 | const testObj = { 257 | address: { 258 | city: "Delhi", 259 | }, 260 | }; 261 | 262 | const { valid, value, errors } = schema.validate(testObj); 263 | 264 | expect(valid).toBe(true); 265 | expect(value).toEqual({ 266 | name: "Username", 267 | address: { 268 | city: "Delhi", 269 | state: "Not Mentioned", 270 | }, 271 | }); 272 | expect(errors).toEqual([]); 273 | }); 274 | 275 | it("checks only the supplied keys", () => { 276 | type Address = { 277 | city: string; 278 | state: string; 279 | }; 280 | 281 | type User = { 282 | name: string; 283 | address: Address; 284 | }; 285 | 286 | const schema = SchemaTypes.object().keys({ 287 | name: SchemaTypes.string().default("Username"), 288 | address: SchemaTypes.object
().keys({ 289 | city: SchemaTypes.string().minLength(10).required(), 290 | state: SchemaTypes.string().required(), 291 | }), 292 | }); 293 | 294 | const testObj = { 295 | address: { 296 | city: "Delhi", 297 | }, 298 | }; 299 | 300 | const { valid, value, errors } = schema.validate(testObj, undefined, { 301 | onlySupplied: true, 302 | }); 303 | 304 | expect(valid).toBe(false); 305 | expect(value).toEqual({ 306 | address: { 307 | city: "Delhi", 308 | }, 309 | }); 310 | expect(errors.length).toBe(1); 311 | expect(errors[0]).toEqual({ 312 | error: "minimum allowed length for city is 10", 313 | errorType: "String/Length", 314 | }); 315 | }); 316 | 317 | it("only checks keys and ignores the pattern check", () => { 318 | type IObjectSchema = { 319 | name: string; 320 | }; 321 | 322 | const objectSchema = SchemaTypes.object() 323 | .keys({ 324 | name: SchemaTypes.string().required(), 325 | }) 326 | .pattern([SchemaTypes.string(), SchemaTypes.number()]); 327 | 328 | const testObj = { 329 | name: "firefly", 330 | age: 10, 331 | }; 332 | 333 | const { valid, value, errors } = objectSchema.validate(testObj, undefined, { 334 | onlyKeys: true, 335 | }); 336 | 337 | expect(valid).toBe(false); 338 | expect(value).toEqual({ 339 | name: "firefly", 340 | }); 341 | expect(errors.length).toBe(1); 342 | expect(errors[0]).toEqual({ 343 | error: "age is not allowed", 344 | errorType: "Object/InvalidKeys", 345 | }); 346 | }); 347 | }); 348 | -------------------------------------------------------------------------------- /src/__tests__/SchemaTypes/String/class.test.ts: -------------------------------------------------------------------------------- 1 | import * as SchemaType from "../../../SchemaTypes/index"; 2 | 3 | describe("test String Schema Parser", () => { 4 | it("returns no errors if passed a string", () => { 5 | type IObjectSchema = { 6 | name: string; 7 | }; 8 | 9 | const objectSchema = SchemaType.object().keys({ 10 | name: SchemaType.string(), 11 | }); 12 | 13 | const testObj = { 14 | name: "Lorem", 15 | }; 16 | 17 | const { valid, value, errors } = objectSchema.validate(testObj); 18 | 19 | expect(valid).toBe(true); 20 | expect(value).toEqual(testObj); 21 | expect(errors).toEqual([]); 22 | }); 23 | 24 | it("returns error if passed a value other than string", () => { 25 | type IObjectSchema = { 26 | name: string; 27 | }; 28 | 29 | const objectSchema = SchemaType.object().keys({ 30 | name: SchemaType.string(), 31 | }); 32 | 33 | const testObj = { 34 | name: 1, 35 | }; 36 | 37 | const { valid, value, errors } = objectSchema.validate(testObj); 38 | 39 | expect(valid).toBe(false); 40 | expect(value).toEqual(testObj); 41 | expect(errors).toEqual([ 42 | { 43 | error: "name must be a string", 44 | errorType: "String/Type", 45 | }, 46 | ]); 47 | }); 48 | 49 | it("returns error if passed required option and no value is passed", () => { 50 | type IObjectSchema = { 51 | name: string; 52 | }; 53 | 54 | const objectSchema = SchemaType.object().keys({ 55 | name: SchemaType.string().required(), 56 | }); 57 | 58 | const { valid, value, errors } = objectSchema.validate({}); 59 | 60 | expect(valid).toBe(false); 61 | expect(value).toEqual({}); 62 | expect(errors).toEqual([ 63 | { 64 | error: "name is required", 65 | errorType: "Object/InvalidKeys", 66 | }, 67 | ]); 68 | }); 69 | 70 | it("trim removes whitespaces", () => { 71 | type IObjectSchema = { 72 | name: string; 73 | }; 74 | 75 | const objectSchema = SchemaType.object().keys({ 76 | name: SchemaType.string().trim(), 77 | }); 78 | 79 | const testObj = { 80 | name: " Lorem ", 81 | }; 82 | 83 | const { valid, value, errors } = objectSchema.validate(testObj); 84 | 85 | expect(valid).toBe(true); 86 | expect(value).toEqual({ 87 | name: "Lorem", 88 | }); 89 | expect(errors).toEqual([]); 90 | }); 91 | 92 | it("minLength throws an error if string is lesser than specified value", () => { 93 | type IObjectSchema = { 94 | name: string; 95 | }; 96 | 97 | const objectSchema = SchemaType.object().keys({ 98 | name: SchemaType.string().minLength(5), 99 | }); 100 | 101 | const testObj = { 102 | name: "test", 103 | }; 104 | 105 | const { valid, value, errors } = objectSchema.validate(testObj); 106 | 107 | expect(valid).toBe(false); 108 | expect(value).toEqual(testObj); 109 | expect(errors).toEqual([ 110 | { 111 | error: "minimum allowed length for name is 5", 112 | errorType: "String/Length", 113 | }, 114 | ]); 115 | }); 116 | 117 | it("maxLength throws an error if string is greater than specified value", () => { 118 | type IObjectSchema = { 119 | name: string; 120 | }; 121 | 122 | const objectSchema = SchemaType.object().keys({ 123 | name: SchemaType.string().maxLength(5), 124 | }); 125 | 126 | const testObj = { 127 | name: "123456", 128 | }; 129 | 130 | const { valid, value, errors } = objectSchema.validate(testObj); 131 | 132 | expect(valid).toBe(false); 133 | expect(value).toEqual(testObj); 134 | expect(errors).toEqual([ 135 | { 136 | error: "maximum allowed length for name is 5", 137 | errorType: "String/Length", 138 | }, 139 | ]); 140 | }); 141 | 142 | it("lowercase formats the string to lowercase value", () => { 143 | type IObjectSchema = { 144 | name: string; 145 | }; 146 | 147 | const objectSchema = SchemaType.object().keys({ 148 | name: SchemaType.string().lowercase(), 149 | }); 150 | 151 | const testObj = { 152 | name: "Lorem", 153 | }; 154 | 155 | const { valid, value, errors } = objectSchema.validate(testObj); 156 | 157 | expect(valid).toBe(true); 158 | expect(value).toEqual({ 159 | name: "lorem", 160 | }); 161 | expect(errors).toEqual([]); 162 | }); 163 | 164 | it("uppercase formats the string to uppercase value", () => { 165 | type IObjectSchema = { 166 | name: string; 167 | }; 168 | 169 | const objectSchema = SchemaType.object().keys({ 170 | name: SchemaType.string().uppercase(), 171 | }); 172 | 173 | const testObj = { 174 | name: "lorem", 175 | }; 176 | 177 | const { valid, value, errors } = objectSchema.validate(testObj); 178 | 179 | expect(valid).toBe(true); 180 | expect(value).toEqual({ 181 | name: "LOREM", 182 | }); 183 | expect(errors).toEqual([]); 184 | }); 185 | 186 | it("email validates successfully if passed a proper email", () => { 187 | type IObjectSchema = { 188 | name: string; 189 | }; 190 | 191 | const objectSchema = SchemaType.object().keys({ 192 | name: SchemaType.string().email(), 193 | }); 194 | 195 | const testObj = { 196 | name: "lorem@gmail.com", 197 | }; 198 | 199 | const { valid, value, errors } = objectSchema.validate(testObj); 200 | 201 | expect(valid).toBe(true); 202 | expect(value).toEqual({ 203 | name: "lorem@gmail.com", 204 | }); 205 | expect(errors).toEqual([]); 206 | }); 207 | 208 | it("email throws an error if passed an invalid email", () => { 209 | type IObjectSchema = { 210 | name: string; 211 | }; 212 | 213 | const objectSchema = SchemaType.object().keys({ 214 | name: SchemaType.string().email(), 215 | }); 216 | 217 | const testObj = { 218 | name: "lorem.com", 219 | }; 220 | 221 | const { valid, value, errors } = objectSchema.validate(testObj); 222 | 223 | expect(valid).toBe(false); 224 | expect(value).toEqual(testObj); 225 | expect(errors).toEqual([ 226 | { 227 | error: "name must be a valid email", 228 | errorType: "String/Format", 229 | }, 230 | ]); 231 | }); 232 | 233 | it("regex validates incoming string", () => { 234 | type IObjectSchema = { 235 | name: string; 236 | }; 237 | 238 | const objectSchema = SchemaType.object().keys({ 239 | name: SchemaType.string().regex(/^[a-zA-Z]+$/), 240 | }); 241 | 242 | const testObj = { 243 | name: "Lorem", 244 | }; 245 | 246 | const { valid, value, errors } = objectSchema.validate(testObj); 247 | 248 | expect(valid).toBe(true); 249 | expect(value).toEqual({ 250 | name: "Lorem", 251 | }); 252 | expect(errors).toEqual([]); 253 | }); 254 | 255 | it("regex throws an error if regex does not match", () => { 256 | type IObjectSchema = { 257 | name: string; 258 | }; 259 | 260 | const objectSchema = SchemaType.object().keys({ 261 | name: SchemaType.string().regex(/^[a-zA-Z]+$/), 262 | }); 263 | 264 | const testObj = { 265 | name: "123", 266 | }; 267 | 268 | const { valid, value, errors } = objectSchema.validate(testObj); 269 | 270 | expect(valid).toBe(false); 271 | expect(value).toEqual(testObj); 272 | expect(errors).toEqual([ 273 | { 274 | error: "name must be of valid format", 275 | errorType: "String/Format", 276 | }, 277 | ]); 278 | }); 279 | 280 | it("enum only allows the specified values", () => { 281 | type IObjectSchema = { 282 | name: string; 283 | }; 284 | 285 | const objectSchema = SchemaType.object().keys({ 286 | name: SchemaType.string().enum(["Lorem", "Ipsum"]), 287 | }); 288 | 289 | const testObj = { 290 | name: "Lorem", 291 | }; 292 | 293 | const { valid, value, errors } = objectSchema.validate(testObj); 294 | 295 | expect(valid).toBe(true); 296 | expect(value).toEqual({ 297 | name: "Lorem", 298 | }); 299 | expect(errors).toEqual([]); 300 | }); 301 | 302 | it("enum throws an error if value is not in the enum", () => { 303 | type IObjectSchema = { 304 | name: string; 305 | }; 306 | 307 | const objectSchema = SchemaType.object().keys({ 308 | name: SchemaType.string().enum(["Lorem", "Ipsum"]), 309 | }); 310 | 311 | const testObj = { 312 | name: "123", 313 | }; 314 | 315 | const { valid, value, errors } = objectSchema.validate(testObj); 316 | 317 | expect(valid).toBe(false); 318 | expect(value).toEqual(testObj); 319 | expect(errors).toEqual([ 320 | { 321 | error: "name is not assignable to specified enum", 322 | errorType: "String/InvalidValue", 323 | }, 324 | ]); 325 | }); 326 | }); 327 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as FireflyNs from "./Firefly"; 2 | import * as ConnectionNs from "./Connection"; 3 | import * as SchemaTypes from "./SchemaTypes"; 4 | import * as ModelNs from "./Model"; 5 | import * as QueryNs from "./Query"; 6 | 7 | class Firefly extends FireflyNs.default {} 8 | const firefly = new Firefly(); 9 | 10 | class Connection extends ConnectionNs.default {} 11 | 12 | class Model extends ModelNs.default {} 13 | 14 | export default firefly; 15 | export { 16 | Firefly, 17 | FireflyNs, 18 | Connection, 19 | ConnectionNs, 20 | SchemaTypes, 21 | Model, 22 | ModelNs, 23 | QueryNs, 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/makeError.ts: -------------------------------------------------------------------------------- 1 | const makeError = (type: T, data: string | object) => { 2 | const error = { 3 | type, 4 | data, 5 | }; 6 | 7 | return new Error(JSON.stringify(error)); 8 | }; 9 | 10 | export default makeError; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | "declaration": true /* Generates corresponding '.d.ts' file. */, 14 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, 15 | "sourceMap": true /* Generates corresponding '.map' file. */, 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./lib" /* Redirect output structure to the directory. */, 18 | "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | "downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */, 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 30 | "strictNullChecks": true /* Enable strict null checks. */, 31 | "strictFunctionTypes": true /* Enable strict checking of function types. */, 32 | "strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */, 33 | "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 34 | "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 35 | "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 36 | 37 | /* Additional Checks */ 38 | "noUnusedLocals": true /* Report errors on unused locals. */, 39 | "noUnusedParameters": true /* Report errors on unused parameters. */, 40 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 41 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 42 | "noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */, 43 | "noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an 'override' modifier. */, 44 | "noPropertyAccessFromIndexSignature": true /* Require undeclared properties from index signatures to use element accesses. */, 45 | 46 | /* Module Resolution Options */ 47 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 48 | "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 66 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true /* Skip type checking of declaration files. */, 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | }, 72 | "include": ["./src/**/*.ts", "./.eslintrc.js"], 73 | "exclude": ["node_modules/", "lib/**/*", "./src/__tests__"], 74 | "compileOnSave": true 75 | } 76 | --------------------------------------------------------------------------------