Plateforme Javascript Objectum

Si vous souhaitez créer facilement des applications Web en utilisant uniquement javascript (full-stack), je vous suggÚre de vous familiariser avec la plate-forme objectum. La nouvelle version de la plate-forme est le fruit d'une expérience de travail sur la version précédente, utilisée depuis 10 ans. Les deux versions sont utilisées dans le développement de divers systÚmes d'information - ce sont des solutions régionales et des systÚmes pour les organisations. La plate-forme de la nouvelle version est déjà utilisée sur les serveurs de production et se développera encore longtemps. Plus de détails.



logo



Captures d'écran des développements existants



Exemple d'application Web:






Exemple 1






Exemple de site (pas de rendu de serveur):






Exemple 2






Packages de plateforme



La plate-forme se compose des packages npm suivants: objectum , objectum-client , objectum-proxy , objectum- react , objectum-cli



objectum



, . ORM PostgreSQL. (Objectum Database Engine), PL/pgSQL. Redis. node cluster. .

‹



objectum-client



objectum-proxy objectum. , , . , , .. , JSON.



objectum-proxy



objectum objectum-client. :



  • . — CRUD SQL.


objectum-react



react UI. bootstrap fontawesome. redux, mobx .

App.js. bootstrap.css:



import "objectum-react/lib/css/bootstrap.css";
import "objectum-react/lib/css/objectum.css";
import "objectum-react/lib/fontawesome/css/all.css";


objectum-cli



. , . .





, , React. . , objectum-react.

objectum. , NodeJS, PostgreSQL Redis.

- catalog .






catalogue








:



npm i -g objectum-cli


/opt/objectum



mkdir /opt/objectum
objectum-cli --create-platform --path /opt/objectum


:



--redis-host 127.0.0.1 -     Redis
--redis-port 6379
--objectum-port 8200 - ,    objectum




"catalog":



objectum-cli --create-project catalog --path /opt/objectum


:



--project-port 3100 - ,    
--db-host 127.0.0.1 -     PostgreSQL
--db-port 5432
--db-dbPassword 1 -   catalog
--db-dbaPassword 12345 -   postgres
--password admin -   


create-react-app. ES Modules.





:



cd /opt/objectum/server 
node index-8200.js


:



cd /opt/objectum/projects/catalog 
node index-3100.js
npm start


http://localhost:3000

, : admin






auth








"Objectum" :



  • —
  • — SQL
  • —
  • —
  • —





auth








UI, objectum-cli.



JSON



cd /opt/objectum/projects/catalog  
objectum-cli --import-json scripts/catalog-cli.json --file-directory scripts/files


catalog-cli.json
{
    "createModel": [
        {
            "name": "Item", 
            "code": "item"
        },
        {
            "name": "Item", 
            "code": "item",
            "parent": "d"
        },
        {
            "name": "Type", 
            "code": "type",
            "parent": "d.item"
        },
        {
            "name": "Item", 
            "code": "item",
            "parent": "t"
        },
        {
            "name": "Comment", 
            "code": "comment",
            "parent": "t.item"
        }
    ],
    "createProperty": [
        {
            "model": "d.item.type", 
            "name": "Name", 
            "code": "name",
            "type": "string"
        },
        {
            "model": "t.item.comment", 
            "name": "Item", 
            "code": "item",
            "type": "item"
        },
        {
            "model": "t.item.comment", 
            "name": "Date",
            "code": "date",
            "type": "date"
        },
        {
            "model": "t.item.comment",
            "name": "Text",
            "code": "text",
            "type": "string"
        },
        {
            "model": "item", 
            "name": "Date", 
            "code": "date",
            "type": "date"
        },
        {
            "model": "item", 
            "name": "Name", 
            "code": "name",
            "type": "string"
        },
        {
            "model": "item",
            "name": "Description",
            "code": "description",
            "type": "string",
            "opts": {
                "wysiwyg": true
            }
        },
        {
            "model": "item", 
            "name": "Cost", 
            "code": "cost",
            "type": "number",
            "opts": {
                "min": 0
            }
        },
        {
            "model": "item", 
            "name": "Type", 
            "code": "type",
            "type": "d.item.type"
        },
        {
            "model": "item", 
            "name": "Photo", 
            "code": "photo",
            "type": "file",
            "opts": {
                "image": {
                    "width": 300,
                    "height": 200,
                    "aspect": 1.5
                }
            }
        }
    ],
    "createQuery": [
        {
            "name": "Item",
            "code": "item"
        },
        {
            "name": "List",
            "code": "list",
            "parent": "item",
            "query": [
                "{\"data\": \"begin\"}",
                "select",
                "    {\"prop\": \"a.id\", \"as\": \"id\"},",
                "    {\"prop\": \"a.name\", \"as\": \"name\"},",
                "    {\"prop\": \"a.description\", \"as\": \"description\"},",
                "    {\"prop\": \"a.cost\", \"as\": \"cost\"},",
                "    {\"prop\": \"a.date\", \"as\": \"date\"},",
                "    {\"prop\": \"a.photo\", \"as\": \"photo\"},",
                "    {\"prop\": \"a.type\", \"as\": \"type\"}",
                "{\"data\": \"end\"}",
                "",
                "{\"count\": \"begin\"}",
                "select",
                "    count (*) as num",
                "{\"count\": \"end\"}",
                "",
                "from",
                "    {\"model\": \"item\", \"alias\": \"a\"}",
                "",
                "{\"where\": \"empty\"}",
                "",
                "{\"order\": \"empty\"}",
                "",
                "limit {\"param\": \"limit\"}",
                "offset {\"param\": \"offset\"}"
            ]
        },
        {
            "name": "Item",
            "code": "item",
            "parent": "t"
        },
        {
            "name": "Comment",
            "code": "comment",
            "parent": "t.item",
            "query": [
                "{\"data\": \"begin\"}",
                "select",
                "    {\"prop\": \"a.id\", \"as\": \"id\"},",
                "    {\"prop\": \"a.item\", \"as\": \"item\"},",
                "    {\"prop\": \"a.date\", \"as\": \"date\"},",
                "    {\"prop\": \"a.text\", \"as\": \"text\"}",
                "{\"data\": \"end\"}",
                "",
                "{\"count\": \"begin\"}",
                "select",
                "    count (*) as num",
                "{\"count\": \"end\"}",
                "",
                "from",
                "    {\"model\": \"t.item.comment\", \"alias\": \"a\"}",
                "",
                "{\"where\": \"empty\"}",
                "",
                "{\"order\": \"empty\"}",
                "",
                "limit {\"param\": \"limit\"}",
                "offset {\"param\": \"offset\"}"
            ]
        }
    ],
    "createRecord": [
        {
            "_model": "d.item.type",
            "name": "Videocard",
            "_ref": "videocardType"
        },
        {
            "_model": "d.item.type",
            "name": "Processor"
        },
        {
            "_model": "d.item.type",
            "name": "Motherboard"
        },
        {
            "_model": "objectum.menu",
            "name": "User",
            "code": "user",
            "_ref": "userMenu"
        },
        {
            "_model": "objectum.menuItem",
            "menu": {
                "_ref": "userMenu"
            },
            "name": "Items",
            "icon": "fas fa-list",
            "order": 1,
            "path": "/model_list/item"
        },
        {
            "_model": "objectum.menuItem",
            "menu": {
                "_ref": "userMenu"
            },
            "name": "Dictionary",
            "icon": "fas fa-book",
            "order": 2,
            "_ref": "dictionaryMenuItem"
        },
        {
            "_model": "objectum.menuItem",
            "menu": {
                "_ref": "userMenu"
            },
            "name": "Item type",
            "icon": "fas fa-book",
            "parent": {
                "_ref": "dictionaryMenuItem"
            },
            "order": 1,
            "path": "/model_list/d_item_type"
        },
        {
            "_model": "objectum.role",
            "name": "User",
            "code": "user",
            "menu": {
                "_ref": "userMenu"
            },
            "_ref": "userRole"
        },
        {
            "_model": "objectum.user",
            "name": "User",
            "login": "user",
            "password": "user",
            "role": {
                "_ref": "userRole"
            }
        },
        {
            "_model": "objectum.menu",
            "name": "Guest",
            "code": "guest",
            "_ref": "guestMenu"
        },
        {
            "_model": "objectum.menuItem",
            "menu": {
                "_ref": "guestMenu"
            },
            "name": "Items",
            "icon": "fas fa-list",
            "order": 1,
            "path": "/model_list/item"
        },
        {
            "_model": "objectum.role",
            "name": "Guest",
            "code": "guest",
            "menu": {
                "_ref": "guestMenu"
            },
            "_ref": "guestRole"
        },
        {
            "_model": "objectum.user",
            "name": "Guest",
            "login": "guest",
            "password": "guest",
            "role": {
                "_ref": "guestRole"
            }
        },
        {
            "_model": "item",
            "name": "RTX 2080",
            "description": [
                "<ul>",
                "<li>11GB GDDR6</span></li>",
                "<li>CUDA Cores: 4352</span></li>",
                "<li>Display Connectors: DisplayPort, HDMI, USB Type-C</span></li>",
                "<li>Maximum Digital Resolution: 7680x4320</span></li>",
                "</ul>"
            ],
            "date": "2020-06-03T19:27:38.292Z",
            "type": {
                "_ref": "videocardType"
            },
            "cost": "800",
            "photo": "rtx2080.png"
        }
    ]
}


:



  • createModel — , name — , code — , parent — . .

    • item, d.item.type, t.item.comment.
    • "d.item.type" — . "item" "type". "d".
    • "t.item.comment" — . "item". "t".
  • createProperty — , name — , code — , model — , type — .. , opts — , , wysiwyg .
  • createQuery — SQL JSON , , , .

    • [] — .
    • {"...": "begin"}...{"...": "end"} SQL : (data), - (count), (where), (order), - (tree).
    • {"model": "item", "alias": "a"} "_id a".
    • {"prop": "a.name"} "a._id".
    • {"prop": "limit"} .
  • createRecord — .

    • _model —
    • name —
    • [] — .
    • _ref — .. id .
    • JSON .
    • "photo": "rtx2080.png" — . photo "rtx2080.png" scripts/files.


CSV



cd /opt/objectum/projects/catalog  
objectum-cli --import-csv scripts/stationery.csv --model item --file-directory/script/files --handler scripts/csv-handler.js
objectum-cli --import-csv scripts/tv.csv --model item --file-directory/script/files --handler scripts/csv-handler.js


CSV:



  • CSV (item)
  • ()
  • (csv-handler.js):

    • .. ()




ItemModel . ReactJS NodeJS ItemClientModel, ItemServerModel ItemModel, ItemClientModel extends ItemModel, ItemServerModel extends ItemModel.





App.js:



import ItemModel from "./models/ItemModel";

store.register ("item", ItemModel);


"item" ItemModel:



let record = await store.createRecord ({
    _model: "item",
    name: "Foo"
});


:



  • record.name = "Bar";
  • record.store
  • await record.sync ()


ItemModel.js
import React from "react";
import {Record, factory} from "objectum-client";
import {Action} from "objectum-react";

class ItemModel extends Record {
    static _renderGrid ({grid, store}) {
        return React.cloneElement (grid, {
            label: "Items", // grid label
            query: "item.list", // grid query
            onRenderTable: ItemModel.onRenderTable, // grid table custom render
            children: store.roleCode === "guest" ? null : <div className="d-flex">
                {grid.props.children}
                <Action label="Server action: getComments" onClickSelected={async ({progress, id}) => {
                    let recs = await store.remote ({
                        model: "item",
                        method: "getComments",
                        id,
                        progress
                    });
                    return JSON.stringify (recs)
                }} />
            </div>
        });
    }

    static onRenderTable ({grid, cols, colMap, recs, store}) {
        return (
            <div className="p-1">
                {recs.map ((rec, i) => {
                    let record = factory ({rsc: "record", data: Object.assign (rec, {_model: "item"}), store});

                    return (
                        <div key={i} className={`row border-bottom my-1 p-1 no-gutters ${grid.state.selected === i ? "bg-secondary text-white" : ""}`} onClick={() => grid.onRowClick (i)} >
                            <div className="col-6">
                                <div className="p-1">
                                    <div>
                                        <strong className="mr-1">Name:</strong>{rec.name}
                                    </div>
                                    <div>
                                        <strong className="mr-1">Date:</strong>{rec.date && rec.date.toLocaleString ()}
                                    </div>
                                    <div>
                                        <strong className="mr-1">Type:</strong>{rec.type && store.dict ["d.item.type"][rec.type].name}
                                    </div>
                                    <div>
                                        <strong className="mr-1">Cost:</strong>{rec.cost}
                                    </div>
                                    <div>
                                        <strong>Description:</strong>
                                    </div>
                                    <div dangerouslySetInnerHTML={{__html: `${record.description || ""}`}} />
                                </div>
                            </div>
                            <div className="col-6 text-right">
                                {record.photo && <div>
                                     <img src={record.getRef ("photo")} className="img-fluid" width={400} height={300} alt={record.photo} />
                                </div>}
                            </div>
                        </div>
                    );
                })}
            </div>
        );
    }

    // item form layout
    static _layout () {
        return {
            "Information": [
                "id",
                [
                    "name", "date"
                ],
                [
                    "type", "cost"
                ],
                [
                    "description"
                ],
                [
                    "photo"
                ],
                [
                    "t.item.comment"
                ]
            ]
        };
    }

    static _renderForm ({form, store}) {
        return React.cloneElement (form, {
            defaults: {
                date: new Date ()
            }
        });
    }

    // new item render
    static _renderField ({field, store}) {
        if (field.props.property === "date") {
            return React.cloneElement (field, {showTime: true});
        } else {
            return field;
        }
    }

    // item render
    _renderField ({field, store}) {
        return ItemModel._renderField ({field, store});
    }
};

export default ItemModel;


:



  • _renderGrid — "item" /model_list/item. ModelList, Grid.
  • _layout — /model_record/:id ModelRecord, . , , , .
  • _renderForm —
  • _renderField — . .


:






auth








index.js:



import ItemModel from "./src/models/ItemServerModel.js";

proxy.register ("item", ItemModel);


store . .



ItemServerModel.js
import objectumClient from "objectum-client";
const {Record} = objectumClient;

function timeout (ms = 500) {
    return new Promise (resolve => setTimeout (() => resolve (), ms));
};

class ItemModel extends Record {
    async getComments ({progress}) {
        for (let i = 0; i < 10; i ++) {
            await timeout (1000);
            progress ({label: "processing", value: i + 1, max: 10});
        }
        return await this.store.getRecs ({
            model: "t.item.comment",
            filters: [
                ["item", "=", this.id]
            ]
        });
    }
};

export default ItemModel;


:



getComments () {
    return await store.remote ({
        model: "item",
        method: "getComments",
        myArg: "" 
    });
}




index.js:



import accessMethods from "./src/modules/access.js";

proxy.registerAccessMethods (accessMethods);


access.js
let map = {
    "guest": {
        "data": {
            "model": {
                "item": true, "d.item.type": true, "t.item.comment": true
            },
            "query": {
                "objectum.userMenuItems": true
            }
        },
        "read": {
            "objectum.role": true, "objectum.user": true, "objectum.menu": true, "objectum.menuItem": true
        }
    }
};
async function _init ({store}) {
};

function _accessData ({store, data}) {
    if (store.roleCode == "guest") {
        if (data.model) {
            return map.guest.data.model [store.getModel (data.model).getPath ()];
        }
        if (data.query) {
            return map.guest.data.query [store.getQuery (data.query).getPath ()];
        }
    } else {
        return true;
    }
};

function _accessFilter ({store, model, alias}) {
};

function _accessCreate ({store, model, data}) {
    return store.roleCode != "guest";
};

function _accessRead ({store, model, record}) {
    let modelPath = model.getPath ();

    if (store.roleCode == "guest") {
        if (modelPath == "objectum.user") {
            return record.login == "guest";
        }
        return map.guest.read [modelPath];
    }
    return true;
};

function _accessUpdate ({store, model, record, data}) {
    return store.roleCode != "guest";
};

function _accessDelete ({store, model, record}) {
    return store.roleCode != "guest";
};

export default {
    _init,
    _accessData,
    _accessFilter,
    _accessCreate,
    _accessRead,
    _accessUpdate,
    _accessDelete
};


. :



  • _init — .
  • _accessCreate — .
  • _accessRead — .
  • _accessUpdate — .
  • _accessDelete — .
  • _accessData — getData
  • _accessFilter — SQL . . .. .


, .

, , , , admin.





. - .

index.js:



import adminMethods from "./src/modules/admin.js";

proxy.registerAdminMethods (adminMethods);


admin.js
import fs from "fs";
import util from "util";

fs.readFileAsync = util.promisify (fs.readFile);

function timeout (ms = 500) {
    return new Promise (resolve => setTimeout (() => resolve (), ms));
};

async function readFile ({store, progress, filename}) {
    for (let i = 0; i < 10; i ++) {
        await timeout (1000);
        progress ({label: "processing", value: i + 1, max: 10});
    }
    return await fs.readFileAsync (filename, "utf8");
};

async function increaseCost ({store, progress}) {
    await store.startTransaction ("demo");

    let records = await store.getRecords ({model: "item"});

    for (let i = 0; i < records.length; i ++) {
        let record = records [i];

        record.cost = record.cost + 1;
        await record.sync ();
    }
    await store.commitTransaction ();

    return "ok";
};

export default {
    readFile,
    increaseCost
};


admin.js. - (guest).

:



await store.remote ({
    model: "admin",
    method: "readFile",
    filename: "package.json"
});


React



:



  • ObjectumApp — -
  • ObjectumRoute —
  • Auth —
  • Grid —

    • tree
  • Form —
  • Tabs, Tab —
  • Fields —

    • StringField — . : textarea, wysiwyg
    • NumberField —
    • BooleanField —
    • DateField — . showTime .
    • FileField — (). .
    • DictField —
    • SelectField —
    • ChooseField — .
    • JsonField — . . JSON
    • Field — .
    • JsonEditor — JSON
  • Tooltip —
  • Fade —
  • Action —


ObjectumApp



props:



  • locale — . "ru".
  • onCustomRender —
  • username, password — (guest).


Grid



(query) (model). :





    • localStorage


Form



. , . "" . , IP- .



Action



:



  • confirm —
  • :

    • ,




createReport, XLSX . :



import {createReport} from "objectum-react";

let recs = await store.getRecs ({model: "item"});
let rows = [
    [
        {text: "", style: "border_center", colSpan: 3}
    ],
    [
        {text: "", style: "border"},
        {text: "", style: "border"},
        {text: "", style: "border"}
    ],
    ...recs.map (rec => {
        return [
            {text: rec.name, style: "border"},
            {text: rec.date.toLocaleString (), style: "border"},
            {text: rec.cost, style: "border"}            
        ];
    })
];
createReport ({
    rows,
    columns: [40, 10, 10],
    font: {
        name: "Arial",
        size: 10
    }
});


:



  • rows — (row)
  • colSpan, rowSpan — HTML
  • columns —




. . , . , , , , . :





    • catalog_dev —
    • catalog_test ( catalog_dev) —
    • catalog_" " ( catalog_dev) —


    • region_dev —
    • region_" " ( region_dev) —
    • region" " ( region" ") —
  • ,


catalog:



let $o = require ("../../server/objectum");

$o.db.execute ({
    "code": "catalog",
    "fn": "export",
    "exceptRecords": ["item"],
    "file": "../schema/schema-catalog.json"
});


exceptRecords , .



:



let $o = require ("../../../server/objectum");

$o.db.execute ({
    "code": "catalog_test",
    "fn": "import",
    "file": "../schema/schema-catalog.json"
});




— . MacBook Pro Mid 2014 (MGX82).



(model.unlogged: false):



100 (.) 1000 (.) .
-: 1, : 1 0.5 4.9 204
-: 1, : 1 0.5 4.6 215
-: 1, : 1 0.5 4.4 227
-: 3, : 1, : 1, : 1 0.5 4.8 209
-: 10, : 10 0.6 5.8 172
-: 10, : 10 0.6 7.1 140
-: 10, : 10 0.6 10.1 98
-: 30, : 10, : 10, : 10 1.2 14.7 68
-: 100 : 100 2.3 27.3 37
-: 100 : 100 2.4 24.1 42
-: 100 : 100 2.3 24.6 40
-: 300 : 100, : 100, : 100 8.9 88.3 11


(model.unlogged: true):



100 (.) 1000 (.) .
-: 1, : 1 0.5 4.3 233
-: 1, : 1 0.4 4.1 244
-: 1, : 1 0.4 3.7 268
-: 3, : 1, : 1, : 1 0.5 3.8 261
-: 10, : 10 0.5 4.1 243
-: 10, : 10 0.4 4.0 251
-: 10, : 10 0.4 4.2 239
-: 30, : 10, : 10, : 10 0.5 4.9 202
-: 100 : 100 0.6 12.4 81
-: 100 : 100 0.7 6.1 162
-: 100 : 100 0.9 7.2 140
-: 300 : 100, : 100, : 100 1.1 11.1 90


‹

:



  • 1-
  • 2- 3-
  • 4- .


test.js



Conclusion



Consultez les pages d'accueil du package sur github pour plus d'informations. J'avoue que les informations sont rares, je vais essayer de les compléter. Licence de plate-forme MIT. Il est prévu de développer des packages supplémentaires pour l'analyse et d'autres domaines nécessaires.

Merci de votre attention.




All Articles