Adjusted the ModelRepository to make repository methods more type-safe

This commit is contained in:
Christopher Allford 2020-10-01 14:31:55 -07:00
parent f2dda16c40
commit 787040db4c
7 changed files with 327 additions and 137 deletions

View File

@ -1,5 +1,17 @@
import { Model } from '../../models/model'; import { Model } from '../../models/model';
import { ModelRepository } from '../model-repository'; import {
CreatesModels,
DeletesChildModels,
DeletesModels,
ListsChildModels,
ListsModels,
ModelRepository,
ModelRepositoryParams,
ReadsChildModels,
ReadsModels,
UpdatesChildModels,
UpdatesModels,
} from '../model-repository';
class DummyModel extends Model { class DummyModel extends Model {
public name: string = ''; public name: string = '';
@ -9,20 +21,59 @@ class DummyModel extends Model {
Object.assign( this, partial ); Object.assign( this, partial );
} }
} }
type DummyModelParams = ModelRepositoryParams< DummyModel, undefined, { search: string }, 'name' >
class DummyChildModel extends Model {
public childName: string = '';
public constructor( partial?: Partial< DummyModel > ) {
super();
Object.assign( this, partial );
}
}
type DummyChildParams = ModelRepositoryParams< DummyChildModel, { parent: string }, { childSearch: string }, 'childName' >
describe( 'ModelRepository', () => { describe( 'ModelRepository', () => {
it( 'should list', async () => { it( 'should list', async () => {
const model = new DummyModel(); const model = new DummyModel();
const callback = jest.fn().mockResolvedValue( [ model ] ); const callback = jest.fn().mockResolvedValue( [ model ] );
const repository = new ModelRepository< DummyModel, { search: string } >( callback, null, null, null, null ); const repository: ListsModels< DummyModelParams > = new ModelRepository< DummyModelParams >(
callback,
null,
null,
null,
null,
);
const listed = await repository.list( { search: 'test' } ); const listed = await repository.list( { search: 'test' } );
expect( listed ).toContain( model ); expect( listed ).toContain( model );
expect( callback ).toHaveBeenCalledWith( { search: 'test' } ); expect( callback ).toHaveBeenCalledWith( { search: 'test' } );
} ); } );
it( 'should list child', async () => {
const model = new DummyChildModel();
const callback = jest.fn().mockResolvedValue( [ model ] );
const repository: ListsChildModels< DummyChildParams > = new ModelRepository< DummyChildParams >(
callback,
null,
null,
null,
null,
);
const listed = await repository.list( { parent: 'test' }, { childSearch: 'test' } );
expect( listed ).toContain( model );
expect( callback ).toHaveBeenCalledWith( { parent: 'test' }, { childSearch: 'test' } );
} );
it( 'should throw error on list without callback', () => { it( 'should throw error on list without callback', () => {
const repository = new ModelRepository< DummyModel >( null, null, null, null, null ); const repository: ListsModels< DummyModelParams > = new ModelRepository< DummyModelParams >(
null,
null,
null,
null,
null,
);
expect( () => repository.list() ).toThrowError( /not supported/i ); expect( () => repository.list() ).toThrowError( /not supported/i );
} ); } );
@ -30,15 +81,43 @@ describe( 'ModelRepository', () => {
it( 'should create', async () => { it( 'should create', async () => {
const model = new DummyModel(); const model = new DummyModel();
const callback = jest.fn().mockResolvedValue( model ); const callback = jest.fn().mockResolvedValue( model );
const repository = new ModelRepository< DummyModel >( null, callback, null, null, null ); const repository: CreatesModels< DummyModelParams > = new ModelRepository< DummyModelParams >(
null,
callback,
null,
null,
null,
);
const created = await repository.create( { name: 'test' } ); const created = await repository.create( { name: 'test' } );
expect( created ).toBe( model ); expect( created ).toBe( model );
expect( callback ).toHaveBeenCalledWith( { name: 'test' } ); expect( callback ).toHaveBeenCalledWith( { name: 'test' } );
} ); } );
it( 'should create child', async () => {
const model = new DummyChildModel();
const callback = jest.fn().mockResolvedValue( model );
const repository: CreatesModels< DummyChildParams > = new ModelRepository< DummyChildParams >(
null,
callback,
null,
null,
null,
);
const created = await repository.create( { childName: 'test' } );
expect( created ).toBe( model );
expect( callback ).toHaveBeenCalledWith( { childName: 'test' } );
} );
it( 'should throw error on create without callback', () => { it( 'should throw error on create without callback', () => {
const repository = new ModelRepository< DummyModel >( null, null, null, null, null ); const repository: CreatesModels< DummyModelParams > = new ModelRepository< DummyModelParams >(
null,
null,
null,
null,
null,
);
expect( () => repository.create( { name: 'test' } ) ).toThrowError( /not supported/i ); expect( () => repository.create( { name: 'test' } ) ).toThrowError( /not supported/i );
} ); } );
@ -46,15 +125,43 @@ describe( 'ModelRepository', () => {
it( 'should read', async () => { it( 'should read', async () => {
const model = new DummyModel(); const model = new DummyModel();
const callback = jest.fn().mockResolvedValue( model ); const callback = jest.fn().mockResolvedValue( model );
const repository = new ModelRepository< DummyModel >( null, null, callback, null, null ); const repository: ReadsModels< DummyModelParams > = new ModelRepository< DummyModelParams >(
null,
null,
callback,
null,
null,
);
const created = await repository.read( 1 ); const created = await repository.read( 1 );
expect( created ).toBe( model ); expect( created ).toBe( model );
expect( callback ).toHaveBeenCalledWith( 1 ); expect( callback ).toHaveBeenCalledWith( 1 );
} ); } );
it( 'should read child', async () => {
const model = new DummyChildModel();
const callback = jest.fn().mockResolvedValue( model );
const repository: ReadsChildModels< DummyChildParams > = new ModelRepository< DummyChildParams >(
null,
null,
callback,
null,
null,
);
const created = await repository.read( { parent: 'yes' }, 1 );
expect( created ).toBe( model );
expect( callback ).toHaveBeenCalledWith( { parent: 'yes' }, 1 );
} );
it( 'should throw error on read without callback', () => { it( 'should throw error on read without callback', () => {
const repository = new ModelRepository< DummyModel >( null, null, null, null, null ); const repository: ReadsModels< DummyModelParams > = new ModelRepository< DummyModelParams >(
null,
null,
null,
null,
null,
);
expect( () => repository.read( 1 ) ).toThrowError( /not supported/i ); expect( () => repository.read( 1 ) ).toThrowError( /not supported/i );
} ); } );
@ -62,30 +169,85 @@ describe( 'ModelRepository', () => {
it( 'should update', async () => { it( 'should update', async () => {
const model = new DummyModel(); const model = new DummyModel();
const callback = jest.fn().mockResolvedValue( model ); const callback = jest.fn().mockResolvedValue( model );
const repository = new ModelRepository< DummyModel, void, 'name' >( null, null, null, callback, null ); const repository: UpdatesModels< DummyModelParams > = new ModelRepository< DummyModelParams >(
null,
null,
null,
callback,
null,
);
const updated = await repository.update( 1, { name: 'new-name' } ); const updated = await repository.update( 1, { name: 'new-name' } );
expect( updated ).toBe( model ); expect( updated ).toBe( model );
expect( callback ).toHaveBeenCalledWith( 1, { name: 'new-name' } ); expect( callback ).toHaveBeenCalledWith( 1, { name: 'new-name' } );
} ); } );
it( 'should update child', async () => {
const model = new DummyChildModel();
const callback = jest.fn().mockResolvedValue( model );
const repository: UpdatesChildModels< DummyChildParams > = new ModelRepository< DummyChildParams >(
null,
null,
null,
callback,
null,
);
const updated = await repository.update( { parent: 'test' }, 1, { childName: 'new-name' } );
expect( updated ).toBe( model );
expect( callback ).toHaveBeenCalledWith( { parent: 'test' }, 1, { childName: 'new-name' } );
} );
it( 'should throw error on update without callback', () => { it( 'should throw error on update without callback', () => {
const repository = new ModelRepository< DummyModel, void, 'name' >( null, null, null, null, null ); const repository: UpdatesModels< DummyModelParams > = new ModelRepository< DummyModelParams >(
null,
null,
null,
null,
null,
);
expect( () => repository.update( 1, { name: 'new-name' } ) ).toThrowError( /not supported/i ); expect( () => repository.update( 1, { name: 'new-name' } ) ).toThrowError( /not supported/i );
} ); } );
it( 'should delete', async () => { it( 'should delete', async () => {
const callback = jest.fn().mockResolvedValue( true ); const callback = jest.fn().mockResolvedValue( true );
const repository = new ModelRepository< DummyModel >( null, null, null, null, callback ); const repository: DeletesModels = new ModelRepository< DummyModelParams >(
null,
null,
null,
null,
callback,
);
const success = await repository.delete( 1 ); const success = await repository.delete( 1 );
expect( success ).toBe( true ); expect( success ).toBe( true );
expect( callback ).toHaveBeenCalledWith( 1 ); expect( callback ).toHaveBeenCalledWith( 1 );
} ); } );
it( 'should delete child', async () => {
const callback = jest.fn().mockResolvedValue( true );
const repository: DeletesChildModels< DummyChildParams > = new ModelRepository< DummyChildParams >(
null,
null,
null,
null,
callback,
);
const success = await repository.delete( { parent: 'yes' }, 1 );
expect( success ).toBe( true );
expect( callback ).toHaveBeenCalledWith( { parent: 'yes' }, 1 );
} );
it( 'should throw error on delete without callback', () => { it( 'should throw error on delete without callback', () => {
const repository = new ModelRepository< DummyModel >( null, null, null, null, null ); const repository: DeletesModels = new ModelRepository< DummyModelParams >(
null,
null,
null,
null,
null,
);
expect( () => repository.delete( 1 ) ).toThrowError( /not supported/i ); expect( () => repository.delete( 1 ) ).toThrowError( /not supported/i );
} ); } );

View File

@ -1,8 +1,43 @@
import { Model, ModelID, ModelParentID } from '../models/model'; import { Model, ModelID } from '../models/model';
// Helpers for making types easier to work with. /**
type UpdateParams< T extends Model, U > = [ U ] extends [ keyof T ] ? Pick< T, U > : undefined; * An interface for describing the shape of parent identifiers for repositories.
type HasParent< P, T, F > = [ P ] extends [ ModelParentID ] ? T : F; *
* @typedef ModelParentID
* @alias Object.<string,ModelID>
*/
interface ModelParentID {
[ key: number ]: ModelID
}
/**
* This type describes the structure of different kinds of data that is extracted
* for use in the repository to provide type-safety to repository actions.
*/
export interface ModelRepositoryParams<
T extends Model = any,
// @ts-ignore
ParentID extends ModelParentID | undefined = any,
// @ts-ignore
ListParams = any,
// @ts-ignore
UpdateParams extends keyof T = any,
> {
// Since TypeScript's type system is structural we need to add something to this type to prevent
// it from matching with everything else (since it is an empty interface).
thisTypeIsDeclarativeOnly: string;
}
/**
* These helpers will extract information about a model from its repository params to be used in the repository.
*/
type ModelClass< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< infer X > ] ? X : never;
type ParentID< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< any, infer X > ] ? X : never;
type ListParams< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< any, any, infer X > ] ? X : never;
type UpdateParams< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< infer C, any, any, infer X > ] ?
( [ X ] extends [ keyof C ] ? Pick< C, X > : never ) :
never;
type HasParent< T extends ModelRepositoryParams, P, C > = [ ParentID< T > ] extends [ ModelParentID ] ? P : C;
/** /**
* A callback for listing models using a data source. * A callback for listing models using a data source.
@ -13,20 +48,23 @@ type HasParent< P, T, F > = [ P ] extends [ ModelParentID ] ? T : F;
* @template {Model} T * @template {Model} T
* @template L * @template L
*/ */
export type ListFn< T extends Model, L > = ( params?: L ) => Promise< T[] >; export type ListFn< T extends ModelRepositoryParams > = ( params?: ListParams< T > ) => Promise< ModelClass< T >[] >;
/** /**
* A callback for listing child models using a data source. * A callback for listing child models using a data source.
* *
* @callback ListChildFn * @callback ListChildFn
* @param {Partial.<T>} parent The parent identifier for the model. * @param {P} parent The parent identifier for the model.
* @param {L} [params] The list parameters for the query. * @param {L} [params] The list parameters for the query.
* @return {Promise.<Array.<T>>} Resolves to an array of created models. * @return {Promise.<Array.<T>>} Resolves to an array of created models.
* @template {Model} T * @template {Model} T
* @template {ModelParentID} P * @template {ModelParentID} P
* @template L * @template L
*/ */
export type ListChildFn< T extends Model, P extends ModelParentID | undefined, L > = ( parent: P, params?: L ) => Promise< T[] >; export type ListChildFn< T extends ModelRepositoryParams > = (
parent: ParentID< T >,
params?: ListParams< T >
) => Promise< ModelClass< T >[] >;
/** /**
* A callback for creating a model using a data source. * A callback for creating a model using a data source.
@ -36,7 +74,7 @@ export type ListChildFn< T extends Model, P extends ModelParentID | undefined, L
* @return {Promise.<T>} Resolves to the created model. * @return {Promise.<T>} Resolves to the created model.
* @template {Model} T * @template {Model} T
*/ */
export type CreateFn< T extends Model > = ( properties: Partial< T > ) => Promise< T >; export type CreateFn< T extends ModelRepositoryParams > = ( properties: Partial< ModelClass< T > > ) => Promise< ModelClass< T > >;
/** /**
* A callback for reading a model using a data source. * A callback for reading a model using a data source.
@ -46,7 +84,7 @@ export type CreateFn< T extends Model > = ( properties: Partial< T > ) => Promis
* @return {Promise.<T>} Resolves to the read model. * @return {Promise.<T>} Resolves to the read model.
* @template {Model} T * @template {Model} T
*/ */
export type ReadFn< T extends Model > = ( id: ModelID ) => Promise< T >; export type ReadFn< T extends ModelRepositoryParams > = ( id: ModelID ) => Promise< ModelClass< T > >;
/** /**
* A callback for reading a child model using a data source. * A callback for reading a child model using a data source.
@ -58,22 +96,21 @@ export type ReadFn< T extends Model > = ( id: ModelID ) => Promise< T >;
* @template {Model} T * @template {Model} T
* @template {ModelParentID} P * @template {ModelParentID} P
*/ */
export type ReadChildFn< T extends Model, P extends ModelParentID | undefined > = ( parent: P, childID: ModelID ) => Promise< T >; export type ReadChildFn< T extends ModelRepositoryParams > = ( parent: ParentID< T >, childID: ModelID ) => Promise< ModelClass< T > >;
/** /**
* A callback for updating a model using a data source. * A callback for updating a model using a data source.
* *
* @callback UpdateFn * @callback UpdateFn
* @param {ModelID} id The ID of the model. * @param {ModelID} id The ID of the model.
* @param {Pick.<T,U>} properties The properties to update. * @param {Partial.<T>} properties The properties to update.
* @return {Promise.<T>} Resolves to the updated model. * @return {Promise.<T>} Resolves to the updated model.
* @template {Model} T * @template {Model} T
* @template {string} U
*/ */
export type UpdateFn< T extends Model, U extends keyof T | undefined > = ( export type UpdateFn< T extends ModelRepositoryParams > = (
id: ModelID, id: ModelID,
properties: UpdateParams< T, U >, properties: UpdateParams< T >,
) => Promise< T >; ) => Promise< ModelClass< T > >;
/** /**
* A callback for updating a child model using a data source. * A callback for updating a child model using a data source.
@ -81,17 +118,16 @@ export type UpdateFn< T extends Model, U extends keyof T | undefined > = (
* @callback UpdateChildFn * @callback UpdateChildFn
* @param {P} parent The parent identifier for the model. * @param {P} parent The parent identifier for the model.
* @param {ModelID} childID The ID of the model. * @param {ModelID} childID The ID of the model.
* @param {Pick.<T,U>} properties The properties to update. * @param {Partial.<T>} properties The properties to update.
* @return {Promise.<T>} Resolves to the updated model. * @return {Promise.<T>} Resolves to the updated model.
* @template {Model} T * @template {Model} T
* @template {ModelParentID} P * @template {ModelParentID} P
* @template {string} U
*/ */
export type UpdateChildFn< T extends Model, P extends ModelParentID | undefined, U extends keyof T | undefined > = ( export type UpdateChildFn< T extends ModelRepositoryParams > = (
parent: P, parent: ParentID< T >,
childID: ModelID, childID: ModelID,
properties: UpdateParams< T, U >, properties: UpdateParams< T >,
) => Promise< T >; ) => Promise< ModelClass< T > >;
/** /**
* A callback for deleting a model from a data source. * A callback for deleting a model from a data source.
@ -111,7 +147,7 @@ export type DeleteFn = ( id: ModelID ) => Promise< boolean >;
* @return {Promise.<boolean>} Resolves to true once the model has been deleted. * @return {Promise.<boolean>} Resolves to true once the model has been deleted.
* @template {ModelParentID} P * @template {ModelParentID} P
*/ */
export type DeleteChildFn< P extends ModelParentID | undefined > = ( parent: P, childID: ModelID ) => Promise< boolean >; export type DeleteChildFn< T extends ModelRepositoryParams > = ( parent: ParentID< T >, childID: ModelID ) => Promise< boolean >;
/** /**
* An interface for repositories that can list models. * An interface for repositories that can list models.
@ -121,8 +157,8 @@ export type DeleteChildFn< P extends ModelParentID | undefined > = ( parent: P,
* @template {Model} T * @template {Model} T
* @template L * @template L
*/ */
export interface ListsModels< T extends Model, L = undefined > { export interface ListsModels< T extends ModelRepositoryParams > {
list( params?: L ): Promise< T[] >; list( params?: ListParams< T > ): Promise< ModelClass< T >[] >;
} }
/** /**
@ -134,8 +170,8 @@ export interface ListsModels< T extends Model, L = undefined > {
* @template {ModelParentID} P * @template {ModelParentID} P
* @template L * @template L
*/ */
export interface ListsChildModels< T extends Model, P extends ModelParentID | undefined, L = undefined > { export interface ListsChildModels< T extends ModelRepositoryParams > {
list( parent: P, params?: L ): Promise< T[] >; list( parent: ParentID< T >, params?: ListParams< T > ): Promise< ModelClass< T >[] >;
} }
/** /**
@ -145,8 +181,8 @@ export interface ListsChildModels< T extends Model, P extends ModelParentID | un
* @property {CreateFn.<T>} create Creates a model using the repository. * @property {CreateFn.<T>} create Creates a model using the repository.
* @template {Model} T * @template {Model} T
*/ */
export interface CreatesModels< T extends Model > { export interface CreatesModels< T extends ModelRepositoryParams > {
create( properties: Partial< T > ): Promise< T >; create( properties: Partial< ModelClass< T > > ): Promise< ModelClass< T > >;
} }
/** /**
@ -156,8 +192,8 @@ export interface CreatesModels< T extends Model > {
* @property {ReadFn.<T>} read Reads a model using the repository. * @property {ReadFn.<T>} read Reads a model using the repository.
* @template {Model} T * @template {Model} T
*/ */
export interface ReadsModels< T extends Model > { export interface ReadsModels< T extends ModelRepositoryParams > {
read( id: ModelID ): Promise< T >; read( id: ModelID ): Promise< ModelClass< T > >;
} }
/** /**
@ -168,33 +204,31 @@ export interface ReadsModels< T extends Model > {
* @template {Model} T * @template {Model} T
* @template {ModelParentID} P * @template {ModelParentID} P
*/ */
export interface ReadsChildModels< T extends Model, P extends ModelParentID | undefined > { export interface ReadsChildModels< T extends ModelRepositoryParams > {
read( parent: P, childID: ModelID ): Promise< T >; read( parent: ParentID< ModelRepositoryParams >, childID: ModelID ): Promise< ModelClass< T > >;
} }
/** /**
* An interface for repositories that can update models. * An interface for repositories that can update models.
* *
* @typedef UpdatesModels * @typedef UpdatesModels
* @property {UpdateFn.<T,U>} update Updates a model using the repository. * @property {UpdateFn.<T>} update Updates a model using the repository.
* @template {Model} T * @template {Model} T
* @template {string} U
*/ */
export interface UpdatesModels< T extends Model, U extends keyof T | undefined > { export interface UpdatesModels< T extends ModelRepositoryParams > {
update( id: ModelID, properties: UpdateParams< T, U > ): Promise< T >; update( id: ModelID, properties: UpdateParams< T > ): Promise< ModelClass< T > >;
} }
/** /**
* An interface for repositories that can update models. * An interface for repositories that can update models.
* *
* @typedef UpdatesChildModels * @typedef UpdatesChildModels
* @property {UpdateChildFn.<T,P,U>} update Updates a model using the repository. * @property {UpdateChildFn.<T,P>} update Updates a model using the repository.
* @template {Model} T * @template {Model} T
* @template {ModelParentID} P * @template {ModelParentID} P
* @template {string} U
*/ */
export interface UpdatesChildModels< T extends Model, P extends ModelParentID | undefined, U extends keyof T | undefined > { export interface UpdatesChildModels< T extends ModelRepositoryParams > {
update( parent: P, childID: ModelID, properties: UpdateParams< T, U > ): Promise< T >; update( parent: ParentID< T >, childID: ModelID, properties: UpdateParams< T > ): Promise< ModelClass< T > >;
} }
/** /**
@ -214,8 +248,8 @@ export interface DeletesModels {
* @property {DeleteChildFn.<P>} delete Deletes a model using the repository. * @property {DeleteChildFn.<P>} delete Deletes a model using the repository.
* @template {ModelParentID} P * @template {ModelParentID} P
*/ */
export interface DeletesChildModels< P extends ModelParentID | undefined > { export interface DeletesChildModels< T extends ModelRepositoryParams > {
delete( parent: P, childID: ModelID ): Promise< boolean >; delete( parent: ParentID< T >, childID: ModelID ): Promise< boolean >;
} }
/** /**
@ -224,31 +258,25 @@ export interface DeletesChildModels< P extends ModelParentID | undefined > {
* error when attempting to perform that action. * error when attempting to perform that action.
* *
* @template {Model} T * @template {Model} T
* @template {Object|undefined} L * @template {ModelParentID} P
* @template {string|undefined} U * @template {Object} L
* @template {ModelParentID|undefined} P
*/ */
export class ModelRepository< export class ModelRepository< T extends ModelRepositoryParams > implements
T extends Model, ListsModels< T >,
L = undefined, ListsChildModels< T >,
U extends keyof T | undefined = undefined,
P extends ModelParentID | undefined = undefined
> implements
ListsModels< T, L >,
ListsChildModels< T, P, L >,
ReadsModels< T >, ReadsModels< T >,
ReadsChildModels< T, HasParent< P, P, undefined > >, ReadsChildModels< T >,
UpdatesModels< T, U >, UpdatesModels< T >,
UpdatesChildModels< T, HasParent< P, P, undefined >, U >, UpdatesChildModels< T >,
DeletesModels, DeletesModels,
DeletesChildModels< HasParent< P, P, undefined > > { DeletesChildModels< T > {
/** /**
* The hook used to list models. * The hook used to list models.
* *
* @type {ListFn.<T,P,L>|ListChildFn<T,P,L>} * @type {ListFn.<T,P,L>|ListChildFn<T,P,L>}
* @private * @private
*/ */
private readonly listHook: HasParent< P, ListChildFn< T, P, L >, ListFn< T, L > > | null; private readonly listHook: HasParent< T, ListChildFn< T >, ListFn< T > > | null;
/** /**
* The hook used to create models * The hook used to create models
@ -264,15 +292,15 @@ export class ModelRepository<
* @type {ReadFn.<T>|ReadChildFn.<T,P>} * @type {ReadFn.<T>|ReadChildFn.<T,P>}
* @private * @private
*/ */
private readonly readHook: HasParent< P, ReadChildFn< T, P >, ReadFn< T > > | null; private readonly readHook: HasParent< T, ReadChildFn< T >, ReadFn< T > > | null;
/** /**
* The hook used to update models. * The hook used to update models.
* *
* @type {UpdateFn.<T,U>|UpdateChildFn.<T,P,U>} * @type {UpdateFn.<T>|UpdateChildFn.<T,P>}
* @private * @private
*/ */
private readonly updateHook: HasParent< P, UpdateChildFn< T, P, U >, UpdateFn< T, U > > | null; private readonly updateHook: HasParent< T, UpdateChildFn< T >, UpdateFn< T > > | null;
/** /**
* The hook used to delete models. * The hook used to delete models.
@ -280,7 +308,7 @@ export class ModelRepository<
* @type {DeleteFn|DeleteChildFn.<P>} * @type {DeleteFn|DeleteChildFn.<P>}
* @private * @private
*/ */
private readonly deleteHook: HasParent< P, DeleteChildFn< P >, DeleteFn > | null; private readonly deleteHook: HasParent< T, DeleteChildFn< T >, DeleteFn > | null;
/** /**
* Creates a new repository instance. * Creates a new repository instance.
@ -288,15 +316,15 @@ export class ModelRepository<
* @param {ListFn.<T,L>|ListChildFn<T,P,L>} listHook The hook for model listing. * @param {ListFn.<T,L>|ListChildFn<T,P,L>} listHook The hook for model listing.
* @param {CreateFn.<T>|null} createHook The hook for model creation. * @param {CreateFn.<T>|null} createHook The hook for model creation.
* @param {ReadFn.<T>|ReadChildFn.<T,P>|null} readHook The hook for model reading. * @param {ReadFn.<T>|ReadChildFn.<T,P>|null} readHook The hook for model reading.
* @param {UpdateFn.<T,U>|UpdateChildFn.<T,P,U>|null} updateHook The hook for model updating. * @param {UpdateFn.<T>|UpdateChildFn.<T,P>|null} updateHook The hook for model updating.
* @param {DeleteFn|DeleteChildFn.<P>|null} deleteHook The hook for model deletion. * @param {DeleteFn|DeleteChildFn.<P>|null} deleteHook The hook for model deletion.
*/ */
public constructor( public constructor(
listHook: HasParent< P, ListChildFn< T, P, L >, ListFn< T, L > > | null, listHook: HasParent< T, ListChildFn< T >, ListFn< T > > | null,
createHook: CreateFn< T > | null, createHook: CreateFn< T > | null,
readHook: HasParent< P, ReadChildFn< T, P >, ReadFn< T > > | null, readHook: HasParent< T, ReadChildFn< T >, ReadFn< T > > | null,
updateHook: HasParent< P, UpdateChildFn< T, P, U >, UpdateFn< T, U > > | null, updateHook: HasParent< T, UpdateChildFn< T >, UpdateFn< T > > | null,
deleteHook: HasParent< P, DeleteChildFn< P >, DeleteFn > | null, deleteHook: HasParent< T, DeleteChildFn< T >, DeleteFn > | null,
) { ) {
this.listHook = listHook; this.listHook = listHook;
this.createHook = createHook; this.createHook = createHook;
@ -312,19 +340,22 @@ export class ModelRepository<
* @param {L} [params] The params when using the parent. * @param {L} [params] The params when using the parent.
* @return {Promise.<Array.<T>>} Resolves to the listed models. * @return {Promise.<Array.<T>>} Resolves to the listed models.
*/ */
public list( paramsOrParent?: L | P, params?: L ): Promise< T[] > { public list(
paramsOrParent?: ListParams< T > | ParentID< T >,
params?: ListParams< T >,
): Promise< ModelClass< T >[] > {
if ( ! this.listHook ) { if ( ! this.listHook ) {
throw new Error( 'The \'create\' operation is not supported on this model.' ); throw new Error( 'The \'create\' operation is not supported on this model.' );
} }
if ( params === undefined ) { if ( params === undefined ) {
return ( this.listHook as ListFn< T, L > )( return ( this.listHook as ListFn< T > )(
paramsOrParent as L, paramsOrParent as ListParams< T >,
); );
} }
return ( this.listHook as ListChildFn< T, P, L > )( return ( this.listHook as ListChildFn< T > )(
paramsOrParent as P, paramsOrParent as ParentID< T >,
params, params,
); );
} }
@ -335,7 +366,7 @@ export class ModelRepository<
* @param {Partial.<T>} properties The properties to create the model with. * @param {Partial.<T>} properties The properties to create the model with.
* @return {Promise.<T>} Resolves to the created model. * @return {Promise.<T>} Resolves to the created model.
*/ */
public create( properties: Partial< T > ): Promise< T > { public create( properties: Partial< ModelClass< T > > ): Promise< ModelClass< T > > {
if ( ! this.createHook ) { if ( ! this.createHook ) {
throw new Error( 'The \'create\' operation is not supported on this model.' ); throw new Error( 'The \'create\' operation is not supported on this model.' );
} }
@ -351,9 +382,9 @@ export class ModelRepository<
* @return {Promise.<T>} Resolves to the loaded model. * @return {Promise.<T>} Resolves to the loaded model.
*/ */
public read( public read(
idOrParent: ModelID | P | undefined, idOrParent: ModelID | ParentID< T >,
childID?: ModelID, childID?: ModelID,
): Promise< T > { ): Promise< ModelClass< T > > {
if ( ! this.readHook ) { if ( ! this.readHook ) {
throw new Error( 'The \'read\' operation is not supported on this model.' ); throw new Error( 'The \'read\' operation is not supported on this model.' );
} }
@ -364,8 +395,8 @@ export class ModelRepository<
); );
} }
return ( this.readHook as ReadChildFn< T, P > )( return ( this.readHook as ReadChildFn< T > )(
idOrParent as P, idOrParent as ParentID< T >,
childID, childID,
); );
} }
@ -379,23 +410,23 @@ export class ModelRepository<
* @return {Promise.<T>} Resolves to the updated model. * @return {Promise.<T>} Resolves to the updated model.
*/ */
public update( public update(
idOrParent: ModelID | P | undefined, idOrParent: ModelID | ParentID< T >,
propertiesOrChildID: UpdateParams< T, U > | ModelID, propertiesOrChildID: UpdateParams< T > | ModelID,
properties?: UpdateParams< T, U >, properties?: UpdateParams< T >,
): Promise< T > { ): Promise< ModelClass< T > > {
if ( ! this.updateHook ) { if ( ! this.updateHook ) {
throw new Error( 'The \'update\' operation is not supported on this model.' ); throw new Error( 'The \'update\' operation is not supported on this model.' );
} }
if ( properties === undefined ) { if ( properties === undefined ) {
return ( this.updateHook as UpdateFn< T, U > )( return ( this.updateHook as UpdateFn< T > )(
idOrParent as ModelID, idOrParent as ModelID,
propertiesOrChildID as UpdateParams< T, U >, propertiesOrChildID as UpdateParams< T >,
); );
} }
return ( this.updateHook as UpdateChildFn< T, P, U > )( return ( this.updateHook as UpdateChildFn< T > )(
idOrParent as P, idOrParent as ParentID< T >,
propertiesOrChildID as ModelID, propertiesOrChildID as ModelID,
properties, properties,
); );
@ -408,7 +439,10 @@ export class ModelRepository<
* @param {ModelID} [childID] The ID of the model when using the parent. * @param {ModelID} [childID] The ID of the model when using the parent.
* @return {Promise.<T>} Resolves to the loaded model. * @return {Promise.<T>} Resolves to the loaded model.
*/ */
public delete( idOrParent: ModelID | P | undefined, childID?: ModelID ): Promise< boolean > { public delete(
idOrParent: ModelID | ParentID< T >,
childID?: ModelID,
): Promise< boolean > {
if ( ! this.deleteHook ) { if ( ! this.deleteHook ) {
throw new Error( 'The \'delete\' operation is not supported on this model.' ); throw new Error( 'The \'delete\' operation is not supported on this model.' );
} }
@ -419,8 +453,8 @@ export class ModelRepository<
); );
} }
return ( this.deleteHook as DeleteChildFn< P > )( return ( this.deleteHook as DeleteChildFn< T > )(
idOrParent as P, idOrParent as ParentID< T >,
childID, childID,
); );
} }

View File

@ -6,16 +6,6 @@
*/ */
export type ModelID = string | number; export type ModelID = string | number;
/**
* An interface for describing the shape of parent identifiers.
*
* @typedef ModelParentID
* @alias Object.<string,ModelID>
*/
export interface ModelParentID {
[ key: number ]: ModelID
}
/** /**
* The base class for all models. * The base class for all models.
*/ */

View File

@ -1,4 +1,4 @@
import { Model, ModelID, ModelParentID } from '../model'; import { Model } from '../model';
import { HTTPClient } from '../../http'; import { HTTPClient } from '../../http';
import { settingRESTRepository } from '../../repositories/rest/settings/setting'; import { settingRESTRepository } from '../../repositories/rest/settings/setting';
@ -7,16 +7,6 @@ import { settingRESTRepository } from '../../repositories/rest/settings/setting'
*/ */
type SettingType = 'text' | 'select' | 'multiselect' | 'checkbox' | 'number'; type SettingType = 'text' | 'select' | 'multiselect' | 'checkbox' | 'number';
/**
* An interface describing the shape of setting parent data.
*
* @typedef SettingParentID
* @property {ModelID} settingGroupID The ID of the setting group for the setting.
*/
export interface SettingParentID extends ModelParentID {
settingGroupID: ModelID;
}
/** /**
* A setting object. * A setting object.
*/ */

View File

@ -1,8 +1,10 @@
import { HTTPClient } from '../../../http'; import { HTTPClient } from '../../../http';
import { CreateFn, CreatesModels, ModelRepository } from '../../../framework/model-repository'; import { CreateFn, CreatesModels, ModelRepository, ModelRepositoryParams } from '../../../framework/model-repository';
import { SimpleProduct } from '../../../models'; import { SimpleProduct } from '../../../models';
function restCreate( httpClient: HTTPClient ): CreateFn< SimpleProduct > { type SimpleProductParams = ModelRepositoryParams< SimpleProduct, never, never, 'regularPrice' >;
function restCreate( httpClient: HTTPClient ): CreateFn< SimpleProductParams > {
return async ( properties ) => { return async ( properties ) => {
const response = await httpClient.post( const response = await httpClient.post(
'/wc/v3/products', '/wc/v3/products',
@ -27,7 +29,7 @@ function restCreate( httpClient: HTTPClient ): CreateFn< SimpleProduct > {
* @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using. * @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using.
* @return {CreatesModels.<SimpleProduct>} A repository for interacting with models via the REST API. * @return {CreatesModels.<SimpleProduct>} A repository for interacting with models via the REST API.
*/ */
export function simpleProductRESTRepository( httpClient: HTTPClient ): CreatesModels< SimpleProduct > { export function simpleProductRESTRepository( httpClient: HTTPClient ): CreatesModels< SimpleProductParams > {
return new ModelRepository( return new ModelRepository(
null, null,
restCreate( httpClient ), restCreate( httpClient ),

View File

@ -1,8 +1,10 @@
import { HTTPClient } from '../../../http'; import { HTTPClient } from '../../../http';
import { ListFn, ListsModels, ModelRepository } from '../../../framework/model-repository'; import { ListFn, ListsModels, ModelRepository, ModelRepositoryParams } from '../../../framework/model-repository';
import { SettingGroup } from '../../../models/settings/setting-group'; import { SettingGroup } from '../../../models';
function restList( httpClient: HTTPClient ): ListFn< SettingGroup, void > { type SettingGroupParams = ModelRepositoryParams< SettingGroup, never, never, never >;
function restList( httpClient: HTTPClient ): ListFn< SettingGroupParams > {
return async () => { return async () => {
const response = await httpClient.get( '/wc/v3/settings' ); const response = await httpClient.get( '/wc/v3/settings' );
@ -26,8 +28,8 @@ function restList( httpClient: HTTPClient ): ListFn< SettingGroup, void > {
* @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using. * @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using.
* @return {ListsModels.<SettingGroup>} A repository for interacting with models via the REST API. * @return {ListsModels.<SettingGroup>} A repository for interacting with models via the REST API.
*/ */
export function settingGroupRESTRepository( httpClient: HTTPClient ): ListsModels< SettingGroup > { export function settingGroupRESTRepository( httpClient: HTTPClient ): ListsModels< SettingGroupParams > {
return new ModelRepository( return new ModelRepository< SettingGroupParams >(
restList( httpClient ), restList( httpClient ),
null, null,
null, null,

View File

@ -3,13 +3,23 @@ import {
ListChildFn, ListChildFn,
ListsChildModels, ListsChildModels,
ModelRepository, ModelRepository,
ModelRepositoryParams,
ReadChildFn, ReadChildFn,
ReadsChildModels, ReadsChildModels,
UpdateChildFn, UpdatesChildModels, UpdateChildFn,
UpdatesChildModels,
} from '../../../framework/model-repository'; } from '../../../framework/model-repository';
import { Setting, SettingParentID } from '../../../models/settings/setting'; import { Setting } from '../../../models';
function restList( httpClient: HTTPClient ): ListChildFn< Setting, SettingParentID, undefined > { /**
* @typedef SettingParentID
* @property {string} settingGroupID The ID of the setting group we're a child of.
*/
type SettingParentID = { settingGroupID: string };
type SettingParams = ModelRepositoryParams< Setting, SettingParentID, never, 'value' >;
function restList( httpClient: HTTPClient ): ListChildFn< SettingParams > {
return async ( parent ) => { return async ( parent ) => {
const response = await httpClient.get( '/wc/v3/settings/' + parent.settingGroupID ); const response = await httpClient.get( '/wc/v3/settings/' + parent.settingGroupID );
@ -30,7 +40,7 @@ function restList( httpClient: HTTPClient ): ListChildFn< Setting, SettingParent
}; };
} }
function restRead( httpClient: HTTPClient ): ReadChildFn< Setting, SettingParentID > { function restRead( httpClient: HTTPClient ): ReadChildFn< SettingParams > {
return async ( parent, id ) => { return async ( parent, id ) => {
const response = await httpClient.get( '/wc/v3/settings/' + parent.settingGroupID + '/' + id ); const response = await httpClient.get( '/wc/v3/settings/' + parent.settingGroupID + '/' + id );
@ -46,7 +56,7 @@ function restRead( httpClient: HTTPClient ): ReadChildFn< Setting, SettingParent
}; };
} }
function restUpdate( httpClient: HTTPClient ): UpdateChildFn< Setting, SettingParentID, 'value' > { function restUpdate( httpClient: HTTPClient ): UpdateChildFn< SettingParams > {
return async ( parent, id, params ) => { return async ( parent, id, params ) => {
const response = await httpClient.patch( const response = await httpClient.patch(
'/wc/v3/settings/' + parent.settingGroupID + '/' + id, '/wc/v3/settings/' + parent.settingGroupID + '/' + id,
@ -76,10 +86,10 @@ function restUpdate( httpClient: HTTPClient ): UpdateChildFn< Setting, SettingPa
* } A repository for interacting with models via the REST API. * } A repository for interacting with models via the REST API.
*/ */
export function settingRESTRepository( httpClient: HTTPClient ): export function settingRESTRepository( httpClient: HTTPClient ):
ListsChildModels< Setting, SettingParentID > & ListsChildModels< SettingParams > &
ReadsChildModels< Setting, SettingParentID > & ReadsChildModels< SettingParams > &
UpdatesChildModels< Setting, SettingParentID, 'value' > { UpdatesChildModels< SettingParams > {
return new ModelRepository< Setting, undefined, 'value', SettingParentID >( return new ModelRepository< SettingParams >(
restList( httpClient ), restList( httpClient ),
null, null,
restRead( httpClient ), restRead( httpClient ),