diff --git a/.gitignore b/.gitignore
index 2a9711974328c5489ada510619dd37e601b87851..c5270de7fdc27533332330e0fc16c767c7176a0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
/node_modules
/coverage
-/dist
\ No newline at end of file
+/dist
+/public
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fabc22d601d6649adc6436c0411dccbf213ee939..de49dbb82537fe0b1ce6bb4511884e9a660f4f19 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,7 @@ image: node
stages:
- build
- test
-# - deploy # deployment is disabled for now
+ - deploy # deployment is enabled only for pages
cache:
- &global_cache_node_modules
@@ -35,6 +35,7 @@ install-dependencies:
build: # This job runs in the build stage, which runs first.
stage: build
+ environment: production
artifacts:
untracked: false
when: on_success
@@ -61,10 +62,35 @@ lint-test-job: # This job also runs in the test stage.
stage: test # It can run at the same time as unit-test-job (in parallel).
script:
- npx prettier . --check
+
+pages-test:
+ stage: test
+ script:
+ - npx typedoc src/index.ts --out test-pages
+ artifacts:
+ when: on_success
+ access: all
+ expire_in: 30 days
+ paths:
+ - test-pages
+ rules:
+ - if: $CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH # this job will be executed only on seconday branches
+
+pages:
+ stage: deploy
+ script:
+ - npx typedoc src/index.ts --out public
+ artifacts:
+ paths:
+ - public
+ when: on_success
+ rules:
+ - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH # This job is executed only on the main branch
+
#deploy-job: # This job runs in the deploy stage.
# stage: deploy # It only runs when *both* jobs in the test stage complete successfully.
# environment: production
-# when: on-success
+# when: on_success
# - job: build
# artifacts: true
# script:
diff --git a/README.md b/README.md
index 94e0d6bac7b040b0f5226927e716df7ec416ee70..fbd3e457c12d334945b4303521e782e61e2f76f2 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,12 @@
+
+
+
+
+
+
+
+
+
# Cream - A Library For Semi-Declarative REST API Creation
Cream stands for Concise REST API Maker and it is a ExpressJS extension mainly targeting TypeScript builds.
@@ -7,13 +16,13 @@ It wasn't tested on plain JS.
If express is not installed:
-```npm
+```bash
npm install express @types/express @creamapi/cream
```
If you've already installed expreess:
-```npm
+```bash
npm install @creamapi/cream
```
@@ -84,7 +93,7 @@ class MyCustomApplication extends ExpressApplication {
public constructor() {
let expressApp = express();
/*
- here you can use any express middleware like cors, json, bodyParser
+ here you can use any express middleware like cors, json, bodyParser, morgan, etc.
*/
expressApp.use(express.json());
@@ -108,7 +117,7 @@ Now if we go to https://localhost:4040/hello-world we will see
Sending a string to the browser is cool, but REST APIs are more complex than this.
They can receive data as a request and give a complex response, like a JSON text.
-## Handling data coming from the client
+### Handling data coming from the client
Let's reuse the last example, but this time we want to get a string from the client and write it on the screen. For this example, to keep it simple, we will use a Get request again, but this time we will use a UrlParameter to retrive the data.
What does it mean? It means that when the user makes a request to http://localhost:4040/hello-world/\ we want to get the value of \ and write it back to the user.
@@ -131,7 +140,7 @@ export class HelloController extends ExpressModule {
Now if we try to go to http://localhost:4040/hello-world/my%20hello we will see
`my hello` written in our browser!
-## Returning complex objects
+### Returning complex objects
Now we want to return a json object containing both our string and its length. To do so we must create a custom class that contains such data and tell cream that we want to serialize it to JSON. We can do it like this:
@@ -152,13 +161,16 @@ import {
@Serializable(CreamSerializers.JSON)
class HelloView {
@AutoMap
- stringLength: number;
+ get stringLength(): number {
+ return this.stringData.length;
+ }
@MapTo('userData')
- stringData: string;
+ public stringData: string;
+
+ otherData: number;
constructor(userString) {
- this.stringLength = stringData.length;
this.stringData = stringData;
}
}
@@ -168,8 +180,14 @@ Here we can see that we tell cream that HelloView is serializable by a JSON seri
We also see AutoMap and MapTo, these two decorators are used to declare which fields are serialized.
+> Non-decorated fields, like otherData, are not serialized by default.
+> This behaviov is helpful to prevent unwanted dataleaks. With a serialize
+> all by default behavior a secure field can be leaked, for example the user's password.
+
> The difference between MapTo and AutoMap is that MapTo allows us to specify the name of the field whilst AutoMap will take the name of the decorated attribute.
+We can see that we can also serialize getters. This allows us to compute dynamically stuff when the object is serializable. Also, `this` correctly points to the correct object.
+
Now we want to use our custom data. As before let's reuse the last example as a base:
```ts
@@ -190,14 +208,14 @@ Now if we go again to http://localhost:4040/hello-world/my%20hello we will not s
```json
{
- "userData": "my hello",
- "stringLength": 8
+ "stringLength": 8,
+ "userData": "my hello"
}
```
## Continuing
-To expand our REST API we also need to receive more complex data from the user, but this topic, how to handle different HTTP requests, is covered in the [User Guide]().
+To expand our REST API we also need to receive more complex data from the user, but this topic, how to handle different HTTP requests, is covered in the [User Guide](public/index.html).
# Comparing it with Express
diff --git a/package-lock.json b/package-lock.json
index 09246b83d60dcc03fcf103c7d5d11a0f97008d49..f97f846c67c0bb588f6e03d150ed8d9b7fb0326d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
"prettier": "3.3.3",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
+ "typedoc": "^0.26.11",
"typescript": "^5.6.3"
}
},
@@ -946,6 +947,62 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@shikijs/core": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.23.0.tgz",
+ "integrity": "sha512-J4Fo22oBlfRHAXec+1AEzcowv+Qdf4ZQkuP/X/UHYH9+KA9LvyFXSXyS+HxuBRFfon+u7bsmKdRBjoZlbDVRkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/engine-javascript": "1.23.0",
+ "@shikijs/engine-oniguruma": "1.23.0",
+ "@shikijs/types": "1.23.0",
+ "@shikijs/vscode-textmate": "^9.3.0",
+ "@types/hast": "^3.0.4",
+ "hast-util-to-html": "^9.0.3"
+ }
+ },
+ "node_modules/@shikijs/engine-javascript": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.23.0.tgz",
+ "integrity": "sha512-CcrppseWShG+8Efp1iil9divltuXVdCaU4iu+CKvzTGZO5RmXyAiSx668M7VbX8+s/vt1ZKu75Vn/jWi8O3G/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "1.23.0",
+ "@shikijs/vscode-textmate": "^9.3.0",
+ "oniguruma-to-es": "0.1.2"
+ }
+ },
+ "node_modules/@shikijs/engine-oniguruma": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.23.0.tgz",
+ "integrity": "sha512-gS8bZLqVvmZXX+E5JUMJICsBp+kx6gj79MH/UEpKHKIqnUzppgbmEn6zLa6mB5D+sHse2gFei3YYJxQe1EzZXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "1.23.0",
+ "@shikijs/vscode-textmate": "^9.3.0"
+ }
+ },
+ "node_modules/@shikijs/types": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.23.0.tgz",
+ "integrity": "sha512-HiwzsihRao+IbPk7FER/EQT/D0dEEK3n5LAtHDzL5iRT+JMblA7y9uitUnjEnHeLkKigNM+ZplrP7MuEyyc5kA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^9.3.0",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz",
+ "integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -1082,6 +1139,16 @@
"@types/node": "*"
}
},
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
"node_modules/@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
@@ -1127,6 +1194,16 @@
"pretty-format": "^29.0.0"
}
},
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
"node_modules/@types/methods": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@@ -1219,6 +1296,13 @@
"@types/superagent": "^8.1.0"
}
},
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/yargs": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
@@ -1236,6 +1320,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -1650,6 +1741,17 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1677,6 +1779,28 @@
"node": ">=10"
}
},
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
@@ -1766,6 +1890,17 @@
"node": ">= 0.8"
}
},
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/component-emitter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
@@ -1947,6 +2082,16 @@
"node": ">= 0.8"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@@ -1968,6 +2113,20 @@
"node": ">=8"
}
},
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/dezalgo": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
@@ -2039,6 +2198,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/emoji-regex-xs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz",
+ "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -2049,6 +2215,19 @@
"node": ">= 0.8"
}
},
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -2588,6 +2767,44 @@
"node": ">= 0.4"
}
},
+ "node_modules/hast-util-to-html": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz",
+ "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "stringify-entities": "^4.0.0",
+ "zwitch": "^2.0.4"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hexoid": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz",
@@ -2605,6 +2822,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -3582,6 +3810,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "uc.micro": "^2.0.0"
+ }
+ },
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -3612,6 +3850,13 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lunr": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
+ "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -3658,6 +3903,60 @@
"tmpl": "1.0.5"
}
},
+ "node_modules/markdown-it": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
+ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "^4.4.0",
+ "linkify-it": "^5.0.0",
+ "mdurl": "^2.0.0",
+ "punycode.js": "^2.3.1",
+ "uc.micro": "^2.1.0"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.mjs"
+ }
+ },
+ "node_modules/markdown-it/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
+ "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -3695,6 +3994,100 @@
"node": ">= 0.6"
}
},
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz",
+ "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -3881,6 +4274,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/oniguruma-to-es": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-0.1.2.tgz",
+ "integrity": "sha512-sBYKVJlIMB0WPO+tSu/NNB1ytSFeHyyJZ3Ayxfx3f/QUuXu0lvZk0VB4K7npmdlHSC0ldqanzh/sUSlAbgCTfw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex-xs": "^1.0.0",
+ "regex": "^4.4.0",
+ "regex-recursion": "^4.1.0"
+ }
+ },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -4110,6 +4515,17 @@
"node": ">= 6"
}
},
+ "node_modules/property-information": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
+ "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -4124,6 +4540,16 @@
"node": ">= 0.10"
}
},
+ "node_modules/punycode.js": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/pure-rand": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
@@ -4196,6 +4622,30 @@
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
"license": "Apache-2.0"
},
+ "node_modules/regex": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/regex/-/regex-4.4.0.tgz",
+ "integrity": "sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/regex-recursion": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-4.2.1.tgz",
+ "integrity": "sha512-QHNZyZAeKdndD1G3bKAbBEKOSSK4KOHQrAJ01N1LJeb0SoH4DJIeFhp0uUpETgONifS4+P3sOgoA1dhzgrQvhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-utilities": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
+ "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -4401,6 +4851,21 @@
"node": ">=8"
}
},
+ "node_modules/shiki": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.23.0.tgz",
+ "integrity": "sha512-xfdu9DqPkIpExH29cmiTlgo0/jBki5la1Tkfhsv+Wu5TT3APLNHslR1acxuKJOCWqVdSc+pIbs/2ozjVRGppdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/core": "1.23.0",
+ "@shikijs/engine-javascript": "1.23.0",
+ "@shikijs/engine-oniguruma": "1.23.0",
+ "@shikijs/types": "1.23.0",
+ "@shikijs/vscode-textmate": "^9.3.0",
+ "@types/hast": "^3.0.4"
+ }
+ },
"node_modules/side-channel": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
@@ -4465,6 +4930,17 @@
"source-map": "^0.6.0"
}
},
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -4524,6 +5000,21 @@
"node": ">=8"
}
},
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -4714,6 +5205,17 @@
"node": ">=0.6"
}
},
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/ts-jest": {
"version": "29.2.5",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz",
@@ -4813,6 +5315,55 @@
"node": ">= 0.6"
}
},
+ "node_modules/typedoc": {
+ "version": "0.26.11",
+ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.11.tgz",
+ "integrity": "sha512-sFEgRRtrcDl2FxVP58Ze++ZK2UQAEvtvvH8rRlig1Ja3o7dDaMHmaBfvJmdGnNEFaLTpQsN8dpvZaTqJSu/Ugw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "lunr": "^2.3.9",
+ "markdown-it": "^14.1.0",
+ "minimatch": "^9.0.5",
+ "shiki": "^1.16.2",
+ "yaml": "^2.5.1"
+ },
+ "bin": {
+ "typedoc": "bin/typedoc"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x"
+ }
+ },
+ "node_modules/typedoc/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/typedoc/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/typescript": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
@@ -4827,6 +5378,13 @@
"node": ">=14.17"
}
},
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
@@ -4834,6 +5392,79 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/unist-util-is": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
+ "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
+ "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -4910,6 +5541,36 @@
"node": ">= 0.8"
}
},
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
+ "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@@ -4992,6 +5653,19 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/yaml": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
+ "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
@@ -5033,6 +5707,17 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
}
}
}
diff --git a/package.json b/package.json
index 15bc7b7b8194782c950de545c26638b132ca6538..b955584f91d0ec5602013a520fc9668d55282196 100644
--- a/package.json
+++ b/package.json
@@ -5,11 +5,13 @@
"main": "dist/index.js",
"types": "dist/index.d.ts",
"directories": {
- "doc": "docs"
+ "doc": "public"
},
"scripts": {
"build": "tsc",
- "test": "jest"
+ "docs": "typedoc src/index.ts --out public",
+ "test": "jest",
+ "deploy": "npm run build && npm publish"
},
"repository": {
"type": "git",
@@ -26,11 +28,11 @@
"typescript"
],
"author": "Raul Radu",
- "license": "SEE LICENSE IN LICENSE",
+ "license": "Apache-2.0",
"bugs": {
- "url": "https://gitlab.com/worklog1/express-utils/issues"
+ "url": "https://gitlab.com/worklog1/cream/issues"
},
- "homepage": "https://gitlab.com/worklog1/express-utils#readme",
+ "homepage": "https://gitlab.com/worklog1/cream/public",
"devDependencies": {
"@types/express": "^5.0.0",
"@types/jest": "^29.5.14",
@@ -41,6 +43,7 @@
"prettier": "3.3.3",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
+ "typedoc": "^0.26.11",
"typescript": "^5.6.3"
},
"dependencies": {
diff --git a/src/ExchangeUtils/Message.ts b/src/ExchangeUtils/Message.ts
index 35390770ae5957d1ef10aa69fe67764afc987d66..accea52733838626e91a8230d71f266fb132f361 100644
--- a/src/ExchangeUtils/Message.ts
+++ b/src/ExchangeUtils/Message.ts
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+/**
+ * This is a collection of mime types and should be updgraded
+ * or better deduced from another existing type
+ */
export type MessageType =
| 'application/json'
| 'image/jpeg'
diff --git a/src/ExpressAdapter/ExpressAdapters.ts b/src/ExpressAdapter/ExpressAdapters.ts
index b2865e80ec891e64022c0d65a008189f6ec6d583..0280582cbe4d0f172e428ff78072bb0baec1a6c5 100644
--- a/src/ExpressAdapter/ExpressAdapters.ts
+++ b/src/ExpressAdapter/ExpressAdapters.ts
@@ -40,6 +40,39 @@ export const PARAMS_METADATA_KEY = Symbol('express:paramAssoc');
export const HEADERS_METADATA_KEY = Symbol('express:headersAssoc');
export const MIDDLEWARE_METADATA_KEY = Symbol('express:middlewareAssoc');
+/**
+ * This method is used to declare a method of a class (that must extend {@link ExpressModule})
+ * to be an API endpoint. This endpoint is bound to the router representing the class basepoint.
+ * The basepoint (or zone) is defined by using {@link ExpressController} decorator.\
+ * The decorated method is not altered and that means it can be used as a normal method.
+ *
+ * @remarks
+ * It is suggested to use {@link Get}, {@link Post}, {@link Put} and {@link Delete}\
+ * Methods are bound to the router in a topdown approach, this means that from Express's point of
+ * view the top method is called first if two paths collide. Two paths collide when both the path and the http ù
+ * method is the same so a path x bound to a Get request will not collide to the same path bound to a Post request.
+ * @example
+ * ```ts
+ * import { ExpressController, ExpressModule, ExpressCall, HttpMethod } from "@creamapi/cream";
+ *
+ * \@ExpressController("/my-base-route")
+ * export class MyController extends ExpressModule {
+ * \@ExpressCall("/hello-world", HttpMethod.GET)
+ * myMethod(): string{
+ * return "hello, world";
+ * }
+ *
+ * // It also works with asynchronous methods
+ * \@ExpressCall("/hello-world-async", HttpMethod.GET)
+ * async myMethodAsync(): Promise {
+ * return "hello, async world!";
+ * }
+ * }
+ * ```
+ * @param relativePath Is the path relative to the basepoint. The path must follow the Express path definition
+ * @param httpMethod The HTTP Method that must be used for the path. See {@link HttpMethod} for available methods
+ * @returns returns the descriptor of the method.
+ */
export function ExpressCall(
relativePath: string,
httpMethod: HttpMethod
@@ -110,7 +143,7 @@ export function ExpressCall(
collections.get(param.collection);
if (collection) {
- if (param.name == '$') {
+ if (param.name == '*') {
args[param.index] = collection;
} else {
args[param.index] = (collection as any)[
@@ -174,22 +207,198 @@ export function ExpressCall(
};
}
+/**
+ * This method is used to declare a method of a class (that must extend {@link ExpressModule})
+ * to be an API endpoint as a GET http method.
+ *
+ * This method just works by calling {@link ExpressCall} with {@link HttpMethod.GET} as the second parameter
+ * To understand its behaviour please see {@link ExpressCall}.
+ *
+ * @remarks Methods are bound to the router in a topdown approach, this means that from Express's point of
+ * view the top method is called first if two paths collide. Two paths collide when both the path and the http ù
+ * method is the same so a path x bound to a Get request will not collide to the same path bound to a Post request.
+ * @example
+ * ```ts
+ * import { ExpressController, ExpressModule, ExpressCall, HttpMethod } from "@creamapi/cream";
+ *
+ * \@ExpressController("/my-base-route")
+ * export class MyController extends ExpressModule {
+ * \@Get("/hello-world")
+ * myMethod(): string{
+ * return "hello, world";
+ * }
+ *
+ * // It also works with asynchronous methods
+ * \@Get("/hello-world-async")
+ * async myMethodAsync(): Promise {
+ * return "hello, async world!";
+ * }
+ * }
+ * ```
+ * @param relativePath Is the path relative to the basepoint. The path must follow the Express path definition
+ * @returns returns the descriptor of the method.
+ */
export function Get(relativePath: string) {
return ExpressCall(relativePath, HttpMethod.GET);
}
+/**
+ * This method is used to declare a method of a class (that must extend {@link ExpressModule})
+ * to be an API endpoint as a POST http method.
+ *
+ * To retrieve the body passed as an argument just use {@link Body} or {@link BodyField}.
+ *
+ * This method just works by calling {@link ExpressCall} with {@link HttpMethod.GET} as the second parameter
+ * To understand its behaviour please see {@link ExpressCall}.
+ *
+ * @remarks
+ * Methods are bound to the router in a topdown approach, this means that from Express's point of
+ * view the top method is called first if two paths collide. Two paths collide when both the path and the http ù
+ * method is the same so a path x bound to a Get request will not collide to the same path bound to a Post request.
+ *
+ * @example
+ * ```ts
+ * import { ExpressController, ExpressModule, ExpressCall, HttpMethod } from "@creamapi/cream";
+ *
+ * \@ExpressController("/my-base-route")
+ * export class MyController extends ExpressModule {
+ * \@Post("/hello-world")
+ * myMethod(@Body body: any): string{
+ * return "hello, world";
+ * }
+ *
+ * // It also works with asynchronous methods
+ * \@Post("/hello-world-async")
+ * async myMethodAsync(@BodyField("myField") myField: string): Promise {
+ * return myField;
+ * }
+ * }
+ * ```
+ * @param relativePath Is the path relative to the basepoint. The path must follow the Express path definition
+ * @returns returns the descriptor of the method.
+ */
export function Post(relativePath: string) {
return ExpressCall(relativePath, HttpMethod.POST);
}
+/**
+ * This method is used to declare a method of a class (that must extend {@link ExpressModule})
+ * to be an API endpoint as a PUT http method.
+ *
+ * To retrieve the body passed as an argument just use {@link Body} or {@link BodyField}.
+ *
+ * This method just works by calling {@link ExpressCall} with {@link HttpMethod.GET} as the second parameter
+ * To understand its behaviour please see {@link ExpressCall}.
+ *
+ * @remarks Methods are bound to the router in a topdown approach, this means that from Express's point of
+ * view the top method is called first if two paths collide. Two paths collide when both the path and the http ù
+ * method is the same so a path x bound to a Get request will not collide to the same path bound to a Post request.
+ * @example
+ * ```ts
+ * import { ExpressController, ExpressModule, ExpressCall, HttpMethod } from "@creamapi/cream";
+ *
+ * \@ExpressController("/my-base-route")
+ * export class MyController extends ExpressModule {
+ * \@Put("/hello-world")
+ * myMethod(@Body body: any): string{
+ * return "hello, world";
+ * }
+ *
+ * // It also works with asynchronous methods
+ * \@Put("/hello-world-async")
+ * async myMethodAsync(@BodyField("myField") myField: string): Promise {
+ * return myField;
+ * }
+ * }
+ * ```
+ * @param relativePath Is the path relative to the basepoint. The path must follow the Express path definition
+ * @returns returns the descriptor of the method.
+ */
export function Put(relativePath: string) {
return ExpressCall(relativePath, HttpMethod.PUT);
}
+/**
+ * This method is used to declare a method of a class (that must extend {@link ExpressModule})
+ * to be an API endpoint as a DELETE http method.
+ *
+ * To retrieve the body passed as an argument just use {@link Body} or {@link BodyField}.
+ *
+ * This method just works by calling {@link ExpressCall} with {@link HttpMethod.GET} as the second parameter
+ * To understand its behaviour please see {@link ExpressCall}.
+ *
+ * @remarks
+ * Methods are bound to the router in a topdown approach, this means that from Express's point of
+ * view the top method is called first if two paths collide. Two paths collide when both the path and the http ù
+ * method is the same so a path x bound to a Get request will not collide to the same path bound to a Post request.
+ *
+ * @example
+ * ```ts
+ * import { ExpressController, ExpressModule, ExpressCall, HttpMethod } from "@creamapi/cream";
+ *
+ * \@ExpressController("/my-base-route")
+ * export class MyController extends ExpressModule {
+ * \@Delete("/hello-world")
+ * myMethod(@Body body: any): string{
+ * return "hello, world";
+ * }
+ *
+ * // It also works with asynchronous methods
+ * \@Delete("/hello-world-async")
+ * async myMethodAsync(@BodyField("myField") myField: string): Promise {
+ * return myField;
+ * }
+ * }
+ * ```
+ * @param relativePath Is the path relative to the basepoint. The path must follow the Express path definition
+ * @returns returns the descriptor of the method.
+ */
export function Delete(relativePath: string) {
return ExpressCall(relativePath, HttpMethod.DELETE);
}
+/**
+ * This decorator is used to make a class to be a controller that handles HTTP requests.
+ * The class decorated as a controller must inherit from {@link ExpressModule}.
+ * In practice this will bound any {@link ExpressCall}-decorated method to an express router.
+ * The router is also bound to the baseRoute parameter.\
+ * The call tree will look something like this:
+ * ```
+ * / <- this is the basepoint (parameter baseRoute) a controller is bound to
+ * |- router1 <- This is one controller
+ * | |- GET
+ * | | |- /path1
+ * | | | |- method1-1 <- this is a endpoint
+ * | | | |- method1-2 <- multiple methods can be bound to the same route (aka they collide)
+ * | | |- /path2
+ * | | | |- method1-3
+ * | | |- / <- this will look like it is bound to the base path
+ * | | |- method1-4
+ * | |- POST
+ * | |- PUT
+ * | |- DELETE
+ * |- router2 <- multiple controller can be bound to the same basepoint
+ * | |- GET
+ * | | |- /path1
+ * | | |- method2-1 <- this method is bound to the same path as method1-1
+ * | |- POST
+ * | |- PUT
+ * | |- DELETE
+ * |- /new-endpoint <- this is another basepoint. Any method bound to this method will be bound to the base path /new-endpoint
+ * | |- router1
+ * | |- GET
+ * | |- POST
+ * | |- PUT
+ * | |- DELETE
+ * ```
+ * @remarks
+ * For whom want to work on low lever prototyping this decorator will alter the prototype of the
+ * decorated class by adding functionalities without altering its behavior, including the constructor.
+ * @param baseRoute the URL to which the controller is bound to
+ * @returns a new class that extends the base decorated class that implements a few functions
+ * that will bound routes to the router. Albeit it is a brand new class its usage is completely
+ * transparent for the users.
+ */
export function ExpressController<
T extends { new (...args: any[]): ExpressModule },
>(baseRoute: string) {
@@ -246,6 +455,15 @@ export function ExpressController<
};
}
+/**
+ * This parameter decorator will decorate a method parameter by associating it with a field in the body.
+ * This permits the autofill of the parameter with the corresponding field (named fieldName) in the body.
+ * @remarks
+ * If the field is undefined the field will be filled as undefined.\
+ * If no body is provided to the request then all parameters decorated with BodyField will be undefined
+ * @param fieldName the field name in the body
+ * @returns the decorator function
+ */
export function BodyField(fieldName: string) {
return function (
target: Object,
@@ -267,10 +485,37 @@ export function BodyField(fieldName: string) {
};
}
+/**
+ * This parameter decorator will decorate a method parameter by associating it with the whole body.
+ * If no body is provided to the request then all attributes decorated with Body will be undefined
+ * @returns the decorator function
+ */
export function Body() {
return BodyField(':body');
}
+/**
+ * This parameter decorator will decorate a method parameter by associating it with a field in the URL.
+ * This field must be defined in the url like normally done in express.
+ * @example
+ * ```ts
+ * // we are in a controller class
+ *
+ * \@Get("/concat-space/:myParam1/:myParam2")
+ * concatWithSpace(
+ * \@UrlParameter("myParam1") param1: string,
+ * \@UrlParameter("myParam2") param2: string
+ * ): string {
+ * return param1 + " " + param2;
+ * }
+ * //...
+ * ```
+ * @remarks If the field is undefined in the URL request the field will be filled as undefined.\
+ * In general parameters are non-null because missing one parameter when making the request
+ * will result to a different HTTP call and by extension a different controller method.
+ * @param fieldName the field name in the body
+ * @returns the decorator function
+ */
export function UrlParameter(fieldName: string) {
return function (
target: Object,
@@ -292,6 +537,12 @@ export function UrlParameter(fieldName: string) {
};
}
+/**
+ * This parameter decorator will decorate a method parameter by associating it with a Request Header (eg. content-type).
+ * @remarks If the header is undefined the field will be filled as undefined.
+ * @param headerName the http header name
+ * @returns the decorator function
+ */
export function Header(headerName: string) {
return function (
target: Object,
diff --git a/src/ExpressAdapter/ExpressModule.ts b/src/ExpressAdapter/ExpressModule.ts
index d802db231187ab35bde5559c9af9ef082b132a65..becfd411b08c32b789e035a45d9d1fa7ef2a8798 100644
--- a/src/ExpressAdapter/ExpressModule.ts
+++ b/src/ExpressAdapter/ExpressModule.ts
@@ -18,12 +18,44 @@ import { Router } from 'express';
import { BaseMiddlewares } from '../ExpressMiddleware/ExpressMiddleware';
import { ExpressApplication } from '../ExpressApplication';
+/**
+ * This class is just a way to allow to explicitly declare
+ * that the class inheriting ExpressModule is a controller.
+ * This is because {@link ExpressController} decorates only
+ * **ExpressModules** and defines some fields that are used by
+ * the ExpressApplication to handle requests and define endpoints.
+ * Also it gives an interface for accessing the express application
+ * handling the class easily (by using ExpressModule.app)
+ */
export class ExpressModule {
+ /**
+ * This allows to access the express router that will handle
+ * the requests. This router will be registered to the
+ * {@link ExpressApplication} that will be defined in {@link ExpressModule.app}
+ */
public accessor router: Router;
+
+ /**
+ * This is the basepoint to which the controller is bound to
+ */
public accessor baseUrl: string;
+
+ /**
+ * This is used to keep the information about the class
+ * that will inherit the ExpressModule and that will be
+ * decorated by {@link ExpressController}
+ */
public accessor className: string;
+
+ /**
+ * The {@link ExpressApplication} the controller is registered to
+ */
private _app!: ExpressApplication;
+ /**
+ * @param middlewareList The list of controller-wise middlewares associated
+ * with the controller. The method-associated middlewares will not appear in this list
+ */
constructor(public middlewareList: BaseMiddlewares = []) {
this.router = Router();
this.baseUrl = '/';
diff --git a/src/ExpressAdapter/ParameterProp.ts b/src/ExpressAdapter/ParameterProp.ts
index efb7b0053549f2f18d10b744c68557e34092a5ab..41c65832729ae6f79b523579624ddcdc87061b3c 100644
--- a/src/ExpressAdapter/ParameterProp.ts
+++ b/src/ExpressAdapter/ParameterProp.ts
@@ -14,7 +14,18 @@
* limitations under the License.
*/
+/**
+ * This class is used to defined meta information about
+ * method parameters.
+ * It is used to define bindings between the target method
+ * and the data source (for example headers, body, ecc.)
+ */
export class ParameterProp {
+ /**
+ *
+ * @param index the index of the parameter in the function call
+ * @param name the name of the parameter in the resource data structure
+ */
constructor(
public index: number,
public name: string
diff --git a/src/ExpressApplication.ts b/src/ExpressApplication.ts
index 7a1eb4278dc00f76ce4563a9749c53cfdd67df85..3594fc80586fe63ae99e0a51228ea321359512fd 100644
--- a/src/ExpressApplication.ts
+++ b/src/ExpressApplication.ts
@@ -25,15 +25,66 @@ import {
import { Server } from 'http';
+/**
+ * This type is just for expressivity to identify
+ * the purpose of any variable that will handle controllers
+ */
type ControllerMap = Map;
+
+/**
+ * This type is just for expressivity to identify
+ * the purpose of any variable that will handle services
+ */
type ServiceMap = ControllerMap;
+/**
+ * This class is the main class for your Cream-based REST API
+ * It will handle controllers, services and will communicate with
+ * express for you.
+ *
+ * @example To use it you can either extend from it or create a new object
+ * ```ts
+ * import express from "express";
+ * import { ExpressApplication } from "@creamapi/cream";
+ *
+ * let expressApp = express();
+ * expressApp.use(express.json());
+ * let app = new ExpressApplication(expressApp, 4040);
+ * app.addControllers([]);
+ * app.start();
+ * ```
+ */
export class ExpressApplication {
+ /**
+ * The map of active and registered controllers.
+ * The key will be the name of the controller.
+ * By this I mean the literal class name.
+ * Only objects that extend ExpressModule can be
+ * used as a controller
+ */
private controllers: ControllerMap;
+
+ /**
+ * The map of active and registered services.
+ * The key will be the id given to the service when describing it.
+ */
private services: ServiceMap;
+
+ /**
+ * The port to which the server will be bounded to.
+ */
private port: number;
+
+ /**
+ * The server instance given by the express API
+ */
private server?: Server;
+ /**
+ * @param app is the express application that will handle the requests.
+ * @param port is the port that the server will be bound to
+ * @param _errorHandler is you custom implementation of the error handler that extends ExpressErrorHandler
+ */
constructor(
private app: Express,
port: number,
@@ -44,6 +95,9 @@ export class ExpressApplication {
this.services = new Map();
}
+ /**
+ * This attribute setter allows for setting a new custom error handler
+ */
set errorHandler(v: ExpressErrorHandler) {
this._errorHandler = v;
}
@@ -191,7 +245,12 @@ export class ExpressApplication {
});
}
- public stop(): Promise {
+ /**
+ * This function is used to stop the server on purpose
+ * @returns void
+ * @throws any generated error by Server.close
+ */
+ public async stop(): Promise {
return new Promise((resolve, reject) => {
this.server!.close(async (err) => {
if (err) {
@@ -202,10 +261,22 @@ export class ExpressApplication {
});
}
+ /**
+ * @returns the active express application
+ */
public getExpressApp(): Express {
return this.app;
}
+ /**
+ * This method is used when we want to retreive a shared service
+ * within a controller. This is useful for example when we want to share
+ * user data among the services but we don't want to access the database
+ * everytime so a runtime service that is synced with the DB but caches data
+ * locally can be used.
+ * @param serviceId the service identifier that is given with IdentifiedBy decorator
+ * @returns the requested service or undefined if the service was not found
+ */
public getService(serviceId: string) {
return this.services.get(serviceId) as T;
}
diff --git a/src/ExpressErrorHandler/ExpressErrorHandler.ts b/src/ExpressErrorHandler/ExpressErrorHandler.ts
index 237570b6ce20f1e433ce0c111621b90bd9890a6e..5d4d5f21229a7acf4e3953d0d3f1b71229e89582 100644
--- a/src/ExpressErrorHandler/ExpressErrorHandler.ts
+++ b/src/ExpressErrorHandler/ExpressErrorHandler.ts
@@ -45,7 +45,7 @@ export interface ErrorInfo {
*/
export interface ExpressErrorHandler {
/**
- * @description This method is called whenever an exception is thrown
+ * This method is called whenever an exception is thrown
*
*/
handle(err: Error, req: Request, res: Response): void;
diff --git a/src/ExpressMiddleware/ExpressMiddleware.ts b/src/ExpressMiddleware/ExpressMiddleware.ts
index 41b224bf70d4d788e8ba50b923074cfef47f32a1..397e162e7efd5a37e6f515fb6624e66bbae73f97 100644
--- a/src/ExpressMiddleware/ExpressMiddleware.ts
+++ b/src/ExpressMiddleware/ExpressMiddleware.ts
@@ -17,15 +17,51 @@
import { NextFunction, Request, Response } from 'express';
import { RestError } from '../ExpressErrorHandler/ExpressErrorHandler';
+/**
+ * @internal
+ * this is just a placeholder type for {} | undefined
+ */
export type MiddlewareDataCollection = {} | undefined;
+/**
+ * @internal
+ * this type is for representing middelware data in requests
+ */
export type MiddlewareDataCollections = Map;
+/**
+ * This interface is used to extend express Request interface with
+ * middleware data
+ */
export interface ExtendedRequest extends Request {
+ /**
+ * The collections that are used by middlewares to communicate data
+ * to the endpoint
+ */
middlewareDataCollections?: MiddlewareDataCollections;
}
+/**
+ * @internal
+ *
+ * common interface for middlewares.
+ * this common interface is used to define the duality of handle.
+ * This duality is due to the fact that handle can be either async
+ * or not sync.
+ *
+ * Having everything async will not give any performance boost,
+ * it will only create more confusion than there already is
+ */
export interface BaseMiddleware {
+ /**
+ * @internal
+ * This method is for handling requests coming from the user.
+ * It is in the correct format for express calls.
+ * @param req the request coming from the user
+ * @param res the response to the user
+ * @param next the next function to be called when finished
+ * working on the request and no response should be given
+ */
handle(
req: Request,
res: Response,
@@ -33,12 +69,46 @@ export interface BaseMiddleware {
): void | Promise;
}
+/**
+ * This abstract class implements the base middleware handle method for handling
+ * ASYNChronous middlewares.
+ * @remarks The API for middlewares is still in its early stage and will need some refactoring
+ * to make it simpler for users.
+ */
export abstract class AsyncExpressMiddleware implements BaseMiddleware {
+ /**
+ * This is the method that your custom middleware should implement.
+ * This method will communicate to the user by returning the collection for
+ * this specific middleware.
+ * @remarks If the collection already exists then all its data will be
+ * overwritten by the return of this function. This sucks I know, any suggestion
+ * on how to change this is welcome.
+ *
+ * If an error should be thrown, like a 404 file not found error, {@link RestError}
+ * should be used to communicate with the user.
+ * Any error that does not extend RestError
+ * will return to the user with a 500 internal server error
+ *
+ * @param req the request coming from the client, with additional data included
+ * by previous middleware coming from the middleware stack
+ * @param res a response to the user, useful for example to send partial data.
+ *
+ * @returns the collection of this middleware, containing information useful for
+ * the following execution stack
+ */
public abstract behaviour(
req: ExtendedRequest,
res: Response
): Promise>;
+ /**
+ * @internal
+ * this implementation of handle is going to set the collection in the
+ * collection map with the new content.
+ * If behavior throws it will check if the error extends RestError
+ * and then uses the rest error statusCode to send information to the user.
+ * @remarks This might not be necessary since the error handler already does this
+ */
async handle(req: ExtendedRequest, res: Response, next: NextFunction) {
try {
let data: MiddlewareReturnData = await this.behaviour(req, res);
@@ -61,12 +131,46 @@ export abstract class AsyncExpressMiddleware implements BaseMiddleware {
}
}
+/**
+ * This abstract class implements the base middleware handle method for handling
+ * SYNChronous middlewares.
+ * @remarks The API for middlewares is still in its early stage and will need some refactoring
+ * to make it simpler for users.
+ */
export abstract class ExpressMiddleware implements BaseMiddleware {
+ /**
+ * This is the method that your custom middleware should implement.
+ * This method will communicate to the user by returning the collection for
+ * this specific middleware.
+ * @remarks If the collection already exists then all its data will be
+ * overwritten by the return of this function. This sucks I know, any suggestion
+ * on how to change this is welcome.
+ *
+ * If an error should be thrown, like a 404 file not found error, {@link RestError}
+ * should be used to communicate with the user.
+ * Any error that does not extend RestError
+ * will return to the user with a 500 internal server error
+ *
+ * @param req the request coming from the client, with additional data included
+ * by previous middleware coming from the middleware stack
+ * @param res a response to the user, useful for example to send partial data.
+ *
+ * @returns the collection of this middleware, containing information useful for
+ * the following execution stack
+ */
public abstract behaviour(
req: ExtendedRequest,
res: Response
): MiddlewareReturnData;
+ /**
+ * @internal
+ * this implementation of handle is going to set the collection in the
+ * collection map with the new content.
+ * If behavior throws it will check if the error extends RestError
+ * and then uses the rest error statusCode to send information to the user.
+ * @remarks This might not be necessary since the error handler already does this
+ */
handle(req: ExtendedRequest, res: Response, next: NextFunction) {
try {
let data: MiddlewareReturnData = this.behaviour(req, res);
@@ -87,9 +191,21 @@ export abstract class ExpressMiddleware implements BaseMiddleware {
}
}
+/**
+ * @internal
+ * this type is just used to declare an array of BaseMiddlewares
+ */
export type BaseMiddlewares = BaseMiddleware[];
+/**
+ * This method is used to define a collection in the collection mapping
+ * To access this data in a method use {@link MiddlewareData}
+ */
export class MiddlewareReturnData {
+ /**
+ * @param collectionName the collection identifier that should be used when saving the data in the map
+ * @param content the content of the collection
+ */
constructor(
public readonly collectionName = 'default',
public readonly content?: T
diff --git a/src/ExpressMiddleware/MiddlewareData.ts b/src/ExpressMiddleware/MiddlewareData.ts
index 292a59fb73218914a0b6801c34d26759f8335534..7b60cbd8f68e7280420840a689623424fa458b8d 100644
--- a/src/ExpressMiddleware/MiddlewareData.ts
+++ b/src/ExpressMiddleware/MiddlewareData.ts
@@ -17,7 +17,19 @@
import { ParameterProp } from '../ExpressAdapter/ParameterProp';
import { MIDDLEWARE_METADATA_KEY } from '../ExpressAdapter/ExpressAdapters';
+/**
+ * @internal
+ * This class is used to define parameter prop for middleware data
+ * This is used to map parameters to the data in a middleware collection
+ */
export class MiddlewareParameterProp extends ParameterProp {
+ /**
+ * @param index the index of the parameter in the parameter call array
+ * @param name The name of the parameter in the data collection
+ * @param collection The collection that the data should be retrieved from
+ *
+ * @remarks The data is accessed like collection[name]
+ */
constructor(
index: number,
name: string,
@@ -27,11 +39,23 @@ export class MiddlewareParameterProp extends ParameterProp {
}
}
+/**
+ * @internal
+ * a type for easily defining arrays
+ */
export type MiddlewareParameterProps = MiddlewareParameterProp[];
+/**
+ * This decorator factory is used to declare that a parameter of a method should be filled
+ * from the collection `collectionName`. The parameter will be filled either with the data or
+ * undefined if the data is not found in the collection or the collection is not found in the map
+ * @param collectionName The collection the data should be retrieved from. The default collection name is 'default'
+ * @param dataName the name of the field in the collection. To retrieve the entire collection the string "*" is used.
+ * @returns the decorator that will effectively decorate the method
+ */
export function MiddlewareData(
collectionName: string = 'default',
- dataName: string = '$'
+ dataName: string = '*'
) {
return function (
target: Object,
diff --git a/src/ExpressMiddleware/UseMiddleware.ts b/src/ExpressMiddleware/UseMiddleware.ts
index 377ffdf93e0868b60473d34d8a35b81afce9767e..55516075533fb16c1113be6d1e13ddd42ac6898d 100644
--- a/src/ExpressMiddleware/UseMiddleware.ts
+++ b/src/ExpressMiddleware/UseMiddleware.ts
@@ -20,6 +20,12 @@ import { Route, Routes, ROUTES_METADATA_KEY } from '../HttpUtils/Route';
import { BaseMiddleware, BaseMiddlewares } from './ExpressMiddleware';
import { ExpressModule } from '../ExpressAdapter/ExpressModule';
+/**
+ * This decorator is used to create a stack of middlewares for a controller.
+ * This stack of middlewares is applied to all ExpressCalls in the controller
+ * @param middlewares the middlewares that should be called before calling the endpoint
+ * @returns a class that extends the target class and will initialize the middlewares
+ */
export function UseMiddlewaresForController<
T extends { new (...args: any[]): ExpressModule },
>(middlewares: BaseMiddlewares) {
@@ -32,6 +38,13 @@ export function UseMiddlewaresForController<
};
}
+/**
+ * This decorator is used to push to the middleware stack a new middleware for the
+ * decorated ExpressCall. Middlewares pushed with this decorator will be executed with
+ * a top-down approach (like a stack)
+ * @param middleware the middleware that should be pushed to the stack
+ * @returns the actual decorator
+ */
export function UseMiddleware(middleware: T) {
return function (
target: ExpressModule,
diff --git a/src/ExpressService/ExpressService.ts b/src/ExpressService/ExpressService.ts
index 5e022ffe5916a0e3cdf9434494a071681340dbd1..978ab633bab96a6cfa4b51b9aae0547c3e97bd8f 100644
--- a/src/ExpressService/ExpressService.ts
+++ b/src/ExpressService/ExpressService.ts
@@ -16,24 +16,60 @@
import { ExpressApplication } from '../ExpressApplication';
+/**
+ * This abstract class is used to declare a service for the app.
+ * This service is active all time during the lifetime of the owner app
+ * A service is mainly used for exchanging information between controllers
+ * for example daisy chaining updates within the controllers.
+ * Another useful example is just to initialize and share a database connection
+ * between multiple controllers or just to run some code at startup.
+ */
export abstract class ExpressService {
+ /**
+ * the application owning the service
+ */
private _app!: ExpressApplication;
+
+ /**
+ * The id of the service. This id is used to retrieve the service from within the app
+ */
private _id!: string;
+ /**
+ * This method must be implemented to bootstrap the service.
+ * If the service is successfully started then this method
+ * must return true. If there is an error with the initialization
+ * the method must return false
+ */
abstract init(): Promise;
+ /**
+ * This is the setter to set the owning application
+ */
public set app(v: ExpressApplication) {
this._app = v;
}
+ /**
+ * This method is use to get the owning application
+ */
public get app(): ExpressApplication {
return this._app;
}
+ /**
+ * This method is used to get the current identifier of the service
+ */
public get id() {
return this._id;
}
+ /**
+ * This decorator is used to declare the identifier of the service
+ * @remarks It is mandatory
+ * @param id The identifier that uniquely identifies the service. Having multiple services with the same ID will give conflicts
+ * @returns the decorator that will create a new class based from the service and will also set the identifier
+ */
public static IdentifiedBy(
id: string
) {
diff --git a/src/HttpUtils/ContentType.ts b/src/HttpUtils/ContentType.ts
index 4038a370905cd411be4d2584a4439f9e45cf4128..b39d45528dcb759b25f6a180977d2f6571448204 100644
--- a/src/HttpUtils/ContentType.ts
+++ b/src/HttpUtils/ContentType.ts
@@ -19,6 +19,12 @@ import { Constructable } from '../Utils/Constructable';
export const HTTP_CONTENT_TYPE_METADATA_KEY = Symbol('cream:http:content-type');
+/**
+ * This decorator is used to decorate a class to add information about
+ * the content type that should be set in the Content-Type header
+ * @param contentType the content type that should be set in the header
+ * @returns the decorator of the function
+ */
export function ContentType(contentType: MessageType) {
return function (target: T): T {
Reflect.defineMetadata(
diff --git a/src/HttpUtils/HttpMethod.ts b/src/HttpUtils/HttpMethod.ts
index f6075f76a3ee46a0b7f6efa742810a3cf940527e..006aac33124fdc70c6148b0ae9de70cd7e084f6a 100644
--- a/src/HttpUtils/HttpMethod.ts
+++ b/src/HttpUtils/HttpMethod.ts
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+/**
+ * A simple enumerator of supported HTTP methods
+ */
export enum HttpMethod {
GET = 'GET',
POST = 'POST',
diff --git a/src/HttpUtils/HttpReturnCode.ts b/src/HttpUtils/HttpReturnCode.ts
index 9414205bf7cbb11081d8382e1cf32f0194422e34..930965e9ad915bd84b09a7fe045f2756a3dd097d 100644
--- a/src/HttpUtils/HttpReturnCode.ts
+++ b/src/HttpUtils/HttpReturnCode.ts
@@ -18,6 +18,13 @@ import { Constructable } from '../Utils/Constructable';
export const HTTP_CODE_METADATA_KEY = Symbol('cream:http:return-code');
+/**
+ * This decorator is used to decorate a class to add information about
+ * the return code that should be sent to the user when the target is serialized
+ * or in general returned from an ExpressCall
+ * @param code the code that should be returned to the user
+ * @returns the decorator of the function
+ */
export function HttpReturnCode(code: number) {
return function (target: T): T {
Reflect.defineMetadata(HTTP_CODE_METADATA_KEY, code, target.prototype);
diff --git a/src/HttpUtils/Route.ts b/src/HttpUtils/Route.ts
index 9ee72bf8ba8238241a47052aeafcd8b06f890ffd..2046c23c4d8de5103ec86466970a5b0dd7af3956 100644
--- a/src/HttpUtils/Route.ts
+++ b/src/HttpUtils/Route.ts
@@ -23,17 +23,40 @@ import {
import { HttpMethod } from './HttpMethod';
import { ExpressModule } from '../ExpressAdapter/ExpressModule';
+/**
+ * @internal
+ * a type to define express-like functions
+ */
export type ExpressFunction = (
req: ExtendedRequest,
res: Response,
next: NextFunction
) => Promise;
+/**
+ * @internal
+ * This type defines a method that creates
+ * a method of type ExpressFunction based on ExpressModule
+ */
export type ExpressFunctionFactory = (
thisArg: ExpressModule
) => ExpressFunction;
+/**
+ * @internal
+ * This class represents the route associated with a ExpressCall.
+ * it is used to map endpoints to Express Functions that will
+ * map parameters to the target method and then call the method.
+ */
export class Route {
+ /**
+ *
+ * @param route The route of the ExpressCall decorated method. It is relative to the base path of the ExpressController
+ * @param method The function that will handle the request and then call the class method with the correct bounded "this"
+ * @param methodName the original method name
+ * @param httpMethod the http method associated with the ExpressCall decorated module
+ * @param middlewares the stack of middleware that should be called before param method
+ */
constructor(
public route: string,
public method: ExpressFunctionFactory,
diff --git a/src/Serializer/CommonSerializers.ts b/src/Serializer/CommonSerializers.ts
index 2b611d1957a63ca4a91cbd7a5cbbadacd7168250..6065e41c4e3904a0bba593ca2a29f8cabf5754d0 100644
--- a/src/Serializer/CommonSerializers.ts
+++ b/src/Serializer/CommonSerializers.ts
@@ -17,8 +17,25 @@
import { Serializer, SerialBite, SerializerCommon } from './ExpressSerializer';
import { SerializerMetaInfo } from './SerializerMetaInfo';
+/**
+ * In this namespace you can find common serializers defined by
+ * Cream
+ */
export namespace CreamSerializers {
+ /**
+ * This serializer serialize objects to JSON notation
+ * It does not serialize methods, only attributes
+ * @remarks circular dependencies are not checked yet and this can
+ * cause a serious issue with your code.
+ */
export class JSON extends Serializer {
+ /**
+ * This method takes a number as input and returns a string corresponding to that number
+ * it just uses {@link Number.toString} under the hood
+ * @param _dataLabel **ignored**
+ * @param num The number to be serialized
+ * @returns a string representing the number num in json format
+ */
async serializeNumber(
_dataLabel: string,
num: number
@@ -26,6 +43,12 @@ export namespace CreamSerializers {
return Number(num).toString();
}
+ /**
+ * This method just adds one quota at the beginning and one at the end of the string
+ * @param _dataLabel **ignored**
+ * @param data The string to be serialized
+ * @returns a string representing the string data in json format
+ */
async serializeString(
_dataLabel: string,
data: string
@@ -33,6 +56,12 @@ export namespace CreamSerializers {
return '"' + data + '"';
}
+ /**
+ * This method returns the string equivalent of the boolean values true/false in the JSON format
+ * @param _dataLabel **ignored**
+ * @param data the boolean data to be serialized
+ * @returns the string equivalent in JSON format of data
+ */
async serializeBoolean(
_dataLabel: string,
data: boolean
@@ -40,10 +69,31 @@ export namespace CreamSerializers {
return data ? 'true' : 'false';
}
+ /**
+ * This method is called when a null valued attribute is found.
+ * @remarks Beware that this doesn't mean that the value was undefined, but rather that
+ * the attribute exists but it is null, with not a value.\
+ * For further information about the difference between undefined and null
+ * you should look in the JavaScript documentation.
+ * @param _dataLabel **ignored**
+ * @returns 'null'
+ */
async serializeNull(_dataLabel: string): Promise {
return 'null';
}
+ /**
+ * This method will do the job of serializing the input
+ * object that is automatically converted to a stream.\
+ * @remarks The stream order is top-down from class notation,
+ * so the first attribute found in the class will be the first
+ * to be inserted in the serialStream
+ * @param _serializedObjectName - **ignored**
+ * @param serialStream - the input stream the object to be serialized was sliced to by the {@link Serializer.serialize} method
+ * @param metaInfo - additional meta information about how to handle the object. These attributes were defined
+ * by {@link Meta} or by the {@link Serializer.serialize} method
+ * @returns a string representing the serialized version of the stream in JSON format
+ */
async handleSerializationStream(
_serializedObjectName: string,
serialStream: SerialBite[],
@@ -56,20 +106,37 @@ export namespace CreamSerializers {
return this.serializeObject(serialStream);
}
+ /**
+ * This method serializes array-like objects previously identified by
+ * the {@link Serializer.serialize} method.
+ * @param serialStream - the serial stream of the array. each object is one object of the array
+ * SerialBite.dataLabel will be the index of the object
+ * @returns the string corresponding to the array. If the array is empty it will return a '[]'
+ * representing the empty array in JSON
+ */
async serializeArray(serialStream: SerialBite[]): Promise {
+ if (serialStream.length == 0) return '[]';
+
let outStream = '[';
for (let elem of serialStream) {
outStream +=
(await this.serialize(elem.dataLabel, elem.data)) + ',';
}
- if (outStream.endsWith(',')) {
- return outStream.slice(0, outStream.length - 1) + ']';
- } else {
- return outStream + ']';
- }
+
+ return outStream.slice(0, outStream.length - 1) + ']';
}
+ /**
+ * This method is used to serialize a stream that contains object-like data
+ * {@link SerialBite.dataLabel} will contain either the attribute name or the alternative
+ * given by the {@link MapTo} decorator. This is not an issue because a reference to
+ * the actual field is held in {@link SerialBite.data}
+ * @param serialStream - the array of SerialBites representing the object
+ * @returns the string representing the serialStream in JSON format
+ */
async serializeObject(serialStream: SerialBite[]): Promise {
+ if (serialStream.length == 0) return '{}';
+
let outStream = '{';
for (let elem of serialStream) {
if (elem.data !== undefined) {
@@ -82,14 +149,16 @@ export namespace CreamSerializers {
}
}
- if (outStream.endsWith(',')) {
- return outStream.slice(0, outStream.length - 1) + '}';
- }
-
- return outStream + '}';
+ return outStream.slice(0, outStream.length - 1) + '}';
}
}
+ /**
+ * @experimental
+ * This class will act as a serializer to XML format.
+ * This is still experimental and it is not yet suggested for
+ * extensive use. Any help on its enhancement is welcome!
+ */
export class XML extends Serializer {
public static Attribute: string = 'xml:attribute';
public static Text: string = 'xml:text';
diff --git a/src/Serializer/ExpressSerializer.ts b/src/Serializer/ExpressSerializer.ts
index 2262caad9f1fd93594dbea1c8e9250130516bc6d..864a0bee3612f18fa1b27c719f2d864cb3b7d4e6 100644
--- a/src/Serializer/ExpressSerializer.ts
+++ b/src/Serializer/ExpressSerializer.ts
@@ -26,21 +26,62 @@ import {
SerializerMetaInfo,
} from './SerializerMetaInfo';
+/**
+ * @internal
+ * Defines the base types that should be serialized
+ */
type BaseSerializable = {} | string | number | boolean;
+/**
+ * @internal
+ * This type defines the unit of data that
+ * should be serialized
+ */
export type SerialBite = {
+ /**
+ * the data that should be serialized
+ */
data: BaseSerializable;
+
+ /**
+ * The label that should be used when serializing
+ * aka the output name
+ */
dataLabel: string;
+
+ /**
+ * Additional meta information that can be
+ * used by serializers when serializing objects
+ */
metaInfo: SerializerMetaInfo | undefined;
};
+/**
+ * This namespace defines Common information
+ * for serializers
+ */
export namespace SerializerCommon {
+ /**
+ * This namespace defines the common attributes to all serializers
+ */
export namespace Attributes {
+ /**
+ * This attribute is used to declare an object as an array
+ */
export const Array: string = 'common:array';
+
+ /**
+ * This attribute is used to automatically serialize everything in the object
+ */
export const AutoSerialize: string = 'common:autoserialize';
}
}
+/**
+ * This base class that implements the complex logic for handling
+ * serialization of objects. It gives a framework to easily implement
+ * complex serializers.
+ */
export abstract class Serializer {
private targetName: string;
private contextStack: object[] = [];
@@ -67,6 +108,12 @@ export abstract class Serializer {
abstract serializeNull(dataLabel: string): Promise;
+ /**
+ * This method is used to serialize a piece of data
+ * @param dataLabel the label of the data
+ * @param data the actual data
+ * @returns a string representing the serialized object
+ */
public async serialize(dataLabel: string, data: any): Promise {
if (typeof data === 'number') {
return this.serializeNumber(dataLabel, data);
@@ -85,6 +132,13 @@ export abstract class Serializer {
return this.serializeAnyObject(dataLabel, data);
}
+ /**
+ * This method is used to serialize anything that is not
+ * a base type
+ * @param dataLabel the data label that should be used when rendering
+ * @param data the data that should be serialized
+ * @returns the string representing the serialized object
+ */
private async serializeAnyObject(
dataLabel: string,
data: Object
@@ -135,19 +189,37 @@ export abstract class Serializer {
return outStream;
}
+ /**
+ * Gets the current context
+ * @returns the current context
+ */
private getContext(): Object {
if (this.contextStack.length == 0) return {};
return this.contextStack[this.contextStack.length - 1];
}
+ /**
+ * removes the current context from the stack
+ */
private popContext() {
this.contextStack.pop();
}
+ /**
+ * This is used to push a context to the stack.
+ * This context is used to infer data ownership
+ * @param context the context that should be pushed
+ */
private pushContext(context: Object) {
this.contextStack.push(context);
}
+ /**
+ * This static method makes any object serializable by iterating through the object
+ * this can break when recursive references are taken in place
+ * @param data the data to be serialized
+ * @returns the decorated data
+ */
public static makeSerializable(data: any) {
for (let key in data) {
AutoMap(data, key);
@@ -156,6 +228,13 @@ export abstract class Serializer {
return data;
}
+ /**
+ * This method is called before handling a custom object
+ * @param dataLabel the label of the object
+ * @param data the object
+ * @param metaInfo any information useful for serialization
+ * @returns a string that should be appended before the serialization of the object
+ */
public async preObject(
dataLabel: string,
data: SerialBite[],
@@ -164,6 +243,13 @@ export abstract class Serializer {
return '';
}
+ /**
+ * This method is called after handling a custom object
+ * @param dataLabel the label of the object
+ * @param data the object
+ * @param metaInfo any information useful for serialization
+ * @returns a string that should be appended after the serialization of the object
+ */
public async postObject(
dataLabel: string,
data: SerialBite[],
@@ -172,10 +258,21 @@ export abstract class Serializer {
return '';
}
+ /**
+ * This method checks if the provided data is an array
+ * @param data the data to be checked
+ * @returns true if data is array, false otherwise
+ */
private dataIsArray(data: Object): boolean {
return data.hasOwnProperty('length');
}
+ /**
+ * This method is used to create a serialization of
+ * the array in data by putting the index as the
+ * field name
+ * @param data the array that should be mapped
+ */
private autoMapArray(data: any[]): void {
let serialMap: SerialMap[] = [];
@@ -189,6 +286,11 @@ export abstract class Serializer {
Reflect.defineMetadata(SERIAL_MAP_METADATA_KEY, serialMap, data);
}
+ /**
+ * This method is used to streamify the data from the object given as input
+ * @param data the data that should be streamified
+ * @returns the stream of SerialBites that will be later handled by the serializer
+ */
private streamify(data: Object): SerialBite[] {
let streamBuffer: SerialBite[] = [];
@@ -216,6 +318,12 @@ export abstract class Serializer {
return streamBuffer;
}
+ /**
+ * This method will return meta information for the current context based on the
+ * dataLabel
+ * @param dataLabel the name of the field that the metadata should be retrieved from
+ * @returns the metadata associated with the dataLabel
+ */
private fetchMetaInfoForObject(dataLabel: string): SerializerMetaInfo {
let serialMap: SerialMap[] | undefined = Reflect.getMetadata(
SERIAL_MAP_METADATA_KEY,
@@ -237,7 +345,10 @@ export abstract class Serializer {
}
/**
+ * @internal
* This class is only used to bootstrap serialization
+ * It will also handle base types when no complex object is returned
+ * The serialization of those types is language agnostic
*/
export class BootstrapSerializer extends Serializer {
async serializeNull(_dataLabel: string): Promise {
diff --git a/src/Serializer/Serializable.ts b/src/Serializer/Serializable.ts
index 7f36c1246b217da8aac7fbce5851f3c0af432f32..c20b4dd4b59dd69ccb59b1e64e42934ecf6828be 100644
--- a/src/Serializer/Serializable.ts
+++ b/src/Serializer/Serializable.ts
@@ -25,6 +25,12 @@ export type SerialMap = {
outName: string;
};
+/**
+ * Declares a class to be serialized and also declares the serializer that
+ * should be used to serialize the decorated class
+ * @param serializer the Serializer that should be used
+ * @returns the decorator that will decorate the class
+ */
export function Serializable(
serializer: Constructable
) {
@@ -38,10 +44,24 @@ export function Serializable(
};
}
+/**
+ * This method is used to declare a field serializable
+ * and it will also get the name of the field automatically
+ * @param target the target class attribute
+ * @param propertyName the name of the class attribute
+ * @returns the decorator that will handle the mapping for serialization
+ */
export function AutoMap(target: any, propertyName: string) {
return MapTo(propertyName)(target, propertyName);
}
+/**
+ * This method is used to declare a field serializable
+ * and it will also get the name of the field automatically but it will
+ * map to a different name declared by the user
+ * @param name the new name of the field in the serialized object
+ * @returns the decorator that will handle the mapping for serialization
+ */
export function MapTo(name: string) {
return function (target: any, propertyName: string) {
let serialMap: SerialMap[] =
diff --git a/src/Serializer/SerializableDataStructures.ts b/src/Serializer/SerializableDataStructures.ts
index e0d275b8fdbfafe35dd988e62bb1afd048d37452..cce85e5502f86cce3a70babd9f8ae5b16f6c3355 100644
--- a/src/Serializer/SerializableDataStructures.ts
+++ b/src/Serializer/SerializableDataStructures.ts
@@ -17,6 +17,9 @@
import { Serializable } from './Serializable';
import { CreamSerializers } from './CommonSerializers';
+/**
+ * A common definition for a top level serializable aray
+ */
@Serializable(CreamSerializers.JSON)
export class JSONSerializableArray extends Array {
constructor(array: T[]) {
diff --git a/src/Serializer/SerializerMetaInfo.ts b/src/Serializer/SerializerMetaInfo.ts
index 4db9daafc37b9c7e0222118f9ff157e221d85bf8..a1c54d3abda4f57ef0c3f969c07a26778da38ef2 100644
--- a/src/Serializer/SerializerMetaInfo.ts
+++ b/src/Serializer/SerializerMetaInfo.ts
@@ -18,6 +18,12 @@ export const SERIALIZER_META_INFO_METADATA_KEY = Symbol(
'cream:data-serializer'
);
+/**
+ * @internal
+ * This class is used to store meta information for
+ * the serializers that will be used to make decisions
+ * when serializing objects
+ */
export class SerializerMetaInfo {
private attributes: string[] = [];
@@ -30,6 +36,14 @@ export class SerializerMetaInfo {
}
}
+/**
+ * Defines a metadata for the attribute that can be used by
+ * serializers to make decisions on serialization.
+ * @example the XML serializer uses the attribute AutoIndex to automatically
+ * add the index of the element as the attribute index in the xml tag
+ * @param attribute the attribute that should be defined
+ * @returns the decorator that will decorate the attribute
+ */
export function Meta(attribute: string) {
return function (target: any, propertyName: string) {
let metaInfo: SerializerMetaInfo =
diff --git a/src/Utils/Constructable.ts b/src/Utils/Constructable.ts
index b4a063dc19c1f8202ff0d279568f0e53ce7ba495..6a1e01ea5ab6c04b38546dd3840c1640734d86c9 100644
--- a/src/Utils/Constructable.ts
+++ b/src/Utils/Constructable.ts
@@ -14,6 +14,15 @@
* limitations under the License.
*/
+/**
+ * @internal
+ * This type is used for identifying objects that implement the new function
+ * in their prototype. This is useful to identify constructable objects
+ */
export type Constructable = { new (...args: any): T };
+/**
+ * @internal
+ * It is just an array of type Constructable
+ */
export type Constructables = Constructable[];