├── .clang-format ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── CompilerWarnings.cmake └── Sanitizers.cmake ├── example ├── async_connection.js ├── create_node_edge.js ├── create_path.js ├── create_triangle.js ├── event_emitter.js ├── promise.js ├── run_query_params.js ├── transaction_control.js └── typescript │ ├── .gitignore │ ├── default.ts │ ├── from.ts │ ├── require.ts │ ├── run.sh │ └── star.ts ├── generate_ts.sh ├── index.d.ts ├── index.js ├── package-lock.json ├── package.json ├── powershell.ps1 ├── src ├── addon.cpp ├── client.cpp ├── client.hpp ├── glue.cpp ├── glue.hpp └── util.hpp ├── test ├── connection.spec.js ├── queries.js ├── queries.spec.js └── util.js ├── tool ├── coverage.sh └── test-binding.gyp └── tsconfig.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | Standard: "C++11" 5 | UseTab: Never 6 | DerivePointerAlignment: false 7 | PointerAlignment: Right 8 | ColumnLimit : 80 9 | ... 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | example/typescript/*.js 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'amd': true, 4 | 'browser': false, 5 | 'es6': true, 6 | 'jest/globals': true, 7 | 'node': true, 8 | }, 9 | 'extends': [ 10 | 'google', 11 | 'eslint:recommended', 12 | 'plugin:node/recommended', 13 | 'plugin:prettier/recommended', 14 | ], 15 | 'globals': { 16 | 'Atomics': 'readonly', 17 | 'SharedArrayBuffer': 'readonly', 18 | }, 19 | 'parserOptions': { 20 | 'ecmaVersion': 2020, 21 | 'sourceType': 'module', 22 | }, 23 | 'plugins': ['prettier', 'jest'], 24 | 'rules': { 25 | 'prettier/prettier': [ 26 | 'error', { 27 | 'singleQuote': true, 28 | 'trailingComma': 'all', 29 | 'printWidth': 80, 30 | } 31 | ], 32 | 'max-len': ['error', {'code': 80, 'ignoreUrls': true}], 33 | 'eqeqeq': 'warn', 34 | 'new-cap': 'off', 35 | 'require-jsdoc': 'off', 36 | 'valid-jsdoc': 'off', 37 | }, 38 | "overrides": [{ 39 | "files": "test/*.js", 40 | "rules": { 41 | "node/no-unpublished-require": 0, 42 | }, 43 | }] 44 | }; 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build_and_test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Install system dependencies 10 | run: sudo apt-get install -y git cmake make gcc g++ libssl-dev 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: '18.x' 15 | - name: Install and build addon 16 | run: | 17 | npm install 18 | npm run build:release 19 | - name: Lint JS code 20 | run: | 21 | npm run lint 22 | - name: Cache Memgraph Docker image 23 | id: cache-memgraph-docker 24 | uses: actions/cache@v1 25 | with: 26 | path: ~/memgraph 27 | key: cache-memgraph-v2.5.2-docker-image 28 | - name: Download Memgraph Docker image 29 | if: steps.cache-memgraph-docker.outputs.cache-hit != 'true' 30 | run: | 31 | mkdir ~/memgraph 32 | curl -L https://memgraph.com/download/memgraph/v2.5.2/docker/memgraph-2.5.2-docker.tar.gz > ~/memgraph/memgraph-docker.tar.gz 33 | - name: Load Memgraph Docker image 34 | run: | 35 | docker load -i ~/memgraph/memgraph-docker.tar.gz 36 | - name: Run JS tests 37 | run: | 38 | npm run test 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | build/ 107 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/* 3 | !build/Release/nodemgclient.node 4 | .cache 5 | .github 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | #### NOTES: 4 | # * HOW TO CHANGE THE COMPILER? 5 | # If you want to use specific compiler, consider using -C | --cc | --cxx cmake-js flags. 6 | # 7 | # * WINDOWS AND MINGW ISSUES? 8 | # cmake-js doesn't support MinGW Makefiles -> https://github.com/cmake-js/cmake-js/issues/195 9 | # /DELAYLOAD:NODE.EXE: No such file or directory -> https://github.com/cmake-js/cmake-js/issues/200 10 | # cmake-js adds /DELAYLOAD:NODE.EXE + there is not an easy solution. 11 | # I've played with paths + setting the CMAKE_SHARED_LINKER_FLAGS but all that is N/A. 12 | # The following does not work because CMake changes the string to be Windows path. 13 | # set(CMAKE_SHARED_LINKER_FLAGS "/DELAYLOAD:/c/Program\\ Files/nodejs/node.exe") 14 | 15 | project(nodemgclient VERSION 0.1.3) 16 | 17 | if (WIN32 AND MINGW) 18 | message(FATAL_ERROR "ON WINDOWS BUILD UNDER MINGW NOT YET POSSIBLE") 19 | endif() 20 | 21 | include(ExternalProject) 22 | find_package(OpenSSL REQUIRED) 23 | 24 | set(CMAKE_C_STANDARD 11) 25 | set(CMAKE_CXX_STANDARD 17) 26 | set(C_STANDARD_REQUIRED ON) 27 | set(CXX_STANDARD_REQUIRED ON) 28 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 29 | 30 | add_library(project_options INTERFACE) 31 | include(cmake/Sanitizers.cmake) 32 | enable_sanitizers(project_options) 33 | 34 | add_library(project_warnings INTERFACE) 35 | include(cmake/CompilerWarnings.cmake) 36 | set_project_warnings(project_warnings) 37 | 38 | set(MGCLIENT_GIT_TAG "v1.4.1" CACHE STRING "mgclient git tag") 39 | set(MGCLIENT_LIBRARY mgclient-lib) 40 | set(MGCLIENT_INSTALL_DIR ${CMAKE_BINARY_DIR}/mgclient) 41 | set(MGCLIENT_INCLUDE_DIRS ${MGCLIENT_INSTALL_DIR}/include) 42 | if (UNIX AND NOT APPLE) 43 | set(MGCLIENT_LIBRARY_PATH ${MGCLIENT_INSTALL_DIR}/lib/libmgclient.so) 44 | elseif (WIN32) 45 | set(MGCLIENT_LIBRARY_PATH ${MGCLIENT_INSTALL_DIR}/lib/mgclient.dll) 46 | elseif (APPLE) 47 | set(MGCLIENT_LIBRARY_PATH ${MGCLIENT_INSTALL_DIR}/lib/libmgclient.dylib) 48 | endif() 49 | ExternalProject_Add(mgclient-proj 50 | PREFIX mgclient-proj 51 | GIT_REPOSITORY https://github.com/memgraph/mgclient.git 52 | GIT_TAG "${MGCLIENT_GIT_TAG}" 53 | CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${MGCLIENT_INSTALL_DIR}" 54 | "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" 55 | "-DBUILD_CPP_BINDINGS=ON" 56 | "-DCMAKE_POSITION_INDEPENDENT_CODE=ON" 57 | "-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}" 58 | BUILD_BYPRODUCTS "${MGCLIENT_LIBRARY_PATH}" 59 | INSTALL_DIR "${PROJECT_BINARY_DIR}/mgclient" 60 | ) 61 | add_library(${MGCLIENT_LIBRARY} SHARED IMPORTED) 62 | target_compile_definitions(${MGCLIENT_LIBRARY} INTERFACE mgclient_shared_EXPORTS) 63 | set_property(TARGET ${MGCLIENT_LIBRARY} PROPERTY IMPORTED_LOCATION ${MGCLIENT_LIBRARY_PATH}) 64 | if (WIN32) 65 | set_property(TARGET ${MGCLIENT_LIBRARY} PROPERTY IMPORTED_IMPLIB ${MGCLIENT_INSTALL_DIR}/lib/mgclient.lib) 66 | endif() 67 | add_dependencies(${MGCLIENT_LIBRARY} mgclient-proj) 68 | 69 | # Define the addon. 70 | include_directories(${CMAKE_JS_INC}) 71 | set(SOURCE_FILES src/addon.cpp src/client.cpp src/glue.cpp) 72 | add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) 73 | target_compile_definitions(${PROJECT_NAME} PRIVATE -Dmgclient_shared_EXPORTS) 74 | add_dependencies(${PROJECT_NAME} ${MGCLIENT_LIBRARY}) 75 | set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") 76 | target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_JS_LIB} ${MGCLIENT_LIBRARY} project_warnings project_options) 77 | if (WIN32) 78 | target_link_libraries(${PROJECT_NAME} PRIVATE Ws2_32) 79 | endif() 80 | target_include_directories(${PROJECT_NAME} PRIVATE ${MGCLIENT_INCLUDE_DIRS}) 81 | # C++ mgclient throws, which means this project has to enable exceptions. If at 82 | # any time we decide to disable exceptions with the combination of NAPI_THROW + 83 | # std::nullopt in case of an error, the code should work as is. 84 | add_definitions(-DNAPI_CPP_EXCEPTIONS) 85 | # Include N-API wrappers. 86 | execute_process(COMMAND node -p "require('node-addon-api').include" 87 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 88 | OUTPUT_VARIABLE NODE_ADDON_API_DIR) 89 | string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) 90 | string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) 91 | target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE "${NODE_ADDON_API_DIR}") 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/memgraph/nodemgclient/workflows/CI/badge.svg)](https://github.com/memgraph/nodemgclient/actions) 2 | [![status: experimental](https://github.com/GIScience/badges/raw/master/status/experimental.svg)](https://www.npmjs.com/package/@memgraph/client) 3 | 4 | # nodemgclient - Node.js Memgraph Client 5 | 6 | `nodemgclient` a Node.js binding for 7 | [mgclient](https://github.com/memgraph/mgclient) used to interact with 8 | [Memgraph](https://memgraph.com). 9 | 10 | ## Installation 11 | 12 | As usual the package could be downloaded from npm by executing: 13 | ``` 14 | npm install @memgraph/client 15 | ``` 16 | At the moment only Linux shared library is shipped inside the package. For any 17 | other operating system or incompatible library version, please proceed with 18 | building from source as explained below. 19 | 20 | Once the package is properly installed, you can run a simple example: 21 | ``` 22 | const memgraph = require('@memgraph/client'); 23 | 24 | (async () => { 25 | try { 26 | const connection = await memgraph.Connect({ 27 | host: 'localhost', 28 | port: 7687, 29 | }); 30 | await connection.ExecuteAndFetchAll("CREATE (:Graphs)-[:ARE]->(:Powerful);"); 31 | console.log(await connection.ExecuteAndFetchAll( 32 | "MATCH (n)-[r]->(m) RETURN n, r, m;")); 33 | } catch (e) { 34 | console.log(e); 35 | } 36 | })(); 37 | ``` 38 | 39 | ## Build from Source 40 | 41 | Below you can find instruction for Linux, MacOS and Windows. You'll know if the 42 | package was successfully build when there will be a file called 43 | `nodemgclient/Release/nodemgclient.node`, that's a shared library required to 44 | use the client. 45 | Once the library is in place you can pull it in your project just by running: 46 | ``` 47 | npm install 48 | ``` 49 | 50 | ### Build from Source on Linux 51 | 52 | To install `nodemgclient` from source you will need: 53 | 54 | * OpenSSL >= 1.0.2 55 | * A CMake >= 3.10 56 | * A Clang compiler supporting C11 and C++17 standard 57 | * Node.js >= 12 58 | 59 | First install the prerequisites: 60 | 61 | * On Debian/Ubuntu: 62 | 63 | ```bash 64 | sudo apt install -y npm nodejs cmake make gcc g++ clang libssl-dev 65 | ``` 66 | 67 | * On RedHat/CentOS: 68 | 69 | ```bash 70 | sudo yum install -y npm nodejs cmake3 make gcc gcc-c++ clang openssl-devel 71 | ``` 72 | 73 | Once prerequisites are in place, you can build `nodemgclient` by running: 74 | 75 | ```bash 76 | npm ci 77 | npm run build:release 78 | ``` 79 | 80 | To test ([Docker](https://docs.docker.com/engine/install) is required) run: 81 | 82 | ```bash 83 | npm run test 84 | ``` 85 | 86 | ### Build from Source on Windows 87 | 88 | #### Build on Windows using Visual Studio 89 | 90 | Since `cmake-js` is used, compiling for Windows is very similar to compiling 91 | for Linux: 92 | ```bash 93 | npm ci 94 | npm run build:release 95 | ``` 96 | 97 | If installing OpenSSL package from 98 | https://slproweb.com/products/Win32OpenSSL.html, make sure to use the full one 99 | because of the header files. 100 | 101 | NOTE: Compilation does NOT work yet under MinGW. 102 | 103 | ### Build from Source on MacOS 104 | 105 | To build on MacOS it's required to install the `openssl` package, e.g.: 106 | ``` 107 | brew install openssl 108 | ``` 109 | Once the package is in place, please set the `OPENSSL_ROOT_DIR` environment variable: 110 | ``` 111 | export OPENSSL_ROOT_DIR="$(brew --prefix openssl)" 112 | ``` 113 | Once OpenSSL is in place, please run: 114 | ``` 115 | npm ci 116 | npm run build:release 117 | ``` 118 | 119 | NOTE: For more adventurous folks, since `cmake-js` is used, it's also possible to set 120 | the OpenSSL path via the following commend: 121 | ``` 122 | npx cmake-js compile --CDOPENSSL_ROOT_DIR="$(brew --prefix openssl)" 123 | ``` 124 | 125 | ## Implementation and Interface Notes 126 | 127 | ### Temporal Types 128 | 129 | Suitable JS type to store Memgrpah temporal types don't exist. In particular, 130 | it's impossible to convert `mg_duration` and `mg_local_time` to the `Date` 131 | type. Since [the temporal 132 | specification](https://github.com/tc39/proposal-temporal) is not yet widely 133 | supported, the decision was to expose plain JS objects (dict) with the exact 134 | fields `mgclient` is providing (for more details, please take a look under 135 | `mgclient` 136 | [header](https://github.com/memgraph/mgclient/blob/master/include/mgclient.h) 137 | and [source](https://github.com/memgraph/mgclient/blob/master/src/mgclient.c) 138 | files). In addition, when possible (`mg_date` and `mg_local_date_time`), are 139 | converted into objects which have `date` property, 140 | which in fact, is the JS `Date` representation of these types. Keep in mind the 141 | loss of precision because JS `Date` time fields can only store up to 142 | milliseconds precision. However, Memgraph supports microsecond precision for 143 | the local time and therefore any use of the `date` property (JS `Date` object) 144 | can potentially cause loss of information. 145 | 146 | Module exposes `create` functions, e.g. `createMgDate`, which simplify creation 147 | of temporal object interpretable by Memgraph. For more details take a look 148 | under the API docs under [index.js](./index.js) file. 149 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | function(set_project_warnings project_name) 2 | 3 | option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" TRUE) 4 | 5 | set(CLANG_WARNINGS 6 | -Wall 7 | -Wextra # reasonable and standard 8 | -Wshadow # warn the user if a variable declaration shadows one from a 9 | # parent context 10 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a 11 | # non-virtual destructor. This helps catch hard to 12 | # track down memory errors 13 | -Wold-style-cast # warn for c-style casts 14 | -Wcast-align # warn for potential performance problem casts 15 | -Wunused # warn on anything being unused 16 | -Woverloaded-virtual # warn if you overload (not override) a virtual 17 | # function 18 | -Wpedantic # warn if non-standard C++ is used 19 | # TODO(gitbuda): Fix mgclient cpp issue and put back the conversion warnings. 20 | # -Wconversion # warn on type conversions that may lose data 21 | # -Wsign-conversion # warn on sign conversions 22 | -Wnull-dereference # warn if a null dereference is detected 23 | -Wdouble-promotion # warn if float is implicit promoted to double 24 | -Wformat=2 # warn on security issues around functions that format output 25 | # (ie printf) 26 | ) 27 | 28 | set(GCC_WARNINGS 29 | ${CLANG_WARNINGS} 30 | -Wmisleading-indentation # warn if identation implies blocks where blocks 31 | # do not exist 32 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 33 | -Wduplicated-branches # warn if if / else branches have duplicated code 34 | -Wlogical-op # warn about logical operations being used where bitwise were 35 | # probably wanted 36 | -Wuseless-cast # warn if you perform a cast to the same type 37 | ) 38 | 39 | set(MSVC_WARNINGS "") 40 | 41 | if (WARNINGS_AS_ERRORS) 42 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 43 | endif() 44 | 45 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 46 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 47 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 48 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 49 | else() 50 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 51 | endif() 52 | 53 | target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) 54 | 55 | endfunction() 56 | -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | # TODO(gitbuda): Replace this with built-in function once CMake 3.12+ is used. 2 | function(JOIN VALUES GLUE OUTPUT) 3 | string (REGEX REPLACE "([^\\]|^);" "\\1${GLUE}" _TMP_STR "${VALUES}") 4 | string (REGEX REPLACE "[\\](.)" "\\1" _TMP_STR "${_TMP_STR}") #fixes escaping 5 | set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE) 6 | endfunction() 7 | 8 | function(enable_sanitizers project_name) 9 | 10 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 11 | option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" FALSE) 12 | 13 | if(ENABLE_COVERAGE) 14 | target_compile_options(project_options INTERFACE -fprofile-instr-generate -fcoverage-mapping -O0 -g) 15 | target_link_libraries(project_options INTERFACE -fprofile-instr-generate -fcoverage-mapping) 16 | endif() 17 | 18 | set(SANITIZERS "") 19 | 20 | option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" FALSE) 21 | if(ENABLE_SANITIZER_ADDRESS) 22 | list(APPEND SANITIZERS "address") 23 | endif() 24 | 25 | option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" FALSE) 26 | if(ENABLE_SANITIZER_MEMORY) 27 | list(APPEND SANITIZERS "memory") 28 | endif() 29 | 30 | option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR 31 | "Enable undefined behavior sanitizer" FALSE) 32 | if(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) 33 | list(APPEND SANITIZERS "undefined") 34 | endif() 35 | 36 | option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" FALSE) 37 | if(ENABLE_SANITIZER_THREAD) 38 | list(APPEND SANITIZERS "thread") 39 | endif() 40 | 41 | JOIN("${SANITIZERS}" "," LIST_OF_SANITIZERS) 42 | 43 | endif() 44 | 45 | if(LIST_OF_SANITIZERS) 46 | if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "") 47 | target_compile_options(${project_name} 48 | INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 49 | target_link_libraries(${project_name} 50 | INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 51 | endif() 52 | endif() 53 | 54 | endfunction() 55 | -------------------------------------------------------------------------------- /example/async_connection.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // TODO(gitbuda): Make tests out of this async example. 16 | 17 | const memgraph = require('..'); 18 | 19 | (async () => { 20 | try { 21 | const client = memgraph.Client(); 22 | client 23 | .Connect({ host: 'localhost' }) 24 | .then(async (connection) => { 25 | console.log('Connected!'); 26 | connection 27 | .Execute('MATCH (n) WHERE n.name = $name RETURN n, n.name;', { 28 | name: 'TEST', 29 | }) 30 | .then(async (_) => { 31 | try { 32 | try { 33 | await connection.Execute('MATCH (n) RETURN n;'); 34 | } catch (e) { 35 | console.log('The second MATCH should fail: ' + e); 36 | } 37 | let data = await connection.FetchAll(); 38 | console.log(data); 39 | data = await connection.FetchOne(); 40 | console.log(data); 41 | data = await connection.FetchOne(); 42 | console.log(data); 43 | } catch (e) { 44 | console.log(e); 45 | } 46 | }) 47 | .catch((e) => { 48 | console.log(e); 49 | }); 50 | }) 51 | .catch((e) => { 52 | console.log(e); 53 | }); 54 | console.log('Connecting...'); 55 | await new Promise((resolve) => setTimeout(resolve, 1000)); 56 | 57 | const txConn = await memgraph 58 | .Client() 59 | .Connect({ host: 'localhost', port: 7687 }); 60 | await txConn.Begin(); 61 | await txConn.Execute('CREATE (), ();'); 62 | await txConn.FetchAll(); 63 | await txConn.Execute('CREATE (), ();'); 64 | const txConnData = await txConn.FetchAll(); 65 | console.log(txConnData); 66 | await txConn.Commit(); 67 | } catch (e) { 68 | console.log(e); 69 | } 70 | })(); 71 | -------------------------------------------------------------------------------- /example/create_node_edge.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const memgraph = require('..'); 16 | const query = require('../test/queries'); 17 | 18 | (async () => { 19 | try { 20 | const connection = await memgraph.Connect({ 21 | host: 'localhost', 22 | port: 7687, 23 | }); 24 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 25 | await connection.ExecuteAndFetchAll(query.CREATE_RICH_NODE); 26 | await connection.ExecuteAndFetchAll(query.CREATE_RICH_EDGE); 27 | 28 | console.log(await connection.ExecuteAndFetchAll(query.NODES)); 29 | console.log(await connection.ExecuteAndFetchAll(query.EDGES)); 30 | } catch (e) { 31 | console.log(e); 32 | } 33 | })(); 34 | -------------------------------------------------------------------------------- /example/create_path.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const memgraph = require('..'); 16 | const query = require('../test/queries'); 17 | 18 | (async () => { 19 | try { 20 | const connection = await memgraph.Connect({ 21 | host: 'localhost', 22 | port: 7687, 23 | }); 24 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 25 | await connection.ExecuteAndFetchAll(query.CREATE_PATH); 26 | const paths = await connection.ExecuteAndFetchAll(query.MATCH_PATHS); 27 | console.log(paths); 28 | } catch (e) { 29 | console.log(e); 30 | } 31 | })(); 32 | -------------------------------------------------------------------------------- /example/create_triangle.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const memgraph = require('..'); 16 | const query = require('../test/queries'); 17 | 18 | (async () => { 19 | try { 20 | const connection = await memgraph.Connect({ 21 | host: 'localhost', 22 | port: 7687, 23 | }); 24 | 25 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 26 | await connection.ExecuteAndFetchAll(query.CREATE_TRIANGLE); 27 | 28 | console.log(await connection.ExecuteAndFetchAll(query.NODE_EDGE_IDS)); 29 | } catch (e) { 30 | console.log(e); 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /example/event_emitter.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // TODO(gitbuda): Port EventEmitter code to async. 16 | 17 | const memgraph = require('..'); 18 | const query = require('../test/queries'); 19 | const EventEmitter = require('events').EventEmitter; 20 | 21 | const emitter = new EventEmitter() 22 | .on('start', () => { 23 | console.log('### START ###'); 24 | }) 25 | .on('record', (record) => { 26 | console.log(record.Values()); 27 | }) 28 | .on('end', (summary) => { 29 | console.log(summary); 30 | console.log('### END ###'); 31 | }); 32 | 33 | (async () => { 34 | try { 35 | // TODO(gitbuda): Design the correct interface. Connect should also return 36 | // a promise. 37 | const connection = memgraph.Connect({ host: 'localhost', port: 7687 }); 38 | 39 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 40 | 41 | const result = await connection.ExecuteLazy( 42 | `UNWIND [0, 1] AS item RETURN "value_x2" AS x, "value_y2" AS y;`, 43 | ); 44 | console.log(result); 45 | // TODO(gitbuda): Figure out how to hide the bind call from a user. 46 | result.Stream(emitter.emit.bind(emitter)); 47 | } catch (e) { 48 | console.log(e); 49 | } 50 | })(); 51 | -------------------------------------------------------------------------------- /example/promise.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const memgraph = require('..'); 16 | const query = require('../test/queries'); 17 | 18 | (async () => { 19 | try { 20 | const connection = await memgraph.Connect({ 21 | host: 'localhost', 22 | port: 7687, 23 | }); 24 | 25 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 26 | 27 | const result = await connection.ExecuteAndFetchAll( 28 | `RETURN "value_x2" AS x, "value_y2" AS y;`, 29 | ); 30 | console.log(result); 31 | 32 | await connection.ExecuteAndFetchAll('FAIL'); 33 | } catch (e) { 34 | console.log(e); 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /example/run_query_params.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const memgraph = require('..'); 16 | 17 | (async () => { 18 | try { 19 | const connection = await memgraph.Connect({ 20 | host: 'localhost', 21 | port: 7687, 22 | }); 23 | 24 | await connection.ExecuteAndFetchAll('CREATE (n:Node {name: $name});', { 25 | name: 'John Swan', 26 | }); 27 | console.log(await connection.ExecuteAndFetchAll('MATCH (n) RETURN n;')); 28 | } catch (e) { 29 | console.log(e); 30 | } 31 | })(); 32 | -------------------------------------------------------------------------------- /example/transaction_control.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const memgraph = require('..'); 16 | const query = require('../test/queries'); 17 | 18 | (async () => { 19 | try { 20 | const connection = await memgraph.Connect({ 21 | host: 'localhost', 22 | port: 7687, 23 | }); 24 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 25 | 26 | await connection.Begin(); 27 | await connection.Execute(`CREATE (n {name: "One"});`); 28 | await connection.Execute(`CREATE (n {name: "Two"});`); 29 | await connection.Commit(); 30 | 31 | const result = await connection.ExecuteAndFetchAll(`MATCH (n) RETURN n;`); 32 | for (const record of result) { 33 | console.log(record); 34 | } 35 | } catch (e) { 36 | console.log(e); 37 | } 38 | })(); 39 | -------------------------------------------------------------------------------- /example/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | -------------------------------------------------------------------------------- /example/typescript/default.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import Memgraph from '../..'; 16 | 17 | (async () => { 18 | try { 19 | const connection:any = await Memgraph.Connect({ host: 'localhost', port: 7687 }); 20 | console.log(await connection.ExecuteAndFetchAll("MATCH (n) RETURN n LIMIT 1;")); 21 | } catch (e) { 22 | console.log(e); 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /example/typescript/from.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {Memgraph, Connection} from '../..'; 16 | 17 | (async () => { 18 | try { 19 | const connection:Connection = await Memgraph.Connect({ host: 'localhost', port: 7687 }); 20 | console.log(await connection.ExecuteAndFetchAll("MATCH (n) RETURN n LIMIT 1;")); 21 | } catch (e) { 22 | console.log(e); 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /example/typescript/require.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import Memgraph = require("../../index"); 16 | 17 | (async () => { 18 | try { 19 | const connection:Memgraph.Connection = await Memgraph.Connect({ host: 'localhost', port: 7687 }); 20 | console.log(await connection.ExecuteAndFetchAll("MATCH (n) RETURN n LIMIT 1;")); 21 | } catch (e) { 22 | console.log(e); 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /example/typescript/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -Eeuo pipefail 4 | script_dir="$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd)" 5 | 6 | cd "$script_dir" 7 | 8 | for file in *.ts; do 9 | echo "Testing $file..." 10 | npx tsc "$file" && node "$(basename $file .ts).js" 11 | done 12 | -------------------------------------------------------------------------------- /example/typescript/star.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as Memgraph from '../..'; 16 | 17 | (async () => { 18 | try { 19 | const connection:Memgraph.Connection = await Memgraph.Connect({ host: 'localhost', port: 7687 }); 20 | console.log(await connection.ExecuteAndFetchAll("MATCH (n) RETURN n LIMIT 1;")); 21 | } catch (e) { 22 | console.log(e); 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /generate_ts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Generates TypeScript declarations. 4 | 5 | set -Eeuo pipefail 6 | script_dir="$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd)" 7 | 8 | cd "$script_dir" 9 | npx tsc index.js --declaration --allowJs --checkJs --resolveJsonModule --emitDeclarationOnly 10 | types=$(cat index.d.ts) 11 | cat << EOF > index.d.ts 12 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 13 | // 14 | // Licensed under the Apache License, Version 2.0 (the "License"); 15 | // you may not use this file except in compliance with the License. 16 | // You may obtain a copy of the License at 17 | // 18 | // http://www.apache.org/licenses/LICENSE-2.0 19 | // 20 | // Unless required by applicable law or agreed to in writing, software 21 | // distributed under the License is distributed on an "AS IS" BASIS, 22 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | // See the License for the specific language governing permissions and 24 | // limitations under the License. 25 | 26 | EOF 27 | echo "$types" >> index.d.ts 28 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export class Connection { 16 | constructor(client: any); 17 | client: any; 18 | Execute(query: any, params?: {}): Promise; 19 | FetchAll(): Promise; 20 | DiscardAll(): Promise; 21 | Begin(): Promise; 22 | Commit(): Promise; 23 | Rollback(): Promise; 24 | ExecuteAndFetchAll(query: any, params?: {}): Promise; 25 | } 26 | export namespace Memgraph { 27 | export function Client_1(): any; 28 | export { Client_1 as Client }; 29 | export function Connect_1(params: any): Promise; 30 | export { Connect_1 as Connect }; 31 | } 32 | /** 33 | * Create Memgraph compatible date object. 34 | * @param {number} days - The number of days since 1970-01-01 (Unix epoch). 35 | */ 36 | export function createMgDate(days: number): { 37 | objectType: string; 38 | days: number; 39 | }; 40 | /** 41 | * Create Memgraph compatible local time object. 42 | * @param {number} nanoseconds - The number of nanoseconds since midnight. 43 | */ 44 | export function createMgLocalTime(nanoseconds: number): { 45 | objectType: string; 46 | nanoseconds: number; 47 | }; 48 | /** 49 | * Create Memgraph compatible local date time object. 50 | * @param {number} seconds - The number of seconds since 1970-01-01T00:00:00 51 | * (Unix epoch). 52 | * @param {number} nanoseconds - The number of nanoseconds since the last 53 | * second. 54 | */ 55 | export function createMgLocalDateTime(seconds: number, nanoseconds: number): { 56 | objectType: string; 57 | seconds: number; 58 | nanoseconds: number; 59 | }; 60 | /** 61 | * Create Memgraph compatible duration object. 62 | * NOTE: Semantically Memgraph duration is a sum of all 63 | * components (days, seconds, nanoseconds). 64 | * @param {number} days - The number of days. 65 | * @param {number} seconds - The number of seconds. 66 | * @param {number} nanoseconds - The number of nanoseconds. 67 | */ 68 | export function createMgDuration(days: number, seconds: number, nanoseconds: number): { 69 | objectType: string; 70 | days: number; 71 | seconds: number; 72 | nanoseconds: number; 73 | }; 74 | import Client = Memgraph.Client; 75 | import Connect = Memgraph.Connect; 76 | export { Memgraph as default, Client, Connect }; 77 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const Bindings = require('bindings')('nodemgclient'); 16 | const pjson = require('./package.json'); 17 | 18 | // The purpose of create functions is to simplify creation of Memgraph specific 19 | // data types, e.g. temporal types. 20 | 21 | /** 22 | * Create Memgraph compatible date object. 23 | * @param {number} days - The number of days since 1970-01-01 (Unix epoch). 24 | */ 25 | function createMgDate(days) { 26 | return { 27 | "objectType": "date", 28 | "days": days, 29 | } 30 | } 31 | 32 | /** 33 | * Create Memgraph compatible local time object. 34 | * @param {number} nanoseconds - The number of nanoseconds since midnight. 35 | */ 36 | function createMgLocalTime(nanoseconds) { 37 | return { 38 | "objectType": "local_time", 39 | "nanoseconds": nanoseconds, 40 | } 41 | } 42 | 43 | /** 44 | * Create Memgraph compatible local date time object. 45 | * @param {number} seconds - The number of seconds since 1970-01-01T00:00:00 46 | * (Unix epoch). 47 | * @param {number} nanoseconds - The number of nanoseconds since the last 48 | * second. 49 | */ 50 | function createMgLocalDateTime(seconds, nanoseconds) { 51 | return { 52 | "objectType": "local_date_time", 53 | "seconds": seconds, 54 | "nanoseconds": nanoseconds, 55 | } 56 | } 57 | 58 | /** 59 | * Create Memgraph compatible duration object. 60 | * NOTE: Semantically Memgraph duration is a sum of all 61 | * components (days, seconds, nanoseconds). 62 | * @param {number} days - The number of days. 63 | * @param {number} seconds - The number of seconds. 64 | * @param {number} nanoseconds - The number of nanoseconds. 65 | */ 66 | function createMgDuration(days, seconds, nanoseconds) { 67 | return { 68 | "objectType": "duration", 69 | "days": days, 70 | "seconds": seconds, 71 | "nanoseconds": nanoseconds, 72 | } 73 | } 74 | 75 | // This class exists becuase of additional logic that is easier to implement in 76 | // JavaScript + to extend the implementation with easy to use primitives. 77 | class Connection { 78 | constructor(client) { 79 | this.client = client; 80 | } 81 | 82 | async Execute(query, params={}) { 83 | return await this.client.Execute(query, params); 84 | } 85 | 86 | async FetchAll() { 87 | return await this.client.FetchAll(); 88 | } 89 | 90 | async DiscardAll() { 91 | return await this.client.DiscardAll(); 92 | } 93 | 94 | async Begin() { 95 | return await this.client.Begin(); 96 | } 97 | 98 | async Commit() { 99 | return await this.client.Commit(); 100 | } 101 | 102 | async Rollback() { 103 | return await this.client.Rollback(); 104 | } 105 | 106 | async ExecuteAndFetchAll(query, params={}) { 107 | await this.client.Execute(query, params); 108 | return await this.client.FetchAll(); 109 | } 110 | } 111 | 112 | const Memgraph = { 113 | Client: () => { 114 | return new Bindings.Client("nodemgclient/" + pjson.version); 115 | }, 116 | Connect: async (params) => { 117 | let client = new Bindings.Client("nodemgclient/" + pjson.version); 118 | // TODO(gitbuda): If the second client is not passed, execution blocks, check why. 119 | client = await client.Connect(params); 120 | return new Connection(client); 121 | } 122 | } 123 | 124 | module.exports = { 125 | Connection, 126 | default: Memgraph, 127 | Client: Memgraph.Client, 128 | Connect: Memgraph.Connect, 129 | Memgraph: Memgraph, 130 | createMgDate: createMgDate, 131 | createMgLocalTime: createMgLocalTime, 132 | createMgLocalDateTime: createMgLocalDateTime, 133 | createMgDuration: createMgDuration, 134 | } 135 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@memgraph/client", 3 | "version": "0.1.3", 4 | "engines": { 5 | "node": ">=12.0.0" 6 | }, 7 | "description": "Node.js mgclient Bindings", 8 | "keywords": [ 9 | "memgraph", 10 | "mgclient" 11 | ], 12 | "homepage": "https://github.com/memgraph/nodemgclient", 13 | "license": "Apache-2.0", 14 | "private": false, 15 | "author": "Marko Budiselic ", 16 | "contributors": [], 17 | "main": "index.js", 18 | "types": "index.d.ts", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/memgraph/nodemgclient.git" 22 | }, 23 | "scripts": { 24 | "clean:build": "cd build && rm -rf ./* && cd ..", 25 | "build:release": "npx cmake-js configure --debug=false && npx cmake-js compile", 26 | "build:debug": "npx cmake-js configure --debug=true && npx cmake-js compile", 27 | "lint": "npx eslint -c .eslintrc.js './{src,test,lib,example}/**/*.js'", 28 | "lint:fix": "npx eslint -c .eslintrc.js --fix './{src,test,lib,example}/**/*.js'", 29 | "test": "npx jest", 30 | "test:coverage": "npx jest --runInBand --logHeapUsage --collectCoverage --coverageDirectory=coverage/js-coverage" 31 | }, 32 | "dependencies": { 33 | "bindings": "~1.2.1" 34 | }, 35 | "devDependencies": { 36 | "cmake-js": "^7.0.0", 37 | "dockerode": "^3.3.0", 38 | "eslint": "^7.32.0", 39 | "eslint-config-google": "^0.14.0", 40 | "eslint-config-prettier": "^8.3.0", 41 | "eslint-plugin-jest": "^24.4.0", 42 | "eslint-plugin-node": "^11.1.0", 43 | "eslint-plugin-prettier": "^3.4.0", 44 | "get-port": "^5.1.1", 45 | "jest": "^29.4.3", 46 | "node-addon-api": "^4.0.0", 47 | "node-gyp": "^8.4.0", 48 | "prettier": "^2.3.2", 49 | "typescript": "^4.4.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /powershell.ps1: -------------------------------------------------------------------------------- 1 | rm -r build 2 | rm -r node_modules -------------------------------------------------------------------------------- /src/addon.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "client.hpp" 18 | 19 | Napi::Object InitAll(Napi::Env env, Napi::Object exports) { 20 | return nodemg::Client::Init(env, exports); 21 | } 22 | 23 | NODE_API_MODULE(addon, InitAll) 24 | -------------------------------------------------------------------------------- /src/client.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "client.hpp" 16 | 17 | #include 18 | #include 19 | 20 | #include "glue.hpp" 21 | #include "mgclient.hpp" 22 | #include "util.hpp" 23 | 24 | namespace nodemg { 25 | 26 | static const std::string CFG_HOST = "host"; 27 | static const std::string CFG_PORT = "port"; 28 | static const std::string CFG_USERNAME = "username"; 29 | static const std::string CFG_PASSWORD = "password"; 30 | static const std::string CFG_CLIENT_NAME = "client_name"; 31 | static const std::string CFG_USE_SSL = "use_ssl"; 32 | 33 | Napi::FunctionReference Client::constructor; 34 | 35 | Napi::Object Client::Init(Napi::Env env, Napi::Object exports) { 36 | Napi::HandleScope scope(env); 37 | 38 | Napi::Function func = 39 | DefineClass(env, "Client", 40 | { 41 | InstanceMethod("Connect", &Client::Connect), 42 | InstanceMethod("Execute", &Client::Execute), 43 | InstanceMethod("FetchAll", &Client::FetchAll), 44 | InstanceMethod("DiscardAll", &Client::DiscardAll), 45 | InstanceMethod("FetchOne", &Client::FetchOne), 46 | InstanceMethod("Begin", &Client::Begin), 47 | InstanceMethod("Commit", &Client::Commit), 48 | InstanceMethod("Rollback", &Client::Rollback), 49 | }); 50 | 51 | constructor = Napi::Persistent(func); 52 | constructor.SuppressDestruct(); 53 | 54 | exports.Set("Client", func); 55 | return exports; 56 | } 57 | 58 | Napi::Object Client::NewInstance(const Napi::CallbackInfo &info) { 59 | Napi::EscapableHandleScope scope(info.Env()); 60 | Napi::Object obj = constructor.New({info[0]}); 61 | return scope.Escape(napi_value(obj)).ToObject(); 62 | } 63 | 64 | std::optional Client::PrepareConnect( 65 | const Napi::CallbackInfo &info) { 66 | Napi::Env env = info.Env(); 67 | 68 | mg::Client::Params mg_params; 69 | mg_params.user_agent = name_; 70 | 71 | if (info.Length() < 1) { 72 | return mg_params; 73 | } 74 | 75 | static const std::string NODEMG_MSG_WRONG_CONNECT_ARG = 76 | "Wrong connect argument. An object containing { host, port, username, " 77 | "password, client_name, use_ssl } is required. All arguments are " 78 | "optional."; 79 | if (!info[0].IsObject()) { 80 | NODEMG_THROW(NODEMG_MSG_WRONG_CONNECT_ARG); 81 | return std::nullopt; 82 | } 83 | 84 | Napi::Object user_params = info[0].As(); 85 | // Used to report an error if user misspelled any argument. 86 | uint32_t counter = 0; 87 | 88 | if (user_params.Has(CFG_HOST)) { 89 | counter++; 90 | auto napi_host = user_params.Get(CFG_HOST); 91 | if (!napi_host.IsString()) { 92 | NODEMG_THROW("`host` connect argument has to be string."); 93 | return std::nullopt; 94 | } 95 | mg_params.host = napi_host.ToString().Utf8Value(); 96 | } 97 | 98 | if (user_params.Has(CFG_PORT)) { 99 | counter++; 100 | auto napi_port = user_params.Get(CFG_PORT); 101 | if (!napi_port.IsNumber()) { 102 | NODEMG_THROW("`port` connect argument has to be number."); 103 | return std::nullopt; 104 | } 105 | auto port = napi_port.ToNumber().Uint32Value(); 106 | if (port > std::numeric_limits::max()) { 107 | NODEMG_THROW( 108 | "`port` connect argument out of range. Port has to be a number " 109 | "between 0 and 65535."); 110 | return std::nullopt; 111 | } 112 | mg_params.port = static_cast(port); 113 | } 114 | 115 | if (user_params.Has(CFG_USERNAME)) { 116 | counter++; 117 | auto napi_username = user_params.Get(CFG_USERNAME); 118 | if (!napi_username.IsString()) { 119 | NODEMG_THROW("`username` connect argument has to be string."); 120 | return std::nullopt; 121 | } 122 | mg_params.username = napi_username.ToString().Utf8Value(); 123 | } 124 | 125 | if (user_params.Has(CFG_PASSWORD)) { 126 | counter++; 127 | auto napi_password = user_params.Get(CFG_PASSWORD); 128 | if (!napi_password.IsString()) { 129 | NODEMG_THROW("`password` connect argument has to be string."); 130 | return std::nullopt; 131 | } 132 | mg_params.username = napi_password.ToString().Utf8Value(); 133 | } 134 | 135 | if (user_params.Has(CFG_CLIENT_NAME)) { 136 | counter++; 137 | auto napi_client_name = user_params.Get(CFG_CLIENT_NAME); 138 | if (!napi_client_name.IsString()) { 139 | NODEMG_THROW("`client_name` connect argument has to be string."); 140 | } 141 | mg_params.user_agent = napi_client_name.ToString().Utf8Value(); 142 | } 143 | 144 | if (user_params.Has(CFG_USE_SSL)) { 145 | counter++; 146 | auto napi_use_ssl = user_params.Get("use_ssl"); 147 | if (!napi_use_ssl.IsBoolean()) { 148 | NODEMG_THROW("`use_ssl` connect argument has to be boolean."); 149 | return std::nullopt; 150 | } 151 | if (napi_use_ssl.ToBoolean()) { 152 | mg_params.use_ssl = MG_SSLMODE_REQUIRE; 153 | } else { 154 | mg_params.use_ssl = MG_SSLMODE_DISABLE; 155 | } 156 | } 157 | 158 | if (user_params.GetPropertyNames().Length() != counter) { 159 | NODEMG_THROW(NODEMG_MSG_WRONG_CONNECT_ARG); 160 | return std::nullopt; 161 | } 162 | 163 | return mg_params; 164 | } 165 | 166 | std::optional> Client::PrepareQuery( 167 | const Napi::CallbackInfo &info) { 168 | Napi::Env env = info.Env(); 169 | 170 | std::string query; 171 | mg_map *query_params = NULL; 172 | 173 | if (info.Length() == 1 || info.Length() == 2) { 174 | auto maybe_query = info[0]; 175 | if (!maybe_query.IsString()) { 176 | NODEMG_THROW("The first execute argument has to be string."); 177 | return std::nullopt; 178 | } 179 | query = maybe_query.As().Utf8Value(); 180 | } 181 | 182 | if (info.Length() == 2) { 183 | auto maybe_params = info[1]; 184 | if (!maybe_params.IsObject()) { 185 | NODEMG_THROW( 186 | "The second execute argument has to be an object containing query " 187 | "parameters."); 188 | return std::nullopt; 189 | } 190 | auto params = maybe_params.As(); 191 | auto maybe_mg_params = NapiObjectToMgMap(env, params); 192 | if (!maybe_mg_params) { 193 | NODEMG_THROW("Unable to create query parameters object."); 194 | return std::nullopt; 195 | } 196 | query_params = *maybe_mg_params; 197 | } 198 | 199 | return std::make_pair(query, mg::ConstMap(query_params)); 200 | } 201 | 202 | Client::Client(const Napi::CallbackInfo &info) 203 | : Napi::ObjectWrap(info), client_(nullptr), name_("nodemgclient") { 204 | if (info.Length() == 1) { 205 | name_ = info[0].As().Utf8Value(); 206 | } 207 | } 208 | 209 | Client::~Client() {} 210 | 211 | void Client::SetMgClient(std::unique_ptr client) { 212 | this->client_ = std::move(client); 213 | } 214 | 215 | class AsyncConnectWorker final : public Napi::AsyncWorker { 216 | public: 217 | AsyncConnectWorker(const Napi::Promise::Deferred &deferred, 218 | mg::Client::Params params) 219 | : AsyncWorker(Napi::Function::New(deferred.Promise().Env(), 220 | [](const Napi::CallbackInfo &) {})), 221 | deferred_(deferred), 222 | params_(std::move(params)) {} 223 | ~AsyncConnectWorker() = default; 224 | 225 | void Execute() { 226 | static const std::string NODEMG_MSG_CONNECT_FAILED = 227 | "Connect failed. Ensure Memgraph is running and Client is properly " 228 | "configured."; 229 | try { 230 | mg::Client::Init(); 231 | client_ = mg::Client::Connect(params_); 232 | if (!client_) { 233 | SetError(NODEMG_MSG_CONNECT_FAILED); 234 | return; 235 | } 236 | } catch (const std::exception &error) { 237 | SetError(NODEMG_MSG_CONNECT_FAILED + " " + error.what()); 238 | return; 239 | } 240 | } 241 | 242 | void OnOK() { 243 | Napi::Object obj = Client::constructor.New({}); 244 | Client *async_connection = Client::Unwrap(obj); 245 | async_connection->SetMgClient(std::move(client_)); 246 | this->deferred_.Resolve(obj); 247 | } 248 | 249 | void OnError(const Napi::Error &e) { 250 | this->deferred_.Reject(Napi::Error::New(Env(), e.Message()).Value()); 251 | } 252 | 253 | private: 254 | Napi::Promise::Deferred deferred_; 255 | mg::Client::Params params_; 256 | std::unique_ptr client_; 257 | }; 258 | 259 | Napi::Value Client::Connect(const Napi::CallbackInfo &info) { 260 | auto env = info.Env(); 261 | 262 | if (client_) { 263 | NODEMG_THROW("Already connected."); 264 | return env.Undefined(); 265 | } 266 | 267 | auto params = PrepareConnect(info); 268 | if (!params) { 269 | return env.Undefined(); 270 | } 271 | 272 | Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env()); 273 | AsyncConnectWorker *wk = new AsyncConnectWorker(deferred, std::move(*params)); 274 | wk->Queue(); 275 | return deferred.Promise(); 276 | } 277 | 278 | class AsyncExecuteWorker final : public Napi::AsyncWorker { 279 | public: 280 | AsyncExecuteWorker(const Napi::Promise::Deferred &deferred, 281 | mg::Client *client, std::string query, mg::ConstMap params) 282 | : AsyncWorker(Napi::Function::New(deferred.Promise().Env(), 283 | [](const Napi::CallbackInfo &) {})), 284 | deferred_(deferred), 285 | client_(client), 286 | query_(std::move(query)), 287 | params_(std::move(params)) {} 288 | ~AsyncExecuteWorker() = default; 289 | 290 | void Execute() { 291 | static const std::string NODEMG_MSG_EXECUTE_FAIL = 292 | "Failed to execute a query."; 293 | try { 294 | auto status = client_->Execute(query_, params_); 295 | if (!status) { 296 | SetError(NODEMG_MSG_EXECUTE_FAIL); 297 | return; 298 | } 299 | } catch (const std::exception &error) { 300 | SetError(NODEMG_MSG_EXECUTE_FAIL + " " + error.what()); 301 | return; 302 | } 303 | } 304 | 305 | void OnOK() { 306 | auto env = deferred_.Env(); 307 | this->deferred_.Resolve(env.Null()); 308 | } 309 | 310 | void OnError(const Napi::Error &e) { 311 | this->deferred_.Reject(Napi::Error::New(Env(), e.Message()).Value()); 312 | } 313 | 314 | private: 315 | Napi::Promise::Deferred deferred_; 316 | mg::Client *client_; 317 | std::string query_; 318 | mg::ConstMap params_; 319 | }; 320 | 321 | Napi::Value Client::Execute(const Napi::CallbackInfo &info) { 322 | auto env = info.Env(); 323 | 324 | auto query_params = PrepareQuery(info); 325 | if (!query_params) { 326 | return info.Env().Undefined(); 327 | } 328 | 329 | Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env); 330 | auto wk = new AsyncExecuteWorker(deferred, client_.get(), 331 | std::move(query_params->first), 332 | std::move(query_params->second)); 333 | wk->Queue(); 334 | return deferred.Promise(); 335 | } 336 | 337 | class AsyncFetchAllWorker final : public Napi::AsyncWorker { 338 | public: 339 | AsyncFetchAllWorker(const Napi::Promise::Deferred &deferred, 340 | mg::Client *client) 341 | : AsyncWorker(Napi::Function::New(deferred.Promise().Env(), 342 | [](const Napi::CallbackInfo &) {})), 343 | deferred_(deferred), 344 | client_(client) {} 345 | ~AsyncFetchAllWorker() = default; 346 | 347 | void Execute() { 348 | static const std::string NODEMG_MSG_FETCH_ONE_FAIL = 349 | "Failed to fetch one record."; 350 | try { 351 | data_ = client_->FetchAll(); 352 | } catch (const std::exception &error) { 353 | SetError(NODEMG_MSG_FETCH_ONE_FAIL + error.what()); 354 | return; 355 | } 356 | } 357 | 358 | void OnOK() { 359 | auto env = deferred_.Env(); 360 | 361 | if (!data_) { 362 | this->deferred_.Resolve(env.Null()); 363 | return; 364 | } 365 | 366 | auto output_array_value = Napi::Array::New(env, data_->size()); 367 | for (uint32_t outer_index = 0; outer_index < data_->size(); ++outer_index) { 368 | auto inner_array = (*data_)[outer_index]; 369 | auto inner_array_size = inner_array.size(); 370 | auto inner_array_value = Napi::Array::New(env, inner_array_size); 371 | for (uint32_t inner_index = 0; inner_index < inner_array_size; 372 | ++inner_index) { 373 | auto value = MgValueToNapiValue(env, inner_array[inner_index].ptr()); 374 | if (!value) { 375 | SetError("Failed to convert fetched data."); 376 | return; 377 | } 378 | inner_array_value[inner_index] = *value; 379 | } 380 | output_array_value[outer_index] = inner_array_value; 381 | } 382 | 383 | this->deferred_.Resolve(output_array_value); 384 | } 385 | 386 | void OnError(const Napi::Error &e) { 387 | this->deferred_.Reject(Napi::Error::New(Env(), e.Message()).Value()); 388 | } 389 | 390 | private: 391 | Napi::Promise::Deferred deferred_; 392 | mg::Client *client_; 393 | decltype(client_->FetchAll()) data_; 394 | }; 395 | 396 | Napi::Value Client::FetchAll(const Napi::CallbackInfo &info) { 397 | Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env()); 398 | auto wk = new AsyncFetchAllWorker(deferred, client_.get()); 399 | wk->Queue(); 400 | return deferred.Promise(); 401 | } 402 | 403 | class AsyncDiscardAllWorker final : public Napi::AsyncWorker { 404 | public: 405 | AsyncDiscardAllWorker(const Napi::Promise::Deferred &deferred, 406 | mg::Client *client) 407 | : AsyncWorker(Napi::Function::New(deferred.Promise().Env(), 408 | [](const Napi::CallbackInfo &) {})), 409 | deferred_(deferred), 410 | client_(client) {} 411 | ~AsyncDiscardAllWorker() = default; 412 | 413 | void Execute() { 414 | static const std::string NODEMG_MSG_DISCARD_ALL_FAIL = 415 | "Failed to discard all data."; 416 | try { 417 | client_->DiscardAll(); 418 | } catch (const std::exception &error) { 419 | SetError(NODEMG_MSG_DISCARD_ALL_FAIL + error.what()); 420 | return; 421 | } 422 | } 423 | 424 | void OnOK() { 425 | auto env = deferred_.Env(); 426 | this->deferred_.Resolve(env.Null()); 427 | } 428 | 429 | void OnError(const Napi::Error &e) { 430 | this->deferred_.Reject(Napi::Error::New(Env(), e.Message()).Value()); 431 | } 432 | 433 | private: 434 | Napi::Promise::Deferred deferred_; 435 | mg::Client *client_; 436 | }; 437 | 438 | Napi::Value Client::DiscardAll(const Napi::CallbackInfo &info) { 439 | Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env()); 440 | auto wk = new AsyncDiscardAllWorker(deferred, client_.get()); 441 | wk->Queue(); 442 | return deferred.Promise(); 443 | } 444 | 445 | class AsyncFetchOneWorker final : public Napi::AsyncWorker { 446 | public: 447 | AsyncFetchOneWorker(const Napi::Promise::Deferred &deferred, 448 | mg::Client *client) 449 | : AsyncWorker(Napi::Function::New(deferred.Promise().Env(), 450 | [](const Napi::CallbackInfo &) {})), 451 | deferred_(deferred), 452 | client_(client) {} 453 | ~AsyncFetchOneWorker() = default; 454 | 455 | void Execute() { 456 | static const std::string NODEMG_MSG_FETCH_ONE_FAIL = 457 | "Failed to fetch one record."; 458 | try { 459 | data_ = client_->FetchOne(); 460 | } catch (const std::exception &error) { 461 | SetError(NODEMG_MSG_FETCH_ONE_FAIL + error.what()); 462 | return; 463 | } 464 | } 465 | 466 | void OnOK() { 467 | auto env = deferred_.Env(); 468 | 469 | if (!data_) { 470 | this->deferred_.Resolve(env.Null()); 471 | return; 472 | } 473 | 474 | auto array_value = Napi::Array::New(env, data_->size()); 475 | for (uint32_t index = 0; index < data_->size(); ++index) { 476 | auto value = MgValueToNapiValue(env, (*data_)[index].ptr()); 477 | if (!value) { 478 | SetError("Failed to convert fetched data."); 479 | return; 480 | } 481 | array_value[index] = *value; 482 | } 483 | 484 | this->deferred_.Resolve(array_value); 485 | } 486 | 487 | void OnError(const Napi::Error &e) { 488 | this->deferred_.Reject(Napi::Error::New(Env(), e.Message()).Value()); 489 | } 490 | 491 | private: 492 | Napi::Promise::Deferred deferred_; 493 | mg::Client *client_; 494 | decltype(client_->FetchOne()) data_; 495 | }; 496 | 497 | Napi::Value Client::FetchOne(const Napi::CallbackInfo &info) { 498 | Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env()); 499 | auto wk = new AsyncFetchOneWorker(deferred, client_.get()); 500 | wk->Queue(); 501 | return deferred.Promise(); 502 | } 503 | 504 | class AsyncTxOpWoker final : public Napi::AsyncWorker { 505 | public: 506 | AsyncTxOpWoker(const Napi::Promise::Deferred &deferred, mg::Client *client, 507 | Client::TxOp tx_op) 508 | : AsyncWorker(Napi::Function::New(deferred.Promise().Env(), 509 | [](const Napi::CallbackInfo &) {})), 510 | deferred_(deferred), 511 | client_(client), 512 | tx_op_(tx_op) {} 513 | ~AsyncTxOpWoker() = default; 514 | 515 | void Execute() { 516 | try { 517 | switch (tx_op_) { 518 | case Client::TxOp::Begin: { 519 | auto status = client_->BeginTransaction(); 520 | if (!status) { 521 | SetError("Fail to BEGIN transaction."); 522 | return; 523 | } 524 | break; 525 | } 526 | case Client::TxOp::Commit: { 527 | auto status = client_->CommitTransaction(); 528 | if (!status) { 529 | SetError("Fail to COMMIT transaction."); 530 | return; 531 | } 532 | break; 533 | } 534 | case Client::TxOp::Rollback: { 535 | auto status = client_->RollbackTransaction(); 536 | if (!status) { 537 | SetError("Fail to ROLLBACK transaction."); 538 | return; 539 | } 540 | break; 541 | } 542 | default: 543 | throw std::runtime_error("Wrong transaction operation."); 544 | } 545 | } catch (const std::exception &error) { 546 | static const std::string NODEMG_MSG_TXOP_FAIL = 547 | "Fail to execute transaction operation."; 548 | SetError(NODEMG_MSG_TXOP_FAIL + " " + error.what()); 549 | return; 550 | } 551 | } 552 | 553 | void OnOK() { 554 | auto env = deferred_.Env(); 555 | this->deferred_.Resolve(env.Null()); 556 | } 557 | 558 | void OnError(const Napi::Error &e) { 559 | this->deferred_.Reject(Napi::Error::New(Env(), e.Message()).Value()); 560 | } 561 | 562 | private: 563 | Napi::Promise::Deferred deferred_; 564 | mg::Client *client_; 565 | Client::TxOp tx_op_; 566 | }; 567 | 568 | Napi::Value Client::Begin(const Napi::CallbackInfo &info) { 569 | Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env()); 570 | auto wk = new AsyncTxOpWoker(deferred, client_.get(), Client::TxOp::Begin); 571 | wk->Queue(); 572 | return deferred.Promise(); 573 | } 574 | 575 | Napi::Value Client::Commit(const Napi::CallbackInfo &info) { 576 | Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env()); 577 | auto wk = new AsyncTxOpWoker(deferred, client_.get(), Client::TxOp::Commit); 578 | wk->Queue(); 579 | return deferred.Promise(); 580 | } 581 | 582 | Napi::Value Client::Rollback(const Napi::CallbackInfo &info) { 583 | Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env()); 584 | auto wk = new AsyncTxOpWoker(deferred, client_.get(), Client::TxOp::Rollback); 585 | wk->Queue(); 586 | return deferred.Promise(); 587 | } 588 | 589 | } // namespace nodemg 590 | -------------------------------------------------------------------------------- /src/client.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | 19 | // TODO(gitbuda): Ensure AsyncConnection can't be missused in the concurrent 20 | // environmnt (multiple threads calling the same object). 21 | 22 | namespace nodemg { 23 | 24 | class Client final : public Napi::ObjectWrap { 25 | public: 26 | static Napi::FunctionReference constructor; 27 | static Napi::Object Init(Napi::Env env, Napi::Object exports); 28 | static Napi::Object NewInstance(const Napi::CallbackInfo &info); 29 | 30 | Client(const Napi::CallbackInfo &info); 31 | ~Client(); 32 | // Public because it's called from AsyncWorker. 33 | void SetMgClient(std::unique_ptr client); 34 | 35 | enum class TxOp { Begin, Commit, Rollback }; 36 | 37 | Napi::Value Connect(const Napi::CallbackInfo &info); 38 | Napi::Value Execute(const Napi::CallbackInfo &info); 39 | Napi::Value FetchAll(const Napi::CallbackInfo &info); 40 | Napi::Value DiscardAll(const Napi::CallbackInfo &info); 41 | Napi::Value FetchOne(const Napi::CallbackInfo &info); 42 | Napi::Value Begin(const Napi::CallbackInfo &info); 43 | Napi::Value Commit(const Napi::CallbackInfo &info); 44 | Napi::Value Rollback(const Napi::CallbackInfo &info); 45 | 46 | private: 47 | std::unique_ptr client_; 48 | std::string name_; 49 | 50 | std::optional PrepareConnect( 51 | const Napi::CallbackInfo &info); 52 | std::optional> PrepareQuery( 53 | const Napi::CallbackInfo &info); 54 | }; 55 | 56 | } // namespace nodemg 57 | -------------------------------------------------------------------------------- /src/glue.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "glue.hpp" 16 | 17 | #include "util.hpp" 18 | 19 | namespace nodemg { 20 | 21 | Napi::Value MgStringToNapiString(Napi::Env env, const mg_string *input_string) { 22 | Napi::EscapableHandleScope scope(env); 23 | Napi::Value output_string = Napi::String::New( 24 | env, mg_string_data(input_string), mg_string_size(input_string)); 25 | return scope.Escape(napi_value(output_string)); 26 | } 27 | 28 | Napi::Value MgDateToNapiDate(Napi::Env env, const mg_date *input) { 29 | Napi::EscapableHandleScope scope(env); 30 | Napi::Object output = Napi::Object::New(env); 31 | auto days = mg_date_days(input); 32 | output.Set("objectType", "date"); 33 | output.Set("days", Napi::BigInt::New(env, days)); 34 | output.Set("date", Napi::Date::New(env, days * 24 * 60 * 60 * 1000)); 35 | return scope.Escape(napi_value(output)); 36 | } 37 | 38 | Napi::Value MgLocalTimeToNapiLocalTime(Napi::Env env, 39 | const mg_local_time *input) { 40 | Napi::EscapableHandleScope scope(env); 41 | auto nanoseconds = mg_local_time_nanoseconds(input); 42 | Napi::Object output = Napi::Object::New(env); 43 | output.Set("objectType", "local_time"); 44 | output.Set("nanoseconds", Napi::BigInt::New(env, nanoseconds)); 45 | return scope.Escape(napi_value(output)); 46 | } 47 | 48 | Napi::Value MgLocalDateTimeToNapiDate(Napi::Env env, 49 | const mg_local_date_time *input) { 50 | Napi::EscapableHandleScope scope(env); 51 | auto seconds = mg_local_date_time_seconds(input); 52 | auto nanoseconds = mg_local_date_time_nanoseconds(input); 53 | // NOTE: An obvious loss of precision (nanoseconds to milliseconds). 54 | auto milliseconds = 1.0 * (seconds * 1000 + nanoseconds / 10000000); 55 | Napi::Object output = Napi::Object::New(env); 56 | output.Set("objectType", "local_date_time"); 57 | output.Set("seconds", Napi::BigInt::New(env, seconds)); 58 | output.Set("nanoseconds", Napi::BigInt::New(env, nanoseconds)); 59 | output.Set("date", Napi::Date::New(env, milliseconds)); 60 | return scope.Escape(napi_value(output)); 61 | } 62 | 63 | Napi::Value MgDurationToNapiDuration(Napi::Env env, const mg_duration *input) { 64 | Napi::EscapableHandleScope scope(env); 65 | auto days = mg_duration_days(input); 66 | auto seconds = mg_duration_seconds(input); 67 | auto nanoseconds = mg_duration_nanoseconds(input); 68 | Napi::Object output = Napi::Object::New(env); 69 | output.Set("objectType", "duration"); 70 | output.Set("days", Napi::BigInt::New(env, days)); 71 | output.Set("seconds", Napi::BigInt::New(env, seconds)); 72 | output.Set("nanoseconds", Napi::BigInt::New(env, nanoseconds)); 73 | return scope.Escape(napi_value(output)); 74 | } 75 | 76 | std::optional MgListToNapiArray(Napi::Env env, 77 | const mg_list *input_list) { 78 | Napi::EscapableHandleScope scope(env); 79 | auto input_list_size = mg_list_size(input_list); 80 | auto output_array = Napi::Array::New(env, input_list_size); 81 | for (uint32_t index = 0; index < input_list_size; ++index) { 82 | auto value = MgValueToNapiValue(env, mg_list_at(input_list, index)); 83 | if (!value) { 84 | return std::nullopt; 85 | } 86 | output_array[index] = *value; 87 | } 88 | return scope.Escape(output_array); 89 | } 90 | 91 | std::optional MgMapToNapiObject(Napi::Env env, 92 | const mg_map *input_map) { 93 | Napi::EscapableHandleScope scope(env); 94 | Napi::Object output_object = Napi::Object::New(env); 95 | for (uint32_t i = 0; i < mg_map_size(input_map); ++i) { 96 | auto key = MgStringToNapiString(env, mg_map_key_at(input_map, i)); 97 | auto value = MgValueToNapiValue(env, mg_map_value_at(input_map, i)); 98 | if (!value) { 99 | return std::nullopt; 100 | } 101 | output_object.Set(key, *value); 102 | } 103 | return scope.Escape(napi_value(output_object)); 104 | } 105 | 106 | std::optional MgNodeToNapiNode(Napi::Env env, 107 | const mg_node *input_node) { 108 | Napi::EscapableHandleScope scope(env); 109 | auto node_id = Napi::BigInt::New(env, mg_node_id(input_node)); 110 | 111 | auto label_count = mg_node_label_count(input_node); 112 | auto node_labels = Napi::Array::New(env, label_count); 113 | for (uint32_t label_index = 0; label_index < label_count; ++label_index) { 114 | auto label = 115 | MgStringToNapiString(env, mg_node_label_at(input_node, label_index)); 116 | node_labels[label_index] = label; 117 | } 118 | 119 | auto node_properties = MgMapToNapiObject(env, mg_node_properties(input_node)); 120 | if (!node_properties) { 121 | return std::nullopt; 122 | } 123 | 124 | Napi::Object output_node = Napi::Object::New(env); 125 | output_node.Set("objectType", "node"); 126 | output_node.Set("id", node_id); 127 | output_node.Set("labels", node_labels); 128 | output_node.Set("properties", *node_properties); 129 | return scope.Escape(napi_value(output_node)); 130 | } 131 | 132 | std::optional MgRelationshipToNapiRelationship( 133 | Napi::Env env, const mg_relationship *input_relationship) { 134 | Napi::EscapableHandleScope scope(env); 135 | 136 | auto relationship_id = 137 | Napi::BigInt::New(env, mg_relationship_id(input_relationship)); 138 | auto relationship_start_node_id = 139 | Napi::BigInt::New(env, mg_relationship_start_id(input_relationship)); 140 | auto relationship_end_node_id = 141 | Napi::BigInt::New(env, mg_relationship_end_id(input_relationship)); 142 | 143 | auto relationship_type = 144 | MgStringToNapiString(env, mg_relationship_type(input_relationship)); 145 | 146 | auto relationship_properties = 147 | MgMapToNapiObject(env, mg_relationship_properties(input_relationship)); 148 | if (!relationship_properties) { 149 | return std::nullopt; 150 | } 151 | 152 | Napi::Object output_relationship = Napi::Object::New(env); 153 | output_relationship.Set("objectType", "relationship"); 154 | output_relationship.Set("id", relationship_id); 155 | output_relationship.Set("startNodeId", relationship_start_node_id); 156 | output_relationship.Set("endNodeId", relationship_end_node_id); 157 | output_relationship.Set("edgeType", relationship_type); 158 | output_relationship.Set("properties", *relationship_properties); 159 | return scope.Escape(napi_value(output_relationship)); 160 | } 161 | 162 | std::optional MgUnboundRelationshipToNapiRelationship( 163 | Napi::Env env, const mg_unbound_relationship *input_unbound_relationship) { 164 | Napi::EscapableHandleScope scope(env); 165 | 166 | auto relationship_id = Napi::BigInt::New( 167 | env, mg_unbound_relationship_id(input_unbound_relationship)); 168 | int64_t relationship_start_node_id = -1; 169 | int64_t relationship_end_node_id = -1; 170 | 171 | auto relationship_type = MgStringToNapiString( 172 | env, mg_unbound_relationship_type(input_unbound_relationship)); 173 | 174 | auto relationship_properties = MgMapToNapiObject( 175 | env, mg_unbound_relationship_properties(input_unbound_relationship)); 176 | if (!relationship_properties) { 177 | return std::nullopt; 178 | } 179 | 180 | Napi::Object output_relationship = Napi::Object::New(env); 181 | output_relationship.Set("objectType", "relationship"); 182 | output_relationship.Set("id", relationship_id); 183 | output_relationship.Set("startNodeId", relationship_start_node_id); 184 | output_relationship.Set("endNodeId", relationship_end_node_id); 185 | output_relationship.Set("edgeType", relationship_type); 186 | output_relationship.Set("properties", *relationship_properties); 187 | return scope.Escape(napi_value(output_relationship)); 188 | } 189 | 190 | std::optional MgPathToNapiPath(Napi::Env env, 191 | const mg_path *input_path) { 192 | Napi::EscapableHandleScope scope(env); 193 | 194 | auto nodes = Napi::Array::New(env); 195 | auto relationships = Napi::Array::New(env); 196 | int64_t prev_node_id = -1; 197 | for (uint32_t index = 0; index <= mg_path_length(input_path); ++index) { 198 | int64_t curr_node_id = mg_node_id(mg_path_node_at(input_path, index)); 199 | auto node = MgNodeToNapiNode(env, mg_path_node_at(input_path, index)); 200 | if (!node) { 201 | return std::nullopt; 202 | } 203 | nodes[index] = *node; 204 | if (index > 0) { 205 | auto relationship = MgUnboundRelationshipToNapiRelationship( 206 | env, mg_path_relationship_at(input_path, index - 1)); 207 | if (!relationship) { 208 | return std::nullopt; 209 | } 210 | if (mg_path_relationship_reversed_at(input_path, index - 1)) { 211 | relationship->As().Set( 212 | "startNodeId", Napi::BigInt::New(env, curr_node_id)); 213 | relationship->As().Set( 214 | "endNodeId", Napi::BigInt::New(env, prev_node_id)); 215 | } else { 216 | relationship->As().Set( 217 | "startNodeId", Napi::BigInt::New(env, prev_node_id)); 218 | relationship->As().Set( 219 | "endNodeId", Napi::BigInt::New(env, curr_node_id)); 220 | } 221 | relationships[index - 1] = *relationship; 222 | } 223 | prev_node_id = curr_node_id; 224 | } 225 | 226 | Napi::Object output_path = Napi::Object::New(env); 227 | output_path.Set("objectType", "path"); 228 | output_path.Set("nodes", nodes); 229 | output_path.Set("relationships", relationships); 230 | return scope.Escape(napi_value(output_path)); 231 | } 232 | 233 | // TODO(gitbuda): Consider the error handling because this method is used to 234 | // during e.g. list construction which doesn't have any error handling inside. 235 | // Policy has to be created. It probably makes sense to have both because 236 | // more granular error messages could be presented to the user. 237 | std::optional MgValueToNapiValue(Napi::Env env, 238 | const mg_value *input_value) { 239 | Napi::EscapableHandleScope scope(env); 240 | switch (mg_value_get_type(input_value)) { 241 | case MG_VALUE_TYPE_NULL: 242 | return scope.Escape(napi_value(env.Null())); 243 | case MG_VALUE_TYPE_BOOL: 244 | return scope.Escape( 245 | napi_value(Napi::Boolean::New(env, mg_value_bool(input_value)))); 246 | case MG_VALUE_TYPE_INTEGER: 247 | return scope.Escape( 248 | napi_value(Napi::BigInt::New(env, mg_value_integer(input_value)))); 249 | case MG_VALUE_TYPE_FLOAT: 250 | return scope.Escape( 251 | napi_value(Napi::Number::New(env, mg_value_float(input_value)))); 252 | case MG_VALUE_TYPE_STRING: 253 | return scope.Escape( 254 | napi_value(MgStringToNapiString(env, mg_value_string(input_value)))); 255 | case MG_VALUE_TYPE_DATE: 256 | return scope.Escape( 257 | napi_value(MgDateToNapiDate(env, mg_value_date(input_value)))); 258 | case MG_VALUE_TYPE_LOCAL_TIME: 259 | return scope.Escape(napi_value( 260 | MgLocalTimeToNapiLocalTime(env, mg_value_local_time(input_value)))); 261 | case MG_VALUE_TYPE_LOCAL_DATE_TIME: 262 | return scope.Escape(napi_value(MgLocalDateTimeToNapiDate( 263 | env, mg_value_local_date_time(input_value)))); 264 | case MG_VALUE_TYPE_DURATION: 265 | return scope.Escape(napi_value( 266 | MgDurationToNapiDuration(env, mg_value_duration(input_value)))); 267 | case MG_VALUE_TYPE_LIST: { 268 | auto list_value = MgListToNapiArray(env, mg_value_list(input_value)); 269 | if (!list_value) { 270 | return std::nullopt; 271 | } 272 | return scope.Escape(napi_value(*list_value)); 273 | } 274 | case MG_VALUE_TYPE_MAP: { 275 | auto map_value = MgMapToNapiObject(env, mg_value_map(input_value)); 276 | if (!map_value) { 277 | return std::nullopt; 278 | } 279 | return scope.Escape(napi_value(*map_value)); 280 | } 281 | case MG_VALUE_TYPE_NODE: { 282 | auto node_value = MgNodeToNapiNode(env, mg_value_node(input_value)); 283 | if (!node_value) { 284 | return std::nullopt; 285 | } 286 | return scope.Escape(napi_value(*node_value)); 287 | } 288 | case MG_VALUE_TYPE_RELATIONSHIP: { 289 | auto relationship_value = MgRelationshipToNapiRelationship( 290 | env, mg_value_relationship(input_value)); 291 | if (!relationship_value) { 292 | return std::nullopt; 293 | } 294 | return scope.Escape(napi_value(*relationship_value)); 295 | } 296 | case MG_VALUE_TYPE_UNBOUND_RELATIONSHIP: { 297 | auto unbound_relationship_value = MgUnboundRelationshipToNapiRelationship( 298 | env, mg_value_unbound_relationship(input_value)); 299 | if (!unbound_relationship_value) { 300 | return std::nullopt; 301 | } 302 | return scope.Escape(napi_value(*unbound_relationship_value)); 303 | } 304 | case MG_VALUE_TYPE_PATH: { 305 | auto path_value = MgPathToNapiPath(env, mg_value_path(input_value)); 306 | if (!path_value) { 307 | return std::nullopt; 308 | } 309 | return scope.Escape(napi_value(*path_value)); 310 | } 311 | case MG_VALUE_TYPE_UNKNOWN: 312 | NODEMG_THROW( 313 | "A value of unknown type encountered during query execution."); 314 | return std::nullopt; 315 | default: 316 | NODEMG_THROW("Unrecognized type encountered during query execution."); 317 | return std::nullopt; 318 | } 319 | } 320 | 321 | std::optional GetInt64Value(Napi::Object input, 322 | const std::string &key) { 323 | if (!input.Has(key)) return std::nullopt; 324 | auto key_value = input.Get(key); 325 | if (!key_value.IsBigInt()) return std::nullopt; 326 | bool lossless{false}; 327 | auto value = key_value.As().Int64Value(&lossless); 328 | if (!lossless) { 329 | return std::nullopt; 330 | } 331 | return value; 332 | } 333 | 334 | std::optional NapiObjectToMgDate(Napi::Object input) { 335 | auto maybe_days = GetInt64Value(input, "days"); 336 | if (!maybe_days) return std::nullopt; 337 | return mg_date_make(*maybe_days); 338 | } 339 | 340 | std::optional NapiObjectToMgLocalTime(Napi::Object input) { 341 | auto maybe_nanoseconds = GetInt64Value(input, "nanoseconds"); 342 | if (!maybe_nanoseconds) return std::nullopt; 343 | return mg_local_time_make(*maybe_nanoseconds); 344 | } 345 | 346 | std::optional NapiObjectToMgLocalDateTime( 347 | Napi::Object input) { 348 | auto maybe_seconds = GetInt64Value(input, "seconds"); 349 | if (!maybe_seconds) return std::nullopt; 350 | auto maybe_nanoseconds = GetInt64Value(input, "nanoseconds"); 351 | if (!maybe_nanoseconds) return std::nullopt; 352 | return mg_local_date_time_make(*maybe_seconds, *maybe_nanoseconds); 353 | } 354 | 355 | std::optional NapiObjectToMgDuration(Napi::Object input) { 356 | auto maybe_days = GetInt64Value(input, "days"); 357 | if (!maybe_days) return std::nullopt; 358 | auto maybe_seconds = GetInt64Value(input, "seconds"); 359 | if (!maybe_seconds) return std::nullopt; 360 | auto maybe_nanoseconds = GetInt64Value(input, "nanoseconds"); 361 | if (!maybe_nanoseconds) return std::nullopt; 362 | return mg_duration_make(0, *maybe_days, *maybe_seconds, *maybe_nanoseconds); 363 | } 364 | 365 | std::optional NapiArrayToMgList(Napi::Env env, 366 | Napi::Array input_array) { 367 | mg_list *output_list = nullptr; 368 | output_list = mg_list_make_empty(input_array.Length()); 369 | if (!output_list) { 370 | mg_list_destroy(output_list); 371 | NODEMG_THROW("Fail to construct Memgraph list."); 372 | return std::nullopt; 373 | } 374 | for (uint32_t index = 0; index < input_array.Length(); ++index) { 375 | auto maybe_value = NapiValueToMgValue(env, input_array[index]); 376 | if (!maybe_value) { 377 | mg_list_destroy(output_list); 378 | NODEMG_THROW("Fail to construct Memgraph value while constructing list."); 379 | return std::nullopt; 380 | } 381 | if (mg_list_append(output_list, *maybe_value) != 0) { 382 | mg_list_destroy(output_list); 383 | NODEMG_THROW("Fail to append Memgraph list value."); 384 | return std::nullopt; 385 | } 386 | } 387 | return output_list; 388 | } 389 | 390 | std::optional NapiValueToMgValue(Napi::Env env, 391 | Napi::Value input_value) { 392 | mg_value *output_value = nullptr; 393 | if (input_value.IsEmpty() || input_value.IsUndefined() || 394 | input_value.IsNull()) { 395 | output_value = mg_value_make_null(); 396 | } else if (input_value.IsBoolean()) { 397 | output_value = mg_value_make_bool(input_value.As().Value()); 398 | } else if (input_value.IsBigInt()) { 399 | bool lossless; 400 | int64_t as_int64 = input_value.As().Int64Value(&lossless); 401 | if (!lossless) { 402 | NODEMG_THROW("Fail to losslessly convert value to Memgraph int64."); 403 | return std::nullopt; 404 | } 405 | output_value = mg_value_make_integer(as_int64); 406 | } else if (input_value.IsNumber()) { 407 | auto as_double = input_value.As().DoubleValue(); 408 | output_value = mg_value_make_float(as_double); 409 | } else if (input_value.IsString()) { 410 | mg_string *input_mg_string = 411 | mg_string_make(input_value.As().Utf8Value().c_str()); 412 | if (!input_mg_string) { 413 | NODEMG_THROW("Fail to construct Memgraph string."); 414 | return std::nullopt; 415 | } 416 | output_value = mg_value_make_string2(input_mg_string); 417 | } else if (input_value.IsArray()) { 418 | auto maybe_mg_list = NapiArrayToMgList(env, input_value.As()); 419 | if (!maybe_mg_list) { 420 | return std::nullopt; 421 | } 422 | output_value = mg_value_make_list(*maybe_mg_list); 423 | // NOTE: The "dispatch" below could be implemented in a much better way 424 | // (probably the whole function as well), but that's a nice exercise for the 425 | // future. 426 | } else if (input_value.IsObject()) { 427 | auto input_object = input_value.As(); 428 | if (input_object.Has("objectType")) { 429 | auto input_object_type = 430 | input_object.Get("objectType").As().Utf8Value(); 431 | if (input_object_type == "date") { 432 | auto maybe_mg_date = NapiObjectToMgDate(input_object); 433 | if (!maybe_mg_date) { 434 | NODEMG_THROW("Converting JS date to Memgraph date failed!"); 435 | return std::nullopt; 436 | } 437 | output_value = mg_value_make_date(*maybe_mg_date); 438 | } else if (input_object_type == "local_time") { 439 | auto maybe_mg_local_time = NapiObjectToMgLocalTime(input_object); 440 | if (!maybe_mg_local_time) { 441 | NODEMG_THROW( 442 | "Converting JS local time to Memgraph local time failed!"); 443 | return std::nullopt; 444 | } 445 | output_value = mg_value_make_local_time(*maybe_mg_local_time); 446 | } else if (input_object_type == "local_date_time") { 447 | auto maybe_mg_local_date_time = 448 | NapiObjectToMgLocalDateTime(input_object); 449 | if (!maybe_mg_local_date_time) { 450 | NODEMG_THROW( 451 | "Converting JS local date time to Memgraph local date time " 452 | "failed!"); 453 | return std::nullopt; 454 | } 455 | output_value = mg_value_make_local_date_time(*maybe_mg_local_date_time); 456 | } else if (input_object_type == "duration") { 457 | auto maybe_mg_duration = NapiObjectToMgDuration(input_object); 458 | if (!maybe_mg_duration) { 459 | NODEMG_THROW("Converting JS duration to Memgraph duration failed!"); 460 | return std::nullopt; 461 | } 462 | output_value = mg_value_make_duration(*maybe_mg_duration); 463 | } else { 464 | NODEMG_THROW("Unknown type of JS Object!"); 465 | return std::nullopt; 466 | } 467 | return output_value; 468 | } 469 | auto maybe_mg_map = NapiObjectToMgMap(env, input_object); 470 | if (!maybe_mg_map) { 471 | return std::nullopt; 472 | } 473 | output_value = mg_value_make_map(*maybe_mg_map); 474 | } else { 475 | NODEMG_THROW("Unrecognized JavaScript value."); 476 | return std::nullopt; 477 | } 478 | if (!output_value) { 479 | NODEMG_THROW("Fail to construct Memgraph value."); 480 | return std::nullopt; 481 | } 482 | return output_value; 483 | } 484 | 485 | std::optional NapiObjectToMgMap(Napi::Env env, 486 | Napi::Object input_object) { 487 | mg_map *output_map = nullptr; 488 | auto keys = input_object.GetPropertyNames(); 489 | output_map = mg_map_make_empty(keys.Length()); 490 | if (!output_map) { 491 | mg_map_destroy(output_map); 492 | NODEMG_THROW("Fail to construct Memgraph map."); 493 | return std::nullopt; 494 | } 495 | for (uint32_t index = 0; index < keys.Length(); index++) { 496 | Napi::Value napi_key = keys[index]; 497 | mg_string *mg_key = 498 | mg_string_make(napi_key.As().Utf8Value().c_str()); 499 | if (!mg_key) { 500 | mg_map_destroy(output_map); 501 | NODEMG_THROW("Fail to constract Memgraph string while creating map."); 502 | return std::nullopt; 503 | } 504 | auto maybe_mg_value = NapiValueToMgValue(env, input_object.Get(napi_key)); 505 | if (!maybe_mg_value) { 506 | mg_map_destroy(output_map); 507 | return std::nullopt; 508 | } 509 | if (mg_map_insert_unsafe2(output_map, mg_key, *maybe_mg_value) != 0) { 510 | mg_map_destroy(output_map); 511 | NODEMG_THROW("Fail to add value to Memgraph map."); 512 | return std::nullopt; 513 | } 514 | } 515 | return output_map; 516 | } 517 | 518 | } // namespace nodemg 519 | -------------------------------------------------------------------------------- /src/glue.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | namespace nodemg { 21 | 22 | [[nodiscard]] std::optional MgValueToNapiValue( 23 | Napi::Env env, const mg_value *input_value); 24 | 25 | [[nodiscard]] std::optional MgListToNapiArray( 26 | Napi::Env env, const mg_list *input_list); 27 | 28 | [[nodiscard]] std::optional MgMapToNapiObject( 29 | Napi::Env env, const mg_map *input_map); 30 | 31 | [[nodiscard]] std::optional NapiValueToMgValue( 32 | Napi::Env env, Napi::Value input_value); 33 | 34 | [[nodiscard]] std::optional NapiObjectToMgMap( 35 | Napi::Env env, Napi::Object input_value); 36 | 37 | } // namespace nodemg 38 | -------------------------------------------------------------------------------- /src/util.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// Throws Napi error in the Napi context. 16 | /// Created because it makes the code cleaner. 17 | /// NOTE: Assumes there is the env variable in the scope! 18 | #define NODEMG_THROW(error) (NAPI_THROW(Napi::Error::New(env, error), ...)) 19 | -------------------------------------------------------------------------------- /test/connection.spec.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const getPort = require('get-port'); 16 | 17 | const memgraph = require('..'); 18 | const util = require('./util'); 19 | 20 | test('Connect empty args should work (port is always different)', async () => { 21 | const port = await getPort(); 22 | await util.checkAgainstMemgraph(async () => { 23 | const connection = await memgraph.Client().Connect({ port: port }); 24 | expect(connection).toBeDefined(); 25 | }, port); 26 | }); 27 | 28 | test('Connect fail because port spelling is wrong', () => { 29 | expect(() => { 30 | memgraph.Client().Connect({ prt: 7687 }); 31 | }).toThrow(); 32 | }); 33 | 34 | test('Connect fail because port is out of range', () => { 35 | expect(() => { 36 | memgraph.Client().Connect({ port: -100 }); 37 | }).toThrow(); 38 | expect(() => { 39 | memgraph.Client().Connect({ port: 1000000 }); 40 | }).toThrow(); 41 | }); 42 | 43 | test('Connect to Memgraph host via SSL multiple times', async () => { 44 | const port = await getPort(); 45 | await util.checkAgainstMemgraph( 46 | async () => { 47 | for (let iter = 0; iter < 100; iter++) { 48 | const connection = await memgraph.Client().Connect({ 49 | host: '127.0.0.1', 50 | port: port, 51 | use_ssl: true, 52 | }); 53 | expect(connection).toBeDefined(); 54 | } 55 | }, 56 | port, 57 | true, 58 | ); 59 | }); 60 | 61 | test('Connect fail because host is wrong', async () => { 62 | const port = await getPort(); 63 | await util.checkAgainstMemgraph(async () => { 64 | await expect( 65 | memgraph.Client().Connect({ host: 'wrong_host' }), 66 | ).rejects.toThrow(); 67 | }, port); 68 | }); 69 | -------------------------------------------------------------------------------- /test/queries.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = Object.freeze({ 16 | NODES: `MATCH (n) RETURN n;`, 17 | EDGES: `MATCH ()-[e]->() RETURN e;`, 18 | NODE_EDGE_IDS: `MATCH (n)-[e]->(m) RETURN n.id, e.id;`, 19 | COUNT_NODES: `MATCH (n) RETURN count(n) AS nodes_no;`, 20 | COUNT_EDGES: `MATCH ()-[e]->() RETURN count(e) AS edges_no;`, 21 | DELETE_ALL: `MATCH (n) DETACH DELETE n;`, 22 | CREATE_TRIANGLE: ` 23 | CREATE (n1:Node {id: 1}), 24 | (n2:Node {id: 2}), 25 | (n3:Node {id: 3}), 26 | (n1)-[e1:Edge {id: 1}]->(n2), 27 | (n2)-[e2:Edge {id: 2}]->(n3), 28 | (n3)-[e3:Edge {id: 3}]->(n1);`, 29 | CREATE_RICH_NODE: ` 30 | CREATE (n:Label1:Label2 {prop0: Null, 31 | prop1: True, 32 | prop2: False, 33 | prop3: 10, 34 | prop4: 100.0, 35 | prop5: "test"})`, 36 | CREATE_RICH_EDGE: ` 37 | CREATE ()-[e:Type {prop1: True, 38 | prop2: False, 39 | prop3: 1, 40 | prop4: 2.0, 41 | prop5: "test"}]->();`, 42 | CREATE_PATH: ` 43 | CREATE 44 | (:Label {id:1}) 45 | -[:Type {id: 1}]-> 46 | (:Label {id:2}) 47 | -[:Type {id: 2}]-> 48 | (:Label {id:3}) 49 | -[:Type {id: 3}]-> 50 | (:Label {id:4});`, 51 | MATCH_PATHS: `MATCH path=(startNode {id: 1})-[*]->(endNode) RETURN path;`, 52 | MATCH_RELATIONSHIPS: ` 53 | MATCH path=(endNode {id: 2})<-[]-(startNode {id: 1}) 54 | RETURN relationships(path);`, 55 | CREATE_NODE_USING_PARAMS: ` 56 | CREATE (n:Node {nullProperty: $nullProperty, 57 | trueProperty: $trueProperty, 58 | falseProperty: $falseProperty, 59 | bigIntProperty: $bigIntProperty, 60 | numberProperty: $numberProperty, 61 | stringProperty: $stringProperty, 62 | arrayProperty: $arrayProperty, 63 | objectProperty: $objectProperty, 64 | dateProperty: $dateProperty, 65 | localTimeProperty: $localTimeProperty, 66 | localDateTimeProperty: $localDateTimeProperty, 67 | durationProperty: $durationProperty});`, 68 | NAMED_COLUMNS: `RETURN "value_x" AS x, "value_y" AS y;`, 69 | TEMPORAL_VALUES: ` 70 | RETURN 71 | DATE("1960-01-12") as date, 72 | LOCALTIME("10:09:08.123456") as local_time, 73 | LOCALDATETIME("2021-09-30T08:01:02") as local_date_time, 74 | DURATION("P1DT2H3M4.56S") AS duration;`, 75 | }); 76 | -------------------------------------------------------------------------------- /test/queries.spec.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const getPort = require('get-port'); 16 | 17 | const memgraph = require('..'); 18 | const query = require('./queries'); 19 | const util = require('./util'); 20 | 21 | test('Queries data types', async () => { 22 | const port = await getPort(); 23 | await util.checkAgainstMemgraph(async () => { 24 | const connection = await memgraph.Connect({ 25 | host: '127.0.0.1', 26 | port: port, 27 | }); 28 | expect(connection).toBeDefined(); 29 | 30 | const nullValue = util.firstRecord( 31 | await connection.ExecuteAndFetchAll('RETURN Null;'), 32 | ); 33 | expect(nullValue).toEqual(null); 34 | 35 | const listValue = util.firstRecord( 36 | await connection.ExecuteAndFetchAll('RETURN [1, 2];'), 37 | ); 38 | expect(listValue).toEqual([1n, 2n]); 39 | 40 | const mapValue = util.firstRecord( 41 | await connection.ExecuteAndFetchAll('RETURN {k1: 1, k2: "v"} as d;'), 42 | ); 43 | expect(mapValue).toEqual({ k1: 1n, k2: 'v' }); 44 | 45 | const temporalValues = await connection.ExecuteAndFetchAll( 46 | query.TEMPORAL_VALUES, 47 | ); 48 | expect(temporalValues[0][0]).toEqual({ 49 | objectType: 'date', 50 | days: -3642n, 51 | date: new Date('1960-01-12T00:00:00.000Z'), 52 | }); 53 | expect(temporalValues[0][1]).toEqual({ 54 | objectType: 'local_time', 55 | nanoseconds: 36548123456000n, 56 | }); 57 | expect(temporalValues[0][2]).toEqual({ 58 | objectType: 'local_date_time', 59 | seconds: 1632988862n, 60 | nanoseconds: 0n, 61 | date: new Date('2021-09-30T08:01:02.000Z'), 62 | }); 63 | expect(temporalValues[0][3]).toEqual({ 64 | objectType: 'duration', 65 | days: 1n, 66 | seconds: 7384n, 67 | nanoseconds: 560000000n, 68 | }); 69 | }, port); 70 | }, 10000); 71 | 72 | test('Queries basic graph', async () => { 73 | const port = await getPort(); 74 | await util.checkAgainstMemgraph(async () => { 75 | const connection = await memgraph.Connect({ 76 | host: '127.0.0.1', 77 | port: port, 78 | }); 79 | expect(connection).toBeDefined(); 80 | 81 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 82 | await connection.ExecuteAndFetchAll(query.CREATE_TRIANGLE); 83 | 84 | const nodesNo = util.firstRecord( 85 | await connection.ExecuteAndFetchAll(query.COUNT_NODES), 86 | ); 87 | expect(nodesNo).toEqual(3n); 88 | 89 | const edgesNo = util.firstRecord( 90 | await connection.ExecuteAndFetchAll(query.COUNT_EDGES), 91 | ); 92 | expect(edgesNo).toEqual(3n); 93 | await expect(connection.Execute('QUERY')).rejects.toThrow(); 94 | }, port); 95 | }, 10000); 96 | 97 | test('Queries create and fetch a node', async () => { 98 | const port = await getPort(); 99 | await util.checkAgainstMemgraph(async () => { 100 | const connection = await memgraph.Connect({ 101 | host: '127.0.0.1', 102 | port: port, 103 | }); 104 | expect(connection).toBeDefined(); 105 | 106 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 107 | await connection.ExecuteAndFetchAll(query.CREATE_RICH_NODE); 108 | 109 | const node = util.firstRecord( 110 | await connection.ExecuteAndFetchAll(query.NODES), 111 | ); 112 | expect(node.id).toBeGreaterThanOrEqual(0); 113 | expect(node.labels).toContain('Label1'); 114 | expect(node.labels).toContain('Label2'); 115 | expect(node.properties.prop0).toEqual(undefined); 116 | expect(node.properties.prop1).toEqual(true); 117 | expect(node.properties.prop2).toEqual(false); 118 | expect(node.properties.prop3).toEqual(10n); 119 | expect(node.properties.prop4).toEqual(100.0); 120 | expect(node.properties.prop5).toEqual('test'); 121 | }, port); 122 | }, 10000); 123 | 124 | test('Queries create and fetch a relationship', async () => { 125 | const port = await getPort(); 126 | await util.checkAgainstMemgraph(async () => { 127 | const connection = await memgraph.Connect({ 128 | host: '127.0.0.1', 129 | port: port, 130 | }); 131 | expect(connection).toBeDefined(); 132 | 133 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 134 | await connection.ExecuteAndFetchAll(query.CREATE_RICH_EDGE); 135 | 136 | const node = util.firstRecord( 137 | await connection.ExecuteAndFetchAll(query.EDGES), 138 | ); 139 | expect(node.id).toBeGreaterThanOrEqual(0); 140 | expect(node.edgeType).toContain('Type'); 141 | expect(node.properties.prop1).toEqual(true); 142 | expect(node.properties.prop2).toEqual(false); 143 | expect(node.properties.prop3).toEqual(1n); 144 | expect(node.properties.prop4).toEqual(2.0); 145 | expect(node.properties.prop5).toEqual('test'); 146 | }, port); 147 | }, 10000); 148 | 149 | test('Queries create and fetch a path', async () => { 150 | const port = await getPort(); 151 | await util.checkAgainstMemgraph(async () => { 152 | const connection = await memgraph.Connect({ 153 | host: '127.0.0.1', 154 | port: port, 155 | }); 156 | expect(connection).toBeDefined(); 157 | 158 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 159 | await connection.ExecuteAndFetchAll(query.CREATE_PATH); 160 | 161 | const nodesNo = util.firstRecord( 162 | await connection.ExecuteAndFetchAll(query.COUNT_NODES), 163 | ); 164 | expect(nodesNo).toEqual(4n); 165 | const edgesNo = util.firstRecord( 166 | await connection.ExecuteAndFetchAll(query.COUNT_EDGES), 167 | ); 168 | expect(edgesNo).toEqual(3n); 169 | const data = await connection.ExecuteAndFetchAll(query.MATCH_PATHS); 170 | expect(data.length).toEqual(3); 171 | const longestPath = data[2][0]; 172 | expect(longestPath.nodes.length).toEqual(4); 173 | expect(longestPath.nodes[0]).toEqual( 174 | expect.objectContaining({ 175 | labels: ['Label'], 176 | properties: { id: 1n }, 177 | }), 178 | ); 179 | expect(longestPath.nodes[1]).toEqual( 180 | expect.objectContaining({ 181 | labels: ['Label'], 182 | properties: { id: 2n }, 183 | }), 184 | ); 185 | expect(longestPath.nodes[2]).toEqual( 186 | expect.objectContaining({ 187 | labels: ['Label'], 188 | properties: { id: 3n }, 189 | }), 190 | ); 191 | expect(longestPath.nodes[3]).toEqual( 192 | expect.objectContaining({ 193 | labels: ['Label'], 194 | properties: { id: 4n }, 195 | }), 196 | ); 197 | expect(longestPath.relationships.length).toEqual(3); 198 | expect(longestPath.relationships[0]).toEqual( 199 | expect.objectContaining({ 200 | startNodeId: 0n, 201 | endNodeId: 1n, 202 | edgeType: 'Type', 203 | properties: { id: 1n }, 204 | }), 205 | ); 206 | expect(longestPath.relationships[1]).toEqual( 207 | expect.objectContaining({ 208 | startNodeId: 1n, 209 | endNodeId: 2n, 210 | edgeType: 'Type', 211 | properties: { id: 2n }, 212 | }), 213 | ); 214 | expect(longestPath.relationships[2]).toEqual( 215 | expect.objectContaining({ 216 | startNodeId: 2n, 217 | endNodeId: 3n, 218 | edgeType: 'Type', 219 | properties: { id: 3n }, 220 | }), 221 | ); 222 | }, port); 223 | }, 10000); 224 | 225 | test('Queries create path and fetch unbound relationship', async () => { 226 | const port = await getPort(); 227 | await util.checkAgainstMemgraph(async () => { 228 | const connection = await memgraph.Connect({ 229 | host: '127.0.0.1', 230 | port: port, 231 | }); 232 | expect(connection).toBeDefined(); 233 | 234 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 235 | await connection.ExecuteAndFetchAll(query.CREATE_PATH); 236 | 237 | const nodesNo = util.firstRecord( 238 | await connection.ExecuteAndFetchAll(query.COUNT_NODES), 239 | ); 240 | expect(nodesNo).toEqual(4n); 241 | const edgesNo = util.firstRecord( 242 | await connection.ExecuteAndFetchAll(query.COUNT_EDGES), 243 | ); 244 | expect(edgesNo).toEqual(3n); 245 | const result = await connection.ExecuteAndFetchAll( 246 | query.MATCH_RELATIONSHIPS, 247 | ); 248 | expect(result.length).toEqual(1); 249 | const relationship = util.firstRecord(result)[0]; 250 | expect(relationship).toEqual( 251 | expect.objectContaining({ 252 | startNodeId: 0n, 253 | endNodeId: 1n, 254 | edgeType: 'Type', 255 | properties: { id: 1n }, 256 | }), 257 | ); 258 | }, port); 259 | }, 10000); 260 | 261 | test('Queries use query parameters', async () => { 262 | const port = await getPort(); 263 | await util.checkAgainstMemgraph(async () => { 264 | const connection = await memgraph.Connect({ 265 | host: '127.0.0.1', 266 | port: port, 267 | }); 268 | expect(connection).toBeDefined(); 269 | const dateProperty = memgraph.createMgDate(-3642n); 270 | const localTimeProperty = memgraph.createMgLocalTime(36548123456000n); 271 | const localDateTimeProperty = memgraph.createMgLocalDateTime( 272 | 1632988862n, 273 | 0n, 274 | ); 275 | const durationProperty = memgraph.createMgDuration(1n, 7384n, 560000000n); 276 | await connection.ExecuteAndFetchAll(query.DELETE_ALL); 277 | await connection.ExecuteAndFetchAll(query.CREATE_NODE_USING_PARAMS, { 278 | nullProperty: null, 279 | trueProperty: true, 280 | falseProperty: false, 281 | bigIntProperty: 10n, 282 | numberProperty: 10.5, 283 | stringProperty: 'string test', 284 | arrayProperty: ['one', 'two'], 285 | objectProperty: { one: 'one', two: 'two' }, 286 | dateProperty: dateProperty, 287 | localTimeProperty: localTimeProperty, 288 | localDateTimeProperty: localDateTimeProperty, 289 | durationProperty: durationProperty, 290 | }); 291 | const nodesNo = util.firstRecord( 292 | await connection.ExecuteAndFetchAll(query.COUNT_NODES), 293 | ); 294 | expect(nodesNo).toEqual(1n); 295 | const node = util.firstRecord( 296 | await connection.ExecuteAndFetchAll(query.NODES), 297 | ); 298 | expect(node).toEqual( 299 | expect.objectContaining({ 300 | id: 0n, 301 | labels: ['Node'], 302 | properties: { 303 | trueProperty: true, 304 | falseProperty: false, 305 | bigIntProperty: 10n, 306 | numberProperty: 10.5, 307 | stringProperty: 'string test', 308 | arrayProperty: ['one', 'two'], 309 | objectProperty: { one: 'one', two: 'two' }, 310 | dateProperty: { 311 | objectType: 'date', 312 | days: -3642n, 313 | date: new Date('1960-01-12T00:00:00.000Z'), 314 | }, 315 | localTimeProperty: localTimeProperty, 316 | localDateTimeProperty: { 317 | objectType: 'local_date_time', 318 | seconds: 1632988862n, 319 | nanoseconds: 0n, 320 | date: new Date('2021-09-30T08:01:02.000Z'), 321 | }, 322 | durationProperty: durationProperty, 323 | }, 324 | }), 325 | ); 326 | }, port); 327 | }, 10000); 328 | 329 | test('Queries query parameters not provided', async () => { 330 | const port = await getPort(); 331 | await util.checkAgainstMemgraph(async () => { 332 | const connection = await memgraph.Connect({ 333 | host: '127.0.0.1', 334 | port: port, 335 | }); 336 | expect(connection).toBeDefined(); 337 | 338 | await expect( 339 | connection.Execute(query.CREATE_NODE_USING_PARAMS), 340 | ).rejects.toThrow(); 341 | }, port); 342 | }, 10000); 343 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const Docker = require('dockerode'); 16 | const assert = require('assert'); 17 | 18 | const docker = new Docker({ socketPath: '/var/run/docker.sock' }); 19 | 20 | async function checkAgainstMemgraph(check, port = 7687, sslEnabled = false) { 21 | const cmd = [ 22 | '--telemetry-enabled=False', 23 | '--log-level=TRACE', 24 | '--also-log-to-stderr', 25 | ]; 26 | if (sslEnabled) { 27 | cmd.push( 28 | '--bolt-key-file=/etc/memgraph/ssl/key.pem', 29 | '--bolt-cert-file=/etc/memgraph/ssl/cert.pem', 30 | ); 31 | } 32 | const container = await docker.createContainer({ 33 | Image: 'memgraph:2.5.2', 34 | Tty: false, 35 | AttachStdin: false, 36 | AttachStdout: false, 37 | AttachStderr: false, 38 | OpenStdin: false, 39 | StdinOnce: false, 40 | Cmd: cmd, 41 | HostConfig: { 42 | AutoRemove: true, 43 | PortBindings: { 44 | '7687/tcp': [{ HostPort: port.toString() }], 45 | }, 46 | }, 47 | }); 48 | await container.start(); 49 | // Waiting is not completly trivial because TCP connections is live while 50 | // Memgraph is still not up and running. 51 | // TODO(gitbuda): Replace wait with the client connection attempts. 52 | await new Promise((resolve) => setTimeout(resolve, 2000)); 53 | try { 54 | await check(); 55 | // eslint-disable-next-line no-useless-catch 56 | } catch (err) { 57 | throw err; 58 | } finally { 59 | await container.remove({ force: true }); 60 | } 61 | } 62 | 63 | function firstRecord(result) { 64 | assert(!!result && typeof result === 'object', 'Result has to be Object'); 65 | const data = result[0]; 66 | assert(!!data && Array.isArray(data), 'Data has to be Array'); 67 | assert(data.length > 0); 68 | return data[0]; 69 | } 70 | 71 | module.exports = { 72 | checkAgainstMemgraph, 73 | firstRecord, 74 | }; 75 | -------------------------------------------------------------------------------- /tool/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -Eeuo pipefail 4 | 5 | script_dir="$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd)" 6 | project_dir="${script_dir}/.." 7 | 8 | # Cleanup all existing build files. 9 | rm -rf "${project_dir}/build" "${project_dir}/coverage" "${project_dir}/node_modules" 10 | 11 | # Generage CPP coverage report based on DEBUG build. 12 | cpp_cov_output_dir="${project_dir}/coverage/cpp-coverage" 13 | cpp_cov_target_file="${project_dir}/build/Debug/nodemgclient.node" 14 | mkdir -p "${cpp_cov_output_dir}" 15 | npm install --prefix "${project_dir}" 16 | npm run build:debug --prefix "${project_dir}" 17 | pushd "${project_dir}/build" && cmake -DENABLE_COVERAGE=True .. && make && popd 18 | LLVM_PROFILE_FILE="${cpp_cov_output_dir}/cpp.profraw" npm run test --prefix "${project_dir}" 19 | llvm-profdata merge -sparse "${cpp_cov_output_dir}/cpp.profraw" -o "${cpp_cov_output_dir}/cpp.profdata" 20 | llvm-cov show "${cpp_cov_target_file}" \ 21 | -format html -instr-profile "${cpp_cov_output_dir}/cpp.profdata" \ 22 | -o "${cpp_cov_output_dir}" \ 23 | -show-line-counts-or-regions -Xdemangler c++filt -Xdemangler -n 24 | 25 | # Generate JS coverage report based on RELEASE build. 26 | rm -rf "${project_dir}/build" "${project_dir}/node_modules" 27 | npm install --prefix "${project_dir}" 28 | npm run build:release --prefix "${project_dir}" 29 | npm run test:coverage --prefix "${project_dir}" 30 | -------------------------------------------------------------------------------- /tool/test-binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'conditions': [ 3 | ['OS=="win"', { 4 | 'variables': { 5 | 'mgclient_dir%': '