From abde872ab2f060a86ea954cebf28e145e7ead831 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 6 Mar 2026 07:52:10 +0000 Subject: [PATCH] feat(apiclient): add TypeScript API client (ts_apiclient) with resource managers and package exports --- changelog.md | 9 + package.json | 9 +- pnpm-lock.yaml | 463 +++++++++++----------- test/test.apiclient.ts | 376 ++++++++++++++++++ ts/00_commitinfo_data.ts | 2 +- ts_apiclient/classes.apitoken.ts | 157 ++++++++ ts_apiclient/classes.certificate.ts | 123 ++++++ ts_apiclient/classes.config.ts | 17 + ts_apiclient/classes.dcrouterapiclient.ts | 112 ++++++ ts_apiclient/classes.email.ts | 77 ++++ ts_apiclient/classes.logs.ts | 37 ++ ts_apiclient/classes.radius.ts | 180 +++++++++ ts_apiclient/classes.remoteingress.ts | 185 +++++++++ ts_apiclient/classes.route.ts | 203 ++++++++++ ts_apiclient/classes.stats.ts | 111 ++++++ ts_apiclient/index.ts | 15 + ts_apiclient/plugins.ts | 8 + ts_web/00_commitinfo_data.ts | 2 +- 18 files changed, 1850 insertions(+), 236 deletions(-) create mode 100644 test/test.apiclient.ts create mode 100644 ts_apiclient/classes.apitoken.ts create mode 100644 ts_apiclient/classes.certificate.ts create mode 100644 ts_apiclient/classes.config.ts create mode 100644 ts_apiclient/classes.dcrouterapiclient.ts create mode 100644 ts_apiclient/classes.email.ts create mode 100644 ts_apiclient/classes.logs.ts create mode 100644 ts_apiclient/classes.radius.ts create mode 100644 ts_apiclient/classes.remoteingress.ts create mode 100644 ts_apiclient/classes.route.ts create mode 100644 ts_apiclient/classes.stats.ts create mode 100644 ts_apiclient/index.ts create mode 100644 ts_apiclient/plugins.ts diff --git a/changelog.md b/changelog.md index fe5669e..8b4b0b2 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2026-03-06 - 11.1.0 - feat(apiclient) +add TypeScript API client (ts_apiclient) with resource managers and package exports + +- Add new ts_apiclient module providing DcRouterApiClient and resource managers: routes, certificates, api tokens, remote ingress, emails, stats, config, logs, and radius (with sub-managers). +- Add resource classes and builders (Route, RemoteIngress, ApiToken, Certificate, Email) and convenience manager APIs for common operations. +- Export apiclient in package.json (exports and files) and add ts_apiclient index and plugins wrapper for @api.global/typedrequest. +- Add comprehensive tests for the API client (test/test.apiclient.ts). +- Bump devDependencies: @git.zone/tsbuild -> ^4.3.0 and @types/node -> ^25.3.5 + ## 2026-03-05 - 11.0.51 - fix(build) include HTML files in tsbundle output and bump tsbuild/tsbundle devDependencies diff --git a/package.json b/package.json index 955cc73..c82cc74 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "type": "module", "exports": { ".": "./dist_ts/index.js", - "./interfaces": "./dist_ts_interfaces/index.js" + "./interfaces": "./dist_ts_interfaces/index.js", + "./apiclient": "./dist_ts_apiclient/index.js" }, "author": "Task Venture Capital GmbH", "license": "MIT", @@ -19,12 +20,12 @@ "watch": "tswatch" }, "devDependencies": { - "@git.zone/tsbuild": "^4.2.6", + "@git.zone/tsbuild": "^4.3.0", "@git.zone/tsbundle": "^2.9.1", "@git.zone/tsrun": "^2.0.1", "@git.zone/tstest": "^3.2.0", "@git.zone/tswatch": "^3.2.5", - "@types/node": "^25.3.3" + "@types/node": "^25.3.5" }, "dependencies": { "@api.global/typedrequest": "^3.3.0", @@ -100,10 +101,12 @@ "files": [ "ts/**/*", "ts_web/**/*", + "ts_apiclient/**/*", "dist/**/*", "dist_*/**/*", "dist_ts/**/*", "dist_ts_web/**/*", + "dist_ts_apiclient/**/*", "assets/**/*", "cli.js", "npmextra.json", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc46f27..edae50c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,8 +115,8 @@ importers: version: 13.0.0 devDependencies: '@git.zone/tsbuild': - specifier: ^4.2.6 - version: 4.2.6 + specifier: ^4.3.0 + version: 4.3.0 '@git.zone/tsbundle': specifier: ^2.9.1 version: 2.9.1 @@ -130,8 +130,8 @@ importers: specifier: ^3.2.5 version: 3.2.5(@tiptap/pm@2.27.2) '@types/node': - specifier: ^25.3.3 - version: 25.3.3 + specifier: ^25.3.5 + version: 25.3.5 packages: @@ -178,127 +178,127 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-s3@3.1002.0': - resolution: {integrity: sha512-tc+vZgvjcm+1Ot+YhQjXZxVELKGGGO3D5cuR4p5xaeitXYX2+RRiz4/WdSak9slumIClnlXsdqhJ0OHognUT+w==} + '@aws-sdk/client-s3@3.1003.0': + resolution: {integrity: sha512-on8GvIWeH1pD0l53NuKbPO84bEC1mk/9zskgU+dVKcVoGxOZI94fVddCJb+IwIUN6rfBHCfXPCVbgVyzsHTAVg==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.973.17': - resolution: {integrity: sha512-VtgGP0TjbCeyp6DQpiBqJKbemTSIaN2bZc3UbeTDCani3lBCyxn75ouJYD6koSSp0bh7rKLEbUpiFsNCI7tr0w==} + '@aws-sdk/core@3.973.18': + resolution: {integrity: sha512-GUIlegfcK2LO1J2Y98sCJy63rQSiLiDOgVw7HiHPRqfI2vb3XozTVqemwO0VSGXp54ngCnAQz0Lf0YPCBINNxA==} engines: {node: '>=20.0.0'} - '@aws-sdk/crc64-nvme@3.972.3': - resolution: {integrity: sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg==} + '@aws-sdk/crc64-nvme@3.972.4': + resolution: {integrity: sha512-HKZIZLbRyvzo/bXZU7Zmk6XqU+1C9DjI56xd02vwuDIxedxBEqP17t9ExhbP9QFeNq/a3l9GOcyirFXxmbDhmw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.15': - resolution: {integrity: sha512-RhHQG1lhkWHL4tK1C/KDjaOeis+9U0tAMnWDiwiSVQZMC7CsST9Xin+sK89XywJ5g/tyABtb7TvFePJ4Te5XSQ==} + '@aws-sdk/credential-provider-env@3.972.16': + resolution: {integrity: sha512-HrdtnadvTGAQUr18sPzGlE5El3ICphnH6SU7UQOMOWFgRKbTRNN8msTxM4emzguUso9CzaHU2xy5ctSrmK5YNA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.17': - resolution: {integrity: sha512-b/bDL76p51+yQ+0O9ZDH5nw/ioE0sRYkjwjOwFWAWZXo6it2kQZUOXhVpjohx3ldKyUxt/SwAivjUu1Nr/PWlQ==} + '@aws-sdk/credential-provider-http@3.972.18': + resolution: {integrity: sha512-NyB6smuZAixND5jZumkpkunQ0voc4Mwgkd+SZ6cvAzIB7gK8HV8Zd4rS8Kn5MmoGgusyNfVGG+RLoYc4yFiw+A==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.972.15': - resolution: {integrity: sha512-qWnM+wB8MmU2kKY7f4KowKjOjkwRosaFxrtseEEIefwoXn1SjN+CbHzXBVdTAQxxkbBiqhPgJ/WHiPtES4grRQ==} + '@aws-sdk/credential-provider-ini@3.972.16': + resolution: {integrity: sha512-hzAnzNXKV0A4knFRWGu2NCt72P4WWxpEGnOc6H3DptUjC4oX3hGw846oN76M1rTHAOwDdbhjU0GAOWR4OUfTZg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.15': - resolution: {integrity: sha512-x92FJy34/95wgu+qOGD8SHcgh1hZ9Qx2uFtQEGn4m9Ljou8ICIv3Ybq5yxdB7A60S8ZGCQB0mIopmjJwiLbh5g==} + '@aws-sdk/credential-provider-login@3.972.16': + resolution: {integrity: sha512-VI0kXTlr0o1FTay+Jvx6AKqx5ECBgp7X4VevGBEbuXdCXnNp7SPU0KvjsOLVhIz3OoPK4/lTXphk43t0IVk65w==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.16': - resolution: {integrity: sha512-7mlt14Ee4rPFAFUVgpWE7+0CBhetJJyzVFqfIsMp7sgyOSm9Y/+qHZOWAuK5I4JNc+Y5PltvJ9kssTzRo92iXQ==} + '@aws-sdk/credential-provider-node@3.972.17': + resolution: {integrity: sha512-98MAcQ2Dk7zkvgwZ5f6fLX2lTyptC3gTSDx4EpvTdJWET8qs9lBPYggoYx7GmKp/5uk0OwVl0hxIDZsDNS/Y9g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.15': - resolution: {integrity: sha512-PrH3iTeD18y/8uJvQD2s/T87BTGhsdS/1KZU7ReWHXsplBwvCqi7AbnnNbML1pFlQwRWCE2RdSZFWDVId3CvkA==} + '@aws-sdk/credential-provider-process@3.972.16': + resolution: {integrity: sha512-n89ibATwnLEg0ZdZmUds5bq8AfBAdoYEDpqP3uzPLaRuGelsKlIvCYSNNvfgGLi8NaHPNNhs1HjJZYbqkW9b+g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.972.15': - resolution: {integrity: sha512-M/+LBHTPKZxxXckM6m4dnJeR+jlm9NynH9b2YDswN4Zj2St05SK/crdL3Wy3WfJTZootnnhm3oTh87Usl7PS7w==} + '@aws-sdk/credential-provider-sso@3.972.16': + resolution: {integrity: sha512-b9of7tQgERxgcEcwAFWvRe84ivw+Kw6b3jVuz/6LQzonkomiY5UoWfprkbjc8FSCQ2VjDqKTvIRA9F0KSQ025w==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.15': - resolution: {integrity: sha512-QTH6k93v+UOfFam/ado8zc71tH+enTVyuvLy9uEWXX1x894dN5ovtf/MdBDgFwq3g6c9mbtgVJ4B+yBqDtXvdA==} + '@aws-sdk/credential-provider-web-identity@3.972.16': + resolution: {integrity: sha512-PaOH5jFoPQX4WkqpKzKh9cM7rieKtbgEGqrZ+ybGmotJhcvhI/xl69yCwMbHGnpQJJmHZIX9q2zaPB7HTBn/4w==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-bucket-endpoint@3.972.6': - resolution: {integrity: sha512-3H2bhvb7Cb/S6WFsBy/Dy9q2aegC9JmGH1inO8Lb2sWirSqpLJlZmvQHPE29h2tIxzv6el/14X/tLCQ8BQU6ZQ==} + '@aws-sdk/middleware-bucket-endpoint@3.972.7': + resolution: {integrity: sha512-goX+axlJ6PQlRnzE2bQisZ8wVrlm6dXJfBzMJhd8LhAIBan/w1Kl73fJnalM/S+18VnpzIHumyV6DtgmvqG5IA==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-expect-continue@3.972.6': - resolution: {integrity: sha512-QMdffpU+GkSGC+bz6WdqlclqIeCsOfgX8JFZ5xvwDtX+UTj4mIXm3uXu7Ko6dBseRcJz1FA6T9OmlAAY6JgJUg==} + '@aws-sdk/middleware-expect-continue@3.972.7': + resolution: {integrity: sha512-mvWqvm61bmZUKmmrtl2uWbokqpenY3Mc3Jf4nXB/Hse6gWxLPaCQThmhPBDzsPSV8/Odn8V6ovWt3pZ7vy4BFQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.973.3': - resolution: {integrity: sha512-C9Mu9pXMpQh7jBydx0MrfQxNIKwJvKbVbJJ0GZthM+cQ+KTChXA01MwttRsMq0ZRb4pBJZQtIKDUxXusDr5OKg==} + '@aws-sdk/middleware-flexible-checksums@3.973.4': + resolution: {integrity: sha512-7CH2jcGmkvkHc5Buz9IGbdjq1729AAlgYJiAvGq7qhCHqYleCsriWdSnmsqWTwdAfXHMT+pkxX3w6v5tJNcSug==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-host-header@3.972.6': - resolution: {integrity: sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w==} + '@aws-sdk/middleware-host-header@3.972.7': + resolution: {integrity: sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-location-constraint@3.972.6': - resolution: {integrity: sha512-XdZ2TLwyj3Am6kvUc67vquQvs6+D8npXvXgyEUJAdkUDx5oMFJKOqpK+UpJhVDsEL068WAJl2NEGzbSik7dGJQ==} + '@aws-sdk/middleware-location-constraint@3.972.7': + resolution: {integrity: sha512-vdK1LJfffBp87Lj0Bw3WdK1rJk9OLDYdQpqoKgmpIZPe+4+HawZ6THTbvjhJt4C4MNnRrHTKHQjkwBiIpDBoig==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-logger@3.972.6': - resolution: {integrity: sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw==} + '@aws-sdk/middleware-logger@3.972.7': + resolution: {integrity: sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-recursion-detection@3.972.6': - resolution: {integrity: sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw==} + '@aws-sdk/middleware-recursion-detection@3.972.7': + resolution: {integrity: sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.17': - resolution: {integrity: sha512-uSyOGoVFMP44pTt29MIMfsOjegqE/7lT0K3HG0GWPiH2lD4rqZC/TRi/kH4zrGiOQdsaLc+dkfd7Sb2q8vh+gA==} + '@aws-sdk/middleware-sdk-s3@3.972.18': + resolution: {integrity: sha512-5E3XxaElrdyk6ZJ0TjH7Qm6ios4b/qQCiLr6oQ8NK7e4Kn6JBTJCaYioQCQ65BpZ1+l1mK5wTAac2+pEz0Smpw==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-ssec@3.972.6': - resolution: {integrity: sha512-acvMUX9jF4I2Ew+Z/EA6gfaFaz9ehci5wxBmXCZeulLuv8m+iGf6pY9uKz8TPjg39bdAz3hxoE0eLP8Qz+IYlA==} + '@aws-sdk/middleware-ssec@3.972.7': + resolution: {integrity: sha512-G9clGVuAml7d8DYzY6DnRi7TIIDRvZ3YpqJPz/8wnWS5fYx/FNWNmkO6iJVlVkQg9BfeMzd+bVPtPJOvC4B+nQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-user-agent@3.972.17': - resolution: {integrity: sha512-HHArkgWzomuwufXwheQqkddu763PWCpoNTq1dGjqXzJT/lojX3VlOqjNSR2Xvb6/T9ISfwYcMOcbFgUp4EWxXA==} + '@aws-sdk/middleware-user-agent@3.972.18': + resolution: {integrity: sha512-KcqQDs/7WtoEnp52+879f8/i1XAJkgka5i4arOtOCPR10o4wWo3VRecDI9Gxoh6oghmLCnIiOSKyRcXI/50E+w==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.996.5': - resolution: {integrity: sha512-zn0WApcULn7Rtl6T+KP2CQTZo/7wOa2YV1yHQnbijTQoi4YXQHM8s21JcJzt33/mqPh8AdvWX1f+83KvKuxlZw==} + '@aws-sdk/nested-clients@3.996.6': + resolution: {integrity: sha512-blNJ3ugn4gCQ9ZSZi/firzKCvVl5LvPFVxv24LprENeWI4R8UApG006UQkF4SkmLygKq2BQXRad2/anQ13Te4Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/region-config-resolver@3.972.6': - resolution: {integrity: sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw==} + '@aws-sdk/region-config-resolver@3.972.7': + resolution: {integrity: sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.5': - resolution: {integrity: sha512-AVIhf74wRMzU1WBPVzcGPjlADF5VxZ8m8Ctm1v7eO4/reWMhZnEBn4tlR4vM4pOYFkdrYp3MTzYVZIikCO+53Q==} + '@aws-sdk/signature-v4-multi-region@3.996.6': + resolution: {integrity: sha512-NnsOQsVmJXy4+IdPFUjRCWPn9qNH1TzS/f7MiWgXeoHs903tJpAWQWQtoFvLccyPoBgomKP9L89RRr2YsT/L0g==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1002.0': - resolution: {integrity: sha512-x972uKOydFn4Rb0PZJzLdNW59rH0KWC78Q2JbQzZpGlGt0DxjYdDRwBG6F42B1MyaEwHGqO/tkGc4r3/PRFfMw==} + '@aws-sdk/token-providers@3.1003.0': + resolution: {integrity: sha512-SOyyWNdT7njKRwtZ1JhwHlH1csv6Pkgf305X96/OIfnhq1pU/EjmT6W6por57rVrjrKuHBuEIXgpWv8OgoMHpg==} engines: {node: '>=20.0.0'} - '@aws-sdk/types@3.973.4': - resolution: {integrity: sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==} + '@aws-sdk/types@3.973.5': + resolution: {integrity: sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-arn-parser@3.972.2': - resolution: {integrity: sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==} + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-endpoints@3.996.3': - resolution: {integrity: sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==} + '@aws-sdk/util-endpoints@3.996.4': + resolution: {integrity: sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-locate-window@3.965.4': - resolution: {integrity: sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==} + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-user-agent-browser@3.972.6': - resolution: {integrity: sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA==} + '@aws-sdk/util-user-agent-browser@3.972.7': + resolution: {integrity: sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw==} - '@aws-sdk/util-user-agent-node@3.973.2': - resolution: {integrity: sha512-lpaIuekdkpw7VRiik0IZmd6TyvEUcuLgKZ5fKRGpCA3I4PjrD/XH15sSwW+OptxQjNU4DEzSxag70spC9SluvA==} + '@aws-sdk/util-user-agent-node@3.973.3': + resolution: {integrity: sha512-8s2cQmTUOwcBlIJyI9PAZNnnnF+cGtdhHc1yzMMsSD/GR/Hxj7m0IGUE92CslXXb8/p5Q76iqOCjN1GFwyf+1A==} engines: {node: '>=20.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -306,8 +306,8 @@ packages: aws-crt: optional: true - '@aws-sdk/xml-builder@3.972.9': - resolution: {integrity: sha512-ItnlMgSqkPrUfJs7EsvU/01zw5UeIb2tNPhD09LBLHbg+g+HDiKibSLwpkuz/ZIlz4F2IMn+5XgE4AK/pfPuog==} + '@aws-sdk/xml-builder@3.972.10': + resolution: {integrity: sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA==} engines: {node: '>=20.0.0'} '@aws/lambda-invoke-store@0.2.3': @@ -538,8 +538,8 @@ packages: resolution: {integrity: sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==} engines: {node: '>=6'} - '@git.zone/tsbuild@4.2.6': - resolution: {integrity: sha512-Vb9ccFMcNb2RK6OC/9DkzQFyORwOk0WDX3YH0wMKt3gOID+7zWFI8kP943mcly8UHMXn3+Yt1DDEXXDAMKPvQw==} + '@git.zone/tsbuild@4.3.0': + resolution: {integrity: sha512-lb6eMQ8RQPaJqAB4kC++GIElOiTAH1pClmoND/q7XHuiMZxv6cXz2/U/sZt339mon2c40dXRG2tkLF2jRsP0pQ==} hasBin: true '@git.zone/tsbundle@2.9.1': @@ -905,8 +905,8 @@ packages: '@push.rocks/smartfs@1.3.1': resolution: {integrity: sha512-ZSduVS8tM+/erbyCTvRRvc9gLWwbpqN5xdIIkMr+gub7fowSeJb7tR2rnGwySa63DyimU0q2KTp79VV9YqGLeg==} - '@push.rocks/smartfs@1.3.3': - resolution: {integrity: sha512-IF16dgeDFDv65j+lhyhqjhm6gFhBrWTrnayVOCwbBg4yJ/6tNpd7sL8YsvBJRBRCBWrMFBCSL7thG0Thvq0VZA==} + '@push.rocks/smartfs@1.4.0': + resolution: {integrity: sha512-4PgteGOyMBiUWKLfTXOjxrZz+sXPLnvcmHeAEHY2gwZJflfp5/YDVPhodctOydersXzkynO359dIGLSCyQnnAQ==} '@push.rocks/smartguard@3.1.0': resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==} @@ -1789,11 +1789,11 @@ packages: '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@22.19.13': - resolution: {integrity: sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==} + '@types/node@22.19.15': + resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} - '@types/node@25.3.3': - resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==} + '@types/node@25.3.5': + resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==} '@types/ping@0.4.4': resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==} @@ -4197,21 +4197,21 @@ snapshots: '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 tslib: 2.8.1 '@aws-crypto/sha1-browser@5.2.0': dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.4 - '@aws-sdk/util-locate-window': 3.965.4 + '@aws-sdk/types': 3.973.5 + '@aws-sdk/util-locate-window': 3.965.5 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -4220,15 +4220,15 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.4 - '@aws-sdk/util-locate-window': 3.965.4 + '@aws-sdk/types': 3.973.5 + '@aws-sdk/util-locate-window': 3.965.5 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 tslib: 2.8.1 '@aws-crypto/supports-web-crypto@5.2.0': @@ -4237,33 +4237,33 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.1002.0': + '@aws-sdk/client-s3@3.1003.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.973.17 - '@aws-sdk/credential-provider-node': 3.972.16 - '@aws-sdk/middleware-bucket-endpoint': 3.972.6 - '@aws-sdk/middleware-expect-continue': 3.972.6 - '@aws-sdk/middleware-flexible-checksums': 3.973.3 - '@aws-sdk/middleware-host-header': 3.972.6 - '@aws-sdk/middleware-location-constraint': 3.972.6 - '@aws-sdk/middleware-logger': 3.972.6 - '@aws-sdk/middleware-recursion-detection': 3.972.6 - '@aws-sdk/middleware-sdk-s3': 3.972.17 - '@aws-sdk/middleware-ssec': 3.972.6 - '@aws-sdk/middleware-user-agent': 3.972.17 - '@aws-sdk/region-config-resolver': 3.972.6 - '@aws-sdk/signature-v4-multi-region': 3.996.5 - '@aws-sdk/types': 3.973.4 - '@aws-sdk/util-endpoints': 3.996.3 - '@aws-sdk/util-user-agent-browser': 3.972.6 - '@aws-sdk/util-user-agent-node': 3.973.2 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/credential-provider-node': 3.972.17 + '@aws-sdk/middleware-bucket-endpoint': 3.972.7 + '@aws-sdk/middleware-expect-continue': 3.972.7 + '@aws-sdk/middleware-flexible-checksums': 3.973.4 + '@aws-sdk/middleware-host-header': 3.972.7 + '@aws-sdk/middleware-location-constraint': 3.972.7 + '@aws-sdk/middleware-logger': 3.972.7 + '@aws-sdk/middleware-recursion-detection': 3.972.7 + '@aws-sdk/middleware-sdk-s3': 3.972.18 + '@aws-sdk/middleware-ssec': 3.972.7 + '@aws-sdk/middleware-user-agent': 3.972.18 + '@aws-sdk/region-config-resolver': 3.972.7 + '@aws-sdk/signature-v4-multi-region': 3.996.6 + '@aws-sdk/types': 3.973.5 + '@aws-sdk/util-endpoints': 3.996.4 + '@aws-sdk/util-user-agent-browser': 3.972.7 + '@aws-sdk/util-user-agent-node': 3.973.3 '@smithy/config-resolver': 4.4.10 '@smithy/core': 3.23.8 '@smithy/eventstream-serde-browser': 4.2.11 @@ -4301,10 +4301,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.973.17': + '@aws-sdk/core@3.973.18': dependencies: - '@aws-sdk/types': 3.973.4 - '@aws-sdk/xml-builder': 3.972.9 + '@aws-sdk/types': 3.973.5 + '@aws-sdk/xml-builder': 3.972.10 '@smithy/core': 3.23.8 '@smithy/node-config-provider': 4.3.11 '@smithy/property-provider': 4.2.11 @@ -4317,23 +4317,23 @@ snapshots: '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 - '@aws-sdk/crc64-nvme@3.972.3': + '@aws-sdk/crc64-nvme@3.972.4': dependencies: '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.15': + '@aws-sdk/credential-provider-env@3.972.16': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/types': 3.973.5 '@smithy/property-provider': 4.2.11 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.17': + '@aws-sdk/credential-provider-http@3.972.18': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/types': 3.973.5 '@smithy/fetch-http-handler': 5.3.13 '@smithy/node-http-handler': 4.4.14 '@smithy/property-provider': 4.2.11 @@ -4343,17 +4343,17 @@ snapshots: '@smithy/util-stream': 4.5.17 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.972.15': + '@aws-sdk/credential-provider-ini@3.972.16': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/credential-provider-env': 3.972.15 - '@aws-sdk/credential-provider-http': 3.972.17 - '@aws-sdk/credential-provider-login': 3.972.15 - '@aws-sdk/credential-provider-process': 3.972.15 - '@aws-sdk/credential-provider-sso': 3.972.15 - '@aws-sdk/credential-provider-web-identity': 3.972.15 - '@aws-sdk/nested-clients': 3.996.5 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/credential-provider-env': 3.972.16 + '@aws-sdk/credential-provider-http': 3.972.18 + '@aws-sdk/credential-provider-login': 3.972.16 + '@aws-sdk/credential-provider-process': 3.972.16 + '@aws-sdk/credential-provider-sso': 3.972.16 + '@aws-sdk/credential-provider-web-identity': 3.972.16 + '@aws-sdk/nested-clients': 3.996.6 + '@aws-sdk/types': 3.973.5 '@smithy/credential-provider-imds': 4.2.11 '@smithy/property-provider': 4.2.11 '@smithy/shared-ini-file-loader': 4.4.6 @@ -4362,11 +4362,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-login@3.972.15': + '@aws-sdk/credential-provider-login@3.972.16': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/nested-clients': 3.996.5 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/nested-clients': 3.996.6 + '@aws-sdk/types': 3.973.5 '@smithy/property-provider': 4.2.11 '@smithy/protocol-http': 5.3.11 '@smithy/shared-ini-file-loader': 4.4.6 @@ -4375,15 +4375,15 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.972.16': + '@aws-sdk/credential-provider-node@3.972.17': dependencies: - '@aws-sdk/credential-provider-env': 3.972.15 - '@aws-sdk/credential-provider-http': 3.972.17 - '@aws-sdk/credential-provider-ini': 3.972.15 - '@aws-sdk/credential-provider-process': 3.972.15 - '@aws-sdk/credential-provider-sso': 3.972.15 - '@aws-sdk/credential-provider-web-identity': 3.972.15 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/credential-provider-env': 3.972.16 + '@aws-sdk/credential-provider-http': 3.972.18 + '@aws-sdk/credential-provider-ini': 3.972.16 + '@aws-sdk/credential-provider-process': 3.972.16 + '@aws-sdk/credential-provider-sso': 3.972.16 + '@aws-sdk/credential-provider-web-identity': 3.972.16 + '@aws-sdk/types': 3.973.5 '@smithy/credential-provider-imds': 4.2.11 '@smithy/property-provider': 4.2.11 '@smithy/shared-ini-file-loader': 4.4.6 @@ -4392,21 +4392,21 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.972.15': + '@aws-sdk/credential-provider-process@3.972.16': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/types': 3.973.5 '@smithy/property-provider': 4.2.11 '@smithy/shared-ini-file-loader': 4.4.6 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.972.15': + '@aws-sdk/credential-provider-sso@3.972.16': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/nested-clients': 3.996.5 - '@aws-sdk/token-providers': 3.1002.0 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/nested-clients': 3.996.6 + '@aws-sdk/token-providers': 3.1003.0 + '@aws-sdk/types': 3.973.5 '@smithy/property-provider': 4.2.11 '@smithy/shared-ini-file-loader': 4.4.6 '@smithy/types': 4.13.0 @@ -4414,11 +4414,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.972.15': + '@aws-sdk/credential-provider-web-identity@3.972.16': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/nested-clients': 3.996.5 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/nested-clients': 3.996.6 + '@aws-sdk/types': 3.973.5 '@smithy/property-provider': 4.2.11 '@smithy/shared-ini-file-loader': 4.4.6 '@smithy/types': 4.13.0 @@ -4426,31 +4426,31 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/middleware-bucket-endpoint@3.972.6': + '@aws-sdk/middleware-bucket-endpoint@3.972.7': dependencies: - '@aws-sdk/types': 3.973.4 - '@aws-sdk/util-arn-parser': 3.972.2 + '@aws-sdk/types': 3.973.5 + '@aws-sdk/util-arn-parser': 3.972.3 '@smithy/node-config-provider': 4.3.11 '@smithy/protocol-http': 5.3.11 '@smithy/types': 4.13.0 '@smithy/util-config-provider': 4.2.2 tslib: 2.8.1 - '@aws-sdk/middleware-expect-continue@3.972.6': + '@aws-sdk/middleware-expect-continue@3.972.7': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@smithy/protocol-http': 5.3.11 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.973.3': + '@aws-sdk/middleware-flexible-checksums@3.973.4': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.973.17 - '@aws-sdk/crc64-nvme': 3.972.3 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/crc64-nvme': 3.972.4 + '@aws-sdk/types': 3.973.5 '@smithy/is-array-buffer': 4.2.2 '@smithy/node-config-provider': 4.3.11 '@smithy/protocol-http': 5.3.11 @@ -4460,38 +4460,38 @@ snapshots: '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.972.6': + '@aws-sdk/middleware-host-header@3.972.7': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@smithy/protocol-http': 5.3.11 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/middleware-location-constraint@3.972.6': + '@aws-sdk/middleware-location-constraint@3.972.7': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.972.6': + '@aws-sdk/middleware-logger@3.972.7': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.972.6': + '@aws-sdk/middleware-recursion-detection@3.972.7': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@aws/lambda-invoke-store': 0.2.3 '@smithy/protocol-http': 5.3.11 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.17': + '@aws-sdk/middleware-sdk-s3@3.972.18': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/types': 3.973.4 - '@aws-sdk/util-arn-parser': 3.972.2 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/types': 3.973.5 + '@aws-sdk/util-arn-parser': 3.972.3 '@smithy/core': 3.23.8 '@smithy/node-config-provider': 4.3.11 '@smithy/protocol-http': 5.3.11 @@ -4504,36 +4504,36 @@ snapshots: '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 - '@aws-sdk/middleware-ssec@3.972.6': + '@aws-sdk/middleware-ssec@3.972.7': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.972.17': + '@aws-sdk/middleware-user-agent@3.972.18': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/types': 3.973.4 - '@aws-sdk/util-endpoints': 3.996.3 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/types': 3.973.5 + '@aws-sdk/util-endpoints': 3.996.4 '@smithy/core': 3.23.8 '@smithy/protocol-http': 5.3.11 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.996.5': + '@aws-sdk/nested-clients@3.996.6': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.973.17 - '@aws-sdk/middleware-host-header': 3.972.6 - '@aws-sdk/middleware-logger': 3.972.6 - '@aws-sdk/middleware-recursion-detection': 3.972.6 - '@aws-sdk/middleware-user-agent': 3.972.17 - '@aws-sdk/region-config-resolver': 3.972.6 - '@aws-sdk/types': 3.973.4 - '@aws-sdk/util-endpoints': 3.996.3 - '@aws-sdk/util-user-agent-browser': 3.972.6 - '@aws-sdk/util-user-agent-node': 3.973.2 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/middleware-host-header': 3.972.7 + '@aws-sdk/middleware-logger': 3.972.7 + '@aws-sdk/middleware-recursion-detection': 3.972.7 + '@aws-sdk/middleware-user-agent': 3.972.18 + '@aws-sdk/region-config-resolver': 3.972.7 + '@aws-sdk/types': 3.973.5 + '@aws-sdk/util-endpoints': 3.996.4 + '@aws-sdk/util-user-agent-browser': 3.972.7 + '@aws-sdk/util-user-agent-node': 3.973.3 '@smithy/config-resolver': 4.4.10 '@smithy/core': 3.23.8 '@smithy/fetch-http-handler': 5.3.13 @@ -4563,28 +4563,28 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/region-config-resolver@3.972.6': + '@aws-sdk/region-config-resolver@3.972.7': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@smithy/config-resolver': 4.4.10 '@smithy/node-config-provider': 4.3.11 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.996.5': + '@aws-sdk/signature-v4-multi-region@3.996.6': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.972.17 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/middleware-sdk-s3': 3.972.18 + '@aws-sdk/types': 3.973.5 '@smithy/protocol-http': 5.3.11 '@smithy/signature-v4': 5.3.11 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1002.0': + '@aws-sdk/token-providers@3.1003.0': dependencies: - '@aws-sdk/core': 3.973.17 - '@aws-sdk/nested-clients': 3.996.5 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/core': 3.973.18 + '@aws-sdk/nested-clients': 3.996.6 + '@aws-sdk/types': 3.973.5 '@smithy/property-provider': 4.2.11 '@smithy/shared-ini-file-loader': 4.4.6 '@smithy/types': 4.13.0 @@ -4592,43 +4592,43 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/types@3.973.4': + '@aws-sdk/types@3.973.5': dependencies: '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/util-arn-parser@3.972.2': + '@aws-sdk/util-arn-parser@3.972.3': dependencies: tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.996.3': + '@aws-sdk/util-endpoints@3.996.4': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@smithy/types': 4.13.0 '@smithy/url-parser': 4.2.11 '@smithy/util-endpoints': 3.3.2 tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.965.4': + '@aws-sdk/util-locate-window@3.965.5': dependencies: tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.972.6': + '@aws-sdk/util-user-agent-browser@3.972.7': dependencies: - '@aws-sdk/types': 3.973.4 + '@aws-sdk/types': 3.973.5 '@smithy/types': 4.13.0 bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.973.2': + '@aws-sdk/util-user-agent-node@3.973.3': dependencies: - '@aws-sdk/middleware-user-agent': 3.972.17 - '@aws-sdk/types': 3.973.4 + '@aws-sdk/middleware-user-agent': 3.972.18 + '@aws-sdk/types': 3.973.5 '@smithy/node-config-provider': 4.3.11 '@smithy/types': 4.13.0 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.972.9': + '@aws-sdk/xml-builder@3.972.10': dependencies: '@smithy/types': 4.13.0 fast-xml-parser: 5.4.1 @@ -4860,14 +4860,14 @@ snapshots: dependencies: '@fortawesome/fontawesome-common-types': 7.2.0 - '@git.zone/tsbuild@4.2.6': + '@git.zone/tsbuild@4.3.0': dependencies: '@git.zone/tspublish': 1.11.2 '@push.rocks/early': 4.0.4 '@push.rocks/smartcli': 4.0.20 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile': 13.1.2 - '@push.rocks/smartfs': 1.3.3 + '@push.rocks/smartfs': 1.4.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 @@ -4915,7 +4915,7 @@ snapshots: '@push.rocks/smartcli': 4.0.20 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile': 13.1.2 - '@push.rocks/smartfs': 1.3.3 + '@push.rocks/smartfs': 1.4.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartnpm': 2.0.6 '@push.rocks/smartpath': 6.0.0 @@ -5036,7 +5036,7 @@ snapshots: '@inquirer/figures': 1.0.15 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.19.13 + '@types/node': 22.19.15 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -5523,7 +5523,7 @@ snapshots: '@push.rocks/smartbucket@3.3.10': dependencies: - '@aws-sdk/client-s3': 3.1002.0 + '@aws-sdk/client-s3': 3.1003.0 '@push.rocks/smartmime': 2.0.4 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 @@ -5743,9 +5743,10 @@ snapshots: dependencies: '@push.rocks/smartpath': 6.0.0 - '@push.rocks/smartfs@1.3.3': + '@push.rocks/smartfs@1.4.0': dependencies: '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartrust': 1.3.1 '@push.rocks/smartguard@3.1.0': dependencies: @@ -6992,18 +6993,18 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/buffer-json@2.0.3': {} '@types/clean-css@4.2.11': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 source-map: 0.6.1 '@types/connect@3.4.38': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/debug@4.1.12': dependencies: @@ -7011,7 +7012,7 @@ snapshots: '@types/express-serve-static-core@5.1.1': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -7024,17 +7025,17 @@ snapshots: '@types/from2@2.3.6': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/glob@8.1.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/hast@3.0.4': dependencies: @@ -7056,12 +7057,12 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/linkify-it@5.0.0': {} @@ -7084,26 +7085,26 @@ snapshots: '@types/mute-stream@0.0.4': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/node-fetch@2.6.13': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 form-data: 4.0.5 '@types/node-forge@1.3.14': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/node@18.19.130': dependencies: undici-types: 5.26.5 - '@types/node@22.19.13': + '@types/node@22.19.15': dependencies: undici-types: 6.21.0 - '@types/node@25.3.3': + '@types/node@25.3.5': dependencies: undici-types: 7.18.2 @@ -7121,22 +7122,22 @@ snapshots: '@types/send@1.2.1': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/symbol-tree@3.2.5': {} '@types/tar-stream@3.1.4': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/through2@2.0.41': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/trusted-types@2.0.7': {} @@ -7166,11 +7167,11 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/yauzl@2.10.3': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 optional: true '@ungap/structured-clone@1.3.0': {} diff --git a/test/test.apiclient.ts b/test/test.apiclient.ts new file mode 100644 index 0000000..40c2a5d --- /dev/null +++ b/test/test.apiclient.ts @@ -0,0 +1,376 @@ +import { tap, expect } from '@git.zone/tstest/tapbundle'; +import { + DcRouterApiClient, + Route, + RouteBuilder, + RouteManager, + Certificate, + CertificateManager, + ApiToken, + ApiTokenBuilder, + ApiTokenManager, + RemoteIngress, + RemoteIngressBuilder, + RemoteIngressManager, + Email, + EmailManager, + StatsManager, + ConfigManager, + LogManager, + RadiusManager, + RadiusClientManager, + RadiusVlanManager, + RadiusSessionManager, +} from '../ts_apiclient/index.js'; + +// ============================================================================= +// Instantiation & Structure +// ============================================================================= + +tap.test('DcRouterApiClient - should instantiate with baseUrl', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + expect(client).toBeTruthy(); + expect(client.baseUrl).toEqual('https://localhost:3000'); + expect(client.identity).toBeUndefined(); +}); + +tap.test('DcRouterApiClient - should strip trailing slashes from baseUrl', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000///' }); + expect(client.baseUrl).toEqual('https://localhost:3000'); +}); + +tap.test('DcRouterApiClient - should accept optional apiToken', async () => { + const client = new DcRouterApiClient({ + baseUrl: 'https://localhost:3000', + apiToken: 'dcr_test_token', + }); + expect(client.apiToken).toEqual('dcr_test_token'); +}); + +tap.test('DcRouterApiClient - should have all resource managers', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + expect(client.routes).toBeInstanceOf(RouteManager); + expect(client.certificates).toBeInstanceOf(CertificateManager); + expect(client.apiTokens).toBeInstanceOf(ApiTokenManager); + expect(client.remoteIngress).toBeInstanceOf(RemoteIngressManager); + expect(client.stats).toBeInstanceOf(StatsManager); + expect(client.config).toBeInstanceOf(ConfigManager); + expect(client.logs).toBeInstanceOf(LogManager); + expect(client.emails).toBeInstanceOf(EmailManager); + expect(client.radius).toBeInstanceOf(RadiusManager); +}); + +// ============================================================================= +// buildRequestPayload +// ============================================================================= + +tap.test('DcRouterApiClient - buildRequestPayload includes identity when set', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const identity = { + jwt: 'test-jwt', + userId: 'user1', + name: 'Admin', + expiresAt: Date.now() + 3600000, + }; + client.identity = identity; + + const payload = client.buildRequestPayload({ extra: 'data' }); + expect(payload.identity).toEqual(identity); + expect(payload.extra).toEqual('data'); +}); + +tap.test('DcRouterApiClient - buildRequestPayload includes apiToken when set', async () => { + const client = new DcRouterApiClient({ + baseUrl: 'https://localhost:3000', + apiToken: 'dcr_abc123', + }); + + const payload = client.buildRequestPayload(); + expect(payload.apiToken).toEqual('dcr_abc123'); +}); + +tap.test('DcRouterApiClient - buildRequestPayload with both identity and apiToken', async () => { + const client = new DcRouterApiClient({ + baseUrl: 'https://localhost:3000', + apiToken: 'dcr_abc123', + }); + client.identity = { + jwt: 'test-jwt', + userId: 'user1', + name: 'Admin', + expiresAt: Date.now() + 3600000, + }; + + const payload = client.buildRequestPayload({ foo: 'bar' }); + expect(payload.identity).toBeTruthy(); + expect(payload.apiToken).toEqual('dcr_abc123'); + expect(payload.foo).toEqual('bar'); +}); + +// ============================================================================= +// Route Builder +// ============================================================================= + +tap.test('RouteBuilder - should support fluent builder pattern', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const builder = client.routes.build(); + expect(builder).toBeInstanceOf(RouteBuilder); + + // Fluent methods return `this` (same reference) + const result = builder + .setName('test-route') + .setMatch({ ports: 443, domains: 'example.com' }) + .setAction({ type: 'forward', targets: [{ host: 'backend', port: 8080 }] }) + .setEnabled(true); + + expect(result === builder).toBeTrue(); +}); + +// ============================================================================= +// ApiToken Builder +// ============================================================================= + +tap.test('ApiTokenBuilder - should support fluent builder pattern', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const builder = client.apiTokens.build(); + expect(builder).toBeInstanceOf(ApiTokenBuilder); + + const result = builder + .setName('ci-token') + .setScopes(['routes:read', 'routes:write']) + .addScope('config:read') + .setExpiresInDays(30); + + expect(result === builder).toBeTrue(); +}); + +// ============================================================================= +// RemoteIngress Builder +// ============================================================================= + +tap.test('RemoteIngressBuilder - should support fluent builder pattern', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const builder = client.remoteIngress.build(); + expect(builder).toBeInstanceOf(RemoteIngressBuilder); + + const result = builder + .setName('edge-1') + .setListenPorts([80, 443]) + .setAutoDerivePorts(true) + .setTags(['production']); + + expect(result === builder).toBeTrue(); +}); + +// ============================================================================= +// Route resource class +// ============================================================================= + +tap.test('Route - should hydrate from IMergedRoute data', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const route = new Route(client, { + route: { + name: 'test-route', + match: { ports: 443, domains: 'example.com' }, + action: { type: 'forward', targets: [{ host: 'backend', port: 8080 }] }, + }, + source: 'programmatic', + enabled: true, + overridden: false, + storedRouteId: 'route-123', + createdAt: 1000, + updatedAt: 2000, + }); + + expect(route.name).toEqual('test-route'); + expect(route.source).toEqual('programmatic'); + expect(route.enabled).toEqual(true); + expect(route.overridden).toEqual(false); + expect(route.storedRouteId).toEqual('route-123'); + expect(route.routeConfig.match.ports).toEqual(443); +}); + +tap.test('Route - should throw on update/delete/toggle for hardcoded routes', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const route = new Route(client, { + route: { + name: 'hardcoded-route', + match: { ports: 80 }, + action: { type: 'forward', targets: [{ host: 'localhost', port: 8080 }] }, + }, + source: 'hardcoded', + enabled: true, + overridden: false, + // No storedRouteId for hardcoded routes + }); + + let updateError: Error | undefined; + try { + await route.update({ name: 'new-name' }); + } catch (e) { + updateError = e as Error; + } + expect(updateError).toBeTruthy(); + expect(updateError!.message).toInclude('hardcoded'); + + let deleteError: Error | undefined; + try { + await route.delete(); + } catch (e) { + deleteError = e as Error; + } + expect(deleteError).toBeTruthy(); + + let toggleError: Error | undefined; + try { + await route.toggle(false); + } catch (e) { + toggleError = e as Error; + } + expect(toggleError).toBeTruthy(); +}); + +// ============================================================================= +// Certificate resource class +// ============================================================================= + +tap.test('Certificate - should hydrate from ICertificateInfo data', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const cert = new Certificate(client, { + domain: 'example.com', + routeNames: ['main-route'], + status: 'valid', + source: 'acme', + tlsMode: 'terminate', + expiryDate: '2027-01-01T00:00:00Z', + issuer: "Let's Encrypt", + canReprovision: true, + }); + + expect(cert.domain).toEqual('example.com'); + expect(cert.status).toEqual('valid'); + expect(cert.source).toEqual('acme'); + expect(cert.canReprovision).toEqual(true); + expect(cert.routeNames.length).toEqual(1); +}); + +// ============================================================================= +// ApiToken resource class +// ============================================================================= + +tap.test('ApiToken - should hydrate from IApiTokenInfo data', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const token = new ApiToken( + client, + { + id: 'token-1', + name: 'ci-token', + scopes: ['routes:read', 'routes:write'], + createdAt: Date.now(), + expiresAt: null, + lastUsedAt: null, + enabled: true, + }, + 'dcr_secret_value', + ); + + expect(token.id).toEqual('token-1'); + expect(token.name).toEqual('ci-token'); + expect(token.scopes.length).toEqual(2); + expect(token.enabled).toEqual(true); + expect(token.tokenValue).toEqual('dcr_secret_value'); +}); + +// ============================================================================= +// RemoteIngress resource class +// ============================================================================= + +tap.test('RemoteIngress - should hydrate from IRemoteIngress data', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const edge = new RemoteIngress(client, { + id: 'edge-1', + name: 'test-edge', + secret: 'secret123', + listenPorts: [80, 443], + enabled: true, + autoDerivePorts: true, + tags: ['prod'], + createdAt: 1000, + updatedAt: 2000, + effectiveListenPorts: [80, 443, 8080], + manualPorts: [80, 443], + derivedPorts: [8080], + }); + + expect(edge.id).toEqual('edge-1'); + expect(edge.name).toEqual('test-edge'); + expect(edge.listenPorts.length).toEqual(2); + expect(edge.effectiveListenPorts!.length).toEqual(3); + expect(edge.autoDerivePorts).toEqual(true); +}); + +// ============================================================================= +// Email resource class +// ============================================================================= + +tap.test('Email - should hydrate from IEmail data', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + const email = new Email(client, { + id: 'email-1', + direction: 'inbound', + status: 'delivered', + from: 'sender@example.com', + to: 'recipient@example.com', + subject: 'Test email', + timestamp: '2026-03-06T00:00:00Z', + messageId: '', + size: '1234', + }); + + expect(email.id).toEqual('email-1'); + expect(email.direction).toEqual('inbound'); + expect(email.status).toEqual('delivered'); + expect(email.from).toEqual('sender@example.com'); + expect(email.subject).toEqual('Test email'); +}); + +// ============================================================================= +// RadiusManager structure +// ============================================================================= + +tap.test('RadiusManager - should have sub-managers', async () => { + const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' }); + expect(client.radius.clients).toBeInstanceOf(RadiusClientManager); + expect(client.radius.vlans).toBeInstanceOf(RadiusVlanManager); + expect(client.radius.sessions).toBeInstanceOf(RadiusSessionManager); +}); + +// ============================================================================= +// Exports verification +// ============================================================================= + +tap.test('Exports - all expected classes should be importable', async () => { + expect(DcRouterApiClient).toBeTruthy(); + expect(Route).toBeTruthy(); + expect(RouteBuilder).toBeTruthy(); + expect(RouteManager).toBeTruthy(); + expect(Certificate).toBeTruthy(); + expect(CertificateManager).toBeTruthy(); + expect(ApiToken).toBeTruthy(); + expect(ApiTokenBuilder).toBeTruthy(); + expect(ApiTokenManager).toBeTruthy(); + expect(RemoteIngress).toBeTruthy(); + expect(RemoteIngressBuilder).toBeTruthy(); + expect(RemoteIngressManager).toBeTruthy(); + expect(Email).toBeTruthy(); + expect(EmailManager).toBeTruthy(); + expect(StatsManager).toBeTruthy(); + expect(ConfigManager).toBeTruthy(); + expect(LogManager).toBeTruthy(); + expect(RadiusManager).toBeTruthy(); + expect(RadiusClientManager).toBeTruthy(); + expect(RadiusVlanManager).toBeTruthy(); + expect(RadiusSessionManager).toBeTruthy(); +}); + +export default tap.start(); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index bc20e56..8d05e2e 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.0.51', + version: '11.1.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts_apiclient/classes.apitoken.ts b/ts_apiclient/classes.apitoken.ts new file mode 100644 index 0000000..5b1840b --- /dev/null +++ b/ts_apiclient/classes.apitoken.ts @@ -0,0 +1,157 @@ +import * as interfaces from '../ts_interfaces/index.js'; +import type { DcRouterApiClient } from './classes.dcrouterapiclient.js'; + +export class ApiToken { + private clientRef: DcRouterApiClient; + + // Data from IApiTokenInfo + public id: string; + public name: string; + public scopes: interfaces.data.TApiTokenScope[]; + public createdAt: number; + public expiresAt: number | null; + public lastUsedAt: number | null; + public enabled: boolean; + + /** Only set on creation or roll. Not persisted on server side. */ + public tokenValue?: string; + + constructor(clientRef: DcRouterApiClient, data: interfaces.data.IApiTokenInfo, tokenValue?: string) { + this.clientRef = clientRef; + this.id = data.id; + this.name = data.name; + this.scopes = data.scopes; + this.createdAt = data.createdAt; + this.expiresAt = data.expiresAt; + this.lastUsedAt = data.lastUsedAt; + this.enabled = data.enabled; + this.tokenValue = tokenValue; + } + + public async revoke(): Promise { + const response = await this.clientRef.request( + 'revokeApiToken', + this.clientRef.buildRequestPayload({ id: this.id }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to revoke token'); + } + } + + public async roll(): Promise { + const response = await this.clientRef.request( + 'rollApiToken', + this.clientRef.buildRequestPayload({ id: this.id }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to roll token'); + } + this.tokenValue = response.tokenValue; + return response.tokenValue!; + } + + public async toggle(enabled: boolean): Promise { + const response = await this.clientRef.request( + 'toggleApiToken', + this.clientRef.buildRequestPayload({ id: this.id, enabled }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to toggle token'); + } + this.enabled = enabled; + } +} + +export class ApiTokenBuilder { + private clientRef: DcRouterApiClient; + private tokenName: string = ''; + private tokenScopes: interfaces.data.TApiTokenScope[] = []; + private tokenExpiresInDays?: number | null; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public setName(name: string): this { + this.tokenName = name; + return this; + } + + public setScopes(scopes: interfaces.data.TApiTokenScope[]): this { + this.tokenScopes = scopes; + return this; + } + + public addScope(scope: interfaces.data.TApiTokenScope): this { + if (!this.tokenScopes.includes(scope)) { + this.tokenScopes.push(scope); + } + return this; + } + + public setExpiresInDays(days: number | null): this { + this.tokenExpiresInDays = days; + return this; + } + + public async save(): Promise { + const response = await this.clientRef.request( + 'createApiToken', + this.clientRef.buildRequestPayload({ + name: this.tokenName, + scopes: this.tokenScopes, + expiresInDays: this.tokenExpiresInDays, + }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to create API token'); + } + return new ApiToken( + this.clientRef, + { + id: response.tokenId!, + name: this.tokenName, + scopes: this.tokenScopes, + createdAt: Date.now(), + expiresAt: this.tokenExpiresInDays + ? Date.now() + this.tokenExpiresInDays * 24 * 60 * 60 * 1000 + : null, + lastUsedAt: null, + enabled: true, + }, + response.tokenValue, + ); + } +} + +export class ApiTokenManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async list(): Promise { + const response = await this.clientRef.request( + 'listApiTokens', + this.clientRef.buildRequestPayload() as any, + ); + return response.tokens.map((t) => new ApiToken(this.clientRef, t)); + } + + public async create(options: { + name: string; + scopes: interfaces.data.TApiTokenScope[]; + expiresInDays?: number | null; + }): Promise { + return this.build() + .setName(options.name) + .setScopes(options.scopes) + .setExpiresInDays(options.expiresInDays ?? null) + .save(); + } + + public build(): ApiTokenBuilder { + return new ApiTokenBuilder(this.clientRef); + } +} diff --git a/ts_apiclient/classes.certificate.ts b/ts_apiclient/classes.certificate.ts new file mode 100644 index 0000000..4887b4a --- /dev/null +++ b/ts_apiclient/classes.certificate.ts @@ -0,0 +1,123 @@ +import * as interfaces from '../ts_interfaces/index.js'; +import type { DcRouterApiClient } from './classes.dcrouterapiclient.js'; + +export class Certificate { + private clientRef: DcRouterApiClient; + + // Data from ICertificateInfo + public domain: string; + public routeNames: string[]; + public status: interfaces.requests.TCertificateStatus; + public source: interfaces.requests.TCertificateSource; + public tlsMode: 'terminate' | 'terminate-and-reencrypt' | 'passthrough'; + public expiryDate?: string; + public issuer?: string; + public issuedAt?: string; + public error?: string; + public canReprovision: boolean; + public backoffInfo?: { + failures: number; + retryAfter?: string; + lastError?: string; + }; + + constructor(clientRef: DcRouterApiClient, data: interfaces.requests.ICertificateInfo) { + this.clientRef = clientRef; + this.domain = data.domain; + this.routeNames = data.routeNames; + this.status = data.status; + this.source = data.source; + this.tlsMode = data.tlsMode; + this.expiryDate = data.expiryDate; + this.issuer = data.issuer; + this.issuedAt = data.issuedAt; + this.error = data.error; + this.canReprovision = data.canReprovision; + this.backoffInfo = data.backoffInfo; + } + + public async reprovision(): Promise { + const response = await this.clientRef.request( + 'reprovisionCertificateDomain', + this.clientRef.buildRequestPayload({ domain: this.domain }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to reprovision certificate'); + } + } + + public async delete(): Promise { + const response = await this.clientRef.request( + 'deleteCertificate', + this.clientRef.buildRequestPayload({ domain: this.domain }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to delete certificate'); + } + } + + public async export(): Promise<{ + id: string; + domainName: string; + created: number; + validUntil: number; + privateKey: string; + publicKey: string; + csr: string; + } | undefined> { + const response = await this.clientRef.request( + 'exportCertificate', + this.clientRef.buildRequestPayload({ domain: this.domain }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to export certificate'); + } + return response.cert; + } +} + +export interface ICertificateSummary { + total: number; + valid: number; + expiring: number; + expired: number; + failed: number; + unknown: number; +} + +export class CertificateManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async list(): Promise<{ certificates: Certificate[]; summary: ICertificateSummary }> { + const response = await this.clientRef.request( + 'getCertificateOverview', + this.clientRef.buildRequestPayload() as any, + ); + return { + certificates: response.certificates.map((c) => new Certificate(this.clientRef, c)), + summary: response.summary, + }; + } + + public async import(cert: { + id: string; + domainName: string; + created: number; + validUntil: number; + privateKey: string; + publicKey: string; + csr: string; + }): Promise { + const response = await this.clientRef.request( + 'importCertificate', + this.clientRef.buildRequestPayload({ cert }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to import certificate'); + } + } +} diff --git a/ts_apiclient/classes.config.ts b/ts_apiclient/classes.config.ts new file mode 100644 index 0000000..67c1eee --- /dev/null +++ b/ts_apiclient/classes.config.ts @@ -0,0 +1,17 @@ +import * as interfaces from '../ts_interfaces/index.js'; +import type { DcRouterApiClient } from './classes.dcrouterapiclient.js'; + +export class ConfigManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async get(section?: string): Promise { + return this.clientRef.request( + 'getConfiguration', + this.clientRef.buildRequestPayload({ section }) as any, + ); + } +} diff --git a/ts_apiclient/classes.dcrouterapiclient.ts b/ts_apiclient/classes.dcrouterapiclient.ts new file mode 100644 index 0000000..16e4574 --- /dev/null +++ b/ts_apiclient/classes.dcrouterapiclient.ts @@ -0,0 +1,112 @@ +import * as plugins from './plugins.js'; +import * as interfaces from '../ts_interfaces/index.js'; + +import { RouteManager } from './classes.route.js'; +import { CertificateManager } from './classes.certificate.js'; +import { ApiTokenManager } from './classes.apitoken.js'; +import { RemoteIngressManager } from './classes.remoteingress.js'; +import { StatsManager } from './classes.stats.js'; +import { ConfigManager } from './classes.config.js'; +import { LogManager } from './classes.logs.js'; +import { EmailManager } from './classes.email.js'; +import { RadiusManager } from './classes.radius.js'; + +export interface IDcRouterApiClientOptions { + baseUrl: string; + apiToken?: string; +} + +export class DcRouterApiClient { + public baseUrl: string; + public apiToken?: string; + public identity?: interfaces.data.IIdentity; + + // Resource managers + public routes: RouteManager; + public certificates: CertificateManager; + public apiTokens: ApiTokenManager; + public remoteIngress: RemoteIngressManager; + public stats: StatsManager; + public config: ConfigManager; + public logs: LogManager; + public emails: EmailManager; + public radius: RadiusManager; + + constructor(options: IDcRouterApiClientOptions) { + this.baseUrl = options.baseUrl.replace(/\/+$/, ''); + this.apiToken = options.apiToken; + + this.routes = new RouteManager(this); + this.certificates = new CertificateManager(this); + this.apiTokens = new ApiTokenManager(this); + this.remoteIngress = new RemoteIngressManager(this); + this.stats = new StatsManager(this); + this.config = new ConfigManager(this); + this.logs = new LogManager(this); + this.emails = new EmailManager(this); + this.radius = new RadiusManager(this); + } + + // ===================== + // Auth + // ===================== + + public async login(username: string, password: string): Promise { + const response = await this.request( + 'adminLoginWithUsernameAndPassword', + { username, password }, + ); + if (response.identity) { + this.identity = response.identity; + } + return response.identity!; + } + + public async logout(): Promise { + await this.request( + 'adminLogout', + { identity: this.identity! }, + ); + this.identity = undefined; + } + + public async verifyIdentity(): Promise<{ valid: boolean; identity?: interfaces.data.IIdentity }> { + const response = await this.request( + 'verifyIdentity', + { identity: this.identity! }, + ); + if (response.identity) { + this.identity = response.identity; + } + return response; + } + + // ===================== + // Internal request helper + // ===================== + + public async request( + method: string, + requestData: T['request'], + ): Promise { + const typedRequest = new plugins.typedrequest.TypedRequest( + `${this.baseUrl}/typedrequest`, + method, + ); + return typedRequest.fire(requestData); + } + + /** + * Build a request payload with identity and optional API token auto-injected. + */ + public buildRequestPayload(extra: Record = {}): Record { + const payload: Record = { ...extra }; + if (this.identity) { + payload.identity = this.identity; + } + if (this.apiToken) { + payload.apiToken = this.apiToken; + } + return payload; + } +} diff --git a/ts_apiclient/classes.email.ts b/ts_apiclient/classes.email.ts new file mode 100644 index 0000000..6228604 --- /dev/null +++ b/ts_apiclient/classes.email.ts @@ -0,0 +1,77 @@ +import * as interfaces from '../ts_interfaces/index.js'; +import type { DcRouterApiClient } from './classes.dcrouterapiclient.js'; + +export class Email { + private clientRef: DcRouterApiClient; + + // Data from IEmail + public id: string; + public direction: interfaces.requests.TEmailDirection; + public status: interfaces.requests.TEmailStatus; + public from: string; + public to: string; + public subject: string; + public timestamp: string; + public messageId: string; + public size: string; + + constructor(clientRef: DcRouterApiClient, data: interfaces.requests.IEmail) { + this.clientRef = clientRef; + this.id = data.id; + this.direction = data.direction; + this.status = data.status; + this.from = data.from; + this.to = data.to; + this.subject = data.subject; + this.timestamp = data.timestamp; + this.messageId = data.messageId; + this.size = data.size; + } + + public async getDetail(): Promise { + const response = await this.clientRef.request( + 'getEmailDetail', + this.clientRef.buildRequestPayload({ emailId: this.id }) as any, + ); + return response.email; + } + + public async resend(): Promise<{ success: boolean; newQueueId?: string }> { + const response = await this.clientRef.request( + 'resendEmail', + this.clientRef.buildRequestPayload({ emailId: this.id }) as any, + ); + return response; + } +} + +export class EmailManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async list(): Promise { + const response = await this.clientRef.request( + 'getAllEmails', + this.clientRef.buildRequestPayload() as any, + ); + return response.emails.map((e) => new Email(this.clientRef, e)); + } + + public async getDetail(emailId: string): Promise { + const response = await this.clientRef.request( + 'getEmailDetail', + this.clientRef.buildRequestPayload({ emailId }) as any, + ); + return response.email; + } + + public async resend(emailId: string): Promise<{ success: boolean; newQueueId?: string }> { + return this.clientRef.request( + 'resendEmail', + this.clientRef.buildRequestPayload({ emailId }) as any, + ); + } +} diff --git a/ts_apiclient/classes.logs.ts b/ts_apiclient/classes.logs.ts new file mode 100644 index 0000000..01af8da --- /dev/null +++ b/ts_apiclient/classes.logs.ts @@ -0,0 +1,37 @@ +import * as interfaces from '../ts_interfaces/index.js'; +import type { DcRouterApiClient } from './classes.dcrouterapiclient.js'; + +export class LogManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async getRecent(options?: { + level?: 'debug' | 'info' | 'warn' | 'error'; + category?: 'smtp' | 'dns' | 'security' | 'system' | 'email'; + limit?: number; + offset?: number; + search?: string; + timeRange?: string; + }): Promise { + return this.clientRef.request( + 'getRecentLogs', + this.clientRef.buildRequestPayload(options || {}) as any, + ); + } + + public async getStream(options?: { + follow?: boolean; + filters?: { + level?: string[]; + category?: string[]; + }; + }): Promise { + return this.clientRef.request( + 'getLogStream', + this.clientRef.buildRequestPayload(options || {}) as any, + ); + } +} diff --git a/ts_apiclient/classes.radius.ts b/ts_apiclient/classes.radius.ts new file mode 100644 index 0000000..52e77d9 --- /dev/null +++ b/ts_apiclient/classes.radius.ts @@ -0,0 +1,180 @@ +import * as interfaces from '../ts_interfaces/index.js'; +import type { DcRouterApiClient } from './classes.dcrouterapiclient.js'; + +// ===================== +// Sub-managers +// ===================== + +export class RadiusClientManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async list(): Promise> { + const response = await this.clientRef.request( + 'getRadiusClients', + this.clientRef.buildRequestPayload() as any, + ); + return response.clients; + } + + public async set(client: { + name: string; + ipRange: string; + secret: string; + description?: string; + enabled: boolean; + }): Promise { + const response = await this.clientRef.request( + 'setRadiusClient', + this.clientRef.buildRequestPayload({ client }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to set RADIUS client'); + } + } + + public async remove(name: string): Promise { + const response = await this.clientRef.request( + 'removeRadiusClient', + this.clientRef.buildRequestPayload({ name }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to remove RADIUS client'); + } + } +} + +export class RadiusVlanManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async list(): Promise { + return this.clientRef.request( + 'getVlanMappings', + this.clientRef.buildRequestPayload() as any, + ); + } + + public async set(mapping: { + mac: string; + vlan: number; + description?: string; + enabled: boolean; + }): Promise { + const response = await this.clientRef.request( + 'setVlanMapping', + this.clientRef.buildRequestPayload({ mapping }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to set VLAN mapping'); + } + } + + public async remove(mac: string): Promise { + const response = await this.clientRef.request( + 'removeVlanMapping', + this.clientRef.buildRequestPayload({ mac }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to remove VLAN mapping'); + } + } + + public async updateConfig(options: { + defaultVlan?: number; + allowUnknownMacs?: boolean; + }): Promise<{ defaultVlan: number; allowUnknownMacs: boolean }> { + const response = await this.clientRef.request( + 'updateVlanConfig', + this.clientRef.buildRequestPayload(options) as any, + ); + if (!response.success) { + throw new Error('Failed to update VLAN config'); + } + return response.config; + } + + public async testAssignment(mac: string): Promise { + return this.clientRef.request( + 'testVlanAssignment', + this.clientRef.buildRequestPayload({ mac }) as any, + ); + } +} + +export class RadiusSessionManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async list(filter?: { + username?: string; + nasIpAddress?: string; + vlanId?: number; + }): Promise { + return this.clientRef.request( + 'getRadiusSessions', + this.clientRef.buildRequestPayload({ filter }) as any, + ); + } + + public async disconnect(sessionId: string, reason?: string): Promise { + const response = await this.clientRef.request( + 'disconnectRadiusSession', + this.clientRef.buildRequestPayload({ sessionId, reason }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to disconnect session'); + } + } +} + +// ===================== +// Main RADIUS Manager +// ===================== + +export class RadiusManager { + private clientRef: DcRouterApiClient; + + public clients: RadiusClientManager; + public vlans: RadiusVlanManager; + public sessions: RadiusSessionManager; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + this.clients = new RadiusClientManager(clientRef); + this.vlans = new RadiusVlanManager(clientRef); + this.sessions = new RadiusSessionManager(clientRef); + } + + public async getAccountingSummary( + startTime: number, + endTime: number, + ): Promise { + const response = await this.clientRef.request( + 'getRadiusAccountingSummary', + this.clientRef.buildRequestPayload({ startTime, endTime }) as any, + ); + return response.summary; + } + + public async getStatistics(): Promise { + return this.clientRef.request( + 'getRadiusStatistics', + this.clientRef.buildRequestPayload() as any, + ); + } +} diff --git a/ts_apiclient/classes.remoteingress.ts b/ts_apiclient/classes.remoteingress.ts new file mode 100644 index 0000000..0a43221 --- /dev/null +++ b/ts_apiclient/classes.remoteingress.ts @@ -0,0 +1,185 @@ +import * as interfaces from '../ts_interfaces/index.js'; +import type { DcRouterApiClient } from './classes.dcrouterapiclient.js'; + +export class RemoteIngress { + private clientRef: DcRouterApiClient; + + // Data from IRemoteIngress + public id: string; + public name: string; + public secret: string; + public listenPorts: number[]; + public enabled: boolean; + public autoDerivePorts: boolean; + public tags?: string[]; + public createdAt: number; + public updatedAt: number; + public effectiveListenPorts?: number[]; + public manualPorts?: number[]; + public derivedPorts?: number[]; + + constructor(clientRef: DcRouterApiClient, data: interfaces.data.IRemoteIngress) { + this.clientRef = clientRef; + this.id = data.id; + this.name = data.name; + this.secret = data.secret; + this.listenPorts = data.listenPorts; + this.enabled = data.enabled; + this.autoDerivePorts = data.autoDerivePorts; + this.tags = data.tags; + this.createdAt = data.createdAt; + this.updatedAt = data.updatedAt; + this.effectiveListenPorts = data.effectiveListenPorts; + this.manualPorts = data.manualPorts; + this.derivedPorts = data.derivedPorts; + } + + public async update(changes: { + name?: string; + listenPorts?: number[]; + autoDerivePorts?: boolean; + enabled?: boolean; + tags?: string[]; + }): Promise { + const response = await this.clientRef.request( + 'updateRemoteIngress', + this.clientRef.buildRequestPayload({ id: this.id, ...changes }) as any, + ); + if (!response.success) { + throw new Error('Failed to update remote ingress'); + } + // Update local state from response + const edge = response.edge; + this.name = edge.name; + this.listenPorts = edge.listenPorts; + this.enabled = edge.enabled; + this.autoDerivePorts = edge.autoDerivePorts; + this.tags = edge.tags; + this.updatedAt = edge.updatedAt; + this.effectiveListenPorts = edge.effectiveListenPorts; + this.manualPorts = edge.manualPorts; + this.derivedPorts = edge.derivedPorts; + } + + public async delete(): Promise { + const response = await this.clientRef.request( + 'deleteRemoteIngress', + this.clientRef.buildRequestPayload({ id: this.id }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to delete remote ingress'); + } + } + + public async regenerateSecret(): Promise { + const response = await this.clientRef.request( + 'regenerateRemoteIngressSecret', + this.clientRef.buildRequestPayload({ id: this.id }) as any, + ); + if (!response.success) { + throw new Error('Failed to regenerate secret'); + } + this.secret = response.secret; + return response.secret; + } + + public async getConnectionToken(hubHost?: string): Promise { + const response = await this.clientRef.request( + 'getRemoteIngressConnectionToken', + this.clientRef.buildRequestPayload({ edgeId: this.id, hubHost }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to get connection token'); + } + return response.token; + } +} + +export class RemoteIngressBuilder { + private clientRef: DcRouterApiClient; + private edgeName: string = ''; + private edgeListenPorts?: number[]; + private edgeAutoDerivePorts?: boolean; + private edgeTags?: string[]; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public setName(name: string): this { + this.edgeName = name; + return this; + } + + public setListenPorts(ports: number[]): this { + this.edgeListenPorts = ports; + return this; + } + + public setAutoDerivePorts(auto: boolean): this { + this.edgeAutoDerivePorts = auto; + return this; + } + + public setTags(tags: string[]): this { + this.edgeTags = tags; + return this; + } + + public async save(): Promise { + const response = await this.clientRef.request( + 'createRemoteIngress', + this.clientRef.buildRequestPayload({ + name: this.edgeName, + listenPorts: this.edgeListenPorts, + autoDerivePorts: this.edgeAutoDerivePorts, + tags: this.edgeTags, + }) as any, + ); + if (!response.success) { + throw new Error('Failed to create remote ingress'); + } + return new RemoteIngress(this.clientRef, response.edge); + } +} + +export class RemoteIngressManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async list(): Promise { + const response = await this.clientRef.request( + 'getRemoteIngresses', + this.clientRef.buildRequestPayload() as any, + ); + return response.edges.map((e) => new RemoteIngress(this.clientRef, e)); + } + + public async getStatuses(): Promise { + const response = await this.clientRef.request( + 'getRemoteIngressStatus', + this.clientRef.buildRequestPayload() as any, + ); + return response.statuses; + } + + public async create(options: { + name: string; + listenPorts?: number[]; + autoDerivePorts?: boolean; + tags?: string[]; + }): Promise { + const builder = this.build().setName(options.name); + if (options.listenPorts) builder.setListenPorts(options.listenPorts); + if (options.autoDerivePorts !== undefined) builder.setAutoDerivePorts(options.autoDerivePorts); + if (options.tags) builder.setTags(options.tags); + return builder.save(); + } + + public build(): RemoteIngressBuilder { + return new RemoteIngressBuilder(this.clientRef); + } +} diff --git a/ts_apiclient/classes.route.ts b/ts_apiclient/classes.route.ts new file mode 100644 index 0000000..ca7ff57 --- /dev/null +++ b/ts_apiclient/classes.route.ts @@ -0,0 +1,203 @@ +import * as interfaces from '../ts_interfaces/index.js'; +import type { IRouteConfig } from '@push.rocks/smartproxy'; +import type { DcRouterApiClient } from './classes.dcrouterapiclient.js'; + +export class Route { + private clientRef: DcRouterApiClient; + + // Data from IMergedRoute + public routeConfig: IRouteConfig; + public source: 'hardcoded' | 'programmatic'; + public enabled: boolean; + public overridden: boolean; + public storedRouteId?: string; + public createdAt?: number; + public updatedAt?: number; + + // Convenience accessors + public get name(): string { + return this.routeConfig.name || ''; + } + + constructor(clientRef: DcRouterApiClient, data: interfaces.data.IMergedRoute) { + this.clientRef = clientRef; + this.routeConfig = data.route; + this.source = data.source; + this.enabled = data.enabled; + this.overridden = data.overridden; + this.storedRouteId = data.storedRouteId; + this.createdAt = data.createdAt; + this.updatedAt = data.updatedAt; + } + + public async update(changes: Partial): Promise { + if (!this.storedRouteId) { + throw new Error('Cannot update a hardcoded route. Use setOverride() instead.'); + } + const response = await this.clientRef.request( + 'updateRoute', + this.clientRef.buildRequestPayload({ id: this.storedRouteId, route: changes }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to update route'); + } + } + + public async delete(): Promise { + if (!this.storedRouteId) { + throw new Error('Cannot delete a hardcoded route. Use setOverride() instead.'); + } + const response = await this.clientRef.request( + 'deleteRoute', + this.clientRef.buildRequestPayload({ id: this.storedRouteId }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to delete route'); + } + } + + public async toggle(enabled: boolean): Promise { + if (!this.storedRouteId) { + throw new Error('Cannot toggle a hardcoded route. Use setOverride() instead.'); + } + const response = await this.clientRef.request( + 'toggleRoute', + this.clientRef.buildRequestPayload({ id: this.storedRouteId, enabled }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to toggle route'); + } + this.enabled = enabled; + } + + public async setOverride(enabled: boolean): Promise { + const response = await this.clientRef.request( + 'setRouteOverride', + this.clientRef.buildRequestPayload({ routeName: this.name, enabled }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to set route override'); + } + this.overridden = true; + this.enabled = enabled; + } + + public async removeOverride(): Promise { + const response = await this.clientRef.request( + 'removeRouteOverride', + this.clientRef.buildRequestPayload({ routeName: this.name }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to remove route override'); + } + this.overridden = false; + } +} + +export class RouteBuilder { + private clientRef: DcRouterApiClient; + private routeConfig: Partial = {}; + private isEnabled: boolean = true; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public setName(name: string): this { + this.routeConfig.name = name; + return this; + } + + public setMatch(match: IRouteConfig['match']): this { + this.routeConfig.match = match; + return this; + } + + public setAction(action: IRouteConfig['action']): this { + this.routeConfig.action = action; + return this; + } + + public setTls(tls: IRouteConfig['action']['tls']): this { + if (!this.routeConfig.action) { + this.routeConfig.action = { type: 'forward' } as IRouteConfig['action']; + } + this.routeConfig.action!.tls = tls; + return this; + } + + public setEnabled(enabled: boolean): this { + this.isEnabled = enabled; + return this; + } + + public async save(): Promise { + const response = await this.clientRef.request( + 'createRoute', + this.clientRef.buildRequestPayload({ + route: this.routeConfig as IRouteConfig, + enabled: this.isEnabled, + }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to create route'); + } + + // Return a Route instance by re-fetching the list + // The created route is programmatic, so we find it by storedRouteId + const { routes } = await new RouteManager(this.clientRef).list(); + const created = routes.find((r) => r.storedRouteId === response.storedRouteId); + if (created) { + return created; + } + + // Fallback: construct from known data + return new Route(this.clientRef, { + route: this.routeConfig as IRouteConfig, + source: 'programmatic', + enabled: this.isEnabled, + overridden: false, + storedRouteId: response.storedRouteId, + }); + } +} + +export class RouteManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async list(): Promise<{ routes: Route[]; warnings: interfaces.data.IRouteWarning[] }> { + const response = await this.clientRef.request( + 'getMergedRoutes', + this.clientRef.buildRequestPayload() as any, + ); + return { + routes: response.routes.map((r) => new Route(this.clientRef, r)), + warnings: response.warnings, + }; + } + + public async create(routeConfig: IRouteConfig, enabled?: boolean): Promise { + const response = await this.clientRef.request( + 'createRoute', + this.clientRef.buildRequestPayload({ route: routeConfig, enabled: enabled ?? true }) as any, + ); + if (!response.success) { + throw new Error(response.message || 'Failed to create route'); + } + return new Route(this.clientRef, { + route: routeConfig, + source: 'programmatic', + enabled: enabled ?? true, + overridden: false, + storedRouteId: response.storedRouteId, + }); + } + + public build(): RouteBuilder { + return new RouteBuilder(this.clientRef); + } +} diff --git a/ts_apiclient/classes.stats.ts b/ts_apiclient/classes.stats.ts new file mode 100644 index 0000000..de18a1b --- /dev/null +++ b/ts_apiclient/classes.stats.ts @@ -0,0 +1,111 @@ +import * as interfaces from '../ts_interfaces/index.js'; +import type { DcRouterApiClient } from './classes.dcrouterapiclient.js'; + +type TTimeRange = '1h' | '6h' | '24h' | '7d' | '30d'; + +export class StatsManager { + private clientRef: DcRouterApiClient; + + constructor(clientRef: DcRouterApiClient) { + this.clientRef = clientRef; + } + + public async getServer(options?: { + timeRange?: TTimeRange; + includeHistory?: boolean; + }): Promise { + return this.clientRef.request( + 'getServerStatistics', + this.clientRef.buildRequestPayload(options || {}) as any, + ); + } + + public async getEmail(options?: { + timeRange?: TTimeRange; + domain?: string; + includeDetails?: boolean; + }): Promise { + return this.clientRef.request( + 'getEmailStatistics', + this.clientRef.buildRequestPayload(options || {}) as any, + ); + } + + public async getDns(options?: { + timeRange?: TTimeRange; + domain?: string; + includeQueryTypes?: boolean; + }): Promise { + return this.clientRef.request( + 'getDnsStatistics', + this.clientRef.buildRequestPayload(options || {}) as any, + ); + } + + public async getRateLimits(options?: { + domain?: string; + ip?: string; + includeBlocked?: boolean; + }): Promise { + return this.clientRef.request( + 'getRateLimitStatus', + this.clientRef.buildRequestPayload(options || {}) as any, + ); + } + + public async getSecurity(options?: { + timeRange?: TTimeRange; + includeDetails?: boolean; + }): Promise { + return this.clientRef.request( + 'getSecurityMetrics', + this.clientRef.buildRequestPayload(options || {}) as any, + ); + } + + public async getConnections(options?: { + protocol?: 'smtp' | 'smtps' | 'http' | 'https'; + state?: string; + }): Promise { + return this.clientRef.request( + 'getActiveConnections', + this.clientRef.buildRequestPayload(options || {}) as any, + ); + } + + public async getQueues(options?: { + queueName?: string; + }): Promise { + return this.clientRef.request( + 'getQueueStatus', + this.clientRef.buildRequestPayload(options || {}) as any, + ); + } + + public async getHealth(detailed?: boolean): Promise { + return this.clientRef.request( + 'getHealthStatus', + this.clientRef.buildRequestPayload({ detailed }) as any, + ); + } + + public async getNetwork(): Promise { + return this.clientRef.request( + 'getNetworkStats', + this.clientRef.buildRequestPayload() as any, + ); + } + + public async getCombined(sections?: { + server?: boolean; + email?: boolean; + dns?: boolean; + security?: boolean; + network?: boolean; + }): Promise { + return this.clientRef.request( + 'getCombinedMetrics', + this.clientRef.buildRequestPayload({ sections }) as any, + ); + } +} diff --git a/ts_apiclient/index.ts b/ts_apiclient/index.ts new file mode 100644 index 0000000..b22d5e2 --- /dev/null +++ b/ts_apiclient/index.ts @@ -0,0 +1,15 @@ +// Main client +export { DcRouterApiClient, type IDcRouterApiClientOptions } from './classes.dcrouterapiclient.js'; + +// Resource classes +export { Route, RouteBuilder, RouteManager } from './classes.route.js'; +export { Certificate, CertificateManager, type ICertificateSummary } from './classes.certificate.js'; +export { ApiToken, ApiTokenBuilder, ApiTokenManager } from './classes.apitoken.js'; +export { RemoteIngress, RemoteIngressBuilder, RemoteIngressManager } from './classes.remoteingress.js'; +export { Email, EmailManager } from './classes.email.js'; + +// Read-only managers +export { StatsManager } from './classes.stats.js'; +export { ConfigManager } from './classes.config.js'; +export { LogManager } from './classes.logs.js'; +export { RadiusManager, RadiusClientManager, RadiusVlanManager, RadiusSessionManager } from './classes.radius.js'; diff --git a/ts_apiclient/plugins.ts b/ts_apiclient/plugins.ts new file mode 100644 index 0000000..f91ee55 --- /dev/null +++ b/ts_apiclient/plugins.ts @@ -0,0 +1,8 @@ +// @api.global scope +import * as typedrequest from '@api.global/typedrequest'; +import * as typedrequestInterfaces from '@api.global/typedrequest-interfaces'; + +export { + typedrequest, + typedrequestInterfaces, +}; diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index bc20e56..8d05e2e 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.0.51', + version: '11.1.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' }