diff --git a/packages/repository/src/__tests__/unit/repositories/define-repository-class.unit.ts b/packages/repository/src/__tests__/unit/repositories/define-repository-class.unit.ts
new file mode 100644
index 000000000000..a2e26deb1bf3
--- /dev/null
+++ b/packages/repository/src/__tests__/unit/repositories/define-repository-class.unit.ts
@@ -0,0 +1,138 @@
+// Copyright IBM Corp. 2020. All Rights Reserved.
+// Node module: @loopback/repository
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {expect} from '@loopback/testlab';
+import {
+ AnyObject,
+ Count,
+ CrudRepository,
+ DataObject,
+ DefaultCrudRepository,
+ DefaultKeyValueRepository,
+ defineCrudRepositoryClass,
+ defineKeyValueRepositoryClass,
+ defineRepositoryClass,
+ Entity,
+ Filter,
+ juggler,
+ model,
+ Model,
+ property,
+ Where,
+} from '../../..';
+
+describe('RepositoryClass builder', () => {
+ describe('defineRepositoryClass', () => {
+ it('should generate custom repository class', async () => {
+ const AddressRepository = defineRepositoryClass<
+ typeof Address,
+ DummyCrudRepository
+ >(Address, DummyCrudRepository);
+ // `CrudRepository.prototype.find` is inherited
+ expect(AddressRepository.prototype.find).to.be.a.Function();
+ // `DummyCrudRepository.prototype.findByTitle` is inherited
+ expect(AddressRepository.prototype.findByTitle).to.be.a.Function();
+ expect(AddressRepository.name).to.equal('AddressRepository');
+ expect(Object.getPrototypeOf(AddressRepository)).to.equal(
+ DummyCrudRepository,
+ );
+ });
+ });
+
+ describe('defineCrudRepositoryClass', () => {
+ it('should generate entity CRUD repository class', async () => {
+ const ProductRepository = defineCrudRepositoryClass(Product);
+
+ expect(ProductRepository.name).to.equal('ProductRepository');
+ expect(ProductRepository.prototype.find).to.be.a.Function();
+ expect(ProductRepository.prototype.findById).to.be.a.Function();
+ expect(Object.getPrototypeOf(ProductRepository)).to.equal(
+ DefaultCrudRepository,
+ );
+ });
+ });
+
+ describe('defineKeyValueRepositoryClass', () => {
+ it('should generate key value repository class', async () => {
+ const ProductRepository = defineKeyValueRepositoryClass(Product);
+
+ expect(ProductRepository.name).to.equal('ProductRepository');
+ expect(ProductRepository.prototype.get).to.be.a.Function();
+ expect(Object.getPrototypeOf(ProductRepository)).to.equal(
+ DefaultKeyValueRepository,
+ );
+ });
+ });
+
+ @model()
+ class Product extends Entity {
+ @property({id: true})
+ id: number;
+
+ @property()
+ name: string;
+ }
+
+ @model()
+ class Address extends Model {
+ @property()
+ street: string;
+
+ @property()
+ city: string;
+
+ @property()
+ state: string;
+ }
+
+ class DummyCrudRepository implements CrudRepository {
+ constructor(
+ private modelCtor: typeof Model & {prototype: M},
+ private dataSource: juggler.DataSource,
+ ) {}
+ create(
+ dataObject: DataObject,
+ options?: AnyObject | undefined,
+ ): Promise {
+ throw new Error('Method not implemented.');
+ }
+ createAll(
+ dataObjects: DataObject[],
+ options?: AnyObject | undefined,
+ ): Promise {
+ throw new Error('Method not implemented.');
+ }
+ find(
+ filter?: Filter | undefined,
+ options?: AnyObject | undefined,
+ ): Promise<(M & {})[]> {
+ throw new Error('Method not implemented.');
+ }
+ updateAll(
+ dataObject: DataObject,
+ where?: Where | undefined,
+ options?: AnyObject | undefined,
+ ): Promise {
+ throw new Error('Method not implemented.');
+ }
+ deleteAll(
+ where?: Where | undefined,
+ options?: AnyObject | undefined,
+ ): Promise {
+ throw new Error('Method not implemented.');
+ }
+ count(
+ where?: Where | undefined,
+ options?: AnyObject | undefined,
+ ): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ // An extra method to verify it's available for the defined repo class
+ findByTitle(title: string): Promise {
+ throw new Error('Method not implemented.');
+ }
+ }
+});
diff --git a/packages/repository/src/define-repository-class.ts b/packages/repository/src/define-repository-class.ts
new file mode 100644
index 000000000000..cf0d66414b63
--- /dev/null
+++ b/packages/repository/src/define-repository-class.ts
@@ -0,0 +1,170 @@
+// Copyright IBM Corp. 2020. All Rights Reserved.
+// Node module: @loopback/repository
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import assert from 'assert';
+import {PrototypeOf} from './common-types';
+import {Entity, Model} from './model';
+import {
+ DefaultCrudRepository,
+ DefaultKeyValueRepository,
+ juggler,
+ Repository,
+} from './repositories';
+
+/**
+ * Signature for a Repository class bound to a given model. The constructor
+ * accepts only the dataSource to use for persistence.
+ *
+ * `define*` functions return a class implementing this interface.
+ *
+ * @typeParam M - Model class
+ * @typeParam R - Repository class/interface
+ */
+export interface ModelRepositoryClass<
+ M extends Model,
+ R extends Repository
+> {
+ /**
+ * The constructor for the generated repository class
+ * @param dataSource - DataSource object
+ */
+ new (dataSource: juggler.DataSource): R;
+ prototype: R;
+}
+
+/**
+ * Signature for repository classes that can be used as the base class for
+ * `define*` functions. The constructor of a base repository class accepts
+ * the target model constructor and the datasource to use.
+ *
+ * `define*` functions require a class implementing this interface on input.
+ *
+ * @typeParam M - Model class constructor, e.g `typeof Model`.
+ * **❗️IMPORTANT: The type argument `M` is describing the model constructor type
+ * (e.g. `typeof Model`), not the model instance type (`Model`) as is the case
+ * in other repository-related types. The constructor type is required
+ * to support custom repository classes requiring a Model subclass in the
+ * constructor arguments, e.g. `Entity` or a user-provided model.**
+ *
+ * @typeParam R - Repository class/interface
+ */
+export interface BaseRepositoryClass<
+ M extends typeof Model,
+ R extends Repository>
+> {
+ /**
+ * The constructor for the generated repository class
+ * @param modelClass - Model class
+ * @param dataSource - DataSource object
+ */
+ new (modelClass: M, dataSource: juggler.DataSource): R;
+ prototype: R;
+}
+
+/**
+ * Create (define) a repository class for the given model.
+ *
+ * See also `defineCrudRepositoryClass` and `defineKeyValueRepositoryClass`
+ * for convenience wrappers providing repository class factory for the default
+ * CRUD and KeyValue implementations.
+ *
+ * **❗️IMPORTANT: The compiler (TypeScript 3.8) is not able to correctly infer
+ * generic arguments `M` and `R` from the class constructors provided in
+ * function arguments. You must always provide both M and R types explicitly.**
+ *
+ * @example
+ *
+ * ```ts
+ * const AddressRepository = defineRepositoryClass<
+ * typeof Address,
+ * DefaultEntityCrudRepository<
+ * Address,
+ * typeof Address.prototype.id,
+ * AddressRelations
+ * >,
+ * >(Address, DefaultCrudRepository);
+ * ```
+ *
+ * @param modelClass - A model class such as `Address`.
+ * @param baseRepositoryClass - Repository implementation to use as the base,
+ * e.g. `DefaultCrudRepository`.
+ *
+ * @typeParam M - Model class constructor (e.g. `typeof Address`)
+ * @typeParam R - Repository class (e.g. `DefaultCrudRepository`)
+ */
+export function defineRepositoryClass<
+ M extends typeof Model,
+ R extends Repository>
+>(
+ modelClass: M,
+ baseRepositoryClass: BaseRepositoryClass,
+): ModelRepositoryClass, R> {
+ const repoName = modelClass.name + 'Repository';
+ const defineNamedRepo = new Function(
+ 'ModelCtor',
+ 'BaseRepository',
+ `return class ${repoName} extends BaseRepository {
+ constructor(dataSource) {
+ super(ModelCtor, dataSource);
+ }
+ };`,
+ );
+
+ const repo = defineNamedRepo(modelClass, baseRepositoryClass);
+ assert.equal(repo.name, repoName);
+ return repo;
+}
+
+/**
+ * Create (define) an entity CRUD repository class for the given model.
+ * This function always uses `DefaultCrudRepository` as the base class,
+ * use `defineRepositoryClass` if you want to use your own base repository.
+ *
+ * @example
+ *
+ * ```ts
+ * const ProductRepository = defineCrudRepositoryClass<
+ * Product,
+ * typeof Product.prototype.id,
+ * ProductRelations
+ * >(Product);
+ * ```
+ *
+ * @param entityClass - An entity class such as `Product`.
+ *
+ * @typeParam E - An entity class
+ * @typeParam IdType - ID type for the entity
+ * @typeParam Relations - Relations for the entity
+ */
+export function defineCrudRepositoryClass<
+ E extends Entity,
+ IdType,
+ Relations extends object
+>(
+ entityClass: typeof Entity & {prototype: E},
+): ModelRepositoryClass> {
+ return defineRepositoryClass(entityClass, DefaultCrudRepository);
+}
+
+/**
+ * Create (define) a KeyValue repository class for the given entity.
+ * This function always uses `DefaultKeyValueRepository` as the base class,
+ * use `defineRepositoryClass` if you want to use your own base repository.
+ *
+ * @example
+ *
+ * ```ts
+ * const ProductKeyValueRepository = defineKeyValueRepositoryClass(Product);
+ * ```
+ *
+ * @param modelClass - An entity class such as `Product`.
+ *
+ * @typeParam M - Model class
+ */
+export function defineKeyValueRepositoryClass(
+ modelClass: typeof Model & {prototype: M},
+): ModelRepositoryClass> {
+ return defineRepositoryClass(modelClass, DefaultKeyValueRepository);
+}
diff --git a/packages/repository/src/index.ts b/packages/repository/src/index.ts
index 51f62b96adbb..420c28b19cc1 100644
--- a/packages/repository/src/index.ts
+++ b/packages/repository/src/index.ts
@@ -30,6 +30,7 @@ export * from './model';
export * from './query';
export * from './relations';
export * from './repositories';
+export * from './define-repository-class';
export * from './transaction';
export * from './type-resolver';
export * from './types';
diff --git a/packages/rest-crud/README.md b/packages/rest-crud/README.md
index 77b1d4c3ec34..b3e2eae2ac2e 100644
--- a/packages/rest-crud/README.md
+++ b/packages/rest-crud/README.md
@@ -64,9 +64,9 @@ class defined without the need for a repository or controller class file.
If you would like more flexibility, e.g. if you would only like to define a
default `CrudRest` controller or repository, you can use the two helper methods
-(`defineCrudRestController` and `defineCrudRepositoryClass`) exposed from
-`@loopback/rest-crud`. These functions will help you create controllers and
-respositories using code.
+(`defineCrudRestController` from `@loopback/rest-crud` and
+`defineCrudRepositoryClass` from `@loopback/repository`). These functions will
+help you create controllers and repositories using code.
For the examples in the following sections, we are also assuming a model named
`Product`, and a datasource named `db` have already been created.
@@ -106,6 +106,8 @@ on the Model) for your app.
Usage example:
```ts
+import {defineCrudRepositoryClass} from '@loopback/repository';
+
const ProductRepository = defineCrudRepositoryClass(Product);
this.repository(ProductRepository);
inject('datasources.db')(ProductRepository, undefined, 0);
@@ -118,6 +120,8 @@ Here is an example of an app which uses `defineCrudRepositoryClass` and
requirements.
```ts
+import {defineCrudRepositoryClass} from '@loopback/repository';
+
export class TryApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
diff --git a/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts b/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts
index 19429ca7185d..b409babbe794 100644
--- a/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts
+++ b/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts
@@ -4,6 +4,7 @@
// License text available at https://opensource.org/licenses/MIT
import {
+ defineCrudRepositoryClass,
Entity,
EntityCrudRepository,
juggler,
@@ -18,7 +19,7 @@ import {
givenHttpServerConfig,
toJSON,
} from '@loopback/testlab';
-import {defineCrudRepositoryClass, defineCrudRestController} from '../..';
+import {defineCrudRestController} from '../..';
// In this test scenario, we create a product with a required & an optional
// property and use the default model settings (strict mode, forceId).
diff --git a/packages/rest-crud/src/crud-rest.api-builder.ts b/packages/rest-crud/src/crud-rest.api-builder.ts
index c16625cf7e25..e211676bf0b3 100644
--- a/packages/rest-crud/src/crud-rest.api-builder.ts
+++ b/packages/rest-crud/src/crud-rest.api-builder.ts
@@ -18,12 +18,13 @@ import {
import {
ApplicationWithRepositories,
Class,
+ defineCrudRepositoryClass,
Entity,
EntityCrudRepository,
} from '@loopback/repository';
import {Model} from '@loopback/rest';
import debugFactory from 'debug';
-import {defineCrudRepositoryClass, defineCrudRestController} from '.';
+import {defineCrudRestController} from '.';
const debug = debugFactory('loopback:boot:crud-rest');
diff --git a/packages/rest-crud/src/index.ts b/packages/rest-crud/src/index.ts
index dc9318c2f8c5..3943ece3349b 100644
--- a/packages/rest-crud/src/index.ts
+++ b/packages/rest-crud/src/index.ts
@@ -13,7 +13,8 @@
* @packageDocumentation
*/
+// Re-export `defineCrudRepositoryClass` for backward-compatibility
+export {defineCrudRepositoryClass} from '@loopback/repository';
export * from './crud-rest.api-builder';
export * from './crud-rest.component';
export * from './crud-rest.controller';
-export * from './repository-builder';
diff --git a/packages/rest-crud/src/repository-builder.ts b/packages/rest-crud/src/repository-builder.ts
deleted file mode 100644
index 92c422250a8b..000000000000
--- a/packages/rest-crud/src/repository-builder.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright IBM Corp. 2019,2020. All Rights Reserved.
-// Node module: @loopback/rest-crud
-// This file is licensed under the MIT License.
-// License text available at https://opensource.org/licenses/MIT
-
-import {
- DefaultCrudRepository,
- Entity,
- EntityCrudRepository,
- juggler,
-} from '@loopback/repository';
-import assert from 'assert';
-
-/**
- * Create (define) a repository class for the given model.
- *
- * @example
- *
- * ```ts
- * const ProductRepository = defineCrudRepositoryClass(Product);
- * ```
- *
- * @param modelCtor A model class, e.g. `Product`.
- */
-export function defineCrudRepositoryClass<
- T extends Entity,
- IdType,
- Relations extends object = {}
->(
- entityClass: typeof Entity & {prototype: T},
-): RepositoryClass {
- const repoName = entityClass.name + 'Repository';
- const defineNamedRepo = new Function(
- 'EntityCtor',
- 'BaseRepository',
- `return class ${repoName} extends BaseRepository {
- constructor(dataSource) {
- super(EntityCtor, dataSource);
- }
- };`,
- );
-
- // TODO(bajtos) make DefaultCrudRepository configurable (?)
- const repo = defineNamedRepo(entityClass, DefaultCrudRepository);
- assert.equal(repo.name, repoName);
- return repo;
-}
-
-export interface RepositoryClass<
- T extends Entity,
- IdType,
- Relations extends object
-> {
- new (ds: juggler.DataSource): EntityCrudRepository;
-}