diff --git a/package.json b/package.json index 14ca8a4..e399279 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,12 @@ "aedes": "^0.51.3", "aedes-persistence": "^10.2.2", "ajv": "^8.17.1", + "better-sqlite3": "^12.4.1", "fastify": "^5.6.1", "jsonwebtoken": "^9.0.2", + "knex": "^3.1.0", "micromatch": "^4.0.8", + "pg": "^8.16.3", "ws": "^8.18.3", "zod": "^4.1.12" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0713e64..5ba243f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,15 +23,24 @@ importers: ajv: specifier: ^8.17.1 version: 8.17.1 + better-sqlite3: + specifier: ^12.4.1 + version: 12.4.1 fastify: specifier: ^5.6.1 version: 5.6.1 jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 + knex: + specifier: ^3.1.0 + version: 3.1.0(better-sqlite3@12.4.1)(pg@8.16.3) micromatch: specifier: ^4.0.8 version: 4.0.8 + pg: + specifier: ^8.16.3 + version: 8.16.3 ws: specifier: ^8.18.3 version: 8.18.3 @@ -1018,6 +1027,13 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} + better-sqlite3@12.4.1: + resolution: {integrity: sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ==} + engines: {node: 20.x || 22.x || 23.x || 24.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -1108,6 +1124,9 @@ packages: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + cli-boxes@2.2.1: resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} engines: {node: '>=6'} @@ -1127,10 +1146,17 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.19: + resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + commist@3.2.0: resolution: {integrity: sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==} @@ -1179,6 +1205,15 @@ packages: supports-color: optional: true + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1188,10 +1223,18 @@ packages: supports-color: optional: true + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1283,6 +1326,10 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1363,6 +1410,10 @@ packages: jiti: optional: true + esm@3.2.25: + resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} + engines: {node: '>=6'} + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1401,6 +1452,10 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + expect-type@1.2.2: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} @@ -1479,6 +1534,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1510,6 +1568,9 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1533,6 +1594,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + get-port@7.1.0: resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} engines: {node: '>=16'} @@ -1552,6 +1617,12 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + getopts@2.3.0: + resolution: {integrity: sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1665,6 +1736,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + interpret@2.2.0: + resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==} + engines: {node: '>= 0.10'} + ip-address@10.0.1: resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} @@ -1894,6 +1969,34 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + knex@3.1.0: + resolution: {integrity: sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==} + engines: {node: '>=16'} + hasBin: true + peerDependencies: + better-sqlite3: '*' + mysql: '*' + mysql2: '*' + pg: '*' + pg-native: '*' + sqlite3: '*' + tedious: '*' + peerDependenciesMeta: + better-sqlite3: + optional: true + mysql: + optional: true + mysql2: + optional: true + pg: + optional: true + pg-native: + optional: true + sqlite3: + optional: true + tedious: + optional: true + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1936,6 +2039,9 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -1995,6 +2101,10 @@ packages: resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} engines: {node: '>=8'} + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2009,6 +2119,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mqemitter@6.0.2: resolution: {integrity: sha512-8RGlznQx/Nb1xC3xKUFXHWov7pn7JdH++YVwlr6SLT6k3ft1h+ImGqZdVudbdKruFckIq9wheq9s4hgCivJDow==} engines: {node: '>=16'} @@ -2024,6 +2137,9 @@ packages: engines: {node: '>=16.0.0'} hasBin: true + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2032,6 +2148,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -2040,6 +2159,10 @@ packages: engines: {node: '>=10'} hasBin: true + node-abi@3.78.0: + resolution: {integrity: sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==} + engines: {node: '>=10'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2180,6 +2303,43 @@ packages: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} + pg-cloudflare@1.2.7: + resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} + + pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} + + pg-connection-string@2.9.1: + resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.10.1: + resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.10.3: + resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.16.3: + resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2209,6 +2369,27 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2274,6 +2455,10 @@ packages: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + read-ini-file@4.0.0: resolution: {integrity: sha512-zz4qv/sKETv7nAkATqSJ9YMbKD8NXRPuA8d17VdYCuNYrVstB1S6UAMU6aytf5vRa9MESbZN7jLZdcmrOxz4gg==} engines: {node: '>=14.6'} @@ -2298,6 +2483,10 @@ packages: resolution: {integrity: sha512-wnWtnywepjg/eHIgWR97R7UuM5i+qHLA195qdN9UPKvcMqfn60+67S8sPPW3vDlSEfYHoFkKU8IvpCNty3zQvQ==} engines: {node: '>=10'} + rechoir@0.8.0: + resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} + engines: {node: '>= 10.13.0'} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -2314,6 +2503,10 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -2439,6 +2632,12 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + slow-redact@0.3.2: resolution: {integrity: sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==} @@ -2549,6 +2748,10 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2568,12 +2771,23 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + tar-fs@3.1.1: resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tarn@3.0.2: + resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} + engines: {node: '>=8.0.0'} + test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} @@ -2587,6 +2801,10 @@ packages: through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + tildify@2.0.0: + resolution: {integrity: sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==} + engines: {node: '>=8'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2632,6 +2850,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -3897,6 +4118,15 @@ snapshots: dependencies: is-windows: 1.0.2 + better-sqlite3@12.4.1: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -4012,6 +4242,8 @@ snapshots: check-error@2.1.1: {} + chownr@1.1.4: {} + cli-boxes@2.2.1: {} cli-columns@4.0.0: @@ -4027,10 +4259,14 @@ snapshots: color-name@1.1.4: {} + colorette@2.0.19: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 + commander@10.0.1: {} + commist@3.2.0: {} concat-map@0.0.1: {} @@ -4081,12 +4317,22 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.3.4: + dependencies: + ms: 2.1.2 + debug@4.4.3: dependencies: ms: 2.1.3 + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} + deep-is@0.1.4: {} defaults@1.0.4: @@ -4259,6 +4505,8 @@ snapshots: '@esbuild/win32-ia32': 0.25.10 '@esbuild/win32-x64': 0.25.10 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.8(eslint@9.37.0): @@ -4370,6 +4618,8 @@ snapshots: transitivePeerDependencies: - supports-color + esm@3.2.25: {} + espree@10.4.0: dependencies: acorn: 8.15.0 @@ -4414,6 +4664,8 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + expand-template@2.0.3: {} + expect-type@1.2.2: {} fast-decode-uri-component@1.0.1: {} @@ -4506,6 +4758,8 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-uri-to-path@1.0.0: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -4545,6 +4799,8 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + fs-constants@1.0.0: {} + fsevents@2.3.3: optional: true @@ -4576,6 +4832,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-package-type@0.1.0: {} + get-port@7.1.0: {} get-proto@1.0.1: @@ -4596,6 +4854,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + getopts@2.3.0: {} + + github-from-package@0.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4691,6 +4953,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + interpret@2.2.0: {} + ip-address@10.0.1: {} ipaddr.js@2.2.0: {} @@ -4922,6 +5186,28 @@ snapshots: dependencies: json-buffer: 3.0.1 + knex@3.1.0(better-sqlite3@12.4.1)(pg@8.16.3): + dependencies: + colorette: 2.0.19 + commander: 10.0.1 + debug: 4.3.4 + escalade: 3.2.0 + esm: 3.2.25 + get-package-type: 0.1.0 + getopts: 2.3.0 + interpret: 2.2.0 + lodash: 4.17.21 + pg-connection-string: 2.6.2 + rechoir: 0.8.0 + resolve-from: 5.0.0 + tarn: 3.0.2 + tildify: 2.0.0 + optionalDependencies: + better-sqlite3: 12.4.1 + pg: 8.16.3 + transitivePeerDependencies: + - supports-color + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -4962,6 +5248,8 @@ snapshots: lodash.once@4.1.1: {} + lodash@4.17.21: {} + loupe@3.2.1: {} lru-cache@10.4.3: {} @@ -5012,6 +5300,8 @@ snapshots: mimic-fn@3.1.0: {} + mimic-response@3.1.0: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -5024,6 +5314,8 @@ snapshots: minipass@7.1.2: {} + mkdirp-classic@0.5.3: {} + mqemitter@6.0.2: dependencies: fastparallel: 2.4.1 @@ -5068,10 +5360,14 @@ snapshots: - supports-color - utf-8-validate + ms@2.1.2: {} + ms@2.1.3: {} nanoid@3.3.11: {} + napi-build-utils@2.0.0: {} + natural-compare@1.4.0: {} ndjson@2.0.0: @@ -5082,6 +5378,10 @@ snapshots: split2: 3.2.2 through2: 4.0.2 + node-abi@3.78.0: + dependencies: + semver: 7.7.3 + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -5220,6 +5520,43 @@ snapshots: pathval@2.0.1: {} + pg-cloudflare@1.2.7: + optional: true + + pg-connection-string@2.6.2: {} + + pg-connection-string@2.9.1: {} + + pg-int8@1.0.1: {} + + pg-pool@3.10.1(pg@8.16.3): + dependencies: + pg: 8.16.3 + + pg-protocol@1.10.3: {} + + 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.7 + postgres-interval: 1.2.0 + + pg@8.16.3: + dependencies: + pg-connection-string: 2.9.1 + pg-pool: 3.10.1(pg@8.16.3) + pg-protocol: 1.10.3 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.2.7 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -5254,6 +5591,31 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-bytea@1.0.0: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.78.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -5297,6 +5659,13 @@ snapshots: quick-lru@4.0.1: {} + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + read-ini-file@4.0.0: dependencies: ini: 3.0.1 @@ -5325,6 +5694,10 @@ snapshots: realpath-missing@1.1.0: {} + rechoir@0.8.0: + dependencies: + resolve: 1.22.10 + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -5349,6 +5722,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-from@5.0.0: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -5508,6 +5883,14 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + slow-redact@0.3.2: {} smart-buffer@4.2.0: {} @@ -5630,6 +6013,8 @@ snapshots: strip-final-newline@2.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} strip-literal@3.1.0: @@ -5646,6 +6031,13 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + tar-fs@3.1.1: dependencies: pump: 3.0.3 @@ -5658,6 +6050,14 @@ snapshots: - bare-buffer - react-native-b4a + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + tar-stream@3.1.7: dependencies: b4a: 1.7.3 @@ -5667,6 +6067,8 @@ snapshots: - bare-abort-controller - react-native-b4a + tarn@3.0.2: {} + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 @@ -5687,6 +6089,8 @@ snapshots: dependencies: readable-stream: 3.6.2 + tildify@2.0.0: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -5723,6 +6127,10 @@ snapshots: tslib@2.8.1: {} + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/src/backbone.ts b/src/backbone.ts new file mode 100644 index 0000000..5468163 --- /dev/null +++ b/src/backbone.ts @@ -0,0 +1,40 @@ +import { AccessHandler } from './access/access.handler.ts'; +import { K8sService } from './k8s/k8s.ts'; +import { MqttServer } from './server/server.ts'; +import { TopicsHandler } from './topics/topics.handler.ts'; +import { Services } from './utils/services.ts'; + +class Backbone { + #services: Services; + + constructor(services = new Services()) { + this.#services = services; + } + + public get services() { + return this.#services; + } + + public get server() { + return this.#services.get(MqttServer); + } + + public get accessHandler() { + return this.#services.get(AccessHandler); + } + + public get topicsHandler() { + return this.#services.get(TopicsHandler); + } + + public get k8s() { + return this.#services.get(K8sService); + } + + public setupK8sOperator = async () => { + await this.k8s.setup(); + this.accessHandler.register('k8s', this.k8s.clients); + }; +} + +export { Backbone }; diff --git a/src/k8s/k8s.clients.ts b/src/k8s/k8s.clients.ts index 119db6a..3a043fe 100644 --- a/src/k8s/k8s.clients.ts +++ b/src/k8s/k8s.clients.ts @@ -1,33 +1,31 @@ -import { KubernetesObjectApi, PatchStrategy, type KubeConfig, type KubernetesObject } from '@kubernetes/client-node'; +import { KubernetesObjectApi, type KubernetesObject } from '@kubernetes/client-node'; -import type { K8sResources } from './k8s.resources.ts'; +import { K8sResources } from './k8s.resources.ts'; import type { K8sBackboneClient } from './k8s.schemas.ts'; import type { AccessProvider } from '#root/access/access.provider.ts'; import type { Statement } from '#root/access/access.schemas.ts'; - -type K8sClientsOptions = { - config: KubeConfig; - resources: K8sResources; -}; +import type { Services } from '#root/utils/services.ts'; +import { K8sConfig } from './k8s.config.ts'; type K8sClient = { statements: Statement[]; }; class K8sClients implements AccessProvider { - #options: K8sClientsOptions; + #services: Services; #clients: Map; - constructor(options: K8sClientsOptions) { + constructor(services: Services) { + this.#services = services; this.#clients = new Map(); - this.#options = options; - const { clients } = options.resources; + const { clients } = services.get(K8sResources); clients.on('updated', this.#handleClientAdded); } #handleClientAdded = async (manifest: KubernetesObject & { spec: K8sBackboneClient }) => { - const { resources, config } = this.#options; + const resources = this.#services.get(K8sResources); + const { config } = this.#services.get(K8sConfig); const secretName = `${manifest.metadata?.name}-secret`; const secret = resources.secrets.manifests.find( (m) => m.metadata?.namespace === manifest.metadata?.namespace && m.metadata?.name === secretName, diff --git a/src/k8s/k8s.config.ts b/src/k8s/k8s.config.ts new file mode 100644 index 0000000..511a6ba --- /dev/null +++ b/src/k8s/k8s.config.ts @@ -0,0 +1,15 @@ +import { KubeConfig } from '@kubernetes/client-node'; + +class K8sConfig { + #config?: KubeConfig; + + public get config() { + if (!this.#config) { + this.#config = new KubeConfig(); + this.#config.loadFromDefault(); + } + return this.#config; + } +} + +export { K8sConfig }; diff --git a/src/k8s/k8s.crd.ts b/src/k8s/k8s.crd.ts index a14ac8d..340a70e 100644 --- a/src/k8s/k8s.crd.ts +++ b/src/k8s/k8s.crd.ts @@ -1,71 +1,80 @@ -import { type KubeConfig, ApiException, ApiextensionsV1Api } from '@kubernetes/client-node'; +import type { Services } from '#root/utils/services.ts'; +import { ApiException, ApiextensionsV1Api } from '@kubernetes/client-node'; import { z, type ZodType } from 'zod'; +import { K8sConfig } from './k8s.config.ts'; type CreateCrdOptions = { - config: KubeConfig; kind: string; apiVersion: string; plural?: string; scope: 'Cluster' | 'Namespaced'; spec: ZodType; }; -const createCrd = async (options: CreateCrdOptions) => { - const { config, ...definition } = options; - const plural = definition.plural ?? definition.kind.toLowerCase() + 's'; - const [version, group] = definition.apiVersion.split('/').toReversed(); - const manifest = { - apiVersion: 'apiextensions.k8s.io/v1', - kind: 'CustomResourceDefinition', - metadata: { - name: `${plural}.${group}`, - }, - spec: { - group: group, - names: { - kind: definition.kind, - plural: plural, - singular: definition.kind.toLowerCase(), +class K8sCrds { + #services: Services; + + constructor(services: Services) { + this.#services = services; + } + + install = async (definition: CreateCrdOptions) => { + const { config } = this.#services.get(K8sConfig); + const plural = definition.plural ?? definition.kind.toLowerCase() + 's'; + const [version, group] = definition.apiVersion.split('/').toReversed(); + const manifest = { + apiVersion: 'apiextensions.k8s.io/v1', + kind: 'CustomResourceDefinition', + metadata: { + name: `${plural}.${group}`, }, - scope: definition.scope, - versions: [ - { - name: version, - served: true, - storage: true, - schema: { - openAPIV3Schema: { - type: 'object', - properties: { - spec: { - ...z.toJSONSchema(definition.spec, { io: 'input' }), - $schema: undefined, - additionalProperties: undefined, - } as ExplicitAny, + spec: { + group: group, + names: { + kind: definition.kind, + plural: plural, + singular: definition.kind.toLowerCase(), + }, + scope: definition.scope, + versions: [ + { + name: version, + served: true, + storage: true, + schema: { + openAPIV3Schema: { + type: 'object', + properties: { + spec: { + ...z.toJSONSchema(definition.spec, { io: 'input' }), + $schema: undefined, + additionalProperties: undefined, + } as ExplicitAny, + }, }, }, + subresources: { + status: {}, + }, }, - subresources: { - status: {}, - }, - }, - ], - }, - }; - const extensionsApi = config.makeApiClient(ApiextensionsV1Api); - try { - await extensionsApi.createCustomResourceDefinition({ - body: manifest, - }); - } catch (error) { - if (error instanceof ApiException && error.code === 409) { - await extensionsApi.patchCustomResourceDefinition({ - name: manifest.metadata.name, - body: [{ op: 'replace', path: '/spec', value: manifest.spec }], + ], + }, + }; + const extensionsApi = config.makeApiClient(ApiextensionsV1Api); + try { + await extensionsApi.createCustomResourceDefinition({ + body: manifest, }); - } else { - throw error; + } catch (error) { + if (error instanceof ApiException && error.code === 409) { + await extensionsApi.patchCustomResourceDefinition({ + name: manifest.metadata.name, + body: [{ op: 'replace', path: '/spec', value: manifest.spec }], + }); + } else { + throw error; + } } - } -}; + }; +} -export { createCrd }; +export { K8sCrds }; diff --git a/src/k8s/k8s.resources.ts b/src/k8s/k8s.resources.ts index 42fb787..a2bbe9c 100644 --- a/src/k8s/k8s.resources.ts +++ b/src/k8s/k8s.resources.ts @@ -1,48 +1,60 @@ -import { KubeConfig, V1Secret, type KubernetesObject } from '@kubernetes/client-node'; +import { V1Secret, type KubernetesObject } from '@kubernetes/client-node'; import { K8sWatcher } from './k8s.watcher.ts'; import type { K8sBackboneClient, K8sBackboneTopic } from './k8s.schemas.ts'; +import type { Services } from '#root/utils/services.ts'; +import { K8sConfig } from './k8s.config.ts'; class K8sResources { - #secrets: K8sWatcher; - #clients: K8sWatcher; - #topics: K8sWatcher; + #services: Services; + #secrets?: K8sWatcher; + #clients?: K8sWatcher; + #topics?: K8sWatcher; - constructor(config: KubeConfig) { - config.loadFromDefault(); - this.#secrets = new K8sWatcher({ - config, - apiVersion: 'v1', - kind: 'Secret', - }); - this.#clients = new K8sWatcher({ - config, - apiVersion: 'backbone.mortenolsen.pro/v1', - kind: 'Client', - }); - this.#topics = new K8sWatcher({ - config, - apiVersion: 'backbone.mortenolsen.pro/v1', - kind: 'Topic', - }); + constructor(services: Services) { + this.#services = services; } public get secrets() { + if (!this.#secrets) { + const { config } = this.#services.get(K8sConfig); + this.#secrets = new K8sWatcher({ + config, + apiVersion: 'v1', + kind: 'Secret', + }); + } return this.#secrets; } public get clients() { + if (!this.#clients) { + const { config } = this.#services.get(K8sConfig); + this.#clients = new K8sWatcher({ + config, + apiVersion: 'backbone.mortenolsen.pro/v1', + kind: 'Client', + }); + } return this.#clients; } public get topics() { - return this.#clients; + if (!this.#topics) { + const { config } = this.#services.get(K8sConfig); + this.#topics = new K8sWatcher({ + config, + apiVersion: 'backbone.mortenolsen.pro/v1', + kind: 'Topic', + }); + } + return this.#topics; } public start = async () => { - await this.#secrets.start(); - await this.#clients.start(); - await this.#topics.start(); + await this.secrets.start(); + await this.clients.start(); + await this.topics.start(); }; } diff --git a/src/k8s/k8s.ts b/src/k8s/k8s.ts index eaabddd..459459e 100644 --- a/src/k8s/k8s.ts +++ b/src/k8s/k8s.ts @@ -1,51 +1,43 @@ import { KubeConfig } from '@kubernetes/client-node'; import { K8sResources } from './k8s.resources.ts'; -import { createCrd } from './k8s.crd.ts'; +import { K8sCrds } from './k8s.crd.ts'; import { k8sBackboneClientSchema, k8sBackboneTopicSchema } from './k8s.schemas.ts'; import { K8sClients } from './k8s.clients.ts'; import { API_VERSION } from '#root/utils/consts.ts'; +import type { Services } from '#root/utils/services.ts'; class K8sService { - #config: KubeConfig; - #resources: K8sResources; - #clients: K8sClients; + #services: Services; - constructor() { - this.#config = new KubeConfig(); - this.#config.loadFromDefault(); - this.#resources = new K8sResources(this.#config); - this.#clients = new K8sClients({ - config: this.#config, - resources: this.resources, - }); + constructor(services: Services) { + this.#services = services; } public get resources() { - return this.#resources; + return this.#services.get(K8sResources); } public get clients() { - return this.#clients; + return this.#services.get(K8sClients); } public setup = async () => { - await createCrd({ - config: this.#config, + const crds = this.#services.get(K8sCrds); + await crds.install({ apiVersion: API_VERSION, kind: 'Client', scope: 'Namespaced', spec: k8sBackboneClientSchema, }); - await createCrd({ - config: this.#config, + await crds.install({ apiVersion: API_VERSION, kind: 'Topic', scope: 'Namespaced', spec: k8sBackboneTopicSchema, }); - await this.#resources.start(); + await this.resources.start(); }; } diff --git a/src/server/server.ts b/src/server/server.ts index ed03d85..c962d7b 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -16,8 +16,9 @@ import { createWebSocketStream } from 'ws'; import { Session } from '../access/access.session.ts'; import { api } from '../api/api.ts'; -import type { AccessHandler } from '#root/access/access.handler.ts'; -import type { TopicsHandler } from '#root/topics/topics.handler.ts'; +import { AccessHandler } from '#root/access/access.handler.ts'; +import { TopicsHandler } from '#root/topics/topics.handler.ts'; +import type { Services } from '#root/utils/services.ts'; type Aedes = ReturnType; @@ -30,23 +31,18 @@ declare module 'aedes' { const packetMetaSymbol = Symbol('packetMeta'); -type MqttServerOptions = { - accessHandler: AccessHandler; - topicsHandler: TopicsHandler; -}; - class AuthError extends Error { public readonly returnCode = 4; } class MqttServer { - #options: MqttServerOptions; + #services: Services; #server: Aedes; #http?: Promise; #tcp?: tcp.Server; - constructor(options: MqttServerOptions) { - this.#options = options; + constructor(services: Services) { + this.#services = services; this.#server = aedes.createBroker({ authenticate: this.#authenticate, authorizePublish: this.#authorizePublish, @@ -61,7 +57,7 @@ class MqttServer { if (!username || !password) { throw new Error('unauthorized'); } - const { accessHandler } = this.#options; + const accessHandler = this.#services.get(AccessHandler); const auth = await accessHandler.validate(username, password.toString('utf8')); client.session = new Session(auth); callback(null, true); @@ -71,7 +67,7 @@ class MqttServer { }; #authorizePublish: AuthorizePublishHandler = (client, packet, callback) => { - const { topicsHandler } = this.#options; + const topicsHandler = this.#services.get(TopicsHandler); (packet as ExplicitAny)[packetMetaSymbol] = { foo: 'bar', }; diff --git a/src/test.ts b/src/test.ts index 5d4ee20..f40f0b1 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,21 +1,11 @@ -import { AccessHandler } from './access/access.handler.ts'; -import { K8sService } from './k8s/k8s.ts'; -import { MqttServer } from './server/server.ts'; -import { TopicsHandler } from './topics/topics.handler.ts'; +import { Backbone } from './backbone.ts'; -const accessHandler = new AccessHandler(); -const topicsHandler = new TopicsHandler(); +const backbone = new Backbone(); +await backbone.setupK8sOperator(); -const k8s = new K8sService(); -await k8s.setup(); -accessHandler.register('k8s', k8s.clients); -const server = new MqttServer({ - accessHandler, - topicsHandler, -}); -const tcp = server.getTcpServer(); +const tcp = backbone.server.getTcpServer(); tcp.listen(1883); -const http = await server.getHttpServer(); +const http = await backbone.server.getHttpServer(); http.listen({ port: 8883 }); console.log('started'); diff --git a/src/utils/services.ts b/src/utils/services.ts new file mode 100644 index 0000000..a14fb5e --- /dev/null +++ b/src/utils/services.ts @@ -0,0 +1,51 @@ +const destroy = Symbol('destroy'); +const instanceKey = Symbol('instances'); + +type ServiceDependency = new (services: Services) => T & { + [destroy]?: () => Promise | void; +}; + +class Services { + [instanceKey]: Map, unknown>; + + constructor() { + this[instanceKey] = new Map(); + } + + public get = (service: ServiceDependency) => { + if (!this[instanceKey].has(service)) { + this[instanceKey].set(service, new service(this)); + } + const instance = this[instanceKey].get(service); + if (!instance) { + throw new Error('Could not generate instance'); + } + return instance as T; + }; + + public set = (service: ServiceDependency, instance: Partial) => { + this[instanceKey].set(service, instance); + }; + + public clone = () => { + const services = new Services(); + services[instanceKey] = Object.fromEntries(this[instanceKey].entries()); + }; + + public destroy = async () => { + await Promise.all( + this[instanceKey].values().map(async (instance) => { + if ( + typeof instance === 'object' && + instance && + destroy in instance && + typeof instance[destroy] === 'function' + ) { + await instance[destroy](); + } + }), + ); + }; +} + +export { Services, destroy }; diff --git a/tests/utils/utils.world.ts b/tests/utils/utils.world.ts index 4b604ec..8a19faf 100644 --- a/tests/utils/utils.world.ts +++ b/tests/utils/utils.world.ts @@ -8,6 +8,7 @@ import { MqttServer } from '#root/server/server.ts'; import type { TopicDefinition } from '#root/topics/topcis.schemas.ts'; import { TopicsHandler } from '#root/topics/topics.handler.ts'; import { TopicsStore } from '#root/topics/topics.store.ts'; +import { Backbone } from '#root/backbone.ts'; type CreateSocketOptions = { port: number; @@ -30,17 +31,16 @@ type WorldOptions = { const createWorld = async (options: WorldOptions) => { const { topics = [] } = options; + const backbone = new Backbone(); const secret = 'test'; - const accessHandler = new AccessHandler(); const accessTokens = new AccessTokens({ secret, }); - accessHandler.register('token', accessTokens); - const topicsHandler = new TopicsHandler(); + backbone.accessHandler.register('token', accessTokens); const topicsStore = new TopicsStore(); topicsStore.register(...topics); - topicsHandler.register(topicsStore); - const server = new MqttServer({ topicsHandler, accessHandler }); + backbone.topicsHandler.register(topicsStore); + const server = backbone.server; const fastify = await server.getHttpServer(); const port = await getPort(); await fastify.listen({ port });