diff --git a/bun.lock b/bun.lock index 683ad0a..914a319 100644 --- a/bun.lock +++ b/bun.lock @@ -7,23 +7,28 @@ "dependencies": { "@types/react": "^19.2.14", "better-sqlite3": "^12.6.2", + "exceljs": "^4.4.0", "fast-xml-parser": "^5.3.7", "ink": "^6.8.0", "ink-select-input": "^6.2.0", "ink-spinner": "^5.0.0", "ink-text-input": "^6.0.0", "mongodb": "^7.1.0", + "mysql2": "^3.18.0", "nanoid": "^5.1.6", "openai": "^6.22.0", + "oracledb": "^6.10.0", "papaparse": "^5.5.3", + "pg": "^8.18.0", "react": "^19.2.4", "string-width": "^8.2.0", - "xlsx": "^0.18.5", }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", "@types/bun": "latest", + "@types/oracledb": "^6.10.1", "@types/papaparse": "^5.5.2", + "@types/pg": "^8.16.0", "lefthook": "^2.1.1", "oxlint": "latest", "typescript": "^5", @@ -130,6 +135,10 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + "@fast-csv/format": ["@fast-csv/format@4.3.5", "", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.isboolean": "^3.0.3", "lodash.isequal": "^4.5.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0" } }, "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A=="], + + "@fast-csv/parse": ["@fast-csv/parse@4.3.6", "", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.groupby": "^4.6.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0", "lodash.isundefined": "^3.0.1", "lodash.uniq": "^4.5.0" } }, "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA=="], + "@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.71", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-rNoDFbq1fAYiEexBvrw613/xiUOPEu5MKVV/X8lI64AgdTzLQUUemr9f9fplxUMPoxCBP2rWzlhOEeTHk/Sf0Q=="], "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], @@ -260,8 +269,12 @@ "@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], + "@types/oracledb": ["@types/oracledb@6.10.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-/P478CfP8rYvXi6897OLn/BgXOP/RnDcKYCX3JRRNSZ/J94gmKJAT1vWiLA+HDRopBmqe0pzzud6hKhKQguJcw=="], + "@types/papaparse": ["@types/papaparse@5.5.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA=="], + "@types/pg": ["@types/pg@8.16.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], @@ -308,8 +321,6 @@ "@vueuse/shared": ["@vueuse/shared@12.8.2", "", { "dependencies": { "vue": "^3.5.13" } }, "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w=="], - "adler-32": ["adler-32@1.3.1", "", {}, "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A=="], - "algoliasearch": ["algoliasearch@5.49.0", "", { "dependencies": { "@algolia/abtesting": "1.15.0", "@algolia/client-abtesting": "5.49.0", "@algolia/client-analytics": "5.49.0", "@algolia/client-common": "5.49.0", "@algolia/client-insights": "5.49.0", "@algolia/client-personalization": "5.49.0", "@algolia/client-query-suggestions": "5.49.0", "@algolia/client-search": "5.49.0", "@algolia/ingestion": "1.49.0", "@algolia/monitoring": "1.49.0", "@algolia/recommend": "5.49.0", "@algolia/requester-browser-xhr": "5.49.0", "@algolia/requester-fetch": "5.49.0", "@algolia/requester-node-http": "5.49.0" } }, "sha512-Tse7vx7WOvbU+kpq/L3BrBhSWTPbtMa59zIEhMn+Z2NoxZlpcCRUDCRxQ7kDFs1T3CHxDgvb+mDuILiBBpBaAA=="], "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], @@ -318,27 +329,51 @@ "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "archiver": ["archiver@5.3.2", "", { "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" } }, "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw=="], + + "archiver-utils": ["archiver-utils@2.1.0", "", { "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" } }, "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "better-sqlite3": ["better-sqlite3@12.6.2", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA=="], + "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="], + + "binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="], + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + "bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="], + + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "bson": ["bson@7.2.0", "", {}, "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ=="], "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "buffer-indexof-polyfill": ["buffer-indexof-polyfill@1.0.2", "", {}, "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A=="], + + "buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="], + "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - "cfb": ["cfb@1.2.2", "", { "dependencies": { "adler-32": "~1.3.0", "crc-32": "~1.2.0" } }, "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA=="], + "chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -358,28 +393,40 @@ "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], - "codepage": ["codepage@1.15.0", "", {}, "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="], - "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + "compress-commons": ["compress-commons@4.1.2", "", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], "copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="], + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], + "crc32-stream": ["crc32-stream@4.0.3", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" } }, "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "duplexer2": ["duplexer2@0.1.4", "", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="], + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], @@ -398,8 +445,12 @@ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "exceljs": ["exceljs@4.4.0", "", { "dependencies": { "archiver": "^5.0.0", "dayjs": "^1.8.34", "fast-csv": "^4.3.1", "jszip": "^3.10.1", "readable-stream": "^3.6.0", "saxes": "^5.0.1", "tmp": "^0.2.0", "unzipper": "^0.10.11", "uuid": "^8.3.0" } }, "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "fast-csv": ["fast-csv@4.3.6", "", { "dependencies": { "@fast-csv/format": "4.3.5", "@fast-csv/parse": "4.3.6" } }, "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw=="], + "fast-xml-parser": ["fast-xml-parser@5.3.7", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-JzVLro9NQv92pOM/jTCR6mHlJh2FGwtomH8ZQjhFj/R29P2Fnj38OgPJVtcvYw6SuKClhgYuwUZf5b3rd8u2mA=="], "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], @@ -408,16 +459,24 @@ "focus-trap": ["focus-trap@7.8.0", "", { "dependencies": { "tabbable": "^6.4.0" } }, "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA=="], - "frac": ["frac@1.1.2", "", {}, "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="], - "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "fstream": ["fstream@1.0.12", "", { "dependencies": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" } }, "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg=="], + + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], @@ -426,10 +485,16 @@ "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], @@ -446,10 +511,18 @@ "is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], "is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="], + + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + "lefthook": ["lefthook@2.1.1", "", { "optionalDependencies": { "lefthook-darwin-arm64": "2.1.1", "lefthook-darwin-x64": "2.1.1", "lefthook-freebsd-arm64": "2.1.1", "lefthook-freebsd-x64": "2.1.1", "lefthook-linux-arm64": "2.1.1", "lefthook-linux-x64": "2.1.1", "lefthook-openbsd-arm64": "2.1.1", "lefthook-openbsd-x64": "2.1.1", "lefthook-windows-arm64": "2.1.1", "lefthook-windows-x64": "2.1.1" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-Tl9h9c+sG3ShzTHKuR3LAIblnnh+Mgxnm2Ul7yu9cu260Z27LEbO3V6Zw4YZFP59/2rlD42pt/llYsQCkkCFzw=="], "lefthook-darwin-arm64": ["lefthook-darwin-arm64@2.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-O/RS1j03/Fnq5zCzEb2r7UOBsqPeBuf1C5pMkIJcO4TSE6hf3rhLUkcorKc2M5ni/n5zLGtzQUXHV08/fSAT3Q=="], @@ -472,6 +545,40 @@ "lefthook-windows-x64": ["lefthook-windows-x64@2.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-1L2oGIzmhfOTxfwbe5mpSQ+m3ilpvGNymwIhn4UHq6hwHsUL6HEhODqx02GfBn6OXpVIr56bvdBAusjL/SVYGQ=="], + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], + + "listenercount": ["listenercount@1.0.1", "", {}, "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="], + + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.difference": ["lodash.difference@4.5.0", "", {}, "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="], + + "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], + + "lodash.flatten": ["lodash.flatten@4.4.0", "", {}, "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="], + + "lodash.groupby": ["lodash.groupby@4.6.0", "", {}, "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], + + "lodash.isfunction": ["lodash.isfunction@3.0.9", "", {}, "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="], + + "lodash.isnil": ["lodash.isnil@4.0.0", "", {}, "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isundefined": ["lodash.isundefined@3.0.1", "", {}, "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA=="], + + "lodash.union": ["lodash.union@4.6.0", "", {}, "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="], + + "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "mark.js": ["mark.js@8.11.1", "", {}, "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ=="], @@ -494,24 +601,34 @@ "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + "minimatch": ["minimatch@5.1.8", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minisearch": ["minisearch@7.2.0", "", {}, "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg=="], "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], "mongodb": ["mongodb@7.1.0", "", { "dependencies": { "@mongodb-js/saslprep": "^1.3.0", "bson": "^7.1.1", "mongodb-connection-string-url": "^7.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.806.0", "@mongodb-js/zstd": "^7.0.0", "gcp-metadata": "^7.0.1", "kerberos": "^7.0.0", "mongodb-client-encryption": ">=7.0.0 <7.1.0", "snappy": "^7.3.2", "socks": "^2.8.6" }, "optionalPeers": ["@aws-sdk/credential-providers", "@mongodb-js/zstd", "gcp-metadata", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg=="], "mongodb-connection-string-url": ["mongodb-connection-string-url@7.0.1", "", { "dependencies": { "@types/whatwg-url": "^13.0.0", "whatwg-url": "^14.1.0" } }, "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ=="], + "mysql2": ["mysql2@3.18.0", "", { "dependencies": { "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.2", "long": "^5.3.2", "lru.min": "^1.1.4", "named-placeholders": "^1.1.6", "sql-escaper": "^1.3.3" }, "peerDependencies": { "@types/node": ">= 8" } }, "sha512-3rupyOFks7Vq0jcjBpmg1gtgfGuCcmgrRJPEfpGzzrB/ydutupbjKkoDJGsGkrJRU6j44o2tb0McduL03/v/dQ=="], + + "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], + "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="], + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], @@ -520,22 +637,54 @@ "openai": ["openai@6.22.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw=="], + "oracledb": ["oracledb@6.10.0", "", {}, "sha512-kGUumXmrEWbSpBuKJyb9Ip3rXcNgKK6grunI3/cLPzrRvboZ6ZoLi9JQ+z6M/RIG924tY8BLflihL4CKKQAYMA=="], + "oxlint": ["oxlint@1.49.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.49.0", "@oxlint/binding-android-arm64": "1.49.0", "@oxlint/binding-darwin-arm64": "1.49.0", "@oxlint/binding-darwin-x64": "1.49.0", "@oxlint/binding-freebsd-x64": "1.49.0", "@oxlint/binding-linux-arm-gnueabihf": "1.49.0", "@oxlint/binding-linux-arm-musleabihf": "1.49.0", "@oxlint/binding-linux-arm64-gnu": "1.49.0", "@oxlint/binding-linux-arm64-musl": "1.49.0", "@oxlint/binding-linux-ppc64-gnu": "1.49.0", "@oxlint/binding-linux-riscv64-gnu": "1.49.0", "@oxlint/binding-linux-riscv64-musl": "1.49.0", "@oxlint/binding-linux-s390x-gnu": "1.49.0", "@oxlint/binding-linux-x64-gnu": "1.49.0", "@oxlint/binding-linux-x64-musl": "1.49.0", "@oxlint/binding-openharmony-arm64": "1.49.0", "@oxlint/binding-win32-arm64-msvc": "1.49.0", "@oxlint/binding-win32-ia32-msvc": "1.49.0", "@oxlint/binding-win32-x64-msvc": "1.49.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.14.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-YZffp0gM+63CJoRhHjtjRnwKtAgUnXM6j63YQ++aigji2NVvLGsUlrXo9gJUXZOdcbfShLYtA6RuTu8GZ4lzOQ=="], + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + "papaparse": ["papaparse@5.5.3", "", {}, "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A=="], "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + "pg": ["pg@8.18.0", "", { "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ=="], + + "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="], + + "pg-connection-string": ["pg-connection-string@2.11.0", "", {}, "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.11.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w=="], + + "pg-protocol": ["pg-protocol@1.11.0", "", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + "preact": ["preact@10.28.4", "", {}, "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ=="], "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], @@ -552,6 +701,8 @@ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], @@ -562,16 +713,24 @@ "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + "rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="], + "rollup": ["rollup@4.58.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.58.0", "@rollup/rollup-android-arm64": "4.58.0", "@rollup/rollup-darwin-arm64": "4.58.0", "@rollup/rollup-darwin-x64": "4.58.0", "@rollup/rollup-freebsd-arm64": "4.58.0", "@rollup/rollup-freebsd-x64": "4.58.0", "@rollup/rollup-linux-arm-gnueabihf": "4.58.0", "@rollup/rollup-linux-arm-musleabihf": "4.58.0", "@rollup/rollup-linux-arm64-gnu": "4.58.0", "@rollup/rollup-linux-arm64-musl": "4.58.0", "@rollup/rollup-linux-loong64-gnu": "4.58.0", "@rollup/rollup-linux-loong64-musl": "4.58.0", "@rollup/rollup-linux-ppc64-gnu": "4.58.0", "@rollup/rollup-linux-ppc64-musl": "4.58.0", "@rollup/rollup-linux-riscv64-gnu": "4.58.0", "@rollup/rollup-linux-riscv64-musl": "4.58.0", "@rollup/rollup-linux-s390x-gnu": "4.58.0", "@rollup/rollup-linux-x64-gnu": "4.58.0", "@rollup/rollup-linux-x64-musl": "4.58.0", "@rollup/rollup-openbsd-x64": "4.58.0", "@rollup/rollup-openharmony-arm64": "4.58.0", "@rollup/rollup-win32-arm64-msvc": "4.58.0", "@rollup/rollup-win32-ia32-msvc": "4.58.0", "@rollup/rollup-win32-x64-gnu": "4.58.0", "@rollup/rollup-win32-x64-msvc": "4.58.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "saxes": ["saxes@5.0.1", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "search-insights": ["search-insights@2.17.3", "", {}, "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ=="], "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="], + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], "shiki": ["shiki@2.5.0", "", { "dependencies": { "@shikijs/core": "2.5.0", "@shikijs/engine-javascript": "2.5.0", "@shikijs/engine-oniguruma": "2.5.0", "@shikijs/langs": "2.5.0", "@shikijs/themes": "2.5.0", "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ=="], @@ -592,7 +751,9 @@ "speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="], - "ssf": ["ssf@0.11.2", "", { "dependencies": { "frac": "~1.1.2" } }, "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sql-escaper": ["sql-escaper@1.3.3", "", {}, "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw=="], "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], @@ -620,10 +781,14 @@ "terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="], + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + "to-rotated": ["to-rotated@1.0.0", "", {}, "sha512-KsEID8AfgUy+pxVRLsWp0VzCa69wxzUDZnzGbyIST/bcgcrMvTYoFBX/QORH4YApoD89EDuUovx4BTdpOn319Q=="], "tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="], + "traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], @@ -644,8 +809,12 @@ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "unzipper": ["unzipper@0.10.14", "", { "dependencies": { "big-integer": "^1.6.17", "binary": "~0.3.0", "bluebird": "~3.4.1", "buffer-indexof-polyfill": "~1.0.0", "duplexer2": "~0.1.4", "fstream": "^1.0.12", "graceful-fs": "^4.2.2", "listenercount": "~1.0.1", "readable-stream": "~2.3.6", "setimmediate": "~1.0.4" } }, "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], @@ -662,32 +831,72 @@ "widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], - "wmf": ["wmf@1.0.2", "", {}, "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="], - - "word": ["word@0.3.0", "", {}, "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA=="], - "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], - "xlsx": ["xlsx@0.18.5", "", { "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", "codepage": "~1.15.0", "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" }, "bin": { "xlsx": "bin/xlsx.njs" } }, "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ=="], + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], + "zip-stream": ["zip-stream@4.1.1", "", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="], + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@fast-csv/format/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], + + "@fast-csv/parse/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], + + "archiver-utils/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "cli-truncate/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + "duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "glob/minimatch": ["minimatch@3.1.3", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA=="], + "ink-text-input/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "unzipper/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "zip-stream/archiver-utils": ["archiver-utils@3.0.4", "", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="], + + "archiver-utils/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "duplexer2/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "duplexer2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "jszip/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "unzipper/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "unzipper/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], } } diff --git a/package.json b/package.json index 3c7f236..228b033 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "devDependencies": { "@types/better-sqlite3": "^7.6.13", "@types/bun": "latest", + "@types/oracledb": "^6.10.1", "@types/papaparse": "^5.5.2", + "@types/pg": "^8.16.0", "lefthook": "^2.1.1", "oxlint": "latest", "typescript": "^5", @@ -38,9 +40,12 @@ "ink-spinner": "^5.0.0", "ink-text-input": "^6.0.0", "mongodb": "^7.1.0", + "mysql2": "^3.18.0", "nanoid": "^5.1.6", "openai": "^6.22.0", + "oracledb": "^6.10.0", "papaparse": "^5.5.3", + "pg": "^8.18.0", "react": "^19.2.4", "string-width": "^8.2.0" } diff --git a/scripts/check-no-private.ts b/scripts/check-no-private.ts index 59fa93f..aa7356a 100644 --- a/scripts/check-no-private.ts +++ b/scripts/check-no-private.ts @@ -1,7 +1,7 @@ /** * CI/lint script that ensures no TypeScript visibility modifiers (private, * protected, public) or JavaScript private class fields (#) appear in the - * Explorer codebase. + * codebase. * * Usage: bun scripts/check-no-private.ts * Exit code 0 = all good, non-zero = violations found. @@ -12,8 +12,8 @@ import { join, relative } from "node:path"; const ROOT = join(import.meta.dir, ".."); const SCAN_DIRS = [ - join(ROOT, "src", "explorer"), - join(ROOT, "tests", "explorer"), + join(ROOT, "src"), + join(ROOT, "tests"), ]; const EXTENSIONS = new Set([".ts", ".tsx"]); diff --git a/src/Accumulator.ts b/src/Accumulator.ts index 269fd8a..555c1f8 100644 --- a/src/Accumulator.ts +++ b/src/Accumulator.ts @@ -7,21 +7,21 @@ import type { Record } from "./Record.ts"; * Analogous to App::RecordStream::Accumulator in Perl. */ export class Accumulator { - #records: Record[] = []; + records: Record[] = []; acceptRecord(record: Record): void { this.accumulateRecord(record); } accumulateRecord(record: Record): void { - this.#records.push(record); + this.records.push(record); } getRecords(): Record[] { - return this.#records; + return this.records; } clear(): void { - this.#records = []; + this.records = []; } } diff --git a/src/BaseRegistry.ts b/src/BaseRegistry.ts index 09f3886..4c96edb 100644 --- a/src/BaseRegistry.ts +++ b/src/BaseRegistry.ts @@ -20,7 +20,7 @@ export interface RegistryEntry { } export class BaseRegistry { - #implementations = new Map>(); + implementations = new Map>(); readonly typeName: string; constructor(typeName: string) { @@ -31,10 +31,10 @@ export class BaseRegistry { * Register an implementation under a name. */ register(name: string, entry: RegistryEntry): void { - this.#implementations.set(name, entry); + this.implementations.set(name, entry); if (entry.aliases) { for (const alias of entry.aliases) { - this.#implementations.set(alias, entry); + this.implementations.set(alias, entry); } } } @@ -51,7 +51,7 @@ export class BaseRegistry { const name = parts[0]!; const args = parts.slice(1); - const entry = this.#implementations.get(name); + const entry = this.implementations.get(name); if (!entry) { throw new Error(`Bad ${this.typeName}: ${name}`); } @@ -71,14 +71,14 @@ export class BaseRegistry { * Check if a name is registered. */ has(name: string): boolean { - return this.#implementations.has(name); + return this.implementations.has(name); } /** * Get an entry by name. */ get(name: string): RegistryEntry | undefined { - return this.#implementations.get(name); + return this.implementations.get(name); } /** @@ -89,9 +89,9 @@ export class BaseRegistry { const entryToNames = new Map, string[]>(); const entries: RegistryEntry[] = []; - const sortedNames = [...this.#implementations.keys()].sort(); + const sortedNames = [...this.implementations.keys()].sort(); for (const name of sortedNames) { - const entry = this.#implementations.get(name)!; + const entry = this.implementations.get(name)!; let names = entryToNames.get(entry); if (!names) { names = []; @@ -113,7 +113,7 @@ export class BaseRegistry { * Get the detailed usage for a specific implementation. */ showImplementation(name: string): string { - const entry = this.#implementations.get(name); + const entry = this.implementations.get(name); if (!entry) { return `Bad ${this.typeName}: ${name}\n`; } @@ -124,6 +124,6 @@ export class BaseRegistry { * Get all registered names. */ names(): string[] { - return [...this.#implementations.keys()]; + return [...this.implementations.keys()]; } } diff --git a/src/DomainLanguage.ts b/src/DomainLanguage.ts index 6028b9d..c89a3ca 100644 --- a/src/DomainLanguage.ts +++ b/src/DomainLanguage.ts @@ -22,12 +22,12 @@ export interface Valuation { /** A valuation that extracts a field value via KeySpec */ export class KeySpecValuation implements Valuation { - #field: string; + field: string; constructor(field: string) { - this.#field = field; + this.field = field; } evaluateRecord(record: Record): JsonValue { - const v = findKey(record.dataRef(), this.#field, true); + const v = findKey(record.dataRef(), this.field, true); return v === undefined ? null : v; } } @@ -41,12 +41,12 @@ export class RecordValuation implements Valuation { /** A valuation from a JS function */ export class FunctionValuation implements Valuation { - #fn: (r: Record) => JsonValue; + fn: (r: Record) => JsonValue; constructor(fn: (r: Record) => JsonValue) { - this.#fn = fn; + this.fn = fn; } evaluateRecord(record: Record): JsonValue { - return this.#fn(record); + return this.fn(record); } } diff --git a/src/Executor.ts b/src/Executor.ts index 984ba7b..d9c7f91 100644 --- a/src/Executor.ts +++ b/src/Executor.ts @@ -18,31 +18,31 @@ export interface SnippetDef { } export class Executor { - #snippets: Map; - #lineCounter = 0; - #currentFilename = "NONE"; - #state: Record = {}; + snippets: Map; + lineCounter = 0; + currentFilename = "NONE"; + state: Record = {}; constructor(codeOrSnippets: string | { [name: string]: SnippetDef }) { - this.#snippets = new Map(); + this.snippets = new Map(); if (typeof codeOrSnippets === "string") { - this.#addSnippet("__DEFAULT", { + this.addSnippet("__DEFAULT", { code: codeOrSnippets, argNames: ["r"], }); } else { for (const [name, def] of Object.entries(codeOrSnippets)) { - this.#addSnippet(name, def); + this.addSnippet(name, def); } } } - #addSnippet(name: string, def: SnippetDef): void { + addSnippet(name: string, def: SnippetDef): void { const transformedCode = transformCode(def.code); const argNames = def.argNames ?? ["r"]; - const fn = compileSnippet(transformedCode, argNames, this.#state); - this.#snippets.set(name, { fn, argNames }); + const fn = compileSnippet(transformedCode, argNames, this.state); + this.snippets.set(name, { fn, argNames }); } /** @@ -57,29 +57,29 @@ export class Executor { * Execute a named snippet with the given arguments. */ executeMethod(name: string, ...args: unknown[]): unknown { - const snippet = this.#snippets.get(name); + const snippet = this.snippets.get(name); if (!snippet) { throw new Error(`No such snippet: ${name}`); } - this.#lineCounter++; - return snippet.fn(...args, this.#lineCounter, this.#currentFilename); + this.lineCounter++; + return snippet.fn(...args, this.lineCounter, this.currentFilename); } setCurrentFilename(filename: string): void { - this.#currentFilename = filename; + this.currentFilename = filename; } getCurrentFilename(): string { - return this.#currentFilename; + return this.currentFilename; } getLine(): number { - return this.#lineCounter; + return this.lineCounter; } resetLine(): void { - this.#lineCounter = 0; + this.lineCounter = 0; } } diff --git a/src/InputStream.ts b/src/InputStream.ts index c205901..5c94c82 100644 --- a/src/InputStream.ts +++ b/src/InputStream.ts @@ -7,23 +7,23 @@ import { Record } from "./Record.ts"; * Analogous to App::RecordStream::InputStream in Perl. */ export class InputStream { - #lines: string[] | null = null; - #lineIndex = 0; + lines: string[] | null = null; + lineIndex = 0; // Using inline type to avoid Bun-specific ReadableStreamDefaultReader incompatibility - #byteReader: { read(): Promise<{ done: boolean; value?: Uint8Array }> } | null = null; - #decoder = new TextDecoder(); - #buffer = ""; - #bufferOffset = 0; - #done = false; - #next: InputStream | null; - #filename: string; + byteReader: { read(): Promise<{ done: boolean; value?: Uint8Array }> } | null = null; + decoder = new TextDecoder(); + buffer = ""; + bufferOffset = 0; + done = false; + next: InputStream | null; + filename: string; constructor(options: { next?: InputStream | null; filename?: string; }) { - this.#next = options.next ?? null; - this.#filename = options.filename ?? "UNKNOWN"; + this.next = options.next ?? null; + this.filename = options.filename ?? "UNKNOWN"; } /** @@ -31,7 +31,7 @@ export class InputStream { */ static fromString(str: string, next?: InputStream): InputStream { const stream = new InputStream({ next, filename: "STRING_INPUT" }); - stream.#lines = str.split("\n").filter((l) => l.trim() !== ""); + stream.lines = str.split("\n").filter((l) => l.trim() !== ""); return stream; } @@ -40,7 +40,7 @@ export class InputStream { */ static fromFile(filePath: string, next?: InputStream): InputStream { const stream = new InputStream({ next, filename: filePath }); - stream.#initFile(filePath); + stream.initFile(filePath); return stream; } @@ -52,7 +52,7 @@ export class InputStream { next?: InputStream ): InputStream { const stream = new InputStream({ next, filename: "STREAM_INPUT" }); - stream.#byteReader = readable.getReader(); + stream.byteReader = readable.getReader(); return stream; } @@ -84,9 +84,9 @@ export class InputStream { return lastStream!; } - #initFile(filePath: string): void { + initFile(filePath: string): void { const file = Bun.file(filePath); - this.#byteReader = file.stream().getReader(); + this.byteReader = file.stream().getReader(); } /** @@ -94,29 +94,29 @@ export class InputStream { * Returns null when all streams are exhausted. */ async getRecord(): Promise { - if (this.#done) { - return this.#callNextRecord(); + if (this.done) { + return this.callNextRecord(); } // String-based input - if (this.#lines !== null) { - if (this.#lineIndex < this.#lines.length) { - const line = this.#lines[this.#lineIndex]!; - this.#lineIndex++; + if (this.lines !== null) { + if (this.lineIndex < this.lines.length) { + const line = this.lines[this.lineIndex]!; + this.lineIndex++; return Record.fromJSON(line); } - this.#done = true; - return this.#callNextRecord(); + this.done = true; + return this.callNextRecord(); } // Stream-based input - if (this.#byteReader) { - const line = await this.#readLine(); + if (this.byteReader) { + const line = await this.readLine(); if (line !== null) { return Record.fromJSON(line); } - this.#done = true; - return this.#callNextRecord(); + this.done = true; + return this.callNextRecord(); } return null; @@ -128,44 +128,44 @@ export class InputStream { // is 2x slower due to per-segment TextDecoder overhead; Bun native stdin // adds subprocess cost. Line reading is ~10% of getRecord() time; JSON // parsing dominates. - async #readLine(): Promise { + async readLine(): Promise { while (true) { - const newlineIndex = this.#buffer.indexOf("\n", this.#bufferOffset); + const newlineIndex = this.buffer.indexOf("\n", this.bufferOffset); if (newlineIndex >= 0) { - const line = this.#buffer.slice(this.#bufferOffset, newlineIndex).trim(); - this.#bufferOffset = newlineIndex + 1; + const line = this.buffer.slice(this.bufferOffset, newlineIndex).trim(); + this.bufferOffset = newlineIndex + 1; if (line !== "") return line; continue; } - if (!this.#byteReader) return null; - const { value, done } = await this.#byteReader.read(); + if (!this.byteReader) return null; + const { value, done } = await this.byteReader.read(); if (done) { // Return any remaining content - const remaining = this.#buffer.slice(this.#bufferOffset).trim(); - this.#buffer = ""; - this.#bufferOffset = 0; + const remaining = this.buffer.slice(this.bufferOffset).trim(); + this.buffer = ""; + this.bufferOffset = 0; return remaining !== "" ? remaining : null; } // Compact consumed portion before appending new data - if (this.#bufferOffset > 0) { - this.#buffer = this.#buffer.slice(this.#bufferOffset); - this.#bufferOffset = 0; + if (this.bufferOffset > 0) { + this.buffer = this.buffer.slice(this.bufferOffset); + this.bufferOffset = 0; } - this.#buffer += this.#decoder.decode(value, { stream: true }); + this.buffer += this.decoder.decode(value, { stream: true }); } } - async #callNextRecord(): Promise { - if (!this.#next) return null; + async callNextRecord(): Promise { + if (!this.next) return null; // Flatten chain to prevent deep recursion - if (this.#next.#done) { - this.#next = this.#next.#next; + if (this.next.done) { + this.next = this.next.next; } - if (!this.#next) return null; + if (!this.next) return null; - return this.#next.getRecord(); + return this.next.getRecord(); } /** @@ -191,8 +191,8 @@ export class InputStream { } getFilename(): string { - if (!this.#done) return this.#filename; - if (this.#next) return this.#next.getFilename(); - return this.#filename; + if (!this.done) return this.filename; + if (this.next) return this.next.getFilename(); + return this.filename; } } diff --git a/src/KeyGroups.ts b/src/KeyGroups.ts index 1dfafb1..6dcde13 100644 --- a/src/KeyGroups.ts +++ b/src/KeyGroups.ts @@ -29,8 +29,8 @@ const VALID_OPTIONS: Record = { }; export class KeyGroups { - #groups: GroupMember[] = []; - #cachedSpecs: string[] | null = null; + groups: GroupMember[] = []; + cachedSpecs: string[] | null = null; constructor(...args: string[]) { for (const arg of args) { @@ -39,19 +39,19 @@ export class KeyGroups { } hasAnyGroup(): boolean { - return this.#groups.length > 0; + return this.groups.length > 0; } addGroups(groups: string): void { for (const groupSpec of groups.split(",")) { if (groupSpec.startsWith("!")) { - this.#groups.push(new KeyGroupRegex(groupSpec)); + this.groups.push(new KeyGroupRegex(groupSpec)); } else { - this.#groups.push(new KeyGroupKeySpec(groupSpec)); + this.groups.push(new KeyGroupKeySpec(groupSpec)); } } // Invalidate cache when groups change - this.#cachedSpecs = null; + this.cachedSpecs = null; } /** @@ -59,7 +59,7 @@ export class KeyGroups { */ getKeyspecsForRecord(record: JsonObject): string[] { const specs: string[] = []; - for (const group of this.#groups) { + for (const group of this.groups) { specs.push(...group.getFields(record)); } return specs; @@ -69,10 +69,10 @@ export class KeyGroups { * Get keyspecs, caching after first call. */ getKeyspecs(record: JsonObject): string[] { - if (this.#cachedSpecs === null) { - this.#cachedSpecs = this.getKeyspecsForRecord(record); + if (this.cachedSpecs === null) { + this.cachedSpecs = this.getKeyspecsForRecord(record); } - return this.#cachedSpecs; + return this.cachedSpecs; } } @@ -80,15 +80,15 @@ export class KeyGroups { * A plain key spec group member - resolves a single key spec. */ class KeyGroupKeySpec implements GroupMember { - #keySpec: KeySpec; + keySpec: KeySpec; constructor(spec: string) { - this.#keySpec = new KeySpec(spec); + this.keySpec = new KeySpec(spec); } getFields(record: JsonObject): string[] { - if (this.#keySpec.hasKeySpec(record)) { - const keyList = this.#keySpec.getKeyListForSpec(record); + if (this.keySpec.hasKeySpec(record)) { + const keyList = this.keySpec.getKeyListForSpec(record); if (keyList.length > 0) { return [keyList.join("/")]; } @@ -187,7 +187,7 @@ class KeyGroupRegex implements GroupMember { getFields(record: JsonObject): string[] { const specs: string[] = []; const regex = new RegExp(this.regex); - const allSpecs = this.#getSpecs(record); + const allSpecs = this.getSpecs(record); for (const spec of allSpecs) { if (regex.test(spec)) { @@ -202,7 +202,7 @@ class KeyGroupRegex implements GroupMember { return specs; } - #getSpecs(record: JsonObject): string[] { + getSpecs(record: JsonObject): string[] { let minDepth = 1; let maxDepth = 1; @@ -215,11 +215,11 @@ class KeyGroupRegex implements GroupMember { } const paths: string[][] = []; - this.#getPaths(record, 1, minDepth, maxDepth, [], paths); + this.getPaths(record, 1, minDepth, maxDepth, [], paths); return paths.map((p) => p.join("/")); } - #getPaths( + getPaths( data: JsonValue, currentDepth: number, minDepth: number, @@ -243,7 +243,7 @@ class KeyGroupRegex implements GroupMember { if (Array.isArray(data)) { for (let index = 0; index < data.length; index++) { if (currentDepth <= maxDepth || maxDepth === -1) { - this.#getPaths( + this.getPaths( data[index]!, currentDepth + 1, minDepth, @@ -259,7 +259,7 @@ class KeyGroupRegex implements GroupMember { if (typeof data === "object" && data !== null && !Array.isArray(data)) { for (const key of Object.keys(data as JsonObject)) { if (currentDepth <= maxDepth || maxDepth === -1) { - this.#getPaths( + this.getPaths( (data as JsonObject)[key]!, currentDepth + 1, minDepth, diff --git a/src/KeySpec.ts b/src/KeySpec.ts index 369a57b..12a6e6f 100644 --- a/src/KeySpec.ts +++ b/src/KeySpec.ts @@ -159,8 +159,8 @@ export class KeySpec { readonly spec: string; readonly parsedKeys: string[]; readonly fuzzy: boolean; - private _compiledGetter: CompiledGetter | null = null; - private _compiledSetter: CompiledSetter | null = null; + compiledGetter: CompiledGetter | null = null; + compiledSetter: CompiledSetter | null = null; constructor(spec: string) { // Check cache first @@ -169,8 +169,8 @@ export class KeySpec { this.spec = cached.spec; this.parsedKeys = cached.parsedKeys; this.fuzzy = cached.fuzzy; - this._compiledGetter = cached._compiledGetter; - this._compiledSetter = cached._compiledSetter; + this.compiledGetter = cached.compiledGetter; + this.compiledSetter = cached.compiledSetter; return; } @@ -186,8 +186,8 @@ export class KeySpec { // Eagerly compile for non-fuzzy multi-key specs if (!this.fuzzy && this.parsedKeys.length > 1) { - this._compiledGetter = compileGetter(this.parsedKeys); - this._compiledSetter = compileSetter(this.parsedKeys); + this.compiledGetter = compileGetter(this.parsedKeys); + this.compiledSetter = compileSetter(this.parsedKeys); } specRegistry.set(spec, this); @@ -216,8 +216,8 @@ export class KeySpec { // Compiled getter fast path (read-only traversal, no throwError // to preserve original error types for scalar-in-path cases) - if (this._compiledGetter && noVivify && !throwError) { - const value = this._compiledGetter(data); + if (this.compiledGetter && noVivify && !throwError) { + const value = this.compiledGetter(data); if (value !== undefined) return { value, found: true }; return { value: undefined, found: false }; } @@ -234,11 +234,11 @@ export class KeySpec { ) as { value: JsonValue | undefined; found: boolean }; // Lazy compile for fuzzy specs after first successful resolution - if (this.fuzzy && !this._compiledGetter && result.found) { + if (this.fuzzy && !this.compiledGetter && result.found) { const resolvedKeys = this.getKeyListForSpec(data); if (resolvedKeys.length > 0) { - this._compiledGetter = compileGetter(resolvedKeys); - this._compiledSetter = compileSetter(resolvedKeys); + this.compiledGetter = compileGetter(resolvedKeys); + this.compiledSetter = compileSetter(resolvedKeys); } } @@ -256,8 +256,8 @@ export class KeySpec { return data[key]; } // Compiled getter - if (this._compiledGetter) { - const value = this._compiledGetter(data); + if (this.compiledGetter) { + const value = this.compiledGetter(data); if (value !== undefined) return value; if (throwError) throw new NoSuchKeyError(); return undefined; @@ -276,19 +276,19 @@ export class KeySpec { } // Compiled setter fast path - if (this._compiledSetter) { - this._compiledSetter(data, value); + if (this.compiledSetter) { + this.compiledSetter(data, value); return; } setNestedValue(data, this.parsedKeys, value, this.fuzzy); // Lazy compile for fuzzy specs after first set - if (this.fuzzy && !this._compiledSetter) { + if (this.fuzzy && !this.compiledSetter) { const resolvedKeys = this.getKeyListForSpec(data); if (resolvedKeys.length > 0) { - this._compiledGetter = compileGetter(resolvedKeys); - this._compiledSetter = compileSetter(resolvedKeys); + this.compiledGetter = compileGetter(resolvedKeys); + this.compiledSetter = compileSetter(resolvedKeys); } } } diff --git a/src/Operation.ts b/src/Operation.ts index 38f90fb..78f261e 100644 --- a/src/Operation.ts +++ b/src/Operation.ts @@ -89,19 +89,19 @@ interface HelpType { export abstract class Operation implements RecordReceiver { next: RecordReceiver; - #filenameKey: string | null = null; - #currentFilename = "NONE"; - #wantsHelp = false; - #exitValue = 0; - #helpTypes: Map; + filenameKey: string | null = null; + currentFilename = "NONE"; + wantsHelp = false; + exitValue = 0; + helpTypes: Map; constructor(next?: RecordReceiver) { this.next = next ?? new PrinterReceiver(); - this.#helpTypes = new Map([ + this.helpTypes = new Map([ ["all", { use: false, skipInAll: true, - code: () => this.#allHelp(), + code: () => this.allHelp(), description: "Output all help for this script", }], ["snippet", { @@ -191,8 +191,8 @@ export abstract class Operation implements RecordReceiver { * Emit a record downstream. */ pushRecord(record: Record): boolean { - if (this.#filenameKey) { - record.set(this.#filenameKey, this.#currentFilename); + if (this.filenameKey) { + record.set(this.filenameKey, this.currentFilename); } return this.next.acceptRecord(record); } @@ -224,18 +224,18 @@ export abstract class Operation implements RecordReceiver { * Set the filename key for annotating records with source filename. */ setFilenameKey(key: string): void { - this.#filenameKey = key; + this.filenameKey = key; } /** * Update the current input filename. */ updateCurrentFilename(filename: string): void { - this.#currentFilename = filename; + this.currentFilename = filename; } getCurrentFilename(): string { - return this.#currentFilename; + return this.currentFilename; } /** @@ -247,19 +247,19 @@ export abstract class Operation implements RecordReceiver { } setWantsHelp(val: boolean): void { - this.#wantsHelp = val; + this.wantsHelp = val; } getWantsHelp(): boolean { - return this.#wantsHelp; + return this.wantsHelp; } setExitValue(val: number): void { - this.#exitValue = val; + this.exitValue = val; } getExitValue(): number { - return this.#exitValue; + return this.exitValue; } /** @@ -274,12 +274,12 @@ export abstract class Operation implements RecordReceiver { * Enable a built-in help type (e.g. "snippet", "keyspecs"). */ useHelpType(type: string): void { - const entry = this.#helpTypes.get(type); + const entry = this.helpTypes.get(type); if (entry) { entry.use = true; } // Enabling any help type also enables --help-all - const allEntry = this.#helpTypes.get("all"); + const allEntry = this.helpTypes.get("all"); if (allEntry) { allEntry.use = true; } @@ -295,7 +295,7 @@ export abstract class Operation implements RecordReceiver { skipInAll = false, optionName?: string, ): void { - this.#helpTypes.set(type, { + this.helpTypes.set(type, { use: true, skipInAll, code, @@ -303,7 +303,7 @@ export abstract class Operation implements RecordReceiver { optionName, }); // Also ensure --help-all is available - const allEntry = this.#helpTypes.get("all"); + const allEntry = this.helpTypes.get("all"); if (allEntry) { allEntry.use = true; } @@ -312,9 +312,9 @@ export abstract class Operation implements RecordReceiver { /** * Generate --help-all output: all enabled help types combined. */ - #allHelp(): string { + allHelp(): string { const parts: string[] = []; - for (const [type, info] of this.#helpTypes) { + for (const [type, info] of this.helpTypes) { if (!info.use || info.skipInAll) continue; parts.push(`Help from: --help-${type}:\n`); parts.push(info.code()); @@ -354,7 +354,7 @@ export abstract class Operation implements RecordReceiver { // Build help option handlers const helpHandlers = new Map void>(); - for (const [type, info] of this.#helpTypes) { + for (const [type, info] of this.helpTypes) { if (!info.use) continue; const optName = info.optionName ?? `help-${type}`; helpHandlers.set(`--${optName}`, () => { @@ -371,7 +371,7 @@ export abstract class Operation implements RecordReceiver { // Check help flags first if (arg === "--help" || arg === "-h") { - this.#wantsHelp = true; + this.wantsHelp = true; i++; continue; } diff --git a/src/OutputStream.ts b/src/OutputStream.ts index 0d00bc1..f46fb89 100644 --- a/src/OutputStream.ts +++ b/src/OutputStream.ts @@ -7,14 +7,14 @@ import type { FileSink } from "bun"; * Analogous to App::RecordStream::OutputStream in Perl. */ export class OutputStream { - #writer: WritableStreamDefaultWriter | null = null; - #sink: FileSink | null = null; + writer: WritableStreamDefaultWriter | null = null; + sink: FileSink | null = null; constructor(writable?: WritableStream) { if (writable) { - this.#writer = writable.getWriter(); + this.writer = writable.getWriter(); } else { - this.#sink = Bun.stdout.writer(); + this.sink = Bun.stdout.writer(); } } @@ -23,10 +23,10 @@ export class OutputStream { */ async write(record: Record): Promise { const line = record.toString() + "\n"; - if (this.#sink) { - this.#sink.write(line); - } else if (this.#writer) { - await this.#writer.write(line); + if (this.sink) { + this.sink.write(line); + } else if (this.writer) { + await this.writer.write(line); } } @@ -35,10 +35,10 @@ export class OutputStream { */ async writeLine(line: string): Promise { const output = line.endsWith("\n") ? line : line + "\n"; - if (this.#sink) { - this.#sink.write(output); - } else if (this.#writer) { - await this.#writer.write(output); + if (this.sink) { + this.sink.write(output); + } else if (this.writer) { + await this.writer.write(output); } } @@ -46,10 +46,10 @@ export class OutputStream { * Close the output stream. */ async close(): Promise { - if (this.#sink) { - await this.#sink.flush(); - } else if (this.#writer) { - await this.#writer.close(); + if (this.sink) { + await this.sink.flush(); + } else if (this.writer) { + await this.writer.close(); } } diff --git a/src/Record.ts b/src/Record.ts index e73c6d5..91c6077 100644 --- a/src/Record.ts +++ b/src/Record.ts @@ -8,25 +8,25 @@ import type { JsonValue, JsonObject } from "./types/json.ts"; * Analogous to App::RecordStream::Record in Perl. */ export class Record { - #data: JsonObject; + data: JsonObject; constructor(data?: JsonObject) { - this.#data = data ?? {}; + this.data = data ?? {}; } /** * Get a top-level field value. For nested access, use getKeySpec(). */ get(key: string): JsonValue | undefined { - return this.#data[key]; + return this.data[key]; } /** * Set a top-level field value. Returns the old value. */ set(key: string, value: JsonValue): JsonValue | undefined { - const old = this.#data[key]; - this.#data[key] = value; + const old = this.data[key]; + this.data[key] = value; return old; } @@ -35,8 +35,8 @@ export class Record { */ remove(...keys: string[]): (JsonValue | undefined)[] { return keys.map((key) => { - const old = this.#data[key]; - delete this.#data[key]; // eslint-disable-line @typescript-eslint/no-dynamic-delete + const old = this.data[key]; + delete this.data[key]; // eslint-disable-line @typescript-eslint/no-dynamic-delete return old; }); } @@ -45,14 +45,14 @@ export class Record { * Check if a top-level field exists. */ has(key: string): boolean { - return key in this.#data; + return key in this.data; } /** * Rename a field. If the old field did not exist, creates the new field with null. */ rename(oldKey: string, newKey: string): void { - const value = this.has(oldKey) ? this.#data[oldKey] : null; + const value = this.has(oldKey) ? this.data[oldKey] : null; this.set(newKey, value!); this.remove(oldKey); } @@ -64,7 +64,7 @@ export class Record { const keep = new Set(keys); for (const key of this.keys()) { if (!keep.has(key)) { - delete this.#data[key]; // eslint-disable-line @typescript-eslint/no-dynamic-delete + delete this.data[key]; // eslint-disable-line @typescript-eslint/no-dynamic-delete } } } @@ -73,7 +73,7 @@ export class Record { * Return all top-level field names. */ keys(): string[] { - return Object.keys(this.#data); + return Object.keys(this.data); } /** @@ -81,14 +81,14 @@ export class Record { * Uses a fast JSON-specific clone (no circular-ref handling needed). */ clone(): Record { - return new Record(cloneJsonObject(this.#data)); + return new Record(cloneJsonObject(this.data)); } /** * Return the underlying data as a plain JSON object (shallow copy). */ toJSON(): JsonObject { - return { ...this.#data }; + return { ...this.data }; } /** @@ -96,14 +96,14 @@ export class Record { * Mutations to the returned object will affect the record. */ dataRef(): JsonObject { - return this.#data; + return this.data; } /** * Serialize to a JSON line (no trailing newline). */ toString(): string { - return JSON.stringify(this.#data); + return JSON.stringify(this.data); } /** @@ -211,18 +211,18 @@ export class Record { if (isSimple && !allHack && !reverse) { // Fast path: simple field, ascending, no ALL hack comparator = (a: Record, b: Record): number => { - return cmpFn(a.#data[simpleKey], b.#data[simpleKey]); + return cmpFn(a.data[simpleKey], b.data[simpleKey]); }; } else if (isSimple && !allHack && reverse) { // Fast path: simple field, descending, no ALL hack comparator = (a: Record, b: Record): number => { - return -cmpFn(a.#data[simpleKey], b.#data[simpleKey]); + return -cmpFn(a.data[simpleKey], b.data[simpleKey]); }; } else { // General path: nested keys or ALL hack comparator = (a: Record, b: Record): number => { - const aVal = isSimple ? a.#data[simpleKey] : getNestedValueFromParts(a.#data, parts); - const bVal = isSimple ? b.#data[simpleKey] : getNestedValueFromParts(b.#data, parts); + const aVal = isSimple ? a.data[simpleKey] : getNestedValueFromParts(a.data, parts); + const bVal = isSimple ? b.data[simpleKey] : getNestedValueFromParts(b.data, parts); let val: number | undefined; diff --git a/src/RecordStream.ts b/src/RecordStream.ts index 305359c..9e47407 100644 --- a/src/RecordStream.ts +++ b/src/RecordStream.ts @@ -21,10 +21,10 @@ export interface SnippetOptions { * Internally uses async iterables for lazy evaluation. */ export class RecordStream { - #source: AsyncIterable; + source: AsyncIterable; constructor(source: AsyncIterable) { - this.#source = source; + this.source = source; } // ─── Static Constructors ─────────────────────────────────────── @@ -87,13 +87,13 @@ export class RecordStream { grep(predicate: string | ((r: Record) => boolean), options?: SnippetOptions): RecordStream { if (typeof predicate === "string" && options?.lang && !isJsLang(options.lang)) { const runner = createSnippetRunner(options.lang); - return new RecordStream(runnerGrepAsync(this.#source, predicate, runner)); + return new RecordStream(runnerGrepAsync(this.source, predicate, runner)); } const fn = typeof predicate === "string" ? makeRecordPredicate(predicate) : predicate; - const src = this.#source; + const src = this.source; return new RecordStream(filterAsync(src, fn)); } @@ -104,10 +104,10 @@ export class RecordStream { eval(snippet: string, options?: SnippetOptions): RecordStream { if (options?.lang && !isJsLang(options.lang)) { const runner = createSnippetRunner(options.lang); - return new RecordStream(runnerEvalAsync(this.#source, snippet, runner)); + return new RecordStream(runnerEvalAsync(this.source, snippet, runner)); } const executor = new Executor(`${snippet}; return r;`); - const src = this.#source; + const src = this.source; return new RecordStream( mapAsync(src, (r) => { executor.executeCode(r); @@ -126,13 +126,13 @@ export class RecordStream { ): RecordStream { if (typeof snippetOrFn === "string" && options?.lang && !isJsLang(options.lang)) { const runner = createSnippetRunner(options.lang); - return new RecordStream(runnerXformAsync(this.#source, snippetOrFn, runner)); + return new RecordStream(runnerXformAsync(this.source, snippetOrFn, runner)); } const fn = typeof snippetOrFn === "string" ? makeRecordXform(snippetOrFn) : snippetOrFn; - const src = this.#source; + const src = this.source; return new RecordStream(flatMapAsync(src, fn)); } @@ -141,7 +141,7 @@ export class RecordStream { * This is a buffering operation (must consume all input). */ sort(...keys: string[]): RecordStream { - const src = this.#source; + const src = this.source; return new RecordStream(sortAsync(src, keys)); } @@ -150,7 +150,7 @@ export class RecordStream { * Records must be sorted by the given keys for correct results. */ uniq(...keys: string[]): RecordStream { - const src = this.#source; + const src = this.source; return new RecordStream(uniqAsync(src, keys)); } @@ -158,7 +158,7 @@ export class RecordStream { * Take the first N records. */ head(n: number): RecordStream { - const src = this.#source; + const src = this.source; return new RecordStream(takeAsync(src, n)); } @@ -166,7 +166,7 @@ export class RecordStream { * Skip the first N records and emit the rest. */ tail(n: number): RecordStream { - const src = this.#source; + const src = this.source; return new RecordStream(skipAsync(src, n)); } @@ -174,7 +174,7 @@ export class RecordStream { * Apply aggregators grouped by keys. */ collate(options: CollateOptions): RecordStream { - const src = this.#source; + const src = this.source; return new RecordStream(collateAsync(src, options)); } @@ -182,7 +182,7 @@ export class RecordStream { * Map each record to a new record using a function. */ map(fn: (r: Record) => Record): RecordStream { - const src = this.#source; + const src = this.source; return new RecordStream(mapAsync(src, fn)); } @@ -190,7 +190,7 @@ export class RecordStream { * Reverse the order of records (buffering operation). */ reverse(): RecordStream { - const src = this.#source; + const src = this.source; return new RecordStream(reverseAsync(src)); } @@ -198,7 +198,7 @@ export class RecordStream { * Flatten array fields into separate records. */ decollate(field: string): RecordStream { - const src = this.#source; + const src = this.source; return new RecordStream( flatMapAsync(src, (r) => { const val = findKey(r.dataRef(), field); @@ -216,8 +216,8 @@ export class RecordStream { * Chain another RecordStream after this one. */ concat(other: RecordStream): RecordStream { - const src1 = this.#source; - const src2 = other.#source; + const src1 = this.source; + const src2 = other.source; return new RecordStream(concatAsync(src1, src2)); } @@ -228,7 +228,7 @@ export class RecordStream { */ async toArray(): Promise { const result: Record[] = []; - for await (const record of this.#source) { + for await (const record of this.source) { result.push(record); } return result; @@ -247,7 +247,7 @@ export class RecordStream { */ async toJsonLines(): Promise { const lines: string[] = []; - for await (const record of this.#source) { + for await (const record of this.source) { lines.push(record.toString()); } return lines.join("\n") + "\n"; @@ -274,7 +274,7 @@ export class RecordStream { async pipe(writable: WritableStream): Promise { const writer = writable.getWriter(); try { - for await (const record of this.#source) { + for await (const record of this.source) { await writer.write(record.toString() + "\n"); } } finally { @@ -286,7 +286,7 @@ export class RecordStream { * Get the async iterable source for manual iteration. */ [Symbol.asyncIterator](): AsyncIterator { - return this.#source[Symbol.asyncIterator](); + return this.source[Symbol.asyncIterator](); } } diff --git a/src/cli/dispatcher.ts b/src/cli/dispatcher.ts index 3e1796b..e4f2d80 100644 --- a/src/cli/dispatcher.ts +++ b/src/cli/dispatcher.ts @@ -122,10 +122,11 @@ for (const [name, Ctor] of operationRegistry) { } /** - * Input operations that can consume bulk stdin content via parseContent(). - * When they have no file args, we read all of stdin and call parseContent(). + * Input operations that consume bulk content via parseContent()/parseXml(). + * For these ops, when no file args are given we read all of stdin as one string. + * When file args are given, the dispatcher reads each file and calls parseContent(). */ -const BULK_STDIN_OPS = new Set(["fromcsv", "fromjsonarray", "fromkv", "fromxml"]); +const BULK_CONTENT_OPS = new Set(["fromcsv", "fromjsonarray", "fromxml"]); /** * Read all of stdin as a string. @@ -175,6 +176,26 @@ async function readStdinLines(callback: (line: string) => boolean): Promise boolean): void { + const content = readFileSync(path); + for (const line of content.split("\n")) { + if (line === "") continue; + if (!callback(line)) return; + } +} + /** * Look up an operation constructor by name. */ @@ -240,79 +261,113 @@ export async function runOperation(command: string, args: string[]): Promise { - if (op.wantsInput()) { - // Determine if this is a line-oriented input op (has custom acceptLine) - // or a record-oriented transform op - const isLineOriented = hasCustomAcceptLine(op); + const fileArgs = getFileArgs(op); + const isBulk = BULK_CONTENT_OPS.has(command); + const isLineOriented = !isBulk && hasCustomAcceptLine(op); + if (fileArgs.length > 0 && (op.wantsInput() || isBulk)) { + // --- Feed from files --- + if (isBulk) { + for (const file of fileArgs) { + op.updateCurrentFilename(file); + const content = readFileSync(file); + callParseContent(command, op, content); + } + } else if (isLineOriented) { + for (const file of fileArgs) { + op.updateCurrentFilename(file); + feedFileLines(file, (line) => op.acceptLine(line)); + } + } else { + // Record-oriented (transform ops) + for (const file of fileArgs) { + op.updateCurrentFilename(file); + feedFileLines(file, (line) => { + try { + const record = Record.fromJSON(line); + return op.acceptRecord(record); + } catch { + return true; + } + }); + } + } + await op.finish(); + } else if (op.wantsInput()) { + // --- Feed from stdin (line or record oriented) --- if (isLineOriented) { - // Feed raw lines from stdin — used by fromre, fromsplit, fromapache, etc. - await readStdinLines((line) => { - if (op.acceptLine) { - return op.acceptLine(line); - } - return true; - }); + await readStdinLines((line) => op.acceptLine(line)); } else { - // Feed JSON records from stdin or file args - // Parse remaining args (after options) to find file paths - // Since init() already consumed options, we check if the operation - // has stashed extra args that look like file paths. await readStdinLines((line) => { try { const record = Record.fromJSON(line); return op.acceptRecord(record); } catch { - // If it's not JSON, skip it return true; } }); } await op.finish(); - } else if (BULK_STDIN_OPS.has(command) && needsStdinContent(op)) { - // Input op that needs bulk stdin content (no file args given) + } else if (isBulk && needsStdinContent(op)) { + // --- Bulk stdin (fromcsv, fromjsonarray, fromxml with no args) --- const content = await readAllStdin(); if (content.trim()) { callParseContent(command, op, content); } await op.finish(); } else { - // Self-contained operation (fromps, fromdb, etc.) or has file args + // --- Self-contained (fromps, fromdb, fromtcpdump, fromxls, etc.) --- await op.finish(); } } +/** + * Get file args from an operation's extraArgs property. + */ +function getFileArgs(op: Operation): string[] { + const opRecord = op as unknown as { [key: string]: unknown }; + const extraArgs = opRecord["extraArgs"]; + if (Array.isArray(extraArgs)) { + return extraArgs as string[]; + } + return []; +} + /** * Check if the operation has a custom acceptLine implementation * (indicating it processes raw text lines, not JSON records). */ function hasCustomAcceptLine(op: Operation): boolean { - // Check if the prototype overrides acceptLine const proto = Object.getPrototypeOf(op) as { [key: string]: unknown }; return typeof proto["acceptLine"] === "function" && proto["acceptLine"] !== Operation.prototype.acceptLine; } /** - * Check if an input operation needs stdin content (has no file args to process). - * We detect this by checking the extraArgs property which input ops use for files. + * Check if an input operation needs stdin content (has no file args or URL args). */ function needsStdinContent(op: Operation): boolean { - // Input ops store file args in extraArgs; if empty, they need stdin const opRecord = op as unknown as { [key: string]: unknown }; const extraArgs = opRecord["extraArgs"]; - if (Array.isArray(extraArgs)) { - return extraArgs.length === 0; - } + if (Array.isArray(extraArgs) && extraArgs.length > 0) return false; + // fromxml stores URL args separately; if it has URLs it doesn't need stdin + const urlArgs = opRecord["urlArgs"]; + if (Array.isArray(urlArgs) && urlArgs.length > 0) return false; return true; } diff --git a/src/operations/input/fromapache.ts b/src/operations/input/fromapache.ts index 8809167..eca82c4 100644 --- a/src/operations/input/fromapache.ts +++ b/src/operations/input/fromapache.ts @@ -31,6 +31,7 @@ type ParseMode = "fast" | "strict"; export class FromApache extends Operation { mode: ParseMode = "fast"; strictFormats: string[] | null = null; + extraArgs: string[] = []; acceptRecord(_record: Record): boolean { return true; @@ -121,6 +122,8 @@ export class FromApache extends Operation { } } } + + this.extraArgs = remaining; } processLine(line: string): void { diff --git a/src/operations/input/fromcsv.ts b/src/operations/input/fromcsv.ts index 395bdfd..2927fbc 100644 --- a/src/operations/input/fromcsv.ts +++ b/src/operations/input/fromcsv.ts @@ -96,19 +96,6 @@ export class FromCsv extends Operation { return false; } - override streamDone(): void { - if (this.extraArgs.length > 0) { - for (const file of this.extraArgs) { - this.updateCurrentFilename(file); - const content = readFileSync(file); - this.parseContent(content); - } - } else { - // For programmatic use, stdin should be passed as content - // In CLI mode, stdin will be handled by the dispatcher - } - } - /** * Parse CSV content and push records. * This is the main entry point for both file and stdin processing. @@ -153,11 +140,6 @@ export class FromCsv extends Operation { } } -function readFileSync(path: string): string { - const fs = require("node:fs") as typeof import("node:fs"); - return fs.readFileSync(path, "utf-8"); -} - import type { CommandDoc } from "../../types/CommandDoc.ts"; export const documentation: CommandDoc = { diff --git a/src/operations/input/fromdb.ts b/src/operations/input/fromdb.ts index f658277..2a24209 100644 --- a/src/operations/input/fromdb.ts +++ b/src/operations/input/fromdb.ts @@ -6,6 +6,8 @@ import type { JsonObject, JsonValue } from "../../types/json.ts"; /** * Execute SQL queries and produce records from results. * + * Supports SQLite (default) and PostgreSQL (--type pg). + * * Analogous to App::RecordStream::Operation::fromdb in Perl. */ export class FromDb extends Operation { @@ -13,6 +15,11 @@ export class FromDb extends Operation { sql: string | null = null; dbFile: string | null = null; dbType = "sqlite"; + host: string | null = null; + port: string | null = null; + dbName: string | null = null; + user: string | null = null; + password: string | null = null; acceptRecord(_record: Record): boolean { return true; @@ -42,7 +49,7 @@ export class FromDb extends Operation { handler: (v) => { this.dbFile = v as string; }, - description: "Path to the database file", + description: "Path to the database file (sqlite)", }, { long: "type", @@ -50,7 +57,55 @@ export class FromDb extends Operation { handler: (v) => { this.dbType = v as string; }, - description: "Database type (default: sqlite)", + description: "Database type: sqlite (default), pg, mysql, oracle", + }, + { + long: "host", + type: "string", + handler: (v) => { + this.host = v as string; + }, + description: "Hostname for database connection (pg, mysql)", + }, + { + long: "port", + type: "string", + handler: (v) => { + this.port = v as string; + }, + description: "Port for database connection (pg, mysql)", + }, + { + long: "db", + type: "string", + handler: (v) => { + this.dbName = v as string; + }, + description: "Database name (pg, mysql, oracle tnsname)", + }, + { + long: "dbname", + type: "string", + handler: (v) => { + this.dbName = v as string; + }, + description: "Database name (alias for --db)", + }, + { + long: "user", + type: "string", + handler: (v) => { + this.user = v as string; + }, + description: "Database user", + }, + { + long: "password", + type: "string", + handler: (v) => { + this.password = v as string; + }, + description: "Database password", }, ]; @@ -63,6 +118,18 @@ export class FromDb extends Operation { if (!this.sql) { this.sql = `SELECT * FROM ${this.tableName}`; } + + if (this.dbType === "pg" && !this.dbName) { + throw new Error("--db is required for PostgreSQL databases"); + } + + if (this.dbType === "mysql" && (!this.host || !this.dbName)) { + throw new Error("--host and --db are required for MySQL databases"); + } + + if (this.dbType === "oracle" && !this.dbName) { + throw new Error("--db (tnsname) is required for Oracle databases"); + } } override wantsInput(): boolean { @@ -70,12 +137,53 @@ export class FromDb extends Operation { } override streamDone(): void { - if (this.dbType !== "sqlite") { - throw new Error( - `Database type '${this.dbType}' is not supported yet. Only sqlite is currently supported.` - ); + if (this.dbType === "sqlite") { + this.runSqlite(); + } + // pg, mysql, oracle are async - handled via streamDoneAsync in finish() + } + + async streamDoneAsync(): Promise { + switch (this.dbType) { + case "sqlite": + this.runSqlite(); + break; + case "pg": + await this.runPostgres(); + break; + case "mysql": + await this.runMysql(); + break; + case "oracle": + await this.runOracle(); + break; + default: + throw new Error( + `Database type '${this.dbType}' is not supported. Supported types: sqlite, pg, mysql, oracle` + ); + } + } + + /** + * Override finish to support async database types. + * The dispatcher and executor both call `await op.finish()`, so returning + * a Promise here is correctly awaited at runtime even though the base + * class types this as void. + */ + override finish(): void { + if (this.dbType === "sqlite") { + this.streamDone(); + this.next.finish(); + return; } + // For async types (pg, mysql, oracle), return the Promise so the + // dispatcher's `await op.finish()` waits for it at runtime. + return this.streamDoneAsync().then(() => { + this.next.finish(); + }) as unknown as void; + } + runSqlite(): void { if (!this.dbFile) { throw new Error("--dbfile is required for sqlite databases"); } @@ -101,6 +209,130 @@ export class FromDb extends Operation { db.close(); } } + + async runPostgres(): Promise { + const { Client } = await import("pg"); + + const config: { + database: string; + host?: string; + port?: number; + user?: string; + password?: string; + } = { + database: this.dbName!, + }; + + // When --host is NOT provided, leave it unset so the driver uses Unix domain socket + if (this.host) { + config.host = this.host; + } + + // Only include port when explicitly provided + if (this.port) { + config.port = parseInt(this.port, 10); + } + + if (this.user) { + config.user = this.user; + } + + if (this.password) { + config.password = this.password; + } + + const client = new Client(config); + try { + await client.connect(); + const result = await client.query(this.sql!); + + for (const row of result.rows) { + this.pushRecord(new Record(row as JsonObject)); + } + } finally { + await client.end(); + } + } + + async runMysql(): Promise { + const mysql = await import("mysql2/promise"); + + const config: { + host: string; + database: string; + port?: number; + user?: string; + password?: string; + } = { + host: this.host!, + database: this.dbName!, + }; + + if (this.port) { + config.port = parseInt(this.port, 10); + } + + if (this.user) { + config.user = this.user; + } + + if (this.password) { + config.password = this.password; + } + + const connection = await mysql.createConnection(config); + try { + const [rows] = await connection.execute(this.sql!); + + for (const row of rows as JsonObject[]) { + this.pushRecord(new Record(row)); + } + } finally { + await connection.end(); + } + } + + async runOracle(): Promise { + let oracledb; + try { + oracledb = await import("oracledb"); + } catch { + throw new Error( + "Oracle support requires the 'oracledb' package. Install with: bun add oracledb" + ); + } + + const config: { + connectString: string; + user?: string; + password?: string; + } = { + connectString: this.dbName!, + }; + + if (this.user) { + config.user = this.user; + } + + if (this.password) { + config.password = this.password; + } + + const connection = await oracledb.default.getConnection(config); + try { + const result = await connection.execute(this.sql!, [], { + outFormat: oracledb.default.OUT_FORMAT_OBJECT, + }); + + if (result.rows) { + for (const row of result.rows) { + this.pushRecord(new Record(row as JsonObject)); + } + } + } finally { + await connection.close(); + } + } } import type { CommandDoc } from "../../types/CommandDoc.ts"; @@ -110,7 +342,7 @@ export const documentation: CommandDoc = { category: "input", synopsis: "recs fromdb [options]", description: - "Execute a select statement on a database and create a record stream from the results. The keys of the record will be the column names and the values the row values.", + "Execute a select statement on a database and create a record stream from the results. The keys of the record will be the column names and the values the row values.\n\nSupports SQLite (default), PostgreSQL (--type pg), MySQL (--type mysql), and Oracle (--type oracle).\n\nPostgreSQL and MySQL require the 'pg' and 'mysql2' packages respectively. Oracle requires the 'oracledb' package (optional, install with: bun add oracledb).", options: [ { flags: ["--table"], @@ -125,22 +357,70 @@ export const documentation: CommandDoc = { { flags: ["--dbfile"], argument: "", - description: "Path to the database file.", + description: "Path to the database file (sqlite).", }, { flags: ["--type"], argument: "", - description: "Database type (default: sqlite).", + description: + "Database type: sqlite (default), pg, mysql, oracle.", + }, + { + flags: ["--host"], + argument: "", + description: + "Hostname for database connection. For pg, omit to use Unix domain socket. Required for mysql.", + }, + { + flags: ["--port"], + argument: "", + description: "Port for database connection (pg, mysql).", + }, + { + flags: ["--db", "--dbname"], + argument: "", + description: + "Database name. Required for pg, mysql. For oracle, this is the TNS name.", + }, + { + flags: ["--user"], + argument: "", + description: "Database user for authentication.", + }, + { + flags: ["--password"], + argument: "", + description: "Database password for authentication.", }, ], examples: [ { - description: "Dump a table", + description: "Dump a SQLite table", command: "recs fromdb --type sqlite --dbfile testDb --table recs", }, { - description: "Run a select statement", - command: "recs fromdb --dbfile testDb --sql 'SELECT * FROM recs WHERE id > 9'", + description: "Run a SQLite select statement", + command: + "recs fromdb --dbfile testDb --sql 'SELECT * FROM recs WHERE id > 9'", + }, + { + description: "Query PostgreSQL via Unix socket", + command: "recs fromdb --type pg --db mydb --table users", + }, + { + description: "Query PostgreSQL with host and credentials", + command: + "recs fromdb --type pg --host db.example.com --port 5432 --db mydb --user admin --password secret --sql 'SELECT * FROM users'", + }, + { + description: "Query MySQL", + command: + "recs fromdb --type mysql --host localhost --db mydb --user root --table orders", + }, + { + description: "Query Oracle", + command: + "recs fromdb --type oracle --db MYDB --user scott --password tiger --table emp", }, ], }; diff --git a/src/operations/input/fromjsonarray.ts b/src/operations/input/fromjsonarray.ts index 2e29fc6..5f3b3b9 100644 --- a/src/operations/input/fromjsonarray.ts +++ b/src/operations/input/fromjsonarray.ts @@ -36,16 +36,6 @@ export class FromJsonArray extends Operation { return false; } - override streamDone(): void { - if (this.extraArgs.length > 0) { - for (const file of this.extraArgs) { - this.updateCurrentFilename(file); - const content = readFileSync(file); - this.parseContent(content); - } - } - } - parseContent(content: string): void { const parsed: unknown = JSON.parse(content); @@ -77,11 +67,6 @@ export class FromJsonArray extends Operation { } } -function readFileSync(path: string): string { - const fs = require("node:fs") as typeof import("node:fs"); - return fs.readFileSync(path, "utf-8"); -} - import type { CommandDoc } from "../../types/CommandDoc.ts"; export const documentation: CommandDoc = { diff --git a/src/operations/input/fromkv.ts b/src/operations/input/fromkv.ts index f7e3b3a..26e4097 100644 --- a/src/operations/input/fromkv.ts +++ b/src/operations/input/fromkv.ts @@ -12,6 +12,7 @@ export class FromKv extends Operation { entryDelim = "\n"; recordDelim = "END\n"; acc: string | null = null; + extraArgs: string[] = []; acceptRecord(_record: Record): boolean { return true; @@ -48,7 +49,7 @@ export class FromKv extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); } override acceptLine(line: string): boolean { diff --git a/src/operations/input/frommultire.ts b/src/operations/input/frommultire.ts index f51368b..9a5ecd0 100644 --- a/src/operations/input/frommultire.ts +++ b/src/operations/input/frommultire.ts @@ -20,6 +20,7 @@ export class FromMultiRe extends Operation { keepAll = false; keepFields: Set = new Set(); currentRecord: Record = new Record(); + extraArgs: string[] = []; acceptRecord(_record: Record): boolean { return true; @@ -127,7 +128,7 @@ export class FromMultiRe extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); } addRegex( diff --git a/src/operations/input/fromre.ts b/src/operations/input/fromre.ts index 091203b..889a31a 100644 --- a/src/operations/input/fromre.ts +++ b/src/operations/input/fromre.ts @@ -47,23 +47,6 @@ export class FromRe extends Operation { return true; } - override wantsInput(): boolean { - return this.extraArgs.length === 0; - } - - override streamDone(): void { - if (this.extraArgs.length > 0) { - for (const file of this.extraArgs) { - this.updateCurrentFilename(file); - const content = readFileSync(file); - for (const line of content.split("\n")) { - if (line === "") continue; - this.processLine(line); - } - } - } - } - processLine(line: string): void { if (!this.pattern) return; @@ -89,11 +72,6 @@ export class FromRe extends Operation { } } -function readFileSync(path: string): string { - const fs = require("node:fs") as typeof import("node:fs"); - return fs.readFileSync(path, "utf-8"); -} - import type { CommandDoc } from "../../types/CommandDoc.ts"; export const documentation: CommandDoc = { diff --git a/src/operations/input/fromsplit.ts b/src/operations/input/fromsplit.ts index fc4e4ef..7fe44bf 100644 --- a/src/operations/input/fromsplit.ts +++ b/src/operations/input/fromsplit.ts @@ -68,23 +68,6 @@ export class FromSplit extends Operation { return true; } - override wantsInput(): boolean { - return this.extraArgs.length === 0; - } - - override streamDone(): void { - if (this.extraArgs.length > 0) { - for (const file of this.extraArgs) { - this.updateCurrentFilename(file); - const content = readFileSync(file); - for (const line of content.split("\n")) { - if (line === "") continue; - this.processLine(line); - } - } - } - } - processLine(line: string): void { if (this.header) { const values = this.splitLine(line); @@ -122,11 +105,6 @@ export class FromSplit extends Operation { } } -function readFileSync(path: string): string { - const fs = require("node:fs") as typeof import("node:fs"); - return fs.readFileSync(path, "utf-8"); -} - import type { CommandDoc } from "../../types/CommandDoc.ts"; export const documentation: CommandDoc = { diff --git a/src/operations/input/fromxferlog.ts b/src/operations/input/fromxferlog.ts index 99a1020..7a62d32 100644 --- a/src/operations/input/fromxferlog.ts +++ b/src/operations/input/fromxferlog.ts @@ -18,12 +18,14 @@ const XFERLOG_RE = * Analogous to App::RecordStream::Operation::fromxferlog in Perl. */ export class FromXferlog extends Operation { + extraArgs: string[] = []; + acceptRecord(_record: Record): boolean { return true; } - init(_args: string[]): void { - // No options + init(args: string[]): void { + this.extraArgs = this.parseOptions(args, []); } processLine(line: string): void { diff --git a/src/operations/input/fromxml.ts b/src/operations/input/fromxml.ts index b895381..0af2a1e 100644 --- a/src/operations/input/fromxml.ts +++ b/src/operations/input/fromxml.ts @@ -12,18 +12,12 @@ export class FromXml extends Operation { elements: string[] = []; nested = false; extraArgs: string[] = []; - stdinLines: string[] = []; + urlArgs: string[] = []; acceptRecord(_record: Record): boolean { return true; } - override acceptLine(line: string): boolean { - // When reading from stdin, accumulate raw XML lines - this.stdinLines.push(line); - return true; - } - init(args: string[]): void { const defs: OptionDef[] = [ { @@ -44,29 +38,33 @@ export class FromXml extends Operation { }, ]; - this.extraArgs = this.parseOptions(args, defs); + const allArgs = this.parseOptions(args, defs); + + // Separate file paths (handled by dispatcher) from URIs (handled here) + for (const arg of allArgs) { + if (arg.startsWith("http://") || arg.startsWith("https://") || arg.startsWith("file:")) { + this.urlArgs.push(arg); + } else { + this.extraArgs.push(arg); + } + } // Deduplicate elements this.elements = [...new Set(this.elements)]; } override wantsInput(): boolean { - return this.extraArgs.length === 0; + return false; } override streamDone(): void { - if (this.extraArgs.length > 0) { - for (const uri of this.extraArgs) { - this.updateCurrentFilename(uri); - const xml = this.getXmlString(uri); - if (xml) { - this.parseXml(xml); - } + // Only handle URI args here — file paths are handled by the dispatcher + for (const uri of this.urlArgs) { + this.updateCurrentFilename(uri); + const xml = this.getXmlString(uri); + if (xml) { + this.parseXml(xml); } - } else if (this.stdinLines.length > 0) { - this.updateCurrentFilename("STDIN"); - const xml = this.stdinLines.join("\n"); - this.parseXml(xml); } } diff --git a/src/operations/transform/annotate.ts b/src/operations/transform/annotate.ts index 6de7338..9bd55de 100644 --- a/src/operations/transform/annotate.ts +++ b/src/operations/transform/annotate.ts @@ -16,12 +16,13 @@ import { createSnippetRunner, isJsLang, langOptionDef } from "../../snippets/ind * Analogous to App::RecordStream::Operation::annotate in Perl. */ export class AnnotateOperation extends Operation { + extraArgs: string[] = []; executor!: Executor; keyGroups = new KeyGroups(); annotations = new Map>(); lang: string | null = null; runner: SnippetRunner | null = null; - #uncachedBatch: { index: number; record: Record; syntheticKey: string }[] = []; + uncachedBatch: { index: number; record: Record; syntheticKey: string }[] = []; override addHelpTypes(): void { this.useHelpType("snippet"); @@ -53,7 +54,19 @@ export class AnnotateOperation extends Operation { ]; const remaining = this.parseOptions(args, defs); - const expression = exprSnippet ?? fileSnippet ?? remaining.join(" "); + + let expression: string; + if (fileSnippet ?? exprSnippet) { + expression = (fileSnippet ?? exprSnippet)!; + this.extraArgs = remaining; + } else { + if (remaining.length === 0) { + expression = ""; + } else { + expression = remaining[0]!; + this.extraArgs = remaining.slice(1); + } + } if (!this.keyGroups.hasAnyGroup()) { throw new Error("Must specify at least one --key, maybe you want xform instead?"); @@ -93,8 +106,8 @@ export class AnnotateOperation extends Operation { if (this.runner) { // Queue this uncached record for batch processing - this.#uncachedBatch.push({ - index: this.#uncachedBatch.length, + this.uncachedBatch.push({ + index: this.uncachedBatch.length, record, syntheticKey, }); @@ -124,13 +137,13 @@ export class AnnotateOperation extends Operation { } override streamDone(): void { - if (this.runner && this.#uncachedBatch.length > 0) { - const records = this.#uncachedBatch.map((entry) => entry.record); + if (this.runner && this.uncachedBatch.length > 0) { + const records = this.uncachedBatch.map((entry) => entry.record); const results = this.runner.executeBatch(records); for (let i = 0; i < results.length; i++) { const result = results[i]!; - const entry = this.#uncachedBatch[i]!; + const entry = this.uncachedBatch[i]!; if (result.error) { process.stderr.write(`annotate: ${result.error}\n`); diff --git a/src/operations/transform/assert.ts b/src/operations/transform/assert.ts index c15fb43..bfbb139 100644 --- a/src/operations/transform/assert.ts +++ b/src/operations/transform/assert.ts @@ -11,13 +11,14 @@ import { createSnippetRunner, isJsLang, langOptionDef } from "../../snippets/ind * Analogous to App::RecordStream::Operation::assert in Perl. */ export class AssertOperation extends Operation { + extraArgs: string[] = []; executor!: Executor; assertion = ""; diagnostic = ""; verbose = false; lang: string | null = null; runner: SnippetRunner | null = null; - #bufferedRecords: Record[] = []; + bufferedRecords: Record[] = []; override addHelpTypes(): void { this.useHelpType("snippet"); @@ -55,9 +56,17 @@ export class AssertOperation extends Operation { ]; const remaining = this.parseOptions(args, defs); - const expression = exprSnippet ?? fileSnippet ?? remaining.join(" "); - if (!expression) { - throw new Error("assert requires an expression argument"); + + let expression: string; + if (fileSnippet ?? exprSnippet) { + expression = (fileSnippet ?? exprSnippet)!; + this.extraArgs = remaining; + } else { + if (remaining.length === 0) { + throw new Error("assert requires an expression argument"); + } + expression = remaining[0]!; + this.extraArgs = remaining.slice(1); } this.assertion = expression; @@ -72,7 +81,7 @@ export class AssertOperation extends Operation { acceptRecord(record: Record): boolean { if (this.runner) { - this.#bufferedRecords.push(record); + this.bufferedRecords.push(record); return true; } @@ -94,11 +103,11 @@ export class AssertOperation extends Operation { } override streamDone(): void { - if (this.runner && this.#bufferedRecords.length > 0) { - const results = this.runner.executeBatch(this.#bufferedRecords); + if (this.runner && this.bufferedRecords.length > 0) { + const results = this.runner.executeBatch(this.bufferedRecords); for (let i = 0; i < results.length; i++) { const result = results[i]!; - const record = this.#bufferedRecords[i]!; + const record = this.bufferedRecords[i]!; if (result.error) { throw new Error(`Assertion failed! ${this.diagnostic}\n` + diff --git a/src/operations/transform/chain.ts b/src/operations/transform/chain.ts index 404c61f..8da559e 100644 --- a/src/operations/transform/chain.ts +++ b/src/operations/transform/chain.ts @@ -232,7 +232,7 @@ export class ChainOperation extends Operation { /** * Whether the first operation overrides acceptLine (line-oriented input op). */ - private firstOpHasCustomAcceptLine(): boolean { + firstOpHasCustomAcceptLine(): boolean { const first = this.operations[0]; if (!first) return false; const proto = Object.getPrototypeOf(first) as { [key: string]: unknown }; @@ -244,7 +244,7 @@ export class ChainOperation extends Operation { * Whether the first operation has a parseContent method and no file args * (bulk-content input ops like fromcsv, fromjsonarray that need stdin). */ - private firstOpNeedsBulkStdin(): boolean { + firstOpNeedsBulkStdin(): boolean { const first = this.operations[0]; if (!first) return false; const opAny = first as unknown as { [key: string]: unknown }; @@ -255,7 +255,7 @@ export class ChainOperation extends Operation { } /** Buffer for bulk stdin content when first op needs parseContent */ - private bulkStdinLines: string[] = []; + bulkStdinLines: string[] = []; acceptRecord(record: Record): boolean { if (this.operations.length > 0) { diff --git a/src/operations/transform/collate.ts b/src/operations/transform/collate.ts index ff31f59..aaf8228 100644 --- a/src/operations/transform/collate.ts +++ b/src/operations/transform/collate.ts @@ -116,6 +116,7 @@ interface CollateCookie { * Analogous to App::RecordStream::Operation::collate in Perl. */ export class CollateOperation extends Operation { + extraArgs: string[] = []; clumperOptions!: ClumperOptions; incremental = false; @@ -302,7 +303,7 @@ export class CollateOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); // Build the aggregators map: start with -a specs, then merge DL aggregators const aggregators = makeAggregators(...aggregatorSpecs); diff --git a/src/operations/transform/decollate.ts b/src/operations/transform/decollate.ts index fca9233..da45733 100644 --- a/src/operations/transform/decollate.ts +++ b/src/operations/transform/decollate.ts @@ -12,6 +12,7 @@ import type { JsonValue } from "../../types/json.ts"; * Analogous to App::RecordStream::Operation::decollate in Perl. */ export class DecollateOperation extends Operation { + extraArgs: string[] = []; deaggregators: Deaggregator[] = []; onlyDeaggregated = false; @@ -46,7 +47,7 @@ export class DecollateOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); for (const spec of deaggSpecs) { this.deaggregators.push(deaggregatorRegistry.parse(spec)); diff --git a/src/operations/transform/delta.ts b/src/operations/transform/delta.ts index 4b71c45..edafcc5 100644 --- a/src/operations/transform/delta.ts +++ b/src/operations/transform/delta.ts @@ -11,6 +11,7 @@ import type { JsonObject } from "../../types/json.ts"; * Analogous to App::RecordStream::Operation::delta in Perl. */ export class DeltaOperation extends Operation { + extraArgs: string[] = []; keyGroups = new KeyGroups(); lastRecord: Record | null = null; @@ -25,7 +26,7 @@ export class DeltaOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); if (!this.keyGroups.hasAnyGroup()) { throw new Error("Must specify --key"); diff --git a/src/operations/transform/eval.ts b/src/operations/transform/eval.ts index 139b2d6..5e15aaf 100644 --- a/src/operations/transform/eval.ts +++ b/src/operations/transform/eval.ts @@ -19,7 +19,8 @@ export class EvalOperation extends Operation { chomp = false; lang: string | null = null; runner: SnippetRunner | null = null; - #bufferedRecords: Record[] = []; + bufferedRecords: Record[] = []; + extraArgs: string[] = []; override addHelpTypes(): void { this.useHelpType("snippet"); @@ -49,9 +50,17 @@ export class EvalOperation extends Operation { ]; const remaining = this.parseOptions(args, defs); - const expression = fileSnippet ?? exprSnippet ?? remaining.join(" "); - if (!expression) { - throw new Error("eval requires an expression argument"); + + let expression: string; + if (fileSnippet ?? exprSnippet) { + expression = (fileSnippet ?? exprSnippet)!; + this.extraArgs = remaining; + } else { + if (remaining.length === 0) { + throw new Error("eval requires an expression argument"); + } + expression = remaining[0]!; + this.extraArgs = remaining.slice(1); } if (this.lang && !isJsLang(this.lang)) { @@ -64,7 +73,7 @@ export class EvalOperation extends Operation { acceptRecord(record: Record): boolean { if (this.runner) { - this.#bufferedRecords.push(record); + this.bufferedRecords.push(record); return true; } @@ -80,8 +89,8 @@ export class EvalOperation extends Operation { } override streamDone(): void { - if (this.runner && this.#bufferedRecords.length > 0) { - const results = this.runner.executeBatch(this.#bufferedRecords); + if (this.runner && this.bufferedRecords.length > 0) { + const results = this.runner.executeBatch(this.bufferedRecords); for (const result of results) { if (result.error) { process.stderr.write(`eval: ${result.error}\n`); diff --git a/src/operations/transform/expandjson.ts b/src/operations/transform/expandjson.ts index 921eb20..51a06d2 100644 --- a/src/operations/transform/expandjson.ts +++ b/src/operations/transform/expandjson.ts @@ -11,6 +11,7 @@ import type { CommandDoc } from "../../types/CommandDoc.ts"; * Analogous to App::RecordStream::Operation::expandjson (new operation). */ export class ExpandJsonOperation extends Operation { + extraArgs: string[] = []; keys: string[] = []; recursive = false; @@ -36,7 +37,7 @@ export class ExpandJsonOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); } acceptRecord(record: Record): boolean { diff --git a/src/operations/transform/flatten.ts b/src/operations/transform/flatten.ts index eb3277b..fe98bb4 100644 --- a/src/operations/transform/flatten.ts +++ b/src/operations/transform/flatten.ts @@ -16,6 +16,7 @@ interface FlattenField { * Analogous to App::RecordStream::Operation::flatten in Perl. */ export class FlattenOperation extends Operation { + extraArgs: string[] = []; fields: FlattenField[] = []; separator = "-"; defaultDepth = 1; @@ -76,7 +77,7 @@ export class FlattenOperation extends Operation { i++; } - this.parseOptions(filteredArgs, defs); + this.extraArgs = this.parseOptions(filteredArgs, defs); } acceptRecord(record: Record): boolean { diff --git a/src/operations/transform/generate.ts b/src/operations/transform/generate.ts index 29c1e9e..228c85b 100644 --- a/src/operations/transform/generate.ts +++ b/src/operations/transform/generate.ts @@ -18,12 +18,13 @@ import { createSnippetRunner, isJsLang, langOptionDef } from "../../snippets/ind * Analogous to App::RecordStream::Operation::generate in Perl. */ export class GenerateOperation extends Operation { + extraArgs: string[] = []; executor!: Executor; keychain = "_chain"; passthrough = false; lang: string | null = null; runner: SnippetRunner | null = null; - #bufferedRecords: Record[] = []; + bufferedRecords: Record[] = []; override addHelpTypes(): void { this.useHelpType("snippet"); @@ -59,9 +60,17 @@ export class GenerateOperation extends Operation { ]; const remaining = this.parseOptions(args, defs); - const expression = fileSnippet ?? exprSnippet ?? remaining.join(" "); - if (!expression) { - throw new Error("generate requires an expression argument"); + + let expression: string; + if (fileSnippet ?? exprSnippet) { + expression = (fileSnippet ?? exprSnippet)!; + this.extraArgs = remaining; + } else { + if (remaining.length === 0) { + throw new Error("generate requires an expression argument"); + } + expression = remaining[0]!; + this.extraArgs = remaining.slice(1); } if (this.lang && !isJsLang(this.lang)) { @@ -74,7 +83,7 @@ export class GenerateOperation extends Operation { acceptRecord(record: Record): boolean { if (this.runner) { - this.#bufferedRecords.push(record); + this.bufferedRecords.push(record); return true; } @@ -110,11 +119,11 @@ export class GenerateOperation extends Operation { } override streamDone(): void { - if (this.runner && this.#bufferedRecords.length > 0) { - const results = this.runner.executeBatch(this.#bufferedRecords); + if (this.runner && this.bufferedRecords.length > 0) { + const results = this.runner.executeBatch(this.bufferedRecords); for (let i = 0; i < results.length; i++) { const result = results[i]!; - const inputRecord = this.#bufferedRecords[i]!; + const inputRecord = this.bufferedRecords[i]!; if (result.error) { process.stderr.write(`generate: ${result.error}\n`); diff --git a/src/operations/transform/grep.ts b/src/operations/transform/grep.ts index 9201534..afc2039 100644 --- a/src/operations/transform/grep.ts +++ b/src/operations/transform/grep.ts @@ -20,7 +20,8 @@ export class GrepOperation extends Operation { seenRecord = false; lang: string | null = null; runner: SnippetRunner | null = null; - #bufferedRecords: Record[] = []; + bufferedRecords: Record[] = []; + extraArgs: string[] = []; override addHelpTypes(): void { this.useHelpType("snippet"); @@ -79,9 +80,18 @@ export class GrepOperation extends Operation { this.beforeCount = context; } - const expression = fileSnippet ?? exprSnippet ?? remaining.join(" "); - if (!expression) { - throw new Error("grep requires an expression argument"); + let expression: string; + if (fileSnippet ?? exprSnippet) { + // Expression provided via -e or -E; remaining args are file paths + expression = (fileSnippet ?? exprSnippet)!; + this.extraArgs = remaining; + } else { + // First positional arg is expression, rest are file paths + if (remaining.length === 0) { + throw new Error("grep requires an expression argument"); + } + expression = remaining[0]!; + this.extraArgs = remaining.slice(1); } if (this.lang && !isJsLang(this.lang)) { @@ -92,13 +102,13 @@ export class GrepOperation extends Operation { } } - #hasContext(): boolean { + hasContext(): boolean { return this.afterCount > 0 || this.beforeCount > 0; } acceptRecord(record: Record): boolean { - if (this.runner && !this.#hasContext()) { - this.#bufferedRecords.push(record); + if (this.runner && !this.hasContext()) { + this.bufferedRecords.push(record); return true; } @@ -113,7 +123,7 @@ export class GrepOperation extends Operation { return true; } if (this.antiMatch) passed = !passed; - return this.#applyContext(record, passed); + return this.applyContext(record, passed); } let matched = this.executor.executeCode(record); @@ -121,10 +131,10 @@ export class GrepOperation extends Operation { matched = !matched; } - return this.#applyContext(record, !!matched); + return this.applyContext(record, !!matched); } - #applyContext(record: Record, matched: boolean): boolean { + applyContext(record: Record, matched: boolean): boolean { let pushedRecord = false; if (matched) { @@ -158,8 +168,8 @@ export class GrepOperation extends Operation { } override streamDone(): void { - if (this.runner && this.#bufferedRecords.length > 0) { - const results = this.runner.executeBatch(this.#bufferedRecords); + if (this.runner && this.bufferedRecords.length > 0) { + const results = this.runner.executeBatch(this.bufferedRecords); for (let i = 0; i < results.length; i++) { const result = results[i]!; if (result.error) { @@ -169,7 +179,7 @@ export class GrepOperation extends Operation { let passed = result.passed ?? false; if (this.antiMatch) passed = !passed; if (passed) { - this.pushRecord(this.#bufferedRecords[i]!); + this.pushRecord(this.bufferedRecords[i]!); this.seenRecord = true; } } diff --git a/src/operations/transform/normalizetime.ts b/src/operations/transform/normalizetime.ts index 1bb6179..709797b 100644 --- a/src/operations/transform/normalizetime.ts +++ b/src/operations/transform/normalizetime.ts @@ -11,6 +11,7 @@ import type { JsonObject } from "../../types/json.ts"; * Analogous to App::RecordStream::Operation::normalizetime in Perl. */ export class NormalizeTimeOperation extends Operation { + extraArgs: string[] = []; key = ""; sanitizedKey = ""; threshold = 0; @@ -58,7 +59,7 @@ export class NormalizeTimeOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); if (!this.key) { throw new Error("Must specify --key"); diff --git a/src/operations/transform/parsedate.ts b/src/operations/transform/parsedate.ts index b7adf33..47e8d4a 100644 --- a/src/operations/transform/parsedate.ts +++ b/src/operations/transform/parsedate.ts @@ -12,6 +12,7 @@ import type { JsonObject } from "../../types/json.ts"; * Inspired by the Perl PR #74 parsedate operation. */ export class ParseDateOperation extends Operation { + extraArgs: string[] = []; inputKey = ""; outputKey = ""; inputFormat: string | null = null; @@ -73,7 +74,7 @@ export class ParseDateOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); if (!this.inputKey) { throw new Error("Must specify --key"); diff --git a/src/operations/transform/sort.ts b/src/operations/transform/sort.ts index 9716937..dedae5c 100644 --- a/src/operations/transform/sort.ts +++ b/src/operations/transform/sort.ts @@ -12,6 +12,7 @@ export class SortOperation extends Operation { accumulator = new Accumulator(); keys: string[] = []; reverse = false; + extraArgs: string[] = []; init(args: string[]): void { const defs: OptionDef[] = [ @@ -33,7 +34,7 @@ export class SortOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); } acceptRecord(record: Record): boolean { diff --git a/src/operations/transform/stream2table.ts b/src/operations/transform/stream2table.ts index 4a4be4a..1426380 100644 --- a/src/operations/transform/stream2table.ts +++ b/src/operations/transform/stream2table.ts @@ -11,6 +11,7 @@ import type { JsonObject, JsonValue } from "../../types/json.ts"; * Analogous to App::RecordStream::Operation::stream2table in Perl. */ export class Stream2TableOperation extends Operation { + extraArgs: string[] = []; field = ""; removeField = false; groups = new Map(); @@ -26,7 +27,7 @@ export class Stream2TableOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); if (!this.field) { throw new Error("You must specify a --field option"); diff --git a/src/operations/transform/substream.ts b/src/operations/transform/substream.ts index b89efd0..164f495 100644 --- a/src/operations/transform/substream.ts +++ b/src/operations/transform/substream.ts @@ -11,6 +11,7 @@ import { Record } from "../../Record.ts"; * Analogous to App::RecordStream::Operation::substream in Perl. */ export class SubstreamOperation extends Operation { + extraArgs: string[] = []; beginExecutor: Executor | null = null; endExecutor: Executor | null = null; inSubstream = false; @@ -37,7 +38,7 @@ export class SubstreamOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); if (beginExpr) { this.beginExecutor = new Executor(autoReturn(beginExpr)); diff --git a/src/operations/transform/topn.ts b/src/operations/transform/topn.ts index 548b3d0..7db5018 100644 --- a/src/operations/transform/topn.ts +++ b/src/operations/transform/topn.ts @@ -11,6 +11,7 @@ import type { JsonObject } from "../../types/json.ts"; * Analogous to App::RecordStream::Operation::topn in Perl. */ export class TopnOperation extends Operation { + extraArgs: string[] = []; keyGroups = new KeyGroups(); num = 10; delimiter = "9t%7Oz%]"; @@ -41,7 +42,7 @@ export class TopnOperation extends Operation { }, ]; - this.parseOptions(args, defs); + this.extraArgs = this.parseOptions(args, defs); if (!this.num) { throw new Error("Must specify --topn "); diff --git a/src/operations/transform/xform.ts b/src/operations/transform/xform.ts index 5ac9213..688f4f2 100644 --- a/src/operations/transform/xform.ts +++ b/src/operations/transform/xform.ts @@ -24,7 +24,8 @@ export class XformOperation extends Operation { suppressR = false; lang: string | null = null; runner: SnippetRunner | null = null; - #bufferedRecords: Record[] = []; + bufferedRecords: Record[] = []; + extraArgs: string[] = []; override addHelpTypes(): void { this.useHelpType("snippet"); @@ -83,9 +84,17 @@ export class XformOperation extends Operation { ]; const remaining = this.parseOptions(args, defs); - const expression = fileSnippet ?? exprSnippet ?? remaining.join(" "); - if (!expression) { - throw new Error("xform requires an expression argument"); + + let expression: string; + if (fileSnippet ?? exprSnippet) { + expression = (fileSnippet ?? exprSnippet)!; + this.extraArgs = remaining; + } else { + if (remaining.length === 0) { + throw new Error("xform requires an expression argument"); + } + expression = remaining[0]!; + this.extraArgs = remaining.slice(1); } if (this.lang && !isJsLang(this.lang)) { @@ -126,20 +135,20 @@ export class XformOperation extends Operation { return executor; } - #hasContext(): boolean { + hasContext(): boolean { return this.beforeCount > 0 || this.afterCount > 0; } acceptRecord(record: Record): boolean { - if (this.runner && !this.#hasContext()) { - this.#bufferedRecords.push(record); + if (this.runner && !this.hasContext()) { + this.bufferedRecords.push(record); return true; } if (this.runner) { // Context mode with non-JS lang: process one record at a time // so the context sliding window logic below can work - this.#runSingleWithRunner(record); + this.runSingleWithRunner(record); return true; } @@ -179,7 +188,7 @@ export class XformOperation extends Operation { return this.runRecordWithContext(this.currentRecord, this.beforeArray, this.afterArray); } - #runSingleWithRunner(record: Record): void { + runSingleWithRunner(record: Record): void { if (!this.runner) return; const results = this.runner.executeBatch([record]); const result = results[0]; @@ -195,8 +204,8 @@ export class XformOperation extends Operation { } override streamDone(): void { - if (this.runner && this.#bufferedRecords.length > 0) { - const results = this.runner.executeBatch(this.#bufferedRecords); + if (this.runner && this.bufferedRecords.length > 0) { + const results = this.runner.executeBatch(this.bufferedRecords); for (const result of results) { if (result.error) { process.stderr.write(`xform: ${result.error}\n`); diff --git a/src/snippets/JsSnippetRunner.ts b/src/snippets/JsSnippetRunner.ts index 1f087cf..484feef 100644 --- a/src/snippets/JsSnippetRunner.ts +++ b/src/snippets/JsSnippetRunner.ts @@ -17,57 +17,57 @@ import type { export class JsSnippetRunner implements SnippetRunner { name = "javascript"; - #executor: Executor | null = null; - #mode: SnippetMode = "eval"; + executor: Executor | null = null; + mode: SnippetMode = "eval"; async init(code: string, context: SnippetContext): Promise { - this.#mode = context.mode; + this.mode = context.mode; switch (context.mode) { case "eval": // Eval mode: run code, return modified record - this.#executor = new Executor(`${code}\n; return r;`); + this.executor = new Executor(`${code}\n; return r;`); break; case "grep": // Grep mode: evaluate as expression, return boolean - this.#executor = new Executor(autoReturn(code)); + this.executor = new Executor(autoReturn(code)); break; case "xform": // Xform mode: run code, return modified record (or array) - this.#executor = new Executor(`${code}\n; return r;`); + this.executor = new Executor(`${code}\n; return r;`); break; case "generate": // Generate mode: evaluate expression, return array of records - this.#executor = new Executor(autoReturn(code)); + this.executor = new Executor(autoReturn(code)); break; } } async executeRecord(record: Record): Promise { - return this.#executeRecordSync(record); + return this.executeRecordSync(record); } executeBatch(records: Record[]): SnippetResult[] { - return records.map((r) => this.#executeRecordSync(r)); + return records.map((r) => this.executeRecordSync(r)); } - #executeRecordSync(record: Record): SnippetResult { - if (!this.#executor) { + executeRecordSync(record: Record): SnippetResult { + if (!this.executor) { return { error: "Runner not initialized" }; } try { - switch (this.#mode) { + switch (this.mode) { case "eval": { - this.#executor.executeCode(record); + this.executor.executeCode(record); return { record: record.toJSON() }; } case "grep": { - const result = this.#executor.executeCode(record); + const result = this.executor.executeCode(record); return { passed: !!result }; } case "xform": { - const result = this.#executor.executeCode(record); + const result = this.executor.executeCode(record); if (Array.isArray(result)) { const records = result .filter((item): item is JsonObject | Record => @@ -87,7 +87,7 @@ export class JsSnippetRunner implements SnippetRunner { return { records: [record.toJSON()] }; } case "generate": { - const result = this.#executor.executeCode(record); + const result = this.executor.executeCode(record); const items = Array.isArray(result) ? result : result ? [result] : []; const records = items .filter((item): item is JsonObject | Record => @@ -109,6 +109,6 @@ export class JsSnippetRunner implements SnippetRunner { } async shutdown(): Promise { - this.#executor = null; + this.executor = null; } } diff --git a/src/snippets/PerlSnippetRunner.ts b/src/snippets/PerlSnippetRunner.ts index 67c2e6a..774fd39 100644 --- a/src/snippets/PerlSnippetRunner.ts +++ b/src/snippets/PerlSnippetRunner.ts @@ -25,12 +25,12 @@ const RUNNER_PATH = join(RUNNER_DIR, "runner.pl"); export class PerlSnippetRunner implements SnippetRunner { name = "perl"; - #code = ""; - #mode: SnippetMode = "eval"; + code = ""; + mode: SnippetMode = "eval"; async init(code: string, context: SnippetContext): Promise { - this.#code = transformCode(code, "lvalue"); - this.#mode = context.mode; + this.code = transformCode(code, "lvalue"); + this.mode = context.mode; } async executeRecord(record: Record): Promise { @@ -40,7 +40,7 @@ export class PerlSnippetRunner implements SnippetRunner { executeBatch(records: Record[]): SnippetResult[] { const lines: string[] = [ - JSON.stringify({ type: "init", code: this.#code, mode: this.#mode }), + JSON.stringify({ type: "init", code: this.code, mode: this.mode }), ...records.map((r) => JSON.stringify({ type: "record", data: r.toJSON() }) ), diff --git a/src/snippets/PythonSnippetRunner.ts b/src/snippets/PythonSnippetRunner.ts index 302e048..28511c4 100644 --- a/src/snippets/PythonSnippetRunner.ts +++ b/src/snippets/PythonSnippetRunner.ts @@ -26,12 +26,12 @@ const RUNNER_PATH = join(RUNNER_DIR, "runner.py"); export class PythonSnippetRunner implements SnippetRunner { name = "python"; - #code = ""; - #mode: SnippetMode = "eval"; + code = ""; + mode: SnippetMode = "eval"; async init(code: string, context: SnippetContext): Promise { - this.#code = transformCode(code, "accessor"); - this.#mode = context.mode; + this.code = transformCode(code, "accessor"); + this.mode = context.mode; } async executeRecord(record: Record): Promise { @@ -41,7 +41,7 @@ export class PythonSnippetRunner implements SnippetRunner { executeBatch(records: Record[]): SnippetResult[] { const lines: string[] = [ - JSON.stringify({ type: "init", code: this.#code, mode: this.#mode }), + JSON.stringify({ type: "init", code: this.code, mode: this.mode }), ...records.map((r) => JSON.stringify({ type: "record", data: r.toJSON() }) ), diff --git a/tests/operations/input/fromcsv.test.ts b/tests/operations/input/fromcsv.test.ts index 9c489ab..60526b8 100644 --- a/tests/operations/input/fromcsv.test.ts +++ b/tests/operations/input/fromcsv.test.ts @@ -70,6 +70,7 @@ describe("FromCsv", () => { }); test("reads from file with header and static key", () => { + const fs = require("node:fs") as typeof import("node:fs"); const collector = new CollectorReceiver(); const op = new FromCsv(collector); op.init([ @@ -79,6 +80,12 @@ describe("FromCsv", () => { "tests/fixtures/data3.csv", "tests/fixtures/data4.csv", ]); + // Simulate dispatcher file reading: read each file and call parseContent + for (const file of op.extraArgs) { + op.updateCurrentFilename(file); + const content = fs.readFileSync(file, "utf-8"); + op.parseContent(content); + } op.finish(); const result = collector.records.map((r) => r.toJSON()); expect(result).toEqual([ diff --git a/tests/operations/input/fromdb.test.ts b/tests/operations/input/fromdb.test.ts index b3f6689..034a8e2 100644 --- a/tests/operations/input/fromdb.test.ts +++ b/tests/operations/input/fromdb.test.ts @@ -11,6 +11,64 @@ function runFromDb(args: string[]): JsonObject[] { return collector.records.map((r) => r.toJSON()); } +async function runFromDbAsync(args: string[]): Promise { + const collector = new CollectorReceiver(); + const op = new FromDb(collector); + op.init(args); + await op.finish(); + return collector.records.map((r) => r.toJSON()); +} + +// -- Detect available databases at module load time -- + +const TEST_ROWS = Array.from({ length: 10 }, (_, i) => ({ + id: i + 1, + foo: String(i + 1), +})); + +const INSERT_SQL = `INSERT INTO recs (id, foo) VALUES ${TEST_ROWS.map((r) => `(${r.id}, '${r.foo}')`).join(", ")}`; + +let pgAvailable = false; +try { + const { Client } = await import("pg"); + const client = new Client({ database: "recs_test" }); + await client.connect(); + await client.query( + `CREATE TABLE IF NOT EXISTS recs (id INTEGER, foo TEXT)` + ); + const { rows } = await client.query("SELECT COUNT(*)::int AS n FROM recs"); + if (rows[0].n === 0) { + await client.query(INSERT_SQL); + } + await client.end(); + pgAvailable = true; +} catch { + // pg not available — tests will be skipped +} + +let mysqlAvailable = false; +try { + const mysql = await import("mysql2/promise"); + const conn = await mysql.createConnection({ + host: "127.0.0.1", + user: "root", + database: "recs_test", + }); + await conn.execute( + `CREATE TABLE IF NOT EXISTS recs (id INTEGER, foo VARCHAR(255))` + ); + const [countRows] = await conn.execute("SELECT COUNT(*) AS n FROM recs"); + if ((countRows as Array<{ n: number }>)[0]!.n === 0) { + await conn.execute(INSERT_SQL); + } + await conn.end(); + mysqlAvailable = true; +} catch { + // mysql not available — tests will be skipped +} + +// -- SQLite tests (always run) -- + describe("FromDb", () => { test("dump entire table", () => { const result = runFromDb([ @@ -51,3 +109,136 @@ describe("FromDb", () => { expect(op.wantsInput()).toBe(false); }); }); + +// -- Option validation tests (always run, no DB needed) -- + +describe("FromDb option validation", () => { + test("requires --db for pg type", () => { + expect(() => { + const op = new FromDb(); + op.init(["--type", "pg", "--table", "recs"]); + }).toThrow("--db is required for PostgreSQL databases"); + }); + + test("requires --host and --db for mysql type", () => { + expect(() => { + const op = new FromDb(); + op.init(["--type", "mysql", "--table", "recs"]); + }).toThrow("--host and --db are required for MySQL databases"); + }); + + test("requires --host for mysql type (--db alone insufficient)", () => { + expect(() => { + const op = new FromDb(); + op.init(["--type", "mysql", "--db", "mydb", "--table", "recs"]); + }).toThrow("--host and --db are required for MySQL databases"); + }); + + test("requires --db for oracle type", () => { + expect(() => { + const op = new FromDb(); + op.init(["--type", "oracle", "--table", "recs"]); + }).toThrow("--db (tnsname) is required for Oracle databases"); + }); + + test("rejects unsupported --type", async () => { + const collector = new CollectorReceiver(); + const op = new FromDb(collector); + op.init(["--type", "bogus", "--table", "t", "--db", "x"]); + await expect(op.finish()).rejects.toThrow("not supported"); + }); +}); + +// -- PostgreSQL integration tests (skipped if pg unavailable) -- + +describe.skipIf(!pgAvailable)("FromDb PostgreSQL", () => { + test("dump entire table via --type pg", async () => { + const result = await runFromDbAsync([ + "--type", "pg", + "--db", "recs_test", + "--table", "recs", + ]); + + expect(result.length).toBe(10); + expect(result[0]).toEqual({ id: 1, foo: "1" }); + expect(result[9]).toEqual({ id: 10, foo: "10" }); + }); + + test("run SQL query with WHERE clause", async () => { + const result = await runFromDbAsync([ + "--type", "pg", + "--db", "recs_test", + "--sql", "SELECT * FROM recs WHERE id > 6 ORDER BY id", + ]); + + expect(result.length).toBe(4); + expect(result[0]).toEqual({ id: 7, foo: "7" }); + expect(result[3]).toEqual({ id: 10, foo: "10" }); + }); + + test("connects via Unix socket by default (no --host)", async () => { + const result = await runFromDbAsync([ + "--type", "pg", + "--db", "recs_test", + "--sql", "SELECT 1 AS val", + ]); + + expect(result).toEqual([{ val: 1 }]); + }); + + test("supports --port option", async () => { + const result = await runFromDbAsync([ + "--type", "pg", + "--db", "recs_test", + "--port", "5432", + "--sql", "SELECT 1 AS val", + ]); + + expect(result).toEqual([{ val: 1 }]); + }); +}); + +// -- MySQL integration tests (skipped if mysql unavailable) -- + +describe.skipIf(!mysqlAvailable)("FromDb MySQL", () => { + test("dump entire table via --type mysql", async () => { + const result = await runFromDbAsync([ + "--type", "mysql", + "--host", "127.0.0.1", + "--db", "recs_test", + "--user", "root", + "--table", "recs", + ]); + + expect(result.length).toBe(10); + expect(result[0]).toEqual({ id: 1, foo: "1" }); + expect(result[9]).toEqual({ id: 10, foo: "10" }); + }); + + test("run SQL query with WHERE clause", async () => { + const result = await runFromDbAsync([ + "--type", "mysql", + "--host", "127.0.0.1", + "--db", "recs_test", + "--user", "root", + "--sql", "SELECT * FROM recs WHERE id > 6 ORDER BY id", + ]); + + expect(result.length).toBe(4); + expect(result[0]).toEqual({ id: 7, foo: "7" }); + expect(result[3]).toEqual({ id: 10, foo: "10" }); + }); + + test("supports --port option", async () => { + const result = await runFromDbAsync([ + "--type", "mysql", + "--host", "127.0.0.1", + "--port", "3306", + "--db", "recs_test", + "--user", "root", + "--sql", "SELECT 1 AS val", + ]); + + expect(result[0]).toHaveProperty("val", 1); + }); +}); diff --git a/tests/operations/input/fromsplit.test.ts b/tests/operations/input/fromsplit.test.ts index 1c7068f..327fd59 100644 --- a/tests/operations/input/fromsplit.test.ts +++ b/tests/operations/input/fromsplit.test.ts @@ -19,9 +19,18 @@ function runFromSplit( describe("FromSplit", () => { test("split file with custom delimiter and field name", () => { + const fs = require("node:fs") as typeof import("node:fs"); const collector = new CollectorReceiver(); const op = new FromSplit(collector); op.init(["-f", "f1", "-d", " ", "tests/fixtures/splitfile"]); + // Simulate dispatcher: read file lines and call acceptLine + for (const file of op.extraArgs) { + const content = fs.readFileSync(file, "utf-8"); + for (const line of content.split("\n")) { + if (line === "") continue; + op.acceptLine(line); + } + } op.finish(); const result = collector.records.map((r) => r.toJSON()); expect(result).toEqual([ @@ -31,9 +40,17 @@ describe("FromSplit", () => { }); test("split file with default delimiter (comma regex)", () => { + const fs = require("node:fs") as typeof import("node:fs"); const collector = new CollectorReceiver(); const op = new FromSplit(collector); op.init(["tests/fixtures/splitfile"]); + for (const file of op.extraArgs) { + const content = fs.readFileSync(file, "utf-8"); + for (const line of content.split("\n")) { + if (line === "") continue; + op.acceptLine(line); + } + } op.finish(); const result = collector.records.map((r) => r.toJSON()); expect(result).toEqual([ @@ -43,9 +60,17 @@ describe("FromSplit", () => { }); test("split file with header", () => { + const fs = require("node:fs") as typeof import("node:fs"); const collector = new CollectorReceiver(); const op = new FromSplit(collector); op.init(["--header", "tests/fixtures/splitfile"]); + for (const file of op.extraArgs) { + const content = fs.readFileSync(file, "utf-8"); + for (const line of content.split("\n")) { + if (line === "") continue; + op.acceptLine(line); + } + } op.finish(); const result = collector.records.map((r) => r.toJSON()); expect(result).toEqual([ diff --git a/tests/perf/bench.ts b/tests/perf/bench.ts index 878b98b..f047db5 100644 --- a/tests/perf/bench.ts +++ b/tests/perf/bench.ts @@ -166,13 +166,13 @@ function fmtDeltaMarkdown(pct: number, threshold: number = 10): string { export class BenchmarkSuite { name: string; - #entries: BenchEntry[] = []; - #results: BenchmarkResult[] = []; - #suiteOptions: SuiteOptions; + entries: BenchEntry[] = []; + results: BenchmarkResult[] = []; + suiteOptions: SuiteOptions; constructor(name: string, options?: SuiteOptions) { this.name = name; - this.#suiteOptions = options ?? {}; + this.suiteOptions = options ?? {}; } /** @@ -183,17 +183,17 @@ export class BenchmarkSuite { fn: () => void | Promise, options?: BenchmarkOptions, ): void { - this.#entries.push({ name, fn, options: options ?? {} }); + this.entries.push({ name, fn, options: options ?? {} }); } /** * Run all registered benchmarks and print results. */ async run(): Promise { - const filter = this.#suiteOptions.filter; + const filter = this.suiteOptions.filter; const entries = filter - ? this.#entries.filter((e) => e.name.includes(filter)) - : this.#entries; + ? this.entries.filter((e) => e.name.includes(filter)) + : this.entries; if (entries.length === 0) { console.log(`\nSuite: ${this.name} — no benchmarks matched filter\n`); @@ -207,15 +207,15 @@ export class BenchmarkSuite { const baseline = loadBaseline(); for (const entry of entries) { - const result = await this.#runOne(entry); - this.#results.push(result); - this.#printResult(result, baseline); + const result = await this.runOne(entry); + this.results.push(result); + this.printResult(result, baseline); } - return this.#results; + return this.results; } - async #runOne(entry: BenchEntry): Promise { + async runOne(entry: BenchEntry): Promise { const warmup = entry.options.warmup ?? 3; const iterations = entry.options.iterations ?? 10; @@ -261,7 +261,7 @@ export class BenchmarkSuite { return result; } - #printResult(result: BenchmarkResult, baseline: BaselineData | null): void { + printResult(result: BenchmarkResult, baseline: BaselineData | null): void { const parts: string[] = [ ` ${result.name}`, ` min=${fmtMs(result.min)} median=${fmtMs(result.median)} p95=${fmtMs(result.p95)} max=${fmtMs(result.max)}`, @@ -290,12 +290,12 @@ export class BenchmarkSuite { * Save current results as the new baseline. */ saveBaseline(): void { - saveBaseline(this.#results); + saveBaseline(this.results); console.log(`\nBaseline saved to ${BASELINE_FILE}`); } getResults(): BenchmarkResult[] { - return [...this.#results]; + return [...this.results]; } }