diff --git a/cspell.words.txt b/cspell.words.txt index 1830cac..bcd2c0a 100644 --- a/cspell.words.txt +++ b/cspell.words.txt @@ -11,4 +11,5 @@ bytea pg_dumpall PGPASSWORD psql -esnext \ No newline at end of file +esnext +SEPA \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index eabfeed..81a3352 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,15 +3,7 @@ import { configs } from 'eslint-config-service-soft'; /** @type {import('eslint').Linter.Config} */ export default [ ...configs, - { - files: ['**/*.ts'], - languageOptions: { - parserOptions: { - project: ['tsconfig.eslint.json'] - } - } - }, - { ignores: ['tsconfig.json', 'tsup.config.ts', 'sandbox', 'docs', 'src/di/default/temp', '**/__testing__/file-output/**'] }, + { ignores: ['sandbox', 'docs', 'src/di/default/temp', '**/__testing__/file-output/**'] }, { files: ['**/__testing__/**/*.ts'], rules: { diff --git a/jest.config.mjs b/jest.config.mjs index 89cc75e..3993075 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -4,7 +4,7 @@ const config = { testEnvironment: 'node', rootDir: 'src', transform: { - '^.+.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.eslint.json' }] + '^.+.tsx?$': ['ts-jest'] }, setupFilesAfterEnv: ['/jest.setup.ts'], bail: false, diff --git a/package-lock.json b/package-lock.json index 3f80fed..af54edf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zibri", - "version": "2.1.7", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zibri", - "version": "2.1.7", + "version": "2.2.0", "license": "MIT", "dependencies": { "@fastify/busboy": "^3.2.0", @@ -243,6 +243,7 @@ "integrity": "sha512-CVskZnF38IIxVVlKWi1VCz7YH/gHMJu2IY9bD1AVoBBGIe0xA4FRXJkW2Y+EDs9vQqZTkZZljhK5gL65Ro1PeQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@angular-eslint/bundled-angular-compiler": "20.7.0", "eslint-scope": "^9.0.0" @@ -420,580 +421,631 @@ } }, "node_modules/@aws-sdk/client-sesv2": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.948.0.tgz", - "integrity": "sha512-7Sl8bRFFLAEQdlvTlaSNFlUHjD+B3N+gbhpS+vH/IlETSmn3fMm4b0Bvve8CWs8jUCctx8nDwXh+0lOWgNDQXw==", + "version": "3.987.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.987.0.tgz", + "integrity": "sha512-gKKuPp9Q1hyV1unezoYBXu+HLtVPjzYM6z7No0MYjfTPbCImO6Np0e/hL0vPFkcNvBfx0RixAN1n1p3NirFl5A==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.947.0", - "@aws-sdk/credential-provider-node": "3.948.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.948.0", - "@aws-sdk/middleware-user-agent": "3.947.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/signature-v4-multi-region": "3.947.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.947.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.7", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.14", - "@smithy/middleware-retry": "^4.4.14", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/credential-provider-node": "^3.972.6", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/signature-v4-multi-region": "3.987.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.987.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.5", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-retry": "^4.4.30", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.13", - "@smithy/util-defaults-mode-node": "^4.2.16", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/util-defaults-mode-browser": "^4.3.29", + "@smithy/util-defaults-mode-node": "^4.2.32", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.948.0.tgz", - "integrity": "sha512-iWjchXy8bIAVBUsKnbfKYXRwhLgRg3EqCQ5FTr3JbR+QR75rZm4ZOYXlvHGztVTmtAZ+PQVA1Y4zO7v7N87C0A==", + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.985.0.tgz", + "integrity": "sha512-81J8iE8MuXhdbMfIz4sWFj64Pe41bFi/uqqmqOC5SlGv+kwoyLsyKS/rH2tW2t5buih4vTUxskRjxlqikTD4oQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.947.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.948.0", - "@aws-sdk/middleware-user-agent": "3.947.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.947.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.7", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.14", - "@smithy/middleware-retry": "^4.4.14", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.985.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.5", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-retry": "^4.4.30", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.13", - "@smithy/util-defaults-mode-node": "^4.2.16", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/util-defaults-mode-browser": "^4.3.29", + "@smithy/util-defaults-mode-node": "^4.2.32", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.985.0.tgz", + "integrity": "sha512-vth7UfGSUR3ljvaq8V4Rc62FsM7GUTH/myxPWkaEgOrprz1/Pc72EgTXxj+cPPPDAfHFIpjhkB7T7Td0RJx+BA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/core": { - "version": "3.947.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.947.0.tgz", - "integrity": "sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw==", + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.7.tgz", + "integrity": "sha512-wNZZQQNlJ+hzD49cKdo+PY6rsTDElO8yDImnrI69p2PLBa7QomeUKAJWYp9xnaR38nlHqWhMHZuYLCQ3oSX+xg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws-sdk/xml-builder": "3.930.0", - "@smithy/core": "^3.18.7", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.4", + "@smithy/core": "^3.22.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.947.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.947.0.tgz", - "integrity": "sha512-VR2V6dRELmzwAsCpK4GqxUi6UW5WNhAXS9F9AzWi5jvijwJo3nH92YNJUP4quMpgFZxJHEWyXLWgPjh9u0zYOA==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.5.tgz", + "integrity": "sha512-LxJ9PEO4gKPXzkufvIESUysykPIdrV7+Ocb9yAhbhJLE4TiAYqbCVUE+VuKP1leGR1bBfjWjYgSV5MxprlX3mQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.947.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.947.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.947.0.tgz", - "integrity": "sha512-inF09lh9SlHj63Vmr5d+LmwPXZc2IbK8lAruhOr3KLsZAIHEgHgGPXWDC2ukTEMzg0pkexQ6FOhXXad6klK4RA==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.7.tgz", + "integrity": "sha512-L2uOGtvp2x3bTcxFTpSM+GkwFIPd8pHfGWO1764icMbo7e5xJh0nfhx1UwkXLnwvocTNEf8A7jISZLYjUSNaTg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.947.0", - "@aws-sdk/types": "3.936.0", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.948.0.tgz", - "integrity": "sha512-Cl//Qh88e8HBL7yYkJNpF5eq76IO6rq8GsatKcfVBm7RFVxCqYEPSSBtkHdbtNwQdRQqAMXc6E/lEB/CZUDxnA==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.5.tgz", + "integrity": "sha512-SdDTYE6jkARzOeL7+kudMIM4DaFnP5dZVeatzw849k4bSXDdErDS188bgeNzc/RA2WGrlEpsqHUKP6G7sVXhZg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.947.0", - "@aws-sdk/credential-provider-env": "3.947.0", - "@aws-sdk/credential-provider-http": "3.947.0", - "@aws-sdk/credential-provider-login": "3.948.0", - "@aws-sdk/credential-provider-process": "3.947.0", - "@aws-sdk/credential-provider-sso": "3.948.0", - "@aws-sdk/credential-provider-web-identity": "3.948.0", - "@aws-sdk/nested-clients": "3.948.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/credential-provider-env": "^3.972.5", + "@aws-sdk/credential-provider-http": "^3.972.7", + "@aws-sdk/credential-provider-login": "^3.972.5", + "@aws-sdk/credential-provider-process": "^3.972.5", + "@aws-sdk/credential-provider-sso": "^3.972.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.5", + "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.948.0.tgz", - "integrity": "sha512-gcKO2b6eeTuZGp3Vvgr/9OxajMrD3W+FZ2FCyJox363ZgMoYJsyNid1vuZrEuAGkx0jvveLXfwiVS0UXyPkgtw==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.5.tgz", + "integrity": "sha512-uYq1ILyTSI6ZDCMY5+vUsRM0SOCVI7kaW4wBrehVVkhAxC6y+e9rvGtnoZqCOWL1gKjTMouvsf4Ilhc5NCg1Aw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.947.0", - "@aws-sdk/nested-clients": "3.948.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.948.0.tgz", - "integrity": "sha512-ep5vRLnrRdcsP17Ef31sNN4g8Nqk/4JBydcUJuFRbGuyQtrZZrVT81UeH2xhz6d0BK6ejafDB9+ZpBjXuWT5/Q==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.6.tgz", + "integrity": "sha512-DZ3CnAAtSVtVz+G+ogqecaErMLgzph4JH5nYbHoBMgBkwTUV+SUcjsjOJwdBJTHu3Dm6l5LBYekZoU2nDqQk2A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.947.0", - "@aws-sdk/credential-provider-http": "3.947.0", - "@aws-sdk/credential-provider-ini": "3.948.0", - "@aws-sdk/credential-provider-process": "3.947.0", - "@aws-sdk/credential-provider-sso": "3.948.0", - "@aws-sdk/credential-provider-web-identity": "3.948.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/credential-provider-env": "^3.972.5", + "@aws-sdk/credential-provider-http": "^3.972.7", + "@aws-sdk/credential-provider-ini": "^3.972.5", + "@aws-sdk/credential-provider-process": "^3.972.5", + "@aws-sdk/credential-provider-sso": "^3.972.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.947.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.947.0.tgz", - "integrity": "sha512-WpanFbHe08SP1hAJNeDdBDVz9SGgMu/gc0XJ9u3uNpW99nKZjDpvPRAdW7WLA4K6essMjxWkguIGNOpij6Do2Q==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.5.tgz", + "integrity": "sha512-HDKF3mVbLnuqGg6dMnzBf1VUOywE12/N286msI9YaK9mEIzdsGCtLTvrDhe3Up0R9/hGFbB+9l21/TwF5L1C6g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.947.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.948.0.tgz", - "integrity": "sha512-gqLhX1L+zb/ZDnnYbILQqJ46j735StfWV5PbDjxRzBKS7GzsiYoaf6MyHseEopmWrez5zl5l6aWzig7UpzSeQQ==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.5.tgz", + "integrity": "sha512-8urj3AoeNeQisjMmMBhFeiY2gxt6/7wQQbEGun0YV/OaOOiXrIudTIEYF8ZfD+NQI6X1FY5AkRsx6O/CaGiybA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.948.0", - "@aws-sdk/core": "3.947.0", - "@aws-sdk/token-providers": "3.948.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/client-sso": "3.985.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/token-providers": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.948.0.tgz", - "integrity": "sha512-MvYQlXVoJyfF3/SmnNzOVEtANRAiJIObEUYYyjTqKZTmcRIVVky0tPuG26XnB8LmTYgtESwJIZJj/Eyyc9WURQ==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.5.tgz", + "integrity": "sha512-OK3cULuJl6c+RcDZfPpaK5o3deTOnKZbxm7pzhFNGA3fI2hF9yDih17fGRazJzGGWaDVlR9ejZrpDef4DJCEsw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.947.0", - "@aws-sdk/nested-clients": "3.948.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", - "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", - "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.948.0.tgz", - "integrity": "sha512-Qa8Zj+EAqA0VlAVvxpRnpBpIWJI9KUwaioY1vkeNVwXPlNaz9y9zCKVM9iU9OZ5HXpoUg6TnhATAHXHAE8+QsQ==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", + "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.947.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.947.0.tgz", - "integrity": "sha512-DS2tm5YBKhPW2PthrRBDr6eufChbwXe0NjtTZcYDfUCXf0OR+W6cIqyKguwHMJ+IyYdey30AfVw9/Lb5KB8U8A==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.7.tgz", + "integrity": "sha512-VtZ7tMIw18VzjG+I6D6rh2eLkJfTtByiFoCIauGDtTTPBEUMQUiGaJ/zZrPlCY6BsvLLeFKz3+E5mntgiOWmIg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.947.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-arn-parser": "3.893.0", - "@smithy/core": "^3.18.7", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-arn-parser": "^3.972.2", + "@smithy/core": "^3.22.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.947.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.947.0.tgz", - "integrity": "sha512-7rpKV8YNgCP2R4F9RjWZFcD2R+SO/0R4VHIbY9iZJdH2MzzJ8ZG7h8dZ2m8QkQd1fjx4wrFJGGPJUTYXPV3baA==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.7.tgz", + "integrity": "sha512-HUD+geASjXSCyL/DHPQc/Ua7JhldTcIglVAoCV8kiVm99IaFSlAbTvEnyhZwdE6bdFyTL+uIaWLaCFSRsglZBQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.947.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@smithy/core": "^3.18.7", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.985.0", + "@smithy/core": "^3.22.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.985.0.tgz", + "integrity": "sha512-vth7UfGSUR3ljvaq8V4Rc62FsM7GUTH/myxPWkaEgOrprz1/Pc72EgTXxj+cPPPDAfHFIpjhkB7T7Td0RJx+BA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.948.0.tgz", - "integrity": "sha512-zcbJfBsB6h254o3NuoEkf0+UY1GpE9ioiQdENWv7odo69s8iaGBEQ4BDpsIMqcuiiUXw1uKIVNxCB1gUGYz8lw==", + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.985.0.tgz", + "integrity": "sha512-TsWwKzb/2WHafAY0CE7uXgLj0FmnkBTgfioG9HO+7z/zCPcl1+YU+i7dW4o0y+aFxFgxTMG+ExBQpqT/k2ao8g==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.947.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.948.0", - "@aws-sdk/middleware-user-agent": "3.947.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.947.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.7", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.14", - "@smithy/middleware-retry": "^4.4.14", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.985.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.5", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-retry": "^4.4.30", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.13", - "@smithy/util-defaults-mode-node": "^4.2.16", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/util-defaults-mode-browser": "^4.3.29", + "@smithy/util-defaults-mode-node": "^4.2.32", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.985.0.tgz", + "integrity": "sha512-vth7UfGSUR3ljvaq8V4Rc62FsM7GUTH/myxPWkaEgOrprz1/Pc72EgTXxj+cPPPDAfHFIpjhkB7T7Td0RJx+BA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", - "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.947.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.947.0.tgz", - "integrity": "sha512-UaYmzoxf9q3mabIA2hc4T6x5YSFUG2BpNjAZ207EA1bnQMiK+d6vZvb83t7dIWL/U1de1sGV19c1C81Jf14rrA==", + "version": "3.987.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.987.0.tgz", + "integrity": "sha512-5kVC6x6+2NO+/NIXWJwN68+8cvqREsoE+tFOMyZWj2fg3EWzCnTGVIFd7hSJZJT2WiP5LqcrdEoFyXtfDta1hg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.947.0", - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/middleware-sdk-s3": "^3.972.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.948.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.948.0.tgz", - "integrity": "sha512-V487/kM4Teq5dcr1t5K6eoUKuqlGr9FRWL3MIMukMERJXHZvio6kox60FZ/YtciRHRI75u14YUqm2Dzddcu3+A==", + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.985.0.tgz", + "integrity": "sha512-+hwpHZyEq8k+9JL2PkE60V93v2kNhUIv7STFt+EAez1UJsJOQDhc5LpzEX66pNjclI5OTwBROs/DhJjC/BtMjQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.947.0", - "@aws-sdk/nested-clients": "3.948.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", - "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", - "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz", + "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==", "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", - "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "version": "3.987.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.987.0.tgz", + "integrity": "sha512-rZnZwDq7Pn+TnL0nyS6ryAhpqTZtLtHbJaqfxuHlDX3v/bq0M7Ch/V3qF9dZWaGgsJ2H9xn7/vFOxlnL4fBMcQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-endpoints": "^3.2.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", - "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "version": "3.965.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.4.tgz", + "integrity": "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==", "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", - "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.947.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.947.0.tgz", - "integrity": "sha512-+vhHoDrdbb+zerV4noQk1DHaUMNzWFWPpPYjVTwW2186k5BEJIecAMChYkghRrBVJ3KPWP1+JnZwOd72F3d4rQ==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.5.tgz", + "integrity": "sha512-GsUDF+rXyxDZkkJxUsDxnA67FG+kc5W1dnloCFLl6fWzceevsCYzJpASBzT+BPjwUgREE6FngfJYYYMQUY5fZQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.947.0", - "@aws-sdk/types": "3.936.0", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -1005,24 +1057,24 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.930.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", - "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", + "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "fast-xml-parser": "5.2.5", + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", - "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1060,6 +1112,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1768,7 +1821,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.18.tgz", "integrity": "sha512-EF77RqROHL+4LhMGW5NTeKqfUd/e4OOv6EDFQ/UQQiFyWuqkEKyEz0NDILxOFxWUEVdjT2GQ2cC7t12B6pESwg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-dart": { "version": "2.3.1", @@ -1915,14 +1969,16 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.13.tgz", "integrity": "sha512-vHzk2xfqQYPvoXtQtywa6ekIonPrUEwe2uftjry3UNRNl89TtzLJVSkiymKJ3WMb+W/DwKXKIb1tKzcIS8ccIg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.4.tgz", "integrity": "sha512-afea+0rGPDeOV9gdO06UW183Qg6wRhWVkgCFwiO3bDupAoyXRuvupbb5nUyqSTsLXIKL8u8uXQlJ9pkz07oVXw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-java": { "version": "5.0.12", @@ -2120,7 +2176,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-vue": { "version": "3.0.5", @@ -2197,7 +2254,6 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2210,7 +2266,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2466,7 +2521,6 @@ "resolved": "https://registry.npmjs.org/@foliojs-fork/fontkit/-/fontkit-1.9.2.tgz", "integrity": "sha512-IfB5EiIb+GZk+77TRB86AHroVaqfq8JRFlUbz0WEwsInyCG0epX2tCPOy+UfaWPju30DeVoUAXfzWXmhn753KA==", "license": "MIT", - "peer": true, "dependencies": { "@foliojs-fork/restructure": "^2.0.2", "brotli": "^1.2.0", @@ -2483,7 +2537,6 @@ "resolved": "https://registry.npmjs.org/@foliojs-fork/linebreak/-/linebreak-1.1.2.tgz", "integrity": "sha512-ZPohpxxbuKNE0l/5iBJnOAfUaMACwvUIKCvqtWGKIMv1lPYoNjYXRfhi9FeeV9McBkBLxsMFWTVVhHJA8cyzvg==", "license": "MIT", - "peer": true, "dependencies": { "base64-js": "1.3.1", "unicode-trie": "^2.0.0" @@ -2494,7 +2547,6 @@ "resolved": "https://registry.npmjs.org/@foliojs-fork/pdfkit/-/pdfkit-0.15.3.tgz", "integrity": "sha512-Obc0Wmy3bm7BINFVvPhcl2rnSSK61DQrlHU8aXnAqDk9LCjWdUOPwhgD8Ywz5VtuFjRxmVOM/kQ/XLIBjDvltw==", "license": "MIT", - "peer": true, "dependencies": { "@foliojs-fork/fontkit": "^1.9.2", "@foliojs-fork/linebreak": "^1.1.1", @@ -2507,8 +2559,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/@foliojs-fork/restructure/-/restructure-2.0.2.tgz", "integrity": "sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@gerrit0/mini-shiki": { "version": "1.27.2", @@ -2643,9 +2694,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "license": "MIT", "dependencies": { "@isaacs/balanced-match": "^4.0.1" @@ -3348,7 +3399,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -3399,7 +3449,6 @@ "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-2.0.2.tgz", "integrity": "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==", "license": "MIT", - "peer": true, "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/url": "^3.0.0", @@ -3414,7 +3463,6 @@ "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-2.0.2.tgz", "integrity": "sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==", "license": "MIT", - "peer": true, "dependencies": { "@oozcitak/util": "^10.0.0" }, @@ -3427,7 +3475,6 @@ "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-3.0.0.tgz", "integrity": "sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==", "license": "MIT", - "peer": true, "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/util": "^10.0.0" @@ -3441,7 +3488,6 @@ "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-10.0.0.tgz", "integrity": "sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0" } @@ -3623,13 +3669,13 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", - "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3637,17 +3683,17 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", - "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -3655,19 +3701,19 @@ } }, "node_modules/@smithy/core": { - "version": "3.18.7", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.7.tgz", - "integrity": "sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.0.tgz", + "integrity": "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.6", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -3677,16 +3723,16 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", - "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -3694,15 +3740,15 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", - "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, @@ -3711,13 +3757,13 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", - "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -3727,13 +3773,13 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", - "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3754,14 +3800,14 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", - "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3769,19 +3815,19 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.14.tgz", - "integrity": "sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg==", + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.14.tgz", + "integrity": "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.7", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/core": "^3.23.0", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -3789,19 +3835,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.14.tgz", - "integrity": "sha512-Z2DG8Ej7FyWG1UA+7HceINtSLzswUgs2np3sZX0YBBxCt+CXG4QUxv88ZDS3+2/1ldW7LqtSY1UO/6VQ1pND8Q==", + "version": "4.4.31", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.31.tgz", + "integrity": "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/service-error-classification": "^4.2.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, @@ -3810,14 +3856,14 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", - "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3825,13 +3871,13 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", - "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3839,15 +3885,15 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", - "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3855,16 +3901,16 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", - "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz", + "integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3872,13 +3918,13 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", - "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3886,13 +3932,13 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", - "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3900,13 +3946,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", - "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, @@ -3915,13 +3961,13 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", - "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3929,26 +3975,26 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", - "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0" + "@smithy/types": "^4.12.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", - "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3956,17 +4002,17 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", - "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -3976,18 +4022,18 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.9.10", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.10.tgz", - "integrity": "sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ==", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.3.tgz", + "integrity": "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.7", - "@smithy/middleware-endpoint": "^4.3.14", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@smithy/core": "^3.23.0", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -3995,9 +4041,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4008,14 +4054,14 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", - "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4091,15 +4137,15 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.13", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.13.tgz", - "integrity": "sha512-hlVLdAGrVfyNei+pKIgqDTxfu/ZI2NSyqj4IDxKd5bIsIqwR/dSlkxlPaYxFiIaDVrBy0he8orsFy+Cz119XvA==", + "version": "4.3.30", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.30.tgz", + "integrity": "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4107,18 +4153,18 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.16", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.16.tgz", - "integrity": "sha512-F1t22IUiJLHrxW9W1CQ6B9PN+skZ9cqSuzB18Eh06HrJPbjsyZ7ZHecAKw80DQtyGTRcVfeukKaCRYebFwclbg==", + "version": "4.2.33", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.33.tgz", + "integrity": "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.3", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4126,14 +4172,14 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", - "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4154,13 +4200,13 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", - "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4168,14 +4214,14 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", - "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4183,15 +4229,15 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", - "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz", + "integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/types": "^4.9.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", @@ -4246,8 +4292,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@sqltools/formatter": { "version": "1.2.5", @@ -4283,6 +4328,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" @@ -4506,29 +4552,25 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", @@ -4752,6 +4794,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.2.tgz", "integrity": "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -5108,6 +5151,7 @@ "integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -5191,6 +5235,7 @@ "integrity": "sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.41.0", @@ -5562,6 +5607,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5584,7 +5630,6 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "license": "MIT", - "peer": true, "dependencies": { "acorn": "^8.11.0" }, @@ -5862,8 +5907,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", @@ -6055,8 +6099,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -6084,14 +6127,14 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "peer": true, "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -6333,7 +6376,6 @@ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "license": "MIT", - "peer": true, "engines": { "node": "^4.5.0 || >= 5.9" } @@ -6461,9 +6503,9 @@ } }, "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "dev": true, "license": "MIT" }, @@ -6496,7 +6538,6 @@ "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.1.2" } @@ -6521,6 +6562,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6596,8 +6638,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -6935,7 +6976,6 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.8" } @@ -6981,7 +7021,6 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", - "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -7165,8 +7204,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -7186,8 +7224,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cspell-config-lib": { "version": "9.2.0", @@ -7420,7 +7457,6 @@ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", "license": "MIT", - "peer": true, "dependencies": { "is-arguments": "^1.1.1", "is-date-object": "^1.0.5", @@ -7492,7 +7528,6 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.4.0" } @@ -7520,15 +7555,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.3.1" } @@ -7705,7 +7738,6 @@ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -7766,7 +7798,6 @@ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "license": "MIT", - "peer": true, "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", @@ -7787,7 +7818,6 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" } @@ -7797,7 +7827,6 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", - "peer": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -7811,7 +7840,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -7829,7 +7857,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -7839,7 +7866,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -7852,7 +7878,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -8078,6 +8103,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8154,6 +8180,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8957,6 +8984,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -9094,9 +9122,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", "dev": true, "funding": [ { @@ -9259,7 +9287,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=4.0" }, @@ -9317,7 +9344,6 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", - "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -9334,7 +9360,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -9344,7 +9369,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -10079,7 +10103,6 @@ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -10683,6 +10706,7 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -11403,8 +11427,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz", "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -11502,6 +11525,7 @@ "integrity": "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^3.0.0", @@ -11607,7 +11631,6 @@ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", - "peer": true, "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -11619,7 +11642,6 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", - "peer": true, "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" @@ -11806,9 +11828,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -11823,43 +11845,37 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.kebabcase": { "version": "4.1.1", @@ -11886,8 +11902,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", @@ -12697,7 +12712,6 @@ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" @@ -12984,8 +12998,7 @@ "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/parent-module": { "version": "2.0.0", @@ -13134,7 +13147,6 @@ "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.2.20.tgz", "integrity": "sha512-bGbxbGFP5p8PWNT3Phsu1ZcRLnRfF6jmnuKTkgmt6i5PZzSdX6JaB+NeTz9q+aocfW8SE9GUjL3o/5GroBqGcQ==", "license": "MIT", - "peer": true, "dependencies": { "@foliojs-fork/linebreak": "^1.1.2", "@foliojs-fork/pdfkit": "^0.15.3", @@ -13150,7 +13162,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -13163,6 +13174,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -13382,8 +13394,7 @@ "node_modules/png-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", - "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==", - "peer": true + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" }, "node_modules/possible-typed-array-names": { "version": "1.1.0", @@ -13449,6 +13460,7 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -13601,8 +13613,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/pump": { "version": "3.0.3", @@ -14080,6 +14091,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -14169,8 +14181,7 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", - "license": "BlueOak-1.0.0", - "peer": true + "license": "BlueOak-1.0.0" }, "node_modules/scslre": { "version": "0.3.0", @@ -14504,7 +14515,6 @@ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", @@ -14523,7 +14533,6 @@ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "license": "MIT", - "peer": true, "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" @@ -14534,7 +14543,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -14552,7 +14560,6 @@ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -14566,7 +14573,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -14584,7 +14590,6 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", - "peer": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -14598,7 +14603,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -14616,7 +14620,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -14626,7 +14629,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -14639,7 +14641,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -15163,9 +15164,9 @@ } }, "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "dev": true, "funding": [ { @@ -15425,8 +15426,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -16025,6 +16025,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16092,6 +16093,7 @@ "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", @@ -16359,7 +16361,6 @@ "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.0", "unicode-trie": "^2.0.0" @@ -16370,7 +16371,6 @@ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", "license": "MIT", - "peer": true, "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" @@ -16477,6 +16477,7 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", + "peer": true, "bin": { "uuid": "dist/esm/bin/uuid" } @@ -16485,8 +16486,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/v8-to-istanbul": { "version": "9.3.0", @@ -16848,7 +16848,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -16899,7 +16898,6 @@ "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-2.0.2.tgz", "integrity": "sha512-UiRwoSStEXS3R+YE8OqYv3jebza8cBBAI2y8g3B15XFkn3SbEOyyLnmPHjLBPZANrPJKEzxxB7A3XwcLikQVlQ==", "license": "MIT", - "peer": true, "dependencies": { "sax": "^1.2.4" }, @@ -17021,7 +17019,6 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } diff --git a/package.json b/package.json index eef5059..e36db4e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zibri", - "version": "2.1.7", + "version": "2.2.0", "main": "./dist/cjs/index.js", "types": "./dist/cjs/index.d.ts", "module": "./dist/esm/index.mjs", diff --git a/sandbox/src/services/dep.service.ts b/sandbox/src/services/dep.service.ts index 571d722..f03eed8 100644 --- a/sandbox/src/services/dep.service.ts +++ b/sandbox/src/services/dep.service.ts @@ -1,9 +1,9 @@ -import { Inject, Injectable } from 'zibri'; +import { AssetService, Inject, Injectable, ZIBRI_DI_TOKENS } from 'zibri'; @Injectable() export class DepService { constructor( - @Inject('42') - private readonly numberValue: string + @Inject(ZIBRI_DI_TOKENS.ASSET_SERVICE) + private readonly assetService: AssetService ) {} } \ No newline at end of file diff --git a/src/application.ts b/src/application.ts index 59186ac..d23fc2f 100644 --- a/src/application.ts +++ b/src/application.ts @@ -12,7 +12,7 @@ import { DataSourceServiceInterface } from './data-source'; import { ZIBRI_DI_TOKENS, inject } from './di'; import { register } from './di/register.function'; import { EmailServiceInterface, MailingListServiceInterface } from './email'; -import { UnmatchedRouteError } from './error-handling'; +import { GlobalErrorHandler, UnmatchedRouteError } from './error-handling'; import { GlobalRegistry } from './global'; import { HandlebarUtilities } from './handlebars/handlebar.utilities'; import { LoggerInterface } from './logging'; @@ -76,7 +76,9 @@ export class ZibriApplication { } // eslint-disable-next-line jsdoc/require-jsdoc - use(handler: RequestHandler): express.Express; + use(handler: RequestHandler): void; + // eslint-disable-next-line jsdoc/require-jsdoc + use(errorHandler: GlobalErrorHandler): void; // eslint-disable-next-line jsdoc/require-jsdoc use(...handlers: RequestHandler[]): void; // eslint-disable-next-line jsdoc/require-jsdoc @@ -84,9 +86,9 @@ export class ZibriApplication { // eslint-disable-next-line jsdoc/require-jsdoc use(path: Route, handlers: RequestHandler[]): void; // eslint-disable-next-line jsdoc/require-jsdoc, typescript/no-explicit-any - use(...args: any[]): express.Express { + use(...args: any[]): void { // eslint-disable-next-line typescript/no-unsafe-argument - return this.express.use(...args); + this.express.use(...args); } /** @@ -167,7 +169,7 @@ export class ZibriApplication { this.multithreadingService = inject(ZIBRI_DI_TOKENS.MULTITHREADING_SERVICE); await this.multithreadingService.init(); - this.websocketService = inject(ZIBRI_DI_TOKENS.WEBSOCKET_SERVICE); + this.websocketService = inject>(ZIBRI_DI_TOKENS.WEBSOCKET_SERVICE); await this.websocketService.attachTo(this); this.backupService = inject(ZIBRI_DI_TOKENS.BACKUP_SERVICE); diff --git a/src/assets/asset.service.ts b/src/assets/asset.service.ts index e0a2e3a..610fb23 100644 --- a/src/assets/asset.service.ts +++ b/src/assets/asset.service.ts @@ -14,6 +14,7 @@ import type { LoggerInterface } from '../logging'; import { FileResponse } from '../parsing/form-data/file-response.model'; import { HtmlResponse } from '../parsing/html/html-response.model'; import { Route } from '../routing'; +import { ObjectUtilities } from '../utilities'; // eslint-disable-next-line jsdoc/require-jsdoc type FileNode = { type: 'file', name: string, route: string }; @@ -131,7 +132,7 @@ export class AssetService implements AssetServiceInterface { } private mapToTree(nodes: NodeMap): TreeNode[] { - return Object.entries(nodes).map(([name, info]) => { + return ObjectUtilities.entries(nodes).map(([name, info]) => { if (!info) { throw new Error('Error building the assets tree'); } diff --git a/src/auth/2fa/two-factor.service.ts b/src/auth/2fa/two-factor.service.ts index 776c005..9019a10 100644 --- a/src/auth/2fa/two-factor.service.ts +++ b/src/auth/2fa/two-factor.service.ts @@ -90,10 +90,7 @@ export class TwoFactorService implements TwoFactorServiceInterface { ): Promise { try { await Promise.any( - allowedMethods.map(async m => { - const twoFactorMethod: TwoFactorMethod = inject(m); - await twoFactorMethod.validate(user, request); - }) + allowedMethods.map(m => inject(m).validate(user, request)) ); return true; } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index af2fa4a..82129cd 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -5,7 +5,7 @@ import { UnauthorizedError } from '../error-handling'; import { HttpRequest } from '../http'; import { LoggerInterface } from '../logging'; import { Newable } from '../types'; -import { MetadataUtilities } from '../utilities'; +import { MetadataUtilities, PromiseUtilities } from '../utilities'; import { WebsocketRequest } from '../websocket'; import { TwoFactorServiceInterface } from './2fa'; import { AuthServiceInterface } from './auth-service.interface'; @@ -274,7 +274,7 @@ export class AuthService implements AuthServiceInterface { // eslint-disable-next-line stylistic/max-len const strategies: AuthStrategyInterface, unknown, unknown, unknown, unknown, unknown, unknown>[] = allowedStrategies.map(s => inject(s)); try { - return await Promise.any(strategies.map(s => s.isLoggedIn(request))); + return await PromiseUtilities.anyValueTrue(strategies, s => s.isLoggedIn(request)); } catch { return false; @@ -290,7 +290,7 @@ export class AuthService implements AuthServiceInterface { // eslint-disable-next-line stylistic/max-len const strategies: AuthStrategyInterface, unknown, unknown, unknown, unknown, unknown, unknown>[] = allowedStrategies.map(s => inject(s)); try { - return await Promise.any(strategies.map(s => s.hasRole(request, allowedRoles))); + return await PromiseUtilities.anyValueTrue(strategies, s => s.hasRole(request, allowedRoles)); } catch { return false; @@ -308,7 +308,10 @@ export class AuthService implements AuthServiceInterface { // eslint-disable-next-line stylistic/max-len const strategies: AuthStrategyInterface, unknown, unknown, unknown, unknown, unknown, unknown>[] = allowedStrategies.map(s => inject(s)); try { - return await Promise.any(strategies.map(s => s.belongsTo(request, targetEntity, targetUserIdKey, targetIdParamKey))); + return await PromiseUtilities.anyValueTrue( + strategies, + s => s.belongsTo(request, targetEntity, targetUserIdKey, targetIdParamKey) + ); } catch { return false; diff --git a/src/auth/strategies/jwt/jwt.auth-strategy.ts b/src/auth/strategies/jwt/jwt.auth-strategy.ts index ce25ffb..4bac2e1 100644 --- a/src/auth/strategies/jwt/jwt.auth-strategy.ts +++ b/src/auth/strategies/jwt/jwt.auth-strategy.ts @@ -11,7 +11,7 @@ import { JwtRefreshToken, JwtRefreshTokenCreateDto } from './jwt-refresh-token.m import { JwtRequestPasswordResetData } from './jwt-request-password-reset-data.model'; import { JwtUtilities } from './jwt.utilities'; import { Repository } from '../../../data-source'; -import { inject, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../../../di'; +import { inject, NoProviderError, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../../../di'; import { EmailPriority, EmailServiceInterface } from '../../../email'; import { BaseEntity } from '../../../entity/base-entity.model'; import { TooManyRequestsError, UnauthorizedError } from '../../../error-handling'; @@ -77,14 +77,26 @@ implements AuthStrategyInterface< } constructor() { - this.accessTokenSecret = inject(ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_SECRET); + const accessTokenSecret: string | undefined = inject(ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_SECRET); + if (!accessTokenSecret) { + throw new NoProviderError(ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_SECRET, [JwtAuthStrategy]); + } + const refreshTokenSecret: string | undefined = inject(ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_SECRET); + if (!refreshTokenSecret) { + throw new NoProviderError(ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_SECRET, [JwtAuthStrategy]); + } + const confirmPasswordResetUrl: string | undefined = inject(ZIBRI_DI_TOKENS.JWT_CONFIRM_PASSWORD_RESET_URL); + if (!confirmPasswordResetUrl) { + throw new NoProviderError(ZIBRI_DI_TOKENS.JWT_CONFIRM_PASSWORD_RESET_URL, [JwtAuthStrategy]); + } + this.accessTokenSecret = accessTokenSecret; this.accessTokenExpiresInMs = inject(ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_EXPIRES_IN_MS); - this.refreshTokenSecret = inject(ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_SECRET); + this.refreshTokenSecret = refreshTokenSecret; this.refreshTokenExpiresInMs = inject(ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_EXPIRES_IN_MS); this.passwordResetTokenExpiresInMs = inject(ZIBRI_DI_TOKENS.JWT_PASSWORD_RESET_TOKEN_EXPIRES_IN_MS); this.userService = inject(ZIBRI_DI_TOKENS.USER_SERVICE); this.emailService = inject(ZIBRI_DI_TOKENS.EMAIL_SERVICE); - this.confirmPasswordResetUrl = inject(ZIBRI_DI_TOKENS.JWT_CONFIRM_PASSWORD_RESET_URL); + this.confirmPasswordResetUrl = confirmPasswordResetUrl; } // eslint-disable-next-line jsdoc/require-jsdoc diff --git a/src/auth/user/user.service.ts b/src/auth/user/user.service.ts index af2468d..edb525a 100644 --- a/src/auth/user/user.service.ts +++ b/src/auth/user/user.service.ts @@ -1,8 +1,8 @@ +import { UserServiceInterface } from './user-service.interface'; import { inject } from '../../di'; import { NotFoundError } from '../../error-handling'; import { GlobalRegistry } from '../../global'; import { BaseUser } from '../models'; -import { UserServiceInterface } from './user-service.interface'; // eslint-disable-next-line jsdoc/require-jsdoc export const NO_USER_REPOSITORIES_PROVIDED_ERROR_MESSAGE: string = 'No user repositories have been provided.'; diff --git a/src/backup/backup-service.test.ts b/src/backup/backup-service.test.ts index 6d361e8..bcab729 100644 --- a/src/backup/backup-service.test.ts +++ b/src/backup/backup-service.test.ts @@ -4,7 +4,6 @@ import path from 'path'; import { beforeAll, afterAll, describe, it, expect } from '@jest/globals'; import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql'; -import { BackupService } from './backup.service'; import { POSTGRES_TEST_IMAGE, testFileFolder } from '../__testing__'; import { BackupEntity } from './backup-entity.model'; import { BackupResourceEntity } from './backup-resource-entity.model'; @@ -18,6 +17,7 @@ import { Newable } from '../types'; import { Backup } from './decorators/backup-resource.decorator'; import { FsBackupTransport } from './transports'; import { PostgresDataSource, PostgresOptions } from '../data-source'; +import { BackupServiceInterface } from './backup-service.interface'; const backupFsFolder: string = path.join(testFileFolder, 'backups'); @@ -57,7 +57,7 @@ describe('Create and restore postgres backup', () => { let itemRepository: Repository; let backupRepository: Repository; let container: StartedPostgreSqlContainer; - let backupService: BackupService; + let backupService: BackupServiceInterface; beforeAll(async () => { await rm(backupFsFolder, { recursive: true, force: true }); diff --git a/src/backup/backup.service.ts b/src/backup/backup.service.ts index 6bb842a..b9b55c3 100644 --- a/src/backup/backup.service.ts +++ b/src/backup/backup.service.ts @@ -7,7 +7,7 @@ import { inject, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../di'; import { GlobalRegistry } from '../global'; import { LoggerInterface } from '../logging'; import { Newable } from '../types'; -import { chunkedPromiseAll, MetadataUtilities, validateEntitiesRegistered } from '../utilities'; +import { MetadataUtilities, PromiseUtilities, validateEntitiesRegistered } from '../utilities'; import { BackupEntity, BackupEntityCreateData } from './backup-entity.model'; import { BackupResourceEntity, BackupResourceEntityCreateData } from './backup-resource-entity.model'; import { BackupResourceInterface } from './backup-resource.interface'; @@ -80,7 +80,7 @@ export class BackupService implements BackupServiceInterface { const groupedEntities: Record = this.groupEntitiesById(entitiesToSync); const mergedEntities: BackupEntity[] = this.mergeEntities(groupedEntities); - await chunkedPromiseAll(mergedEntities.map(e => this.backupRepository.create(e, { allowId: true }))); + await PromiseUtilities.allChunked(mergedEntities, e => this.backupRepository.create(e, { allowId: true })); } private mergeEntities(groupedEntities: Record): BackupEntity[] { diff --git a/src/change-sets/change-set-repository.model.ts b/src/change-sets/change-set-repository.model.ts index 0bda3c6..564fa4b 100644 --- a/src/change-sets/change-set-repository.model.ts +++ b/src/change-sets/change-set-repository.model.ts @@ -12,7 +12,7 @@ import { inject, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../di'; import { PropertyMetadata } from '../entity'; import { BadRequestError } from '../error-handling'; import { HttpRequest } from '../http'; -import { chunkedPromiseAll, MetadataUtilities } from '../utilities'; +import { MetadataUtilities, ObjectUtilities, PromiseUtilities } from '../utilities'; /** * The result for resetting a change set on an entity. @@ -55,7 +55,7 @@ export class ChangeSetRepository< this.keysToExcludeFromChangeSets = ['changeSets']; const props: Record = MetadataUtilities.getModelProperties(entityClass); - for (const [key, m] of Object.entries(props)) { + for (const [key, m] of ObjectUtilities.entries(props)) { if (m.excludeFromChangeSets) { this.keysToExcludeFromChangeSets.push(key as keyof T); } @@ -311,8 +311,9 @@ export class ChangeSetRepository< options?: BaseRepositoryOptions ): Promise { const entitiesToRollback: T[] = await this.findAll({ where: where, ...options }); - await chunkedPromiseAll( - entitiesToRollback.map(e => this.rollbackToDate(e, date, createChangeSet, preserveCreateChangeSet, options)) + await PromiseUtilities.allChunked( + entitiesToRollback, + e => this.rollbackToDate(e, date, createChangeSet, preserveCreateChangeSet, options) ); return entitiesToRollback.length; } diff --git a/src/data-source/data-source.service.ts b/src/data-source/data-source.service.ts index 87aa011..74ab722 100644 --- a/src/data-source/data-source.service.ts +++ b/src/data-source/data-source.service.ts @@ -6,7 +6,7 @@ import { inject, ZIBRI_DI_TOKENS } from '../di'; import { MailingList, MailingListSubscriber } from '../email'; import { BaseEntity } from '../entity/base-entity.model'; import { Log, LoggerInterface } from '../logging'; -import { Invoice, NumberInvoices } from '../plugin'; +import { Invoice, NumberInvoices, Payment } from '../plugin'; import { Newable } from '../types'; import { validateEntitiesRegistered } from '../utilities'; import { DataSourceInterface } from './data-sources/data-source.interface'; @@ -28,7 +28,8 @@ export class DataSourceService implements DataSourceServiceInterface { BackupResourceEntity, BackupEntity, NumberInvoices, - Invoice + Invoice, + Payment ]; constructor() { diff --git a/src/data-source/data-sources/postgres-data-source.model.ts b/src/data-source/data-sources/postgres-data-source.model.ts index a842ba7..556feaa 100644 --- a/src/data-source/data-sources/postgres-data-source.model.ts +++ b/src/data-source/data-sources/postgres-data-source.model.ts @@ -2,13 +2,13 @@ import { ChildProcessByStdio, spawn } from 'node:child_process'; import { PassThrough, Readable, Writable } from 'node:stream'; import { DataSource as TODataSource, Repository as TORepository, EntityMetadata as TOEntityMetadata, EntitySchema, EntitySchemaColumnOptions, QueryRunner, EntitySchemaRelationOptions, Table, TableColumnOptions, TableColumn, EntityTarget } from 'typeorm'; -import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions'; -import { IsolationLevel } from 'typeorm/driver/types/IsolationLevel'; -import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; -import { OnDeleteType } from 'typeorm/metadata/types/OnDeleteType'; -import { OnUpdateType } from 'typeorm/metadata/types/OnUpdateType'; +import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; +import { IsolationLevel } from 'typeorm/driver/types/IsolationLevel.js'; +import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata.js'; +import { OnDeleteType } from 'typeorm/metadata/types/OnDeleteType.js'; +import { OnUpdateType } from 'typeorm/metadata/types/OnUpdateType.js'; -import { DataSourceInterface } from '.'; +import { DataSourceInterface } from './data-source.interface'; import { ChangeSetEntity, ChangeSetRepository, isChangeSetEntityNewable, isSoftDeleteEntityNewable, SoftDeleteEntity, SoftDeleteRepository } from '../../change-sets'; import { inject, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../../di'; import { register } from '../../di/register.function'; @@ -19,6 +19,7 @@ import { GlobalRegistry } from '../../global'; import { LoggerInterface } from '../../logging'; import { ExcludeStrict, Newable, OmitStrict, Version } from '../../types'; import { compareVersion, MetadataUtilities } from '../../utilities'; +import { ObjectUtilities } from '../../utilities/object.utilities'; import { Migration, MigrationEntity } from '../migration'; import { ColumnType, DataSourceOptions } from '../models'; import { Repository } from '../repository'; @@ -197,7 +198,7 @@ export abstract class PostgresDataSource implements DataSourceInterface { } const props: Record = MetadataUtilities.getModelProperties(cls); - const numberOfPrimaryKeys: number = Object.values(props).filter(d => (d as StringPropertyMetadata).primary).length; + const numberOfPrimaryKeys: number = ObjectUtilities.values(props).filter(d => (d as StringPropertyMetadata).primary).length; if (numberOfPrimaryKeys === 0) { throw new Error(`no primary key specified for entity "${cls.name}".`); } @@ -207,7 +208,7 @@ export abstract class PostgresDataSource implements DataSourceInterface { const columns: Record = {}; const relations: Record = {}; - for (const [key, m] of Object.entries(props)) { + for (const [key, m] of ObjectUtilities.entries(props)) { if ( m.type === Relation.MANY_TO_ONE || m.type === Relation.ONE_TO_MANY @@ -351,7 +352,7 @@ export abstract class PostgresDataSource implements DataSourceInterface { ...metadata, type: metadata.format === 'uuid' || metadata.primary ? 'uuid' : this.columnTypeMapping[metadata.type], length: metadata.maxLength, - enum: metadata.enum ? Object.values(metadata.enum) : undefined, + enum: metadata.enum ? ObjectUtilities.values(metadata.enum) : undefined, default: undefined }; } @@ -478,7 +479,7 @@ export abstract class PostgresDataSource implements DataSourceInterface { ...columnMetadata, ...newColumn, enum: 'enum' in newColumn && newColumn.enum - ? Object.values(newColumn.enum).map(v => String(v)) + ? ObjectUtilities.values(newColumn.enum).map(v => String(v)) : columnMetadata.enum ? columnMetadata.enum.map(v => String(v)) : undefined, diff --git a/src/data-source/models/where/where-filter-to-find-options-where.function.ts b/src/data-source/models/where/where-filter-to-find-options-where.function.ts index ca5746b..090dd96 100644 --- a/src/data-source/models/where/where-filter-to-find-options-where.function.ts +++ b/src/data-source/models/where/where-filter-to-find-options-where.function.ts @@ -11,6 +11,7 @@ import { ManyToOnePropertyMetadata, ObjectPropertyMetadata, OneToOnePropertyMeta import { BaseEntity } from '../../../entity/base-entity.model'; import { ExcludeStrict, Newable } from '../../../types'; import { MetadataUtilities } from '../../../utilities'; +import { ObjectUtilities } from '../../../utilities/object.utilities'; /** * Transforms the given Zibri where filter to typeorm's FindOptionsWhere. @@ -45,7 +46,7 @@ function singleWhereFilterToFindOptionsWhere( properties: Record ): ToFindOptionsWhere { const res: ToFindOptionsWhere = {}; - for (const key of Object.keys(filter) as (keyof WhereFilter)[]) { + for (const key of ObjectUtilities.keys(filter)) { const prop: WhereFilterProperty | WhereFilterProperty[] | undefined = filter[key]; if (prop === undefined) { continue; @@ -148,7 +149,7 @@ const whereFilterKeysRecord: Record = { isIncludedIn: 'isIncludedIn' }; -const whereFilterKeys: WhereFilterKeys[] = Object.values(whereFilterKeysRecord); +const whereFilterKeys: WhereFilterKeys[] = ObjectUtilities.values(whereFilterKeysRecord); /** * Transforms a single where filter property to a typeorm FindOperator. @@ -176,13 +177,13 @@ function singlePropertyToFindOperator( } const operators: FindOperator[] = []; - const filterKeys: (keyof WhereFilterProperty)[] = Object.keys(property) as (keyof WhereFilterProperty)[]; + const filterKeys: (keyof (WhereFilterProperty & {}))[] = ObjectUtilities.keys(property); if (!filterKeys.length) { throw new Error('Empty where filter'); } for (const key of filterKeys) { if (!isWhereFilterKey(key)) { - throw new Error(`Unknown key "${key.toString()}" on where filer ${property}`); + throw new Error(`Unknown key "${key.toString()}" on where filter ${property}`); } const value: unknown = (property as Record)[key]; diff --git a/src/di/decorators/inject-repository.decorator.ts b/src/di/decorators/inject-repository.decorator.ts index fd2b269..5ab7d0b 100644 --- a/src/di/decorators/inject-repository.decorator.ts +++ b/src/di/decorators/inject-repository.decorator.ts @@ -1,15 +1,20 @@ +import { Repository } from '../../data-source'; import { BaseEntity } from '../../entity/base-entity.model'; import { Newable } from '../../types'; import { MetadataUtilities } from '../../utilities'; -import { DiToken } from '../models'; +import { DiToken, InjectionToken } from '../models'; + +const allRepositoryTokens: Record>> = {}; /** * Gets the repository token for the provided entity class. * @param entity - The entity class to resolve the repository token for. * @returns The DI token. */ -export function repositoryTokenFor(entity: Newable): string { - return `Repository<${entity.name}>`; +export function repositoryTokenFor>(entity: T): DiToken>> { + const key: string = `Repository<${entity.name}>`; + allRepositoryTokens[key] ??= new InjectionToken(key); + return allRepositoryTokens[key] as DiToken>>; } // eslint-disable-next-line jsdoc/require-returns diff --git a/src/di/default/zibri-di-providers.default.ts b/src/di/default/zibri-di-providers.default.ts index c8d1d41..653ec0a 100644 --- a/src/di/default/zibri-di-providers.default.ts +++ b/src/di/default/zibri-di-providers.default.ts @@ -3,31 +3,29 @@ import os from 'node:os'; import path from 'node:path'; import { ZIBRI_DI_TOKENS } from './zibri-di-tokens.default'; -import { AssetService, AssetServiceInterface } from '../../assets'; -import { AuthService, AuthServiceInterface, UserService, UserServiceInterface, TwoFactorService, TwoFactorServiceInterface } from '../../auth'; -import { BackupService, BackupServiceInterface } from '../../backup'; -import { CronService, CronServiceInterface } from '../../cron'; -import { DataSourceService, DataSourceServiceInterface } from '../../data-source'; -import { EmailConfigInput, EmailService, EmailServiceInterface, MailingListService, MailingListServiceInterface } from '../../email'; -import { errorHandler, GlobalErrorHandler } from '../../error-handling'; -import { HttpRequest } from '../../http'; -import { HttpClient, HttpClientInterface } from '../../http-client'; -import { FormatDateFn, FormatPercentFn, FormatPriceFn, LocalizeOptions, LocalizeOptionsInput } from '../../localization'; +import { AssetService } from '../../assets'; +import { AuthService, UserService, TwoFactorService } from '../../auth'; +import { BackupService } from '../../backup'; +import { CronService } from '../../cron'; +import { DataSourceService } from '../../data-source'; +import { EmailService, MailingListService } from '../../email'; +import { errorHandler } from '../../error-handling'; +import { HttpClient } from '../../http-client'; +import { LocalizeOptionsInput } from '../../localization'; import { formatDate } from '../../localization/formatting/format-date.function'; import { formatPercent } from '../../localization/formatting/format-percent.function'; import { formatPrice } from '../../localization/formatting/format-price.function'; -import { BaseLoggerTransportConfig, Logger, LoggerInterface, LoggerTransport, LogLevel } from '../../logging'; -import { MetricsServiceInterface, PrometheusMetricsService } from '../../metrics'; -import { MultithreadingOptions, MultithreadingService, MultithreadingServiceInterface } from '../../multithreading'; -import { OpenApiService, OpenApiServiceInterface } from '../../open-api'; -import { Parser, ParserInterface } from '../../parsing'; -import { getCurrentRequest, Router, RouterInterface } from '../../routing'; -import { OmitStrict } from '../../types'; +import { Logger, LoggerTransport, LogLevel } from '../../logging'; +import { PrometheusMetricsService } from '../../metrics'; +import { MultithreadingService } from '../../multithreading'; +import { OpenApiService } from '../../open-api'; +import { Parser } from '../../parsing'; +import { getCurrentRequest, Router } from '../../routing'; import { Ms } from '../../utilities'; -import { ValidationService, ValidationServiceInterface } from '../../validation'; -import { WebsocketOptions, WebsocketService, WebsocketServiceInterface } from '../../websocket'; +import { ValidationService } from '../../validation'; +import { WebsocketService } from '../../websocket'; import { inject } from '../inject.function'; -import { DiProvider } from '../models'; +import { DiTokenProviderRecord } from '../models'; const allThreads: number = os.availableParallelism(); @@ -39,65 +37,16 @@ const availableThreads: number = allThreads - reserveThreadsLibUv - reserveThrea const maxThreads: number = Math.max(1, availableThreads - 1); const maxPriorityThreads: number = availableThreads <= 1 ? 0 : 1; -type ZibriDiProvider = OmitStrict, 'token'>; - -type ZibriDiProviders = { - [ZIBRI_DI_TOKENS.ROUTER]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.LOGGER]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.LOGGER_TRANSPORTS]: ZibriDiProvider[]>, - [ZIBRI_DI_TOKENS.LOGGER_CLEANUP_AFTER_MS]: ZibriDiProvider>, - [ZIBRI_DI_TOKENS.METRICS_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.ASSET_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.BACKUP_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.GLOBAL_ERROR_HANDLER]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.OPEN_API_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.PARSER]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.VALIDATION_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.DATA_SOURCE_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.AUTH_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.TWO_FACTOR_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.OTP_HEADER]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.OTP_LENGTH]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.USER_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_SECRET]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_SECRET]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_EXPIRES_IN_MS]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_EXPIRES_IN_MS]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.JWT_PASSWORD_RESET_TOKEN_EXPIRES_IN_MS]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.JWT_CONFIRM_PASSWORD_RESET_URL]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.CRON_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.FILE_UPLOAD_TEMP_FOLDER]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.LOCALIZE_OPTIONS_INPUT]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.LOCALIZE_OPTIONS]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.FORMAT_DATE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.FORMAT_PRICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.FORMAT_PERCENT]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.EMAIL_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.EMAIL_CONFIG]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.MAILING_LIST_SERVICE]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.MAILING_LIST_SUBSCRIPTION_CONFIRMATION_TOKEN_EXPIRES_IN_MS]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.CURRENT_REQUEST]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.MULTITHREADING_OPTIONS]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.MULTITHREADING_SERVICE]: ZibriDiProvider, - // eslint-disable-next-line typescript/no-explicit-any - [ZIBRI_DI_TOKENS.WEBSOCKET_SERVICE]: ZibriDiProvider>, - [ZIBRI_DI_TOKENS.WEBSOCKET_OPTIONS]: ZibriDiProvider, - [ZIBRI_DI_TOKENS.HTTP_CLIENT]: ZibriDiProvider -}; - -export const ZIBRI_DI_PROVIDERS: Record< - typeof ZIBRI_DI_TOKENS[keyof typeof ZIBRI_DI_TOKENS], - ZibriDiProvider -> = { - [ZIBRI_DI_TOKENS.ROUTER]: { useClass: Router }, - [ZIBRI_DI_TOKENS.LOGGER]: { useClass: Logger }, - [ZIBRI_DI_TOKENS.LOGGER_TRANSPORTS]: { +export const ZIBRI_DI_PROVIDERS: DiTokenProviderRecord = { + ROUTER: { useClass: Router }, + LOGGER: { useClass: Logger }, + LOGGER_TRANSPORTS: { useFactory: () => [ LoggerTransport.console(LogLevel.INFO), LoggerTransport.db(LogLevel.INFO) ] }, - [ZIBRI_DI_TOKENS.LOGGER_CLEANUP_AFTER_MS]: { + LOGGER_CLEANUP_AFTER_MS: { useFactory: () => ({ [LogLevel.DEBUG]: Ms.WEEK * 2, [LogLevel.INFO]: Ms.WEEK * 2, @@ -106,30 +55,30 @@ export const ZIBRI_DI_PROVIDERS: Record< [LogLevel.CRITICAL]: Ms.WEEK * 2 }) }, - [ZIBRI_DI_TOKENS.METRICS_SERVICE]: { useClass: PrometheusMetricsService }, - [ZIBRI_DI_TOKENS.ASSET_SERVICE]: { useClass: AssetService }, - [ZIBRI_DI_TOKENS.BACKUP_SERVICE]: { useClass: BackupService }, - [ZIBRI_DI_TOKENS.GLOBAL_ERROR_HANDLER]: { useFactory: () => errorHandler }, - [ZIBRI_DI_TOKENS.OPEN_API_SERVICE]: { useClass: OpenApiService }, - [ZIBRI_DI_TOKENS.PARSER]: { useClass: Parser }, - [ZIBRI_DI_TOKENS.VALIDATION_SERVICE]: { useClass: ValidationService }, - [ZIBRI_DI_TOKENS.DATA_SOURCE_SERVICE]: { useClass: DataSourceService }, - [ZIBRI_DI_TOKENS.AUTH_SERVICE]: { useFactory: () => new AuthService() }, - [ZIBRI_DI_TOKENS.TWO_FACTOR_SERVICE]: { useFactory: () => new TwoFactorService() }, - [ZIBRI_DI_TOKENS.OTP_HEADER]: { useFactory: () => 'X-Authorization-OTP' }, - [ZIBRI_DI_TOKENS.OTP_LENGTH]: { useFactory: () => 6 }, - [ZIBRI_DI_TOKENS.USER_SERVICE]: { useFactory: () => new UserService() }, - [ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_SECRET]: { useFactory: () => undefined }, - [ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_SECRET]: { useFactory: () => undefined }, - [ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_EXPIRES_IN_MS]: { useFactory: () => Ms.HOUR }, - [ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_EXPIRES_IN_MS]: { useFactory: () => 100 * Ms.DAY }, - [ZIBRI_DI_TOKENS.CRON_SERVICE]: { useClass: CronService }, - [ZIBRI_DI_TOKENS.EMAIL_SERVICE]: { useClass: EmailService }, - [ZIBRI_DI_TOKENS.MAILING_LIST_SERVICE]: { useClass: MailingListService }, - [ZIBRI_DI_TOKENS.MAILING_LIST_SUBSCRIPTION_CONFIRMATION_TOKEN_EXPIRES_IN_MS]: { useFactory: () => Ms.DAY }, - [ZIBRI_DI_TOKENS.FILE_UPLOAD_TEMP_FOLDER]: { useFactory: () => path.join(__dirname, 'temp') }, - [ZIBRI_DI_TOKENS.LOCALIZE_OPTIONS_INPUT]: { useFactory: () => ({}) }, - [ZIBRI_DI_TOKENS.LOCALIZE_OPTIONS]: { + METRICS_SERVICE: { useClass: PrometheusMetricsService }, + ASSET_SERVICE: { useClass: AssetService }, + BACKUP_SERVICE: { useClass: BackupService }, + GLOBAL_ERROR_HANDLER: { useFactory: () => errorHandler }, + OPEN_API_SERVICE: { useClass: OpenApiService }, + PARSER: { useClass: Parser }, + VALIDATION_SERVICE: { useClass: ValidationService }, + DATA_SOURCE_SERVICE: { useClass: DataSourceService }, + AUTH_SERVICE: { useFactory: () => new AuthService() }, + TWO_FACTOR_SERVICE: { useFactory: () => new TwoFactorService() }, + OTP_HEADER: { useFactory: () => 'X-Authorization-OTP' }, + OTP_LENGTH: { useFactory: () => 6 }, + USER_SERVICE: { useFactory: () => new UserService() }, + JWT_ACCESS_TOKEN_SECRET: { useFactory: () => undefined }, + JWT_REFRESH_TOKEN_SECRET: { useFactory: () => undefined }, + JWT_ACCESS_TOKEN_EXPIRES_IN_MS: { useFactory: () => Ms.HOUR }, + JWT_REFRESH_TOKEN_EXPIRES_IN_MS: { useFactory: () => 100 * Ms.DAY }, + CRON_SERVICE: { useClass: CronService }, + EMAIL_SERVICE: { useClass: EmailService }, + MAILING_LIST_SERVICE: { useClass: MailingListService }, + MAILING_LIST_SUBSCRIPTION_CONFIRMATION_TOKEN_EXPIRES_IN_MS: { useFactory: () => Ms.DAY }, + FILE_UPLOAD_TEMP_FOLDER: { useFactory: () => path.join(__dirname, 'temp') }, + LOCALIZE_OPTIONS_INPUT: { useFactory: () => ({}) }, + LOCALIZE_OPTIONS: { useFactory: () => { const input: LocalizeOptionsInput = inject(ZIBRI_DI_TOKENS.LOCALIZE_OPTIONS_INPUT); return { @@ -139,14 +88,14 @@ export const ZIBRI_DI_PROVIDERS: Record< }; } }, - [ZIBRI_DI_TOKENS.FORMAT_DATE]: { useFactory: () => formatDate }, - [ZIBRI_DI_TOKENS.FORMAT_PRICE]: { useFactory: () => formatPrice }, - [ZIBRI_DI_TOKENS.FORMAT_PERCENT]: { useFactory: () => formatPercent }, - [ZIBRI_DI_TOKENS.EMAIL_CONFIG]: { useFactory: () => undefined }, - [ZIBRI_DI_TOKENS.JWT_PASSWORD_RESET_TOKEN_EXPIRES_IN_MS]: { useFactory: () => 300000 }, - [ZIBRI_DI_TOKENS.JWT_CONFIRM_PASSWORD_RESET_URL]: { useFactory: () => undefined }, - [ZIBRI_DI_TOKENS.CURRENT_REQUEST]: { useFactory: () => getCurrentRequest() }, - [ZIBRI_DI_TOKENS.MULTITHREADING_OPTIONS]: { + FORMAT_DATE: { useFactory: () => formatDate }, + FORMAT_PRICE: { useFactory: () => formatPrice }, + FORMAT_PERCENT: { useFactory: () => formatPercent }, + EMAIL_CONFIG: { useFactory: () => undefined }, + JWT_PASSWORD_RESET_TOKEN_EXPIRES_IN_MS: { useFactory: () => 300000 }, + JWT_CONFIRM_PASSWORD_RESET_URL: { useFactory: () => undefined }, + CURRENT_REQUEST: { useFactory: () => getCurrentRequest() }, + MULTITHREADING_OPTIONS: { useFactory: () => ({ maxThreads, maxPriorityThreads, @@ -154,8 +103,8 @@ export const ZIBRI_DI_PROVIDERS: Record< defaultTimeoutPriorityMs: Ms.MINUTE * 5 }) }, - [ZIBRI_DI_TOKENS.MULTITHREADING_SERVICE]: { useClass: MultithreadingService }, - [ZIBRI_DI_TOKENS.WEBSOCKET_SERVICE]: { useClass: WebsocketService }, - [ZIBRI_DI_TOKENS.WEBSOCKET_OPTIONS]: { useFactory: () => ({ timeoutInMs: Ms.SECOND * 5, isAllowedToConnect: () => true }) }, - [ZIBRI_DI_TOKENS.HTTP_CLIENT]: { useClass: HttpClient } -} satisfies ZibriDiProviders; \ No newline at end of file + MULTITHREADING_SERVICE: { useClass: MultithreadingService }, + WEBSOCKET_SERVICE: { useClass: WebsocketService }, + WEBSOCKET_OPTIONS: { useFactory: () => ({ timeoutInMs: Ms.SECOND * 5, isAllowedToConnect: () => true }) }, + HTTP_CLIENT: { useClass: HttpClient } +}; \ No newline at end of file diff --git a/src/di/default/zibri-di-tokens.default.ts b/src/di/default/zibri-di-tokens.default.ts index 61655e3..3822b50 100644 --- a/src/di/default/zibri-di-tokens.default.ts +++ b/src/di/default/zibri-di-tokens.default.ts @@ -1,47 +1,74 @@ +import { AssetServiceInterface } from '../../assets'; +import { AuthServiceInterface, TwoFactorServiceInterface, UserServiceInterface } from '../../auth'; +import { BackupServiceInterface } from '../../backup'; +import { CronServiceInterface } from '../../cron'; +import { DataSourceServiceInterface } from '../../data-source'; +import { EmailConfigInput, EmailServiceInterface, MailingListServiceInterface } from '../../email'; +import { GlobalErrorHandler } from '../../error-handling'; +import { HttpRequest } from '../../http'; +import { HttpClientInterface } from '../../http-client'; +import { FormatDateFn, FormatPercentFn, FormatPriceFn, LocalizeOptions, LocalizeOptionsInput } from '../../localization'; +import { BaseLoggerTransportConfig, LoggerInterface, LoggerTransport, LogLevel } from '../../logging'; +import { MetricsServiceInterface } from '../../metrics'; +import { MultithreadingOptions, MultithreadingServiceInterface } from '../../multithreading'; +import { OpenApiServiceInterface } from '../../open-api'; +import { ParserInterface } from '../../parsing'; +import { RouterInterface } from '../../routing'; +import { ValidationServiceInterface } from '../../validation'; +import { WebsocketOptions, WebsocketServiceInterface } from '../../websocket'; +import { InjectionToken, TokenRecord } from '../models'; + +// eslint-disable-next-line jsdoc/require-jsdoc +function ziToken(k: `zi.${string}`): InjectionToken { + return new InjectionToken(k); +} /** * Injection Tokens used and provided by Zibri. */ // eslint-disable-next-line typescript/typedef export const ZIBRI_DI_TOKENS = { - ROUTER: 'zi.router', - LOGGER: 'zi.logger', - LOGGER_TRANSPORTS: 'zi.logger_transports', - LOGGER_CLEANUP_AFTER_MS: 'zi.logger_cleanup_after_ms', - METRICS_SERVICE: 'zi.metrics_service', - ASSET_SERVICE: 'zi.asset_service', - BACKUP_SERVICE: 'zi.backup_service', - GLOBAL_ERROR_HANDLER: 'zi.global_error_handler', - OPEN_API_SERVICE: 'zi.open_api_service', - AUTH_SERVICE: 'zi.auth_service', - TWO_FACTOR_SERVICE: 'zi.two_factor_service', - OTP_HEADER: 'zi.otp_header', - OTP_LENGTH: 'zi.otp_length', - PARSER: 'zi.parser_service', - VALIDATION_SERVICE: 'zi.validation_service', - DATA_SOURCE_SERVICE: 'zi.data_source_service', - JWT_ACCESS_TOKEN_SECRET: 'zi.jwt_access_token_secret', - JWT_ACCESS_TOKEN_EXPIRES_IN_MS: 'zi.jwt_access_token_expires_in_ms', - JWT_REFRESH_TOKEN_SECRET: 'zi.jwt_refresh_token_secret', - JWT_REFRESH_TOKEN_EXPIRES_IN_MS: 'zi.jwt_refresh_token_expires_in_ms', - JWT_PASSWORD_RESET_TOKEN_EXPIRES_IN_MS: 'zi.jwt_password_reset_token_expires_in_ms', - JWT_CONFIRM_PASSWORD_RESET_URL: 'zi.jwt_confirm_password_reset_url', - MAILING_LIST_SUBSCRIPTION_CONFIRMATION_TOKEN_EXPIRES_IN_MS: 'zi.mailing_list_subscription_confirmation_token_expires_in_ms', - USER_SERVICE: 'zi.user_service', - CRON_SERVICE: 'zi.cron_service', - FILE_UPLOAD_TEMP_FOLDER: 'zi.file_upload_temp_folder', - LOCALIZE_OPTIONS_INPUT: 'zi.localize_options_input', - LOCALIZE_OPTIONS: 'zi.localize_options', - FORMAT_DATE: 'zi.format_date', - FORMAT_PRICE: 'zi.format_price', - FORMAT_PERCENT: 'zi.format_percent', - EMAIL_SERVICE: 'zi.email_service', - EMAIL_CONFIG: 'zi.email_config', - MAILING_LIST_SERVICE: 'zi.mailing_list_service', - CURRENT_REQUEST: 'zi.current_request', - MULTITHREADING_SERVICE: 'zi.multithreading_service', - MULTITHREADING_OPTIONS: 'zi.multithreading_options', - WEBSOCKET_SERVICE: 'zi.websocket_service', - WEBSOCKET_OPTIONS: 'zi.websocket_options', - HTTP_CLIENT: 'zi.http_client' -} as const satisfies Record; \ No newline at end of file + ROUTER: ziToken('zi.router'), + LOGGER: ziToken('zi.logger'), + LOGGER_TRANSPORTS: ziToken[]>('zi.logger_transports'), + LOGGER_CLEANUP_AFTER_MS: ziToken>('zi.logger_cleanup_after_ms'), + METRICS_SERVICE: ziToken('zi.metrics_service'), + ASSET_SERVICE: ziToken('zi.asset_service'), + BACKUP_SERVICE: ziToken('zi.backup_service'), + GLOBAL_ERROR_HANDLER: ziToken('zi.global_error_handler'), + OPEN_API_SERVICE: ziToken('zi.open_api_service'), + AUTH_SERVICE: ziToken('zi.auth_service'), + TWO_FACTOR_SERVICE: ziToken('zi.two_factor_service'), + OTP_HEADER: ziToken('zi.otp_header'), + OTP_LENGTH: ziToken('zi.otp_length'), + PARSER: ziToken('zi.parser_service'), + VALIDATION_SERVICE: ziToken('zi.validation_service'), + DATA_SOURCE_SERVICE: ziToken('zi.data_source_service'), + JWT_ACCESS_TOKEN_SECRET: ziToken('zi.jwt_access_token_secret'), + JWT_ACCESS_TOKEN_EXPIRES_IN_MS: ziToken('zi.jwt_access_token_expires_in_ms'), + JWT_REFRESH_TOKEN_SECRET: ziToken('zi.jwt_refresh_token_secret'), + JWT_REFRESH_TOKEN_EXPIRES_IN_MS: ziToken('zi.jwt_refresh_token_expires_in_ms'), + JWT_PASSWORD_RESET_TOKEN_EXPIRES_IN_MS: ziToken('zi.jwt_password_reset_token_expires_in_ms'), + JWT_CONFIRM_PASSWORD_RESET_URL: ziToken('zi.jwt_confirm_password_reset_url'), + MAILING_LIST_SUBSCRIPTION_CONFIRMATION_TOKEN_EXPIRES_IN_MS: ziToken( + 'zi.mailing_list_subscription_confirmation_token_expires_in_ms' + ), + USER_SERVICE: ziToken('zi.user_service'), + CRON_SERVICE: ziToken('zi.cron_service'), + FILE_UPLOAD_TEMP_FOLDER: ziToken('zi.file_upload_temp_folder'), + LOCALIZE_OPTIONS_INPUT: ziToken('zi.localize_options_input'), + LOCALIZE_OPTIONS: ziToken('zi.localize_options'), + FORMAT_DATE: ziToken('zi.format_date'), + FORMAT_PRICE: ziToken('zi.format_price'), + FORMAT_PERCENT: ziToken('zi.format_percent'), + EMAIL_SERVICE: ziToken('zi.email_service'), + EMAIL_CONFIG: ziToken('zi.email_config'), + MAILING_LIST_SERVICE: ziToken('zi.mailing_list_service'), + CURRENT_REQUEST: ziToken('zi.current_request'), + MULTITHREADING_SERVICE: ziToken('zi.multithreading_service'), + MULTITHREADING_OPTIONS: ziToken('zi.multithreading_options'), + // eslint-disable-next-line typescript/no-explicit-any + WEBSOCKET_SERVICE: ziToken>('zi.websocket_service'), + WEBSOCKET_OPTIONS: ziToken('zi.websocket_options'), + HTTP_CLIENT: ziToken('zi.http_client') +} as const satisfies TokenRecord; \ No newline at end of file diff --git a/src/di/di-container.ts b/src/di/di-container.ts index 90467a3..7a7b05b 100644 --- a/src/di/di-container.ts +++ b/src/di/di-container.ts @@ -1,10 +1,10 @@ import { GlobalRegistry } from '../global'; import { Newable } from '../types'; -import { MetadataUtilities } from '../utilities'; +import { MetadataUtilities, ObjectUtilities } from '../utilities'; import { ZIBRI_DI_TOKENS } from './default'; import { ZIBRI_DI_PROVIDERS } from './default/zibri-di-providers.default'; import { NoProviderError } from './errors/no-provider.error'; -import { DiToken, DiProvider } from './models'; +import { DiToken, DiProvider, providersFromTokenRecord } from './models'; /** * The dependency injection container. @@ -18,9 +18,9 @@ export class DiContainer { for (const injectable of GlobalRegistry.injectables) { this.register(injectable); } - for (const key in ZIBRI_DI_TOKENS) { - const token: typeof ZIBRI_DI_TOKENS[keyof typeof ZIBRI_DI_TOKENS] = ZIBRI_DI_TOKENS[key as keyof typeof ZIBRI_DI_TOKENS]; - this.register({ token, ...ZIBRI_DI_PROVIDERS[token] }); + const defaultProviders: DiProvider[] = providersFromTokenRecord(ZIBRI_DI_TOKENS, ZIBRI_DI_PROVIDERS); + for (const provider of defaultProviders) { + this.register(provider); } } @@ -39,9 +39,6 @@ export class DiContainer { * @throws When the provider is invalid. */ register(provider: DiProvider): void { - if (!provider.useClass && !provider.useFactory) { - throw new Error(`Provider for token ${provider.token.toString()} must specify useClass or useFactory`); - } this.providers.set(provider.token, provider); } @@ -71,10 +68,9 @@ export class DiContainer { throw new NoProviderError(token, resolvingStack); } - if (!provider.useClass && !provider.useFactory) { - throw new Error(`Provider for ${provider.token.toString()} is invalid`); + if (provider.useClass || provider.useFactory) { + resolvingStack.push(provider.useClass ?? provider.useFactory); } - resolvingStack.push((provider.useClass ?? provider.useFactory) as Function); const instance: T = this.createInstanceFromProvider(provider, resolvingStack); resolvingStack.pop(); @@ -84,9 +80,11 @@ export class DiContainer { } private createInstanceFromProvider(provider: DiProvider, resolvingStack: Function[]): T { - const provide: Newable | ((...deps: unknown[]) => T) | undefined = provider.useClass ?? provider.useFactory; + const provide: Newable | ((...deps: unknown[]) => T) | T | undefined = provider.useClass + ?? provider.useFactory + ?? provider.useValue; - if (!provide) { + if (provide == undefined) { throw new Error(`Provider for ${provider.token.toString()} is invalid`); } @@ -94,7 +92,7 @@ export class DiContainer { const paramTypes: unknown[] = MetadataUtilities.getParamTypes(provide); // compute max number of parameters to resolve - const highestExplicitIndex: number = Object.keys(explicitTokens).reduce((acc, k) => { + const highestExplicitIndex: number = ObjectUtilities.keys(explicitTokens).reduce((acc, k) => { const idx: number = Number(k); return Number.isFinite(idx) ? Math.max(acc, idx) : acc; }, -1); @@ -117,7 +115,10 @@ export class DiContainer { if (provider.useFactory) { return provider.useFactory(...deps); } + if ('useValue' in provider) { + return provider.useValue; + } - throw new Error(`Provider for ${provider.token.toString()} is invalid`); + throw new Error(`Provider for ${(provider as DiProvider).token.toString()} is invalid`); } } \ No newline at end of file diff --git a/src/di/errors/no-provider.error.ts b/src/di/errors/no-provider.error.ts index 00be4b1..e4a28bb 100644 --- a/src/di/errors/no-provider.error.ts +++ b/src/di/errors/no-provider.error.ts @@ -1,13 +1,6 @@ import { getDependencyStackTrace } from './get-dependency-stack-trace.function'; import { MetadataUtilities } from '../../utilities'; -import { DiToken } from '../models'; - -// eslint-disable-next-line jsdoc/require-jsdoc -function tokenIsPrimitiveValue(token: DiToken): boolean { - return [String, Number, Boolean, Date].includes( - token as unknown as StringConstructor | NumberConstructor | BooleanConstructor | DateConstructor - ); -} +import { DiToken, InjectionToken } from '../models'; /** * Get the no providers error message from the provided token and stack. @@ -16,16 +9,13 @@ function tokenIsPrimitiveValue(token: DiToken): boolean { * @returns The message as a string. */ function getNoProviderMessage(token: DiToken, resolvingStack: Function[]): string { - if (typeof token === 'string') { - if (token.startsWith('Repository<') && token.endsWith('>')) { - const entity: string = token.split('Repository<')[1].split('>')[0]; - return `No provider for repository token "${token}". Did you forget to register the entity "${entity}" in a data source?`; + if (token instanceof InjectionToken) { + if (token.key.startsWith('Repository<') && token.key.endsWith('>')) { + const entity: string = token.key.split('Repository<')[1].split('>')[0]; + return `No provider for repository token "${token.key}". Did you forget to register the entity "${entity}" in a data source?`; } - return `No provider for custom token "${token}"`; - } - if (tokenIsPrimitiveValue(token)) { if (!resolvingStack.length) { - return `No provider for token "${token.name}". Did you forget to decorate it with @Inject()?`; + return `No provider for token "${token.key}". Did you forget to decorate it with @Inject()?`; } const currentClass: Function = resolvingStack[resolvingStack.length - 1]; const paramTypes: unknown[] = MetadataUtilities.getParamTypes(currentClass); diff --git a/src/di/index.ts b/src/di/index.ts index 4ad8cfb..6ba9fd5 100644 --- a/src/di/index.ts +++ b/src/di/index.ts @@ -1,4 +1,5 @@ export * from './decorators'; export * from './models'; export * from './default'; -export * from './inject.function'; \ No newline at end of file +export * from './inject.function'; +export * from './errors'; \ No newline at end of file diff --git a/src/di/models/di-provider.model.ts b/src/di/models/di-provider.model.ts index 0dcc229..50a4a62 100644 --- a/src/di/models/di-provider.model.ts +++ b/src/di/models/di-provider.model.ts @@ -1,20 +1,58 @@ -import { DiToken } from '.'; -import { Newable } from '../../types'; +import { DiToken } from './di-token.model'; +import { Newable, OmitStrict } from '../../types'; /** * A DI provider. */ -export type DiProvider = { +export type DiProvider = ClassDiProvider | FactoryDiProvider | ValueDiProvider; + +/** + * A DiProvider without its token. + */ +export type DiProviderWithoutToken = OmitStrict, 'token'> + | OmitStrict, 'token'> + | OmitStrict, 'token'>; + +// eslint-disable-next-line jsdoc/require-jsdoc +type BaseDiProvider = { /** * The token under which the value should be registered. */ - token: DiToken, + token: DiToken +}; + +// eslint-disable-next-line jsdoc/require-jsdoc +type ClassDiProvider = BaseDiProvider & { /** * A class to register for the token. */ - useClass?: Newable, + useClass: Newable, + // eslint-disable-next-line jsdoc/require-jsdoc + useFactory?: never, + // eslint-disable-next-line jsdoc/require-jsdoc + useValue?: never +}; + +// eslint-disable-next-line jsdoc/require-jsdoc +type FactoryDiProvider = BaseDiProvider & { /** * A factory function that resolves the value to register for the token. */ - useFactory?: (...deps: unknown[]) => T + useFactory: (...deps: unknown[]) => T, + // eslint-disable-next-line jsdoc/require-jsdoc + useClass?: never, + // eslint-disable-next-line jsdoc/require-jsdoc + useValue?: never +}; + +// eslint-disable-next-line jsdoc/require-jsdoc +type ValueDiProvider = BaseDiProvider & { + /** + * A value to register for the token. + */ + useValue: T, + // eslint-disable-next-line jsdoc/require-jsdoc + useFactory?: never, + // eslint-disable-next-line jsdoc/require-jsdoc + useClass?: never }; \ No newline at end of file diff --git a/src/di/models/di-token.model.ts b/src/di/models/di-token.model.ts index f4b4126..6ff9c1b 100644 --- a/src/di/models/di-token.model.ts +++ b/src/di/models/di-token.model.ts @@ -1,6 +1,48 @@ +import { DiProvider, DiProviderWithoutToken } from './di-provider.model'; +import { InjectionToken } from './injection-token.model'; import { Newable } from '../../types'; +import { ObjectUtilities } from '../../utilities'; /** * A token where DI values can be registered under. */ -export type DiToken = Newable | string; \ No newline at end of file +export type DiToken = Newable | InjectionToken; + +// eslint-disable-next-line jsdoc/require-jsdoc +type UnwrapDiToken + = T extends InjectionToken ? U + // eslint-disable-next-line typescript/no-explicit-any + : T extends new (...args: any[]) => infer U ? U + : unknown; + +/** + * A record of DI tokens. + */ +export type TokenRecord = Record, DiToken>; + +/** + * A record that maps the given DI tokens to a matching provider. + */ +export type DiTokenProviderRecord = { + [K in keyof Tokens]: DiProviderWithoutToken>; +}; + +/** + * Resolves the DI providers from the given tokens and values. + * @param tokens - The token record. + * @param providers - The provider values matching the tokens. + * @returns An array of the built together DI providers. + */ +export function providersFromTokenRecord( + tokens: Tokens, + providers: DiTokenProviderRecord +): DiProvider[] { + const res: DiProvider[] = []; + + for (const k of ObjectUtilities.keys(tokens)) { + const token: DiToken = (tokens as TokenRecord)[k]; + res.push({ token, ...providers[k] }); + } + + return res; +} \ No newline at end of file diff --git a/src/di/models/index.ts b/src/di/models/index.ts index b4eded7..3527043 100644 --- a/src/di/models/index.ts +++ b/src/di/models/index.ts @@ -1,2 +1,3 @@ export * from './di-token.model'; -export * from './di-provider.model'; \ No newline at end of file +export * from './di-provider.model'; +export * from './injection-token.model'; \ No newline at end of file diff --git a/src/di/models/injection-token.model.ts b/src/di/models/injection-token.model.ts new file mode 100644 index 0000000..28f90ed --- /dev/null +++ b/src/di/models/injection-token.model.ts @@ -0,0 +1,25 @@ + +const allInjectionTokenKeys: string[] = []; + +/** + * Defines an injection token for the given key. + */ +export class InjectionToken { + // eslint-disable-next-line jsdoc/require-jsdoc + protected readonly __brand?: T; + + constructor(readonly key: string) { + if (allInjectionTokenKeys.includes(key)) { + throw new Error([ + `An InjectionToken with the key "${key}" already exists.`, + 'If you wanted to override it, you need to use the existing InjectionToken instance.' + ].join('\n')); + } + allInjectionTokenKeys.push(key); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + toString(): string { + return this.key; + } +} \ No newline at end of file diff --git a/src/email/mailing-list/mailing-list.service.ts b/src/email/mailing-list/mailing-list.service.ts index 683961e..ec98ca6 100644 --- a/src/email/mailing-list/mailing-list.service.ts +++ b/src/email/mailing-list/mailing-list.service.ts @@ -10,7 +10,7 @@ import { BaseEmailTemplateData, renderTemplate, renderTemplateString } from '../ import { Route } from '../../routing'; import { EmailServiceInterface } from '../email-service.interface'; import { MailingList, MailingListSubscriber, MailingListSubscriptionConfirmationToken, MailingListSubscriptionConfirmationTokenCreateData } from './models'; -import { chunkedPromiseAll, validateEntitiesRegistered } from '../../utilities'; +import { PromiseUtilities, validateEntitiesRegistered } from '../../utilities'; import { EmailPriority } from '../models'; /** @@ -66,8 +66,9 @@ export class MailingListService implements MailingListServiceInterface { // eslint-disable-next-line jsdoc/require-jsdoc async queueEmailForList(listId: string, data: MailingListQueueEmailData): Promise { const list: MailingList = await this.mailingListRepository.findById(listId); - await chunkedPromiseAll( - list.subscribers.map(async s => { + await PromiseUtilities.allChunked( + list.subscribers, + async s => { const base: BaseEmailTemplateData['base'] = { ...data.templateData.base, baseUrl: GlobalRegistry.getAppData('baseUrl') ?? '', @@ -92,7 +93,7 @@ export class MailingListService implements MailingListServiceInterface { persist: false, ...data }); - }) + } ); } diff --git a/src/entity/any-object.model.ts b/src/entity/any-object.model.ts new file mode 100644 index 0000000..a64670a --- /dev/null +++ b/src/entity/any-object.model.ts @@ -0,0 +1,6 @@ +/** + * Base class for any object. + */ +export class AnyObject { + [key: string]: unknown +} \ No newline at end of file diff --git a/src/entity/generation/generate-entity-file.function.ts b/src/entity/generation/generate-entity-file.function.ts index 1600351..e157734 100644 --- a/src/entity/generation/generate-entity-file.function.ts +++ b/src/entity/generation/generate-entity-file.function.ts @@ -2,7 +2,7 @@ import { getEntityFileName } from './get-entity-file-name.function'; import { EntityGenerationProvider } from './providers/entity-generation-provider.interface'; import { OpenApiReferenceObject, OpenApiSchemaObject, OpenApiSchemas } from '../../open-api'; -import { toPascalCase } from '../../utilities'; +import { ObjectUtilities, toPascalCase } from '../../utilities'; import { addImportStatement } from '../../utilities/add-import-statement.function'; /** @@ -64,7 +64,7 @@ export function generateEntityFile( const foundSchemas: OpenApiSchemas = {}; - for (const [propName, propSchema] of Object.entries(properties)) { + for (const [propName, propSchema] of ObjectUtilities.entries(properties)) { const isRequired: boolean = required.has(propName); const optional: string = isRequired ? '!' : '?'; const { type, isRef, schema } = mapSchemaToTsType(propSchema, propName); diff --git a/src/entity/generation/generate-entity-files-for-provider.function.ts b/src/entity/generation/generate-entity-files-for-provider.function.ts index c522c96..0f52f44 100644 --- a/src/entity/generation/generate-entity-files-for-provider.function.ts +++ b/src/entity/generation/generate-entity-files-for-provider.function.ts @@ -1,12 +1,12 @@ import { mkdir } from 'fs/promises'; import path from 'path'; -import { generateEntityFile } from './generate-entity-file.function'; +import { generateEntityFile, GenerateEntityFileResult } from './generate-entity-file.function'; import { getEntityFileName } from './get-entity-file-name.function'; import { EntityGenerationProvider } from './providers'; import { warn } from '../../logging/logger.helpers'; import { OpenApiDefinition, OpenApiOperation, OpenApiReferenceObject, OpenApiResponseObject, OpenApiSchemaObject, OpenApiSchemas } from '../../open-api'; -import { pathExists, toKebabCase, toPascalCase } from '../../utilities'; +import { ObjectUtilities, pathExists, toKebabCase, toPascalCase } from '../../utilities'; /** * All data needed to generate a file. @@ -53,7 +53,7 @@ export async function generateEntityFilesForProvider( const definition: OpenApiDefinition = await provider.resolveSpec(); const schemas: OpenApiSchemas = definition.components?.schemas ?? {}; if (provider.generateSchemasFromPaths) { - for (const [key, path] of Object.entries(definition.paths ?? {})) { + for (const [key, path] of ObjectUtilities.entries(definition.paths ?? {})) { for (const method of OP_METHODS) { const operation: OpenApiOperation | undefined = path[method]; if (!operation) { @@ -63,7 +63,7 @@ export async function generateEntityFilesForProvider( const baseFromOp: string = operation.operationId ?? toPascalCase(key); if (operation.requestBody && !('$ref' in operation.requestBody)) { - for (const media of Object.values(operation.requestBody.content ?? {})) { + for (const media of ObjectUtilities.values(operation.requestBody.content ?? {})) { if (!media.schema) { continue; } @@ -71,7 +71,7 @@ export async function generateEntityFilesForProvider( } } - for (const value of Object.values(operation.responses ?? {})) { + for (const value of ObjectUtilities.values(operation.responses ?? {})) { // eslint-disable-next-line typescript/no-unsafe-assignment const response: OpenApiResponseObject | OpenApiReferenceObject | undefined = value; if (response == undefined) { @@ -81,7 +81,7 @@ export async function generateEntityFilesForProvider( // ignore response $ref (could point to components.responses); responses often wrap schemas inside content continue; } - for (const media of Object.values(response.content ?? {})) { + for (const media of ObjectUtilities.values(response.content ?? {})) { if (!media.schema) { continue; } @@ -93,7 +93,7 @@ export async function generateEntityFilesForProvider( } } - if (Object.keys(schemas).length === 0) { + if (ObjectUtilities.keys(schemas).length === 0) { warn(`Could not find any schemas on spec for provider with prefix "${provider.prefix}"`); return { filesToGenerate: [], @@ -107,8 +107,8 @@ export async function generateEntityFilesForProvider( const processedSchemas: Set = new Set(); let foundSchemas: OpenApiSchemas = schemas; - while (Object.keys(foundSchemas).length) { - const entries: [string, OpenApiSchemaObject | OpenApiReferenceObject][] = Object.entries(foundSchemas); + while (ObjectUtilities.keys(foundSchemas).length) { + const entries: [string, OpenApiSchemaObject | OpenApiReferenceObject][] = ObjectUtilities.entries(foundSchemas); const nextFoundSchemas: OpenApiSchemas = {}; for (const [key, value] of entries) { // skip if we already generated this schema earlier @@ -128,11 +128,10 @@ export async function generateEntityFilesForProvider( continue; } - // eslint-disable-next-line typescript/typedef - const generatedData = generateEntityFile(provider, key, value); + const generatedData: GenerateEntityFileResult = generateEntityFile(provider, key, value); // accumulate any inline schemas discovered while generating this file - for (const inlineKey of Object.keys(generatedData.foundSchemas)) { + for (const inlineKey of ObjectUtilities.keys(generatedData.foundSchemas)) { if (!processedSchemas.has(inlineKey) && !(inlineKey in nextFoundSchemas)) { nextFoundSchemas[inlineKey] = generatedData.foundSchemas[inlineKey]; } diff --git a/src/entity/index.ts b/src/entity/index.ts index 64b9c72..24543d3 100644 --- a/src/entity/index.ts +++ b/src/entity/index.ts @@ -4,4 +4,5 @@ export * from './intersection-class.model'; export * from './partial-class.model'; export * from './pick-class.model'; export * from './models'; -export * from './generation'; \ No newline at end of file +export * from './generation'; +export * from './any-object.model'; \ No newline at end of file diff --git a/src/entity/partial-class.model.ts b/src/entity/partial-class.model.ts index eeac1a5..08fe3f0 100644 --- a/src/entity/partial-class.model.ts +++ b/src/entity/partial-class.model.ts @@ -1,6 +1,7 @@ import type { Newable } from '../types'; import type { PropertyMetadata } from './decorators'; import { MetadataUtilities } from '../utilities/metadata.utilities'; +import { ObjectUtilities } from '../utilities/object.utilities'; // eslint-disable-next-line jsdoc/require-returns /** @@ -15,7 +16,7 @@ export function PartialClass( const original: Record = MetadataUtilities.getModelProperties(Base); const partialMeta: Record = {}; - for (const [prop, meta] of Object.entries(original)) { + for (const [prop, meta] of ObjectUtilities.entries(original)) { partialMeta[prop] = 'required' in meta ? { ...meta, required: false } : meta; } MetadataUtilities.setModelProperties(PartialClass, partialMeta); diff --git a/src/handlebars/generate-handlebar-type-files.function.ts b/src/handlebars/generate-handlebar-type-files.function.ts index 0ad2bf0..83535ff 100644 --- a/src/handlebars/generate-handlebar-type-files.function.ts +++ b/src/handlebars/generate-handlebar-type-files.function.ts @@ -4,7 +4,7 @@ import { dirname, basename, join } from 'path'; import { sync as globSync } from 'glob'; import { parse } from 'handlebars'; -import { pathExists } from '../utilities'; +import { ObjectUtilities, pathExists } from '../utilities'; import { AstProgram } from './ast.model'; import { resolveAllArrayKeys } from './resolve-all-array-keys.function'; import { resolveTree } from './resolve-tree.function'; @@ -75,7 +75,7 @@ function generateInterfaceLines( currentPath = '' ): string[] { const lines: string[] = []; - const keys: string[] = Object.keys(tree); + const keys: string[] = ObjectUtilities.keys(tree); for (let i: number = 0; i < keys.length; i++) { const key: string = keys[i]; @@ -86,7 +86,7 @@ function generateInterfaceLines( const suffix: string = arrayKeys.includes(fullPath) ? '[]' : ''; const comma: string = isLast ? '' : ','; // no comma for last - const childKeys: string[] = Object.keys(subtree); + const childKeys: string[] = ObjectUtilities.keys(subtree); if (childKeys.length === 0) { lines.push(`${indent}${key}: string${suffix}${comma}`); diff --git a/src/http-client/http-client.test.ts b/src/http-client/http-client.test.ts index b2edd68..67f57da 100644 --- a/src/http-client/http-client.test.ts +++ b/src/http-client/http-client.test.ts @@ -76,7 +76,7 @@ describe('post', () => { baseUrl = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; - const parser: Parser = inject(ZIBRI_DI_TOKENS.PARSER); + const parser: Parser = inject(ZIBRI_DI_TOKENS.PARSER) as Parser; parser['bodyParsers'].push(inject(JsonBodyParser), inject(FormDataBodyParser)); http = inject(ZIBRI_DI_TOKENS.HTTP_CLIENT); }); diff --git a/src/http-client/http-client.ts b/src/http-client/http-client.ts index 0e92719..dbeec54 100644 --- a/src/http-client/http-client.ts +++ b/src/http-client/http-client.ts @@ -13,23 +13,24 @@ import { Newable } from '../types'; import { Ms } from '../utilities'; import { type ValidationServiceInterface } from '../validation/validation-service.interface'; -const responseTypeForMimeType: Record = { +const responseTypeForMimeType: Record = { [MimeType.JSON]: 'json', - [MimeType.XML]: 'text', - [MimeType.HTML]: 'text', - [MimeType.TXT]: 'text', - [MimeType.FORM_DATA]: 'stream', - [MimeType.OCTET_STREAM]: 'stream', - [MimeType.PNG]: 'stream', - [MimeType.JPEG]: 'stream', - [MimeType.ZIP]: 'stream', - [MimeType.SVG]: 'stream', - [MimeType.CSS]: 'stream', - [MimeType.TTF]: 'stream', - [MimeType.PDF]: 'stream', - [MimeType.CSV]: 'stream', - [MimeType.XLSX]: 'stream', - [MimeType.DOCX]: 'stream' + // [MimeType.XML]: 'text', + // [MimeType.HTML]: 'text', + // [MimeType.TXT]: 'text', + [MimeType.FORM_DATA]: 'stream' + // [MimeType.FORM_URL_ENCODED]: 'text', + // [MimeType.OCTET_STREAM]: 'stream', + // [MimeType.PNG]: 'stream', + // [MimeType.JPEG]: 'stream', + // [MimeType.ZIP]: 'stream', + // [MimeType.SVG]: 'stream', + // [MimeType.CSS]: 'stream', + // [MimeType.TTF]: 'stream', + // [MimeType.PDF]: 'stream', + // [MimeType.CSV]: 'stream', + // [MimeType.XLSX]: 'stream', + // [MimeType.DOCX]: 'stream' }; /** diff --git a/src/http/known-header.enum.ts b/src/http/known-header.enum.ts index e1782a5..61e5a9b 100644 --- a/src/http/known-header.enum.ts +++ b/src/http/known-header.enum.ts @@ -1,3 +1,5 @@ +import { ObjectUtilities } from '../utilities'; + /** * Known http headers. */ @@ -35,5 +37,5 @@ export enum KnownHeader { * @returns True when the KnownHeader enum values include the given value, false otherwise. */ export function isKnownHeader(value: string): value is KnownHeader { - return Object.values(KnownHeader).includes(value as KnownHeader); + return ObjectUtilities.values(KnownHeader).includes(value as KnownHeader); } \ No newline at end of file diff --git a/src/http/mime-type.enum.ts b/src/http/mime-type.enum.ts index fda41d9..7777bbb 100644 --- a/src/http/mime-type.enum.ts +++ b/src/http/mime-type.enum.ts @@ -8,6 +8,7 @@ export enum MimeType { XML = 'application/xml', HTML = 'text/html', FORM_DATA = 'multipart/form-data', + FORM_URL_ENCODED = 'application/x-www-form-urlencoded', OCTET_STREAM = 'application/octet-stream', PNG = 'image/png', JPEG = 'image/jpeg', @@ -25,7 +26,7 @@ export enum MimeType { /** * File mime types. */ -export type FileMimeType = ExcludeStrict; +export type FileMimeType = ExcludeStrict; /** * File mime types with the possibility to provide custom mime types. diff --git a/src/http/mime-type.helpers.ts b/src/http/mime-type.helpers.ts index 9a32e47..4662e76 100644 --- a/src/http/mime-type.helpers.ts +++ b/src/http/mime-type.helpers.ts @@ -1,3 +1,4 @@ +import { ObjectUtilities } from '../utilities'; import { FileMimeType, LooseFileMimeType, MimeType } from './mime-type.enum'; /** @@ -69,5 +70,5 @@ export function resolveFileExtension(type: LooseFileMimeType): FileExtension | u * @param value - The value to check. */ export function isMimeType(value: string): value is MimeType { - return Object.values(MimeType).includes(value as MimeType); + return ObjectUtilities.values(MimeType).includes(value as MimeType); } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 20ef79f..e3cefc5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,11 +31,11 @@ export * from './websocket'; export * from './backup'; export * from './http-client'; -export * from './types/newable.model'; +export * from './types/newable.type'; export * from './types/version.type'; export * from './utilities/compare-versions.function'; export * from './utilities/is-version.function'; -export * from './utilities/chunked-promise-all.function'; +export * from './utilities/promise.utilities'; export * from './utilities/ms'; export * from './utilities/big-number.utilities'; export * from './utilities/validate-entities-registered.function'; diff --git a/src/multithreading/models/thread-job-entity.model.ts b/src/multithreading/models/thread-job-entity.model.ts index 9cb1dce..a51c39f 100644 --- a/src/multithreading/models/thread-job-entity.model.ts +++ b/src/multithreading/models/thread-job-entity.model.ts @@ -1,9 +1,9 @@ +import { BaseThreadJobWorkerData } from './base-thread-job-worker-data.model'; +import { ThreadJobStatus } from './thread-job-status.enum'; import { Entity, Property } from '../../entity'; import { BaseEntity } from '../../entity/base-entity.model'; import type { OmitStrict, Percentage } from '../../types'; import { ThreadJob } from '../services'; -import { BaseThreadJobWorkerData } from './base-thread-job-worker-data.model'; -import { ThreadJobStatus } from './thread-job-status.enum'; // eslint-disable-next-line unusedImports/no-unused-vars const omitValues: (keyof ThreadJob)[] = [ diff --git a/src/open-api/open-api.service.ts b/src/open-api/open-api.service.ts index a4514d1..8f48480 100644 --- a/src/open-api/open-api.service.ts +++ b/src/open-api/open-api.service.ts @@ -17,7 +17,7 @@ import { OpenApiContentObject, OpenApiDefinition, OpenApiOperation, OpenApiParam import { FileResponse } from '../parsing'; import { MissingBaseRouteError } from '../routing/missing-base-route.error'; import { Newable } from '../types'; -import { MetadataUtilities } from '../utilities'; +import { MetadataUtilities, ObjectUtilities } from '../utilities'; const defaultDescriptionForHttpStatus: Record = { default: 'Response', @@ -527,7 +527,7 @@ export class OpenApiService implements OpenApiServiceInterface { const properties: Record = {}; const required: string[] = []; - for (const [key, meta] of Object.entries(propMeta)) { + for (const [key, meta] of ObjectUtilities.entries(propMeta)) { // mark required if (( typeof meta.required === 'boolean' @@ -553,7 +553,7 @@ export class OpenApiService implements OpenApiServiceInterface { required: undefined, minimum: meta.min, maximum: meta.max, - enum: meta.enum ? Object.values(meta.enum) : undefined + enum: meta.enum ? ObjectUtilities.values(meta.enum) : undefined }; continue; } @@ -570,7 +570,7 @@ export class OpenApiService implements OpenApiServiceInterface { ...meta, required: undefined, pattern: meta.regex?.toString(), - enum: meta.enum ? Object.values(meta.enum) : undefined + enum: meta.enum ? ObjectUtilities.values(meta.enum) : undefined }; continue; } @@ -678,7 +678,7 @@ export class OpenApiService implements OpenApiServiceInterface { params: Record, location: OpenApiParameterLocation ): OpenApiParameter[] { - return Object.values(params).map(meta => ({ + return ObjectUtilities.values(params).map(meta => ({ name: meta.name, in: location, required: typeof meta.required === 'boolean' ? meta.required : undefined, @@ -705,7 +705,7 @@ export class OpenApiService implements OpenApiServiceInterface { required: undefined, minimum: meta.min, maximum: meta.max, - enum: meta.enum ? Object.values(meta.enum) : undefined + enum: meta.enum ? ObjectUtilities.values(meta.enum) : undefined }; } case 'string': { @@ -713,7 +713,7 @@ export class OpenApiService implements OpenApiServiceInterface { ...meta, required: undefined, pattern: meta.regex?.toString(), - enum: meta.enum ? Object.values(meta.enum) : undefined + enum: meta.enum ? ObjectUtilities.values(meta.enum) : undefined }; } case 'date': { diff --git a/src/parsing/functions/parse-object.function.ts b/src/parsing/functions/parse-object.function.ts index ac69211..9f2491c 100644 --- a/src/parsing/functions/parse-object.function.ts +++ b/src/parsing/functions/parse-object.function.ts @@ -6,7 +6,7 @@ import { parseNumber } from './parse-number.function'; import { parseString } from './parse-string.function'; import { PropertyMetadata, Relation } from '../../entity'; import { Newable } from '../../types'; -import { MetadataUtilities } from '../../utilities'; +import { MetadataUtilities, ObjectUtilities } from '../../utilities'; // eslint-disable-next-line jsdoc/require-jsdoc export function parseObject( @@ -34,7 +34,7 @@ export function parseObject( const res: Record = simpleParsedValue as Record; const properties: Record = MetadataUtilities.getModelProperties(cls); - for (const [propertyKey, m] of Object.entries(properties)) { + for (const [propertyKey, m] of ObjectUtilities.entries(properties)) { switch (m.type) { case 'string': { res[propertyKey] = parseString(res[propertyKey]); diff --git a/src/parsing/json/json.body-parser.ts b/src/parsing/json/json.body-parser.ts index 3a5d049..5d0089e 100644 --- a/src/parsing/json/json.body-parser.ts +++ b/src/parsing/json/json.body-parser.ts @@ -58,7 +58,7 @@ export class JsonBodyParser implements BodyParserInterface { if (req.body !== undefined) { return req.body; } - const contentLength: string | undefined = req.headers[KnownHeader.CONTENT_LENGTH] ?? req.headers[KnownHeader.CONTENT_LENGTH]; + const contentLength: string | undefined = req.headers[KnownHeader.CONTENT_LENGTH]; if (contentLength && BigNumberUtilities.new(Number(contentLength)).isGreaterThan(metadata.maxSize)) { throw new ContentTooLargeError(); } diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 4bf0deb..454e87f 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -1,2 +1,3 @@ export * from './plugin.model'; -export * from './invoicing'; \ No newline at end of file +export * from './invoicing'; +export * from './payment'; \ No newline at end of file diff --git a/src/plugin/invoicing/invoicing.plugin.ts b/src/plugin/invoicing/invoicing.plugin.ts index 419c278..60cf1dd 100644 --- a/src/plugin/invoicing/invoicing.plugin.ts +++ b/src/plugin/invoicing/invoicing.plugin.ts @@ -1,8 +1,8 @@ /* eslint-disable jsdoc/require-jsdoc */ -import { ZIBRI_INVOICING_DI_TOKENS, ZibriInvoicingPluginDiProvider, ZibriInvoicingPluginDiProviders } from './invoicing.tokens'; +import { ZIBRI_INVOICING_DI_TOKENS } from './invoicing.tokens'; import { Invoice, InvoicingOptions, InvoicingOptionsInput, NumberInvoices } from './models'; import { InvoiceCalcService, InvoiceNumberService, InvoicePdfService, PeppolConformanceService, XRechnungConformanceService } from './services'; -import { DiProvider, inject } from '../../di'; +import { DiProvider, DiTokenProviderRecord, inject, providersFromTokenRecord } from '../../di'; import { NoProviderError } from '../../di/errors'; import { validateEntitiesRegistered } from '../../utilities'; import { ZibriPlugin } from '../plugin.model'; @@ -11,22 +11,19 @@ import { ZibriPlugin } from '../plugin.model'; * Plugin that includes everything for handling invoices. */ export class ZibriInvoicingPlugin extends ZibriPlugin { - private readonly defaultDiProviders: Record< - typeof ZIBRI_INVOICING_DI_TOKENS[keyof typeof ZIBRI_INVOICING_DI_TOKENS], - ZibriInvoicingPluginDiProvider - > = { - [ZIBRI_INVOICING_DI_TOKENS.INVOICE_NUMBER_SERVICE]: { useClass: InvoiceNumberService }, - [ZIBRI_INVOICING_DI_TOKENS.INVOICE_PDF_SERVICE]: { useClass: InvoicePdfService }, - [ZIBRI_INVOICING_DI_TOKENS.INVOICE_CALC_SERVICE]: { useClass: InvoiceCalcService }, - [ZIBRI_INVOICING_DI_TOKENS.INVOICE_CONFORMANCE_SERVICES]: { + private readonly defaultDiProviders: DiTokenProviderRecord = { + INVOICE_NUMBER_SERVICE: { useClass: InvoiceNumberService }, + INVOICE_PDF_SERVICE: { useClass: InvoicePdfService }, + INVOICE_CALC_SERVICE: { useClass: InvoiceCalcService }, + INVOICE_CONFORMANCE_SERVICES: { useFactory: () => [inject(XRechnungConformanceService), inject(PeppolConformanceService)] }, - [ZIBRI_INVOICING_DI_TOKENS.OPTIONS_INPUT]: { + OPTIONS_INPUT: { useFactory: () => { throw new NoProviderError(ZIBRI_INVOICING_DI_TOKENS.OPTIONS_INPUT, []); } }, - [ZIBRI_INVOICING_DI_TOKENS.OPTIONS]: { + OPTIONS: { useFactory: () => { const input: InvoicingOptionsInput = inject(ZIBRI_INVOICING_DI_TOKENS.OPTIONS_INPUT); const res: InvoicingOptions = { @@ -70,19 +67,9 @@ export class ZibriInvoicingPlugin extends ZibriPlugin { } } - } satisfies ZibriInvoicingPluginDiProviders; + }; - providers: DiProvider[] = this.getProviders(); - - private getProviders(): DiProvider[] { - const res: DiProvider[] = []; - for (const key in ZIBRI_INVOICING_DI_TOKENS) { - // eslint-disable-next-line stylistic/max-len - const token: typeof ZIBRI_INVOICING_DI_TOKENS[keyof typeof ZIBRI_INVOICING_DI_TOKENS] = ZIBRI_INVOICING_DI_TOKENS[key as keyof typeof ZIBRI_INVOICING_DI_TOKENS]; - res.push({ token, ...this.defaultDiProviders[token] }); - } - return res; - } + providers: DiProvider[] = providersFromTokenRecord(ZIBRI_INVOICING_DI_TOKENS, this.defaultDiProviders); validate(): void { validateEntitiesRegistered(this.constructor.name, Invoice, NumberInvoices); diff --git a/src/plugin/invoicing/invoicing.tokens.ts b/src/plugin/invoicing/invoicing.tokens.ts index 08a0506..2da0efb 100644 --- a/src/plugin/invoicing/invoicing.tokens.ts +++ b/src/plugin/invoicing/invoicing.tokens.ts @@ -1,33 +1,25 @@ /* eslint-disable jsdoc/require-jsdoc */ import { InvoicingOptions, InvoicingOptionsInput } from './models'; import { InvoiceCalcServiceInterface, InvoiceNumberServiceInterface, InvoicePdfServiceInterface, InvoiceConformanceServiceInterface } from './services'; -import { DiProvider } from '../../di'; -import { OmitStrict } from '../../types'; +import { InjectionToken, TokenRecord } from '../../di'; /** * The dependency injection tokens used by the ZibriInvoicingPlugin. */ // eslint-disable-next-line typescript/typedef export const ZIBRI_INVOICING_DI_TOKENS = { - INVOICE_NUMBER_SERVICE: 'zi.invoicing.invoice_number_service', - OPTIONS_INPUT: 'zi.invoicing.options_input', - OPTIONS: 'zi.invoicing.options', - INVOICE_CALC_SERVICE: 'zi.invoicing.invoice_calc_service', - INVOICE_PDF_SERVICE: 'zi.invoicing.invoice_pdf_service', - INVOICE_CONFORMANCE_SERVICES: 'zi.invoicing.invoice_conformance_services' -} as const satisfies Record; - -export type ZibriInvoicingPluginDiProvider = OmitStrict, 'token'>; - -export type ZibriInvoicingPluginDiProviders = { // eslint-disable-next-line typescript/no-explicit-any - [ZIBRI_INVOICING_DI_TOKENS.INVOICE_NUMBER_SERVICE]: ZibriInvoicingPluginDiProvider>, - [ZIBRI_INVOICING_DI_TOKENS.OPTIONS]: ZibriInvoicingPluginDiProvider, - [ZIBRI_INVOICING_DI_TOKENS.OPTIONS_INPUT]: ZibriInvoicingPluginDiProvider, + INVOICE_NUMBER_SERVICE: invoicingToken>('zi.invoicing.invoice_number_service'), + OPTIONS_INPUT: invoicingToken('zi.invoicing.options_input'), + OPTIONS: invoicingToken('zi.invoicing.options'), // eslint-disable-next-line typescript/no-explicit-any - [ZIBRI_INVOICING_DI_TOKENS.INVOICE_PDF_SERVICE]: ZibriInvoicingPluginDiProvider>, + INVOICE_CALC_SERVICE: invoicingToken>('zi.invoicing.invoice_calc_service'), // eslint-disable-next-line typescript/no-explicit-any - [ZIBRI_INVOICING_DI_TOKENS.INVOICE_CALC_SERVICE]: ZibriInvoicingPluginDiProvider>, + INVOICE_PDF_SERVICE: invoicingToken>('zi.invoicing.invoice_pdf_service'), // eslint-disable-next-line typescript/no-explicit-any - [ZIBRI_INVOICING_DI_TOKENS.INVOICE_CONFORMANCE_SERVICES]: ZibriInvoicingPluginDiProvider[]> -}; \ No newline at end of file + INVOICE_CONFORMANCE_SERVICES: invoicingToken[]>('zi.invoicing.invoice_conformance_services') +} as const satisfies TokenRecord; + +function invoicingToken(k: `zi.invoicing.${string}`): InjectionToken { + return new InjectionToken(k); +} \ No newline at end of file diff --git a/src/plugin/payment/index.ts b/src/plugin/payment/index.ts new file mode 100644 index 0000000..03c4e5f --- /dev/null +++ b/src/plugin/payment/index.ts @@ -0,0 +1,5 @@ +export * from './payment.plugin'; +export * from './payment.tokens'; +export * from './models'; +export * from './services'; +export * from './providers'; \ No newline at end of file diff --git a/src/plugin/payment/models/index.ts b/src/plugin/payment/models/index.ts new file mode 100644 index 0000000..0bac7af --- /dev/null +++ b/src/plugin/payment/models/index.ts @@ -0,0 +1,5 @@ +export * from './payment-plugin-options-input.model'; +export * from './payment-plugin-options.model'; +export * from './payment-method.model'; +export * from './payment.model'; +export * from './payment-status.enum'; \ No newline at end of file diff --git a/src/plugin/payment/models/payment-method.model.ts b/src/plugin/payment/models/payment-method.model.ts new file mode 100644 index 0000000..fdf2468 --- /dev/null +++ b/src/plugin/payment/models/payment-method.model.ts @@ -0,0 +1,15 @@ +/** + * Known payment methods. + */ +export enum KnownPaymentMethod { + PAY_PAL = 'PAY_PAL', + APPLE_PAY = 'APPLE_PAY', + GOOGLE_PAY = 'GOOGLE_PAY', + SEPA_BANK_TRANSFER = 'SEPA_BANK_TRANSFER', + CREDIT_CARD = 'CREDIT_CARD' +} + +/** + * All possible payment methods, including unknown ones. + */ +export type PaymentMethod = KnownPaymentMethod | string & {}; \ No newline at end of file diff --git a/src/plugin/payment/models/payment-plugin-options-input.model.ts b/src/plugin/payment/models/payment-plugin-options-input.model.ts new file mode 100644 index 0000000..06f002b --- /dev/null +++ b/src/plugin/payment/models/payment-plugin-options-input.model.ts @@ -0,0 +1,23 @@ +import { AnyObject } from '../../../entity'; +import { PaymentProviderInterface } from '../providers'; +import { PaymentMethod } from './payment-method.model'; +import { PaymentPluginOptions } from './payment-plugin-options.model'; + +/** + * Input for configuring the payment plugin. + */ +export type PaymentPluginOptionsInput< + M extends readonly PaymentMethod[], + P extends readonly PaymentProviderInterface< + M[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> = PaymentPluginOptions; \ No newline at end of file diff --git a/src/plugin/payment/models/payment-plugin-options.model.ts b/src/plugin/payment/models/payment-plugin-options.model.ts new file mode 100644 index 0000000..66b0fca --- /dev/null +++ b/src/plugin/payment/models/payment-plugin-options.model.ts @@ -0,0 +1,35 @@ +import { AnyObject } from '../../../entity'; +import { PaymentProviderInterface } from '../providers'; +import { PaymentMethod } from './payment-method.model'; + +/** + * Configuration for the payment plugin. + */ +export type PaymentPluginOptions< + M extends readonly PaymentMethod[], + P extends readonly PaymentProviderInterface< + M[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> = { + /** + * The payment methods to use (credit card, bank transfer etc.). + */ + readonly paymentMethods: M, + /** + * The payment providers to register (Stripe, PayPal etc.). + */ + readonly paymentProviders: P, + /** + * The mapping of which payment provider should be used for what payment method. + */ + readonly providerNameForMethod: Record +}; \ No newline at end of file diff --git a/src/plugin/payment/models/payment-status.enum.ts b/src/plugin/payment/models/payment-status.enum.ts new file mode 100644 index 0000000..6566721 --- /dev/null +++ b/src/plugin/payment/models/payment-status.enum.ts @@ -0,0 +1,31 @@ +/** + * The different status a payment can have. + */ +export enum PaymentStatus { + /** + * When the payment has been created but is not confirmed yet. + * Eg. When waiting for a SEPA transfer to happen. + */ + CREATED = 'CREATED', + /** + * When the payment has ben successfully paid. + */ + PAID = 'PAID', + /** + * When the payment was cancelled through the customer somewhere. + */ + CANCELLED = 'CANCELLED', + /** + * When the payment could not be processed due to an error. + * The error data with more information is saved on the payment. + */ + FAILED = 'FAILED', + /** + * When the payment has been refunded to the customer. + */ + REFUNDED = 'REFUNDED', + /** + * When the payment has been reserved/frozen but not yet collected. + */ + RESERVED = 'RESERVED' +} \ No newline at end of file diff --git a/src/plugin/payment/models/payment.model.ts b/src/plugin/payment/models/payment.model.ts new file mode 100644 index 0000000..60f7123 --- /dev/null +++ b/src/plugin/payment/models/payment.model.ts @@ -0,0 +1,47 @@ +import { type PaymentMethod } from './payment-method.model'; +import { PaymentStatus } from './payment-status.enum'; +import { AnyObject, Entity, Property } from '../../../entity'; +import { BaseEntity } from '../../../entity/base-entity.model'; +import { type CurrencyCode } from '../../../localization'; + +/** + * Entity for an payment that has been made. + */ +@Entity() +export class Payment extends BaseEntity { + /** + * The transactionId, should be provided eg. From the client side of a checkout to prevent duplicate payments. + */ + @Property.string({ format: 'uuid', unique: true }) + transactionId!: string; + /** + * The status of the payment. + */ + @Property.string({ enum: PaymentStatus }) + status!: PaymentStatus; + /** + * The amount of the payment. + */ + @Property.number() + amount!: number; + /** + * The currency that the payment is in. + */ + @Property.string() + currencyCode!: CurrencyCode; + /** + * The used payment method. + */ + @Property.string() + paymentMethod!: M; + /** + * Additional data stored by the provider. + */ + @Property.unknown({ required: false }) + data?: Data; + /** + * An error that the payment failed with. + */ + @Property.unknown({ required: false }) + error?: Error; +} \ No newline at end of file diff --git a/src/plugin/payment/payment.plugin.ts b/src/plugin/payment/payment.plugin.ts new file mode 100644 index 0000000..5066812 --- /dev/null +++ b/src/plugin/payment/payment.plugin.ts @@ -0,0 +1,49 @@ +import { Payment, PaymentMethod, PaymentPluginOptions, PaymentPluginOptionsInput } from './models'; +import { DefaultPaymentProviderArray, ZIBRI_PAYMENT_DI_TOKENS } from './payment.tokens'; +import { DiProvider, DiTokenProviderRecord, inject, NoProviderError, providersFromTokenRecord } from '../../di'; +import { validateEntitiesRegistered } from '../../utilities'; +import { ZibriPlugin } from '../plugin.model'; + +/** + * Plugin that includes everything for handling payments. + */ +export class ZibriPaymentPlugin extends ZibriPlugin { + + private readonly defaultDiProviders: DiTokenProviderRecord = { + OPTIONS_INPUT: { + useFactory: () => { + throw new NoProviderError(ZIBRI_PAYMENT_DI_TOKENS.OPTIONS_INPUT, []); + } + }, + OPTIONS: { + useFactory: () => { + const input: PaymentPluginOptionsInput< + PaymentMethod[], + DefaultPaymentProviderArray + > = inject(ZIBRI_PAYMENT_DI_TOKENS.OPTIONS_INPUT); + const res: PaymentPluginOptions = { + ...input + }; + return res; + } + } + }; + + // eslint-disable-next-line jsdoc/require-jsdoc + providers: DiProvider[] = providersFromTokenRecord(ZIBRI_PAYMENT_DI_TOKENS, this.defaultDiProviders); + + // eslint-disable-next-line jsdoc/require-jsdoc + validate(): void { + validateEntitiesRegistered(this.constructor.name, Payment); + const options: PaymentPluginOptions = inject(ZIBRI_PAYMENT_DI_TOKENS.OPTIONS); + + const allNames: string[] = options.paymentProviders.map(p => p.name); + if (allNames.length >= [...new Set(allNames)].length) { + throw new Error('There are duplicate payment provider names'); + } + + if (options.paymentMethods.length >= [...new Set(options.paymentMethods)].length) { + throw new Error('There are duplicate payment methods'); + } + } +} \ No newline at end of file diff --git a/src/plugin/payment/payment.tokens.ts b/src/plugin/payment/payment.tokens.ts new file mode 100644 index 0000000..3646a3d --- /dev/null +++ b/src/plugin/payment/payment.tokens.ts @@ -0,0 +1,29 @@ +/* eslint-disable jsdoc/require-jsdoc */ +import { PaymentMethod, PaymentPluginOptionsInput } from './models'; +import { PaymentProviderInterface } from './providers'; +import { InjectionToken, TokenRecord } from '../../di'; +import { AnyObject } from '../../entity'; + +export type DefaultPaymentProviderArray = PaymentProviderInterface< + PaymentMethod[], + Record, + Record, + Record, + Record, + Record, + Record, + Record +>[]; + +/** + * The dependency injection tokens used by the ZibriPaymentPlugin. + */ +// eslint-disable-next-line typescript/typedef +export const ZIBRI_PAYMENT_DI_TOKENS = { + OPTIONS_INPUT: paymentToken>('zi.payment.options_input'), + OPTIONS: paymentToken>('zi.payment.options') +} as const satisfies TokenRecord; + +function paymentToken(k: `zi.payment.${string}`): InjectionToken { + return new InjectionToken(k); +} \ No newline at end of file diff --git a/src/plugin/payment/providers/index.ts b/src/plugin/payment/providers/index.ts new file mode 100644 index 0000000..4b3bb3b --- /dev/null +++ b/src/plugin/payment/providers/index.ts @@ -0,0 +1,2 @@ +export * from './payment-provider.interface'; +export * from './pay-pal'; \ No newline at end of file diff --git a/src/plugin/payment/providers/pay-pal/index.ts b/src/plugin/payment/providers/pay-pal/index.ts new file mode 100644 index 0000000..0f48c8c --- /dev/null +++ b/src/plugin/payment/providers/pay-pal/index.ts @@ -0,0 +1 @@ +export * from './pay-pal.payment-provider'; \ No newline at end of file diff --git a/src/plugin/payment/providers/pay-pal/pay-pal-client.ts b/src/plugin/payment/providers/pay-pal/pay-pal-client.ts new file mode 100644 index 0000000..2f03e57 --- /dev/null +++ b/src/plugin/payment/providers/pay-pal/pay-pal-client.ts @@ -0,0 +1,459 @@ +import { PayPalProviderOptions } from './pay-pal.payment-provider'; +import { inject, ZIBRI_DI_TOKENS } from '../../../../di'; +import { Property } from '../../../../entity'; +import { KnownHeader, MimeType } from '../../../../http'; +import { HttpClientInterface } from '../../../../http-client'; + +/** + * The intent of the new payment. + * + * Can either be 'CAPTURE' (pay now, eg. Online shop) or 'AUTHORIZE' (reserve funds, eg. For hotel booking). + */ +type PayPalPaymentIntent = 'CAPTURE' | 'AUTHORIZE'; + +/** + * The access token received from the pay-pal api. + */ +type PayPalAccessToken = { + /** + * The actual token value. + */ + accessToken: string, + /** + * The timestamp in ms at which the token expires. + */ + expiresAt: number +}; + +/** + * Response for authenticating with pay-pal. + */ +class AuthResp { + /** + * The actual access token value. + */ + @Property.string() + access_token!: string; + /** + * In how much seconds the access token becomes invalid. + */ + @Property.number() + expires_in!: number; +} + +/** + * A payment amount, consisting of the value and the currency. + */ +class PaymentAmount { + /** + * The value of the payment. + */ + @Property.string() + value!: string; + /** + * The currency of the payment. + */ + @Property.string() + currency_code!: string; +} + +/** + * A PayPal link. + */ +class PayPalLink { + /** + * The html href => the actual link. + */ + @Property.string() + href!: string; + /** + * The html rel. + */ + @Property.string() + rel!: string; + /** + * The method of the link. + */ + @Property.string({ required: false }) + method?: string; +} + +/** + * Response for creating a order. + */ +class CreateOrderResp { + /** + * The id of the order. + */ + @Property.string() + id!: string; + /** + * The status of the order. + */ + @Property.string({ required: false }) + status?: string; + /** + * The links of the order. + */ + @Property.array({ required: false, items: { type: 'object', cls: () => PayPalLink } }) + links?: PayPalLink[]; +} + +/** + * The body for creating a new order. + */ +type CreateOrderBody = { + /** + * The intent of the order to create. + */ + intent: PayPalPaymentIntent, + /** + * The purchase units describe the amount to be paid. + */ + purchase_units: { + /** + * The payment amount. + */ + amount: PaymentAmount + }[], + /** + * Additional context of the application. + */ + application_context?: { + /** + * The url to return to after the payment has been confirmed. + */ + return_url?: string, + /** + * The url to return to after the payment has been cancelled. + */ + cancel_url?: string + } +}; + +/** + * Represents a capture returned inside purchase_units[].payments.captures. + */ +export class PayPalCapture { + /** + * The id of a captured payment. + */ + @Property.string() + id!: string; + + /** + * The status of the capture. + */ + @Property.string({ required: false }) + status?: string; +} + +/** + * Payments object inside a purchase_unit. + */ +class PayPalPayments { + /** + * The payment captures. + */ + @Property.array({ required: false, items: { type: 'object', cls: () => PayPalCapture } }) + captures?: PayPalCapture[]; +} + +/** + * Purchase_unit in capture response. + */ +class PayPalPurchaseUnit { + /** + * Any payments that belong to this purchase unit. + */ + @Property.object({ required: false, cls: () => PayPalPayments }) + payments?: PayPalPayments; +} + +/** + * Typed response for captureOrder. + */ +export class CaptureOrderResp { + /** + * The id of the captured order. + */ + @Property.string() + id!: string; + + /** + * The status of the capture. + */ + @Property.string({ required: false }) + status?: string; + + /** + * The purchase units of the capture. + */ + @Property.array({ required: false, items: { type: 'object', cls: () => PayPalPurchaseUnit } }) + purchase_units?: PayPalPurchaseUnit[]; +} + +/** + * Authorization (Reservation) of a payment. + */ +class PayPalAuthorization { + /** + * The id of the authorization. + */ + @Property.string() + id!: string; + + /** + * The status of the authorization. + */ + @Property.string({ required: false }) + status?: string; + + /** + * The amount that has been authorized. + */ + @Property.object({ required: false, cls: () => PaymentAmount }) + amount?: PaymentAmount; +} + +/** + * Payments with their authorizations. + */ +class PayPalPaymentsWithAuth { + /** + * The authorizations for the payments. + */ + @Property.array({ required: false, items: { type: 'object', cls: () => PayPalAuthorization } }) + authorizations?: PayPalAuthorization[]; +} + +/** + * Purchase units with their authorizations. + */ +class PayPalPurchaseUnitWithAuth { + /** + * The payments including the authorizations. + */ + @Property.object({ required: false, cls: () => PayPalPaymentsWithAuth }) + payments?: PayPalPaymentsWithAuth; +} + +/** + * Response for getting a order. + */ +export class GetOrderResp { + /** + * The id of the order. + */ + @Property.string() + id!: string; + + /** + * The status of the order. + */ + @Property.string({ required: false }) + status?: string; + + /** + * The purchase units of this order. + */ + @Property.array({ required: false, items: { type: 'object', cls: () => PayPalPurchaseUnitWithAuth } }) + purchase_units?: PayPalPurchaseUnitWithAuth[]; +} + +/** + * Response for capturing an authorization. + */ +export class AuthorizationCaptureResp { + /** + * The id of the authorization. + */ + @Property.string() + id!: string; + + /** + * The status of the authorization. + */ + @Property.string({ required: false }) + status?: string; + + /** + * Any links that belong to this authorization. + */ + @Property.array({ required: false, items: { type: 'object', cls: () => PayPalLink } }) + links?: PayPalLink[]; +} + +/** + * Result for capturing a refund. + */ +export class RefundCaptureResp { + /** + * The id of the refund. + */ + @Property.string() + id!: string; + + /** + * The status of the refund. + */ + @Property.string({ required: false }) + status?: string; + + /** + * Any links that belong to this refund. + */ + @Property.array({ required: false, items: { type: 'object', cls: () => PayPalLink } }) + links?: PayPalLink[]; +} + +/** + * Client for handling anything related with the PayPal API. + */ +export class PayPalClient { + private readonly baseUrl: string; + private token?: PayPalAccessToken; + private readonly http: HttpClientInterface; + + constructor(private readonly options: PayPalProviderOptions) { + this.baseUrl = options.env === 'live' ? 'https://api-m.paypal.com' : 'https://api-m.sandbox.paypal.com'; + this.http = inject(ZIBRI_DI_TOKENS.HTTP_CLIENT); + } + + private async getAccessToken(): Promise { + const nowInMs: number = Date.now(); + if (this.token && this.token.expiresAt > nowInMs + 5000) { + return this.token.accessToken; + } + + const url: string = `${this.baseUrl}/v1/oauth2/token`; + const auth: string = Buffer.from(`${this.options.clientId}:${this.options.clientSecret}`).toString('base64'); + + const body: string = 'grant_type=client_credentials'; + + const resp: AuthResp = (await this.http.post(url, body, { + headers: { + [KnownHeader.AUTHORIZATION]: `Basic ${auth}`, + [KnownHeader.CONTENT_TYPE]: MimeType.FORM_URL_ENCODED + }, + responseBody: AuthResp + })).body; + + this.token = { + accessToken: resp.access_token, + expiresAt: Date.now() + (resp.expires_in * 1000) + }; + return this.token.accessToken; + } + + /** + * Creates a new Order. + * @param body - The create body as required by the api. + * @returns The created order. + */ + async createOrder(body: CreateOrderBody): Promise { + const token: string = await this.getAccessToken(); + const url: string = `${this.baseUrl}/v2/checkout/orders`; + + return (await this.http.post( + url, + body, + { + responseBody: CreateOrderResp, + headers: { + Authorization: `Bearer ${token}` + } + } + )).body; + } + + /** + * Captures a completed order with the given id. + * @param orderId - The id of the order to capture. + * @returns The captured order. + */ + async captureOrder(orderId: string): Promise { + const token: string = await this.getAccessToken(); + const url: string = `${this.baseUrl}/v2/checkout/orders/${encodeURIComponent(orderId)}/capture`; + + const resp: CaptureOrderResp = (await this.http.post( + url, + {}, + { + responseBody: CaptureOrderResp, + headers: { + Authorization: `Bearer ${token}` + } + } + )).body; + + return resp; + } + + /** + * Gets an order with the given id. + * @param orderId - The id of the order to retrieve. + * @returns The found order. + */ + async getOrder(orderId: string): Promise { + const token: string = await this.getAccessToken(); + const url: string = `${this.baseUrl}/v2/checkout/orders/${encodeURIComponent(orderId)}`; + return (await this.http.get(url, { + responseBody: GetOrderResp, + headers: { + Authorization: `Bearer ${token}` + } + })).body; + } + + /** + * Captures authorization with the given id. + * @param authorizationId - The id of the (already confirmed) authorization. + * @param amount - The amount to capture. + * @returns The captured authorization. + */ + async captureAuthorization(authorizationId: string, amount: PaymentAmount): Promise { + const token: string = await this.getAccessToken(); + const url: string = `${this.baseUrl}/v2/payments/authorizations/${encodeURIComponent(authorizationId)}/capture`; + + return (await this.http.post(url, { amount }, { + responseBody: AuthorizationCaptureResp, + headers: { + Authorization: `Bearer ${token}` + } + })).body; + } + + /** + * Cancels the existing authorization with the given id. + * @param authorizationId - The id of the authorization to cancel. + */ + async voidAuthorization(authorizationId: string): Promise { + const token: string = await this.getAccessToken(); + const url: string = `${this.baseUrl}/v2/payments/authorizations/${encodeURIComponent(authorizationId)}/void`; + + await this.http.post(url, {}, { + headers: { + Authorization: `Bearer ${token}` + } + }); + } + + /** + * Refunds the payment of the capture with the given id. + * @param captureId - The id of the capture to refund. + * @param amount - The amount to refund. + * @returns The refund result. + */ + async refundCapture(captureId: string, amount: PaymentAmount): Promise { + const token: string = await this.getAccessToken(); + const url: string = `${this.baseUrl}/v2/payments/captures/${encodeURIComponent(captureId)}/refund`; + + return (await this.http.post(url, { amount }, { + responseBody: RefundCaptureResp, + headers: { + Authorization: `Bearer ${token}` + } + })).body; + } +} \ No newline at end of file diff --git a/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.ts b/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.ts new file mode 100644 index 0000000..bf67dd3 --- /dev/null +++ b/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.ts @@ -0,0 +1,490 @@ +import { AuthorizationCaptureResp, CaptureOrderResp, GetOrderResp, PayPalCapture, PayPalClient, RefundCaptureResp } from './pay-pal-client'; +import { Repository } from '../../../../data-source'; +import { inject, repositoryTokenFor } from '../../../../di'; +import { KnownPaymentMethod, Payment, PaymentStatus } from '../../models'; +import { PaymentProviderInterface } from '../payment-provider.interface'; + +/** + * The environment of the pay-pal client. + */ +type PayPalEnv = 'sandbox' | 'live'; + +/** + * The supported methods of the PayPalPaymentProvider. + */ +type SupportedMethods = [KnownPaymentMethod.PAY_PAL]; + +/** + * The data for creating a new payment. + */ +type PayPalPaymentData = { + /** + * The amount of the payment. + */ + amount: number, + /** + * The currency of the payment. + */ + currencyCode: string, + /** + * The url to return to to confirm the payment. + */ + returnUrl?: string, + /** + * The url to return to to cancel the payment. + */ + cancelUrl?: string, + /** + * The transactionId, used to prevent duplicate payments. + */ + transactionId: string +}; + +/** + * The validated payment data. + */ +type PayPalValidatedPaymentData = PayPalPaymentData; + +/** + * The payment data stored by the provider. + */ +type PayPalProviderPaymentData = { + /** + * The id of the order. + */ + orderId?: string, + /** + * The url to navigate to for approval. + */ + approvalUrl?: string, + /** + * The id of the authorization. + */ + authorizationId?: string, + /** + * The id of the payment capture. + */ + captureId?: string +}; + +/** + * The payment reservation data. + */ +type PayPalProviderReservationPaymentData = PayPalProviderPaymentData; + +/** + * Maps payment methods to their payment data. + */ +type PaymentDataMap = { + /** + * The payment data for the PayPal payment method. + */ + PAY_PAL: PayPalPaymentData +}; + +/** + * Maps payment methods to their validated payment data. + */ +type ValidatedPaymentDataMap = { + /** + * The validated payment data for the PayPal payment method. + */ + PAY_PAL: PayPalValidatedPaymentData +}; + +/** + * Maps payment methods to their provider payment data. + */ +type ProviderPaymentDataMap = { + /** + * The provider payment data for the PayPal payment method. + */ + PAY_PAL: PayPalProviderPaymentData +}; + +/** + * Maps payment methods to their provider reservation payment data. + */ +type ProviderReservationPaymentDataMap = { + /** + * The provider reservation payment data for the PayPal payment method. + */ + PAY_PAL: PayPalProviderReservationPaymentData +}; + +/** + * Defines whether a given method supports cancellation. + */ +type CancellationSupport = { + /** + * PayPal. + */ + PAY_PAL: true +}; + +/** + * Defines whether a given method supports refunding. + */ +type RefundSupport = { + /** + * PayPal. + */ + PAY_PAL: true +}; + +/** + * Defines whether a given method supports reservation. + */ +type ReservationSupport = { + /** + * PayPal. + */ + PAY_PAL: true +}; + +/** + * Options for configuring the PayPalPaymentProvider. + */ +export type PayPalProviderOptions = { + /** + * The clientId needed to authenticate with the api. + */ + clientId: string, + /** + * The clientSecret needed to authenticate with the api. + */ + clientSecret: string, + /** + * The PayPal environment, either 'sandbox' or 'live'. + */ + env: PayPalEnv +}; + +/** + * Payment Provider that uses the PayPal API. + */ +export class PayPalPaymentProvider implements PaymentProviderInterface< + SupportedMethods, + PaymentDataMap, + ValidatedPaymentDataMap, + ProviderPaymentDataMap, + ProviderReservationPaymentDataMap, + CancellationSupport, + RefundSupport, + ReservationSupport +> { + // eslint-disable-next-line jsdoc/require-jsdoc + readonly cancellationSupported: CancellationSupport = { PAY_PAL: true }; + // eslint-disable-next-line jsdoc/require-jsdoc + readonly refundSupported: RefundSupport = { PAY_PAL: true }; + // eslint-disable-next-line jsdoc/require-jsdoc + readonly reservationSupported: ReservationSupport = { PAY_PAL: true }; + + /** + * Client that encapsulates the PayPal API. + */ + protected readonly client: PayPalClient; + + constructor( + readonly name: string, + options: PayPalProviderOptions + ) { + this.client = new PayPalClient(options); + } + + /** + * Injects a correctly typed payment repository. + * @returns A correctly typed payment repository. + */ + protected getPaymentRepository< + M extends SupportedMethods[number], + Data extends ProviderPaymentDataMap[M] | ProviderReservationPaymentDataMap[M] + >(): Repository> { + return inject(repositoryTokenFor(Payment)) as Repository>; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + validatePaymentData( + method: M, + data: PaymentDataMap[M] + ): ValidatedPaymentDataMap[M] { + // eslint-disable-next-line sonar/no-small-switch + switch (method) { + case KnownPaymentMethod.PAY_PAL: { + if (data.amount <= 0) { + throw new Error('amount must be > 0'); + } + if (!data.currencyCode) { + throw new Error('currencyCode required'); + } + return data; + } + default: { + throw new Error(`unsupported payment method ${method}`); + } + } + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async startPayment( + method: M, + data: ValidatedPaymentDataMap[M] + ): Promise> { + const { id, links } = await this.client.createOrder({ + intent: 'CAPTURE', + purchase_units: [{ amount: { value: String(data.amount), currency_code: data.currencyCode } }], + application_context: { + cancel_url: data.cancelUrl, + return_url: data.returnUrl + } + }); + const approvalUrl: string | undefined = links?.find(l => l.rel === 'approve' || l.rel === 'approval_url')?.href; + + return await this.getPaymentRepository().create({ + amount: data.amount, + currencyCode: data.currencyCode, + data: { + orderId: id, + approvalUrl + }, + transactionId: data.transactionId, + paymentMethod: method, + status: PaymentStatus.CREATED + }); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async startPaymentReservation< + M extends { + [K in SupportedMethods[number]]: ReservationSupport[K] extends true ? K : never + }[SupportedMethods[number]] + >( + method: M, + data: ValidatedPaymentDataMap[M] + ): Promise> { + const { id, links } = await this.client.createOrder({ + intent: 'AUTHORIZE', + purchase_units: [{ amount: { value: String(data.amount), currency_code: data.currencyCode } }], + application_context: { + cancel_url: data.cancelUrl, + return_url: data.returnUrl + } + }); + + const approvalUrl: string | undefined = links?.find(l => l.rel === 'approve' || l.rel === 'approval_url')?.href; + + return await this.getPaymentRepository().create({ + amount: data.amount, + currencyCode: data.currencyCode, + data: { + orderId: id, + approvalUrl + }, + transactionId: data.transactionId, + paymentMethod: method, + status: PaymentStatus.CREATED + }); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async confirmPaymentReservation< + M extends { + [K in SupportedMethods[number]]: ReservationSupport[K] extends true ? K : never + }[SupportedMethods[number]] + >(payment: Payment): Promise { + if (!payment.data?.orderId) { + throw new Error('missing payment data orderId; cannot confirm reservation'); + } + + try { + const resp: GetOrderResp = await this.client.getOrder(payment.data.orderId); + + let foundAuthId: string | undefined; + for (const pu of resp.purchase_units ?? []) { + const id: string | undefined = pu.payments?.authorizations?.at(0)?.id; + if (id) { + foundAuthId = id; + break; + } + } + + if (!foundAuthId) { + throw new Error('no authorization found on order; reservation not confirmed yet'); + } + + payment.data = { + ...payment.data, + authorizationId: foundAuthId + }; + payment.status = PaymentStatus.RESERVED; + payment.error = undefined; + } + catch (error: unknown) { + payment.status = PaymentStatus.FAILED; + payment.error = error instanceof Error ? error : new Error(String(error)); + } + + await this.getPaymentRepository().updateById(payment.id, payment); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async confirmPayment( + payment: Payment + ): Promise { + if (!payment.data?.orderId) { + throw new Error('missing payment data orderId; cannot confirm payment'); + } + + try { + const resp: CaptureOrderResp = await this.client.captureOrder(payment.data.orderId); + // TODO: handle payments with multiple parts + const capture: PayPalCapture | undefined = resp.purchase_units?.[0]?.payments?.captures?.[0]; + const captureStatus: string | undefined = capture?.status ?? resp.status; + + if (capture?.id && captureStatus === 'COMPLETED') { + payment.status = PaymentStatus.PAID; + payment.data = { ...payment.data, captureId: capture.id }; + payment.error = undefined; + } + else { + payment.status = PaymentStatus.FAILED; + payment.error = new Error(`capture failed, status=${String(captureStatus)}`); + payment.data = { ...payment.data, captureId: capture?.id }; + } + } + catch (error) { + payment.status = PaymentStatus.FAILED; + payment.error = error instanceof Error ? error : new Error(String(error)); + } + + await this.getPaymentRepository().updateById(payment.id, payment); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async collectPaymentFromReservation< + M extends { + [K in SupportedMethods[number]]: ReservationSupport[K] extends true ? K : never + }[SupportedMethods[number]] + >(payment: Payment): Promise { + // ensure we have an authorization id + let authId: string | undefined = payment.data?.authorizationId; + if (!authId && payment.data?.orderId) { + const orderResp: GetOrderResp = await this.client.getOrder(String(payment.data.orderId)); + for (const pu of orderResp.purchase_units ?? []) { + const id: string | undefined = pu.payments?.authorizations?.at(0)?.id; + if (id) { + authId = id; + break; + } + } + } + + if (!authId) { + throw new Error('missing authorizationId; cannot collect from reservation'); + } + + try { + const captureResp: AuthorizationCaptureResp = await this.client.captureAuthorization( + authId, + { value: String(payment.amount), currency_code: payment.currencyCode } + ); + + if (captureResp.id && captureResp.status === 'COMPLETED') { + payment.status = PaymentStatus.PAID; + payment.data = { + ...payment.data, + captureId: captureResp.id + }; + payment.error = undefined; + } + else { + payment.status = PaymentStatus.FAILED; + payment.error = new Error(`authorization capture failed, status=${String(captureResp.status)}`); + payment.data = { + ...payment.data, + captureId: captureResp.id + }; + } + } + catch (error: unknown) { + payment.status = PaymentStatus.FAILED; + payment.error = error instanceof Error ? error : new Error(String(error)); + } + + await this.getPaymentRepository().updateById(payment.id, payment); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async cancelPayment( + payment: Payment | Payment + ): Promise { + // If there's an authorization, try to void it + if (payment.data?.authorizationId) { + try { + await this.client.voidAuthorization(payment.data?.authorizationId); + payment.status = PaymentStatus.CANCELLED; + payment.error = undefined; + } + catch (error: unknown) { + payment.status = PaymentStatus.FAILED; + payment.error = error instanceof Error ? error : new Error(String(error)); + } + + await this.getPaymentRepository().updateById(payment.id, payment); + return; + } + + // If payment is only created (order created, not authorized), we can cancel locally + if (payment.status === PaymentStatus.CREATED) { + payment.status = PaymentStatus.CANCELLED; + payment.error = undefined; + await this.getPaymentRepository().updateById(payment.id, payment); + return; + } + + // If payment is already captured, cancellation isn't possible — refund must be used + if (payment.data?.captureId || payment.status === PaymentStatus.PAID) { + throw new Error('payment already captured; use refundPayment to refund the capture'); + } + + // Fallback: mark cancelled + payment.status = PaymentStatus.CANCELLED; + payment.error = undefined; + await this.getPaymentRepository().updateById(payment.id, payment); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async refundPayment< + M extends { + [K in SupportedMethods[number]]: RefundSupport[K] extends true ? K : never + }[SupportedMethods[number]] + >(payment: Payment): Promise { + if (!payment.data?.captureId) { + throw new Error('missing captureId on payment.data; cannot refund payment'); + } + + try { + const resp: RefundCaptureResp = await this.client.refundCapture(payment.data.captureId, { + value: String(payment.amount), + currency_code: payment.currencyCode + }); + + if (resp.id && resp.status === 'COMPLETED') { + payment.status = PaymentStatus.REFUNDED; + payment.data = { ...payment.data, refundId: resp.id }; + payment.error = undefined; + } + else { + payment.status = PaymentStatus.FAILED; + payment.error = new Error(`refund failed, status=${String(resp.status)}`); + payment.data = { ...payment.data, refundId: resp.id }; + } + } + catch (error: unknown) { + payment.status = PaymentStatus.FAILED; + payment.error = error instanceof Error ? error : new Error(String(error)); + } + + await this.getPaymentRepository().updateById(payment.id, payment); + } +} \ No newline at end of file diff --git a/src/plugin/payment/providers/payment-provider.interface.ts b/src/plugin/payment/providers/payment-provider.interface.ts new file mode 100644 index 0000000..c07c6dd --- /dev/null +++ b/src/plugin/payment/providers/payment-provider.interface.ts @@ -0,0 +1,100 @@ +import { AnyObject } from '../../../entity'; +import { Payment, PaymentMethod } from '../models'; + +/** + * Interface for a payment provider. + */ +export interface PaymentProviderInterface< + SupportedMethods extends readonly PaymentMethod[], + // eslint-disable-next-line jsdoc/require-jsdoc + PaymentDataForMethod extends Record, + // eslint-disable-next-line jsdoc/require-jsdoc + ValidatedPaymentDataForMethod extends Record, + ProviderPaymentDataForMethod extends Record, + ProviderReservationPaymentDataForMethod extends Record, + CancellationSupport extends Record, + RefundSupport extends Record, + ReservationSupport extends Record +> { + /** + * The unique name of the provider, used to differentiate between them. + */ + readonly name: string, + /** + * A map that defines which payment methods support cancellation. + */ + readonly cancellationSupported: CancellationSupport, + /** + * A map that defines which payment methods support refunding. + */ + readonly refundSupported: RefundSupport, + /** + * A map that defines which payment methods support reservation. + */ + readonly reservationSupported: ReservationSupport, + /** + * Validates the given payment data for the given method. + */ + validatePaymentData: ( + method: M, + data: PaymentDataForMethod[M] + ) => ValidatedPaymentDataForMethod[M] | Promise, + /** + * Starts a new payment with the given method and data. + */ + startPayment: ( + method: M, + data: ValidatedPaymentDataForMethod[M] + ) => Payment | Promise>, + /** + * Starts a new payment reservation with the given method and data. + */ + startPaymentReservation: < + M extends { + [K in SupportedMethods[number]]: ReservationSupport[K] extends true ? K : never + }[SupportedMethods[number]] + >( + method: M, + data: ValidatedPaymentDataForMethod[M] + ) => Payment | Promise>, + /** + * Confirms The given payment reservation. This does NOT move any money yet, it just reserves the funds. + */ + confirmPaymentReservation: < + M extends { + [K in SupportedMethods[number]]: ReservationSupport[K] extends true ? K : never + }[SupportedMethods[number]] + >(payment: Payment) => void | Promise, + /** + * Confirms and finishes a payment. + */ + confirmPayment: ( + payment: Payment + ) => void | Promise, + /** + * Collects payment from the given payment reservation. + */ + collectPaymentFromReservation: < + M extends { + [K in SupportedMethods[number]]: ReservationSupport[K] extends true ? K : never + }[SupportedMethods[number]] + >(payment: Payment) => void | Promise, + /** + * Cancels the given payment. + */ + cancelPayment: < + M extends { + [K in SupportedMethods[number]]: CancellationSupport[K] extends true ? K : ReservationSupport[K] extends true ? K : never + }[SupportedMethods[number]] + >( + payment: Payment | Payment + ) => void | Promise, + /** + * Refunds the given payment. + */ + refundPayment: < + M extends { + [K in SupportedMethods[number]]: RefundSupport[K] extends true ? K : never + }[SupportedMethods[number]] + >(payment: Payment) => void | Promise +} \ No newline at end of file diff --git a/src/plugin/payment/services/index.ts b/src/plugin/payment/services/index.ts new file mode 100644 index 0000000..bed72f8 --- /dev/null +++ b/src/plugin/payment/services/index.ts @@ -0,0 +1,3 @@ +export * from './payment-service.interface'; +export * from './payment-service.types'; +export * from './payment.service'; \ No newline at end of file diff --git a/src/plugin/payment/services/payment-service.interface.ts b/src/plugin/payment/services/payment-service.interface.ts new file mode 100644 index 0000000..0b747dc --- /dev/null +++ b/src/plugin/payment/services/payment-service.interface.ts @@ -0,0 +1,73 @@ +import { AnyObject } from '../../../entity'; +import { PaymentMethod } from '../models'; +import { PaymentProviderInterface } from '../providers'; +import { AllowedCancellationMethods, AllowedRefundMethods, AllowedReservationMethods, PaymentDataForMethod, PaymentForMethod, PaymentReservationForMethod, ValidatedPaymentDataForMethod } from './payment-service.types'; + +/** + * Interface for a payment service. + */ +export interface PaymentServiceInterface< + Methods extends readonly PaymentMethod[], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> { + /** + * Validates the given data of the given payment method. + */ + validatePaymentData: ( + method: M, + data: PaymentDataForMethod + ) => ValidatedPaymentDataForMethod | Promise>, + /** + * Starts a payment with the given method and data. + */ + startPayment: ( + method: M, + data: ValidatedPaymentDataForMethod + ) => PaymentForMethod | Promise>, + /** + * Starts a payment reservation. + */ + startPaymentReservation: >( + method: M, + data: ValidatedPaymentDataForMethod + ) => PaymentReservationForMethod | Promise>, + /** + * Confirms a payment reservations. This just reserves/freezes the funds, it does NOT move any money yet. + */ + confirmPaymentReservation: >( + payment: PaymentReservationForMethod + ) => void | Promise, + /** + * Confirms the given payment. + */ + confirmPayment: (payment: PaymentForMethod) => void | Promise, + /** + * Collects a payment of the given reserved payment. + */ + collectPaymentFromReservation: >( + payment: PaymentReservationForMethod + ) => void | Promise, + /** + * Cancels the given payment. Only works if it has not been paid already. + */ + cancelPayment: >( + payment: PaymentForMethod | PaymentReservationForMethod + ) => void | Promise, + /** + * Refunds the given payment. + */ + refundPayment: >( + payment: PaymentForMethod + ) => void | Promise +} \ No newline at end of file diff --git a/src/plugin/payment/services/payment-service.types.ts b/src/plugin/payment/services/payment-service.types.ts new file mode 100644 index 0000000..623691b --- /dev/null +++ b/src/plugin/payment/services/payment-service.types.ts @@ -0,0 +1,258 @@ +/* eslint-disable unusedImports/no-unused-vars */ +import { AnyObject } from '../../../entity'; +import { Payment, PaymentMethod, PaymentPluginOptions } from '../models'; +import { PaymentProviderInterface } from '../providers'; + +/** + * Helper: pick the provider *instance* (from the P tuple) for the given method M. + */ +type ProviderInstanceForMethod< + Methods extends readonly PaymentMethod[], + M extends Methods[number], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> = Extract< + P[number], + // eslint-disable-next-line jsdoc/require-jsdoc + { name: PaymentPluginOptions['providerNameForMethod'][M] } +>; + +/** + * PaymentDataForMethod: the incoming (raw) data type accepted by the provider. + */ +export type PaymentDataForMethod< + Methods extends readonly PaymentMethod[], + M extends Methods[number], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> + = ProviderInstanceForMethod extends PaymentProviderInterface< + infer _SM, + infer PaymentDataMap, + infer _ValidatedMap, + infer _ProviderPaymentData, + infer _ProviderReservationPaymentData, + infer _CS, + infer _RS, + infer _ReservationSupport + > + ? PaymentDataMap[M] + : never; + +/** + * ValidatedPaymentDataForMethod: the validated/normalized data type returned by validatePaymentData. + */ +export type ValidatedPaymentDataForMethod< + Methods extends readonly PaymentMethod[], + M extends Methods[number], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> + = ProviderInstanceForMethod extends PaymentProviderInterface< + infer _SM, + infer _PaymentDataMap, + infer ValidatedMap, + infer _ProviderPaymentData, + infer _ProviderReservationPaymentData, + infer _CS, + infer _RS, + infer _ReservationSupport + > + ? ValidatedMap[M] + : never; + +/** + * The payment entity type used for payments. + */ +export type PaymentForMethod< + Methods extends readonly PaymentMethod[], + M extends Methods[number], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> + = ProviderInstanceForMethod extends PaymentProviderInterface< + infer _SM, + infer _PaymentDataMap, + infer _ValidatedMap, + infer ProviderPaymentDataMap, + infer _ProviderReservationPaymentData, + infer _CS, + infer _RS, + infer _ReservationSupport + > + ? Payment + : never; + +/** + * The payment entity type used for reservation payments. + */ +export type PaymentReservationForMethod< + Methods extends readonly PaymentMethod[], + M extends Methods[number], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> + = ProviderInstanceForMethod extends PaymentProviderInterface< + infer _SM, + infer _PaymentDataMap, + infer _ValidatedMap, + infer _ProviderPaymentDataMap, + infer ProviderReservationPaymentDataMap, + infer _CS, + infer _RS, + infer _ReservationSupport + > + ? Payment + : never; + +/** + * Filters to all methods that are allowed for reservation. + */ +export type AllowedReservationMethods< + Methods extends readonly PaymentMethod[], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> = { + [K in Methods[number]]: ProviderInstanceForMethod extends PaymentProviderInterface< + infer _SM, + infer _PaymentDataMap, + infer _ValidatedMap, + infer _ProviderPaymentDataMap, + infer _ProviderReservationPaymentDataMap, + infer _CancellationSupport, + infer _RefundSupport, + infer ReservationSupport + > + ? ReservationSupport[K] extends true + ? K + : never + : never; +}[Methods[number]]; + +/** + * Filters to all methods that are allowed for cancellation. + */ +export type AllowedCancellationMethods< + Methods extends readonly PaymentMethod[], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> = { + [K in Methods[number]]: ProviderInstanceForMethod extends PaymentProviderInterface< + infer _SM, + infer _PaymentDataMap, + infer _ValidatedMap, + infer _ProviderPaymentDataMap, + infer _ProviderReservationPaymentDataMap, + infer CancellationSupport, + infer _RefundSupport, + infer ReservationSupport + > + ? CancellationSupport[K] extends true + ? K + : ReservationSupport[K] extends true ? K : never + : never; +}[Methods[number]]; + +/** + * Filters to all methods that are allowed for refunding. + */ +export type AllowedRefundMethods< + Methods extends readonly PaymentMethod[], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +> = { + [K in Methods[number]]: ProviderInstanceForMethod extends PaymentProviderInterface< + infer _SM, + infer _PaymentDataMap, + infer _ValidatedMap, + infer _ProviderPaymentDataMap, + infer _ProviderReservationPaymentDataMap, + infer _CancellationSupport, + infer _RefundSupport, + infer _ReservationSupport + > + ? _RefundSupport[K] extends true + ? K + : never + : never; +}[Methods[number]]; \ No newline at end of file diff --git a/src/plugin/payment/services/payment.service.ts b/src/plugin/payment/services/payment.service.ts new file mode 100644 index 0000000..5a322d3 --- /dev/null +++ b/src/plugin/payment/services/payment.service.ts @@ -0,0 +1,159 @@ +import { Inject, InjectRepository, ZIBRI_DI_TOKENS } from '../../../di'; +import { AnyObject } from '../../../entity'; +import { type LoggerInterface } from '../../../logging'; +import { Payment, PaymentMethod, PaymentStatus, type PaymentPluginOptions } from '../models'; +import { ZIBRI_PAYMENT_DI_TOKENS } from '../payment.tokens'; +import { PaymentProviderInterface } from '../providers'; +import { PaymentServiceInterface } from './payment-service.interface'; +import { AllowedCancellationMethods, AllowedRefundMethods, AllowedReservationMethods, PaymentDataForMethod, PaymentForMethod, PaymentReservationForMethod, ValidatedPaymentDataForMethod } from './payment-service.types'; +import { Repository } from '../../../data-source'; + +/** + * Default payment service implementation of zibri. + */ +export class PaymentService< + Methods extends readonly PaymentMethod[], + P extends readonly PaymentProviderInterface< + Methods[number][], + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + // eslint-disable-next-line jsdoc/require-jsdoc + Record, + Record, + Record, + Record, + Record, + Record + >[] +>implements PaymentServiceInterface { + + constructor( + @Inject(ZIBRI_PAYMENT_DI_TOKENS.OPTIONS) + protected readonly options: PaymentPluginOptions, + @Inject(ZIBRI_DI_TOKENS.LOGGER) + protected readonly logger: LoggerInterface, + @InjectRepository(Payment) + protected readonly paymentRepository: Repository> + ) {} + + private findPaymentProviderForMethod(method: M): P[number] { + const name: string = this.options.providerNameForMethod[method]; + const provider: P[number] | undefined = this.options.paymentProviders.find(p => p.name === name); + if (!provider) { + throw new Error(`No provider found for payment method "${method}"`); + } + return provider; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async validatePaymentData( + method: M, + data: PaymentDataForMethod + ): Promise> { + const provider: P[number] = this.findPaymentProviderForMethod(method); + const res: ValidatedPaymentDataForMethod = await provider.validatePaymentData( + method, + data + ) as ValidatedPaymentDataForMethod; + if ((await this.paymentRepository.findAll({ where: { transactionId: data.transactionId } })).length) { + throw new Error(`a payment for the transactionId "${data.transactionId}" already exists`); + } + return res; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async startPayment( + method: M, + data: ValidatedPaymentDataForMethod + ): Promise> { + const provider: P[number] = this.findPaymentProviderForMethod(method); + return await provider.startPayment(method, data) as PaymentForMethod; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async startPaymentReservation>( + method: M, + data: ValidatedPaymentDataForMethod + ): Promise> { + const provider: P[number] = this.findPaymentProviderForMethod(method); + return await provider.startPayment(method, data) as PaymentReservationForMethod; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async confirmPaymentReservation>( + payment: PaymentReservationForMethod + ): Promise { + if (payment.status === PaymentStatus.RESERVED) { + // already reserved, a warning is enough here as this is probably just a timing problem. + await this.logger.warn( + `Tried to confirm a payment reservation (transactionId: ${payment.transactionId}) that has already been reserved` + ); + return undefined; + } + if (payment.status !== PaymentStatus.CREATED) { + throw new Error(`Cannot confirm payment in status ${payment.status}`); + } + const provider: P[number] = this.findPaymentProviderForMethod(payment.paymentMethod); + await provider.confirmPaymentReservation(payment); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async confirmPayment(payment: PaymentForMethod): Promise { + if (payment.status === PaymentStatus.PAID) { + // already confirmed, a warning is enough here as this is probably just a timing problem. + await this.logger.warn(`Tried to confirm a payment (transactionId: ${payment.transactionId}) that has already been confirmed`); + return undefined; + } + if (payment.status !== PaymentStatus.CREATED) { + throw new Error(`Cannot confirm payment in status ${payment.status}`); + } + const provider: P[number] = this.findPaymentProviderForMethod(payment.paymentMethod); + await provider.confirmPayment(payment); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async collectPaymentFromReservation>( + payment: PaymentReservationForMethod + ): Promise { + if (payment.status === PaymentStatus.PAID) { + // already confirmed, a warning is enough here as this is probably just a timing problem. + await this.logger.warn(`Tried to collect a payment (transactionId: ${payment.transactionId}) that has already been collected`); + return undefined; + } + if (payment.status !== PaymentStatus.RESERVED) { + throw new Error('Can only collect payments from reserved payments'); + } + const provider: P[number] = this.findPaymentProviderForMethod(payment.paymentMethod); + await provider.collectPaymentFromReservation(payment); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async cancelPayment>( + payment: PaymentForMethod | PaymentReservationForMethod + ): Promise { + if (payment.status === PaymentStatus.CANCELLED) { + // already cancelled, a warning is enough here as this is probably just a timing problem. + await this.logger.warn(`Tried to collect a payment (transactionId: ${payment.transactionId}) that has already been collected`); + return undefined; + } + if (payment.status !== PaymentStatus.CREATED && payment.status !== PaymentStatus.RESERVED) { + throw new Error(`Cannot cancel payment in status ${payment.status}`); + } + const provider: P[number] = this.findPaymentProviderForMethod(payment.paymentMethod); + await provider.cancelPayment(payment); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + async refundPayment>(payment: PaymentForMethod): Promise { + if (payment.status === PaymentStatus.REFUNDED) { + // already refunded, a warning is enough here as this is probably just a timing problem. + await this.logger.warn(`Tried to refund a payment (transactionId: ${payment.transactionId}) that has already been refunded`); + return undefined; + } + if (payment.status !== PaymentStatus.PAID) { + throw new Error(`Cannot refund payment in status ${payment.status}`); + } + const provider: P[number] = this.findPaymentProviderForMethod(payment.paymentMethod); + await provider.refundPayment(payment); + } +} \ No newline at end of file diff --git a/src/routing/resolve-route-params.function.ts b/src/routing/resolve-route-params.function.ts index 4553968..80bd3dd 100644 --- a/src/routing/resolve-route-params.function.ts +++ b/src/routing/resolve-route-params.function.ts @@ -2,7 +2,7 @@ import { AuthServiceInterface, CurrentUserMetadata } from '../auth'; import { HttpRequest } from '../http'; import { ParserInterface } from '../parsing'; import { Newable } from '../types'; -import { MetadataUtilities } from '../utilities'; +import { MetadataUtilities, ObjectUtilities } from '../utilities'; import { ValidationServiceInterface } from '../validation'; import { BaseWebsocketConnection, CurrentWebsocketConnectionMetadata, WebsocketRequest } from '../websocket'; import { BodyMetadata, HeaderParamMetadata, PathParamMetadata, QueryParamMetadata } from './decorators'; @@ -25,12 +25,12 @@ export async function resolveRouteParams( // 1) Path decorators const pathParams: Record = MetadataUtilities.getRoutePathParams(controllerClass, controllerMethod); - for (const [indexStr, metadata] of Object.entries(pathParams)) { + for (const [indexStr, metadata] of ObjectUtilities.entries(pathParams)) { const idx: number = Number(indexStr); params[idx] = parser.parsePathParam(req, metadata); validationService.validatePathParam(params[idx], metadata); } - resolvedParamCount += Object.keys(pathParams).length; + resolvedParamCount += ObjectUtilities.keys(pathParams).length; // 2) Body decorator const requestBody: BodyMetadata | undefined = MetadataUtilities.getRouteBody(controllerClass, controllerMethod); @@ -42,21 +42,21 @@ export async function resolveRouteParams( // 3) Query decorators const queryParams: Record = MetadataUtilities.getRouteQueryParams(controllerClass, controllerMethod); - for (const [indexStr, metadata] of Object.entries(queryParams)) { + for (const [indexStr, metadata] of ObjectUtilities.entries(queryParams)) { const idx: number = Number(indexStr); params[idx] = parser.parseQueryParam(req, metadata); validationService.validateQueryParam(params[idx], metadata); } - resolvedParamCount += Object.keys(queryParams).length; + resolvedParamCount += ObjectUtilities.keys(queryParams).length; // 3) Header decorators const headerParams: Record = MetadataUtilities.getRouteHeaderParams(controllerClass, controllerMethod); - for (const [indexStr, metadata] of Object.entries(headerParams)) { + for (const [indexStr, metadata] of ObjectUtilities.entries(headerParams)) { const idx: number = Number(indexStr); params[idx] = parser.parseHeaderParam(req, metadata); validationService.validateHeaderParam(params[idx], metadata); } - resolvedParamCount += Object.keys(headerParams).length; + resolvedParamCount += ObjectUtilities.keys(headerParams).length; // 4) CurrentUser decorator const currentUser: CurrentUserMetadata | undefined = MetadataUtilities.getRouteCurrentUser(controllerClass, controllerMethod); diff --git a/src/types/index.ts b/src/types/index.ts index 1575221..9434e5b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,5 @@ export * from './percentage.type'; -export * from './newable.model'; +export * from './newable.type'; export * from './deep-partial.type'; export * from './omit-strict.type'; export * from './version.type'; diff --git a/src/types/newable.model.ts b/src/types/newable.type.ts similarity index 100% rename from src/types/newable.model.ts rename to src/types/newable.type.ts diff --git a/src/utilities/chunked-promise-all.function.ts b/src/utilities/chunked-promise-all.function.ts deleted file mode 100644 index 8a97306..0000000 --- a/src/utilities/chunked-promise-all.function.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Options for chunking. - */ -export type ChunkingOptions = { - /** - * The size of a single chunk. - */ - chunkSize: number -}; - -/** - * Like Promise.all, but chunked. - * @param promises - The promises to resolve. - * @param options - Options for chunking, like the size of chunks etc. - * @returns The resolved promises after all chunks have been resolved. - */ -export async function chunkedPromiseAll( - promises: Promise[], - options: ChunkingOptions = { - chunkSize: 50 - } -): Promise { - const res: T[] = []; - const chunkSize: number = options.chunkSize; - - for (let i: number = 0; i < promises.length; i += chunkSize) { - const p: Promise[] = promises.slice(i, i + chunkSize); - res.push(...await Promise.all(p)); - } - - return res; -} \ No newline at end of file diff --git a/src/utilities/index.ts b/src/utilities/index.ts index d092f0c..995f400 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -1,6 +1,6 @@ export * from './to-snake-case.function'; export * from './big-number.utilities'; -export * from './chunked-promise-all.function'; +export * from './promise.utilities'; export * from './compare-versions.function'; export * from './is-date.function'; export * from './is-numeric.function'; @@ -13,4 +13,5 @@ export * from './validate-entities-registered.function'; export * from './uuid.utilities'; export * from './to-kebab-case.function'; export * from './to-pascal-case.function'; -export * from './to-camel-case.function'; \ No newline at end of file +export * from './to-camel-case.function'; +export * from './object.utilities'; \ No newline at end of file diff --git a/src/utilities/object.utilities.ts b/src/utilities/object.utilities.ts new file mode 100644 index 0000000..828776a --- /dev/null +++ b/src/utilities/object.utilities.ts @@ -0,0 +1,33 @@ +/** + * Utilities for handling objects. + */ +export abstract class ObjectUtilities { + /** + * Gets the correctly typed keys for the given object. + * @param obj - The object to get the keys from. + * @returns The correctly typed keys as an array. + */ + static keys(obj: T): Extract<(keyof T), string>[] { + return Object.keys(obj) as Extract<(keyof T), string>[]; + } + + /** + * Resolves the values of the given object. + * @param obj - The object to get the values from. + * @returns An array of all values of the objects properties. + */ + static values(obj: T): T[(keyof T)][] { + return Object.values(obj) as T[(keyof T)][]; + } + + /** + * Resolves the KeyValue entries of the given object. + * @param obj - The object to get the entries from. + * @returns An array of tuples of the key and the value of the objects properties. + */ + static entries( + obj: T + ): [Extract<(keyof T), string>, T[Extract<(keyof T), string>]][] { + return this.keys(obj).map((key) => [key, obj[key]]); + } +} \ No newline at end of file diff --git a/src/utilities/promise.utilities.ts b/src/utilities/promise.utilities.ts new file mode 100644 index 0000000..c724c18 --- /dev/null +++ b/src/utilities/promise.utilities.ts @@ -0,0 +1,70 @@ +/** + * Options for chunking. + */ +export type ChunkingOptions = { + /** + * The size of a single chunk. + */ + chunkSize: number +}; + +/** + * Encapsulates functionality for handling promises. + */ +export abstract class PromiseUtilities { + private static readonly defaultChunkSize: number = 50; + + /** + * Like Promise.all, but chunked. + * @param items - The items that are mapped to promises. + * @param fn - The async function that each item is mapped to. + * @param options - Options for chunking, like the size of chunks etc. + * @returns The resolved promises after all chunks have been resolved. + */ + static async allChunked( + items: T[], + fn: (item: T) => Promise, + options?: ChunkingOptions + ): Promise[]> { + const results: Awaited[] = []; + const chunkSize: number = options?.chunkSize ?? this.defaultChunkSize; + + for (let i: number = 0; i < items.length; i += chunkSize) { + const promises: Promise[] = items.slice(i, i + chunkSize).map(fn); + results.push(...await Promise.all(promises)); + } + + return results; + } + /** + * Like Promise.any, but it doesn't only checks if the promise resolves at all, but if it does so with the value true. + * @param items - The items that are mapped to promises. + * @param fn - The async function that each item is mapped to. + * @param options - Options for chunking, like the size of chunks etc. + * @returns The first resolved promise that returns "true". + */ + static async anyValueTrue( + items: T[], + fn: (item: T) => Promise, + options?: ChunkingOptions + ): Promise { + const chunkSize: number = options?.chunkSize ?? this.defaultChunkSize; + + for (let i: number = 0; i < items.length; i += chunkSize) { + const res: boolean = await Promise.any( + items.slice(i, i + chunkSize).map(async item => { + const r: boolean = await fn(item); + if (!r) { + throw new Error('not true'); + } + return r; + }) + ).catch(() => false); + if (res) { + return res; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/validation/functions/validate-number.function.ts b/src/validation/functions/validate-number.function.ts index e9ca65d..cb5e6bc 100644 --- a/src/validation/functions/validate-number.function.ts +++ b/src/validation/functions/validate-number.function.ts @@ -1,5 +1,6 @@ import { NumberPropertyMetadata, PropertyMetadata } from '../../entity'; import { QueryParamMetadata, HeaderParamMetadata, PathParamMetadata, NumberParamMetadata } from '../../routing'; +import { ObjectUtilities } from '../../utilities'; import { IsRequiredValidationProblem, TypeMismatchValidationProblem, ValidationProblem } from '../validation-problem.model'; /** @@ -39,8 +40,8 @@ export function validateNumber( if (typeof property !== 'number') { return [new TypeMismatchValidationProblem(fullKey, 'number')]; } - if (meta.enum && !Object.values(meta.enum).includes(property)) { - return [{ key: fullKey, message: `needs to match one of "${Object.values(meta.enum)}"` }]; + if (meta.enum && !ObjectUtilities.values(meta.enum).includes(property)) { + return [{ key: fullKey, message: `needs to match one of "${ObjectUtilities.values(meta.enum)}"` }]; } if (meta.min != undefined && property < meta.min) { return [{ key: fullKey, message: `needs to be at least ${meta.min}` }]; diff --git a/src/validation/functions/validate-string.function.ts b/src/validation/functions/validate-string.function.ts index c9d093b..dae9280 100644 --- a/src/validation/functions/validate-string.function.ts +++ b/src/validation/functions/validate-string.function.ts @@ -1,6 +1,7 @@ import { PropertyMetadata, StringFormat, StringPropertyMetadata } from '../../entity'; import { HeaderParamMetadata, PathParamMetadata, QueryParamMetadata, StringParamMetadata } from '../../routing'; +import { ObjectUtilities } from '../../utilities'; import { IsRequiredValidationProblem, TypeMismatchValidationProblem, ValidationProblem } from '../validation-problem.model'; const UUID_REGEX: RegExp = /^[\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[1-5][\dA-Fa-f]{3}-[89ABab][\dA-Fa-f]{3}-[\dA-Fa-f]{12}$/; @@ -50,8 +51,8 @@ export function validateString( if (meta.regex != undefined && !new RegExp(meta.regex).test(property)) { return [{ key: fullKey, message: `needs to match regex "${meta.regex}"` }]; } - if (meta.enum && !Object.values(meta.enum).includes(property)) { - return [{ key: fullKey, message: `needs to match one of "${Object.values(meta.enum)}"` }]; + if (meta.enum && !ObjectUtilities.values(meta.enum).includes(property)) { + return [{ key: fullKey, message: `needs to match one of "${ObjectUtilities.values(meta.enum)}"` }]; } if (meta.minLength && meta.minLength > property.length) { return [{ key: fullKey, message: `needs to be at least ${meta.minLength} characters long` }]; diff --git a/src/validation/validation.service.ts b/src/validation/validation.service.ts index b615b43..4872edd 100644 --- a/src/validation/validation.service.ts +++ b/src/validation/validation.service.ts @@ -6,7 +6,7 @@ import { MimeType } from '../http'; import { FormData } from '../parsing'; import { BodyMetadata, HeaderParamMetadata, PathParamMetadata, QueryParamMetadata } from '../routing'; import { ExcludeStrict, Newable, OmitStrict } from '../types'; -import { MetadataUtilities } from '../utilities'; +import { MetadataUtilities, ObjectUtilities } from '../utilities'; import { WebsocketRequest } from '../websocket'; import { validateBoolean, validateDate, validateFile, validateNumber, validateString } from './functions'; import { IsRequiredValidationProblem, RelationsNotAllowedValidationProblem, TypeMismatchValidationProblem, ValidationProblem } from './validation-problem.model'; @@ -203,8 +203,8 @@ export class ValidationService implements ValidationServiceInterface { ): ValidationProblem[] { const modelProperties: Record = MetadataUtilities.getModelProperties(cls); - const keysOfBody: string[] = Object.keys(body as Record); - const keysOfModel: string[] = Object.keys(modelProperties); + const keysOfBody: string[] = ObjectUtilities.keys(body as Record); + const keysOfModel: string[] = ObjectUtilities.keys(modelProperties); const unknownKeys: string[] = keysOfBody.filter(k => !keysOfModel.includes(k)); const res: ValidationProblem[] = []; for (const key of unknownKeys) { @@ -214,7 +214,7 @@ export class ValidationService implements ValidationServiceInterface { const fullKey: string = parentKey ? `${parentKey}.${key}` : key; res.push({ key: fullKey, message: 'this key is unknown' }); } - for (const [propertyKey, metadata] of Object.entries(modelProperties)) { + for (const [propertyKey, metadata] of ObjectUtilities.entries(modelProperties)) { const property: unknown = (body as Record)[propertyKey]; const errors: ValidationProblem[] = this.validateProperty(propertyKey, property, metadata, parentKey, body); res.push(...errors); @@ -308,8 +308,8 @@ export class ValidationService implements ValidationServiceInterface { const res: ValidationProblem[] = []; if (!metadata.allowAdditionalProperties) { - const keysOfBody: string[] = Object.keys(property as Record); - const keysOfModel: string[] = Object.keys(objectProperties); + const keysOfBody: string[] = ObjectUtilities.keys(property as Record); + const keysOfModel: string[] = ObjectUtilities.keys(objectProperties); const unknownKeys: string[] = keysOfBody.filter(k => !keysOfModel.includes(k)); for (const k of unknownKeys) { @@ -320,7 +320,7 @@ export class ValidationService implements ValidationServiceInterface { } } - for (const [propertyKey, m] of Object.entries(objectProperties)) { + for (const [propertyKey, m] of ObjectUtilities.entries(objectProperties)) { const childProperty: unknown = (property as Record)[propertyKey]; const errors: ValidationProblem[] = this.validateProperty(propertyKey, childProperty, m, key, entity); res.push(...errors); diff --git a/tsconfig.eslint.json b/tsconfig.json similarity index 100% rename from tsconfig.eslint.json rename to tsconfig.json