0.0.8 • Published 4 years ago

m-model-core v0.0.8

Weekly downloads
5
License
MIT
Repository
github
Last release
4 years ago

m-model-core

m-model-core brings back-end like model in front-end. It manages global state in OOP style.. You can use it with state containers such as Redux without writing any single action or reducer.

It is inspired by mongoose and is intended to be used in front-end applications for synchronously manipulating the data. It is written in typescript.

When to use

m-model-core is designed to handle data instances that can be represented as objects and have unique identifier, just like in most of the databases. Most of the time, it can be perfectly used for managing mongodb documents or mysql rows.

Creating first model

// models/user.ts
import { createModel, createCRUDActionTypes } from "m-model-core";
import { store } from "../store"; // you will see this file later

// declaring type of data that will be managed by our model
interface IUser {
	id: number;
	firstname: string;
	lastname: string;
}

// creating base class
const UserBaseModel = createModel<"id", IUser>({
	keyOfId: "id", // unique indentifier key for our documents
	dockeys: ["id", "firstname", "lastname"], // list of all properties that our raw document will have
	actionTypes: createCRUDActionTypes("USER"), // easily create action types by passing word which will be used in the middle of the action type names
	getInstances: (() => store.getState().users) as any, // Tell our model how to get instances. `as any` is required here to avoid circular dependence on typescript.
	dispatch: (action => store.dispatch(action)) as any, // Provide dispatch function to our model
	subscribe: (listener => store.subscribe(listener)) as any, // Provide subscribe function. Most likely, store will not be created yet, so we need to wrap it inside another function
});

export class User extends UserBaseModel {
    //custom instance method
	getFullname() {
		return this.firstname + "" + this.lastname;
	}

    // custom static method
	static findNameless() {
		return this.findManySync({ firstname: "" });
	}
}

In this example, we will use redux as our state container. To create store, we can cimply write

// store.ts
import { combineReducers } from "redux";
import { User } from "./models/user";

const appRootReducer = combineReducers({
	//... other reducers
	users: User.reducer,
	//... other reducers
});
export const store = createStore(appRootReducer);

As you see, our model ships with its own reducer. We are easily creating action types by calling createCRUDActionTypes function.

Using model

All the synchronous methods are provided for you. You will notice that mosth of the methods are ended with "Sync" suffix. This is intended, as unlike models in back-end, our model behaves synchronously and does not affect real database, just our store.

Searching

You can search documents by id

const user1 = User.findByIdSync(12);

or query

const user2 = User.findOneSync({ firstname: "Tom", lastname: "Holland" });
if (user2) {
    // Note that search result will have all the instance methods that we have defined above
    console.log("Hello", user2.getFullname(), "!");
}

You can also search multiple documents the same way

const users = User.findManyByIdsSync([ 13, 183, 20 ]);
const tomUsers = User.findManySync({ firstname: "Tom" });

For geting all documents:

const allUsers = User.getAllSync();

Updating

This will set firstname to Thomas for the first user that has firstname "Tom"

User.updateOneSync({ firstname: "Tom" }, { firstname: "Thomas" });

For updating multiple documents, simply use updateManySync

User.updateManySync({ firstname: "Tom" }, { firstname: "Thomas" });

On the other hand, you can direclty manipulate instance of User class;

const tom = User.findOneSync({ firstname: "Tom" });
if (tom) {
    tom.firstname = "Thomas";
    tom.saveSync(); // call saveSync to update document in store
}

Deleting

You can delete by id or query

User.deleteByIdSync(13);
User.deleteOneSync({ firstname: "Thomas" }); // Sorry tom :'(

For deleting multiple documents, simply use the following methods:

User.deleteManyByIdsSync([ 13, 183, 20 ]);
User.deleteManySync({ lastname: "***" }); // don't want to offend any surname

For deleting all the documents:

User.clearAllSync();

Creating

As opposed to most back-end models, we are not creating document directly in database. Most of the time, we are sending requests to get documents from server and then loading into our store using our model. That's why we use "load" instead of "create" when naming.

User.loadOneSync({
	id: 234,
	firstname: "Zendaya",
    lastname: "Coleman",
});

For multiple documents:

const users = await loadUsers(); // load users array from somewhere
User.loadManySync(users);

Alternatively, you can directly create instance of our model and call saveSync on it:

const boss = new User({
    id: 255,
    firstname: "Samuel",
    lastname: "Jackson"
});
boss.saveSync();

Advanced

Our helpful packages for models

  • For easily make your store in sync with respect to other tabs, saving documents in localStorage and easily solving other problems in front-end, please check m-model-common package.
  • For using optimized react hooks for searching documents in model and being reactive, check m-model-react
  • If you use @hapi/Joi, see m-model-joi package as it ships with helper functions for validating documents

Subscriptions

You can subscribe data modification in several ways.

  • subscribeChange
  • subscribeChangeById
  • subscribeOneDocChangeByQuery
  • subscribeManyDocsChangeByIds
  • subscribeManyDocsChangeByQuery

They all return function, which will unsubscribe upon calling it.

Additional configurations

You can easily add indexes to you model by providing indices during creating base model

const UserBaseModel = createModel<"id", IUser>({
	// ... other parameters
	indices: [
		{ fields: ["lastname"] },
		{ fields: ["firstname", "lastname"], unique: true },
	],
	// ... other parameters
})

You can easily add timestamps:

const UserBaseModel = createModel<"id", IUser>({
	// ... other parameters
    timestamps: { createdAt: "created_at", updatedAt: "updated_at" }, // provide the keys which will be used as timestamps. Their type must be `Date` in document interface
	// ... other parameters
})

loading many documents

When you fetch all the documents from server, usually what you want to do is to replace all the documents with new ones.

User.loadManySync(docs);

However, if we had loaded some documents previously and they have been deleted by now, such documents will still exist in our store. That's why we need to clear our store first by using clearAllSync. Alternatively, you can use write:

User.loadManySync(docs, "replaceAll");

and it will clear old documents and load new ones in one action. Instead of "replaceAll", you can pass query too. For instance, you loaded all users with firstname "Tom", what you want is to delete all the local documents with "Tom" and load the fresh documents. In one action, you can:

User.loadManySync(docs, { firstname: "Tom" });

Raw documents

If you have instance of the model, you can call .toObject() method to convert it into plain javascript objects:

const user = User.findByIdSync(34);
if (user) {
    console.log(user.toObject());
}

As we have seen, all the search and loading methods return model instances. If you want to get raw objects and avoid creating class instances, view the following codes:

const rawUser = User.findByIdSync(34, "raw");
const rawUsesr = User.findManyByIdsSync([34], "raw");

Same for updateOneSync, updateManySync, getAllSync, loadOneSync and loadManySync methods;

0.0.8

4 years ago

0.0.7

4 years ago

0.0.5

4 years ago

0.0.6

4 years ago

0.0.4

4 years ago

0.0.3

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago