├── .eslintrc.js ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .jsdoc.js ├── .npmignore ├── .prettierrc.js ├── .snyk ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── assets └── placeholder.md ├── docs ├── Agent.html ├── Client.html ├── Connection.html ├── Conversion%20Utilities.html ├── Credentials.html ├── Data.html ├── Filemaker%20Utilities.html ├── Filemaker.html ├── Session.html ├── client.model.js.html ├── connection.model.js.html ├── credentials.model.js.html ├── data.model.js.html ├── filemaker.model.js.html ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.svg │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-BoldItalic-webfont.svg │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.svg │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.svg │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ ├── OpenSans-LightItalic-webfont.svg │ ├── OpenSans-LightItalic-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.svg │ ├── OpenSans-Regular-webfont.woff │ ├── OpenSans-Semibold-webfont.eot │ ├── OpenSans-Semibold-webfont.svg │ ├── OpenSans-Semibold-webfont.ttf │ ├── OpenSans-Semibold-webfont.woff │ ├── OpenSans-SemiboldItalic-webfont.eot │ ├── OpenSans-SemiboldItalic-webfont.svg │ ├── OpenSans-SemiboldItalic-webfont.ttf │ └── OpenSans-SemiboldItalic-webfont.woff ├── global.html ├── global.html#Credentials ├── index.html ├── index.js.html ├── instance.html ├── models_agent.model.js.html ├── models_client.model.js.html ├── models_connection.model.js.html ├── models_credentials.model.js.html ├── models_data.model.js.html ├── models_session.model.js.html ├── module-Client.html ├── module-Connection.html ├── module-Container%20Service.html ├── module-Conversion%20Utilities.html ├── module-Credentials.html ├── module-Data-Data.html ├── module-Data.html ├── module-Filemaker.html ├── module-FormData.html ├── module-Metadata%20Service.html ├── module-Request%20Service.html ├── module-Transformation%20Service.html ├── module-Utilities.html ├── module-filemaker%20utilities.html ├── module-recordId.html ├── module-request.html ├── module-sanitizeParameters.html ├── module-urls.html ├── request.service.js.html ├── scripts │ ├── linenumber.js │ └── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js ├── services_container.service.js.html ├── services_metadata.service.js.html ├── services_request.service.js.html ├── services_transform.service.js.html ├── styles │ ├── jsdoc-default.css │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css ├── urls.html ├── utilities.service.js.html ├── utilities_conversion.utilities.js.html ├── utilities_filemaker.utilities.js.html ├── utilities_index.js.html ├── utilities_request.utilities.js.html ├── utilities_transform.utilities.js.html ├── utilities_urls.module_utilities.html └── utilities_urls.utilities.js.html ├── examples ├── authentication.examples.js ├── create.examples.js ├── datastore.examples.js ├── delete.examples.js ├── duplicate.examples.js ├── edit.examples.js ├── find.examples.js ├── get.examples.js ├── globals.examples.js ├── index.js ├── list.examples.js ├── metadata.examples.js ├── results │ ├── client-login-example.json │ ├── client-logout-example.json │ ├── client-reset-example.json │ ├── client-status-example.json │ ├── container-data-example.json │ ├── create-many-records-example.json │ ├── create-record-example.json │ ├── create-record-merge-example.json │ ├── databases-info-example.json │ ├── databases-utility-example.json │ ├── delete-record-example.json │ ├── duplicate-record-example.json │ ├── edit-record-example.json │ ├── edit-record-merge-example.json │ ├── field-data-utility-example.json │ ├── field-data-utility-original-example.json │ ├── find-records-example.json │ ├── get-record-example.json │ ├── layout-details-example.json │ ├── layouts-example.json │ ├── list-records-example.json │ ├── product-info-example.json │ ├── product-info-utility-example.json │ ├── record-id-utility-example.json │ ├── record-id-utility-original-example.json │ ├── run-script-example.json │ ├── run-script-string-example.json │ ├── run-script-with-parameters-example.json │ ├── run-scripts-example.json │ ├── run-single-script-example.json │ ├── script-run-example.json │ ├── script-string-run-example.json │ ├── script-string-run-with-parameters-example.json │ ├── script-trigger-example.json │ ├── scripts-example.json │ ├── set-globals-example.json │ ├── transform-utility-example.json │ ├── transform-utility-no-convert-example.json │ ├── transform-utility-original-example.json │ ├── trigger-scripts-on-create-example.json │ ├── upload-image-example.json │ └── upload-specific-record-example.json ├── schema │ ├── data-schema.json │ ├── portals-array-schema.json │ ├── query-schema.json │ ├── scripts-array-schema.json │ └── sort-schema.json ├── script.examples.js ├── scripts.examples.js ├── services │ ├── index.js │ ├── logger.service.js │ ├── regex.service.js │ └── storage.service.js ├── templates │ ├── README.hbs │ └── partials │ │ └── parameters.hbs ├── upload.examples.js └── utility.examples.js ├── index.js ├── package-lock.json ├── package.json ├── src ├── index.js ├── models │ ├── agent.model.js │ ├── client.model.js │ ├── connection.model.js │ ├── credentials.model.js │ ├── data.model.js │ ├── index.js │ └── session.model.js ├── services │ ├── container.service.js │ ├── index.js │ ├── metadata.service.js │ ├── request.service.js │ └── transform.service.js └── utilities │ ├── conversion.utilities.js │ ├── filemaker.utilities.js │ ├── index.js │ └── urls.utilities.js └── test ├── admin └── index.js ├── agent.model.test.js ├── agent.test.js ├── authentication.test.js ├── client.test.js ├── containerData.test.js ├── create.test.js ├── databases.test.js ├── delete.test.js ├── duplicate.test.js ├── edit.test.js ├── env.manifest ├── fieldData.test.js ├── find.test.js ├── get.test.js ├── globals.test.js ├── interceptors.test.js ├── layout.test.js ├── layouts.test.js ├── list.test.js ├── productInfo.test.js ├── queue.test.js ├── recordId.test.js ├── run.test.js ├── script.test.js ├── scripts.test.js ├── session.test.js ├── storage.test.js ├── transform.test.js ├── upload.test.js ├── urls.test.js ├── usage.test.js └── utilities.test.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | es6: true 5 | }, 6 | parserOptions: { 7 | sourceType: 'module', 8 | ecmaVersion: '2019' 9 | }, 10 | plugins: ['prettier'], 11 | extends: ['google', 'eslint:recommended', 'prettier'], 12 | rules: { 13 | 'prettier/prettier': 'error', 14 | 'prefer-promise-reject-errors': 'off' 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ Luidog ] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **Expected behavior** 11 | A clear and concise description of what you expected to happen. 12 | 13 | **Code Examples** 14 | If applicable, add example code to help explain your bug. 15 | 16 | **Tests** 17 | If possible write a failing test that showcases the bug. 18 | 19 | **Additional context** 20 | Add any other context about the problem here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | # Dependency directories 16 | node_modules/ 17 | # Optional npm cache directory 18 | .npm 19 | # Optional eslint cache 20 | .eslintcache 21 | # Optional REPL history 22 | .node_repl_history 23 | # Output of 'npm pack' 24 | *.tgz 25 | # Yarn Integrity file 26 | .yarn-integrity 27 | # dotenv environment variables file 28 | .env 29 | # make your own distributions 30 | dist/ 31 | bundles/ 32 | # we don't need and nedDB dbs. 33 | *.nedb 34 | # Dev data need not be saved. 35 | data/ 36 | .DS_Store 37 | .nyc_output 38 | assets/* 39 | !assets/placeholder.md -------------------------------------------------------------------------------- /.jsdoc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | tags: { 5 | allowUnknownTags: true, 6 | dictionaries: ['jsdoc'] 7 | }, 8 | source: { 9 | include: ['README.md', 'src'], 10 | includePattern: '.js$', 11 | excludePattern: '(node_modules/|docs)' 12 | }, 13 | plugins: ['plugins/markdown'], 14 | templates: { 15 | cleverLinks: false, 16 | monospaceLinks: true, 17 | useLongnameInNav: false, 18 | showInheritedInNav: true 19 | }, 20 | opts: { 21 | destination: './docs/', 22 | encoding: 'utf8', 23 | private: true, 24 | recurse: true, 25 | template: './node_modules/minami' 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | # Optional npm cache directory 16 | .npm 17 | # Optional eslint cache 18 | .eslintcache 19 | # Optional REPL history 20 | .node_repl_history 21 | # dotenv environment variables file 22 | .env 23 | #code coverage output 24 | .nyc_output 25 | .github 26 | docs/ 27 | examples/ 28 | assets/ 29 | test/ 30 | .travis.yml 31 | .eslintrc.js 32 | .jsdoc.js 33 | .prettierrc.js 34 | .github/ 35 | CODE_OF_CONDUCT.md 36 | .DS_Store -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | tabWidth: 2, 4 | arrowParens: 'avoid', 5 | trailingComma: 'none' 6 | }; 7 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.13.5 3 | ignore: {} 4 | # patches apply the minimum changes required to fix a vulnerability 5 | patch: 6 | SNYK-JS-LODASH-450202: 7 | - marpat > lodash: 8 | patched: '2019-07-07T14:47:05.925Z' 9 | - lodash: 10 | patched: '2019-07-07T14:46:10.103Z' 11 | - snyk > @snyk/dep-graph > lodash: 12 | patched: '2019-07-07T20:07:54.255Z' 13 | - snyk > inquirer > lodash: 14 | patched: '2019-07-07T20:07:54.255Z' 15 | - snyk > snyk-config > lodash: 16 | patched: '2019-07-07T20:07:54.255Z' 17 | - snyk > snyk-mvn-plugin > lodash: 18 | patched: '2019-07-07T20:07:54.255Z' 19 | - snyk > snyk-nodejs-lockfile-parser > lodash: 20 | patched: '2019-07-07T20:07:54.255Z' 21 | - snyk > snyk-nuget-plugin > lodash: 22 | patched: '2019-07-07T20:07:54.255Z' 23 | - snyk > @snyk/dep-graph > graphlib > lodash: 24 | patched: '2019-07-07T14:47:05.925Z' 25 | - snyk > snyk-go-plugin > graphlib > lodash: 26 | patched: '2019-07-07T14:47:05.925Z' 27 | - snyk > snyk-nodejs-lockfile-parser > graphlib > lodash: 28 | patched: '2019-07-07T14:47:05.925Z' 29 | - snyk > snyk-php-plugin > @snyk/composer-lockfile-parser > lodash: 30 | patched: '2019-07-07T14:47:05.925Z' 31 | - lodash: 32 | patched: '2019-07-07T14:47:05.925Z' 33 | - snyk > lodash: 34 | patched: '2019-07-07T14:47:05.925Z' 35 | - marpat > snyk > snyk-config > lodash: 36 | patched: '2019-07-07T20:07:54.255Z' 37 | - marpat > snyk > snyk-mvn-plugin > lodash: 38 | patched: '2019-07-07T20:07:54.255Z' 39 | - marpat > snyk > snyk-nodejs-lockfile-parser > lodash: 40 | patched: '2019-07-07T20:07:54.255Z' 41 | - marpat > snyk > snyk-nuget-plugin > lodash: 42 | patched: '2019-07-07T20:07:54.255Z' 43 | - snyk > snyk-php-plugin > @snyk/composer-lockfile-parser > lodash: 44 | patched: '2019-07-07T20:07:54.255Z' 45 | - marpat > snyk > @snyk/dep-graph > graphlib > lodash: 46 | patched: '2019-07-07T20:07:54.255Z' 47 | - marpat > snyk > snyk-go-plugin > graphlib > lodash: 48 | patched: '2019-07-07T20:07:54.255Z' 49 | - marpat > snyk > snyk-nodejs-lockfile-parser > graphlib > lodash: 50 | patched: '2019-07-07T20:07:54.255Z' 51 | - marpat > snyk > snyk-php-plugin > @snyk/composer-lockfile-parser > lodash: 52 | patched: '2019-07-07T20:07:54.255Z' 53 | - lodash: 54 | patched: '2019-07-07T20:07:54.255Z' 55 | - snyk > lodash: 56 | patched: '2019-07-07T20:07:54.255Z' 57 | - marpat > lodash: 58 | patched: '2019-07-07T20:07:54.255Z' 59 | - marpat > snyk > lodash: 60 | patched: '2019-07-07T20:07:54.255Z' 61 | - snyk > @snyk/dep-graph > graphlib > lodash: 62 | patched: '2019-07-07T20:07:54.255Z' 63 | - snyk > snyk-go-plugin > graphlib > lodash: 64 | patched: '2019-07-07T20:07:54.255Z' 65 | - snyk > snyk-nodejs-lockfile-parser > graphlib > lodash: 66 | patched: '2019-07-07T20:07:54.255Z' 67 | - marpat > snyk > @snyk/dep-graph > lodash: 68 | patched: '2019-07-07T20:07:54.255Z' 69 | - marpat > snyk > inquirer > lodash: 70 | patched: '2019-07-07T20:07:54.255Z' 71 | SNYK-JS-HTTPSPROXYAGENT-469131: 72 | - snyk > proxy-agent > https-proxy-agent: 73 | patched: '2019-10-04T04:37:00.187Z' 74 | - snyk > proxy-agent > pac-proxy-agent > https-proxy-agent: 75 | patched: '2019-10-04T04:37:00.187Z' 76 | - marpat > snyk > proxy-agent > https-proxy-agent: 77 | patched: '2019-10-04T04:37:00.187Z' 78 | - marpat > snyk > proxy-agent > pac-proxy-agent > https-proxy-agent: 79 | patched: '2019-10-04T04:37:00.187Z' 80 | SNYK-JS-TREEKILL-536781: 81 | - snyk > snyk-sbt-plugin > tree-kill: 82 | patched: '2019-12-12T07:52:09.956Z' 83 | - marpat > snyk > snyk-sbt-plugin > tree-kill: 84 | patched: '2019-12-12T07:52:09.956Z' 85 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | services: mongodb 5 | install: 6 | - npm install 7 | - npm install marpat 8 | before_script: 9 | - node --version 10 | - npm --version 11 | after_success: npm run coverage -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at lui@mutesymphony.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | 47 | [version]: http://contributor-covenant.org/version/1/4/ 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lui de la Parra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/placeholder.md: -------------------------------------------------------------------------------- 1 | ## Nothing To See Here 2 | 3 | # Just a file to hold the directory 4 | -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luidog/fms-api-client/753bbda03113cbc1db9a1d5a13ef9c5e2407282f/docs/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: hsl(240, 100%, 50%); } 17 | 18 | /* a comment */ 19 | .com { 20 | color: hsl(0, 0%, 60%); } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: hsl(240, 100%, 32%); } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: hsl(240, 100%, 40%); } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #000000; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #000000; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #000000; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /examples/authentication.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log, store } = require('./services'); 4 | 5 | //#client-login-example 6 | const login = client => 7 | client.login().then(result => log('client-login-example', result)); 8 | //# 9 | 10 | //#client-logout-example 11 | const logout = client => 12 | client 13 | .login() 14 | .then(() => client.logout()) 15 | .then(result => log('client-logout-example', result)); 16 | //# 17 | 18 | const authentication = client => 19 | Promise.all([login(client), logout(client)]).then(responses => { 20 | store(responses); 21 | return client; 22 | }); 23 | 24 | module.exports = { authentication }; 25 | -------------------------------------------------------------------------------- /examples/create.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log, store } = require('./services'); 4 | 5 | // #create-record-example 6 | const createRecord = client => 7 | client 8 | .create('Heroes', { 9 | name: 'George Lucas' 10 | }) 11 | .then(result => log('create-record-example', result)); 12 | // # 13 | 14 | //#create-record-merge 15 | const mergeDataOnCreate = client => 16 | client 17 | .create( 18 | 'Heroes', 19 | { 20 | name: 'George Lucas' 21 | }, 22 | { merge: true } 23 | ) 24 | .then(result => log('create-record-merge-example', result)); 25 | // # 26 | 27 | //#create-many-records 28 | const createManyRecords = client => 29 | Promise.all([ 30 | client.create('Heroes', { name: 'Anakin Skywalker' }, { merge: true }), 31 | client.create('Heroes', { name: 'Obi-Wan' }, { merge: true }), 32 | client.create('Heroes', { name: 'Yoda' }, { merge: true }) 33 | ]).then(result => log('create-many-records-example', result)); 34 | //# 35 | 36 | //#trigger-scripts-on-create 37 | const triggerScriptsOnCreate = client => 38 | client 39 | .create( 40 | 'Heroes', 41 | { name: 'Anakin Skywalker' }, 42 | { 43 | merge: true, 44 | scripts: [ 45 | { name: 'Create Droids', param: { droids: ['C3-PO', 'R2-D2'] } } 46 | ] 47 | } 48 | ) 49 | .then(result => log('trigger-scripts-on-create-example', result)); 50 | //# 51 | const creates = (client, examples) => 52 | Promise.all([ 53 | createRecord(client), 54 | mergeDataOnCreate(client), 55 | createManyRecords(client), 56 | triggerScriptsOnCreate(client) 57 | ]).then(responses => { 58 | store(responses); 59 | return client; 60 | }); 61 | 62 | module.exports = { creates }; 63 | -------------------------------------------------------------------------------- /examples/datastore.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { examples } = require('./services'); 4 | const { Filemaker } = require('../index.js'); 5 | 6 | const removeExampleRecords = client => 7 | Filemaker.findOne({ _id: client._id }).then(client => 8 | examples.map(object => 9 | client 10 | .delete('Heroes', object.recordId) 11 | .catch(error => console.log('remove-error', error)) 12 | ) 13 | ); 14 | 15 | const datastore = client => Promise.all([removeExampleRecords(client)]); 16 | 17 | module.exports = { datastore }; 18 | -------------------------------------------------------------------------------- /examples/delete.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./services'); 4 | 5 | //#delete-record-example 6 | const deleteRecords = client => 7 | client 8 | .find('Heroes', [{ name: 'yoda' }], { limit: 1 }) 9 | .then(response => response.data[0].recordId) 10 | .then(recordId => client.delete('Heroes', recordId)) 11 | .then(result => log('delete-record-example', result)); 12 | //# 13 | 14 | const revive = client => client.create('Heroes', { name: 'yoda' }); 15 | 16 | const deletes = (client, examples) => 17 | Promise.all([deleteRecords(client), revive(client)]).then( 18 | responses => client 19 | ); 20 | 21 | module.exports = { deletes }; 22 | -------------------------------------------------------------------------------- /examples/duplicate.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./services'); 4 | 5 | const duplicateHero = client => 6 | client.find('Heroes', { name: 'yoda' }, { limit: 1 }).then( 7 | response => 8 | //#duplicate-record-example 9 | client 10 | .duplicate('Heroes', response.data[0].recordId) 11 | .then(result => log('duplicate-record-example', result)) 12 | //# 13 | ); 14 | 15 | const duplicate = (client, examples) => 16 | Promise.all([duplicateHero(client)]).then(responses => client); 17 | 18 | module.exports = { duplicate }; 19 | -------------------------------------------------------------------------------- /examples/edit.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log, store } = require('./services'); 4 | 5 | //#edit-record-example 6 | const editRecord = client => 7 | client 8 | .find('Heroes', [{ name: 'Anakin Skywalker' }], { limit: 1 }) 9 | .then(response => response.data[0].recordId) 10 | .then(recordId => client.edit('Heroes', recordId, { name: 'Darth Vader' })) 11 | .then(result => log('edit-record-example', result)); 12 | //# 13 | 14 | //#edit-record-merge-example 15 | const editRecordMerge = client => 16 | client 17 | .find('Heroes', [{ name: 'Anakin Skywalker' }], { limit: 1 }) 18 | .then(response => response.data[0].recordId) 19 | .then(recordId => 20 | client.edit('Heroes', recordId, { name: 'Darth Vader' }, { merge: true }) 21 | ) 22 | .then(result => log('edit-record-merge-example', result)); 23 | //# 24 | 25 | const edits = (client, examples) => 26 | Promise.all([editRecord(client), editRecordMerge(client)]).then(responses => { 27 | store(responses); 28 | return client; 29 | }); 30 | 31 | module.exports = { edits }; 32 | -------------------------------------------------------------------------------- /examples/find.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./services'); 4 | 5 | //#find-records-example 6 | const findRecords = client => 7 | client 8 | .find('Heroes', [{ name: 'Anakin Skywalker' }], { limit: 1 }) 9 | .then(result => log('find-records-example', result)); 10 | //# 11 | 12 | const finds = client => 13 | Promise.all([findRecords(client)]).then(responses => client); 14 | 15 | module.exports = { finds }; 16 | -------------------------------------------------------------------------------- /examples/get.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./services'); 4 | 5 | const getHero = client => 6 | client.find('Heroes', { name: 'yoda' }, { limit: 1 }).then( 7 | response => 8 | //#get-record-example 9 | client 10 | .get('Heroes', response.data[0].recordId) 11 | .then(result => log('get-record-example', result)) 12 | //# 13 | ); 14 | 15 | const gets = (client, examples) => 16 | Promise.all([getHero(client)]).then(responses => client); 17 | 18 | module.exports = { gets }; 19 | -------------------------------------------------------------------------------- /examples/globals.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./services'); 4 | 5 | //#set-globals-example 6 | const setGlobals = client => 7 | client 8 | .globals({ 'Globals::ship': 'Millenium Falcon' }) 9 | .then(result => log('set-globals-example', result)); 10 | //# 11 | 12 | const globals = (client, examples) => 13 | Promise.all([setGlobals(client)]).then(responses => client); 14 | 15 | module.exports = { globals }; 16 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const environment = require('dotenv'); 4 | const varium = require('varium'); 5 | const { Filemaker } = require('../index.js'); 6 | const { authentication } = require('./authentication.examples'); 7 | const { metadata } = require('./metadata.examples'); 8 | const { creates } = require('./create.examples'); 9 | const { duplicate } = require('./duplicate.examples'); 10 | const { gets } = require('./get.examples'); 11 | const { lists } = require('./list.examples'); 12 | const { finds } = require('./find.examples'); 13 | const { edits } = require('./edit.examples'); 14 | const { script } = require('./script.examples'); 15 | const { scripts } = require('./scripts.examples'); 16 | const { globals } = require('./globals.examples'); 17 | const { deletes } = require('./delete.examples'); 18 | const { uploads } = require('./upload.examples'); 19 | const { utilities } = require('./utility.examples'); 20 | const { datastore } = require('./datastore.examples'); 21 | 22 | environment.config({ path: './test/.env' }); 23 | 24 | varium({ manifestPath: '../test/env.manifest' }); 25 | 26 | //#datastore-connect-example 27 | const { connect } = require('marpat'); 28 | connect('nedb://memory') 29 | //# 30 | .then(db => { 31 | //#client-create-example 32 | const client = Filemaker.create({ 33 | name: process.env.CLIENT_NAME, 34 | database: process.env.DATABASE, 35 | concurrency: 3, 36 | server: process.env.SERVER, 37 | user: process.env.USERNAME, 38 | password: process.env.PASSWORD, 39 | usage: process.env.CLIENT_USAGE_TRACKING 40 | }); 41 | //# 42 | //#client-save-example 43 | return client.save(); 44 | }) 45 | .then(client => authentication(client)) 46 | .then(client => metadata(client)) 47 | .then(client => creates(client)) 48 | .then(client => duplicate(client)) 49 | .then(client => gets(client)) 50 | .then(client => lists(client)) 51 | .then(client => finds(client)) 52 | .then(client => edits(client)) 53 | .then(client => scripts(client)) 54 | .then(client => script(client)) 55 | .then(client => globals(client)) 56 | .then(client => deletes(client)) 57 | .then(client => uploads(client)) 58 | .then(client => utilities(client)) 59 | // # 60 | .then(client => datastore(client)) 61 | .catch(error => console.log('error', error)); 62 | -------------------------------------------------------------------------------- /examples/list.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./services'); 4 | 5 | //#list-records-example 6 | const listHeroes = client => 7 | client 8 | .list('Heroes', { limit: 2 }) 9 | .then(result => log('list-records-example', result)); 10 | //# 11 | 12 | const lists = (client, examples) => 13 | Promise.all([listHeroes(client)]).then(responses => client); 14 | 15 | module.exports = { lists }; 16 | -------------------------------------------------------------------------------- /examples/metadata.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log, store } = require('./services'); 4 | 5 | //#product-info-example 6 | const productInfo = client => 7 | client.productInfo().then(result => log('product-info-example', result)); 8 | //# 9 | 10 | //#databases-info-example 11 | const databases = client => 12 | client.databases().then(result => log('databases-info-example', result)); 13 | //# 14 | 15 | //#layouts-example 16 | const layouts = client => 17 | client.layouts().then(result => log('layouts-example', result)); 18 | //# 19 | 20 | //#layout-details-example 21 | const layout = client => 22 | client 23 | .layout(process.env.LAYOUT) 24 | .then(result => log('layout-details-example', result)); 25 | //# 26 | 27 | //#scripts-example 28 | const scripts = client => 29 | client 30 | .scripts(process.env.LAYOUT) 31 | .then(result => log('scripts-example', result)); 32 | //# 33 | 34 | const metadata = client => 35 | Promise.all([ 36 | productInfo(client), 37 | databases(client), 38 | layouts(client), 39 | layout(client), 40 | scripts(client) 41 | ]).then(responses => { 42 | store(responses); 43 | return client; 44 | }); 45 | 46 | module.exports = { metadata }; 47 | -------------------------------------------------------------------------------- /examples/results/client-login-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "shuttle-tydirium", 3 | "id": "r2d2-c3po-l3-37-bb-8" 4 | } 5 | -------------------------------------------------------------------------------- /examples/results/client-logout-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": {}, 3 | "messages": [ 4 | { 5 | "code": "0", 6 | "message": "OK" 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /examples/results/client-reset-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Client Reset" 3 | } 4 | -------------------------------------------------------------------------------- /examples/results/client-status-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "since": "2019-09-15T15:30:43-07:00", 4 | "in": "438 Bytes", 5 | "out": "29.8 kB" 6 | }, 7 | "queue": [], 8 | "pending": [], 9 | "sessions": [ 10 | { 11 | "issued": "2019-09-15T15:30:44-07:00", 12 | "expires": "2019-09-15T15:45:47-07:00", 13 | "id": "r2d2-c3po-l3-37-bb-8", 14 | "active": false 15 | }, 16 | { 17 | "issued": "2019-09-15T15:30:44-07:00", 18 | "expires": "2019-09-15T15:45:44-07:00", 19 | "id": "r2d2-c3po-l3-37-bb-8", 20 | "active": true 21 | }, 22 | { 23 | "issued": "2019-09-15T15:30:44-07:00", 24 | "expires": "2019-09-15T15:45:44-07:00", 25 | "id": "r2d2-c3po-l3-37-bb-8", 26 | "active": true 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /examples/results/container-data-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "IMG_0001.PNG", 4 | "path": "assets/IMG_0001.PNG" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /examples/results/create-many-records-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Anakin Skywalker", 4 | "recordId": "1138", 5 | "modId": "327" 6 | }, 7 | { 8 | "name": "Obi-Wan", 9 | "recordId": "1138", 10 | "modId": "327" 11 | }, 12 | { 13 | "name": "Yoda", 14 | "recordId": "1138", 15 | "modId": "327" 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /examples/results/create-record-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "recordId": "1138", 3 | "modId": "327" 4 | } 5 | -------------------------------------------------------------------------------- /examples/results/create-record-merge-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "George Lucas", 3 | "recordId": "1138", 4 | "modId": "327" 5 | } 6 | -------------------------------------------------------------------------------- /examples/results/databases-info-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "databases": [ 3 | { 4 | "name": "archives" 5 | }, 6 | { 7 | "name": "fms-api-app" 8 | }, 9 | { 10 | "name": "fms-api-app-two" 11 | }, 12 | { 13 | "name": "node-red-app" 14 | }, 15 | { 16 | "name": "node-red-contrib-filemaker" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /examples/results/databases-utility-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "databases": [ 3 | { 4 | "name": "fms-api-app" 5 | }, 6 | { 7 | "name": "node-red-app" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /examples/results/delete-record-example.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/results/duplicate-record-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "recordId": "1138", 3 | "modId": "327" 4 | } 5 | -------------------------------------------------------------------------------- /examples/results/edit-record-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "modId": "327" 3 | } 4 | -------------------------------------------------------------------------------- /examples/results/edit-record-merge-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Darth Vader", 3 | "recordId": "1138", 4 | "modId": "327" 5 | } 6 | -------------------------------------------------------------------------------- /examples/results/field-data-utility-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Yoda", 4 | "image": "https://some-server.com/Streaming_SSL/MainDB/27EA97DBE4B14E5BE2E09713E6C5CF75604F9458EC0885B00E436B618C798D87?RCType=EmbeddedRCFileProcessor", 5 | "object": "", 6 | "array": "", 7 | "height": "", 8 | "id": "r2d2-c3po-l3-37-bb-8", 9 | "imageName": "placeholder.md", 10 | "creationAccountName": "obi-wan", 11 | "creationTimestamp": "05/25/1977 6:00:00", 12 | "modificationAccountName": "obi-wan", 13 | "modificationTimestamp": "05/25/1977 6:00:00", 14 | "Vehicles::name": "", 15 | "recordId": "1138", 16 | "modId": "327" 17 | }, 18 | { 19 | "name": "yoda", 20 | "image": "", 21 | "object": "", 22 | "array": "", 23 | "height": "", 24 | "id": "r2d2-c3po-l3-37-bb-8", 25 | "imageName": "", 26 | "creationAccountName": "obi-wan", 27 | "creationTimestamp": "05/25/1977 6:00:00", 28 | "modificationAccountName": "obi-wan", 29 | "modificationTimestamp": "05/25/1977 6:00:00", 30 | "Vehicles::name": "", 31 | "recordId": "1138", 32 | "modId": "327" 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /examples/results/field-data-utility-original-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fieldData": { 4 | "name": "Yoda", 5 | "image": "https://some-server.com/Streaming_SSL/MainDB/8F7489E114CFFE0FC13E8A45A2F52D77A50ABFB36B05C0858114F37EA3141634?RCType=EmbeddedRCFileProcessor", 6 | "object": "", 7 | "array": "", 8 | "height": "", 9 | "id": "r2d2-c3po-l3-37-bb-8", 10 | "imageName": "placeholder.md", 11 | "creationAccountName": "obi-wan", 12 | "creationTimestamp": "05/25/1977 6:00:00", 13 | "modificationAccountName": "obi-wan", 14 | "modificationTimestamp": "05/25/1977 6:00:00", 15 | "Vehicles::name": "" 16 | }, 17 | "portalData": { 18 | "Planets": [], 19 | "Vehicles": [] 20 | }, 21 | "recordId": "1138", 22 | "modId": "327", 23 | "portalDataInfo": [ 24 | { 25 | "database": "fms-api-app", 26 | "table": "Planets", 27 | "foundCount": 0, 28 | "returnedCount": 0 29 | }, 30 | { 31 | "database": "fms-api-app", 32 | "table": "Vehicles", 33 | "foundCount": 0, 34 | "returnedCount": 0 35 | } 36 | ] 37 | }, 38 | { 39 | "fieldData": { 40 | "name": "yoda", 41 | "image": "", 42 | "object": "", 43 | "array": "", 44 | "height": "", 45 | "id": "r2d2-c3po-l3-37-bb-8", 46 | "imageName": "", 47 | "creationAccountName": "obi-wan", 48 | "creationTimestamp": "05/25/1977 6:00:00", 49 | "modificationAccountName": "obi-wan", 50 | "modificationTimestamp": "05/25/1977 6:00:00", 51 | "Vehicles::name": "" 52 | }, 53 | "portalData": { 54 | "Planets": [], 55 | "Vehicles": [] 56 | }, 57 | "recordId": "1138", 58 | "modId": "327", 59 | "portalDataInfo": [ 60 | { 61 | "database": "fms-api-app", 62 | "table": "Planets", 63 | "foundCount": 0, 64 | "returnedCount": 0 65 | }, 66 | { 67 | "database": "fms-api-app", 68 | "table": "Vehicles", 69 | "foundCount": 0, 70 | "returnedCount": 0 71 | } 72 | ] 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /examples/results/find-records-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataInfo": { 3 | "database": "fms-api-app", 4 | "layout": "Heroes", 5 | "table": "Heroes", 6 | "totalRecordCount": "1977", 7 | "foundCount": 153, 8 | "returnedCount": 1 9 | }, 10 | "data": [ 11 | { 12 | "fieldData": { 13 | "name": "Anakin Skywalker", 14 | "image": "", 15 | "object": "", 16 | "array": "", 17 | "height": "", 18 | "id": "r2d2-c3po-l3-37-bb-8", 19 | "imageName": "", 20 | "creationAccountName": "obi-wan", 21 | "creationTimestamp": "05/25/1977 6:00:00", 22 | "modificationAccountName": "obi-wan", 23 | "modificationTimestamp": "05/25/1977 6:00:00", 24 | "Vehicles::name": "" 25 | }, 26 | "portalData": { 27 | "Planets": [], 28 | "Vehicles": [] 29 | }, 30 | "recordId": "1138", 31 | "modId": "327", 32 | "portalDataInfo": [ 33 | { 34 | "database": "fms-api-app", 35 | "table": "Planets", 36 | "foundCount": 0, 37 | "returnedCount": 0 38 | }, 39 | { 40 | "database": "fms-api-app", 41 | "table": "Vehicles", 42 | "foundCount": 0, 43 | "returnedCount": 0 44 | } 45 | ] 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /examples/results/get-record-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataInfo": { 3 | "database": "fms-api-app", 4 | "layout": "Heroes", 5 | "table": "Heroes", 6 | "totalRecordCount": "1977", 7 | "foundCount": 1, 8 | "returnedCount": 1 9 | }, 10 | "data": [ 11 | { 12 | "fieldData": { 13 | "name": "yoda", 14 | "image": "https://some-server.com/Streaming_SSL/MainDB/E8BDBF29B9388B2F212002F7F6A7D6B8EF87A16E7E2D231EA27B8FC32147F64D?RCType=EmbeddedRCFileProcessor", 15 | "object": "", 16 | "array": "", 17 | "height": "", 18 | "id": "r2d2-c3po-l3-37-bb-8", 19 | "imageName": "placeholder.md", 20 | "creationAccountName": "obi-wan", 21 | "creationTimestamp": "05/25/1977 6:00:00", 22 | "modificationAccountName": "obi-wan", 23 | "modificationTimestamp": "05/25/1977 6:00:00", 24 | "Vehicles::name": "" 25 | }, 26 | "portalData": { 27 | "Planets": [], 28 | "Vehicles": [] 29 | }, 30 | "recordId": "1138", 31 | "modId": "327", 32 | "portalDataInfo": [ 33 | { 34 | "database": "fms-api-app", 35 | "table": "Planets", 36 | "foundCount": 0, 37 | "returnedCount": 0 38 | }, 39 | { 40 | "database": "fms-api-app", 41 | "table": "Vehicles", 42 | "foundCount": 0, 43 | "returnedCount": 0 44 | } 45 | ] 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /examples/results/layout-details-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "fieldMetaData": [ 3 | { 4 | "name": "name", 5 | "type": "normal", 6 | "displayType": "editText", 7 | "result": "text", 8 | "global": false, 9 | "autoEnter": false, 10 | "fourDigitYear": false, 11 | "maxRepeat": 1, 12 | "maxCharacters": 0, 13 | "notEmpty": false, 14 | "numeric": false, 15 | "timeOfDay": false, 16 | "repetitionStart": 1, 17 | "repetitionEnd": 1 18 | }, 19 | { 20 | "name": "image", 21 | "type": "normal", 22 | "displayType": "editText", 23 | "result": "container", 24 | "global": false, 25 | "autoEnter": false, 26 | "fourDigitYear": false, 27 | "maxRepeat": 1, 28 | "maxCharacters": 0, 29 | "notEmpty": false, 30 | "numeric": false, 31 | "timeOfDay": false, 32 | "repetitionStart": 1, 33 | "repetitionEnd": 1 34 | }, 35 | { 36 | "name": "object", 37 | "type": "normal", 38 | "displayType": "editText", 39 | "result": "text", 40 | "global": false, 41 | "autoEnter": false, 42 | "fourDigitYear": false, 43 | "maxRepeat": 1, 44 | "maxCharacters": 0, 45 | "notEmpty": false, 46 | "numeric": false, 47 | "timeOfDay": false, 48 | "repetitionStart": 1, 49 | "repetitionEnd": 1 50 | }, 51 | { 52 | "name": "array", 53 | "type": "normal", 54 | "displayType": "editText", 55 | "result": "text", 56 | "global": false, 57 | "autoEnter": false, 58 | "fourDigitYear": false, 59 | "maxRepeat": 1, 60 | "maxCharacters": 0, 61 | "notEmpty": false, 62 | "numeric": false, 63 | "timeOfDay": false, 64 | "repetitionStart": 1, 65 | "repetitionEnd": 1 66 | }, 67 | { 68 | "name": "height", 69 | "type": "normal", 70 | "displayType": "editText", 71 | "result": "text", 72 | "global": false, 73 | "autoEnter": false, 74 | "fourDigitYear": false, 75 | "maxRepeat": 1, 76 | "maxCharacters": 0, 77 | "notEmpty": false, 78 | "numeric": false, 79 | "timeOfDay": false, 80 | "repetitionStart": 1, 81 | "repetitionEnd": 1 82 | }, 83 | { 84 | "name": "id", 85 | "type": "normal", 86 | "displayType": "editText", 87 | "result": "text", 88 | "global": false, 89 | "autoEnter": true, 90 | "fourDigitYear": false, 91 | "maxRepeat": 1, 92 | "maxCharacters": 0, 93 | "notEmpty": false, 94 | "numeric": false, 95 | "timeOfDay": false, 96 | "repetitionStart": 1, 97 | "repetitionEnd": 1 98 | }, 99 | { 100 | "name": "imageName", 101 | "type": "calculation", 102 | "displayType": "editText", 103 | "result": "text", 104 | "global": false, 105 | "autoEnter": false, 106 | "fourDigitYear": false, 107 | "maxRepeat": 1, 108 | "maxCharacters": 0, 109 | "notEmpty": false, 110 | "numeric": false, 111 | "timeOfDay": false, 112 | "repetitionStart": 1, 113 | "repetitionEnd": 1 114 | }, 115 | { 116 | "name": "creationAccountName", 117 | "type": "normal", 118 | "displayType": "editText", 119 | "result": "text", 120 | "global": false, 121 | "autoEnter": true, 122 | "fourDigitYear": false, 123 | "maxRepeat": 1, 124 | "maxCharacters": 0, 125 | "notEmpty": false, 126 | "numeric": false, 127 | "timeOfDay": false, 128 | "repetitionStart": 1, 129 | "repetitionEnd": 1 130 | }, 131 | { 132 | "name": "creationTimestamp", 133 | "type": "normal", 134 | "displayType": "editText", 135 | "result": "timeStamp", 136 | "global": false, 137 | "autoEnter": true, 138 | "fourDigitYear": false, 139 | "maxRepeat": 1, 140 | "maxCharacters": 0, 141 | "notEmpty": false, 142 | "numeric": false, 143 | "timeOfDay": false, 144 | "repetitionStart": 1, 145 | "repetitionEnd": 1 146 | }, 147 | { 148 | "name": "modificationAccountName", 149 | "type": "normal", 150 | "displayType": "editText", 151 | "result": "text", 152 | "global": false, 153 | "autoEnter": true, 154 | "fourDigitYear": false, 155 | "maxRepeat": 1, 156 | "maxCharacters": 0, 157 | "notEmpty": false, 158 | "numeric": false, 159 | "timeOfDay": false, 160 | "repetitionStart": 1, 161 | "repetitionEnd": 1 162 | }, 163 | { 164 | "name": "modificationTimestamp", 165 | "type": "normal", 166 | "displayType": "editText", 167 | "result": "timeStamp", 168 | "global": false, 169 | "autoEnter": true, 170 | "fourDigitYear": false, 171 | "maxRepeat": 1, 172 | "maxCharacters": 0, 173 | "notEmpty": false, 174 | "numeric": false, 175 | "timeOfDay": false, 176 | "repetitionStart": 1, 177 | "repetitionEnd": 1 178 | }, 179 | { 180 | "name": "Vehicles::name", 181 | "type": "normal", 182 | "displayType": "editText", 183 | "result": "text", 184 | "global": false, 185 | "autoEnter": false, 186 | "fourDigitYear": false, 187 | "maxRepeat": 1, 188 | "maxCharacters": 0, 189 | "notEmpty": false, 190 | "numeric": false, 191 | "timeOfDay": false, 192 | "repetitionStart": 1, 193 | "repetitionEnd": 1 194 | } 195 | ], 196 | "portalMetaData": { 197 | "Planets": [ 198 | { 199 | "name": "Planets::name", 200 | "type": "normal", 201 | "displayType": "editText", 202 | "result": "text", 203 | "global": false, 204 | "autoEnter": false, 205 | "fourDigitYear": false, 206 | "maxRepeat": 1, 207 | "maxCharacters": 0, 208 | "notEmpty": false, 209 | "numeric": false, 210 | "timeOfDay": false, 211 | "repetitionStart": 1, 212 | "repetitionEnd": 1 213 | }, 214 | { 215 | "name": "Species::name", 216 | "type": "normal", 217 | "displayType": "editText", 218 | "result": "text", 219 | "global": false, 220 | "autoEnter": false, 221 | "fourDigitYear": false, 222 | "maxRepeat": 1, 223 | "maxCharacters": 0, 224 | "notEmpty": false, 225 | "numeric": false, 226 | "timeOfDay": false, 227 | "repetitionStart": 1, 228 | "repetitionEnd": 1 229 | } 230 | ], 231 | "Vehicles": [ 232 | { 233 | "name": "Vehicles::name", 234 | "type": "normal", 235 | "displayType": "editText", 236 | "result": "text", 237 | "global": false, 238 | "autoEnter": false, 239 | "fourDigitYear": false, 240 | "maxRepeat": 1, 241 | "maxCharacters": 0, 242 | "notEmpty": false, 243 | "numeric": false, 244 | "timeOfDay": false, 245 | "repetitionStart": 1, 246 | "repetitionEnd": 1 247 | }, 248 | { 249 | "name": "Vehicles::type", 250 | "type": "normal", 251 | "displayType": "editText", 252 | "result": "text", 253 | "global": false, 254 | "autoEnter": false, 255 | "fourDigitYear": false, 256 | "maxRepeat": 1, 257 | "maxCharacters": 0, 258 | "notEmpty": false, 259 | "numeric": false, 260 | "timeOfDay": false, 261 | "repetitionStart": 1, 262 | "repetitionEnd": 1 263 | } 264 | ] 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /examples/results/layouts-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "layouts": [ 3 | { 4 | "name": "Parameter Storage Demo" 5 | }, 6 | { 7 | "name": "Chalmuns" 8 | }, 9 | { 10 | "name": "Card Windows", 11 | "isFolder": true, 12 | "folderLayoutNames": [ 13 | { 14 | "name": "Authorization Window" 15 | } 16 | ] 17 | }, 18 | { 19 | "name": "API Integration Layouts", 20 | "isFolder": true, 21 | "folderLayoutNames": [ 22 | { 23 | "name": "authentication" 24 | } 25 | ] 26 | }, 27 | { 28 | "name": "Base Tables", 29 | "isFolder": true, 30 | "folderLayoutNames": [ 31 | { 32 | "name": "RequestLog" 33 | }, 34 | { 35 | "name": "SyncLog" 36 | }, 37 | { 38 | "name": "AuthenticationStore" 39 | } 40 | ] 41 | }, 42 | { 43 | "name": "Heroes" 44 | }, 45 | { 46 | "name": "Transform" 47 | }, 48 | { 49 | "name": "Hero Search" 50 | }, 51 | { 52 | "name": "Hero" 53 | }, 54 | { 55 | "name": "Globals" 56 | }, 57 | { 58 | "name": "Scripts" 59 | }, 60 | { 61 | "name": "Logs" 62 | }, 63 | { 64 | "name": "Planets" 65 | }, 66 | { 67 | "name": "Driods" 68 | }, 69 | { 70 | "name": "Vehicles" 71 | }, 72 | { 73 | "name": "Species" 74 | }, 75 | { 76 | "name": "Images" 77 | } 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /examples/results/list-records-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataInfo": { 3 | "database": "fms-api-app", 4 | "layout": "Heroes", 5 | "table": "Heroes", 6 | "totalRecordCount": "1977", 7 | "foundCount": 29493, 8 | "returnedCount": 2 9 | }, 10 | "data": [ 11 | { 12 | "fieldData": { 13 | "name": "George Lucas", 14 | "image": "https://some-server.com/Streaming_SSL/MainDB/6BA0C4191156DE2DF3036BB1ABF079B0CF8BF6F286316A5603EF789FB45C66D7.png?RCType=EmbeddedRCFileProcessor", 15 | "object": "", 16 | "array": "", 17 | "height": "", 18 | "id": "r2d2-c3po-l3-37-bb-8", 19 | "imageName": "IMG_0001.PNG", 20 | "creationAccountName": "obi-wan", 21 | "creationTimestamp": "05/25/1977 6:00:00", 22 | "modificationAccountName": "obi-wan", 23 | "modificationTimestamp": "05/25/1977 6:00:00", 24 | "Vehicles::name": "test" 25 | }, 26 | "portalData": { 27 | "Planets": [], 28 | "Vehicles": [ 29 | { 30 | "recordId": "1138", 31 | "Vehicles::name": "test", 32 | "Vehicles::type": "", 33 | "modId": "327" 34 | } 35 | ] 36 | }, 37 | "recordId": "1138", 38 | "modId": "327", 39 | "portalDataInfo": [ 40 | { 41 | "database": "fms-api-app", 42 | "table": "Planets", 43 | "foundCount": 0, 44 | "returnedCount": 0 45 | }, 46 | { 47 | "database": "fms-api-app", 48 | "table": "Vehicles", 49 | "foundCount": 1, 50 | "returnedCount": 1 51 | } 52 | ] 53 | }, 54 | { 55 | "fieldData": { 56 | "name": "George Lucas", 57 | "image": "", 58 | "object": "", 59 | "array": "", 60 | "height": "", 61 | "id": "r2d2-c3po-l3-37-bb-8", 62 | "imageName": "", 63 | "creationAccountName": "obi-wan", 64 | "creationTimestamp": "05/25/1977 6:00:00", 65 | "modificationAccountName": "obi-wan", 66 | "modificationTimestamp": "05/25/1977 6:00:00", 67 | "Vehicles::name": "" 68 | }, 69 | "portalData": { 70 | "Planets": [], 71 | "Vehicles": [] 72 | }, 73 | "recordId": "1138", 74 | "modId": "327", 75 | "portalDataInfo": [ 76 | { 77 | "database": "fms-api-app", 78 | "table": "Planets", 79 | "foundCount": 0, 80 | "returnedCount": 0 81 | }, 82 | { 83 | "database": "fms-api-app", 84 | "table": "Vehicles", 85 | "foundCount": 0, 86 | "returnedCount": 0 87 | } 88 | ] 89 | } 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /examples/results/product-info-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FileMaker Data API Engine", 3 | "buildDate": "07/05/2019", 4 | "version": "18.0.2.217", 5 | "dateFormat": "MM/dd/yyyy", 6 | "timeFormat": "HH:mm:ss", 7 | "timeStampFormat": "MM/dd/yyyy HH:mm:ss" 8 | } 9 | -------------------------------------------------------------------------------- /examples/results/product-info-utility-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FileMaker Data API Engine", 3 | "buildDate": "07/05/2019", 4 | "version": "18.0.2.217", 5 | "dateFormat": "MM/dd/yyyy", 6 | "timeFormat": "HH:mm:ss", 7 | "timeStampFormat": "MM/dd/yyyy HH:mm:ss" 8 | } 9 | -------------------------------------------------------------------------------- /examples/results/record-id-utility-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | "751326", 3 | "751329" 4 | ] 5 | -------------------------------------------------------------------------------- /examples/results/record-id-utility-original-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fieldData": { 4 | "name": "Yoda", 5 | "image": "https://some-server.com/Streaming_SSL/MainDB/1D86123EDDA1866BDDC2962F71108F8D78EFCAE349E66934D2737E3615918167?RCType=EmbeddedRCFileProcessor", 6 | "object": "", 7 | "array": "", 8 | "height": "", 9 | "id": "r2d2-c3po-l3-37-bb-8", 10 | "imageName": "placeholder.md", 11 | "creationAccountName": "obi-wan", 12 | "creationTimestamp": "05/25/1977 6:00:00", 13 | "modificationAccountName": "obi-wan", 14 | "modificationTimestamp": "05/25/1977 6:00:00", 15 | "Vehicles::name": "" 16 | }, 17 | "portalData": { 18 | "Planets": [], 19 | "Vehicles": [] 20 | }, 21 | "recordId": "1138", 22 | "modId": "327", 23 | "portalDataInfo": [ 24 | { 25 | "database": "fms-api-app", 26 | "table": "Planets", 27 | "foundCount": 0, 28 | "returnedCount": 0 29 | }, 30 | { 31 | "database": "fms-api-app", 32 | "table": "Vehicles", 33 | "foundCount": 0, 34 | "returnedCount": 0 35 | } 36 | ] 37 | }, 38 | { 39 | "fieldData": { 40 | "name": "yoda", 41 | "image": "", 42 | "object": "", 43 | "array": "", 44 | "height": "", 45 | "id": "r2d2-c3po-l3-37-bb-8", 46 | "imageName": "", 47 | "creationAccountName": "obi-wan", 48 | "creationTimestamp": "05/25/1977 6:00:00", 49 | "modificationAccountName": "obi-wan", 50 | "modificationTimestamp": "05/25/1977 6:00:00", 51 | "Vehicles::name": "" 52 | }, 53 | "portalData": { 54 | "Planets": [], 55 | "Vehicles": [] 56 | }, 57 | "recordId": "1138", 58 | "modId": "327", 59 | "portalDataInfo": [ 60 | { 61 | "database": "fms-api-app", 62 | "table": "Planets", 63 | "foundCount": 0, 64 | "returnedCount": 0 65 | }, 66 | { 67 | "database": "fms-api-app", 68 | "table": "Vehicles", 69 | "foundCount": 0, 70 | "returnedCount": 0 71 | } 72 | ] 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /examples/results/run-script-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptResult": { 3 | "answer": "Han shot first" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/results/run-script-string-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptResult": { 3 | "answer": "? * Line 1, Column 1\r Syntax error: value, object or array expected. shot first" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/results/run-script-with-parameters-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptResult": { 3 | "answer": "Han shot first" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/results/run-scripts-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptResult.presort": { 3 | "answer": "Han shot first" 4 | }, 5 | "scriptResult": { 6 | "answer": "Han shot first" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/results/run-single-script-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptResult": { 3 | "answer": "Han shot first" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/results/script-run-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptResult": { 3 | "answer": "Han shot first" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/results/script-string-run-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptResult": { 3 | "answer": "? * Line 1, Column 1\r Syntax error: value, object or array expected. shot first" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/results/script-string-run-with-parameters-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptResult": { 3 | "answer": "Han shot first" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/results/script-trigger-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptResult": { 3 | "answer": "Han shot first" 4 | }, 5 | "scriptError": "0" 6 | } 7 | -------------------------------------------------------------------------------- /examples/results/scripts-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": [ 3 | { 4 | "name": "Upon Open", 5 | "isFolder": false 6 | }, 7 | { 8 | "name": "FMS Triggered Script", 9 | "isFolder": false 10 | }, 11 | { 12 | "name": "node-red-script", 13 | "isFolder": false 14 | }, 15 | { 16 | "name": "example script", 17 | "isFolder": false 18 | }, 19 | { 20 | "name": "Error Script", 21 | "isFolder": false 22 | }, 23 | { 24 | "name": "Non JSON Script", 25 | "isFolder": false 26 | }, 27 | { 28 | "name": "Create Droids", 29 | "isFolder": false 30 | }, 31 | { 32 | "name": "Delete All Records", 33 | "isFolder": false 34 | }, 35 | { 36 | "name": "Buttons", 37 | "folderScriptNames": [ 38 | { 39 | "name": "New Clip Record", 40 | "isFolder": false 41 | }, 42 | { 43 | "name": "Update Clip Button", 44 | "isFolder": false 45 | }, 46 | { 47 | "name": "Replace Clip Record Data", 48 | "isFolder": false 49 | }, 50 | { 51 | "name": "Delete All Clip Records", 52 | "isFolder": false 53 | }, 54 | { 55 | "name": "Push all flagged clips", 56 | "isFolder": false 57 | }, 58 | { 59 | "name": "Set Clipboard", 60 | "isFolder": false 61 | } 62 | ], 63 | "isFolder": true 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /examples/results/set-globals-example.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/results/transform-utility-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "starships": "", 4 | "vehicles": "", 5 | "species": "", 6 | "biography": "", 7 | "birthYear": "", 8 | "id": "r2d2-c3po-l3-37-bb-8", 9 | "name": "Han Solo", 10 | "Planets": [ 11 | { 12 | "recordId": "1138", 13 | "name": "Coriella", 14 | "modId": "327" 15 | } 16 | ], 17 | "Vehicles": [ 18 | { 19 | "recordId": "1138", 20 | "name": "Millenium Falcon", 21 | "type": "Starship", 22 | "modId": "327" 23 | } 24 | ], 25 | "recordId": "1138", 26 | "modId": "327" 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /examples/results/transform-utility-no-convert-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "starships": "", 4 | "vehicles": "", 5 | "species": "", 6 | "biography": "", 7 | "birthYear": "", 8 | "id": "r2d2-c3po-l3-37-bb-8", 9 | "name": "Han Solo", 10 | "Planets": [ 11 | { 12 | "recordId": "1138", 13 | "Planets::name": "Coriella", 14 | "modId": "327" 15 | } 16 | ], 17 | "Vehicles": [ 18 | { 19 | "recordId": "1138", 20 | "Vehicles::name": "Millenium Falcon", 21 | "Vehicles::type": "Starship", 22 | "modId": "327" 23 | } 24 | ], 25 | "recordId": "1138", 26 | "modId": "327" 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /examples/results/transform-utility-original-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fieldData": { 4 | "starships": "", 5 | "vehicles": "", 6 | "species": "", 7 | "biography": "", 8 | "birthYear": "", 9 | "id": "r2d2-c3po-l3-37-bb-8", 10 | "name": "Han Solo" 11 | }, 12 | "portalData": { 13 | "Planets": [ 14 | { 15 | "recordId": "1138", 16 | "Planets::name": "Coriella", 17 | "modId": "327" 18 | } 19 | ], 20 | "Vehicles": [ 21 | { 22 | "recordId": "1138", 23 | "Vehicles::name": "Millenium Falcon", 24 | "Vehicles::type": "Starship", 25 | "modId": "327" 26 | } 27 | ] 28 | }, 29 | "recordId": "1138", 30 | "modId": "327", 31 | "portalDataInfo": [ 32 | { 33 | "database": "fms-api-app", 34 | "table": "Planets", 35 | "foundCount": 1, 36 | "returnedCount": 1 37 | }, 38 | { 39 | "database": "fms-api-app", 40 | "table": "Vehicles", 41 | "foundCount": 1, 42 | "returnedCount": 1 43 | } 44 | ] 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /examples/results/trigger-scripts-on-create-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Anakin Skywalker", 3 | "scriptError": "0", 4 | "recordId": "1138", 5 | "modId": "327" 6 | } 7 | -------------------------------------------------------------------------------- /examples/results/upload-image-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "modId": "327", 3 | "recordId": "1138" 4 | } 5 | -------------------------------------------------------------------------------- /examples/results/upload-specific-record-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "modId": "327", 3 | "recordId": "1138" 4 | } 5 | -------------------------------------------------------------------------------- /examples/schema/data-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "name": "Yoda", 4 | "Vehicles::name": "The Force" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/schema/portals-array-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "portals": [ 3 | { "name": "planets", "limit": 1, "offset": 1 }, 4 | { "name": "vehicles", "limit": 2 } 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/schema/query-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": [ 3 | { 4 | "name": "Han solo", 5 | "Vehicles::name": "Millenium Falcon" 6 | }, 7 | { 8 | "name": "Luke Skywalker", 9 | "Vehicles::name": "X-Wing Starfighter" 10 | }, 11 | { 12 | "age": ">10000" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/schema/scripts-array-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": [ 3 | { 4 | "name": "At Mos Eisley", 5 | "phase": "presort", 6 | "param": "awesome bar" 7 | }, 8 | { 9 | "name": "First Shot", 10 | "phase": "prerequest", 11 | "param": "Han" 12 | }, 13 | { 14 | "name": "Moof Milker", 15 | "param": "Greedo" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /examples/schema/sort-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "sort": [ 3 | { "fieldName": "name", "sortOrder": "ascend" }, 4 | { "fieldName": "modificationTimestamp", "sortOrder": "descend" } 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/script.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./services'); 4 | 5 | //#script-trigger-example 6 | const triggerScript = client => 7 | client 8 | .script('Heroes', 'FMS Triggered Script', { name: 'Han' }) 9 | .then(result => log('script-trigger-example', result)); 10 | //# 11 | 12 | const script = client => 13 | Promise.all([triggerScript(client)]).then(responses => client); 14 | 15 | module.exports = { script }; 16 | -------------------------------------------------------------------------------- /examples/scripts.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./services'); 4 | 5 | //#run-script-example 6 | const runScript = client => 7 | client 8 | .run('Heroes', { name: 'FMS Triggered Script', param: { name: 'Han' } }) 9 | .then(result => log('run-script-example', result)); 10 | //# 11 | 12 | //#run-script-string-example 13 | const runScriptsString = client => 14 | client 15 | .run('Heroes', 'FMS Triggered Script') 16 | .then(result => log('run-script-string-example', result)); 17 | //# 18 | 19 | //#run-script-string-with-parameters-example 20 | const runScriptsStringWithParameters = client => 21 | client 22 | .run('Heroes', 'FMS Triggered Script', { name: 'Han' }) 23 | .then(result => log('run-script-with-parameters-example', result)); 24 | //# 25 | 26 | //#run-scripts-example 27 | const runMultipleScripts = client => 28 | client 29 | .run('Heroes', [ 30 | { name: 'FMS Triggered Script', param: { name: 'Han' } }, 31 | { name: 'FMS Triggered Script', phase: 'presort', param: { name: 'Han' } } 32 | ]) 33 | .then(result => log('run-scripts-example', result)); 34 | //# 35 | 36 | //#run-single-script-example 37 | const runSingleScript = client => 38 | client 39 | .run('Heroes', [{ name: 'FMS Triggered Script', param: { name: 'Han' } }]) 40 | .then(result => log('run-single-script-example', result)); 41 | //# 42 | 43 | const scripts = client => 44 | Promise.all([ 45 | runScript(client), 46 | runSingleScript(client), 47 | runMultipleScripts(client), 48 | runScriptsString(client), 49 | runScriptsStringWithParameters(client) 50 | ]).then(responses => client); 51 | 52 | module.exports = { scripts }; 53 | -------------------------------------------------------------------------------- /examples/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./logger.service'); 4 | const { examples, store } = require('./storage.service'); 5 | 6 | module.exports = { 7 | log, 8 | examples, 9 | store 10 | }; 11 | -------------------------------------------------------------------------------- /examples/services/logger.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fse = require('fs-extra'); 4 | const path = require('path'); 5 | const deepMap = require('deep-map'); 6 | 7 | const toFile = file => path.resolve(__dirname, '../results', `${file}.json`); 8 | 9 | const filter = values => 10 | deepMap(values, (value, key) => { 11 | if (typeof value === 'string' && value.includes(process.env.SERVER)) { 12 | return value.replace(process.env.SERVER, 'https://some-server.com'); 13 | } else if (key === 'recordId') { 14 | return '1138'; 15 | } else if (key === 'modId') { 16 | return '327'; 17 | } else if (key === 'modificationTimestamp' || key === 'creationTimestamp') { 18 | return '05/25/1977 6:00:00'; 19 | } else if (key === 'id' || key === 'creationTimestamp') { 20 | return 'r2d2-c3po-l3-37-bb-8'; 21 | } else if (key === 'totalRecordCount') { 22 | return '1977'; 23 | } else if (key === 'token') { 24 | return 'shuttle-tydirium'; 25 | } else if (key === 'databases') { 26 | return [ 27 | { name: 'A New Hope' }, 28 | { name: 'Empire Stikes Back' }, 29 | { name: 'Return of the Jedi' } 30 | ]; 31 | } else { 32 | return value; 33 | } 34 | }); 35 | 36 | const log = (file, result) => { 37 | if (process.env.RESULTS) { 38 | fse.outputJson(toFile(file), filter(result), { spaces: 2 }); 39 | } 40 | console.log(result); 41 | return result; 42 | }; 43 | 44 | module.exports = { log }; 45 | -------------------------------------------------------------------------------- /examples/services/regex.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fse = require('fs-extra'); 4 | const path = require('path'); 5 | const regex = /\/g; 6 | const readme = path.join(__dirname, '../../README.md'); 7 | 8 | fse.readFile(readme, 'utf8', (error, markdown) => 9 | fse.writeFile(readme, markdown.replace(regex, '')) 10 | ); 11 | -------------------------------------------------------------------------------- /examples/services/storage.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const examples = []; 4 | 5 | const store = responses => 6 | Array.isArray(responses) 7 | ? responses.map( 8 | response => 9 | typeof response === 'object' && response.recordId 10 | ? examples.push({ recordId: response.recordId }) 11 | : null 12 | ) 13 | : null; 14 | 15 | module.exports = { examples, store }; 16 | -------------------------------------------------------------------------------- /examples/templates/partials/parameters.hbs: -------------------------------------------------------------------------------- 1 | {{#if params}} 2 | 3 | {{tableHead params "name|Param" "type|Type" "defaultvalue|Default" "description|Description" ~}} 4 | {{#tableRow params "name" "type" "defaultvalue" "description" ~}} 5 | | {{#if @col1}}{{>param-table-name}} | {{/if~}} 6 | {{#if @col2}}{{>linked-type-list types=type.names delimiter=" \| " }} | {{/if~}} 7 | {{#if @col3}}{{>defaultvalue}} | {{/if~}} 8 | {{#if @col4}}{{{stripNewlines (inlineLinks description)}}} |{{/if}} 9 | {{/tableRow}} 10 | 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /examples/upload.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log, store } = require('./services'); 4 | 5 | //#upload-image-example 6 | const uploadImage = client => 7 | client 8 | .upload('./assets/placeholder.md', 'Heroes', 'image') 9 | .then(result => log('upload-image-example', result)); 10 | //# 11 | 12 | const uploadSpecificImage = client => 13 | client 14 | .find('Heroes', [{ name: 'yoda' }], { limit: 1 }) 15 | .then(response => response.data[0].recordId) 16 | .then(recordId => { 17 | setTimeout( 18 | () => 19 | //#upload-specific-record-example 20 | client 21 | .upload('./assets/placeholder.md', 'Heroes', 'image', recordId) 22 | .then(result => log('upload-specific-record-example', result)) 23 | .catch(error => error), 24 | //# 25 | 1000 26 | ); 27 | }); 28 | 29 | //# 30 | 31 | const uploads = client => 32 | Promise.all([uploadImage(client), uploadSpecificImage(client)]).then( 33 | responses => { 34 | store(responses); 35 | return client; 36 | } 37 | ); 38 | 39 | module.exports = { uploads }; 40 | -------------------------------------------------------------------------------- /examples/utility.examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { log } = require('./services'); 4 | const { 5 | recordId, 6 | fieldData, 7 | transform, 8 | containerData, 9 | productInfo, 10 | databases 11 | } = require('../index.js'); 12 | 13 | //#record-id-utility--original-example 14 | const extractRecordId = client => 15 | client 16 | .find('Heroes', { name: 'yoda' }, { limit: 2 }) 17 | .then(response => response.data) 18 | .then(result => log('record-id-utility-original-example', result)); 19 | //# 20 | 21 | //#record-id-utility-example 22 | const extractRecordIdOriginal = client => 23 | client 24 | .find('Heroes', { name: 'yoda' }, { limit: 2 }) 25 | .then(response => recordId(response.data)) 26 | .then(result => log('record-id-utility-example', result)); 27 | //# 28 | 29 | //#field-data-utility-original-example 30 | const extractFieldDataOriginal = client => 31 | client 32 | .find('Heroes', { name: 'yoda' }, { limit: 2 }) 33 | .then(response => response.data) 34 | .then(result => log('field-data-utility-original-example', result)); 35 | //# 36 | 37 | //#field-data-utility-example 38 | const extractFieldData = client => 39 | client 40 | .find('Heroes', { name: 'yoda' }, { limit: 2 }) 41 | .then(response => fieldData(response.data)) 42 | .then(result => log('field-data-utility-example', result)); 43 | //# 44 | 45 | //#transform-utility-example 46 | const transformData = client => 47 | client 48 | .find('Transform', { name: 'Han Solo' }, { limit: 1 }) 49 | .then(result => transform(result.data)) 50 | .then(result => log('transform-utility-example', result)); 51 | //# 52 | // 53 | 54 | //#transform-utility-original-example 55 | const transformDataOriginal = client => 56 | client 57 | .find('Transform', { name: 'Han Solo' }, { limit: 1 }) 58 | .then(result => result.data) 59 | .then(result => log('transform-utility-original-example', result)); 60 | //# 61 | 62 | //#transform-utility-no-convert-example 63 | const transformDataNoConvert = client => 64 | client 65 | .find('Transform', { name: 'Han Solo' }, { limit: 1 }) 66 | .then(result => transform(result.data, { convert: false })) 67 | .then(result => log('transform-utility-no-convert-example', result)); 68 | //# 69 | 70 | //#container-data-example 71 | const getContainerData = client => 72 | client 73 | .find('Heroes', { imageName: '*' }, { limit: 1 }) 74 | .then(result => 75 | containerData( 76 | result.data, 77 | 'fieldData.image', 78 | './assets', 79 | 'fieldData.imageName' 80 | ) 81 | ) 82 | .then(result => log('container-data-example', result)); 83 | //# 84 | 85 | //#product-info-utility-example 86 | const getProductInfo = client => 87 | productInfo(process.env.SERVER).then(result => 88 | log('product-info-utility-example', result) 89 | ); 90 | //# 91 | 92 | //#databases-utility-example 93 | const getDatabases = client => 94 | databases(process.env.SERVER).then(result => 95 | log('databases-utility-example', result) 96 | ); 97 | //# 98 | 99 | //#client-status-example 100 | const getStatus = client => 101 | client.status().then(result => log('client-status-example', result)); 102 | //# 103 | 104 | //#client-reset-example 105 | const resetClient = client => 106 | client.reset().then(result => log('client-reset-example', result)); 107 | //# 108 | 109 | const utilities = client => 110 | Promise.all([ 111 | extractFieldDataOriginal(client), 112 | extractFieldData(client), 113 | extractRecordIdOriginal(client), 114 | extractRecordId(client), 115 | transformDataOriginal(client), 116 | transformData(client), 117 | transformDataNoConvert(client), 118 | getContainerData(client), 119 | getProductInfo(client), 120 | getDatabases(client), 121 | getStatus(client), 122 | resetClient(client) 123 | ]).then(responses => client); 124 | 125 | module.exports = { utilities }; 126 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | Client, 5 | fieldData, 6 | recordId, 7 | transform, 8 | containerData, 9 | productInfo, 10 | databases 11 | } = require('./src'); 12 | 13 | /** 14 | * @module Filemaker 15 | */ 16 | module.exports = { 17 | Filemaker: Client, 18 | fieldData, 19 | recordId, 20 | transform, 21 | containerData, 22 | productInfo, 23 | databases 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fms-api-client", 3 | "version": "2.3.4", 4 | "description": "A FileMaker Data API client designed to allow easier interaction with a FileMaker database from a web environment.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "nyc _mocha --recursive ./test/*.test.js --timeout=50000 --exit", 8 | "coverage": "nyc report --reporter=text-lcov | coveralls", 9 | "report": "nyc report --reporter=html", 10 | "examples": "node examples/index.js", 11 | "update-results": "RESULTS=true npm run examples", 12 | "update-docs": "node_modules/.bin/jsdoc --configure .jsdoc.js --verbose", 13 | "update-readme": "jsdoc2md --template examples/templates/readme.hbs --partial \"examples/templates/partials/*.hbs\" --files \"src/**/*.js\" > README.md", 14 | "mos-inject": "npm run update-results && mos && npm run update-docs", 15 | "remove-mos": "node ./examples/services/regex.service.js", 16 | "generate-docs": "npm run update-readme && npm run mos-inject && npm run remove-mos", 17 | "snyk-protect": "snyk protect", 18 | "prepare": "npm run snyk-protect" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://Luidog@github.com/Luidog/fms-api-client.git" 23 | }, 24 | "keywords": [ 25 | "FileMaker", 26 | "Data API", 27 | "REST", 28 | "Marpat" 29 | ], 30 | "author": "Lui de la Parra", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/Luidog/fms-api-client/issues" 34 | }, 35 | "mos": { 36 | "plugins": [ 37 | "execute" 38 | ] 39 | }, 40 | "homepage": "https://github.com/Luidog/fms-api-client", 41 | "dependencies": { 42 | "axios": "^0.21.3", 43 | "axios-cookiejar-support": "^1.0.0", 44 | "form-data": "^3.0.0", 45 | "into-stream": "^5.1.1", 46 | "lodash": "^4.17.15", 47 | "marpat": "^3.0.5", 48 | "mime-types": "^2.1.26", 49 | "moment": "^2.24.0", 50 | "object-sizeof": "^1.6.0", 51 | "prettysize": "^2.0.0", 52 | "snyk": "^1.305.1", 53 | "stream-to-array": "^2.3.0", 54 | "tough-cookie": "^4.0.0", 55 | "uuid": "^7.0.3" 56 | }, 57 | "devDependencies": { 58 | "chai": "^4.2.0", 59 | "chai-as-promised": "^7.1.1", 60 | "coveralls": "^3.0.11", 61 | "deep-map": "^2.0.0", 62 | "dotenv": "^8.2.0", 63 | "es6-weak-map": "^2.0.3", 64 | "eslint": "^6.8.0", 65 | "eslint-config-google": "^0.14.0", 66 | "eslint-config-prettier": "^6.10.1", 67 | "eslint-plugin-prettier": "^3.1.3", 68 | "fs-extra": "^9.0.0", 69 | "handlebars": "^4.7.6", 70 | "http-proxy": "^1.18.0", 71 | "jsdoc": "^3.6.4", 72 | "jsdoc-to-markdown": "^5.0.3", 73 | "minami": "^1.2.3", 74 | "mocha": "^7.1.1", 75 | "mocha-lcov-reporter": "^1.3.0", 76 | "mos": "^1.3.1", 77 | "mos-plugin-dependencies": "^2.2.2", 78 | "mos-plugin-execute": "^1.0.2", 79 | "mos-plugin-installation": "^1.2.2", 80 | "mos-plugin-license": "^1.2.2", 81 | "mos-plugin-snippet": "^2.1.3", 82 | "nyc": "^15.0.1", 83 | "prettier": "^2.0.4", 84 | "sinon": "^9.0.2", 85 | "varium": "^2.0.6" 86 | }, 87 | "snyk": true 88 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Client } = require('./models'); 4 | const { 5 | fieldData, 6 | recordId, 7 | transform, 8 | containerData, 9 | productInfo, 10 | databases 11 | } = require('./services'); 12 | 13 | module.exports = { 14 | Client, 15 | Filemaker: Client, 16 | fieldData, 17 | recordId, 18 | transform, 19 | containerData, 20 | productInfo, 21 | databases 22 | }; 23 | -------------------------------------------------------------------------------- /src/models/credentials.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { EmbeddedDocument } = require('marpat'); 4 | 5 | /** 6 | * @class Credentials 7 | * @classdesc The class used to authenticate with into the FileMaker API. 8 | */ 9 | class Credentials extends EmbeddedDocument { 10 | /** @constructs */ 11 | constructor() { 12 | super(); 13 | this.schema({ 14 | /** A string containing the time the token token was issued. 15 | * @member Credentials#password 16 | * @type String 17 | */ 18 | password: { 19 | type: String, 20 | required: true 21 | }, 22 | /** The token to use when querying an endpoint. 23 | * @member Credentials#user 24 | * @type String 25 | */ 26 | user: { 27 | type: String, 28 | required: true 29 | } 30 | }); 31 | } 32 | 33 | /** 34 | * @method basic 35 | * @public 36 | * @memberof Credentials 37 | * @description This method constructs the basic authentication headers used 38 | * when authenticating a FileMaker DAPI session. 39 | * @return {String} A string containing the user and password authentication 40 | * pair. 41 | */ 42 | basic() { 43 | const auth = `Basic ${Buffer.from(`${this.user}:${this.password}`).toString( 44 | 'base64' 45 | )}`; 46 | return auth; 47 | } 48 | } 49 | 50 | module.exports = { 51 | Credentials 52 | }; 53 | -------------------------------------------------------------------------------- /src/models/data.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const moment = require('moment'); 4 | const sizeof = require('object-sizeof'); 5 | const pretty = require('prettysize'); 6 | const { EmbeddedDocument } = require('marpat'); 7 | 8 | /** 9 | * @class Data 10 | * @classdesc The class used to track FileMaker API data usage. 11 | **/ 12 | class Data extends EmbeddedDocument { 13 | /** @constructs */ 14 | constructor() { 15 | super(); 16 | this.schema({ 17 | /** A boolean value set to true if the client should track data usage. 18 | * @member Data#track 19 | * @type Boolean 20 | */ 21 | track: { 22 | type: Boolean, 23 | required: true, 24 | default: true 25 | }, 26 | /** A number containing the total amount of data called since the class 27 | * was created or last cleared. 28 | * @member Data#in 29 | * @type Number 30 | */ 31 | in: { 32 | type: Number, 33 | required: true, 34 | default: 0 35 | }, 36 | /** A number containing the total amount of data called since the class 37 | * was created or last cleared. 38 | * @member Data#out 39 | * @type Number 40 | */ 41 | out: { 42 | type: Number, 43 | required: true, 44 | default: 0 45 | }, 46 | /** A string containing the ISO date from the last time the class was created or last cleared. 47 | * @member Data#out 48 | * @type string 49 | */ 50 | since: { 51 | type: String, 52 | required: true, 53 | default: () => moment().format() 54 | } 55 | }); 56 | } 57 | 58 | /** 59 | * @method incoming 60 | * @public 61 | * @memberof Data 62 | * @description increments the amount of data being sent to FileMaker. 63 | * @param {Any} data The data to record. 64 | * @return {Any} Returns data unmutated. 65 | */ 66 | incoming(data) { 67 | this.track ? (this.in += sizeof(data)) : null; 68 | return data; 69 | } 70 | 71 | /** 72 | * @method outgoing 73 | * @public 74 | * @memberof Data 75 | * @description increments the amount of data being recieved from filemaker. 76 | * @param {Any} data The data to record. 77 | * @return {Any} Returns data unmutated. 78 | */ 79 | outgoing(data) { 80 | this.track ? (this.out += sizeof(data)) : null; 81 | return data; 82 | } 83 | 84 | /** 85 | * @method clear 86 | * @public 87 | * @memberof Data 88 | * @description Clears the data in and out and resets the since date to the current time as an ISO date string. 89 | */ 90 | clear() { 91 | this.since = moment().format(); 92 | this.in = this.out = 0; 93 | } 94 | 95 | /** 96 | * @method status 97 | * @public 98 | * @memberof Data 99 | * @description Prettifies the class data by stringifying the in and out data and returning since. 100 | * @return {Object} An object contain the key of data with keys of since, in, and out as strings. 101 | */ 102 | status() { 103 | const status = { 104 | data: { 105 | since: this.since, 106 | in: pretty(this.in), 107 | out: pretty(this.out) 108 | } 109 | }; 110 | return status; 111 | } 112 | } 113 | 114 | module.exports = { 115 | Data 116 | }; 117 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Client } = require('./client.model'); 4 | 5 | module.exports = { Client }; 6 | -------------------------------------------------------------------------------- /src/models/session.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const moment = require('moment'); 4 | const { EmbeddedDocument } = require('marpat'); 5 | const { v4: uuidv4 } = require('uuid'); 6 | /** 7 | * @class Session 8 | * @classdesc The class used to save FileMaker Data API Session information 9 | */ 10 | class Session extends EmbeddedDocument { 11 | /** @constructs */ 12 | constructor() { 13 | super(); 14 | this.schema({ 15 | /** A string containing a unique identifier for a session. 16 | * @member Session#id 17 | * @type String 18 | */ 19 | id: { 20 | type: String, 21 | default: () => uuidv4() 22 | }, 23 | /** A string containing the time the token token was issued. 24 | * @member Session#issued 25 | * @type String 26 | */ 27 | issued: { 28 | type: String, 29 | default: () => moment().format() 30 | }, 31 | /* A string containing the time the token will expire. 32 | * @member Session#expires 33 | * @type String 34 | */ 35 | expires: { 36 | type: String, 37 | default: () => moment().add(15, 'minutes').format() 38 | }, 39 | /* A string containing the last time the token was used. 40 | * @member Session#used 41 | * @type String 42 | */ 43 | used: { 44 | type: String 45 | }, 46 | /* A string containing the request created when the token is in use.. 47 | * @member Session#request 48 | * @type String 49 | */ 50 | request: { 51 | type: String 52 | }, 53 | /* A boolean set if the current session is in use. 54 | * @member Session#active 55 | * @type Boolean 56 | */ 57 | active: { 58 | type: Boolean, 59 | default: () => false 60 | }, 61 | /** The token to use when querying an endpoint. 62 | * @member Session#token 63 | * @type String 64 | */ 65 | token: { 66 | type: String 67 | } 68 | }); 69 | } 70 | 71 | /** 72 | * @method valid 73 | * @public 74 | * @memberof Session 75 | * @see {@link Agent#watch} 76 | * @see {@link Connection#available} 77 | * @description This method checks to see if the session is not currently active and not expired. 78 | * @return {Boolean} True if the token is valid, otherwise False. 79 | */ 80 | valid() { 81 | return ( 82 | !this.active && 83 | this.token !== undefined && 84 | moment().isBetween(this.issued, this.expires, '()') 85 | ); 86 | } 87 | 88 | /** 89 | * @method expired 90 | * @public 91 | * @memberof Session 92 | * @description This method checks to see if a session has expired. 93 | * @see {@link Agent#watch} 94 | * @see {@link Connection#available} 95 | * @return {Boolean} True if the token has expired, otherwise False. 96 | */ 97 | expired() { 98 | return moment().isSameOrAfter(this.expires); 99 | } 100 | 101 | /** 102 | * @method extend 103 | * @memberof Session 104 | * @public 105 | * @description This method extends a Data API session. 106 | * @see {@link Agent#handleResponse} 107 | */ 108 | extend() { 109 | this.active = false; 110 | this.expires = moment().add(15, 'minutes').format(); 111 | } 112 | 113 | /** 114 | * @method deactivate 115 | * @memberOf Sessions 116 | * @public 117 | * @description This method sets deactivates a session by setting active to false. 118 | * @see {@link Agent#handleResponse} 119 | * @see {@link Agent#handleError} 120 | */ 121 | deactivate() { 122 | this.request = ""; 123 | this.active = false; 124 | } 125 | } 126 | 127 | module.exports = { 128 | Session 129 | }; 130 | -------------------------------------------------------------------------------- /src/services/container.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const axios = require('axios'); 4 | const axiosCookieJarSupport = require('axios-cookiejar-support').default; 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const toArray = require('stream-to-array'); 8 | const { CookieJar } = require('tough-cookie'); 9 | const mime = require('mime-types'); 10 | const { v4: uuidv4 } = require('uuid'); 11 | const _ = require('lodash'); 12 | const { interceptResponse } = require('./request.service') 13 | 14 | const instance = axios.create(); 15 | 16 | axiosCookieJarSupport(instance); 17 | 18 | /** 19 | * @class Container Service 20 | */ 21 | 22 | /** 23 | * Attach Interceptors to Axios instance 24 | * @param {Any} response Web publishing response. 25 | * @param {Any} error We publishing error. 26 | */ 27 | instance.interceptors.response.use( 28 | response => interceptResponse(response), 29 | error => handleError(error) 30 | ); 31 | 32 | /** 33 | * @function handleError 34 | * @private 35 | * @memberof Container Service 36 | * @description handles errors from the Web Publishing service by ensuring the error is an object. 37 | * @param {Error} error The error recieved from the Web Publishing service. 38 | * @return {Object} An object with a code and a message. 39 | */ 40 | const handleError = error => 41 | Promise.reject({ code: 100, message: error.message }); 42 | 43 | /** 44 | * @function transport 45 | * @private 46 | * @memberof Container Service 47 | * @description This function retrieves container data from the FileMaker WPE. 48 | * @see writeFile 49 | * @see writeBuffer 50 | * @param {String} url The url to use for retrieval. 51 | * @param {String} destination The file's destination. Send buffer if it should be set to buffer. 52 | * @param {String} name The name of the file. 53 | * @param {Object} [parameters] Request configuration parameters. 54 | * @return {Promise} A promise which will resolve to the file. 55 | */ 56 | const transport = (url, destination, name, parameters = {}) => 57 | instance 58 | .get( 59 | url, 60 | Object.assign( 61 | { 62 | jar: new CookieJar(), 63 | responseType: 'stream', 64 | withCredentials: true 65 | }, 66 | parameters 67 | ) 68 | ) 69 | .then(response => { 70 | const filename = path.extname(name) 71 | ? name 72 | : `${name}.${mime.extension(response.headers['content-type'])}`; 73 | return destination && destination !== 'buffer' 74 | ? writeFile(response.data, filename, destination) 75 | : bufferFile(response.data, filename); 76 | }); 77 | 78 | /** 79 | * @function writeFile 80 | * @private 81 | * @memberof Container Service 82 | * @description This function will write a file to the filesystem. 83 | * @param {Buffer} stream The stream of data to write. 84 | * @param {String} name The file's name and extension. 85 | * @param {String} destination The destination path to write the file. 86 | * @return {Promise} A promise which will resolve with file path and name. 87 | */ 88 | const writeFile = (stream, name, destination) => 89 | new Promise((resolve, reject) => { 90 | const output = fs.createWriteStream(path.join(destination, name)); 91 | output.on('error', error => reject({ message: error.message, code: 100 })); 92 | output.on('finish', () => 93 | resolve({ name, path: path.join(destination, name) }) 94 | ); 95 | stream.pipe(output); 96 | }); 97 | 98 | /** 99 | * @function bufferFile 100 | * @private 101 | * @memberof Metadata Service 102 | * @description This function will write a stream to a buffer object. 103 | * @param {Stream} stream The url to use for retrieval. 104 | * @param {String} name The name and extension of the file. 105 | * @return {Promise} A promise which will resolve with file path and name. 106 | */ 107 | const bufferFile = (stream, name) => 108 | toArray(stream).then(parts => ({ name, buffer: Buffer.concat(parts) })); 109 | 110 | /** 111 | * @function containerData 112 | * @public 113 | * @memberof Metadata Service 114 | * @description This function retrieves container data from the FileMaker WPE. 115 | * @see transport 116 | * @param {Object|Array} data The response recieved from the FileMaker DAPI. 117 | * @param {String} field The container field name to target. This can be a nested property. 118 | * @param {String} destination "buffer" if a buffer object should be returned or the path to write the file. 119 | * @param {String} name The field to use for the file name or a static string. 120 | * @param {Object=} parameters Request configuration parameters. 121 | * @param {Number=} parameters.timeout a timeout for the request. 122 | * @return {Promise} a promise which will resolve to the file data. 123 | */ 124 | const containerData = (data, field, destination, name, parameters) => { 125 | return Array.isArray(data) 126 | ? Promise.all( 127 | data.map(datum => 128 | transport( 129 | _.get(datum, field), 130 | destination, 131 | _.get(datum, name, datum.recordId || uuidv4()), 132 | parameters 133 | ) 134 | ) 135 | ) 136 | : transport( 137 | _.get(data, field), 138 | destination, 139 | _.get(data, name, data.recordId || uuidv4()), 140 | parameters 141 | ); 142 | }; 143 | 144 | module.exports = { 145 | containerData 146 | }; 147 | -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { fieldData, recordId, transform } = require('./transform.service'); 4 | const { containerData } = require('./container.service'); 5 | const { instance } = require('./request.service'); 6 | const { productInfo, databases } = require('./metadata.service'); 7 | 8 | module.exports = { 9 | fieldData, 10 | recordId, 11 | transform, 12 | containerData, 13 | instance, 14 | productInfo, 15 | databases 16 | }; 17 | -------------------------------------------------------------------------------- /src/services/metadata.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { instance } = require('./request.service'); 4 | const { urls } = require('../utilities'); 5 | 6 | /** 7 | * @class Metadata Service 8 | */ 9 | 10 | /** 11 | * @function productInfo 12 | * @public 13 | * @memberof Metadata Service 14 | * @description The productInfo function gets FileMaker server metadata information. 15 | * @see urls#productInfo 16 | * @param {String} host The host FileMaker server. 17 | * @param {String} [version="vLatest"] The Data API version to use. The default is 'vLatest'. 18 | * @param {Object} [parameters] Request configuration parameters. 19 | * @return {Promise} A promise that will either resolve or reject based on the Data API. 20 | */ 21 | 22 | const productInfo = (host, version = 'vLatest', parameters = {}) => 23 | new Promise((resolve, reject) => { 24 | if (!host) reject({ message: 'You must specify a host', code: '1630' }); 25 | instance 26 | .get(urls.productInfo(host, version), parameters) 27 | .then(response => response.data) 28 | .then(data => resolve(data.response.productInfo)) 29 | .catch(error => reject(error)); 30 | }); 31 | 32 | /** 33 | * @function databases 34 | * @public 35 | * @memberof Metadata Service 36 | * @description The databases function gets a list of databases that are accessible either without credentials or with the credentials bassed to it. 37 | * @see urls#databases 38 | * @param {String} host The host FileMaker server. 39 | * @param {String} [credentials] Credentials to use when getting a list of databases. 40 | * @param {String} [version="vLatest"] The Data API version to use. The default is 'vLatest'. 41 | * @param {Object} [parameters] Request configuration parameters. 42 | * @return {Promise} A promise that will either resolve or reject based on the Data API. 43 | */ 44 | 45 | const databases = ( 46 | host, 47 | credentials = {}, 48 | version = 'vLatest', 49 | parameters = {} 50 | ) => 51 | new Promise((resolve, reject) => { 52 | if (!host) reject({ message: 'You must specify a host', code: '1630' }); 53 | instance 54 | .get( 55 | urls.databases(host, version), 56 | Object.assign( 57 | typeof credentials === 'object' && 58 | credentials.user && 59 | credentials.password 60 | ? { 61 | headers: { 62 | Authorization: `Basic ${Buffer.from( 63 | `${credentials.user}:${credentials.password}` 64 | ).toString('base64')}` 65 | } 66 | } 67 | : parameters 68 | ) 69 | ) 70 | .then(response => response.data) 71 | .then(data => resolve(data.response)) 72 | .catch(error => reject(error)); 73 | }); 74 | 75 | module.exports = { productInfo, databases }; 76 | -------------------------------------------------------------------------------- /src/services/request.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const axios = require('axios'); 4 | const axiosCookieJarSupport = require('axios-cookiejar-support').default; 5 | 6 | const instance = axios.create(); 7 | 8 | axiosCookieJarSupport(instance); 9 | 10 | /** 11 | * @class Request Service 12 | */ 13 | 14 | /** 15 | * @method interceptError 16 | * @private 17 | * @description This method evaluates the error response. This method will substitute 18 | * a non json error or a bad gateway status with a json code and message error. This 19 | * method will add an expired property to the error response if it recieves a invalid 20 | * token response. 21 | * @param {Object} error The error recieved from the requested resource. 22 | * @return {Promise} A promise rejection containing a code and a message 23 | */ 24 | const interceptError = error => { 25 | if (error.code) { 26 | return Promise.reject({ code: error.code, message: error.message }); 27 | } else if ( 28 | error.response.status === 400 && 29 | error.request.path.includes('RCType=EmbeddedRCFileProcessor') 30 | ) { 31 | return Promise.reject({ 32 | message: 'FileMaker WPE rejected the request', 33 | code: '9' 34 | }); 35 | } else if ( 36 | error.response.status === 502 || 37 | typeof error.response.data !== 'object' || 38 | !error.response.data.messages 39 | ) { 40 | return Promise.reject({ 41 | message: 'The Data API is currently unavailable', 42 | code: '1630' 43 | }); 44 | } else { 45 | return Promise.reject(error.response.data.messages[0]); 46 | } 47 | }; 48 | 49 | /** 50 | * @function interceptResponse 51 | * @public 52 | * @memberof Request Service 53 | * @description handles response data before it is sent to the client. This function 54 | * will eventually be used to cancel the request and return the configuration body. 55 | * This function will test the url for an http proticol and reject if none exist. 56 | * @param {Object} response The axios response 57 | * @return {Promise} the axios response 58 | */ 59 | const interceptResponse = response => { 60 | if (typeof response.data !== 'object') { 61 | return Promise.reject({ 62 | message: 'The Data API is currently unavailable', 63 | code: '1630' 64 | }); 65 | } else { 66 | return response; 67 | } 68 | }; 69 | 70 | /** 71 | * Attach Interceptors to Axios instance 72 | * @param {Any} response Web publishing response. 73 | * @param {Any} error We publishing error. 74 | */ 75 | instance.interceptors.response.use( 76 | response => interceptResponse(response), 77 | error => interceptError(error) 78 | ); 79 | 80 | module.exports = { 81 | instance, 82 | interceptResponse 83 | }; 84 | -------------------------------------------------------------------------------- /src/services/transform.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | /** 6 | * @class Transformation Service 7 | */ 8 | 9 | /** 10 | * @function transformRelatedTables 11 | * @public 12 | * @memberof Transformation Service 13 | * @description This function tranforms an object given by turning related tables into objects 14 | * @param {Object} object The response recieved from the FileMaker DAPI. 15 | * @param {String} parentKey The response recieved from the FileMaker DAPI. 16 | * @return {Object} A JSON object containing the selected data from the Data API Response. 17 | */ 18 | const transformRelatedTables = (object, parentKey) => 19 | _.transform( 20 | object, 21 | (accumulator, value, key) => { 22 | if (key.includes('::')) { 23 | const position = key.indexOf('::'); 24 | const parent = _.camelCase(parentKey); 25 | const table = _.camelCase(key.slice(0, position)); 26 | const field = _.camelCase(key.slice(position + 2)); 27 | if ( 28 | !Object.prototype.hasOwnProperty.call(accumulator, table) && 29 | table !== parent 30 | ) { 31 | accumulator[table] = {}; 32 | } 33 | table !== parent 34 | ? (accumulator[table][field] = value) 35 | : (accumulator[field] = value); 36 | } else { 37 | accumulator[key] = value; 38 | } 39 | }, 40 | {} 41 | ); 42 | 43 | /** 44 | * @function transformPortals 45 | * @private 46 | * @memberof Transformation Service 47 | * @description this function transforms portals by mapping keys found in the portalData object into the 48 | * transformRelatedTables function. This function passes the parent portal key to properly transform 49 | * portal data related to the main portal table occurance. 50 | * @param {Object} portals The response recieved from the FileMaker DAPI. 51 | * @return {Object} A JSON object containing the selected data from the Data API Response. 52 | */ 53 | const transformPortals = portals => 54 | _.flatMap(portals, (values, key, collection) => { 55 | const portal = {}; 56 | portal[key] = _.map(values, value => transformRelatedTables(value, key)); 57 | return portal; 58 | }); 59 | 60 | /** 61 | * @function transformObject 62 | * @private 63 | * @memberof Transformation Service 64 | * @description this function transforms portals by mapping keys found in the portalData object into the 65 | * transformRelatedTables function. This function passes the parent portal key to properly transform 66 | * portal data related to the main portal table occurance. 67 | * @param {Object} object The response recieved from the FileMaker DAPI. 68 | * @param {Object} options The response recieved from the FileMaker DAPI. 69 | * @return {Object} A JSON object containing the selected data from the Data API Response. 70 | */ 71 | const transformObject = (object, options = {}) => { 72 | const { fieldData, portalData, recordId, modId } = object; 73 | const transformedFieldData = 74 | options.convert !== false ? transformRelatedTables(fieldData) : fieldData; 75 | const transformedPortalData = 76 | options.convert !== false 77 | ? _.merge(...transformPortals(portalData)) 78 | : portalData; 79 | const portals = options.portalData !== false ? transformedPortalData : {}; 80 | const fields = options.fieldData !== false ? transformedFieldData : {}; 81 | return Object.assign(fields, portals, { recordId, modId }); 82 | }; 83 | 84 | /** 85 | * @function fieldData 86 | * @public 87 | * @memberof Transformation Service 88 | * @description fieldData is a helper function that strips the filemaker structural layout and portal information 89 | * from a record. It returns only the data contained in the fieldData key and the recordId. 90 | * @param {Object|Array} data The raw data returned from a filemaker. This can be an array or an object. 91 | * @return {Object|Array} A JSON object containing fieldData from the record. 92 | */ 93 | const fieldData = data => 94 | Array.isArray(data) 95 | ? _.map(data, object => 96 | Object.assign({}, object.fieldData, { 97 | recordId: object.recordId, 98 | modId: object.modId 99 | }) 100 | ) 101 | : Object.assign(data.fieldData, { 102 | recordId: data.recordId, 103 | modId: data.modId 104 | }); 105 | 106 | /** 107 | * @function recordId 108 | * @public 109 | * @memberof Transformation Service 110 | * @description returns record ids for the data parameters passed to it. This can be an array of ids or an object. 111 | * from a record. It returns only the data contained in the fieldData key adn the recordId. 112 | * @param {Object|Array} data the raw data returned from a filemaker. This can be an array or an object. 113 | * @return {Object} a JSON object containing fieldData from the record. 114 | */ 115 | const recordId = data => 116 | Array.isArray(data) 117 | ? _.map(data, object => object.recordId) 118 | : data.recordId.toString(); 119 | 120 | /** 121 | * @function transform 122 | * @public 123 | * @memberof Transformation Service 124 | * @description this function transforms portals by mapping keys found in the portalData object into the 125 | * transformRelatedTables function. This function passes the parent portal key to properly transform 126 | * portal data related to the main portal table occurance. 127 | * @param {Object} data The data to transform. 128 | * @param {Object} options transformation options to pass to transformObject. 129 | * @return {Object} A JSON object containing the selected data from the Data API Response. 130 | * @see transformObject 131 | */ 132 | const transform = (data, options) => 133 | Array.isArray(data) 134 | ? _.map(data, object => transformObject(object, options)) 135 | : transformObject(data, options); 136 | 137 | module.exports = { recordId, fieldData, transform }; 138 | -------------------------------------------------------------------------------- /src/utilities/conversion.utilities.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | /** 6 | * @class Conversion Utilities 7 | */ 8 | 9 | /** 10 | * @function toStrings 11 | * @public 12 | * @memberof Conversion Utilities 13 | * @description The toStrings function converts arrays of objects or a single object into stringified values. 14 | * @see stringify 15 | * @param {Object|Array} data The data to stringify. 16 | * @return {Object|Array} a JSON object containing stringified data. 17 | */ 18 | const toStrings = data => 19 | Array.isArray(data) ? data.map(datum => stringify(datum)) : stringify(data); 20 | 21 | /** 22 | * @function stringify 23 | * @public 24 | * @memberof Conversion Utilities 25 | * @description The stringify function converts numbers, objects, or booleans to strings. 26 | * @param {Object} data The object to stringify. 27 | * @return {Object} a JSON object containing stringified data. 28 | */ 29 | const stringify = data => 30 | _.mapValues(data, value => 31 | typeof value === 'string' 32 | ? value 33 | : typeof value === 'object' 34 | ? JSON.stringify(value) 35 | : value.toString() 36 | ); 37 | 38 | /** 39 | * @function toArray 40 | * @public 41 | * @memberof Conversion Utilities 42 | * @description The toArray function converts an object into an array. This function uses the object prototype function 43 | * isArray to check if the incoming data is an array. If the incoming data is not an array this function will 44 | * return the data in an array. 45 | * @param {Object|Array} data An array or object containing query information. This can be an array or an object. 46 | * @return {Object} An array containing the data passed to the function. 47 | */ 48 | const toArray = data => (Array.isArray(data) ? data : [data]); 49 | 50 | /** 51 | * @function isJSON 52 | * @public 53 | * @memberof Conversion Utilities 54 | * @description The isJSON function uses the try / catch to parse incoming data safely as JSON. 55 | * This function will return true if it is able to cast the incoming data as JSON. 56 | * @param {Any} data The data to be evaluated as JSON. 57 | * @return {Boolean} A boolean result depending on if the data passed to it is valid JSON 58 | */ 59 | const isJSON = data => { 60 | data = typeof data !== 'string' ? JSON.stringify(data) : data; 61 | 62 | try { 63 | data = JSON.parse(data); 64 | } catch (e) { 65 | return false; 66 | } 67 | 68 | if (typeof data === 'object' && data !== null) { 69 | return true; 70 | } 71 | 72 | return false; 73 | }; 74 | 75 | /** 76 | * @function isEmptyObject 77 | * @public 78 | * @memberof Conversion Utilities 79 | * @description The isEmptyObject function uses the try / catch to parse incoming data safely as JSON. 80 | * This function will return true if it is able to cast the incoming data as JSON. 81 | * @see stringify 82 | * @param {Any} data The data to be evaluated as JSON. 83 | * @return {Boolean} A boolean result depending on if the data passed to it is valid JSON. 84 | */ 85 | const isEmpty = data => (isJSON(data) ? _.isEmpty(data) : false); 86 | 87 | /** 88 | * @function omit 89 | * @public 90 | * @memberof Conversion Utilities 91 | * @description The omit function will remove properties from the first object or array passed to it that are in the second parameter passed it. 92 | * @param {Object|Array} data The data to parse for omits. 93 | * @param {Array} properties An array properties to remove. 94 | * @return {Object|Array} A JSON object or array of objects without the properties passed to it. 95 | */ 96 | const omit = (data, properties) => 97 | Array.isArray(data) 98 | ? _.map(data, object => _.omit(object, properties)) 99 | : _.omit(data, properties); 100 | 101 | /** 102 | * @function parse 103 | * @public 104 | * @memberof Conversion Utilities 105 | * @description The parse function performs a try catch before attempting to parse the value as JSON. If the value is not valid JSON it wil return the value. 106 | * @see isJSON 107 | * @param {Any} value The value to attempt to parse. 108 | * @return {Object|Any} A JSON object or array of objects without the properties passed to it 109 | */ 110 | const parse = value => (isJSON(value) ? JSON.parse(value) : value); 111 | 112 | /** 113 | * @function pick 114 | * @public 115 | * @memberof Filemaker Utilities 116 | * @description The parseScriptResults function filters the FileMaker DAPI response by testing if a script was triggered 117 | * with the request, then either selecting the response, script error, and script result from the 118 | * response or selecting just the response. 119 | * @param {Array|Object} data The response recieved from the FileMaker DAPI. 120 | * @param {Array|String} filter The response recieved from the FileMaker DAPI. 121 | * @return {Object} A json object containing the selected data from the Data API Response. 122 | */ 123 | const pick = (data, filter) => 124 | Array.isArray(data) 125 | ? data.map(object => 126 | _.pickBy(object, (value, key) => 127 | Array.isArray(filter) ? _.includes(filter, key) : key.includes(filter) 128 | ) 129 | ) 130 | : _.pickBy(data, (value, key) => 131 | Array.isArray(filter) ? _.includes(filter, key) : key.includes(filter) 132 | ); 133 | 134 | /** 135 | * @function deepMapKeys 136 | * @public 137 | * @memberof Conversion Utilities 138 | * @description deepMapKeys provides deep mapping of objects using a recursive lodash function. This function expects an iteratee that matches the iteratee of _.mapKeys. 139 | * @param {Object|Array} data The object or array to deep map 140 | * @param {Function} iteratee The function to use to map the object's keys. 141 | * @return {Object|Array} An object or array whose keys or members are modified by the iteratee. 142 | */ 143 | const deepMapKeys = (data, iteratee) => 144 | Array.isArray(data) 145 | ? data.map((value, key) => 146 | _.isObject(value) ? deepMapKeys(value, iteratee) : value 147 | ) 148 | : _.mapValues(_.mapKeys(data, iteratee), value => 149 | _.isObject(value) ? deepMapKeys(value, iteratee) : value 150 | ); 151 | 152 | module.exports = { 153 | toStrings, 154 | stringify, 155 | toArray, 156 | isJSON, 157 | isEmpty, 158 | omit, 159 | pick, 160 | parse, 161 | deepMapKeys 162 | }; 163 | -------------------------------------------------------------------------------- /src/utilities/filemaker.utilities.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const { stringify, parse } = require('./conversion.utilities'); 5 | 6 | /** @class Filemaker Utilities */ 7 | 8 | /** 9 | * @function convertPortals 10 | * @public 11 | * @memberof Filemaker Utilities 12 | * @description The convertPortals function converts a request containing a portal array into the syntax 13 | * supported by the FileMaker Data API. 14 | * @param {Object|Array} data The data to use when invoking the function. 15 | * @return {Object} A new object with the FileMaker required parameters for portals. 16 | */ 17 | const convertPortals = data => { 18 | const portalArray = []; 19 | const { portals } = data; 20 | const converted = Array.isArray(portals) 21 | ? _.chain(portals) 22 | .map(portal => 23 | _.mapKeys(portal, (value, key) => 24 | key === 'limit' 25 | ? `limit.${portal.name}` 26 | : key === 'offset' 27 | ? `offset.${portal.name}` 28 | : key === 'name' 29 | ? portalArray.push(value) 30 | : `remove` 31 | ) 32 | ) 33 | .map(portal => _.omitBy(portal, (value, key) => key.includes('remove'))) 34 | .value() 35 | : []; 36 | return Object.assign({ portals: portalArray }, converted); 37 | }; 38 | 39 | /** 40 | * @function convertScripts 41 | * @public 42 | * @memberof Filemaker Utilities 43 | * @description The converScript function abstracts the lodash map method to reduce the number of imports required 44 | * for each model. This method accepts an array and a function to use when mapping the array of values. 45 | * @param {Object} data The data to convert. 46 | * @return {Object} A new object based on the assignment of incoming properties. 47 | */ 48 | const convertScripts = data => { 49 | const { scripts } = data; 50 | const converted = Array.isArray(scripts) 51 | ? _.chain(scripts) 52 | .map(script => 53 | _.mapKeys(script, (value, key) => 54 | !_.isEmpty(script.phase) 55 | ? key === 'name' 56 | ? `script.${script.phase}` 57 | : `script.${script.phase}.${key}` 58 | : key === 'name' 59 | ? `script` 60 | : `script.${key}` 61 | ) 62 | ) 63 | .map(script => _.omitBy(script, (value, key) => key.includes('.phase'))) 64 | .value() 65 | : []; 66 | return Object.assign({}, ...converted); 67 | }; 68 | 69 | /** 70 | * @function convertParameters 71 | * @public 72 | * @memberof Filemaker Utilities 73 | * @description The converParameters function handles converting portals and scripts from array based parameters to object key based 74 | * parameters to FileMaker required parameters. 75 | * @see convertPortals 76 | * @see convertScripts 77 | * @param {Object|Array} data The raw data to use converting parameters 78 | * @return {Object|Array} A json object or array of objects without the properties passed to it 79 | */ 80 | const convertParameters = data => 81 | Object.assign(convertPortals(data), stringify(convertScripts(data)), data); 82 | 83 | /** 84 | * @function sanitizeParameters 85 | * @public 86 | * @memberof Filemaker Utilities 87 | * @description The santizeParameters function filters unsafe parameters from its return object based 88 | * on the safeOarameters array that is passed to it. This function is currently used in the client.create 89 | * function to seperate the merge option from the paramaters that are safe to send to FileMaker. 90 | * @see convertParameters 91 | * @param {Object} parameters An array values being passed to the FileMaker DAPI. 92 | * @param {Array} safeParameters An array values allowed to be sent to filemaker. 93 | * @return {Object|Array} returns an object or array of objects with only allowed keys 94 | * and values mapped to strings. 95 | */ 96 | const sanitizeParameters = (parameters, safeParameters) => 97 | _.mapValues( 98 | _.pickBy( 99 | convertParameters(parameters), 100 | (value, key) => 101 | _.includes(safeParameters, key) || 102 | (_.includes(safeParameters, '_offset.*') && 103 | _.startsWith(key, '_offset.')) || 104 | (_.includes(safeParameters, '_limit.*') && 105 | _.startsWith(key, '_limit.')) || 106 | (_.includes(safeParameters, 'offset.*') && 107 | _.startsWith(key, 'offset.')) || 108 | (_.includes(safeParameters, 'limit.*') && _.startsWith(key, 'limit.')) 109 | ), 110 | value => (_.isNumber(value) ? value.toString() : value) 111 | ); 112 | 113 | /** 114 | * @function parseScriptResults 115 | * @public 116 | * @memberof Filemaker Utilities 117 | * @description The parseScriptResults function filters the FileMaker DAPI response by testing if a script was triggered 118 | * with the request, then either selecting the response, script error, and script result from the 119 | * response or selecting just the response. 120 | * @param {Object} data The response recieved from the FileMaker DAPI. 121 | * @return {Object} A json object containing the selected data from the Data API Response. 122 | */ 123 | const parseScriptResult = data => 124 | _.mapValues(data.response, (value, property, object) => 125 | property.includes('scriptResult') 126 | ? (object[property] = parse(value)) 127 | : value 128 | ); 129 | 130 | /** 131 | * @function namespace 132 | * @public 133 | * @memberof Filemaker Utilities 134 | * @description The namespace functions maps through an incoming data object's keys and replaces the properties 135 | * of limit, offset, and sort with their _ counterparts. 136 | * @param {Object} data An object used in a DAPI query. 137 | * @return {Object} A modified object containing modified keys to match expected properties 138 | */ 139 | const namespace = data => 140 | _.mapKeys(data, (value, key) => 141 | _.includes(['limit', 'offset', 'sort'], key) ? `_${key}` : key 142 | ); 143 | 144 | /** 145 | * @function setData 146 | * @public 147 | * @memberof Filemaker Utilities 148 | * @description The setData function checks the incoming data for a fieldData property. If 149 | * the fieldData property is not found it will create the property and add all properties 150 | * except portalData to this new property. It will also stringify any numbers or objects 151 | * in portalData's properties. 152 | * @param {Object} data An object to use when creating or editing records. 153 | * @return {Object} A modified object containing a fieldData property. 154 | */ 155 | const setData = data => 156 | Object.assign( 157 | {}, 158 | { 159 | fieldData: !_.has(data, 'fieldData') 160 | ? stringify(_.omit(data, 'portalData')) 161 | : stringify(data.fieldData) 162 | }, 163 | _.has(data, 'portalData') 164 | ? { 165 | portalData: _.mapValues(data.portalData, data => 166 | _.map(data, object => stringify(object)) 167 | ) 168 | } 169 | : {} 170 | ); 171 | 172 | module.exports = { 173 | namespace, 174 | parseScriptResult, 175 | sanitizeParameters, 176 | setData 177 | }; 178 | -------------------------------------------------------------------------------- /src/utilities/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | namespace, 5 | parseScriptResult, 6 | sanitizeParameters, 7 | setData 8 | } = require('./filemaker.utilities'); 9 | 10 | const { 11 | omit, 12 | pick, 13 | toStrings, 14 | toArray, 15 | deepMapKeys, 16 | isJSON, 17 | isEmpty 18 | } = require('./conversion.utilities'); 19 | 20 | const { urls } = require('./urls.utilities'); 21 | 22 | module.exports = { 23 | omit, 24 | pick, 25 | toStrings, 26 | setData, 27 | toArray, 28 | isJSON, 29 | isEmpty, 30 | deepMapKeys, 31 | namespace, 32 | parseScriptResult, 33 | sanitizeParameters, 34 | urls 35 | }; 36 | -------------------------------------------------------------------------------- /test/admin/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const path = require('path'); 3 | const environment = require('dotenv'); 4 | const varium = require('varium'); 5 | const manifestPath = path.join(__dirname, '../env.manifest'); 6 | 7 | environment.config({ path: './test/.env' }); 8 | varium({ manifestPath }); 9 | 10 | const instance = axios.create({ 11 | baseURL: process.env.SERVER 12 | }); 13 | 14 | let adminToken = false; 15 | 16 | const login = () => 17 | instance 18 | .post( 19 | '/fmi/admin/api/v2/user/auth', 20 | {}, 21 | { 22 | auth: { 23 | username: process.env.ADMIN_USER, 24 | password: process.env.ADMIN_PASSWORD 25 | } 26 | } 27 | ) 28 | .then(response => { 29 | adminToken = response.data.response.token; 30 | return response.data.response.token; 31 | }); 32 | 33 | const logout = (token = adminToken) => 34 | token 35 | ? instance 36 | .delete(`/fmi/admin/api/v2/user/auth/${token}`, {}) 37 | .then(response => { 38 | adminToken = false; 39 | return response.data.response.token; 40 | }) 41 | : Promise.resolve(); 42 | 43 | const remove = ({ id }, token = adminToken) => 44 | instance 45 | .delete(`/fmi/admin/api/v2/clients/${id}`, { 46 | params: { 47 | graceTime: 0 48 | }, 49 | headers: { Authorization: `Bearer ${token}` } 50 | }) 51 | .then(response => response.data) 52 | .catch(error => console.log(error.response.data)); 53 | 54 | const drop = account => 55 | find(account).then(sessions => { 56 | const removals = []; 57 | sessions.forEach(session => removals.push(remove(session))); 58 | return Promise.all(removals); 59 | }); 60 | 61 | const find = ({ userName }, token = adminToken) => 62 | instance 63 | .get('/fmi/admin/api/v2/clients', { 64 | headers: { Authorization: `Bearer ${token}` } 65 | }) 66 | .then(response => 67 | response.data.response.clients.filter( 68 | client => client.userName === userName 69 | ) 70 | ); 71 | 72 | module.exports = { admin: { login, logout, sessions: { find, drop } } }; 73 | -------------------------------------------------------------------------------- /test/agent.model.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after afterEach it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const sinon = require('sinon'); 19 | const { Filemaker } = require('../index.js'); 20 | 21 | const sandbox = sinon.createSandbox(); 22 | 23 | chai.use(chaiAsPromised); 24 | 25 | const manifestPath = path.join(__dirname, './env.manifest'); 26 | 27 | const error = { 28 | response: { 29 | config: { headers: { Authorization: 'Bearer Invalid' } }, 30 | status: 401, 31 | data: { messages: [{ code: '952', message: 'Invalid Data API Token' }] } 32 | } 33 | }; 34 | 35 | const request = { 36 | url: 'fmp://test-server' 37 | }; 38 | 39 | describe('Agent Configuration Capabilities', () => { 40 | let database; 41 | let client; 42 | before(done => { 43 | environment.config({ path: './test/.env' }); 44 | varium({ manifestPath }); 45 | connect('nedb://memory') 46 | .then(db => { 47 | database = db; 48 | return database.dropDatabase(); 49 | }) 50 | .then(() => done()); 51 | }); 52 | 53 | before(done => { 54 | client = Filemaker.create({ 55 | database: process.env.DATABASE, 56 | server: process.env.SERVER, 57 | user: process.env.USERNAME, 58 | password: process.env.PASSWORD 59 | }); 60 | client.save().then(client => done()); 61 | }); 62 | 63 | after(done => { 64 | client 65 | .reset() 66 | .then(response => done()) 67 | .catch(error => done()); 68 | }); 69 | 70 | afterEach(done => { 71 | sandbox.restore(); 72 | return done(); 73 | }); 74 | 75 | it('should attempt to clear invalid sessions', () => { 76 | return expect( 77 | client 78 | .login() 79 | .then(response => client.agent.handleError(error)) 80 | .catch(error => error) 81 | ) 82 | .to.eventually.be.an('object') 83 | .that.has.all.keys('message', 'code'); 84 | }); 85 | 86 | it('should attempt to clear invalid sessions even if there is no header', () => { 87 | delete error.response.config.headers.Authorization; 88 | return expect( 89 | client 90 | .login() 91 | .then(response => client.agent.handleError(error)) 92 | .catch(error => error) 93 | ) 94 | .to.eventually.be.an('object') 95 | .that.has.all.keys('message', 'code'); 96 | }); 97 | 98 | it('should reject non http protocol requests', () => { 99 | return expect( 100 | client 101 | .login() 102 | .then(response => client.agent.handleRequest(request)) 103 | .catch(error => error) 104 | ) 105 | .to.eventually.be.an('object') 106 | .that.has.all.keys('message', 'code'); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /test/client.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Client Capabilities', () => { 25 | let database; 26 | let client; 27 | before(done => { 28 | environment.config({ path: './test/.env' }); 29 | varium({ manifestPath }); 30 | connect('nedb://memory') 31 | .then(db => { 32 | database = db; 33 | return database.dropDatabase(); 34 | }) 35 | .then(() => { 36 | return done(); 37 | }); 38 | }); 39 | 40 | before(done => { 41 | client = Filemaker.create({ 42 | database: process.env.DATABASE, 43 | server: process.env.SERVER, 44 | user: process.env.USERNAME, 45 | password: process.env.PASSWORD 46 | }); 47 | client.save().then(client => done()); 48 | }); 49 | 50 | after(done => { 51 | client 52 | .reset() 53 | .then(response => done()) 54 | .catch(error => done()); 55 | }); 56 | 57 | it('should show the current status', () => { 58 | return expect(client.status()) 59 | .to.eventually.be.a('object') 60 | .that.has.all.keys('data', 'pending', 'queue', 'sessions') 61 | .and.property('data'); 62 | }); 63 | 64 | it('should show the current status', () => { 65 | client.agent.pending.push({ url: 'FileMaker DAPI URL' }); 66 | client.agent.queue.push({ url: 'FileMaker DAPI URL' }); 67 | client.agent.connection.sessions.push({ url: 'FileMaker DAPI URL' }); 68 | return expect(client.status()) 69 | .to.eventually.be.a('object') 70 | .that.has.all.keys('data', 'pending', 'queue', 'sessions'); 71 | }); 72 | 73 | it('should reset the client', () => { 74 | return expect(client.reset().catch(error => error)) 75 | .to.eventually.be.a('object') 76 | .that.has.all.keys('message'); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/containerData.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker, containerData } = require('../index'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('ContainerData Capabilities', () => { 25 | let database; 26 | let client; 27 | 28 | before(done => { 29 | environment.config({ path: './test/.env' }); 30 | varium({ manifestPath }); 31 | connect('nedb://memory') 32 | .then(db => { 33 | database = db; 34 | return database.dropDatabase(); 35 | }) 36 | .then(() => { 37 | client = Filemaker.create({ 38 | database: process.env.DATABASE, 39 | server: process.env.SERVER, 40 | user: process.env.USERNAME, 41 | password: process.env.PASSWORD 42 | }); 43 | client.save().then(client => done()); 44 | }); 45 | }); 46 | 47 | after(done => { 48 | client 49 | .reset() 50 | .then(response => done()) 51 | .catch(error => done()); 52 | }); 53 | 54 | it('should download container data from an object to a file', () => { 55 | return expect( 56 | client 57 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 1 }) 58 | .then(response => 59 | containerData( 60 | response.data[0], 61 | 'fieldData.image', 62 | './assets', 63 | 'fieldData.imageName' 64 | ) 65 | ) 66 | ) 67 | .to.eventually.be.a('object') 68 | .and.to.all.include.keys('name', 'path'); 69 | }); 70 | 71 | it('should download container data from an array to a file', () => { 72 | return expect( 73 | client 74 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 2 }) 75 | .then(response => 76 | containerData( 77 | response.data, 78 | 'fieldData.image', 79 | './assets', 80 | 'fieldData.imageName' 81 | ) 82 | ) 83 | ) 84 | .to.eventually.be.a('array') 85 | .and.property('0') 86 | .to.be.a('object') 87 | .and.to.all.include.keys('name', 'path'); 88 | }); 89 | 90 | it('should return an array of records if it is passed an array', () => { 91 | return expect( 92 | client 93 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 2 }) 94 | .then(response => 95 | containerData(response.data, 'fieldData.image', './assets') 96 | ) 97 | ) 98 | .to.eventually.be.a('array') 99 | .and.property('0') 100 | .to.be.a('object') 101 | .and.to.all.include.keys('name', 'path'); 102 | }); 103 | it('should return a single record object if passed an object', () => { 104 | return expect( 105 | client 106 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 1 }) 107 | .then(response => 108 | containerData(response.data[0], 'fieldData.image', './assets') 109 | ) 110 | ) 111 | .to.eventually.be.a('object') 112 | .and.to.all.include.keys('name', 'path'); 113 | }); 114 | 115 | it('should download container data from an array to a buffer', () => { 116 | return expect( 117 | client 118 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 2 }) 119 | .then(response => 120 | containerData( 121 | response.data, 122 | 'fieldData.image', 123 | 'buffer', 124 | 'fieldData.imageName' 125 | ) 126 | ) 127 | ) 128 | .to.eventually.be.a('array') 129 | .and.property('0') 130 | .to.be.a('object') 131 | .and.to.all.include.keys('name', 'buffer'); 132 | }); 133 | 134 | it('should download container data from an object to a buffer', () => { 135 | return expect( 136 | client 137 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 1 }) 138 | .then(response => 139 | containerData( 140 | response.data[0], 141 | 'fieldData.image', 142 | 'buffer', 143 | 'fieldData.imageName' 144 | ) 145 | ) 146 | ) 147 | .to.be.eventually.be.a('object') 148 | .and.to.all.include.keys('name', 'buffer'); 149 | }); 150 | 151 | it('should substitute a uuid if the record id can not be found in an object', () => { 152 | return expect( 153 | client 154 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 1 }) 155 | .then(response => { 156 | const data = response.data[0]; 157 | delete data.recordId; 158 | return data; 159 | }) 160 | .then(data => containerData(data, 'fieldData.image')) 161 | ) 162 | .to.be.eventually.be.a('object') 163 | .and.to.all.include.keys('name', 'buffer'); 164 | }); 165 | 166 | it('should substitute a uuid if the record id can not be found in an array', () => { 167 | return expect( 168 | client 169 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 2 }) 170 | .then(response => { 171 | delete response.data[0].recordId; 172 | delete response.data[1].recordId; 173 | return response.data; 174 | }) 175 | .then(data => containerData(data, 'fieldData.image')) 176 | ) 177 | .to.eventually.be.a('array') 178 | .and.property('0') 179 | .to.be.a('object') 180 | .and.to.all.include.keys('name', 'buffer'); 181 | }); 182 | 183 | it('should reject with an error and a message', () => { 184 | return expect( 185 | client 186 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 1 }) 187 | .then(response => 188 | containerData( 189 | response.data[0], 190 | 'fieldData.image', 191 | './path/does/not/exist', 192 | 'fieldData.imageName' 193 | ) 194 | ) 195 | .catch(error => error) 196 | ) 197 | .to.eventually.be.a('object') 198 | .that.has.all.keys('code', 'message'); 199 | }); 200 | it('should reject if WPE rejects the request', () => { 201 | return expect( 202 | client 203 | .find(process.env.LAYOUT, { imageName: '*' }, { limit: 1 }) 204 | .then(response => { 205 | response.data[0].fieldData.image = response.data[0].fieldData.image.replace( 206 | 'MainDB', 207 | 'FailDB' 208 | ); 209 | return containerData( 210 | response.data[0], 211 | 'fieldData.image', 212 | 'buffer', 213 | 'fieldData.imageName' 214 | ); 215 | }) 216 | 217 | .catch(error => error) 218 | ) 219 | .to.eventually.be.a('object') 220 | .that.has.all.keys('code', 'message'); 221 | }); 222 | }); 223 | -------------------------------------------------------------------------------- /test/databases.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after afterEach it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const sinon = require('sinon'); 15 | const chaiAsPromised = require('chai-as-promised'); 16 | const environment = require('dotenv'); 17 | const varium = require('varium'); 18 | const { connect } = require('marpat'); 19 | const { Filemaker, databases } = require('../index.js'); 20 | const { urls } = require('../src/utilities'); 21 | 22 | const sandbox = sinon.createSandbox(); 23 | 24 | const manifestPath = path.join(__dirname, './env.manifest'); 25 | 26 | chai.use(chaiAsPromised); 27 | 28 | process.on('unhandledRejection', () => {}); 29 | 30 | process.on('rejectionHandled', () => {}); 31 | 32 | describe('Databases Capabilities', () => { 33 | let database; 34 | let client; 35 | before(done => { 36 | environment.config({ path: './test/.env' }); 37 | varium({ manifestPath }); 38 | connect('nedb://memory') 39 | .then(db => { 40 | database = db; 41 | return database.dropDatabase(); 42 | }) 43 | .then(() => { 44 | return done(); 45 | }); 46 | }); 47 | 48 | before(done => { 49 | client = Filemaker.create({ 50 | database: process.env.DATABASE, 51 | server: process.env.SERVER, 52 | user: process.env.USERNAME, 53 | password: process.env.PASSWORD 54 | }); 55 | client.save().then(client => done()); 56 | }); 57 | 58 | after(done => { 59 | client 60 | .reset() 61 | .then(response => done()) 62 | .catch(error => done()); 63 | }); 64 | 65 | afterEach(done => { 66 | sandbox.restore(); 67 | return done(); 68 | }); 69 | 70 | it('should get hosted databases', () => { 71 | return expect(client.databases()) 72 | .to.eventually.be.a('object') 73 | .that.has.all.keys('databases'); 74 | }); 75 | it('should fail with a code and a message', () => { 76 | sandbox.stub(urls, 'databases').callsFake(() => 'https://httpstat.us/502'); 77 | return expect(client.databases().catch(error => error)) 78 | .to.eventually.be.a('object') 79 | .that.has.all.keys('message', 'code'); 80 | }); 81 | }); 82 | 83 | describe('Databases Utility Capabilities', () => { 84 | before(done => { 85 | environment.config({ path: './test/.env' }); 86 | varium({ manifestPath }); 87 | return done(); 88 | }); 89 | 90 | it('should retrieve databases without credentials', () => { 91 | return expect(databases(process.env.SERVER)) 92 | .to.eventually.be.a('object') 93 | .that.has.all.keys('databases'); 94 | }); 95 | it('should retrieve databases using account credentials', () => { 96 | return expect( 97 | databases(process.env.SERVER, { 98 | user: process.env.USER, 99 | password: process.env.PASSWORD 100 | }) 101 | ) 102 | .to.eventually.be.a('object') 103 | .that.has.all.keys('databases'); 104 | }); 105 | it('should fail with a code and a message', () => { 106 | return expect(databases('http://not-a-server.com').catch(error => error)) 107 | .to.eventually.be.a('object') 108 | .that.has.all.keys('message', 'code'); 109 | }); 110 | it('should require a server to list databases', () => { 111 | return expect(databases().catch(error => error)) 112 | .to.eventually.be.a('object') 113 | .that.has.all.keys('message', 'code'); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/delete.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Delete Capabilities', () => { 25 | let database; 26 | let client; 27 | before(done => { 28 | environment.config({ path: './test/.env' }); 29 | varium({ manifestPath }); 30 | connect('nedb://memory') 31 | .then(db => { 32 | database = db; 33 | return database.dropDatabase(); 34 | }) 35 | .then(() => { 36 | return done(); 37 | }); 38 | }); 39 | 40 | before(done => { 41 | client = Filemaker.create({ 42 | database: process.env.DATABASE, 43 | server: process.env.SERVER, 44 | user: process.env.USERNAME, 45 | password: process.env.PASSWORD 46 | }); 47 | client.save().then(client => done()); 48 | }); 49 | 50 | after(done => { 51 | client 52 | .reset() 53 | .then(response => done()) 54 | .catch(error => done()); 55 | }); 56 | 57 | it('should delete FileMaker records.', () => 58 | expect( 59 | client 60 | .create(process.env.LAYOUT, { name: 'Darth Vader' }) 61 | .then(response => client.delete(process.env.LAYOUT, response.recordId)) 62 | ).to.eventually.be.a('object')); 63 | 64 | it('should allow you to specify a timeout', () => 65 | expect( 66 | client 67 | .create(process.env.LAYOUT, { name: 'Darth Vader' }) 68 | .then(response => 69 | client.delete(process.env.LAYOUT, response.recordId, { 70 | request: { timeout: 10 } 71 | }) 72 | ) 73 | .catch(error => error) 74 | ) 75 | .to.eventually.be.an('object') 76 | .with.any.keys('message', 'code')); 77 | 78 | it('should trigger scripts via an array when deleting records.', () => 79 | expect( 80 | client 81 | .create(process.env.LAYOUT, { name: 'Darth Vader' }) 82 | .then(response => 83 | client.delete(process.env.LAYOUT, response.recordId, { 84 | scripts: [ 85 | { 86 | name: 'Error Script', 87 | phase: 'prerequest', 88 | param: 'A Parameter' 89 | }, 90 | { name: 'Error Script', param: 'A Parameter' }, 91 | { name: 'Error Script', phase: 'presort', param: 'A Parameter' } 92 | ] 93 | }) 94 | ) 95 | ).to.eventually.be.a('object')); 96 | 97 | it('should trigger scripts via parameters when deleting records.', () => 98 | expect( 99 | client 100 | .create(process.env.LAYOUT, { name: 'Darth Vader' }) 101 | .then(response => 102 | client.delete(process.env.LAYOUT, response.recordId, { 103 | 'script.prerequest': 'Error Script', 104 | 'script.prerequest.param': 'A Parameter' 105 | }) 106 | ) 107 | ).to.eventually.be.a('object')); 108 | 109 | it('should allow you to mix script parameters and scripts array when deleting records.', () => 110 | expect( 111 | client 112 | .create(process.env.LAYOUT, { name: 'Darth Vader' }) 113 | .then(response => 114 | client.delete(process.env.LAYOUT, response.recordId, { 115 | 'script.prerequest': 'Error Script', 116 | 'script.prerequest.param': 'A Parameter', 117 | scripts: [ 118 | { name: 'Error Script', param: 'A Parameter' }, 119 | { name: 'Error Script', phase: 'presort', param: 'A Parameter' } 120 | ] 121 | }) 122 | ) 123 | ).to.eventually.be.a('object')); 124 | 125 | it('should stringify script parameters.', () => 126 | expect( 127 | client 128 | .create(process.env.LAYOUT, { name: 'Darth Vader' }) 129 | .then(response => 130 | client.delete(process.env.LAYOUT, response.recordId, { 131 | 'script.prerequest': 'Error Script', 132 | 'script.prerequest.param': 2, 133 | scripts: [ 134 | { name: 'Error Script', param: 'A Parameter' }, 135 | { name: 'Error Script', phase: 'presort', param: { data: true } } 136 | ] 137 | }) 138 | ) 139 | ).to.eventually.be.a('object')); 140 | 141 | it('should reject deletions that do not specify a recordId', () => 142 | expect(client.delete(process.env.LAYOUT).catch(error => error)) 143 | .to.eventually.be.a('object') 144 | .that.has.all.keys('code', 'message')); 145 | 146 | it('should reject deletions that do not specify an invalid recordId', () => 147 | expect(client.delete(process.env.LAYOUT, '-2').catch(error => error)) 148 | .to.eventually.be.a('object') 149 | .that.has.all.keys('code', 'message')); 150 | }); 151 | -------------------------------------------------------------------------------- /test/duplicate.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Duplicate Record Capabilities', () => { 25 | let database; 26 | let client; 27 | before(done => { 28 | environment.config({ path: './test/.env' }); 29 | varium({ manifestPath }); 30 | connect('nedb://memory') 31 | .then(db => { 32 | database = db; 33 | return database.dropDatabase(); 34 | }) 35 | .then(() => { 36 | return done(); 37 | }); 38 | }); 39 | 40 | before(done => { 41 | client = Filemaker.create({ 42 | database: process.env.DATABASE, 43 | server: process.env.SERVER, 44 | user: process.env.USERNAME, 45 | password: process.env.PASSWORD 46 | }); 47 | client.save().then(client => done()); 48 | }); 49 | 50 | after(done => { 51 | client 52 | .reset() 53 | .then(response => done()) 54 | .catch(error => done()); 55 | }); 56 | 57 | it('should allow you to duplicate a record', () => 58 | expect( 59 | client 60 | .create(process.env.LAYOUT, { name: 'Han Solo' }) 61 | .then(result => client.duplicate(process.env.LAYOUT, result.recordId)) 62 | ) 63 | .to.eventually.be.a('object') 64 | .that.has.all.keys('recordId', 'modId')); 65 | 66 | it('should require an id to duplicate a record', () => 67 | expect( 68 | client 69 | .create(process.env.LAYOUT, { name: 'Han Solo' }) 70 | .then(result => client.duplicate(process.env.LAYOUT)) 71 | .catch(error => error) 72 | ) 73 | .to.eventually.be.an('object') 74 | .that.has.all.keys('code', 'message')); 75 | }); 76 | -------------------------------------------------------------------------------- /test/env.manifest: -------------------------------------------------------------------------------- 1 | # FileMaker Configuration 2 | 3 | DATABASE : String 4 | SERVER : String 5 | USERNAME : String 6 | PASSWORD : String 7 | LAYOUT : String 8 | CONTAINER_LAYOUT : String 9 | 10 | # FileMaker Server Configuration 11 | 12 | ADMIN_USER: String 13 | ADMIN_PASSWORD: String 14 | 15 | # Client Configuration 16 | 17 | CLIENT_NAME: String | star-wars-fan 18 | CLIENT_USAGE_TRACKING: Bool | true 19 | 20 | # Documentation Configuration 21 | 22 | RESULTS: Bool | false -------------------------------------------------------------------------------- /test/fieldData.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker, fieldData } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('FieldData Capabilities', () => { 25 | let database; 26 | let client; 27 | 28 | before(done => { 29 | client = Filemaker.create({ 30 | database: process.env.DATABASE, 31 | server: process.env.SERVER, 32 | user: process.env.USERNAME, 33 | password: process.env.PASSWORD 34 | }); 35 | client.save().then(client => done()); 36 | }); 37 | 38 | after(done => { 39 | client 40 | .reset() 41 | .then(response => done()) 42 | .catch(error => done()); 43 | }); 44 | 45 | before(done => { 46 | environment.config({ path: './test/.env' }); 47 | varium({ manifestPath }); 48 | connect('nedb://memory') 49 | .then(db => { 50 | database = db; 51 | return database.dropDatabase(); 52 | }) 53 | .then(() => { 54 | return done(); 55 | }); 56 | }); 57 | 58 | it('it should extract field data while maintaining the array', () => { 59 | return expect( 60 | client 61 | .create(process.env.LAYOUT, { name: 'Obi-Wan' }) 62 | .then(response => client.get(process.env.LAYOUT, response.recordId)) 63 | .then(record => fieldData(record.data)) 64 | ) 65 | .to.eventually.be.a('array') 66 | .and.property('0') 67 | .to.be.a('object') 68 | .and.to.all.include.keys('modId', 'recordId') 69 | .and.to.not.include.keys('fieldData'); 70 | }); 71 | 72 | it('it should extract field data while maintaining the object', () => { 73 | return expect( 74 | client 75 | .create(process.env.LAYOUT, { name: 'Obi-Wan' }) 76 | .then(response => client.get(process.env.LAYOUT, response.recordId)) 77 | .then(record => fieldData(record.data[0])) 78 | ) 79 | .to.eventually.be.a('object') 80 | .and.to.all.include.keys('modId', 'recordId') 81 | .and.to.not.include.keys('fieldData'); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/get.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Get Capabilities', () => { 25 | let database; 26 | let client; 27 | before(done => { 28 | environment.config({ path: './test/.env' }); 29 | varium({ manifestPath }); 30 | connect('nedb://memory') 31 | .then(db => { 32 | database = db; 33 | return database.dropDatabase(); 34 | }) 35 | .then(() => { 36 | return done(); 37 | }); 38 | }); 39 | 40 | before(done => { 41 | client = Filemaker.create({ 42 | database: process.env.DATABASE, 43 | server: process.env.SERVER, 44 | user: process.env.USERNAME, 45 | password: process.env.PASSWORD 46 | }); 47 | client.save().then(client => done()); 48 | }); 49 | 50 | after(done => { 51 | client 52 | .reset() 53 | .then(response => done()) 54 | .catch(error => done()); 55 | }); 56 | 57 | it('should get specific FileMaker records.', () => { 58 | return expect( 59 | client 60 | .create(process.env.LAYOUT, { name: 'Obi-Wan' }) 61 | .then(response => client.get(process.env.LAYOUT, response.recordId)) 62 | ) 63 | .to.eventually.be.a('object') 64 | .that.has.all.keys('data', 'dataInfo'); 65 | }); 66 | 67 | it('should allow you to specify a timeout', () => { 68 | return expect( 69 | client 70 | .create(process.env.LAYOUT, { name: 'Darth Vader' }) 71 | .then(response => 72 | client.get(process.env.LAYOUT, response.recordId, { 73 | request: { timeout: 10 } 74 | }) 75 | ) 76 | .catch(error => error) 77 | ) 78 | .to.eventually.be.an('object') 79 | .with.any.keys('message', 'code'); 80 | }); 81 | 82 | it('should reject get requests that do not specify a recordId', () => { 83 | return expect( 84 | client 85 | .create(process.env.LAYOUT, { name: 'Obi-Wan' }) 86 | .then(response => client.get(process.env.LAYOUT, '-2')) 87 | .catch(error => error) 88 | ) 89 | .to.eventually.be.a('object') 90 | .that.has.all.keys('code', 'message'); 91 | }); 92 | 93 | it('should allow you to limit the number of portal records to return', () => { 94 | return expect( 95 | client.create(process.env.LAYOUT, { name: 'Obi-Wan' }).then(response => 96 | client.get(process.env.LAYOUT, response.recordId, { 97 | portal: ['planets'], 98 | 'limit.planets': 2 99 | }) 100 | ) 101 | ) 102 | .to.eventually.be.a('object') 103 | .that.has.all.keys('data', 'dataInfo') 104 | .and.property('data'); 105 | }); 106 | 107 | it('should accept namespaced portal limit and offset parameters', () => { 108 | return expect( 109 | client.create(process.env.LAYOUT, { name: 'Obi-Wan' }).then(response => 110 | client.get(process.env.LAYOUT, response.recordId, { 111 | portal: ['planets'], 112 | '_limit.planets': 2 113 | }) 114 | ) 115 | ) 116 | .to.eventually.be.a('object') 117 | .that.has.all.keys('data', 'dataInfo') 118 | .and.property('data'); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/globals.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Global Capabilities', () => { 25 | let database; 26 | let client; 27 | before(done => { 28 | environment.config({ path: './test/.env' }); 29 | varium({ manifestPath }); 30 | connect('nedb://memory') 31 | .then(db => { 32 | database = db; 33 | return database.dropDatabase(); 34 | }) 35 | .then(() => { 36 | return done(); 37 | }); 38 | }); 39 | 40 | before(done => { 41 | client = Filemaker.create({ 42 | database: process.env.DATABASE, 43 | server: process.env.SERVER, 44 | user: process.env.USERNAME, 45 | password: process.env.PASSWORD 46 | }); 47 | client.save().then(client => done()); 48 | }); 49 | 50 | after(done => { 51 | client 52 | .reset() 53 | .then(response => done()) 54 | .catch(error => done()); 55 | }); 56 | 57 | it('should allow you to set session globals', () => { 58 | return expect( 59 | client.globals({ 'Globals::ship': 'Millenium Falcon' }) 60 | ).to.eventually.be.a('object'); 61 | }); 62 | 63 | it('should allow you to specify a timeout', () => { 64 | return expect( 65 | client 66 | .globals( 67 | { 'Globals::ship': 'Millenium Falcon' }, 68 | { 69 | request: { timeout: 10 } 70 | } 71 | ) 72 | .catch(error => error) 73 | ) 74 | .to.eventually.be.an('object') 75 | .with.any.keys('message', 'code'); 76 | }); 77 | 78 | it('should reject with a message and code if it fails to set a global', () => { 79 | return expect( 80 | client.globals({ ship: 'Millenium Falcon' }).catch(error => error) 81 | ) 82 | .to.eventually.be.a('object') 83 | .that.has.all.keys('message', 'code'); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/interceptors.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after afterEach it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const sinon = require('sinon'); 15 | const chaiAsPromised = require('chai-as-promised'); 16 | const environment = require('dotenv'); 17 | const varium = require('varium'); 18 | 19 | const { connect } = require('marpat'); 20 | const { Filemaker } = require('../index.js'); 21 | const { urls } = require('../src/utilities'); 22 | 23 | const sandbox = sinon.createSandbox(); 24 | const manifestPath = path.join(__dirname, './env.manifest'); 25 | 26 | chai.use(chaiAsPromised); 27 | 28 | describe('Request Interceptor Capabilities', () => { 29 | let database; 30 | let client; 31 | 32 | before(done => { 33 | environment.config({ path: './test/.env' }); 34 | varium({ manifestPath }); 35 | connect('nedb://memory') 36 | .then(db => { 37 | database = db; 38 | return database.dropDatabase(); 39 | }) 40 | .then(() => { 41 | return done(); 42 | }); 43 | }); 44 | 45 | before(done => { 46 | client = Filemaker.create({ 47 | database: process.env.DATABASE, 48 | server: process.env.SERVER, 49 | user: process.env.USERNAME, 50 | password: process.env.PASSWORD 51 | }); 52 | client.save().then(client => done()); 53 | }); 54 | 55 | after(done => { 56 | client 57 | .reset() 58 | .then(response => done()) 59 | .catch(error => done()); 60 | }); 61 | 62 | afterEach(done => { 63 | sandbox.restore(); 64 | return done(); 65 | }); 66 | 67 | it('should reject if the server errors', () => { 68 | sandbox 69 | .stub(urls, 'authentication') 70 | .callsFake(() => 'https://httpstat.us/502'); 71 | return expect( 72 | client 73 | .save() 74 | .then(client => client.login()) 75 | .catch(error => error) 76 | ) 77 | .to.eventually.be.an('object') 78 | .that.has.all.keys('code', 'message'); 79 | }); 80 | 81 | it('should intercept authentication errors', () => { 82 | sandbox 83 | .stub(urls, 'authentication') 84 | .callsFake(() => 'https://httpstat.us/400'); 85 | return expect( 86 | client 87 | .save() 88 | .then(client => { 89 | client.agent.connection.sessions = []; 90 | return client.login(); 91 | }) 92 | .catch(error => error) 93 | ) 94 | .to.eventually.be.an('object') 95 | .that.has.all.keys('code', 'message'); 96 | }); 97 | 98 | it('should intercept json responses that do not return a token', () => { 99 | sandbox 100 | .stub(urls, 'authentication') 101 | .callsFake(() => 'https://httpstat.us/200'); 102 | 103 | return expect( 104 | client 105 | .save() 106 | .then(client => client.login()) 107 | .catch(error => error) 108 | ) 109 | .to.eventually.be.an('object') 110 | .that.has.all.keys('code', 'message'); 111 | }); 112 | 113 | it('should intercept non json responses', () => { 114 | sandbox.stub(urls, 'authentication').callsFake(() => 'https://httpstat.us'); 115 | 116 | return expect( 117 | client 118 | .save() 119 | .then(client => client.login()) 120 | .catch(error => error) 121 | ) 122 | .to.eventually.be.an('object') 123 | .that.has.all.keys('code', 'message'); 124 | }); 125 | 126 | it('should reject non http requests to the server with a json error', () => { 127 | sandbox 128 | .stub(urls, 'authentication') 129 | .callsFake( 130 | () => 131 | `${process.env.SERVER.replace( 132 | 'https://', 133 | '' 134 | )}/fmi/data/v1/databases/${process.env.application}/sessions` 135 | ); 136 | return expect( 137 | client 138 | .save() 139 | .then(client => client.login()) 140 | .catch(error => error) 141 | ) 142 | .to.eventually.be.an('object') 143 | .that.has.all.keys('code', 'message'); 144 | }); 145 | 146 | it('should reject non https requests to the server with a json error', () => { 147 | sandbox 148 | .stub(urls, 'authentication') 149 | .callsFake( 150 | () => 151 | `${process.env.SERVER.replace( 152 | 'https://', 153 | 'http://' 154 | )}/fmi/data/v1/databases/${process.env.application}/sessions` 155 | ); 156 | return expect( 157 | client 158 | .save() 159 | .then(client => client.login()) 160 | .catch(error => error) 161 | ) 162 | .to.eventually.be.an('object') 163 | .that.has.all.keys('message', 'code'); 164 | }); 165 | 166 | it('should convert non json responses to json', () => { 167 | return expect( 168 | client 169 | .save() 170 | .then(client => client.agent.handleResponse('string response')) 171 | .catch(error => error) 172 | ) 173 | .to.eventually.be.an('object') 174 | .that.has.all.keys('message', 'code'); 175 | }); 176 | }); 177 | -------------------------------------------------------------------------------- /test/layout.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after afterEach it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const sinon = require('sinon'); 15 | const chaiAsPromised = require('chai-as-promised'); 16 | const environment = require('dotenv'); 17 | const varium = require('varium'); 18 | const { connect } = require('marpat'); 19 | const { Filemaker } = require('../index.js'); 20 | 21 | const sandbox = sinon.createSandbox(); 22 | const manifestPath = path.join(__dirname, './env.manifest'); 23 | 24 | chai.use(chaiAsPromised); 25 | 26 | describe('Layout Metadata Capabilities', () => { 27 | let database; 28 | let client; 29 | before(done => { 30 | environment.config({ path: './test/.env' }); 31 | varium({ manifestPath }); 32 | connect('nedb://memory') 33 | .then(db => { 34 | database = db; 35 | return database.dropDatabase(); 36 | }) 37 | .then(() => { 38 | return done(); 39 | }); 40 | }); 41 | 42 | before(done => { 43 | client = Filemaker.create({ 44 | database: process.env.DATABASE, 45 | server: process.env.SERVER, 46 | user: process.env.USERNAME, 47 | password: process.env.PASSWORD 48 | }); 49 | client.save().then(client => done()); 50 | }); 51 | 52 | after(done => { 53 | client 54 | .reset() 55 | .then(response => done()) 56 | .catch(error => done()); 57 | }); 58 | 59 | afterEach(done => { 60 | sandbox.restore(); 61 | return done(); 62 | }); 63 | 64 | it('should get field and portal metadata for a layout', () => { 65 | return expect(client.layout(process.env.LAYOUT)) 66 | .to.eventually.be.a('object') 67 | .that.has.all.keys('fieldMetaData', 'portalMetaData'); 68 | }); 69 | it('should require a layout', () => { 70 | return expect(client.layout().catch(error => error)) 71 | .to.eventually.be.a('object') 72 | .that.has.all.keys('message', 'code'); 73 | }); 74 | it('should fail with a code and a message', () => { 75 | return expect(client.layout('not a layout').catch(error => error)) 76 | .to.eventually.be.a('object') 77 | .that.has.all.keys('message', 'code'); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/layouts.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after afterEach it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const sinon = require('sinon'); 15 | const chaiAsPromised = require('chai-as-promised'); 16 | const environment = require('dotenv'); 17 | const varium = require('varium'); 18 | const { connect } = require('marpat'); 19 | const { Filemaker } = require('../index.js'); 20 | const { urls } = require('../src/utilities'); 21 | 22 | const sandbox = sinon.createSandbox(); 23 | 24 | const manifestPath = path.join(__dirname, './env.manifest'); 25 | 26 | chai.use(chaiAsPromised); 27 | 28 | describe('Database Layout List Capabilities', () => { 29 | let database; 30 | let client; 31 | before(done => { 32 | environment.config({ path: './test/.env' }); 33 | varium({ manifestPath }); 34 | connect('nedb://memory') 35 | .then(db => { 36 | database = db; 37 | return database.dropDatabase(); 38 | }) 39 | .then(() => { 40 | return done(); 41 | }); 42 | }); 43 | 44 | before(done => { 45 | client = Filemaker.create({ 46 | database: process.env.DATABASE, 47 | server: process.env.SERVER, 48 | user: process.env.USERNAME, 49 | password: process.env.PASSWORD 50 | }); 51 | client.save().then(client => done()); 52 | }); 53 | 54 | after(done => { 55 | client 56 | .reset() 57 | .then(response => done()) 58 | .catch(error => done()); 59 | }); 60 | 61 | afterEach(done => { 62 | sandbox.restore(); 63 | return done(); 64 | }); 65 | 66 | it('should get a list of Layouts and folders for the currently configured database', () => { 67 | return expect(client.layouts()) 68 | .to.eventually.be.a('object') 69 | .that.has.all.keys('layouts'); 70 | }); 71 | it('should fail with a code and a message', () => { 72 | sandbox.stub(urls, 'layouts').callsFake(() => 'https://httpstat.us/502'); 73 | return expect(client.layouts().catch(error => error)) 74 | .to.eventually.be.a('object') 75 | .that.has.all.keys('message', 'code'); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/list.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('List Capabilities', () => { 25 | let database; 26 | let client; 27 | before(done => { 28 | environment.config({ path: './test/.env' }); 29 | varium({ manifestPath }); 30 | connect('nedb://memory') 31 | .then(db => { 32 | database = db; 33 | return database.dropDatabase(); 34 | }) 35 | .then(() => { 36 | return done(); 37 | }); 38 | }); 39 | 40 | before(done => { 41 | client = Filemaker.create({ 42 | database: process.env.DATABASE, 43 | server: process.env.SERVER, 44 | user: process.env.USERNAME, 45 | password: process.env.PASSWORD 46 | }); 47 | client.save().then(client => done()); 48 | }); 49 | 50 | after(done => { 51 | client 52 | .reset() 53 | .then(response => done()) 54 | .catch(error => done()); 55 | }); 56 | 57 | it('should allow you to list records', () => { 58 | return expect(client.list(process.env.LAYOUT)) 59 | .to.eventually.be.a('object') 60 | .that.has.all.keys('data', 'dataInfo') 61 | .and.property('data'); 62 | }); 63 | 64 | it('should allow you to specify a timeout', () => { 65 | return expect( 66 | client 67 | .list(process.env.LAYOUT, { 68 | request: { timeout: 10 } 69 | }) 70 | .catch(error => error) 71 | ) 72 | .to.eventually.be.a('object') 73 | .that.has.all.keys('code', 'message'); 74 | }); 75 | 76 | it('should allow you use parameters to modify the list response', () => { 77 | return expect(client.list(process.env.LAYOUT, { _limit: '2' })) 78 | .to.eventually.be.a('object') 79 | .that.has.all.keys('data', 'dataInfo') 80 | .and.property('data') 81 | .to.have.a.lengthOf(2); 82 | }); 83 | 84 | it('should should allow you to use numbers in parameters', () => { 85 | return expect(client.list(process.env.LAYOUT, { _limit: 2 })) 86 | .to.eventually.be.a('object') 87 | .that.has.all.keys('data', 'dataInfo') 88 | .and.property('data') 89 | .to.have.a.lengthOf(2); 90 | }); 91 | 92 | it('should should allow you to provide an array of portals in parameters', () => { 93 | return expect( 94 | client.list(process.env.LAYOUT, { 95 | _limit: 2, 96 | portals: [{ name: 'planets', limit: 1, offset: 1 }] 97 | }) 98 | ) 99 | .to.eventually.be.a('object') 100 | .that.has.all.keys('data', 'dataInfo') 101 | .and.property('data') 102 | .to.be.a('array') 103 | .and.property(0) 104 | .to.have.all.keys( 105 | 'fieldData', 106 | 'modId', 107 | 'portalData', 108 | 'recordId', 109 | 'portalDataInfo' 110 | ) 111 | .and.property('portalData') 112 | .to.be.a('object'); 113 | }); 114 | 115 | it('should should remove non used properties from a portal object', () => { 116 | return expect( 117 | client.list(process.env.LAYOUT, { 118 | _limit: 2, 119 | portals: [{ name: 'planets', limit: 1, offset: 1, han: 'solo' }] 120 | }) 121 | ) 122 | .to.eventually.be.a('object') 123 | .that.has.all.keys('data', 'dataInfo') 124 | .and.property('data') 125 | .to.be.a('array') 126 | .and.property(0) 127 | .to.have.all.keys( 128 | 'fieldData', 129 | 'modId', 130 | 'portalData', 131 | 'recordId', 132 | 'portalDataInfo' 133 | ) 134 | .and.property('portalData') 135 | .to.be.a('object'); 136 | }); 137 | 138 | it('should modify requests to comply with DAPI name reservations', () => { 139 | return expect(client.list(process.env.LAYOUT, { limit: 2 })) 140 | .to.eventually.be.a('object') 141 | .that.has.all.keys('data', 'dataInfo') 142 | .and.property('data') 143 | .to.have.a.lengthOf(2); 144 | }); 145 | 146 | it('should allow strings while complying with DAPI name reservations', () => { 147 | return expect(client.list(process.env.LAYOUT, { limit: '2' })) 148 | .to.eventually.be.a('object') 149 | .that.has.all.keys('data', 'dataInfo') 150 | .and.property('data') 151 | .to.have.a.lengthOf(2); 152 | }); 153 | 154 | it('should allow you to offset the list response', () => { 155 | return expect(client.list(process.env.LAYOUT, { limit: 2, offset: 2 })) 156 | .to.eventually.be.a('object') 157 | .that.has.all.keys('data', 'dataInfo') 158 | .and.property('data') 159 | .to.have.a.lengthOf(2); 160 | }); 161 | 162 | it('should santize parameters that would cause unexpected parameters', () => { 163 | return expect( 164 | client.list(process.env.LAYOUT, { error: 'fail', limit: 2, offset: 2 }) 165 | ) 166 | .to.eventually.be.a('object') 167 | .that.has.all.keys('data', 'dataInfo'); 168 | }); 169 | 170 | it('should allow you to limit the number of portal records to return', () => { 171 | return expect( 172 | client.list(process.env.LAYOUT, { 173 | portal: ['planets'], 174 | 'limit.planets': 2, 175 | limit: 2 176 | }) 177 | ) 178 | .to.eventually.be.a('object') 179 | .that.has.all.keys('data', 'dataInfo') 180 | .and.property('data') 181 | .to.have.a.lengthOf(2); 182 | }); 183 | 184 | it('should accept namespaced portal limit and offset parameters', () => { 185 | return expect( 186 | client.list(process.env.LAYOUT, { 187 | portal: ['planets'], 188 | '_limit.planets': 2, 189 | limit: 2 190 | }) 191 | ) 192 | .to.eventually.be.a('object') 193 | .that.has.all.keys('data', 'dataInfo') 194 | .and.property('data') 195 | .to.have.a.lengthOf(2); 196 | }); 197 | 198 | it('should reject invalid parameters', () => { 199 | return expect( 200 | client 201 | .list(process.env.LAYOUT, { error: 'fail', limit: -2, offset: 2 }) 202 | .catch(error => error) 203 | ) 204 | .to.eventually.be.a('object') 205 | .that.has.all.keys('message', 'code'); 206 | }); 207 | }); 208 | -------------------------------------------------------------------------------- /test/productInfo.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after afterEach it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const sinon = require('sinon'); 15 | const chaiAsPromised = require('chai-as-promised'); 16 | const environment = require('dotenv'); 17 | const varium = require('varium'); 18 | const { connect } = require('marpat'); 19 | const { Filemaker, productInfo } = require('../index.js'); 20 | const { urls } = require('../src/utilities'); 21 | 22 | const sandbox = sinon.createSandbox(); 23 | const manifestPath = path.join(__dirname, './env.manifest'); 24 | 25 | chai.use(chaiAsPromised); 26 | 27 | process.on('unhandledRejection', () => {}); 28 | process.on('rejectionHandled', () => {}); 29 | 30 | describe('Client Product Info Capabilities', () => { 31 | let database; 32 | let client; 33 | before(done => { 34 | environment.config({ path: './test/.env' }); 35 | varium({ manifestPath }); 36 | connect('nedb://memory') 37 | .then(db => { 38 | database = db; 39 | return database.dropDatabase(); 40 | }) 41 | .then(() => { 42 | return done(); 43 | }); 44 | }); 45 | 46 | before(done => { 47 | client = Filemaker.create({ 48 | database: process.env.DATABASE, 49 | server: process.env.SERVER, 50 | user: process.env.USERNAME, 51 | password: process.env.PASSWORD 52 | }); 53 | client.save().then(client => done()); 54 | }); 55 | 56 | after(done => { 57 | client 58 | .reset() 59 | .then(response => done()) 60 | .catch(error => done()); 61 | }); 62 | 63 | afterEach(done => { 64 | sandbox.restore(); 65 | return done(); 66 | }); 67 | 68 | it('should get FileMaker Server Information', () => { 69 | return expect(client.productInfo()) 70 | .to.eventually.be.a('object') 71 | .that.has.all.keys( 72 | 'name', 73 | 'buildDate', 74 | 'version', 75 | 'dateFormat', 76 | 'timeFormat', 77 | 'timeStampFormat' 78 | ); 79 | }); 80 | it('should fail with a code and a message', () => { 81 | sandbox 82 | .stub(urls, 'productInfo') 83 | .callsFake(() => 'https://httpstat.us/502'); 84 | return expect(client.productInfo().catch(error => error)) 85 | .to.eventually.be.a('object') 86 | .that.has.all.keys('message', 'code'); 87 | }); 88 | }); 89 | 90 | describe('Product Info Utility Capabilities', () => { 91 | before(done => { 92 | environment.config({ path: './test/.env' }); 93 | varium({ manifestPath }); 94 | return done(); 95 | }); 96 | 97 | it('should get FileMaker Server Information', () => { 98 | return expect(productInfo(process.env.SERVER)) 99 | .to.eventually.be.a('object') 100 | .that.has.all.keys( 101 | 'name', 102 | 'buildDate', 103 | 'version', 104 | 'dateFormat', 105 | 'timeFormat', 106 | 'timeStampFormat' 107 | ); 108 | }); 109 | it('should fail with a code and a message', () => { 110 | return expect(productInfo('http://not-a-server.com').catch(error => error)) 111 | .to.eventually.be.a('object') 112 | .that.has.all.keys('message', 'code'); 113 | }); 114 | it('should require a server parameter', () => { 115 | return expect(productInfo().catch(error => error)) 116 | .to.eventually.be.a('object') 117 | .that.has.all.keys('message', 'code'); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/queue.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Request Queue Capabilities', () => { 25 | let database; 26 | let client; 27 | 28 | before(done => { 29 | environment.config({ path: './test/.env' }); 30 | varium({ manifestPath }); 31 | connect('nedb://memory') 32 | .then(db => { 33 | database = db; 34 | return database.dropDatabase(); 35 | }) 36 | .then(() => { 37 | return done(); 38 | }); 39 | }); 40 | 41 | before(done => { 42 | client = Filemaker.create({ 43 | database: process.env.DATABASE, 44 | server: process.env.SERVER, 45 | concurrency: 25, 46 | user: process.env.USERNAME, 47 | password: process.env.PASSWORD 48 | }); 49 | client.save().then(client => done()); 50 | }); 51 | 52 | after(done => { 53 | client 54 | .reset() 55 | .then(response => done()) 56 | .catch(error => done()); 57 | }); 58 | 59 | it('should queue requests to FileMaker', () => { 60 | let requests = []; 61 | for (var i = 6; i >= 0; i--) { 62 | requests.push( 63 | client.find( 64 | process.env.LAYOUT, 65 | { id: '*' }, 66 | { sort: [{ fieldName: 'id', sortOrder: 'descend' }], limit: 2 } 67 | ) 68 | ); 69 | } 70 | 71 | return Promise.all(requests).then(results => 72 | expect(client.agent.queue).to.be.an('array') 73 | ); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/recordId.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker, recordId } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('RecordId Capabilities', () => { 25 | let database; 26 | let client; 27 | 28 | before(done => { 29 | client = Filemaker.create({ 30 | database: process.env.DATABASE, 31 | server: process.env.SERVER, 32 | user: process.env.USERNAME, 33 | password: process.env.PASSWORD 34 | }); 35 | client.save().then(client => done()); 36 | }); 37 | 38 | after(done => { 39 | client 40 | .reset() 41 | .then(response => done()) 42 | .catch(error => done()); 43 | }); 44 | 45 | before(done => { 46 | environment.config({ path: './test/.env' }); 47 | varium({ manifestPath }); 48 | connect('nedb://memory') 49 | .then(db => { 50 | database = db; 51 | return database.dropDatabase(); 52 | }) 53 | .then(() => { 54 | return done(); 55 | }); 56 | }); 57 | 58 | it('it should extract the recordId while maintaining the array', () => { 59 | return expect( 60 | client 61 | .create(process.env.LAYOUT, { name: 'Obi-Wan' }) 62 | .then(response => client.get(process.env.LAYOUT, response.recordId)) 63 | .then(record => recordId(record.data)) 64 | ) 65 | .to.eventually.be.a('array') 66 | .and.property('0') 67 | .to.be.a('string'); 68 | }); 69 | 70 | it('it should extract recordId while maintaining the object', () => { 71 | return expect( 72 | client 73 | .create(process.env.LAYOUT, { name: 'Obi-Wan' }) 74 | .then(response => client.get(process.env.LAYOUT, response.recordId)) 75 | .then(record => recordId(record.data[0])) 76 | ).to.eventually.be.a('string'); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/run.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Script Queue Capabilities', () => { 25 | let database; 26 | let client; 27 | 28 | before(done => { 29 | environment.config({ path: './test/.env' }); 30 | varium({ manifestPath }); 31 | connect('nedb://memory') 32 | .then(db => { 33 | database = db; 34 | return database.dropDatabase(); 35 | }) 36 | .then(() => { 37 | return done(); 38 | }); 39 | }); 40 | 41 | before(done => { 42 | client = Filemaker.create({ 43 | database: process.env.DATABASE, 44 | server: process.env.SERVER, 45 | user: process.env.USERNAME, 46 | password: process.env.PASSWORD 47 | }); 48 | client.save().then(client => done()); 49 | }); 50 | 51 | after(done => { 52 | client 53 | .reset() 54 | .then(response => done()) 55 | .catch(error => done()); 56 | }); 57 | 58 | it('should allow you to trigger a script with an object', () => { 59 | return expect( 60 | client.run(process.env.LAYOUT, { 61 | name: 'FMS Triggered Script', 62 | phase: 'presort', 63 | param: { 64 | name: 'han', 65 | number: 102, 66 | object: { child: 'ben' }, 67 | array: ['leia', 'chewbacca'] 68 | } 69 | }) 70 | ) 71 | .to.eventually.be.a('object') 72 | .that.has.all.keys('scriptResult.presort'); 73 | }); 74 | 75 | it('should allow you to trigger a script with an object', () => { 76 | return expect( 77 | client.run(process.env.LAYOUT, { 78 | name: 'FMS Triggered Script', 79 | phase: 'presort', 80 | param: { 81 | name: 'han', 82 | number: 102, 83 | object: { child: 'ben' }, 84 | array: ['leia', 'chewbacca'] 85 | } 86 | }) 87 | ) 88 | .to.eventually.be.a('object') 89 | .that.has.all.keys('scriptResult.presort'); 90 | }); 91 | 92 | it('should allow you to trigger a script with an array', () => { 93 | return expect( 94 | client.run(process.env.LAYOUT, [ 95 | { 96 | name: 'FMS Triggered Script', 97 | param: { 98 | name: 'han', 99 | number: 102, 100 | object: { child: 'ben' }, 101 | array: ['leia', 'chewbacca'] 102 | } 103 | } 104 | ]) 105 | ) 106 | .to.eventually.be.a('object') 107 | .that.has.all.keys('scriptResult'); 108 | }); 109 | 110 | it('should allow you to trigger a script via a string', () => { 111 | return expect(client.run(process.env.LAYOUT, 'FMS Triggered Script')) 112 | .to.eventually.be.a('object') 113 | .that.has.all.keys('scriptResult'); 114 | }); 115 | 116 | it('should allow you to specify a timeout', () => { 117 | return expect( 118 | client 119 | .run( 120 | process.env.LAYOUT, 121 | { 122 | name: 'FMS Triggered Script', 123 | param: { 124 | name: 'han', 125 | number: 102, 126 | object: { child: 'ben' }, 127 | array: ['leia', 'chewbacca'] 128 | } 129 | }, 130 | { 131 | request: { timeout: 10 } 132 | } 133 | ) 134 | .catch(error => error) 135 | ) 136 | .to.eventually.be.an('object') 137 | .with.any.keys('message', 'code'); 138 | }); 139 | 140 | it('should allow you to trigger a script without specifying a parameter', () => { 141 | return expect(client.run(process.env.LAYOUT, 'FMS Triggered Script')) 142 | .to.eventually.be.a('object') 143 | .that.has.all.keys('scriptResult'); 144 | }); 145 | 146 | it('should allow you to trigger a script specifying a string as a parameter', () => { 147 | return expect( 148 | client.run(process.env.LAYOUT, 'FMS Triggered Script', 'string-here') 149 | ) 150 | .to.eventually.be.a('object') 151 | .that.has.all.keys('scriptResult'); 152 | }); 153 | 154 | it('should allow you to trigger a script specifying a number as a parameter', () => { 155 | return expect(client.run(process.env.LAYOUT, 'FMS Triggered Script', 1)) 156 | .to.eventually.be.a('object') 157 | .that.has.all.keys('scriptResult'); 158 | }); 159 | 160 | it('should allow you to trigger a script specifying an object as a parameter', () => { 161 | return expect( 162 | client.run(process.env.LAYOUT, 'FMS Triggered Script', { 163 | object: true 164 | }) 165 | ) 166 | .to.eventually.be.a('object') 167 | .that.has.all.keys('scriptResult'); 168 | }); 169 | 170 | it('should reject a script that does not exist', () => { 171 | return expect( 172 | client.run(process.env.LAYOUT, 'Made up Script').catch(error => error) 173 | ) 174 | .to.eventually.be.a('object') 175 | .that.has.all.keys('code', 'message'); 176 | }); 177 | 178 | it('should allow return a result even if a script returns an error', () => { 179 | return expect( 180 | client.run(process.env.LAYOUT, 'Error Script').catch(error => error) 181 | ) 182 | .to.eventually.be.a('object') 183 | .that.has.all.keys('scriptResult'); 184 | }); 185 | 186 | it('should parse script results if the results are json', () => { 187 | return expect( 188 | client.run(process.env.LAYOUT, { 189 | name: 'FMS Triggered Script', 190 | param: 'Han' 191 | }) 192 | ) 193 | .to.eventually.be.a('object') 194 | .that.has.all.keys('scriptResult'); 195 | }); 196 | 197 | it('should not parse script results if the results are not json', () => { 198 | return expect(client.run(process.env.LAYOUT, { name: 'Non JSON Script' })) 199 | .to.eventually.be.a('object') 200 | .that.has.all.keys('scriptResult'); 201 | }); 202 | }); 203 | -------------------------------------------------------------------------------- /test/scripts.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after afterEach it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const sinon = require('sinon'); 15 | const chaiAsPromised = require('chai-as-promised'); 16 | const environment = require('dotenv'); 17 | const varium = require('varium'); 18 | const { connect } = require('marpat'); 19 | const { Filemaker } = require('../index.js'); 20 | const { urls } = require('../src/utilities'); 21 | 22 | const sandbox = sinon.createSandbox(); 23 | const manifestPath = path.join(__dirname, './env.manifest'); 24 | 25 | chai.use(chaiAsPromised); 26 | 27 | describe('Database Script List Capabilities', () => { 28 | let database; 29 | let client; 30 | before(done => { 31 | environment.config({ path: './test/.env' }); 32 | varium({ manifestPath }); 33 | connect('nedb://memory') 34 | .then(db => { 35 | database = db; 36 | return database.dropDatabase(); 37 | }) 38 | .then(() => { 39 | return done(); 40 | }); 41 | }); 42 | 43 | before(done => { 44 | client = Filemaker.create({ 45 | database: process.env.DATABASE, 46 | server: process.env.SERVER, 47 | user: process.env.USERNAME, 48 | password: process.env.PASSWORD 49 | }); 50 | client.save().then(client => done()); 51 | }); 52 | 53 | after(done => { 54 | client 55 | .reset() 56 | .then(response => done()) 57 | .catch(error => done()); 58 | }); 59 | 60 | afterEach(done => { 61 | sandbox.restore(); 62 | return done(); 63 | }); 64 | 65 | it('should get a list of scripts and folders for the currently configured database', () => { 66 | return expect(client.scripts()) 67 | .to.eventually.be.a('object') 68 | .that.has.all.keys('scripts'); 69 | }); 70 | it('should fail with a code and a message', () => { 71 | sandbox.stub(urls, 'scripts').callsFake(() => 'https://httpstat.us/502'); 72 | return expect(client.scripts().catch(error => error)) 73 | .to.eventually.be.a('object') 74 | .that.has.all.keys('message', 'code'); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/session.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | const { admin } = require('./admin'); 10 | 11 | /* eslint-enable */ 12 | 13 | const path = require('path'); 14 | const chai = require('chai'); 15 | const chaiAsPromised = require('chai-as-promised'); 16 | const environment = require('dotenv'); 17 | const varium = require('varium'); 18 | const { connect } = require('marpat'); 19 | const { Filemaker } = require('../index.js'); 20 | 21 | const manifestPath = path.join(__dirname, './env.manifest'); 22 | 23 | chai.use(chaiAsPromised); 24 | 25 | describe('Session Capabilities', () => { 26 | describe('Session Efficency', () => { 27 | let database; 28 | let client; 29 | const name = 'testing-client'; 30 | 31 | before(done => { 32 | environment.config({ path: './test/.env' }); 33 | varium({ manifestPath }); 34 | connect('nedb://memory') 35 | .then(db => { 36 | database = db; 37 | return database.dropDatabase(); 38 | }) 39 | .then(() => { 40 | client = Filemaker.create({ 41 | name, 42 | database: process.env.DATABASE, 43 | server: process.env.SERVER, 44 | user: process.env.USERNAME, 45 | concurrency: 25, 46 | password: process.env.PASSWORD 47 | }); 48 | return client.save(); 49 | }) 50 | .then(client => admin.login()) 51 | .then(() => admin.sessions.drop({ userName: process.env.USERNAME })) 52 | .then(() => setTimeout(() => done(), 15000)); 53 | }); 54 | 55 | after(done => { 56 | admin 57 | .logout() 58 | .then(() => done()) 59 | .catch(error => done(new Error(error.response.data.messages[0].text))); 60 | }); 61 | 62 | it('should reuse sessions when they are avalable', () => { 63 | const wait = 5000; 64 | const repetition = 5; 65 | const results = []; 66 | const chain = client => 67 | new Promise((resolve, reject) => { 68 | const interval = setInterval(() => { 69 | client 70 | .find( 71 | process.env.LAYOUT, 72 | { id: '*' }, 73 | { omit: true, name: 'Darth Vader' } 74 | ) 75 | .then(result => { 76 | results.push(results); 77 | if (results.length >= repetition) { 78 | clearInterval(interval); 79 | resolve({ client, results }); 80 | } 81 | }); 82 | }, wait); 83 | }); 84 | 85 | return expect( 86 | Filemaker.findOne({ name }) 87 | .then(client => chain(client)) 88 | .then( 89 | ({ client, results }) => 90 | new Promise(resolve => 91 | setTimeout(() => resolve({ client, results }), 12000) 92 | ) 93 | ) 94 | .then(() => 95 | admin.sessions.find({ 96 | userName: process.env.USERNAME 97 | }) 98 | ) 99 | ) 100 | .to.eventually.be.a('array') 101 | .to.have.a.lengthOf(1); 102 | }); 103 | }); 104 | 105 | describe('Session Concurrency', () => { 106 | let database; 107 | let client; 108 | const name = 'testing-client'; 109 | 110 | before(done => { 111 | environment.config({ path: './test/.env' }); 112 | varium({ manifestPath }); 113 | connect('nedb://memory') 114 | .then(db => { 115 | database = db; 116 | return database.dropDatabase(); 117 | }) 118 | .then(() => { 119 | client = Filemaker.create({ 120 | name, 121 | database: process.env.DATABASE, 122 | server: process.env.SERVER, 123 | user: process.env.USERNAME, 124 | concurrency: 25, 125 | password: process.env.PASSWORD 126 | }); 127 | return client.save(); 128 | }) 129 | .then(client => admin.login()) 130 | .then(() => admin.sessions.drop({ userName: process.env.USERNAME })) 131 | .then(() => setTimeout(() => done(), 15000)); 132 | }); 133 | 134 | after(done => { 135 | admin 136 | .logout() 137 | .then(() => done()) 138 | .catch(error => done(new Error(error.response.data.messages[0].text))); 139 | }); 140 | 141 | it('should create new sessions to prevent request collisions', () => { 142 | const wait = 1000; 143 | let current = 0; 144 | const repetition = 5; 145 | const results = []; 146 | const chain = client => 147 | new Promise((resolve, reject) => { 148 | const interval = setInterval(() => { 149 | current = current + 1; 150 | if (current <= repetition) { 151 | client 152 | .script(process.env.LAYOUT, 'Pause Script', { 153 | pause: 5 154 | }) 155 | .then(result => { 156 | results.push(results); 157 | if (results.length >= repetition) { 158 | resolve({ client, results }); 159 | } 160 | }); 161 | } else { 162 | clearInterval(interval); 163 | } 164 | }, wait); 165 | }); 166 | 167 | return expect( 168 | Filemaker.findOne({ name }) 169 | .then(client => chain(client)) 170 | .then( 171 | ({ client, results }) => 172 | new Promise(resolve => 173 | setTimeout(() => resolve({ client, results }), 12000) 174 | ) 175 | ) 176 | .then(() => 177 | admin.sessions.find({ 178 | userName: process.env.USERNAME 179 | }) 180 | ) 181 | ) 182 | .to.eventually.be.a('array') 183 | .to.have.a.lengthOf(repetition); 184 | }); 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /test/storage.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before beforeEach it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Storage', () => { 25 | let database; 26 | let client; 27 | 28 | before(done => { 29 | environment.config({ path: './test/.env' }); 30 | varium({ manifestPath }); 31 | connect('nedb://memory') 32 | .then(db => { 33 | database = db; 34 | return database.dropDatabase(); 35 | }) 36 | .then(() => { 37 | return done(); 38 | }); 39 | }); 40 | 41 | beforeEach(done => { 42 | client = Filemaker.create({ 43 | database: process.env.DATABASE, 44 | server: process.env.SERVER, 45 | user: process.env.USERNAME, 46 | password: process.env.PASSWORD 47 | }); 48 | done(); 49 | }); 50 | 51 | it('should allow an instance to be created', () => { 52 | return expect(Promise.resolve(client)) 53 | .to.eventually.be.an('object') 54 | .that.has.all.keys('_schema', '_id', 'data', 'agent', 'name'); 55 | }); 56 | 57 | it('should allow an instance to be saved.', () => { 58 | return expect(client.save()) 59 | .to.eventually.be.an('object') 60 | .that.has.all.keys('_schema', '_id', 'data', 'agent', 'name'); 61 | }); 62 | 63 | it('should reject if a client can not be validated', () => { 64 | client = Filemaker.create({ 65 | database: process.env.DATABASE, 66 | server: 'mutesymphony.com', 67 | user: process.env.USERNAME, 68 | password: process.env.PASSWORD 69 | }); 70 | return expect(client.save().catch(error => error)).to.eventually.be.an( 71 | 'error' 72 | ); 73 | }); 74 | 75 | it('should allow an instance to be recalled', () => { 76 | return expect(Filemaker.findOne({})) 77 | .to.eventually.be.an('object') 78 | .that.has.all.keys('_schema', '_id', 'data', 'agent', 'name'); 79 | }); 80 | 81 | it('should allow instances to be listed', () => { 82 | return expect(Filemaker.find({})).to.eventually.be.an('array'); 83 | }); 84 | 85 | it('should allow you to remove an instance', () => { 86 | return expect(Filemaker.deleteOne({})) 87 | .to.eventually.be.an('number') 88 | .and.equal(1); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/transform.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker, transform } = require('../index'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Transform Capabilities', () => { 25 | let database; 26 | let client; 27 | 28 | before(done => { 29 | client = Filemaker.create({ 30 | database: process.env.DATABASE, 31 | server: process.env.SERVER, 32 | user: process.env.USERNAME, 33 | password: process.env.PASSWORD 34 | }); 35 | client.save().then(client => done()); 36 | }); 37 | 38 | after(done => { 39 | client 40 | .reset() 41 | .then(response => done()) 42 | .catch(error => done()); 43 | }); 44 | 45 | before(done => { 46 | environment.config({ path: './test/.env' }); 47 | varium({ manifestPath }); 48 | connect('nedb://memory') 49 | .then(db => { 50 | database = db; 51 | return database.dropDatabase(); 52 | }) 53 | .then(() => { 54 | return done(); 55 | }); 56 | }); 57 | 58 | it('should merge portal data and field data from an array', () => { 59 | return expect( 60 | client 61 | .find(process.env.LAYOUT, { name: 'Han Solo' }) 62 | .then(response => transform(response.data)) 63 | ) 64 | .to.eventually.be.a('array') 65 | .and.property('0') 66 | .to.be.a('object') 67 | .and.to.all.include.keys('modId', 'recordId') 68 | .and.to.not.include.keys('portalData', 'fieldData'); 69 | }); 70 | 71 | it('should merge portal data and field data from an object', () => { 72 | return expect( 73 | client 74 | .find(process.env.LAYOUT, { name: 'Han Solo' }) 75 | .then(response => transform(response.data[0])) 76 | ) 77 | .to.eventually.to.be.a('object') 78 | .and.to.all.include.keys('modId', 'recordId') 79 | .and.to.not.include.keys('portalData', 'fieldData'); 80 | }); 81 | 82 | it('should optionally not convert table::field keys from an array', () => { 83 | return expect( 84 | client 85 | .find(process.env.LAYOUT, { name: 'Han Solo' }) 86 | .then(response => transform(response.data, { convert: false })) 87 | ) 88 | .to.eventually.be.a('array') 89 | .and.property('0') 90 | .to.be.a('object') 91 | .and.to.all.include.keys('modId', 'recordId') 92 | .and.to.not.include.keys('portalData', 'fieldData'); 93 | }); 94 | 95 | it('should optionally not convert table::field keys from an object', () => { 96 | return expect( 97 | client 98 | .find(process.env.LAYOUT, { name: 'Han Solo' }) 99 | .then(response => transform(response.data[0], { convert: false })) 100 | ) 101 | .to.eventually.to.be.a('object') 102 | .and.to.all.include.keys('modId', 'recordId') 103 | .and.to.not.include.keys('portalData', 'fieldData'); 104 | }); 105 | 106 | it('should allow you to remove field data from an array', () => { 107 | return expect( 108 | client 109 | .find(process.env.LAYOUT, { name: 'Han Solo' }) 110 | .then(response => transform(response.data, { fieldData: false })) 111 | ) 112 | .to.eventually.be.a('array') 113 | .and.property('0') 114 | .to.be.a('object') 115 | .and.to.all.include.keys('modId', 'recordId') 116 | .and.to.not.include.keys('fieldData'); 117 | }); 118 | 119 | it('should allow you to remove field data from an object', () => { 120 | return expect( 121 | client 122 | .find(process.env.LAYOUT, { name: 'Han Solo' }) 123 | .then(response => transform(response.data[0], { fieldData: false })) 124 | ) 125 | .to.eventually.be.a('object') 126 | .and.to.all.include.keys('modId', 'recordId') 127 | .and.to.not.include.keys('fieldData'); 128 | }); 129 | 130 | it('should allow you to remove portal data from an array', () => { 131 | return expect( 132 | client 133 | .find(process.env.LAYOUT, { name: 'Han Solo' }) 134 | .then(response => transform(response.data, { portalData: false })) 135 | ) 136 | .to.eventually.be.a('array') 137 | .and.property('0') 138 | .to.be.a('object') 139 | .and.to.all.include.keys('modId', 'recordId') 140 | .and.to.not.include.keys('portalData'); 141 | }); 142 | 143 | it('should allow you to remove portal data from an object', () => { 144 | return expect( 145 | client 146 | .find(process.env.LAYOUT, { name: 'Han Solo' }) 147 | .then(response => transform(response.data[0], { portalData: false })) 148 | ) 149 | .to.eventually.to.be.a('object') 150 | .and.to.all.include.keys('modId', 'recordId') 151 | .and.to.not.include.keys('portalData'); 152 | }); 153 | 154 | it('should merge portal data and portal data from an array', () => { 155 | return expect( 156 | client 157 | .find(process.env.LAYOUT, { name: 'Han Solo' }) 158 | .then(response => transform(response.data, { portalData: false })) 159 | ) 160 | .to.eventually.be.a('array') 161 | .and.property('0') 162 | .to.be.a('object') 163 | .and.to.all.include.keys('modId', 'recordId') 164 | .and.to.not.include.keys('portalData'); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /test/usage.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe before after it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const path = require('path'); 13 | const chai = require('chai'); 14 | const chaiAsPromised = require('chai-as-promised'); 15 | const environment = require('dotenv'); 16 | const varium = require('varium'); 17 | const { connect } = require('marpat'); 18 | const { Filemaker } = require('../index.js'); 19 | 20 | const manifestPath = path.join(__dirname, './env.manifest'); 21 | 22 | chai.use(chaiAsPromised); 23 | 24 | describe('Data Usage ', () => { 25 | let database; 26 | let client; 27 | 28 | describe('Tracks Data Usage', () => { 29 | before(done => { 30 | environment.config({ path: './test/.env' }); 31 | varium({ manifestPath }); 32 | connect('nedb://memory') 33 | .then(db => { 34 | database = db; 35 | return database.dropDatabase(); 36 | }) 37 | .then(() => { 38 | return done(); 39 | }); 40 | }); 41 | 42 | before(done => { 43 | client = Filemaker.create({ 44 | database: process.env.DATABASE, 45 | server: process.env.SERVER, 46 | user: process.env.USERNAME, 47 | password: process.env.PASSWORD 48 | }); 49 | client.save().then(client => done()); 50 | }); 51 | 52 | after(done => { 53 | client 54 | .logout() 55 | .then(response => done()) 56 | .catch(error => done()); 57 | }); 58 | 59 | it('should track API usage data.', () => { 60 | return expect( 61 | client 62 | .create(process.env.LAYOUT, { name: 'Luke Skywalker' }) 63 | .then(response => client.data.status()) 64 | ) 65 | .to.eventually.be.an('object') 66 | .that.has.all.keys('data'); 67 | }); 68 | 69 | it('should allow you to reset usage data.', () => { 70 | return expect( 71 | client 72 | .create(process.env.LAYOUT, { name: 'Luke Skywalker' }) 73 | .then(response => { 74 | client.data.clear(); 75 | return client; 76 | }) 77 | .then(filemaker => client.data.status()) 78 | ) 79 | .to.eventually.be.an('object') 80 | .that.has.all.keys('data'); 81 | }); 82 | }); 83 | describe('Does Not Track Data Usage', () => { 84 | before(done => { 85 | environment.config({ path: './test/.env' }); 86 | varium({ manifestPath }); 87 | connect('nedb://memory') 88 | .then(db => { 89 | database = db; 90 | return database.dropDatabase(); 91 | }) 92 | .then(() => { 93 | return done(); 94 | }); 95 | }); 96 | 97 | before(done => { 98 | client = Filemaker.create({ 99 | database: process.env.DATABASE, 100 | server: process.env.SERVER, 101 | user: process.env.USERNAME, 102 | password: process.env.PASSWORD, 103 | usage: false 104 | }); 105 | client.save().then(client => done()); 106 | }); 107 | 108 | after(done => { 109 | client 110 | .logout() 111 | .then(response => done()) 112 | .catch(error => done()); 113 | }); 114 | 115 | it('should not track data usage in', () => { 116 | return expect( 117 | client 118 | .create(process.env.LAYOUT, { name: 'Luke Skywalker' }) 119 | .then(response => client.data.status()) 120 | ) 121 | .to.eventually.be.an('object') 122 | .and.property('data') 123 | .that.has.all.keys('in', 'out', 'since') 124 | .and.property('in', '0 Bytes'); 125 | }); 126 | 127 | it('should not track data usage out', () => { 128 | return expect( 129 | client 130 | .create(process.env.LAYOUT, { name: 'Luke Skywalker' }) 131 | .then(response => { 132 | client.data.clear(); 133 | return client; 134 | }) 135 | .then(filemaker => client.data.status()) 136 | ) 137 | .to.eventually.be.an('object') 138 | .and.property('data') 139 | .that.has.all.keys('in', 'out', 'since') 140 | .and.property('out', '0 Bytes'); 141 | }); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /test/utilities.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global describe it */ 4 | 5 | /* eslint-disable */ 6 | 7 | const assert = require('assert'); 8 | const { expect, should } = require('chai'); 9 | 10 | /* eslint-enable */ 11 | 12 | const chai = require('chai'); 13 | const chaiAsPromised = require('chai-as-promised'); 14 | const { 15 | pick, 16 | omit, 17 | parse, 18 | isJSON 19 | } = require('../src/utilities/conversion.utilities'); 20 | 21 | chai.use(chaiAsPromised); 22 | 23 | describe('Conversion Utility Capabilities', () => { 24 | describe('Omit Utility', () => { 25 | it('it should remove properties while maintaing the array', () => { 26 | return expect( 27 | omit( 28 | [ 29 | { name: 'Luke Skywalker', planet: 'tatooine' }, 30 | { name: 'Luke Skywalker', planet: 'tatooine' } 31 | ], 32 | ['planet'] 33 | ) 34 | ) 35 | .to.be.a('array') 36 | .and.property('0') 37 | .to.be.a('object') 38 | .and.to.not.include.keys('planet'); 39 | }); 40 | 41 | it('it should remove properties while maintaing the object', () => { 42 | return expect( 43 | omit({ name: 'Luke Skywalker', planet: 'tatooine' }, ['planet']) 44 | ) 45 | .to.be.a('object') 46 | .and.to.not.include.keys('planet'); 47 | }); 48 | }); 49 | describe('Parse Utility', () => { 50 | it('it should return a string when given a string', () => { 51 | return expect(parse('A String')).to.be.a('string'); 52 | }); 53 | it('it should return an object when given a stringified object', () => { 54 | return expect(parse(JSON.stringify({ name: 'Han Solo' }))) 55 | .to.be.a('object') 56 | .and.to.include.keys('name'); 57 | }); 58 | }); 59 | describe('isJSON Utility', () => { 60 | it('it should return true for an object', () => { 61 | return expect(isJSON({ object: true })).to.equal(true); 62 | }); 63 | it('it should return true for an empty object', () => { 64 | return expect(isJSON({ object: true })).to.equal(true); 65 | }); 66 | it('it should return true for a stringified object', () => { 67 | return expect(isJSON({})).to.equal(true); 68 | }); 69 | it('it should return false for a number', () => { 70 | return expect(isJSON(1)).to.equal(false); 71 | }); 72 | it('it should return false for undefined', () => { 73 | return expect(isJSON()).to.equal(false); 74 | }); 75 | it('it should return false for a string', () => { 76 | return expect(isJSON('string')).to.equal(false); 77 | }); 78 | it('it should return false for null', () => { 79 | return expect(isJSON(null)).to.equal(false); 80 | }); 81 | }); 82 | }); 83 | 84 | describe('Filemaker Utility Capabilities', () => { 85 | describe('filter Results', () => { 86 | it('it should pick an array of properties while maintaing the array', () => { 87 | return expect( 88 | pick( 89 | [ 90 | { name: 'Luke Skywalker', planet: 'tatooine' }, 91 | { name: 'Luke Skywalker', planet: 'tatooine' } 92 | ], 93 | ['name'] 94 | ) 95 | ) 96 | .to.be.a('array') 97 | .and.property('0') 98 | .to.be.a('object') 99 | .and.to.include.keys('name'); 100 | }); 101 | 102 | it('it should pick an array of properties while maintaing the object', () => { 103 | return expect( 104 | pick( 105 | { name: 'Luke Skywalker', affiliation: 'jedi', planet: 'tatooine' }, 106 | ['name', 'planet'] 107 | ) 108 | ) 109 | .to.be.a('object') 110 | .and.to.include.all.keys('planet', 'name'); 111 | }); 112 | it('it should pick a string property while maintaing the array', () => { 113 | return expect( 114 | pick( 115 | [ 116 | { name: 'Luke Skywalker', planet: 'tatooine' }, 117 | { name: 'Luke Skywalker', planet: 'tatooine' } 118 | ], 119 | 'name' 120 | ) 121 | ) 122 | .to.be.a('array') 123 | .and.property('0') 124 | .to.be.a('object') 125 | .and.to.include.keys('name'); 126 | }); 127 | 128 | it('it should pick a string property while maintaing the object', () => { 129 | return expect( 130 | pick( 131 | { name: 'Luke Skywalker', affiliation: 'jedi', planet: 'tatooine' }, 132 | 'name' 133 | ) 134 | ) 135 | .to.be.a('object') 136 | .and.to.include.all.keys('name'); 137 | }); 138 | }); 139 | }); 140 | --------------------------------------------------------------------------------