diff --git a/CODEOWNERS b/CODEOWNERS index b1a5c843deaf..637b2f8087a3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -179,6 +179,7 @@ # - Primary owner(s): @agnes512 # - Standby owner(s): @hacksparrow # Includes the following repos: loopback-datasource-juggler +/packages/filter @agnes512 @hacksparrow /packages/repository @agnes512 @hacksparrow /packages/repository-json-schema @agnes512 @hacksparrow /packages/repository-tests @agnes512 @hacksparrow diff --git a/bin/update-contributors.js b/bin/update-contributors.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/site/MONOREPO.md b/docs/site/MONOREPO.md index 03fe3ffcda3b..b6387907046a 100644 --- a/docs/site/MONOREPO.md +++ b/docs/site/MONOREPO.md @@ -58,6 +58,7 @@ one in the monorepo: `npm run update-monorepo-file` | [packages/core](https://github.com/strongloop/loopback-next/tree/master/packages/core) | @loopback/core | Define and implement core constructs such as Application and Component | | [packages/eslint-config](https://github.com/strongloop/loopback-next/tree/master/packages/eslint-config) | @loopback/eslint-config | ESLint configuration for LoopBack projects | | [packages/express](https://github.com/strongloop/loopback-next/tree/master/packages/express) | @loopback/express | Integrate with Express and expose middleware infrastructure for sequence and interceptors | +| [packages/filter](https://github.com/strongloop/loopback-next/tree/master/packages/filter) | @loopback/filter | Utility typings and filters for LoopBack filters. | | [packages/http-caching-proxy](https://github.com/strongloop/loopback-next/tree/master/packages/http-caching-proxy) | @loopback/http-caching-proxy | A caching HTTP proxy for integration tests. NOT SUITABLE FOR PRODUCTION USE! | | [packages/http-server](https://github.com/strongloop/loopback-next/tree/master/packages/http-server) | @loopback/http-server | A wrapper for creating HTTP/HTTPS servers | | [packages/metadata](https://github.com/strongloop/loopback-next/tree/master/packages/metadata) | @loopback/metadata | Utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata | diff --git a/packages/filter/.npmrc b/packages/filter/.npmrc new file mode 100644 index 000000000000..34fbbbb3f3e4 --- /dev/null +++ b/packages/filter/.npmrc @@ -0,0 +1,2 @@ +package-lock=true +scripts-prepend-node-path=true diff --git a/packages/filter/LICENSE b/packages/filter/LICENSE new file mode 100644 index 000000000000..93bc9a8ae4ee --- /dev/null +++ b/packages/filter/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) IBM Corp. 2020. +Node module: @loopback/filter +This project is licensed under the MIT License, full text below. + +-------- + +MIT License + +MIT License Copyright (c) IBM Corp. 2020 + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/filter/README.md b/packages/filter/README.md new file mode 100644 index 000000000000..9ad50eac3ef8 --- /dev/null +++ b/packages/filter/README.md @@ -0,0 +1,135 @@ +# @loopback/filter + +A set of utility typings and filter builders to aid in constructing LoopBack +filters using the +[builder pattern](https://en.wikipedia.org/wiki/Builder_pattern). + +## Overview + +This lightweight module provides strongly-typed typings and filter builders to +intuitively construct LoopBack filters to be used against querying a LoopBack +filter-compatible server over the network or within the server itself. + +## Installation + +{% include note.html content="Already installed `@loopback/repository`? Import from there instead. This package is meant for standalone use without `@loopback/repository`." %} + +```sh +npm install --save @loopback/filter +``` + +## Basic use + +### WhereBuilder + +The `WhereBuilder` is a builder specifically meant to construct the `where` +portion of the filter. + +```ts +import {WhereBuilder} from '@loopback/filter'; + +const whereBuilder = new WhereBuilder(); +const where = whereBuilder + .between('price', 99, 299) + .and({brand: 'LoopBack'}, {discount: {lt: 20}}) + .or({instock: true}) + .build(); +``` + +### FilterBuilder + +The `FilterBuilder` is a builder to construct the filter as a whole. + +```ts +import {FilterBuilder} from '@loopback/filter'; + +const filterBuilder = new FilterBuilder(); +const filter = filterBuilder + .fields('id', 'a', 'b') + .limit(10) + .offset(0) + .order(['a ASC', 'b DESC']) + .where({id: 1}) + .build(); +``` + +`FilterBuilder.where()` also accepts an instance of `WhereBuilder`. + +```ts +const filterBuilder = new FilterBuilder(); +const filter = filterBuilder + // ... + .where(where) + .build(); +``` + +## Advanced use + +### Strong typings + +The `FilterBuilder` and `WhereBuilder` accept a model or any string-based key +objects' typing for strong typings: + +```ts +/** + * Everything was imported from `@loopback/repository` as `@loopback/filter` + * is re-exported in that package. + **/ +import { + FilterBuilder, + model, + property, + WhereBuilder, +} from '@loopback/repository'; + +@model() +class Todo extends Entity { + @property({id: true}) + id: number; + + @property() + title: string; + + @property() + description: string; + + @property() + priority: number; +} + +const whereBuilder = new WhereBuilder(); +const where = whereBuilder.between('priority', 1, 3).build(); + +const filterBuilder = new FilterBuilder(); +const filter = filterBuilder + .fields('id', 'title') + .order(['title DESC']) + .where(where) + .build(); +``` + +## API docs + +See the API docs for +[FilterBuilder](https://loopback.io/doc/en/lb4/apidocs.filter.filterbuilder.html) +and +[WhereBuilder](https://loopback.io/doc/en/lb4/apidocs.filter.wherebuilder.html) +for more details on the full API. + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/packages/filter/package-lock.json b/packages/filter/package-lock.json new file mode 100644 index 000000000000..54b8667c51eb --- /dev/null +++ b/packages/filter/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "@loopback/filter", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "10.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.28.tgz", + "integrity": "sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ==", + "dev": true + }, + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + }, + "typescript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "dev": true + } + } +} diff --git a/packages/filter/package.json b/packages/filter/package.json new file mode 100644 index 000000000000..01568eee84d1 --- /dev/null +++ b/packages/filter/package.json @@ -0,0 +1,43 @@ +{ + "name": "@loopback/filter", + "version": "1.0.0", + "description": "Utility typings and filters for LoopBack filters.", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": ">=10.16" + }, + "scripts": { + "build": "lb-tsc", + "build:watch": "lb-tsc --watch", + "pretest": "npm run clean && npm run build", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "clean": "lb-clean dist *.tsbuildinfo .eslintcache" + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git", + "directory": "packages/filter" + }, + "author": "IBM Corp.", + "license": "MIT", + "files": [ + "README.md", + "dist", + "src", + "!*/__tests__" + ], + "dependencies": { + "tslib": "^2.0.0" + }, + "devDependencies": { + "@loopback/build": "^6.2.1", + "@loopback/testlab": "^3.2.3", + "@types/node": "^10.17.28", + "typescript": "~4.0.2" + }, + "copyright.owner": "IBM Corp.", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/repository/src/__tests__/unit/query/query-builder.unit.ts b/packages/filter/src/__tests__/acceptance/query-builder.acceptance.ts similarity index 99% rename from packages/repository/src/__tests__/unit/query/query-builder.unit.ts rename to packages/filter/src/__tests__/acceptance/query-builder.acceptance.ts index 2ccf357bf859..f74a8aa93c41 100644 --- a/packages/repository/src/__tests__/unit/query/query-builder.unit.ts +++ b/packages/filter/src/__tests__/acceptance/query-builder.acceptance.ts @@ -1,18 +1,18 @@ -// Copyright IBM Corp. 2019,2020. All Rights Reserved. -// Node module: @loopback/repository +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/filter // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import {expect} from '@loopback/testlab'; import { - AnyObject, FilterBuilder, filterTemplate, isFilter, KeyOf, Where, WhereBuilder, -} from '../../../'; +} from '../..'; +import {AnyObject} from '../../types'; describe('WhereBuilder', () => { it('builds where object', () => { diff --git a/packages/filter/src/index.ts b/packages/filter/src/index.ts new file mode 100644 index 000000000000..d296d7e02fbf --- /dev/null +++ b/packages/filter/src/index.ts @@ -0,0 +1,20 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/filter +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * A set of utility typings and filter builders to aid in constructing LoopBack + * filters using the + * {@link https://en.wikipedia.org/wiki/Builder_pattern | builder pattern}. + * + * @remarks + * This lightweight module provides strongly-typed typings and filter builders + * to intuitively construct LoopBack filters to be used against querying a + * LoopBack filter-compatible server over the network or within the server + * itself. + * + * @packageDocumentation + */ + +export * from './query'; diff --git a/packages/repository/src/query.ts b/packages/filter/src/query.ts similarity index 99% rename from packages/repository/src/query.ts rename to packages/filter/src/query.ts index 3056ace74be0..209876ef4eca 100644 --- a/packages/repository/src/query.ts +++ b/packages/filter/src/query.ts @@ -1,10 +1,10 @@ -// Copyright IBM Corp. 2017,2020. All Rights Reserved. -// Node module: @loopback/repository +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/filter // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import assert from 'assert'; -import {AnyObject} from './common-types'; +import {AnyObject} from './types'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -513,7 +513,7 @@ export class WhereBuilder { * ```ts * const filterBuilder = new FilterBuilder(); * const filter = filterBuilder - * .fields('id', a', 'b') + * .fields('id', 'a', 'b') * .limit(10) * .offset(0) * .order(['a ASC', 'b DESC']) diff --git a/packages/filter/src/types.ts b/packages/filter/src/types.ts new file mode 100644 index 000000000000..6234449bf8ac --- /dev/null +++ b/packages/filter/src/types.ts @@ -0,0 +1,10 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/filter +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * Objects with open properties + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AnyObject = Record; diff --git a/packages/filter/tsconfig.json b/packages/filter/tsconfig.json new file mode 100644 index 000000000000..134a68e33164 --- /dev/null +++ b/packages/filter/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../core/tsconfig.json" + }, + { + "path": "../testlab/tsconfig.json" + } + ] +} diff --git a/packages/repository/package.json b/packages/repository/package.json index ad8474b5c508..400d0492b8d7 100644 --- a/packages/repository/package.json +++ b/packages/repository/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@loopback/core": "^2.9.5", + "@loopback/filter": "^1.0.0", "@types/debug": "^4.1.5", "debug": "^4.1.1", "lodash": "^4.17.20", diff --git a/packages/repository/src/connectors/crud.connector.ts b/packages/repository/src/connectors/crud.connector.ts index 137975858ef1..a32f1aa03a2c 100644 --- a/packages/repository/src/connectors/crud.connector.ts +++ b/packages/repository/src/connectors/crud.connector.ts @@ -3,10 +3,10 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Connector} from './connector'; +import {Filter, Where} from '@loopback/filter'; +import {Class, Count, Options} from '../common-types'; import {Entity, EntityData} from '../model'; -import {Filter, Where} from '../query'; -import {Class, Options, Count} from '../common-types'; +import {Connector} from './connector'; /** * CRUD operations for connector implementations diff --git a/packages/repository/src/connectors/kv.connector.ts b/packages/repository/src/connectors/kv.connector.ts index d83ab0fc0554..8630db02d571 100644 --- a/packages/repository/src/connectors/kv.connector.ts +++ b/packages/repository/src/connectors/kv.connector.ts @@ -3,10 +3,10 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Connector} from './connector'; -import {Entity, EntityData} from '../model'; +import {Filter} from '@loopback/filter'; import {Class, Options} from '../common-types'; -import {Filter} from '../query'; +import {Entity, EntityData} from '../model'; +import {Connector} from './connector'; /** * Key/Value operations for connector implementations diff --git a/packages/repository/src/index.ts b/packages/repository/src/index.ts index 288e5ed13da0..c8ca6f4e319e 100644 --- a/packages/repository/src/index.ts +++ b/packages/repository/src/index.ts @@ -12,6 +12,7 @@ * @packageDocumentation */ +export * from '@loopback/filter'; export {JSONSchema7 as JsonSchema} from 'json-schema'; export * from './common-types'; export * from './connectors'; @@ -23,7 +24,6 @@ export * from './errors'; export * from './keys'; export * from './mixins'; export * from './model'; -export * from './query'; export * from './relations'; export * from './repositories'; export * from './transaction'; diff --git a/packages/repository/src/relations/belongs-to/belongs-to.inclusion-resolver.ts b/packages/repository/src/relations/belongs-to/belongs-to.inclusion-resolver.ts index 311add19d758..30426364501c 100644 --- a/packages/repository/src/relations/belongs-to/belongs-to.inclusion-resolver.ts +++ b/packages/repository/src/relations/belongs-to/belongs-to.inclusion-resolver.ts @@ -5,7 +5,7 @@ import {AnyObject, Options} from '../../common-types'; import {Entity} from '../../model'; -import {Filter, Inclusion} from '../../query'; +import {Filter, Inclusion} from '@loopback/filter'; import {EntityCrudRepository} from '../../repositories/repository'; import { deduplicate, diff --git a/packages/repository/src/relations/has-many/has-many.inclusion-resolver.ts b/packages/repository/src/relations/has-many/has-many.inclusion-resolver.ts index 06ee2c5b5a62..b1ee285a4c8f 100644 --- a/packages/repository/src/relations/has-many/has-many.inclusion-resolver.ts +++ b/packages/repository/src/relations/has-many/has-many.inclusion-resolver.ts @@ -3,10 +3,10 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {Filter, Inclusion} from '@loopback/filter'; import debugFactory from 'debug'; import {AnyObject, Options} from '../../common-types'; import {Entity} from '../../model'; -import {Filter, Inclusion} from '../../query'; import {EntityCrudRepository} from '../../repositories/repository'; import { findByForeignKeys, diff --git a/packages/repository/src/relations/has-many/has-many.repository.ts b/packages/repository/src/relations/has-many/has-many.repository.ts index a36e4e110217..715d42232f0e 100644 --- a/packages/repository/src/relations/has-many/has-many.repository.ts +++ b/packages/repository/src/relations/has-many/has-many.repository.ts @@ -4,9 +4,9 @@ // License text available at https://opensource.org/licenses/MIT import {Getter} from '@loopback/core'; +import {Filter, Where} from '@loopback/filter'; import {Count, DataObject, Options} from '../../common-types'; import {Entity} from '../../model'; -import {Filter, Where} from '../../query'; import { constrainDataObject, constrainFilter, diff --git a/packages/repository/src/relations/has-one/has-one.inclusion-resolver.ts b/packages/repository/src/relations/has-one/has-one.inclusion-resolver.ts index 202d2de14e06..dcaa63b90518 100644 --- a/packages/repository/src/relations/has-one/has-one.inclusion-resolver.ts +++ b/packages/repository/src/relations/has-one/has-one.inclusion-resolver.ts @@ -3,9 +3,9 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {Filter, Inclusion} from '@loopback/filter'; import {AnyObject, Options} from '../../common-types'; import {Entity} from '../../model'; -import {Filter, Inclusion} from '../../query'; import {EntityCrudRepository} from '../../repositories/repository'; import { findByForeignKeys, diff --git a/packages/repository/src/relations/has-one/has-one.repository.ts b/packages/repository/src/relations/has-one/has-one.repository.ts index 1d05f95f5b53..274d3d142c63 100644 --- a/packages/repository/src/relations/has-one/has-one.repository.ts +++ b/packages/repository/src/relations/has-one/has-one.repository.ts @@ -4,10 +4,10 @@ // License text available at https://opensource.org/licenses/MIT import {Getter} from '@loopback/core'; +import {Filter} from '@loopback/filter'; import {Count, DataObject, Options} from '../../common-types'; import {EntityNotFoundError} from '../../errors'; import {Entity} from '../../model'; -import {Filter} from '../../query'; import { constrainDataObject, constrainFilter, diff --git a/packages/repository/src/relations/relation.types.ts b/packages/repository/src/relations/relation.types.ts index ffc5bc98a960..e3dc68ad0c4a 100644 --- a/packages/repository/src/relations/relation.types.ts +++ b/packages/repository/src/relations/relation.types.ts @@ -3,9 +3,9 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {Inclusion} from '@loopback/filter'; import {Options} from '../common-types'; import {Entity} from '../model'; -import {Inclusion} from '../query'; import {TypeResolver} from '../type-resolver'; export enum RelationType { diff --git a/packages/repository/src/repositories/constraint-utils.ts b/packages/repository/src/repositories/constraint-utils.ts index 58eda13b3f4c..e1a6b81ef543 100644 --- a/packages/repository/src/repositories/constraint-utils.ts +++ b/packages/repository/src/repositories/constraint-utils.ts @@ -3,10 +3,10 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {Filter, FilterBuilder, Where, WhereBuilder} from '@loopback/filter'; import {cloneDeep} from 'lodash'; import {AnyObject, DataObject} from '../common-types'; import {Entity} from '../model'; -import {Filter, FilterBuilder, Where, WhereBuilder} from '../query'; /** * A utility function which takes a filter and enforces constraint(s) diff --git a/packages/repository/src/repositories/legacy-juggler-bridge.ts b/packages/repository/src/repositories/legacy-juggler-bridge.ts index a02116d2b6b4..89a48d4e04cb 100644 --- a/packages/repository/src/repositories/legacy-juggler-bridge.ts +++ b/packages/repository/src/repositories/legacy-juggler-bridge.ts @@ -4,6 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import {Getter} from '@loopback/core'; +import {Filter, FilterExcludingWhere, Inclusion, Where} from '@loopback/filter'; import assert from 'assert'; import legacy from 'loopback-datasource-juggler'; import { @@ -23,7 +24,6 @@ import { PropertyType, rejectNavigationalPropertiesInData, } from '../model'; -import {Filter, FilterExcludingWhere, Inclusion, Where} from '../query'; import { BelongsToAccessor, BelongsToDefinition, diff --git a/packages/repository/src/repositories/repository.ts b/packages/repository/src/repositories/repository.ts index 9ff4c6205710..8bb1464e2a1d 100644 --- a/packages/repository/src/repositories/repository.ts +++ b/packages/repository/src/repositories/repository.ts @@ -3,6 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {Filter, FilterExcludingWhere, Where} from '@loopback/filter'; import { AnyObject, Command, @@ -16,7 +17,6 @@ import {CrudConnector} from '../connectors'; import {DataSource} from '../datasource'; import {EntityNotFoundError} from '../errors'; import {Entity, Model, ValueObject} from '../model'; -import {Filter, FilterExcludingWhere, Where} from '../query'; import {InclusionResolver} from '../relations/relation.types'; import {IsolationLevel, Transaction} from '../transaction'; diff --git a/packages/repository/tsconfig.json b/packages/repository/tsconfig.json index 134a68e33164..bcaafd0cf140 100644 --- a/packages/repository/tsconfig.json +++ b/packages/repository/tsconfig.json @@ -13,6 +13,9 @@ { "path": "../core/tsconfig.json" }, + { + "path": "../filter/tsconfig.json" + }, { "path": "../testlab/tsconfig.json" } diff --git a/tsconfig.json b/tsconfig.json index 9335eaa67e3b..039ccf2429df 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -136,6 +136,9 @@ { "path": "packages/express/tsconfig.json" }, + { + "path": "packages/filter/tsconfig.json" + }, { "path": "packages/http-caching-proxy/tsconfig.json" },