diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3afe0e966..04897e3a6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -247,3 +247,22 @@ jobs: - name: Type-check JSDocs code blocks run: npm run test:jsdocs + + typescript-benchmarks: + name: TypeScript Benchmarks + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run benchmarks + run: npm run bench:ts diff --git a/package-lock.json b/package-lock.json index 2e0f9838f..faf6b6934 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "kysely", - "version": "0.27.4", + "version": "0.27.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kysely", - "version": "0.27.4", + "version": "0.27.5", "license": "MIT", "devDependencies": { "@arethetypeswrong/cli": "^0.17.0", + "@ark/attest": "^0.36.0", "@types/better-sqlite3": "^7.6.11", "@types/chai": "^4.3.17", "@types/chai-as-promised": "^7.1.8", @@ -113,6 +114,52 @@ "node": ">=14.17" } }, + "node_modules/@ark/attest": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@ark/attest/-/attest-0.36.0.tgz", + "integrity": "sha512-eXDHXRQyCS2VjU6AmrXKfd/XYQs5WngjFiBwvwBB5osI3aW4/fuDO7s4t4jBqh3UyZCLObjirjvlGch7bferyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ark/fs": "0.32.0", + "@ark/util": "0.32.0", + "@prettier/sync": "0.5.2", + "@typescript/analyze-trace": "0.10.1", + "@typescript/vfs": "1.6.0", + "arktype": "2.0.0-rc.32", + "prettier": "3.4.2" + }, + "bin": { + "attest": "out/cli/cli.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@ark/fs": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@ark/fs/-/fs-0.32.0.tgz", + "integrity": "sha512-BgQ4pVLk7QHxCy9CD1dKFsRbhEX/LqqGuJxDmoguRD8OUe6P3lzjn0d8dIc7z1ci1noJp7Z1eicv7R/Yzk096g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ark/schema": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.32.0.tgz", + "integrity": "sha512-7d/7xvuNEiHXkVAc2kn63xXaTG/rpUZs1zyNZiGGfDAPItPJGD572GK4UbOPBpsF1XpgK1akPxtpxs4tW+hBfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ark/util": "0.32.0" + } + }, + "node_modules/@ark/util": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.32.0.tgz", + "integrity": "sha512-gh8KwsRyUm2NXLrQ76XnF0H2NYTBWjziJnnazDd3NRsz9ouZ8Xs2k5gJNSbr/PMgg4+dGJbIoXgjESiyeZsCRQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@azure/abort-controller": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", @@ -1333,6 +1380,22 @@ "@octokit/openapi-types": "^20.0.0" } }, + "node_modules/@prettier/sync": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@prettier/sync/-/sync-0.5.2.tgz", + "integrity": "sha512-Yb569su456XNx5BsH/Vyem7xD6g/y9iLmLUzRKM1a/dhU/D7HqqvkAG72znulXlMXztbV0iiu9O5AL8K98TzZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "make-synchronized": "^0.2.8" + }, + "funding": { + "url": "https://github.com/prettier/prettier-synchronized?sponsor=1" + }, + "peerDependencies": { + "prettier": "*" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1565,6 +1628,51 @@ "@types/node": "*" } }, + "node_modules/@typescript/analyze-trace": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@typescript/analyze-trace/-/analyze-trace-0.10.1.tgz", + "integrity": "sha512-RnlSOPh14QbopGCApgkSx5UBgGda5MX1cHqp2fsqfiDyCwGL/m1jaeB9fzu7didVS81LQqGZZuxFBcg8YU8EVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "exit": "^0.1.2", + "jsonparse": "^1.3.1", + "jsonstream-next": "^3.0.0", + "p-limit": "^3.1.0", + "split2": "^3.2.2", + "treeify": "^1.1.0", + "yargs": "^16.2.0" + }, + "bin": { + "analyze-trace": "bin/analyze-trace", + "print-trace-types": "bin/print-trace-types", + "simplify-trace-types": "bin/simplify-trace-types" + } + }, + "node_modules/@typescript/analyze-trace/node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "license": "ISC", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/@typescript/vfs": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.0.tgz", + "integrity": "sha512-hvJUjNVeBMp77qPINuUvYXj4FyWeeMMKZkxEATEU3hqBAQ7qdTBCUFT7Sp0Zu0faeEtFf+ldXxMEDr/bk73ISg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + }, + "peerDependencies": { + "typescript": "*" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1675,6 +1783,17 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/arktype": { + "version": "2.0.0-rc.32", + "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.0.0-rc.32.tgz", + "integrity": "sha512-C8KqauKkLK8+IMIlWIMsK41LtCf0OKd8ukX23NtnsM3IT44ZK5V3LToa4Q5kM4tLfRe/lqy1JuPCVi03NTeazA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ark/schema": "0.32.0", + "@ark/util": "0.32.0" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2431,6 +2550,15 @@ "node": ">=0.8.x" } }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -3036,6 +3164,33 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/jsonstream-next": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonstream-next/-/jsonstream-next-3.0.0.tgz", + "integrity": "sha512-aAi6oPhdt7BKyQn1SrIIGZBt0ukKuOUE1qV6kJ3GgioSOYzsRc8z9Hfr1BVmacA/jLe9nARfmgMGgn68BqIAgg==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through2": "^4.0.2" + }, + "bin": { + "jsonstream-next": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -3230,6 +3385,16 @@ "node": ">=16.14" } }, + "node_modules/make-synchronized": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/make-synchronized/-/make-synchronized-0.2.9.tgz", + "integrity": "sha512-4wczOs8SLuEdpEvp3vGo83wh8rjJ78UsIk7DIX5fxdfmfMJGog4bQzxfvOwq7Q3yCHLC4jp1urPHIxRS/A93gA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/fisker/make-synchronized?sponsor=1" + } + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -4173,10 +4338,11 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -5100,6 +5266,16 @@ "node": ">=0.8" } }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, "node_modules/tinyglobby": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", @@ -5154,6 +5330,16 @@ "node": ">=8.0" } }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", diff --git a/package.json b/package.json index 03712fef5..9e98a2acf 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ }, "scripts": { "clean": "rm -rf dist & rm -rf test/node/dist & rm -rf test/browser/bundle.js & rm -rf helpers", + "bench:ts": "npm run build && cd ./test/ts-benchmarks && tsx ./index.ts", "test": "npm run build && npm run test:node:build && npm run test:node:run && npm run test:typings && npm run test:esmimports && npm run test:exports", "test:node:build": "tsc -p test/node", "test:node": "npm run build && npm run test:node:build && npm run test:node:run", @@ -82,6 +83,7 @@ ], "devDependencies": { "@arethetypeswrong/cli": "^0.17.0", + "@ark/attest": "^0.36.0", "@types/better-sqlite3": "^7.6.11", "@types/chai": "^4.3.17", "@types/chai-as-promised": "^7.1.8", diff --git a/src/parser/select-from-parser.ts b/src/parser/select-from-parser.ts index 521daafd5..385360ece 100644 --- a/src/parser/select-from-parser.ts +++ b/src/parser/select-from-parser.ts @@ -11,19 +11,18 @@ export type SelectFrom< DB, TB extends keyof DB, TE extends TableExpressionOrList, -> = - TE extends ReadonlyArray - ? SelectQueryBuilder, FromTables, {}> - : TE extends keyof DB & string - ? // This branch creates a good-looking type for the most common case: - // selectFrom('person') --> SelectQueryBuilder. - // ExtractTableAlias is needed for the case where DB == any. Without it: - // selectFrom('person as p') --> SelectQueryBuilder - SelectQueryBuilder, {}> - : // This branch creates a good-looking type for common aliased case: - // selectFrom('person as p') --> SelectQueryBuilder. - TE extends `${infer T} as ${infer A}` - ? T extends keyof DB - ? SelectQueryBuilder, TB | A, {}> - : never - : SelectQueryBuilder, FromTables, {}> +> = TE extends keyof DB & string + ? // This branch creates a good-looking type for the most common case: + // selectFrom('person') --> SelectQueryBuilder. + // ExtractTableAlias is needed for the case where DB == any. Without it: + // selectFrom('person as p') --> SelectQueryBuilder + SelectQueryBuilder, {}> + : // This branch creates a good-looking type for common aliased case: + // selectFrom('person as p') --> SelectQueryBuilder. + TE extends `${infer T} as ${infer A}` + ? T extends keyof DB + ? SelectQueryBuilder, TB | A, {}> + : never + : TE extends ReadonlyArray + ? SelectQueryBuilder, FromTables, {}> + : SelectQueryBuilder, FromTables, {}> diff --git a/test/ts-benchmarks/index.ts b/test/ts-benchmarks/index.ts new file mode 100644 index 000000000..81d8c59b6 --- /dev/null +++ b/test/ts-benchmarks/index.ts @@ -0,0 +1 @@ +import './selectFrom.bench.js' diff --git a/test/ts-benchmarks/selectFrom.bench.ts b/test/ts-benchmarks/selectFrom.bench.ts new file mode 100644 index 000000000..e20f4c032 --- /dev/null +++ b/test/ts-benchmarks/selectFrom.bench.ts @@ -0,0 +1,95 @@ +import { bench } from '@ark/attest' +import type { DB } from '../typings/test-d/huge-db.test-d' +import type { Kysely } from '../../dist/esm/index.js' + +declare const kysely: Kysely +declare const knex: Kysely + +bench.baseline(() => {}) + +bench('kysely.selectFrom(table)', () => { + return kysely.selectFrom('table_fff4c6195261874920bc7ce92d67d2c2') +}).types([388, 'instantiations']) + +bench('kysely.selectFrom(~table)', () => { + return kysely.selectFrom('my_table2') +}).types([9314, 'instantiations']) + +bench('kysely.selectFrom(table as alias)', () => { + return kysely.selectFrom('my_table as mt') +}).types([401, 'instantiations']) + +bench('kysely.selectFrom([table])', () => { + return kysely.selectFrom(['my_table']) +}).types([413, 'instantiations']) + +bench('kysely.selectFrom([~table])', () => { + return kysely.selectFrom(['my_table2']) +}).types([9364, 'instantiations']) + +bench('kysely.selectFrom([table as alias])', () => { + return kysely.selectFrom(['my_table as mt']) +}).types([413, 'instantiations']) + +bench('kysely.selectFrom([table, table])', () => { + return kysely.selectFrom([ + 'my_table', + 'table_000a8a0cb7f265a624c851d3e7f8b946', + ]) +}).types([413, 'instantiations']) + +bench('kysely.selectFrom([table, ~table])', () => { + return kysely.selectFrom([ + 'my_table', + 'table_000a8a0cb7f265a624c851d3e7f8b9462', + ]) +}).types([9367, 'instantiations']) + +bench('kysely.selectFrom([table as alias, table as alias])', () => { + return kysely.selectFrom([ + 'my_table as mt', + 'table_000a8a0cb7f265a624c851d3e7f8b946 as t', + ]) +}).types([413, 'instantiations']) + +bench('knex.selectFrom(table)', () => { + return knex.selectFrom('table_fff4c6195261874920bc7ce92d67d2c2') +}).types([140, 'instantiations']) + +bench('knex.selectFrom(~table)', () => { + return knex.selectFrom('my_table2') +}).types([140, 'instantiations']) + +bench('knex.selectFrom(table as alias)', () => { + return knex.selectFrom('my_table as mt') +}).types([140, 'instantiations']) + +bench('knex.selectFrom([table])', () => { + return knex.selectFrom(['my_table']) +}).types([165, 'instantiations']) + +bench('knex.selectFrom([~table])', () => { + return knex.selectFrom(['my_table2']) +}).types([165, 'instantiations']) + +bench('knex.selectFrom([table as alias])', () => { + return knex.selectFrom(['my_table as mt']) +}).types([165, 'instantiations']) + +bench('knex.selectFrom([table, table])', () => { + return knex.selectFrom(['my_table', 'table_000a8a0cb7f265a624c851d3e7f8b946']) +}).types([165, 'instantiations']) + +bench('knex.selectFrom([table, ~table])', () => { + return knex.selectFrom([ + 'my_table', + 'table_000a8a0cb7f265a624c851d3e7f8b9462', + ]) +}).types([165, 'instantiations']) + +bench('knex.selectFrom([table as alias, table as alias])', () => { + return knex.selectFrom([ + 'my_table as mt', + 'table_000a8a0cb7f265a624c851d3e7f8b946 as t', + ]) +}).types([165, 'instantiations']) diff --git a/test/ts-benchmarks/tsconfig.json b/test/ts-benchmarks/tsconfig.json new file mode 100644 index 000000000..348736603 --- /dev/null +++ b/test/ts-benchmarks/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig-base.json", + "include": ["**/*.ts"], +}