Skip to content

Commit

Permalink
feat(mango): plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
unicornware committed May 22, 2021
1 parent fae840b commit 1d69bd9
Show file tree
Hide file tree
Showing 36 changed files with 1,399 additions and 143 deletions.
11 changes: 10 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,23 @@ module.exports = {
'bool',
'bson',
'commitlint',
'dto',
'enums',
'enum',
'execa',
'formatter',
'mingo',
'mparser',
'nullable',
'perf',
'postpublish',
'readonly',
'tgz',
'typeof',
'uids',
'uid',
'utf8',
'vin',
'wip',
'zsh'
],
Expand Down Expand Up @@ -168,7 +173,11 @@ module.exports = {
}
},
{
files: ['.eslintrc.*'],
files: [
'.eslintrc.*',
'__tests__/__fixtures__/cars.fixture.ts',
'docs/examples/subscribers.ts'
],
rules: {
'spellcheck/spell-checker': 0
}
Expand Down
148 changes: 136 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Mango is a MongoDB-like API for in-memory object collections. It combines the
power of [mingo][1] and [qs-to-mongo][2] to allow:

- running aggregation pipelines
- executing searches with query criteria and options
- performing searches (with query criteria **and** URL queries)
- parsing and converting URL query objects and strings

## Installation
Expand All @@ -39,8 +39,8 @@ power of [mingo][1] and [qs-to-mongo][2] to allow:
## Usage

[Configuration](#configuration)
[🚧 Creating a New Mango Query Client](#🚧-creating-a-new-mango-query-client)
[🚧 Mango API](#🚧-mango-api)
[Creating a New Mango Plugin](#creating-a-new-mango-plugin)
[Mango Plugin API](#mango-plugin-api)

### Configuration

Expand All @@ -52,11 +52,11 @@ power of [mingo][1] and [qs-to-mongo][2] to allow:
#### Mingo

The `Mango` class integrates with [mingo][1], a MongoDB query language for
in-memory objects, to support aggregation pipelines and querying.
in-memory objects, to support aggregation pipelines and executing searches.

Operators loaded by Mango can be viewed in the [config](src/config/mingo.ts)
file. If additional operators are needed, you'll need to load them _before_
[creating a new query client](#🚧-creating-a-new-mango-query-client).
file. If additional operators are needed, load them _before_
[creating a new plugin](#creating-a-new-mango-plugin).

#### TypeScript

Expand All @@ -75,21 +75,145 @@ For shorter import paths, TypeScript users can add the following aliases:

These aliases will be used in following code examples.

### 🚧 Creating a New Mango Query Client
### Creating a New Mango Plugin

**TODO:** Update documentation.
#### Documents

### 🚧 Mango API
A document is an object from an in-memory collection. Each document should have
a unique identifier (uid).

**TODO:** Update documentation.
By default, this value is assumed to map to the `_id` field of each document.
This can be changed via the [plugin settings](#plugin-settings).

```typescript
import type { MangoParsedUrlQuery, MangoSearchParams } from '@mango/types'

export interface ISubscriber {
email: string
first_name: string
last_name: string
}

export type SubscriberUID = 'email'
export type SubscriberParams = MangoSearchParams<ISubscriber>
export type SubscriberQuery = MangoParsedUrlQuery<ISubscriber>
```
#### Plugin
The Mango plugin accepts an options object thats gets passed down to the
[mingo][1] and [qs-to-mongo][2] modules.
Via the options dto, you can:
- set initial collection cache
- set uid field for each document
- set date fields and fields searchable by text
```typescript
import { Mango } from '@mango'
import type { MangoOptionsDTO } from '@mango/dto'

const options: MangoOptionsDTO<ISubscriber, SubscriberUID> = {
cache: {
collection: [
{
email: '[email protected]',
first_name: 'Nate',
last_name: 'Maxstead'
},
{
email: '[email protected]',
first_name: 'Roland',
last_name: 'Brisseau'
},
{
email: '[email protected]',
first_name: 'Kippar',
last_name: 'Smidmoor'
},
{
email: '[email protected]',
first_name: 'Godfree',
last_name: 'Durnford'
},
{
email: '[email protected]',
first_name: 'Madelle',
last_name: 'Fauguel'
}
]
},
mingo: { idKey: 'email' },
parser: {
fullTextFields: ['first_name', 'last_name']
}
}

export const SubscribersMango = new Mango<ISubscriber, SubscriberUID>(options)
```

**Note**: All properties are optional.

To learn more about [qs-to-mongo][3] options, see [Options][4] from the package
documentation. Note that the `objectIdFields` and `parameters` options are not
accepted by the Mango parser.

### Mango Plugin API

The Mango plugin allows users to run aggregation pipelines and execute searches
against in-memory object collections. Query documents using a URL query, or
search for them using a query criteria and options object.

Documentation can be viewed [here](src/plugins/mango.plugin.ts).

```typescript
/**
* `Mango` plugin interface.
*
* - https://github.com/kofrasa/mingo
* - https://github.com/fox1t/qs-to-mongo
*
* @template D - Document (collection object)
* @template U - Name of document uid field
* @template P - Search parameters (query criteria and options)
* @template Q - Parsed URL query object
*/
export interface IMango<
D extends PlainObject = PlainObject,
U extends keyof D = '_id',
P extends MangoSearchParams<D> = MangoSearchParams<D>,
Q extends MangoParsedUrlQuery<D> = MangoParsedUrlQuery<D>
> {
readonly cache: Readonly<MangoCache<D>>
readonly logger: Debugger
readonly mingo: typeof mingo
readonly mparser: IMangoParser<D>
readonly options: MangoOptions<D, U>

aggregate(
pipeline?: OneOrMany<AggregationStages<D>>
): AggregationPipelineResult<D>
find(params?: P): DocumentPartial<D, U>[]
findByIds(uids?: NumberString[], params?: P): DocumentPartial<D, U>[]
findOne(uid: NumberString, params?: P): DocumentPartial<D, U> | null
findOneOrFail(uid: NumberString, params?: P): DocumentPartial<D, U>
query(query?: Q | string): DocumentPartial<D, U>[]
queryByIds(uids?: NumberString[], query?: Q | string): DocumentPartial<D, U>[]
queryOne(uid: NumberString, query?: Q | string): DocumentPartial<D, U> | null
queryOneOrFail(uid: NumberString, query?: Q | string): DocumentPartial<D, U>
resetCache(collection?: D[]): MangoCache<D>
}
```

## Built With

- [debug][3] - Debugging utility
- [mingo][1] - MongoDB query language for in-memory objects
- [qs-to-mongo][2] - Parse and convert query parameters into MongoDB query
criteria and options
- [qs-to-mongo][2] - Parse and convert URL queries into MongoDB query criteria
and options

[1]: https://github.com/kofrasa/mingo
[2]: https://github.com/fox1t/qs-to-mongo
[3]: https://github.com/visionmedia/debug
[4]: https://github.com/fox1t/qs-to-mongo#options
Empty file removed __tests__/__fixtures__/.gitkeep
Empty file.
59 changes: 59 additions & 0 deletions __tests__/__fixtures__/cars.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { MangoCache } from '@/interfaces'
import type { MangoParsedUrlQuery, MangoSearchParams } from '@/types'

/**
* @file Global Test Fixture - Cars Collection
* @module tests/fixtures/cars
*/

export interface ICar {
make: string
model: string
model_year: number
vin: string
}

export type CarUID = 'vin'
export type CarParams = MangoSearchParams<ICar>
export type CarQuery = MangoParsedUrlQuery<ICar>

export const CARS_IDKEY: CarUID = 'vin'

export const CARS_MOCK_CACHE_EMPTY: MangoCache<ICar> = {
collection: Object.freeze([])
}

export const CARS_MOCK_CACHE: MangoCache<ICar> = {
collection: Object.freeze([
{
make: 'Scion',
model: 'tC',
model_year: 2010,
vin: '3221085d-6f55-4d23-842a-aeb0e413fca8'
},
{
make: 'Mitsubishi',
model: '3000GT',
model_year: 1999,
vin: '5b38c222-bf0c-4972-9810-d8cd7e399a56'
},
{
make: 'Nissan',
model: 'Quest',
model_year: 1994,
vin: '6e177f82-055d-464d-b118-cf36b10fb77d'
},
{
make: 'Chevrolet',
model: 'Aveo',
model_year: 2006,
vin: 'e3df6457-3901-4c25-90cd-6aaabf3cdcb8'
},
{
make: 'Subaru',
model: 'Impreza',
model_year: 1994,
vin: 'eda31c5a-7b59-4250-b365-e66661930bc8'
}
])
}
Empty file removed docs/examples/.gitkeep
Empty file.
56 changes: 56 additions & 0 deletions docs/examples/subscribers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Mango } from '@mango'
import type { MangoOptionsDTO } from '@mango/dto'
import type { MangoParsedUrlQuery, MangoSearchParams } from '@mango/types'

/**
* @file Example - Subscribers Collection
* @module docs/examples/subscribers
*/

export interface ISubscriber {
email: string
first_name: string
last_name: string
}

export type SubscriberUID = 'email'
export type SubscriberParams = MangoSearchParams<ISubscriber>
export type SubscriberQuery = MangoParsedUrlQuery<ISubscriber>

const options: MangoOptionsDTO<ISubscriber, SubscriberUID> = {
cache: {
collection: [
{
email: '[email protected]',
first_name: 'Nate',
last_name: 'Maxstead'
},
{
email: '[email protected]',
first_name: 'Roland',
last_name: 'Brisseau'
},
{
email: '[email protected]',
first_name: 'Kippar',
last_name: 'Smidmoor'
},
{
email: '[email protected]',
first_name: 'Godfree',
last_name: 'Durnford'
},
{
email: '[email protected]',
first_name: 'Madelle',
last_name: 'Fauguel'
}
]
},
mingo: { idKey: 'email' },
parser: {
fullTextFields: ['first_name', 'last_name']
}
}

export const SubscribersMango = new Mango<ISubscriber, SubscriberUID>(options)
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@
"dependencies": {
"@flex-development/exceptions": "2.0.0",
"@flex-development/tutils": "1.0.0",
"@types/debug": "latest",
"@types/lodash.isempty": "latest",
"@types/lodash.merge": "latest",
"@types/node": "latest",
"@types/qs": "latest",
"debug": "latest",
"lodash.isempty": "latest",
"lodash.merge": "latest",
"mingo": "latest",
"qs-to-mongo": "latest"
"@types/debug": "4.1.5",
"@types/lodash.isempty": "4.4.6",
"@types/lodash.merge": "4.6.6",
"@types/node": "15.3.1",
"@types/qs": "6.9.6",
"debug": "4.3.1",
"lodash.isempty": "4.4.0",
"lodash.merge": "4.6.2",
"mingo": "4.1.2",
"qs-to-mongo": "2.0.0"
},
"devDependencies": {
"@babel/eslint-parser": "latest",
Expand All @@ -63,6 +63,7 @@
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
"@zerollup/ts-transform-paths": "latest",
"axios": "latest",
"dotenv-cli": "latest",
"eslint": "latest",
"eslint-config-prettier": "latest",
Expand All @@ -86,7 +87,6 @@
"ts-jest": "27.0.0-next.9",
"ts-node": "latest",
"ttypescript": "latest",
"type-plus": "latest",
"typescript": "4.2.4",
"underscore-cli": "latest",
"yarn": "latest"
Expand Down
9 changes: 9 additions & 0 deletions src/config/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import debug from 'debug'

/**
* @file Config - Logger
* @module config/logger
* @see https://github.com/visionmedia/debug
*/

export default debug('mango')
Loading

0 comments on commit 1d69bd9

Please sign in to comment.