diff --git a/.gitignore b/.gitignore index 1170717..8f5a1e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,136 +1,136 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# vitepress build output -**/.vitepress/dist - -# vitepress cache directory -**/.vitepress/cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/LICENSE b/LICENSE index 6d8cea4..0fe7849 100644 --- a/LICENSE +++ b/LICENSE @@ -1,190 +1,190 @@ -EUROPEAN UNION PUBLIC LICENCE v. 1.2 -EUPL © the European Union 2007, 2016 - -This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the -terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such -use is covered by a right of the copyright holder of the Work). -The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following -notice immediately following the copyright notice for the Work: - Licensed under the EUPL -or has expressed by any other means his willingness to license under the EUPL. - -1.Definitions -In this Licence, the following terms have the following meaning: -— ‘The Licence’:this Licence. -— ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available -as Source Code and also as Executable Code as the case may be. -— ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or -modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work -required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in -the country mentioned in Article 15. -— ‘The Work’:the Original Work or its Derivative Works. -— ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and -modify. -— ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by -a computer as a program. -— ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence. -— ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to -the creation of a Derivative Work. -— ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the -Licence. -— ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating, -transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential -functionalities at the disposal of any other natural or legal person. - -2.Scope of the rights granted by the Licence -The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for -the duration of copyright vested in the Original Work: -— use the Work in any circumstance and for all usage, -— reproduce the Work, -— modify the Work, and make Derivative Works based upon the Work, -— communicate to the public, including the right to make available or display the Work or copies thereof to the public -and perform publicly, as the case may be, the Work, -— distribute the Work or copies thereof, -— lend and rent the Work or copies thereof, -— sublicense rights in the Work or copies thereof. -Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the -applicable law permits so. -In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed -by law in order to make effective the licence of the economic rights here above listed. -The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the -extent necessary to make use of the rights granted on the Work under this Licence. - -3.Communication of the Source Code -The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as -Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with -each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to -the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to -distribute or communicate the Work. - -4.Limitations on copyright -Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the -exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations -thereto. - -5.Obligations of the Licensee -The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those -obligations are the following: - -Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to -the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the -Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work -to carry prominent notices stating that the Work has been modified and the date of modification. - -Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this -Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless -the Original Work is expressly distributed only under this version of the Licence — for example by communicating -‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the -Work or Derivative Work that alter or restrict the terms of the Licence. - -Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both -the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done -under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed -in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with -his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail. - -Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide -a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available -for as long as the Licensee continues to distribute or communicate the Work. -Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names -of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and -reproducing the content of the copyright notice. - -6.Chain of Authorship -The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or -licensed to him/her and that he/she has the power and authority to grant the Licence. -Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or -licensed to him/her and that he/she has the power and authority to grant the Licence. -Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions -to the Work, under the terms of this Licence. - -7.Disclaimer of Warranty -The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work -and may therefore contain defects or ‘bugs’ inherent to this type of development. -For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind -concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or -errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this -Licence. -This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work. - -8.Disclaimer of Liability -Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be -liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the -Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss -of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However, -the Licensor will be liable under statutory product liability laws as far such laws apply to the Work. - -9.Additional agreements -While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services -consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole -responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, -defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by -the fact You have accepted any warranty or additional liability. - -10.Acceptance of the Licence -The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window -displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of -applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms -and conditions. -Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You -by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution -or Communication by You of the Work or copies thereof. - -11.Information to the public -In case of any Distribution or Communication of the Work by means of electronic communication by You (for example, -by offering to download the Work from a remote location) the distribution channel or media (for example, a website) -must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence -and the way it may be accessible, concluded, stored and reproduced by the Licensee. - -12.Termination of the Licence -The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms -of the Licence. -Such a termination will not terminate the licences of any person who has received the Work from the Licensee under -the Licence, provided such persons remain in full compliance with the Licence. - -13.Miscellaneous -Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the -Work. -If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or -enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid -and enforceable. -The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of -the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. -New versions of the Licence will be published with a unique version number. -All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take -advantage of the linguistic version of their choice. - -14.Jurisdiction -Without prejudice to specific agreement between parties, -— any litigation resulting from the interpretation of this License, arising between the European Union institutions, -bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice -of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union, -— any litigation arising between other parties and resulting from the interpretation of this License, will be subject to -the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business. - -15.Applicable Law -Without prejudice to specific agreement between parties, -— this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat, -resides or has his registered office, -— this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside -a European Union Member State. - - - Appendix - -‘Compatible Licences’ according to Article 5 EUPL are: -— GNU General Public License (GPL) v. 2, v. 3 -— GNU Affero General Public License (AGPL) v. 3 -— Open Software License (OSL) v. 2.1, v. 3.0 -— Eclipse Public License (EPL) v. 1.0 -— CeCILL v. 2.0, v. 2.1 -— Mozilla Public Licence (MPL) v. 2 -— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 -— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software -— European Union Public Licence (EUPL) v. 1.1, v. 1.2 -— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+). - -The European Commission may update this Appendix to later versions of the above licences without producing -a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the -covered Source Code from exclusive appropriation. -All other changes or additions to this Appendix require the production of a new EUPL version. +EUROPEAN UNION PUBLIC LICENCE v. 1.2 +EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the +terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). +The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following +notice immediately following the copyright notice for the Work: + Licensed under the EUPL +or has expressed by any other means his willingness to license under the EUPL. + +1.Definitions +In this Licence, the following terms have the following meaning: +— ‘The Licence’:this Licence. +— ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available +as Source Code and also as Executable Code as the case may be. +— ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or +modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work +required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in +the country mentioned in Article 15. +— ‘The Work’:the Original Work or its Derivative Works. +— ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and +modify. +— ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by +a computer as a program. +— ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence. +— ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to +the creation of a Derivative Work. +— ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the +Licence. +— ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating, +transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential +functionalities at the disposal of any other natural or legal person. + +2.Scope of the rights granted by the Licence +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for +the duration of copyright vested in the Original Work: +— use the Work in any circumstance and for all usage, +— reproduce the Work, +— modify the Work, and make Derivative Works based upon the Work, +— communicate to the public, including the right to make available or display the Work or copies thereof to the public +and perform publicly, as the case may be, the Work, +— distribute the Work or copies thereof, +— lend and rent the Work or copies thereof, +— sublicense rights in the Work or copies thereof. +Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the +applicable law permits so. +In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed +by law in order to make effective the licence of the economic rights here above listed. +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the +extent necessary to make use of the rights granted on the Work under this Licence. + +3.Communication of the Source Code +The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as +Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with +each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to +the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to +distribute or communicate the Work. + +4.Limitations on copyright +Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the +exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5.Obligations of the Licensee +The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those +obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to +the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the +Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work +to carry prominent notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this +Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless +the Original Work is expressly distributed only under this version of the Licence — for example by communicating +‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the +Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both +the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done +under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed +in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide +a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available +for as long as the Licensee continues to distribute or communicate the Work. +Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names +of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6.Chain of Authorship +The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or +licensed to him/her and that he/she has the power and authority to grant the Licence. +Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or +licensed to him/her and that he/she has the power and authority to grant the Licence. +Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions +to the Work, under the terms of this Licence. + +7.Disclaimer of Warranty +The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work +and may therefore contain defects or ‘bugs’ inherent to this type of development. +For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind +concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or +errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this +Licence. +This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work. + +8.Disclaimer of Liability +Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be +liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the +Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss +of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However, +the Licensor will be liable under statutory product liability laws as far such laws apply to the Work. + +9.Additional agreements +While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services +consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by +the fact You have accepted any warranty or additional liability. + +10.Acceptance of the Licence +The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window +displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms +and conditions. +Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You +by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution +or Communication by You of the Work or copies thereof. + +11.Information to the public +In case of any Distribution or Communication of the Work by means of electronic communication by You (for example, +by offering to download the Work from a remote location) the distribution channel or media (for example, a website) +must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence +and the way it may be accessible, concluded, stored and reproduced by the Licensee. + +12.Termination of the Licence +The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms +of the Licence. +Such a termination will not terminate the licences of any person who has received the Work from the Licensee under +the Licence, provided such persons remain in full compliance with the Licence. + +13.Miscellaneous +Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the +Work. +If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or +enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid +and enforceable. +The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of +the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. +New versions of the Licence will be published with a unique version number. +All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take +advantage of the linguistic version of their choice. + +14.Jurisdiction +Without prejudice to specific agreement between parties, +— any litigation resulting from the interpretation of this License, arising between the European Union institutions, +bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice +of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union, +— any litigation arising between other parties and resulting from the interpretation of this License, will be subject to +the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business. + +15.Applicable Law +Without prejudice to specific agreement between parties, +— this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat, +resides or has his registered office, +— this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside +a European Union Member State. + + + Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: +— GNU General Public License (GPL) v. 2, v. 3 +— GNU Affero General Public License (AGPL) v. 3 +— Open Software License (OSL) v. 2.1, v. 3.0 +— Eclipse Public License (EPL) v. 1.0 +— CeCILL v. 2.0, v. 2.1 +— Mozilla Public Licence (MPL) v. 2 +— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software +— European Union Public Licence (EUPL) v. 1.1, v. 1.2 +— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above licences without producing +a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the +covered Source Code from exclusive appropriation. +All other changes or additions to this Appendix require the production of a new EUPL version. diff --git a/README.md b/README.md index 3a32a8a..5ad354f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# Sludge Settler - +# Sludge Settler + Sludge settler node \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..42ca027 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +# settler Example Flows + +Import-ready Node-RED examples for settler. + +## Files +- basic.flow.json +- integration.flow.json +- edge.flow.json diff --git a/examples/basic.flow.json b/examples/basic.flow.json new file mode 100644 index 0000000..1610376 --- /dev/null +++ b/examples/basic.flow.json @@ -0,0 +1,6 @@ +[ + {"id":"settler_basic_tab","type":"tab","label":"settler basic","disabled":false,"info":"settler basic example"}, + {"id":"settler_basic_node","type":"settler","z":"settler_basic_tab","name":"settler basic","x":420,"y":180,"wires":[["settler_basic_dbg"]]}, + {"id":"settler_basic_inj","type":"inject","z":"settler_basic_tab","name":"basic trigger","props":[{"p":"topic","vt":"str"},{"p":"payload","vt":"str"}],"topic":"ping","payload":"1","payloadType":"str","x":160,"y":180,"wires":[["settler_basic_node"]]}, + {"id":"settler_basic_dbg","type":"debug","z":"settler_basic_tab","name":"settler basic debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":660,"y":180,"wires":[]} +] diff --git a/examples/edge.flow.json b/examples/edge.flow.json new file mode 100644 index 0000000..04d37b1 --- /dev/null +++ b/examples/edge.flow.json @@ -0,0 +1,6 @@ +[ + {"id":"settler_edge_tab","type":"tab","label":"settler edge","disabled":false,"info":"settler edge example"}, + {"id":"settler_edge_node","type":"settler","z":"settler_edge_tab","name":"settler edge","x":420,"y":180,"wires":[["settler_edge_dbg"]]}, + {"id":"settler_edge_inj","type":"inject","z":"settler_edge_tab","name":"unknown topic","props":[{"p":"topic","vt":"str"},{"p":"payload","vt":"str"}],"topic":"doesNotExist","payload":"x","payloadType":"str","x":170,"y":180,"wires":[["settler_edge_node"]]}, + {"id":"settler_edge_dbg","type":"debug","z":"settler_edge_tab","name":"settler edge debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":660,"y":180,"wires":[]} +] diff --git a/examples/integration.flow.json b/examples/integration.flow.json new file mode 100644 index 0000000..65c0b3a --- /dev/null +++ b/examples/integration.flow.json @@ -0,0 +1,6 @@ +[ + {"id":"settler_int_tab","type":"tab","label":"settler integration","disabled":false,"info":"settler integration example"}, + {"id":"settler_int_node","type":"settler","z":"settler_int_tab","name":"settler integration","x":420,"y":180,"wires":[["settler_int_dbg"]]}, + {"id":"settler_int_inj","type":"inject","z":"settler_int_tab","name":"registerChild","props":[{"p":"topic","vt":"str"},{"p":"payload","vt":"str"}],"topic":"registerChild","payload":"example-child-id","payloadType":"str","x":170,"y":180,"wires":[["settler_int_node"]]}, + {"id":"settler_int_dbg","type":"debug","z":"settler_int_tab","name":"settler integration debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":680,"y":180,"wires":[]} +] diff --git a/package.json b/package.json index c2150aa..ca2e650 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "author": "P.R. van der Wilt", "main": "settler.js", "scripts": { - "test": "node settler.js" + "test": "node --test test/basic/*.test.js test/integration/*.test.js test/edge/*.test.js" }, "node-red": { "nodes": { diff --git a/settler.html b/settler.html index 5ca254f..24578fb 100644 --- a/settler.html +++ b/settler.html @@ -1,68 +1,68 @@ - - - - - - - + + + + + + \ No newline at end of file diff --git a/settler.js b/settler.js index 12fbb6b..db68c4e 100644 --- a/settler.js +++ b/settler.js @@ -1,26 +1,26 @@ -const nameOfNode = "settler"; // name of the node, should match file name and node type in Node-RED -const nodeClass = require('./src/nodeClass.js'); // node class -const { MenuManager } = require('generalFunctions'); - - -module.exports = function (RED) { - // Register the node type - RED.nodes.registerType(nameOfNode, function (config) { - // Initialize the Node-RED node first - RED.nodes.createNode(this, config); - // Then create your custom class and attach it - this.nodeClass = new nodeClass(config, RED, this, nameOfNode); - }); - - const menuMgr = new MenuManager(); - - // Serve /settler/menu.js - RED.httpAdmin.get(`/${nameOfNode}/menu.js`, (req, res) => { - try { - const script = menuMgr.createEndpoint(nameOfNode, ['logger', 'position']); - res.type('application/javascript').send(script); - } catch (err) { - res.status(500).send(`// Error generating menu: ${err.message}`); - } - }); +const nameOfNode = "settler"; // name of the node, should match file name and node type in Node-RED +const nodeClass = require('./src/nodeClass.js'); // node class +const { MenuManager } = require('generalFunctions'); + + +module.exports = function (RED) { + // Register the node type + RED.nodes.registerType(nameOfNode, function (config) { + // Initialize the Node-RED node first + RED.nodes.createNode(this, config); + // Then create your custom class and attach it + this.nodeClass = new nodeClass(config, RED, this, nameOfNode); + }); + + const menuMgr = new MenuManager(); + + // Serve /settler/menu.js + RED.httpAdmin.get(`/${nameOfNode}/menu.js`, (req, res) => { + try { + const script = menuMgr.createEndpoint(nameOfNode, ['logger', 'position']); + res.type('application/javascript').send(script); + } catch (err) { + res.status(500).send(`// Error generating menu: ${err.message}`); + } + }); }; \ No newline at end of file diff --git a/src/nodeClass.js b/src/nodeClass.js index 4757683..f0a8c11 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -1,114 +1,121 @@ -const { Settler } = require('./specificClass.js'); - - -class nodeClass { - /** - * Node-RED node class for settler. - * @param {object} uiConfig - Node-RED node configuration - * @param {object} RED - Node-RED runtime API - * @param {object} nodeInstance - Node-RED node instance - * @param {string} nameOfNode - Name of the node - */ - constructor(uiConfig, RED, nodeInstance, nameOfNode) { - // Preserve RED reference for HTTP endpoints if needed - this.node = nodeInstance; - this.RED = RED; - this.name = nameOfNode; - this.source = null; - - this._loadConfig(uiConfig) - this._setupClass(); - - this._attachInputHandler(); - this._registerChild(); - this._startTickLoop(); - this._attachCloseHandler(); - } - - /** - * Handle node-red input messages - */ +const { Settler } = require('./specificClass.js'); + + +class nodeClass { + /** + * Node-RED node class for settler. + * @param {object} uiConfig - Node-RED node configuration + * @param {object} RED - Node-RED runtime API + * @param {object} nodeInstance - Node-RED node instance + * @param {string} nameOfNode - Name of the node + */ + constructor(uiConfig, RED, nodeInstance, nameOfNode) { + // Preserve RED reference for HTTP endpoints if needed + this.node = nodeInstance; + this.RED = RED; + this.name = nameOfNode; + this.source = null; + + this._loadConfig(uiConfig) + this._setupClass(); + + this._attachInputHandler(); + this._registerChild(); + this._startTickLoop(); + this._attachCloseHandler(); + } + + /** + * Handle node-red input messages + */ _attachInputHandler() { this.node.on('input', (msg, send, done) => { - - switch (msg.topic) { - case 'registerChild': - // Register this node as a parent of the child node - const childId = msg.payload; - const childObj = this.RED.nodes.getNode(childId); - this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent); - break; - default: - console.log("Unknown topic: " + msg.topic); + try { + switch (msg.topic) { + case 'registerChild': { + const childId = msg.payload; + const childObj = this.RED.nodes.getNode(childId); + if (!childObj || !childObj.source) { + this.source?.logger?.warn(`registerChild skipped: missing child/source for id=${childId}`); + break; + } + this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent); + break; + } + default: + this.source?.logger?.warn(`Unknown topic: ${msg.topic}`); + } + } catch (error) { + this.source?.logger?.error(`Input handler failure: ${error.message}`); } - if (done) { + if (typeof done === 'function') { done(); } }); } - - /** - * Parse node configuration - * @param {object} uiConfig Config set in UI in node-red - */ - _loadConfig(uiConfig) { - this.config = { - general: { - name: uiConfig.name || this.name, - id: this.node.id, - unit: null, - logging: { - enabled: uiConfig.enableLog, - logLevel: uiConfig.logLevel - } - }, - functionality: { - positionVsParent: uiConfig.positionVsParent || 'atEquipment', // Default to 'atEquipment' if not specified - softwareType: "settler" // should be set in config manager - } - } - } - - /** - * Register this node as a child upstream and downstream. - * Delayed to avoid Node-RED startup race conditions. - */ - _registerChild() { - setTimeout(() => { - this.node.send([ - null, - null, - { topic: 'registerChild', payload: this.node.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' } - ]); - }, 100); - } - - /** - * Setup settler class - */ - _setupClass() { - - this.source = new Settler(this.config); // protect from reassignment - this.node.source = this.source; - } - - _startTickLoop() { - setTimeout(() => { - this._tickInterval = setInterval(() => this._tick(), 1000); - }, 1000); - } - - _tick(){ - this.node.send([this.source.getEffluent, null, null]); - } - + + /** + * Parse node configuration + * @param {object} uiConfig Config set in UI in node-red + */ + _loadConfig(uiConfig) { + this.config = { + general: { + name: uiConfig.name || this.name, + id: this.node.id, + unit: null, + logging: { + enabled: uiConfig.enableLog, + logLevel: uiConfig.logLevel + } + }, + functionality: { + positionVsParent: uiConfig.positionVsParent || 'atEquipment', // Default to 'atEquipment' if not specified + softwareType: "settler" // should be set in config manager + } + } + } + + /** + * Register this node as a child upstream and downstream. + * Delayed to avoid Node-RED startup race conditions. + */ + _registerChild() { + setTimeout(() => { + this.node.send([ + null, + null, + { topic: 'registerChild', payload: this.node.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' } + ]); + }, 100); + } + + /** + * Setup settler class + */ + _setupClass() { + + this.source = new Settler(this.config); // protect from reassignment + this.node.source = this.source; + } + + _startTickLoop() { + setTimeout(() => { + this._tickInterval = setInterval(() => this._tick(), 1000); + }, 1000); + } + + _tick(){ + this.node.send([this.source.getEffluent, null, null]); + } + _attachCloseHandler() { this.node.on('close', (done) => { clearInterval(this._tickInterval); - done(); + if (typeof done === 'function') done(); }); } } - -module.exports = nodeClass; \ No newline at end of file + +module.exports = nodeClass; diff --git a/src/specificClass.js b/src/specificClass.js index 5b87aef..3f2627c 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -1,144 +1,157 @@ -const { childRegistrationUtils, logger, MeasurementContainer } = require('generalFunctions'); -const EventEmitter = require('events'); - -class Settler { - constructor(config) { - this.config = config; - // EVOLV stuff - this.logger = new logger(this.config.general.logging.enabled, this.config.general.logging.logLevel, config.general.name); - this.emitter = new EventEmitter(); - this.measurements = new MeasurementContainer(); - this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility - - this.upstreamReactor = null; - this.returnPump = null; - - // state variables - this.F_in = 0; // debit in - this.Cs_in = new Array(13).fill(0); // Concentrations in - this.C_TS = 2500; // Total solids concentration sludge - } - - get getEffluent() { - // constrain flow to prevent negatives - const F_s = Math.min((this.F_in * this.Cs_in[12]) / this.C_TS, this.F_in); - const F_eff = this.F_in - F_s; - - let F_sr = 0; - if (this.returnPump) { - F_sr = Math.min(this.returnPump.measurements.type("flow").variant("measured").position("atEquipment").getCurrentValue(), F_s); - } - const F_so = F_s - F_sr; - - // effluent - const Cs_eff = structuredClone(this.Cs_in); - if (F_s > 0) { - Cs_eff[7] = 0; - Cs_eff[8] = 0; - Cs_eff[9] = 0; - Cs_eff[10] = 0; - Cs_eff[11] = 0; - Cs_eff[12] = 0; - } - - // sludge - const Cs_s = structuredClone(this.Cs_in); - if (F_s > 0) { - Cs_s[7] = this.F_in * this.Cs_in[7] / F_s; - Cs_s[8] = this.F_in * this.Cs_in[8] / F_s; - Cs_s[9] = this.F_in * this.Cs_in[9] / F_s; - Cs_s[10] = this.F_in * this.Cs_in[10] / F_s; - Cs_s[11] = this.F_in * this.Cs_in[11] / F_s; - Cs_s[12] = this.F_in * this.Cs_in[12] / F_s; - } - - return [ - { topic: "Fluent", payload: { inlet: 0, F: F_eff, C: Cs_eff }, timestamp: Date.now() }, - { topic: "Fluent", payload: { inlet: 1, F: F_so, C: Cs_s }, timestamp: Date.now() }, - { topic: "Fluent", payload: { inlet: 2, F: F_sr, C: Cs_s }, timestamp: Date.now() } - ]; - } - - registerChild(child, softwareType) { - if(!child) { - this.logger.error(`Invalid ${softwareType} child provided.`); - return; - } - - switch (softwareType) { - case "measurement": - this.logger.debug(`Registering measurement child...`); - this._connectMeasurement(child); - break; - case "reactor": - this.logger.debug(`Registering reactor child...`); - this._connectReactor(child); - break; - case "machine": - this.logger.debug(`Registering machine child...`); - this._connectMachine(child); - break; - - default: - this.logger.error(`Unrecognized softwareType: ${softwareType}`); - } - } - - _connectMeasurement(measurementChild) { - const position = measurementChild.config.functionality.positionVsParent; - const measurementType = measurementChild.config.asset.type; - const eventName = `${measurementType}.measured.${position}`; - - // Register event listener for measurement updates - measurementChild.measurements.emitter.on(eventName, (eventData) => { - this.logger.debug(`${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`); - - // Store directly in parent's measurement container - this.measurements - .type(measurementType) - .variant("measured") - .position(position) - .value(eventData.value, eventData.timestamp, eventData.unit); - - this._updateMeasurement(measurementType, eventData.value, position, eventData); - }); - } - - _connectReactor(reactorChild) { - if (reactorChild.config.functionality.positionVsParent != "upstream") { - this.logger.warn("Reactor children of settlers should be upstream."); - } - - this.upstreamReactor = reactorChild; - - reactorChild.emitter.on("stateChange", (eventData) => { - this.logger.debug(`State change of upstream reactor detected.`); - const effluent = this.upstreamReactor.getEffluent[0]; - this.F_in = effluent.payload.F; - this.Cs_in = effluent.payload.C; - }); - } - - _connectMachine(machineChild) { - if (machineChild.config.functionality.positionVsParent == "downstream") { - machineChild.upstreamSource = this; - this.returnPump = machineChild; - return; - } - this.logger.warn(`Failed to register machine child.`); - } - - _updateMeasurement(measurementType, value, position, context) { - switch(measurementType) { - case "quantity (tss)": - this.C_TS = value; - break; - - default: - this.logger.error(`Type '${measurementType}' not recognized for measured update.`); - return; - } - } -} - -module.exports = { Settler }; \ No newline at end of file +const { childRegistrationUtils, logger, MeasurementContainer } = require('generalFunctions'); +const EventEmitter = require('events'); + +// Compatibility-safe array clone for Node runtimes without global structuredClone. +function cloneArray(values) { + if (typeof structuredClone === 'function') { + return structuredClone(values); + } + return Array.isArray(values) ? [...values] : values; +} + +/** + * Settler domain model. + * Splits influent into effluent, sludge and return sludge based on solids balance. + */ +class Settler { + constructor(config) { + this.config = config; + // EVOLV stuff + this.logger = new logger(this.config.general.logging.enabled, this.config.general.logging.logLevel, config.general.name); + this.emitter = new EventEmitter(); + this.measurements = new MeasurementContainer(); + this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility + + this.upstreamReactor = null; + this.returnPump = null; + + // state variables + this.F_in = 0; // debit in + this.Cs_in = new Array(13).fill(0); // Concentrations in + this.C_TS = 2500; // Total solids concentration sludge + } + + get getEffluent() { + // constrain flow to prevent negatives + const F_s = Math.min((this.F_in * this.Cs_in[12]) / this.C_TS, this.F_in); + const F_eff = this.F_in - F_s; + + let F_sr = 0; + if (this.returnPump) { + F_sr = Math.min(this.returnPump.measurements.type("flow").variant("measured").position("atEquipment").getCurrentValue(), F_s); + } + const F_so = F_s - F_sr; + + // effluent + const Cs_eff = cloneArray(this.Cs_in); + if (F_s > 0) { + Cs_eff[7] = 0; + Cs_eff[8] = 0; + Cs_eff[9] = 0; + Cs_eff[10] = 0; + Cs_eff[11] = 0; + Cs_eff[12] = 0; + } + + // sludge + const Cs_s = cloneArray(this.Cs_in); + if (F_s > 0) { + Cs_s[7] = this.F_in * this.Cs_in[7] / F_s; + Cs_s[8] = this.F_in * this.Cs_in[8] / F_s; + Cs_s[9] = this.F_in * this.Cs_in[9] / F_s; + Cs_s[10] = this.F_in * this.Cs_in[10] / F_s; + Cs_s[11] = this.F_in * this.Cs_in[11] / F_s; + Cs_s[12] = this.F_in * this.Cs_in[12] / F_s; + } + + return [ + { topic: "Fluent", payload: { inlet: 0, F: F_eff, C: Cs_eff }, timestamp: Date.now() }, + { topic: "Fluent", payload: { inlet: 1, F: F_so, C: Cs_s }, timestamp: Date.now() }, + { topic: "Fluent", payload: { inlet: 2, F: F_sr, C: Cs_s }, timestamp: Date.now() } + ]; + } + + registerChild(child, softwareType) { + if(!child) { + this.logger.error(`Invalid ${softwareType} child provided.`); + return; + } + + switch (softwareType) { + case "measurement": + this.logger.debug(`Registering measurement child...`); + this._connectMeasurement(child); + break; + case "reactor": + this.logger.debug(`Registering reactor child...`); + this._connectReactor(child); + break; + case "machine": + this.logger.debug(`Registering machine child...`); + this._connectMachine(child); + break; + + default: + this.logger.error(`Unrecognized softwareType: ${softwareType}`); + } + } + + _connectMeasurement(measurementChild) { + const position = measurementChild.config.functionality.positionVsParent; + const measurementType = measurementChild.config.asset.type; + const eventName = `${measurementType}.measured.${position}`; + + // Register event listener for measurement updates + measurementChild.measurements.emitter.on(eventName, (eventData) => { + this.logger.debug(`${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`); + + // Store directly in parent's measurement container + this.measurements + .type(measurementType) + .variant("measured") + .position(position) + .value(eventData.value, eventData.timestamp, eventData.unit); + + this._updateMeasurement(measurementType, eventData.value, position, eventData); + }); + } + + _connectReactor(reactorChild) { + if (reactorChild.config.functionality.positionVsParent != "upstream") { + this.logger.warn("Reactor children of settlers should be upstream."); + } + + this.upstreamReactor = reactorChild; + + reactorChild.emitter.on("stateChange", (eventData) => { + this.logger.debug(`State change of upstream reactor detected.`); + const raw = this.upstreamReactor.getEffluent; + const effluent = Array.isArray(raw) ? raw[0] : raw; + this.F_in = effluent.payload.F; + this.Cs_in = effluent.payload.C; + }); + } + + _connectMachine(machineChild) { + if (machineChild.config.functionality.positionVsParent == "downstream") { + machineChild.upstreamSource = this; + this.returnPump = machineChild; + return; + } + this.logger.warn(`Failed to register machine child.`); + } + + _updateMeasurement(measurementType, value, position, context) { + switch(measurementType) { + case "quantity (tss)": + this.C_TS = value; + break; + + default: + this.logger.error(`Type '${measurementType}' not recognized for measured update.`); + return; + } + } +} + +module.exports = { Settler }; diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..bf56344 --- /dev/null +++ b/test/README.md @@ -0,0 +1,12 @@ +# settler Test Suite Layout + +Required EVOLV layout: +- basic/ +- integration/ +- edge/ +- helpers/ + +Baseline structure tests: +- basic/structure-module-load.basic.test.js +- integration/structure-examples.integration.test.js +- edge/structure-examples-node-type.edge.test.js diff --git a/test/basic/.gitkeep b/test/basic/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/basic/structure-module-load.basic.test.js b/test/basic/structure-module-load.basic.test.js new file mode 100644 index 0000000..a802a4e --- /dev/null +++ b/test/basic/structure-module-load.basic.test.js @@ -0,0 +1,8 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); + +test('settler module load smoke', () => { + assert.doesNotThrow(() => { + require('../../settler.js'); + }); +}); diff --git a/test/edge/.gitkeep b/test/edge/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/edge/structure-examples-node-type.edge.test.js b/test/edge/structure-examples-node-type.edge.test.js new file mode 100644 index 0000000..6e24585 --- /dev/null +++ b/test/edge/structure-examples-node-type.edge.test.js @@ -0,0 +1,11 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const flow = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../examples/basic.flow.json'), 'utf8')); + +test('basic example includes node type settler', () => { + const count = flow.filter((n) => n && n.type === 'settler').length; + assert.equal(count >= 1, true); +}); diff --git a/test/helpers/.gitkeep b/test/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/.gitkeep b/test/integration/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/structure-examples.integration.test.js b/test/integration/structure-examples.integration.test.js new file mode 100644 index 0000000..d061bbc --- /dev/null +++ b/test/integration/structure-examples.integration.test.js @@ -0,0 +1,23 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const dir = path.resolve(__dirname, '../../examples'); + +function loadJson(file) { + return JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8')); +} + +test('examples package exists for settler', () => { + for (const file of ['README.md', 'basic.flow.json', 'integration.flow.json', 'edge.flow.json']) { + assert.equal(fs.existsSync(path.join(dir, file)), true, file + ' missing'); + } +}); + +test('example flows are parseable arrays for settler', () => { + for (const file of ['basic.flow.json', 'integration.flow.json', 'edge.flow.json']) { + const parsed = loadJson(file); + assert.equal(Array.isArray(parsed), true); + } +});