├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ ├── pull_request.yml │ └── pull_request_lint.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .yarn ├── releases │ └── yarn-4.0.0-rc.32.cjs └── sdks │ ├── eslint │ ├── bin │ │ └── eslint.js │ ├── lib │ │ └── api.js │ └── package.json │ ├── integrations.yml │ ├── prettier │ ├── index.js │ └── package.json │ └── typescript │ ├── bin │ ├── tsc │ └── tsserver │ ├── lib │ ├── tsc.js │ ├── tsserver.js │ ├── tsserverlibrary.js │ └── typescript.js │ └── package.json ├── .yarnrc.yml ├── LICENSE ├── README.md ├── babel.config.js ├── main.d.ts ├── main.js ├── main.js.map ├── main.test.ts ├── main.ts ├── package.json ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ESLint configuration. 3 | * 4 | * @see https://eslint.org/docs/user-guide/configuring 5 | * @type {import("eslint").Linter.Config} 6 | * 7 | * @copyright 2021-present Kriasoft (https://git.io/JOevo) 8 | */ 9 | 10 | module.exports = { 11 | root: true, 12 | 13 | env: { 14 | es6: true, 15 | node: true, 16 | }, 17 | 18 | extends: ["eslint:recommended", "prettier"], 19 | 20 | parserOptions: { 21 | ecmaVersion: 2020, 22 | }, 23 | 24 | overrides: [ 25 | { 26 | files: ["*.ts"], 27 | parser: "@typescript-eslint/parser", 28 | extends: ["plugin:@typescript-eslint/recommended"], 29 | plugins: ["@typescript-eslint"], 30 | parserOptions: { 31 | sourceType: "module", 32 | warnOnUnsupportedTypeScriptVersion: true, 33 | }, 34 | }, 35 | { 36 | files: ["*.test.js", "*.test.ts"], 37 | env: { 38 | jest: true, 39 | }, 40 | }, 41 | ], 42 | 43 | ignorePatterns: [ 44 | "/.cache", 45 | "/.git", 46 | "/.yarn", 47 | "*.d.ts", 48 | "*.js", 49 | "!.eslintrc.js", 50 | "!babel.config.js", 51 | ], 52 | }; 53 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: koistya 4 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions workflow 2 | # https://help.github.com/actions 3 | 4 | name: PR 5 | 6 | on: [pull_request] 7 | 8 | env: 9 | CI: true 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/cache@v2 17 | with: 18 | path: | 19 | ${{ github.workspace }}/.yarn/cache 20 | ${{ github.workspace }}/.yarn/unplugged 21 | key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} 22 | restore-keys: ${{ runner.os }}-yarn- 23 | - run: yarn install 24 | - run: yarn prettier --check . 25 | - run: yarn lint --no-cache 26 | -------------------------------------------------------------------------------- /.github/workflows/pull_request_lint.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions workflow 2 | # https://help.github.com/actions 3 | 4 | name: "conventionalcommits.org" 5 | 6 | on: 7 | pull_request: 8 | types: 9 | - opened 10 | - edited 11 | - synchronize 12 | 13 | jobs: 14 | main: 15 | name: lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: amannn/action-semantic-pull-request@v4 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Yarn package manager 2 | # https://yarnpkg.com/getting-started/qa/#which-files-should-be-gitignored 3 | .yarn/* 4 | !.yarn/patches 5 | !.yarn/releases 6 | !.yarn/plugins 7 | !.yarn/sdks 8 | !.yarn/versions 9 | .pnp.* 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode 3 | .yarn 4 | .eslintrc.js 5 | .pnp.* 6 | .prettierignore 7 | .yarnrc.yml 8 | babel.config.js 9 | main.test.ts 10 | main.ts 11 | tsconfig.json -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.yarn 2 | /.pnp.* 3 | /main.js 4 | /tsconfig.json -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-node", 9 | "request": "launch", 10 | "name": "Jest", 11 | "runtimeExecutable": "yarn", 12 | "runtimeArgs": ["jest"], 13 | "skipFiles": ["/**"] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.organizeImports": true 4 | }, 5 | "editor.tabSize": 2, 6 | "editor.formatOnSave": true, 7 | "editor.defaultFormatter": "esbenp.prettier-vscode", 8 | "files.exclude": { 9 | ".gitignore": true, 10 | ".npmignore": true, 11 | ".pnp.*": true, 12 | ".prettierignore": true, 13 | ".yarn": true, 14 | ".yarnrc.yml": true, 15 | "yarn.lock": true 16 | }, 17 | "search.exclude": { 18 | "**/.yarn": true, 19 | "**/.pnp.*": true 20 | }, 21 | "eslint.nodePath": ".yarn/sdks", 22 | "prettier.prettierPath": ".yarn/sdks/prettier/index.js", 23 | "typescript.tsdk": ".yarn/sdks/typescript/lib", 24 | "typescript.enablePromptUseWorkspaceTsdk": true 25 | } 26 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/bin/eslint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint/bin/eslint.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint/bin/eslint.js your application uses 20 | module.exports = absRequire(`eslint/bin/eslint.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/lib/api.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint your application uses 20 | module.exports = absRequire(`eslint`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint", 3 | "version": "8.28.0-sdk", 4 | "main": "./lib/api.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/integrations.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by @yarnpkg/sdks. 2 | # Manual changes might be lost! 3 | 4 | integrations: 5 | - vscode 6 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require prettier/index.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real prettier/index.js your application uses 20 | module.exports = absRequire(`prettier/index.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier", 3 | "version": "2.8.0-sdk", 4 | "main": "./index.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsc 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsc your application uses 20 | module.exports = absRequire(`typescript/bin/tsc`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsserver 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsserver your application uses 20 | module.exports = absRequire(`typescript/bin/tsserver`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/tsc.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/tsc.js your application uses 20 | module.exports = absRequire(`typescript/lib/tsc.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsserver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | const moduleWrapper = tsserver => { 13 | if (!process.versions.pnp) { 14 | return tsserver; 15 | } 16 | 17 | const {isAbsolute} = require(`path`); 18 | const pnpApi = require(`pnpapi`); 19 | 20 | const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); 21 | const isPortal = str => str.startsWith("portal:/"); 22 | const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); 23 | 24 | const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { 25 | return `${locator.name}@${locator.reference}`; 26 | })); 27 | 28 | // VSCode sends the zip paths to TS using the "zip://" prefix, that TS 29 | // doesn't understand. This layer makes sure to remove the protocol 30 | // before forwarding it to TS, and to add it back on all returned paths. 31 | 32 | function toEditorPath(str) { 33 | // We add the `zip:` prefix to both `.zip/` paths and virtual paths 34 | if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { 35 | // We also take the opportunity to turn virtual paths into physical ones; 36 | // this makes it much easier to work with workspaces that list peer 37 | // dependencies, since otherwise Ctrl+Click would bring us to the virtual 38 | // file instances instead of the real ones. 39 | // 40 | // We only do this to modules owned by the the dependency tree roots. 41 | // This avoids breaking the resolution when jumping inside a vendor 42 | // with peer dep (otherwise jumping into react-dom would show resolution 43 | // errors on react). 44 | // 45 | const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; 46 | if (resolved) { 47 | const locator = pnpApi.findPackageLocator(resolved); 48 | if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { 49 | str = resolved; 50 | } 51 | } 52 | 53 | str = normalize(str); 54 | 55 | if (str.match(/\.zip\//)) { 56 | switch (hostInfo) { 57 | // Absolute VSCode `Uri.fsPath`s need to start with a slash. 58 | // VSCode only adds it automatically for supported schemes, 59 | // so we have to do it manually for the `zip` scheme. 60 | // The path needs to start with a caret otherwise VSCode doesn't handle the protocol 61 | // 62 | // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 63 | // 64 | // 2021-10-08: VSCode changed the format in 1.61. 65 | // Before | ^zip:/c:/foo/bar.zip/package.json 66 | // After | ^/zip//c:/foo/bar.zip/package.json 67 | // 68 | // 2022-04-06: VSCode changed the format in 1.66. 69 | // Before | ^/zip//c:/foo/bar.zip/package.json 70 | // After | ^/zip/c:/foo/bar.zip/package.json 71 | // 72 | // 2022-05-06: VSCode changed the format in 1.68 73 | // Before | ^/zip/c:/foo/bar.zip/package.json 74 | // After | ^/zip//c:/foo/bar.zip/package.json 75 | // 76 | case `vscode <1.61`: { 77 | str = `^zip:${str}`; 78 | } break; 79 | 80 | case `vscode <1.66`: { 81 | str = `^/zip/${str}`; 82 | } break; 83 | 84 | case `vscode <1.68`: { 85 | str = `^/zip${str}`; 86 | } break; 87 | 88 | case `vscode`: { 89 | str = `^/zip/${str}`; 90 | } break; 91 | 92 | // To make "go to definition" work, 93 | // We have to resolve the actual file system path from virtual path 94 | // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) 95 | case `coc-nvim`: { 96 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 97 | str = resolve(`zipfile:${str}`); 98 | } break; 99 | 100 | // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) 101 | // We have to resolve the actual file system path from virtual path, 102 | // everything else is up to neovim 103 | case `neovim`: { 104 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 105 | str = `zipfile://${str}`; 106 | } break; 107 | 108 | default: { 109 | str = `zip:${str}`; 110 | } break; 111 | } 112 | } 113 | } 114 | 115 | return str; 116 | } 117 | 118 | function fromEditorPath(str) { 119 | switch (hostInfo) { 120 | case `coc-nvim`: { 121 | str = str.replace(/\.zip::/, `.zip/`); 122 | // The path for coc-nvim is in format of //zipfile://.yarn/... 123 | // So in order to convert it back, we use .* to match all the thing 124 | // before `zipfile:` 125 | return process.platform === `win32` 126 | ? str.replace(/^.*zipfile:\//, ``) 127 | : str.replace(/^.*zipfile:/, ``); 128 | } break; 129 | 130 | case `neovim`: { 131 | str = str.replace(/\.zip::/, `.zip/`); 132 | // The path for neovim is in format of zipfile:////.yarn/... 133 | return str.replace(/^zipfile:\/\//, ``); 134 | } break; 135 | 136 | case `vscode`: 137 | default: { 138 | return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) 139 | } break; 140 | } 141 | } 142 | 143 | // Force enable 'allowLocalPluginLoads' 144 | // TypeScript tries to resolve plugins using a path relative to itself 145 | // which doesn't work when using the global cache 146 | // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 147 | // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but 148 | // TypeScript already does local loads and if this code is running the user trusts the workspace 149 | // https://github.com/microsoft/vscode/issues/45856 150 | const ConfiguredProject = tsserver.server.ConfiguredProject; 151 | const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; 152 | ConfiguredProject.prototype.enablePluginsWithOptions = function() { 153 | this.projectService.allowLocalPluginLoads = true; 154 | return originalEnablePluginsWithOptions.apply(this, arguments); 155 | }; 156 | 157 | // And here is the point where we hijack the VSCode <-> TS communications 158 | // by adding ourselves in the middle. We locate everything that looks 159 | // like an absolute path of ours and normalize it. 160 | 161 | const Session = tsserver.server.Session; 162 | const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; 163 | let hostInfo = `unknown`; 164 | 165 | Object.assign(Session.prototype, { 166 | onMessage(/** @type {string | object} */ message) { 167 | const isStringMessage = typeof message === 'string'; 168 | const parsedMessage = isStringMessage ? JSON.parse(message) : message; 169 | 170 | if ( 171 | parsedMessage != null && 172 | typeof parsedMessage === `object` && 173 | parsedMessage.arguments && 174 | typeof parsedMessage.arguments.hostInfo === `string` 175 | ) { 176 | hostInfo = parsedMessage.arguments.hostInfo; 177 | if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { 178 | const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( 179 | // The RegExp from https://semver.org/ but without the caret at the start 180 | /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ 181 | ) ?? []).map(Number) 182 | 183 | if (major === 1) { 184 | if (minor < 61) { 185 | hostInfo += ` <1.61`; 186 | } else if (minor < 66) { 187 | hostInfo += ` <1.66`; 188 | } else if (minor < 68) { 189 | hostInfo += ` <1.68`; 190 | } 191 | } 192 | } 193 | } 194 | 195 | const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { 196 | return typeof value === 'string' ? fromEditorPath(value) : value; 197 | }); 198 | 199 | return originalOnMessage.call( 200 | this, 201 | isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) 202 | ); 203 | }, 204 | 205 | send(/** @type {any} */ msg) { 206 | return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { 207 | return typeof value === `string` ? toEditorPath(value) : value; 208 | }))); 209 | } 210 | }); 211 | 212 | return tsserver; 213 | }; 214 | 215 | if (existsSync(absPnpApiPath)) { 216 | if (!process.versions.pnp) { 217 | // Setup the environment to be able to require typescript/lib/tsserver.js 218 | require(absPnpApiPath).setup(); 219 | } 220 | } 221 | 222 | // Defer to the real typescript/lib/tsserver.js your application uses 223 | module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); 224 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsserverlibrary.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | const moduleWrapper = tsserver => { 13 | if (!process.versions.pnp) { 14 | return tsserver; 15 | } 16 | 17 | const {isAbsolute} = require(`path`); 18 | const pnpApi = require(`pnpapi`); 19 | 20 | const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); 21 | const isPortal = str => str.startsWith("portal:/"); 22 | const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); 23 | 24 | const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { 25 | return `${locator.name}@${locator.reference}`; 26 | })); 27 | 28 | // VSCode sends the zip paths to TS using the "zip://" prefix, that TS 29 | // doesn't understand. This layer makes sure to remove the protocol 30 | // before forwarding it to TS, and to add it back on all returned paths. 31 | 32 | function toEditorPath(str) { 33 | // We add the `zip:` prefix to both `.zip/` paths and virtual paths 34 | if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { 35 | // We also take the opportunity to turn virtual paths into physical ones; 36 | // this makes it much easier to work with workspaces that list peer 37 | // dependencies, since otherwise Ctrl+Click would bring us to the virtual 38 | // file instances instead of the real ones. 39 | // 40 | // We only do this to modules owned by the the dependency tree roots. 41 | // This avoids breaking the resolution when jumping inside a vendor 42 | // with peer dep (otherwise jumping into react-dom would show resolution 43 | // errors on react). 44 | // 45 | const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; 46 | if (resolved) { 47 | const locator = pnpApi.findPackageLocator(resolved); 48 | if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { 49 | str = resolved; 50 | } 51 | } 52 | 53 | str = normalize(str); 54 | 55 | if (str.match(/\.zip\//)) { 56 | switch (hostInfo) { 57 | // Absolute VSCode `Uri.fsPath`s need to start with a slash. 58 | // VSCode only adds it automatically for supported schemes, 59 | // so we have to do it manually for the `zip` scheme. 60 | // The path needs to start with a caret otherwise VSCode doesn't handle the protocol 61 | // 62 | // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 63 | // 64 | // 2021-10-08: VSCode changed the format in 1.61. 65 | // Before | ^zip:/c:/foo/bar.zip/package.json 66 | // After | ^/zip//c:/foo/bar.zip/package.json 67 | // 68 | // 2022-04-06: VSCode changed the format in 1.66. 69 | // Before | ^/zip//c:/foo/bar.zip/package.json 70 | // After | ^/zip/c:/foo/bar.zip/package.json 71 | // 72 | // 2022-05-06: VSCode changed the format in 1.68 73 | // Before | ^/zip/c:/foo/bar.zip/package.json 74 | // After | ^/zip//c:/foo/bar.zip/package.json 75 | // 76 | case `vscode <1.61`: { 77 | str = `^zip:${str}`; 78 | } break; 79 | 80 | case `vscode <1.66`: { 81 | str = `^/zip/${str}`; 82 | } break; 83 | 84 | case `vscode <1.68`: { 85 | str = `^/zip${str}`; 86 | } break; 87 | 88 | case `vscode`: { 89 | str = `^/zip/${str}`; 90 | } break; 91 | 92 | // To make "go to definition" work, 93 | // We have to resolve the actual file system path from virtual path 94 | // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) 95 | case `coc-nvim`: { 96 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 97 | str = resolve(`zipfile:${str}`); 98 | } break; 99 | 100 | // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) 101 | // We have to resolve the actual file system path from virtual path, 102 | // everything else is up to neovim 103 | case `neovim`: { 104 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 105 | str = `zipfile://${str}`; 106 | } break; 107 | 108 | default: { 109 | str = `zip:${str}`; 110 | } break; 111 | } 112 | } 113 | } 114 | 115 | return str; 116 | } 117 | 118 | function fromEditorPath(str) { 119 | switch (hostInfo) { 120 | case `coc-nvim`: { 121 | str = str.replace(/\.zip::/, `.zip/`); 122 | // The path for coc-nvim is in format of //zipfile://.yarn/... 123 | // So in order to convert it back, we use .* to match all the thing 124 | // before `zipfile:` 125 | return process.platform === `win32` 126 | ? str.replace(/^.*zipfile:\//, ``) 127 | : str.replace(/^.*zipfile:/, ``); 128 | } break; 129 | 130 | case `neovim`: { 131 | str = str.replace(/\.zip::/, `.zip/`); 132 | // The path for neovim is in format of zipfile:////.yarn/... 133 | return str.replace(/^zipfile:\/\//, ``); 134 | } break; 135 | 136 | case `vscode`: 137 | default: { 138 | return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) 139 | } break; 140 | } 141 | } 142 | 143 | // Force enable 'allowLocalPluginLoads' 144 | // TypeScript tries to resolve plugins using a path relative to itself 145 | // which doesn't work when using the global cache 146 | // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 147 | // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but 148 | // TypeScript already does local loads and if this code is running the user trusts the workspace 149 | // https://github.com/microsoft/vscode/issues/45856 150 | const ConfiguredProject = tsserver.server.ConfiguredProject; 151 | const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; 152 | ConfiguredProject.prototype.enablePluginsWithOptions = function() { 153 | this.projectService.allowLocalPluginLoads = true; 154 | return originalEnablePluginsWithOptions.apply(this, arguments); 155 | }; 156 | 157 | // And here is the point where we hijack the VSCode <-> TS communications 158 | // by adding ourselves in the middle. We locate everything that looks 159 | // like an absolute path of ours and normalize it. 160 | 161 | const Session = tsserver.server.Session; 162 | const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; 163 | let hostInfo = `unknown`; 164 | 165 | Object.assign(Session.prototype, { 166 | onMessage(/** @type {string | object} */ message) { 167 | const isStringMessage = typeof message === 'string'; 168 | const parsedMessage = isStringMessage ? JSON.parse(message) : message; 169 | 170 | if ( 171 | parsedMessage != null && 172 | typeof parsedMessage === `object` && 173 | parsedMessage.arguments && 174 | typeof parsedMessage.arguments.hostInfo === `string` 175 | ) { 176 | hostInfo = parsedMessage.arguments.hostInfo; 177 | if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { 178 | const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( 179 | // The RegExp from https://semver.org/ but without the caret at the start 180 | /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ 181 | ) ?? []).map(Number) 182 | 183 | if (major === 1) { 184 | if (minor < 61) { 185 | hostInfo += ` <1.61`; 186 | } else if (minor < 66) { 187 | hostInfo += ` <1.66`; 188 | } else if (minor < 68) { 189 | hostInfo += ` <1.68`; 190 | } 191 | } 192 | } 193 | } 194 | 195 | const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { 196 | return typeof value === 'string' ? fromEditorPath(value) : value; 197 | }); 198 | 199 | return originalOnMessage.call( 200 | this, 201 | isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) 202 | ); 203 | }, 204 | 205 | send(/** @type {any} */ msg) { 206 | return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { 207 | return typeof value === `string` ? toEditorPath(value) : value; 208 | }))); 209 | } 210 | }); 211 | 212 | return tsserver; 213 | }; 214 | 215 | if (existsSync(absPnpApiPath)) { 216 | if (!process.versions.pnp) { 217 | // Setup the environment to be able to require typescript/lib/tsserverlibrary.js 218 | require(absPnpApiPath).setup(); 219 | } 220 | } 221 | 222 | // Defer to the real typescript/lib/tsserverlibrary.js your application uses 223 | module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); 224 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/typescript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/typescript.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/typescript.js your application uses 20 | module.exports = absRequire(`typescript/lib/typescript.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "4.9.3-sdk", 4 | "main": "./lib/typescript.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: .yarn/releases/yarn-4.0.0-rc.32.cjs 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021-present Kriasoft 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Knex.js types generator 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/knex-types?style=flat-square)](https://www.npmjs.com/package/knex-types) 4 | [![NPM Downloads](https://img.shields.io/npm/dm/knex-types?style=flat-square)](https://www.npmjs.com/package/knex-types) 5 | [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg?style=flat-square)](http://www.typescriptlang.org/) 6 | [![Donate](https://img.shields.io/badge/dynamic/json?color=%23ff424d&label=Patreon&style=flat-square&query=data.attributes.patron_count&suffix=%20patrons&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F233228)](http://patreon.com/koistya) 7 | [![Discord](https://img.shields.io/discord/643523529131950086?label=Chat&style=flat-square)](https://discord.gg/bSsv7XM) 8 | 9 | An utility module for [Knex.js](https://knexjs.org/) that generates TypeScript 10 | definitions (types) from a PostgreSQL database schema. 11 | 12 | ``` 13 | $ npm install knex 14 | $ npm install knex-types --dev 15 | ``` 16 | 17 | ## Usage Example 18 | 19 | ```js 20 | const { knex } = require("knex"); 21 | const { updateTypes } = require("knex-types"); 22 | 23 | const db = knex(require("./knexfile")); 24 | 25 | updateTypes(db, { output: "./types.ts" }).catch((err) => { 26 | console.error(err); 27 | process.exit(1); 28 | }); 29 | ``` 30 | 31 | Find an example of generated types in [`./main.test.ts`](./main.test.ts). 32 | 33 | ## Related Projects 34 | 35 | - [GraphQL API Starter Kit](https://github.com/kriasoft/graphql-starter) — monorepo template, pre-configured with TypeScript, GraphQL.js, React, and Relay 36 | - [Node.js API Starter Kit](https://github.com/kriasoft/node-starter-kit) — Node.js project template (PostgreSQL, Knex, OAuth 2.0, emails, Cloud Functions) 37 | 38 | ## How to Contribute 39 | 40 | Please create a [PR](https://docs.github.com/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) or send me a message on [Discord](https://discord.gg/bSsv7XM). 41 | 42 | ## License 43 | 44 | Copyright © 2021-present Kriasoft. This source code is licensed under the MIT license found in the 45 | [LICENSE](https://github.com/kriasoft/knex-types/blob/main/LICENSE) file. 46 | 47 | --- 48 | 49 | Made with ♥ by Konstantin Tarkus ([@koistya](https://twitter.com/koistya), [blog](https://medium.com/@koistya)) 50 | and [contributors](https://github.com/kriasoft/knex-types/graphs/contributors). 51 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Babel configuration. 3 | * 4 | * @see https://babeljs.io/docs/en/options 5 | * @copyright 2021-present Kriasoft (https://git.io/JmNtC) 6 | * 7 | * @type {import("@babel/core").ConfigAPI} 8 | */ 9 | 10 | module.exports = function config(api) { 11 | return { 12 | presets: [["@babel/preset-env", { targets: { node: "10" } }]], 13 | 14 | plugins: [ 15 | ["@babel/plugin-proposal-class-properties"], 16 | [ 17 | "babel-plugin-import", 18 | { 19 | libraryName: "lodash", 20 | libraryDirectory: "", 21 | camel2DashComponentName: false, 22 | }, 23 | ], 24 | ], 25 | 26 | ignore: api.env() === "test" ? [] : ["*.d.ts", "*.test.ts"], 27 | 28 | sourceMaps: api.env() === "production", 29 | 30 | overrides: [ 31 | { 32 | test: /\.ts$/, 33 | presets: ["@babel/preset-typescript"], 34 | }, 35 | ], 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /main.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Knex } from "knex"; 3 | import type { Writable } from "stream"; 4 | export type Options = { 5 | /** 6 | * Filename or output stream where the type definitions needs to be written. 7 | */ 8 | output: Writable | string; 9 | /** 10 | * Name overrides for enums, classes, and fields. 11 | * 12 | * @example 13 | * overrides: { 14 | * "identity_provider.linkedin": "LinkedIn" 15 | * } 16 | */ 17 | overrides?: Record; 18 | prefix?: string; 19 | suffix?: string; 20 | /** 21 | * Schemas that should be included/excluded when generating types. 22 | * 23 | * By default if this is null/not specified, "public" will be the only schema added, but if this property 24 | * is specified, public will be excluded by default and has to be included in the list to be added to the output. 25 | * If a schema is added by its name, i.e. 'log' all tables from that schema will be added. 26 | * If a schema name is added with an exclamation mark it will be ignored, i.e. "!log". 27 | * 28 | * The table-type will be prefixed by its schema name, the table log.message will become LogMessage. 29 | * 30 | * @example 31 | * // This will include "public" and "log", but exclude the "auth" schema. 32 | * schema: ["public", "log", "!auth"] 33 | * @default 34 | * "public" 35 | */ 36 | schema?: string[] | string; 37 | /** 38 | * A comma separated list or an array of tables that should be excluded when 39 | * generating types. 40 | * 41 | * @example 42 | * exclude: ["migration", "migration_lock"] 43 | */ 44 | exclude?: string[] | string; 45 | }; 46 | /** 47 | * Generates TypeScript definitions (types) from a PostgreSQL database schema. 48 | */ 49 | export declare function updateTypes(db: Knex, options: Options): Promise; 50 | export declare function getType( 51 | udt: string, 52 | customTypes: Map, 53 | defaultValue: string | null 54 | ): string; 55 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.getType = getType; 7 | exports.updateTypes = updateTypes; 8 | var _camelCase2 = _interopRequireDefault(require("lodash/camelCase")); 9 | var _upperFirst2 = _interopRequireDefault(require("lodash/upperFirst")); 10 | var _fs = _interopRequireDefault(require("fs")); 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | /* SPDX-FileCopyrightText: 2016-present Kriasoft */ 13 | /* SPDX-License-Identifier: MIT */ 14 | 15 | /** 16 | * Generates TypeScript definitions (types) from a PostgreSQL database schema. 17 | */ 18 | async function updateTypes(db, options) { 19 | var _options$overrides, _ref, _ref2; 20 | const overrides = (_options$overrides = options.overrides) !== null && _options$overrides !== void 0 ? _options$overrides : {}; 21 | const output = typeof options.output === "string" ? _fs.default.createWriteStream(options.output, { 22 | encoding: "utf-8" 23 | }) : options.output; 24 | ["// The TypeScript definitions below are automatically generated.\n", "// Do not touch them, or risk, your modifications being lost.\n\n"].forEach(line => output.write(line)); 25 | const schema = (_ref = typeof options.schema === "string" ? options.schema.split(",").map(x => x.trim()) : options.schema) !== null && _ref !== void 0 ? _ref : ["public"]; 26 | 27 | // Schemas to include or exclude 28 | const [includeSchemas, excludeSchemas] = schema.reduce((acc, s) => acc[+s.startsWith("!")].push(s) && acc, [[], []]); 29 | 30 | // Tables to exclude 31 | const exclude = (_ref2 = typeof options.exclude === "string" ? options.exclude.split(",").map(x => x.trim()) : options.exclude) !== null && _ref2 !== void 0 ? _ref2 : []; 32 | if (options.prefix) { 33 | output.write(options.prefix); 34 | output.write("\n\n"); 35 | } 36 | try { 37 | // Fetch the list of custom enum types 38 | const enums = await db.table("pg_type").join("pg_enum", "pg_enum.enumtypid", "pg_type.oid").orderBy("pg_type.typname").orderBy("pg_enum.enumsortorder").select("pg_type.typname as key", "pg_enum.enumlabel as value"); 39 | 40 | // Construct TypeScript enum types 41 | enums.forEach((x, i) => { 42 | var _overrides; 43 | // The first line of enum declaration 44 | if (!(enums[i - 1] && enums[i - 1].key === x.key)) { 45 | var _overrides$x$key; 46 | const enumName = (_overrides$x$key = overrides[x.key]) !== null && _overrides$x$key !== void 0 ? _overrides$x$key : (0, _upperFirst2.default)((0, _camelCase2.default)(x.key)); 47 | output.write(`export enum ${enumName} {\n`); 48 | } 49 | 50 | // Enum body 51 | const key = (_overrides = overrides[`${x.key}.${x.value}`]) !== null && _overrides !== void 0 ? _overrides : (0, _upperFirst2.default)((0, _camelCase2.default)(x.value.replace(/[.-]/g, "_"))); 52 | output.write(` ${key} = "${x.value}",\n`); 53 | 54 | // The closing line 55 | if (!(enums[i + 1] && enums[i + 1].key === x.key)) { 56 | output.write("}\n\n"); 57 | } 58 | }); 59 | const enumsMap = new Map(enums.map(x => { 60 | var _overrides$x$key2; 61 | return [x.key, (_overrides$x$key2 = overrides[x.key]) !== null && _overrides$x$key2 !== void 0 ? _overrides$x$key2 : (0, _upperFirst2.default)((0, _camelCase2.default)(x.key))]; 62 | })); 63 | 64 | // Fetch the list of tables/columns 65 | const columns = await db.withSchema("information_schema").table("columns").whereIn("table_schema", includeSchemas).whereNotIn("table_schema", excludeSchemas).whereNotIn("table_name", exclude).orderBy("table_schema").orderBy("table_name").orderBy("ordinal_position").select("table_schema as schema", "table_name as table", "column_name as column", db.raw("(is_nullable = 'YES') as nullable"), "column_default as default", "data_type as type", "udt_name as udt"); 66 | 67 | // The list of database tables as enum 68 | output.write("export enum Table {\n"); 69 | const tableSet = new Set(columns.map(x => { 70 | const schema = x.schema !== "public" ? `${x.schema}.` : ""; 71 | return `${schema}${x.table}`; 72 | })); 73 | Array.from(tableSet).forEach(value => { 74 | var _overrides$value; 75 | const key = (_overrides$value = overrides[value]) !== null && _overrides$value !== void 0 ? _overrides$value : (0, _upperFirst2.default)((0, _camelCase2.default)(value)); 76 | output.write(` ${key} = "${value}",\n`); 77 | }); 78 | output.write("}\n\n"); 79 | // The list of tables as type 80 | output.write("export type Tables = {\n"); 81 | Array.from(tableSet).forEach(key => { 82 | var _overrides$key; 83 | const value = (_overrides$key = overrides[key]) !== null && _overrides$key !== void 0 ? _overrides$key : (0, _upperFirst2.default)((0, _camelCase2.default)(key)); 84 | output.write(` "${key}": ${value},\n`); 85 | }); 86 | output.write("};\n\n"); 87 | 88 | // Construct TypeScript db record types 89 | columns.forEach((x, i) => { 90 | if (!(columns[i - 1] && columns[i - 1].table === x.table)) { 91 | var _overrides$x$table; 92 | const tableName = (_overrides$x$table = overrides[x.table]) !== null && _overrides$x$table !== void 0 ? _overrides$x$table : (0, _upperFirst2.default)((0, _camelCase2.default)(x.table)); 93 | const schemaName = x.schema !== "public" ? (0, _upperFirst2.default)((0, _camelCase2.default)(x.schema)) : ""; 94 | output.write(`export type ${schemaName}${tableName} = {\n`); 95 | } 96 | let type = x.type === "ARRAY" ? `${getType(x.udt.substring(1), enumsMap, x.default)}[]` : getType(x.udt, enumsMap, x.default); 97 | if (x.nullable) { 98 | type += " | null"; 99 | } 100 | output.write(` ${sanitize(x.column)}: ${type};\n`); 101 | if (!(columns[i + 1] && columns[i + 1].table === x.table)) { 102 | output.write("};\n\n"); 103 | } 104 | }); 105 | if (options.suffix) { 106 | output.write(options.suffix); 107 | output.write("\n"); 108 | } 109 | } finally { 110 | output.end(); 111 | db.destroy(); 112 | } 113 | } 114 | function getType(udt, customTypes, defaultValue) { 115 | var _customTypes$get; 116 | switch (udt) { 117 | case "bool": 118 | return "boolean"; 119 | case "text": 120 | case "citext": 121 | case "money": 122 | case "numeric": 123 | case "int8": 124 | case "char": 125 | case "character": 126 | case "bpchar": 127 | case "varchar": 128 | case "time": 129 | case "tsquery": 130 | case "tsvector": 131 | case "uuid": 132 | case "xml": 133 | case "cidr": 134 | case "inet": 135 | case "macaddr": 136 | return "string"; 137 | case "smallint": 138 | case "integer": 139 | case "int": 140 | case "int2": 141 | case "int4": 142 | case "real": 143 | case "float": 144 | case "float4": 145 | case "float8": 146 | return "number"; 147 | case "date": 148 | case "timestamp": 149 | case "timestamptz": 150 | return "Date"; 151 | case "json": 152 | case "jsonb": 153 | if (defaultValue) { 154 | if (defaultValue.startsWith("'{")) { 155 | return "Record"; 156 | } 157 | if (defaultValue.startsWith("'[")) { 158 | return "unknown[]"; 159 | } 160 | } 161 | return "unknown"; 162 | case "bytea": 163 | return "Buffer"; 164 | case "interval": 165 | return "PostgresInterval"; 166 | default: 167 | return (_customTypes$get = customTypes.get(udt)) !== null && _customTypes$get !== void 0 ? _customTypes$get : "unknown"; 168 | } 169 | } 170 | 171 | /** 172 | * Wraps the target property identifier into quotes in case it contains any 173 | * invalid characters. 174 | * 175 | * @see https://developer.mozilla.org/docs/Glossary/Identifier 176 | */ 177 | function sanitize(name) { 178 | return /^[a-zA-Z$_][a-zA-Z$_0-9]*$/.test(name) ? name : JSON.stringify(name); 179 | } 180 | //# sourceMappingURL=main.js.map -------------------------------------------------------------------------------- /main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.js","names":["updateTypes","db","options","overrides","output","fs","createWriteStream","encoding","forEach","line","write","schema","split","map","x","trim","includeSchemas","excludeSchemas","reduce","acc","s","startsWith","push","exclude","prefix","enums","table","join","orderBy","select","i","key","enumName","value","replace","enumsMap","Map","columns","withSchema","whereIn","whereNotIn","raw","tableSet","Set","Array","from","tableName","schemaName","type","getType","udt","substring","default","nullable","sanitize","column","suffix","end","destroy","customTypes","defaultValue","get","name","test","JSON","stringify"],"sources":["main.ts"],"sourcesContent":["/* SPDX-FileCopyrightText: 2016-present Kriasoft */\n/* SPDX-License-Identifier: MIT */\n\nimport fs from \"fs\";\nimport { Knex } from \"knex\";\nimport { camelCase, upperFirst } from \"lodash\";\nimport type { Writable } from \"stream\";\n\nexport type Options = {\n /**\n * Filename or output stream where the type definitions needs to be written.\n */\n output: Writable | string;\n\n /**\n * Name overrides for enums, classes, and fields.\n *\n * @example\n * overrides: {\n * \"identity_provider.linkedin\": \"LinkedIn\"\n * }\n */\n overrides?: Record;\n\n prefix?: string;\n suffix?: string;\n\n /**\n * Schemas that should be included/excluded when generating types.\n *\n * By default if this is null/not specified, \"public\" will be the only schema added, but if this property\n * is specified, public will be excluded by default and has to be included in the list to be added to the output.\n * If a schema is added by its name, i.e. 'log' all tables from that schema will be added.\n * If a schema name is added with an exclamation mark it will be ignored, i.e. \"!log\".\n *\n * The table-type will be prefixed by its schema name, the table log.message will become LogMessage.\n *\n * @example\n * // This will include \"public\" and \"log\", but exclude the \"auth\" schema.\n * schema: [\"public\", \"log\", \"!auth\"]\n * @default\n * \"public\"\n */\n schema?: string[] | string;\n\n /**\n * A comma separated list or an array of tables that should be excluded when\n * generating types.\n *\n * @example\n * exclude: [\"migration\", \"migration_lock\"]\n */\n exclude?: string[] | string;\n};\n\n/**\n * Generates TypeScript definitions (types) from a PostgreSQL database schema.\n */\nexport async function updateTypes(db: Knex, options: Options): Promise {\n const overrides: Record = options.overrides ?? {};\n const output: Writable =\n typeof options.output === \"string\"\n ? fs.createWriteStream(options.output, { encoding: \"utf-8\" })\n : options.output;\n\n [\n \"// The TypeScript definitions below are automatically generated.\\n\",\n \"// Do not touch them, or risk, your modifications being lost.\\n\\n\",\n ].forEach((line) => output.write(line));\n\n const schema = (typeof options.schema === \"string\"\n ? options.schema.split(\",\").map((x) => x.trim())\n : options.schema) ?? [\"public\"];\n\n // Schemas to include or exclude\n const [includeSchemas, excludeSchemas] = schema.reduce(\n (acc, s) =>\n (acc[+s.startsWith(\"!\")].push(s) && acc) as [string[], string[]],\n [[] as string[], [] as string[]]\n );\n\n // Tables to exclude\n const exclude =\n (typeof options.exclude === \"string\"\n ? options.exclude.split(\",\").map((x) => x.trim())\n : options.exclude) ?? [];\n\n if (options.prefix) {\n output.write(options.prefix);\n output.write(\"\\n\\n\");\n }\n\n try {\n // Fetch the list of custom enum types\n const enums = await db\n .table(\"pg_type\")\n .join(\"pg_enum\", \"pg_enum.enumtypid\", \"pg_type.oid\")\n .orderBy(\"pg_type.typname\")\n .orderBy(\"pg_enum.enumsortorder\")\n .select(\"pg_type.typname as key\", \"pg_enum.enumlabel as value\");\n\n // Construct TypeScript enum types\n enums.forEach((x, i) => {\n // The first line of enum declaration\n if (!(enums[i - 1] && enums[i - 1].key === x.key)) {\n const enumName = overrides[x.key] ?? upperFirst(camelCase(x.key));\n output.write(`export enum ${enumName} {\\n`);\n }\n\n // Enum body\n const key =\n overrides[`${x.key}.${x.value}`] ??\n upperFirst(camelCase(x.value.replace(/[.-]/g, \"_\")));\n output.write(` ${key} = \"${x.value}\",\\n`);\n\n // The closing line\n if (!(enums[i + 1] && enums[i + 1].key === x.key)) {\n output.write(\"}\\n\\n\");\n }\n });\n\n const enumsMap = new Map(\n enums.map((x) => [\n x.key,\n overrides[x.key] ?? upperFirst(camelCase(x.key)),\n ])\n );\n\n // Fetch the list of tables/columns\n const columns = await db\n .withSchema(\"information_schema\")\n .table(\"columns\")\n .whereIn(\"table_schema\", includeSchemas)\n .whereNotIn(\"table_schema\", excludeSchemas)\n .whereNotIn(\"table_name\", exclude)\n .orderBy(\"table_schema\")\n .orderBy(\"table_name\")\n .orderBy(\"ordinal_position\")\n .select(\n \"table_schema as schema\",\n \"table_name as table\",\n \"column_name as column\",\n db.raw(\"(is_nullable = 'YES') as nullable\"),\n \"column_default as default\",\n \"data_type as type\",\n \"udt_name as udt\"\n );\n\n // The list of database tables as enum\n output.write(\"export enum Table {\\n\");\n const tableSet = new Set(\n columns.map((x) => {\n const schema = x.schema !== \"public\" ? `${x.schema}.` : \"\";\n return `${schema}${x.table}`;\n })\n );\n Array.from(tableSet).forEach((value) => {\n const key = overrides[value] ?? upperFirst(camelCase(value));\n output.write(` ${key} = \"${value}\",\\n`);\n });\n output.write(\"}\\n\\n\");\n // The list of tables as type\n output.write(\"export type Tables = {\\n\");\n Array.from(tableSet).forEach((key) => {\n const value = overrides[key] ?? upperFirst(camelCase(key));\n output.write(` \"${key}\": ${value},\\n`);\n });\n output.write(\"};\\n\\n\");\n\n // Construct TypeScript db record types\n columns.forEach((x, i) => {\n if (!(columns[i - 1] && columns[i - 1].table === x.table)) {\n const tableName = overrides[x.table] ?? upperFirst(camelCase(x.table));\n const schemaName =\n x.schema !== \"public\" ? upperFirst(camelCase(x.schema)) : \"\";\n output.write(`export type ${schemaName}${tableName} = {\\n`);\n }\n\n let type =\n x.type === \"ARRAY\"\n ? `${getType(x.udt.substring(1), enumsMap, x.default)}[]`\n : getType(x.udt, enumsMap, x.default);\n\n if (x.nullable) {\n type += \" | null\";\n }\n\n output.write(` ${sanitize(x.column)}: ${type};\\n`);\n\n if (!(columns[i + 1] && columns[i + 1].table === x.table)) {\n output.write(\"};\\n\\n\");\n }\n });\n\n if (options.suffix) {\n output.write(options.suffix);\n output.write(\"\\n\");\n }\n } finally {\n output.end();\n db.destroy();\n }\n}\n\ntype Enum = {\n key: string;\n value: string;\n};\n\ntype Column = {\n table: string;\n column: string;\n schema: string;\n nullable: boolean;\n default: string | null;\n type: string;\n udt: string;\n};\n\nexport function getType(\n udt: string,\n customTypes: Map,\n defaultValue: string | null\n): string {\n switch (udt) {\n case \"bool\":\n return \"boolean\";\n case \"text\":\n case \"citext\":\n case \"money\":\n case \"numeric\":\n case \"int8\":\n case \"char\":\n case \"character\":\n case \"bpchar\":\n case \"varchar\":\n case \"time\":\n case \"tsquery\":\n case \"tsvector\":\n case \"uuid\":\n case \"xml\":\n case \"cidr\":\n case \"inet\":\n case \"macaddr\":\n return \"string\";\n case \"smallint\":\n case \"integer\":\n case \"int\":\n case \"int2\":\n case \"int4\":\n case \"real\":\n case \"float\":\n case \"float4\":\n case \"float8\":\n return \"number\";\n case \"date\":\n case \"timestamp\":\n case \"timestamptz\":\n return \"Date\";\n case \"json\":\n case \"jsonb\":\n if (defaultValue) {\n if (defaultValue.startsWith(\"'{\")) {\n return \"Record\";\n }\n if (defaultValue.startsWith(\"'[\")) {\n return \"unknown[]\";\n }\n }\n return \"unknown\";\n case \"bytea\":\n return \"Buffer\";\n case \"interval\":\n return \"PostgresInterval\";\n default:\n return customTypes.get(udt) ?? \"unknown\";\n }\n}\n\n/**\n * Wraps the target property identifier into quotes in case it contains any\n * invalid characters.\n *\n * @see https://developer.mozilla.org/docs/Glossary/Identifier\n */\nfunction sanitize(name: string): string {\n return /^[a-zA-Z$_][a-zA-Z$_0-9]*$/.test(name) ? name : JSON.stringify(name);\n}\n"],"mappings":"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"} -------------------------------------------------------------------------------- /main.test.ts: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: 2016-present Kriasoft */ 2 | /* SPDX-License-Identifier: MIT */ 3 | 4 | import { knex } from "knex"; 5 | import { PassThrough } from "stream"; 6 | import { updateTypes } from "./main"; 7 | 8 | const db = knex({ client: "pg", connection: { database: "update_types" } }); 9 | 10 | beforeAll(async function setup() { 11 | await createDatabase(); 12 | 13 | await db.raw(`CREATE DOMAIN short_id AS TEXT CHECK(VALUE ~ '^[0-9a-z]{6}$')`); 14 | await db.raw(`CREATE TYPE identity_provider AS ENUM ('google', 'facebook', 'linkedin')`); // prettier-ignore 15 | 16 | await db.schema.createTable("user", (table) => { 17 | table.increments("int").notNullable().primary(); 18 | table.specificType("provider", "identity_provider").notNullable(); 19 | table.specificType("provider_null", "identity_provider"); 20 | table.specificType("provider_array", "identity_provider[]").notNullable(); 21 | table.specificType("int_array", "integer[]").notNullable(); 22 | table.specificType("short_id", "short_id").notNullable(); 23 | table.decimal("decimal").notNullable(); 24 | table.specificType("decimal_array", "decimal[]").notNullable(); 25 | table.double("double").notNullable(); 26 | table.specificType("double_array", "float8[]").notNullable(); 27 | table.float("float").notNullable(); 28 | table.specificType("float_array", "float4[]").notNullable(); 29 | table.specificType("money", "money").notNullable(); 30 | table.bigInteger("bigint").notNullable(); 31 | table.smallint("smallint").notNullable(); 32 | table.specificType("int2", "int2").notNullable(); 33 | table.specificType("int4", "int4").notNullable(); 34 | table.specificType("int8", "int8").notNullable(); 35 | table.binary("binary").notNullable(); 36 | table.binary("binary_null"); 37 | table.specificType("binary_array", "bytea[]").notNullable(); 38 | table.uuid("uuid").notNullable(); 39 | table.uuid("uuid_null"); 40 | table.specificType("uuid_array", "uuid[]").notNullable(); 41 | table.text("text").notNullable(); 42 | table.text("text_null"); 43 | table.specificType("text_array", "text[]").notNullable(); 44 | table.specificType("citext", "citext").notNullable(); 45 | table.specificType("citext_null", "citext"); 46 | table.specificType("citext_array", "citext[]").notNullable(); 47 | table.specificType("char", "char(2)").notNullable(); 48 | table.string("varchar", 10).notNullable(); 49 | table.boolean("bool").notNullable(); 50 | table.boolean("bool_null"); 51 | table.specificType("bool_array", "bool[]").notNullable(); 52 | table.jsonb("jsonb_object").notNullable().defaultTo("{}"); 53 | table.jsonb("jsonb_object_null").defaultTo("{}"); 54 | table.jsonb("jsonb_array").notNullable().defaultTo("[]"); 55 | table.jsonb("jsonb_array_null").defaultTo("[]"); 56 | table.timestamp("timestamp").notNullable(); 57 | table.timestamp("timestamp_null"); 58 | table.time("time").notNullable(); 59 | table.time("time_null"); 60 | table.specificType("time_array", "time[]").notNullable(); 61 | table.specificType("interval", "interval").notNullable(); 62 | table.text("display name"); 63 | table.text("1invalidIdentifierName"); 64 | table.text(`name with a "`); 65 | }); 66 | 67 | await db.schema.createTable("login", (table) => { 68 | table.increments("secret").notNullable().primary(); 69 | }); 70 | 71 | await db.schema.withSchema("log").createTable("messages", (table) => { 72 | table.increments("int").notNullable().primary(); 73 | table.text("notes"); 74 | table.timestamp("timestamp").notNullable(); 75 | }); 76 | 77 | await db.schema.withSchema("secret").createTable("secret", (table) => { 78 | table.increments("int").notNullable().primary(); 79 | table.text("notes"); 80 | table.timestamp("timestamp").notNullable(); 81 | }); 82 | }); 83 | 84 | afterAll(async function teardown() { 85 | await db.destroy(); 86 | }); 87 | 88 | test("updateTypes", async function () { 89 | const output = new PassThrough(); 90 | const overrides = { 91 | "identity_provider.linkedin": "LinkedIn", 92 | }; 93 | 94 | await updateTypes(db, { 95 | output, 96 | overrides, 97 | prefix: 'import { PostgresInterval} from "postgres-interval";', 98 | suffix: "// user supplied suffix", 99 | schema: ["public", "log", "!secret"], 100 | exclude: ["login"], 101 | }); 102 | 103 | expect(await toString(output)).toMatchInlineSnapshot(` 104 | "// The TypeScript definitions below are automatically generated. 105 | // Do not touch them, or risk, your modifications being lost. 106 | 107 | import { PostgresInterval} from "postgres-interval"; 108 | 109 | export enum IdentityProvider { 110 | Google = "google", 111 | Facebook = "facebook", 112 | LinkedIn = "linkedin", 113 | } 114 | 115 | export enum Table { 116 | LogMessages = "log.messages", 117 | User = "user", 118 | } 119 | 120 | export type Tables = { 121 | "log.messages": LogMessages, 122 | "user": User, 123 | }; 124 | 125 | export type LogMessages = { 126 | int: number; 127 | notes: string | null; 128 | timestamp: Date; 129 | }; 130 | 131 | export type User = { 132 | int: number; 133 | provider: IdentityProvider; 134 | provider_null: IdentityProvider | null; 135 | provider_array: IdentityProvider[]; 136 | int_array: number[]; 137 | short_id: string; 138 | decimal: string; 139 | decimal_array: string[]; 140 | double: number; 141 | double_array: number[]; 142 | float: number; 143 | float_array: number[]; 144 | money: string; 145 | bigint: string; 146 | smallint: number; 147 | int2: number; 148 | int4: number; 149 | int8: string; 150 | binary: Buffer; 151 | binary_null: Buffer | null; 152 | binary_array: Buffer[]; 153 | uuid: string; 154 | uuid_null: string | null; 155 | uuid_array: string[]; 156 | text: string; 157 | text_null: string | null; 158 | text_array: string[]; 159 | citext: string; 160 | citext_null: string | null; 161 | citext_array: string[]; 162 | char: string; 163 | varchar: string; 164 | bool: boolean; 165 | bool_null: boolean | null; 166 | bool_array: boolean[]; 167 | jsonb_object: Record; 168 | jsonb_object_null: Record | null; 169 | jsonb_array: unknown[]; 170 | jsonb_array_null: unknown[] | null; 171 | timestamp: Date; 172 | timestamp_null: Date | null; 173 | time: string; 174 | time_null: string | null; 175 | time_array: string[]; 176 | interval: PostgresInterval; 177 | "display name": string | null; 178 | "1invalidIdentifierName": string | null; 179 | "name with a \\"": string | null; 180 | }; 181 | 182 | // user supplied suffix 183 | " 184 | `); 185 | }); 186 | 187 | async function createDatabase(): Promise { 188 | try { 189 | await db.select(db.raw("version()")).first(); 190 | } catch (err) { 191 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 192 | if (err instanceof Error && (err as any).code !== "3D000") throw err; 193 | // Create a new test database if it doesn't exist 194 | const tmp = knex({ client: "pg", connection: { database: "template1" } }); 195 | try { 196 | const dbName = db.client.config.connection.database; 197 | await tmp.raw("create database ?", [dbName]); 198 | } finally { 199 | await tmp.destroy(); 200 | } 201 | } 202 | 203 | await db.schema.raw("DROP SCHEMA IF EXISTS public CASCADE"); 204 | await db.schema.raw("DROP SCHEMA IF EXISTS log CASCADE"); 205 | await db.schema.raw("DROP SCHEMA IF EXISTS secret CASCADE"); 206 | await db.schema.raw("CREATE SCHEMA public"); 207 | await db.schema.raw("CREATE SCHEMA log"); 208 | await db.schema.raw("CREATE SCHEMA secret"); 209 | await db.raw(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`); 210 | await db.raw(`CREATE EXTENSION IF NOT EXISTS "hstore"`); 211 | await db.raw(`CREATE EXTENSION IF NOT EXISTS "citext"`); 212 | } 213 | 214 | function toString(stream: PassThrough): Promise { 215 | const chunks: Buffer[] = []; 216 | return new Promise((resolve, reject) => { 217 | stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))); 218 | stream.on("error", (err) => reject(err)); 219 | stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); 220 | }); 221 | } 222 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: 2016-present Kriasoft */ 2 | /* SPDX-License-Identifier: MIT */ 3 | 4 | import fs from "fs"; 5 | import { Knex } from "knex"; 6 | import { camelCase, upperFirst } from "lodash"; 7 | import type { Writable } from "stream"; 8 | 9 | export type Options = { 10 | /** 11 | * Filename or output stream where the type definitions needs to be written. 12 | */ 13 | output: Writable | string; 14 | 15 | /** 16 | * Name overrides for enums, classes, and fields. 17 | * 18 | * @example 19 | * overrides: { 20 | * "identity_provider.linkedin": "LinkedIn" 21 | * } 22 | */ 23 | overrides?: Record; 24 | 25 | prefix?: string; 26 | suffix?: string; 27 | 28 | /** 29 | * Schemas that should be included/excluded when generating types. 30 | * 31 | * By default if this is null/not specified, "public" will be the only schema added, but if this property 32 | * is specified, public will be excluded by default and has to be included in the list to be added to the output. 33 | * If a schema is added by its name, i.e. 'log' all tables from that schema will be added. 34 | * If a schema name is added with an exclamation mark it will be ignored, i.e. "!log". 35 | * 36 | * The table-type will be prefixed by its schema name, the table log.message will become LogMessage. 37 | * 38 | * @example 39 | * // This will include "public" and "log", but exclude the "auth" schema. 40 | * schema: ["public", "log", "!auth"] 41 | * @default 42 | * "public" 43 | */ 44 | schema?: string[] | string; 45 | 46 | /** 47 | * A comma separated list or an array of tables that should be excluded when 48 | * generating types. 49 | * 50 | * @example 51 | * exclude: ["migration", "migration_lock"] 52 | */ 53 | exclude?: string[] | string; 54 | }; 55 | 56 | /** 57 | * Generates TypeScript definitions (types) from a PostgreSQL database schema. 58 | */ 59 | export async function updateTypes(db: Knex, options: Options): Promise { 60 | const overrides: Record = options.overrides ?? {}; 61 | const output: Writable = 62 | typeof options.output === "string" 63 | ? fs.createWriteStream(options.output, { encoding: "utf-8" }) 64 | : options.output; 65 | 66 | [ 67 | "// The TypeScript definitions below are automatically generated.\n", 68 | "// Do not touch them, or risk, your modifications being lost.\n\n", 69 | ].forEach((line) => output.write(line)); 70 | 71 | const schema = (typeof options.schema === "string" 72 | ? options.schema.split(",").map((x) => x.trim()) 73 | : options.schema) ?? ["public"]; 74 | 75 | // Schemas to include or exclude 76 | const [includeSchemas, excludeSchemas] = schema.reduce( 77 | (acc, s) => 78 | (acc[+s.startsWith("!")].push(s) && acc) as [string[], string[]], 79 | [[] as string[], [] as string[]] 80 | ); 81 | 82 | // Tables to exclude 83 | const exclude = 84 | (typeof options.exclude === "string" 85 | ? options.exclude.split(",").map((x) => x.trim()) 86 | : options.exclude) ?? []; 87 | 88 | if (options.prefix) { 89 | output.write(options.prefix); 90 | output.write("\n\n"); 91 | } 92 | 93 | try { 94 | // Fetch the list of custom enum types 95 | const enums = await db 96 | .table("pg_type") 97 | .join("pg_enum", "pg_enum.enumtypid", "pg_type.oid") 98 | .orderBy("pg_type.typname") 99 | .orderBy("pg_enum.enumsortorder") 100 | .select("pg_type.typname as key", "pg_enum.enumlabel as value"); 101 | 102 | // Construct TypeScript enum types 103 | enums.forEach((x, i) => { 104 | // The first line of enum declaration 105 | if (!(enums[i - 1] && enums[i - 1].key === x.key)) { 106 | const enumName = overrides[x.key] ?? upperFirst(camelCase(x.key)); 107 | output.write(`export enum ${enumName} {\n`); 108 | } 109 | 110 | // Enum body 111 | const key = 112 | overrides[`${x.key}.${x.value}`] ?? 113 | upperFirst(camelCase(x.value.replace(/[.-]/g, "_"))); 114 | output.write(` ${key} = "${x.value}",\n`); 115 | 116 | // The closing line 117 | if (!(enums[i + 1] && enums[i + 1].key === x.key)) { 118 | output.write("}\n\n"); 119 | } 120 | }); 121 | 122 | const enumsMap = new Map( 123 | enums.map((x) => [ 124 | x.key, 125 | overrides[x.key] ?? upperFirst(camelCase(x.key)), 126 | ]) 127 | ); 128 | 129 | // Fetch the list of tables/columns 130 | const columns = await db 131 | .withSchema("information_schema") 132 | .table("columns") 133 | .whereIn("table_schema", includeSchemas) 134 | .whereNotIn("table_schema", excludeSchemas) 135 | .whereNotIn("table_name", exclude) 136 | .orderBy("table_schema") 137 | .orderBy("table_name") 138 | .orderBy("ordinal_position") 139 | .select( 140 | "table_schema as schema", 141 | "table_name as table", 142 | "column_name as column", 143 | db.raw("(is_nullable = 'YES') as nullable"), 144 | "column_default as default", 145 | "data_type as type", 146 | "udt_name as udt" 147 | ); 148 | 149 | // The list of database tables as enum 150 | output.write("export enum Table {\n"); 151 | const tableSet = new Set( 152 | columns.map((x) => { 153 | const schema = x.schema !== "public" ? `${x.schema}.` : ""; 154 | return `${schema}${x.table}`; 155 | }) 156 | ); 157 | Array.from(tableSet).forEach((value) => { 158 | const key = overrides[value] ?? upperFirst(camelCase(value)); 159 | output.write(` ${key} = "${value}",\n`); 160 | }); 161 | output.write("}\n\n"); 162 | // The list of tables as type 163 | output.write("export type Tables = {\n"); 164 | Array.from(tableSet).forEach((key) => { 165 | const value = overrides[key] ?? upperFirst(camelCase(key)); 166 | output.write(` "${key}": ${value},\n`); 167 | }); 168 | output.write("};\n\n"); 169 | 170 | // Construct TypeScript db record types 171 | columns.forEach((x, i) => { 172 | if (!(columns[i - 1] && columns[i - 1].table === x.table)) { 173 | const tableName = overrides[x.table] ?? upperFirst(camelCase(x.table)); 174 | const schemaName = 175 | x.schema !== "public" ? upperFirst(camelCase(x.schema)) : ""; 176 | output.write(`export type ${schemaName}${tableName} = {\n`); 177 | } 178 | 179 | let type = 180 | x.type === "ARRAY" 181 | ? `${getType(x.udt.substring(1), enumsMap, x.default)}[]` 182 | : getType(x.udt, enumsMap, x.default); 183 | 184 | if (x.nullable) { 185 | type += " | null"; 186 | } 187 | 188 | output.write(` ${sanitize(x.column)}: ${type};\n`); 189 | 190 | if (!(columns[i + 1] && columns[i + 1].table === x.table)) { 191 | output.write("};\n\n"); 192 | } 193 | }); 194 | 195 | if (options.suffix) { 196 | output.write(options.suffix); 197 | output.write("\n"); 198 | } 199 | } finally { 200 | output.end(); 201 | db.destroy(); 202 | } 203 | } 204 | 205 | type Enum = { 206 | key: string; 207 | value: string; 208 | }; 209 | 210 | type Column = { 211 | table: string; 212 | column: string; 213 | schema: string; 214 | nullable: boolean; 215 | default: string | null; 216 | type: string; 217 | udt: string; 218 | }; 219 | 220 | export function getType( 221 | udt: string, 222 | customTypes: Map, 223 | defaultValue: string | null 224 | ): string { 225 | switch (udt) { 226 | case "bool": 227 | return "boolean"; 228 | case "text": 229 | case "citext": 230 | case "money": 231 | case "numeric": 232 | case "int8": 233 | case "char": 234 | case "character": 235 | case "bpchar": 236 | case "varchar": 237 | case "time": 238 | case "tsquery": 239 | case "tsvector": 240 | case "uuid": 241 | case "xml": 242 | case "cidr": 243 | case "inet": 244 | case "macaddr": 245 | return "string"; 246 | case "smallint": 247 | case "integer": 248 | case "int": 249 | case "int2": 250 | case "int4": 251 | case "real": 252 | case "float": 253 | case "float4": 254 | case "float8": 255 | return "number"; 256 | case "date": 257 | case "timestamp": 258 | case "timestamptz": 259 | return "Date"; 260 | case "json": 261 | case "jsonb": 262 | if (defaultValue) { 263 | if (defaultValue.startsWith("'{")) { 264 | return "Record"; 265 | } 266 | if (defaultValue.startsWith("'[")) { 267 | return "unknown[]"; 268 | } 269 | } 270 | return "unknown"; 271 | case "bytea": 272 | return "Buffer"; 273 | case "interval": 274 | return "PostgresInterval"; 275 | default: 276 | return customTypes.get(udt) ?? "unknown"; 277 | } 278 | } 279 | 280 | /** 281 | * Wraps the target property identifier into quotes in case it contains any 282 | * invalid characters. 283 | * 284 | * @see https://developer.mozilla.org/docs/Glossary/Identifier 285 | */ 286 | function sanitize(name: string): string { 287 | return /^[a-zA-Z$_][a-zA-Z$_0-9]*$/.test(name) ? name : JSON.stringify(name); 288 | } 289 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knex-types", 3 | "version": "0.5.0", 4 | "description": "Generates TypeScript definitions (types) from a (PostgreSQL) database schema.", 5 | "keywords": [ 6 | "database", 7 | "db", 8 | "definitions", 9 | "generate", 10 | "generator", 11 | "postgres", 12 | "postgresql", 13 | "scaffold", 14 | "scaffolding", 15 | "schema", 16 | "template", 17 | "types", 18 | "typescript" 19 | ], 20 | "license": "MIT", 21 | "author": { 22 | "name": "Kriasoft", 23 | "email": "hello@kriasoft.com", 24 | "url": "https://kriasoft.com" 25 | }, 26 | "repository": "kriasoft/knex-types", 27 | "contributors": [ 28 | "Konstantin Tarkus (https://tarkus.me/)" 29 | ], 30 | "funding": { 31 | "type": "patreon", 32 | "url": "https://patreon.com/koistya" 33 | }, 34 | "main": "main.js", 35 | "types": "main.d.ts", 36 | "scripts": { 37 | "lint": "eslint --report-unused-disable-directives .", 38 | "test": "jest", 39 | "build": "babel --env-name production -x \".ts\" --out-dir . \"*.ts\" && tsc && rm *.test.d.ts && prettier --write ." 40 | }, 41 | "packageManager": "yarn@4.0.0-rc.32", 42 | "dependencies": { 43 | "lodash": "^4.17.21", 44 | "pg": "^8.8.0" 45 | }, 46 | "peerDependencies": { 47 | "knex": ">=0.95" 48 | }, 49 | "devDependencies": { 50 | "@babel/cli": "^7.19.3", 51 | "@babel/core": "^7.20.5", 52 | "@babel/plugin-proposal-class-properties": "^7.18.6", 53 | "@babel/preset-env": "^7.20.2", 54 | "@babel/preset-typescript": "^7.18.6", 55 | "@types/jest": "^29.2.3", 56 | "@types/lodash": "^4.14.191", 57 | "@types/node": "^18.11.10", 58 | "@typescript-eslint/eslint-plugin": "^5.45.0", 59 | "@typescript-eslint/parser": "^5.45.0", 60 | "babel-plugin-import": "^1.13.5", 61 | "eslint": "^8.28.0", 62 | "eslint-config-prettier": "^8.5.0", 63 | "jest": "^29.3.1", 64 | "knex": "^2.3.0", 65 | "prettier": "^2.8.0", 66 | "typescript": "^4.9.3" 67 | }, 68 | "jest": { 69 | "testEnvironment": "node", 70 | "moduleFileExtensions": [ 71 | "ts", 72 | "js" 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./", /* Redirect output structure to the directory. */ 18 | "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 44 | 45 | /* Module Resolution Options */ 46 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | 57 | /* Source Map Options */ 58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 62 | 63 | /* Experimental Options */ 64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 66 | 67 | /* Advanced Options */ 68 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 69 | "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ 70 | "emitDeclarationOnly": true /* Only emit .d.ts files; do not emit .js files. */ 71 | }, 72 | "include": ["*.ts"], 73 | "exclude": ["*.d.ts"] 74 | } 75 | --------------------------------------------------------------------------------