From 2d0a84c62ea06ebc01f3ad6b864d8f60d568df9d Mon Sep 17 00:00:00 2001 From: Simon Hofer Date: Fri, 27 Mar 2026 16:26:13 +0100 Subject: [PATCH 1/2] feat: allow raw queries for where filter feat: allow raw queries for where filter Adapted types and functions Added tests Updated dependencies safely Adapted github workflow --- .github/workflows/main.yml | 40 ++++-- README.md | 10 +- package-lock.json | 133 +++++++++--------- package.json | 4 +- .../static/queriesWithQueryLanguage.ts.txt | 16 ++- test/queries/where.spec.ts | 125 ++++++++++++++++ tsconfig.node.json | 2 +- vitest.config.ts | 2 +- 8 files changed, 246 insertions(+), 86 deletions(-) create mode 100644 test/queries/where.spec.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a648d9f..53d16eb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,9 +34,13 @@ jobs: name: build path: dist - test: - name: Test + generate: + name: Generate SDK runs-on: ubuntu-latest + needs: build + strategy: + matrix: + target: [node, node.rx, browser, browser.rx] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -49,16 +53,23 @@ jobs: - name: Install dependencies run: npm ci - - name: Run tests - run: npm run test + - name: Download dist files + uses: actions/download-artifact@v4 + with: + name: build + path: dist - generate: - name: Generate SDK + - name: Generate SDK + run: | + chmod +x ./bin/cli.js + ./bin/cli.js test/openapi.json --target ${{ matrix.target }} + ./bin/cli.js test/openapi_v2.json --target ${{ matrix.target }} + ./bin/cli.js test/openapi_v3.json --target ${{ matrix.target }} + + test: + name: Test runs-on: ubuntu-latest needs: build - strategy: - matrix: - target: [node, node.rx, browser, browser.rx] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -77,11 +88,16 @@ jobs: name: build path: dist - - name: Generate SDK + - name: Generate SDK for tests run: | chmod +x ./bin/cli.js - ./bin/cli.js test/openapi.json --target ${{ matrix.target }} - ./bin/cli.js test/openapi_v2.json --target ${{ matrix.target }} + ./bin/cli.js test/openapi_v3.json --target browser --use-query-language + + - name: Run type-check + run: npm run type-check + + - name: Run tests + run: npm run test publish: name: Publish release version diff --git a/README.md b/README.md index ac990d6..352bbc7 100644 --- a/README.md +++ b/README.md @@ -349,7 +349,7 @@ wServices['article'].some({ where: { AND: [ { - OR: [{ name: { LIKE: '%test%', lower: true } }, { articleNumber: { LIKE: '%345%' } }] + OR: [{ name: { LIKE: '%test%', LOWER: true } }, { articleNumber: { LIKE: '%345%' } }] }, { batchNumberRequired: { EQ: true } } ] @@ -357,6 +357,14 @@ wServices['article'].some({ }); ``` +Some API operations like the arithmetic ones are not supported as type, due to their complexity. In this case a raw filter expression can be passed as string to `where` + +```ts +wServices['article'].some({ + where: '(articleLength * articleWidth * articleHeight) <= 3000' +}); +``` + "where" parameters are ANDed with other filter parameters. It is also possible to set an empty list within an IN-query: diff --git a/package-lock.json b/package-lock.json index 912b6e4..3e4c9b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -688,9 +688,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1586,9 +1586,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1978,9 +1978,9 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -2917,9 +2917,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3088,9 +3088,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.5.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz", - "integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==", + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz", + "integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==", "dev": true, "funding": [ { @@ -3101,8 +3101,8 @@ "license": "MIT", "dependencies": { "fast-xml-builder": "^1.1.4", - "path-expression-matcher": "^1.1.3", - "strnum": "^2.1.2" + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.2" }, "bin": { "fxparser": "src/cli/cli.js" @@ -3369,13 +3369,14 @@ "dev": true }, "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, @@ -4319,9 +4320,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", - "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", + "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", "dev": true, "funding": [ { @@ -4364,9 +4365,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -4984,9 +4985,9 @@ } }, "node_modules/strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", + "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", "dev": true, "funding": [ { @@ -5510,9 +5511,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", "bin": { @@ -5887,9 +5888,9 @@ "dev": true }, "brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "requires": { "balanced-match": "^4.0.2" @@ -6402,9 +6403,9 @@ "dev": true }, "brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "requires": { "balanced-match": "^4.0.2" @@ -6659,9 +6660,9 @@ "dev": true }, "brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -7326,9 +7327,9 @@ "dev": true }, "brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "requires": { "balanced-match": "^4.0.2" @@ -7457,14 +7458,14 @@ } }, "fast-xml-parser": { - "version": "5.5.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz", - "integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==", + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz", + "integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==", "dev": true, "requires": { "fast-xml-builder": "^1.1.4", - "path-expression-matcher": "^1.1.3", - "strnum": "^2.1.2" + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.2" } }, "fdir": { @@ -7663,13 +7664,13 @@ "dev": true }, "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "requires": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" @@ -8372,9 +8373,9 @@ "dev": true }, "path-expression-matcher": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", - "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", + "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", "dev": true }, "path-key": { @@ -8401,9 +8402,9 @@ "dev": true }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, "pify": { "version": "2.3.0", @@ -8856,9 +8857,9 @@ } }, "strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", + "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", "dev": true }, "supports-color": { @@ -9146,9 +9147,9 @@ "dev": true }, "yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true }, "yargs": { diff --git a/package.json b/package.json index 06bad44..54f4d35 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "cli:browser:v2": "./bin/cli.js test/openapi_v2.json --target browser", "cli:browser.rx:v2": "./bin/cli.js test/openapi_v2.json --target browser.rx", "cli:browser:v3": "./bin/cli.js test/openapi_v3.json --target browser", + "cli:browser:v3:ql": "./bin/cli.js test/openapi_v3.json --target browser --use-query-language", "cli:browser:v3:cache": "./bin/cli.js test/openapi_v3.json --target browser --cache", "cli:browser.rx:v3": "./bin/cli.js test/openapi_v3.json --target browser.rx", "cli:browser.rx:v3:cache": "./bin/cli.js test/openapi_v3.json --target browser.rx --cache", @@ -55,9 +56,10 @@ "prettier:fix": "prettier . --write", "test": "vitest run", "test:coverage": "vitest run --coverage", + "type-check": "tsc -p tsconfig.node.json --noEmit --skipLibCheck", "lint": "eslint ./src --cache", "lint:fix": "npm run lint -- --fix", - "ci": "npm run prettier && npm run lint && npm run test && npm run build && npm run cli:browser:v1 && npm run cli:browser:v2 && npm run cli:browser:v3", + "ci": "npm run prettier && npm run lint && npm run build && npm run cli:browser:v1 && npm run cli:browser:v2 && npm run cli:browser:v3 && npm run cli:browser:v3:ql && npm run type-check && npm run test", "release": "standard-version" }, "devDependencies": { diff --git a/src/generator/01-base/static/queriesWithQueryLanguage.ts.txt b/src/generator/01-base/static/queriesWithQueryLanguage.ts.txt index 6be4287..6156e4e 100644 --- a/src/generator/01-base/static/queriesWithQueryLanguage.ts.txt +++ b/src/generator/01-base/static/queriesWithQueryLanguage.ts.txt @@ -69,14 +69,14 @@ export type QueryFilter = SingleFilterExpr & { }; export type CountQuery = { - where?: QueryFilter; + where?: QueryFilter | string; }; export type SomeQuery = { serializeNulls?: boolean; include?: QuerySelect; properties?: P; - where?: QueryFilter; + where?: QueryFilter | string; select?: QuerySelect; sort?: Sort[]; pagination?: Pagination; @@ -210,9 +210,17 @@ const flattenWhere = ( }; const assembleFilterParam = ( - obj: QueryFilter = {} + filter?: QueryFilter | string ): Record => { - const flattedFilter = flattenWhere(obj, []); + if(!filter) { + return {}; + } + + if (typeof filter === 'string') { + return { filter }; + } + + const flattedFilter = flattenWhere(filter, []); return flattedFilter.length ? { filter: flattedFilter.join(' and ') } : {}; }; diff --git a/test/queries/where.spec.ts b/test/queries/where.spec.ts new file mode 100644 index 0000000..ec8dc3d --- /dev/null +++ b/test/queries/where.spec.ts @@ -0,0 +1,125 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { articleService, setGlobalConfig } from '@sdk/dist'; + +describe('where filter', () => { + let capturedRequest: Request; + + beforeAll(() => { + setGlobalConfig({ + host: 'test.example.com', + secure: true, + key: 'test-key', + interceptors: { + request: (request) => { + capturedRequest = request; + return new Response( + JSON.stringify({ + result: [], + referencedEntities: {}, + additionalProperties: {} + }), + { + status: 200, + headers: { 'content-type': 'application/json' } + } + ); + } + } + }); + }); + + describe('some query', () => { + it('should assemble a typed where object into a filter query param', async () => { + const service = articleService(); + + await service.some({ + where: { + articleNumber: { EQ: 'ART-001' } + } + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('filter')).toBe('articleNumber = "ART-001"'); + }); + + it('should pass a raw string where directly as the filter query param', async () => { + const service = articleService(); + + await service.some({ + where: 'articleNumber = "ART-001"' + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('filter')).toBe('articleNumber = "ART-001"'); + }); + + it('should pass sort as a comma-separated query param', async () => { + const service = articleService(); + + await service.some({ + sort: [{ articleNumber: 'asc' }, { name: 'desc' }] + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('sort')).toBe('articleNumber,-name'); + }); + + it('should pass pagination as page and pageSize query params', async () => { + const service = articleService(); + + await service.some({ + pagination: { page: 3, pageSize: 25 } + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('page')).toBe('3'); + expect(url.searchParams.get('pageSize')).toBe('25'); + }); + + it('should combine where, sort and pagination into a single request', async () => { + const service = articleService(); + + await service.some({ + where: 'articleNumber = "ART-001"', + sort: [{ articleNumber: 'asc' }], + pagination: { page: 1, pageSize: 10 } + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('filter')).toBe('articleNumber = "ART-001"'); + expect(url.searchParams.get('sort')).toBe('articleNumber'); + expect(url.searchParams.get('page')).toBe('1'); + expect(url.searchParams.get('pageSize')).toBe('10'); + }); + }); + + describe('count query', () => { + it('should assemble a typed where object into a filter query param', async () => { + const service = articleService(); + + await service.count({ + where: { + articleNumber: { EQ: 'ART-001' } + } + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('filter')).toBe('articleNumber = "ART-001"'); + }); + + it('should pass a raw string where directly as the filter query param', async () => { + const service = articleService(); + + await service.count({ + where: 'articleNumber = "ART-001"' + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('filter')).toBe('articleNumber = "ART-001"'); + }); + }); + + afterAll(() => { + setGlobalConfig(undefined); + }); +}); diff --git a/tsconfig.node.json b/tsconfig.node.json index c01a844..ffe350a 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -23,5 +23,5 @@ "@sdk/*": ["sdk/*"] } }, - "include": ["src/**/*.ts", "rollup.config.ts", "vitest.config.ts"] + "include": ["src/**/*.ts", "test/**/*.spec.ts", "rollup.config.ts", "vitest.config.ts"] } diff --git a/vitest.config.ts b/vitest.config.ts index 04e585b..0783fef 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,6 +5,6 @@ export default defineConfig({ plugins: [tsconfigPaths()], test: { globals: true, - include: ['src/**/*.spec.ts'] + include: ['src/**/*.spec.ts', 'test/**/*.spec.ts'] } }); From 6851a605df1866dcaf2dadb0e03ad1f0cec48c80 Mon Sep 17 00:00:00 2001 From: Simon Hofer Date: Mon, 30 Mar 2026 11:39:23 +0200 Subject: [PATCH 2/2] feat: allow raw queries for where filter Add support for tests with and without query language --- .github/workflows/main.yml | 24 ++++-- eslint.config.js | 2 +- package.json | 12 ++- rollup.config.ts | 2 +- test/former-queries/filter.spec.ts | 105 +++++++++++++++++++++++++ tsconfig.json | 6 ++ tsconfig.node.json | 2 +- tsconfig.rollup.json | 4 + tsconfig.typecheck-former-queries.json | 4 + tsconfig.typecheck-query-language.json | 4 + 10 files changed, 153 insertions(+), 12 deletions(-) create mode 100644 test/former-queries/filter.spec.ts create mode 100644 tsconfig.rollup.json create mode 100644 tsconfig.typecheck-former-queries.json create mode 100644 tsconfig.typecheck-query-language.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 53d16eb..c087ed4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -88,16 +88,30 @@ jobs: name: build path: dist - - name: Generate SDK for tests + - name: Run generator tests + run: npm run test:generator + + - name: Generate SDK for tests with former query syntax + run: | + chmod +x ./bin/cli.js + ./bin/cli.js test/openapi_v3.json --target browser + + - name: Run type-check (former-queries) + run: npm run type-check:former-queries + + - name: Run tests (former-queries) + run: npm run test:former-queries + + - name: Generate SDK for tests with query language run: | chmod +x ./bin/cli.js ./bin/cli.js test/openapi_v3.json --target browser --use-query-language - - name: Run type-check - run: npm run type-check + - name: Run type-check (query-language) + run: npm run type-check:query-language - - name: Run tests - run: npm run test + - name: Run tests (query-language) + run: npm run test:query-language publish: name: Publish release version diff --git a/eslint.config.js b/eslint.config.js index 1788f5f..ce2dc6a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,7 +3,7 @@ import tsEslint from 'typescript-eslint'; export default tsEslint.config( { name: 'app/files-to-ignore', - ignores: ['**/*.d.ts', '**/.*', '**/dist', '**/sdk', '**/node_modules'] + ignores: ['**/*.d.ts', '**/.*', '**/dist', '**/sdk', '**/node_modules', 'test/**'] }, { extends: [...tsEslint.configs.recommendedTypeChecked], diff --git a/package.json b/package.json index 54f4d35..ca8fa57 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ } }, "scripts": { - "build": "rollup --config rollup.config.ts --configPlugin typescript={tsconfig:\\'tsconfig.node.json\\'} --configImportAttributesKey with", + "build": "rollup --config rollup.config.ts --configPlugin typescript={tsconfig:\\'tsconfig.rollup.json\\'} --configImportAttributesKey with", "build:watch": "npm run build -- --watch", "cli:browser:v1": "./bin/cli.js test/openapi.json --target browser", "cli:browser.rx:v1": "./bin/cli.js test/openapi.json --target browser.rx", @@ -54,12 +54,16 @@ "cli:node.rx:cache": "./bin/cli.js test/openapi.json --target node.rx --cache", "prettier": "prettier . --check", "prettier:fix": "prettier . --write", - "test": "vitest run", + "test:generator": "vitest run src/", + "test:former-queries": "vitest run test/former-queries/", + "test:query-language": "vitest run test/queries/", "test:coverage": "vitest run --coverage", - "type-check": "tsc -p tsconfig.node.json --noEmit --skipLibCheck", + "type-check:former-queries": "tsc -p tsconfig.typecheck-former-queries.json --noEmit --skipLibCheck", + "type-check:query-language": "tsc -p tsconfig.typecheck-query-language.json --noEmit --skipLibCheck", "lint": "eslint ./src --cache", "lint:fix": "npm run lint -- --fix", - "ci": "npm run prettier && npm run lint && npm run build && npm run cli:browser:v1 && npm run cli:browser:v2 && npm run cli:browser:v3 && npm run cli:browser:v3:ql && npm run type-check && npm run test", + "ci:test": "npm run test:generator && npm run cli:browser:v3 && npm run type-check:former-queries && npm run test:former-queries && npm run cli:browser:v3:ql && npm run type-check:query-language && npm run test:query-language", + "ci": "npm run prettier && npm run lint && npm run build && npm run cli:browser:v1 && npm run cli:browser:v2 && npm run cli:browser:v3 && npm run ci:test", "release": "standard-version" }, "devDependencies": { diff --git a/rollup.config.ts b/rollup.config.ts index c169243..460babe 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -20,7 +20,7 @@ const txt = (): Plugin => { export default defineConfig({ input: 'src/index.ts', - plugins: [txt(), ts({ tsconfig: 'tsconfig.node.json' })], + plugins: [txt(), ts({ tsconfig: 'tsconfig.rollup.json' })], external: [ ...Object.keys(pkg.dependencies), ...Object.keys(pkg.peerDependencies), diff --git a/test/former-queries/filter.spec.ts b/test/former-queries/filter.spec.ts new file mode 100644 index 0000000..ba095b9 --- /dev/null +++ b/test/former-queries/filter.spec.ts @@ -0,0 +1,105 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { articleService, setGlobalConfig } from '@sdk/dist'; + +describe('filter', () => { + let capturedRequest: Request; + + beforeAll(() => { + setGlobalConfig({ + host: 'test.example.com', + secure: true, + key: 'test-key', + interceptors: { + request: (request) => { + capturedRequest = request; + return new Response( + JSON.stringify({ + result: [], + referencedEntities: {}, + additionalProperties: {} + }), + { + status: 200, + headers: { 'content-type': 'application/json' } + } + ); + } + } + }); + }); + + describe('some query', () => { + it('should assemble a typed filter object into individual query params', async () => { + const service = articleService(); + + await service.some({ + filter: { + articleNumber: { EQ: 'ART-001' } + } + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('articleNumber-eq')).toBe('ART-001'); + }); + + it('should pass sort as a comma-separated query param', async () => { + const service = articleService(); + + await service.some({ + sort: [{ articleNumber: 'asc' }, { name: 'desc' }] + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('sort')).toBe('articleNumber,-name'); + }); + + it('should pass pagination as page and pageSize query params', async () => { + const service = articleService(); + + await service.some({ + pagination: { page: 3, pageSize: 25 } + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('page')).toBe('3'); + expect(url.searchParams.get('pageSize')).toBe('25'); + }); + + it('should combine filter, sort and pagination into a single request', async () => { + const service = articleService(); + + await service.some({ + filter: { + articleNumber: { EQ: 'ART-001' } + }, + sort: [{ articleNumber: 'asc' }], + pagination: { page: 1, pageSize: 10 } + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('articleNumber-eq')).toBe('ART-001'); + expect(url.searchParams.get('sort')).toBe('articleNumber'); + expect(url.searchParams.get('page')).toBe('1'); + expect(url.searchParams.get('pageSize')).toBe('10'); + }); + }); + + describe('count query', () => { + it('should assemble a typed filter object into individual query params', async () => { + const service = articleService(); + + await service.count({ + filter: { + articleNumber: { EQ: 'ART-001' } + } + }); + + const url = new URL(capturedRequest.url); + expect(url.searchParams.get('articleNumber-eq')).toBe('ART-001'); + }); + }); + + afterAll(() => { + setGlobalConfig(undefined); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 06495b7..e938217 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,12 @@ }, { "path": "./tsconfig.sdk.json" + }, + { + "path": "./tsconfig.typecheck-former-queries.json" + }, + { + "path": "./tsconfig.typecheck-query-language.json" } ] } diff --git a/tsconfig.node.json b/tsconfig.node.json index ffe350a..c01a844 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -23,5 +23,5 @@ "@sdk/*": ["sdk/*"] } }, - "include": ["src/**/*.ts", "test/**/*.spec.ts", "rollup.config.ts", "vitest.config.ts"] + "include": ["src/**/*.ts", "rollup.config.ts", "vitest.config.ts"] } diff --git a/tsconfig.rollup.json b/tsconfig.rollup.json new file mode 100644 index 0000000..72cb0e6 --- /dev/null +++ b/tsconfig.rollup.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.node.json", + "include": ["src/**/*.ts", "rollup.config.ts"] +} diff --git a/tsconfig.typecheck-former-queries.json b/tsconfig.typecheck-former-queries.json new file mode 100644 index 0000000..c535492 --- /dev/null +++ b/tsconfig.typecheck-former-queries.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.node.json", + "include": ["test/former-queries/**/*.spec.ts"] +} diff --git a/tsconfig.typecheck-query-language.json b/tsconfig.typecheck-query-language.json new file mode 100644 index 0000000..9beacec --- /dev/null +++ b/tsconfig.typecheck-query-language.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.node.json", + "include": ["test/queries/**/*.spec.ts"] +}