diff --git a/examples/pnpm-lock.yaml b/examples/pnpm-lock.yaml
index 7ee3ba0..ca6e10e 100644
--- a/examples/pnpm-lock.yaml
+++ b/examples/pnpm-lock.yaml
@@ -33,7 +33,7 @@ dependencies:
devDependencies:
'@stutzlab/eslint-config':
specifier: ^3.1.1
- version: 3.1.1(@typescript-eslint/eslint-plugin@7.18.0)(@typescript-eslint/parser@7.18.0)(eslint-config-airbnb-base@15.0.0)(eslint-config-airbnb-typescript@18.0.0)(eslint-config-prettier@9.1.0)(eslint-import-resolver-typescript@3.6.3)(eslint-plugin-fp@2.3.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jest@28.8.1)(eslint-plugin-prettier@5.2.1)(eslint-plugin-promise@6.6.0)(eslint@8.57.0)(prettier@3.3.3)
+ version: 3.1.1(@typescript-eslint/eslint-plugin@7.18.0)(@typescript-eslint/parser@7.18.0)(eslint-config-airbnb-base@15.0.0)(eslint-config-airbnb-typescript@18.0.0)(eslint-config-prettier@9.1.0)(eslint-import-resolver-typescript@3.6.3)(eslint-plugin-fp@2.3.0)(eslint-plugin-import@2.30.0)(eslint-plugin-jest@28.8.3)(eslint-plugin-prettier@5.2.1)(eslint-plugin-promise@6.6.0)(eslint@8.57.0)(prettier@3.3.3)
'@tsconfig/node20':
specifier: ^20.1.4
version: 20.1.4
@@ -1155,8 +1155,8 @@ packages:
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: true
- /@eslint-community/regexpp@4.11.0:
- resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==}
+ /@eslint-community/regexpp@4.11.1:
+ resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: true
@@ -1577,6 +1577,10 @@ packages:
rollup: 2.79.1
dev: false
+ /@rtsao/scc@1.1.0:
+ resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+ dev: true
+
/@sinclair/typebox@0.27.8:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
dev: true
@@ -2029,7 +2033,7 @@ packages:
resolution: {integrity: sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A==}
engines: {node: '>=8.3.0'}
dependencies:
- '@stoplight/json': 3.21.6
+ '@stoplight/json': 3.21.7
'@stoplight/path': 1.3.2
'@stoplight/types': 13.20.0
'@types/urijs': 1.19.25
@@ -2041,8 +2045,8 @@ packages:
urijs: 1.19.11
dev: false
- /@stoplight/json@3.21.6:
- resolution: {integrity: sha512-KGisXfNigoYdWIj1jA4p3IAAIW5YFpU9BdoECdjyDLBbhWGGHzs77e0STSCBmXQ/K3ApxfED2R7mQ79ymjzlvQ==}
+ /@stoplight/json@3.21.7:
+ resolution: {integrity: sha512-xcJXgKFqv/uCEgtGlPxy3tPA+4I+ZI4vAuMJ885+ThkTHFVkC+0Fm58lA9NlsyjnkpxFh4YiQWpH+KefHdbA0A==}
engines: {node: '>=8.3.0'}
dependencies:
'@stoplight/ordered-object-literal': 1.0.5
@@ -2063,20 +2067,20 @@ packages:
engines: {node: '>=8'}
dev: false
- /@stoplight/spectral-cli@6.11.1:
- resolution: {integrity: sha512-1zqsQ0TOuVSnxxZ9mHBfC0IygV6ex7nAY6Mp59mLmw5fW103U9yPVK5ZcX9ZngCmr3PdteAnMDUIIaoDGso6nA==}
+ /@stoplight/spectral-cli@6.13.1:
+ resolution: {integrity: sha512-v6ipX4w6wRhtbOotwdPL7RrEkP0m1OwHTIyqzVrAPi932F/zkee24jmf1CHNrTynonmfGoU6/XpeqUHtQdKDFw==}
engines: {node: ^12.20 || >= 14.13}
hasBin: true
dependencies:
- '@stoplight/json': 3.21.6
+ '@stoplight/json': 3.21.7
'@stoplight/path': 1.3.2
- '@stoplight/spectral-core': 1.18.3
- '@stoplight/spectral-formatters': 1.3.0
+ '@stoplight/spectral-core': 1.19.1
+ '@stoplight/spectral-formatters': 1.4.0
'@stoplight/spectral-parsers': 1.0.4
'@stoplight/spectral-ref-resolver': 1.0.4
- '@stoplight/spectral-ruleset-bundler': 1.5.2
- '@stoplight/spectral-ruleset-migrator': 1.9.5
- '@stoplight/spectral-rulesets': 1.19.1
+ '@stoplight/spectral-ruleset-bundler': 1.6.0
+ '@stoplight/spectral-ruleset-migrator': 1.10.0
+ '@stoplight/spectral-rulesets': 1.20.2
'@stoplight/spectral-runtime': 1.1.2
'@stoplight/types': 13.20.0
chalk: 4.1.2
@@ -2091,12 +2095,12 @@ packages:
- encoding
dev: false
- /@stoplight/spectral-core@1.18.3:
- resolution: {integrity: sha512-YY8x7X2SWJIhGTLPol+eFiQpWPz0D0mJdkK2i4A0QJG68KkNhypP6+JBC7/Kz3XWjqr0L/RqAd+N5cQLPOKZGQ==}
+ /@stoplight/spectral-core@1.19.1:
+ resolution: {integrity: sha512-YiWhXdjyjn4vCl3102ywzwCEJzncxapFcj4dxcj1YP/bZ62DFeGJ8cEaMP164vSw2kI3rX7EMMzI/c8XOUnTfQ==}
engines: {node: ^12.20 || >= 14.13}
dependencies:
'@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1)
- '@stoplight/json': 3.21.6
+ '@stoplight/json': 3.21.7
'@stoplight/path': 1.3.2
'@stoplight/spectral-parsers': 1.0.4
'@stoplight/spectral-ref-resolver': 1.0.4
@@ -2120,29 +2124,31 @@ packages:
- encoding
dev: false
- /@stoplight/spectral-formats@1.6.0:
- resolution: {integrity: sha512-X27qhUfNluiduH0u/QwJqhOd8Wk5YKdxVmKM03Aijlx0AH1H5mYt3l9r7t2L4iyJrsBaFPnMGt7UYJDGxszbNA==}
+ /@stoplight/spectral-formats@1.7.0:
+ resolution: {integrity: sha512-vJ1vIkA2s96fdJp0d3AJBGuPAW3sj8yMamyzR+dquEFO6ZAoYBo/BVsKKQskYzZi/nwljlRqUmGVmcf2PncIaA==}
engines: {node: '>=12'}
dependencies:
- '@stoplight/json': 3.21.6
- '@stoplight/spectral-core': 1.18.3
+ '@stoplight/json': 3.21.7
+ '@stoplight/spectral-core': 1.19.1
'@types/json-schema': 7.0.15
tslib: 2.7.0
transitivePeerDependencies:
- encoding
dev: false
- /@stoplight/spectral-formatters@1.3.0:
- resolution: {integrity: sha512-ryuMwlzbPUuyn7ybSEbFYsljYmvTaTyD51wyCQs4ROzgfm3Yo5QDD0IsiJUzUpKK/Ml61ZX8ebgiPiRFEJtBpg==}
+ /@stoplight/spectral-formatters@1.4.0:
+ resolution: {integrity: sha512-nxYQldDzVg32pxQ4cMX27eMtB1A39ea+GSf0wIJ20mqkSBfIgLnRZ+GKkBxhgF9JzSolc4YtweydsubGQGd7ag==}
engines: {node: ^12.20 || >=14.13}
dependencies:
'@stoplight/path': 1.3.2
- '@stoplight/spectral-core': 1.18.3
+ '@stoplight/spectral-core': 1.19.1
'@stoplight/spectral-runtime': 1.1.2
'@stoplight/types': 13.20.0
+ '@types/markdown-escape': 1.1.3
chalk: 4.1.2
cliui: 7.0.4
lodash: 4.17.21
+ markdown-escape: 2.0.0
node-sarif-builder: 2.0.3
strip-ansi: 6.0.1
text-table: 0.2.0
@@ -2151,14 +2157,14 @@ packages:
- encoding
dev: false
- /@stoplight/spectral-functions@1.8.0:
- resolution: {integrity: sha512-ZrAkYA/ZGbuQ6EyG1gisF4yQ5nWP/+glcqVoGmS6kH6ekaynz2Yp6FL0oIamWj3rWedFUN7ppwTRUdo+9f/uCw==}
+ /@stoplight/spectral-functions@1.9.0:
+ resolution: {integrity: sha512-T+xl93ji8bpus4wUsTq8Qr2DSu2X9PO727rbxW61tTCG0s17CbsXOLYI+Ezjg5P6aaQlgXszGX8khtc57xk8Yw==}
engines: {node: '>=12'}
dependencies:
'@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1)
- '@stoplight/json': 3.21.6
- '@stoplight/spectral-core': 1.18.3
- '@stoplight/spectral-formats': 1.6.0
+ '@stoplight/json': 3.21.7
+ '@stoplight/spectral-core': 1.19.1
+ '@stoplight/spectral-formats': 1.7.0
'@stoplight/spectral-runtime': 1.1.2
ajv: 8.17.1
ajv-draft-04: 1.0.0(ajv@8.17.1)
@@ -2174,7 +2180,7 @@ packages:
resolution: {integrity: sha512-nCTVvtX6q71M8o5Uvv9kxU31Gk1TRmgD6/k8HBhdCmKG6FWcwgjiZouA/R3xHLn/VwTI/9k8SdG5Mkdy0RBqbQ==}
engines: {node: ^12.20 || >=14.13}
dependencies:
- '@stoplight/json': 3.21.6
+ '@stoplight/json': 3.21.7
'@stoplight/types': 14.1.1
'@stoplight/yaml': 4.3.0
tslib: 2.7.0
@@ -2193,19 +2199,19 @@ packages:
- encoding
dev: false
- /@stoplight/spectral-ruleset-bundler@1.5.2:
- resolution: {integrity: sha512-4QUVUFAU+S7IQ9XeCu+0TQMYxKFpKnkOAfa9unRQ1iPL2cviaipEN6witpbAptdHJD3UUjx4OnwlX8WwmXSq9w==}
+ /@stoplight/spectral-ruleset-bundler@1.6.0:
+ resolution: {integrity: sha512-8CU7e4aEGdfU9ncVDtlnJSawg/6epzAHrQTjuNu1QfKAOoiwyG7oUk2XUTHWcvq6Q67iUctb0vjOokR+MPVg0Q==}
engines: {node: ^12.20 || >= 14.13}
dependencies:
'@rollup/plugin-commonjs': 22.0.2(rollup@2.79.1)
'@stoplight/path': 1.3.2
- '@stoplight/spectral-core': 1.18.3
- '@stoplight/spectral-formats': 1.6.0
- '@stoplight/spectral-functions': 1.8.0
+ '@stoplight/spectral-core': 1.19.1
+ '@stoplight/spectral-formats': 1.7.0
+ '@stoplight/spectral-functions': 1.9.0
'@stoplight/spectral-parsers': 1.0.4
'@stoplight/spectral-ref-resolver': 1.0.4
- '@stoplight/spectral-ruleset-migrator': 1.9.5
- '@stoplight/spectral-rulesets': 1.19.1
+ '@stoplight/spectral-ruleset-migrator': 1.10.0
+ '@stoplight/spectral-rulesets': 1.20.2
'@stoplight/spectral-runtime': 1.1.2
'@stoplight/types': 13.20.0
'@types/node': 20.16.2
@@ -2217,14 +2223,14 @@ packages:
- encoding
dev: false
- /@stoplight/spectral-ruleset-migrator@1.9.5:
- resolution: {integrity: sha512-76n/HETr3UinVl/xLNldrH9p0JNoD8Gz4K75J6E4OHp4xD0P+BA2e8+W30HjIvqm1LJdLU2BNma0ioy+q3B9RA==}
+ /@stoplight/spectral-ruleset-migrator@1.10.0:
+ resolution: {integrity: sha512-nDfkVfYeWWv0UvILC4TWZSnRqQ4rHgeOJO1/lHQ7XHeG5iONanQ639B1aK6ZS6vuUc8gwuyQsrPF67b4sHIyYw==}
engines: {node: '>=12'}
dependencies:
- '@stoplight/json': 3.21.6
+ '@stoplight/json': 3.21.7
'@stoplight/ordered-object-literal': 1.0.5
'@stoplight/path': 1.3.2
- '@stoplight/spectral-functions': 1.8.0
+ '@stoplight/spectral-functions': 1.9.0
'@stoplight/spectral-runtime': 1.1.2
'@stoplight/types': 13.20.0
'@stoplight/yaml': 4.2.3
@@ -2239,16 +2245,16 @@ packages:
- encoding
dev: false
- /@stoplight/spectral-rulesets@1.19.1:
- resolution: {integrity: sha512-rfGK87Y1JJCEeLC8MVdLkjUkRH+Y6VnSF388D+UWihfU9xuq2eNB9phWpTFkG+AG4HLRyGx963BmO6PyM9dBag==}
+ /@stoplight/spectral-rulesets@1.20.2:
+ resolution: {integrity: sha512-7Y8orZuNyGyeHr9n50rMfysgUJ+/zzIEHMptt66jiy82GUWl+0nr865DkMuXdC5GryfDYhtjoRTUCVsXu80Nkg==}
engines: {node: '>=12'}
dependencies:
'@asyncapi/specs': 4.3.1
'@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1)
- '@stoplight/json': 3.21.6
- '@stoplight/spectral-core': 1.18.3
- '@stoplight/spectral-formats': 1.6.0
- '@stoplight/spectral-functions': 1.8.0
+ '@stoplight/json': 3.21.7
+ '@stoplight/spectral-core': 1.19.1
+ '@stoplight/spectral-formats': 1.7.0
+ '@stoplight/spectral-functions': 1.9.0
'@stoplight/spectral-runtime': 1.1.2
'@stoplight/types': 13.20.0
'@types/json-schema': 7.0.15
@@ -2266,7 +2272,7 @@ packages:
resolution: {integrity: sha512-fr5zRceXI+hrl82yAVoME+4GvJie8v3wmOe9tU+ZLRRNonizthy8qDi0Z/z4olE+vGreSDcuDOZ7JjRxFW5kTw==}
engines: {node: '>=12'}
dependencies:
- '@stoplight/json': 3.21.6
+ '@stoplight/json': 3.21.7
'@stoplight/path': 1.3.2
'@stoplight/types': 12.5.0
abort-controller: 3.0.0
@@ -2337,7 +2343,7 @@ packages:
tslib: 2.7.0
dev: false
- /@stutzlab/eslint-config@3.1.1(@typescript-eslint/eslint-plugin@7.18.0)(@typescript-eslint/parser@7.18.0)(eslint-config-airbnb-base@15.0.0)(eslint-config-airbnb-typescript@18.0.0)(eslint-config-prettier@9.1.0)(eslint-import-resolver-typescript@3.6.3)(eslint-plugin-fp@2.3.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jest@28.8.1)(eslint-plugin-prettier@5.2.1)(eslint-plugin-promise@6.6.0)(eslint@8.57.0)(prettier@3.3.3):
+ /@stutzlab/eslint-config@3.1.1(@typescript-eslint/eslint-plugin@7.18.0)(@typescript-eslint/parser@7.18.0)(eslint-config-airbnb-base@15.0.0)(eslint-config-airbnb-typescript@18.0.0)(eslint-config-prettier@9.1.0)(eslint-import-resolver-typescript@3.6.3)(eslint-plugin-fp@2.3.0)(eslint-plugin-import@2.30.0)(eslint-plugin-jest@28.8.3)(eslint-plugin-prettier@5.2.1)(eslint-plugin-promise@6.6.0)(eslint@8.57.0)(prettier@3.3.3):
resolution: {integrity: sha512-XmwQJE6UkSBmr2ZFGOprxVO/8Yioxu0vTzoLDm/0dB9E2RwGp1r6N4cyRT0MI8PCyYSUOz2kBH1aL5qpJkfFwQ==}
peerDependencies:
'@typescript-eslint/eslint-plugin': ^7.15.0
@@ -2357,13 +2363,13 @@ packages:
'@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.0)(typescript@5.5.4)
'@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
eslint: 8.57.0
- eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0)
- eslint-config-airbnb-typescript: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0)(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0)(eslint@8.57.0)
+ eslint-config-airbnb-typescript: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0)(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.30.0)(eslint@8.57.0)
eslint-config-prettier: 9.1.0(eslint@8.57.0)
- eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.30.0)(eslint@8.57.0)
eslint-plugin-fp: 2.3.0(eslint@8.57.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
- eslint-plugin-jest: 28.8.1(@typescript-eslint/eslint-plugin@7.18.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.5.4)
+ eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
+ eslint-plugin-jest: 28.8.3(@typescript-eslint/eslint-plugin@7.18.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.5.4)
eslint-plugin-prettier: 5.2.1(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.3.3)
eslint-plugin-promise: 6.6.0(eslint@8.57.0)
prettier: 3.3.3
@@ -2432,8 +2438,8 @@ packages:
resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
dev: false
- /@types/estree@1.0.5:
- resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
+ /@types/estree@1.0.6:
+ resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
dev: false
/@types/graceful-fs@4.1.9:
@@ -2473,6 +2479,10 @@ packages:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: true
+ /@types/markdown-escape@1.1.3:
+ resolution: {integrity: sha512-JIc1+s3y5ujKnt/+N+wq6s/QdL2qZ11fP79MijrVXsAAnzSxCbT2j/3prHRouJdZ2yFLN3vkP0HytfnoCczjOw==}
+ dev: false
+
/@types/node@20.16.2:
resolution: {integrity: sha512-91s/n4qUPV/wg8eE9KHYW1kouTfDk2FPGjXbBMfRWP/2vg1rCXNQL1OCabwGs0XSdukuK+MwCDXE30QpSeMUhQ==}
dependencies:
@@ -2527,7 +2537,7 @@ packages:
typescript:
optional: true
dependencies:
- '@eslint-community/regexpp': 4.11.0
+ '@eslint-community/regexpp': 4.11.1
'@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
'@typescript-eslint/scope-manager': 7.18.0
'@typescript-eslint/type-utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
@@ -2557,7 +2567,7 @@ packages:
'@typescript-eslint/types': 7.18.0
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4)
'@typescript-eslint/visitor-keys': 7.18.0
- debug: 4.3.6
+ debug: 4.3.7
eslint: 8.57.0
typescript: 5.5.4
transitivePeerDependencies:
@@ -2572,12 +2582,12 @@ packages:
'@typescript-eslint/visitor-keys': 7.18.0
dev: true
- /@typescript-eslint/scope-manager@8.3.0:
- resolution: {integrity: sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==}
+ /@typescript-eslint/scope-manager@8.7.0:
+ resolution: {integrity: sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
dependencies:
- '@typescript-eslint/types': 8.3.0
- '@typescript-eslint/visitor-keys': 8.3.0
+ '@typescript-eslint/types': 8.7.0
+ '@typescript-eslint/visitor-keys': 8.7.0
dev: true
/@typescript-eslint/type-utils@7.18.0(eslint@8.57.0)(typescript@5.5.4):
@@ -2592,7 +2602,7 @@ packages:
dependencies:
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4)
'@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
- debug: 4.3.6
+ debug: 4.3.7
eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.5.4)
typescript: 5.5.4
@@ -2605,8 +2615,8 @@ packages:
engines: {node: ^18.18.0 || >=20.0.0}
dev: true
- /@typescript-eslint/types@8.3.0:
- resolution: {integrity: sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==}
+ /@typescript-eslint/types@8.7.0:
+ resolution: {integrity: sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
dev: true
@@ -2621,7 +2631,7 @@ packages:
dependencies:
'@typescript-eslint/types': 7.18.0
'@typescript-eslint/visitor-keys': 7.18.0
- debug: 4.3.6
+ debug: 4.3.7
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.5
@@ -2632,8 +2642,8 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/typescript-estree@8.3.0(typescript@5.5.4):
- resolution: {integrity: sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==}
+ /@typescript-eslint/typescript-estree@8.7.0(typescript@5.5.4):
+ resolution: {integrity: sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '*'
@@ -2641,9 +2651,9 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/types': 8.3.0
- '@typescript-eslint/visitor-keys': 8.3.0
- debug: 4.3.6
+ '@typescript-eslint/types': 8.7.0
+ '@typescript-eslint/visitor-keys': 8.7.0
+ debug: 4.3.7
fast-glob: 3.3.2
is-glob: 4.0.3
minimatch: 9.0.5
@@ -2670,16 +2680,16 @@ packages:
- typescript
dev: true
- /@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.4):
- resolution: {integrity: sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==}
+ /@typescript-eslint/utils@8.7.0(eslint@8.57.0)(typescript@5.5.4):
+ resolution: {integrity: sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
- '@typescript-eslint/scope-manager': 8.3.0
- '@typescript-eslint/types': 8.3.0
- '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.4)
+ '@typescript-eslint/scope-manager': 8.7.0
+ '@typescript-eslint/types': 8.7.0
+ '@typescript-eslint/typescript-estree': 8.7.0(typescript@5.5.4)
eslint: 8.57.0
transitivePeerDependencies:
- supports-color
@@ -2694,11 +2704,11 @@ packages:
eslint-visitor-keys: 3.4.3
dev: true
- /@typescript-eslint/visitor-keys@8.3.0:
- resolution: {integrity: sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==}
+ /@typescript-eslint/visitor-keys@8.7.0:
+ resolution: {integrity: sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
dependencies:
- '@typescript-eslint/types': 8.3.0
+ '@typescript-eslint/types': 8.7.0
eslint-visitor-keys: 3.4.3
dev: true
@@ -3580,8 +3590,8 @@ packages:
ms: 2.1.2
dev: true
- /debug@4.3.6:
- resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
+ /debug@4.3.7:
+ resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
@@ -3589,7 +3599,7 @@ packages:
supports-color:
optional: true
dependencies:
- ms: 2.1.2
+ ms: 2.1.3
dev: true
/decode-uri-component@0.2.2:
@@ -3887,8 +3897,8 @@ packages:
engines: {node: '>=6'}
dev: true
- /escalade@3.1.2:
- resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
+ /escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
dev: false
@@ -3915,7 +3925,7 @@ packages:
lodash.zip: 4.2.0
dev: true
- /eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0):
+ /eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.30.0)(eslint@8.57.0):
resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==}
engines: {node: ^10.12.0 || >=12.0.0}
peerDependencies:
@@ -3924,13 +3934,13 @@ packages:
dependencies:
confusing-browser-globals: 1.0.11
eslint: 8.57.0
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
+ eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
object.assign: 4.1.5
object.entries: 1.1.8
semver: 6.3.1
dev: true
- /eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.18.0)(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0):
+ /eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.18.0)(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.30.0)(eslint@8.57.0):
resolution: {integrity: sha512-oc+Lxzgzsu8FQyFVa4QFaVKiitTYiiW3frB9KYW5OWdPrqFc7FzxgB20hP4cHMlr+MBzGcLl3jnCOVOydL9mIg==}
peerDependencies:
'@typescript-eslint/eslint-plugin': ^7.0.0
@@ -3940,7 +3950,7 @@ packages:
'@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.0)(typescript@5.5.4)
'@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
eslint: 8.57.0
- eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0)(eslint@8.57.0)
transitivePeerDependencies:
- eslint-plugin-import
dev: true
@@ -3964,7 +3974,7 @@ packages:
- supports-color
dev: true
- /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0):
+ /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.30.0)(eslint@8.57.0):
resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
@@ -3978,14 +3988,14 @@ packages:
optional: true
dependencies:
'@nolyfill/is-core-module': 1.0.39
- debug: 4.3.6
+ debug: 4.3.7
enhanced-resolve: 5.17.1
eslint: 8.57.0
- eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
+ eslint-module-utils: 2.11.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
+ eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
fast-glob: 3.3.2
- get-tsconfig: 4.8.0
- is-bun-module: 1.1.0
+ get-tsconfig: 4.8.1
+ is-bun-module: 1.2.1
is-glob: 4.0.3
transitivePeerDependencies:
- '@typescript-eslint/parser'
@@ -3994,8 +4004,8 @@ packages:
- supports-color
dev: true
- /eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
- resolution: {integrity: sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==}
+ /eslint-module-utils@2.11.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
+ resolution: {integrity: sha512-EwcbfLOhwVMAfatfqLecR2yv3dE5+kQ8kx+Rrt0DvDXEVwW86KQ/xbMDQhtp5l42VXukD5SOF8mQQHbaNtO0CQ==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
@@ -4019,13 +4029,13 @@ packages:
debug: 3.2.7
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.30.0)(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
dev: true
- /eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
- resolution: {integrity: sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==}
+ /eslint-module-utils@2.11.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
+ resolution: {integrity: sha512-EwcbfLOhwVMAfatfqLecR2yv3dE5+kQ8kx+Rrt0DvDXEVwW86KQ/xbMDQhtp5l42VXukD5SOF8mQQHbaNtO0CQ==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
@@ -4048,7 +4058,7 @@ packages:
'@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
debug: 3.2.7
eslint: 8.57.0
- eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.30.0)(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
dev: true
@@ -4066,8 +4076,8 @@ packages:
req-all: 0.1.0
dev: true
- /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
- resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
+ /eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
+ resolution: {integrity: sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
@@ -4076,6 +4086,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
+ '@rtsao/scc': 1.1.0
'@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
@@ -4085,7 +4096,7 @@ packages:
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
+ eslint-module-utils: 2.11.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -4101,8 +4112,8 @@ packages:
- supports-color
dev: true
- /eslint-plugin-jest@28.8.1(@typescript-eslint/eslint-plugin@7.18.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.5.4):
- resolution: {integrity: sha512-G46XMyYu6PtSNJUkQ0hsPjzXYpzq/O4vpCciMizTKRJG8kNsRreGoMRDG6H9FIB/xVgfFuclVnuX4XRvFUzrZQ==}
+ /eslint-plugin-jest@28.8.3(@typescript-eslint/eslint-plugin@7.18.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.5.4):
+ resolution: {integrity: sha512-HIQ3t9hASLKm2IhIOqnu+ifw7uLZkIlR7RYNv7fMcEi/p0CIiJmfriStQS2LDkgtY4nyLbIZAD+JL347Yc2ETQ==}
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
peerDependencies:
'@typescript-eslint/eslint-plugin': ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -4115,7 +4126,7 @@ packages:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.0)(typescript@5.5.4)
- '@typescript-eslint/utils': 8.3.0(eslint@8.57.0)(typescript@5.5.4)
+ '@typescript-eslint/utils': 8.7.0(eslint@8.57.0)(typescript@5.5.4)
eslint: 8.57.0
jest: 29.7.0(@types/node@20.16.2)(ts-node@10.9.2)
transitivePeerDependencies:
@@ -4630,8 +4641,8 @@ packages:
es-errors: 1.3.0
get-intrinsic: 1.2.4
- /get-tsconfig@4.8.0:
- resolution: {integrity: sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==}
+ /get-tsconfig@4.8.1:
+ resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==}
dependencies:
resolve-pkg-maps: 1.0.0
dev: true
@@ -4909,8 +4920,8 @@ packages:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
dev: true
- /is-bun-module@1.1.0:
- resolution: {integrity: sha512-4mTAVPlrXpaN3jtF0lsnPCMGnq4+qZjVIKq0HCpfcqf8OC1SM5oATCIAPM5V5FN05qp2NNnFndphmdZS9CV3hA==}
+ /is-bun-module@1.2.1:
+ resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==}
dependencies:
semver: 7.6.3
dev: true
@@ -5047,7 +5058,7 @@ packages:
/is-reference@1.2.1:
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
dependencies:
- '@types/estree': 1.0.5
+ '@types/estree': 1.0.6
dev: false
/is-regex@1.1.4:
@@ -5897,6 +5908,10 @@ packages:
object-visit: 1.0.1
dev: true
+ /markdown-escape@2.0.0:
+ resolution: {integrity: sha512-Trz4v0+XWlwy68LJIyw3bLbsJiC8XAbRCKF9DbEtZjyndKOGVx6n+wNB0VfoRmY2LKboQLeniap3xrb6LGSJ8A==}
+ dev: false
+
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true
@@ -7435,7 +7450,7 @@ packages:
engines: {node: '>=12'}
dependencies:
cliui: 8.0.1
- escalade: 3.1.2
+ escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
@@ -7458,7 +7473,7 @@ packages:
dev: false
file:../lib/dist/cdk-practical-constructs-0.0.1.tgz(@asteasolutions/zod-to-openapi@7.0.0)(aws-cdk-lib@2.155.0)(zod@3.23.8):
- resolution: {integrity: sha512-ZKTk0nCY2DMvvSi0dPxlUXITsd2ahY6cN4GIXyRs+EB65HSmAQj/ftDJtOyy26zJ3MeVW/fzrGo2/5e5xROuNA==, tarball: file:../lib/dist/cdk-practical-constructs-0.0.1.tgz}
+ resolution: {integrity: sha512-jkisyPDSbCIaaqO4z1PtlrxVvO74UDQQ7lf5PhU+eLwkzxPppEYK0KHQOy4pJ7uNhcRdNJ76K7zyuOOB3HiCxw==, tarball: file:../lib/dist/cdk-practical-constructs-0.0.1.tgz}
id: file:../lib/dist/cdk-practical-constructs-0.0.1.tgz
name: cdk-practical-constructs
version: 0.0.1
@@ -7470,7 +7485,7 @@ packages:
'@apiture/openapi-down-convert': 0.9.0
'@asteasolutions/zod-to-openapi': 7.0.0(zod@3.23.8)
'@aws-sdk/client-secrets-manager': 3.624.0
- '@stoplight/spectral-cli': 6.11.1
+ '@stoplight/spectral-cli': 6.13.1
aws-cdk-lib: 2.155.0(constructs@10.3.0)
aws-lambda: 1.0.7
axios: 1.7.5
diff --git a/lib/src/wso2/wso2-api/handler/index.test.ts b/lib/src/wso2/wso2-api/handler/index.test.ts
index 438a4ff..eef8c70 100644
--- a/lib/src/wso2/wso2-api/handler/index.test.ts
+++ b/lib/src/wso2/wso2-api/handler/index.test.ts
@@ -3,12 +3,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import nock from 'nock';
-import { mockClient } from 'aws-sdk-client-mock';
-import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { petstoreOpenapi } from '../__tests__/petstore';
import { ApiFromListV1, PublisherPortalAPIv1, Wso2ApiDefinitionV1 } from '../v1/types';
import { Wso2ApiCustomResourceProperties } from '../types';
+import { nockBasicWso2SDK } from '../../wso2-utils.test';
import { Wso2ApiCustomResourceEvent, handler } from './index';
@@ -44,7 +43,7 @@ describe('wso2 custom resource lambda', () => {
});
it('basic wso2 api create', async () => {
- nockBasicWso2SDK();
+ nockBasicWso2SDK(baseWso2Url);
// api list mock
nock(baseWso2Url)
@@ -88,7 +87,7 @@ describe('wso2 custom resource lambda', () => {
});
it('basic wso2 api update', async () => {
- nockBasicWso2SDK();
+ nockBasicWso2SDK(baseWso2Url);
// api list mock
const testDefs: Wso2ApiDefinitionV1 = {
@@ -136,7 +135,7 @@ describe('wso2 custom resource lambda', () => {
});
it('basic wso2 api change on UPDATE operation', async () => {
- nockBasicWso2SDK();
+ nockBasicWso2SDK(baseWso2Url);
// api list mock
const testDefs: Wso2ApiDefinitionV1 = {
@@ -181,7 +180,7 @@ describe('wso2 custom resource lambda', () => {
});
it('should pass with success if wso2 answers properly after a few retries', async () => {
- nockBasicWso2SDK();
+ nockBasicWso2SDK(baseWso2Url);
// api list mock
const testDefs: Wso2ApiDefinitionV1 = {
@@ -257,7 +256,7 @@ describe('wso2 custom resource lambda', () => {
});
it('should fail after retrying checking WSO2 api for a few times', async () => {
- nockBasicWso2SDK();
+ nockBasicWso2SDK(baseWso2Url);
// api list mock
const testDefs: Wso2ApiDefinitionV1 = {
@@ -317,7 +316,7 @@ describe('wso2 custom resource lambda', () => {
});
it('basic wso2 api delete on DELETE operation', async () => {
- nockBasicWso2SDK();
+ nockBasicWso2SDK(baseWso2Url);
// api update mock
nock(baseWso2Url)
@@ -415,32 +414,6 @@ describe('wso2 custom resource lambda', () => {
retryOptions: testRetryOptions,
};
- const nockBasicWso2SDK = (): void => {
- const secretMock = mockClient(SecretsManagerClient);
- secretMock.on(GetSecretValueCommand).resolves({
- SecretBinary: Buffer.from(JSON.stringify({ user: 'user1', pwd: 'pwd1' })),
- });
-
- // register client mock
- nock(baseWso2Url).post('/client-registration/v0.17/register').reply(200, {
- clientId: 'clientId1',
- clientSecret: 'clientSecret1',
- });
-
- // get token mock
- nock(baseWso2Url).post('/oauth2/token').reply(200, {
- access_token: '1111-1111-1111',
- });
-
- // mock server check
- nock(baseWso2Url)
- .get('/services/Version')
- .reply(
- 200,
- 'WSO2 API Manager-3.2.0',
- );
- };
-
const nockAfterUpdateCreate = (testDefs: Wso2ApiDefinitionV1): void => {
// api openapi update mock
nock(baseWso2Url)
diff --git a/lib/src/wso2/wso2-application/handler/index.test.ts b/lib/src/wso2/wso2-application/handler/index.test.ts
index 4096e1d..1d50e34 100644
--- a/lib/src/wso2/wso2-application/handler/index.test.ts
+++ b/lib/src/wso2/wso2-application/handler/index.test.ts
@@ -3,11 +3,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import nock from 'nock';
-import { mockClient } from 'aws-sdk-client-mock';
-import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { Wso2ApplicationDefinition } from '../v1/types';
import { Wso2ApplicationCustomResourceProperties } from '../types';
+import { nockBasicWso2SDK } from '../../wso2-utils.test';
import { Wso2ApplicationCustomResourceEvent, handler } from './index';
@@ -43,7 +42,7 @@ describe('wso2 application custom resource lambda', () => {
});
it('wso2 application delete', async () => {
- nockBasicWso2SDK();
+ nockBasicWso2SDK(baseWso2Url);
// application get mock
nock(baseWso2Url)
@@ -56,7 +55,7 @@ describe('wso2 application custom resource lambda', () => {
});
it('basic wso2 application update', async () => {
- nockBasicWso2SDK();
+ nockBasicWso2SDK(baseWso2Url);
const testDefs: Wso2ApplicationDefinition = testApplicationDefs();
@@ -89,7 +88,7 @@ describe('wso2 application custom resource lambda', () => {
});
it('basic wso2 application create', async () => {
- nockBasicWso2SDK();
+ nockBasicWso2SDK(baseWso2Url);
// api list mock
nock(baseWso2Url)
@@ -183,30 +182,4 @@ describe('wso2 application custom resource lambda', () => {
applicationDefinition: testApplicationDefs(),
retryOptions: testRetryOptions,
};
-
- const nockBasicWso2SDK = (): void => {
- const secretMock = mockClient(SecretsManagerClient);
- secretMock.on(GetSecretValueCommand).resolves({
- SecretBinary: Buffer.from(JSON.stringify({ user: 'user1', pwd: 'pwd1' })),
- });
-
- // register client mock
- nock(baseWso2Url).post('/client-registration/v0.17/register').reply(200, {
- clientId: 'clientId1',
- clientSecret: 'clientSecret1',
- });
-
- // get token mock
- nock(baseWso2Url).post('/oauth2/token').reply(200, {
- access_token: '1111-1111-1111',
- });
-
- // mock server check
- nock(baseWso2Url)
- .get('/services/Version')
- .reply(
- 200,
- 'WSO2 API Manager-3.2.0',
- );
- };
});
diff --git a/lib/src/wso2/wso2-subscription/handler/index.test.ts b/lib/src/wso2/wso2-subscription/handler/index.test.ts
new file mode 100644
index 0000000..56682dd
--- /dev/null
+++ b/lib/src/wso2/wso2-subscription/handler/index.test.ts
@@ -0,0 +1,182 @@
+/* eslint-disable no-console */
+/* eslint-disable camelcase */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import nock from 'nock';
+
+import { Wso2SubscriptionDefinition } from '../v1/types';
+import { Wso2SubscriptionCustomResourceProperties } from '../types';
+import { nockBasicWso2SDK } from '../../wso2-utils.test';
+
+import { handler, Wso2SubscriptionCustomResourceEvent } from './index';
+
+const baseWso2Url = 'https://mywso2.com';
+
+const testRetryOptions = {
+ checkRetries: {
+ startingDelay: 100,
+ delayFirstAttempt: true,
+ maxDelay: 100,
+ numOfAttempts: 0,
+ timeMultiple: 1.1,
+ },
+ mutationRetries: {
+ startingDelay: 100,
+ delayFirstAttempt: true,
+ maxDelay: 100,
+ numOfAttempts: 0,
+ timeMultiple: 1.1,
+ },
+};
+
+const originalConsoleLog = console.log;
+
+describe('wso2 subscription custom resource lambda', () => {
+ beforeEach(() => {
+ nock.cleanAll();
+ // silence verbose console logs. comment this for debugging
+ // console.log = (): void => {};
+ });
+ afterEach(() => {
+ console.log = originalConsoleLog;
+ });
+
+ it('wso2 subscription delete', async () => {
+ nockBasicWso2SDK(baseWso2Url);
+
+ // application get mock
+ nock(baseWso2Url)
+ .delete(/.*\/store\/v1\/subscriptions\/[^\\/]+$/)
+ .times(1) // check if was created
+ .reply(200);
+
+ const eres = await handler(testCFNEventDelete(testEvent, '123-456'));
+ expect(eres.Status).toBe('SUCCESS');
+ });
+
+ it('basic wso2 subscription update', async () => {
+ nockBasicWso2SDK(baseWso2Url);
+
+ const testDefs: Wso2SubscriptionDefinition = testSubscriptionDefs();
+
+ // subscriptions list mock
+ nock(baseWso2Url)
+ .get(/.*\/store\/v1\/subscriptions.*/)
+ .query(true)
+ .times(1) // check create or update
+ .reply(200, { list: [{ ...testDefs, subscriptionId: '123-456' }] });
+
+ // subscription update mock
+ nock(baseWso2Url)
+ .put(/.*\/store\/v1\/subscriptions\/[^\\/]+$/)
+ .times(1)
+ .reply(200);
+
+ // subscription get mock
+ nock(baseWso2Url)
+ .get(/.*\/store\/v1\/subscriptions\/[^\\/]+$/)
+ .times(1) // check if was created
+ .reply(200, { ...testDefs });
+
+ const eres = await handler(
+ testCFNEventCreate({
+ ...testEvent,
+ }),
+ );
+ expect(eres.PhysicalResourceId).toBe('123-456');
+ expect(eres.Status).toBe('SUCCESS');
+ });
+
+ it('basic wso2 subscription create', async () => {
+ nockBasicWso2SDK(baseWso2Url);
+
+ // subscriptions list mock
+ nock(baseWso2Url)
+ .get(/.*\/store\/v1\/subscriptions.*/)
+ .query(true)
+ .times(1) // check create or update
+ .reply(200, { list: [] });
+
+ const testDefs: Wso2SubscriptionDefinition = testSubscriptionDefs();
+
+ // subscription create mock
+ nock(baseWso2Url)
+ .post(/.*\/store\/v1\/subscriptions$/)
+ .reply(201, { ...testDefs, subscriptionId: '123-456' });
+
+ // subscription get mock
+ nock(baseWso2Url)
+ .get(/.*\/store\/v1\/subscriptions\/[^\\/]+$/)
+ .times(1) // check if was created
+ .reply(200, { ...testDefs });
+
+ const eres = await handler(
+ testCFNEventCreate({
+ ...testEvent,
+ }),
+ );
+ expect(eres.PhysicalResourceId).toBe('123-456');
+ expect(eres.Status).toBe('SUCCESS');
+ });
+
+ const testSubscriptionDefs = (): Wso2SubscriptionDefinition => {
+ return {
+ apiId: '111-222',
+ applicationId: '333-444',
+ throttlingPolicy: 'Unlimited',
+ };
+ };
+
+ const commonEvt = {
+ StackId: 'test-stack',
+ RequestId: '123-123123',
+ LogicalResourceId: 'abc abc',
+ ServiceToken: 'arn:somelambdatest',
+ ResponseURL: 's3bucketxxx',
+ ResourceType: 'wso2subscription',
+ };
+
+ const testCFNEventCreate = (
+ baseProperties: Wso2SubscriptionCustomResourceProperties,
+ ): Wso2SubscriptionCustomResourceEvent => {
+ return {
+ ...commonEvt,
+ RequestType: 'Create',
+ ResourceProperties: { ...baseProperties, ServiceToken: 'arn:somelambdatest' },
+ };
+ };
+ const testCFNEventDelete = (
+ baseProperties: Wso2SubscriptionCustomResourceProperties,
+ PhysicalResourceId: string,
+ ): Wso2SubscriptionCustomResourceEvent => {
+ return {
+ ...commonEvt,
+ RequestType: 'Delete',
+ ResourceProperties: { ...baseProperties, ServiceToken: 'arn:somelambdatest' },
+ PhysicalResourceId,
+ };
+ };
+ // const testCFNEventUpdate = (
+ // baseProperties: Wso2SubscriptionCustomResourceProperties,
+ // PhysicalResourceId: string,
+ // oldResourceProperties: Record,
+ // ): Wso2SubscriptionCustomResourceEvent => {
+ // return {
+ // ...commonEvt,
+ // RequestType: 'Update',
+ // ResourceProperties: { ...baseProperties, ServiceToken: 'arn:somelambdatest' },
+ // PhysicalResourceId,
+ // OldResourceProperties: oldResourceProperties,
+ // };
+ // };
+
+ const testEvent: Wso2SubscriptionCustomResourceProperties = {
+ wso2Config: {
+ baseApiUrl: baseWso2Url,
+ credentialsSecretId: 'arn:aws:secretsmanager:us-east-1:123123123:secret:MySecret',
+ apiVersion: 'v1',
+ },
+ subscriptionDefinition: testSubscriptionDefs(),
+ retryOptions: testRetryOptions,
+ };
+});
diff --git a/lib/src/wso2/wso2-subscription/handler/index.ts b/lib/src/wso2/wso2-subscription/handler/index.ts
new file mode 100644
index 0000000..24c9148
--- /dev/null
+++ b/lib/src/wso2/wso2-subscription/handler/index.ts
@@ -0,0 +1,143 @@
+/* eslint-disable no-console */
+
+import type { CdkCustomResourceEvent, CdkCustomResourceResponse } from 'aws-lambda';
+import type { AxiosInstance } from 'axios';
+
+import { prepareAxiosForWso2Calls } from '../../wso2-utils';
+import { applyRetryDefaults, truncateStr } from '../../utils';
+import type { Wso2SubscriptionInfo } from '../v1/types';
+import type { Wso2SubscriptionCustomResourceProperties } from '../types';
+
+import { createUpdateSubscriptionInWso2, removeSubscriptionInWso2 } from './wso2-v1';
+
+export type Wso2SubscriptionCustomResourceEvent = CdkCustomResourceEvent & {
+ ResourceProperties: Wso2SubscriptionCustomResourceProperties;
+};
+
+export type Wso2SubscriptionCustomResourceResponse = CdkCustomResourceResponse & {
+ Data?: {
+ ApiEndpointUrl?: string;
+ Error?: unknown;
+ };
+ Status?: 'SUCCESS' | 'FAILED';
+ Reason?: string;
+};
+
+export const handler = async (
+ event: Wso2SubscriptionCustomResourceEvent,
+): Promise => {
+ console.log(`WSO2 API Custom Resource invoked with: ${JSON.stringify(event)}`);
+
+ if (!event.ResourceProperties.subscriptionDefinition) {
+ throw new Error('event.subscriptionDefinition should be defined');
+ }
+ if (!event.ResourceProperties.wso2Config) {
+ throw new Error('event.wso2Config should be defined');
+ }
+
+ const response: Wso2SubscriptionCustomResourceResponse = {
+ StackId: event.StackId,
+ RequestId: event.RequestId,
+ LogicalResourceId: event.LogicalResourceId,
+ };
+
+ try {
+ console.log('>>> Prepare WSO2 API client...');
+ const wso2Axios = await prepareAxiosForWso2Calls(event.ResourceProperties.wso2Config);
+
+ if (event.RequestType === 'Create' || event.RequestType === 'Update') {
+ if (event.RequestType === 'Update') {
+ response.PhysicalResourceId = event.PhysicalResourceId;
+ }
+ console.log('>>> Creating or Updating WSO2 Subscription...');
+ const wso2SubscriptionId = await createOrUpdateWso2Subscription(event, wso2Axios);
+ response.PhysicalResourceId = wso2SubscriptionId;
+ response.Data = {
+ Wso2SubscriptionId: wso2SubscriptionId,
+ };
+ response.Status = 'SUCCESS';
+ return response;
+ }
+ if (event.RequestType === 'Delete') {
+ console.log('>>> Deleting WSO2 Subscription...');
+ response.PhysicalResourceId = event.PhysicalResourceId;
+ await removeSubscriptionInWso2({
+ wso2Axios,
+ wso2SubscriptionId: event.PhysicalResourceId,
+ });
+ response.Status = 'SUCCESS';
+ return response;
+ }
+ throw new Error('Unrecognized RequestType');
+ } catch (error) {
+ console.log(`An error has occurred. err=${error}`);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const err = error as any;
+ if (err.stack) {
+ console.log(err.stack);
+ }
+ throw new Error(truncateStr(`${error}`, 1000));
+ }
+};
+
+const createOrUpdateWso2Subscription = async (
+ event: Wso2SubscriptionCustomResourceEvent,
+ wso2Axios: AxiosInstance,
+): Promise => {
+ if (!event.ResourceProperties.subscriptionDefinition?.apiId) {
+ throw new Error('subscriptionDefinition.apiId should be defined');
+ }
+ if (!event.ResourceProperties.subscriptionDefinition?.applicationId) {
+ throw new Error('subscriptionDefinition.applicationId should be defined');
+ }
+
+ // find existing WSO2 subscription to the same apiId by the same applicationId
+ console.log('Searching if Subscription already exists in WSO2...');
+ let existingSubscription: Wso2SubscriptionInfo | undefined;
+ const apil = await wso2Axios.get(`/api/am/store/v1/subscriptions`, {
+ params: {
+ applicationId: event.ResourceProperties.subscriptionDefinition.applicationId,
+ apiId: event.ResourceProperties.subscriptionDefinition.apiId,
+ },
+ });
+ const apiRes = apil.data.list as Wso2SubscriptionInfo[];
+ if (apiRes.length > 1) {
+ throw new Error(
+ `More than one Subscription for apiId='${event.ResourceProperties.subscriptionDefinition.apiId}' and applicationId='${event.ResourceProperties.subscriptionDefinition.applicationId}' was found in WSO2 so we cannot determine which subscription to manage automatically`,
+ );
+ }
+ if (apiRes.length === 1) {
+ existingSubscription = apiRes[0];
+ console.log(
+ `Found existing WSO2 Subscription. subscriptionId=${existingSubscription.subscriptionId}; apiId=${existingSubscription.apiId}; applicationId=${existingSubscription.applicationId}`,
+ );
+ }
+
+ if (
+ event.RequestType === 'Create' &&
+ existingSubscription &&
+ event.ResourceProperties.failIfExists
+ ) {
+ throw new Error(
+ `WSO2 Subscription '${existingSubscription.subscriptionId}' already exists but cannot be managed by this resource. Change 'failIfExists' to change this behavior`,
+ );
+ }
+
+ if (event.RequestType === 'Update' && !existingSubscription) {
+ console.log(
+ `WARNING: This is an Update operation but the Subscription couldn't be found in WSO2. It will be created again`,
+ );
+ }
+
+ if (event.RequestType === 'Create' || event.RequestType === 'Update') {
+ return createUpdateSubscriptionInWso2({
+ wso2Axios,
+ wso2Tenant: event.ResourceProperties.wso2Config.tenant ?? '',
+ subscriptionDefinition: event.ResourceProperties.subscriptionDefinition,
+ existingSubscription,
+ retryOptions: applyRetryDefaults(event.ResourceProperties.retryOptions),
+ });
+ }
+
+ throw new Error(`Invalid requestType found. requestType=${event.ResourceType}`);
+};
diff --git a/lib/src/wso2/wso2-subscription/handler/wso2-v1.ts b/lib/src/wso2/wso2-subscription/handler/wso2-v1.ts
new file mode 100644
index 0000000..4ffee7f
--- /dev/null
+++ b/lib/src/wso2/wso2-subscription/handler/wso2-v1.ts
@@ -0,0 +1,96 @@
+/* eslint-disable no-console */
+import { backOff } from 'exponential-backoff';
+import { AxiosInstance } from 'axios';
+
+import type { Wso2SubscriptionDefinition, Wso2SubscriptionInfo } from '../v1/types';
+import type { RetryOptions } from '../../types';
+
+export type UpsertWso2Args = {
+ wso2Axios: AxiosInstance;
+ wso2Tenant: string;
+ existingSubscription?: Wso2SubscriptionInfo;
+ subscriptionDefinition: Wso2SubscriptionDefinition;
+ retryOptions: RetryOptions;
+};
+
+/**
+ * Delete Subscription in WSO2 server
+ */
+export const removeSubscriptionInWso2 = async (args: {
+ wso2Axios: AxiosInstance;
+ wso2SubscriptionId: string;
+}): Promise => {
+ if (!args.wso2SubscriptionId) {
+ throw new Error('wso2SubscriptionId is required for deleting Application');
+ }
+ await args.wso2Axios.delete(`/api/am/store/v1/subscriptions/${args.wso2SubscriptionId}`);
+};
+
+/**
+ * Perform calls in WSO2 API to create or update an Application
+ * @returns {string} Id of the Application in WSO2
+ */
+export const createUpdateSubscriptionInWso2 = async (args: UpsertWso2Args): Promise => {
+ console.log('');
+ console.log(`>>> Create or update subscription in WSO2...`);
+ // will retry create/update api operation if fails
+ const wso2SubscriptionId = await backOff(
+ async () => createUpdateSubscriptionInWso2AndCheck(args),
+ args.retryOptions.mutationRetries,
+ );
+
+ console.log('Subscription created/updated on WSO2 server successfuly');
+
+ return wso2SubscriptionId;
+};
+
+export const createUpdateSubscriptionInWso2AndCheck = async (
+ args: UpsertWso2Args,
+): Promise => {
+ // create new Subscription in WSO2
+ if (!args.existingSubscription) {
+ console.log(`Creating new Subscription in WSO2...`);
+ const apir = await args.wso2Axios.post(
+ `/api/am/store/v1/subscriptions`,
+ args.subscriptionDefinition,
+ );
+
+ const dataRes = apir.data as Wso2SubscriptionInfo;
+ if (!dataRes.subscriptionId) {
+ throw new Error(
+ `'subscriptionId' wasn't returned as part of the Subscription creation response`,
+ );
+ }
+ console.log(`Subscription "${dataRes.subscriptionId}" created in WSO2`);
+
+ console.log(`Checking if the Subscription was created in WSO2 by retrying checks`);
+ await backOff(async () => {
+ await args.wso2Axios.get(`/api/am/store/v1/subscriptions/${dataRes.subscriptionId}`);
+ // TODO check if returned contents match desired state
+ }, args.retryOptions.checkRetries);
+
+ return dataRes.subscriptionId;
+ }
+
+ // update existing Subscription in WSO2
+ console.log(`Updating Subscription definitions in WSO2`);
+
+ if (!args.existingSubscription.subscriptionId) {
+ throw new Error('Existing subscriptionId should be defined');
+ }
+
+ await args.wso2Axios.put(
+ `/api/am/store/v1/subscriptions/${args.existingSubscription.subscriptionId}`,
+ args.subscriptionDefinition,
+ );
+
+ console.log(`Checking if the Subscription exists in WSO2 by retrying checks`);
+ await backOff(async () => {
+ await args.wso2Axios.get(
+ `/api/am/store/v1/subscriptions/${args.existingSubscription?.subscriptionId}`,
+ );
+ // TODO check if returned contents match desired state
+ }, args.retryOptions.checkRetries);
+
+ return args.existingSubscription.subscriptionId;
+};
diff --git a/lib/src/wso2/wso2-subscription/types.ts b/lib/src/wso2/wso2-subscription/types.ts
new file mode 100644
index 0000000..cad0eca
--- /dev/null
+++ b/lib/src/wso2/wso2-subscription/types.ts
@@ -0,0 +1,12 @@
+import { Wso2BaseProperties } from '../types';
+
+import { Wso2SubscriptionDefinition } from './v1/types';
+
+export type Wso2SubscriptionCustomResourceProperties = Wso2SubscriptionProps;
+
+/**
+ * WSO2 Subscription construct parameters
+ */
+export type Wso2SubscriptionProps = Wso2BaseProperties & {
+ subscriptionDefinition: Wso2SubscriptionDefinition;
+};
diff --git a/lib/src/wso2/wso2-subscription/v1/types.ts b/lib/src/wso2/wso2-subscription/v1/types.ts
new file mode 100644
index 0000000..c950dfe
--- /dev/null
+++ b/lib/src/wso2/wso2-subscription/v1/types.ts
@@ -0,0 +1,41 @@
+export type Wso2SubscriptionInfo = Wso2SubscriptionDefinition & {
+ /**
+ * Subscription Id
+ * @example 123-456-789
+ */
+ subscriptionId: string;
+};
+
+export type Wso2SubscriptionDefinition = {
+ /**
+ * Api Id that will be subscribed by an application
+ * @example 123-456-789
+ */
+ apiId: string;
+ /**
+ * Application Id that will subscribe to the API
+ * @example 123-456-789
+ */
+ applicationId: string;
+ // /**
+ // * Subscription Id of the subscription. If not defined a new Subscription might be created during createOrUpdate operations.
+ // * @example 123-456-789
+ // */
+ // subscriptionId?: string;
+ /**
+ * Throttling policy applied to the calls from this Application
+ * @example Unlimited
+ * */
+ throttlingPolicy: 'Unlimited' | 'Bronze' | 'Silver' | 'Gold' | string;
+ /**
+ * Status of the subscription
+ * @example Unlimited
+ * */
+ status?:
+ | 'BLOCKED'
+ | 'PROD_ONLY_BLOCKED'
+ | 'UNBLOCKED'
+ | 'ON_HOLD'
+ | 'REJECTED'
+ | 'TIER_UPDATE_PENDING';
+};
diff --git a/lib/src/wso2/wso2-subscription/wso2-subscription.test.ts b/lib/src/wso2/wso2-subscription/wso2-subscription.test.ts
new file mode 100644
index 0000000..2a775eb
--- /dev/null
+++ b/lib/src/wso2/wso2-subscription/wso2-subscription.test.ts
@@ -0,0 +1,44 @@
+/* eslint-disable camelcase */
+/* eslint-disable @typescript-eslint/ban-ts-comment */
+/* eslint-disable fp/no-mutating-methods */
+
+import { App, Stack } from 'aws-cdk-lib/core';
+import { Template } from 'aws-cdk-lib/assertions';
+
+import { Wso2SubscriptionProps } from './types';
+import { Wso2Subscription } from './wso2-subscription';
+
+describe('wso2-subscription-construct', () => {
+ it('minimal wso2 api', async () => {
+ const app = new App();
+ const stack = new Stack(app);
+
+ const testProps1 = testProps();
+ const wso2Subscription = new Wso2Subscription(stack, 'wso2', testProps1);
+
+ expect(wso2Subscription.customResourceFunction).toBeDefined();
+
+ const template = Template.fromStack(stack);
+ // eslint-disable-next-line no-console
+ // console.log(JSON.stringify(template.toJSON(), null, 2));
+
+ template.hasResourceProperties('Custom::Wso2Subscription', {
+ wso2Config: testProps1.wso2Config,
+ subscriptionDefinition: testProps1.subscriptionDefinition,
+ });
+ });
+});
+
+const testProps = (): Wso2SubscriptionProps => {
+ return {
+ wso2Config: {
+ baseApiUrl: 'http://localhost:8080/wso2',
+ credentialsSecretId: 'arn::creds',
+ },
+ subscriptionDefinition: {
+ apiId: '1111-2222',
+ applicationId: '3333-4444',
+ throttlingPolicy: 'Unlimited',
+ },
+ };
+};
diff --git a/lib/src/wso2/wso2-subscription/wso2-subscription.ts b/lib/src/wso2/wso2-subscription/wso2-subscription.ts
new file mode 100644
index 0000000..5d1dd2e
--- /dev/null
+++ b/lib/src/wso2/wso2-subscription/wso2-subscription.ts
@@ -0,0 +1,75 @@
+import { Construct } from 'constructs';
+import { CustomResource, RemovalPolicy } from 'aws-cdk-lib/core';
+import { IFunction } from 'aws-cdk-lib/aws-lambda';
+
+import { addLambdaAndProviderForWso2Operations } from '../utils-cdk';
+
+import { Wso2SubscriptionCustomResourceProperties, Wso2SubscriptionProps } from './types';
+
+/**
+ * WSO2 API CDK construct for creating a WSO2 subscription from one application to an API
+ * This construct is related to one "physical" subscription in WSO2.
+ *
+ * The internal implementation tries to protect itself from various scenarios where larger or more complex
+ * WSO2 clusters might lead to out-of-order or delays in operations that happen assynchronously after the API
+ * accepts the requests, so for every mutation, there is a check to verify sanity.
+ */
+export class Wso2Subscription extends Construct {
+ readonly customResourceFunction: IFunction;
+
+ constructor(scope: Construct, id: string, props: Wso2SubscriptionProps) {
+ super(scope, id);
+
+ // Do as much of the logic in the construct as possible and leave only
+ // the minimal complexity to the Lambda Custom Resource as it's harder
+ // to debug and eventual errors will rollback the entire stack and will
+ // make the feedback cycle much longer.
+
+ // Keep this construct stateless (don't access WSO2 apis) and
+ // leave the stateful part to the Lambda Custom Resource (accessing WSO2 apis etc)
+
+ validateProps(props);
+
+ const { customResourceProvider, customResourceFunction } =
+ addLambdaAndProviderForWso2Operations({
+ scope: this,
+ id: `${id}-wso2sub`,
+ props,
+ baseDir: __dirname,
+ });
+
+ // eslint-disable-next-line no-new
+ new CustomResource(this, `${id}-wso2sub-custom-resource`, {
+ serviceToken: customResourceProvider.serviceToken,
+ properties: {
+ wso2Config: props.wso2Config,
+ subscriptionDefinition: props.subscriptionDefinition,
+ retryOptions: props.retryOptions,
+ } as Wso2SubscriptionCustomResourceProperties,
+ resourceType: 'Custom::Wso2Subscription',
+ removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,
+ });
+
+ this.customResourceFunction = customResourceFunction.nodeJsFunction;
+ }
+}
+
+export const validateProps = (props: Wso2SubscriptionProps): void => {
+ if (!props.wso2Config) throw new Error('wso2Config is required');
+ if (!props.wso2Config.baseApiUrl) throw new Error('wso2Config.baseApiUrl is required');
+ if (!props.wso2Config.credentialsSecretId) {
+ throw new Error('wso2Config.credentialsSecretManagerPath is required');
+ }
+ if (!props.subscriptionDefinition) {
+ throw new Error('subscriptionDefinition is required');
+ }
+ if (!props.subscriptionDefinition.apiId) {
+ throw new Error('subscriptionDefinition.apiId is required');
+ }
+ if (!props.subscriptionDefinition.applicationId) {
+ throw new Error('subscriptionDefinition.applicationId is required');
+ }
+ if (!props.subscriptionDefinition.throttlingPolicy) {
+ throw new Error('subscriptionDefinition.throttlingPolicy is required');
+ }
+};
diff --git a/lib/src/wso2/wso2-utils.test.ts b/lib/src/wso2/wso2-utils.test.ts
new file mode 100644
index 0000000..96b50fb
--- /dev/null
+++ b/lib/src/wso2/wso2-utils.test.ts
@@ -0,0 +1,30 @@
+import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
+import { mockClient } from 'aws-sdk-client-mock';
+import nock from 'nock';
+
+export const nockBasicWso2SDK = (baseWso2Url: string): void => {
+ const secretMock = mockClient(SecretsManagerClient);
+ secretMock.on(GetSecretValueCommand).resolves({
+ SecretBinary: Buffer.from(JSON.stringify({ user: 'user1', pwd: 'pwd1' })),
+ });
+
+ // register client mock
+ nock(baseWso2Url).post('/client-registration/v0.17/register').reply(200, {
+ clientId: 'clientId1',
+ clientSecret: 'clientSecret1',
+ });
+
+ // get token mock
+ nock(baseWso2Url).post('/oauth2/token').reply(200, {
+ // eslint-disable-next-line camelcase
+ access_token: '1111-1111-1111',
+ });
+
+ // mock server check
+ nock(baseWso2Url)
+ .get('/services/Version')
+ .reply(
+ 200,
+ 'WSO2 API Manager-3.2.0',
+ );
+};