From 265f8b444149bd4754da7a2bfed942cd0301f148 Mon Sep 17 00:00:00 2001 From: Philip Masek Date: Wed, 23 Oct 2024 11:56:15 +0200 Subject: [PATCH 1/4] feat(typegen): generate types for columns with json_schema constraint --- package-lock.json | 384 ++++++++++++++++++++++------- package.json | 1 + src/server/constants.ts | 2 +- src/server/templates/typescript.ts | 51 +++- src/server/utils.ts | 32 +++ test/db/00-init.sql | 1 + test/db/01-memes.sql | 55 ++++- test/db/Dockerfile | 9 + test/server/typegen.ts | 234 ++++++++++++++++-- test/server/utils.ts | 93 +++++++ 10 files changed, 739 insertions(+), 123 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b8bc90a..ffcb5579 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "crypto-js": "^4.0.0", "fastify": "^4.24.3", "fastify-metrics": "^10.0.0", + "json-schema-to-typescript": "^15.0.2", "pg": "^8.7.1", "pg-connection-string": "^2.5.0", "pg-format": "^1.0.4", @@ -46,6 +47,23 @@ "npm": ">=9" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", + "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, "node_modules/@babel/runtime": { "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", @@ -544,7 +562,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -561,7 +578,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -573,7 +589,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -584,14 +599,12 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -608,7 +621,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -623,7 +635,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -669,6 +680,12 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -777,7 +794,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true, "engines": { "node": ">=14" @@ -1001,6 +1017,18 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.12.tgz", + "integrity": "sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.11.16", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", @@ -1263,7 +1291,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1595,7 +1622,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1606,8 +1632,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-support": { "version": "1.1.3", @@ -1726,7 +1751,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1855,8 +1879,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -2187,7 +2210,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -2203,7 +2225,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -2686,7 +2707,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2703,7 +2723,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -2857,6 +2876,18 @@ "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", "dev": true }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", @@ -2876,6 +2907,97 @@ "fast-deep-equal": "^3.1.3" } }, + "node_modules/json-schema-to-typescript": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.2.tgz", + "integrity": "sha512-+cRBw+bBJ3k783mZroDIgz1pLNPB4hvj6nnbHTWwEVl0dkW8qdZ+M9jWhBb+Y0FAdHvNsXACga3lewGO8lktrw==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "glob": "^10.3.12", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/json-schema-to-typescript/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/json-schema-to-typescript/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/json-schema-to-typescript/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/json-schema-to-typescript/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/json-schema-to-typescript/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2977,6 +3099,12 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -3112,9 +3240,13 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minipass": { "version": "3.3.6", @@ -3889,6 +4021,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", @@ -3919,7 +4057,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -3931,35 +4068,31 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", - "dev": true, + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/path-scurry/node_modules/minipass": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", - "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -4898,7 +5031,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4910,7 +5042,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -5129,7 +5260,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5198,7 +5328,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5843,7 +5972,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5905,6 +6033,16 @@ } }, "dependencies": { + "@apidevtools/json-schema-ref-parser": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", + "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + } + }, "@babel/runtime": { "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", @@ -6188,7 +6326,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "requires": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -6201,26 +6338,22 @@ "ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" }, "ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "requires": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -6231,7 +6364,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "requires": { "ansi-regex": "^6.0.1" } @@ -6240,7 +6372,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "requires": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -6278,6 +6409,11 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -6362,7 +6498,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true }, "@rollup/rollup-android-arm-eabi": { @@ -6502,6 +6637,16 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "@types/lodash": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.12.tgz", + "integrity": "sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ==" + }, "@types/node": { "version": "20.11.16", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", @@ -6703,7 +6848,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -6943,7 +7087,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -6951,8 +7094,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "color-support": { "version": "1.1.3", @@ -7040,7 +7182,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -7136,8 +7277,7 @@ "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "emoji-regex": { "version": "8.0.0", @@ -7419,7 +7559,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, "requires": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -7428,8 +7567,7 @@ "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" } } }, @@ -7772,8 +7910,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -7784,7 +7921,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -7885,6 +8021,14 @@ "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", "dev": true }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, "jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", @@ -7904,6 +8048,67 @@ "fast-deep-equal": "^3.1.3" } }, + "json-schema-to-typescript": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.2.tgz", + "integrity": "sha512-+cRBw+bBJ3k783mZroDIgz1pLNPB4hvj6nnbHTWwEVl0dkW8qdZ+M9jWhBb+Y0FAdHvNsXACga3lewGO8lktrw==", + "requires": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "glob": "^10.3.12", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + } + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -7982,6 +8187,11 @@ "pkg-types": "^1.0.3" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -8089,9 +8299,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { "version": "3.3.6", @@ -8643,6 +8853,11 @@ "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", "dev": true }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, "packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", @@ -8666,8 +8881,7 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.7", @@ -8676,26 +8890,23 @@ "dev": true }, "path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", - "dev": true, + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "requires": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "dependencies": { "lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", - "dev": true + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, "minipass": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", - "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", - "dev": true + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==" } } }, @@ -9360,7 +9571,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -9368,8 +9578,7 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "shell-quote": { "version": "1.7.3", @@ -9549,7 +9758,6 @@ "version": "npm:string-width@4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -9599,7 +9807,6 @@ "version": "npm:strip-ansi@6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -10014,7 +10221,6 @@ "version": "npm:wrap-ansi@7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", diff --git a/package.json b/package.json index 33c101a5..90cb25f7 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "crypto-js": "^4.0.0", "fastify": "^4.24.3", "fastify-metrics": "^10.0.0", + "json-schema-to-typescript": "^15.0.2", "pg": "^8.7.1", "pg-connection-string": "^2.5.0", "pg-format": "^1.0.4", diff --git a/src/server/constants.ts b/src/server/constants.ts index c87d3fbd..80052e00 100644 --- a/src/server/constants.ts +++ b/src/server/constants.ts @@ -37,7 +37,7 @@ if (PG_META_DB_SSL_ROOT_CERT) { export const EXPORT_DOCS = process.env.PG_META_EXPORT_DOCS === 'true' export const GENERATE_TYPES = process.env.PG_META_GENERATE_TYPES export const GENERATE_TYPES_INCLUDED_SCHEMAS = GENERATE_TYPES - ? (process.env.PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS?.split(',') ?? []) + ? process.env.PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS?.split(',') ?? [] : [] export const GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS = process.env.PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS === 'true' diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index 25c38a18..f222e49f 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -8,6 +8,7 @@ import type { PostgresView, } from '../../lib/index.js' import type { GeneratorMetadata } from '../../lib/generators.js' +import { generateTypeFromCheckConstraint } from '../utils.js' export const apply = async ({ schemas, @@ -26,10 +27,37 @@ export const apply = async ({ const columnsByTableId = Object.fromEntries( [...tables, ...foreignTables, ...views, ...materializedViews].map((t) => [t.id, []]) ) - columns - .filter((c) => c.table_id in columnsByTableId) - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - .forEach((c) => columnsByTableId[c.table_id].push(c)) + + const jsonSchemaTs: Record = {} + + await Promise.all( + columns + .filter((c) => c.table_id in columnsByTableId) + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + .map(async (c) => { + // If the column is of type json/jsonb and has a check constraint that matches a JSON schema, we'll generate a type for it. + if ( + ['json', 'jsonb'].includes(c.format.toLowerCase()) && + /json_matches_schema|jsonb_matches_schema/gi.test(c.check ?? '') + ) { + const ts = await generateTypeFromCheckConstraint(c.check) + // The only mutation required on the column is to reference the correct type + c.format = `json_schema_Database['${c.schema}']['SchemaTypes']['${c.table}']['${c.name}']` + + if (!jsonSchemaTs[c.schema]) { + jsonSchemaTs[c.schema] = {} + } + + if (!jsonSchemaTs[c.schema][c.table]) { + jsonSchemaTs[c.schema][c.table] = {} + } + + jsonSchemaTs[c.schema][c.table][c.name] = ts + } + + columnsByTableId[c.table_id].push(c) + }) + ) let output = ` export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[] @@ -367,6 +395,19 @@ export type Database = { ) } } + SchemaTypes: { + ${schemaTables + .filter((table) => jsonSchemaTs[schema.name][table.name]) + .map( + (table) => ` + ${table.name}: { + ${Object.entries(jsonSchemaTs[schema.name][table.name] ?? {}).map( + ([columnName, ts]) => `${JSON.stringify(columnName)}: ${ts}` + )} + } + ` + )} + } CompositeTypes: { ${ schemaCompositeTypes.length === 0 @@ -529,6 +570,8 @@ const pgTypeToTsType = ( return 'string' } else if (['json', 'jsonb'].includes(pgType)) { return 'Json' + } else if (/^json_schema_/gi.test(pgType)) { + return pgType.replace(/json_schema_/, '') } else if (pgType === 'void') { return 'undefined' } else if (pgType === 'record') { diff --git a/src/server/utils.ts b/src/server/utils.ts index 00888335..d8b84b6c 100644 --- a/src/server/utils.ts +++ b/src/server/utils.ts @@ -1,5 +1,6 @@ import pgcs from 'pg-connection-string' import { FastifyRequest } from 'fastify' +import { compile } from 'json-schema-to-typescript' export const extractRequestForLogging = (request: FastifyRequest) => { let pg: string = 'unknown' @@ -32,3 +33,34 @@ export function translateErrorToResponseCode( } return defaultResponseCode } + +export async function generateTypeFromCheckConstraint( + check: string | object | null +): Promise { + if (!check) { + throw new Error('check constraint is empty') + } + + let inputStr + + if (typeof check === 'string') { + inputStr = check + } else if (typeof check === 'object') { + inputStr = JSON.stringify(check) + } else { + throw new Error('invalid input type') + } + + const match = /[jsonb?_matches_schema\(]?\'?(\{.*\})\'?.*/gms.exec(inputStr) + const extractedJsonStr = match ? match[1] : null + const jsonSchema = JSON.parse(extractedJsonStr ?? '{}') + const tsType = await compile(jsonSchema, 'Type', { + bannerComment: '', + style: { + singleQuote: true, + semi: false, + }, + }) + + return tsType.replaceAll('export interface Type ', '') +} diff --git a/test/db/00-init.sql b/test/db/00-init.sql index e28a0b16..16989e11 100644 --- a/test/db/00-init.sql +++ b/test/db/00-init.sql @@ -83,6 +83,7 @@ $$ select string_to_array($1.details, ' '); $$ language sql stable; +create extension pg_jsonschema; create extension postgres_fdw; create server foreign_server foreign data wrapper postgres_fdw options (host 'localhost', port '5432', dbname 'postgres'); create user mapping for postgres server foreign_server options (user 'postgres', password 'postgres'); diff --git a/test/db/01-memes.sql b/test/db/01-memes.sql index d4909b7a..10b86441 100644 --- a/test/db/01-memes.sql +++ b/test/db/01-memes.sql @@ -1,5 +1,3 @@ - - CREATE TABLE public.category ( id serial NOT NULL PRIMARY KEY, name text NOT NULL @@ -29,10 +27,63 @@ CREATE TABLE public.memes ( name text NOT NULL, category INTEGER REFERENCES category(id), metadata jsonb, + json_metadata json, + free_metadata jsonb, created_at TIMESTAMP NOT NULL, status meme_status DEFAULT 'old' ); +ALTER TABLE public.memes ADD CONSTRAINT json_metadata_schema_check +CHECK ( + (json_matches_schema('{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "popularity_score": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + }', json_metadata)) +); + +ALTER TABLE public.memes ADD CONSTRAINT metadata_schema_check +CHECK ( + (jsonb_matches_schema('{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "popularity_score": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "address": { + "type": "object", + "properties": { + "city": { + "type": "string" + }, + "street": { + "type": "string" + } + }, + "required": [ + "city", + "street" + ] + } + }, + "required": [ + "popoularity_score" + ] + }', metadata)) +); + INSERT INTO public.memes (name, category, created_at) VALUES ('NO. Rage Face', 5, NOW()), ('"Not Bad" Obama Face', 5, NOW()), diff --git a/test/db/Dockerfile b/test/db/Dockerfile index fb81d611..0b941cca 100644 --- a/test/db/Dockerfile +++ b/test/db/Dockerfile @@ -1,3 +1,12 @@ FROM supabase/postgres:14.1.0 COPY --chown=postgres:postgres --chmod=600 server.key server.crt /var/lib/postgresql/ + +ADD "https://github.com/supabase/pg_jsonschema/releases/download/v0.1.4/pg_jsonschema-v0.1.4-pg14-amd64-linux-gnu.deb" \ + /tmp/pg_jsonschema.deb + +RUN apt-get update && apt-get install -y --no-install-recommends \ + /tmp/*.deb \ + # Needed for anything using libcurl + # https://github.com/supabase/postgres/issues/573 + ca-certificates \ No newline at end of file diff --git a/test/server/typegen.ts b/test/server/typegen.ts index 63569fb8..923f06dd 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -58,26 +58,44 @@ test('typegen: typescript', async () => { Row: { category: number | null created_at: string + free_metadata: Json | null id: number - metadata: Json | null name: string status: Database["public"]["Enums"]["meme_status"] | null + json_metadata: + | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] + | null + metadata: + | Database["public"]["SchemaTypes"]["memes"]["metadata"] + | null } Insert: { category?: number | null created_at: string + free_metadata?: Json | null id?: number - metadata?: Json | null name: string status?: Database["public"]["Enums"]["meme_status"] | null + json_metadata?: + | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] + | null + metadata?: + | Database["public"]["SchemaTypes"]["memes"]["metadata"] + | null } Update: { category?: number | null created_at?: string + free_metadata?: Json | null id?: number - metadata?: Json | null name?: string status?: Database["public"]["Enums"]["meme_status"] | null + json_metadata?: + | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] + | null + metadata?: + | Database["public"]["SchemaTypes"]["memes"]["metadata"] + | null } Relationships: [ { @@ -384,6 +402,20 @@ test('typegen: typescript', async () => { name: string }[] } + json_matches_schema: { + Args: { + schema: Json + instance: Json + } + Returns: boolean + } + jsonb_matches_schema: { + Args: { + schema: Json + instance: Json + } + Returns: boolean + } polymorphic_function: | { Args: { @@ -420,6 +452,24 @@ test('typegen: typescript', async () => { meme_status: "new" | "old" | "retired" user_status: "ACTIVE" | "INACTIVE" } + SchemaTypes: { + memes: { + json_metadata: { + popularity_score?: number + name?: string + } + metadata: { + popularity_score?: number + name?: string + address?: { + city: string + street: string + [k: string]: unknown + } + [k: string]: unknown + } + } + } CompositeTypes: { composite_type_with_array_attribute: { my_text_array: string[] | null @@ -589,26 +639,44 @@ test('typegen w/ one-to-one relationships', async () => { Row: { category: number | null created_at: string + free_metadata: Json | null id: number - metadata: Json | null name: string status: Database["public"]["Enums"]["meme_status"] | null + json_metadata: + | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] + | null + metadata: + | Database["public"]["SchemaTypes"]["memes"]["metadata"] + | null } Insert: { category?: number | null created_at: string + free_metadata?: Json | null id?: number - metadata?: Json | null name: string status?: Database["public"]["Enums"]["meme_status"] | null + json_metadata?: + | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] + | null + metadata?: + | Database["public"]["SchemaTypes"]["memes"]["metadata"] + | null } Update: { category?: number | null created_at?: string + free_metadata?: Json | null id?: number - metadata?: Json | null name?: string status?: Database["public"]["Enums"]["meme_status"] | null + json_metadata?: + | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] + | null + metadata?: + | Database["public"]["SchemaTypes"]["memes"]["metadata"] + | null } Relationships: [ { @@ -928,6 +996,20 @@ test('typegen w/ one-to-one relationships', async () => { name: string }[] } + json_matches_schema: { + Args: { + schema: Json + instance: Json + } + Returns: boolean + } + jsonb_matches_schema: { + Args: { + schema: Json + instance: Json + } + Returns: boolean + } polymorphic_function: | { Args: { @@ -964,6 +1046,24 @@ test('typegen w/ one-to-one relationships', async () => { meme_status: "new" | "old" | "retired" user_status: "ACTIVE" | "INACTIVE" } + SchemaTypes: { + memes: { + json_metadata: { + popularity_score?: number + name?: string + } + metadata: { + popularity_score?: number + name?: string + address?: { + city: string + street: string + [k: string]: unknown + } + [k: string]: unknown + } + } + } CompositeTypes: { composite_type_with_array_attribute: { my_text_array: string[] | null @@ -1133,26 +1233,44 @@ test('typegen: typescript w/ one-to-one relationships', async () => { Row: { category: number | null created_at: string + free_metadata: Json | null id: number - metadata: Json | null name: string status: Database["public"]["Enums"]["meme_status"] | null + json_metadata: + | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] + | null + metadata: + | Database["public"]["SchemaTypes"]["memes"]["metadata"] + | null } Insert: { category?: number | null created_at: string + free_metadata?: Json | null id?: number - metadata?: Json | null name: string status?: Database["public"]["Enums"]["meme_status"] | null + json_metadata?: + | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] + | null + metadata?: + | Database["public"]["SchemaTypes"]["memes"]["metadata"] + | null } Update: { category?: number | null created_at?: string + free_metadata?: Json | null id?: number - metadata?: Json | null name?: string status?: Database["public"]["Enums"]["meme_status"] | null + json_metadata?: + | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] + | null + metadata?: + | Database["public"]["SchemaTypes"]["memes"]["metadata"] + | null } Relationships: [ { @@ -1472,6 +1590,20 @@ test('typegen: typescript w/ one-to-one relationships', async () => { name: string }[] } + json_matches_schema: { + Args: { + schema: Json + instance: Json + } + Returns: boolean + } + jsonb_matches_schema: { + Args: { + schema: Json + instance: Json + } + Returns: boolean + } polymorphic_function: | { Args: { @@ -1508,6 +1640,24 @@ test('typegen: typescript w/ one-to-one relationships', async () => { meme_status: "new" | "old" | "retired" user_status: "ACTIVE" | "INACTIVE" } + SchemaTypes: { + memes: { + json_metadata: { + popularity_score?: number + name?: string + } + metadata: { + popularity_score?: number + name?: string + address?: { + city: string + street: string + [k: string]: unknown + } + [k: string]: unknown + } + } + } CompositeTypes: { composite_type_with_array_attribute: { my_text_array: string[] | null @@ -1753,30 +1903,36 @@ test('typegen: go', async () => { } type PublicMemesSelect struct { - Category sql.NullInt32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - Id int32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status sql.NullString \`json:"status"\` + Category sql.NullInt32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + FreeMetadata interface{} \`json:"free_metadata"\` + Id int32 \`json:"id"\` + JsonMetadata interface{} \`json:"json_metadata"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status sql.NullString \`json:"status"\` } type PublicMemesInsert struct { - Category sql.NullInt32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - Id sql.NullInt32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status sql.NullString \`json:"status"\` + Category sql.NullInt32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + FreeMetadata interface{} \`json:"free_metadata"\` + Id sql.NullInt32 \`json:"id"\` + JsonMetadata interface{} \`json:"json_metadata"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status sql.NullString \`json:"status"\` } type PublicMemesUpdate struct { - Category sql.NullInt32 \`json:"category"\` - CreatedAt sql.NullString \`json:"created_at"\` - Id sql.NullInt32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name sql.NullString \`json:"name"\` - Status sql.NullString \`json:"status"\` + Category sql.NullInt32 \`json:"category"\` + CreatedAt sql.NullString \`json:"created_at"\` + FreeMetadata interface{} \`json:"free_metadata"\` + Id sql.NullInt32 \`json:"id"\` + JsonMetadata interface{} \`json:"json_metadata"\` + Metadata interface{} \`json:"metadata"\` + Name sql.NullString \`json:"name"\` + Status sql.NullString \`json:"status"\` } type PublicTodosViewSelect struct { @@ -1886,14 +2042,18 @@ test('typegen: swift', async () => { internal struct MemesSelect: Codable, Hashable, Sendable { internal let category: Int32? internal let createdAt: String + internal let freeMetadata: AnyJSON? internal let id: Int32 + internal let jsonMetadata: AnyJSON? internal let metadata: AnyJSON? internal let name: String internal let status: MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" + case freeMetadata = "free_metadata" case id = "id" + case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" case status = "status" @@ -1902,14 +2062,18 @@ test('typegen: swift', async () => { internal struct MemesInsert: Codable, Hashable, Sendable { internal let category: Int32? internal let createdAt: String + internal let freeMetadata: AnyJSON? internal let id: Int32? + internal let jsonMetadata: AnyJSON? internal let metadata: AnyJSON? internal let name: String internal let status: MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" + case freeMetadata = "free_metadata" case id = "id" + case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" case status = "status" @@ -1918,14 +2082,18 @@ test('typegen: swift', async () => { internal struct MemesUpdate: Codable, Hashable, Sendable { internal let category: Int32? internal let createdAt: String? + internal let freeMetadata: AnyJSON? internal let id: Int32? + internal let jsonMetadata: AnyJSON? internal let metadata: AnyJSON? internal let name: String? internal let status: MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" + case freeMetadata = "free_metadata" case id = "id" + case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" case status = "status" @@ -2231,14 +2399,18 @@ test('typegen: swift w/ public access control', async () => { public struct MemesSelect: Codable, Hashable, Sendable { public let category: Int32? public let createdAt: String + public let freeMetadata: AnyJSON? public let id: Int32 + public let jsonMetadata: AnyJSON? public let metadata: AnyJSON? public let name: String public let status: MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" + case freeMetadata = "free_metadata" case id = "id" + case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" case status = "status" @@ -2247,14 +2419,18 @@ test('typegen: swift w/ public access control', async () => { public struct MemesInsert: Codable, Hashable, Sendable { public let category: Int32? public let createdAt: String + public let freeMetadata: AnyJSON? public let id: Int32? + public let jsonMetadata: AnyJSON? public let metadata: AnyJSON? public let name: String public let status: MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" + case freeMetadata = "free_metadata" case id = "id" + case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" case status = "status" @@ -2263,14 +2439,18 @@ test('typegen: swift w/ public access control', async () => { public struct MemesUpdate: Codable, Hashable, Sendable { public let category: Int32? public let createdAt: String? + public let freeMetadata: AnyJSON? public let id: Int32? + public let jsonMetadata: AnyJSON? public let metadata: AnyJSON? public let name: String? public let status: MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" + case freeMetadata = "free_metadata" case id = "id" + case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" case status = "status" diff --git a/test/server/utils.ts b/test/server/utils.ts index 0222fcf3..5de962cb 100644 --- a/test/server/utils.ts +++ b/test/server/utils.ts @@ -1,3 +1,96 @@ +import { expect, test } from 'vitest' import { build as buildApp } from '../../src/server/app' +import { generateTypeFromCheckConstraint } from '../../src/server/utils' export const app = buildApp() + +test('generate type string from json_matches check constraint', async () => { + const checkConstraintStr = ` + jsonb_matches_schema('{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "popularity_score": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "address": { + "type": "object", + "properties": { + "city": { + "type": "string" + }, + "street": { + "type": "string" + } + }, + "required": [ + "city", + "street" + ] + } + }, + "required": [ + "popoularity_score" + ] + }'::json, metadata) + ` + + await expect(generateTypeFromCheckConstraint(checkConstraintStr)).resolves.toMatchInlineSnapshot(` + "{ + popularity_score?: number + name?: string + address?: { + city: string + street: string + [k: string]: unknown + } + [k: string]: unknown + } + " + `) +}) + +test('generate type string from pure json schema', async () => { + const checkConstraintStr = { + $schema: 'http://json-schema.org/draft-04/schema#', + type: 'object', + properties: { + popularity_score: { + type: 'integer', + }, + name: { + type: 'string', + }, + address: { + type: 'object', + properties: { + city: { + type: 'string', + }, + street: { + type: 'string', + }, + }, + required: ['city', 'street'], + }, + }, + required: ['popoularity_score'], + } + + await expect(generateTypeFromCheckConstraint(checkConstraintStr)).resolves.toMatchInlineSnapshot(` + "{ + popularity_score?: number + name?: string + address?: { + city: string + street: string + [k: string]: unknown + } + [k: string]: unknown + } + " + `) +}) From 610692b5d6ff3602e229b1aa1451ee3f082ab8a9 Mon Sep 17 00:00:00 2001 From: Philip Masek Date: Wed, 23 Oct 2024 14:29:41 +0200 Subject: [PATCH 2/4] chore: constraint generate types function to only string --- src/server/utils.ts | 14 ++++---------- test/server/utils.ts | 42 ------------------------------------------ 2 files changed, 4 insertions(+), 52 deletions(-) diff --git a/src/server/utils.ts b/src/server/utils.ts index d8b84b6c..d74bdf5c 100644 --- a/src/server/utils.ts +++ b/src/server/utils.ts @@ -35,23 +35,17 @@ export function translateErrorToResponseCode( } export async function generateTypeFromCheckConstraint( - check: string | object | null + checkConstraints: string | null ): Promise { - if (!check) { + if (!checkConstraints) { throw new Error('check constraint is empty') } - let inputStr - - if (typeof check === 'string') { - inputStr = check - } else if (typeof check === 'object') { - inputStr = JSON.stringify(check) - } else { + if (typeof checkConstraints !== 'string') { throw new Error('invalid input type') } - const match = /[jsonb?_matches_schema\(]?\'?(\{.*\})\'?.*/gms.exec(inputStr) + const match = /jsonb?_matches_schema\(\'([\{|\[].*[\}|\]])/gms.exec(checkConstraints) const extractedJsonStr = match ? match[1] : null const jsonSchema = JSON.parse(extractedJsonStr ?? '{}') const tsType = await compile(jsonSchema, 'Type', { diff --git a/test/server/utils.ts b/test/server/utils.ts index 5de962cb..b7492794 100644 --- a/test/server/utils.ts +++ b/test/server/utils.ts @@ -52,45 +52,3 @@ test('generate type string from json_matches check constraint', async () => { " `) }) - -test('generate type string from pure json schema', async () => { - const checkConstraintStr = { - $schema: 'http://json-schema.org/draft-04/schema#', - type: 'object', - properties: { - popularity_score: { - type: 'integer', - }, - name: { - type: 'string', - }, - address: { - type: 'object', - properties: { - city: { - type: 'string', - }, - street: { - type: 'string', - }, - }, - required: ['city', 'street'], - }, - }, - required: ['popoularity_score'], - } - - await expect(generateTypeFromCheckConstraint(checkConstraintStr)).resolves.toMatchInlineSnapshot(` - "{ - popularity_score?: number - name?: string - address?: { - city: string - street: string - [k: string]: unknown - } - [k: string]: unknown - } - " - `) -}) From 7f3f465f05407b4a0bd1f221a0455923c0f3569a Mon Sep 17 00:00:00 2001 From: Philip Masek Date: Wed, 23 Oct 2024 14:30:08 +0200 Subject: [PATCH 3/4] chore: additional test case to test other check constraint --- test/db/01-memes.sql | 6 ++++ test/server/typegen.ts | 72 ++++++++++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/test/db/01-memes.sql b/test/db/01-memes.sql index 10b86441..f2728a61 100644 --- a/test/db/01-memes.sql +++ b/test/db/01-memes.sql @@ -27,6 +27,7 @@ CREATE TABLE public.memes ( name text NOT NULL, category INTEGER REFERENCES category(id), metadata jsonb, + other_check_metadata jsonb, json_metadata json, free_metadata jsonb, created_at TIMESTAMP NOT NULL, @@ -84,6 +85,11 @@ CHECK ( }', metadata)) ); +ALTER TABLE public.memes ADD CONSTRAINT other_check_metadata_schema_check +CHECK ( + (other_check_metadata <> '{}'::jsonb) +); + INSERT INTO public.memes (name, category, created_at) VALUES ('NO. Rage Face', 5, NOW()), ('"Not Bad" Obama Face', 5, NOW()), diff --git a/test/server/typegen.ts b/test/server/typegen.ts index 923f06dd..d7c246bf 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -61,6 +61,7 @@ test('typegen: typescript', async () => { free_metadata: Json | null id: number name: string + other_check_metadata: Json | null status: Database["public"]["Enums"]["meme_status"] | null json_metadata: | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] @@ -75,6 +76,7 @@ test('typegen: typescript', async () => { free_metadata?: Json | null id?: number name: string + other_check_metadata?: Json | null status?: Database["public"]["Enums"]["meme_status"] | null json_metadata?: | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] @@ -89,6 +91,7 @@ test('typegen: typescript', async () => { free_metadata?: Json | null id?: number name?: string + other_check_metadata?: Json | null status?: Database["public"]["Enums"]["meme_status"] | null json_metadata?: | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] @@ -642,6 +645,7 @@ test('typegen w/ one-to-one relationships', async () => { free_metadata: Json | null id: number name: string + other_check_metadata: Json | null status: Database["public"]["Enums"]["meme_status"] | null json_metadata: | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] @@ -656,6 +660,7 @@ test('typegen w/ one-to-one relationships', async () => { free_metadata?: Json | null id?: number name: string + other_check_metadata?: Json | null status?: Database["public"]["Enums"]["meme_status"] | null json_metadata?: | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] @@ -670,6 +675,7 @@ test('typegen w/ one-to-one relationships', async () => { free_metadata?: Json | null id?: number name?: string + other_check_metadata?: Json | null status?: Database["public"]["Enums"]["meme_status"] | null json_metadata?: | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] @@ -1236,6 +1242,7 @@ test('typegen: typescript w/ one-to-one relationships', async () => { free_metadata: Json | null id: number name: string + other_check_metadata: Json | null status: Database["public"]["Enums"]["meme_status"] | null json_metadata: | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] @@ -1250,6 +1257,7 @@ test('typegen: typescript w/ one-to-one relationships', async () => { free_metadata?: Json | null id?: number name: string + other_check_metadata?: Json | null status?: Database["public"]["Enums"]["meme_status"] | null json_metadata?: | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] @@ -1264,6 +1272,7 @@ test('typegen: typescript w/ one-to-one relationships', async () => { free_metadata?: Json | null id?: number name?: string + other_check_metadata?: Json | null status?: Database["public"]["Enums"]["meme_status"] | null json_metadata?: | Database["public"]["SchemaTypes"]["memes"]["json_metadata"] @@ -1903,36 +1912,39 @@ test('typegen: go', async () => { } type PublicMemesSelect struct { - Category sql.NullInt32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - FreeMetadata interface{} \`json:"free_metadata"\` - Id int32 \`json:"id"\` - JsonMetadata interface{} \`json:"json_metadata"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status sql.NullString \`json:"status"\` + Category sql.NullInt32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + FreeMetadata interface{} \`json:"free_metadata"\` + Id int32 \`json:"id"\` + JsonMetadata interface{} \`json:"json_metadata"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + OtherCheckMetadata interface{} \`json:"other_check_metadata"\` + Status sql.NullString \`json:"status"\` } type PublicMemesInsert struct { - Category sql.NullInt32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - FreeMetadata interface{} \`json:"free_metadata"\` - Id sql.NullInt32 \`json:"id"\` - JsonMetadata interface{} \`json:"json_metadata"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status sql.NullString \`json:"status"\` + Category sql.NullInt32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + FreeMetadata interface{} \`json:"free_metadata"\` + Id sql.NullInt32 \`json:"id"\` + JsonMetadata interface{} \`json:"json_metadata"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + OtherCheckMetadata interface{} \`json:"other_check_metadata"\` + Status sql.NullString \`json:"status"\` } type PublicMemesUpdate struct { - Category sql.NullInt32 \`json:"category"\` - CreatedAt sql.NullString \`json:"created_at"\` - FreeMetadata interface{} \`json:"free_metadata"\` - Id sql.NullInt32 \`json:"id"\` - JsonMetadata interface{} \`json:"json_metadata"\` - Metadata interface{} \`json:"metadata"\` - Name sql.NullString \`json:"name"\` - Status sql.NullString \`json:"status"\` + Category sql.NullInt32 \`json:"category"\` + CreatedAt sql.NullString \`json:"created_at"\` + FreeMetadata interface{} \`json:"free_metadata"\` + Id sql.NullInt32 \`json:"id"\` + JsonMetadata interface{} \`json:"json_metadata"\` + Metadata interface{} \`json:"metadata"\` + Name sql.NullString \`json:"name"\` + OtherCheckMetadata interface{} \`json:"other_check_metadata"\` + Status sql.NullString \`json:"status"\` } type PublicTodosViewSelect struct { @@ -2047,6 +2059,7 @@ test('typegen: swift', async () => { internal let jsonMetadata: AnyJSON? internal let metadata: AnyJSON? internal let name: String + internal let otherCheckMetadata: AnyJSON? internal let status: MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" @@ -2056,6 +2069,7 @@ test('typegen: swift', async () => { case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" + case otherCheckMetadata = "other_check_metadata" case status = "status" } } @@ -2067,6 +2081,7 @@ test('typegen: swift', async () => { internal let jsonMetadata: AnyJSON? internal let metadata: AnyJSON? internal let name: String + internal let otherCheckMetadata: AnyJSON? internal let status: MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" @@ -2076,6 +2091,7 @@ test('typegen: swift', async () => { case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" + case otherCheckMetadata = "other_check_metadata" case status = "status" } } @@ -2087,6 +2103,7 @@ test('typegen: swift', async () => { internal let jsonMetadata: AnyJSON? internal let metadata: AnyJSON? internal let name: String? + internal let otherCheckMetadata: AnyJSON? internal let status: MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" @@ -2096,6 +2113,7 @@ test('typegen: swift', async () => { case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" + case otherCheckMetadata = "other_check_metadata" case status = "status" } } @@ -2404,6 +2422,7 @@ test('typegen: swift w/ public access control', async () => { public let jsonMetadata: AnyJSON? public let metadata: AnyJSON? public let name: String + public let otherCheckMetadata: AnyJSON? public let status: MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" @@ -2413,6 +2432,7 @@ test('typegen: swift w/ public access control', async () => { case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" + case otherCheckMetadata = "other_check_metadata" case status = "status" } } @@ -2424,6 +2444,7 @@ test('typegen: swift w/ public access control', async () => { public let jsonMetadata: AnyJSON? public let metadata: AnyJSON? public let name: String + public let otherCheckMetadata: AnyJSON? public let status: MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" @@ -2433,6 +2454,7 @@ test('typegen: swift w/ public access control', async () => { case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" + case otherCheckMetadata = "other_check_metadata" case status = "status" } } @@ -2444,6 +2466,7 @@ test('typegen: swift w/ public access control', async () => { public let jsonMetadata: AnyJSON? public let metadata: AnyJSON? public let name: String? + public let otherCheckMetadata: AnyJSON? public let status: MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" @@ -2453,6 +2476,7 @@ test('typegen: swift w/ public access control', async () => { case jsonMetadata = "json_metadata" case metadata = "metadata" case name = "name" + case otherCheckMetadata = "other_check_metadata" case status = "status" } } From b8870dd858450669b46204d737b1d466b9eec444 Mon Sep 17 00:00:00 2001 From: Philip Masek Date: Wed, 23 Oct 2024 14:42:36 +0200 Subject: [PATCH 4/4] refactor: disable formatting to increase performance, handled elsewhere --- src/server/utils.ts | 1 + test/server/utils.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/server/utils.ts b/src/server/utils.ts index d74bdf5c..b5037b37 100644 --- a/src/server/utils.ts +++ b/src/server/utils.ts @@ -54,6 +54,7 @@ export async function generateTypeFromCheckConstraint( singleQuote: true, semi: false, }, + format: false }) return tsType.replaceAll('export interface Type ', '') diff --git a/test/server/utils.ts b/test/server/utils.ts index b7492794..c543d3e4 100644 --- a/test/server/utils.ts +++ b/test/server/utils.ts @@ -40,14 +40,14 @@ test('generate type string from json_matches check constraint', async () => { await expect(generateTypeFromCheckConstraint(checkConstraintStr)).resolves.toMatchInlineSnapshot(` "{ - popularity_score?: number - name?: string - address?: { - city: string - street: string - [k: string]: unknown - } - [k: string]: unknown + popularity_score?: number + name?: string + address?: { + city: string + street: string + [k: string]: unknown + } + [k: string]: unknown } " `)