База данных


Модуль pgorm.js


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


Модуль Base[Class].js


Командная строка


База данных


При старте web-сервер создает в базе данных схему pgorm с процедурами, таблицами и другими служебными объектами

pgorm.orm_relation_enable(schema name, name name)


Процедура включает объектно-реляционное отображение (ORM) для указанной таблицы или представления, создает JavaSctipt модули

Параметры:
schema-схема
name-имя таблицы или представления

Пример вызова:
call pgorm.orm_relation_enable('example', 'invoice');

pgorm.orm_relation_disable(schema name, name name)


Процедура выключает объектно-реляционное отображение (ORM) для указанной таблицы или представления, удаляет модули

Параметры:
schema-схема
name-имя таблицы или представления

Пример вызова:
call pgorm.orm_relation_disable('example', 'invoice');

pgorm.orm_relation


Таблица pgorm.orm_relation содержит список таблиц и представлений, для которых включен ORM. Допускается редактирование поля module_source, при этом модуль будет автоматически обновлен

Поля таблицы:
schema-схема
name-имя таблицы или представления
type-тип: 'table' или 'view'
class_name-имя JavaScript-класса
module_source-исходный код модуля orm/[schema]/[table_name].js

Пример использования:
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


Таблица pgorm.orm_relation_column содержит список столбцов таблиц и представлений, для которых включен ORM. Допускается редактирование поля class_field_name, при этом JavaScript модуль будет автоматически обновлен

Поля таблицы:
schema-схема
relation_name-имя таблицы или представления
column_name-имя столбца
class_field_name-имя поля класса

Пример использования:
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


Таблица pgorm.orm_table_fkey содержит список внешних ключей (связей) таблиц, для которых включен ORM. Допускается редактирование полей child_column_sort_pos name,class_field_parent_name,class_field_child_name, при этом JavaScript модуль будет автоматически обновлен

Поля таблицы:
parent_table_schema-схема родительской таблицы
parent_table_name-имя родительской таблицы
child_table_schema-схема дочерней таблицы
child_table_name-имя дочерней таблицы
child_columns-столбцы внешнего ключа дочерней таблицы
child_column_sort_pos-столбец сортировки дочерней таблицы
class_field_parent_name-имя поля родильского класса/объекта
class_field_child_name-имя поля дочернего класса/объекта

Пример использования:
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';

Модуль /orm/pgorm.js


Модуль предназначен для взаимодействия с базой данной и содержит классы:

Классы модуля:
Relation-описание таблицы и класса
Column-описание столбца таблицы и соответствующего поля класса
Record-запись (строка таблицы), от него наследуется основной создаваемый класс
RecordArray-массив записей (строк таблицы), от него наследуется создаваемый класс
Relationship-описание связи таблицы по внешнему ключу
ORMConnection-методы для работы с базой данных
ORMUtil-методы проверки и преобразования значений
ORMError-описание ошибки

Исходный код:
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}):
${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


Модуль является редактируемым, создается при первом вызове pgorm.orm_relation_enable и предназначен для написания дополнительных(прикладных) методов и переопределения существующих (например, save)

Пример:
// 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


Модуль содержит методы для взаимодействия с таблицей и ее описание, создается при вызове pgorm.orm_relation_enable и автоматически обновляется при выполнении DDL операций над таблицей. Ручное редактирование модуля не предусмотрено.

Пример:
// 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; }

}

Командная строка


Параметры командной строки (после установки их можно посмотреть запустив 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