@amalgamaco/entity-store
TypeScript icon, indicating that this package has built-in type declarations

1.1.2 • Public • Published

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.

Readme

Keywords

none

Package Sidebar

Install

npm i @amalgamaco/entity-store

Weekly Downloads

13

Version

1.1.2

License

ISC

Unpacked Size

33.2 kB

Total Files

22

Last publish

Collaborators

  • sebakz
  • ezeaguerre
  • damianh97
  • maurobender