Database


Module pgorm.js


Module [schema]/[Class].js


Module Base[Class].js


Command line


Database


When started, web server creates schema pgorm in database with procedures, tables, and other service objects

pgorm.orm_relation_enable(schema name, table_name name)


Procedure enable object-relational mapping (ORM) for specified table or view, create JavaScript modules

Parameters:
schemaschema
nametable or view name

Call example:
call pgorm.orm_relation_enable('example', 'invoice');

pgorm.orm_relation_disable(schema name, table_name name)


Procedure disable object-relational mapping (ORM) for specified table or view, remove JavaScript modules

Parameters:
schemaschema
nametable or view name

Call example:
call pgorm.orm_relation_disable('example', 'invoice');

pgorm.orm_relation


Table pgorm.orm_relation contains list of tables and views for enabled ORM. Edit of module_source field is allowed, and JavaScript module will be automatically updated

Table columns:
schemaschema
nametable or view name
typetype: 'table' or 'view'
class_nameJavaScript class name
module_sourcesource code of module orm/[schema]/[table_name].js

Usage example:
select * from pgorm.orm_relation;

update pgorm.orm_relation set module_source =
'import { BaseInvoice,BaseInvoiceArray } from "/orm/example/base/BaseInvoice.js"

export class Invoice extends BaseInvoice {

}

export class InvoiceArray extends BaseInvoiceArray {

}'
where schema='example' and name='invoice'; 

pgorm.orm_relation_column


Table pgorm.orm_relation_column contains list columns of tables or views for enabled ORM. Edit of class_field_name field is allowed, and JavaScript module will be automatically updated

Table columns:
schemaschema
relation_nametable or view name
column_namecolumn name
class_field_nameclass field name

Usage example:
select * from pgorm.orm_relation_column;

update pgorm.orm_relation_column
  set class_field_name='ExtInfo'
  where schema='example' and relation_name='invoice' and column_name='extended_information';

pgorm.orm_table_fkey


Table pgorm.orm_table_fkey contains foreign keys (relations) of tables for which ORM is enabled. Edit of child_column_sort_pos name,class_field_parent_name,class_field_child_name fields is allowed, and JavaScript module will be automatically updated

Поля таблицы:
parent_table_schemaparent table schema
parent_table_nameparent table name
child_table_schemachild table schema
child_table_namechild table name
child_columnschild table foreign key columns
child_column_sort_poschild table sort column
class_field_parent_namefield name of parent class/object
class_field_child_namefield name of child class/object

Usage example:
select * from pgorm.orm_table_fkey;

update pgorm.orm_table_fkey
  set class_field_parent_name='Invoice'
  where parent_table_schema='example' and parent_table_name='invoice' and child_table_name='invoice_product';

Module /orm/pgorm.js


Module is designed to interact with the database and contains the following classes:

Module classes:
Relationtable and class description
Columndescription of table column and corresponding class field
Recordrecord (table row), main class is inherited from it
RecordArrayarray of records, class is inherited from it
Relationshipdescription of relationship by foreign key
ORMConnectionmethods for working with database
ORMUtilmethods for validating and casting values
ORMErrorerror description

Source code:
export class Relation {

    #schema;
    #name;
    #type;
    #comment;
    #columns;
    #columnsPrimaryKey;
    #columnNames;
    #columnNamesPrimaryKey;
    #recordClassFunc;
    #recordArrayClassFunc;
    #relationshipsParent;
    #relationshipsChildFunc;

    constructor(schema, name, type, comment, columns, columnsPrimaryKey, recordClassFunc, recordArrayClassFunc, relationshipsParent, relationshipsChildFunc) {
        this.#schema                 = schema;
        this.#name                   = name;
        this.#type                   = type;
        this.#comment                = comment;
        this.#columns                = columns;
        this.#columnsPrimaryKey      = columnsPrimaryKey;
        this.#columnNames            = columns.map( property => property.getName() );
        this.#columnNamesPrimaryKey  = columnsPrimaryKey.map( property => property.getName() );
        this.#recordClassFunc        = recordClassFunc;
        this.#recordArrayClassFunc   = recordArrayClassFunc;
        this.#relationshipsParent    = relationshipsParent;
        this.#relationshipsChildFunc = relationshipsChildFunc;
    }

    getSchema()                { return this.#schema;                   }
    getName()                  { return this.#name;                     }
    getType()                  { return this.#type;                     }
    getComment()               { return this.#comment;                  }
    getColumns()               { return this.#columns;                  }
    getColumnsPrimaryKey()     { return this.#columnsPrimaryKey;        }
    getColumnNames()           { return this.#columnNames;              }
    getColumnNamesPrimaryKey() { return this.#columnNamesPrimaryKey;    }
    getRecordClass()           { return this.#recordClassFunc();        }
    getRecordArrayClass()      { return this.#recordArrayClassFunc();   }
    getRelationshipsParent()   { return this.#relationshipsParent;      }
    getRelationshipsChild()    { return this.#relationshipsChildFunc(); }

}

export class Column {

    #name;
    #type;
    #typeArray;
    #fieldName;
    #typePrimitive;
    #typeObject;
    #notNull;
    #recordClassFunc;
    #validateValueFunc;
    #jsValueFunc;
    #pgValueFunc;
    #comment;

    constructor(name, type, typeArray, fieldName, typePrimitive, typeObject, notNull, recordClassFunc, validateValueFunc, jsValueFunc, pgValueFunc, comment) {
        this.#name              = name;
        this.#type              = type;
        this.#typeArray         = typeArray;
        this.#fieldName         = fieldName;
        this.#typePrimitive     = typePrimitive;
        this.#typeObject        = typeObject;
        this.#notNull           = notNull;
        this.#recordClassFunc   = recordClassFunc;
        this.#validateValueFunc = validateValueFunc;
        this.#jsValueFunc       = jsValueFunc;
        this.#pgValueFunc       = pgValueFunc;
        this.#comment           = comment;
    }

    getName()          { return this.#name;              }
    getType()          { return this.#type;              }
    isTypeArray()      { return this.#typeArray;         }
    getFieldName()     { return this.#fieldName;         }
    getTypePrimitive() { return this.#typePrimitive;     }
    getTypeObject()    { return this.#typeObject;        }
    isNotNull()        { return this.#notNull;           }
    getRecordClass()   { return this.#recordClassFunc(); }
    getComment()       { return this.#comment;           }

    validateValue(value) { this.#validateValueFunc(value); }

    jsValue(pgValue) { return this.#jsValueFunc(pgValue); }
    pgValue(jsValue) { return this.#pgValueFunc(jsValue); }

}

export class Record {

    constructor() { if (this.constructor===Record) throw new Error("Record is abstract class"); }

    #recordExists = false;
    isRecordExists() { return this.#recordExists; }

    setFieldValue(column, value) { return this["set"+column.getFieldName()](value);   }
    getFieldValue(column, value) { return this["get"+column.getFieldName()]();        }
    isFieldValueChanged(column)  { return this["isChanged"+column.getFieldName()]();  }

    save(connection) {
        validateConnection(connection);
        let relation = this.constructor.getRelation();
        let columnsChanged = [];
        let values = [];
        relation.getColumns().filter( column => this.isFieldValueChanged(column) ).forEach( column => {
            columnsChanged.push(column.getName());
            let value = this.getFieldValue(column);
            column.validateValue(value);
            values.push(column.pgValue(value));
        });
        let valuesPrimaryKey = relation.getColumnsPrimaryKey().map( column => column.pgValue(this.getFieldValue(column)) );
        let fieldValues = sendRequest("/pgorm/record-save", { 
            "connectionID":      connection.getID(), 
            "relationSchema":    relation.getSchema(),
            "relationName":      relation.getName(),
            "columns":           relation.getColumnNames(),
            "recordExists":      this.#recordExists,
            "columnsChanged":    columnsChanged,
            "values":            values,
            "columnsPrimaryKey": relation.getColumnNamesPrimaryKey(),
            "valuesPrimaryKey":  valuesPrimaryKey
        }).record;
        this._initialize(fieldValues, null, true);
        return this;
    }    

    delete(connection) {
        validateConnection(connection);
        let relation = this.constructor.getRelation();
        let valuesPrimaryKey = relation.getColumnsPrimaryKey().map( column => column.pgValue(this.getFieldValue(column)) );
        let fieldValues = sendRequest("/pgorm/record-delete", { 
            "connectionID":      connection.getID(), 
            "relationSchema":    relation.getSchema(),
            "relationName":      relation.getName(),
            "columns":           relation.getColumnNames(),
            "columnsPrimaryKey": relation.getColumnNamesPrimaryKey(),
            "valuesPrimaryKey":  valuesPrimaryKey
        }).record;
        this._initialize(fieldValues, null, false);
        return this;
    }    

    static loadWhere(recordClass, connection, condition, params) {
        validateConnection(connection);
        let relation = recordClass.getRelation();
        let fieldValues = sendRequest("/pgorm/record-load-where", { 
            "connectionID":   connection.getID(), 
            "relationSchema": relation.getSchema(),
            "relationName":   relation.getName(),
            "columns":        relation.getColumnNames(),
            "condition":      condition,
            "params":         ORMUtil.pgArrayParam(params)
        }).record;
        if (fieldValues===null) return null;
        return new recordClass()._initialize(fieldValues, null, true);
    }    

    toJSON() {
        let relation = this.constructor.getRelation();
        let json = {};
        let columnsChanged = [];
        relation.getColumns().forEach( column => { 
            json[column.getName()] = column.pgValue(this.getFieldValue(column));
            if (this.isFieldValueChanged(column)) columnsChanged.push(column.getName());
        });        
        json["#orm"] = { 
            "relation" :           relation.getSchema()+"."+relation.getName(),
            "record_exists":       this.#recordExists,
            "columns_primary_key": relation.getColumnNamesPrimaryKey(),
            "columns_changed":     columnsChanged
        };
        return json;
    }

    static fromJSON(recordClass, json) {    
        let relation = recordClass.getRelation();
        let pgValues = [];
        relation.getColumns().forEach(column => pgValues.push(json[column.getName()]) );
        let valuesChanged;
        let recordExists;
        if ("#orm" in json) {
            let orm = json["#orm"];
            if (orm["relation"]!=(relation.getSchema()+"."+relation.getName()))
                throw new ORMError(311, `Relation ${orm["relation"]} does not match class ${recordClass.name}`);
            recordExists = orm["record_exists"];
            let columnNames = relation.getColumnNames();
            valuesChanged = Array(columnNames.length).fill(false);
            orm["columns_changed"].forEach(columnChanged => { valuesChanged[columnNames.indexOf(columnChanged)] = true; });
        } else {
            recordExists = true;
            relation.getColumnsPrimaryKey.forEach(column => { if ( !(column.getName() in json) || json[column.getName()]===null) recordExists = false; } );
            valuesChanged = null;
        }        
        return new recordClass()._initialize(pgValues, valuesChanged, rowExists);
    }

    _initialize(recordExists) { this.#recordExists = recordExists; return this; }

}

export class RecordArray extends Array {

    constructor() { super(); if (this.constructor===RecordArray) throw new Error("RecordArray is abstract class"); }

    static load(recordArrayClass, connection, condition, params) {
        validateConnection(connection);
        let relation = recordArrayClass.getRelation();
        let records = sendRequest("/pgorm/record-array-load", { 
            "connectionID":   connection.getID(), 
            "relationSchema": relation.getSchema(),
            "relationName":   relation.getName(),
            "columns":        relation.getColumnNames(),
            "condition":      condition!==null && condition!==undefined ? condition : "",
            "params":         ORMUtil.pgArrayParam(params)
        }).records;
        let recordArray = new recordArrayClass(); 
        records.forEach( fieldValues => {
            recordArray.push(new (relation.getRecordClass())()._initialize(fieldValues, true));
        });
        return recordArray;
    }

    static delete(recordArrayClass, connection, condition, params) {
        validateConnection(connection);
        let relation = recordArrayClass.getRelation();
        return sendRequest("/pgorm/record-array-delete", { 
            "connectionID": connection.getID(), 
            "relationSchema": relation.getSchema(),
            "relationName":   relation.getName(),
            "condition":      condition!==null && condition!==undefined ? condition : "",
            "params":         ORMUtil.pgArrayParam(params)
        }).records_deleted;
    }

    toJSON() {
        let json = JSON.parse('[]');
        this.forEach( record=>json.push(record.toJSON()) );
        return json;
    }

    toArrayJSON() {
        let arrayJSON = [];
        this.forEach( record=>arrayJSON.push(record.toJSON()) );
        return arrayJSON;
    }

}

export class Relationship {

    #parentTableSchema;
    #parentTableName;
    #parentClassFunc;
    #childTableSchema;
    #childTableName;
    #childClassFunc;
    #childColumns;
    #childColumnOrderPos;

    constructor(parentTableSchema, parentTableName, parentClassFunc, childTableSchema, childTableName, childClassFunc, childColumns, childColumnOrderPos) {
        this.#parentTableSchema   = parentTableSchema;
        this.#parentTableName     = parentTableName;
        this.#parentClassFunc     = parentClassFunc;
        this.#childTableSchema    = childTableSchema;
        this.#childTableName      = childTableName;
        this.#childClassFunc      = childClassFunc;
        this.#childColumns        = childColumns;
        this.#childColumnOrderPos = childColumnOrderPos;
    }

    getParentTableSchema()   { return this.#parentTableSchema;   }
    getParentTableName()     { return this.#parentTableName;     }
    getParentClass()         { return this.#parentClassFunc();   }
    getChildTableSchema()    { return this.#childTableSchema;    }
    getChildTableName()      { return this.#childTableName;      }
    getChildClass()          { return this.#childClassFunc();    }
    getChildColumns()        { return this.#childColumns;        }
    getChildColumnOrderPos() { return this.#childColumnOrderPos; }

}

export class ORMConnection {

    static #LOCAL_STORAGE_KEY = "ORMConnection.ID";

    #id;

    getID() { return this.#id; }

    connect(user, password, idleSessionTimeout = '1h') {
        this.#id = sendRequest("/pgorm/connect", { "user": user, "password": password, "idleSessionTimeout": idleSessionTimeout }).connectionID;
        return this;
    }

    disconnect() {
        sendRequest("/pgorm/disconnect", { "connectionID": this.#id });
        if (this.#id==localStorage.getItem(ORMConnection.#LOCAL_STORAGE_KEY))
            localStorage.clear(ORMConnection.#LOCAL_STORAGE_KEY); 
        this.#id = undefined;
    }

    execute(query, params) {
        return sendRequest("/pgorm/execute", { "connectionID": this.#id, "query": query, "params": ORMUtil.pgArrayParam(params) });
    }

    setDefault() { 
        localStorage.setItem(ORMConnection.#LOCAL_STORAGE_KEY, this.#id); 
        return this;
    }

    static getDefault() {         
        let id = localStorage.getItem(ORMConnection.#LOCAL_STORAGE_KEY); 
        if (id===null)
               return null;      
        let connection = new ORMConnection();
        connection.#id = id; 
        return connection;
    }

}

export class ORMError extends Error {

    constructor(code, message) {
        super( (message.startsWith("PGORM-") ? "" : "PGORM-"+code.toString().padStart(3, "0")+" ")+message ); 
        this.code = code;
        this.name = "ORMError";
    }

}

export class ORMUtil {

    constructor() { throw new Error("Final abstract class"); }

    static validateNumber     (value) { if (value!==null && value!==undefined && (typeof value)!=="number"  && !(value instanceof Number))  throw new ORMError(303, `Value ${ORMUtil._valueToStringShort(value)} is not number`);  }
    static validateBoolean    (value) { if (value!==null && value!==undefined && (typeof value)!=="boolean" && !(value instanceof Boolean)) throw new ORMError(303, `Value ${ORMUtil._valueToStringShort(value)} is not boolean`); }
    static validateString     (value) { if (value!==null && value!==undefined && (typeof value)!=="string"  && !(value instanceof String))  throw new ORMError(303, `Value ${ORMUtil._valueToStringShort(value)} is not string`);  }
    static validateObject     (value) { if (value!==null && value!==undefined && (typeof value)!=="object")       throw new ORMError(303, `Value ${ORMUtil._valueToStringShort(value)} is not object`);      }
    static validateDate       (value) { if (value!==null && value!==undefined && !(value instanceof Date))        throw new ORMError(303, `Value ${ORMUtil._valueToStringShort(value)} is not Date`);        }
    static validateUint8Array (value) { if (value!==null && value!==undefined && !(value instanceof Uint8Array))  throw new ORMError(303, `Value ${ORMUtil._valueToStringShort(value)} is not Uint8Array`);  }
    static validateXMLDocument(value) { if (value!==null && value!==undefined && !(value instanceof XMLDocument)) throw new ORMError(303, `Value ${ORMUtil._valueToStringShort(value)} is not XMLDocument`); }

    static validateArray(array, validateElementFunc) { 
        if (array===null || array===undefined) return;
        if (!(array instanceof Array)) throw new ORMError(303, `Value ${ORMUtil._valueToStringShort(array)} is not Array`);  
        array.forEach( element => element instanceof Array ? validateArray(element, validateElementFunc) : validateElementFunc(element) );
    }

    static validateArrayNumber     (array) { ORMUtil.validateArray(array, ORMUtil.validateNumber);      }
    static validateArrayBoolean    (array) { ORMUtil.validateArray(array, ORMUtil.validateBoolean);     }
    static validateArrayString     (array) { ORMUtil.validateArray(array, ORMUtil.validateString);      }
    static validateArrayObject     (array) { ORMUtil.validateArray(array, ORMUtil.validateObject);      }
    static validateArrayDate       (array) { ORMUtil.validateArray(array, ORMUtil.validateDate);        }
    static validateArrayUint8Array (array) { ORMUtil.validateArray(array, ORMUtil.validateUint8Array);  }
    static validateArrayXMLDocument(array) { ORMUtil.validateArray(array, ORMUtil.validateXMLDocument); }

    static _valueToStringShort(value) { return JSON.stringify(value.toString().length<=15 ? value.toString() : value.toString().substr(1,12)+"..."); }

    static jsPrimitive  (pgValue) { return pgValue; }
    static jsDate       (pgValue) { return pgValue!==null ? new Date(pgValue) : null; }
    static jsUint8Array (pgValue) { return pgValue!==null ? new Uint8Array([...pgValue].map((c,i) => i && !(i%2) ? parseInt(pgValue.substr(i,2),16) : null).filter(b => b!==null)) : null; }
    static jsXMLDocument(pgValue) { return pgValue!==null ? new DOMParser().parseFromString(pgValue, "text/xml") : null; }

    static pgPrimitive     (jsValue) { return jsValue!==undefined ? jsValue : null; }
    static pgDate          (jsValue) { return jsValue!==null && jsValue!==undefined ? jsValue.getFullYear()+"-"+(jsValue.getMonth()+1).toString().padStart(2,"0")+"-"+jsValue.getDate().toString().padStart(2,"0") : null; }
    static pgTime          (jsValue) { return jsValue!==null && jsValue!==undefined ? jsValue.getHours().toString().padStart(2,"0")+":"+jsValue.getMinutes().toString().padStart(2,"0")+":"+jsValue.getSeconds().toString().padStart(2,"0")+"."+jsValue.getMilliseconds().toString().padStart(3,"0") : null; }
    static pgTimezoneOffset(jsValue) { return jsValue!==null && jsValue!==undefined ? (jsValue.getTimezoneOffset()>0 ? "-" : "+")+(Math.abs(jsValue.getTimezoneOffset())/60|0).toString().padStart(2,"0")+(Math.abs(jsValue.getTimezoneOffset())%60).toString().padStart(2,"0") : null; }
    static pgTimetz        (jsValue) { return jsValue!==null && jsValue!==undefined ? ORMUtil.pgTime(jsValue)+ORMUtil.pgTimezoneOffset(jsValue) : null; }
    static pgTimestamp     (jsValue) { return jsValue!==null && jsValue!==undefined ? ORMUtil.pgDate(jsValue)+"T"+ORMUtil.pgTime(jsValue) : null; }
    static pgTimestamptz   (jsValue) { return jsValue!==null && jsValue!==undefined ? ORMUtil.pgDate(jsValue)+"T"+ORMUtil.pgTime(jsValue)+ORMUtil.pgTimezoneOffset(jsValue) : null; }
    static pgInterval      (jsValue) { return jsValue!==null && jsValue!==undefined ? (jsValue/(24*60*60*1000)|0)+" day "+(jsValue%(24*60*60*1000)/(60*60*1000)|0).toString().padStart(2,"0")+":"+(jsValue%(60*60*1000)/(60*1000)|0).toString().padStart(2,"0")+":"+(jsValue%(60*1000)/(1000)|0).toString().padStart(2,"0")+"."+(jsValue%1000).toString().padEnd(3,"0") : null; }
    static pgBytea         (jsValue) { return jsValue!==null && jsValue!==undefined ? "\\x"+jsValue.map(byte=>byte.toString(16).padStart(2,'0')).join("") : null; }
    static pgJSON          (jsValue) { return jsValue!==null && jsValue!==undefined ? JSON.stringify(jsValue) : null; }
    static pgXML           (jsValue) { return jsValue!==null && jsValue!==undefined ? new XMLSerializer().serializeToString(jsValue) : null; }
    static pgRecord        (jsValue) { return jsValue!==null && jsValue!==undefined ? "("+jsValue.constructor.getRelation().getColumns().map(column=> { let v = jsValue.getFieldValue(column); return v!==null ? JSON.stringify(column.pgValue(v)) : ""; }).join(",")+")" : null; }

    static jsArray(pgArray, jsElementFunc) { return pgArray!==null && pgArray!==undefined ? pgArray.map( element => jsElementFunc(element) ) : null; }

    static jsArrayPrimitive  (pgArray) { return ORMUtil.jsArray(pgArray, ORMUtil.jsPrimitive);   }
    static jsArrayDate       (pgArray) { return ORMUtil.jsArray(pgArray, ORMUtil.jsDate);        }
    static jsArrayUint8Array (pgArray) { return ORMUtil.jsArray(pgArray, ORMUtil.jsUint8Array);  }
    static jsArrayXMLDocument(pgArray) { return ORMUtil.jsArray(pgArray, ORMUtil.jsXMLDocument); }

    static pgArray(jsArray, pgElementFunc) { return jsArray!==null && jsArray!==undefined ? "{" + jsArray.map( (element,index) => element===null || element===undefined ? "null" : element instanceof Array ? ORMUtil.pgArray(element, pgElementFunc) : JSON.stringify(pgElementFunc(element)) ).join() + "}" : null; }

    static pgArrayPrimitive     (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgPrimitive);      }
    static pgArrayDate          (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgDate);           }
    static pgArrayTime          (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgTime);           }
    static pgArrayTimezoneOffset(jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgTimezoneOffset); }
    static pgArrayTimetz        (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgTimetz);         }
    static pgArrayTimestamp     (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgTimestamp);      }
    static pgArrayTimestamptz   (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgTimestamptz);    }
    static pgArrayInterval      (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgInterval);       }
    static pgArrayBytea         (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgBytea);          }
    static pgArrayJSON          (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgJSON);           }
    static pgArrayXML           (jsArray) { return ORMUtil.pgArray(jsArray, ORMUtil.pgXML);            }

    static #pgParam(jsValue) { 
        console.log("pgValue: "+ jsValue);
        if (jsValue===null || jsValue===undefined) return null;
        if ((typeof jsValue)==="number" || (typeof jsValue)==="boolean") return jsValue;
        if (jsValue instanceof Date) return ORMUtil.pgTimestamptz(jsValue);
        if (jsValue instanceof Uint8Array) return ORMUtil.pgBytea(jsValue);
        if (jsValue instanceof XMLDocument) return ORMUtil.pgXML(jsValue);
        if (jsValue instanceof Record) return ORMUtil.pgRecord(jsValue);
        if (jsValue instanceof Array) return ORMUtil.pgArray(jsValue, ORMUtil.#pgParam);
        return JSON.stringify(jsValue);
    }

    static pgArrayParam(jsArray) { return jsArray!==null && jsArray!==undefined ? jsArray instanceof Array ? jsArray.map( element => ORMUtil.#pgParam(element) ) : [ORMUtil.#pgParam(jsArray)] : []; }

    static #compareNull  (value1,   value2,   nullsFirst)       { return value1===null ? value2!==null ? (nullsFirst ? -1 : +1) : 0 : (nullsFirst ? +1 : -1); }
    static compareNumber (number1,  number2,  desc, nullsFirst) { return number1!==null && number2!==null ? !desc ? number1-number2                : number2-number1                : ORMUtil.#compareNull(number1, number2, nullsFirst); }
    static compareString (string1,  string2,  desc, nullsFirst) { return string1!==null && string2!==null ? !desc ? string1.localeCompare(string2) : string2.localeCompare(string1) : ORMUtil.#compareNull(string1, string2, nullsFirst); }
    static compareBoolean(boolean1, boolean2, desc, nullsFirst) { return ORMUtil.compareNumber(boolean1!==null ? boolean1|0      : null, boolean2!==null ? boolean2|0      : null, desc, nullsFirst); }
    static compareDate   (date1,    date2,    desc, nullsFirst) { return ORMUtil.compareNumber(date1!==null    ? date1.getTime() : null, date2!==null    ? date2.getTime() : null, desc, nullsFirst); }

}

function sendRequest(path, body) {
    let xhr = new XMLHttpRequest();
    try {
        xhr.open("POST", path, false);
        xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");    
        xhr.send(JSON.stringify(body));
    } catch (error) {
        throw new ORMError(305, `Error on send HTTP request (path: ${path}):\n${error.name} ${error.message}`);
    }
    if (xhr.readyState!=4) 
        throw new ORMError(306, `Incorrect HTTP request state (${xhr.readyState})`);
    if (xhr.status!=200) 
        throw new ORMError(307, `Incorrect HTTP request status (${xhr.status})`);
    let response = JSON.parse(xhr.responseText);
    if (!response.sucess) 
        throw new ORMError(response.errorCode, response.errorText);
    return response; 
}


function validateConnection(connection) {
    if (connection===null)
        throw new ORMError(308, "Connection is null");
}

Модуль /orm/[schema]/[Class].js


Module is editable, created on first call to pgorm.orm_relation_enable and is intended for overrides of existing methods (e.g. save)

Example:
// Module can be edited by changing field "module_source" of table "pgorm.orm_table"

import { BaseInvoice,BaseInvoiceArray } from "/orm/public/base/BaseInvoice.js"

export class Invoice extends BaseInvoice {

}

export class InvoiceArray extends BaseInvoiceArray {

}

Модуль /orm/[schema]/base/Base[Class].js


Module contains methods for working with table and its description. Module is automatically updated when performing DDL operations on table. Edit of module is not provided.

Example:
// Module made automatically based on relation (table definition) and cannot be edited

import { Relation,Column,Row,RowArray,Relationship,ORMConnection,ORMUtil,ORMError } from "/orm/pgorm-db.js"

import { Invoice,InvoiceArray } from "/orm/public/Invoice.js"

export class BaseInvoice extends Row {

    // Columns
    static #columnID     = new Column("id",     "int4",    false, "ID",     "number", Number, true,  BaseInvoice.getRowClass, ORMUtil.validateNumber, ORMUtil.jsPrimitive, ORMUtil.pgPrimitive, null);
    static #columnDate   = new Column("date",   "date",    false, "Date",   null,     Date,   true,  BaseInvoice.getRowClass, ORMUtil.validateDate,   ORMUtil.jsDate,      ORMUtil.pgDate,      null);
    static #columnNumber = new Column("number", "varchar", false, "Number", "string", String, true,  BaseInvoice.getRowClass, ORMUtil.validateString, ORMUtil.jsPrimitive, ORMUtil.pgPrimitive, null);
    static #columnAmount = new Column("amount", "numeric", false, "Amount", "number", Number, true,  BaseInvoice.getRowClass, ORMUtil.validateNumber, ORMUtil.jsPrimitive, ORMUtil.pgPrimitive, null);

    static getColumnID()     { return BaseInvoice.#columnID;     } 
    static getColumnDate()   { return BaseInvoice.#columnDate;   } 
    static getColumnNumber() { return BaseInvoice.#columnNumber; } 
    static getColumnAmount() { return BaseInvoice.#columnAmount; } 

    // Relation (table definition)
    static #relation = new Relation("public", "invoice", null, [BaseInvoice.#columnID,BaseInvoice.#columnDate,BaseInvoice.#columnNumber,BaseInvoice.#columnAmount], [BaseInvoice.#columnID], function() { return Invoice; }, function() { return InvoiceArray; }, [], function() { return []; });
    static getRelation() { return BaseInvoice.#relation; }

    // Row fields
    #ID     = null; #ID$changed     = false; 
    #Date   = null; #Date$changed   = false; 
    #Number = null; #Number$changed = false; 
    #Amount = null; #Amount$changed = false; 

    // Set values
    setID    (value) { ORMUtil.validateNumber(value); this.#ID     = value; this.#ID$changed     = true; return this; }
    setDate  (value) { ORMUtil.validateDate  (value); this.#Date   = value; this.#Date$changed   = true; return this; }
    setNumber(value) { ORMUtil.validateString(value); this.#Number = value; this.#Number$changed = true; return this; }
    setAmount(value) { ORMUtil.validateNumber(value); this.#Amount = value; this.#Amount$changed = true; return this; }

    // Get values
    getID()     { return this.#ID;     } 
    getDate()   { return this.#Date;   } 
    getNumber() { return this.#Number; } 
    getAmount() { return this.#Amount; } 

    // Get change marks 
    isChangedID()     { return this.#ID$changed;     } 
    isChangedDate()   { return this.#Date$changed;   } 
    isChangedNumber() { return this.#Number$changed; } 
    isChangedAmount() { return this.#Amount$changed; } 

    // Save row and get values (SQL statement: insert or update with returning)
    save(connection = ORMConnection.getDefault()) { return super.save(connection); }

    // Delete row and get values (SQL statement: delete with returning)
    delete(connection = ORMConnection.getDefault()) { super.delete(connection);  }

    // Load one row by condition (SQL statement: select)
    static loadByCondition(condition, params = null, connection = ORMConnection.getDefault()) { return Row.loadByCondition(Invoice, connection, condition, params); }

    // Load row by primary key
    static load(ID, connection = ORMConnection.getDefault()) { return Invoice.loadByCondition("id=$1", [ID], connection); }

    // Load row by unique index "invoice_pkey"
    static loadByUnique$ID(ID, connection = ORMConnection.getDefault()) { return Invoice.loadByCondition("id=$1", [ID], connection); }

    // Initialize field values fetched from database
    _initialize(pgValues, rowExists) {
        this.#ID     = ORMUtil.jsPrimitive(pgValues[0]); this.#ID$changed     = false;
        this.#Date   = ORMUtil.jsDate     (pgValues[1]); this.#Date$changed   = false;
        this.#Number = ORMUtil.jsPrimitive(pgValues[2]); this.#Number$changed = false;
        this.#Amount = ORMUtil.jsPrimitive(pgValues[3]); this.#Amount$changed = false;
        return super._initialize(rowExists);
    }

}

export class BaseInvoiceArray extends RowArray {

    // Get relation
    static getRelation() { return BaseInvoice.getRelation(); }

    // Load any row by condition (SQL statement: select)
    // If there is no condition, all row load
    static load(condition = null, params = null, connection = ORMConnection.getDefault()) { return RowArray.load(InvoiceArray, connection, condition, params); }

    // Sort by fields
    sortByID    (desc = false, nullsFirst = false) { this.sort( (row1,row2) => ORMUtil.compareNumber(row1.getID(),     row2.getID(),     desc, nullsFirst) ); return this; }
    sortByDate  (desc = false, nullsFirst = false) { this.sort( (row1,row2) => ORMUtil.compareDate  (row1.getDate(),   row2.getDate(),   desc, nullsFirst) ); return this; }
    sortByNumber(desc = false, nullsFirst = false) { this.sort( (row1,row2) => ORMUtil.compareString(row1.getNumber(), row2.getNumber(), desc, nullsFirst) ); return this; }
    sortByAmount(desc = false, nullsFirst = false) { this.sort( (row1,row2) => ORMUtil.compareNumber(row1.getAmount(), row2.getAmount(), desc, nullsFirst) ); return this; }

}

Command line


Command line options (after installation, can view them by executing pgorm --help):
[root@vds2157681 ~]# pgorm --help
PGORM is web server for database PostgreSQL
version 23.1.6, linux 64 bits


Usage:
  pgorm start|execute|status|stop [OPTIONS]

Parameter:
  start      execute pgorm in new process
  execute    execute pgorm in current process
  status     print info about running pgorm
  stop       stop pgorm

Database options:
  -h HOSTNAME        database server host (default: 5432)
  -p PORT            database server port (default: 127.0.0.1)
  -d DBNAME          database name (default: site)
  -U USERNAME        superuser name (default: postgres)
  -W PASSWORD        superuser password in PostgreSQL (if nessesary)

HTTP options:
  -hd DIRECTORY       site directory (default: /site)
  -hp PORT            HTTP port (default: 80)

Administration options:
  -ap PORT            administration port (default: 1080)

Logging options:
  -l  FILE           log file

Info:
  -h, --help          print this help

Examples:
  pgorm start
  pgorm start -d sitedb -hd /mysite1 -l /tmp/pgorm.log