diff --git a/package-lock.json b/package-lock.json index 19f90f3..e39b641 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ }, "devDependencies": { "@biomejs/biome": "^1.8.3", - "@microsoft/api-extractor": "^7.42.3", + "@microsoft/api-extractor": "^7.47.7", "@nitrogql/esbuild-register": "^1.6.0-beta.0", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^11.1.0", @@ -816,28 +816,6 @@ "node": ">=v14" } }, - "node_modules/@commitlint/config-validator/node_modules/ajv": { - "version": "8.12.0", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@commitlint/execute-rule": { "version": "17.4.0", "dev": true, @@ -1976,38 +1954,38 @@ "license": "BSD-3-Clause" }, "node_modules/@microsoft/api-extractor": { - "version": "7.42.3", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.42.3.tgz", - "integrity": "sha512-JNLJFpGHz6ekjS6bvYXxUBeRGnSHeCMFNvRbCQ+7XXB/ZFrgLSMPwWtEq40AiWAy+oyG5a4RSNwdJTp0B2USvQ==", - "dev": true, - "dependencies": { - "@microsoft/api-extractor-model": "7.28.13", - "@microsoft/tsdoc": "0.14.2", - "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "4.0.2", - "@rushstack/rig-package": "0.5.2", - "@rushstack/terminal": "0.10.0", - "@rushstack/ts-command-line": "4.19.1", + "version": "7.47.7", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.47.7.tgz", + "integrity": "sha512-fNiD3G55ZJGhPOBPMKD/enozj8yxJSYyVJWxRWdcUtw842rvthDHJgUWq9gXQTensFlMHv2wGuCjjivPv53j0A==", + "dev": true, + "dependencies": { + "@microsoft/api-extractor-model": "7.29.6", + "@microsoft/tsdoc": "~0.15.0", + "@microsoft/tsdoc-config": "~0.17.0", + "@rushstack/node-core-library": "5.7.0", + "@rushstack/rig-package": "0.5.3", + "@rushstack/terminal": "0.14.0", + "@rushstack/ts-command-line": "4.22.6", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", - "typescript": "5.3.3" + "typescript": "5.4.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.28.13", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.13.tgz", - "integrity": "sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==", + "version": "7.29.6", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.29.6.tgz", + "integrity": "sha512-gC0KGtrZvxzf/Rt9oMYD2dHvtN/1KPEYsrQPyMKhLHnlVuO/f4AFN3E4toqZzD2pt4LhkKoYmL2H9tX3yCOyRw==", "dev": true, "dependencies": { - "@microsoft/tsdoc": "0.14.2", - "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "4.0.2" + "@microsoft/tsdoc": "~0.15.0", + "@microsoft/tsdoc-config": "~0.17.0", + "@rushstack/node-core-library": "5.7.0" } }, "node_modules/@microsoft/api-extractor/node_modules/minimatch": { @@ -2022,48 +2000,22 @@ "node": "*" } }, - "node_modules/@microsoft/api-extractor/node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/@microsoft/tsdoc": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", - "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz", + "integrity": "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==", "dev": true }, "node_modules/@microsoft/tsdoc-config": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz", - "integrity": "sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.0.tgz", + "integrity": "sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==", "dev": true, "dependencies": { - "@microsoft/tsdoc": "0.14.2", - "ajv": "~6.12.6", + "@microsoft/tsdoc": "0.15.0", + "ajv": "~8.12.0", "jju": "~1.4.0", - "resolve": "~1.19.0" - } - }, - "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", - "dev": true, - "dependencies": { - "is-core-module": "^2.1.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "resolve": "~1.22.2" } }, "node_modules/@nitrogql/esbuild-register": { @@ -2536,17 +2488,19 @@ ] }, "node_modules/@rushstack/node-core-library": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz", - "integrity": "sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.7.0.tgz", + "integrity": "sha512-Ff9Cz/YlWu9ce4dmqNBZpA45AEya04XaBFIjV7xTVeEf+y/kTjEasmozqFELXlNG4ROdevss75JrrZ5WgufDkQ==", "dev": true, "dependencies": { + "ajv": "~8.13.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", "fs-extra": "~7.0.1", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", - "semver": "~7.5.4", - "z-schema": "~5.0.2" + "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" @@ -2557,6 +2511,22 @@ } } }, + "node_modules/@rushstack/node-core-library/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -2590,9 +2560,9 @@ } }, "node_modules/@rushstack/rig-package": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.2.tgz", - "integrity": "sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.3.tgz", + "integrity": "sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==", "dev": true, "dependencies": { "resolve": "~1.22.1", @@ -2600,12 +2570,12 @@ } }, "node_modules/@rushstack/terminal": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.0.tgz", - "integrity": "sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.14.0.tgz", + "integrity": "sha512-juTKMAMpTIJKudeFkG5slD8Z/LHwNwGZLtU441l/u82XdTBfsP+LbGKJLCNwP5se+DMCT55GB8x9p6+C4UL7jw==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "5.7.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -2642,12 +2612,12 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.1.tgz", - "integrity": "sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg==", + "version": "4.22.6", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.22.6.tgz", + "integrity": "sha512-QSRqHT/IfoC5nk9zn6+fgyqOPXHME0BfchII9EUPR19pocsNp/xSbeBCbD3PIR2Lg+Q5qk7OFqk1VhWPMdKHJg==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.10.0", + "@rushstack/terminal": "0.14.0", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -3636,14 +3606,14 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -3651,6 +3621,37 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "dev": true, @@ -7184,9 +7185,9 @@ "license": "MIT" }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/json-stringify-safe": { @@ -7386,12 +7387,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -7406,12 +7401,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true - }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -11402,7 +11391,6 @@ "version": "2.0.2", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -13244,15 +13232,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/validator": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", - "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vite": { "version": "5.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", @@ -14044,36 +14023,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/z-schema": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", - "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", - "dev": true, - "dependencies": { - "lodash.get": "^4.4.2", - "lodash.isequal": "^4.5.0", - "validator": "^13.7.0" - }, - "bin": { - "z-schema": "bin/z-schema" - }, - "engines": { - "node": ">=8.0.0" - }, - "optionalDependencies": { - "commander": "^9.4.1" - } - }, - "node_modules/z-schema/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, - "optional": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, "node_modules/zip-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", diff --git a/package.json b/package.json index a2d83c2..2250bc2 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@biomejs/biome": "^1.8.3", - "@microsoft/api-extractor": "^7.42.3", + "@microsoft/api-extractor": "^7.47.7", "@nitrogql/esbuild-register": "^1.6.0-beta.0", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^11.1.0", diff --git a/src/__tests__/clone.test.ts b/src/__tests__/clone.test.ts index 5ea5e48..fda3426 100644 --- a/src/__tests__/clone.test.ts +++ b/src/__tests__/clone.test.ts @@ -102,7 +102,7 @@ describe("clone", () => { assert.equal( query.sql, - 'select "q0"."customer___user_id" as "customer___user_id", "q0"."employee___user_id" as "employee___user_id", "q0"."invoice___invoice_id" as "invoice___invoice_id", "q0"."customer___count" as "customer___count", "q1"."employee___count" as "employee___count" from (select "s0"."customer___user_id" as "customer___user_id", "s0"."employee___user_id" as "employee___user_id", "s0"."invoice___invoice_id" as "invoice___invoice_id", COUNT(DISTINCT "count___metric_ref_0") as "customer___count" from (select distinct "User"."UserId" as "customer___user_id", "User"."UserId" as "employee___user_id", "Invoice"."InvoiceId" as "invoice___invoice_id", "User"."UserId" as "count___metric_ref_0" from "User" left join "Invoice" on "User"."UserId" = "Invoice"."CustomerId" right join "User" on "User"."UserId" = "Invoice"."EmployeeId") as "s0" group by "s0"."customer___user_id", "s0"."employee___user_id", "s0"."invoice___invoice_id") as "q0" inner join (select "s1"."customer___user_id" as "customer___user_id", "s1"."employee___user_id" as "employee___user_id", "s1"."invoice___invoice_id" as "invoice___invoice_id", COUNT(DISTINCT "count___metric_ref_0") as "employee___count" from (select distinct "User"."UserId" as "customer___user_id", "User"."UserId" as "employee___user_id", "Invoice"."InvoiceId" as "invoice___invoice_id", "User"."UserId" as "count___metric_ref_0" from "User" left join "Invoice" on "User"."UserId" = "Invoice"."EmployeeId" right join "User" on "User"."UserId" = "Invoice"."CustomerId") as "s1" group by "s1"."customer___user_id", "s1"."employee___user_id", "s1"."invoice___invoice_id") as "q1" on "q0"."customer___user_id" = "q1"."customer___user_id" and "q0"."employee___user_id" = "q1"."employee___user_id" and "q0"."invoice___invoice_id" = "q1"."invoice___invoice_id" order by "customer___count" desc', + 'select "q0"."customer___user_id" as "customer___user_id", "q0"."employee___user_id" as "employee___user_id", "q0"."invoice___invoice_id" as "invoice___invoice_id", "q0"."customer___count" as "customer___count", "q1"."employee___count" as "employee___count" from (select "s0"."customer___user_id" as "customer___user_id", "s0"."employee___user_id" as "employee___user_id", "s0"."invoice___invoice_id" as "invoice___invoice_id", COUNT(DISTINCT "customer___count___mr_0") as "customer___count" from (select distinct "User"."UserId" as "customer___user_id", "User"."UserId" as "employee___user_id", "Invoice"."InvoiceId" as "invoice___invoice_id", "User"."UserId" as "customer___count___mr_0" from "User" left join "Invoice" on "User"."UserId" = "Invoice"."CustomerId" right join "User" on "User"."UserId" = "Invoice"."EmployeeId") as "s0" group by "s0"."customer___user_id", "s0"."employee___user_id", "s0"."invoice___invoice_id") as "q0" inner join (select "s1"."customer___user_id" as "customer___user_id", "s1"."employee___user_id" as "employee___user_id", "s1"."invoice___invoice_id" as "invoice___invoice_id", COUNT(DISTINCT "employee___count___mr_0") as "employee___count" from (select distinct "User"."UserId" as "customer___user_id", "User"."UserId" as "employee___user_id", "Invoice"."InvoiceId" as "invoice___invoice_id", "User"."UserId" as "employee___count___mr_0" from "User" left join "Invoice" on "User"."UserId" = "Invoice"."EmployeeId" right join "User" on "User"."UserId" = "Invoice"."CustomerId") as "s1" group by "s1"."customer___user_id", "s1"."employee___user_id", "s1"."invoice___invoice_id") as "q1" on "q0"."customer___user_id" = "q1"."customer___user_id" and "q0"."employee___user_id" = "q1"."employee___user_id" and "q0"."invoice___invoice_id" = "q1"."invoice___invoice_id" order by "customer___count" desc', ); }); }); diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 89361ad..8900fcc 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -1653,38 +1653,46 @@ describe("semantic layer", async () => { customers___customer_id: { memberType: "dimension", path: "customers.customer_id", + alias: "customers___customer_id", type: "number", description: "The unique identifier of the customer", format: undefined, isPrimaryKey: true, isGranularity: false, + isPrivate: false, }, invoices___invoice_id: { memberType: "dimension", path: "invoices.invoice_id", + alias: "invoices___invoice_id", type: "number", description: "The unique identifier of the invoice", format: undefined, isPrimaryKey: true, isGranularity: false, + isPrivate: false, }, invoices___customer_id: { memberType: "dimension", path: "invoices.customer_id", + alias: "invoices___customer_id", type: "number", description: "The unique identifier of the invoice customer", format: undefined, isPrimaryKey: false, isGranularity: false, + isPrivate: false, }, invoices___total: { memberType: "metric", path: "invoices.total", + alias: "invoices___total", format: "percentage", type: "string", description: undefined, isPrimaryKey: false, isGranularity: false, + isPrivate: false, }, }); }); @@ -3099,4 +3107,105 @@ describe("semantic layer", async () => { ]); }); }); + + describe("repository with private members", () => { + const customersModel = semanticLayer + .model() + .withName("customers") + .fromTable("Customer") + .withDimension("customer_id", { + type: "number", + primaryKey: true, + private: true, + sql: ({ model, sql }) => sql`${model.column("CustomerId")}`, + }) + .withDimension("public_customer_id", { + type: "number", + primaryKey: true, + sql: ({ model, sql }) => sql`${model.dimension("customer_id")}`, + }) + .withMetric("count", { + type: "string", + private: true, + sql: ({ model, sql }) => + sql`COUNT(DISTINCT ${model.column("CustomerId")})`, + }) + .withMetric("public_count", { + type: "string", + sql: ({ model, sql }) => sql`${model.metric("count").aggregated()}`, + }); + + const repository = semanticLayer + .repository() + .withModel(customersModel) + .withCalculatedDimension("public_customer_id", { + type: "string", + sql: ({ sql, models }) => + sql`${models.customers.dimension( + "customer_id", + )} || ' - ' || ${models.customers.dimension("public_customer_id")}`, + }) + .withCalculatedMetric("public_count", { + type: "string", + sql: ({ sql, models }) => + sql`${models.customers.metric("count").aggregated()} || ' - ' || ${models.customers.metric("public_count").aggregated()}`, + }); + + const queryBuilder = repository.build("postgresql"); + + it("can have private members", async () => { + const validationResult = queryBuilder.querySchema.safeParse({ + members: ["customers.customer_id", "customers.count"], + }); + + // @ts-expect-error - We expect the validation to fail + assert.deepEqual(validationResult.error.issues, [ + { code: "custom", message: "Member not found", path: ["members", 0] }, + { code: "custom", message: "Member not found", path: ["members", 1] }, + ]); + + const query = queryBuilder.buildQuery({ + members: [ + "customers.public_customer_id", + "public_customer_id", + "customers.public_count", + "public_count", + ], + limit: 1, + order: [{ member: "customers.public_customer_id", direction: "asc" }], + }); + + const result = await client.query>( + query.sql, + query.bindings, + ); + + assert.deepEqual(result.rows, [ + { + customers___public_count: "1", + customers___public_customer_id: 1, + public_count: "1 - 1", + public_customer_id: "1 - 1", + }, + ]); + + const introspectionResult = queryBuilder.introspect({ + members: ["customers.customer_id"], + }); + + assert.deepEqual(introspectionResult, { + customers___customer_id: { + memberType: "dimension", + path: "customers.customer_id", + alias: "customers___customer_id", + format: undefined, + type: "number", + description: undefined, + isPrimaryKey: true, + isGranularity: false, + isPrivate: true, + }, + }); + }); + }); }); diff --git a/src/__tests__/types.test-d.ts b/src/__tests__/types.test-d.ts index 1c5ea3f..35d1a7f 100644 --- a/src/__tests__/types.test-d.ts +++ b/src/__tests__/types.test-d.ts @@ -21,6 +21,26 @@ const customersModel = semanticLayer type: "number", sql: ({ model, sql }) => sql`COUNT(DISTINCT ${model.column("CustomerId")})`, }) + .withDimension("created_at", { + type: "datetime", + sql: ({ model, sql }) => sql`${model.column("CreatedAt")}`, + }) + .withDimension("updated_at", { + type: "datetime", + sql: ({ model, sql }) => sql`${model.column("UpdatedAt")}`, + private: true, + }) + .withDimension("private_dimension", { + type: "string", + private: true, + sql: ({ model }) => model.column("private_dimension"), + }) + .withMetric("private_metric", { + type: "number", + private: true, + sql: ({ model, sql }) => + sql`COUNT(DISTINCT ${model.column("PrivateMetric")})`, + }) .withCategoricalHierarchy("customerHierarchy1", ({ element }) => [ element("customer") .withDimensions(["customer_id", "first_name"]) @@ -30,19 +50,37 @@ const customersModel = semanticLayer ({ dimension }) => `${dimension("first_name")}`, ), ]) - .withCategoricalHierarchy("customerHierarchy2", ({ element }) => [ - element("customer") - .withDimensions(["customer_id", "first_name"]) - .withKey(["customer_id"]) + .withTemporalHierarchy("customerHierarchy2", ({ element }) => [ + element("created_at") + .withDimensions(["created_at"]) + .withKey(["created_at"]) .withFormat( - ["first_name"], - ({ dimension }) => `${dimension("first_name")}`, + ["created_at"], + ({ dimension }) => `${dimension("created_at")}`, ), ]); const repository = semanticLayer .repository<{ foo: string }>() - .withModel(customersModel); + .withModel(customersModel) + .withCategoricalHierarchy("repositoryHierarchy1", ({ element }) => [ + element("customer") + .withDimensions(["customers.customer_id", "customers.first_name"]) + .withKey(["customers.customer_id"]) + .withFormat( + ["customers.first_name"], + ({ dimension }) => `${dimension("customers.first_name")}`, + ), + ]) + .withTemporalHierarchy("repositoryHierarchy2", ({ element }) => [ + element("created_at") + .withDimensions(["customers.created_at"]) + .withKey(["customers.created_at"]) + .withFormat( + ["customers.created_at"], + ({ dimension }) => `${dimension("customers.created_at")}`, + ), + ]); const queryBuilder = repository.build("postgresql"); @@ -77,17 +115,70 @@ describe("model", () => { >().toEqualTypeOf<{ "customers.customer_id": "number"; "customers.first_name": "string"; + "customers.created_at": "datetime"; + "customers.created_at.date": "date"; + "customers.created_at.time": "time"; + "customers.created_at.hour": "string"; + "customers.created_at.year": "number"; + "customers.created_at.quarter": "string"; + "customers.created_at.quarter_of_year": "number"; + "customers.created_at.month": "string"; + "customers.created_at.month_num": "number"; + "customers.created_at.week": "string"; + "customers.created_at.week_num": "number"; + "customers.created_at.day_of_month": "number"; + "customers.created_at.hour_of_day": "number"; + "customers.created_at.minute": "string"; + "customers.updated_at": "datetime"; + "customers.updated_at.date": "date"; + "customers.updated_at.time": "time"; + "customers.updated_at.hour": "string"; + "customers.updated_at.year": "number"; + "customers.updated_at.quarter": "string"; + "customers.updated_at.quarter_of_year": "number"; + "customers.updated_at.month": "string"; + "customers.updated_at.month_num": "number"; + "customers.updated_at.week": "string"; + "customers.updated_at.week_num": "number"; + "customers.updated_at.day_of_month": "number"; + "customers.updated_at.hour_of_day": "number"; + "customers.updated_at.minute": "string"; + "customers.private_dimension": "string"; }>(); expectTypeOf< semanticLayer.GetModelMetrics >().toEqualTypeOf<{ "customers.count": "number"; + "customers.private_metric": "number"; }>(); + expectTypeOf< + semanticLayer.GetModelPrivateMembers + >().toEqualTypeOf< + | "customers.private_dimension" + | "customers.private_metric" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute" + >(); + expectTypeOf< semanticLayer.GetModelHierarchies - >().toEqualTypeOf<"customerHierarchy1" | "customerHierarchy2">(); + >().toEqualTypeOf< + "customerHierarchy1" | "customerHierarchy2" | "created_at" + >(); }); it("can type check MemberFormat", () => { @@ -123,12 +214,43 @@ describe("model", () => { type: "string"; description?: string | undefined; format?: semanticLayer.MemberFormat<"string"> | undefined; + private?: boolean | undefined; sql?: | semanticLayer.BasicDimensionSqlFn< { foo: string; }, - "customer_id" | "first_name" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension" > | undefined; primaryKey?: boolean | undefined; @@ -140,12 +262,43 @@ describe("model", () => { type: "number"; description?: string | undefined; format?: semanticLayer.MemberFormat<"number"> | undefined; + private?: boolean | undefined; sql?: | semanticLayer.BasicDimensionSqlFn< { foo: string; }, - "customer_id" | "first_name" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension" > | undefined; primaryKey?: boolean | undefined; @@ -157,12 +310,43 @@ describe("model", () => { type: "boolean"; description?: string | undefined; format?: semanticLayer.MemberFormat<"boolean"> | undefined; + private?: boolean | undefined; sql?: | semanticLayer.BasicDimensionSqlFn< { foo: string; }, - "customer_id" | "first_name" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension" > | undefined; primaryKey?: boolean | undefined; @@ -175,12 +359,43 @@ describe("model", () => { description?: string | undefined; omitGranularity?: boolean | undefined; format?: semanticLayer.MemberFormat<"datetime"> | undefined; + private?: boolean | undefined; sql?: | semanticLayer.BasicDimensionSqlFn< { foo: string; }, - "customer_id" | "first_name" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension" > | undefined; primaryKey?: boolean | undefined; @@ -193,12 +408,43 @@ describe("model", () => { description?: string | undefined; omitGranularity?: boolean | undefined; format?: semanticLayer.MemberFormat<"date"> | undefined; + private?: boolean | undefined; sql?: | semanticLayer.BasicDimensionSqlFn< { foo: string; }, - "customer_id" | "first_name" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension" > | undefined; primaryKey?: boolean | undefined; @@ -211,12 +457,43 @@ describe("model", () => { description?: string | undefined; omitGranularity?: boolean | undefined; format?: semanticLayer.MemberFormat<"time"> | undefined; + private?: boolean | undefined; sql?: | semanticLayer.BasicDimensionSqlFn< { foo: string; }, - "customer_id" | "first_name" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension" > | undefined; primaryKey?: boolean | undefined; @@ -260,12 +537,43 @@ describe("model", () => { type: "string"; description?: string | undefined; format?: semanticLayer.MemberFormat<"string"> | undefined; + private?: boolean | undefined; sql: semanticLayer.BasicMetricSqlFn< { foo: string; }, - "customer_id" | "first_name", - "count" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension", + "count" | "private_metric" >; }>(); @@ -275,12 +583,43 @@ describe("model", () => { type: "number"; description?: string | undefined; format?: semanticLayer.MemberFormat<"number"> | undefined; + private?: boolean | undefined; sql: semanticLayer.BasicMetricSqlFn< { foo: string; }, - "customer_id" | "first_name", - "count" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension", + "count" | "private_metric" >; }>(); @@ -290,12 +629,43 @@ describe("model", () => { type: "boolean"; description?: string | undefined; format?: semanticLayer.MemberFormat<"boolean"> | undefined; + private?: boolean | undefined; sql: semanticLayer.BasicMetricSqlFn< { foo: string; }, - "customer_id" | "first_name", - "count" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension", + "count" | "private_metric" >; }>(); @@ -305,12 +675,43 @@ describe("model", () => { type: "datetime"; description?: string | undefined; format?: semanticLayer.MemberFormat<"datetime"> | undefined; + private?: boolean | undefined; sql: semanticLayer.BasicMetricSqlFn< { foo: string; }, - "customer_id" | "first_name", - "count" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension", + "count" | "private_metric" >; }>(); @@ -320,12 +721,43 @@ describe("model", () => { type: "date"; description?: string | undefined; format?: semanticLayer.MemberFormat<"date"> | undefined; + private?: boolean | undefined; sql: semanticLayer.BasicMetricSqlFn< { foo: string; }, - "customer_id" | "first_name", - "count" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension", + "count" | "private_metric" >; }>(); @@ -335,12 +767,43 @@ describe("model", () => { type: "time"; description?: string | undefined; format?: semanticLayer.MemberFormat<"time"> | undefined; + private?: boolean | undefined; sql: semanticLayer.BasicMetricSqlFn< { foo: string; }, - "customer_id" | "first_name", - "count" + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute" + | "updated_at" + | "updated_at.date" + | "updated_at.time" + | "updated_at.hour" + | "updated_at.year" + | "updated_at.quarter" + | "updated_at.quarter_of_year" + | "updated_at.month" + | "updated_at.month_num" + | "updated_at.week" + | "updated_at.week_num" + | "updated_at.day_of_month" + | "updated_at.hour_of_day" + | "updated_at.minute" + | "private_dimension", + "count" | "private_metric" >; }>(); @@ -390,13 +853,59 @@ describe("model", () => { ): semanticLayer.HierarchyElementInit<{ customer_id: "number"; first_name: "string"; + created_at: "datetime"; + "created_at.date": "date"; + "created_at.time": "time"; + "created_at.hour": "string"; + "created_at.year": "number"; + "created_at.quarter": "string"; + "created_at.quarter_of_year": "number"; + "created_at.month": "string"; + "created_at.month_num": "number"; + "created_at.week": "string"; + "created_at.week_num": "number"; + "created_at.day_of_month": "number"; + "created_at.hour_of_day": "number"; + "created_at.minute": "string"; }>; - fromDimension( + fromDimension< + DN extends + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute", + >( name: DN, ): semanticLayer.HierarchyElement< { customer_id: "number"; first_name: "string"; + created_at: "datetime"; + "created_at.date": "date"; + "created_at.time": "time"; + "created_at.hour": "string"; + "created_at.year": "number"; + "created_at.quarter": "string"; + "created_at.quarter_of_year": "number"; + "created_at.month": "string"; + "created_at.month_num": "number"; + "created_at.week": "string"; + "created_at.week_num": "number"; + "created_at.day_of_month": "number"; + "created_at.hour_of_day": "number"; + "created_at.minute": "string"; }, DN >; @@ -422,12 +931,44 @@ describe("model", () => { customer_id: "number"; first_name: "string"; }>; - fromDimension( + fromDimension< + DN extends + | "customer_id" + | "first_name" + | "created_at" + | "created_at.date" + | "created_at.time" + | "created_at.hour" + | "created_at.year" + | "created_at.quarter" + | "created_at.quarter_of_year" + | "created_at.month" + | "created_at.month_num" + | "created_at.week" + | "created_at.week_num" + | "created_at.day_of_month" + | "created_at.hour_of_day" + | "created_at.minute", + >( name: DN, ): semanticLayer.HierarchyElement< { customer_id: "number"; first_name: "string"; + created_at: "datetime"; + "created_at.date": "date"; + "created_at.time": "time"; + "created_at.hour": "string"; + "created_at.year": "number"; + "created_at.quarter": "string"; + "created_at.quarter_of_year": "number"; + "created_at.month": "string"; + "created_at.month_num": "number"; + "created_at.week": "string"; + "created_at.week_num": "number"; + "created_at.day_of_month": "number"; + "created_at.hour_of_day": "number"; + "created_at.minute": "string"; }, DN >; @@ -456,6 +997,7 @@ describe("repository", () => { any, any, any, + any, any > ? TContext @@ -467,6 +1009,7 @@ describe("repository", () => { any, any, any, + any, any > ? TModelNames @@ -478,6 +1021,7 @@ describe("repository", () => { infer TDimensions, any, any, + any, any > ? TDimensions @@ -489,17 +1033,31 @@ describe("repository", () => { any, infer TMetrics, any, + any, any > ? TMetrics : never; + type GetRepositoryPrivateMembers = T extends semanticLayer.Repository< + any, + any, + any, + any, + infer TPrivateMembers, + any, + any + > + ? TPrivateMembers + : never; + type GetRepositoryHierarchies = T extends semanticLayer.Repository< any, any, any, any, any, + any, infer THierarchies > ? THierarchies @@ -515,14 +1073,67 @@ describe("repository", () => { expectTypeOf>().branded.toEqualTypeOf<{ "customers.customer_id": "number"; "customers.first_name": "string"; + "customers.private_dimension": "string"; + "customers.created_at": "datetime"; + "customers.created_at.date": "date"; + "customers.created_at.time": "time"; + "customers.created_at.hour": "string"; + "customers.created_at.year": "number"; + "customers.created_at.quarter": "string"; + "customers.created_at.quarter_of_year": "number"; + "customers.created_at.month": "string"; + "customers.created_at.month_num": "number"; + "customers.created_at.week": "string"; + "customers.created_at.week_num": "number"; + "customers.created_at.day_of_month": "number"; + "customers.created_at.hour_of_day": "number"; + "customers.created_at.minute": "string"; + "customers.updated_at": "datetime"; + "customers.updated_at.date": "date"; + "customers.updated_at.time": "time"; + "customers.updated_at.hour": "string"; + "customers.updated_at.year": "number"; + "customers.updated_at.quarter": "string"; + "customers.updated_at.quarter_of_year": "number"; + "customers.updated_at.month": "string"; + "customers.updated_at.month_num": "number"; + "customers.updated_at.week": "string"; + "customers.updated_at.week_num": "number"; + "customers.updated_at.day_of_month": "number"; + "customers.updated_at.hour_of_day": "number"; + "customers.updated_at.minute": "string"; }>(); expectTypeOf>().branded.toEqualTypeOf<{ "customers.count": "number"; + "customers.private_metric": "number"; }>(); + expectTypeOf>().toEqualTypeOf< + | "customers.private_dimension" + | "customers.private_metric" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute" + >(); + expectTypeOf>().toEqualTypeOf< - "customers.customerHierarchy1" | "customers.customerHierarchy2" + | "customers.customerHierarchy1" + | "customers.customerHierarchy2" + | "customers.created_at" + | "repositoryHierarchy1" + | "repositoryHierarchy2" >(); }); @@ -542,12 +1153,43 @@ describe("repository", () => { type: "string"; description?: string | undefined; format?: semanticLayer.MemberFormat<"string"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedDimensionSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute" >; }>(); @@ -562,12 +1204,43 @@ describe("repository", () => { type: "number"; description?: string | undefined; format?: semanticLayer.MemberFormat<"number"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedDimensionSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute" >; }>(); @@ -582,12 +1255,43 @@ describe("repository", () => { type: "number"; description?: string | undefined; format?: semanticLayer.MemberFormat<"number"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedDimensionSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute" >; }>(); @@ -602,12 +1306,43 @@ describe("repository", () => { type: "date"; description?: string | undefined; format?: semanticLayer.MemberFormat<"date"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedDimensionSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute" >; }>(); @@ -622,12 +1357,43 @@ describe("repository", () => { type: "datetime"; description?: string | undefined; format?: semanticLayer.MemberFormat<"datetime"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedDimensionSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute" >; }>(); @@ -642,12 +1408,43 @@ describe("repository", () => { type: "time"; description?: string | undefined; format?: semanticLayer.MemberFormat<"time"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedDimensionSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute" >; }>(); @@ -697,13 +1494,44 @@ describe("repository", () => { type: "string"; description?: string | undefined; format?: semanticLayer.MemberFormat<"string"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedMetricSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name", - "customers.count" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute", + "customers.count" | "customers.private_metric" >; }>(); @@ -718,13 +1546,44 @@ describe("repository", () => { type: "number"; description?: string | undefined; format?: semanticLayer.MemberFormat<"number"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedMetricSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name", - "customers.count" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute", + "customers.count" | "customers.private_metric" >; }>(); @@ -739,13 +1598,44 @@ describe("repository", () => { type: "number"; description?: string | undefined; format?: semanticLayer.MemberFormat<"number"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedMetricSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name", - "customers.count" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute", + "customers.count" | "customers.private_metric" >; }>(); @@ -757,13 +1647,44 @@ describe("repository", () => { type: "date"; description?: string | undefined; format?: semanticLayer.MemberFormat<"date"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedMetricSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name", - "customers.count" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute", + "customers.count" | "customers.private_metric" >; }>(); @@ -778,13 +1699,44 @@ describe("repository", () => { type: "datetime"; description?: string | undefined; format?: semanticLayer.MemberFormat<"datetime"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedMetricSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name", - "customers.count" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute", + "customers.count" | "customers.private_metric" >; }>(); @@ -796,13 +1748,44 @@ describe("repository", () => { type: "time"; description?: string | undefined; format?: semanticLayer.MemberFormat<"time"> | undefined; + private?: boolean | undefined; sql: semanticLayer.CalculatedMetricSqlFn< { foo: string; }, "customers", - "customers.customer_id" | "customers.first_name", - "customers.count" + | "customers.customer_id" + | "customers.first_name" + | "customers.private_dimension" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.updated_at" + | "customers.updated_at.date" + | "customers.updated_at.time" + | "customers.updated_at.hour" + | "customers.updated_at.year" + | "customers.updated_at.quarter" + | "customers.updated_at.quarter_of_year" + | "customers.updated_at.month" + | "customers.updated_at.month_num" + | "customers.updated_at.week" + | "customers.updated_at.week_num" + | "customers.updated_at.day_of_month" + | "customers.updated_at.hour_of_day" + | "customers.updated_at.minute", + "customers.count" | "customers.private_metric" >; }>(); @@ -855,15 +1838,59 @@ describe("repository", () => { ): semanticLayer.HierarchyElementInit<{ "customers.customer_id": "number"; "customers.first_name": "string"; + "customers.created_at": "datetime"; + "customers.created_at.date": "date"; + "customers.created_at.time": "time"; + "customers.created_at.hour": "string"; + "customers.created_at.year": "number"; + "customers.created_at.quarter": "string"; + "customers.created_at.quarter_of_year": "number"; + "customers.created_at.month": "string"; + "customers.created_at.month_num": "number"; + "customers.created_at.week": "string"; + "customers.created_at.week_num": "number"; + "customers.created_at.day_of_month": "number"; + "customers.created_at.hour_of_day": "number"; + "customers.created_at.minute": "string"; }>; fromDimension< - DN extends "customers.customer_id" | "customers.first_name", + DN extends + | "customers.customer_id" + | "customers.first_name" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute", >( name: DN, ): semanticLayer.HierarchyElement< { "customers.customer_id": "number"; "customers.first_name": "string"; + "customers.created_at": "datetime"; + "customers.created_at.date": "date"; + "customers.created_at.time": "time"; + "customers.created_at.hour": "string"; + "customers.created_at.year": "number"; + "customers.created_at.quarter": "string"; + "customers.created_at.quarter_of_year": "number"; + "customers.created_at.month": "string"; + "customers.created_at.month_num": "number"; + "customers.created_at.week": "string"; + "customers.created_at.week_num": "number"; + "customers.created_at.day_of_month": "number"; + "customers.created_at.hour_of_day": "number"; + "customers.created_at.minute": "string"; }, DN >; @@ -890,15 +1917,59 @@ describe("repository", () => { ): semanticLayer.HierarchyElementInit<{ "customers.customer_id": "number"; "customers.first_name": "string"; + "customers.created_at": "datetime"; + "customers.created_at.date": "date"; + "customers.created_at.time": "time"; + "customers.created_at.hour": "string"; + "customers.created_at.year": "number"; + "customers.created_at.quarter": "string"; + "customers.created_at.quarter_of_year": "number"; + "customers.created_at.month": "string"; + "customers.created_at.month_num": "number"; + "customers.created_at.week": "string"; + "customers.created_at.week_num": "number"; + "customers.created_at.day_of_month": "number"; + "customers.created_at.hour_of_day": "number"; + "customers.created_at.minute": "string"; }>; fromDimension< - DN extends "customers.customer_id" | "customers.first_name", + DN extends + | "customers.customer_id" + | "customers.first_name" + | "customers.created_at" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute", >( name: DN, ): semanticLayer.HierarchyElement< { "customers.customer_id": "number"; "customers.first_name": "string"; + "customers.created_at": "datetime"; + "customers.created_at.date": "date"; + "customers.created_at.time": "time"; + "customers.created_at.hour": "string"; + "customers.created_at.year": "number"; + "customers.created_at.quarter": "string"; + "customers.created_at.quarter_of_year": "number"; + "customers.created_at.month": "string"; + "customers.created_at.month_num": "number"; + "customers.created_at.week": "string"; + "customers.created_at.week_num": "number"; + "customers.created_at.day_of_month": "number"; + "customers.created_at.hour_of_day": "number"; + "customers.created_at.minute": "string"; }, DN >; @@ -917,13 +1988,43 @@ describe("query builder", () => { type QF = Simplify[number]>; expectTypeOf().toEqualTypeOf< - "customers.customer_id" | "customers.first_name" | "customers.count" + | "customers.created_at" + | "customers.customer_id" + | "customers.first_name" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" + | "customers.count" >(); expectTypeOf[number]>().toEqualTypeOf<{ member: + | "customers.created_at" | "customers.customer_id" | "customers.first_name" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" | "customers.count"; direction: "asc" | "desc"; }>(); @@ -931,8 +2032,22 @@ describe("query builder", () => { expectTypeOf>().toEqualTypeOf<{ operator: "equals"; member: + | "customers.created_at" | "customers.customer_id" | "customers.first_name" + | "customers.created_at.date" + | "customers.created_at.time" + | "customers.created_at.hour" + | "customers.created_at.year" + | "customers.created_at.quarter" + | "customers.created_at.quarter_of_year" + | "customers.created_at.month" + | "customers.created_at.month_num" + | "customers.created_at.week" + | "customers.created_at.week_num" + | "customers.created_at.day_of_month" + | "customers.created_at.hour_of_day" + | "customers.created_at.minute" | "customers.count"; value: (string | number | bigint | boolean | Date)[]; }>(); diff --git a/src/lib/member.ts b/src/lib/member.ts index 89334cb..fdcee39 100644 --- a/src/lib/member.ts +++ b/src/lib/member.ts @@ -10,6 +10,8 @@ export abstract class Member { abstract isMetric(): this is Metric; abstract isDimension(): this is Dimension; + abstract isPrivate(): boolean; + abstract getAlias(): string; abstract getPath(): string; abstract getDescription(): string | undefined; diff --git a/src/lib/model.ts b/src/lib/model.ts index a69c4eb..f3b5043 100644 --- a/src/lib/model.ts +++ b/src/lib/model.ts @@ -51,11 +51,12 @@ export type ModelSqlFn = (args: { }*/ export class Model< - C, - N extends string, - D extends MemberNameToType = MemberNameToType, - M extends MemberNameToType = MemberNameToType, - G extends string = never, + TContext, + TModelName extends string, + TModelDimensions extends MemberNameToType = MemberNameToType, + TModelMetrics extends MemberNameToType = MemberNameToType, + TPrivateMembers extends string = never, + TModelHierarchyNames extends string = never, > { public readonly dimensions: Record = {}; public readonly metrics: Record = {}; @@ -70,24 +71,49 @@ export class Model< public readonly hierarchyNames: Set = new Set(); constructor( - public readonly name: N, - public readonly config: ModelConfig, + public readonly name: TModelName, + public readonly config: ModelConfig, ) {} withDimension< - DN1 extends string, - DP extends BasicDimensionProps, - DG extends boolean = DimensionHasTemporalGranularity, + TDimensionName extends string, + TDimensionProps extends BasicDimensionProps< + TContext, + string & keyof TModelDimensions + >, + TDimensionIsPrivate extends + boolean = TDimensionProps["private"] extends true ? true : false, + TDimensionHasTemporalGranularity extends + boolean = DimensionHasTemporalGranularity, >( - name: Exclude, - dimensionProps: DP, + name: Exclude, + dimensionProps: TDimensionProps, ): Model< - C, - N, - DG extends true - ? D & WithTemporalGranularityDimensions - : D & { [k in DN1]: DP["type"] }, - M, - DG extends true ? G | DN1 : G + TContext, + TModelName, + TDimensionHasTemporalGranularity extends true + ? TModelDimensions & + WithTemporalGranularityDimensions< + TDimensionName, + TDimensionProps["type"] + > + : TModelDimensions & { [k in TDimensionName]: TDimensionProps["type"] }, + TModelMetrics, + TDimensionIsPrivate extends true + ? + | TPrivateMembers + | (TDimensionIsPrivate extends true + ? string & + keyof WithTemporalGranularityDimensions< + TDimensionName, + TDimensionProps["type"] + > + : TDimensionName) + : TPrivateMembers, + TDimensionHasTemporalGranularity extends true + ? TDimensionIsPrivate extends true + ? TModelHierarchyNames + : TModelHierarchyNames | TDimensionName + : TModelHierarchyNames > { invariant( !(this.dimensions[name] || this.metrics[name]), @@ -98,10 +124,10 @@ export class Model< this.dimensions[name] = dimension; if ( // TODO: figure out why typeHasGranularity is not working anymore - dimensionProps.type === "datetime" || - dimensionProps.type === "date" || - (dimensionProps.type === "time" && - dimensionProps.omitGranularity !== true) + (dimensionProps.type === "datetime" || + dimensionProps.type === "date" || + dimensionProps.type === "time") && + dimensionProps.omitGranularity !== true ) { const granularityDimensions = TemporalGranularityByDimensionType[dimensionProps.type]; @@ -120,21 +146,39 @@ export class Model< g, ); } - this.unsafeWithHierarchy( - name, - makeTemporalHierarchyElementsForDimension(name, dimensionProps.type), - "temporal", - ); + if (!dimensionProps.private) { + this.unsafeWithHierarchy( + name, + makeTemporalHierarchyElementsForDimension(name, dimensionProps.type), + "temporal", + ); + } } return this; } withMetric< - MN1 extends string, - MP extends BasicMetricProps, + TMetricName extends string, + TMetricProps extends BasicMetricProps< + TContext, + string & keyof TModelDimensions, + string & keyof TModelMetrics + >, + TMetricIsPrivate extends boolean = TMetricProps["private"] extends true + ? true + : false, >( - name: Exclude, - metric: MP, - ): Model { + name: Exclude, + metric: TMetricProps, + ): Model< + TContext, + TModelName, + TModelDimensions, + TModelMetrics & { [k in TMetricName]: TMetricProps["type"] }, + TMetricIsPrivate extends true + ? TPrivateMembers | TMetricName + : TPrivateMembers, + TModelHierarchyNames + > { invariant( !(this.dimensions[name] || this.metrics[name]), `Member "${name}" already exists`, @@ -150,7 +194,7 @@ export class Model< ) { invariant( this.hierarchyNames.has(hierarchyName) === false, - `Granularity ${hierarchyName} already exists`, + `Hierarchy ${hierarchyName} already exists`, ); this.hierarchyNames.add(hierarchyName); if (type === "categorical") { @@ -160,34 +204,56 @@ export class Model< } return this; } - withCategoricalHierarchy( - hierarchyName: Exclude, + withCategoricalHierarchy( + hierarchyName: Exclude, builder: (args: { - element: ReturnType>; + element: ReturnType< + typeof makeHierarchyElementInitMaker< + Omit + > + >; }) => [AnyHierarchyElement, ...AnyHierarchyElement[]], - ): Model { + ): Model< + TContext, + TModelName, + TModelDimensions, + TModelMetrics, + TPrivateMembers, + TModelHierarchyNames | THierarchyName + > { const elements = builder({ element: makeHierarchyElementInitMaker(), }); return this.unsafeWithHierarchy(hierarchyName, elements, "categorical"); } - withTemporalHierarchy( - hierarchyName: Exclude, + withTemporalHierarchy( + hierarchyName: Exclude, builder: (args: { - element: ReturnType>; + element: ReturnType< + typeof makeHierarchyElementInitMaker< + Omit + > + >; }) => [AnyHierarchyElement, ...AnyHierarchyElement[]], - ): Model { + ): Model< + TContext, + TModelName, + TModelDimensions, + TModelMetrics, + TPrivateMembers, + TModelHierarchyNames | THierarchyName + > { const elements = builder({ element: makeHierarchyElementInitMaker(), }); return this.unsafeWithHierarchy(hierarchyName, elements, "temporal"); } - getMetric(name: string & keyof M) { + getMetric(name: string & keyof TModelMetrics) { const metric = this.metrics[name]; invariant(metric, `Metric ${name} not found in model ${this.name}`); return metric; } - getDimension(name: string & keyof D) { + getDimension(name: string & keyof TModelDimensions) { const dimension = this.dimensions[name]; invariant(dimension, `Dimension ${name} not found in model ${this.name}`); return dimension; @@ -195,7 +261,7 @@ export class Model< getPrimaryKeyDimensions() { return Object.values(this.dimensions).filter((d) => d.props.primaryKey); } - getMember(name: string & (keyof D | keyof M)) { + getMember(name: string & (keyof TModelDimensions | keyof TModelMetrics)) { const member = this.dimensions[name] || this.metrics[name]; invariant(member, `Member ${name} not found in model ${this.name}`); return member; @@ -210,7 +276,7 @@ export class Model< repository: AnyRepository, queryContext: QueryContext, dialect: AnyBaseDialect, - context: C, + context: TContext, ) { invariant(this.config.type === "table", "Model is not a table"); @@ -237,7 +303,7 @@ export class Model< repository: AnyRepository, queryContext: QueryContext, dialect: AnyBaseDialect, - context: C, + context: TContext, ) { invariant(this.config.type === "sqlQuery", "Model is not an SQL query"); @@ -253,7 +319,7 @@ export class Model< repository: AnyRepository, queryContext: QueryContext, dialect: AnyBaseDialect, - context: C, + context: TContext, ) { if (this.config.type === "table") { const { sql, bindings } = this.getTableName( @@ -276,7 +342,7 @@ export class Model< repository: AnyRepository, queryContext: QueryContext, dialect: AnyBaseDialect, - context: C, + context: TContext, ) { if (this.config.type === "sqlQuery") { return SqlFragment.make({ @@ -288,8 +354,15 @@ export class Model< return this.getTableName(repository, queryContext, dialect, context); } - clone(name: N) { - const newModel = new Model(name, this.config); + clone(name: TNewModelName) { + const newModel = new Model< + TContext, + TNewModelName, + TModelDimensions, + TModelMetrics, + TPrivateMembers, + TModelHierarchyNames + >(name, this.config); for (const [key, value] of Object.entries(this.dimensions)) { newModel.dimensions[key] = value.clone(newModel); } @@ -373,11 +446,23 @@ export type GetModelMetrics = T extends Model< } : never; +export type GetModelPrivateMembers = T extends Model< + any, + infer TModelName, + any, + any, + infer TPrivateMembers, + any +> + ? `${TModelName}.${TPrivateMembers}` + : never; + export type GetModelHierarchies = T extends Model< any, any, any, any, + any, infer THierarchies > ? THierarchies diff --git a/src/lib/model/basic-dimension.ts b/src/lib/model/basic-dimension.ts index 188a7ca..c802b2b 100644 --- a/src/lib/model/basic-dimension.ts +++ b/src/lib/model/basic-dimension.ts @@ -91,6 +91,9 @@ export class BasicDimension extends Dimension { getFormat() { return this.props.format; } + isPrivate() { + return !!this.props.private; + } clone(model: AnyModel) { return new BasicDimension(model, this.name, { ...this.props }); } diff --git a/src/lib/model/basic-metric.ts b/src/lib/model/basic-metric.ts index bd8c55d..4b31136 100644 --- a/src/lib/model/basic-metric.ts +++ b/src/lib/model/basic-metric.ts @@ -74,6 +74,9 @@ export class BasicMetric extends Metric { getFormat() { return this.props.format; } + isPrivate() { + return !!this.props.private; + } clone(model: AnyModel) { return new BasicMetric(model, this.name, { ...this.props }); } @@ -122,7 +125,7 @@ export class BasicMetricQueryMember extends MetricQueryMember { private callSqlFn(): SqlFn { let refAliasCounter = 0; const getNextRefAlias = () => - `${this.member.name}___metric_ref_${refAliasCounter++}`; + `${this.member.model.name}___${this.member.name}___mr_${refAliasCounter++}`; return this.member.props.sql({ identifier: (name: string) => new IdentifierRef(name), diff --git a/src/lib/query-builder.ts b/src/lib/query-builder.ts index 5a260e8..e40141b 100644 --- a/src/lib/query-builder.ts +++ b/src/lib/query-builder.ts @@ -13,10 +13,11 @@ import { import invariant from "tiny-invariant"; import { Simplify } from "type-fest"; import { AnyBaseDialect } from "./dialect/base.js"; +import { pathToAlias } from "./helpers.js"; import { HierarchyElementConfig } from "./hierarchy.js"; import { buildQuery } from "./query-builder/build-query.js"; import { FilterBuilder } from "./query-builder/filter-builder.js"; -import { getQueryPlan } from "./query-builder/query-plan.js"; +import { QueryPlan, getQueryPlan } from "./query-builder/query-plan.js"; import { QueryContext } from "./query-builder/query-plan/query-context.js"; import { QuerySchema, buildQuerySchema } from "./query-schema.js"; import type { AnyRepository } from "./repository.js"; @@ -29,12 +30,13 @@ function isValidGranularityConfigElements( } export class QueryBuilder< - C, - D extends MemberNameToType, - M extends MemberNameToType, - F, - P, - G, + TContext, + TDimensions extends MemberNameToType, + TMetrics extends MemberNameToType, + TMemberNames extends string, + TFilters, + TDialectParamsReturnType, + THierarchyNames, > { public readonly querySchema: QuerySchema; public readonly hierarchies: HierarchyConfig[]; @@ -122,7 +124,7 @@ export class QueryBuilder< return hierarchy; } - getHierarchy(hierarchyName: G1 & string) { + getHierarchy(hierarchyName: G1 & string) { return this.unsafeGetHierarchy(hierarchyName); } @@ -141,11 +143,12 @@ export class QueryBuilder< return sqlQuery.toSQL(); } + // Return type annotation is needed because otherwise build generates incorrect index.d.ts getQueryPlan( queryContext: QueryContext, context: unknown, query: AnyInputQuery, - ) { + ): QueryPlan { return getQueryPlan(this, queryContext, context, query); } @@ -157,18 +160,18 @@ export class QueryBuilder< ).toNative(); return { sql, - bindings: bindings as P, + bindings: bindings as TDialectParamsReturnType, }; } buildQuery( query: Q & InputQuery< - string & keyof D, - string & keyof M, - F & { member: string & (keyof D | keyof M) } + string & keyof TDimensions, + string & keyof TMetrics, + TFilters & { member: string & TMemberNames } >, - ...rest: C extends undefined ? [] : [C] + ...rest: TContext extends undefined ? [] : [TContext] ) { const [context] = rest; const { sql, bindings } = this.unsafeBuildQuery(query, context); @@ -176,14 +179,14 @@ export class QueryBuilder< const result: SqlQueryResult< Simplify< QueryReturnType< - D & M, - QueryMemberName & (keyof D | keyof M) + TDimensions & TMetrics, + QueryMemberName & (keyof TDimensions | keyof TMetrics) > >, - P + TDialectParamsReturnType > = { sql, - bindings: bindings as P, + bindings: bindings as TDialectParamsReturnType, }; return result; @@ -199,15 +202,18 @@ export class QueryBuilder< return query.members.reduce((acc, memberName) => { const member = this.repository.getMember(memberName); const isDimension = member.isDimension(); + const alias = pathToAlias(memberName); - acc[memberName.replaceAll(".", "___")] = { + acc[alias] = { memberType: isDimension ? "dimension" : "metric", path: member.getPath(), + alias, format: member.getFormat() as AnyMemberFormat, type: member.getType(), description: member.getDescription(), isPrimaryKey: isDimension ? member.isPrimaryKey() : false, isGranularity: isDimension ? member.isGranularity() : false, + isPrivate: member.isPrivate(), }; return acc; @@ -217,17 +223,20 @@ export class QueryBuilder< export type QueryBuilderQuery = Q extends QueryBuilder< any, - infer D, - infer M, - infer F, + infer TDimensions, + infer TMetrics, + infer TMemberNames, + infer TFilters, any, any > ? InputQuery< - string & keyof D, - string & keyof M, - F & { member: string & (keyof D | keyof M) } + string & keyof TDimensions, + string & keyof TMetrics, + TFilters & { + member: TMemberNames; + } > : never; -export type AnyQueryBuilder = QueryBuilder; +export type AnyQueryBuilder = QueryBuilder; diff --git a/src/lib/query-builder/query-plan.ts b/src/lib/query-builder/query-plan.ts index 2108dc9..82f5773 100644 --- a/src/lib/query-builder/query-plan.ts +++ b/src/lib/query-builder/query-plan.ts @@ -9,13 +9,13 @@ import { CalculatedMetricQueryMember } from "../repository/calculated-metric.js" import { findOptimalJoinGraph } from "./optimal-join-graph.js"; import { QueryContext } from "./query-plan/query-context.js"; -export type QueryFilterConnective = { +export type QueryPlanQueryFilterConnective = { operator: "and" | "or"; - filters: QueryFilter[]; + filters: QueryPlanQueryFilter[]; }; -export type QueryFilter = - | QueryFilterConnective +export type QueryPlanQueryFilter = + | QueryPlanQueryFilterConnective | { operator: string; member: string; @@ -23,8 +23,8 @@ export type QueryFilter = }; function filterIsConnective( - filter: QueryFilter, -): filter is QueryFilterConnective { + filter: QueryPlanQueryFilter, +): filter is QueryPlanQueryFilterConnective { return filter.operator === "and" || filter.operator === "or"; } @@ -107,7 +107,7 @@ function getOrderWithOnlyProjectedMembers( } } -function getFirstMemberFilter(filter: QueryFilter) { +function getFirstMemberFilter(filter: QueryPlanQueryFilter) { if (filterIsConnective(filter)) { return getFirstMemberFilter(filter.filters[0]!); } @@ -116,11 +116,11 @@ function getFirstMemberFilter(filter: QueryFilter) { function getDimensionAndMetricFilters( repository: AnyRepository, - filters: QueryFilter[] | undefined, + filters: QueryPlanQueryFilter[] | undefined, ) { return (filters ?? []).reduce<{ - dimensionFilters: QueryFilter[]; - metricFilters: QueryFilter[]; + dimensionFilters: QueryPlanQueryFilter[]; + metricFilters: QueryPlanQueryFilter[]; }>( (acc, filter) => { const memberFilter = getFirstMemberFilter(filter); @@ -136,7 +136,10 @@ function getDimensionAndMetricFilters( ); } -function getFiltersMembers(repository: AnyRepository, filters: QueryFilter[]) { +function getFiltersMembers( + repository: AnyRepository, + filters: QueryPlanQueryFilter[], +) { const members: Member[] = []; const filtersToProcess = [...filters]; while (filtersToProcess.length > 0) { @@ -285,7 +288,7 @@ function getSegmentQueryMetricsRefsSubQueryPlan( context: unknown, dimensions: string[], metrics: string[], - filters: QueryFilter[], + filters: QueryPlanQueryFilter[], ): { joinOnDimensions: string[]; queryPlan: QueryPlan } | undefined { const metricRefs = Array.from( new Set( @@ -399,7 +402,7 @@ function getSegmentQueryJoins( interface SegmentInput { dimensions: { projected: Dimension[]; filter: Dimension[] }; metrics?: ReturnType[number]; - filters: QueryFilter[]; + filters: QueryPlanQueryFilter[]; } function getSegmentQuery( diff --git a/src/lib/query-builder/query-plan/query-member.ts b/src/lib/query-builder/query-plan/query-member.ts index b3b2eeb..fa0e050 100644 --- a/src/lib/query-builder/query-plan/query-member.ts +++ b/src/lib/query-builder/query-plan/query-member.ts @@ -3,6 +3,7 @@ import { DimensionRef, MetricAliasColumnOrDimensionRef, MetricAliasMetricRef, + MetricRef, SqlFn, valueIsMetricAliasRef, } from "../../sql-fn.js"; @@ -132,7 +133,8 @@ export abstract class MetricQueryMember extends QueryMember { ); return [fragment]; } - getMetricRefs() { + // Return type annotation is needed because otherwise build generates incorrect index.d.ts + getMetricRefs(): MetricRef[] { const filterFn = (ref: unknown): ref is MetricAliasMetricRef => ref instanceof MetricAliasMetricRef; return this.sqlFnResult.filterRefs(filterFn).map((v) => v.aliasOf); diff --git a/src/lib/query-schema.ts b/src/lib/query-schema.ts index c3141c9..3c33fa6 100644 --- a/src/lib/query-schema.ts +++ b/src/lib/query-schema.ts @@ -10,9 +10,11 @@ function isRecord(value: unknown): value is Record { export function buildQuerySchema(queryBuilder: AnyQueryBuilder) { const dimensionPaths = queryBuilder.repository .getDimensions() + .filter((d) => !d.isPrivate()) .map((d) => d.getPath()); const metricPaths = queryBuilder.repository .getMetrics() + .filter((m) => !m.isPrivate()) .map((m) => m.getPath()); const memberToTypeIndex = { @@ -52,10 +54,10 @@ export function buildQuerySchema(queryBuilder: AnyQueryBuilder) { // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Essential complexity for validating filters. We need to validate that all and/or connectives have filters with members of the same type (dimension or metric), and we need to do this recursively. Using a queue to avoid stack overflows (although it's not a big chance that we'll have more than a few levels of nesting). const getConnectiveFilterMemberTypes = (filters: AnyQueryFilter[]) => { const connectiveFiltersMemberTypes: Set<"metric" | "dimension"> = new Set(); - const filtersQueue = [...filters]; + const filtersToProcess = [...filters]; - while (filtersQueue.length > 0) { - const filter = filtersQueue.shift(); + while (filtersToProcess.length > 0) { + const filter = filtersToProcess.shift(); // We don't have any type safety here because at this moment we don't know which filters are registered (although we should in practice have two types of structures - filters which should all have format of {operator: string, member: string, ...} or connectives which should have a format of {operator: "and" | "or", filters: [...]}), so we do some extra checks // if (isRecord(filter)) { if ( @@ -63,7 +65,7 @@ export function buildQuerySchema(queryBuilder: AnyQueryBuilder) { (filter.operator === "or" && Array.isArray(filter.filters)) ) { const subFilters = filter.filters as AnyQueryFilter[]; - filtersQueue.push(...subFilters); + filtersToProcess.push(...subFilters); } else { const member = typeof filter.member === "string" ? filter.member : null; @@ -127,7 +129,9 @@ export function buildQuerySchema(queryBuilder: AnyQueryBuilder) { .array( z .string() - .refine((arg) => memberPaths.includes(arg)) + .refine((arg) => memberPaths.includes(arg), { + message: "Member not found", + }) .describe("Dimension or metric name"), ) .min(1), @@ -135,7 +139,12 @@ export function buildQuerySchema(queryBuilder: AnyQueryBuilder) { offset: z.number().optional(), order: z .array( - z.object({ member: z.string(), direction: z.enum(["asc", "desc"]) }), + z.object({ + member: z.string().refine((arg) => memberPaths.includes(arg), { + message: "Member not found", + }), + direction: z.enum(["asc", "desc"]), + }), ) .optional(), filters: filters.optional(), diff --git a/src/lib/repository.ts b/src/lib/repository.ts index 15cb296..0fb470a 100644 --- a/src/lib/repository.ts +++ b/src/lib/repository.ts @@ -23,6 +23,7 @@ import { GetModelHierarchies, GetModelMetrics, GetModelName, + GetModelPrivateMembers, } from "./model.js"; import { @@ -59,6 +60,7 @@ export class Repository< TModelNames extends string = never, TDimensions extends MemberNameToType = MemberNameToType, TMetrics extends MemberNameToType = MemberNameToType, + TPrivateMembers extends string = never, TFilters = GetFilterFragmentBuilderRegistryPayload< ReturnType >, @@ -107,6 +109,7 @@ export class Repository< TModelNames | GetModelName, TDimensions & GetModelDimensions, TMetrics & GetModelMetrics, + TPrivateMembers | GetModelPrivateMembers, TFilters, THierarchies | `${GetModelName}.${GetModelHierarchies}` >; @@ -134,6 +137,7 @@ export class Repository< [k in TCalculatedDimensionName]: TCalculatedDimensionProps["type"]; }, TMetrics, + TPrivateMembers, TFilters, THierarchies >; @@ -162,6 +166,7 @@ export class Repository< TMetrics & { [k in TCalculatedMetricName]: TCalculatedMetricProps["type"]; }, + TPrivateMembers, TFilters, THierarchies >; @@ -184,36 +189,42 @@ export class Repository< } return this; } - withCategoricalHierarchy( - hierarchyName: Exclude, + withCategoricalHierarchy( + hierarchyName: Exclude, builder: (args: { - element: ReturnType>; + element: ReturnType< + typeof makeHierarchyElementInitMaker> + >; }) => [AnyHierarchyElement, ...AnyHierarchyElement[]], ): Repository< TContext, TModelNames, TDimensions, TMetrics, + TPrivateMembers, TFilters, - THierarchies | GN + THierarchies | THierarchyName > { const elements = builder({ element: makeHierarchyElementInitMaker(), }); return this.unsafeWithHierarchy(hierarchyName, elements, "categorical"); } - withTemporalHierarchy( - hierarchyName: Exclude, + withTemporalHierarchy( + hierarchyName: Exclude, builder: (args: { - element: ReturnType>; + element: ReturnType< + typeof makeHierarchyElementInitMaker> + >; }) => [AnyHierarchyElement, ...AnyHierarchyElement[]], ): Repository< TContext, TModelNames, TDimensions, TMetrics, + TPrivateMembers, TFilters, - THierarchies | GN + THierarchies | THierarchyName > { const elements = builder({ element: makeHierarchyElementInitMaker(), @@ -230,6 +241,7 @@ export class Repository< TModelNames, TDimensions, TMetrics, + TPrivateMembers, GetFilterFragmentBuilderRegistryPayload, THierarchies >; @@ -447,8 +459,9 @@ export class Repository< const dialect = AvailableDialects[dialectName]; return new QueryBuilder< TContext, - TDimensions, - TMetrics, + Omit, + Omit, + Exclude, TFilters, P, THierarchies diff --git a/src/lib/repository/calculated-dimension.ts b/src/lib/repository/calculated-dimension.ts index 6873b20..c7e80a0 100644 --- a/src/lib/repository/calculated-dimension.ts +++ b/src/lib/repository/calculated-dimension.ts @@ -84,6 +84,9 @@ export class CalculatedDimension extends Dimension { getFormat() { return this.props.format; } + isPrivate() { + return !!this.props.private; + } isPrimaryKey() { return false; } diff --git a/src/lib/repository/calculated-metric.ts b/src/lib/repository/calculated-metric.ts index 94dcbea..bab2f70 100644 --- a/src/lib/repository/calculated-metric.ts +++ b/src/lib/repository/calculated-metric.ts @@ -113,7 +113,9 @@ export class CalculatedMetric extends Metric { getFormat() { return this.props.format; } - + isPrivate() { + return !!this.props.private; + } getQueryMember( queryContext: QueryContext, repository: AnyRepository, @@ -159,7 +161,7 @@ export class CalculatedMetricQueryMember extends MetricQueryMember { let refAliasCounter = 0; const models = this.repository.getModels(); const getNextRefAlias = () => - `${this.member.getAlias()}___metric_ref_${refAliasCounter++}`; + `${this.member.getAlias()}___mr_${refAliasCounter++}`; return this.member.props.sql({ identifier: (name: string) => new IdentifierRef(name), diff --git a/src/lib/semantic-layer.ts b/src/lib/semantic-layer.ts index b5f53a1..8daebe2 100644 --- a/src/lib/semantic-layer.ts +++ b/src/lib/semantic-layer.ts @@ -15,6 +15,7 @@ export type * from "./member.js"; export type * from "./model/member.js"; export * from "./query-builder/query-plan/query-context.js"; export type * from "./query-builder/query-plan/query-member.js"; +export type * from "./query-builder/query-plan.js"; export type * from "./repository/member.js"; import * as helpers from "./helpers.js"; diff --git a/src/lib/types.ts b/src/lib/types.ts index 843c3dc..d16795b 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -265,6 +265,7 @@ export type MemberProps< > = { [K in MemberType]: { type: K; + private?: boolean; description?: string; format?: MemberFormat; } & TSharedAdditionalProps & @@ -336,11 +337,13 @@ export type IntrospectionResult = Record< { memberType: "dimension" | "metric"; path: string; + alias: string; format?: AnyMemberFormat | undefined; type: MemberType | "unknown"; description?: string | undefined; isPrimaryKey: boolean; isGranularity: boolean; + isPrivate: boolean; } >;