├── ci ├── staged │ └── .gitkeep ├── .gitignore ├── aggregate-conventional-changelog.mjs ├── aggregate-npm.mjs ├── conventional-changelog-microfleet.mjs └── aggregate-release.mjs ├── benchmarks ├── .gitignore ├── microfleet-17 │ ├── actions │ │ └── hello.js │ ├── server.js │ └── package.json ├── microfleet-next │ ├── actions │ │ └── hello.js │ └── server.js ├── fastify │ ├── package.json │ └── server.js └── package.json ├── packages ├── core-types │ ├── private.d.ts │ ├── .gitignore │ ├── .release-it.cjs │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── package.json ├── plugin-socketio │ ├── README.md │ ├── src │ │ └── index.ts │ ├── .release-it.cjs │ ├── .mdeprc.cjs │ ├── tsconfig.json │ ├── schemas │ │ └── socketio.json │ ├── tsconfig.build.json │ └── __tests__ │ │ └── socketio.spec.ts ├── plugin-amqp │ ├── README.md │ ├── .release-it.cjs │ ├── src │ │ ├── index.ts │ │ └── types │ │ │ └── plugin.ts │ ├── tsconfig.json │ ├── .mdeprc.cjs │ ├── tsconfig.build.json │ └── __tests__ │ │ └── utils │ │ └── health-check.ts ├── plugin-casl │ ├── src │ │ ├── index.ts │ │ ├── allowed-extension.ts │ │ ├── overrides.ts │ │ └── casl.ts │ ├── .release-it.cjs │ ├── __tests__ │ │ └── artifacts │ │ │ ├── schemas │ │ │ ├── protected.json │ │ │ ├── scoped.user.json │ │ │ └── scoped.profile.json │ │ │ └── actions │ │ │ ├── protected.ts │ │ │ └── scoped │ │ │ ├── user.ts │ │ │ └── profile.ts │ ├── tsconfig.json │ ├── .mdeprc.cjs │ ├── tsconfig.build.json │ └── schemas │ │ └── casl.json ├── plugin-dlock │ ├── src │ │ ├── index.ts │ │ └── utils │ │ │ └── lock-action-wrapper.ts │ ├── .release-it.cjs │ ├── tsconfig.json │ ├── .mdeprc.cjs │ ├── tsconfig.build.json │ ├── README.md │ ├── schemas │ │ └── dlock.json │ └── __tests__ │ │ ├── dlock-sentinel.spec.ts │ │ └── dlock-cluster.spec.ts ├── plugin-knex │ ├── src │ │ └── index.ts │ ├── .release-it.cjs │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── .mdeprc.cjs │ ├── schemas │ │ ├── knex.pg.json │ │ └── knex.json │ └── README.md ├── plugin-consul │ ├── src │ │ ├── index.ts │ │ └── patch.ts │ ├── .release-it.cjs │ ├── __tests__ │ │ └── docker-compose.yml │ ├── tsconfig.json │ ├── .mdeprc.cjs │ └── tsconfig.build.json ├── plugin-couchdb │ ├── src │ │ └── index.ts │ ├── .release-it.cjs │ ├── tsconfig.json │ ├── .mdeprc.cjs │ ├── tsconfig.build.json │ ├── README.md │ └── __tests__ │ │ └── couchdb.spec.ts ├── plugin-logger │ ├── src │ │ ├── index.ts │ │ ├── types │ │ │ └── lsmod │ │ │ │ └── index.d.ts │ │ ├── constants.ts │ │ └── logger │ │ │ └── streams │ │ │ └── sentry-worker.ts │ ├── .release-it.cjs │ ├── .eslintrc.json │ ├── .mocharc.js │ ├── .mdeprc.js │ ├── tsconfig.json │ ├── tsconfig.build.json │ └── __tests__ │ │ └── sentry.beforeSend.js ├── plugin-prometheus │ ├── src │ │ ├── index.ts │ │ └── metrics.ts │ ├── .release-it.cjs │ ├── tsconfig.json │ ├── .mdeprc.js │ ├── tsconfig.build.json │ ├── schemas │ │ └── prometheus.json │ └── __tests__ │ │ └── prometheus.spec.ts ├── plugin-redis-core │ ├── src │ │ ├── module.d.ts │ │ ├── index.ts │ │ └── constants.ts │ ├── .release-it.cjs │ ├── tsconfig.json │ └── tsconfig.build.json ├── plugin-router-amqp │ ├── README.md │ ├── src │ │ ├── index.ts │ │ └── types │ │ │ └── plugin.ts │ ├── .release-it.cjs │ ├── __tests__ │ │ └── artifacts │ │ │ └── actions │ │ │ └── echo.ts │ ├── tsconfig.json │ ├── .mdeprc.cjs │ ├── tsconfig.build.json │ └── schemas │ │ └── router-amqp.json ├── plugin-router-socketio │ ├── src │ │ ├── index.ts │ │ ├── attach.ts │ │ └── plugin.ts │ ├── .release-it.cjs │ ├── tsconfig.json │ ├── __tests__ │ │ ├── artifacts │ │ │ └── actions │ │ │ │ └── echo.ts │ │ └── suites │ │ │ └── router-socketio.spec.ts │ ├── .mdeprc.cjs │ ├── README.md │ └── tsconfig.build.json ├── plugin-validator │ ├── src │ │ └── index.ts │ ├── .release-it.cjs │ ├── __tests__ │ │ └── fixtures │ │ │ ├── test.json │ │ │ ├── test-types.json │ │ │ └── config.json │ ├── tsconfig.json │ ├── .mdeprc.cjs │ └── tsconfig.build.json ├── plugin-aws-elasticsearch │ ├── src │ │ ├── index.ts │ │ └── utils │ │ │ └── createAwsElasticsearchConnector.ts │ ├── .release-it.cjs │ ├── tsconfig.json │ ├── schemas │ │ └── aws-elasticsearch.json │ ├── README.md │ ├── tsconfig.build.json │ ├── .mdeprc.cjs │ └── __tests__ │ │ └── aws-elasticsearch.spec.ts ├── plugin-elasticsearch │ ├── src │ │ └── index.ts │ ├── .release-it.cjs │ ├── tsconfig.json │ ├── .mdeprc.cjs │ ├── tsconfig.build.json │ └── __tests__ │ │ └── elasticsearch.spec.ts ├── plugin-redis-cluster │ ├── __tests__ │ │ ├── migrations │ │ │ ├── .eslintignore │ │ │ ├── 001.unsorted │ │ │ │ ├── migration.lua │ │ │ │ └── index.js │ │ │ ├── migration-one │ │ │ │ ├── migration.lua │ │ │ │ └── index.js │ │ │ ├── migration_01.js │ │ │ └── script-migration │ │ │ │ ├── migration.lua │ │ │ │ └── index.js │ │ ├── fixtures │ │ │ ├── test-script.lua │ │ │ └── echo-woo.lua │ │ └── utils │ │ │ └── health-check.ts │ ├── src │ │ ├── index.ts │ │ └── types │ │ │ └── module.d.ts │ ├── .release-it.cjs │ ├── .mdeprc.js │ ├── tsconfig.json │ ├── tsconfig.build.json │ └── schemas │ │ └── redis-cluster.json ├── plugin-redis-sentinel │ ├── __tests__ │ │ ├── migrations │ │ │ ├── 001.unsorted │ │ │ │ ├── migration.lua │ │ │ │ └── index.js │ │ │ ├── migration-one │ │ │ │ ├── migration.lua │ │ │ │ └── index.js │ │ │ ├── migration_01.js │ │ │ └── script-migration │ │ │ │ ├── migration.lua │ │ │ │ └── index.js │ │ ├── fixtures │ │ │ ├── test-script.lua │ │ │ └── echo-woo.lua │ │ └── utils │ │ │ └── health-check.ts │ ├── src │ │ └── index.ts │ ├── .release-it.cjs │ ├── tsconfig.json │ ├── .mdeprc.js │ └── tsconfig.build.json ├── plugin-router-hapi │ ├── README.md │ ├── .release-it.cjs │ ├── src │ │ ├── index.ts │ │ ├── types │ │ │ └── plugin.ts │ │ ├── utils │ │ │ └── action-name.ts │ │ └── plugin.ts │ ├── __tests__ │ │ └── artifacts │ │ │ ├── templates │ │ │ └── view.hbs │ │ │ └── actions │ │ │ ├── success.ts │ │ │ ├── view.ts │ │ │ ├── redirect.ts │ │ │ ├── echo.ts │ │ │ ├── external-redirect.ts │ │ │ ├── validation.ts │ │ │ └── hapi-raw-body.ts │ ├── tsconfig.json │ ├── schemas │ │ └── router-hapi.json │ ├── .mdeprc.cjs │ └── tsconfig.build.json ├── core │ ├── .release-it.cjs │ ├── schemas │ │ ├── generic │ │ │ └── health.json │ │ └── core.json │ ├── docs │ │ ├── reference │ │ │ ├── plugins │ │ │ │ ├── http.md │ │ │ │ ├── router.md │ │ │ │ ├── knex-migration.md │ │ │ │ ├── amqp │ │ │ │ │ └── publish-options.md │ │ │ │ ├── redis │ │ │ │ │ ├── cluster.md │ │ │ │ │ └── sentinel.md │ │ │ │ └── knex.md │ │ │ ├── core.md │ │ │ └── plugins.md │ │ ├── recipes.md │ │ ├── reference.md │ │ └── recipes │ │ │ └── maintenance.md │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── src │ │ └── defaults.ts │ └── bin │ │ └── mfleet.js ├── utils │ ├── tsconfig.json │ ├── .release-it.cjs │ ├── src │ │ ├── index.ts │ │ ├── packageInfo.ts │ │ ├── defaults-deep.ts │ │ └── constants.ts │ ├── tsconfig.build.json │ └── package.json ├── plugin-hapi │ ├── .release-it.cjs │ ├── src │ │ ├── index.ts │ │ ├── plugins │ │ │ ├── redirect.ts │ │ │ ├── state.ts │ │ │ └── views.ts │ │ └── types │ │ │ └── plugin.ts │ ├── __tests__ │ │ └── artifacts │ │ │ └── actions │ │ │ └── echo.ts │ ├── tsconfig.json │ ├── .mdeprc.cjs │ ├── README.md │ └── tsconfig.build.json ├── plugin-kafka │ ├── .release-it.cjs │ ├── src │ │ ├── index.ts │ │ ├── util.ts │ │ └── custom │ │ │ └── rdkafka-extra.ts │ ├── schemas │ │ └── kafka.json │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── .mdeprc.cjs │ └── __tests__ │ │ └── docker-configs │ │ └── toxy.json ├── plugin-router │ ├── .release-it.cjs │ ├── __tests__ │ │ └── artifacts │ │ │ ├── schemas │ │ │ ├── throws.json │ │ │ ├── retry.json │ │ │ ├── long-running.json │ │ │ ├── validate-response.json │ │ │ ├── nested.test.json │ │ │ ├── without-schema.json │ │ │ ├── qs.json │ │ │ ├── simple.json │ │ │ └── response │ │ │ │ ├── validate-response.json │ │ │ │ └── validate-response-without-schema.json │ │ │ ├── actions │ │ │ ├── nested │ │ │ │ └── test.ts │ │ │ ├── without-schema.ts │ │ │ ├── maintenance │ │ │ │ ├── amqp.ts │ │ │ │ ├── http.ts │ │ │ │ ├── http-readonly.ts │ │ │ │ └── http-amqp.ts │ │ │ ├── throws.ts │ │ │ ├── esm │ │ │ │ ├── simple-js.mjs │ │ │ │ └── simple-ts.mts │ │ │ ├── validate-response-without-schema.ts │ │ │ ├── validate-response.ts │ │ │ ├── validate-response-skip.ts │ │ │ ├── simple.ts │ │ │ ├── cjs │ │ │ │ └── simple.cts │ │ │ ├── long-running.ts │ │ │ ├── retry.ts │ │ │ └── qs.ts │ │ │ └── child-service.ts │ ├── tsconfig.test.json │ ├── tsconfig.json │ ├── src │ │ ├── lifecycle │ │ │ ├── handlers │ │ │ │ ├── handler.ts │ │ │ │ ├── request.ts │ │ │ │ ├── allowed.ts │ │ │ │ ├── validate.ts │ │ │ │ └── validate-response.ts │ │ │ └── utils.ts │ │ ├── extensions │ │ │ ├── index.ts │ │ │ ├── audit │ │ │ │ └── timing.ts │ │ │ └── validate │ │ │ │ ├── query-string-parser.ts │ │ │ │ └── transport-options.ts │ │ ├── index.ts │ │ ├── actions │ │ │ └── health.ts │ │ ├── types │ │ │ └── plugin.ts │ │ └── routes.ts │ ├── .mdeprc.cjs │ ├── tsconfig.build.json │ └── README.md ├── plugin-kafka-types │ ├── .release-it.cjs │ ├── tsconfig.json │ └── package.json └── plugin-signed-request │ ├── .release-it.cjs │ ├── __tests__ │ └── artifacts │ │ ├── schemas │ │ └── signed.json │ │ └── actions │ │ └── signed.ts │ ├── src │ ├── index.ts │ ├── errors.ts │ ├── router-extension.ts │ ├── plugins │ │ ├── restify.ts │ │ ├── hapi.ts │ │ └── fastify.ts │ ├── typings │ │ └── http-signature │ │ │ └── index.d.ts │ ├── plugin.ts │ └── types.ts │ ├── tsconfig.json │ ├── schemas │ └── signed-request.json │ ├── .mdeprc.cjs │ └── tsconfig.build.json ├── .husky ├── commit-msg └── prepare-commit-msg ├── assets └── mf-concept-core-2.png ├── renovate.json ├── .mdeprc.cjs ├── .editorconfig ├── tsconfig.json ├── .codeclimate.yml ├── .npmrc ├── .gitattributes ├── .eslintrc.json ├── tsconfig.base.json ├── .commitlintrc.cjs ├── pnpm-workspace.yaml ├── .gitignore ├── package.json ├── .release-it.cjs ├── LICENSE ├── .pnpmfile.cjs └── .release-it.root.cjs /ci/staged/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | *.lock 2 | -------------------------------------------------------------------------------- /packages/core-types/private.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/plugin-socketio/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ci/.gitignore: -------------------------------------------------------------------------------- 1 | staged/** 2 | !staged/.gitkeep 3 | -------------------------------------------------------------------------------- /packages/core-types/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.map 3 | -------------------------------------------------------------------------------- /packages/plugin-amqp/README.md: -------------------------------------------------------------------------------- 1 | # Microfleet plugin-amqp 2 | -------------------------------------------------------------------------------- /packages/plugin-casl/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './casl' 2 | -------------------------------------------------------------------------------- /packages/plugin-dlock/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin' 2 | -------------------------------------------------------------------------------- /packages/plugin-knex/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './knex' 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | "`npm x -- mdep bin commitlint`" --edit $1 2 | -------------------------------------------------------------------------------- /packages/plugin-consul/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin' 2 | -------------------------------------------------------------------------------- /packages/plugin-couchdb/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './couchdb' 2 | -------------------------------------------------------------------------------- /packages/plugin-logger/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './logger' 2 | -------------------------------------------------------------------------------- /packages/plugin-socketio/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin' 2 | -------------------------------------------------------------------------------- /packages/plugin-prometheus/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './prometheus' 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-core/src/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'sort-by' 2 | -------------------------------------------------------------------------------- /packages/plugin-router-amqp/README.md: -------------------------------------------------------------------------------- 1 | # Microfleet plugin-router-amqp 2 | -------------------------------------------------------------------------------- /packages/plugin-router-amqp/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin' 2 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin' 2 | -------------------------------------------------------------------------------- /packages/plugin-validator/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validator' 2 | -------------------------------------------------------------------------------- /packages/plugin-aws-elasticsearch/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin' 2 | -------------------------------------------------------------------------------- /packages/plugin-elasticsearch/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './elasticsearch' 2 | -------------------------------------------------------------------------------- /packages/plugin-logger/src/types/lsmod/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'lsmod' 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/migrations/.eslintignore: -------------------------------------------------------------------------------- 1 | ./**/*.js 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/migrations/001.unsorted/migration.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './redis-cluster' 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/migrations/001.unsorted/migration.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/README.md: -------------------------------------------------------------------------------- 1 | # @microfleet/plugin-router-hapi 2 | -------------------------------------------------------------------------------- /packages/core/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/fixtures/test-script.lua: -------------------------------------------------------------------------------- 1 | return nil; 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/fixtures/test-script.lua: -------------------------------------------------------------------------------- 1 | return nil; 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './redis-sentinel' 2 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/utils/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/core-types/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-amqp/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-casl/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-consul/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-couchdb/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-dlock/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-hapi/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-kafka/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-knex/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-logger/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-router/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-amqp/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin' 2 | export * from './types/plugin' 3 | -------------------------------------------------------------------------------- /packages/plugin-hapi/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin' 2 | export * from './types/plugin' 3 | -------------------------------------------------------------------------------- /packages/plugin-kafka-types/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-prometheus/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-core/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-router-amqp/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/throws.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "throws" 3 | } 4 | -------------------------------------------------------------------------------- /packages/plugin-socketio/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-validator/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-aws-elasticsearch/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-elasticsearch/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-logger/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const SENTRY_FINGERPRINT_DEFAULT = '{{ default }}' 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/fixtures/echo-woo.lua: -------------------------------------------------------------------------------- 1 | return redis.call('get', KEYS[1]); 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/fixtures/echo-woo.lua: -------------------------------------------------------------------------------- 1 | return redis.call('get', KEYS[1]); 2 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/.release-it.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.release-it.cjs') 2 | -------------------------------------------------------------------------------- /assets/mf-concept-core-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microfleet/core/HEAD/assets/mf-concept-core-2.png -------------------------------------------------------------------------------- /packages/core/schemas/generic/health.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "generic.health", 3 | "type": "object" 4 | } 5 | -------------------------------------------------------------------------------- /packages/plugin-kafka/src/index.ts: -------------------------------------------------------------------------------- 1 | export { KafkaFactory } from './kafka' 2 | export * from './kafka' 3 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin' 2 | export * from './types/plugin' 3 | -------------------------------------------------------------------------------- /packages/plugin-validator/__tests__/fixtures/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "test-schema", 3 | "type": "object" 4 | } 5 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/retry.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "retry", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /packages/plugin-redis-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants' 2 | export * from './migrate' 3 | export * from './utils' 4 | -------------------------------------------------------------------------------- /packages/plugin-logger/src/logger/streams/sentry-worker.ts: -------------------------------------------------------------------------------- 1 | import { sentryTransport } from './sentry' 2 | 3 | export = sentryTransport 4 | -------------------------------------------------------------------------------- /packages/plugin-kafka-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin-validator/__tests__/fixtures/test-types.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "test-types-schema", 3 | "type": "integer", 4 | "enum": [1] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/migrations/migration-one/migration.lua: -------------------------------------------------------------------------------- 1 | if KEYS[2] == nil then 2 | return redis.error_reply('damn it'); 3 | end 4 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/migrations/migration-one/migration.lua: -------------------------------------------------------------------------------- 1 | if KEYS[2] == nil then 2 | return redis.error_reply('damn it'); 3 | end 4 | -------------------------------------------------------------------------------- /packages/plugin-logger/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc", 3 | "env": { 4 | "mocha": true, 5 | "jest": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges", 5 | "schedule:weekly", 6 | ":automergePatch" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/core-types/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../utils/tsconfig.build.json" 5 | }] 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/long-running.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "long-running", 3 | "type": "object", 4 | "additionalProperties": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants' 2 | export { defaultsDeep, mergeDeep } from './defaults-deep' 3 | export { getVersion } from './packageInfo' 4 | -------------------------------------------------------------------------------- /benchmarks/microfleet-17/actions/hello.js: -------------------------------------------------------------------------------- 1 | async function hello() { 2 | return { hello: 'world' } 3 | } 4 | 5 | hello.schema = false 6 | 7 | module.exports = hello 8 | -------------------------------------------------------------------------------- /benchmarks/microfleet-next/actions/hello.js: -------------------------------------------------------------------------------- 1 | async function hello() { 2 | return { hello: 'world' } 3 | } 4 | 5 | hello.schema = false 6 | 7 | module.exports = hello 8 | -------------------------------------------------------------------------------- /packages/plugin-router/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS" 5 | }, 6 | "files": [] 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/migrations/001.unsorted/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | final: 2, 3 | min: 1, 4 | script: `${__dirname}/migration.lua`, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/migrations/001.unsorted/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | final: 2, 3 | min: 1, 4 | script: `${__dirname}/migration.lua`, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/plugin-validator/__tests__/fixtures/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "invalid": { 5 | "type": "boolean" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /packages/plugin-casl/__tests__/artifacts/schemas/protected.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "$id": "protected", 4 | "type": "object" 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/migrations/migration_01.js: -------------------------------------------------------------------------------- 1 | exports.min = 10 2 | exports.final = 11 3 | exports.script = ({ redis }) => redis.set('migration_01', 'done') 4 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/migrations/script-migration/migration.lua: -------------------------------------------------------------------------------- 1 | -- do some test iterations 2 | local userIndexKey = KEYS[2]; 3 | local cardinality = ARGV[1]; 4 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/migrations/migration_01.js: -------------------------------------------------------------------------------- 1 | exports.min = 10 2 | exports.final = 11 3 | exports.script = ({ redis }) => redis.set('migration_01', 'done') 4 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/migrations/script-migration/migration.lua: -------------------------------------------------------------------------------- 1 | -- do some test iterations 2 | local userIndexKey = KEYS[2]; 3 | local cardinality = ARGV[1]; 4 | -------------------------------------------------------------------------------- /packages/plugin-casl/__tests__/artifacts/schemas/scoped.user.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "$id": "scoped.user", 4 | "type": "object" 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/__tests__/artifacts/schemas/signed.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "$id": "signed", 4 | "type": "object" 5 | } 6 | -------------------------------------------------------------------------------- /benchmarks/fastify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@benchmark/fastify", 3 | "private": true, 4 | "version": "0.0.1", 5 | "dependencies": { 6 | "fastify": "^3.24.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-casl/__tests__/artifacts/schemas/scoped.profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "$id": "scoped.profile", 4 | "type": "object" 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-consul/__tests__/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | consul: 3 | image: consul:1.6 4 | hostname: consul 5 | environment: 6 | CONSUL_BIND_INTERFACE: eth0 7 | -------------------------------------------------------------------------------- /.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | node: '22', 3 | mutagenWorkingDir: '/src', 4 | mutagenDir: __dirname, 5 | mutagenVolumeExternal: true, 6 | mutagenVolumeName: 'microfleet-code', 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugin-logger/.mocharc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | require: [ 3 | 'tsx/cjs', 4 | 'tsconfig-paths/register', 5 | ], 6 | extension: ['ts', 'js', 'cjs'], 7 | timeout: 10000, 8 | } 9 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | case "$2,$3" in 5 | merge,) 6 | ex "+%s/Merge branch '\([^']\+\)'/chore(merge): \1/i" -scwq $1 ;; 7 | *) ;; 8 | esac 9 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/migrations/migration-one/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | min: 0, 3 | final: 1, 4 | keys: ['predefined-key'], 5 | script: `${__dirname}/migration.lua`, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/migrations/migration-one/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | min: 0, 3 | final: 1, 4 | keys: ['predefined-key'], 5 | script: `${__dirname}/migration.lua`, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/plugin-knex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.json" 7 | }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/nested/test.ts: -------------------------------------------------------------------------------- 1 | import { ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default async (request: ServiceRequest): Promise => request.params 4 | -------------------------------------------------------------------------------- /packages/plugin-consul/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.json" 7 | }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-couchdb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.json" 7 | }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './signed-request' 2 | export * from './plugin' 3 | export * from './plugins/restify' 4 | export * from './plugins/hapi' 5 | export * from './plugins/fastify' 6 | -------------------------------------------------------------------------------- /packages/plugin-validator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core-types/tsconfig.json" 5 | }, { 6 | "path": "../core/tsconfig.json" 7 | }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-redis-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core-types/tsconfig.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.json" 7 | }] 8 | } 9 | -------------------------------------------------------------------------------- /benchmarks/fastify/server.js: -------------------------------------------------------------------------------- 1 | const fastify = require('fastify') 2 | 3 | const server = fastify() 4 | 5 | server.get('/hello', function (req, reply) { 6 | reply.send({ hello: 'world' }) 7 | }) 8 | 9 | server.listen(3000) 10 | -------------------------------------------------------------------------------- /packages/core-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../utils/tsconfig.json" 5 | }], 6 | "files": [ 7 | "index.d.ts", 8 | "private.d.ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/validate-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "success" 5 | ], 6 | "properties": { 7 | "success": { 8 | "type": "boolean" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/without-schema.ts: -------------------------------------------------------------------------------- 1 | import { ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default async function withoutSchema(request: ServiceRequest): Promise { 4 | return request.params 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/nested.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "nested.test", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "properties": { 6 | "foo": { 7 | "type": "integer" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/__tests__/artifacts/templates/view.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{title}} 4 | 5 | 6 |

This is test page

7 |
{{content}}
8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/without-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "without-schema", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "properties": { 6 | "foo": { 7 | "type": "integer" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /packages/plugin-amqp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.json" 7 | }, { 8 | "path": "../plugin-router/tsconfig.json" 9 | }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-dlock/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.json" 7 | }, { 8 | "path": "../plugin-router/tsconfig.json" 9 | }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../plugin-hapi/tsconfig.json" 7 | }, { 8 | "path": "../plugin-router/tsconfig.json" 9 | }] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@microfleet/*": [ 7 | "packages/*/src", 8 | "packages/*", 9 | "node_modules/@microfleet/*" 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/qs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "qs", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "properties": { 6 | "sample": { 7 | "type": "integer" 8 | }, 9 | "bool": { 10 | "type": "boolean" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/__tests__/artifacts/actions/success.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport } from '@microfleet/plugin-router' 2 | 3 | export default function successAction(): any { 4 | return { redirected: true } 5 | } 6 | 7 | successAction.schema = false 8 | successAction.transports = [ActionTransport.http] 9 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/maintenance/amqp.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport } from '@microfleet/plugin-router' 2 | 3 | export default async function handler(): Promise { 4 | return { success: true } 5 | } 6 | 7 | handler.schema = false 8 | handler.transports = [ActionTransport.amqp] 9 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/maintenance/http.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport } from '@microfleet/plugin-router' 2 | 3 | export default async function handler(): Promise { 4 | return { success: true } 5 | } 6 | 7 | handler.schema = false 8 | handler.transports = [ActionTransport.http] 9 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/schemas/router-hapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "router-hapi", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "required": [ 6 | "prefix" 7 | ], 8 | "properties": { 9 | "prefix": { 10 | "type": "string", 11 | "default": "" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/throws.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatusError } from 'common-errors' 2 | import { ActionTransport } from '@microfleet/plugin-router' 3 | 4 | export default function handler(): Promise { 5 | throw new HttpStatusError(202, 'ok') 6 | } 7 | 8 | handler.transports = [ActionTransport.amqp] 9 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/src/errors.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatusError } from 'common-errors' 2 | 3 | export class InvalidSignatureError extends HttpStatusError { 4 | public inner_error?: Error 5 | 6 | constructor(message: string, error?: Error) { 7 | super(403, message) 8 | this.inner_error = error 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/docs/reference/plugins/http.md: -------------------------------------------------------------------------------- 1 | # HTTP Plugin 2 | 3 | ### Info 4 | | Parameter | Value | 5 | |---------------|-------------| 6 | | Name | `http` | 7 | | Type | transport | 8 | | Priority | 0 | 9 | | Requirements | [Validator](./validator.md) plugin should be enabled | 10 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../utils/tsconfig.json" 5 | }, { 6 | "path": "../core-types/tsconfig.json" 7 | }, { 8 | "path": "../plugin-validator/tsconfig.json" 9 | }, { 10 | "path": "../plugin-logger/tsconfig.json" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-casl/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | },{ 6 | "path": "../core-types/tsconfig.json" 7 | }, { 8 | "path": "../plugin-validator/tsconfig.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.json" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-prometheus/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | },{ 6 | "path": "../core-types/tsconfig.json" 7 | }, { 8 | "path": "../plugin-validator/tsconfig.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.json" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-router-amqp/__tests__/artifacts/actions/echo.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport, ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default async function echoAction(request: ServiceRequest): Promise { 4 | return request.params 5 | } 6 | 7 | echoAction.schema = false 8 | echoAction.transports = [ActionTransport.amqp] 9 | -------------------------------------------------------------------------------- /packages/plugin-router-amqp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../plugin-amqp/tsconfig.json" 7 | }, { 8 | "path": "../plugin-logger/tsconfig.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.json" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | },{ 6 | "path": "../core-types/tsconfig.json" 7 | }, { 8 | "path": "../plugin-validator/tsconfig.json" 9 | }, { 10 | "path": "../plugin-hapi/tsconfig.json" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-elasticsearch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | },{ 6 | "path": "../core-types/tsconfig.json" 7 | }, { 8 | "path": "../plugin-logger/tsconfig.json" 9 | }, { 10 | "path": "../plugin-validator/tsconfig.json" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../core-types/tsconfig.json" 7 | }, { 8 | "path": "../plugin-socketio/tsconfig.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.json" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/maintenance/http-readonly.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport } from '@microfleet/plugin-router' 2 | 3 | export default async function handler(): Promise { 4 | return { success: true } 5 | } 6 | 7 | handler.schema = false 8 | handler.transports = [ActionTransport.http] 9 | handler.readonly = true 10 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../core-types/tsconfig.json" 7 | }, { 8 | "path": "../plugin-redis-core/tsconfig.json" 9 | }, { 10 | "path": "../plugin-validator/tsconfig.json" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-kafka/schemas/kafka.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "kafka", 3 | "type": "object", 4 | "additionalProperties": true, 5 | "required": [ 6 | "metadata.broker.list" 7 | ], 8 | "properties": { 9 | "metadata.broker.list": { 10 | "type": "string" 11 | }, 12 | "group.id": { 13 | "type": "string" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib" 6 | }, 7 | "include": [ 8 | "./src/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "**/node_modules/**", 12 | "lib", 13 | "dist", 14 | "**.test.ts", 15 | "**.spec.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/src/types/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'promise-toolbox/fromEvent' { 2 | import EventEmitter from "events" 3 | 4 | function fromEvent(em: EventEmitter, event: string | symbol, opts?: { 5 | array?: boolean 6 | ignoreErrors?: boolean 7 | error?: string 8 | }) : Promise 9 | 10 | export = fromEvent 11 | } 12 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "simple", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "properties": { 6 | "isAdmin": { 7 | "type": "boolean", 8 | "default": false 9 | }, 10 | "token": { 11 | "type": "boolean", 12 | "default": false 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/__tests__/artifacts/actions/view.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport, ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default function viewAction(request: ServiceRequest): any { 4 | return request.transportRequest.sendView('view', request.params) 5 | } 6 | 7 | viewAction.schema = false 8 | viewAction.transports = [ActionTransport.http] 9 | -------------------------------------------------------------------------------- /packages/plugin-aws-elasticsearch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "strictNullChecks": false 5 | }, 6 | "references": [{ 7 | "path": "../core-types/tsconfig.json" 8 | }, { 9 | "path": "../plugin-logger/tsconfig.json" 10 | }, { 11 | "path": "../plugin-validator/tsconfig.json" 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/__tests__/artifacts/actions/redirect.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport, ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default function redirectAction(request: ServiceRequest): any { 4 | return request.transportRequest.redirect('success') 5 | } 6 | 7 | redirectAction.schema = false 8 | redirectAction.transports = [ActionTransport.http] 9 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/migrations/script-migration/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs') 2 | 3 | const script = fs.readFileSync(`${__dirname}/migration.lua`, 'utf8') 4 | exports.min = 2 5 | exports.final = 10 6 | 7 | exports.script = async ({ redis }) => { 8 | await redis.get('cardinality') 9 | return redis.eval(script, 1, 'some-index-key', 10) 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/migrations/script-migration/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs') 2 | 3 | const script = fs.readFileSync(`${__dirname}/migration.lua`, 'utf8') 4 | exports.min = 2 5 | exports.final = 10 6 | 7 | exports.script = async ({ redis }) => { 8 | await redis.get('cardinality') 9 | return redis.eval(script, 1, 'some-index-key', 10) 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/__tests__/artifacts/actions/echo.ts: -------------------------------------------------------------------------------- 1 | // @todo @microfleet/test-tools 2 | import { ActionTransport, ServiceRequest } from '@microfleet/plugin-router' 3 | 4 | export default function echoAction(request: ServiceRequest): any { 5 | return Promise.resolve(request.params) 6 | } 7 | 8 | echoAction.schema = false 9 | echoAction.transports = [ActionTransport.http] 10 | -------------------------------------------------------------------------------- /packages/plugin-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../core-types/tsconfig.json" 7 | }, { 8 | "path": "../plugin-logger/tsconfig.json" 9 | }, { 10 | "path": "../plugin-amqp/tsconfig.json" 11 | }, { 12 | "path": "../utils/tsconfig.json" 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /packages/plugin-hapi/__tests__/artifacts/actions/echo.ts: -------------------------------------------------------------------------------- 1 | // @todo @microfleet/test-tools 2 | import { ActionTransport, ServiceRequest } from '@microfleet/plugin-router' 3 | 4 | export default function echoAction(request: ServiceRequest): Promise { 5 | return Promise.resolve(request.params) 6 | } 7 | 8 | echoAction.schema = false 9 | echoAction.transports = [ActionTransport.socketio] 10 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/__tests__/artifacts/actions/external-redirect.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport, ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default function redirectAction(request: ServiceRequest): any { 4 | return request.transportRequest.redirect('https://google.com') 5 | } 6 | 7 | redirectAction.schema = false 8 | redirectAction.transports = [ActionTransport.http] 9 | -------------------------------------------------------------------------------- /packages/plugin-router/src/lifecycle/handlers/handler.ts: -------------------------------------------------------------------------------- 1 | import { Microfleet } from '@microfleet/core' 2 | import { ServiceRequest } from '../../types/router' 3 | 4 | async function handler(this: Microfleet, request: ServiceRequest): Promise { 5 | const result = await request.action.handler.call(this, request) 6 | request.response = result 7 | } 8 | 9 | export default handler 10 | -------------------------------------------------------------------------------- /packages/plugin-amqp/src/types/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { Configuration } from '@microfleet/transport-amqp' 2 | 3 | type HostConfig = { 4 | host: string, 5 | port: number; 6 | } 7 | 8 | export type AMQPPluginTransportConnectionConfig = { 9 | host: string | string[] | HostConfig[]; 10 | port: number; 11 | } 12 | 13 | export type AMQPPluginConfig = { 14 | transport: Configuration 15 | } 16 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/__tests__/artifacts/actions/echo.ts: -------------------------------------------------------------------------------- 1 | // @todo @microfleet/test-tools 2 | import { ActionTransport, ServiceRequest } from '@microfleet/plugin-router' 3 | 4 | export default function echoAction(request: ServiceRequest): any { 5 | return Promise.resolve(request.params) 6 | } 7 | 8 | echoAction.schema = false 9 | echoAction.transports = [ActionTransport.socketio] 10 | -------------------------------------------------------------------------------- /packages/plugin-hapi/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.json" 7 | }, { 8 | "path": "../plugin-socketio/tsconfig.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.json" 11 | }, { 12 | "path": "../plugin-validator/tsconfig.json" 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/response/validate-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "description": "Sample response schema with one property that should not allow additionalProperties", 4 | "required": [ 5 | "validResponse" 6 | ], 7 | "additionalProperties": false, 8 | "properties": { 9 | "validResponse": { 10 | "type": "boolean" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/src/attach.ts: -------------------------------------------------------------------------------- 1 | import { Server } from 'socket.io' 2 | import { Router } from '@microfleet/plugin-router' 3 | import { Logger } from '@microfleet/plugin-logger' 4 | import getSocketIORouterAdapter from './adapter' 5 | 6 | export default function attachSocketIORouter(socketIO: Server, router: Router, log: Logger): void { 7 | socketIO.on('connection', getSocketIORouterAdapter(router, log)) 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-consul/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/**/*.spec.ts", 10 | extras: { 11 | tester: { 12 | working_dir: `/src/packages/${dir}`, 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/plugin-hapi/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/**/*.spec.ts", 10 | extras: { 11 | tester: { 12 | working_dir: `/src/packages/${dir}`, 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/plugin-kafka/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": [ 4 | "**/node_modules/**", 5 | "lib", 6 | "dist", 7 | ], 8 | "references": [{ 9 | "path": "../core/tsconfig.json" 10 | }, { 11 | "path": "../utils/tsconfig.json" 12 | }, { 13 | "path": "../plugin-kafka-types/tsconfig.json" 14 | }, { 15 | "path": "../plugin-logger/tsconfig.json" 16 | }] 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin-logger/.mdeprc.js: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/**/*.spec.ts", 10 | extras: { 11 | tester: { 12 | working_dir: `/src/packages/${dir}`, 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/plugin-prometheus/.mdeprc.js: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/*.spec.ts", 10 | extras: { 11 | tester: { 12 | working_dir: `/src/packages/${dir}`, 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/plugin-validator/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/*.spec.ts", 10 | extras: { 11 | tester: { 12 | working_dir: `/src/packages/${dir}`, 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/schemas/response/validate-response-without-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "description": "Sample response auto-schema with one property that should not allow additionalProperties", 4 | "required": [ 5 | "validResponse" 6 | ], 7 | "additionalProperties": false, 8 | "properties": { 9 | "validResponse": { 10 | "type": "boolean" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/schemas/signed-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "$id": "signed-request", 4 | "type": "object", 5 | "additionalProperties": false, 6 | "properties": { 7 | "headers": { 8 | "type": "array", 9 | "minItems": 1, 10 | "items": { "type": "string", "minLength": 1 } 11 | }, 12 | "clockSkew": { "type": "number" } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /benchmarks/microfleet-17/server.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const { Microfleet } = require('@microfleet/core') 3 | 4 | const server = new Microfleet({ 5 | name: 'tester', 6 | plugins: [ 7 | 'logger', 8 | 'validator', 9 | 'router', 10 | 'http', 11 | ], 12 | router: { 13 | routes: { 14 | directory: resolve(__dirname, './actions'), 15 | }, 16 | }, 17 | }) 18 | 19 | server.connect() 20 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/suites/*.spec.ts", 10 | extras: { 11 | tester: { 12 | working_dir: `/src/packages/${dir}`, 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /benchmarks/microfleet-17/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@benchmark/microfleet-17", 3 | "private": true, 4 | "version": "0.0.1", 5 | "dependencies": { 6 | "@hapi/boom": "^9.1.4", 7 | "@hapi/hapi": "^20.2.1", 8 | "@hapi/joi": "^17.1.1", 9 | "@microfleet/core": "workspace:^", 10 | "@microfleet/transport-amqp": "^15.2.8", 11 | "@microfleet/validation": "^12.0.0", 12 | "common-errors": "^1.2.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/suites/*.spec.ts", 10 | extras: { 11 | tester: { 12 | working_dir: `/src/packages/${dir}`, 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/plugin-hapi/src/plugins/redirect.ts: -------------------------------------------------------------------------------- 1 | import { Request, Server } from '@hapi/hapi' 2 | 3 | export const name = 'mservice-redirect' 4 | export const version = '1.0.0' 5 | export const once = true 6 | export function register(server: Server): void { 7 | (server as any)._core.root.decorate('request', 'redirect', function redirectResponse(this: Request, url: string) { 8 | return this.generateResponse(null).redirect(url) 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-logger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "typeRoots": [ 5 | "../../node_modules/@types", 6 | "./node_modules/@types", 7 | "./src/types" 8 | ] 9 | }, 10 | "references": [{ 11 | "path": "../core/tsconfig.json" 12 | }, { 13 | "path": "../core-types/tsconfig.json" 14 | }, { 15 | "path": "../plugin-validator/tsconfig.json" 16 | }] 17 | } 18 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | checks: 3 | argument-count: 4 | enabled: false 5 | exclude_patterns: 6 | - config/ 7 | - db/ 8 | - dist/ 9 | - features/ 10 | - "**/node_modules/" 11 | - script/ 12 | - "**/spec/" 13 | - "**/test/" 14 | - "**/tests/" 15 | - "**/__tests__/" 16 | - "**/*.spec.ts" 17 | - "**/*.spec.js" 18 | - Tests/ 19 | - "**/vendor/" 20 | - "**/*_test.go" 21 | - "**/*.d.ts" 22 | - "**/jest.config.js" 23 | -------------------------------------------------------------------------------- /packages/plugin-socketio/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | services: ["rabbitmq"], 8 | root: '/src/node_modules/.bin', 9 | test_framework: "tsx --test", 10 | tests: "__tests__/*.spec.ts", 11 | extras: { 12 | tester: { 13 | working_dir: `/src/packages/${dir}`, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/plugin-socketio/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.json" 5 | }, { 6 | "path": "../utils/tsconfig.json" 7 | }, { 8 | "path": "../core-types/tsconfig.json" 9 | }, { 10 | "path": "../plugin-logger/tsconfig.json" 11 | }, { 12 | "path": "../plugin-validator/tsconfig.json" 13 | },{ 14 | "path": "../plugin-router/tsconfig.json" 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /packages/plugin-validator/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core-types/tsconfig.build.json" 5 | }], 6 | "compilerOptions": { 7 | "rootDir": "./src", 8 | "outDir": "./lib" 9 | }, 10 | "include": [ 11 | "./src/**/*.ts" 12 | ], 13 | "exclude": [ 14 | "**/node_modules/**", 15 | "lib", 16 | "dist", 17 | "**.test.ts", 18 | "**.spec.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/plugin-amqp/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/**/*.spec.ts", 10 | services: ['rabbitmq'], 11 | extras: { 12 | tester: { 13 | working_dir: `/src/packages/${dir}`, 14 | } 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /packages/plugin-casl/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/**/*.spec.ts", 10 | services: ['rabbitmq'], 11 | extras: { 12 | tester: { 13 | working_dir: `/src/packages/${dir}`, 14 | } 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /packages/plugin-router/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | services: ['rabbitmq'], 8 | root: '/src/node_modules/.bin', 9 | test_framework: "tsx --test", 10 | tests: "__tests__/suites/*.spec.ts", 11 | extras: { 12 | tester: { 13 | working_dir: `/src/packages/${dir}`, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/.mdeprc.js: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | services: ['redisCluster'], 8 | root: '/src/node_modules/.bin', 9 | test_framework: "tsx --test", 10 | tests: "__tests__/*.spec.ts", 11 | extras: { 12 | tester: { 13 | working_dir: `/src/packages/${dir}`, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/.mdeprc.js: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | services: ['redisSentinel'], 8 | root: '/src/node_modules/.bin', 9 | test_framework: "tsx --test", 10 | tests: "__tests__/*.spec.ts", 11 | extras: { 12 | tester: { 13 | working_dir: `/src/packages/${dir}`, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/plugin-router-amqp/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | services: ['rabbitmq'], 8 | root: '/src/node_modules/.bin', 9 | test_framework: "tsx --test", 10 | tests: "__tests__/suites/*.spec.ts", 11 | extras: { 12 | tester: { 13 | working_dir: `/src/packages/${dir}`, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/plugin-elasticsearch/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | services: ["elasticsearch"], 7 | auto_compose: true, 8 | root: '/src/node_modules/.bin', 9 | test_framework: "tsx --test", 10 | tests: "__tests__/**/*.spec.ts", 11 | extras: { 12 | tester: { 13 | working_dir: `/src/packages/${dir}`, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/src/types/plugin.ts: -------------------------------------------------------------------------------- 1 | import { ServerRoute } from '@hapi/hapi' 2 | 3 | declare module '@microfleet/core-types' { 4 | export interface ConfigurationOptional { 5 | routerHapi: RouterHapiPluginConfig 6 | } 7 | } 8 | 9 | declare module '@microfleet/plugin-router' { 10 | export interface TransportOptions { 11 | hapi?: Partial 12 | } 13 | } 14 | 15 | export type RouterHapiPluginConfig = { 16 | prefix: string 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin-router/src/extensions/index.ts: -------------------------------------------------------------------------------- 1 | import auditLog, { hrTimeDurationInMs } from './audit/log' 2 | import { initTimingExtension } from './audit/timing' 3 | import validateQueryStringParser from './validate/query-string-parser' 4 | import validateTransportOptions from './validate/transport-options' 5 | 6 | export { 7 | auditLog, 8 | validateQueryStringParser, 9 | validateTransportOptions, 10 | hrTimeDurationInMs, 11 | initTimingExtension, 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | services: ["rabbitmq"], 8 | root: '/src/node_modules/.bin', 9 | test_framework: "tsx --test", 10 | tests: "__tests__/suites/*.spec.ts", 11 | extras: { 12 | tester: { 13 | working_dir: `/src/packages/${dir}`, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | access = public 2 | public-hoist-pattern[] = '*eslint*' 3 | public-hoist-pattern[] = '@prettier/plugin-*' 4 | public-hoist-pattern[] = '*prettier-plugin-*' 5 | public-hoist-pattern[] = "@swc/*" 6 | public-hoist-pattern[] = "@swc-node/*" 7 | public-hoist-pattern[] = "esbuild" 8 | public-hoist-pattern[] = "tsx" 9 | public-hoist-pattern[] = "@esbuild/*" 10 | public-hoist-pattern[] = "@node-rs/*" 11 | public-hoist-pattern[] = "@types/*" 12 | enable-pre-post-scripts = true 13 | -------------------------------------------------------------------------------- /packages/plugin-couchdb/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | services: [ 7 | 'couchdb', 8 | ], 9 | auto_compose: true, 10 | root: '/src/node_modules/.bin', 11 | test_framework: "tsx --test", 12 | tests: "__tests__/**/*.spec.ts", 13 | extras: { 14 | tester: { 15 | working_dir: `/src/packages/${dir}`, 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/plugin-redis-core/src/constants.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionError, NotPermittedError } from 'common-errors' 2 | 3 | export const ERROR_NOT_STARTED = new NotPermittedError('redis was not started') 4 | export const ERROR_NOT_HEALTHY = new ConnectionError('redis connection is not healthy') 5 | export const ERROR_ALREADY_STARTED = new NotPermittedError('redis was already started') 6 | 7 | export const REDIS_TYPE_CLUSTER = 'redisCluster' 8 | export const REDIS_TYPE_SENTINEL = 'redisSentinel' 9 | -------------------------------------------------------------------------------- /packages/plugin-hapi/src/types/plugin.ts: -------------------------------------------------------------------------------- 1 | import { ServerOptions, Plugin } from '@hapi/hapi' 2 | 3 | export type HapiPluginConfig = { 4 | attachSocketio: boolean 5 | plugins: HapiPluginPluginsConfig 6 | server: ServerOptions 7 | views?: any 8 | } 9 | 10 | export type HapiPluginPluginsConfig = { 11 | list: HapiPluginPlugin[] 12 | options?: any 13 | } 14 | 15 | export interface HapiPluginPlugin { 16 | plugin: string | Plugin 17 | options?: any 18 | once?: boolean 19 | } 20 | -------------------------------------------------------------------------------- /packages/utils/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | // @ts-expect-error TS1479 - works on node 22.12+ 3 | import { readPackageUpSync } from 'read-package-up' 4 | 5 | function getVersion(): string { 6 | const pkgUp = readPackageUpSync() 7 | const version = pkgUp && pkgUp.packageJson && pkgUp.packageJson.version || '' 8 | 9 | assert(version, 'unable to find package.json or .version does not exist') 10 | 11 | return version 12 | } 13 | 14 | export { getVersion } 15 | -------------------------------------------------------------------------------- /packages/plugin-dlock/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | services: [ 7 | 'redisSentinel', 8 | 'redisCluster', 9 | ], 10 | auto_compose: true, 11 | root: '/src/node_modules/.bin', 12 | test_framework: "tsx --test", 13 | tests: "__tests__/**/*.spec.ts", 14 | extras: { 15 | tester: { 16 | working_dir: `/src/packages/${dir}`, 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/esm/simple-js.mjs: -------------------------------------------------------------------------------- 1 | import { NotPermittedError } from 'common-errors' 2 | 3 | export async function handler(request) { 4 | return { 5 | response: 'success', 6 | token: request.params.token, 7 | user: request.auth?.credentials, 8 | } 9 | } 10 | 11 | export const auth = 'token' 12 | export const allowed = async (request) => { 13 | if (request.params.isAdmin !== true) { 14 | throw new NotPermittedError('You are not admin') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/maintenance/http-amqp.ts: -------------------------------------------------------------------------------- 1 | import { Microfleet } from '@microfleet/core' 2 | import { ActionTransport } from '@microfleet/plugin-router' 3 | 4 | export default async function handler(this: Microfleet): Promise { 5 | await this.amqp.publishAndWait('maintenance.amqp', { 6 | some: 'data', 7 | }) 8 | 9 | return { success: true } 10 | } 11 | 12 | handler.schema = false 13 | handler.transports = [ActionTransport.http] 14 | handler.readonly = true 15 | -------------------------------------------------------------------------------- /packages/plugin-consul/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.build.json" 7 | }], 8 | "compilerOptions": { 9 | "rootDir": "./src", 10 | "outDir": "./lib" 11 | }, 12 | "include": [ 13 | "./src/**/*.ts" 14 | ], 15 | "exclude": [ 16 | "**/node_modules/**", 17 | "lib", 18 | "dist", 19 | "**.test.ts", 20 | "**.spec.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/plugin-couchdb/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.build.json" 7 | }], 8 | "compilerOptions": { 9 | "rootDir": "./src", 10 | "outDir": "./lib" 11 | }, 12 | "include": [ 13 | "./src/**/*.ts" 14 | ], 15 | "exclude": [ 16 | "**/node_modules/**", 17 | "lib", 18 | "dist", 19 | "**.test.ts", 20 | "**.spec.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/plugin-hapi/src/plugins/state.ts: -------------------------------------------------------------------------------- 1 | import { Request, Server } from '@hapi/hapi' 2 | 3 | export const name = 'mservice-state' 4 | export const version = '1.0.0' 5 | export const once = true 6 | export function register(server: Server): void { 7 | (server as any)._core.root.decorate( 8 | 'request', 'setState', 9 | function setState(this: Request, cookieName: string, value: any, stateOptions: any) { 10 | return (this as any)._setState(cookieName, value, stateOptions) 11 | } 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /packages/plugin-knex/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.build.json" 7 | }], 8 | "compilerOptions": { 9 | "rootDir": "./src", 10 | "outDir": "./lib" 11 | }, 12 | "include": [ 13 | "./src/**/*.ts" 14 | ], 15 | "exclude": [ 16 | "**/node_modules/**", 17 | "lib", 18 | "dist", 19 | "**.test.ts", 20 | "**.spec.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/plugin-router-amqp/src/types/plugin.ts: -------------------------------------------------------------------------------- 1 | export type RouterAMQPPluginRetryConfig = { 2 | enabled: boolean; 3 | min: number; 4 | max: number; 5 | maxRetries: number; 6 | factor: number; 7 | queue: string; 8 | predicate: (err: T, actionName: string) => boolean; 9 | } 10 | 11 | export type RouterAMQPPluginConfig = { 12 | prefix: string; 13 | autoDeserialize: boolean; 14 | retry: RouterAMQPPluginRetryConfig; 15 | multiAckEvery: number; 16 | multiAckAfter: number; 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/validate-response-without-schema.ts: -------------------------------------------------------------------------------- 1 | import { ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default async function withoutResponseSchema(request: ServiceRequest): Promise { 4 | if (request.params.success) { 5 | return { 6 | validResponse: true, 7 | } 8 | } 9 | 10 | return { 11 | validResponse: false, 12 | withAdditionalProperty: true, 13 | } 14 | } 15 | 16 | withoutResponseSchema.schema = 'validate-response' 17 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "typeRoots": [ 5 | "../../node_modules/@types", 6 | "./node_modules/@types", 7 | "./src/types" 8 | ] 9 | }, 10 | "references": [{ 11 | "path": "../core/tsconfig.json" 12 | }, { 13 | "path": "../core-types/tsconfig.json" 14 | }, { 15 | "path": "../plugin-redis-core/tsconfig.json" 16 | }, { 17 | "path": "../plugin-validator/tsconfig.json" 18 | }] 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/schemas/core.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "microfleet.core", 3 | "type": "object", 4 | "additionalProperties": true, 5 | "required": ["name"], 6 | "properties": { 7 | "name": { 8 | "type": "string", 9 | "pattern": "^[a-zA-Z0-9_\\-]{3,32}$", 10 | "description": "service name" 11 | }, 12 | "maintenanceMode": { 13 | "type": "boolean", 14 | "default": false, 15 | "description": "display 418 status message for actions not marked as readonly" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/plugin-redis-core/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core-types/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.build.json" 7 | }], 8 | "compilerOptions": { 9 | "rootDir": "./src", 10 | "outDir": "./lib" 11 | }, 12 | "include": [ 13 | "./src/**/*.ts" 14 | ], 15 | "exclude": [ 16 | "**/node_modules/**", 17 | "lib", 18 | "dist", 19 | "**.test.ts", 20 | "**.spec.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/plugin-knex/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/**/*.spec.ts", 10 | services: ['postgres'], 11 | extras: { 12 | postgres: { 13 | image: 'postgres:17-alpine', 14 | }, 15 | tester: { 16 | working_dir: `/src/packages/${dir}`, 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/plugin-logger/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core-types/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-validator/tsconfig.build.json" 7 | }], 8 | "compilerOptions": { 9 | "rootDir": "./src", 10 | "outDir": "./lib" 11 | }, 12 | "include": [ 13 | "./src/**/*.ts" 14 | ], 15 | "exclude": [ 16 | "**/node_modules/**", 17 | "lib", 18 | "dist", 19 | "**/**.test.ts", 20 | "**/**.spec.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/plugin-hapi/README.md: -------------------------------------------------------------------------------- 1 | # @microfleet/plugin-hapi 2 | 3 | ## Usage 4 | 5 | ```js 6 | const service = new Microfleet({ 7 | name: 'http-server', 8 | plugins: [ 9 | 'validator', // essensial plugin 10 | 'logger', // essensial plugin 11 | 'hapi', 12 | ], 13 | hapi: { 14 | // server: Hapi server options 15 | // plugins: list of plugins 16 | // views: list of views (e.g. html templates) 17 | } 18 | }) 19 | 20 | await service.connect() 21 | 22 | // service.hapi -- instance of Hapi server 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/plugin-aws-elasticsearch/src/utils/createAwsElasticsearchConnector.ts: -------------------------------------------------------------------------------- 1 | import { Config } from 'aws-sdk' 2 | import { GetAmazonConnection } from './AmazonConnection' 3 | import { GetAmazonTransport } from './AmazonTransport' 4 | 5 | export const createAwsElasticsearchConnector = (awsConfig: Config): { 6 | Connection: ReturnType, 7 | Transport: ReturnType 8 | } => ({ 9 | Connection: GetAmazonConnection(awsConfig), 10 | Transport: GetAmazonTransport(awsConfig) 11 | }) 12 | -------------------------------------------------------------------------------- /benchmarks/microfleet-next/server.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const { Microfleet } = require('../../packages/core') 3 | 4 | const server = new Microfleet({ 5 | name: 'tester', 6 | plugins: [ 7 | 'logger', 8 | 'validator', 9 | 'hapi', 10 | 'router', 11 | 'router-hapi', 12 | ], 13 | router: { 14 | routes: { 15 | directory: resolve(__dirname, './actions'), 16 | }, 17 | }, 18 | hapi: { 19 | server: { 20 | port: 3000, 21 | }, 22 | }, 23 | }) 24 | 25 | server.connect() 26 | -------------------------------------------------------------------------------- /packages/plugin-aws-elasticsearch/schemas/aws-elasticsearch.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "awsElasticsearch", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "required": [ 6 | "accessKeyId", 7 | "secretAccessKey", 8 | "node" 9 | ], 10 | "properties": { 11 | "accessKeyId": { 12 | "type": "string" 13 | }, 14 | "secretAccessKey": { 15 | "type": "string" 16 | }, 17 | "region": { 18 | "type": "string" 19 | }, 20 | "node": { 21 | "type": "string" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/__tests__/artifacts/actions/validation.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport } from '@microfleet/plugin-router' 2 | import { ValidationError } from 'common-errors' 3 | 4 | export default function validationError(): any { 5 | const err = new ValidationError('sample') 6 | err.addErrors([ 7 | new ValidationError('first', 'E_FIRST'), 8 | new ValidationError('first', 'E_SECOND') 9 | ]) 10 | 11 | throw err 12 | } 13 | 14 | validationError.schema = false 15 | validationError.transports = [ActionTransport.http] 16 | -------------------------------------------------------------------------------- /packages/core/docs/recipes.md: -------------------------------------------------------------------------------- 1 | # Recipes 2 | 3 | - [Quick start guide](recipes/quick-start.md) 4 | - [Configuration](recipes/configuration.md) 5 | - [Recommended structure](recipes/structure.md) 6 | - [Authentication](recipes/authentication.md) 7 | - [Connect to Redis](recipes/redis.md) 8 | - [Validation](recipes/validation.md) 9 | - [Testing](recipes/testing.md) 10 | - [Maintenance mode](recipes/maintenance.md) 11 | - [AMQP Plugin](recipes/amqp.md) 12 | - [Connect to PgSQL using KNEX](recipes/knex.md) 13 | - [Knex Migration](recipes/knex-migration.md) 14 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/validate-response.ts: -------------------------------------------------------------------------------- 1 | import { ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default async function responseValidate(request: ServiceRequest): Promise { 4 | if (request.params.success) { 5 | return { 6 | validResponse: true, 7 | } 8 | } 9 | 10 | return { 11 | validResponse: false, 12 | withAdditionalProperty: true, 13 | } 14 | } 15 | 16 | responseValidate.responseSchema = 'response.validate-response' 17 | responseValidate.schema = 'validate-response' 18 | -------------------------------------------------------------------------------- /packages/plugin-logger/__tests__/sentry.beforeSend.js: -------------------------------------------------------------------------------- 1 | const FILE_MISSING_ERROR_MESSAGE = 'could not find associated data' 2 | const FILE_ALREADY_PROCESSED_MESSAGE = '412: upload was already processed' 3 | 4 | module.exports = { 5 | beforeSend(event, hint) { 6 | const err = hint.originalException 7 | if ( 8 | err && 9 | err.message && 10 | (err.message === FILE_MISSING_ERROR_MESSAGE || 11 | err.message === FILE_ALREADY_PROCESSED_MESSAGE) 12 | ) { 13 | return null 14 | } 15 | 16 | return event 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /benchmarks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@benchmarks/root", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "compile": "cd .. && yarn compile", 7 | "doctor": "NODE_ENV=production clinic doctor --autocannon [ -m GET 'http://localhost:3000/hello' ] -- node", 8 | "flame": "NODE_ENV=production clinic flame --on-port 'autocannon localhost:$PORT/hello' -- node" 9 | }, 10 | "dependencies": { 11 | "autocannon": "^7.5.0", 12 | "clinic": "^10.0.0" 13 | }, 14 | "devDependencies": { 15 | "lerna": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ci/aggregate-conventional-changelog.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import ConventionalChangelog from '@release-it/conventional-changelog' 3 | 4 | class AggregateConventionalChangelog extends ConventionalChangelog { 5 | async beforeRelease() { 6 | const { version, latestVersion } = this.config.getContext() 7 | if (version === latestVersion) { 8 | this.debug('skipping writing changelog') 9 | return 10 | } 11 | 12 | return super.beforeRelease() 13 | } 14 | } 15 | 16 | export default AggregateConventionalChangelog 17 | -------------------------------------------------------------------------------- /packages/plugin-amqp/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-router/tsconfig.build.json" 9 | }], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./lib" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [ 18 | "**/node_modules/**", 19 | "lib", 20 | "dist", 21 | "**.test.ts", 22 | "**.spec.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/plugin-dlock/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-router/tsconfig.build.json" 9 | }], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./lib" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [ 18 | "**/node_modules/**", 19 | "lib", 20 | "dist", 21 | "**.test.ts", 22 | "**.spec.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/plugin-aws-elasticsearch/README.md: -------------------------------------------------------------------------------- 1 | # Microfleet AWS-Elasticsearch Plugin 2 | 3 | Adds AWS Elastic service support to microfleet. 4 | 5 | ## Install 6 | 7 | `yarn add @microfleet/plugin-aws-elasticsearch` 8 | 9 | ## Configuration 10 | 11 | To make use of the plugin, adjust microfleet configuration in the following way: 12 | 13 | ```ts 14 | exports.plugins = [ 15 | ..., 16 | 'aws-elasticsearch' 17 | ... 18 | ] 19 | ``` 20 | 21 | ```ts 22 | exports.awsElasticsearch = { 23 | 'awsAccessKey', 24 | 'awsSecretKey', 25 | 'node', 26 | 'region' 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/src/utils/action-name.ts: -------------------------------------------------------------------------------- 1 | // @todo move to plugin-http src/utils 2 | export function fromPathToName(path: string, prefix: string): string { 3 | const { length } = prefix 4 | const start = length ? length + 2 : 1 5 | const end = path[path.length - 1] === '/' ? -1 : undefined 6 | 7 | return path.slice(start, end).replace(/\//g, '.') 8 | } 9 | 10 | export function fromNameToPath(name: string, prefix: string): string { 11 | const actionName = prefix.length ? `/${prefix}/${name}` : `/${name}` 12 | 13 | return actionName.replace(/\./g, '/') 14 | } 15 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-hapi/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-router/tsconfig.build.json" 9 | }], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./lib" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [ 18 | "**/node_modules/**", 19 | "lib", 20 | "dist", 21 | "**.test.ts", 22 | "**.spec.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/plugin-socketio/schemas/socketio.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "socketio", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "description": "Socket.io plugin config", 6 | "properties": { 7 | "adapter": { 8 | "type": "object", 9 | "properties": { 10 | "name": { 11 | "type": "string" 12 | }, 13 | "options": { 14 | "type": "object", 15 | "default": {} 16 | } 17 | } 18 | }, 19 | "socketioOptions": { 20 | "type": "object", 21 | "default": {} 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/__tests__/artifacts/actions/signed.ts: -------------------------------------------------------------------------------- 1 | import { ServiceRequest, ServiceAction, ActionTransport } from '@microfleet/plugin-router' 2 | 3 | const action: Partial = async function action(request: ServiceRequest): Promise { 4 | return { 5 | response: 'success', 6 | params: request.params, 7 | credentials: await request.signature?.getCredentials() 8 | } 9 | } 10 | 11 | action.transports = [ 12 | ActionTransport.internal, ActionTransport.http 13 | ] 14 | 15 | action.auth = 'token-or-signed' 16 | 17 | export default action 18 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/README.md: -------------------------------------------------------------------------------- 1 | # Microfleet plugin-router-socketio 2 | 3 | Attach socket.io transport to router. 4 | 5 | ## Typical usage 6 | 7 | ```js 8 | const service = new Microfleet({ 9 | name: 'tester', 10 | plugins: [ 11 | 'validator', // essential plugin 12 | 'logger', // essential plugin 13 | 'socketio', // init socket.io 14 | 'hapi', // init http and attach socket.io 15 | 'router', // init router 16 | 'router-socketio' // attach socket.io transport to router 17 | ], 18 | hapi: { 19 | attachSocketio: true, 20 | }, 21 | }) 22 | ``` 23 | -------------------------------------------------------------------------------- /packages/plugin-elasticsearch/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core-types/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-validator/tsconfig.build.json" 9 | }], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./lib" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [ 18 | "**/node_modules/**", 19 | "lib", 20 | "dist", 21 | "**.test.ts", 22 | "**.spec.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/plugin-router/src/extensions/audit/timing.ts: -------------------------------------------------------------------------------- 1 | import { Lifecycle, LifecycleExtension } from '../../lifecycle/index' 2 | import type { ServiceRequest } from '../../types/router' 3 | 4 | declare module '../../types/router' { 5 | interface ServiceRequest { 6 | requestStarted?: [number, number]; 7 | requestEnded?: [number, number]; 8 | } 9 | } 10 | 11 | export const initTimingExtension: LifecycleExtension = { 12 | point: Lifecycle.hooks.preRequest, 13 | async handler(request: ServiceRequest): Promise { 14 | request.requestStarted = process.hrtime() 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /packages/plugin-aws-elasticsearch/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core-types/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-validator/tsconfig.build.json" 9 | }], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./lib" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [ 18 | "**/node_modules/**", 19 | "lib", 20 | "dist", 21 | "**.test.ts", 22 | "**.spec.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/esm/simple-ts.mts: -------------------------------------------------------------------------------- 1 | import { NotPermittedError } from 'common-errors' 2 | import { ServiceRequest } from '@microfleet/plugin-router' 3 | 4 | export async function handler(request: ServiceRequest) { 5 | return { 6 | response: 'success', 7 | token: request.params.token, 8 | user: request.auth?.credentials, 9 | } 10 | } 11 | 12 | export const auth = 'token' 13 | export const allowed = async (request: ServiceRequest) => { 14 | if (request.params.isAdmin !== true) { 15 | throw new NotPermittedError('You are not admin') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/validate-response-skip.ts: -------------------------------------------------------------------------------- 1 | import { ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default async function skipResponseValidate(request: ServiceRequest): Promise { 4 | if (request.params.success) { 5 | return { 6 | validResponse: true, 7 | } 8 | } 9 | 10 | return { 11 | validResponse: false, 12 | withAdditionalProperty: true, 13 | } 14 | } 15 | 16 | skipResponseValidate.validateResponse = false 17 | skipResponseValidate.responseSchema = 'response.validate-response' 18 | skipResponseValidate.schema = 'validate-response' 19 | -------------------------------------------------------------------------------- /packages/core/docs/reference/core.md: -------------------------------------------------------------------------------- 1 | # Core config 2 | 3 | | Name | Type | Required | Description | 4 | |--------|------|----------|-------------| 5 | | `name` | `string` | `true` | Service name | 6 | | `maintenanceMode` | `bool` | `false` | Maintenance mode on/off | 7 | 8 | ## Name 9 | Service name setting is used to uniquely identify the service. 10 | 11 | ## Maintenance mode 12 | Toggles service maintenance mode. Read more on the [maintenance mode recipe](../recipes/maintenance.md) 13 | 14 | ## Configuration example 15 | 16 | Set up it like this: 17 | ```js 18 | const config = { 19 | name: 'my-service-name', 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /packages/plugin-kafka/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../utils/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-kafka-types/tsconfig.json" 9 | }, { 10 | "path": "../plugin-logger/tsconfig.build.json" 11 | }], 12 | "compilerOptions": { 13 | "rootDir": "./src", 14 | "outDir": "./lib" 15 | }, 16 | "include": [ 17 | "./src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "**/node_modules/**", 21 | "lib", 22 | "dist", 23 | "**.test.ts", 24 | "**.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/plugin-router/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../core-types/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-logger/tsconfig.build.json" 9 | }, { 10 | "path": "../utils/tsconfig.build.json" 11 | }], 12 | "compilerOptions": { 13 | "rootDir": "./src", 14 | "outDir": "./lib" 15 | }, 16 | "include": [ 17 | "./src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "**/node_modules/**", 21 | "lib", 22 | "dist", 23 | "**.test.ts", 24 | "**.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/docs/reference.md: -------------------------------------------------------------------------------- 1 | # Configuration reference 2 | 3 | - [Core](reference/core.md) 4 | - [Plugins](reference/plugins.md) 5 | - [HTTP](reference/plugins/http.md) 6 | - [AMQP](reference/plugins/amqp.md) 7 | - [Logger](reference/plugins/logger.md) 8 | - [Router](reference/plugins/router.md) 9 | - [Validator](reference/plugins/validator.md) 10 | - Redis Family plugins 11 | - [Redis Sentinel](reference/plugins/redis/sentinel.md) 12 | - [Redis Cluster](reference/plugins/redis/cluster.md) 13 | - [Knex] 14 | - [*SQL Database access](reference/plugins/knex.md) 15 | - [*Sql Database Migrations](reference/plugins/knex-migration.md) 16 | -------------------------------------------------------------------------------- /packages/plugin-casl/__tests__/artifacts/actions/protected.ts: -------------------------------------------------------------------------------- 1 | import { ServiceRequest, ServiceAction, ActionTransport } from '@microfleet/plugin-router' 2 | 3 | const actionWithRbac: Partial = async function actionWithRbac(request: ServiceRequest): Promise { 4 | return { 5 | response: 'success', 6 | token: request.params.token, 7 | user: request.auth, 8 | } 9 | } 10 | 11 | actionWithRbac.transports = [ 12 | ActionTransport.internal 13 | ] 14 | actionWithRbac.auth = 'token' 15 | actionWithRbac.rbac = { 16 | action: 'read', 17 | subject: 'my-subject' 18 | } 19 | 20 | export default actionWithRbac 21 | -------------------------------------------------------------------------------- /packages/plugin-casl/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | },{ 6 | "path": "../core-types/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-validator/tsconfig.build.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.build.json" 11 | }], 12 | "compilerOptions": { 13 | "rootDir": "./src", 14 | "outDir": "./lib" 15 | }, 16 | "include": [ 17 | "./src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "**/node_modules/**", 21 | "lib", 22 | "dist", 23 | "**.test.ts", 24 | "**.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/plugin-knex/schemas/knex.pg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "knex.pg", 3 | "type": "object", 4 | "required": [ 5 | "client", 6 | "connection" 7 | ], 8 | "properties": { 9 | "client": { 10 | "type": "string", 11 | "const": "pg" 12 | }, 13 | "connection": { 14 | "oneOf": [{ 15 | "type": "string" 16 | }, { 17 | "type": "object" 18 | }] 19 | }, 20 | "searchPath": { 21 | "oneOf": [{ 22 | "type": "string" 23 | }, { 24 | "type": "array", 25 | "items": { 26 | "type": "string" 27 | } 28 | }] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/simple.ts: -------------------------------------------------------------------------------- 1 | import { NotPermittedError } from 'common-errors' 2 | import { ServiceRequest } from '@microfleet/plugin-router' 3 | 4 | export default async function simpleAction(request: ServiceRequest): Promise { 5 | return { 6 | response: 'success', 7 | token: request.params.token, 8 | user: request.auth?.credentials as unknown, 9 | } 10 | } 11 | 12 | simpleAction.auth = 'token' 13 | simpleAction.allowed = async (request: ServiceRequest): Promise => { 14 | if (request.params.isAdmin !== true) { 15 | throw new NotPermittedError('You are not admin') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin-prometheus/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | },{ 6 | "path": "../core-types/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-validator/tsconfig.build.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.build.json" 11 | }], 12 | "compilerOptions": { 13 | "rootDir": "./src", 14 | "outDir": "./lib" 15 | }, 16 | "include": [ 17 | "./src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "**/node_modules/**", 21 | "lib", 22 | "dist", 23 | "**.test.ts", 24 | "**.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/plugin-router-amqp/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-amqp/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-logger/tsconfig.build.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.build.json" 11 | }], 12 | "compilerOptions": { 13 | "rootDir": "./src", 14 | "outDir": "./lib" 15 | }, 16 | "include": [ 17 | "./src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "**/node_modules/**", 21 | "lib", 22 | "dist", 23 | "**.test.ts", 24 | "**.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/cjs/simple.cts: -------------------------------------------------------------------------------- 1 | import { NotPermittedError } from 'common-errors' 2 | import { ServiceRequest } from '@microfleet/plugin-router' 3 | 4 | export default async function simpleAction(request: ServiceRequest): Promise { 5 | return { 6 | response: 'success', 7 | token: request.params.token, 8 | user: request.auth?.credentials as unknown, 9 | } 10 | } 11 | 12 | simpleAction.auth = 'token' 13 | simpleAction.allowed = async (request: ServiceRequest): Promise => { 14 | if (request.params.isAdmin !== true) { 15 | throw new NotPermittedError('You are not admin') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../core-types/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-redis-core/tsconfig.build.json" 9 | }, { 10 | "path": "../plugin-validator/tsconfig.build.json" 11 | }], 12 | "compilerOptions": { 13 | "rootDir": "./src", 14 | "outDir": "./lib" 15 | }, 16 | "include": [ 17 | "./src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "**/node_modules/**", 21 | "lib", 22 | "dist", 23 | "**.test.ts", 24 | "**.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../core-types/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-socketio/tsconfig.build.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.build.json" 11 | }], 12 | "compilerOptions": { 13 | "rootDir": "./src", 14 | "outDir": "./lib" 15 | }, 16 | "include": [ 17 | "./src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "**/node_modules/**", 21 | "lib", 22 | "dist", 23 | "**.test.ts", 24 | "**.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib" 6 | }, 7 | "include": [ 8 | "./src/*.ts", 9 | "./src/**/*.ts" 10 | ], 11 | "exclude": [ 12 | "**/node_modules/**", 13 | "lib", 14 | "dist", 15 | "**.test.ts", 16 | "**.spec.ts" 17 | ], 18 | "references": [{ 19 | "path": "../utils/tsconfig.build.json" 20 | }, { 21 | "path": "../core-types/tsconfig.build.json" 22 | }, { 23 | "path": "../plugin-validator/tsconfig.build.json" 24 | }, { 25 | "path": "../plugin-logger/tsconfig.build.json" 26 | }] 27 | } 28 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../core-types/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-redis-core/tsconfig.build.json" 9 | }, { 10 | "path": "../plugin-validator/tsconfig.build.json" 11 | }], 12 | "compilerOptions": { 13 | "rootDir": "./src", 14 | "outDir": "./lib" 15 | }, 16 | "include": [ 17 | "./src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "**/node_modules/**", 21 | "lib", 22 | "dist", 23 | "**.test.ts", 24 | "**.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | },{ 6 | "path": "../core-types/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-validator/tsconfig.build.json" 9 | }, { 10 | "path": "../plugin-hapi/tsconfig.build.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "rootDir": "./src", 15 | "outDir": "./lib" 16 | }, 17 | "include": [ 18 | "./src/**/*.ts" 19 | ], 20 | "exclude": [ 21 | "**/node_modules/**", 22 | "lib", 23 | "dist", 24 | "**.test.ts", 25 | "**.spec.ts" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /packages/plugin-casl/__tests__/artifacts/actions/scoped/user.ts: -------------------------------------------------------------------------------- 1 | import { ServiceRequest, ServiceAction, ActionTransport } from '@microfleet/plugin-router' 2 | 3 | const actionWithRbac: Partial = async function actionWithRbac(request: ServiceRequest): Promise { 4 | return { 5 | response: 'success', 6 | scope: 'app:user', 7 | token: request.params.token, 8 | user: request.auth, 9 | } 10 | } 11 | 12 | actionWithRbac.transports = [ 13 | ActionTransport.internal 14 | ] 15 | actionWithRbac.auth = 'token' 16 | actionWithRbac.rbac = { 17 | action: 'read', 18 | subject: 'app:user' 19 | } 20 | 21 | export default actionWithRbac 22 | -------------------------------------------------------------------------------- /packages/plugin-kafka/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { userInfo } = require('node:os') 2 | const { basename } = require('node:path') 3 | const dir = basename(__dirname) 4 | 5 | const { uid } = userInfo() 6 | 7 | module.exports = { 8 | ...require('../../.mdeprc.cjs'), 9 | tester_flavour: "rdkafka-tester", 10 | auto_compose: true, 11 | root: '/src/node_modules/.bin', 12 | test_framework: "tsx --test", 13 | tests: "__tests__/**/*.spec.ts", 14 | euser: uid, 15 | tuser: uid, 16 | arbitrary_exec: [ 17 | 'npm rebuild @makeomatic/node-rdkafka', 18 | ], 19 | extras: { 20 | tester: { 21 | working_dir: `/src/packages/${dir}`, 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/plugin-kafka/__tests__/docker-configs/toxy.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "kafka-proxy", 4 | "listen": "0.0.0.0:29092", 5 | "upstream": "kafka:29092", 6 | "enabled": true 7 | }, 8 | { 9 | "name": "kafka-proxy-2s", 10 | "listen": "0.0.0.0:39092", 11 | "upstream": "kafka:39092", 12 | "enabled": true 13 | }, 14 | { 15 | "name": "kafka-proxy-small-timeout", 16 | "listen": "0.0.0.0:49092", 17 | "upstream": "kafka:49092", 18 | "enabled": true 19 | }, 20 | { 21 | "name": "kafka-proxy-always-on", 22 | "listen": "0.0.0.0:9092", 23 | "upstream": "kafka:9092", 24 | "enabled": true 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/src/router-extension.ts: -------------------------------------------------------------------------------- 1 | import { Microfleet } from '@microfleet/core-types' 2 | import { ActionTransport, Lifecycle, LifecycleExtension, ServiceRequest } from '@microfleet/plugin-router' 3 | 4 | export const extendServiceRequest: LifecycleExtension = { 5 | point: Lifecycle.hooks.preRequest, 6 | async handler(this: Microfleet, request: ServiceRequest): Promise { 7 | if (request.transport === ActionTransport.http) { 8 | const { transportRequest } = request 9 | if (transportRequest?.plugins?.signature) { 10 | request.signature = transportRequest.plugins.signature 11 | } 12 | } 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /packages/plugin-casl/__tests__/artifacts/actions/scoped/profile.ts: -------------------------------------------------------------------------------- 1 | import { ServiceRequest, ServiceAction, ActionTransport } from '@microfleet/plugin-router' 2 | 3 | const actionWithRbac: Partial = async function actionWithRbac(request: ServiceRequest): Promise { 4 | return { 5 | response: 'success', 6 | scope: 'app:profile', 7 | token: request.params.token, 8 | user: request.auth, 9 | } 10 | } 11 | 12 | actionWithRbac.transports = [ 13 | ActionTransport.internal 14 | ] 15 | actionWithRbac.auth = 'token' 16 | actionWithRbac.rbac = { 17 | action: 'read', 18 | subject: 'app:profile' 19 | } 20 | 21 | export default actionWithRbac 22 | -------------------------------------------------------------------------------- /packages/plugin-hapi/src/plugins/views.ts: -------------------------------------------------------------------------------- 1 | import type { Request, Server } from '@hapi/hapi' 2 | import type { ServerViewsConfiguration } from '@hapi/vision' 3 | 4 | export const name = 'view-wrapper' 5 | export const dependencies = '@hapi/vision' 6 | export const version = '1.0.0' 7 | export const once = true 8 | export function register(server: Server, options: ServerViewsConfiguration): void { 9 | (server as any)._core.root.views(options); 10 | (server as any)._core.root.decorate('request', 'sendView', async function sendView(this: Request, ...args: any[]) { 11 | const page = await (this as any).render(...args) 12 | return this.generateResponse(page) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/src/plugins/restify.ts: -------------------------------------------------------------------------------- 1 | import type * as _ from '../types' 2 | import type { Request } from 'restify' 3 | 4 | import { SignedRequest, Config, CredentialsStore } from '../signed-request' 5 | 6 | export const RestifySignedRequestPlugin = (store: CredentialsStore, config: Config) => { 7 | return async function verifySignature(req: Request) { 8 | 9 | if (!SignedRequest.isSignedRequest(req.headers)) { 10 | return 11 | } 12 | 13 | const signature = req.signature = new SignedRequest(config, store) 14 | await signature.initialize(req) 15 | 16 | signature.appendPayload(req.rawBody) 17 | signature.verifyPayload() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/docs/reference/plugins/router.md: -------------------------------------------------------------------------------- 1 | ## Router Plugin 2 | 3 | ### Info 4 | | Parameter | Value | 5 | |---------------|-------------| 6 | | Name | `router` | 7 | | Type | essential | 8 | | Priority | 100 | 9 | | Requirements | [Validator](./validator.md) and [Logger](./logger.md) plugins should be enabled | 10 | 11 | ### Configure routes folder 12 | Begin with setting up the directory which should serve provide action handlers: 13 | ```js 14 | // demo-app/src/config/router.js 15 | exports.router = { 16 | routes: { 17 | directory: path.resolve(__dirname, './actions'), 18 | }, 19 | }; 20 | ``` 21 | 22 | ```js 23 | // 24 | ``` 25 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/long-running.ts: -------------------------------------------------------------------------------- 1 | import { delay } from 'bluebird' 2 | import { Microfleet } from '@microfleet/core' 3 | import { ServiceRequest } from '@microfleet/plugin-router' 4 | 5 | export default async function longAction(this: Microfleet, request: ServiceRequest): Promise { 6 | const { params } = request 7 | const delayFromRequest = params.pause || 100 8 | 9 | this.log.debug({ params, msg: request.transportRequest.properties }, 'got params') 10 | 11 | return delay(delayFromRequest).then(() => { 12 | this.log.debug('delayed response', { params }) 13 | 14 | return { 15 | success: true, 16 | params, 17 | } 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /packages/plugin-hapi/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../plugin-logger/tsconfig.build.json" 7 | }, { 8 | "path": "../plugin-socketio/tsconfig.build.json" 9 | }, { 10 | "path": "../plugin-router/tsconfig.build.json" 11 | }, { 12 | "path": "../plugin-validator/tsconfig.build.json" 13 | }], 14 | "compilerOptions": { 15 | "rootDir": "./src", 16 | "outDir": "./lib" 17 | }, 18 | "include": [ 19 | "./src/**/*.ts" 20 | ], 21 | "exclude": [ 22 | "**/node_modules/**", 23 | "lib", 24 | "dist", 25 | "**.test.ts", 26 | "**.spec.ts" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/plugin-router/src/lifecycle/handlers/request.ts: -------------------------------------------------------------------------------- 1 | import { Microfleet } from '@microfleet/core' 2 | import { HttpStatusError, NotFoundError } from 'common-errors' 3 | import { ServiceRequest } from '../../types/router' 4 | 5 | async function requestHandler(this: Microfleet, request: ServiceRequest): Promise { 6 | const { action, route } = request 7 | 8 | if (action === undefined) { 9 | // @todo HttpStatusError (there is a legacy code) 10 | throw new NotFoundError(`route "${route}" not found`) 11 | } 12 | 13 | const { maintenanceMode } = this.config 14 | 15 | if (maintenanceMode && !action.readonly) { 16 | throw new HttpStatusError(418, 'Server Maintenance') 17 | } 18 | } 19 | 20 | export default requestHandler 21 | -------------------------------------------------------------------------------- /ci/aggregate-npm.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import NPM from '../node_modules/release-it/lib/plugin/npm/npm.js' 3 | 4 | class AggregateNPM extends NPM { 5 | static disablePlugin() { 6 | return ['npm'] 7 | } 8 | 9 | getInitialOptions(options, namespace) { 10 | options[namespace] = options.npm 11 | return options[namespace] 12 | } 13 | 14 | async bump(version) { 15 | if (version === this.getContext('latestVersion') || !version) { 16 | this.debug('skipping npm version') 17 | return false 18 | } 19 | 20 | const tag = this.options.tag || (await this.resolveTag(version)) 21 | this.setContext({ version, tag }) 22 | 23 | return false 24 | } 25 | } 26 | 27 | export default AggregateNPM 28 | -------------------------------------------------------------------------------- /packages/plugin-socketio/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "references": [{ 4 | "path": "../core/tsconfig.build.json" 5 | }, { 6 | "path": "../utils/tsconfig.build.json" 7 | }, { 8 | "path": "../core-types/tsconfig.build.json" 9 | }, { 10 | "path": "../plugin-logger/tsconfig.build.json" 11 | }, { 12 | "path": "../plugin-validator/tsconfig.build.json" 13 | }, { 14 | "path": "../plugin-router/tsconfig.build.json" 15 | }], 16 | "compilerOptions": { 17 | "rootDir": "./src", 18 | "outDir": "./lib" 19 | }, 20 | "include": [ 21 | "./src/**/*.ts" 22 | ], 23 | "exclude": [ 24 | "**/node_modules/**", 25 | "lib", 26 | "dist", 27 | "**.test.ts", 28 | "**.spec.ts" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/utils/src/defaults-deep.ts: -------------------------------------------------------------------------------- 1 | import { mergeWith } from 'lodash' 2 | 3 | export function customizer(_objectValue: Record, sourceValue: Record): Record | void { 4 | return Array.isArray(sourceValue) ? sourceValue : undefined 5 | } 6 | 7 | export function defaultsDeep(...sources: Partial[]): T { 8 | const output = Object.create(null) 9 | 10 | for (const source of sources.reverse()) { 11 | mergeWith(output, source, customizer) 12 | } 13 | 14 | return output 15 | } 16 | 17 | export function mergeDeep(...sources: Partial[]): T { 18 | const output = Object.create(null) 19 | 20 | for (const source of sources) { 21 | mergeWith(output, source, customizer) 22 | } 23 | 24 | return output 25 | } 26 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/__tests__/artifacts/actions/hapi-raw-body.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport, ServiceRequest, ServiceAction } from '@microfleet/plugin-router' 2 | 3 | const rawBodyAction: Partial = function rawBodyAction({ transportRequest }: ServiceRequest): any { 4 | if (Buffer.isBuffer(transportRequest.payload)) { 5 | return transportRequest.payload 6 | } 7 | 8 | return new Error('It is not buffer!!!') 9 | } 10 | 11 | rawBodyAction.schema = false 12 | rawBodyAction.transports = [ActionTransport.http] 13 | rawBodyAction.transportOptions = { 14 | hapi: { 15 | options: { 16 | payload: { 17 | output: 'data', 18 | parse: false, 19 | }, 20 | }, 21 | method: ['POST'], 22 | }, 23 | } 24 | 25 | export default rawBodyAction 26 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/src/typings/http-signature/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'http-signature' { 2 | import type { IncomingMessage } from "http" 3 | import { KeyLike } from "crypto" 4 | 5 | export type HttpSignature = { 6 | keyId: string, 7 | algorithm: string, 8 | signStr: string, 9 | } 10 | 11 | export type ParseOptions = { 12 | strict?: boolean, 13 | headers: string[], 14 | clockSkew?: number, 15 | } 16 | 17 | export function parseRequest(req: IncomingMessage, opts: ParseOptions): HttpSignature; 18 | export function verifyHMAC(parsed: HttpSignature, key: KeyLike): boolean; 19 | export function verifySignature(parsed: HttpSignature, key: KeyLike): boolean; 20 | export function sign(req: IncomingMessage, opts: ParseOptions): void; 21 | } 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Don't allow people to merge changes to these generated files, because the result 2 | # may be invalid. You need to run "rush update" again. 3 | pnpm-lock.yaml merge=text 4 | shrinkwrap.yaml merge=binary 5 | npm-shrinkwrap.json merge=binary 6 | yarn.lock merge=binary 7 | 8 | # Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic 9 | # syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor 10 | # may also require a special configuration to allow comments in JSON. 11 | # 12 | # For more information, see this issue: https://github.com/microsoft/rushstack/issues/1088 13 | # 14 | *.json linguist-language=JSON-with-Comments 15 | -------------------------------------------------------------------------------- /packages/core/src/defaults.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default plugins that each service would likely require 3 | * It's advised to revamp this per your project 4 | */ 5 | export const plugins = [ 6 | 'validator', 7 | 'logger', 8 | 'router', 9 | 'hapi', 10 | 'router-hapi', 11 | ] 12 | 13 | /** 14 | * Contains function definitions for service-specific hooks 15 | */ 16 | export const hooks = {} 17 | 18 | /** 19 | * Enables graceful handling of shutdown for supported plugins 20 | */ 21 | export const sigterm = true 22 | 23 | export const prometheus = { 24 | config: { 25 | port: 9102, 26 | path: '/metrics', 27 | }, 28 | } 29 | 30 | /** 31 | * Health check retry options 32 | * https://www.npmjs.com/package/bluebird-retry 33 | */ 34 | export const healthChecks = { 35 | interval: 500, 36 | max_tries: 3, 37 | } 38 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "project": "tsconfig.json", 10 | "sourceType": "module" 11 | }, 12 | "plugins": [ 13 | "@typescript-eslint" 14 | ], 15 | "extends": [ 16 | "eslint:recommended", 17 | "plugin:@typescript-eslint/eslint-recommended", 18 | "plugin:@typescript-eslint/recommended" 19 | ], 20 | "rules": { 21 | "semi": ["error", "never"], 22 | "no-console": ["error"], 23 | "@typescript-eslint/no-explicit-any": "off", 24 | "@typescript-eslint/explicit-function-return-type": "off", 25 | "@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_+$" }], 26 | "@typescript-eslint/no-unsafe-declaration-merging": "off" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/docs/reference/plugins/knex-migration.md: -------------------------------------------------------------------------------- 1 | # Knex plugin migrations 2 | 3 | `@microfleet/plugin-knex` database migrations are using the `knex` migrations. 4 | Migration interface of the `@microfleet/plugin-knex` plugin, should be registered as `Connector` with type `ConnectorType.migration` inside `@microfleet/core` service. 5 | 6 | ## Dependencies 7 | 8 | NPM Packages: 9 | 10 | * `@microfleet/plugin-knex` 11 | 12 | ## Methods 13 | 14 | | Method | Description | 15 | | ---------------------------------- | --------------------| 16 | | `service.migrate('knex', [config]: Object)` | Applies migrations | 17 | 18 | The `config` parameter of the `migrate` method accepts `Object` with migration configuration. 19 | Please read [Migration API](http://knexjs.org/#Migrations-API) for the list of the available options. 20 | -------------------------------------------------------------------------------- /packages/plugin-casl/src/allowed-extension.ts: -------------------------------------------------------------------------------- 1 | import { NotPermittedError } from 'common-errors' 2 | 3 | import type { Microfleet } from '@microfleet/core-types' 4 | import { Lifecycle, type LifecycleExtension, type ServiceRequest } from '@microfleet/plugin-router' 5 | 6 | export const rbacExtension: LifecycleExtension = { 7 | point: Lifecycle.hooks.preAllowed, 8 | async handler(this: Microfleet, request: ServiceRequest): Promise { 9 | const { action, auth } = request 10 | 11 | if (action.rbac && auth?.scopes) { 12 | const { subject, action: subjectAction } = action.rbac 13 | const ability = this.rbac.createAbility(auth?.scopes) 14 | 15 | if (!this.rbac.can(ability, subjectAction, subject)) { 16 | throw new NotPermittedError(`cannot execute action '${subjectAction}' on '${subject}'`) 17 | } 18 | } 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /packages/plugin-prometheus/schemas/prometheus.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "prometheus", 3 | "type": "object", 4 | "required": ["config"], 5 | "properties": { 6 | "config": { 7 | "type": "object", 8 | "required": ["port", "path"], 9 | "properties": { 10 | "port": { 11 | "type": "number", 12 | "description": "prometheus metrics port", 13 | "default": 9102 14 | }, 15 | "path": { 16 | "type": "string", 17 | "description": "prometheus metrics path", 18 | "default": "/metrics", 19 | "minLength": 1 20 | }, 21 | "durationBuckets": { 22 | "type": "array", 23 | "description": "default buckets for microfleet request duration histogram", 24 | "default": [ 50, 100, 250, 500, 1000, 2000 ] 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/plugin-router/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Extensions from './extensions/index' // @TODO remove import? 2 | import RequestCountTracker from './tracker' 3 | import './lifecycle/handlers/allowed' 4 | 5 | export type { LifecycleExtension } from './lifecycle/index' 6 | export { Lifecycle } from './lifecycle/index' 7 | export type { ServiceActionAuthConfig } from './lifecycle/handlers/auth' 8 | 9 | export { Extensions, RequestCountTracker } 10 | 11 | export * from './plugin' 12 | export type * from './types/plugin' 13 | 14 | export { Router, ActionTransport } from './router' 15 | export type { 16 | ServiceRequest, 17 | ServiceAction, 18 | ServiceMiddleware, 19 | ServiceActionHandler, 20 | ServiceActionAuthGetName, 21 | RequestGenericInterface, 22 | ReplyGenericInterface 23 | } from './types/router' 24 | 25 | export type { AuthInfo } from './lifecycle/handlers/auth' 26 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "target": "ESNext", 7 | "declaration": true, 8 | "declarationMap": true, 9 | "alwaysStrict": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "allowSyntheticDefaultImports": true, 16 | "esModuleInterop": true, 17 | "resolveJsonModule": true, 18 | "preserveConstEnums": true, 19 | "composite": true, 20 | "skipLibCheck": true 21 | }, 22 | "exclude": [ 23 | "node_modules", 24 | "packages/*/node_modules", 25 | "**/node_modules/**", 26 | "lib/**", 27 | "packages/*/lib/**", 28 | "dist", 29 | "packages/*/dist/**" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /packages/plugin-amqp/__tests__/utils/health-check.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert' 2 | import { Microfleet } from "@microfleet/core" 3 | 4 | export type HealthCheck = { 5 | handler: () => Promise; 6 | } 7 | 8 | const findByName = (pluginName: string) => (i: any) => ( 9 | pluginName === i.name 10 | ) 11 | 12 | /** 13 | * Returns a registered health check by plugin name 14 | * @param service link to Mservice 15 | * @param pluginName name of a plugin to perform look up. 16 | * @returns {Object} a health check module 17 | * @todo test util (there are duplicates) 18 | */ 19 | export function findHealthCheck(service: Microfleet, pluginName: string): HealthCheck { 20 | const check = service 21 | .getHealthChecks() 22 | .find(findByName(pluginName)) 23 | 24 | assert(check) 25 | 26 | return { 27 | ...check, 28 | handler: check.handler.bind(service), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/plugin-aws-elasticsearch/.mdeprc.cjs: -------------------------------------------------------------------------------- 1 | const { basename } = require('node:path') 2 | const dir = basename(__dirname) 3 | 4 | module.exports = { 5 | ...require('../../.mdeprc.cjs'), 6 | auto_compose: true, 7 | root: '/src/node_modules/.bin', 8 | test_framework: "tsx --test", 9 | tests: "__tests__/**/*.spec.ts", 10 | extras: { 11 | tester: { 12 | working_dir: `/src/packages/${dir}`, 13 | environment: {}, 14 | } 15 | } 16 | } 17 | 18 | try { 19 | const filepath = path.join(__dirname, '.env') 20 | if (fs.statSync(filepath).isFile()) { 21 | module.exports.extras.tester.env_file = [filepath] 22 | return 23 | } 24 | } catch (e) { 25 | Object.assign(module.exports.extras.tester.environment, { 26 | AWS_ELASTIC_KEY_ID: "${AWS_ELASTIC_KEY_ID}", 27 | AWS_ELASTIC_ACCESS_KEY: "${AWS_ELASTIC_ACCESS_KEY}", 28 | AWS_ELASTIC_NODE: "${AWS_ELASTIC_NODE}" 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /packages/plugin-casl/src/overrides.ts: -------------------------------------------------------------------------------- 1 | import type * as _ from '@microfleet/plugin-validator' 2 | import type * as __ from '@microfleet/plugin-router' 3 | 4 | import { Rbac, RbacConfig, RuleDefinition, ActionRbac } from './rbac' 5 | 6 | declare module '@microfleet/core-types' { 7 | interface Microfleet { 8 | rbac: Rbac 9 | } 10 | 11 | interface ConfigurationOptional { 12 | casl: RbacConfig 13 | } 14 | } 15 | 16 | declare module '@microfleet/plugin-router' { 17 | // Extend credentials 18 | interface AuthInfo { 19 | /** 20 | * Auth middleware should return `scopes` property. 21 | * The value of this property will be used in ServiceAction access policy validation. 22 | */ 23 | scopes?: RuleDefinition 24 | } 25 | 26 | // Service action plugin config 27 | interface ServiceAction { 28 | /** RBAC/PBAC specific configuration */ 29 | rbac?: ActionRbac 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/retry.ts: -------------------------------------------------------------------------------- 1 | import { Microfleet } from '@microfleet/core' 2 | import { ActionTransport, ServiceRequest } from '@microfleet/plugin-router' 3 | 4 | /** 5 | * Custom action that rejects based on params. 6 | * @param {ServiceRequest} request - Service Request. 7 | * @param {number} request.params - Max retries. 8 | * @param {Object} request.headers - AMQP properties. 9 | * @param {Object} request.headers.headers - AMQP headers. 10 | */ 11 | export default function retryAction( 12 | this: Microfleet, 13 | { params, headers: { headers } }: ServiceRequest 14 | ): Promise { 15 | if (headers['x-retry-count'] === undefined || headers['x-retry-count'] < params) { 16 | throw new Error(`count: ${headers['x-retry-count'] || 1}`) 17 | } 18 | 19 | return headers['x-retry-count'] 20 | } 21 | 22 | retryAction.transports = [ 23 | ActionTransport.amqp, 24 | ] 25 | -------------------------------------------------------------------------------- /packages/plugin-redis-sentinel/__tests__/utils/health-check.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert' 2 | import { Microfleet } from "@microfleet/core" 3 | 4 | export type HealthCheck = { 5 | handler: () => Promise; 6 | } 7 | 8 | const findByName = (pluginName: string) => (i: any) => ( 9 | pluginName === i.name 10 | ) 11 | 12 | /** 13 | * Returns a registered health check by plugin name 14 | * @param service link to Mservice 15 | * @param pluginName name of a plugin to perform look up. 16 | * @returns {Object} a health check module 17 | * @todo test util (there are duplicates) 18 | */ 19 | export function findHealthCheck(service: Microfleet, pluginName: string): HealthCheck { 20 | const check = service 21 | .getHealthChecks() 22 | .find(findByName(pluginName)) 23 | 24 | assert(check) 25 | 26 | return { 27 | ...check, 28 | handler: check.handler.bind(service), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/plugin-router/src/lifecycle/handlers/allowed.ts: -------------------------------------------------------------------------------- 1 | import { Microfleet } from '@microfleet/core' 2 | import { HttpStatusError, NotPermittedError } from 'common-errors' 3 | 4 | import { ServiceRequest } from '../../types/router' 5 | 6 | declare module '../../types/router' { 7 | interface ServiceAction { 8 | allowed?: ServiceMiddleware 9 | } 10 | } 11 | 12 | async function allowedHandler(this: Microfleet, request: ServiceRequest): Promise { 13 | const { allowed } = request.action 14 | 15 | if (allowed === undefined) { 16 | return 17 | } 18 | 19 | try { 20 | await allowed.call(this, request) 21 | } catch (error: any) { 22 | switch (error.constructor) { 23 | case NotPermittedError: 24 | case HttpStatusError: 25 | throw error 26 | 27 | default: 28 | throw new NotPermittedError(error) 29 | } 30 | } 31 | } 32 | 33 | export default allowedHandler 34 | -------------------------------------------------------------------------------- /.commitlintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'body-leading-blank': [1, 'always'], 4 | 'footer-leading-blank': [1, 'always'], 5 | 'header-max-length': [2, 'always', 72], 6 | 'scope-case': [2, 'always', 'lower-case'], 7 | 'subject-case': [ 8 | 2, 9 | 'never', 10 | ['sentence-case', 'start-case', 'pascal-case', 'upper-case'] 11 | ], 12 | 'subject-empty': [2, 'never'], 13 | 'subject-full-stop': [2, 'never', '.'], 14 | 'type-case': [2, 'always', 'lower-case'], 15 | 'type-empty': [2, 'never'], 16 | 'type-enum': [2, 'always', [ 17 | 'build', 18 | 'ci', 19 | 'docs', 20 | 'feat', 21 | 'fix', 22 | 'perf', 23 | 'refactor', 24 | 'revert', 25 | 'style', 26 | 'test', 27 | 'major', 28 | 'minor', 29 | 'patch', 30 | 'chore' 31 | ] 32 | ] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/__tests__/utils/health-check.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert' 2 | import type { Microfleet } from '@microfleet/core-types' 3 | 4 | export type HealthCheck = { 5 | handler: () => Promise; 6 | } 7 | 8 | const findByName = (pluginName: string) => (i: any) => ( 9 | pluginName === i.name 10 | ) 11 | 12 | /** 13 | * Returns a registered health check by plugin name 14 | * @param service link to Mservice 15 | * @param pluginName name of a plugin to perform look up. 16 | * @returns {Object} a health check module 17 | * @todo test util (there are duplicates) 18 | */ 19 | export function findHealthCheck(service: Microfleet, pluginName: string): HealthCheck { 20 | const check = service 21 | .getHealthChecks() 22 | .find(findByName(pluginName)) 23 | 24 | assert(check) 25 | 26 | return { 27 | ...check, 28 | handler: check.handler.bind(service), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - ./packages/core 3 | - ./packages/core-types 4 | - ./packages/plugin-amqp 5 | - ./packages/plugin-aws-elasticsearch 6 | - ./packages/plugin-consul 7 | - ./packages/plugin-couchdb 8 | - ./packages/plugin-dlock 9 | - ./packages/plugin-elasticsearch 10 | - ./packages/plugin-hapi 11 | - ./packages/plugin-kafka 12 | - ./packages/plugin-kafka-types 13 | - ./packages/plugin-knex 14 | - ./packages/plugin-logger 15 | - ./packages/plugin-prometheus 16 | - ./packages/plugin-redis-cluster 17 | - ./packages/plugin-redis-core 18 | - ./packages/plugin-redis-sentinel 19 | - ./packages/plugin-router 20 | - ./packages/plugin-router-amqp 21 | - ./packages/plugin-router-hapi 22 | - ./packages/plugin-router-socketio 23 | - ./packages/plugin-socketio 24 | - ./packages/plugin-validator 25 | - ./packages/utils 26 | - ./packages/plugin-casl 27 | - ./packages/plugin-signed-request 28 | -------------------------------------------------------------------------------- /packages/core/docs/recipes/maintenance.md: -------------------------------------------------------------------------------- 1 | ### Maintenance mode 2 | 3 | There are many situations when your service would need to set a maintenance mode. Examples are: 4 | - making database changes, migrations 5 | - reduce high load due to write overload 6 | - disable some actions for various reasons 7 | 8 | To set this feature, you need: 9 | 1) Enable maintenance mode in the configuration: 10 | ```js 11 | module.exports = { 12 | name: 'servicename', 13 | maintenanceMode: true, 14 | //... 15 | } 16 | ``` 17 | 2) Modify actions which will be available in maintenance mode by passing `readonly` flag: 18 | ```js 19 | async function actionHandler(request) { 20 | // 21 | } 22 | 23 | module.exports = handler 24 | module.exports.readonly = true 25 | 26 | ``` 27 | 3) Restart your service 28 | 29 | 30 | In this actions without according flag (or parent actions which trigger such actions) will return http status code `418 Server Maintenance`. 31 | -------------------------------------------------------------------------------- /packages/plugin-dlock/README.md: -------------------------------------------------------------------------------- 1 | # Microfleet DLock Plugin 2 | 3 | Wrapper for [Distributed callback queue](https://github.com/AVVS/distributed-callback-queue). 4 | 5 | ## Install 6 | 7 | `yarn add @microfleet/plugin-dlock` 8 | 9 | ## Configuration 10 | 11 | To make use of the plugin adjust microfleet configuration in the following way: 12 | 13 | ```ts 14 | exports.plugins = [ 15 | ..., 16 | 'redisCluster', // or 'redisSentinel' 17 | 'dlock', 18 | ... 19 | ] 20 | 21 | exports.dlock = { 22 | // pubsubChannel: string; 23 | // lock?: { 24 | // timeout?: number; 25 | // retries?: number; 26 | // delay?: number; 27 | // }; 28 | // lockPrefix: string; 29 | } 30 | ``` 31 | 32 | ## Interface 33 | 34 | Microfleet DLock Plugin extends service interface with the following methods: 35 | 36 | ### service.dlock: type of 'dlock' 37 | 38 | Initialized instance. Look at the docs here - https://github.com/AVVS/distributed-callback-queue 39 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/actions/qs.ts: -------------------------------------------------------------------------------- 1 | import { ActionTransport, ServiceRequest } from '@microfleet/plugin-router' 2 | 3 | export default async function QSAction(request: ServiceRequest): Promise { 4 | return { 5 | response: 'success', 6 | qs: request.query, 7 | } 8 | } 9 | 10 | QSAction.transformQuery = (query: any): any => { 11 | // toFloat 12 | query.sample *= 1 13 | 14 | // true/1 as true, rest as false 15 | switch (query.bool) { 16 | case 'true': 17 | case '1': 18 | query.bool = true 19 | break 20 | 21 | case 'false': 22 | case '0': 23 | case undefined: 24 | query.bool = false 25 | break 26 | 27 | // fail validation 28 | default: 29 | query.bool = null 30 | } 31 | 32 | return query 33 | } 34 | QSAction.transports = [ActionTransport.http] 35 | QSAction.transportsOptions = { 36 | [ActionTransport.http]: { 37 | methods: ['get'], 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/docs/reference/plugins/amqp/publish-options.md: -------------------------------------------------------------------------------- 1 | # Publish options 2 | | Option | Type | Default Value | Description | 3 | |--------|------|---------------|-------------| 4 | | `confirm` | `boolean` | `false` | Whether to wait for commit confirmation before resolving | 5 | | `immediate` | `boolean` | `false` | Waits for the message to be delivered and resolves if it can, rejects otherwise, not implemented by RammitMQ | 6 | | `mandatory` | `boolean` | `false` | When true and message cant be routed to a queue – exception returned, otherwise its dropped | 7 | | `contentType` | `string` | `application/json` | Default content-type for messages | 8 | | `contentEncoding` | `string`| `plain` | Default content-encoding | 9 | | `headers` | `object` | `undefined` @todo check | Headers set | 10 | | `simpleResponse` | `boolean` | `true` | Whether to return only response data or include headers etc. | 11 | | `deliveryMode` | `number` | `1` | `1` – transient, `2` – saved on disk | 12 | -------------------------------------------------------------------------------- /packages/plugin-router/__tests__/artifacts/child-service.ts: -------------------------------------------------------------------------------- 1 | import { Microfleet } from '@microfleet/core' 2 | import { withResponseValidateAction } from './utils' 3 | import '@microfleet/plugin-amqp' 4 | 5 | const service = new Microfleet(withResponseValidateAction('tester', { 6 | sigterm: true, 7 | routerAmqp: { 8 | prefix: 'amqp', 9 | }, 10 | hapi: { 11 | server: { 12 | port: parseInt(process.argv[2], 10) || 8000, 13 | }, 14 | }, 15 | router: { 16 | routes: { 17 | enabledGenericActions: ['health'], 18 | }, 19 | }, 20 | })); 21 | 22 | (async () => { 23 | try { 24 | process.stdout.write(`${JSON.stringify(process.argv)}\n`) 25 | await service.connect() 26 | process.stdout.write(`${JSON.stringify({ childServiceReady: true, amqp: service.config.amqp.transport.exchange })}\n`) 27 | } catch (e: any) { 28 | /* eslint-disable-next-line no-console */ 29 | console.error(e) 30 | process.exit(128) 31 | } 32 | })() 33 | -------------------------------------------------------------------------------- /packages/plugin-router/src/actions/health.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatusError } from 'common-errors' 2 | import { 3 | Microfleet, 4 | HealthStatus, 5 | PLUGIN_STATUS_FAIL 6 | } from '@microfleet/core' 7 | 8 | import { ServiceRequest } from '../types/router' 9 | 10 | const kUnhealthy = new HttpStatusError(500, 'unhealthy') 11 | 12 | export default async function genericHealthCheck(this: Microfleet, request: ServiceRequest): Promise<{ data: HealthStatus }> { 13 | const data = await this.getHealthStatus() 14 | 15 | if (PLUGIN_STATUS_FAIL === data.status) { 16 | switch (request.transport) { 17 | case 'amqp': 18 | case 'internal': { 19 | const plugins = data.failed.map(it => it.name).join(', ') 20 | throw new HttpStatusError(500, `Unhealthy due to following plugins: ${plugins}`) 21 | } 22 | 23 | default: 24 | this.log.error({ data }, 'health check failed') 25 | throw kUnhealthy 26 | } 27 | } 28 | 29 | return { data } 30 | } 31 | -------------------------------------------------------------------------------- /packages/plugin-dlock/schemas/dlock.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "dlock", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "required": ["pubsubChannel", "lockPrefix"], 6 | "properties": { 7 | "pubsubChannel": { 8 | "type": "string", 9 | "minLength": 1 10 | }, 11 | "lock": { 12 | "type": "object", 13 | "additionalProperties": false, 14 | "required": ["timeout", "retries", "delay"], 15 | "default": {}, 16 | "properties": { 17 | "timeout": { 18 | "type": "integer", 19 | "minimum": 1, 20 | "default": 10000 21 | }, 22 | "retries": { 23 | "type": "integer", 24 | "minimum": 0, 25 | "default": 1 26 | }, 27 | "delay": { 28 | "type": "integer", 29 | "minimum": 1, 30 | "default": 100 31 | } 32 | } 33 | }, 34 | "lockPrefix": { 35 | "type": "string", 36 | "minLength": 1 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type * as _ from '@microfleet/plugin-socketio' 2 | import type * as __ from '@microfleet/plugin-router' 3 | import { strict as assert } from 'assert' 4 | import { Microfleet, PluginTypes } from '@microfleet/core' 5 | import type { Socket } from 'socket.io' 6 | import attachSocketioRouter from './attach' 7 | 8 | declare module '@microfleet/plugin-router' { 9 | export interface ServiceRequest { 10 | socket?: Socket 11 | } 12 | } 13 | 14 | export const name = 'routerSocketio' 15 | export const type = PluginTypes.transport 16 | export const priority = 30 // after plugin-socketio and plugin router 17 | 18 | export const attach = function attachRouterSocketioPlugin(this: Microfleet): void { 19 | assert(this.hasPlugin('router'), 'router plugin is required') 20 | assert(this.hasPlugin('socketio'), 'socketio plugin is required') 21 | assert(this.hasPlugin('logger'), 'logger plugin is required') 22 | 23 | attachSocketioRouter(this.socketio, this.router, this.log) 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.xml 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 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 29 | node_modules 30 | src/actions 31 | schemas/add.json 32 | 33 | # compiled source code 34 | lib 35 | .env 36 | .nyc_output 37 | 38 | *.tsbuildinfo 39 | .releaserc.json 40 | .vscode 41 | .clinic 42 | .swc 43 | 44 | # Rush temporary files 45 | common/deploy/ 46 | common/temp/ 47 | common/autoinstallers/*/.npmrc 48 | **/.rush/temp/ 49 | 50 | # Heft 51 | .heft 52 | *.tgz 53 | .pnpm-store 54 | 55 | .DS_Store 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "microfleet", 3 | "version": "24.0.0", 4 | "private": true, 5 | "scripts": { 6 | "semantic-release": "semantic-release", 7 | "prepare": "mdep install", 8 | "prebuild": "pnpm clean", 9 | "clean": "pnpm -r clean", 10 | "build": "pnpm tsc -b ./tsconfig.build.json", 11 | "pretest": "pnpm build", 12 | "test": "pnpm test -r --workspace-concurrency 3", 13 | "version": "rimraf ./ci/staged/*.json; pnpm --filter='./packages/*' exec -- pnpm release-it", 14 | "release-it": "release-it -c .release-it.root.cjs" 15 | }, 16 | "devDependencies": { 17 | "@esbuild/linux-arm64": "^0.23.1", 18 | "@makeomatic/deploy": "^13.1.0", 19 | "@release-it/conventional-changelog": "^10.0.0", 20 | "@types/node": "22.10.10", 21 | "conventional-changelog-angular": "^8.0.0", 22 | "debug": "^4.4.0", 23 | "esbuild": "^0.23.1", 24 | "glob": "^11.0.1", 25 | "js-yaml": "^4.1.0", 26 | "prepend-file": "^2.0.1", 27 | "release-it": "^18.1.2", 28 | "rimraf": "^6.0.1", 29 | "typescript": "~5.7.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/plugin-couchdb/README.md: -------------------------------------------------------------------------------- 1 | # Microfleet CouchDB Plugin 2 | 3 | Adds CouchDB support to microfleet. Learn more at https://github.com/apache/couchdb-nano and 4 | https://couchdb.apache.org/ 5 | 6 | ## Install 7 | 8 | `yarn add @microfleet/plugin-couchdb` 9 | 10 | ## Configuration 11 | 12 | To make use of the plugin adjust microfleet configuration in the following way: 13 | 14 | ```ts 15 | exports.plugins = [ 16 | ..., 17 | 'couchdb', 18 | ... 19 | ] 20 | 21 | exports.couchdb = { 22 | connection: 'http://username:password@database:5984', // will connect to this instance 23 | database: 'sample', // all operations will be scoped to this database 24 | indexDefinitions: [{ // optional section for indexes to be created on startup 25 | fields: ['_id', 'name'], 26 | name: 'basic', 27 | ddoc: '_id_for_index', // this is important so that we dont create the same index over and over again 28 | }] 29 | } 30 | ``` 31 | 32 | ## Interface 33 | 34 | `service.couchdb` exposes document functions interface as described in here: 35 | https://github.com/apache/couchdb-nano#document-functions 36 | -------------------------------------------------------------------------------- /packages/plugin-router/src/lifecycle/handlers/validate.ts: -------------------------------------------------------------------------------- 1 | import { Error } from 'common-errors' 2 | import { Microfleet } from '@microfleet/core' 3 | import { HttpStatusError } from '@microfleet/validation' 4 | 5 | import { RequestDataKey } from '../../router' 6 | import { ServiceRequest } from '../../types/router' 7 | 8 | async function validateHandler(this: Microfleet, request: ServiceRequest): Promise { 9 | const { validator } = this 10 | const { schema } = request.action 11 | 12 | // disable validation 13 | if (schema === null || schema === false) { 14 | return 15 | } 16 | 17 | const paramsKey = RequestDataKey[request.method] ?? 'params' 18 | 19 | try { 20 | // @todo (important) handle schema not found error and log it 21 | const validationResult = await validator.validate(schema as string, request[paramsKey]) 22 | 23 | request[paramsKey] = validationResult 24 | } catch (error: any) { 25 | if (error.constructor === HttpStatusError) { 26 | throw error 27 | } 28 | 29 | throw new Error('internal validation error', error) 30 | } 31 | } 32 | 33 | export default validateHandler 34 | -------------------------------------------------------------------------------- /packages/plugin-router/src/extensions/validate/query-string-parser.ts: -------------------------------------------------------------------------------- 1 | import { parse, type ParsedQs } from 'qs' 2 | import { Lifecycle } from '../../lifecycle/index' 3 | import { ServiceRequest } from '../../types/router' 4 | 5 | type QSParserAugmentedAction = ServiceRequest & { 6 | action: ServiceRequest['action'] & { 7 | transformQuery?: (input: Record) => ParsedQs; 8 | transformOpts?: Parameters[1]; 9 | }; 10 | } 11 | 12 | const identity = (param: T): T => param 13 | 14 | async function preValidate(request: QSParserAugmentedAction): Promise { 15 | const { query } = request 16 | 17 | // if present - remap, otherwise just noop 18 | if (query) { 19 | const { action } = request 20 | const { transformQuery = identity, transformOpts } = action 21 | 22 | // module actually handles all variations of input 23 | request.query = transformQuery(parse(query as any, { 24 | depth: 1, 25 | parameterLimit: 10, 26 | parseArrays: false, 27 | ...transformOpts, 28 | })) 29 | } 30 | } 31 | 32 | export default [{ 33 | handler: preValidate, 34 | point: Lifecycle.hooks.preValidate, 35 | }] 36 | -------------------------------------------------------------------------------- /packages/plugin-router/src/lifecycle/utils.ts: -------------------------------------------------------------------------------- 1 | import { Microfleet } from '@microfleet/core' 2 | 3 | import type { Hook, Hooks } from './' 4 | import type { ServiceRequest, ServiceMiddleware } from '../types/router' 5 | 6 | export async function runHook( 7 | hooks: Hooks, 8 | hook: Hook, 9 | context: Microfleet, 10 | request: ServiceRequest 11 | ): Promise { 12 | const handlers = hooks.get(hook) 13 | 14 | if (handlers !== undefined) { 15 | for (const handler of handlers) { 16 | await handler.call(context, request) 17 | } 18 | } 19 | } 20 | 21 | export async function runHandler( 22 | handler: ServiceMiddleware, 23 | hooks: Hooks, 24 | preHook: Hook, 25 | postHook: Hook, 26 | context: Microfleet, 27 | request: ServiceRequest 28 | ): Promise { 29 | await runHook(hooks, preHook, context, request) 30 | 31 | try { 32 | await handler.call(context, request) 33 | } catch (error: any) { 34 | request.error = error 35 | } 36 | 37 | await runHook(hooks, postHook, context, request) 38 | 39 | const onRequestError = request.error 40 | 41 | if (onRequestError !== undefined) { 42 | throw onRequestError 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.release-it.cjs: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const cwd = process.cwd() 3 | const pkg = path.resolve(cwd, 'package.json') 4 | const { name } = require(pkg) 5 | 6 | module.exports = { 7 | git: { 8 | changelog: 'git log --pretty=format:\"* %s (%h)\" \${from}...\${to} -- ./', 9 | commitMessage: 'chore(release): ${name} -- v${version}', 10 | tagName: `${name}@\${version}`, 11 | commit: false, 12 | tag: false, 13 | push: false, 14 | requireCleanWorkingDir: false, 15 | requireBranch: 'master' 16 | }, 17 | npm: { 18 | publish: false, 19 | ignoreVersion: false, 20 | }, 21 | plugins: { 22 | [path.resolve(__dirname, './ci/aggregate-npm.mjs')]: {}, 23 | [path.resolve(__dirname, './ci/aggregate-conventional-changelog.mjs')]: { 24 | infile: "CHANGELOG.md", 25 | preset: { 26 | name: path.resolve(__dirname, './ci/conventional-changelog-microfleet.mjs'), 27 | parserOpts: { 28 | noteKeywords: ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"] 29 | }, 30 | }, 31 | }, 32 | [path.resolve(__dirname, './ci/aggregate-release.mjs')]: {}, 33 | }, 34 | 'disable-metrics': true, 35 | } 36 | -------------------------------------------------------------------------------- /packages/plugin-router/src/types/plugin.ts: -------------------------------------------------------------------------------- 1 | import Router from '../router' 2 | import { ServiceAction, ServiceRequest } from './router' 3 | import { AuthConfig } from '../lifecycle/handlers/auth' 4 | import { ValidateResponseConfig } from '../lifecycle/handlers/validate-response' 5 | import { LifecycleExtensions } from '../lifecycle' 6 | 7 | declare module '@microfleet/core-types' { 8 | export interface Microfleet { 9 | router: Router 10 | dispatch(route: string, request: Partial): Promise 11 | } 12 | 13 | export interface ConfigurationOptional { 14 | router: RouterPluginConfig 15 | } 16 | } 17 | 18 | export type RouterPluginConfig = { 19 | auth: AuthConfig 20 | extensions: { 21 | register: LifecycleExtensions[] 22 | } 23 | routes: RouterPluginRoutesConfig 24 | } 25 | 26 | export interface RouterPluginRoutesConfig { 27 | directory?: string 28 | enabled?: Record }> 29 | disabled?: Record 30 | allRoutes?: Partial 31 | prefix?: string 32 | responseValidation?: ValidateResponseConfig 33 | enabledGenericActions?: string[] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 Vitaly Aminev 4 | Copyright (c) 2017 Remi Development L.P. 5 | Copyright (c) 2018-2022 Makeomatic Inc 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /packages/plugin-elasticsearch/__tests__/elasticsearch.spec.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'node:assert' 2 | import test from 'node:test' 3 | import { Microfleet } from '@microfleet/core' 4 | import { Config } from '@microfleet/plugin-elasticsearch' 5 | 6 | const getConfig = (): Config => ({ 7 | node: 'http://elasticsearch:9200', 8 | }) 9 | 10 | let service: Microfleet 11 | 12 | test('Elasticsearch suite', async (t) => { 13 | t.after(async () => { 14 | await service?.close() 15 | }) 16 | 17 | await t.test('should throw an error when plugin isn\'t included', async () => { 18 | service = new Microfleet({ 19 | name: 'tester', 20 | plugins: [], 21 | }) 22 | assert(!service.elasticsearch) 23 | }) 24 | 25 | await t.test('able to connect to elasticsearch when plugin is included', async () => { 26 | service = new Microfleet({ 27 | name: 'tester', 28 | plugins: ['validator', 'logger', 'elasticsearch'], 29 | elasticsearch: getConfig(), 30 | }) 31 | 32 | await service.connect() 33 | const { elasticsearch } = service 34 | 35 | assert(elasticsearch.transport, 'elasticsearch should have transport property') 36 | assert(elasticsearch.cluster, 'elasticsearch should have cluster property') 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/plugin-redis-cluster/schemas/redis-cluster.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "redisCluster", 3 | "type": "object", 4 | "required": ["hosts"], 5 | "properties": { 6 | "hosts": { 7 | "type": "array", 8 | "minItems": 1, 9 | "items": { 10 | "type": "object", 11 | "required": ["host", "port"], 12 | "properties": { 13 | "host": { 14 | "type": "string", 15 | "anyOf": [ 16 | { "format": "hostname"}, 17 | { "format": "ipv4" }, 18 | { "format": "ipv6" } 19 | ] 20 | }, 21 | "port": { 22 | "anyOf": [{ 23 | "type": "integer" 24 | }, { 25 | "type": "string" 26 | }] 27 | } 28 | } 29 | } 30 | }, 31 | "options": { 32 | "type": "object", 33 | "properties": { 34 | "keyPrefix": { 35 | "type": "string" 36 | }, 37 | "redisOptions": { 38 | "type": "object", 39 | "properties": { 40 | "keyPrefix": { 41 | "type": "string" 42 | }, 43 | "dropBufferSupport": { 44 | "type": "boolean" 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/core/docs/reference/plugins.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | Microfleet Core has plugin system. 3 | Each plugin is defined with its type, priority within type group and [a connector](#connector-interface) function. 4 | 5 | ## Enable plugin 6 | Add plugin name to the list of enabled plugins: 7 | ```js 8 | // configs/plugins.js 9 | exports.plugins = [ 10 | 'validator', 11 | 'logger', 12 | 'amqp', 13 | ]; 14 | ``` 15 | 16 | ## Plugin types 17 | Plugin types are 18 | 19 | | Name | Purpose | 20 | |---------------|---------| 21 | | `application` | What is application plugin | 22 | | `database` | All database connectors | 23 | | `essential` | Plugins that the core needs - `validator`, `logger`, `opentracing`, `router` | 24 | | `transport` | HTTP, AMQP - anything | 25 | 26 | ## Plugin priority 27 | Priority is just a number that is responsible for the priority within a group of plugins with the same type. 28 | 29 | ## Connector 30 | Connector is a function that attaches plugin to a service and could provide following lifecycle methods: 31 | 32 | | Method | Description | 33 | |------------|-------------| 34 | |`connect()` | | 35 | |`close()` | | 36 | |`status()` | | 37 | 38 | ## Debugging 39 | 40 | ## Plugin list 41 | - [AMQP](plugins/amqp.md) 42 | - [Router](plugins/router.md) 43 | -------------------------------------------------------------------------------- /ci/conventional-changelog-microfleet.mjs: -------------------------------------------------------------------------------- 1 | import createAngularPreset from 'conventional-changelog-angular' 2 | import _debug from 'debug' 3 | 4 | const debug = _debug('release-it:aggregate-conventional-changelog') 5 | 6 | const whatBump = (commits) => { 7 | let level = undefined 8 | let breakings = 0 9 | let features = 0 10 | let patches = 0 11 | 12 | commits.forEach(commit => { 13 | debug(commit) 14 | 15 | if (commit.notes.length > 0) { 16 | breakings += commit.notes.length 17 | level = 0 18 | } else if (commit.type === 'feat') { 19 | features += 1 20 | if (level === 2 || level === undefined) { 21 | level = 1 22 | } 23 | } else if (['fix', 'patch', 'revert', 'refactor', 'docs', 'style'].includes(commit.type)) { 24 | patches += 1 25 | if (level === undefined) { 26 | level = 2 27 | } 28 | } 29 | }) 30 | 31 | return { 32 | level: level, 33 | reason: breakings === 1 34 | ? `There is ${breakings} BREAKING CHANGE, ${features} features and ${patches} patches` 35 | : `There are ${breakings} BREAKING CHANGES, ${features} features and ${patches} patches` 36 | } 37 | } 38 | 39 | export default async function createPreset() { 40 | const preset = await createAngularPreset() 41 | preset.whatBump = whatBump 42 | return preset 43 | } 44 | -------------------------------------------------------------------------------- /packages/plugin-knex/schemas/knex.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "knex", 3 | "type": "object", 4 | "required": [ 5 | "client", 6 | "debug" 7 | ], 8 | "properties": { 9 | "client": { 10 | "type": "string", 11 | "enum": [ 12 | "pg", 13 | "mysql", 14 | "sqlite3" 15 | ] 16 | }, 17 | "debug": { 18 | "type": "boolean", 19 | "default": false 20 | }, 21 | "migrations": { 22 | "additionalProperties": false, 23 | "type": "object", 24 | "properties": { 25 | "tableName": { 26 | "type": "string" 27 | }, 28 | "directory": { 29 | "type": "string" 30 | }, 31 | "extension": { 32 | "type": "string" 33 | }, 34 | "disableTransactions": { 35 | "type": "boolean" 36 | } 37 | } 38 | }, 39 | "seeds": { 40 | "additionalProperties": false, 41 | "type": "object", 42 | "properties": { 43 | "directory": { 44 | "type": "string" 45 | } 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /packages/plugin-kafka/src/util.ts: -------------------------------------------------------------------------------- 1 | import { TopicMetadata, SubscribeTopic, SubscribeTopicList } from '@makeomatic/node-rdkafka' 2 | import { TopicNotFoundError } from './custom/errors' 3 | import type { Level } from 'pino' 4 | 5 | /** 6 | * `librdkafka` uses syslog severity levels 7 | */ 8 | export const kafkaSeverityToLogMapping: { [level: number]: Level } = { 9 | 0: 'fatal', 1: 'fatal', 10 | 2: 'fatal', 3: 'error', 11 | 4: 'warn', 5: 'info', 12 | 6: 'info', 7: 'debug', 13 | } 14 | 15 | /** 16 | * Convert syslog level to generic level 17 | * @param level syslog level 18 | */ 19 | export function getLogFnName(level: number): Level { 20 | return kafkaSeverityToLogMapping[level] || 'debug' 21 | } 22 | 23 | /** 24 | * Checks whether topics exist 25 | * @param data - List of topics received on Client.connect 26 | * @param topics - Required topics 27 | */ 28 | export function topicExists(data: TopicMetadata[], topics: SubscribeTopic | SubscribeTopicList): void { 29 | const topicList = Array.isArray(topics) ? topics : [topics] 30 | for (const topic of topicList) { 31 | const found = data.find((metaDataTopic) => { 32 | if (topic instanceof RegExp) return topic.test(metaDataTopic.name) 33 | return topicList.includes(metaDataTopic.name) 34 | }) 35 | 36 | if (!found) throw new TopicNotFoundError('Missing consumer topic', topicList) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-casl/src/casl.ts: -------------------------------------------------------------------------------- 1 | import { NotFoundError } from 'common-errors' 2 | import { resolve } from 'node:path' 3 | import { strict as assert } from 'node:assert' 4 | 5 | import type { Microfleet } from '@microfleet/core' 6 | import type { PluginInterface } from '@microfleet/core-types' 7 | import { PluginTypes } from '@microfleet/utils' 8 | 9 | import type * as _ from './overrides' 10 | 11 | import { Rbac, RbacConfig } from './rbac' 12 | import { rbacExtension } from './allowed-extension' 13 | 14 | export * from './rbac' 15 | 16 | /** 17 | * Plugin Name 18 | */ 19 | export const name = 'casl' 20 | 21 | /** 22 | * Plugin Type 23 | */ 24 | export const type = PluginTypes.transport 25 | 26 | /** 27 | * Relative priority inside the same plugin group type 28 | */ 29 | export const priority = 15 30 | 31 | /** 32 | * Attaches plugin to the MService class. 33 | */ 34 | export async function attach(this: Microfleet, opts: Partial = {}): Promise { 35 | assert(this.hasPlugin('validator'), new NotFoundError('validator module must be included')) 36 | await this.validator.addLocation(resolve(__dirname, '../schemas')) 37 | 38 | const config = this.validator.ifError(name, opts) 39 | 40 | if (this.hasPlugin('router')) { 41 | this.router.lifecycle.addHook(rbacExtension) 42 | } 43 | 44 | this.rbac = new Rbac(config) 45 | 46 | return {} 47 | } 48 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/src/plugins/hapi.ts: -------------------------------------------------------------------------------- 1 | import type * as _ from '../types' 2 | import type { Server } from '@hapi/hapi' 3 | 4 | import { boomify } from '@hapi/boom' 5 | import { SignedRequest, CredentialsStore, Config } from '../signed-request' 6 | 7 | export const HapiSignedRequestPlugin = (store: CredentialsStore, config: Config) => ({ 8 | name: 'hapi-signed-request', 9 | register(server: Server) { 10 | server.ext('onRequest', async (request, h) => { 11 | if (SignedRequest.isSignedRequest(request.headers)) { 12 | try { 13 | const signature = request.plugins.signature = new SignedRequest(config, store) 14 | await signature.initialize(request.raw.req) 15 | 16 | signature.verifyHeaders() 17 | 18 | request.events.on('peek', (chunk) => { 19 | signature.appendPayload(chunk) 20 | }) 21 | 22 | } catch (e: any) { 23 | throw boomify(e, { statusCode: e.statusCode || e.status_code }) 24 | } 25 | } 26 | 27 | return h.continue 28 | }) 29 | 30 | server.ext('onPreHandler', (request, h) => { 31 | if (request.plugins.signature) { 32 | try { 33 | request.plugins.signature.verifyPayload() 34 | } catch (e: any) { 35 | throw boomify(e, { statusCode: e.statusCode || e.status_code }) 36 | } 37 | } 38 | 39 | return h.continue 40 | }) 41 | }, 42 | }) 43 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type * as _ from '@microfleet/plugin-logger' 2 | import type * as __ from '@microfleet/plugin-validator' 3 | 4 | import { strict as assert } from 'assert' 5 | import { Microfleet, PluginTypes } from '@microfleet/core' 6 | import { Config } from './types' 7 | import { resolve } from 'path' 8 | import { HapiSignedRequestPlugin } from './plugins/hapi' 9 | import { extendServiceRequest } from './router-extension' 10 | import { PluginInterface } from '@microfleet/core-types' 11 | 12 | export const name = 'signedRequest' 13 | export const type = PluginTypes.transport 14 | export const priority = 31 15 | 16 | export async function attach(this: Microfleet, options: Partial = {}): Promise { 17 | assert(this.hasPlugin('validator'), 'validator plugin must be included') 18 | assert(this.hasPlugin('routerHapi'), 'router-hapi plugin must be included') 19 | 20 | await this.validator.addLocation(resolve(__dirname, '../schemas')) 21 | 22 | const config = this.validator.ifError('signed-request', options) 23 | 24 | if (this.hasPlugin('router')) { 25 | this.router.lifecycle.addHook(extendServiceRequest) 26 | } 27 | 28 | return { 29 | async connect(this: Microfleet) { 30 | assert(this.credentialsStore, 'credentials store should be initialized') 31 | this.hapi.register(HapiSignedRequestPlugin(this.credentialsStore, config)) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.pnpmfile.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * When using the PNPM package manager, you can use pnpmfile.js to workaround 5 | * dependencies that have mistakes in their package.json file. (This feature is 6 | * functionally similar to Yarn's "resolutions".) 7 | * 8 | * For details, see the PNPM documentation: 9 | * https://pnpm.js.org/docs/en/hooks.html 10 | * 11 | * IMPORTANT: SINCE THIS FILE CONTAINS EXECUTABLE CODE, MODIFYING IT IS LIKELY TO INVALIDATE 12 | * ANY CACHED DEPENDENCY ANALYSIS. After any modification to pnpmfile.js, it's recommended to run 13 | * "rush update --full" so that PNPM will recalculate all version selections. 14 | */ 15 | module.exports = { 16 | hooks: { 17 | readPackage 18 | } 19 | }; 20 | 21 | /** 22 | * This hook is invoked during installation before a package's dependencies 23 | * are selected. 24 | * The `packageJson` parameter is the deserialized package.json 25 | * contents for the package that is about to be installed. 26 | * The `context` parameter provides a log() function. 27 | * The return value is the updated object. 28 | */ 29 | function readPackage(packageJson, context) { 30 | 31 | // // The karma types have a missing dependency on typings from the log4js package. 32 | // if (packageJson.name === '@types/karma') { 33 | // context.log('Fixed up dependencies for @types/karma'); 34 | // packageJson.dependencies['log4js'] = '0.6.38'; 35 | // } 36 | 37 | return packageJson; 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/bin/mfleet.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-console */ 3 | /* eslint-disable @typescript-eslint/no-var-requires */ 4 | 5 | const { resolve } = require('node:path') 6 | const parser = require('yargs-parser') 7 | 8 | async function loadAny(loaders) { 9 | const errors = [] 10 | for (const loader of loaders) { 11 | try { 12 | return await loader() 13 | } catch (e) { 14 | errors.push(e) 15 | } 16 | } 17 | 18 | for (const [idx, err] of errors.entries()) { 19 | console.error('Loader %d\n-----', idx) 20 | console.error(err) 21 | } 22 | 23 | process.exit(128) 24 | } 25 | 26 | !(async () => { 27 | const argv = parser(process.argv.slice(2)) 28 | 29 | // prepare variables 30 | const cwd = argv.cwd || process.cwd() 31 | const source = argv.src ? resolve(cwd, argv.src) : resolve(cwd, 'src') 32 | const prop = argv.prop || 'default' 33 | 34 | const Service = await loadAny([ 35 | () => require(source)[prop], 36 | () => require(cwd)[prop], 37 | () => import(source).then(x => x[prop]), 38 | () => import(cwd).then(x => x[prop]), 39 | ]) 40 | 41 | // init service as there is no top-level async/await 42 | const service = Service[Symbol.toStringTag] === 'AsyncFunction' 43 | ? await Service() 44 | : new Service() 45 | 46 | await service.connect() 47 | 48 | service.log.info('service started') 49 | })().catch((err) => { 50 | console.error(err) 51 | process.exit(128) 52 | }) 53 | -------------------------------------------------------------------------------- /packages/plugin-router-hapi/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type * as _ from '@microfleet/plugin-hapi' 2 | import type * as __ from '@microfleet/plugin-validator' 3 | import { strict as assert } from 'assert' 4 | import { resolve } from 'path' 5 | import { Microfleet, PluginTypes } from '@microfleet/core' 6 | 7 | import attachRouter from './attach' 8 | import { RouterHapiPluginConfig } from './types/plugin' 9 | import { PluginInterface } from '@microfleet/core-types' 10 | 11 | export const name = 'routerHapi' 12 | export const type = PluginTypes.transport 13 | export const priority = 30 // should be after plugin-hapi and plugin router 14 | 15 | declare module '@microfleet/core-types' { 16 | interface ConfigurationOptional { 17 | routerHapi: RouterHapiPluginConfig 18 | } 19 | } 20 | 21 | /** 22 | * Attaches HTTP handler. 23 | * @param config - HTTP handler configuration to attach. 24 | */ 25 | export async function attach( 26 | this: Microfleet, 27 | options: Partial = {} 28 | ): Promise { 29 | assert(this.hasPlugin('validator'), 'validator module must be included') 30 | 31 | await this.validator.addLocation(resolve(__dirname, '../schemas')) 32 | 33 | const config = this.validator.ifError('router-hapi', options) 34 | const routerPlugin = attachRouter(this, config) 35 | 36 | return { 37 | async connect(this: Microfleet) { 38 | await this.hapi.register(routerPlugin) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/plugin-aws-elasticsearch/__tests__/aws-elasticsearch.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'node:test' 2 | import assert from 'node:assert/strict' 3 | import { Client } from '@opensearch-project/opensearch' 4 | import { Microfleet } from '@microfleet/core' 5 | 6 | describe.skip('AWS Elasticsearch suite', async () => { 7 | let service: Microfleet 8 | 9 | it('should throw an error when plugin isn\'t included', async () => { 10 | service = new Microfleet({ 11 | name: 'tester', 12 | plugins: [], 13 | }) 14 | assert.equal(service.awsElasticsearch, undefined) 15 | }) 16 | 17 | it('able to connect to elasticsearch when plugin is included', async () => { 18 | service = new Microfleet({ 19 | name: 'tester', 20 | plugins: ['validator', 'logger', 'aws-elasticsearch'], 21 | awsElasticsearch: { 22 | node: process.env.AWS_ELASTIC_NODE, 23 | accessKeyId: process.env.AWS_ELASTIC_KEY_ID, 24 | secretAccessKey: process.env.AWS_ELASTIC_ACCESS_KEY, 25 | } 26 | }) 27 | 28 | await service.connect() 29 | 30 | const { awsElasticsearch } = service 31 | assert.ok(Object.hasOwn(awsElasticsearch, 'transport')) 32 | assert.ok(awsElasticsearch instanceof Client) 33 | assert.ok(service.awsElasticsearch instanceof Client) 34 | }) 35 | 36 | it('able to close connection to elasticsearch', async () => { 37 | await service.close() 38 | assert.equal(service.awsElasticsearch, undefined) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /packages/plugin-dlock/src/utils/lock-action-wrapper.ts: -------------------------------------------------------------------------------- 1 | import Bluebird from 'bluebird' 2 | // @ts-expect-error: possible to import sync ESM from node >= 22.12.0 3 | import { LockAcquisitionError } from '@microfleet/ioredis-lock' 4 | import { HttpStatusError } from 'common-errors' 5 | import type { Microfleet } from '@microfleet/core' 6 | import type { ServiceRequest, ServiceAction } from '@microfleet/plugin-router' 7 | 8 | export type RequestMapper = { 9 | (request: ServiceRequest): string; 10 | } 11 | 12 | const concurrentRequests = new HttpStatusError(429, 'multiple concurrent requests') 13 | 14 | function acquireLockWrapper( 15 | this: Microfleet, 16 | request: ServiceRequest, 17 | action: ServiceAction, 18 | lockKeyPrefix: string, 19 | lockKeySuffixes: RequestMapper[] = [] 20 | ) { 21 | const keyParts = [lockKeyPrefix] 22 | 23 | for (const mapper of lockKeySuffixes) { 24 | keyParts.push(mapper(request)) 25 | } 26 | 27 | const lock = this.dlock.acquireLock(keyParts.join('-')) 28 | 29 | return Bluebird 30 | .using(this, request, lock, () => action.handler.call(this, request, lock)) 31 | .catchThrow(LockAcquisitionError, concurrentRequests) 32 | } 33 | 34 | export default ( 35 | action: ServiceAction, 36 | lockKeyPrefix: string, 37 | lockKeySuffixes: RequestMapper[] 38 | ) => function actionLockWrapper(this: Microfleet, request: ServiceRequest): Promise { 39 | return acquireLockWrapper.call(this, request, action, lockKeyPrefix, lockKeySuffixes) 40 | } 41 | -------------------------------------------------------------------------------- /packages/plugin-router/src/lifecycle/handlers/validate-response.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert' 2 | import { Error } from 'common-errors' 3 | import { Microfleet } from '@microfleet/core' 4 | import { HttpStatusError } from '@microfleet/validation' 5 | 6 | import { ServiceRequest } from '../../types/router' 7 | 8 | export interface ValidateResponseConfig { 9 | enabled: boolean 10 | maxSample: number 11 | panic: boolean 12 | } 13 | 14 | export default (config?: ValidateResponseConfig) => async function validateResponseHandler( 15 | this: Microfleet, 16 | request: ServiceRequest, 17 | ): Promise { 18 | if (config === undefined) { 19 | return 20 | } 21 | 22 | const { enabled, maxSample, panic } = config 23 | if (enabled === false) { 24 | return 25 | } 26 | // @todo fast? 27 | if (Math.round(Math.random() * 100) > maxSample) { 28 | return 29 | } 30 | 31 | const { validateResponse, responseSchema, actionName } = request.action 32 | if (validateResponse === false) { 33 | return 34 | } 35 | 36 | assert(responseSchema) 37 | 38 | try { 39 | await this.validator.validate(responseSchema, request.response) 40 | } catch (error: any) { 41 | if (panic) { 42 | if (error.constructor === HttpStatusError) { 43 | throw error 44 | } 45 | 46 | throw new Error('internal response validation error', error) 47 | } 48 | 49 | this.log.warn({ error, action: actionName }, '[response] validation failed') 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/plugin-casl/schemas/casl.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "$id": "casl", 4 | "type": "object", 5 | "additionalProperties": false, 6 | "properties": { 7 | "cache": { 8 | "type": "object", 9 | "properties": { 10 | "enabled": { "type": "object" }, 11 | "maxAge": { "type": "number", "minimum": 1 } 12 | } 13 | }, 14 | "abilities": { 15 | "type": "object", 16 | "additionalProperties": { 17 | "$ref": "#/definitions/scopes" 18 | } 19 | }, 20 | "actions": { 21 | "type": "object", 22 | "additionalProperties": { 23 | "type": "array", 24 | "items": { "type": "string" }, 25 | "minItems": 1 26 | } 27 | }, 28 | "anyAction": { "type": "string", "minLength": 1 }, 29 | "anySubjectType": { "type": "string", "minLength": 1}, 30 | "detectSubjectType": { "description": "Function that detects subject type (obj) => string" } 31 | }, 32 | "definitions": { 33 | "scopes": { 34 | "type": "array", 35 | "items": { 36 | "type": "object", 37 | "description": "PBAC/RBAC scopes list. See https://casl.js.org/v5/en/guide/define-rules", 38 | "required": ["action"], 39 | "properties": { 40 | "action": { "type": "string" }, 41 | "subject": { "type": "string" }, 42 | "conditions": { "type": "object" }, 43 | "inverted": { "type": "boolean" } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/plugin-dlock/__tests__/dlock-sentinel.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import { test } from 'node:test' 3 | import { Microfleet } from '@microfleet/core' 4 | import Bluebird from 'bluebird' 5 | 6 | test('@microfleet/plugin-dlock + sentinel', async (t) => { 7 | let microfleet: Microfleet 8 | 9 | t.before(async () => { 10 | microfleet = new Microfleet({ 11 | name: 'tester', 12 | plugins: ['logger', 'validator', 'redis-sentinel', 'dlock'], 13 | redis: { 14 | name: 'mservice', 15 | sentinels: [ 16 | { host: 'redis-sentinel', port: 26379 }, 17 | ], 18 | }, 19 | dlock: { 20 | pubsubChannel: 'test-dlock', 21 | lockPrefix: 'perchik', 22 | }, 23 | }) 24 | }) 25 | 26 | t.after(async () => { 27 | if (microfleet) await microfleet.close() 28 | }) 29 | 30 | await t.test('microfleet connects', async () => { 31 | await microfleet.connect() 32 | assert(microfleet.dlock) 33 | }) 34 | 35 | await t.test('able to acquire lock', async () => { 36 | await Bluebird.using(microfleet.dlock.acquireLock('single lock'), async (lock) => { 37 | microfleet.log.info('lock acquired') 38 | lock.extend() 39 | }) 40 | }) 41 | 42 | await t.test('able to acquire multi-lock', async () => { 43 | await Bluebird.using(microfleet.dlock.acquireLock('single lock', 'second key'), async (lock) => { 44 | microfleet.log.info('lock acquired') 45 | lock.extend() 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /packages/plugin-kafka-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microfleet/plugin-kafka-types", 3 | "description": "Types for Apache Kafka adapter for microfleet", 4 | "version": "8.0.0", 5 | "main": "./index.d.ts", 6 | "types": "./index.d.ts", 7 | "scripts": { 8 | "lint": "eslint './**/*.ts'", 9 | "test": "npm run lint; tsc", 10 | "build": "tsc -b tsconfig.json", 11 | "clean": "rimraf ./lib *.tsbuildinfo" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/microfleet/core.git", 16 | "directory": "packages/plugin-kafka-types" 17 | }, 18 | "author": "Vitaly Aminev ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/microfleet/core/issues" 22 | }, 23 | "homepage": "https://github.com/microfleet/core#readme", 24 | "files": [ 25 | "index.d.ts" 26 | ], 27 | "devDependencies": { 28 | "@makeomatic/deploy": "^13.1.0", 29 | "@release-it/conventional-changelog": "^10.0.0", 30 | "@types/node": "^22.10.10", 31 | "@typescript-eslint/eslint-plugin": "^7.0.0", 32 | "@typescript-eslint/parser": "^7.0.0", 33 | "cross-env": "^7.0.3", 34 | "eslint": "^8.57.1", 35 | "eslint-config-makeomatic": "^6.0.0", 36 | "eslint-plugin-import": "^2.31.0", 37 | "eslint-plugin-unicorn": "^56.0.1", 38 | "release-it": "^18.1.2", 39 | "rimraf": "^6.0.1", 40 | "sinon": "^19.0.2", 41 | "typescript": "~5.7.3" 42 | }, 43 | "dependencies": { 44 | "@makeomatic/node-rdkafka": "^2.18.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/core-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microfleet/core-types", 3 | "version": "7.0.0", 4 | "description": "@microfleet/core Types", 5 | "main": "./index.d.ts", 6 | "types": "./index.d.ts", 7 | "scripts": { 8 | "test": "tsc -b ./tsconfig.build.json", 9 | "build": "tsc -b ./tsconfig.build.json", 10 | "clean": "rimraf ./lib *.tsbuildinfo" 11 | }, 12 | "files": [ 13 | "index.d.ts", 14 | "private.d.ts" 15 | ], 16 | "repository": { 17 | "directory": "packages/core-types", 18 | "type": "git", 19 | "url": "https://github.com/microfleet/core.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/micofleet/core/issues" 23 | }, 24 | "dependencies": { 25 | "@microfleet/utils": "workspace:^", 26 | "bluebird": "^3.7.2", 27 | "bluebird-retry": "^0.11.0", 28 | "eventemitter3": "^5.0.1" 29 | }, 30 | "devDependencies": { 31 | "@makeomatic/deploy": "^13.1.0", 32 | "@release-it/conventional-changelog": "^10.0.0", 33 | "@types/bluebird": "^3.5.42", 34 | "@types/bluebird-retry": "^0.11.8", 35 | "@types/node": "^22.10.10", 36 | "@typescript-eslint/eslint-plugin": "^7.0.0", 37 | "@typescript-eslint/parser": "^7.0.0", 38 | "cross-env": "^7.0.3", 39 | "eslint": "^8.57.1", 40 | "eslint-config-makeomatic": "^6.0.0", 41 | "eslint-plugin-import": "^2.31.0", 42 | "eslint-plugin-unicorn": "^56.0.1", 43 | "release-it": "^18.1.2", 44 | "rimraf": "^6.0.1", 45 | "sinon": "^19.0.2", 46 | "typescript": "~5.7.3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/plugin-prometheus/__tests__/prometheus.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import { test } from 'node:test' 3 | import { fetch, getGlobalDispatcher } from 'undici' 4 | import { Microfleet } from '@microfleet/core' 5 | 6 | 7 | test('prometheus plugin', async (t) => { 8 | let service: Microfleet 9 | 10 | t.afterEach(() => ( 11 | service && service.close() 12 | )) 13 | 14 | await t.test('should be able to throw error if plugin is not included', async () => { 15 | service = new Microfleet({ name: 'micro', plugins: [] }) 16 | await service.register() 17 | assert(!service.prometheus) 18 | }) 19 | 20 | await t.test('should be able to initialize', async () => { 21 | service = new Microfleet({ 22 | name: 'tester', 23 | plugins: ['logger', 'validator', 'prometheus'], 24 | }) 25 | 26 | await service.register() 27 | assert.ok(service.prometheus) 28 | }) 29 | 30 | await t.test('should be able to provide metrics', async () => { 31 | service = new Microfleet({ 32 | name: 'tester', 33 | plugins: ['logger', 'validator', 'prometheus'], 34 | }) 35 | 36 | await service.connect() 37 | 38 | const res = await fetch('http://0.0.0.0:9102/metrics') 39 | const text = await res.text() 40 | assert.ok(text.includes('TYPE application_version_info gauge')) 41 | assert.ok(text.includes('TYPE microfleet_request_duration_milliseconds histogram')) 42 | }) 43 | 44 | t.after(async () => { 45 | await getGlobalDispatcher().close() 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /packages/plugin-router/src/extensions/validate/transport-options.ts: -------------------------------------------------------------------------------- 1 | import { NotSupportedError } from 'common-errors' 2 | 3 | import { Lifecycle } from '../../lifecycle/index' 4 | import { ServiceRequest } from '../../types/router' 5 | import { RequestDataKey, ActionTransport } from '../../router' 6 | 7 | declare module '../../types/router' { 8 | interface ServiceAction { 9 | transportsOptions?: TransportsOptions 10 | } 11 | } 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 14 | export interface TransportsOptions extends Partial> {} 15 | 16 | export type TransportsTransportOptions = { 17 | methods?: (keyof typeof RequestDataKey)[] 18 | } 19 | 20 | async function postRequest(request: ServiceRequest): Promise { 21 | if (request.error) { 22 | return 23 | } 24 | 25 | const { method, transport, action: { transportsOptions } } = request 26 | 27 | if (transportsOptions === undefined) { 28 | return 29 | } 30 | 31 | const transportOptions = transportsOptions[transport] 32 | 33 | if (transportOptions === undefined) { 34 | return 35 | } 36 | 37 | const { methods } = transportOptions 38 | 39 | if (methods === undefined) { 40 | return 41 | } 42 | 43 | if (!methods.includes(method)) { 44 | throw new NotSupportedError(`Route ${request.route} method ${method}`) 45 | } 46 | 47 | return 48 | } 49 | 50 | export default [{ 51 | handler: postRequest, 52 | point: Lifecycle.hooks.postRequest, 53 | }] 54 | -------------------------------------------------------------------------------- /ci/aggregate-release.mjs: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'release-it' 2 | import fs from 'node:fs/promises' 3 | import { resolve } from 'node:path' 4 | 5 | const staged = resolve(import.meta.dirname, './staged') 6 | 7 | export default class AggregateRelease extends Plugin { 8 | getIncrementedVersion() { 9 | // if we are at this stage then previous plugin 10 | // did not return version and we are at the same version as before 11 | return this.config.getContext('latestVersion') 12 | } 13 | 14 | getIncrementedVersionCI(options) { 15 | return this.getIncrementedVersion(options) 16 | } 17 | 18 | async beforeBump() { 19 | const { name } = this.config.getContext() 20 | const kFilename = resolve(staged, `${name.replace(/\//g, '__')}.json`) 21 | 22 | this.setContext({ staged: kFilename }) 23 | const { isDryRun } = this.config 24 | if (!isDryRun) { 25 | await fs.unlink(kFilename).catch(() => {/* noop */}) 26 | } 27 | } 28 | 29 | async bump(version) { 30 | const { name, latestVersion, changelog } = this.config.getContext() 31 | this.debug({ name, latestVersion, version }) 32 | 33 | if (version === latestVersion || !version) { 34 | this.debug(`skipping ${name} release`) 35 | return 36 | } 37 | 38 | const release = JSON.stringify({ 39 | name, 40 | latestVersion, 41 | version, 42 | changelog 43 | }) 44 | 45 | const { isDryRun } = this.config 46 | if (!isDryRun) { 47 | await fs.writeFile(this.getContext('staged'), release) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/src/plugins/fastify.ts: -------------------------------------------------------------------------------- 1 | import type * as _ from '../types' 2 | 3 | import { strict } from 'node:assert' 4 | import type { FastifyInstance } from 'fastify' 5 | 6 | import { Config, CredentialsStore, SignedRequest } from '../signed-request' 7 | 8 | declare module 'fastify' { 9 | interface FastifyRequest { 10 | signature: SignedRequest | null 11 | } 12 | } 13 | 14 | export const FastifyRequestSignaturePlugin = (store: CredentialsStore, config: Config = {}) => { 15 | // eslint-disable-next-line @typescript-eslint/no-var-requires 16 | const fp = require('fastify-plugin') 17 | 18 | return fp(async function fastifyRequestSignaturePlugin(instance: FastifyInstance) { 19 | strict(store, 'Credential store is required') 20 | 21 | instance.decorateRequest('signature', null) 22 | 23 | instance.addHook('onRequest', async (req) => { 24 | if (SignedRequest.isSignedRequest(req.raw.headers)) { 25 | req.signature = new SignedRequest(config, store) 26 | } 27 | }) 28 | 29 | instance.addHook('preParsing', async (req, _reply, payload) => { 30 | if (req.signature) { 31 | const { signature } = req 32 | await signature.initialize(req.raw) 33 | signature.verifyHeaders() 34 | 35 | payload.on('data', (chunk: any) => { signature.appendPayload(chunk) }) 36 | } 37 | 38 | return payload 39 | }) 40 | 41 | instance.addHook('preValidation', async (req) => { 42 | if (req.signature) { 43 | req.signature.verifyPayload() 44 | } 45 | }) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /packages/plugin-dlock/__tests__/dlock-cluster.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import { test } from 'node:test' 3 | import { Microfleet } from '@microfleet/core' 4 | import Bluebird from 'bluebird' 5 | 6 | test('@microfleet/plugin-dlock + cluster', async (t) => { 7 | let microfleet: Microfleet 8 | 9 | t.before(async () => { 10 | microfleet = new Microfleet({ 11 | name: 'tester', 12 | plugins: ['logger', 'validator', 'redis-cluster', 'dlock'], 13 | redis: { 14 | hosts: [ 15 | { host: 'redis-cluster', port: 7000 }, 16 | { host: 'redis-cluster', port: 7001 }, 17 | { host: 'redis-cluster', port: 7002 }, 18 | ], 19 | }, 20 | dlock: { 21 | pubsubChannel: 'test-dlock', 22 | lockPrefix: 'perchik', 23 | }, 24 | }) 25 | }) 26 | 27 | t.after(async () => { 28 | if (microfleet) await microfleet.close() 29 | }) 30 | 31 | await t.test('microfleet connects', async () => { 32 | await microfleet.connect() 33 | assert(microfleet.dlock) 34 | }) 35 | 36 | await t.test('able to acquire lock', async () => { 37 | await Bluebird.using(microfleet.dlock.acquireLock('single lock'), async (lock) => { 38 | microfleet.log.info('lock acquired') 39 | lock.extend() 40 | }) 41 | }) 42 | 43 | await t.test('able to acquire multi-lock', async () => { 44 | await Bluebird.using(microfleet.dlock.acquireLock('single lock', 'second key'), async (lock) => { 45 | microfleet.log.info('lock acquired') 46 | lock.extend() 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /packages/utils/src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns first arg that is passed to the function 3 | */ 4 | export const identity = (arg: T): T => arg 5 | 6 | /** 7 | * Connector property. 8 | */ 9 | export const CONNECTORS_PROPERTY = 'connectors' 10 | 11 | /** 12 | * Destructor property. 13 | */ 14 | export const DESTRUCTORS_PROPERTY = 'destructors' 15 | 16 | /** 17 | * Health checks property 18 | */ 19 | export const HEALTH_CHECKS_PROPERTY = 'healthChecks' 20 | 21 | /** 22 | * Constants with connect types to control order of service bootstrap 23 | */ 24 | export const ConnectorsTypes = { 25 | application: 'application', 26 | database: 'database', 27 | essential: 'essential', 28 | migration: 'migration', 29 | transport: 'transport', 30 | } as const 31 | 32 | /** 33 | * Plugin Types 34 | */ 35 | export const PluginTypes = { 36 | application: 'application', 37 | database: 'database', 38 | essential: 'essential', 39 | transport: 'transport', 40 | } as const 41 | 42 | /** 43 | * Default priority of connectors during bootstrap 44 | */ 45 | export const ConnectorsPriority = [ 46 | ConnectorsTypes.essential, 47 | ConnectorsTypes.database, 48 | ConnectorsTypes.migration, 49 | ConnectorsTypes.transport, 50 | ConnectorsTypes.application, 51 | ] 52 | 53 | /** 54 | * Plugin boot priority 55 | */ 56 | export const PluginsPriority = [ 57 | PluginTypes.essential, 58 | PluginTypes.database, 59 | PluginTypes.transport, 60 | PluginTypes.application, 61 | ] 62 | 63 | export const PLUGIN_STATUS_OK = 'ok' as const 64 | export const PLUGIN_STATUS_FAIL = 'fail' as const 65 | -------------------------------------------------------------------------------- /packages/plugin-router-socketio/__tests__/suites/router-socketio.spec.ts: -------------------------------------------------------------------------------- 1 | import { strictEqual } from 'node:assert' 2 | import { describe, it, before, after } from 'node:test' 3 | import { Microfleet } from '@microfleet/core' 4 | import { resolve } from 'node:path' 5 | import { io as SocketIOClient } from 'socket.io-client' 6 | import { once } from 'node:events' 7 | 8 | describe('@microfleet/plugin-router-socketio', async () => { 9 | const service = new Microfleet({ 10 | name: 'tester', 11 | plugins: [ 12 | 'validator', 13 | 'logger', 14 | 'router', 15 | 'socketio', 16 | 'hapi', 17 | 'router-socketio' 18 | ], 19 | hapi: { 20 | attachSocketio: true, 21 | server: { 22 | port: 17003, 23 | }, 24 | }, 25 | router: { 26 | routes: { 27 | directory: resolve(__dirname, '../artifacts/actions'), 28 | }, 29 | }, 30 | }) 31 | 32 | before(async () => { 33 | await service.connect() 34 | }) 35 | 36 | after(async () => { 37 | await service.close() 38 | }) 39 | 40 | it('should be able to attach socket.io transport to router (using http plugin)', async () => { 41 | const client = SocketIOClient('http://0.0.0.0:17003') 42 | // @ts-expect-error invalid typing 43 | await once(client, 'connect') 44 | await new Promise((resolve) => { 45 | client.emit('echo', { message: 'foo' }, (error: any, response: any) => { 46 | strictEqual(error, null) 47 | strictEqual(response.message, 'foo') 48 | client.close() 49 | resolve() 50 | }) 51 | }) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /packages/plugin-couchdb/__tests__/couchdb.spec.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'node:test' 2 | import assert from 'node:assert/strict' 3 | import { Microfleet } from '@microfleet/core' 4 | 5 | let service: Microfleet 6 | let couchdb: Microfleet['couchdb'] 7 | 8 | test('couchdb', async (t) => { 9 | t.after(async () => { 10 | if (service) await service.close() 11 | }) 12 | 13 | await t.test('should be able to initialize', async () => { 14 | service = new Microfleet({ 15 | name: 'tester', 16 | plugins: ['logger', 'validator', 'couchdb'], 17 | couchdb: { 18 | connection: 'http://admin:admin@couchdb:5984', 19 | database: 'sample', 20 | indexDefinitions: [{ 21 | index: { 22 | fields: [{ group: 'desc' }, { date: 'desc' }, { boring: 'desc' }], 23 | }, 24 | name: 'test', 25 | ddoc: 'immutable', 26 | }], 27 | }, 28 | }) 29 | }) 30 | 31 | await t.test('should be able to connect', async () => { 32 | await service.connect() 33 | assert.ok(service.couchdb) 34 | couchdb = service.couchdb 35 | }) 36 | 37 | await t.test('should be able to create document', async () => { 38 | const result = await couchdb?.insert({ 39 | _id: 'happy', 40 | ami: true, 41 | }) 42 | 43 | assert.equal(result?.ok, true) 44 | assert.equal(result?.id, 'happy') 45 | }) 46 | 47 | await t.test('should be able to disconnect', async () => { 48 | await service.close() 49 | }) 50 | 51 | await t.test('should be able to connect again', async () => { 52 | await service.connect() 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /packages/plugin-kafka/src/custom/rdkafka-extra.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | /** 3 | * * Promisify prototypes. 4 | * * Add async defs. 5 | * * Provide access for hidden classes 6 | */ 7 | import { promisifyAll } from 'bluebird' 8 | import { 9 | KafkaConsumer, 10 | Producer as KafkaProducer, 11 | ConsumerStream, 12 | ProducerStream, 13 | LibrdKafkaError as LibrdKafkaError, 14 | Client, 15 | KafkaClientEvents, 16 | } from '@makeomatic/node-rdkafka' 17 | 18 | export * from '@makeomatic/node-rdkafka' 19 | 20 | /** 21 | * Library hides ConsumerStream or ProducerStream when using typescript 22 | * https://blizzard.github.io/node-rdkafka/current/KafkaConsumerStream.html 23 | * But but we want to create Producer or Consumer before streams 24 | */ 25 | export const KafkaProducerStream = require('@makeomatic/node-rdkafka/lib/producer-stream') as ProducerStream 26 | export const KafkaConsumerStream = require('@makeomatic/node-rdkafka/lib/kafka-consumer-stream') as ConsumerStream 27 | export const KafkaClient = require('@makeomatic/node-rdkafka/lib/client') as Client 28 | export const LibrdKafkaErrorClass = require('@makeomatic/node-rdkafka/lib/error') as LibrdKafkaError 29 | 30 | export type KafkaProducerStream = ProducerStream 31 | export type KafkaConsumerStream = ConsumerStream 32 | export type LibrdKafkaErrorClass = LibrdKafkaError 33 | export type KafkaClient = Client 34 | 35 | promisifyAll(KafkaProducerStream.prototype) 36 | promisifyAll(KafkaConsumerStream.prototype) 37 | promisifyAll(KafkaConsumer.prototype) 38 | promisifyAll(KafkaProducer.prototype) 39 | -------------------------------------------------------------------------------- /packages/plugin-socketio/__tests__/socketio.spec.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'node:test' 2 | import { strictEqual, strict } from 'node:assert' 3 | import { Microfleet } from '@microfleet/core' 4 | import { Server as SocketIOStatic } from 'socket.io' 5 | import { Transport, Adapter } from 'ms-socket.io-adapter-amqp' 6 | 7 | test('plugin-socketio', async (t) => { 8 | await t.test('should not be able to create socket.io instance when plugin is not included', async () => { 9 | const service = new Microfleet({ name: 'tester', plugins: [] }) 10 | await service.register() 11 | strictEqual(service.socketio, undefined) 12 | }) 13 | 14 | await t.test('should create socket.io instance when plugin is included', async () => { 15 | const service = new Microfleet({ 16 | name: 'tester', 17 | plugins: ['validator', 'socketio'], 18 | }) 19 | 20 | await service.register() 21 | strictEqual(service.socketio instanceof SocketIOStatic, true) 22 | }) 23 | 24 | await t.test('should be able to set up AMQP adapter', async () => { 25 | const service = new Microfleet({ 26 | name: 'tester', 27 | plugins: ['validator', 'socketio'], 28 | socketio: { 29 | adapter: { 30 | name: 'amqp', 31 | options: { 32 | connection: { 33 | host: 'rabbitmq', 34 | port: 5672, 35 | }, 36 | }, 37 | }, 38 | }, 39 | }) 40 | 41 | await service.register() 42 | strict(service.socketio.sockets.adapter instanceof Adapter) 43 | strict(service.socketio.sockets.adapter.transport instanceof Transport) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /packages/plugin-knex/README.md: -------------------------------------------------------------------------------- 1 | # Microfleet Knex Plugin 2 | 3 | Adds Knex support to microfleet. This can be used to interact with \*SQL 4 | databases 5 | 6 | ## Install 7 | 8 | `yarn add @microfleet/plugin-knex` 9 | 10 | ## Configuration 11 | 12 | To make use of the plugin adjust microfleet configuration in the following way: 13 | 14 | ```ts 15 | interface Config { 16 | debug?: boolean; 17 | client?: string | typeof Client; 18 | dialect?: string; 19 | version?: string; 20 | connection?: 21 | | string 22 | | ConnectionConfig 23 | | MariaSqlConnectionConfig 24 | | MySqlConnectionConfig 25 | | MsSqlConnectionConfig 26 | | OracleDbConnectionConfig 27 | | Sqlite3ConnectionConfig 28 | | SocketConnectionConfig; 29 | pool?: PoolConfig; 30 | migrations?: MigratorConfig; 31 | postProcessResponse?: (result: any, queryContext: any) => any; 32 | wrapIdentifier?: ( 33 | value: string, 34 | origImpl: (value: string) => string, 35 | queryContext: any 36 | ) => string; 37 | seeds?: SeedsConfig; 38 | acquireConnectionTimeout?: number; 39 | useNullAsDefault?: boolean; 40 | searchPath?: string | string[]; 41 | asyncStackTraces?: boolean; 42 | log?: Logger; 43 | } 44 | 45 | // make sure to add this to the list of loadable plugins 46 | exports.plugins = [ 47 | ..., 48 | 'knex', 49 | ... 50 | ] 51 | 52 | // knex configuration 53 | exports.knex: Config = { ... } 54 | ``` 55 | 56 | ## Interface 57 | 58 | Microfleet Knex Plugin extends service interface with the following methods: 59 | 60 | ### service.knex: Knex 61 | 62 | Initialized instance. Look at the docs here - http://knexjs.org/ 63 | -------------------------------------------------------------------------------- /packages/plugin-prometheus/src/metrics.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert' 2 | import type { Microfleet, MserviceError } from '@microfleet/core-types' 3 | 4 | import { Lifecycle, LifecycleExtension, Extensions } from '@microfleet/plugin-router' 5 | import type { ServiceRequest } from '@microfleet/plugin-router' 6 | 7 | function extractStatusCode(error: MserviceError): number { 8 | if (!error) { 9 | return 200 10 | } 11 | 12 | switch (error.name) { 13 | case 'AuthenticationRequiredError': 14 | return 400 15 | case 'ValidationError': 16 | return 401 17 | case 'NotPermittedError': 18 | return 403 19 | case 'NotFoundError': 20 | return 404 21 | case 'NotSupportedError': 22 | return 405 23 | default: 24 | return error.statusCode || 500 25 | } 26 | } 27 | 28 | export const metricObservability: LifecycleExtension[] = [ 29 | Extensions.initTimingExtension, 30 | { 31 | point: Lifecycle.hooks.postResponse, 32 | async handler(this: Microfleet, request: ServiceRequest): Promise { 33 | assert(request.requestStarted !== undefined) 34 | 35 | const latency = Extensions.hrTimeDurationInMs(request.requestStarted, request.requestEnded || process.hrtime()) || 0 36 | const labels = { 37 | method: request.method, 38 | // NOTE: route empty in case of 404 - should we extract real path from the `transportRequest` ? 39 | route: request.route, 40 | transport: request.transport, 41 | statusCode: extractStatusCode(request.error), 42 | } 43 | 44 | this.metricMicrofleetDuration.observe(labels, latency) 45 | }, 46 | }, 47 | ] 48 | -------------------------------------------------------------------------------- /packages/plugin-router/src/routes.ts: -------------------------------------------------------------------------------- 1 | import type { ReplyGenericInterface, RequestGenericInterface, ServiceAction } from './types/router' 2 | 3 | export type RouteName = string 4 | export type TransportName = string 5 | export type RoutesCollection< 6 | DefaultRequest extends RequestGenericInterface = RequestGenericInterface, 7 | DefaultResponse extends ReplyGenericInterface = ReplyGenericInterface 8 | > = Map> 9 | export type RoutesGroupedByTransport = Map 10 | export default class Routes { 11 | protected groupedByTransport: RoutesGroupedByTransport = new Map() 12 | 13 | get< 14 | DefaultRequest extends RequestGenericInterface = RequestGenericInterface, 15 | DefaultResponse extends ReplyGenericInterface = ReplyGenericInterface 16 | >(transport: TransportName): RoutesCollection { 17 | const { groupedByTransport } = this 18 | const routes: RoutesCollection = groupedByTransport.get(transport) || new Map() 19 | 20 | groupedByTransport.set(transport, routes) 21 | 22 | return routes 23 | } 24 | 25 | getAction(transport: TransportName, route: RouteName): ServiceAction | undefined { 26 | return this.groupedByTransport.get(transport)?.get(route) 27 | } 28 | 29 | add(transport: TransportName, route: RouteName, handler: ServiceAction): void { 30 | const { groupedByTransport } = this 31 | const routes: RoutesCollection = groupedByTransport.get(transport) || new Map() 32 | 33 | routes.set(route, handler) 34 | groupedByTransport.set(transport, routes) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/plugin-signed-request/src/types.ts: -------------------------------------------------------------------------------- 1 | import { AssertionError } from 'node:assert' 2 | import type { KeyLike, Verify, Hmac } from 'node:crypto' 3 | import { HttpSignature } from 'http-signature' 4 | 5 | import type * as _ from '@microfleet/plugin-validator' 6 | import type * as __ from '@microfleet/plugin-router' 7 | import { SignedRequest } from './signed-request' 8 | 9 | export type Config = { 10 | headers?: string[]; 11 | clockSkew?: number; 12 | } 13 | 14 | export interface CredentialsStore { 15 | getKey(key: string): Promise 16 | getCredentials = any>(key: string, ...args: any[]): Promise 17 | } 18 | 19 | export type RequestInfo = { 20 | headers: Record; 21 | hash: string; 22 | algorithm: string; 23 | parsedSignature: HttpSignature; 24 | payloadSignature: Verify | Hmac; 25 | signKey: KeyLike; 26 | } 27 | 28 | export function assertRequestInitialized(req?: RequestInfo): asserts req is RequestInfo { 29 | if (!req) { 30 | throw new AssertionError({ 31 | message: 'req should be initialized' 32 | }) 33 | } 34 | } 35 | 36 | declare module '@microfleet/core-types' { 37 | interface Microfleet { 38 | credentialsStore: CredentialsStore 39 | } 40 | 41 | interface ConfigurationOptional { 42 | signedRequest: Config 43 | } 44 | } 45 | 46 | declare module '@microfleet/plugin-router' { 47 | interface ServiceRequest { 48 | signature?: SignedRequest 49 | } 50 | } 51 | 52 | declare module '@hapi/hapi' { 53 | export interface PluginsStates { 54 | signature?: SignedRequest, 55 | } 56 | } 57 | 58 | declare module 'restify' { 59 | export interface Request { 60 | signature?: SignedRequest, 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microfleet/utils", 3 | "description": "utils for microfleet", 4 | "main": "./lib/index.js", 5 | "types": "./lib/index.d.ts", 6 | "version": "7.0.0", 7 | "scripts": { 8 | "lint": "eslint './src/**/*.ts'", 9 | "clean": "rimraf ./lib *.tsbuildinfo", 10 | "build": "tsc -b ./tsconfig.build.json", 11 | "test": "true" 12 | }, 13 | "tags": [ 14 | "microservice", 15 | "microfleet", 16 | "utils" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/microfleet/core.git", 21 | "directory": "packages/utils" 22 | }, 23 | "author": "Vitaly Aminev ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/microfleet/core/issues" 27 | }, 28 | "homepage": "https://github.com/microfleet/core#readme", 29 | "dependencies": { 30 | "lodash": "^4.17.21", 31 | "read-package-up": "^11.0.0" 32 | }, 33 | "devDependencies": { 34 | "@makeomatic/deploy": "^13.1.0", 35 | "@release-it/conventional-changelog": "^10.0.0", 36 | "@types/lodash": "^4.17.14", 37 | "@types/node": "^22.10.10", 38 | "@typescript-eslint/eslint-plugin": "^7.0.0", 39 | "@typescript-eslint/parser": "^7.0.0", 40 | "cross-env": "^7.0.3", 41 | "eslint": "^8.57.1", 42 | "eslint-config-makeomatic": "^6.0.0", 43 | "eslint-plugin-import": "^2.31.0", 44 | "eslint-plugin-unicorn": "^56.0.1", 45 | "release-it": "^18.1.2", 46 | "rimraf": "^6.0.1", 47 | "sinon": "^19.0.2", 48 | "typescript": "~5.7.3" 49 | }, 50 | "engines": { 51 | "node": ">= 22.12.0", 52 | "npm": ">= 8.0.0" 53 | }, 54 | "files": [ 55 | "lib/", 56 | "src/" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /packages/plugin-router-amqp/schemas/router-amqp.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "router-amqp", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "required": ["prefix", "retry"], 6 | "properties": { 7 | "prefix": { 8 | "description": "Prefix for routes", 9 | "type": "string", 10 | "default": "", 11 | "maxLength": 120 12 | }, 13 | "multiAckEvery": { 14 | "type": "number", 15 | "minimum": 0 16 | }, 17 | "multiAckAfter": { 18 | "type": "number", 19 | "description": "multi-ack message at least that often in miliseconds", 20 | "minimum": 100 21 | }, 22 | "autoDeserialize": { 23 | "type": "boolean", 24 | "default": true 25 | }, 26 | "retry": { 27 | "type": "object", 28 | "default": {}, 29 | "description": "if present and enabled sets custom QoS onComplete function with delayed retry", 30 | "required": ["enabled"], 31 | "properties": { 32 | "enabled": { 33 | "type": "boolean", 34 | "default": false 35 | }, 36 | "min": { 37 | "type": "integer", 38 | "minimum": 0, 39 | "default": 1000 40 | }, 41 | "max": { 42 | "type": "integer", 43 | "minimum": 0, 44 | "default": 5000 45 | }, 46 | "maxRetries": { 47 | "type": "integer", 48 | "minimum": 0, 49 | "default": 10 50 | }, 51 | "factor": { 52 | "type": "number", 53 | "minimum": 1, 54 | "default": 1.2 55 | }, 56 | "queue": { 57 | "description": "custom DLX queue name", 58 | "type": "string" 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.release-it.root.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { globSync } = require('glob') 3 | 4 | /* THIS IS A QUICK FALLBACK WHEN THERE ARE NO RELEASES */ 5 | const staged = globSync('./ci/staged/*.json', { cwd: __dirname }) 6 | if (staged.length === 0) { 7 | module.exports = { 8 | git: false, 9 | npm: false, 10 | github: false, 11 | 'disable-metrics': true, 12 | } 13 | return 14 | } 15 | 16 | module.exports = { 17 | git: { 18 | requireCleanWorkingDir: false, 19 | tag: true, 20 | commit: true, 21 | requireBranch: 'master', 22 | commitMessage: 'chore(release): v${version} -- updated ${affectedModules} modules [skip ci]\n\n${updateLog}', 23 | tagName: 'microfleet@v${version}', 24 | tagAnnotation: 'Release ${stagedModule.version}\n\n${stagedModule.changelog}', 25 | pnpm: true, 26 | changelog: true, 27 | bump: true, 28 | infile: 'CHANGELOG.md', 29 | }, 30 | npm: { 31 | publish: false, 32 | }, 33 | github: false, 34 | plugins: { 35 | '@release-it/conventional-changelog': { 36 | infile: false, 37 | preset: { 38 | name: 'angular', 39 | parserOpts: { 40 | noteKeywords: ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"] 41 | } 42 | } 43 | }, 44 | [path.resolve(__dirname, './ci/pnpm-git.mjs')]: {}, 45 | [require.resolve('./node_modules/release-it/lib/plugin/github/GitHub.js')]: { 46 | release: true, 47 | releaseName: "Release ${version}", 48 | releaseNotes: null, 49 | preRelease: false, 50 | draft: false, 51 | tokenRef: "GITHUB_TOKEN", 52 | assets: null, 53 | host: null, 54 | timeout: 0, 55 | proxy: null, 56 | skipChecks: false 57 | }, 58 | }, 59 | 'disable-metrics': true, 60 | } 61 | -------------------------------------------------------------------------------- /packages/plugin-consul/src/patch.ts: -------------------------------------------------------------------------------- 1 | import consul from 'consul' 2 | // @ts-expect-error no types 3 | import errors from 'consul/lib/errors' 4 | // @ts-expect-error no types 5 | import utils from 'consul/lib/utils' 6 | 7 | // magic flag 0x2ddccbc058a50c18 8 | const LOCK_FLAG_VALUE = '3304740253564472344' 9 | 10 | /** 11 | * Wait for non-locked resource 12 | */ 13 | consul.Lock.prototype._wait = function (ctx: any) { 14 | const retry = () => { 15 | utils.setTimeoutContext(() => { 16 | this._run(ctx) 17 | }, ctx, ctx.lockRetryTime) 18 | } 19 | 20 | const opts = utils.defaults({ 21 | key: ctx.key, 22 | wait: ctx.lockWaitTime, 23 | timeout: ctx.lockWaitTimeout, 24 | ctx: ctx, 25 | index: ctx.index, 26 | }, this._defaults, this.consul._defaults) 27 | 28 | this.consul.kv.get(opts, (err: any, data: any, res: any) => { 29 | if (err) return this._end(ctx, err, res) 30 | 31 | if (data) { 32 | // we try to use the same magic number as consul/api in an attempt to be 33 | // compatible 34 | if (data.Flags !== +LOCK_FLAG_VALUE) { 35 | err = errors.Validation('consul: lock: existing key does not match lock use') 36 | return this._end(ctx, err, res) 37 | } 38 | 39 | const newIndex = res.headers['x-consul-index'] 40 | if (utils.hasIndexChanged(newIndex, ctx.index)) ctx.index = newIndex 41 | 42 | if (data.Session && (data.Session !== ctx.session.id)) { 43 | this.emit('retry', { leader: data.Session }) 44 | return retry() 45 | } 46 | } else if (res.statusCode !== 404) { 47 | return this._end(ctx, new Error('consul: lock: error getting key'), res) 48 | } 49 | 50 | ctx.state = 'acquire' 51 | 52 | this._run(ctx) 53 | }) 54 | } 55 | 56 | export default consul 57 | -------------------------------------------------------------------------------- /packages/core/docs/reference/plugins/redis/cluster.md: -------------------------------------------------------------------------------- 1 | # Redis Cluster Plugin 2 | The plugin allows connecting to Redis Server working in Cluster mode. 3 | Internals of this plugin heavily lay on [`ioredis`](https://github.com/luin/ioredis) package with [`BluebirdJs`](http://bluebirdjs.com/) as default Promise engine. 4 | 5 | ## Dependencies 6 | NPM Packages: 7 | * ioredis 8 | * Bluebird 9 | 10 | Other plugins: 11 | * [Validator plugin](../validator.md) 12 | 13 | ## Methods 14 | | Method | Description | 15 | |--------|-------------| 16 | | `attach(service: Microfleet, opts = {})` | Register plugin for Microfleet `service` with provided `options`.| 17 | 18 | ## Lifecycle Methods 19 | | Method | Description | 20 | |--------|--| 21 | | `connect()`| Initiates Redis Cluster Connection and starts plugin. | 22 | | `status()` | Get plugin health status. | 23 | | `close()` | Disconnects from Redis Cluster and deregisters plugin from Microfleet service. | 24 | 25 | 26 | ## Events 27 | | Event name | When | 28 | |------------|-------| 29 | | `plugin:connect:redisCluster` | Plugin started and connected to Redis Successfully. | 30 | | `plugin:close:redisCluster` | Plugin closed connection to the Redis . | 31 | 32 | ## Configuration 33 | | Option | Type | Description | 34 | |--------|------|-------------| 35 | | `options` | Object | Configuration options passed to `ioredis` connect method. For further information see [Cluster connection options](https://github.com/luin/ioredis/blob/master/API.md#Cluster) and [Connection options](https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options).| 36 | | `hosts` | Array | List of Redis Cluster servers to connect. See [ioredis Custer](https://github.com/luin/ioredis#cluster).| 37 | | `luaScripts` | `String|String[]` | Path to LUA scripts directory. | 38 | -------------------------------------------------------------------------------- /packages/core/docs/reference/plugins/redis/sentinel.md: -------------------------------------------------------------------------------- 1 | # Redis Sentinel Plugin 2 | The plugin allows connecting to Redis Server working with Sentinel. 3 | Internals of this plugin heavily lay on [`ioredis`](https://github.com/luin/ioredis) package with [`BluebirdJs`](http://bluebirdjs.com) as default Promise engine. 4 | 5 | ## Dependencies 6 | NPM packages: 7 | * ioredis 8 | * Bluebird 9 | 10 | Plugin depends on other plugins: 11 | * [Validator plugin](../validator.md) 12 | 13 | ## Methods 14 | | Method | Description | 15 | |--------|-------------| 16 | | `attach(service: Microfleet, config = {})` | Register plugin for Microfleet `service` with provided `config`.| 17 | 18 | 19 | ## Lifecycle 20 | | Method | Description | 21 | |--------|-------------| 22 | | `connect()`| Initiates Redis Cluster Connection and starts plugin. | 23 | | `status()` | Get plugin health status. | 24 | | `close()` | Disconnects from Redis Cluster and deregisters plugin from Microfleet service. | 25 | 26 | 27 | ## Events 28 | | Event name | When | 29 | |------------|-------| 30 | | `plugin:connect:redisSentinel` | Plugin started and connected to Redis Successfully. | 31 | | `plugin:close:redisSentinel` | Plugin closed connection to the Redis. | 32 | 33 | ## Configuration 34 | | Option | Type | Description | 35 | |--------|------|-------------| 36 | | `options` | Object | Configuration options passed to `ioredis` connect method. For further information see [Connection options](https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options)| 37 | | `sentinels` | Array | List of Redis Sentinels to connect. See [ioredis Sentinel](https://github.com/luin/ioredis#sentinel) | 38 | | `name` | Group name of the Redis instances on Sentinel | 39 | | `luaScripts` | `String|String[]` | Path to LUA scripts directory. | 40 | -------------------------------------------------------------------------------- /packages/core/docs/reference/plugins/knex.md: -------------------------------------------------------------------------------- 1 | # Knex SQL Query builder plugin 2 | 3 | This plugin provides [Knex](http://knexjs.org/) initialization and injecting into Microfleet service. 4 | Knex provides universal interface for querying SQL database services. 5 | 6 | ## Dependencies 7 | 8 | NPM Packages: 9 | 10 | * `knex` 11 | * `bluebird-retry` 12 | 13 | Other plugins: 14 | 15 | * [Validator plugin](../validator.md) 16 | * [Logger plugin](../logger.md) 17 | 18 | ## Methods 19 | 20 | | Method | Description | 21 | | ---------------------------------------- | ----------------------------------------------------------------- | 22 | | `attach(service: Microfleet, opts = {})` | Register plugin for Microfleet `service` with provided `options`. | 23 | 24 | ## Lifecycle Methods 25 | 26 | | Method | Description | 27 | | ----------- | ----------------------------------------------------- | 28 | | `connect()` | Initiates Knex and connects to SQL server. | 29 | | `status()` | Get plugin health status. | 30 | | `close()` | Disconnects from SQL server and closes Knex instance. | 31 | 32 | ## Events 33 | 34 | | Event name | When | 35 | | --------------------- | ------------------------------------------------------- | 36 | | `plugin:connect:knex` | Plugin has started and connected to Redis successfully. | 37 | | `plugin:close:knex` | Plugin closed connection to Redis. | 38 | 39 | ## Configuration 40 | 41 | There are no plugin-specific configuration options. All options passed directly to the Knex. 42 | Please read [this page](http://knexjs.org/#Installation-client) to find Knex configuration options. 43 | -------------------------------------------------------------------------------- /packages/plugin-router/README.md: -------------------------------------------------------------------------------- 1 | # @microfleet/plugin-router 2 | 3 | Router for `Microfleet`. Adds ability to call specific functions - `ServiceAction` - using different transports (`amqp`, `http`, `socketio`). 4 | 5 | ## Usage 6 | 7 | ```json 8 | // schemas/add-cat.json 9 | { 10 | "$id": "create-cat", 11 | "type": "object", 12 | "required": ["name"], 13 | "additionalProperties": false, 14 | "properties": { 15 | "name": { 16 | "type": "string" 17 | }, 18 | "color": { 19 | "type": "string", 20 | "default": "gray" 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | ```js 27 | // src/actions/create-cat.ts 28 | import { Microfleet } from '@microfleet/core' 29 | import { ServiceRequest } from '@microfleet/plugin-router' 30 | 31 | const cats = [] 32 | 33 | export default async function createCatAction(this: Microfleet, request: ServiceRequest): Promise { 34 | const { color, name } = request.params 35 | const cat = { color, name } 36 | 37 | cats.push(cat) 38 | 39 | return `${name} was created!` 40 | } 41 | ``` 42 | 43 | ```js 44 | // src/index.js 45 | const service = new Microfleet({ 46 | name: 'cats api', 47 | plugins: [ 48 | 'validator', 49 | 'logger', 50 | 'hapi', 51 | 'router', 52 | 'router-hapi', 53 | ], 54 | }) 55 | 56 | await service.connect() 57 | ``` 58 | 59 | ```sh 60 | $ curl -XPOST "http://0.0.0.0:3000/create-cats" -d '{"name":"perchik","color":"pepper"}' -H "Content-Type: application/json" 61 | Perchik was created! 62 | ``` 63 | 64 | ## ServiceAction 65 | 66 | Handler for transport request. 67 | 68 | ### Signature 69 | 70 | ```js 71 | (this: Microfleet, request: ServiceRequest) => Promise 72 | ``` 73 | 74 | ### Additional properties 75 | 76 | @todo 77 | 78 | ## ServiceRequest 79 | 80 | Object with the following structure. 81 | 82 | ```js 83 | 84 | ``` 85 | 86 | ## Lifecycle 87 | 88 | ## Extensions 89 | --------------------------------------------------------------------------------