{"id":31562504,"url":"https://github.com/inversion-api/inversion-engine","last_synced_at":"2025-10-05T04:12:48.818Z","repository":{"id":36052149,"uuid":"212625733","full_name":"inversion-api/inversion-engine","owner":"inversion-api","description":"Inversion Cloud API Engine","archived":false,"fork":false,"pushed_at":"2024-10-24T16:42:56.000Z","size":17455,"stargazers_count":17,"open_issues_count":0,"forks_count":4,"subscribers_count":9,"default_branch":"main","last_synced_at":"2024-11-15T04:58:21.402Z","etag":null,"topics":["dynamodb","elasticsearch","graphql","inversion-api","lambda","mysql","rest-api","rql","serverless","spring-boot"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inversion-api.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-03T16:24:47.000Z","updated_at":"2024-10-10T15:33:08.000Z","dependencies_parsed_at":"2024-09-17T22:23:07.667Z","dependency_job_id":null,"html_url":"https://github.com/inversion-api/inversion-engine","commit_stats":{"total_commits":1159,"total_committers":21,"mean_commits":55.19047619047619,"dds":"0.27092320966350303","last_synced_commit":"4bafa11b6c6a5d145f401acd2860bfa2287429ee"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/inversion-api/inversion-engine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inversion-api%2Finversion-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inversion-api%2Finversion-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inversion-api%2Finversion-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inversion-api%2Finversion-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inversion-api","download_url":"https://codeload.github.com/inversion-api/inversion-engine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inversion-api%2Finversion-engine/sbom","scorecard":{"id":492206,"data":{"date":"2025-08-11","repo":{"name":"github.com/inversion-api/inversion-engine","commit":"57545497093c3d5fa56a41557be585044a0087a1"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.1,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/7 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build-gradle-project.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-gradle-project.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/inversion-api/inversion-engine/build-gradle-project.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-gradle-project.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/inversion-api/inversion-engine/build-gradle-project.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-gradle-project.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/inversion-api/inversion-engine/build-gradle-project.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/build-gradle-project.yml:15"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 24 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T19:28:31.614Z","repository_id":36052149,"created_at":"2025-08-19T19:28:31.614Z","updated_at":"2025-08-19T19:28:31.614Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278406819,"owners_count":25981688,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dynamodb","elasticsearch","graphql","inversion-api","lambda","mysql","rest-api","rql","serverless","spring-boot"],"created_at":"2025-10-05T04:12:47.526Z","updated_at":"2025-10-05T04:12:48.800Z","avatar_url":"https://github.com/inversion-api.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Inversion Logo Title](docs/images/logoheader.png)\n\n\nInversion is the fastest way to deliver full featured and secure REST APIs.\n\nWith Inversion, you can create an API that connects your app front end directly to your back end data source without any server side programming required.\n\nInversion is not a code generator it is a runtime service that reflectively creates secure best practice JSON REST APIs for CRUD operations against\nmultiple back end data sources including Relational Database Systems (RDBMS) such as MySQL, and PostgreSQL, NoSQL systems including Elasticsearch and Amazon's DynamoDB, Azure CosmosDb and many more.\n\n## Contents\n- [Inversion Cloud API Engine](#inversion-cloud-api-engine)\n    - [Build Status](#build-status)\n    - [Contents](#contents)\n    - [Features and Benefits](#features-and-benefits)\n    - [Quick Start](#quick-start)\n        - [Maven Repos](#maven-repos)\n        - [Coding Your Own API](#coding-your-own-api)\n        - [Configuration Instead of Code](#configuration-instead-of-code)\n    - [URL Structure](#url-structure)\n    - [Configuring Your API](#configuring-your-api)\n        - [Configuration File Loading](#configuration-file-loading)\n    - [Keeping Passwords out of Config Files](#keeping-passwords-out-of-config-files)\n    - [Resource Query Language (RQL)](#resource-query-language-rql)\n        - [General](#general)\n        - [Query Functions](#query-functions)\n        - [Sorting and Ordering](#sorting-and-ordering)\n        - [Pagination, Offset and Limit](#pagination-offset-and-limit)\n        - [Property Inclusion / Exclusion](#property-inclusion--exclusion)\n        - [Aggregations](#aggregations)\n        - [Nested Document Expansion](#nested-document-expansion)\n        - [Reserved Query String Parameters](#reserved-query-string-parameters)\n        - [Restricted and Required Query Parameters](#restricted-and-required-query-parameters)\n        - [Miscellaneous](#miscellaneous)\n    - [Core Object Model Concepts](#core-object-model-concepts)\n        - [Apis](#apis)\n        - [Dbs, Tables, Columns and Indexes](#dbs-tables-columns-and-indexes)\n        - [Collections, Entities, Attributes and Relationships](#collections-entities-attributes-and-relationships)\n        - [Endpoints and Actions](#endpoints-and-actions)\n        - [AclAction and AclRules](#aclaction-and-aclrules)\n        - [Path Matching](#codecPath-matching)\n    - [Security Model](#security-model)\n        - [Account Roles](#account-roles)\n        - [Api Permissions](#api-permissions)\n        - [Authentication](#authentication)\n        - [Authorization](#authorization)\n        - [Multi-Tenant APIs](#multi-tenant-apis)\n        - [Row Level Security](#row-level-security)\n    - [Elasticsearch Specifics](#elasticsearch-specifics)\n        - [Elasticsearch RQL Examples](#elasticsearch-rql-examples)\n    - [DynamoDB Specifics](#dynamodb-specifics)\n    - [Developer Notes](#developer-notes)\n        - [Javadocs](#javadocs)\n        - [Logging](#logging)\n        - [Gradle, Maven, etc.](#gradle-maven-etc)\n    - [REST API Design Resources](#rest-api-design-resources)\n        - [HTTP Status Codes](#http-status-codes)\n        - [Standards-ish](#standards-ish)\n        - [Web Query Languages](#web-query-languages)\n        - [Best Practices and Design Resources](#best-practices-and-design-resources)\n        - [REST APIs in the Wild](#rest-apis-in-the-wild)\n\n\n## Features and Benefits\n* Deploy a secure JSON REST API against your back end database in five minutes without any coding.\n* All APIs are automatically documented in OAS 3.0.3 (Swagger)\n* Support for MySql, PostgreSQL, Microsoft SqlServer, AWS DynamoDb, ElasticSearch, Azure CommosDb, S3, Redis and many more.\n* Makes structured relational database backed APIs as frictionless as working with an unstructured document store dbs.\n* Database tables are automatically exposed as REST collections endpoints.\n* Database foreign key relationships result in nested documents that can be retrieved in a single request to eliminate over fetching.\n* Elegant \"beautification\" and pluralization of \"ugly\" table and column names.  All collection and attribute names are converted to JSON friendly camelCase.  \"MY_COLUMN_NAME\" gets converted to \"myColumnName\".\n* Smart pagination and ordering with a consistent document envelope.\n* Powerful Resource Query Language (RQL) lets you select the exact resources you are looking for.\n* For complex nested document PUT/POSTS, all resources are \"upserted\" with new or updated foreign key dependencies updated first and relationships resolved automatically.  You can compose a rich client side JSON model mixing existing and new resources and PUT/POST the entire object in one request.\n* SQL Injection proof through the use of prepared statements instead of dynamic SQL.\n* Permission and role based declarative security model.\n* Always CORS cross site enabled.\n* Expose multiple back ends databases in a single API.\n* Designed to support single and multi-tenant API usage patterns.\n* \"Explain\" mode shows you exactly what the back end is doing including all SQL statements run.\n* Configured via drop dead simple properties files or directly in Java code.\n* If you are a Java / Spring Boot developer, Inversion can work natively within your Spring Boot application to add extensive capabilities but not interfering with custom Spring Boot code or configuration.\n\n\n\n## Quick Start\n\nWith just a few lines of code, [Demo001SqlDbNorthwind.java](https://github.com/inversion-api/inversion-demos/blob/master/src/main/java/io/rocketpartners/demo/demo001/Demo001SqlDbNorthwind.java)\nlaunches a full featured demo API that exposes SQL database tables as REST collection endpoints.  The demo\nsupports full GET,PUT,POST,DELETE operations with an extensive Resource Query Language (RQL) for GET requests.\n\nThe demo connects to an in memory H2 SQL database that gets initialized from\nscratch each time the demo is run.  That means you can fully explore\nmodifying operations (POST,PUT,PATCH,DELETE) and 'break' whatever you want. When\nyou restart that data will be back in its original demo state.\n\nTo run the demo simply clone the GitHub repo, build it with Gradle, then launch the demo app via Gradle.\n\n```\ngit clone https://github.com/inversion-api/inversion-engine.git\n./gradlew build\n./gradlew demo1\n```\n\nThe demo API is now running at 'http://localhost:8080/northwind with REST collection endpoints for each DB resource.\n\nYou can get started by exploring some of these urls:\n- GET http://localhost:8080/northwind/products\n- GET http://localhost:8080/northwind/orders?expands=orderDetails\u0026page=2\n- GET http://localhost:8080/northwind/customers?in(country,France,Spain)\u0026sort=-customerid\u0026pageSize=10\n- GET http://localhost:8080/northwind/customers?orders.shipCity=Mannheim\n\nAppend '\u0026explain=true' to any query string to see an explanation of what is happening under the covers\n- GET http://localhost:8080/northwind/employees?title='Sales%20Representative'\u0026sort=employeeid\u0026pageSize=2\u0026page=2\u0026explain=true\n\n\n### Maven Repos\n\nThis project uses Travis CI to publish master branch commits as snapshot builds to\nthe [oss.jfrog.org (aka OJO) maven repository](https://oss.jfrog.org/artifactory/webapp/#/artifacts/browse/tree/General/oss-snapshot-local/io/inversion/) and\nrelease branch commits to to the [Inversion bintray maven repository](https://bintray.com/inversion/repo)\nwhich syncs to jcenter.\n\nIf you are a Java developer comfortable with maven, you can include the following repos\nin your project pull release or snapshot builds.  Here is a gradle config example.\n\n```\n   repositories {\n \t\tmavenCentral()\n    }\n    \n    dependencies {\n\n\t\t//change to pull the version you want \n\t\tcompile \"io.inversion:inversion-api:0.6+\"\n\t\t\n\t\t//you can pin to a specific release build like ths\n\t\t//these come from jcenter / bintray\n\t\t//compile \"io.inversion:inversion-api:0.6.18\"\n\t\t\n\t\t//any version that includes \"-SNAPSHOT\" will pull\n\t\t//from the oss.jfrog.org repo, not jcenter\n\t\t//compile \"io.inversion:inversion-api:0.6.18-SNAPSHOT\"\n\t\t\n\t\t//if you are including multiple inversion libs, it is a good \n\t\t//idea to set a variable such as \"inversionVersion=0.6.18\" in your \n\t\t//gradle.properties file and use that to import your dependencies \n\t\t//compile \"io.inversion:inversion-api:${inversionVersion}\"\n\t\t//compile \"io.inversion:inversion-dynamodb:${inversionVersion}\"\n\t\t//compile \"io.inversion:inversion-s3:${inversionVersion}\"\n\t\t//compile \"io.inversion:inversion-elasticsearch:${inversionVersion}\"\n\n\t}\n    \n```\n\nIf OJO snapshots are not cutting edge enough for you, you can also pull snapshots directly from GitHub\nvia [Jitpack](https://jitpack.io/#inversion-api/inversion-engine).  While you can pull tagged releases\nfrom Jitpack, it is not recommended.  Please consider only use Jitpack for pulling snapshots of branches.\n\n```\n\trepositories {\n\t\tjcenter()\n\t\tmaven { url 'https://jitpack.io' }\n\t\t}\n\t\t\n\tdependencies {\n\t\t\n\t\tcompile 'compile 'com.github.inversion-api.inversion-engine:inversion-api:master-SNAPSHOT'\n\t\tcompile 'compile 'com.github.inversion-api.inversion-engine:inversion-dynamodb:master-SNAPSHOT'\n\t\t\n\t}\n```\n\n\n\n### Coding Your Own API\n\nYou can build an API that connects to your own DB back end with just a few lines of Java code.\n\n```java\nInversionMain.run(new Api()\n            .withName(\"demo\")\n            .withDb(new JdbcDb(\"dbNickname\", \n                              \"${YOUR_JDBC_DRIVER}\", \n                              \"${YOUR_JDBC_URL}\", \n                              \"${YOUR_JDBC_USERNAME}\", \n                              \"${YOUR_JDBC_PASSWORD\"))\n            .withEndpoint(\"GET,PUT,POST,DELETE\", \"/*\", new DbAction()));\n```\n\n### Configuration Instead of Code\n\nOr, if you prefer, you can wire up an API via configuration files, instead of through code.\nThe properties file below will create an identical API to the Java coded example above\n\nPlace the example below into a file \"./inversion.properties\".\n\n```properties\ndemo.class=io.inversion.Api\n\ndb.class=io.inversion.jdbc.JdbcDb\ndb.driver=${YOUR_JDBC_DRIVER}\ndb.url=${YOUR_JDBC_URL}\ndb.user=${YOUR_JDBC_USERNAME}\ndb.pass=${YOUR_JDBC_PASSWORD}\n\nep.class=io.inversion.Endpoint\nep.methods=GET,PUT,POST,DELETE\nep.codecPath=/*\nep.actions=rest\n\nrest.class=io.inversion.action.db.DbAction\n```\n\nThen launch Inversion and it will wire up your API from the configuration.  If\nInversion has already been built, you skip the 'gradle build' in the example below.\n\n```\ngradle build\njava -jar build/libs/rocket-inversion-master.jar\n```\n\n## URL Structure\n\nInversion is designed to host multiple APIs potentially owned by different tenants.  All URLs are prefixed with an accountCode and apiCode codecPath components.  The accountCode uniquely identifies the organization that owns the API and is unique to the host server. The apiCode uniquely identifies the Api within the namespace created by the accountCode.\n\nValid based URL formats are:\n* http(s)://host.com/[${servletPath}]/${accountCode}/${apiCode}/\n* http(s)://host.com/[${servletPath}]/${accountCode}/${apiCode}/\n* http(s)://${accountCode}.host.com/[${servletPath}]/${apiCode}/\n* http(s)://host.com/[${servletPath}]/${accountCode} ONLY when apiCode and accountCode are the same thing (used to make a prettier URL for a 'default' Api per Account)\n\nA default configuration would then offer Endpoint URLs such as below where ${COLLECTION} is the pluralized version of your table names.  Non\nplural versions will be redirected to the plural url.\n* ${API_URL}/[${OPTIONAL_ENDPOINT_PATH}]/${COLLECTION}/\n* ${API_URL}/[${OPTIONAL_ENDPOINT_PATH}]/${COLLECTION}/${RESOURCE}\n* ${API_URL}/[${OPTIONAL_ENDPOINT_PATH}]/${COLLECTION}/${RESOURCE}/${RELATIONSHIP}\n\nExamples example:\n* 'http\u0026#58;//localhost/johns_books/orders' would return a paginated listing of all orders from the api with an accountCode and apiCode of 'johns_books'\n* 'http\u0026#58;//localhost/johns_books/orders/1234' would return the details of order 1234\n* 'http\u0026#58;//localhost/johns_books/orders/1234/books' would return all of the books related to the order without returning order 1234 itself\n* 'http\u0026#58;//localhost/johns_books/orders/1234?expands=books' would return the 1234 details document with the related array of books already expanded (see document expansion below)\n\n## Configuring Your API\n\n\n### Configuration File Loading\n\nAn Inversion API can be wired up natively in Java code or via configuration files.\n\nWhen started, Inversion attempts to locate files with the following naming conventions in the classpath (including the working directory).\n* inversion[-][1-100].properties\n* inversion[-][1-100][-${inversion.profile}].properties\n* inversion[-][-${inversion.profile}][-][1-100].properties\n\n${inversion.profile} is an optional environment variable or Java system param that allows you to load different configrations at runtime based on container attributes.  Files are loaded sequentially and files with a matching ${inversion.profile} in their hame are loaded AFTER files without a profile.\n\nAll files are loaded into a shared map so \"the last loaded\nkey wins\" in terms of overwriting settings.  This design is intended to make it easy to support multiple runtime configurations such as 'dev' or 'prod' with files that do not have to duplicate config between them.\n\nA typical configuration, for example, may load:\n* inversion1.properties, inversion2.properties,inversion-dev-1.properties, if ${inversion.profile} has been set to 'dev'\n* OR, inversion.properties, inversion-prod.properties,inversion-prod-1.properties if ${inversion.profile} has been set to 'prod'\n\nA helpful development trick here is to launch Inversion with a JVM parameter '-Dinversion.profile=dev' add something like \"inversion99-dev.properties\"\nto your .gitignore and keep any local developement only settings in that file.  That way you won't commit settings you don't want to share\nand you custom settings will load last trumping any other keys shared with other files.\n\nEach config file itself is a glorified bean property map in form of bean.name=value. Any bean in scope can be used as a value on the right side of the assignment and '.'\nnotation to any level of nesting on the left hand side is valid.  You can assign multiple values to a list on the left hand side by setting a comma separated list ex:\nbean.aList=bean1,bean2,bean3.  Nearly any JavaBean property in the object model (see Java Docs) can be wired up through the config file.\n\n\nConfiguration and API bootstrapping takes place in the following stages:\n\n1. Inversion service wiring - All 'inversion.*' bean properties are set on the core Inversion service instance.  This is useful for things like setting 'inversion.debug' or changing 'inversion.profile'.\n1. Initial loading - This stage loads/merges all of the user supplied config files according to the above algorithm\n1. Api and Db instantiation - All Api and Db instances from the user supplied config are instantiated and wired up.  This is a minimum initial wiring.\n1. Db reflection - 'db.bootstrapApi(api)' is called for each Db configed by the user.  The Db instance reflectively inspects its data source and creates a Table,Column,Index\n   model to match the datasource and then adds Collection,Resource,Attribute,Relationship objects to the Api that map back to the Tables,Columns and Indexes.\n1. Serialization - The dynamically configured Api model is then serialized back out to name=value property format as if it were user supplied config.\n1. The user supplied configs from step 1 are then merged down on onto the system generated config.  This means that you can overwrite any dynamically configured key/value pair.\n1. JavaBeans are auto-wired together and all Api objects in the resulting output are then loading into Inversion.addApi() and are ready to run.\n\nThis process allows the user supplied configuration to be kept to a minimum while also allowing any reflectively generated configuration to be overridden.  Instead\nof configing up the entire db-to-api mapping, all you have to supply are the changes you want to make to the generated defaults.  This reflective config generation\nhappens in memory at runtime NOT development time.\n\nIf you set the config property \"inversion.configOut=some/file/codecPath/merged.properties\" Inversion will output the final merged properites file so you can inpspect any keys to find\nany that you may want to customize.\n\nThe wiring parser is made to be as forgiving as possible and knows about the expected relationships between different object types.  For instance if you do NOT\nsupply a key such as \"api.dbs=db1,db2,db3\" the system will go ahead and assign all of the configed Dbs to the Api.  If there is more than one Api defined or if\nthe dbs have been set via config already, this auto wiring step will not happen.  Same thing goes for Endpoints being automatically added to an Api.\n\n\n## Keeping Passwords out of Config Files\n\nIf you want to keep your database passwords (or any other sensative info) out of your inversion.properties config files, you can simply set an environment variable OR\nJVM system property using the relevant key.  For example you could add '-Ddb.pass=MY_PASSWORD' to your JVM launch configuration OR something like 'EXPORT db.pass=MY_PASSWORD' to the top of the batch file you use to launch our app or application server.\n\n\n\n## Resource Query Language (RQL)\n\nRQL is the set of HTTP query string parameters that allows developers to \"slice and dice\" the data returned from the API to meet their specific needs.\n\n### General\n\n* Many functions can be written in one of several equivelant forms:\n* name=value - the traditional query string format\n* function(column, value OR expression) - eq(col,value), lt(column,value), and(eq(col,value), lt(column,value)), in(col, val1, val2, val3, val4)\n* name=eq=value, column=lt=value, col=in=val1,val2,val3\n* Quotes \u0026 Escaping Quotes - ' or \" can be used to quote values.  Use a \\ to escape any inner occurances of the outter quote.  If you quote with single quotes you don't have to escape inner double quotes and vice versa\n* Wildcards - the '*' character is treated as a univeral wildcard for all supported back ends.  \n  For example, for SQL back ends '*' would be substituded with \"%\" and instead of using '=' operator the system would substitude 'LIKE'.  You use the normal '=' or 'eq' operator but the system uses LIKE and % under the covers.\n\n\n[See TestSqlQuery.java for many examples of complex RQL queries and how they translate into SQL](https://github.com/RocketPartners/rocket-inversion/blob/master/src/test/java/io/rocketpartners/action/sql/TestSqlQuery.java)\n\n\n### Query Functions\n\n| RQL Function                     |      Database      |      Elastic       |       Dynamo       | Description                                                                                          |\n | -------------------------------- | :----------------: | :----------------: | :----------------: | ---------------------------------------------------------------------------------------------------- |\n| column=value                     | :heavy_check_mark: |  :grey_question:   |  :grey_question:   | translates as expected into a sql column equality check \"column = value\"                             |\n| column='singleTicks'             | :heavy_check_mark: |  :grey_question:   |  :grey_question:   | 'values can have spaces with encapsulated in quotes'                                                 |\n| column=\"doubleQuotes\"            | :heavy_check_mark: |  :grey_question:   |  :grey_question:   | \"double or single quotes work\"                                                                       |\n| column=\" ' \"                     | :heavy_check_mark: |  :grey_question:   |  :grey_question:   | \"the first quote type wins so a single quote like ' inside of double quotes is considered a literal\" |\n| column=\" \\\" \"                    | :heavy_check_mark: |  :grey_question:   |  :grey_question:   | \"you can also \\\" escape quotes with a backslash\"                                                     |\n| column=wild*card                 | :heavy_check_mark: |  :grey_question:   |  :grey_question:   | something*blah - translates into \"column LIKE 'something%blah'\"                                      |\n| eq(column,value)                 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | alternate form of column=value                                                                       |\n| gt(column,value)                 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | greater than query filter eg: \"column \u003c value\"                                                       |\n| ge(column,value)                 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | greater than or equal to                                                                             |\n| lt(column,value)                 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | less than filter                                                                                     |\n| le(column,value)                 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | less than or equal to                                                                                |\n| ne(column,value)                 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | not equal                                                                                            |\n| in(column,val1,[val2...valN])    | :heavy_check_mark: | :heavy_check_mark: |                    | translates into \"where column in (val1,....valN)\"                                                    |\n| out(column,val1,[val2...valN])   | :heavy_check_mark: | :heavy_check_mark: |                    | translates into \"where column NOT in (val1,....valN)\"                                                |\n| and(clause1,clause2,[...clauseN) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ANDs multiple clauses.  Example: and(eq(city,Atlanta),gt(zipCode,30030))                             |\n| or(clause1,clause2,[...clauseN)  | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ORs multiple clauses.  Example: or(eq(city,Atlanta),gt(zipCode,30030))                               |\n| emp(column)                      |  :grey_question:   | :heavy_check_mark: |                    | retrieves empty rows for a column value. null or empty string values will be retrieved               |\n| nemp(column)                     |  :grey_question:   | :heavy_check_mark: |                    | retrieves all rows that do not contain an empty string or null value for a specified column          |\n| n(column)                        |  :grey_question:   | :heavy_check_mark: | :heavy_check_mark: | retrieves all rows that contain a null value for a specified column                                  |\n| nn(column)                       |  :grey_question:   | :heavy_check_mark: | :heavy_check_mark: | retrieves all rows that do not contain a null value for a specified column                           |\n| w(column,[value])                |                    | :heavy_check_mark: | :heavy_check_mark: | retrieves all rows 'with' that wildcarded value in the specified column                              |\n| ew(column,[value])               |                    | :heavy_check_mark: |                    | retrieves all rows that 'end with' that wildcarded value in the specified column                     |\n| sw(column,[value])               |                    | :heavy_check_mark: | :heavy_check_mark: | retrieves all rows that 'start with' that wildcarded value in the specified column                   |\n\n\n### Sorting and Ordering\n\n| RQL Function               |      Database      | Elastic |       Dynamo       | Description                                                                         |\n | -------------------------- | :----------------: | :-----: | :----------------: | ----------------------------------------------------------------------------------- |\n| sort=col1,+col2,-col3,colN | :heavy_check_mark: |         | :grey_exclamation: | use of the + operator is the implied default.  Prefixing with \"-\" sorts descending. |\n| sort(col1,[...colN])       | :heavy_check_mark: |         |                    | same as sort= but with \"function format\"                                            |\n| order                      | :heavy_check_mark: |         |                    | an overloaded synonym for \"sort\", the two are equivelant.                           |\n\n\n### Pagination, Offset and Limit\n\n| RQL Function |      Database      | Elastic | Dynamo | Description                                                                                  |\n | ------------ | :----------------: | :-----: | :----: | -------------------------------------------------------------------------------------------- |\n| page=N       | :heavy_check_mark: |         |        | translates into an offset clause using pagesize (or the default page size) as the multiplier |\n| pagenum=N    | :heavy_check_mark: |         |        | an overloaded synonym for \"page\", the two are equivelant.                                    |\n| pagesize=N   | :heavy_check_mark: |         |        | the number of results to return                                                              |\n| offset=N     | :heavy_check_mark: |         |        | directly translates into a sql offset clause, overrides any page/pagenum params supplied     |\n| limit=N      | :heavy_check_mark: |         |        | directly translates into a SQL limit clause, overrides any pagesize params supplied          |\n\n\n### Property Inclusion / Exclusion\n\n| RQL Function            |      Database      | Elastic | Dynamo | Description                                                                                            |\n | ----------------------- | :----------------: | :-----: | :----: | ------------------------------------------------------------------------------------------------------ |\n| includes=col1,col2,colN | :heavy_check_mark: |         |        | restricts the properties returned in the document to the ones specified.  All others will be excluded. |\n| includes(col1...colN)   | :heavy_check_mark: |         |        | same as above                                                                                          |\n| excludes=col1,col2,colN | :heavy_check_mark: |         |        | specifically excludes the supplied props.  All others will be included.                                |\n| excludes(col1...colN)   | :heavy_check_mark: |         |        | same as above                                                                                          |\n\n\n\n\n### Aggregations\n\n| RQL Function                                        |      Database      | Elastic | Dynamo | Description                                                              |\n | --------------------------------------------------- | :----------------: | :-----: | :----: | ------------------------------------------------------------------------ |\n| group(col1, [...colN])                              | :heavy_check_mark: |         |        | adds cols to a GROUP BY clause                                           |\n| sum(col, [renamedAs])                               | :heavy_check_mark: |         |        | sums the given column and optionally names the resulting JSON property   |\n| count(col, [renamedAs])                             | :heavy_check_mark: |         |        | counts the given column and optionally names the resulting JSON property |\n| min(col, [renamedAs])                               | :heavy_check_mark: |         |        | sums the given column and optionally names the resulting JSON property   |\n| max(col, [renamedAs])                               | :heavy_check_mark: |         |        | sums the given column and optionally names the resulting JSON property   |\n| sum(col, [renamedAs])                               | :heavy_check_mark: |         |        | sums the given column and optionally names the resulting JSON property   |\n| countascol(col, value, [...valueN])                 | :heavy_check_mark: |         |        | Roughly translates to \"select sum(if(eq(col, value), 1, 0)) as value     |\n| distinct                                            | :heavy_check_mark: |         |        | filters out duplicate rows                                               |\n| distinct(column)                                    | :heavy_check_mark: |         |        | filters out duplicates based on the given column                         |\n| if(column OR expression, valwhentrue, valwhenfalse) | :heavy_check_mark: |         |        |\n\n\n\n* To Document\n    * function(sqlfunction, col, value, [...valueN]) - tries to apply the requested aggregate function\n    * rowcount\n\n\n### Nested Document Expansion\n\n| RQL Function                                                   |      Database      | Elastic | Dynamo | Description                                                                                                                                     |\n | -------------------------------------------------------------- | :----------------: | :-----: | :----: | ----------------------------------------------------------------------------------------------------------------------------------------------- |\n| expands=collection.property[...property][,table2.property2...] | :heavy_check_mark: |         |        | if \"property\" is a foreign key, referenced resource will be included as a nested document in the returned JSON instead of an HREF reference value |\n\n\n### Reserved Query String Parameters\n\n* **explain** - if you include an 'explain' param (any value other than 'explain=false' is exactly the same as not providing a value) the response will include additional debug information including the SQL run.  The response body will NOT be valid JSON.  For security reasons, Api.debug must be true or the request must be to \"localhost\" for this to work.\n* **expands** - A comma separated list of relationships that should be expanded into nested documents instead of referenced by URL in the response body.  For example, if a db 'Order' table has a foreign key to the 'Customer' table, you could query \"/orders?expands=customer\" or \"/customers?expands=orders\" to pre expand the relationship and avoid haveing to execute multiple requrests.\n* **includes** - A comma separted list of collection attributes (including dotted.codecPath.references for nested document attributes )that should be included in the response.  All attributes are included if this param is empty...unless they are excluded as below.\n* **excludes** - A comma separated list of collection attributes to exclude.\n\n\n### Restricted and Required Query Parameters\n\nIf a table has a column named \"userId\" or \"accountId\" these are special case known columns who's\nvalues may not be supplied by an api user request.  The value of these fields always comes from\nthe User object which is configured during authentication (see above).  This is true for\nRQL query params as well as for JSON properties.\n\n### Miscellaneous\n\n* as(col, renamed) - you can rename a property in the returned JSON using the 'as' operator.  Works just like the SQL as operator.\n\n| RQL Function     |      Database      | Elastic | Dynamo | Description                                                                            |\n | ---------------- | :----------------: | :-----: | :----: | -------------------------------------------------------------------------------------- |\n| as(col, renamed) | :heavy_check_mark: |         |        | change the name of the property in the return JSON, works just like SQL 'as' operator. |\n\n\n## Core Object Model Concepts\n\n### Apis\n\nAn Api exposes a set of Endpoints offering access to Collections backed by Db Tables.\n\n### Dbs, Tables, Columns and Indexes\n\nThe Db, Table, Column and Index objects are modeled off of RDBMS systems but are not specifically bound to RDBMS or SQL based systems.  For example, there are DynamoDb and ElasticsearchDB subclasses of Db.  As would be expected, Tables are composed of Columns and a Db can have one or more Tables. Tables are optimized, related to each other, and constrained through Indexes.\n\nImplementing support for a new back end datasource is simply a matter of implementing a new Db subclass.  The data source specific Db/Table/Column/Index implementation should abstract all back end details away so that datasource agnostic Actions can work transparently with Collections that may be backed by completely different back end types.\n\n### Collections, Entities, Attributes and Relationships\n\nCollections logically map to Db Tables.  An Resource logically represents a row in the Table.  An Attribute logically represents a Table Column.  Clients send GET/PUT/POST/DELETE requests to Collections to perform CRUD operations on the underlying Db Tables.  Collection and Attribute names can be mapped (or aliased) when the Table name or Column name would not work well in a URL or as a JSON property name.\n\nExample of aliased collection: ``api.collections.db_users.alias=profile`` Notice that the name of the database should be included with the name of the collection in order to set the alias property.\n\n\n### Endpoints and Actions\n\nAn Endpoint maps one or more HTTP methods and URL pattern to an ordered list of one or more Actions via [codecPath matching](#codecPath-matching).  Actions are where the work actually gets done.  If an API needs custom business logic that can not be achieved by configuring an existing Action, a custom Action subclass is the answer.\n\nAn Action can be set directly on an Endpoint to be 'private' to that endpoint, or an Action can be added directly to the Api and be eligible to be run across multiple Endpoints.  An example of an Action that would normaly be configured to run across multiple Endpoints would be something like the security AclAction or LogAction.\n\n\n\nExample [Handlers](https://rocketpartners.github.io/rckt_inversion/0.3.x/javadoc/io/rcktapp/api/Handler.html):\n* RestGetAction - Returns a Collection listing matching RQL criteria or can return a single requested Resource from a Db Table.\n* RestPostAction - Intelligently handles both PUT (update) and POST (insert) operations including complex nested documents\n* RestDeleteDelete - Deletes Collection Entities from the underlying Db Table.\n* AuthAction - Logs a user in and puts a User object in the Request\n* AclAction - Processes AclRules to secure your Endpoints\n* LogAction - Logs requests and the JSON payloads\n* S3UploadAction - Allows you to do a HTTP multi-part post upload to an S3 bucket\n* RateLimitAction - Protecs Endpoints from accidental or intentional DoS type traffic\n\n\n### AclAction and AclRules\n\nAclRules allow you to declare that a User must have specified roles and permissions to access resources at a given url codecPath and http method.\n\nGenerally AuthHandler and AclHandler will\nbe setup (in that order) to protect resources according to configed AclRules.  AclRules are matched to urls via [codecPath matching](#codecPath-matching) just like Endpoints and Actions.\n\n\n### Path Matching\n\nEndpoints, Actions and AclRules are selected when they can be matched to a request url codecPath.  Each one of these objects contains an \"includesPaths\" and \"excludesPaths\" configuration property that takes a comma separated list of paths definitions.  The wildcard character \"*\" can be used to match any arbitrary codecPath ending.  Regular expressions can be used to match specific codecPath requirements.  If a codecPath is both included and excluded, the exclusion will \"win\" and the codecPath will not be considered a match.\nLeading and trailing '/' characters are not considered when codecPath matching.\n\nRegular expression matches are modeled off of [angular-ui](https://github.com/angular-ui/ui-router/wiki/URL-Routing#url-parameters) regex codecPath matching.  A regex-based match component follows the pattern \"{optionalParamName:regex}\".  If you surround any codecPath part with [] it makes that part and all subsequent\ncodecPath parts optional.  Regular expression matches can not match across a '/'.\n\nHere are some examples:\n\n* endpoint1.includesPaths=dir1/dir2/*\n* endpoint1.excludePaths=dir1/dir2/hidden/*\n* endpoint2.includePath=something/{collection:[a-z]}/*\n\nOne easy way to restrict the underlying tables exposed by an Api is to use regex codecPath matching on your Endopoint.\n\n* endpoint3.includesPaths={collection:customers|books|albums}/[{resource:[0-9]}]/{relationship:[a-z]}\n\nIf codecPath params are given names {like_this:regex} then the codecPath value will be added as a name/value pair to the Request params overriding any matching key that may\nhave been supplied by the query string.\n\nThe names \"component\", \"resource\", and \"relationship\" are special.  If you configure a codecPath match to them, Inversion will use those values when configuring the Request object.\nIf you don't supply them the parser will assume the pattern .../[endpoint.codecPath]/[collection]/[resource]/[relationship].\n\n\n## Security Model\n\n### Account Roles\n\nAn Api is associated with a single Account\n\nUsers have a system level Role relationship with one or more Accounts.\n\nRoles are not designed to have a functional relationship to an Api being served.  Generally, Roles should not\nbe used by Api designers to provide application level entitlements.  Roles are designed to provide Inversion system level entitlements.\n\n\nRoles:\n* Owner - The person who can delete a Inversion account.\n* Administrator - Someone who can configure an account including changing security and managing Users.\n  (Owner and Administrator are not designed to be functionally useful for Api clients.  However, there is nothing stopping you from\n  requiring Owner or Administrator Roles to access various Api Endpoints, see below)\n* Member - Generally, someone who will be calling the Api.  An admin user of an end application may only have the\n  Inversion Member role.\n* Guest - Represents an unauthenticated caller.\n\nRoles are hierarchical by privilege.  Ex. having the Owner role gives you Administrator, Member and Guest authority.\n\n### Api Permissions\n\nFor each Api, a User can be assigned Api-defined named Permissions.  Permissions are simple string tokens.  They do\nnot confer hierarchical privilege like roles.  An Api designer might define a Permission string such as \"ADMINISTRATOR\"\nor \"SOMECOLLECTION_SOMEHTTPMETHOD\".  This is a total designers choice free for all.\n\nGroups of Users can be created at the Account level and each Group can be given Roles and Permissions\nthat the Users inherit.  In this way, you could create a functional \"Admin\" Group (different from Administrator Role)\nand give that Group all of the Permissions desired.  Users assigned to this \"Admin\" group would then inherit all those assigned Roles and Permissions.\n\n\n### Authentication\n\nAs far as the framework is concerned, authentication involves populating a User object with their entitled\nRoles and Permissions and placing the User object on the Request.  Session management, barer tokens, etc. are left up\nto the handler implementation.  Authentication does not consider Roles and Permissions: it only validates the username/password.\n\nAuthHandler is the default authentication provider.  It currently supports HTTP basic auth along with username/password\nquery string params and session tokens.  Additionally, if an application chooses to provide a JWT signed with a User's\nsecretKey, the roles and permissions defined in the JWT will become the roles and permissions assigned to the\nUser for that request.  Any roles and permissions defined in the DB will not be used.\n\nIf you want to secure your Api, you have to configure an instance of the AuthHandler (or create your own custom authentication handler)\nand map it to the desired Endpoints through an Action.\n\nFailing Authentication should return a 401 Unauthorized HTTP response code. (There is a longstanding gripe with HTTP status\ncodes that 401 should be called something other than 'Unauthorized' because generally elsewhere in software development\nauthorization (see below) is the process of determining if a users can access requested resources, NOT validating a users credentials.)\n\n\n### Authorization\n\nAuthorization is managed by the AclHandler. If you want to use Role and Permission based authorization,\nyou must configure an instance of the AclHandler (or create your own implementation) and associate it to\nthe desired Endpoints through an Action.\n\nThe AclHandler matches configured AclRules objects against the Url codecPath and HTTP method of the request.\n\nAclHandler processes AclRules in sorted order and the first rule to allow access \"wins\".  If no rule allows access,\nthen a 403 Forbidden HTTP status code is returned.\n\n\n\n### Multi-Tenant APIs\n\nApis can be flagged as 'multiTenant'.  If so, a tenantCode must immediately follow the apiCode in the URL.\n\nEx: ```http://localhost/accountCode/apiCode/tenantCode/[endpoint.codecPath]/collectionKey/[resourceKey]```\n\nIf the AuthAction is being used, it will enforce that the Url tenantCode matches the logged in users tenantCode (if there is a logged in user).\n\nIMPORTANT: this Url match restriction alone will not prevent access to cross tenant data. To fully\nrestrict and require the tenantId and tenantCode query string parameters and JSON body properties\nwith the following configuration:\n\n```properties\n\ntenantAcl.class=io.rcktapp.api.AclRule\ntenantAcl.info=true\ntenantAcl.includePaths=*\n#tenantAcl.excludePaths=\ntenantAcl.restricts=*.tenantId,*.tenantCode\ntenantAcl.requires=*.tenantId\ntenantAcl.methods=GET,PUT,POST,DELETE\n\n```\n\nIncluding this configuration will ensure that tenantId is always considered in queries (if it exists on the target Table) and can not be supplied by the caller, it will be pulled from the logged in user.\n\n\n### Row Level Security\n\nThe simplest way to restrict a user's interaction with a row is to provide a \"userId\" column on the\ntable in question.  Then use an AclRule to \"require/restrict\" userId.  This way a user can only\nread and write the rows they 'own'.  You could utilize a different combination of AclRules to achieve\npermission based read and owner based write/delete.\n\n```properties\n\nuserAcl.class=io.rcktapp.api.AclRule\nuserAcl.info=true\nuserAcl.includePaths=*\n#userAcl.excludePaths=\nuserAcl.restricts=*.userId\nuserAcl.requires=*.userId\nuserAcl.methods=GET,PUT,POST,DELETE\n\n```\n\nIf you need to implement a row level security feature that can not be mapped to a userId column,\nyou can configure JOIN filters that will limit a users access to the desired rows.\n\nTODO: add more specific doco here.\n\n\n## Elasticsearch Specifics\nCurrently, the following functions are available for use:\n\n* `source=value1,value2,valueN` - MUST be a separate parameter. Limits returned data to only these property values.\n* auto-suggest paths should be in the following format: `.../elastic/indexType/suggest?suggestField=value\u0026type=prefix`\n\u003e a `type` auto-suggest parameter can be set to define the type of search.  By default, auto-suggest will do a 'prefix' search.  If no results are found, auto-suggest will then try a wildcard search and return those results.  If you want to limit auto-suggest to one type of search, set `type=prefix` or `type=wildcard`.  While both types are fast, prefix searches are the fastest (~2ms vs ~20ms)\n* nested searching is allowed simply by specifying the name of the nested field, such as: `player.location.city` would retrieve the nested `location.city` value from a player.\n\nThe index/type will be automatically generated so that only one value needs to be sent.  \n`/elastic/location/location?and(and(eq(locationCode,270*),eq(city,Chandler)),and(eq(address1,*McQueen*)))`\n\nThe index/type used above `.../elastic/location/location?and(...` is the same as `/elastic/location?and(...`.\nIn the second example, it is assumed that the index/type `location` are the same value.\n\nElastic pagination: paging beyond the 10,000th item is significantly slower than paging within the range of 0-10,000. Once the 10k index has been reached, elastic must manually cycle through the data in order to obtain the desired page.  10,000 is the default max_result value within ElasticSearch.\n\n\n#### Elasticsearch RQL Examples\nRetrieve all location data for the locations in the city of Chandler\n`http://localhost:8080/apiCode/elastic/location?eq(city,Chandler)`\n\nRetrieve only the address properties for the locations in the city of Chandler\n`http://localhost:8080/apiCode/elastic/location?eq(city,Chandler)?source=address1,address2,address3`\n\nRetrieve the locations in the city of Chandler AND have a locationCode of 270*** **AND** have an address including *McQueen*\n`http://localhost:8080/apiCode/elastic/location?and(and(eq(locationCode,270*),eq(city,Chandler)),and(eq(address1,*McQueen*)))`\n\nRetrieve all locations with players.registerNum \u003e 5\n`http://localhost:8080/apiCode/elastic/location?gt(players.registerNum,5)`\n\n\nRetrieve the locations with an address1 that includes 'VALLEY' AND PHOENIX locations that have deleted players\n`http://localhost:8080/apiCode/elastic/location?and(and(eq(players.deleted,true),eq(city,PHOENIX)),and(eq(address1,*VALLEY*)))`\n\nRetrieve auto-suggested cities that start with 'chan'\n`http://localhost:8080/apiCode/elastic/location/suggest?suggestCity=chan`\n\nRetrieve locations with an empty state value\n`http://localhost:8080/apiCode/elastic/location?emp(state)`\n\nExample Config\n```\nelasticdb.class=io.rcktapp.api.handler.elastic.ElasticDb\nelasticdb.url=https://yourElasticSearchDB.amazonaws.com\n\nelasticH.class=io.rcktapp.api.handler.elastic.ElasticDbRestHandler\nelasticEp.class=io.rcktapp.api.Endpoint\nelasticEp.codecPath=desiredPath\nelasticEp.methods=GET\nelasticEp.handler=elasticH\n```\n\n[See io.rcktapp.rql.RqlToElasticSearchTest for several examples of RQL to Elastic queries](https://github.com/RocketPartners/rckt_inversion/blob/wb/readme_updates/src/test/java/io/rcktapp/rql/RqlToElasticSearchTest.java)\n\n\n## DynamoDB Specifics\n\nConfiguration is done on the Endpoint's config property.\n\n* tableMap\n    * Maps a collection name to a dynamo table\n    * FORMAT: collection name | dynamodb name  (comma separated)\n    * EXAMPLE: promo|promo-dev\n* conditionalWriteConf\n    * Allows a conditional write expression to be configured for a dynamo table\n    * FORMAT: collection name | withConditionExpression | payload fields  (comma separated)\n    * EXAMPLE: promo|attribute_not_exists(primarykey) OR enddate \u003c= :enddate|enddate\n* blueprintRow\n    * Config which row should be used for building the collection typeMap (otherwise first row of scan will be used) - *(Note: you will probably not need to use this unless new columns are introduced to a table in the future.)*\n    * FORMAT: collection name | primaryKey | sortKey (optional)\n    * EXAMPLE: loyalty-punchcard | 111 | abc\n* appendTenantIdToPk\n    * Enables appending the tenant id to the primary key.\n    * *(Note: must be multi-tenant api also for this to have any effect)*\n    * FORMAT: collection name (comma separated)\n    * EXAMPLE: promo,loyalty-punchcard\n    * On POST, this will append the tenant id to the primary key, for example, if the pk is a mobile number and was sent up as 4045551212 and the tenantId was 1, this record will be stored with a primary key of 1::4045551212.  On GET and DELETE, this will automatically append the tenantId prior to looking up the record and will strip it out of the results. So, this is completely invisible to the api user, they will only ever see the mobilenumber as 4045551212.  If you login to the AWS console and view the table, there you will see the actually mobilenumber as 1::4045551212.\n* sorting queries\n    * Scans cannot be sorted, only queries can.\n    * When sorting a query, the field that is sorted MUST match the sort key of the query index\n\nExample Config\n```\ndynamoH.class=io.rcktapp.api.service.ext.DynamoDbHandler\n\ndynamoEp.class=io.rcktapp.api.Endpoint\ndynamoEp.includePaths=dynamo*\ndynamoEp.methods=GET,POST,DELETE\ndynamoEp.handler=dynamoH\ndynamoEp.config=tableMap=promo|promo-dev,loyalty-punchcard|loyalty-punchcard-dev\u0026conditionalWriteConf=promo|attribute_not_exists(primarykey) OR enddate \u003c= :enddate|enddate\u0026appendTenantIdToPk=loyalty-punchcard\n```\n\n\n## Developer Notes\n\n### Javadocs\n\nFor all Handler configuration options or to understand how to use Inversion as a framework for a custom application\ncheck out the Javadocs.\n\n* 0.3.x - https://rocketpartners.github.io/rckt_inversion/0.3.x/javadoc/\n\n\n### Logging\n\n* Inversion uses logback, but it is not configured out of the box - the service implementing Inversion will be responsible for providing their own logback.xml config file!\n```\ndependencies {\n    ...\n    compile 'net.rakugakibox.spring.boot:logback-access-spring-boot-starter:2.6.0'\n}\n```\n\n\n### Gradle, Maven, etc.\n\n#### Publishing Releases\n1. Checkout the master branch\n1. Update the version property in gradle.properties\n1. Commit your changes\n1. Tag the repo:\n* git tag -a 1.4 -m \"version 1.4\"\n* git push --tags\n1. Merge your code into the current release branch to have Travis CI publish the built assets to Bintray\n\n\n#### Maven Repo for Releases\n\n```gradle\nrepositories {\n\tmaven { url 'https://dl.bintray.com/inversion/repo' }\n}\n\ndependencies {\n    compile \"io.inversion:inversion-spring-boot:${inversionVersion}\"\n}\n```\n\n\n#### Maven Repo for SNAPSHOTS\n\nIf you want to extend Inversion as part of a custom application, you can use jitpack to pull your preferred branch directly from GitHub into your project.\n\n```gradle\nrepositories { \n   maven { url 'https://jitpack.io' }\n}\n\nconfigurations.all {\n    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'\n}\n\ndependencies {\n    compile 'com.github.inversion-api.inversion-engine:inversion-spring-boot:master-SNAPSHOT'\n} \n```   \n\n\n## REST API Design Resources\n\n### HTTP Status Codes\n* http://msdn.microsoft.com/en-us/library/azure/dd179357.aspx\n* http://www.restapitutorial.com/httpstatuscodes.html\n* http://www.restapitutorial.com/lessons/httpmethods.html\n* https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections\n\n\n### Standards-ish\n\n* http://stackoverflow.com/questions/12806386/standard-json-api-response-format\n* https://jsonapi.org/\n* https://labs.omniti.com/labs/jsend\n* http://stateless.co/hal_specification.html\n* http://www.odata.org/\n* https://en.wikipedia.org/wiki/HATEOAS\n* https://github.com/swagger-api/swagger-core/wiki\n* http://json-schema.org/\n* https://google.github.io/styleguide/jsoncstyleguide.xml\n* https://developers.facebook.com/docs/graph-api/\n\n\n### Web Query Languages\n\n* http://dundalek.com/rql/\n* https://doc.apsstandard.org/2.1/spec/rql/\n* https://www.sitepen.com/blog/2010/11/02/resource-query-language-a-query-language-for-the-web-nosql/\n* http://msdn.microsoft.com/en-us/library/gg309461.aspx - odata endpoint filters\n* http://tools.ietf.org/html/draft-nottingham-atompub-fiql-00\n* http://stackoverflow.com/questions/16371610/rest-rql-java-implementation\n* https://graphql.org/\n\n\n### Best Practices and Design Resources\n\n* https://stormpath.com/blog/linking-and-resource-expansion-rest-api-tips/\n* http://docs.stormpath.com/rest/product-guide/#link-expansion\n* http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api\n* http://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/\n* http://samplacette.com/five-json-rest-api-link-formats-compared/\n* https://www.mnot.net/blog/2011/11/25/linking_in_json\n* https://blog.safaribooksonline.com/2013/05/23/instrumenting-apis-with-links-in-rest/\n* http://stackoverflow.com/questions/297005/what-is-the-gold-standard-for-website-apis-twitter-flickr-facebook-etc\n* https://www.moesif.com/blog/technical/api-design/REST-API-Design-Filtering-Sorting-and-Pagination/#\n* https://github.com/NationalBankBelgium/REST-API-Design-Guide/wiki\n* https://cloud.google.com/bigquery/docs/reference/legacy-sql\n* https://github.com/Azure/azure-rest-api-specs\n\n### REST APIs in the Wild\n\n* Stripe - https://stripe.com/docs/api\n* Wordpress - https://developer.wordpress.org/rest-api/\n* Google - https://developers.google.com/\n* Facebook - https://developers.facebook.com/docs\n* LinkedIn - https://developer.linkedin.com/docs\n* Amazon S3 - https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html\n* Twitter - https://developer.twitter.com/\n* Spotify - https://developer.spotify.com/documentation/web-api/\n\n  \n\n             \n             \n             \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finversion-api%2Finversion-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finversion-api%2Finversion-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finversion-api%2Finversion-engine/lists"}