mmelotti / acey

OOP State Manager built with Lodash. ⚡

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

acey + Control. | - Code. | + Scalability. | - Debugging. | + Productivity.


OOP State Manager built with Lodash.

What super power it gives me ?

To Encapsulate your states inside Models and Collections to treat, access, format, and organize your data in a one and same place. 🔌


I work on React {Native}, can I use it ?

Yes, Acey works smoothly with React environment, its dev experience is the logical evolution of Redux.
On Acey there is:
- No action types.
- No reducers.
- No selectors.
- No context.
AND you can trigger your actions from wherever you want without any binding. 💥
Nice hmm? 😉


Seems great! 🤑 and it works as well with NodeJS, right?

Right, so Acey enable a built-in feature auto-syncing your states with your local storage. So Acey in the back-end, use this feature by storing your state in a JSON DB 🗄️.
When your program run, all your JSON files are pulled and directly added in the state of your collection (It's 100% cached, like Redis 📚).

So yeah, it works amazing for embedded systems, CLI tools, prototypes, MVP, or any other program that can work with a full DB cached. 💨









Quick implementations

1. A React Counter

See code

Step 1/2 - State | ./counter-model.ts

import { Model } from 'acey'

class CounterModel extends Model {

  constructor(initialState: any, options: any){
    super(initialState, options)
  }
  
  get = () => this.state.counter
  
  increment = () => this.setState({counter: this.get() + 1}).save()
  decrement = () => this.setState({counter: this.get() - 1}).save()
  
  /* `save()` save the Model's state in the Acey Store */
}

/* A `connected` Model has its state connected with the Acey store */
export default new CounterModel({counter: 0}, {connected: true, key: 'counter'})

Step 2/2 - Component | ./app.tsx

import React from 'react'
import { useAcey } from 'react-acey'
import Counter from './counter-model'

const App = () => {

  /* Bind the Counter Model with component. */
  useAcey([ Counter ])

  return (
    <div>
      <button onClick={Counter.decrement}>decrement</button>
      {Counter.get()}
      <button onClick={Counter.increment}>increment</button>
    </div>
  );
}

export default App;

2. A RESTful NodeJS API

See code

Step 1/2 - State | ./todos.ts

import { Model, Collection } from 'acey'
import { v4 as uuid } from 'uuid'

export class TodoModel extends Model {
    constructor(initialState: any = {}, options: any){
        super(initialState, options)
    }
}

export class TodoCollection extends Collection {
    constructor(initialState: any, options: any){
        super(initialState, [TodoModel, TodoCollection], options)
    }

    create = (content: string) => {
        todos.push({
            id: uuid(),
            created_at: new Date(),
            content
        }).store()
        return todos.last()
    }

    orderByLastCreation = () => this.orderBy(['created_at'], ['desc'])
}

export default new TodoCollection([], {connected: true, key: 'todolist'})

Step 2/2 - Server | ./index.ts

import { config } from 'acey'
import express from 'express' 
import morgan from 'morgan' //request logger
import LocalStorage from 'acey-node-store'
import todos from './todos'

const initServer = async () => {
    config.setStoreEngine(new LocalStorage('./db'))
    await config.done()

    const server = express()
    server.use(express.json());
    server.use(morgan('tiny'))
    return server
}

initServer().then((server) => {
    console.log('Server started ')

    server.post('/', (req: express.Request, res: express.Response) => {
        const t = todos.create(req.body.content)
        res.json(t.to().plain())
    })
    
    server.delete('/:id', (req: express.Request, res: express.Response) => {
        todos.deleteBy({'id': req.params.id}).store()
        res.sendStatus(200)
    })
    
    server.get('/', (req: express.Request, res: express.Response) => {
        res.json(todos.orderByLastCreation().to().plain())
    })
    
    server.get('/:id', (req: express.Request, res: express.Response) => {
        const t = todos.find({id: req.params.id})
        t ? res.json(t.to().plain()) : res.sendStatus(404)
        
    })
    
    server.listen(PORT, err => {
        if (err) throw err
        console.log(`> Ready on http://localhost:${PORT}`)
    })
})

3. A React-Native micro-blogging app

See code

Step 1/3 - State | ./post.ts

import { Model, Collection } from 'acey'
import moment from 'moment'

export class PostModel extends Model {

    constructor(initialState = {}, options){
        super(initialState, options)
    }

    ID = () => this.state.id
    content = () => this.state.content
    createdAt = () => this.state.created_at
    formatedCreationDate = () => moment(this.createdAt()).format("MMM Do");

    /* `save()` save the Model's state in the Acey Store */
    updateContent = (content) => this.setState({content}).save().store()
}

export class PostCollection extends Collection {
    constructor(initialState = [], options){
        super(initialState, [PostModel, PostCollection], options)
    }

    sortByCreationDate = () => this.orderBy(['created_at'], ['desc'])
    
    create = (content) => {
        PostList.push({
          id: Math.random().toString(), 
          content, 
          created_at: new Date()
        }).save().store()
    }
    
    /* 
      `store()` store the state in the Local Store
      
      (i) Acey auto-sync the local store's data with 
          their Model/Collection when the app reload.
    */
}

Step 2/3 - Components

Styles are not present to purposely make the code shorter and more readable.

./components/add-post-input.js

import React, {useState} from 'react';
import { 
    View,
    TextInput,
    TouchableOpacity,
    Text,
    Dimensions
 } from 'react-native';

const AddPostInput = (props) => {

    const { onSubmit } = props

    const [text, setText] = useState('')

    const onLocalSubmit = () => {
        onSubmit(text)
        setText('')
    }

    const renderSubmitTouchable = () => (
        <SubmitTouchable onPress={onLocalSubmit}>
            <SubmitText>CREATE</SubmitText>
        </SubmitTouchable>
    )

    return (
        <Container>
            <Input
                value={text}
                onChangeText={(text) => setText(text)}
                multiline
            />
            {renderSubmitTouchable()}
        </Container>        
    )
}

export default AddPostInput

Styles are not present to purposely make the code shorter and more readable.

./components/post.js

import React, { useState } from 'react'
import { 
    View,
    Text,
    TextInput,
    TouchableOpacity,
    Dimensions
} from 'react-native';

const Post = (props) => {
    const {
        post,
        onDelete
    } = props

    const [updateText, setUpdateText] = useState(post.content())
    const [isUpdating, setUpdatingStatus] = useState(false)

    onSubmitUpdate = () => {
        post.updateContent(updateText)
        setUpdatingStatus(false)
    }

    const renderUpdateContainer = () => (
        <UpdateContainer>
            <UpdateInput multiline={true} value={updateText} onChangeText={(text) => setUpdateText(text)} />
            <UpdateSubmitTouchable onPress={onSubmitUpdate}>
                <UpdateSubmitText>
                    UPDATE
                </UpdateSubmitText>
            </UpdateSubmitTouchable>
        </UpdateContainer>
    )

    const renderAction = (title = '', color = '', onPress = null) => (
        <ActionTouchable onPress={onPress} color={color}>
            <ActionText color={color}>{title}</ActionText>
        </ActionTouchable>
    )

    const renderActions = () => (
        <ActionsWrapper>
            {renderAction('Update', 'blue', () => setUpdatingStatus(true))}
            {renderAction('Delete', 'red', () => onDelete(post))}
        </ActionsWrapper>
    )

    return (
        <Container>
            {!isUpdating && <View>
                <TopWrapper>
                    <DateText>{post.formatedCreationDate()}</DateText>
                </TopWrapper>
                <ContentText>{post.content()}</ContentText>
                {renderActions()}
            </View>}
            {isUpdating && renderUpdateContainer()}
        </Container>
    )

}

export default Post

Step 3/3 - Main

Styles are not present to purposely make the code shorter and more readable.

./App.js

import React from 'react';
import {
  SafeAreaView,
  ScrollView,
  View,
  Text,
} from 'react-native';

import { config } from 'acey'
import { useAcey } from 'react-acey'
import { PostCollection } from './posts'

import Post from './src/components/post'
import AddPostInput from './src/components/add-post-input'

const PostList = new PostCollection([], {connected: true, key: 'postlist'})
config.setStoreEngine(AsyncStorage)
config.done()

const App = () => {

  useAcey([ PostList ])

  const onSubmit = (content) => PostList.create(content)
  const onDelete = (post) => PostList.delete(post).save().store()

  return (
    <>
      <ScrollView>
        <AddPostInput onSubmit={onSubmit} />
        {PostList.sortByCreationDate().map((post, index) => {
          return (
            <View key={index}>
              <Post post={post} onDelete={onDelete} />
            </View>
          )
        })}
      </ScrollView>
    </>
  );
};

export default App;


Get Started

Usage

yarn add acey

To start the Acey engine, you need to declare the configuration as done at the root of your application. Here's how, according to your environment:

ReactJS

import { config } from 'acey' //HERE
config.done() //HERE

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

make sure to install react-acey to bind your React components with your Models and Collections.

yarn add react-acey

React-Native

At the root of your app, bind the React Native Store Engine (AsyncStorage) with Acey to benefit Acey's key features.

import AsyncStorage from '@react-native-community/async-storage'
import { config } from 'acey'

config.setStoreEngine(AsyncStorage)
config.done()

make sure to install and link async-storage .

yarn add @react-native-community/async-storage

NextJS

Refer to the Next Acey wrapper documentation 💡


NodeJS

After all your collections have been instanced:

  1. bind the Acey Store Engine for Node with acey-node-store
  2. And set the config as done.
import NodeStorage from 'acey-node-store'
import { config } from 'acey'
import MyCollection1 from './my-collection-1'
import MyCollection2 from './my-collection-2'
...

const myCollect1 = new MyCollection1([], {connected: true, key: 'collection-1'})
const myCollect2 = new MyCollection2([], {connected: true, key: 'collection-2'})
...

config.setStoreEngine(NodeStorage)
config.done()

make sure to install acey-node-store .

yarn add acey-node-store


Small code demonstrations

1. hydrate vs setState and Model nesting. Click here

2. Model's local storage cycle exlained. Click here

About

OOP State Manager built with Lodash. ⚡


Languages

Language:TypeScript 98.4%Language:JavaScript 1.6%