diff --git a/.github/workflows/coverage-check.yml b/.github/workflows/coverage-check.yml new file mode 100644 index 0000000..6cf518b --- /dev/null +++ b/.github/workflows/coverage-check.yml @@ -0,0 +1,26 @@ +name: 'TS SDK - Unit Testing' + +on: + pull_request: + branches: + - development + - staging + - main + +jobs: + coverage: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + checks: write + + steps: + - uses: actions/checkout@v4 + + - name: Run unit tests with coverage + uses: ArtiomTr/jest-coverage-report-action@v2 + id: coverage + with: + test-script: npm run test:unit + threshold: 95 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0041e52..ba8db37 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ coverage .dccache dist/* *.log -.nx/ +.nx/ \ No newline at end of file diff --git a/.talismanrc b/.talismanrc index 2aa5661..cf4e646 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,6 +1,10 @@ fileignoreconfig: - filename: package-lock.json - checksum: 32308dbe614c142c4804ff7c81baedddba058c5458e1d233fefb1d8070bf1905 + checksum: 46c0d87a82455d4b2bae3347f7361dda71d2b979426b1c95ef707a9166c17778 +- filename: test/unit/contentstack.spec.ts + checksum: d5b99c01459ab8bc597baaa9e6cc4aa91ac6d9bf78af08e1d0220d0c5db3d0b3 +- filename: test/unit/utils.spec.ts + checksum: 79ce5bd78376db37a34df82c0fea19031e995b66a5a246e73f8262fa05d65a9c - filename: test/unit/query-optimization-comprehensive.spec.ts checksum: f5aaf6c784d7c101a05ca513c584bbd6e95f963d1e42779f2596050d9bcbac96 - filename: src/lib/entries.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c38140..6aa0f90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### Version: 4.10.2 +#### Date: Nov-12-2025 +Enhancement: Added logHandler interceptors for request and response logging +Enhancement: Upgraded @contentstack/utils dependency to version 1.6.2 +Refactor: Replaced region handling logic to use getContentstackEndpoint from @contentstack/utils + ### Version: 4.10.1 #### Date: Oct-27-2025 Fix: Upgrade dependecies diff --git a/package-lock.json b/package-lock.json index ed6a740..f4c9b83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "@contentstack/delivery-sdk", - "version": "4.10.1", + "version": "4.10.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/delivery-sdk", - "version": "4.10.1", + "version": "4.10.2", "license": "MIT", "dependencies": { - "@contentstack/core": "^1.3.1", - "@contentstack/utils": "^1.5.0", - "axios": "^1.12.2", + "@contentstack/core": "^1.3.3", + "@contentstack/utils": "^1.6.2", + "axios": "^1.13.1", "humps": "^2.0.1" }, "devDependencies": { @@ -1889,12 +1889,12 @@ "license": "MIT" }, "node_modules/@contentstack/core": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.3.1.tgz", - "integrity": "sha512-RuaqNMZreN/ihnFJtGvtxK5NYuQuar1qBwWf0wqMsESHZCp+7Ohk1iSwq5E+7JN8Rzz40eiBiXklllzhoC0+5g==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.3.3.tgz", + "integrity": "sha512-GHT93tgjbcItxkKta9MDObDiw/ICMJItCzaLuGQ+NZvrp2JgZdYsyXEWGEPgRvIcqHFiJlU5huwkwDX89gYxvA==", "license": "MIT", "dependencies": { - "axios": "^1.11.0", + "axios": "^1.12.2", "axios-mock-adapter": "^2.1.0", "husky": "^9.1.7", "lodash": "^4.17.21", @@ -1916,9 +1916,10 @@ } }, "node_modules/@contentstack/utils": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@contentstack/utils/-/utils-1.5.0.tgz", - "integrity": "sha512-tL1pcC4hJ+zcrvHq9c/ShTLjCVg8ACWahLDZvqT5VAalTsnR5Ik7QltjEcRsfpz/ucLQ1GVyRQRpezELCIon4A==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@contentstack/utils/-/utils-1.6.2.tgz", + "integrity": "sha512-HWyCXchCIUUwhcaqEwMEQNSmbVih8x4QKo4UxbFSj5RmIfFDPY/szAl5hQT0Xvnhh6C3uZu2gDI/HmUcDzJQkQ==", + "hasInstallScript": true, "license": "MIT" }, "node_modules/@cspotcode/source-map-support": { @@ -1956,9 +1957,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", - "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -1973,9 +1974,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", - "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -1990,9 +1991,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", - "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -2007,9 +2008,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", - "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -2024,9 +2025,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", - "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -2041,9 +2042,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", - "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -2058,9 +2059,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", - "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -2075,9 +2076,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", - "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -2092,9 +2093,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", - "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -2109,9 +2110,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", - "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -2126,9 +2127,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", - "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -2143,9 +2144,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", - "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -2160,9 +2161,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", - "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -2177,9 +2178,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", - "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -2194,9 +2195,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", - "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -2211,9 +2212,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", - "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -2228,9 +2229,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", - "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -2245,9 +2246,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", - "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ "arm64" ], @@ -2262,9 +2263,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", - "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -2279,9 +2280,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", - "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "cpu": [ "arm64" ], @@ -2296,9 +2297,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", - "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -2313,9 +2314,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", - "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", "cpu": [ "arm64" ], @@ -2330,9 +2331,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", - "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -2347,9 +2348,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", - "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -2364,9 +2365,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", - "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -2381,9 +2382,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", - "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -3657,9 +3658,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", - "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz", + "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==", "cpu": [ "arm" ], @@ -3671,9 +3672,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", - "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz", + "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==", "cpu": [ "arm64" ], @@ -3685,9 +3686,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", - "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz", + "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==", "cpu": [ "arm64" ], @@ -3699,9 +3700,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", - "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz", + "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==", "cpu": [ "x64" ], @@ -3713,9 +3714,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", - "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz", + "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==", "cpu": [ "arm64" ], @@ -3727,9 +3728,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", - "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz", + "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==", "cpu": [ "x64" ], @@ -3741,9 +3742,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", - "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz", + "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==", "cpu": [ "arm" ], @@ -3755,9 +3756,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", - "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz", + "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==", "cpu": [ "arm" ], @@ -3769,9 +3770,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", - "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz", + "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==", "cpu": [ "arm64" ], @@ -3783,9 +3784,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", - "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz", + "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==", "cpu": [ "arm64" ], @@ -3797,9 +3798,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", - "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz", + "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==", "cpu": [ "loong64" ], @@ -3811,9 +3812,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", - "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz", + "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==", "cpu": [ "ppc64" ], @@ -3825,9 +3826,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", - "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz", + "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==", "cpu": [ "riscv64" ], @@ -3839,9 +3840,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", - "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz", + "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==", "cpu": [ "riscv64" ], @@ -3853,9 +3854,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", - "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz", + "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==", "cpu": [ "s390x" ], @@ -3867,9 +3868,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", - "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz", + "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==", "cpu": [ "x64" ], @@ -3881,9 +3882,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", - "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz", + "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==", "cpu": [ "x64" ], @@ -3895,9 +3896,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", - "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz", + "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==", "cpu": [ "arm64" ], @@ -3909,9 +3910,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", - "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz", + "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==", "cpu": [ "arm64" ], @@ -3923,9 +3924,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", - "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz", + "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==", "cpu": [ "ia32" ], @@ -3937,9 +3938,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", - "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz", + "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==", "cpu": [ "x64" ], @@ -3951,9 +3952,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", - "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz", + "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==", "cpu": [ "x64" ], @@ -3992,17 +3993,17 @@ } }, "node_modules/@slack/bolt": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-4.5.0.tgz", - "integrity": "sha512-1YbgO/UDLYa0vOtGsTohpnl/dSKwo7RbUd29IJMfqNDLn+t81MmIL0w2KPNjZJQLsoevTRNCdHDeh4PJyY8DIA==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-4.6.0.tgz", + "integrity": "sha512-xPgfUs2+OXSugz54Ky07pA890+Qydk22SYToi8uGpXeHSt1JWwFJkRyd/9Vlg5I1AdfdpGXExDpwnbuN9Q/2dQ==", "dev": true, "license": "MIT", "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^3.0.4", "@slack/socket-mode": "^2.0.5", - "@slack/types": "^2.17.0", - "@slack/web-api": "^7.11.0", + "@slack/types": "^2.18.0", + "@slack/web-api": "^7.12.0", "axios": "^1.12.0", "express": "^5.0.0", "path-to-regexp": "^8.1.0", @@ -4242,16 +4243,16 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.4.tgz", - "integrity": "sha512-g64dbryHk7loCIrsa0R3shBnEu5p6LPJ09bu9NG58+jz+cRUjFrc3Bz0kNQ7j9bXeCsrRDvNET1G54P/GJkAyA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { @@ -4378,9 +4379,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", - "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5035,9 +5036,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -5283,9 +5284,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", - "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", + "version": "2.8.26", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.26.tgz", + "integrity": "sha512-73lC1ugzwoaWCLJ1LvOgrR5xsMLTqSKIEoMHVtL9E/HNk0PXtTM76ZIm84856/SF7Nv8mPZxKoBsgpm0tR1u1Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5349,9 +5350,9 @@ } }, "node_modules/browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", "dev": true, "funding": [ { @@ -5369,10 +5370,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { @@ -5530,9 +5531,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001751", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", - "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", "dev": true, "funding": [ { @@ -6199,9 +6200,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.240", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", - "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==", + "version": "1.5.250", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz", + "integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==", "dev": true, "license": "ISC" }, @@ -6286,9 +6287,9 @@ } }, "node_modules/envinfo": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.19.0.tgz", - "integrity": "sha512-DoSM9VyG6O3vqBf+p3Gjgr/Q52HYBBtO3v+4koAxt1MnWr+zEnxE+nke/yXS4lt2P4SYCHQ4V3f1i88LQVOpAw==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.20.0.tgz", + "integrity": "sha512-+zUomDcLXsVkQ37vUqWBvQwLaLlj8eZPSi61llaEFAVBY5mhcXdaSw1pSJVl4yTYD5g/gEfpNl28YYk4IPvrrg==", "dev": true, "license": "MIT", "bin": { @@ -6362,9 +6363,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", - "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6375,32 +6376,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.11", - "@esbuild/android-arm": "0.25.11", - "@esbuild/android-arm64": "0.25.11", - "@esbuild/android-x64": "0.25.11", - "@esbuild/darwin-arm64": "0.25.11", - "@esbuild/darwin-x64": "0.25.11", - "@esbuild/freebsd-arm64": "0.25.11", - "@esbuild/freebsd-x64": "0.25.11", - "@esbuild/linux-arm": "0.25.11", - "@esbuild/linux-arm64": "0.25.11", - "@esbuild/linux-ia32": "0.25.11", - "@esbuild/linux-loong64": "0.25.11", - "@esbuild/linux-mips64el": "0.25.11", - "@esbuild/linux-ppc64": "0.25.11", - "@esbuild/linux-riscv64": "0.25.11", - "@esbuild/linux-s390x": "0.25.11", - "@esbuild/linux-x64": "0.25.11", - "@esbuild/netbsd-arm64": "0.25.11", - "@esbuild/netbsd-x64": "0.25.11", - "@esbuild/openbsd-arm64": "0.25.11", - "@esbuild/openbsd-x64": "0.25.11", - "@esbuild/openharmony-arm64": "0.25.11", - "@esbuild/sunos-x64": "0.25.11", - "@esbuild/win32-arm64": "0.25.11", - "@esbuild/win32-ia32": "0.25.11", - "@esbuild/win32-x64": "0.25.11" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/esbuild-plugin-file-path-extensions": { @@ -9302,9 +9303,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", - "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -10362,9 +10363,9 @@ } }, "node_modules/rollup": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", + "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", "dev": true, "license": "MIT", "dependencies": { @@ -10378,28 +10379,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", + "@rollup/rollup-android-arm-eabi": "4.53.2", + "@rollup/rollup-android-arm64": "4.53.2", + "@rollup/rollup-darwin-arm64": "4.53.2", + "@rollup/rollup-darwin-x64": "4.53.2", + "@rollup/rollup-freebsd-arm64": "4.53.2", + "@rollup/rollup-freebsd-x64": "4.53.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", + "@rollup/rollup-linux-arm-musleabihf": "4.53.2", + "@rollup/rollup-linux-arm64-gnu": "4.53.2", + "@rollup/rollup-linux-arm64-musl": "4.53.2", + "@rollup/rollup-linux-loong64-gnu": "4.53.2", + "@rollup/rollup-linux-ppc64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-musl": "4.53.2", + "@rollup/rollup-linux-s390x-gnu": "4.53.2", + "@rollup/rollup-linux-x64-gnu": "4.53.2", + "@rollup/rollup-linux-x64-musl": "4.53.2", + "@rollup/rollup-openharmony-arm64": "4.53.2", + "@rollup/rollup-win32-arm64-msvc": "4.53.2", + "@rollup/rollup-win32-ia32-msvc": "4.53.2", + "@rollup/rollup-win32-x64-gnu": "4.53.2", + "@rollup/rollup-win32-x64-msvc": "4.53.2", "fsevents": "~2.3.2" } }, @@ -11031,9 +11032,9 @@ } }, "node_modules/terser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", - "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, "license": "BSD-2-Clause", "peer": true, diff --git a/package.json b/package.json index f7202f7..5c8cf1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/delivery-sdk", - "version": "4.10.1", + "version": "4.10.2", "type": "module", "license": "MIT", "main": "./dist/legacy/index.cjs", @@ -35,9 +35,9 @@ "husky-check": "npm run build && husky && chmod +x .husky/pre-commit" }, "dependencies": { - "@contentstack/core": "^1.3.1", - "@contentstack/utils": "^1.5.0", - "axios": "^1.12.2", + "@contentstack/core": "^1.3.3", + "@contentstack/utils": "^1.6.2", + "axios": "^1.13.1", "humps": "^2.0.1" }, "files": [ diff --git a/src/lib/base-query.ts b/src/lib/base-query.ts index ed75ce6..a1347ed 100644 --- a/src/lib/base-query.ts +++ b/src/lib/base-query.ts @@ -2,6 +2,7 @@ import { AxiosInstance, getData } from '@contentstack/core'; import { Pagination } from './pagination'; import { FindResponse, params } from './types'; import { encodeQueryParams } from './utils'; +import type { Query } from './query'; export class BaseQuery extends Pagination { _parameters: params = {}; // Params of query class ?query={} @@ -10,6 +11,14 @@ export class BaseQuery extends Pagination { protected _urlPath!: string; protected _variants!: string; + /** + * Helper method to cast this instance to Query type + * @private + */ + protected asQuery(): Query { + return this as unknown as Query; + } + /** * @method includeCount * @memberof BaseQuery @@ -23,12 +32,12 @@ export class BaseQuery extends Pagination { * // OR * const asset = await stack.asset().includeCount().find() * - * @returns {BaseQuery} + * @returns {Query} */ - includeCount(): BaseQuery { + includeCount(): Query { this._queryParams.include_count = 'true'; - return this; + return this.asQuery(); } /** @@ -44,12 +53,12 @@ export class BaseQuery extends Pagination { * // OR * const asset = await stack.asset().orderByAscending().find() * - * @returns {BaseQuery} + * @returns {Query} */ - orderByAscending(key: string): BaseQuery { + orderByAscending(key: string): Query { this._queryParams.asc = key; - return this; + return this.asQuery(); } /** @@ -65,12 +74,12 @@ export class BaseQuery extends Pagination { * // OR * const asset = await stack.asset().orderByDescending().find() * - * @returns {BaseQuery} + * @returns {Query} */ - orderByDescending(key: string): BaseQuery { + orderByDescending(key: string): Query { this._queryParams.desc = key; - return this; + return this.asQuery(); } /** @@ -86,12 +95,12 @@ export class BaseQuery extends Pagination { * // OR * const asset = await stack.asset().limit(5).find() * - * @returns {BaseQuery} + * @returns {Query} */ - limit(key: number): BaseQuery { + limit(key: number): Query { this._queryParams.limit = key; - return this; + return this.asQuery(); } /** @@ -107,12 +116,12 @@ export class BaseQuery extends Pagination { * // OR * const asset = await stack.asset().skip(5).find() * - * @returns {BaseQuery} + * @returns {Query} */ - skip(key: number): BaseQuery { + skip(key: number): Query { this._queryParams.skip = key; - return this; + return this.asQuery(); } @@ -130,12 +139,12 @@ export class BaseQuery extends Pagination { * // OR * const asset = await stack.asset().param("key", "value").find() * - * @returns {BaseQuery} + * @returns {Query} */ - param(key: string, value: string | number): BaseQuery { + param(key: string, value: string | number): Query { this._queryParams[key] = value; - return this; + return this.asQuery(); } /** @@ -151,12 +160,12 @@ export class BaseQuery extends Pagination { * // OR * const asset = await stack.asset().addParams({"key": "value"}).find() * - * @returns {BaseQuery} + * @returns {Query} */ - addParams(paramObj: { [key: string]: string | boolean | number }): BaseQuery { + addParams(paramObj: { [key: string]: string | boolean | number }): Query { this._queryParams = { ...this._queryParams, ...paramObj }; - return this; + return this.asQuery(); } /** @@ -172,12 +181,12 @@ export class BaseQuery extends Pagination { * // OR * const asset = await stack.asset().removeParam("query_param_key").find() * - * @returns {BaseQuery} + * @returns {Query} */ - removeParam(key: string): BaseQuery { + removeParam(key: string): Query { delete this._queryParams[key]; - return this; + return this.asQuery(); } /** diff --git a/src/lib/contentstack.ts b/src/lib/contentstack.ts index 057a4e8..837b99d 100644 --- a/src/lib/contentstack.ts +++ b/src/lib/contentstack.ts @@ -4,7 +4,8 @@ import { handleRequest } from './cache'; import { Stack as StackClass } from './stack'; import { Policy, StackConfig, ContentstackPlugin } from './types'; import * as Utility from './utils'; -export * as Utils from '@contentstack/utils'; +import * as Utils from '@contentstack/utils'; +export { Utils }; let version = '{{VERSION}}'; @@ -33,8 +34,10 @@ let version = '{{VERSION}}'; */ // eslint-disable-next-line @typescript-eslint/naming-convention export function stack(config: StackConfig): StackClass { + const DEFAULT_HOST = Utility.getHostforRegion(config.region || "aws_na", config.host); + let defaultConfig = { - defaultHostname: 'cdn.contentstack.io', + defaultHostname: DEFAULT_HOST, headers: {} as AxiosRequestHeaders, params: {} as any, live_preview: {} as any, @@ -42,7 +45,6 @@ export function stack(config: StackConfig): StackClass { ...config }; - defaultConfig.defaultHostname = config.host || Utility.getHost(config.region, config.host); config.host = defaultConfig.defaultHostname; if (config.apiKey) { @@ -104,6 +106,67 @@ export function stack(config: StackConfig): StackClass { }); }; } + // LogHandler interceptors + if (config.debug) { + // Request interceptor for logging + client.interceptors.request.use((requestConfig: any) => { + config.logHandler!('info', { + type: 'request', + method: requestConfig.method?.toUpperCase(), + url: requestConfig.url, + headers: requestConfig.headers, + params: requestConfig.params, + timestamp: new Date().toISOString() + }); + return requestConfig; + }); + + // Response interceptor for logging + client.interceptors.response.use( + (response: any) => { + const level = getLogLevelFromStatus(response.status); + config.logHandler!(level, { + type: 'response', + status: response.status, + statusText: response.statusText, + url: response.config?.url, + method: response.config?.method?.toUpperCase(), + headers: response.headers, + data: response.data, + timestamp: new Date().toISOString() + }); + return response; + }, + (error: any) => { + const status = error.response?.status || 0; + const level = getLogLevelFromStatus(status); + config.logHandler!(level, { + type: 'response_error', + status: status, + statusText: error.response?.statusText || error.message, + url: error.config?.url, + method: error.config?.method?.toUpperCase(), + error: error.message, + timestamp: new Date().toISOString() + }); + throw error; + } + ); + } + + // Helper function to determine log level based on HTTP status code + function getLogLevelFromStatus(status: number): string { + if (status >= 200 && status < 300) { + return 'info'; + } else if (status >= 300 && status < 400) { + return 'warn'; + } else if (status >= 400) { + return 'error'; + } else { + return 'debug'; + } + } + // Retry policy handlers const errorHandler = (error: any) => { return retryResponseErrorHandler(error, config, client); diff --git a/src/lib/stack.ts b/src/lib/stack.ts index 324533d..b4fe63c 100644 --- a/src/lib/stack.ts +++ b/src/lib/stack.ts @@ -8,6 +8,7 @@ import { synchronization } from './synchronization'; import {TaxonomyQuery} from './taxonomy-query'; import { GlobalFieldQuery } from './global-field-query'; import { GlobalField } from './global-field'; +import { getHostforRegion } from './utils'; export class Stack { readonly config: StackConfig; @@ -227,4 +228,20 @@ export class Stack { if (typeof debug === "boolean") this.config.debug = debug; return this; } + + /** + * @method setHost + * @memberOf Stack + * @description Sets the host based on cloud region + * @param {String} cloudRegion - Cloud region (e.g., 'aws_na', 'aws_eu') + * @param {String} host - Optional custom host + * @return {Promise} - Returns the host URL + * @instance + * */ + async setHost(region: string = "aws_na", host?: string): Promise { + const resolvedHost = getHostforRegion(region, host); + + this._client.defaults.baseURL = `https://${resolvedHost}`; + } + } diff --git a/src/lib/types.ts b/src/lib/types.ts index e19a032..4b548c5 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -76,7 +76,7 @@ export interface StackConfig extends HttpClientParams { environment: string; branch?: string; early_access?: string[]; - region?: Region; + region?: string; locale?: string; plugins?: ContentstackPlugin[]; logHandler?: (level: string, data: any) => void; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9ed390e..341e1de 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,14 +1,10 @@ import { Region, params } from './types'; +import { getContentstackEndpoint } from '@contentstack/utils'; -export function getHost(region: Region = Region.US, host?: string) { +export function getHostforRegion(region: string = "aws_na", host?: string): string { if (host) return host; - let url = 'cdn.contentstack.io'; - if (region !== Region.US) { - url = region.toString().toLowerCase() + '-cdn.contentstack.com'; - } - - return url; + return getContentstackEndpoint(region, 'contentDelivery', true) as string; } export function isBrowser() { diff --git a/test/api/base-query-casting.specs.ts b/test/api/base-query-casting.specs.ts new file mode 100644 index 0000000..b9983e1 --- /dev/null +++ b/test/api/base-query-casting.specs.ts @@ -0,0 +1,463 @@ +import { QueryOperation } from "../../src/lib/types"; +import { stackInstance } from "../utils/stack-instance"; +import { TEntries, TEntry, TAssets } from "./types"; + +const stack = stackInstance(); + +function makeQuery(contentType: string, queryObj?: { [key: string]: any }) { + return stack.contentType(contentType).entry().query(queryObj); +} + +function makeAssetQuery() { + return stack.asset().query(); +} + +describe("BaseQuery Casting API Tests", () => { + describe("Query Type Return Enhancement", () => { + it("should support method chaining with Query type for entries", async () => { + const result = await stack + .contentType("blog_post") + .entry() + .query() + .limit(5) + .skip(0) + .includeCount() + .orderByAscending("title") + .find(); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + if (result.count !== undefined) { + expect(typeof result.count).toBe("number"); + } + }); + + it("should support method chaining with Query type for assets", async () => { + const result = await stack + .asset() + .query() + .limit(5) + .skip(0) + .includeCount() + .orderByDescending("created_at") + .find(); + + expect(result).toBeDefined(); + expect(result.assets).toBeDefined(); + }); + + it("should chain multiple BaseQuery methods and maintain Query type", async () => { + const query = makeQuery("blog_post"); + + // Chain all BaseQuery methods + const chainedQuery = query + .limit(10) + .skip(5) + .includeCount() + .orderByAscending("title") + .orderByDescending("created_at") + .param("locale", "en-us") + .addParams({ include_count: "true" }) + .removeParam("locale"); + + // Verify the query is still functional + const result = await chainedQuery.find(); + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should support method chaining with where clauses and BaseQuery methods", async () => { + const result = await makeQuery("blog_post") + .where("title", QueryOperation.EQUALS, "The future of business with AI") + .limit(5) + .skip(0) + .includeCount() + .orderByAscending("title") + .find(); + + expect(result).toBeDefined(); + if (result.entries && result.entries.length > 0) { + expect(result.entries[0].title).toBeDefined(); + } + }); + + it("should support method chaining with asset queries", async () => { + const result = await makeAssetQuery() + .limit(3) + .skip(1) + .includeCount() + .orderByAscending("filename") + .find(); + + expect(result).toBeDefined(); + expect(result.assets).toBeDefined(); + }); + + it("should handle complex method chaining with multiple query operations", async () => { + const result = await makeQuery("blog_post") + .where("_version", QueryOperation.IS_GREATER_THAN, 1) + .limit(5) + .skip(0) + .includeCount() + .orderByDescending("created_at") + .param("locale", "en-us") + .find(); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should support method chaining across different query types", async () => { + // Test entry query + const entryResult = await makeQuery("blog_post") + .limit(2) + .includeCount() + .find(); + expect(entryResult).toBeDefined(); + + // Test asset query + const assetResult = await makeAssetQuery() + .limit(2) + .includeCount() + .find(); + expect(assetResult).toBeDefined(); + }); + }); + + describe("Encoding Enhancement - API Integration Tests", () => { + it("should handle special characters in query parameters with encoding", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Test & Encode", + description: "URL with ?param=value&other=test" + }; + + const result = await query.find(true); + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle unicode characters with encoding in API calls", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Café français", + description: "Testing unicode: ñáéíóú 中文" + }; + + const result = await query.find(true); + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should encode nested objects in real API calls", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + author: { + name: "John & Jane", + email: "user@example.com?ref=test" + } + }; + + const result = await query.find(true); + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with method chaining", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Test & Special Characters" + }; + + const result = await query + .limit(5) + .skip(0) + .includeCount() + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with where clauses", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + category: "news & tech" + }; + + const result = await query + .where("title", QueryOperation.EQUALS, "The future of business with AI") + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with asset queries", async () => { + const query = makeAssetQuery(); + query._parameters = { + filename: "test & file.jpg", + description: "Image with ?special=chars" + }; + + const result = await query + .limit(5) + .find(true); + + expect(result).toBeDefined(); + expect(result.assets).toBeDefined(); + }); + + it("should handle mixed encoding scenarios with complex queries", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + $and: [ + { title: { $regex: "test & pattern" } }, + { category: "news+tech" }, + { author: { name: "John & Jane" } } + ] + }; + + const result = await query + .limit(10) + .includeCount() + .orderByAscending("title") + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should maintain backward compatibility - no encoding by default", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Simple Title" + }; + + // Default behavior (no encoding) + const result1 = await query.find(); + expect(result1).toBeDefined(); + expect(result1.entries).toBeDefined(); + + // Explicitly no encoding + const result2 = await query.find(false); + expect(result2).toBeDefined(); + expect(result2.entries).toBeDefined(); + }); + + it("should handle encoding with pagination", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Test & Pagination" + }; + + const result = await query + .limit(5) + .skip(0) + .includeCount() + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + if (result.count !== undefined) { + expect(typeof result.count).toBe("number"); + } + }); + + it("should handle encoding with sorting", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + category: "news & updates" + }; + + const result = await query + .orderByAscending("title") + .orderByDescending("created_at") + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with param() method", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Test & Param" + }; + + const result = await query + .param("locale", "en-us") + .param("include_count", "true") + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with addParams() method", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Test & AddParams" + }; + + const result = await query + .addParams({ + locale: "en-us", + include_count: "true" + }) + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with deeply nested objects", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + metadata: { + author: { + name: "John & Jane", + contact: { + email: "user@example.com?ref=test" + } + } + } + }; + + const result = await query.find(true); + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with empty parameters", async () => { + const query = makeQuery("blog_post"); + query._parameters = {}; + + const result = await query + .limit(5) + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with array values in parameters", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + tags: ["tech & news", "development + coding"] + }; + + const result = await query.find(true); + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with number and boolean values", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + count: 42, + active: true, + title: "Test & Title" + }; + + const result = await query.find(true); + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + }); + + describe("Combined Enhancement Tests", () => { + it("should combine Query type return and encoding in single query", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Test & Combined" + }; + + const result = await query + .limit(5) + .skip(0) + .includeCount() + .orderByAscending("title") + .param("locale", "en-us") + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle complex query with both enhancements", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + $and: [ + { title: { $regex: "test & pattern" } }, + { author: { name: "John & Jane", email: "user@example.com" } } + ] + }; + + const result = await query + .where("_version", QueryOperation.IS_GREATER_THAN, 1) + .limit(10) + .skip(0) + .includeCount() + .orderByDescending("created_at") + .addParams({ locale: "en-us" }) + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle method chaining with encoding across different content types", async () => { + // Test with blog_post + const blogQuery = makeQuery("blog_post"); + blogQuery._parameters = { category: "news & tech" }; + const blogResult = await blogQuery + .limit(3) + .includeCount() + .find(true); + expect(blogResult).toBeDefined(); + + // Test with assets + const assetQuery = makeAssetQuery(); + assetQuery._parameters = { filename: "test & file.jpg" }; + const assetResult = await assetQuery + .limit(3) + .includeCount() + .find(true); + expect(assetResult).toBeDefined(); + }); + + it("should handle encoding with removeParam() in method chain", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Test & RemoveParam" + }; + + const result = await query + .param("locale", "en-us") + .param("include_count", "true") + .removeParam("include_count") + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + + it("should handle encoding with multiple addParams() calls", async () => { + const query = makeQuery("blog_post"); + query._parameters = { + title: "Test & MultipleParams" + }; + + const result = await query + .addParams({ locale: "en-us" }) + .addParams({ include_count: "true" }) + .find(true); + + expect(result).toBeDefined(); + expect(result.entries).toBeDefined(); + }); + }); +}); + + diff --git a/test/unit/asset-query.spec.ts b/test/unit/asset-query.spec.ts index 7f290c3..de314bb 100644 --- a/test/unit/asset-query.spec.ts +++ b/test/unit/asset-query.spec.ts @@ -48,6 +48,12 @@ describe('AssetQuery class', () => { expect(assetQuery._queryParams.include_fallback).toBe('true'); }); + it('should add "include_metadata" in queryParameter when includeMetadata method is called', () => { + const returnedValue = assetQuery.includeMetadata(); + expect(returnedValue).toBeInstanceOf(AssetQuery); + expect(assetQuery._queryParams.include_metadata).toBe('true'); + }); + it('should add "locale" in Parameter when locale method is called', () => { const returnedValue = assetQuery.locale('en-us'); expect(returnedValue).toBeInstanceOf(AssetQuery); diff --git a/test/unit/asset.spec.ts b/test/unit/asset.spec.ts index 7fcedcb..f8be0b7 100644 --- a/test/unit/asset.spec.ts +++ b/test/unit/asset.spec.ts @@ -42,6 +42,12 @@ describe('Asset class', () => { expect(asset._queryParams.include_fallback).toBe('true'); }); + it('should add "include_metadata" in _queryParams when includeMetadata method is called', () => { + const returnedValue = asset.includeMetadata(); + expect(returnedValue).toBeInstanceOf(Asset); + expect(asset._queryParams.include_metadata).toBe('true'); + }); + it('should add "relative_urls" in _queryParams when relativeUrl method is called', () => { const returnedValue = asset.relativeUrls(); expect(returnedValue).toBeInstanceOf(Asset); @@ -59,4 +65,13 @@ describe('Asset class', () => { const returnedValue = await asset.fetch(); expect(returnedValue).toEqual(assetFetchDataMock.asset); }); + + it('should return response directly when asset property is not present', async () => { + const responseWithoutAsset = { data: 'test', uid: 'test-uid' }; + mockClient.onGet(`/assets/assetUid`).reply(200, responseWithoutAsset); + + const result = await asset.fetch(); + + expect(result).toEqual(responseWithoutAsset); + }); }); diff --git a/test/unit/base-query.spec.ts b/test/unit/base-query.spec.ts index 1a2da5c..3d3ba58 100644 --- a/test/unit/base-query.spec.ts +++ b/test/unit/base-query.spec.ts @@ -1,4 +1,9 @@ import { BaseQuery } from '../../src/lib/base-query'; +import { Query } from '../../src/lib/query'; +import { httpClient, AxiosInstance } from '@contentstack/core'; +import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; +import MockAdapter from 'axios-mock-adapter'; +import { entryFindMock } from '../utils/mocks'; describe('BaseQuery class', () => { let baseQuery: BaseQuery; @@ -69,4 +74,325 @@ describe('BaseQuery class', () => { baseQuery.removeParam('key2'); expect(baseQuery._queryParams).toEqual({ key1: 'value1' }); }); + + describe('Enhancement: Methods return Query type', () => { + it('should return Query-compatible type from includeCount()', () => { + const returnedValue = baseQuery.includeCount(); + + // Should be instance of BaseQuery (Query extends BaseQuery) + expect(returnedValue).toBeInstanceOf(BaseQuery); + expect(baseQuery._queryParams.include_count).toBe('true'); + }); + + it('should return Query-compatible type from orderByAscending()', () => { + const returnedValue = baseQuery.orderByAscending('title'); + + expect(returnedValue).toBeInstanceOf(BaseQuery); + expect(baseQuery._queryParams.asc).toBe('title'); + }); + + it('should return Query-compatible type from orderByDescending()', () => { + const returnedValue = baseQuery.orderByDescending('created_at'); + + expect(returnedValue).toBeInstanceOf(BaseQuery); + expect(baseQuery._queryParams.desc).toBe('created_at'); + }); + + it('should return Query-compatible type from limit()', () => { + const returnedValue = baseQuery.limit(10); + + expect(returnedValue).toBeInstanceOf(BaseQuery); + expect(baseQuery._queryParams.limit).toBe(10); + }); + + it('should return Query-compatible type from skip()', () => { + const returnedValue = baseQuery.skip(5); + + expect(returnedValue).toBeInstanceOf(BaseQuery); + expect(baseQuery._queryParams.skip).toBe(5); + }); + + it('should return Query-compatible type from param()', () => { + const returnedValue = baseQuery.param('locale', 'en-us'); + + expect(returnedValue).toBeInstanceOf(BaseQuery); + expect(baseQuery._queryParams.locale).toBe('en-us'); + }); + + it('should return Query-compatible type from addParams()', () => { + const returnedValue = baseQuery.addParams({ include_count: 'true' }); + + expect(returnedValue).toBeInstanceOf(BaseQuery); + expect(baseQuery._queryParams.include_count).toBe('true'); + }); + + it('should return Query-compatible type from removeParam()', () => { + baseQuery.param('key1', 'value1'); + const returnedValue = baseQuery.removeParam('key1'); + + expect(returnedValue).toBeInstanceOf(BaseQuery); + expect(baseQuery._queryParams.key1).toBeUndefined(); + }); + + it('should support method chaining with Query type', () => { + const chained = baseQuery + .limit(5) + .skip(0) + .includeCount() + .orderByAscending('title'); + + expect(chained).toBeInstanceOf(BaseQuery); + expect(baseQuery._queryParams.limit).toBe(5); + expect(baseQuery._queryParams.skip).toBe(0); + expect(baseQuery._queryParams.include_count).toBe('true'); + expect(baseQuery._queryParams.asc).toBe('title'); + }); + }); +}); + +class TestableBaseQuery extends BaseQuery { + constructor(client: AxiosInstance, urlPath: string | null = null) { + super(); + this._client = client; + if (urlPath !== null) { + this._urlPath = urlPath; + } + this._variants = ''; + } + + setVariants(variants: string) { + this._variants = variants; + } + + setParameters(params: any) { + this._parameters = params; + } + + setUrlPath(path: string) { + this._urlPath = path; + } +} + +describe('BaseQuery find method', () => { + let client: AxiosInstance; + let mockClient: MockAdapter; + let query: TestableBaseQuery; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + query = new TestableBaseQuery(client, '/content_types/test_uid/entries'); + mockClient.reset(); + }); + + it('should call find with encode parameter true', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + // Verify that query parameters are encoded + const queryParam = config.params?.query; + expect(queryParam).toBeDefined(); + // When encoded, special characters should be URL encoded + return [200, entryFindMock]; + }); + + query.setParameters({ title: 'Test & Encode' }); + const result = await query.find(true); + + expect(result).toEqual(entryFindMock); + }); + + it('should encode query parameters when encode is true', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + const queryParam = config.params?.query; + // Verify encoding: 'Test & Value' should be encoded + expect(queryParam.title).toBe('Test%20%26%20Value'); + return [200, entryFindMock]; + }); + + query.setParameters({ title: 'Test & Value' }); + await query.find(true); + }); + + it('should not encode query parameters when encode is false', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + const queryParam = config.params?.query; + // Verify no encoding: raw value should be present + expect(queryParam.title).toBe('Test & Value'); + return [200, entryFindMock]; + }); + + query.setParameters({ title: 'Test & Value' }); + await query.find(false); + }); + + it('should not encode query parameters when encode is not provided', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + const queryParam = config.params?.query; + // Verify no encoding by default + expect(queryParam.title).toBe('Test & Value'); + return [200, entryFindMock]; + }); + + query.setParameters({ title: 'Test & Value' }); + await query.find(); + }); + + it('should encode nested query parameters when encode is true', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + const queryParam = config.params?.query; + // Verify nested object encoding + expect(queryParam.nested.name).toBe('John%20%26%20Jane'); + expect(queryParam.nested.deeply.nested).toBe('value%20%2B%20symbols'); + return [200, entryFindMock]; + }); + + query.setParameters({ + nested: { + name: 'John & Jane', + deeply: { + nested: 'value + symbols' + } + } + }); + await query.find(true); + }); + + it('should encode special characters correctly', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + const queryParam = config.params?.query; + // Test various special characters + expect(queryParam.symbols).toBe('hello%40world.com%3Fparam%3Dvalue'); + expect(queryParam.unicode).toBe('caf%C3%A9%20fran%C3%A7ais'); + return [200, entryFindMock]; + }); + + query.setParameters({ + symbols: 'hello@world.com?param=value', + unicode: 'café français' + }); + await query.find(true); + }); + + it('should preserve non-string values when encoding', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + const queryParam = config.params?.query; + // Numbers and booleans should not be encoded + expect(queryParam.numberValue).toBe(42); + expect(queryParam.booleanTrue).toBe(true); + expect(queryParam.booleanFalse).toBe(false); + // Strings should be encoded + expect(queryParam.stringValue).toBe('encode%20me'); + return [200, entryFindMock]; + }); + + query.setParameters({ + stringValue: 'encode me', + numberValue: 42, + booleanTrue: true, + booleanFalse: false + }); + await query.find(true); + }); + + it('should call find without parameters', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply(200, entryFindMock); + + const result = await query.find(); + + expect(result).toEqual(entryFindMock); + }); + + it('should call find with variants header when variants are set', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + expect(config.headers?.['x-cs-variant-uid']).toBe('variant1,variant2'); + return [200, entryFindMock]; + }); + + query.setVariants('variant1,variant2'); + await query.find(); + }); + + it('should extract content type UID from URL path', async () => { + mockClient.onGet('/content_types/my_content_type/entries').reply(200, entryFindMock); + + const queryWithContentType = new TestableBaseQuery(client, '/content_types/my_content_type/entries'); + const result = await queryWithContentType.find(); + + expect(result).toEqual(entryFindMock); + }); + + it('should return null for content type UID when URL does not match pattern', async () => { + mockClient.onGet('/assets').reply(200, entryFindMock); + + const queryWithoutContentType = new TestableBaseQuery(client, '/assets'); + const result = await queryWithoutContentType.find(); + + expect(result).toEqual(entryFindMock); + }); + + it('should handle find with both encode and variants', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + expect(config.headers?.['x-cs-variant-uid']).toBe('test-variant'); + return [200, entryFindMock]; + }); + + query.setVariants('test-variant'); + query.setParameters({ status: 'published' }); + const result = await query.find(true); + + expect(result).toEqual(entryFindMock); + }); + + it('should handle empty _urlPath gracefully', () => { + const queryWithoutUrlPath = new TestableBaseQuery(client, null); + queryWithoutUrlPath.setUrlPath(''); + + // Verify that URL path is empty (testing the null check in extractContentTypeUidFromUrl) + expect(queryWithoutUrlPath).toBeInstanceOf(TestableBaseQuery); + }); + + it('should handle find with empty parameters and encode', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply(200, entryFindMock); + + query.setParameters({}); + const result = await query.find(true); + + expect(result).toEqual(entryFindMock); + }); + + it('should combine query params and _parameters correctly when encoding', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + const params = config.params; + // Verify both query params and _parameters are included + expect(params.limit).toBe(10); + expect(params.query).toBeDefined(); + expect(params.query.title).toBe('Test%20Title'); + return [200, entryFindMock]; + }); + + query.setParameters({ title: 'Test Title' }); + query.limit(10); + await query.find(true); + }); + + it('should handle find with complex nested parameters and encoding', async () => { + mockClient.onGet('/content_types/test_uid/entries').reply((config) => { + const queryParam = config.params?.query; + expect(queryParam.complex.nested.deep.value).toBe('encoded%20value'); + return [200, entryFindMock]; + }); + + query.setParameters({ + complex: { + nested: { + deep: { + value: 'encoded value' + } + } + } + }); + await query.find(true); + }); }); \ No newline at end of file diff --git a/test/unit/cache.spec.ts b/test/unit/cache.spec.ts index 3e42f9c..1ae933d 100644 --- a/test/unit/cache.spec.ts +++ b/test/unit/cache.spec.ts @@ -329,4 +329,93 @@ describe("Cache handleRequest function", () => { cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); }); + + describe("Enhanced cache key with entryUid", () => { + it("should extract entryUid from URL pattern", async () => { + const cacheOptions = { policy: Policy.CACHE_THEN_NETWORK, maxAge: 3600 }; + const defaultAdapter = jest.fn((_config) => ({ + data: JSON.stringify("foo"), + })); + const configWithUrl = { + ...config, + url: '/content_types/test_ct/entries/entry123', + }; + + const cacheStore = new PersistanceStore(cacheOptions); + + await handleRequest( + cacheOptions, + apiKey, + defaultAdapter, + resolve, + reject, + configWithUrl + ); + + expect(defaultAdapter).toHaveBeenCalled(); + expect(resolve).toBeCalledWith({ data: "foo" }); + + // Clean up with enhanced key that includes entry UID + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}_entry_entry123`; + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); + }); + + it("should use entryUid from config when available", async () => { + const cacheOptions = { policy: Policy.CACHE_THEN_NETWORK, maxAge: 3600 }; + const defaultAdapter = jest.fn((_config) => ({ + data: JSON.stringify("foo"), + })); + const configWithEntryUid = { + ...config, + entryUid: 'entry456', + }; + + const cacheStore = new PersistanceStore(cacheOptions); + + await handleRequest( + cacheOptions, + apiKey, + defaultAdapter, + resolve, + reject, + configWithEntryUid + ); + + expect(defaultAdapter).toHaveBeenCalled(); + expect(resolve).toBeCalledWith({ data: "foo" }); + + // Clean up with enhanced key that includes entry UID + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}_entry_entry456`; + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); + }); + + it("should return null when URL does not match entry pattern", async () => { + const cacheOptions = { policy: Policy.CACHE_THEN_NETWORK, maxAge: 3600 }; + const defaultAdapter = jest.fn((_config) => ({ + data: JSON.stringify("foo"), + })); + const configWithInvalidUrl = { + ...config, + url: '/assets', + }; + + const cacheStore = new PersistanceStore(cacheOptions); + + await handleRequest( + cacheOptions, + apiKey, + defaultAdapter, + resolve, + reject, + configWithInvalidUrl + ); + + expect(defaultAdapter).toHaveBeenCalled(); + expect(resolve).toBeCalledWith({ data: "foo" }); + + // Clean up with standard enhanced key (no entry UID) + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); + }); + }); }); diff --git a/test/unit/content-validation-comprehensive.spec.ts b/test/unit/content-validation-comprehensive.spec.ts index 2ed1147..5bebc36 100644 --- a/test/unit/content-validation-comprehensive.spec.ts +++ b/test/unit/content-validation-comprehensive.spec.ts @@ -872,6 +872,9 @@ describe('Content Validation - Comprehensive Test Suite', () => { describe('Content Validation Edge Cases', () => { it('should handle null and undefined values gracefully', () => { + // Mock console.error to suppress validation messages + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const query = new Query(client, {}, {}, '', 'blog_post'); // Test with null values @@ -881,6 +884,9 @@ describe('Content Validation - Comprehensive Test Suite', () => { // Test with empty strings expect(() => query.equalTo('title', '')).not.toThrow(); expect(() => query.equalTo('view_count', 0)).not.toThrow(); + + // Restore console.error + consoleSpy.mockRestore(); }); it('should validate content type without schema', async () => { diff --git a/test/unit/contentstack-debug-integration.spec.ts b/test/unit/contentstack-debug-integration.spec.ts new file mode 100644 index 0000000..0187037 --- /dev/null +++ b/test/unit/contentstack-debug-integration.spec.ts @@ -0,0 +1,226 @@ +import { httpClient, AxiosInstance } from '@contentstack/core'; +import * as Contentstack from '../../src/lib/contentstack'; +import { Stack } from '../../src/lib/stack'; +import { Policy, StackConfig } from '../../src/lib/types'; +import MockAdapter from 'axios-mock-adapter'; + +describe('Contentstack Debug Logging Integration', () => { + let mockLogHandler: jest.Mock; + + beforeEach(() => { + mockLogHandler = jest.fn(); + }); + + it('should execute debug logging for request interceptor', async () => { + const config: StackConfig = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + debug: true, + logHandler: mockLogHandler, + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + const mockClient = new MockAdapter(client); + + mockClient.onGet('/content_types/test').reply(200, { + content_types: [] + }); + + // Make an actual request to trigger interceptors + try { + await client.get('/content_types/test'); + } catch (e) { + // Ignore errors + } + + // Verify request logging was called + const requestLogs = mockLogHandler.mock.calls.filter((call: any) => + call[1]?.type === 'request' + ); + expect(requestLogs.length).toBeGreaterThan(0); + + mockClient.restore(); + }); + + it('should execute debug logging for response interceptor with 2xx status', async () => { + const config: StackConfig = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + debug: true, + logHandler: mockLogHandler, + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + const mockClient = new MockAdapter(client); + + mockClient.onGet('/content_types/test').reply(200, { + content_types: [] + }); + + await client.get('/content_types/test'); + + // Verify response logging was called with info level + const responseLogs = mockLogHandler.mock.calls.filter((call: any) => + call[1]?.type === 'response' && call[0] === 'info' + ); + expect(responseLogs.length).toBeGreaterThan(0); + + mockClient.restore(); + }); + + it('should execute debug logging for response interceptor with 3xx status', async () => { + const config: StackConfig = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + debug: true, + logHandler: mockLogHandler, + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + const mockClient = new MockAdapter(client); + + // 3xx responses are treated as errors by axios-mock-adapter + mockClient.onGet('/content_types/test').reply(304, {}); + + try { + await client.get('/content_types/test'); + } catch (e) { + // Expected - 3xx responses trigger error handler in mock adapter + } + + // Verify error response logging was called - 3xx goes through error interceptor + const errorLogs = mockLogHandler.mock.calls.filter((call: any) => + call[1]?.type === 'response_error' && call[1]?.status === 304 + ); + expect(errorLogs.length).toBeGreaterThan(0); + + mockClient.restore(); + }); + + it('should execute debug logging for error response interceptor with 4xx status', async () => { + const config: StackConfig = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + debug: true, + logHandler: mockLogHandler, + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + const mockClient = new MockAdapter(client); + + mockClient.onGet('/content_types/test').reply(404, { + error: 'Not found' + }); + + try { + await client.get('/content_types/test'); + } catch (e) { + // Expected error + } + + // Verify error logging was called + const errorLogs = mockLogHandler.mock.calls.filter((call: any) => + call[1]?.type === 'response_error' && call[0] === 'error' + ); + expect(errorLogs.length).toBeGreaterThan(0); + + mockClient.restore(); + }); + + it('should execute debug logging for error response without status', async () => { + const config: StackConfig = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + debug: true, + logHandler: mockLogHandler, + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + const mockClient = new MockAdapter(client); + + mockClient.onGet('/content_types/test').networkError(); + + try { + await client.get('/content_types/test'); + } catch (e) { + // Expected network error + } + + // Verify error logging was called with debug level for no status + const errorLogs = mockLogHandler.mock.calls.filter((call: any) => + call[1]?.type === 'response_error' && call[0] === 'debug' + ); + expect(errorLogs.length).toBeGreaterThan(0); + + mockClient.restore(); + }); + + it('should set cache adapter when cacheOptions is provided', () => { + const config: StackConfig = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + cacheOptions: { + policy: Policy.CACHE_THEN_NETWORK, + maxAge: 3600, + }, + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Verify the custom adapter was set + const customAdapter = client.defaults.adapter; + expect(customAdapter).toBeDefined(); + expect(typeof customAdapter).toBe('function'); + }); + + it('should set cache adapter with NETWORK_ELSE_CACHE policy', () => { + const config: StackConfig = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + cacheOptions: { + policy: Policy.NETWORK_ELSE_CACHE, + maxAge: 3600, + }, + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + const customAdapter = client.defaults.adapter; + expect(customAdapter).toBeDefined(); + expect(typeof customAdapter).toBe('function'); + }); + + it('should set cache adapter with CACHE_ELSE_NETWORK policy', () => { + const config: StackConfig = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + cacheOptions: { + policy: Policy.CACHE_ELSE_NETWORK, + maxAge: 3600, + }, + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + const customAdapter = client.defaults.adapter; + expect(customAdapter).toBeDefined(); + expect(typeof customAdapter).toBe('function'); + }); +}); + diff --git a/test/unit/contentstack.spec.ts b/test/unit/contentstack.spec.ts index 2205c66..6cc48e6 100644 --- a/test/unit/contentstack.spec.ts +++ b/test/unit/contentstack.spec.ts @@ -1,16 +1,21 @@ -import exp = require("constants"); +import * as exp from "constants"; import * as core from "@contentstack/core"; import * as Contentstack from "../../src/lib/contentstack"; import { Stack } from "../../src/lib/stack"; import { Policy, Region, StackConfig } from "../../src/lib/types"; +import { StorageType } from "../../src/persistance/types/storage-type"; import { CUSTOM_HOST, DUMMY_URL, HOST_AU_REGION, HOST_EU_REGION, HOST_URL, + HOST_AZURE_NA_REGION, + HOST_GCP_NA_REGION, + HOST_GCP_EU_REGION, } from "../utils/constant"; import { AxiosRequestConfig, AxiosResponse } from "axios"; +import * as utils from "../../src/lib/utils"; jest.mock("@contentstack/core"); const createHttpClientMock = >( @@ -257,4 +262,430 @@ describe("Contentstack", () => { createHttpClientMock.mockReset(); done(); }); + + describe('getHostforRegion integration in stack creation', () => { + it('should use getHostforRegion to set default hostname for aws_na region', () => { + const getHostforRegionSpy = jest.spyOn(utils, 'getHostforRegion'); + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + region: "aws_na", + }; + + const stackInstance = createStackInstance(config); + + expect(getHostforRegionSpy).toHaveBeenCalledWith("aws_na", undefined); + expect(stackInstance).toBeInstanceOf(Stack); + + getHostforRegionSpy.mockRestore(); + }); + + it('should use getHostforRegion to set default hostname for eu region', () => { + const getHostforRegionSpy = jest.spyOn(utils, 'getHostforRegion'); + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + region: "eu", + }; + + const stackInstance = createStackInstance(config); + + expect(getHostforRegionSpy).toHaveBeenCalledWith("eu", undefined); + expect(stackInstance).toBeInstanceOf(Stack); + + getHostforRegionSpy.mockRestore(); + }); + + it('should use getHostforRegion with custom host when both region and host are provided', () => { + const getHostforRegionSpy = jest.spyOn(utils, 'getHostforRegion'); + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + region: "eu", + host: CUSTOM_HOST, + }; + + const stackInstance = createStackInstance(config); + + expect(getHostforRegionSpy).toHaveBeenCalledWith("eu", CUSTOM_HOST); + expect(stackInstance).toBeInstanceOf(Stack); + + getHostforRegionSpy.mockRestore(); + }); + + it('should use getHostforRegion for azure-na region', () => { + const getHostforRegionSpy = jest.spyOn(utils, 'getHostforRegion'); + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + region: "azure-na", + }; + + const stackInstance = createStackInstance(config); + + expect(getHostforRegionSpy).toHaveBeenCalledWith("azure-na", undefined); + expect(stackInstance).toBeInstanceOf(Stack); + + getHostforRegionSpy.mockRestore(); + }); + + it('should use getHostforRegion for gcp-na region', () => { + const getHostforRegionSpy = jest.spyOn(utils, 'getHostforRegion'); + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + region: "gcp-na", + }; + + const stackInstance = createStackInstance(config); + + expect(getHostforRegionSpy).toHaveBeenCalledWith("gcp-na", undefined); + expect(stackInstance).toBeInstanceOf(Stack); + + getHostforRegionSpy.mockRestore(); + }); + + it('should use getHostforRegion for gcp-eu region', () => { + const getHostforRegionSpy = jest.spyOn(utils, 'getHostforRegion'); + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + region: "gcp-eu", + }; + + const stackInstance = createStackInstance(config); + + expect(getHostforRegionSpy).toHaveBeenCalledWith("gcp-eu", undefined); + expect(stackInstance).toBeInstanceOf(Stack); + + getHostforRegionSpy.mockRestore(); + }); + + it('should handle getHostforRegion error gracefully', () => { + const getHostforRegionSpy = jest.spyOn(utils, 'getHostforRegion').mockImplementation(() => { + throw new Error('Unable to set host using the provided region. Please provide a valid region.'); + }); + + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + region: "invalid_region", + }; + + expect(() => createStackInstance(config)).toThrow( + 'Unable to set host using the provided region. Please provide a valid region.' + ); + + getHostforRegionSpy.mockRestore(); + }); + + it('should use getHostforRegion with undefined region when no region is provided', () => { + const getHostforRegionSpy = jest.spyOn(utils, 'getHostforRegion'); + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + }; + + const stackInstance = createStackInstance(config); + + // When no region is provided, the default parameter "aws_na" is used + expect(getHostforRegionSpy).toHaveBeenCalledWith("aws_na", undefined); + expect(stackInstance).toBeInstanceOf(Stack); + + getHostforRegionSpy.mockRestore(); + }); + }); + + describe('locale configuration', () => { + it('should set locale in params when locale is provided in config', () => { + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + locale: "fr-fr", + }; + + const stackInstance = createStackInstance(config); + + expect(stackInstance).toBeInstanceOf(Stack); + expect(stackInstance.config.locale).toEqual("fr-fr"); + }); + }); + + describe('live preview configuration in browser environment', () => { + const originalDocument = global.document; + const originalWindow = global.window; + + beforeEach(() => { + // Mock browser environment + (utils.isBrowser as jest.Mock) = jest.fn(); + delete (global as any).document; + delete (global as any).window; + }); + + afterEach(() => { + global.document = originalDocument; + global.window = originalWindow; + jest.restoreAllMocks(); + }); + + it('should extract live_preview params from URL in browser environment', () => { + const isBrowserSpy = jest.spyOn(utils, 'isBrowser').mockReturnValue(true); + + // Mock document.location + const mockSearchParams = new Map([ + ['live_preview', 'test_hash'], + ['release_id', 'release123'], + ['preview_timestamp', '123456789'] + ]); + + (global as any).document = { + location: { + toString: () => 'http://localhost?live_preview=test_hash&release_id=release123&preview_timestamp=123456789' + } + }; + + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + live_preview: { + enable: true, + live_preview: 'default_hash' + }, + }; + + const stackInstance = createStackInstance(config); + + expect(isBrowserSpy).toHaveBeenCalled(); + expect(stackInstance).toBeInstanceOf(Stack); + + isBrowserSpy.mockRestore(); + }); + + it('should use fallback value when live_preview param is empty (line 74 || branch)', () => { + const isBrowserSpy = jest.spyOn(utils, 'isBrowser').mockReturnValue(true); + + // Mock document.location with empty live_preview param + (global as any).document = { + location: { + toString: () => 'http://localhost?live_preview=' + } + }; + + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + live_preview: { + enable: true, + live_preview: 'fallback_hash' + }, + }; + + const stackInstance = createStackInstance(config); + + // Should use the fallback value when params.get returns empty string + expect(stackInstance.config.live_preview?.live_preview).toBe('fallback_hash'); + + isBrowserSpy.mockRestore(); + }); + + it('should not extract params when not in browser environment', () => { + const isBrowserSpy = jest.spyOn(utils, 'isBrowser').mockReturnValue(false); + + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + live_preview: { + enable: true, + }, + }; + + const stackInstance = createStackInstance(config); + + expect(isBrowserSpy).toHaveBeenCalled(); + expect(stackInstance).toBeInstanceOf(Stack); + + isBrowserSpy.mockRestore(); + }); + }); + + describe('cache adapter configuration', () => { + it('should set cache adapter when cacheOptions with policy is provided', () => { + const mockAdapter = jest.fn(); + const mockClient = { + defaults: { + host: HOST_URL, + adapter: mockAdapter, + }, + interceptors: { + request: { + use: reqInterceptor, + }, + response: { + use: resInterceptor, + }, + }, + }; + + createHttpClientMock.mockReturnValue(mockClient as any); + + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + cacheOptions: { + policy: Policy.CACHE_THEN_NETWORK, + storeType: 'localStorage' as StorageType + }, + }; + + const stackInstance = createStackInstance(config); + + expect(stackInstance).toBeInstanceOf(Stack); + expect(mockClient.defaults.adapter).toBeDefined(); + }); + }); + + describe('debug mode with logging interceptors', () => { + it('should add request and response logging interceptors when debug is enabled', () => { + const mockLogHandler = jest.fn(); + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + debug: true, + logHandler: mockLogHandler, + }; + + const stackInstance = createStackInstance(config); + + expect(stackInstance).toBeInstanceOf(Stack); + expect(reqInterceptor).toHaveBeenCalled(); + expect(resInterceptor).toHaveBeenCalled(); + }); + }); + + describe('plugin interceptors execution', () => { + it('should execute plugin onRequest and onResponse methods', () => { + const mockOnRequest = jest.fn((req) => req); + const mockOnResponse = jest.fn((req, res, data) => res); + let requestInterceptor: any; + let responseInterceptor: any; + + const mockClient = { + defaults: { + host: HOST_URL, + }, + interceptors: { + request: { + use: jest.fn((interceptor) => { + requestInterceptor = interceptor; + }), + }, + response: { + use: jest.fn((successInterceptor) => { + responseInterceptor = successInterceptor; + }), + }, + }, + }; + + createHttpClientMock.mockReturnValue(mockClient as any); + + const mockPlugin = { + onRequest: mockOnRequest, + onResponse: mockOnResponse, + }; + + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + plugins: [mockPlugin], + }; + + createStackInstance(config); + + // Test that interceptors were registered + expect(mockClient.interceptors.request.use).toHaveBeenCalled(); + expect(mockClient.interceptors.response.use).toHaveBeenCalled(); + + // Test request interceptor execution + const mockRequest = { url: '/test' }; + requestInterceptor(mockRequest); + expect(mockOnRequest).toHaveBeenCalledWith(mockRequest); + + // Test response interceptor execution + const mockResponse = { + request: {}, + data: {}, + }; + responseInterceptor(mockResponse); + expect(mockOnResponse).toHaveBeenCalledWith(mockResponse.request, mockResponse, mockResponse.data); + }); + + it('should handle multiple plugins in order', () => { + const executionOrder: string[] = []; + let requestInterceptor: any; + + const mockClient = { + defaults: { + host: HOST_URL, + }, + interceptors: { + request: { + use: jest.fn((interceptor) => { + requestInterceptor = interceptor; + }), + }, + response: { + use: jest.fn(), + }, + }, + }; + + createHttpClientMock.mockReturnValue(mockClient as any); + + const mockPlugin1 = { + onRequest: jest.fn((req) => { + executionOrder.push('plugin1'); + return req; + }), + onResponse: jest.fn((req, res, data) => res), + }; + + const mockPlugin2 = { + onRequest: jest.fn((req) => { + executionOrder.push('plugin2'); + return req; + }), + onResponse: jest.fn((req, res, data) => res), + }; + + const config = { + apiKey: "apiKey", + deliveryToken: "delivery", + environment: "env", + plugins: [mockPlugin1, mockPlugin2], + }; + + createStackInstance(config); + + const mockRequest = { url: '/test' }; + requestInterceptor(mockRequest); + + expect(executionOrder).toEqual(['plugin1', 'plugin2']); + }); + }); }); diff --git a/test/unit/contenttype.spec.ts b/test/unit/contenttype.spec.ts index 1b62c34..20b821d 100644 --- a/test/unit/contenttype.spec.ts +++ b/test/unit/contenttype.spec.ts @@ -41,4 +41,13 @@ describe('ContentType class', () => { const response = await contentType.fetch(); expect(response).toEqual(contentTypeResponseMock.content_type); }); + + it('should return response directly when content_type property is not present', async () => { + const responseWithoutContentType = { data: 'test', uid: 'test-uid' }; + mockClient.onGet('/content_types/contentTypeUid').reply(200, responseWithoutContentType); + + const result = await contentType.fetch(); + + expect(result).toEqual(responseWithoutContentType); + }); }); diff --git a/test/unit/entries.spec.ts b/test/unit/entries.spec.ts index 020b3ae..aa6f233 100644 --- a/test/unit/entries.spec.ts +++ b/test/unit/entries.spec.ts @@ -38,6 +38,18 @@ describe('Entries class', () => { expect(entry._queryParams['include[]']).toContain(referenceFieldUid); }); + it('should handle multiple reference field UIDs', () => { + entry.includeReference('ref1', 'ref2', ['ref3', 'ref4']); + expect(entry._queryParams['include[]']).toEqual(['ref1', 'ref2', 'ref3', 'ref4']); + }); + + it('should log error when includeReference called with no arguments', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + entry.includeReference(); + expect(consoleErrorSpy).toHaveBeenCalledWith('Argument should be a String or an Array.'); + consoleErrorSpy.mockRestore(); + }); + it('should add "include_fallback" in _queryParams when includeFallback method is called', () => { const returnedValue = entry.includeFallback(); expect(returnedValue).toBeInstanceOf(Entries); @@ -79,6 +91,13 @@ describe('Entries class', () => { expect(returnedValue).toBeInstanceOf(Query); }); + it('should return Query instance with queryObj when query method is called with object', () => { + const queryObj = { title: 'Test' }; + const returnedValue = entry.query(queryObj); + expect(returnedValue).toBeInstanceOf(Query); + expect(returnedValue._parameters).toEqual(queryObj); + }); + it('should add a fieldUid to the _queryParams object', () => { entry.only('fieldUid'); expect(entry._queryParams).toEqual({ 'only[BASE][]': 'fieldUid' }); @@ -109,6 +128,20 @@ describe('Entries class', () => { expect(entry._queryParams).toEqual({ 'except[BASE][]': 'fieldUid2' }); }); + it('should handle except with array of fieldUids', () => { + entry.except(['field1', 'field2', 'field3']); + expect(entry._queryParams['except[BASE][0]']).toBe('field1'); + expect(entry._queryParams['except[BASE][1]']).toBe('field2'); + expect(entry._queryParams['except[BASE][2]']).toBe('field3'); + }); + + it('should handle only with array of fieldUids', () => { + entry.only(['field1', 'field2', 'field3']); + expect(entry._queryParams['only[BASE][0]']).toBe('field1'); + expect(entry._queryParams['only[BASE][1]']).toBe('field2'); + expect(entry._queryParams['only[BASE][2]']).toBe('field3'); + }); + it('should provide proper response when find method is called', async () => { mockClient.onGet(`/content_types/contentTypeUid/entries`).reply(200, entryFindMock); const returnedValue = await entry.find(); @@ -176,6 +209,10 @@ class TestVariants extends Entries { this.variants(['variant1', 'variant2']); return this._variants || ""; } + + getVariants(): string { + return this._variants || ""; + } } describe('Variants test', () => { @@ -191,4 +228,76 @@ describe('Variants test', () => { expect(testVariantObj.setAndGetVariantsHeaders()).toBe('variant1,variant2'); }); + + it('should set variants as string', () => { + const testVariantObj = new TestVariants(client); + testVariantObj.variants('variant1'); + expect(testVariantObj.getVariants()).toBe('variant1'); + }); + + it('should set variants as comma-separated string from array', () => { + const testVariantObj = new TestVariants(client); + testVariantObj.variants(['variant1', 'variant2', 'variant3']); + expect(testVariantObj.getVariants()).toBe('variant1,variant2,variant3'); + }); + + it('should not set variants when empty string is provided', () => { + const testVariantObj = new TestVariants(client); + testVariantObj.variants(''); + expect(testVariantObj.getVariants()).toBe(''); + }); + + it('should not set variants when empty array is provided', () => { + const testVariantObj = new TestVariants(client); + testVariantObj.variants([]); + expect(testVariantObj.getVariants()).toBe(''); + }); +}); + +describe('Find with encode and variants', () => { + let client: AxiosInstance; + let mockClient: MockAdapter; + let entry: Entries; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + entry = new Entries(client, 'contentTypeUid'); + mockClient.reset(); + }); + + it('should call find with encode parameter true', async () => { + mockClient.onGet('/content_types/contentTypeUid/entries').reply(200, entryFindMock); + + entry.query().where('title', QueryOperation.EQUALS, 'Test'); + const result = await entry.find(true); + + expect(result).toEqual(entryFindMock); + }); + + it('should call find with variants header when variants are set', async () => { + mockClient.onGet('/content_types/contentTypeUid/entries').reply((config) => { + expect(config.headers?.['x-cs-variant-uid']).toBe('variant1,variant2'); + return [200, entryFindMock]; + }); + + entry.variants(['variant1', 'variant2']); + await entry.find(); + }); + + it('should handle find with both encode and variants', async () => { + mockClient.onGet('/content_types/contentTypeUid/entries').reply((config) => { + expect(config.headers?.['x-cs-variant-uid']).toBe('test-variant'); + return [200, entryFindMock]; + }); + + entry.variants('test-variant'); + entry.query().where('status', QueryOperation.EQUALS, 'published'); + const result = await entry.find(true); + + expect(result).toEqual(entryFindMock); + }); }) \ No newline at end of file diff --git a/test/unit/entry.spec.ts b/test/unit/entry.spec.ts index 58b9453..47bc5d4 100644 --- a/test/unit/entry.spec.ts +++ b/test/unit/entry.spec.ts @@ -44,6 +44,18 @@ describe('Entry class', () => { expect(entry._queryParams['include[]']).toContain(referenceFieldUid); }); + it('should handle multiple reference field UIDs', () => { + entry.includeReference('ref1', 'ref2', ['ref3', 'ref4']); + expect(entry._queryParams['include[]']).toEqual(['ref1', 'ref2', 'ref3', 'ref4']); + }); + + it('should log error when includeReference called with no arguments', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + entry.includeReference(); + expect(consoleErrorSpy).toHaveBeenCalledWith('Argument should be a String or an Array.'); + consoleErrorSpy.mockRestore(); + }); + it('should add "include_metadata" in _queryParams when includeMetadata method is called', () => { const returnedValue = entry.includeMetadata(); expect(returnedValue).toBeInstanceOf(Entry); @@ -77,6 +89,13 @@ describe('Entry class', () => { expect(entry._queryParams).toEqual({ 'only[BASE][]': 'fieldUid2' }); }); + it('should handle only with array of fieldUids', () => { + entry.only(['field1', 'field2', 'field3']); + expect(entry._queryParams['only[BASE][0]']).toBe('field1'); + expect(entry._queryParams['only[BASE][1]']).toBe('field2'); + expect(entry._queryParams['only[BASE][2]']).toBe('field3'); + }); + it('should add a fieldUid to the _queryParams object', () => { entry.except('fieldUid'); expect(entry._queryParams).toEqual({ 'except[BASE][]': 'fieldUid' }); @@ -92,6 +111,22 @@ describe('Entry class', () => { expect(entry._queryParams).toEqual({ 'except[BASE][]': 'fieldUid2' }); }); + it('should handle except with array of fieldUids', () => { + entry.except(['field1', 'field2', 'field3']); + expect(entry._queryParams['except[BASE][0]']).toBe('field1'); + expect(entry._queryParams['except[BASE][1]']).toBe('field2'); + expect(entry._queryParams['except[BASE][2]']).toBe('field3'); + }); + + it('should add params to _queryParams using addParams', () => { + const params = { key1: 'value1', key2: 123, key3: ['value3'] }; + const returnedValue = entry.addParams(params); + expect(returnedValue).toBeInstanceOf(Entry); + expect(entry._queryParams.key1).toBe('value1'); + expect(entry._queryParams.key2).toBe(123); + expect(entry._queryParams.key3).toEqual(['value3']); + }); + it('should get the API response when fetch method is called', async () => { mockClient.onGet(`/content_types/contentTypeUid/entries/entryUid`).reply(200, entryFetchMock); const returnedValue = await entry.fetch(); @@ -110,6 +145,10 @@ class TestVariants extends Entry { this.variants(['variant1', 'variant2']); // setting the variants headers so it doesnt give empty string return this._variants || ""; } + + getVariants(): string { + return this._variants || ""; + } } describe('Variants test', () => { @@ -125,4 +164,76 @@ describe('Variants test', () => { expect(testVariantObj.setAndGetVariantsHeaders()).toBe('variant1,variant2'); }); + + it('should set variants as string', () => { + const testVariantObj = new TestVariants(client); + testVariantObj.variants('variant1'); + expect(testVariantObj.getVariants()).toBe('variant1'); + }); + + it('should set variants as comma-separated string from array', () => { + const testVariantObj = new TestVariants(client); + testVariantObj.variants(['variant1', 'variant2', 'variant3']); + expect(testVariantObj.getVariants()).toBe('variant1,variant2,variant3'); + }); + + it('should not set variants when empty string is provided', () => { + const testVariantObj = new TestVariants(client); + testVariantObj.variants(''); + expect(testVariantObj.getVariants()).toBe(''); + }); + + it('should not set variants when empty array is provided', () => { + const testVariantObj = new TestVariants(client); + testVariantObj.variants([]); + expect(testVariantObj.getVariants()).toBe(''); + }); +}); + +describe('Fetch with variants', () => { + let client: AxiosInstance; + let mockClient: MockAdapter; + let entry: Entry; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + entry = new Entry(client, 'contentTypeUid', 'entryUid'); + mockClient.reset(); + }); + + it('should call fetch with variants header when variants are set', async () => { + mockClient.onGet('/content_types/contentTypeUid/entries/entryUid').reply((config) => { + expect(config.headers?.['x-cs-variant-uid']).toBe('variant1,variant2'); + return [200, entryFetchMock]; + }); + + entry.variants(['variant1', 'variant2']); + const result = await entry.fetch(); + + expect(result).toEqual(entryFetchMock.entry); + }); + + it('should call fetch without variant header when variants are not set', async () => { + mockClient.onGet('/content_types/contentTypeUid/entries/entryUid').reply((config) => { + expect(config.headers?.['x-cs-variant-uid']).toBeUndefined(); + return [200, entryFetchMock]; + }); + + const result = await entry.fetch(); + + expect(result).toEqual(entryFetchMock.entry); + }); + + it('should return response directly when entry property is not present', async () => { + const responseWithoutEntry = { data: 'test', uid: 'test-uid' }; + mockClient.onGet('/content_types/contentTypeUid/entries/entryUid').reply(200, responseWithoutEntry); + + const result = await entry.fetch(); + + expect(result).toEqual(responseWithoutEntry); + }); }) diff --git a/test/unit/pagination.spec.ts b/test/unit/pagination.spec.ts index 92a0d64..ff999a5 100644 --- a/test/unit/pagination.spec.ts +++ b/test/unit/pagination.spec.ts @@ -25,4 +25,27 @@ describe('Pagination class', () => { pageObject.previous(); expect(pageObject._queryParams).toEqual({ skip: 0, limit: 10 }); }); + + it('should initialize pagination when next is called without prior paginate', () => { + const pageObject = new Pagination(); + pageObject.next(); + expect(pageObject._queryParams).toEqual({ skip: 10, limit: 10 }); + }); + + it('should initialize pagination when previous is called without prior paginate', () => { + const pageObject = new Pagination(); + pageObject.previous(); + expect(pageObject._queryParams).toEqual({ skip: 0, limit: 10 }); + }); + + it('should set skip to 0 when previous would result in negative skip', () => { + const pageObject = new Pagination().paginate({ skip: 5, limit: 10 }); + pageObject.previous(); + expect(pageObject._queryParams.skip).toEqual(0); + }); + + it('should use default values when paginate is called without arguments', () => { + const pageObject = new Pagination().paginate(); + expect(pageObject._queryParams).toEqual({ skip: 0, limit: 10 }); + }); }); diff --git a/test/unit/query.spec.ts b/test/unit/query.spec.ts index 301d124..d244a27 100644 --- a/test/unit/query.spec.ts +++ b/test/unit/query.spec.ts @@ -55,7 +55,7 @@ describe('Query class', () => { }); it('should add a where-in filter to the query parameters', () => { - const subQuery = getQueryObject(client, 'your-referenced-content-type-uid'); + const subQuery = getQueryObject(client, 'referenced-content-type-uid'); subQuery.where('your-field-uid', QueryOperation.EQUALS, 'your-field-value'); query.whereIn('your-reference-field-uid', subQuery); // eslint-disable-next-line prettier/prettier, @typescript-eslint/naming-convention @@ -63,7 +63,7 @@ describe('Query class', () => { }); it('should add a where-not-in filter to the query parameters', () => { - const subQuery = getQueryObject(client, 'your-referenced-content-type-uid'); + const subQuery = getQueryObject(client, 'referenced-content-type-uid'); subQuery.where('your-field-uid', QueryOperation.EQUALS, 'your-field-value'); query.whereNotIn('your-reference-field-uid', subQuery); // eslint-disable-next-line prettier/prettier, @typescript-eslint/naming-convention @@ -84,10 +84,15 @@ describe('Query class', () => { }); it('should result in error when regex method is called with invalid regex', async () => { - const regexQuery = getQueryObject(client, 'your-referenced-content-type-uid'); + const regexQuery = getQueryObject(client, 'referenced-content-type-uid'); expect(() => regexQuery.regex("fieldUid", "[a-z")).toThrow("Invalid regexPattern: Must be a valid regular expression"); }); + it('should throw error when regex method is called with invalid characters', async () => { + const regexQuery = getQueryObject(client, 'referenced-content-type-uid'); + expect(() => regexQuery.regex("fieldUid", "test