1.1.2 • Published 2 years ago

@amalgamaco/entity-store v1.1.2

Weekly downloads
-
License
ISC
Repository
github
Last release
2 years ago

Entity Store Package

A set of base classes for defining entities, stores for each entity, and relationships between them, facilitating the tasks of creating, fetching, updating and deleting them.

[TOC]

Complete example

// entities/User.types.ts
export interface UserAttributes{
	id: number
	email: string
	fullName: string,
	avatarUrl: string,
}

export interface UserSerialization {
	id: number
	email: string
	full_name: string,
	avatar_url: string,
}

// entities/User.ts
import { StoreEntity, IRootStore } from '@amalgamaco/entity-store';
import { makeObservable, observable } from 'mobx';
import { UserAttributes, UserSerialization } from './User.types';

export default class User extends StoreEntity {
	id: number;
	email: string;
	fullName: string;
	avatarUrl: string;

	constructor( attributes: UserAttributes, rootStore?: IRootStore ) {
		super( rootStore );

		this.id = attributes.id;
		this.email = attributes.email;
		this.fullName = attributes.fullName;
		this.avatarUrl = attributes.avatarUrl;

		makeObservable( this, {
			id: observable,
			email: observable,
			fullName: observable,
			avatarUrl: observable
		} );
	}

	updateWith( other: User ): User {
		this.fullName = other.fullName;
		this.email = other.email;
		this.avatarUrl = other.avatarUrl;

		return this;
	}

	toJSON() {
		return {
			id: this.id,
			email: this.email,
			full_name: this.fullName,
			avatar_url: this.avatarUrl
		};
	}

	static fromJSON( attributes: UserSerialization, rootStore?: IRootStore ) {
		return new User( {
			id: attributes.id,
			email: attributes.email,
			fullName: attributes.full_name,
			avatarUrl: attributes.avatar_url
		}, rootStore );
	}
}

// stores/RootStore.types.ts
import type { AttrsType, EntityStore } from '@amalgamaco/entity-store';
import User, { UserSerialization } from '../entities/User';

export type UserStore = EntityStore<User, AttrsType<typeof User>>;

export type UsersStoreSerialization = UserSerialization[];

export interface RootStoreSerialization {
	usersStore?: UsersStoreSerialization
}

// stores/RootStore.ts
import { makeAutoObservable } from 'mobx';
import { PersistableRootStore } from '@amalgama/mobx-store-persistor'; // Private repository
import { EntityStore, AttrsType } from '@amalgamaco/entity-store';
import User from '../entities/User';
import {
	RootStoreSerialization, UsersStoreSerialization, UserStore
} from './RooStore.types';

export class RootStore implements PersistableRootStore {
	userStore: UserStore;
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	[ key: string ]: any;

	constructor() {
		this.userStore = new EntityStore<User, AttrsType<typeof User>>( User, this );

		makeAutoObservable( this );
	}

	serializationToPersist(): RootStoreSerialization {
		return {
			usersStore: this.userStore.serialize() as UsersStoreSerialization
		};
	}

	rehydrateWithSerialization( serialization: RootStoreSerialization ) {
		this.userStore.hydrate( serialization.usersStore || [] );
	}

	getStore( storeName: string ) {
		return this[ `${storeName}Store` ] || null;
	}

	clearStore() {
		this.userStore.clear();
	}
}

EntityStore

A store for entities of a given type.

import { EntityStore, AttrsType } from '@amalgamaco/entity-store';
import Item from '../entities/Item';
import { rootStore } from './shared';

const itemsStore = new EntityStore<Item, AttrsType<typeof Item>>( Item, rootStore );
rootStore.itemsStore = itemsStore;

const item = itemsStore.create( { id: 1, ... } );

Methods

constructor( EntityClass, rootStore ): EntityStore<Entity, EntityAttrs>

Creates a new store for the given EntityClass and rootStore.

Parameters | Name | Type | Description | | --- | --- | --- | | EntityClass | IEntityClass<Entity, EntityAttrs> | The class of the entities that will be stored in this store. | | rootStore | IRootStore | The root store that saves a reference to the stores that contain the relations for the entities stored in this store. When creating a new entity using the method create, this root store will be automatically set to the created store entity. |

create( attributes: EntityAttrs )

Creates a new entity with the given attributes using the entity's constructor method. Sets the store's root store as the entity root store.

Parameters | Name | Type | Description | | --- | --- | --- | | attributes| EntityAttrs which should extend IEntityRequiredAttributes | The attributes to create the entity with. |

add( entity: Entity )

Adds a new entity to the store. If an entity with the same id already exists, it's updated with the passed entity by calling updateWith on the existing entity.

Parameters | Name | Type | Description | | --- | --- | --- | | entity | Entity which should extend IEntity | The entity to add to the store. |

has( id: ID ): boolean

Returns true if there is an entity with the passed id stored in the store. Returns false otherwise.

Parameters | Name | Type | Description | | --- | --- | --- | | id | ID | The id to check. |

get( id: ID ): Entity | null

Returns the entity with the passed id or null if there is no entity for that id.

Parameters | Name | Type | Description | | --- | --- | --- | | id | ID | The id of the entity to retrieve from the store. |

all(): Entity[]

Returns a list with all the entities stored in the store.

where( condition: ( entity: Entity ) => boolean ): Entity[]

Returns a list of all the entities stored in the store that meet the passed condition.

Parameters | Name | Type | Description | | --- | --- | --- | | condition | ( entity: Entity ) => boolean | The condition to check against the entities in the store. This callback receives an entity and must return a boolean indicating if the given entity meets the condition or not. |

delete( id: ID )

Deletes the entity with the passed id from the store.

Parameters | Name | Type | Description | | --- | --- | --- | | id | ID | The id of the entity to delete. |

deleteAll( ids: ID[] )

Deletes all the entities identified by the passed ids list.

Parameters | Name | Type | Description | | --- | --- | --- | | ids | ID[] | A list of ids of the entities to delete. |

replace( entities: Entity[] )

Replaces the stored entities with the ones passed.

Parameters | Name | Type | Description | | --- | --- | --- | | entities | Entity[] which should extend IEntity | The entities to replace the store content with. |

clear()

Empties the store.

serialize(): IStoreSerialization

Serializes the store. It rerurns a list of the serializations for all the entities in the store.

hydrate( serialization : IStoreSerialization )

Fills the store with the entities created from the serialization passed.

Parameters | Name | Type | Description | | --- | --- | --- | | serialization | IStoreSerialization | The serialization that will be used to fill the store. |

Types

ID

The type of the id attribute of an entity.

type ID = number | string;

IRootStore

Represents the type of a EntityStore's root store.

interface IRootStore {
	[ key: string ]: IStore
}

IEntity

Represents the type of a entity that will be stored in a EntityStore.

interface IEntity {
	id: ID,
	rootStore?: IRootStore

	updateWith( anotherEntity: IEntity ): IEntity,
	toJSON(): IEntitySerialization,
}

IEntityClass

Represents the type of the class that contructs the entities that will be stored in a EntityStore.

interface IEntityClass<T extends IEntity, Attrs extends IEntityRequiredAttributes> {
	new( attributes: Attrs, rootStore?: IRootStore ): T
	fromJSON( attributes: IEntitySerialization, rootStore?: IRootStore ): T
}

IEntityRequiredAttributes

Represents the required atttributes for all entities.

interface IEntityRequiredAttributes {
	id: ID
}

AttrsType

Represents the attributes of an Entity. This attributes are calculated as the first parameter of the entity's constructor.

type AttrsType<T extends new ( ...args: any ) => any> = First<ConstructorParameters<T>>;

IEntitySerialization

Represents the serialization of an entity.

interface IEntitySerialization {
	id: ID
}

IStoreSerialization

Represents the serialization of a store.

type IStoreSerialization = IEntitySerialization[];

JSONValue

Represents any possible JSON value.

type JSONValue =
	| string
	| number
	| boolean
	| null
	| { [x: string]: JSONValue }
	| Array<JSONValue>;

StoreEntity

A base class for entities that will be stored using an EntityStore.

Relations

The are two ways to define relations between entities, one using Typescript property decorators and one using a static function defined in the Entity class. You can use the one you find more convinient for you.

decorators

This package provides two decorators to define properties that come from a related store: @hasMany and @belongsTo.

@hasMany

Specifies that the decorated property values will be retrieved from a related store.

parameters | Name | Description | | --- | --- | | storeName | The name of the store in the root store to retrieve the related entities from. | | lkName | The name of the property in this entity that returns the ids of the related entities. |

usage

import { StoreEntity, hasMany } from '@amalgamaco/entity-store';
import Comment from './Comment';

class Post extends StoreEntiy {
	// This property holds the ids of the related comments and
	// will be used to retrive the related comments from their store.
	commentIDs: number[];

	// Here we decorate the comments property with the @hasMany decorator
	// passing the name of the related entities store and the name of the
	// property that holds the releated entities ids.
	@hasMany( 'commentsStore', 'commentIDs' )
	comments!: Comment[];

	...
}

IMPORTANT: Don't forget to add the ! at the end of the decorated property definition telling Typescript that property will have a value (calculated by the decorator) even if we don't set a default one.

@belongsTo

Specifies that the decorated property value will be retrieved from a related store.

parameters | Name | Description | | --- | --- | | storeName | The name of the store in the root store to retrieve the related entity from. | | lkName | The name of the property in this entity that returns the id of the related entity. |

usage

import { StoreEntity, belongsTo } from '@amalgamaco/entity-store';
import User from './User';

class Post extends StoreEntiy {
	// This property holds the id of the related author and
	// will be used to retrive the related author from its store.
	authorID: number[];

	// Here we decorate the author property with the @belongsTo decorator
	// passing the name of the related entity store and the name of the
	// property that holds the releated entity id.
	@belongsTo( 'usersStore', 'authorID' )
	author?: User;

	...
}

IMPORTANT: Don't forget to add the ? at the end of the decorated property definition telling Typescript that property may be undefined and that we don't need to set a default value for it.

relationships static method

You can also specify the entity relations using the relationships static method. This method must return a list of IRelationshipConfig items.

IRelationshipConfig Each item on the relationships static method must meet the next interface:

export interface IRelationshipConfig {
	name: string,
	type: 'BELONGS_TO' | 'HAS_MANY',
	store: string,
	lookupKey: string,
}
  • name: The name of the property that will hold the relationship.
  • type: The type of relationship to define:
    • BELONGS_TO: This property will only return an entity whose id is indicated by the value of the property lookupKey.
    • HAS_MANY: This property willl return a list of entities whose ids are indicated by the value of the property lookupKey.
  • store: The name of the store in the root store where the related entities are stored.
  • lookupKey: The name of the property in the entity that holds the id or ids of the related entities.

usage

import { StoreEntity } from '@amalgamaco/entity-store';
import User from './User';
import Comment from './Comment';

class Post extends StoreEntiy {
	// This property holds the id of the related author and
	// will be used to retrive the related author from its store.
	authorID: number[];

	// This property holds the ids of the related comments and
	// will be used to retrive the related comments from their store.
	commentIDs: number[];

	author?: User;
	comments!: Comment[];


	static relationships(): IRelationshipConfig[] {
		return [
			{
				name: 'author',
				lookupKey: 'authorID',
				store: 'usersStore',
				type: 'BELONGS_TO'
			},
			{
				name: 'comments',
				lookupKey: 'commentIDs',
				store: 'commentsStore',
				type: 'HAS_MANY'
			}
		];
	}

	...
}

IMPORTANT: When declaring the properties that will return the related entities don't forget to add a ? at the end of the property name for BELONGS_TO relations and a ! at the end of the property name for HAS_MANY relations to prevent Typescript from asking to initialize the properties with default values.

1.1.2

2 years ago

1.1.1

2 years ago