├── spec ├── integration.spec.js ├── support │ ├── jasmine.json │ ├── reporter.js │ ├── helper.js │ ├── jasmine.js │ └── server.js ├── mocks │ └── sqs.js └── SQSEventEmitterMQ.spec.js ├── index.js ├── .nycrc ├── .releaserc ├── template.hbs ├── footer.hbs ├── header.hbs └── commit.hbs ├── NOTICE ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── ---2-feature-request.md │ └── ---1-report-an-issue.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── release-automated.yml │ └── ci.yml ├── .gitignore ├── eslint.config.mjs ├── package.json ├── lib └── SQSEventEmitterMQ.js ├── CHANGELOG.md ├── release.config.js ├── README.md └── LICENSE /spec/integration.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const SQSEventEmitterMQ = require('./lib/SQSEventEmitterMQ'); 2 | 3 | module.exports = SQSEventEmitterMQ; 4 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": [ 3 | "lcov", 4 | "text-summary" 5 | ], 6 | "exclude": [ 7 | "**/spec/**" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.releaserc/template.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 | {{#each commitGroups}} 4 | 5 | {{#if title}} 6 | ### {{title}} 7 | 8 | {{/if}} 9 | {{#each commits}} 10 | {{> commit root=@root}} 11 | {{/each}} 12 | {{/each}} 13 | 14 | {{> footer}} 15 | -------------------------------------------------------------------------------- /.releaserc/footer.hbs: -------------------------------------------------------------------------------- 1 | {{#if noteGroups}} 2 | {{#each noteGroups}} 3 | 4 | ### {{title}} 5 | 6 | {{#each notes}} 7 | * {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{text}} ([{{commit.shortHash}}]({{commit.shortHash}})) 8 | {{/each}} 9 | {{/each}} 10 | 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "support/helper.js", 8 | "support/jasmine.js", 9 | "support/reporter.js" 10 | ], 11 | "stopSpecOnExpectationFailure": false, 12 | "random": true 13 | } 14 | -------------------------------------------------------------------------------- /spec/support/reporter.js: -------------------------------------------------------------------------------- 1 | const { SpecReporter } = require('jasmine-spec-reporter'); 2 | 3 | jasmine.getEnv().clearReporters(); 4 | jasmine.getEnv().addReporter( 5 | new SpecReporter({ 6 | spec: { 7 | displayPending: true, 8 | displayDuration: true, 9 | displayStacktrace: 'pretty', 10 | }, 11 | }) 12 | ); 13 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Parse Server SQS Message Queue Adapter 2 | 3 | Copyright 2016-present Parse Platform 4 | 5 | This product includes software developed at Parse Platform. 6 | www.parseplatform.org 7 | 8 | --- 9 | 10 | As of April 5, 2017, Parse, LLC has transferred this code to the Parse Platform organization, and will no longer be contributing to or distributing this code. 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🙋🏽‍♀️ Getting help with code 4 | url: https://stackoverflow.com/questions/tagged/parse-platform 5 | about: Get help with code-level questions on Stack Overflow. 6 | - name: 🙋 Getting general help 7 | url: https://community.parseplatform.org 8 | about: Get help with other questions on our Community Forum. 9 | -------------------------------------------------------------------------------- /spec/support/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { startServer, stopServer, reconfigureServer } = require('./server'); 3 | 4 | jasmine.DEFAULT_TIMEOUT_INTERVAL = process.env.TESTING_TIMEOUT || '360000'; 5 | 6 | beforeAll(async () => { 7 | await startServer(); 8 | }); 9 | 10 | afterAll(async () => { 11 | await stopServer(); 12 | }); 13 | 14 | beforeEach(async () => { 15 | await reconfigureServer(); 16 | }); 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot dependency updates 2 | # Docs: https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "npm" 7 | # Location of package-lock.json 8 | directory: "/" 9 | # Check daily for updates 10 | schedule: 11 | interval: "daily" 12 | commit-message: 13 | # Set commit message prefix 14 | prefix: "refactor" 15 | -------------------------------------------------------------------------------- /.releaserc/header.hbs: -------------------------------------------------------------------------------- 1 | {{#if isPatch~}} 2 | ## 3 | {{~else~}} 4 | # 5 | {{~/if}} {{#if @root.linkCompare~}} 6 | [{{version}}]( 7 | {{~#if @root.repository~}} 8 | {{~#if @root.host}} 9 | {{~@root.host}}/ 10 | {{~/if}} 11 | {{~#if @root.owner}} 12 | {{~@root.owner}}/ 13 | {{~/if}} 14 | {{~@root.repository}} 15 | {{~else}} 16 | {{~@root.repoUrl}} 17 | {{~/if~}} 18 | /compare/{{previousTag}}...{{currentTag}}) 19 | {{~else}} 20 | {{~version}} 21 | {{~/if}} 22 | {{~#if title}} "{{title}}" 23 | {{~/if}} 24 | {{~#if date}} ({{date}}) 25 | {{/if}} 26 | -------------------------------------------------------------------------------- /spec/support/jasmine.js: -------------------------------------------------------------------------------- 1 | const semver = require('semver'); 2 | 3 | const satisfiesParseServerVersion = version => { 4 | const envVersion = process.env.PARSE_SERVER_VERSION; 5 | const semverVersion = semver.coerce(envVersion); 6 | return !envVersion || !semverVersion || semver.satisfies(semverVersion, version); 7 | }; 8 | 9 | global.it_only_parse_server_version = version => satisfiesParseServerVersion(version) ? it : xit; 10 | global.fit_only_parse_server_version = version => satisfiesParseServerVersion(version) ? fit : xit; 11 | global.describe_only_parse_server_version = version => satisfiesParseServerVersion(version) ? describe : xdescribe; 12 | global.fdescribe_only_parse_server_version = version => satisfiesParseServerVersion(version) ? fdescribe : xdescribe; 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | ## Pull Request 3 | 4 | - Report security issues [confidentially](https://github.com/parse-community/parse-server-sqs-mq-adapter/security/policy). 5 | - Any contribution is under this [license](https://github.com/parse-community/parse-server-sqs-mq-adapter/blob/main/LICENSE). 6 | - Link this pull request to an [issue](https://github.com/parse-community/parse-server-sqs-mq-adapter/issues?q=is%3Aissue). 7 | 8 | ## Issue 9 | 10 | 11 | Closes: FILL_THIS_OUT 12 | 13 | ## Approach 14 | 15 | 16 | ## Tasks 17 | 18 | 19 | - [ ] Add tests 20 | - [ ] Add changes to documentation (guides, repository pages, code comments) 21 | -------------------------------------------------------------------------------- /spec/mocks/sqs.js: -------------------------------------------------------------------------------- 1 | const { SQSEventEmitterMQ } = require('../../'); 2 | 3 | function getMockSqsOptions() { 4 | const response = { 5 | Messages: [ 6 | { 7 | ReceiptHandle: 'receipt-handle', 8 | MessageId: '123', 9 | Body: 'hi', 10 | }, 11 | ], 12 | }; 13 | 14 | let call = 0; 15 | const sqs = { 16 | sendMessageBatch: () => Promise.resolve({}), 17 | send: () => { 18 | call += 1; 19 | if (call === 1) { 20 | return Promise.resolve(response); 21 | } 22 | return Promise.resolve({}); 23 | }, 24 | }; 25 | 26 | return { 27 | messageQueueAdapter: SQSEventEmitterMQ, 28 | queueUrl: 'test-queue', 29 | region: 'mock', 30 | sqs, 31 | }; 32 | } 33 | 34 | module.exports = { getMockSqsOptions }; 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | test_logs/* 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | test_logs/* 41 | .eslintcache 42 | -------------------------------------------------------------------------------- /.github/workflows/release-automated.yml: -------------------------------------------------------------------------------- 1 | name: release-automated 2 | on: 3 | push: 4 | branches: [ main, master, release, alpha, beta, next-major ] 5 | jobs: 6 | release: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v4 11 | with: 12 | persist-credentials: false 13 | - name: Setup Node 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 22 17 | cache: 'npm' 18 | - name: Install dependencies 19 | run: npm ci 20 | - name: Run semantic-release 21 | run: npx semantic-release 22 | env: 23 | GH_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 24 | GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 25 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 27 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import globals from 'globals'; 3 | 4 | export default [ 5 | js.configs.recommended, 6 | { 7 | files: ['**/*.js'], 8 | ignores: ['node_modules/**'], 9 | languageOptions: { 10 | ecmaVersion: 2022, 11 | sourceType: 'module', 12 | globals: { 13 | ...globals.node, 14 | Parse: 'readonly' 15 | } 16 | }, 17 | rules: { 18 | indent: ["error", 2, { SwitchCase: 1 }], 19 | "linebreak-style": ["error", "unix"], 20 | "no-trailing-spaces": "error", 21 | "eol-last": "error", 22 | "space-in-parens": ["error", "never"], 23 | "no-multiple-empty-lines": "warn", 24 | "prefer-const": "error", 25 | "space-infix-ops": "error", 26 | "no-useless-escape": "off", 27 | "require-atomic-updates": "off", 28 | "object-curly-spacing": ["error", "always"], 29 | curly: ["error", "all"], 30 | "block-spacing": ["error", "always"], 31 | "no-unused-vars": "off", 32 | "no-console": "warn" 33 | }, 34 | }, 35 | { 36 | files: ['spec/**/*.js'], 37 | languageOptions: { 38 | globals: { 39 | ...globals.node, 40 | ...globals.jasmine, 41 | Parse: 'readonly' 42 | } 43 | } 44 | } 45 | ]; 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---2-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4A1 Request a feature" 3 | about: Suggest new functionality or an enhancement of existing functionality. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### New Feature / Enhancement Checklist 11 | 16 | 17 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-server-sqs-mq-adapter/security/policy). 18 | - [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/main/SUPPORT.md). 19 | - [ ] I have searched through [existing issues](https://github.com/parse-community/parse-server-sqs-mq-adapter/issues?q=is%3Aissue). 20 | 21 | ### Current Limitation 22 | 23 | 24 | ### Feature / Enhancement Description 25 | 26 | 27 | ### Example Use Case 28 | 29 | 30 | ### Alternatives / Workarounds 31 | 32 | 33 | ### 3rd Party References 34 | 35 | -------------------------------------------------------------------------------- /.releaserc/commit.hbs: -------------------------------------------------------------------------------- 1 | *{{#if scope}} **{{scope}}:** 2 | {{~/if}} {{#if subject}} 3 | {{~subject}} 4 | {{~else}} 5 | {{~header}} 6 | {{~/if}} 7 | 8 | {{~!-- commit link --}} {{#if @root.linkReferences~}} 9 | ([{{shortHash}}]( 10 | {{~#if @root.repository}} 11 | {{~#if @root.host}} 12 | {{~@root.host}}/ 13 | {{~/if}} 14 | {{~#if @root.owner}} 15 | {{~@root.owner}}/ 16 | {{~/if}} 17 | {{~@root.repository}} 18 | {{~else}} 19 | {{~@root.repoUrl}} 20 | {{~/if}}/ 21 | {{~@root.commit}}/{{hash}})) 22 | {{~else}} 23 | {{~shortHash}} 24 | {{~/if}} 25 | 26 | {{~!-- commit references --}} 27 | {{~#if references~}} 28 | , closes 29 | {{~#each references}} {{#if @root.linkReferences~}} 30 | [ 31 | {{~#if this.owner}} 32 | {{~this.owner}}/ 33 | {{~/if}} 34 | {{~this.repository}}#{{this.issue}}]( 35 | {{~#if @root.repository}} 36 | {{~#if @root.host}} 37 | {{~@root.host}}/ 38 | {{~/if}} 39 | {{~#if this.repository}} 40 | {{~#if this.owner}} 41 | {{~this.owner}}/ 42 | {{~/if}} 43 | {{~this.repository}} 44 | {{~else}} 45 | {{~#if @root.owner}} 46 | {{~@root.owner}}/ 47 | {{~/if}} 48 | {{~@root.repository}} 49 | {{~/if}} 50 | {{~else}} 51 | {{~@root.repoUrl}} 52 | {{~/if}}/ 53 | {{~@root.issue}}/{{this.issue}}) 54 | {{~else}} 55 | {{~#if this.owner}} 56 | {{~this.owner}}/ 57 | {{~/if}} 58 | {{~this.repository}}#{{this.issue}} 59 | {{~/if}}{{/each}} 60 | {{~/if}} 61 | 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@parse/sqs-mq-adapter", 3 | "version": "2.1.0", 4 | "description": "Spread work queue across cluster of parse servers using SQS", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/parse-community/parse-server-sqs-mq-adapter.git" 9 | }, 10 | "author": "Parse Platform", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "@aws-sdk/client-sqs": "3.948.0", 14 | "sqs-consumer": "12.0.0", 15 | "sqs-producer": "7.0.0" 16 | }, 17 | "peerDependencies": { 18 | "parse-server": "*" 19 | }, 20 | "devDependencies": { 21 | "@eslint/js": "9.32.0", 22 | "@semantic-release/changelog": "6.0.3", 23 | "@semantic-release/commit-analyzer": "13.0.1", 24 | "@semantic-release/git": "10.0.1", 25 | "@semantic-release/github": "12.0.2", 26 | "@semantic-release/npm": "13.1.2", 27 | "@semantic-release/release-notes-generator": "14.1.0", 28 | "eslint": "9.39.1", 29 | "globals": "16.5.0", 30 | "jasmine": "5.13.0", 31 | "jasmine-spec-reporter": "7.0.0", 32 | "mongodb-runner": "6.4.0", 33 | "nyc": "17.1.0", 34 | "parse-server": "8.6.0", 35 | "semantic-release": "25.0.2" 36 | }, 37 | "scripts": { 38 | "lint": "eslint --cache ./index.js ./lib/** ./spec/**/*.js", 39 | "lint-fix": "eslint --cache --fix ./index.js ./lib/** ./spec/**/*.js", 40 | "pretest": "npm run test:mongodb:runnerstart", 41 | "posttest": "npm run test:mongodb:runnerstop", 42 | "test": "npm run test:only", 43 | "test:only": "TESTING=1 nyc jasmine", 44 | "test:mongodb:runnerstart": "mongodb-runner start -t standalone -- --port 27017", 45 | "test:mongodb:runnerstop": "mongodb-runner stop --all", 46 | "coverage": "nyc jasmine" 47 | }, 48 | "engines": { 49 | "node": ">=18.20.4 <19.0.0 || >=20.18.0 <21.0.0 || >=22.12.0 <23.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---1-report-an-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Report an issue" 3 | about: A feature is not working as expected. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### New Issue Checklist 11 | 16 | 17 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-server-sqs-mq-adapter/security/policy). 18 | - [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/main/SUPPORT.md). 19 | - [ ] I have searched through [existing issues](https://github.com/parse-community/parse-server-push-adapter/issues?q=is%3Aissue). 20 | - [ ] I can reproduce the issue with the latest versions of [Parse Server](https://github.com/parse-community/parse-server/releases) and the [Parse Server SQS MQ Adapter](https://github.com/parse-community/parse-server-sqs-mq-adapter/releases). 21 | 22 | ### Issue Description 23 | 24 | 25 | ### Steps to reproduce 26 | 27 | 28 | ### Actual Outcome 29 | 30 | 31 | ### Expected Outcome 32 | 33 | 34 | ### Environment 35 | 36 | 37 | Client 38 | - Parse Server SQS MQ Adapter version: `FILL_THIS_OUT` 39 | 40 | Server 41 | - Parse Server version: `FILL_THIS_OUT` 42 | - Operating system: `FILL_THIS_OUT` 43 | - Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): `FILL_THIS_OUT` 44 | 45 | Database 46 | - System (MongoDB or Postgres): `FILL_THIS_OUT` 47 | - Database version: `FILL_THIS_OUT` 48 | - Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): `FILL_THIS_OUT` 49 | 50 | ### Logs 51 | 52 | -------------------------------------------------------------------------------- /spec/support/server.js: -------------------------------------------------------------------------------- 1 | const { ParseServer } = require('parse-server'); 2 | const express = require('express'); 3 | const http = require('http'); 4 | const { getMockSqsOptions } = require('../mocks/sqs'); 5 | const Config = require('../../node_modules/parse-server/lib/Config.js'); 6 | 7 | const expressApp = express(); 8 | const queueOptions = getMockSqsOptions(); 9 | 10 | let serverState = {}; 11 | 12 | const defaultConfig = { 13 | databaseURI: 'mongodb://127.0.0.1:27017/sqs-mq-adapter', 14 | appId: 'test', 15 | masterKey: 'test', 16 | maintenanceKey: 'test-maintenance-key', 17 | serverURL: 'http://127.0.0.1:1327/api/parse', 18 | port: 1327, 19 | mountPath: '/api/parse', 20 | verbose: false, 21 | silent: true, 22 | queueOptions, 23 | verifyUserEmails: false, 24 | }; 25 | 26 | async function startServer(config = {}) { 27 | if (!process.env.TESTING) { 28 | throw 'requires test environment to run'; 29 | } 30 | 31 | const serverConfig = Object.assign({}, config, defaultConfig); 32 | const parseServer = ParseServer(serverConfig); 33 | await parseServer.start(); 34 | expressApp.use(serverConfig.mountPath, parseServer.app); 35 | 36 | const httpServer = http.createServer(expressApp); 37 | await new Promise((resolve, reject) => { 38 | httpServer 39 | .listen(serverConfig.port) 40 | .once('listening', resolve) 41 | .once('error', (e) => reject(e)); 42 | }).catch((e) => { 43 | throw new Error(`parse-server failed to launch with error: ${e}`); 44 | }); 45 | 46 | Object.assign(serverState, { 47 | parseServer, 48 | httpServer, 49 | serverConfig, 50 | }); 51 | } 52 | 53 | async function stopServer() { 54 | if (!process.env.TESTING) { 55 | throw 'requires test environment to run'; 56 | } 57 | 58 | await Parse.User.logOut(); 59 | const app = Config.get(defaultConfig.appId); 60 | await app?.database.deleteEverything(true); 61 | 62 | const { httpServer } = serverState; 63 | await new Promise((resolve) => httpServer.close(resolve)); 64 | serverState = {}; 65 | } 66 | 67 | async function reconfigureServer(config = {}) { 68 | await stopServer(); 69 | return await startServer(config); 70 | } 71 | 72 | function getServerConfig() { 73 | return serverState.serverConfig || defaultConfig; 74 | } 75 | 76 | module.exports = { 77 | reconfigureServer, 78 | startServer, 79 | stopServer, 80 | getServerConfig, 81 | }; 82 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - '**' 9 | jobs: 10 | check-lint: 11 | name: Lint 12 | timeout-minutes: 5 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: 22 19 | cache: npm 20 | - run: npm ci 21 | - run: npm run lint 22 | test: 23 | strategy: 24 | matrix: 25 | include: 26 | - name: Parse Server 8, Node.js 18 27 | NODE_VERSION: 18.20.4 28 | PARSE_SERVER_VERSION: 8 29 | - name: Parse Server 8, Node.js 20 30 | NODE_VERSION: 20.15.1 31 | PARSE_SERVER_VERSION: 8 32 | - name: Parse Server 8, Node.js 22 33 | NODE_VERSION: 22.4.1 34 | PARSE_SERVER_VERSION: 8 35 | - name: Parse Server 7, Node.js 18 36 | NODE_VERSION: 18.20.4 37 | PARSE_SERVER_VERSION: 7 38 | - name: Parse Server 7, Node.js 20 39 | NODE_VERSION: 20.15.1 40 | PARSE_SERVER_VERSION: 7 41 | - name: Parse Server 7, Node.js 22 42 | NODE_VERSION: 22.4.1 43 | PARSE_SERVER_VERSION: 7 44 | fail-fast: false 45 | name: ${{ matrix.name }} 46 | timeout-minutes: 15 47 | runs-on: ubuntu-latest 48 | env: 49 | NODE_VERSION: ${{ matrix.NODE_VERSION }} 50 | PARSE_SERVER_VERSION: ${{ matrix.PARSE_SERVER_VERSION }} 51 | steps: 52 | - uses: actions/checkout@v4 53 | - name: Use Node.js ${{ matrix.NODE_VERSION }} 54 | uses: actions/setup-node@v4 55 | with: 56 | node-version: ${{ matrix.NODE_VERSION }} 57 | cache: npm 58 | - name: Install Parse Server ${{ matrix.PARSE_SERVER_VERSION }} 59 | run: npm i -DE parse-server@${{ matrix.PARSE_SERVER_VERSION }} 60 | - name: Install dependencies 61 | run: npm ci 62 | - name: Run tests 63 | run: npm run test 64 | - name: Upload code coverage 65 | uses: codecov/codecov-action@v4 66 | with: 67 | fail_ci_if_error: false 68 | token: ${{ secrets.CODECOV_TOKEN }} 69 | env: 70 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 71 | concurrency: 72 | group: ${{ github.workflow }}-${{ github.ref }} 73 | cancel-in-progress: true 74 | -------------------------------------------------------------------------------- /lib/SQSEventEmitterMQ.js: -------------------------------------------------------------------------------- 1 | const events = require('events'); 2 | const { logger } = require('parse-server'); 3 | const { Producer: SQSProducer } = require('sqs-producer'); 4 | const { Consumer: SQSConsumer } = require('sqs-consumer'); 5 | 6 | class Publisher { 7 | constructor(config) { 8 | const producer = SQSProducer.create(config); 9 | this.emitter = producer; 10 | } 11 | 12 | publish(channel, message) { 13 | let payload; 14 | if (Array.isArray(message)) { 15 | payload = message.map((body, index) => Object.assign({ id: index.toString(), body }, channel 16 | ? { groupId: channel } : {})); 17 | } else { 18 | payload = Object.assign({ id: '0', body: message }, channel ? { groupId: channel } : {}); 19 | } 20 | 21 | // basic validation to keep error logging synchronous for invalid payloads 22 | if (typeof payload === 'object' && !Array.isArray(payload) && payload.body === undefined) { 23 | logger.error(new Error("Object messages must have 'body' prop")); 24 | return; 25 | } 26 | try { 27 | const result = this.emitter.send(payload); 28 | if (result && typeof result.catch === 'function') { 29 | result.catch((err) => { 30 | logger.error(err); 31 | }); 32 | } 33 | } catch (err) { 34 | logger.error(err); 35 | } 36 | } 37 | } 38 | 39 | class Consumer extends events.EventEmitter { 40 | constructor(config) { 41 | super(); 42 | if (!config.queueUrl) { 43 | throw new Error('No queueUrl found in config'); 44 | } 45 | this.config = config; 46 | } 47 | 48 | subscribe(channel) { 49 | this.unsubscribe(channel); 50 | 51 | const handleMessage = async (message) => { 52 | this.emit('message', channel, message.Body); 53 | }; 54 | 55 | const createOptions = Object.assign(this.config, { handleMessage }); 56 | this.emitter = SQSConsumer.create(createOptions); 57 | 58 | this.subscriptions.set(channel, handleMessage); 59 | this.emitter.start(); 60 | } 61 | 62 | unsubscribe(channel) { 63 | if (this.emitter) { 64 | this.emitter.stop(); 65 | } 66 | 67 | if (!this.subscriptions.has(channel)) { 68 | logger.debug('No channel to unsub from'); 69 | return; 70 | } 71 | logger.debug('unsub ', channel); 72 | if (this.emitter) { 73 | this.emitter.removeListener(channel, this.subscriptions.get(channel)); 74 | } 75 | this.subscriptions.delete(channel); 76 | } 77 | } 78 | 79 | Consumer.prototype.subscriptions = new Map(); 80 | 81 | function createPublisher(config) { 82 | return new Publisher(config); 83 | } 84 | 85 | function createSubscriber(config) { 86 | return new Consumer(config); 87 | } 88 | 89 | const SQSEventEmitterMQ = { 90 | createPublisher, 91 | createSubscriber, 92 | }; 93 | 94 | module.exports = { 95 | SQSEventEmitterMQ, 96 | }; 97 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.1.0](https://github.com/parse-community/parse-server-sqs-mq-adapter/compare/2.0.0...2.1.0) (2025-07-06) 2 | 3 | 4 | ### Features 5 | 6 | * Add official support for Parse Server 7 and 8 ([#162](https://github.com/parse-community/parse-server-sqs-mq-adapter/issues/162)) ([e7e76b3](https://github.com/parse-community/parse-server-sqs-mq-adapter/commit/e7e76b3db52cc29964db1be33c08298fe166f9b6)) 7 | 8 | # [2.0.0](https://github.com/parse-community/parse-server-sqs-mq-adapter/compare/1.4.0...2.0.0) (2025-07-05) 9 | 10 | 11 | ### Features 12 | 13 | * Remove support for Node 12, 14, 15, 16, 17 ([#158](https://github.com/parse-community/parse-server-sqs-mq-adapter/issues/158)) ([26c114e](https://github.com/parse-community/parse-server-sqs-mq-adapter/commit/26c114e77e4d6e64085f513c54899e44fc5c3a61)) 14 | 15 | 16 | ### BREAKING CHANGES 17 | 18 | * Removes support for outdated Node versions 12, 14, 15, 16, 17. ([26c114e](26c114e)) 19 | 20 | # [1.4.0](https://github.com/parse-community/parse-server-sqs-mq-adapter/compare/1.3.8...1.4.0) (2024-09-26) 21 | 22 | 23 | ### Features 24 | 25 | * Add support for Node 20 and 22 ([#101](https://github.com/parse-community/parse-server-sqs-mq-adapter/issues/101)) ([d19b0c2](https://github.com/parse-community/parse-server-sqs-mq-adapter/commit/d19b0c2e12d0a5bff48524f8aee1c2d2a232baa9)) 26 | 27 | ## [1.3.8](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/compare/1.3.7...1.3.8) (2023-10-18) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * Security bump @babel/traverse from 7.18.2 to 7.23.2 ([#68](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/issues/68)) ([74de7d2](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/commit/74de7d2cd23c6bc4223a062e676e7c5b4b91c12e)) 33 | 34 | ## [1.3.7](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/compare/1.3.6...1.3.7) (2022-12-14) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * Security upgrade ajv and eslint ([#56](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/issues/56)) ([7c18531](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/commit/7c185313e51dd92ec8a558c2f900532d6351f199)) 40 | 41 | ## [1.3.6](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/compare/1.3.5...1.3.6) (2022-12-14) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * Security upgrade qs from 6.5.2 to 6.5.3 ([#55](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/issues/55)) ([5c43964](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/commit/5c43964c4b6008b1d6e6d2fa981783c164cb37a4)) 47 | 48 | ## [1.3.5](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/compare/1.3.4...1.3.5) (2022-06-17) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * security upgrade parse-server from 5.2.1 to 5.2.2 ([#41](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/issues/41)) ([6e29cf0](https://github.com/parse-server-modules/parse-server-sqs-mq-adapter/commit/6e29cf0edd369ed6ef15cebe54043c0142865c77)) 54 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Semantic Release Config 3 | */ 4 | 5 | // For CommonJS use: 6 | const { readFile } = require('fs').promises; 7 | const { resolve } = require('path'); 8 | 9 | // For ES6 modules use: 10 | // import { readFile } from 'fs/promises'; 11 | // import { resolve, dirname } from 'path'; 12 | // import { fileURLToPath } from 'url'; 13 | 14 | // Get env vars 15 | const ref = process.env.GITHUB_REF; 16 | const serverUrl = process.env.GITHUB_SERVER_URL; 17 | const repository = process.env.GITHUB_REPOSITORY; 18 | const repositoryUrl = serverUrl + '/' + repository; 19 | 20 | // Declare params 21 | const resourcePath = './.releaserc/'; 22 | const templates = { 23 | main: { file: 'template.hbs', text: undefined }, 24 | header: { file: 'header.hbs', text: undefined }, 25 | commit: { file: 'commit.hbs', text: undefined }, 26 | footer: { file: 'footer.hbs', text: undefined }, 27 | }; 28 | 29 | // Declare semantic config 30 | async function config() { 31 | 32 | // Get branch 33 | const branch = ref?.split('/')?.pop()?.split('-')[0] || '(current branch could not be determined)'; 34 | console.log(`Running on branch: ${branch}`); 35 | 36 | // Set changelog file 37 | //const changelogFile = `./changelogs/CHANGELOG_${branch}.md`; 38 | const changelogFile = `./CHANGELOG.md`; 39 | console.log(`Changelog file output to: ${changelogFile}`); 40 | 41 | // Load template file contents 42 | await loadTemplates(); 43 | 44 | const config = { 45 | branches: [ 46 | 'main', 47 | 'master', 48 | 'release', 49 | { name: 'alpha', prerelease: true }, 50 | { name: 'beta', prerelease: true }, 51 | 'next-major', 52 | // Long-Term-Support branches 53 | // { name: 'release-1', range: '1.x.x', channel: '1.x' }, 54 | // { name: 'release-2', range: '2.x.x', channel: '2.x' }, 55 | // { name: 'release-3', range: '3.x.x', channel: '3.x' }, 56 | // { name: 'release-4', range: '4.x.x', channel: '4.x' }, 57 | ], 58 | dryRun: false, 59 | debug: true, 60 | ci: true, 61 | tagFormat: '${version}', 62 | plugins: [ 63 | ['@semantic-release/commit-analyzer', { 64 | preset: 'angular', 65 | releaseRules: [ 66 | { type: 'docs', scope: 'README', release: 'patch' }, 67 | { scope: 'no-release', release: false }, 68 | ], 69 | parserOpts: { 70 | noteKeywords: [ 'BREAKING CHANGE' ], 71 | }, 72 | }], 73 | ['@semantic-release/release-notes-generator', { 74 | preset: 'angular', 75 | parserOpts: { 76 | noteKeywords: [ 'BREAKING CHANGE' ] 77 | }, 78 | writerOpts: { 79 | commitsSort: ['subject', 'scope'], 80 | mainTemplate: templates.main.text, 81 | headerPartial: templates.header.text, 82 | commitPartial: templates.commit.text, 83 | footerPartial: templates.footer.text, 84 | }, 85 | }], 86 | ['@semantic-release/changelog', { 87 | 'changelogFile': changelogFile, 88 | }], 89 | ['@semantic-release/npm', { 90 | 'npmPublish': true, 91 | }], 92 | ['@semantic-release/git', { 93 | assets: [changelogFile, 'package.json', 'package-lock.json'], 94 | }], 95 | ['@semantic-release/github', { 96 | successComment: getReleaseComment(), 97 | labels: ['type:ci'], 98 | releasedLabels: ['state:released<%= nextRelease.channel ? `-\${nextRelease.channel}` : "" %>'] 99 | }], 100 | ], 101 | }; 102 | 103 | return config; 104 | } 105 | 106 | async function loadTemplates() { 107 | for (const template of Object.keys(templates)) { 108 | // For ES6 modules use: 109 | // const fileUrl = import.meta.url; 110 | // const __dirname = dirname(fileURLToPath(fileUrl)); 111 | 112 | const filePath = resolve(__dirname, resourcePath, templates[template].file); 113 | const text = await readFile(filePath, 'utf-8'); 114 | templates[template].text = text; 115 | } 116 | } 117 | 118 | function getReleaseComment() { 119 | const url = repositoryUrl + '/releases/tag/${nextRelease.gitTag}'; 120 | let comment = '🎉 This change has been released in version [${nextRelease.version}](' + url + ')'; 121 | return comment; 122 | } 123 | 124 | // For CommonJS use: 125 | module.exports = config(); 126 | 127 | // For ES6 modules use: 128 | // export default config(); 129 | -------------------------------------------------------------------------------- /spec/SQSEventEmitterMQ.spec.js: -------------------------------------------------------------------------------- 1 | const { ParseMessageQueue } = require('../node_modules/parse-server/lib/ParseMessageQueue'); 2 | const { SQSEventEmitterMQ } = require('../'); 3 | const { logger } = require('parse-server'); 4 | const { getServerConfig } = require('./support/server.js'); 5 | const { getMockSqsOptions } = require('./mocks/sqs.js'); 6 | 7 | let config; 8 | 9 | describe('SMSEventEmitterMQ', () => { 10 | beforeEach(() => { 11 | config = getMockSqsOptions(); 12 | }); 13 | 14 | describe('integration', () => { 15 | it('publishes a message', done => { 16 | const options = getServerConfig().queueOptions; 17 | const subscriber = ParseMessageQueue.createSubscriber(options); 18 | const publisher = ParseMessageQueue.createPublisher(options); 19 | const channel = 'foo'; 20 | const message = 'hi'; 21 | 22 | subscriber.subscribe(channel); 23 | subscriber.on('message', (channel, message) => { 24 | expect(channel).toBe(channel); 25 | expect(message).toBe(message); 26 | 27 | // Give aws-sdk some time to mark the message to avoid flaky test 28 | setTimeout(done, 200); 29 | }); 30 | 31 | publisher.publish(channel, message); 32 | }); 33 | }); 34 | 35 | describe('subscriber', () => { 36 | it('should only have one subscription map', () => { 37 | const subscriber1 = ParseMessageQueue.createSubscriber(config); 38 | subscriber1.subscribe('foo'); 39 | const subscriber2 = ParseMessageQueue.createSubscriber(config); 40 | subscriber2.subscribe('bar'); 41 | // subscribe twice for coverage sake! 42 | subscriber2.subscribe('bar'); 43 | expect(subscriber2.subscriptions === subscriber1.subscriptions).toBe(true); 44 | }); 45 | 46 | it('should throw if no config', () => { 47 | expect(() => ParseMessageQueue.createSubscriber({ messageQueueAdapter: SQSEventEmitterMQ })) 48 | .toThrow(new Error('No queueUrl found in config')); 49 | }); 50 | 51 | it('should allow unsubscribe', () => { 52 | const subscriber = ParseMessageQueue.createSubscriber(config); 53 | expect(() => subscriber.unsubscribe('foo')).not.toThrow(); 54 | }); 55 | 56 | it('calls the handleMessage function when a message is received', (done) => { 57 | const subscriber = ParseMessageQueue.createSubscriber(config); 58 | subscriber.subscribe('message_processed'); 59 | subscriber.on('message', (event, message) => { 60 | expect(event).toBe('message_processed'); 61 | expect(message).toBe('hi'); 62 | done(); 63 | }); 64 | }); 65 | }); 66 | 67 | describe('publisher', () => { 68 | it('should throw if no config', () => { 69 | expect(() => ParseMessageQueue.createPublisher({ messageQueueAdapter: SQSEventEmitterMQ })) 70 | .toThrow(new Error('Missing SQS producer option [queueUrl].')); 71 | }); 72 | 73 | it('should handle happy path', () => { 74 | expect(() => ParseMessageQueue.createPublisher(config)).not.toThrow(); 75 | }); 76 | 77 | it('should publish', () => { 78 | const publisher = ParseMessageQueue.createPublisher(config); 79 | expect(() => publisher.publish('foo', 'bar')).not.toThrow(); 80 | }); 81 | 82 | it('should error', () => { 83 | const publisher = ParseMessageQueue.createPublisher(config); 84 | spyOn(logger, 'error'); 85 | publisher.publish(); 86 | const expectedError = new Error("Object messages must have 'body' prop"); 87 | expect(logger.error).toHaveBeenCalledWith(expectedError); 88 | }); 89 | 90 | it('should process a batch', () => { 91 | const publisher = ParseMessageQueue.createPublisher(config); 92 | spyOn(publisher.emitter, 'send'); 93 | publisher.publish('channel', ['foo', 'bar']); 94 | const payload = [ 95 | { id: '0', body: 'foo', groupId: 'channel' }, 96 | { id: '1', body: 'bar', groupId: 'channel' }, 97 | ]; 98 | expect(publisher.emitter.send).toHaveBeenCalledWith(payload); 99 | }); 100 | 101 | it('should process a batch with no channel', () => { 102 | const publisher = ParseMessageQueue.createPublisher(config); 103 | spyOn(publisher.emitter, 'send'); 104 | publisher.publish(undefined, ['foo', 'bar']); 105 | const payload = [ 106 | { id: '0', body: 'foo' }, 107 | { id: '1', body: 'bar' }, 108 | ]; 109 | expect(publisher.emitter.send).toHaveBeenCalledWith(payload); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parse Server SQS Message Queue Adapter 2 | 3 | [![Build Status](https://github.com/parse-community/parse-server-sqs-mq-adapter/workflows/ci/badge.svg?branch=main)](https://github.com/parse-community/parse-server-sqs-mq-adapter/actions?query=workflow%3Aci+branch%3Amain) 4 | [![Snyk Badge](https://snyk.io/test/github/parse-community/parse-server-sqs-mq-adapter/badge.svg)](https://snyk.io/test/github/parse-community/parse-server-sqs-mq-adapter) 5 | [![Coverage](https://img.shields.io/codecov/c/github/parse-community/parse-server-sqs-mq-adapter/main.svg)](https://codecov.io/github/parse-community/parse-server-sqs-mq-adapter?branch=main) 6 | [![auto-release](https://img.shields.io/badge/%F0%9F%9A%80-auto--release-9e34eb.svg)](https://github.com/parse-community/parse-server-sqs-mq-adapter/releases) 7 | 8 | [![npm latest version](https://img.shields.io/npm/v/@parse/gcs-files-adapter.svg)](https://www.npmjs.com/package/@parse/sqs-mq-adapter) 9 | 10 | --- 11 | 12 | The Parse Server AWS SQS Message Queue Adapter integrates Amazon SQS as the underlying message queue for Parse Server. It allows push notifications, jobs and LiveQuery events to be distributed across multiple Parse Server instances. 13 | 14 | --- 15 | 16 | - [Installation](#installation) 17 | - [Usage](#usage) 18 | - [Integrate with Parse Server](#integrate-with-parse-server) 19 | - [Credentials](#credentials) 20 | - [Push Notifications](#push-notifications) 21 | ## Installation 22 | 23 | `npm install --save @parse/sqs-mq-adapter` 24 | 25 | ## Usage 26 | 27 | ```js 28 | const ParseServer = require('parse-server').ParseServer; 29 | const SQSEventEmitterMQ = require('@parse/sqs-mq-adapter').SQSEventEmitterMQ; 30 | 31 | config = { 32 | .... 33 | queueOptions: { 34 | messageQueueAdapter: SQSEventEmitterMQ, 35 | queueUrl: 'https://sqs.us-east-1.amazonaws.com/XXX/Parse-Queue', // required 36 | region: 'us-east-1', 37 | }, 38 | }; 39 | 40 | const parseServer = new ParseServer(config); 41 | ``` 42 | 43 | 44 | ### Integrate with Parse Server 45 | 46 | 1. **Install dependencies** 47 | 48 | ```bash 49 | npm install parse-server @parse/sqs-mq-adapter 50 | ``` 51 | 52 | 2. **Configure the adapter** in your Parse Server configuration: 53 | 54 | ```js 55 | const { ParseServer } = require('parse-server'); 56 | const { SQSEventEmitterMQ } = require('@parse/sqs-mq-adapter'); 57 | 58 | const config = { 59 | databaseURI: 'mongodb://localhost:27017/app', 60 | appId: 'myAppId', 61 | masterKey: 'myMasterKey', 62 | serverURL: 'https://example.com/parse', 63 | queueOptions: { 64 | messageQueueAdapter: SQSEventEmitterMQ, 65 | queueUrl: 'https://sqs.us-east-1.amazonaws.com/XXX/Parse-Queue', 66 | region: 'us-east-1', 67 | }, 68 | }; 69 | 70 | const server = new ParseServer(config); 71 | ``` 72 | 73 | 3. **Start Parse Server** and the adapter will listen to the configured SQS queue. 74 | 75 | See: [sqs-consumer](https://www.npmjs.com/package/sqs-consumer#options) for complete list of configuration options. 76 | 77 | ### Credentials 78 | 79 | By default the consumer will look for AWS credentials in the places [specified by the AWS SDK](http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-configuring.html#Setting_AWS_Credentials). The simplest option is to export your credentials as environment variables: 80 | 81 | ```bash 82 | export AWS_SECRET_ACCESS_KEY=... 83 | export AWS_ACCESS_KEY_ID=... 84 | ``` 85 | 86 | If you need to specify your credentials manually, you can use a pre-configured instance of the [AWS SQS](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SQS.html) client: 87 | 88 | 89 | ```js 90 | const ParseServer = require('parse-server').ParseServer; 91 | const SQSEventEmitterMQ = require('@parse/sqs-mq-adapter').SQSEventEmitterMQ; 92 | const AWS = require('aws-sdk'); 93 | 94 | AWS.config.update({ 95 | region: 'eu-west-1', 96 | accessKeyId: '...', 97 | secretAccessKey: '...' 98 | }); 99 | 100 | config = { 101 | .... 102 | messageQueueAdapter: SQSEventEmitterMQ, 103 | SQSEventEmitterMQOptions: { 104 | queueUrl: 'https://sqs.us-east-1.amazonaws.com/XXX/Parse-Queue', 105 | sqs: new AWS.SQS(), 106 | }, 107 | }; 108 | 109 | const parseServer = new ParseServer(config); 110 | ``` 111 | 112 | ### Push Notifications 113 | 114 | Parse Server sends push notifications as part of its workload using an internal push queue. When sending large amounts of push notifications this may impact other parts of the workload. This adapter allows Parse Server to only enqueue push notifications into a shared push queue so that another, dedicated Parse Server instance can process the push queue and send the push notification to the push service provider. 115 | 116 | The Parse Server instance that should only enqueue pushes must have set `disablePushWorker: true`. The Parse Server instance that should process and send the enqueued pushes must omit this option, or set `disablePushWorker: false`. 117 | 118 | ```js 119 | const { ParseServer } = require('parse-server'); 120 | const { SQSEventEmitterMQ } = require('@parse/sqs-mq-adapter'); 121 | 122 | const config = { 123 | push: { 124 | adapter: new MyPushAdapter(), 125 | queueOptions: { 126 | messageQueueAdapter: SQSEventEmitterMQ, 127 | queueUrl: 'https://sqs.us-east-1.amazonaws.com/XXX/Push-Queue', 128 | region: 'us-east-1', 129 | disablePushWorker: true, 130 | }, 131 | }, 132 | }; 133 | 134 | const server = new ParseServer(config); 135 | ``` 136 | 137 | This works for any instance constellation, with one or multiple instances enqueuing pushes and one or multiple instances sending pushes. 138 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS --------------------------------------------------------------------------------