{"id":19584828,"url":"https://github.com/flex-development/mango","last_synced_at":"2026-05-09T19:37:13.753Z","repository":{"id":50899128,"uuid":"369340643","full_name":"flex-development/mango","owner":"flex-development","description":":mango: MongoDB-like API for in-memory object collections","archived":false,"fork":false,"pushed_at":"2023-03-06T02:34:45.000Z","size":1318,"stargazers_count":0,"open_issues_count":7,"forks_count":0,"subscribers_count":3,"default_branch":"next","last_synced_at":"2025-01-09T05:45:35.334Z","etag":null,"topics":["in-memory","in-memory-repo","mingo","mongodb","mongodb-query","mongodb-query-language","mongodb-url-query","qs-to-mongo","repository-pattern","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flex-development.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"docs/CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-20T21:19:42.000Z","updated_at":"2021-05-29T00:32:43.000Z","dependencies_parsed_at":"2025-01-10T07:31:27.894Z","dependency_job_id":null,"html_url":"https://github.com/flex-development/mango","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flex-development%2Fmango","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flex-development%2Fmango/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flex-development%2Fmango/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flex-development%2Fmango/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flex-development","download_url":"https://codeload.github.com/flex-development/mango/tar.gz/refs/heads/next","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240858536,"owners_count":19868997,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["in-memory","in-memory-repo","mingo","mongodb","mongodb-query","mongodb-query-language","mongodb-url-query","qs-to-mongo","repository-pattern","typescript"],"created_at":"2024-11-11T07:50:00.025Z","updated_at":"2026-05-09T19:37:08.728Z","avatar_url":"https://github.com/flex-development.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# :mango: Mango\n\nMongoDB query plugin and repository API for in-memory object collections\n\n[![TypeScript](https://badgen.net/badge/-/typescript?icon=typescript\u0026label)](https://www.typescriptlang.org/)\n[![tested with jest](https://img.shields.io/badge/tested_with-jest-99424f.svg)](https://github.com/facebook/jest)\n\n## Overview\n\n[Getting Started](#getting-started)  \n[Installation](#installation)  \n[Usage](#usage)  \n[Built With](#built-with)  \n[Contributing](docs/CONTRIBUTING.md)\n\n## Getting Started\n\nMongoDB query plugin and repository API for in-memory object collections.\n\n- run aggregation pipelines\n- execute searches (with query criteria **and** URL queries)\n- parse and convert URL query objects and strings\n- perform CRUD operations on repositories\n- validate collection objects\n\n## Installation\n\n1. Create or edit an `.npmrc` file with the following information:\n\n   ```utf-8\n   @flex-development:registry=https://npm.pkg.github.com/\n   ```\n\n2. Add project to `dependencies`\n\n   ```zsh\n   yarn add @flex-development/mango # or npm i @flex-development/mango\n   ```\n\n## Usage\n\n[Configuration](#configuration)  \n[Mango Finder](#mango-finder)  \n[Mango Repository](#mango-repository)  \n[Mango Validator](#mango-validator)\n\n### Configuration\n\n#### Environment Variables\n\n- `DEBUG`: Toggle [debug][4] logs from the `mango` namespace\n- `DEBUG_COLORS`: Toggle [debug][4] log namespace colors\n\n#### Mingo\n\nThe `MangoFinder` and `MangoFinderAsync` plugins integrate with [mingo][5], a\nMongoDB query language for in-memory objects, to support aggregation pipelines\nand executing searches.\n\nOperators loaded by Mango can be viewed in the [config](src/config/mingo.ts)\nfile. If additional operators are needed, load them _before_\n[creating a new plugin](#creating-a-new-mango-plugin).\n\n#### TypeScript\n\nFor shorter import paths, TypeScript users can add the following aliases:\n\n```json\n{\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@mango\": [\"node_modules/@flex-development/mango/index\"],\n      \"@mango/*\": [\"node_modules/@flex-development/mango/*\"]\n    }\n  }\n}\n```\n\nThese aliases will be used in following code examples.\n\n### Mango Finder\n\nThe Mango Finder plugins allow users to run aggregation pipelines and execute\nsearches against in-memory object collections. Query documents using a URL\nquery, or search for them using a query criteria and options object.\n\n#### Plugin Documentation\n\n- [`AbstractMangoFinder`](src/abstracts/mango-finder.abstract.ts)\n- [`MangoFinderAsync`](src/plugins/mango-finder-async.plugin.ts)\n- [`MangoFinder`](src/plugins/mango-finder.plugin.ts)\n\n```typescript\n/**\n * `AbstractMangoFinder` plugin interface.\n *\n * Used to define class contract of `MangoFinder`, `MangoFinderAsync`, and\n * possible derivatives.\n *\n * See:\n *\n * - https://github.com/kofrasa/mingo\n * - https://github.com/fox1t/qs-to-mongo\n *\n * @template D - Document (collection object)\n * @template U - Name of document uid field\n * @template P - Search parameters (query criteria and options)\n * @template Q - Parsed URL query object\n *\n * @extends IAbstractMangoFinderBase\n */\nexport interface IAbstractMangoFinder\u003c\n  D extends ObjectPlain = ObjectUnknown,\n  U extends string = DUID,\n  P extends MangoSearchParams\u003cD\u003e = MangoSearchParams\u003cD\u003e,\n  Q extends MangoParsedUrlQuery\u003cD\u003e = MangoParsedUrlQuery\u003cD\u003e\n\u003e extends IAbstractMangoFinderBase\u003cD, U\u003e {\n  aggregate(\n    pipeline?: OneOrMany\u003cAggregationStages\u003cD\u003e\u003e\n  ): OrPromise\u003cAggregationPipelineResult\u003cD\u003e\u003e\n  find(params?: P): OrPromise\u003cDocumentPartial\u003cD, U\u003e[]\u003e\n  findByIds(uids?: UID[], params?: P): OrPromise\u003cDocumentPartial\u003cD, U\u003e[]\u003e\n  findOne(uid: UID, params?: P): OrPromise\u003cDocumentPartial\u003cD, U\u003e | null\u003e\n  findOneOrFail(uid: UID, params?: P): OrPromise\u003cDocumentPartial\u003cD, U\u003e\u003e\n  query(query?: Q | string): OrPromise\u003cDocumentPartial\u003cD, U\u003e[]\u003e\n  queryByIds(\n    uids?: UID[],\n    query?: Q | string\n  ): OrPromise\u003cDocumentPartial\u003cD, U\u003e[]\u003e\n  queryOne(\n    uid: UID,\n    query?: Q | string\n  ): OrPromise\u003cDocumentPartial\u003cD, U\u003e | null\u003e\n  queryOneOrFail(uid: UID, query?: Q | string): OrPromise\u003cDocumentPartial\u003cD, U\u003e\u003e\n  setCache(collection?: D[]): OrPromise\u003cMangoCacheFinder\u003cD\u003e\u003e\n  uid(): string\n}\n\n/**\n * Base `AbstractMangoFinder` plugin interface.\n *\n * Used to define properties of `MangoFinder`, `MangoFinderAsync`, and\n * possible derivatives.\n *\n * @template D - Document (collection object)\n * @template U - Name of document uid field\n */\nexport interface IAbstractMangoFinderBase\u003c\n  D extends ObjectPlain = ObjectUnknown,\n  U extends string = DUID\n\u003e {\n  readonly cache: Readonly\u003cMangoCacheFinder\u003cD\u003e\u003e\n  readonly logger: Debugger\n  readonly mingo: typeof mingo\n  readonly mparser: IMangoParser\u003cD\u003e\n  readonly options: MangoFinderOptions\u003cD, U\u003e\n}\n```\n\n#### Documents\n\nA document is an object from an in-memory collection. Each document should have\na unique identifier (uid).\n\nBy default, this value is assumed to map to the `id` field of each document, but\ncan be changed via the [plugin settings](#plugin-settings).\n\n```typescript\nimport type { MangoParsedUrlQuery, MangoSearchParams } from '@mango/types'\n\nexport interface IPerson {\n  email: string\n  first_name: string\n  last_name: string\n}\n\nexport type PersonUID = 'email'\nexport type PersonParams = MangoSearchParams\u003cIPerson\u003e\nexport type PersonQuery = MangoParsedUrlQuery\u003cIPerson\u003e\n```\n\n#### Creating a New Finder\n\nBoth the `MangoFinder` and `MangoFinderAsync` plugins accept an `options` object\nthats gets passed down to the [mingo][5] and [qs-to-mongo][6] modules.\n\nVia the options dto, you can:\n\n- set initial collection cache\n- set uid field for each document\n- set date fields and fields searchable by text\n\n```typescript\nimport { MangoFinder, MangoFinderAsync } from '@mango'\nimport type { MangoFinderOptionsDTO } from '@mango/dto'\n\nconst options: MangoFinderOptionsDTO\u003cIPerson, PersonUID\u003e = {\n  cache: {\n    collection: [\n      {\n        email: 'nmaxstead0@arizona.edu',\n        first_name: 'Nate',\n        last_name: 'Maxstead'\n      },\n      {\n        email: 'rbrisseau1@sohu.com',\n        first_name: 'Roland',\n        last_name: 'Brisseau'\n      },\n      {\n        email: 'ksmidmoor2@sphinn.com',\n        first_name: 'Kippar',\n        last_name: 'Smidmoor'\n      },\n      {\n        email: 'gdurnford3@360.cn',\n        first_name: 'Godfree',\n        last_name: 'Durnford'\n      },\n      {\n        email: 'mfauguel4@webnode.com',\n        first_name: 'Madelle',\n        last_name: 'Fauguel'\n      }\n    ]\n  },\n  mingo: { idKey: 'email' },\n  parser: {\n    fullTextFields: ['first_name', 'last_name']\n  }\n}\n\nexport const PeopleFinder = new MangoFinder\u003cIPerson, PersonUID\u003e(options)\nexport const PeopleFinderA = new MangoFinderAsync\u003cIPerson, PersonUID\u003e(options)\n```\n\n**Note**: All properties are optional.\n\nTo learn more about [qs-to-mongo][6] options, see [Options][8] from the package\ndocumentation. Note that the `objectIdFields` and `parameters` options are not\naccepted by the [`MangoParser`](src/mixins/mango-parser.mixin.ts).\n\n### Mango Repository\n\nThe Mango Repositories extend the [Mango Finder](#mango-finder) plugins and\nallow users to perform write operations on an object collection.\n\n#### Repository Documentation\n\n- [`AbstractMangoRepository`](src/abstracts/mango-repo.abstract.ts)\n- [`MangoRepositoryAsync`](src/repositories/mango-async.repository.ts)\n- [`MangoRepository`](src/repositories/mango.repository.ts)\n\n```typescript\n/**\n * `AbstractMangoRepository` class interface.\n *\n * Used to define class contract of `MangoRepository`, `MangoRepositoryAsync`,\n * and possible derivatives.\n *\n * @template E - Entity\n * @template U - Name of entity uid field\n * @template P - Repository search parameters (query criteria and options)\n * @template Q - Parsed URL query object\n *\n * @extends IAbstractMangoFinder\n * @extends IAbstractMangoRepositoryBase\n */\nexport interface IAbstractMangoRepository\u003c\n  E extends ObjectPlain = ObjectUnknown,\n  U extends string = DUID,\n  P extends MangoSearchParams\u003cE\u003e = MangoSearchParams\u003cE\u003e,\n  Q extends MangoParsedUrlQuery\u003cE\u003e = MangoParsedUrlQuery\u003cE\u003e\n\u003e extends Omit\u003cIAbstractMangoFinder\u003cE, U, P, Q\u003e, 'cache' | 'options'\u003e,\n    IAbstractMangoRepositoryBase\u003cE, U\u003e {\n  clear(): OrPromise\u003cboolean\u003e\n  create(dto: CreateEntityDTO\u003cE\u003e): OrPromise\u003cE\u003e\n  delete(uid?: OneOrMany\u003cUID\u003e, should_exist?: boolean): OrPromise\u003cUID[]\u003e\n  patch(uid: UID, dto?: PatchEntityDTO\u003cE\u003e, rfields?: string[]): OrPromise\u003cE\u003e\n  setCache(collection?: E[]): OrPromise\u003cMangoCacheRepo\u003cE\u003e\u003e\n  save(dto?: OneOrMany\u003cEntityDTO\u003cE\u003e\u003e): OrPromise\u003cE[]\u003e\n}\n\n/**\n * Base `AbstractMangoRepository` class interface.\n *\n * Used to define properties of `MangoRepository`, `MangoRepositoryAsync`,\n * and possible derivatives.\n *\n * @template E - Entity\n * @template U - Name of entity uid field\n *\n * @extends IAbstractMangoFinderBase\n */\nexport interface IAbstractMangoRepositoryBase\u003c\n  E extends ObjectPlain = ObjectUnknown,\n  U extends string = DUID\n\u003e extends IAbstractMangoFinderBase\u003cE, U\u003e {\n  readonly cache: MangoCacheRepo\u003cE\u003e\n  readonly options: MangoRepoOptions\u003cE, U\u003e\n  readonly validator: IMangoValidator\u003cE\u003e\n}\n```\n\n#### Modeling Entities\n\nBefore creating a new repository, a model needs to be created.\n\nFor the next set of examples, the model `User` will be used.\n\n```typescript\nimport { IsStrongPassword, IsUnixTimestamp } from '@mango/decorators'\nimport type { MangoParsedUrlQuery, MangoSearchParams } from '@mango/types'\nimport {\n  IsEmail,\n  IsNotEmpty,\n  IsOptional,\n  IsPhoneNumber,\n  IsString\n} from 'class-validator'\nimport type { IPerson, PersonUID } from './people'\n\nexport interface IUser extends IPerson {\n  created_at: number\n  password: string\n  phone?: string\n  updated_at?: number\n}\n\nexport type UserParams = MangoSearchParams\u003cIUser\u003e\nexport type UserQuery = MangoParsedUrlQuery\u003cIUser\u003e\n\nexport class User implements IUser {\n  @IsUnixTimestamp()\n  created_at: IUser['created_at']\n\n  @IsEmail()\n  email: IUser['email']\n\n  @IsString()\n  @IsNotEmpty()\n  first_name: IUser['first_name']\n\n  @IsString()\n  @IsNotEmpty()\n  last_name: IUser['last_name']\n\n  @IsStrongPassword()\n  password: IUser['password']\n\n  @IsOptional()\n  @IsPhoneNumber()\n  phone?: IUser['phone']\n\n  @IsOptional()\n  @IsUnixTimestamp()\n  updated_at: IUser['updated_at']\n}\n```\n\nFor more information about validation decorators, see the [class-validator][3]\npackage.\n\nMango also exposes a set of [custom decorators](src/decorators/index.ts).\n\n#### Creating a New Repository\n\nThe `MangoRepository` class accepts an `options` object that gets passed down to\nthe [`MangoFinder`](#mango-finder) and [`MangoValidator`](#mango-validator).\n\n```typescript\nimport { MangoRepository, MangoRepositoryAsync } from '@mango'\nimport type { MangoRepoOptionsDTO } from '@mango/dtos'\n\nconst options: MangoRepoOptionsDTO\u003cIUser, PersonUID\u003e = {\n  cache: { collection: [] },\n  mingo: { idKey: 'email' },\n  parser: {\n    fullTextFields: ['first_name', 'last_name']\n  },\n  validation: {\n    enabled: true,\n    transformer: {},\n    validator: {}\n  }\n}\n\nexport const UsersRepo = new MangoRepository\u003cIUser, PersonUID\u003e(User, options)\nexport const UsersRepoA = new MangoRepositoryAsync\u003cIUser, PersonUID\u003e(\n  User,\n  options\n)\n```\n\nSee [Mango Validator](#mango-validator) for more information about `validation`\noptions.\n\n### Mango Validator\n\nThe `MangoValidator` mixin allows for **decorator-based** model validation.\n\nUnder the hood, it uses [class-transformer-validator][1].\n\n#### Validator Documentation\n\n- [`MangoValidator`](src/mixins/mango-validator.mixin.ts)\n\n```typescript\n/**\n * `MangoValidator` mixin interface.\n *\n * @template E - Entity\n */\nexport interface IMangoValidator\u003cE extends ObjectPlain = ObjectUnknown\u003e {\n  readonly enabled: boolean\n  readonly model: ClassType\u003cE\u003e\n  readonly model_name: string\n  readonly tvo: Omit\u003cMangoValidatorOptions, 'enabled'\u003e\n  readonly validator: typeof transformAndValidate\n  readonly validatorSync: typeof transformAndValidateSync\n\n  check\u003cV extends unknown = ObjectPlain\u003e(value?: V): Promise\u003cE | V\u003e\n  checkSync\u003cV extends unknown = ObjectPlain\u003e(value?: V): E | V\n  handleError(error: Error | ValidationError[]): Exception\n}\n```\n\nEach [repository](#mango-repository) has it owns validator, but the validator\ncan be used standalone as well.\n\n```typescript\nimport { MangoValidator } from '@mango'\nimport type { MangoValidatorOptions } from '@mango/types'\n\nconst options: MangoValidatorOptions = {\n  transformer: {},\n  validator: {}\n}\n\nexport const UsersValidator = new MangoValidator\u003cIUser\u003e(User, options)\n```\n\nValidation options will be merged with the following object:\n\n```typescript\nimport type { TVODefaults } from '@mango/types'\n\n/**\n * @property {TVODefaults} TVO_DEFAULTS - `class-transformer-validator` options\n * @see https://github.com/MichalLytek/class-transformer-validator\n */\nexport const TVO_DEFAULTS: TVODefaults = Object.freeze({\n  transformer: {},\n  validator: {\n    enableDebugMessages: true,\n    forbidNonWhitelisted: true,\n    stopAtFirstError: false,\n    validationError: { target: false, value: true },\n    whitelist: true\n  }\n})\n```\n\n## Built With\n\n- [class-transformer-validator][1] - Plugin for [class-transformer][2] and\n  [class-validator][3]\n- [debug][4] - Debugging utility\n- [mingo][5] - MongoDB query language for in-memory objects\n- [qs-to-mongo][6] - Parse and convert URL queries into MongoDB query criteria\n  and options\n- [uuid][7] - Generate RFC-compliant UUIDs\n\n[1]: https://github.com/MichalLytek/class-transformer-validator\n[2]: https://github.com/typestack/class-transformer\n[3]: https://github.com/typestack/class-validator\n[4]: https://github.com/visionmedia/debug\n[5]: https://github.com/kofrasa/mingo\n[6]: https://github.com/fox1t/qs-to-mongo\n[7]: https://github.com/uuidjs/uuid\n[8]: https://github.com/fox1t/qs-to-mongo#options\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflex-development%2Fmango","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflex-development%2Fmango","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflex-development%2Fmango/lists"}