orange-orm
Version:
Object Relational Mapper
1,061 lines (931 loc) • 28.7 kB
JavaScript
const createPatch = require('./createPatch');
const stringify = require('./stringify');
const cloneFromDb = require('./cloneFromDb');
const netAdapter = require('./netAdapter');
const toKeyPositionMap = require('./toKeyPositionMap');
const rootMap = new WeakMap();
const fetchingStrategyMap = new WeakMap();
const targetKey = Symbol();
const map = require('./clientMap');
const clone = require('rfdc/default');
const createAxiosInterceptor = require('./axiosInterceptor');
const flags = require('../flags');
function rdbClient(options = {}) {
flags.useLazyDefaults = false;
if (options.pg)
options = { db: options };
let transaction = options.transaction;
let _reactive = options.reactive;
let providers = options.providers || {};
let baseUrl = options.db;
if (typeof providers === 'function')
baseUrl = typeof options.db === 'function' ? providers(options.db) : options.db;
const axiosInterceptor = createAxiosInterceptor();
function client(_options = {}) {
if (_options.pg)
_options = { db: _options };
return rdbClient({ ...options, ..._options });
}
client.reactive = (cb => _reactive = cb);
client.map = map.bind(null, client);
Object.defineProperty(client, 'metaData', {
get: getMetaData,
enumerable: true,
configurable: false
});
client.interceptors = axiosInterceptor;
client.createPatch = _createPatch;
client.table = table;
client.or = column('or');
client.and = column('and');
client.not = column('not');
client.filter = {
or: client.or,
and: client.and,
not: client.not,
toJSON: function() {
return;
}
};
client.query = query;
client.transaction = runInTransaction;
client.db = baseUrl;
client.mssql = onProvider.bind(null, 'mssql');
client.mssqlNative = onProvider.bind(null, 'mssqlNative');
client.pg = onProvider.bind(null, 'pg');
client.pglite = onProvider.bind(null, 'pglite');
client.postgres = onProvider.bind(null, 'postgres');
client.d1 = onProvider.bind(null, 'd1');
client.sqlite = onProvider.bind(null, 'sqlite');
client.sap = onProvider.bind(null, 'sap');
client.oracle = onProvider.bind(null, 'oracle');
client.http = onProvider.bind(null, 'http');//todo
client.mysql = onProvider.bind(null, 'mysql');
client.express = express;
client.close = close;
function close() {
return client.db.end ? client.db.end() : Promise.resolve();
}
function onProvider(name, ...args) {
let db = providers[name].apply(null, args);
return client({ db });
}
if (options.tables) {
// for (let name in options.tables) {
// client[name] = table(options.tables[name], name, { ...readonly, ...clone(options[name]) });
// }
client.tables = options.tables;
// return client;
}
// else {
let handler = {
get(_target, property,) {
if (property in client)
return Reflect.get(...arguments);
else {
const readonly = { readonly: options.readonly, concurrency: options.concurrency };
return table(options?.tables?.[property] || baseUrl, property, { ...readonly, ...clone(options[property]) });
}
}
};
return new Proxy(client, handler);
// }
function getMetaData() {
const result = { readonly: options.readonly, concurrency: options.concurrency };
for (let name in options.tables) {
result[name] = getMetaDataTable(options.tables[name], inferOptions(options, name));
}
return result;
}
function inferOptions(defaults, property) {
const parent = {};
if ('readonly' in defaults)
parent.readonly = defaults.readonly;
if ('concurrency' in defaults)
parent.concurrency = defaults.concurrency;
return { ...parent, ...(defaults[property] || {}) };
}
function getMetaDataTable(table, options) {
const result = {};
for (let i = 0; i < table._columns.length; i++) {
const name = table._columns[i].alias;
result[name] = inferOptions(options, name);
}
for (let name in table._relations) {
if (!isJoinRelation(name, table))
result[name] = getMetaDataTable(table._relations[name].childTable, inferOptions(options, name));
}
return result;
function isJoinRelation(name, table) {
return table[name] && table[name]._relation.columns;
}
}
async function query() {
const adapter = netAdapter(baseUrl, undefined, { tableOptions: { db: baseUrl, transaction } });
return adapter.query.apply(null, arguments);
}
function express(arg) {
if (providers.express) {
return providers.express(client, { ...options, ...arg });
}
else
throw new Error('Cannot host express clientside');
}
function _createPatch(original, modified, ...restArgs) {
if (!Array.isArray(original)) {
original = [original];
modified = [modified];
}
let args = [original, modified, ...restArgs];
return createPatch(...args);
}
async function getDb() {
let db = baseUrl;
if (typeof db === 'function') {
let dbPromise = db();
if (dbPromise.then)
db = await dbPromise;
else
db = dbPromise;
}
return db;
}
async function runInTransaction(fn, _options) {
let db = await getDb();
if (!db.createTransaction)
throw new Error('Transaction not supported through http');
const transaction = db.createTransaction(_options);
try {
const nextClient = client({ transaction });
await fn(nextClient);
await transaction(transaction.commit);
}
catch (e) {
await transaction(transaction.rollback.bind(null, e));
}
}
function table(url, tableName, tableOptions) {
tableOptions = tableOptions || {};
tableOptions = { db: baseUrl, ...tableOptions, transaction };
let meta;
let c = {
count,
getMany,
aggregate: groupBy,
getAll,
getOne,
getById,
proxify,
update,
replace,
updateChanges,
insert,
insertAndForget,
delete: _delete,
deleteCascade,
patch,
expand,
};
let handler = {
get(_target, property,) {
if (property in c)
return Reflect.get(...arguments);
else
return column(property);
}
};
let _table = new Proxy(c, handler);
return _table;
function expand() {
return c;
}
async function getAll() {
let _getMany = getMany.bind(null, undefined);
return _getMany.apply(null, arguments);
}
async function getMany(_, strategy) {
let metaPromise = getMeta();
strategy = extractFetchingStrategy({}, strategy);
let args = [_, strategy].concat(Array.prototype.slice.call(arguments).slice(2));
let rows = await getManyCore.apply(null, args);
await metaPromise;
return proxify(rows, strategy, true);
}
async function groupBy(strategy) {
let args = negotiateGroupBy(null, strategy);
let body = stringify({
path: 'aggregate',
args
});
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
return adapter.post(body);
}
async function count(_) {
let args = [_].concat(Array.prototype.slice.call(arguments).slice(1));
let body = stringify({
path: 'count',
args
});
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
return adapter.post(body);
}
async function getOne(filter, strategy) {
let metaPromise = getMeta();
strategy = extractFetchingStrategy({}, strategy);
let _strategy = { ...strategy, ...{ limit: 1 } };
let args = [filter, _strategy].concat(Array.prototype.slice.call(arguments).slice(2));
let rows = await getManyCore.apply(null, args);
await metaPromise;
if (rows.length === 0)
return;
return proxify(rows[0], strategy, true);
}
async function getById() {
if (arguments.length === 0)
return;
let meta = await getMeta();
let keyFilter = client.filter;
for (let i = 0; i < meta.keys.length; i++) {
let keyName = meta.keys[i].name;
let keyValue = arguments[i];
keyFilter = keyFilter.and(_table[keyName].eq(keyValue));
}
let args = [keyFilter].concat(Array.prototype.slice.call(arguments).slice(meta.keys.length));
return getOne.apply(null, args);
}
async function getManyCore() {
let args = negotiateWhere.apply(null, arguments);
let body = stringify({
path: 'getManyDto',
args
});
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
return adapter.post(body);
}
function negotiateWhere(_, strategy, ...rest) {
const args = Array.prototype.slice.call(arguments);
if (strategy)
return [_, negotiateWhereSingle(strategy), ...rest];
else
return args;
}
function negotiateWhereSingle(_strategy, path = '') {
if (typeof _strategy !== 'object' || _strategy === null)
return _strategy;
if (Array.isArray(_strategy)) {
return _strategy.map(item => negotiateWhereSingle(item, path));
}
const strategy = { ..._strategy };
for (let name in _strategy) {
if (name === 'where' && typeof strategy[name] === 'function')
strategy.where = column(path + 'where')(strategy.where); // Assuming `column` is defined elsewhere.
else if (typeof strategy[name] === 'function') {
strategy[name] = aggregate(path, strategy[name]);
}
else
strategy[name] = negotiateWhereSingle(_strategy[name], path + name + '.');
}
return strategy;
}
function negotiateGroupBy(_, strategy, ...rest) {
const args = Array.prototype.slice.call(arguments);
if (strategy)
return [_, where(strategy), ...rest];
else
return args;
function where(_strategy, path = '') {
if (typeof _strategy !== 'object' || _strategy === null)
return _strategy;
if (Array.isArray(_strategy)) {
return _strategy.map(item => where(item, path));
}
const strategy = { ..._strategy };
for (let name in _strategy) {
if (name === 'where' && typeof strategy[name] === 'function')
strategy.where = column(path + 'where')(strategy.where); // Assuming `column` is defined elsewhere.
else if (typeof strategy[name] === 'function') {
strategy[name] = groupByAggregate(path, strategy[name]);
}
else
strategy[name] = where(_strategy[name], path + name + '.');
}
return strategy;
}
}
async function _delete() {
let args = Array.prototype.slice.call(arguments);
let body = stringify({
path: 'delete',
args
});
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
return adapter.post(body);
}
async function deleteCascade() {
let args = Array.prototype.slice.call(arguments);
let body = stringify({
path: 'deleteCascade',
args
});
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
return adapter.post(body);
}
async function update(_row, _where, strategy) {
let args = [_row, negotiateWhereSingle(_where), negotiateWhereSingle(strategy)];
let body = stringify({
path: 'update',
args
});
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
const result = await adapter.post(body);
if (strategy)
return proxify(result, strategy);
}
async function replace(_row, strategy) {
let args = [_row, negotiateWhereSingle(strategy)];
let body = stringify({
path: 'replace',
args
});
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
const result = await adapter.post(body);
if (strategy)
return proxify(result, strategy);
}
async function updateChanges(rows, oldRows, ...rest) {
const concurrency = undefined;
const args = [concurrency].concat(rest);
if (Array.isArray(rows)) {
const proxy = await getMany.apply(null, [rows, ...rest]);
proxy.splice.apply(proxy, [0, proxy.length, ...rows]);
await proxy.saveChanges.apply(proxy, args);
return proxy;
}
else {
let proxy = proxify([oldRows], args[0]);
proxy.splice.apply(proxy, [0, 1, rows]);
await proxy.saveChanges.apply(proxy, args);
return proxify(proxy[0], args[0]);
}
}
async function insert(rows, ...rest) {
const concurrency = undefined;
const args = [concurrency].concat(rest);
if (Array.isArray(rows)) {
let proxy = proxify([], rest[0]);
proxy.splice.apply(proxy, [0, 0, ...rows]);
await proxy.saveChanges.apply(proxy, args);
return proxy;
}
else {
let proxy = proxify([], args[0]);
proxy.splice.apply(proxy, [0, 0, rows]);
await proxy.saveChanges.apply(proxy, args);
return proxify(proxy[0], rest[0]);
}
}
async function insertAndForget(rows) {
const concurrency = undefined;
let args = [concurrency, { insertAndForget: true }];
if (Array.isArray(rows)) {
let proxy = proxify([], args[0]);
proxy.splice.apply(proxy, [0, 0, ...rows]);
await proxy.saveChanges.apply(proxy, args);
}
else {
let proxy = proxify([], args[0]);
proxy.splice.apply(proxy, [0, 0, rows]);
await proxy.saveChanges.apply(proxy, args);
}
}
function proxify(itemOrArray, strategy, fast) {
if (Array.isArray(itemOrArray))
return proxifyArray(itemOrArray, strategy, fast);
else
return proxifyRow(itemOrArray, strategy, fast);
}
function proxifyArray(array, strategy, fast) {
let _array = array;
if (_reactive)
array = _reactive(array);
let handler = {
get(_target, property) {
if (property === 'toJSON')
return () => {
return toJSON(array);
};
else if (property === 'save' || property === 'saveChanges')
return saveArray.bind(null, array);
else if (property === 'delete')
return deleteArray.bind(null, array);
else if (property === 'refresh')
return refreshArray.bind(null, array);
else if (property === 'clearChanges')
return clearChangesArray.bind(null, array);
else if (property === 'acceptChanges')
return acceptChangesArray.bind(null, array);
else if (property === targetKey)
return _array;
else
return Reflect.get.apply(_array, arguments);
}
};
let watcher = onChange(array, () => {
rootMap.set(array, { json: cloneFromDb(array, fast), strategy, originalArray: [...array] });
});
let innerProxy = new Proxy(watcher, handler);
if (strategy !== undefined) {
const { limit, ...cleanStrategy } = { ...strategy };
fetchingStrategyMap.set(array, cleanStrategy);
}
return innerProxy;
}
function proxifyRow(row, strategy, fast) {
let handler = {
get(_target, property,) {
if (property === 'save' || property === 'saveChanges') //call server then acceptChanges
return saveRow.bind(null, row);
else if (property === 'delete') //call server then remove from json and original
return deleteRow.bind(null, row);
else if (property === 'refresh') //refresh from server then acceptChanges
return refreshRow.bind(null, row);
else if (property === 'clearChanges') //refresh from json, update original if present
return clearChangesRow.bind(null, row);
else if (property === 'acceptChanges') //remove from json
return acceptChangesRow.bind(null, row);
else if (property === 'toJSON')
return () => {
return toJSON(row);
};
else if (property === targetKey)
return row;
else
return Reflect.get(...arguments);
}
};
let watcher = onChange(row, () => {
rootMap.set(row, { json: cloneFromDb(row, fast), strategy });
});
let innerProxy = new Proxy(watcher, handler);
fetchingStrategyMap.set(row, strategy);
return innerProxy;
}
function toJSON(row, _meta = meta) {
if (!row)
return null;
if (!_meta)
return row;
if (Array.isArray(row)) {
return row.map(x => toJSON(x, _meta));
}
let result = {};
for (let name in row) {
if (name in _meta.relations)
result[name] = toJSON(row[name], _meta.relations[name]);
else if (name in _meta.columns) {
if (_meta.columns[name].serializable)
result[name] = row[name];
}
else
result[name] = row[name];
}
return result;
}
async function getMeta() {
if (meta)
return meta;
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
meta = await adapter.get();
while (hasUnresolved(meta)) {
meta = parseMeta(meta);
}
return meta;
function parseMeta(meta, map = new Map()) {
if (typeof meta === 'number') {
return map.get(meta) || meta;
}
map.set(meta.id, meta);
for (let p in meta.relations) {
meta.relations[p] = parseMeta(meta.relations[p], map);
}
return meta;
}
function hasUnresolved(meta, set = new WeakSet()) {
if (typeof meta === 'number')
return true;
else if (set.has(meta))
return false;
else {
set.add(meta);
return Object.values(meta.relations).reduce((prev, current) => {
return prev || hasUnresolved(current, set);
}, false);
}
}
}
async function saveArray(array, concurrencyOptions, strategy) {
let deduceStrategy = false;
let json = rootMap.get(array)?.json;
if (!json)
return;
strategy = extractStrategy({ strategy }, array);
strategy = extractFetchingStrategy(array, strategy);
let meta = await getMeta();
const patch = createPatch(json, array, meta);
if (patch.length === 0)
return;
let body = stringify({ patch, options: { strategy, ...tableOptions, ...concurrencyOptions, deduceStrategy } });
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
let p = adapter.patch(body);
if (strategy?.insertAndForget) {
await p;
return;
}
let updatedPositions = extractChangedRowsPositions(array, patch, meta);
let insertedPositions = getInsertedRowsPosition(array);
let { changed, strategy: newStrategy } = await p;
copyIntoArray(changed, array, [...insertedPositions, ...updatedPositions]);
rootMap.set(array, { json: cloneFromDb(array), strategy: newStrategy, originalArray: [...array] });
}
async function patch(patch, concurrencyOptions, strategy) {
let deduceStrategy = false;
if (patch.length === 0)
return;
let body = stringify({ patch, options: { strategy, ...tableOptions, ...concurrencyOptions, deduceStrategy } });
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
await adapter.patch(body);
return;
}
function extractChangedRowsPositions(rows, patch, meta) {
const positions = [];
const originalSet = new Set(rootMap.get(rows).originalArray);
const positionsAdded = {};
const keyPositionMap = toKeyPositionMap(rows, meta);
for (let i = 0; i < patch.length; i++) {
const element = patch[i];
const pathArray = element.path.split('/');
const position = keyPositionMap[pathArray[1]];
if (position >= 0 && originalSet.has(rows[position]) && !positionsAdded[position]) {
positions.push(position);
positionsAdded[position] = true;
}
}
return positions;
}
function getInsertedRowsPosition(array) {
const positions = [];
const originalSet = new Set(rootMap.get(array).originalArray);
for (let i = 0; i < array.length; i++) {
if (!originalSet.has(array[i]))
positions.push(i);
}
return positions;
}
function copyInto(from, to) {
for (let i = 0; i < from.length; i++) {
for (let p in from[i]) {
to[i][p] = from[i][p];
}
}
}
function copyIntoArray(from, to, positions) {
for (let i = 0; i < from.length; i++) {
to[positions[i]] = from[i];
}
}
function extractStrategy(options, obj) {
if (options?.strategy !== undefined)
return options.strategy;
if (obj) {
let context = rootMap.get(obj);
if (context?.strategy !== undefined) {
// @ts-ignore
let { limit, ...strategy } = { ...context.strategy };
return strategy;
}
}
}
function extractFetchingStrategy(obj, strategy) {
if (strategy !== undefined)
return strategy;
else if (fetchingStrategyMap.get(obj) !== undefined) {
// @ts-ignore
const { limit, ...strategy } = { ...fetchingStrategyMap.get(obj) };
return strategy;
}
}
function clearChangesArray(array) {
let json = rootMap.get(array)?.json;
if (!json)
return;
let old = cloneFromDb(json);
array.splice(0, old.length, ...old);
}
function acceptChangesArray(array) {
const map = rootMap.get(array);
if (!map)
return;
map.json = cloneFromDb(array);
map.originalArray = [...array];
}
async function deleteArray(array, options) {
if (array.length === 0)
return;
let meta = await getMeta();
let patch = createPatch(array, [], meta);
let body = stringify({ patch, options });
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
let { strategy } = await adapter.patch(body);
array.length = 0;
rootMap.set(array, { json: cloneFromDb(array), strategy });
}
function setMapValue(rowsMap, keys, row, index) {
let keyValue = row[keys[0].name];
if (keys.length > 1) {
let subMap = rowsMap.get(keyValue);
if (!subMap) {
subMap = new Map();
rowsMap.set(keyValue, subMap);
}
setMapValue(subMap, keys.slice(1), row, index);
}
else
rowsMap.set(keyValue, index);
}
function getMapValue(rowsMap, keys, row) {
let keyValue = row[keys[0].name];
if (keys.length > 1)
return getMapValue(rowsMap.get(keyValue), keys.slice(1));
else
return rowsMap.get(keyValue);
}
async function refreshArray(array, strategy) {
clearChangesArray(array);
strategy = extractStrategy({ strategy }, array);
strategy = extractFetchingStrategy(array, strategy);
if (array.length === 0)
return;
let meta = await getMeta();
let filter = client.filter;
let rowsMap = new Map();
for (let rowIndex = 0; rowIndex < array.length; rowIndex++) {
let row = array[rowIndex];
let keyFilter = client.filter;
for (let i = 0; i < meta.keys.length; i++) {
let keyName = meta.keys[i].name;
let keyValue = row[keyName];
keyFilter = keyFilter.and(_table[keyName].eq(keyValue));
}
setMapValue(rowsMap, meta.keys, row, rowIndex);
filter = filter.or(keyFilter);
}
let rows = await getManyCore(filter, strategy);
let removedIndexes = new Set();
if (array.length !== rows.length)
for (var i = 0; i < array.length; i++) {
removedIndexes.add(i);
}
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
let originalIndex = getMapValue(rowsMap, meta.keys, row);
if (array.length !== rows.length)
removedIndexes.delete(originalIndex);
array[originalIndex] = row;
}
let offset = 0;
for (let i of removedIndexes) {
array.splice(i + offset, 1);
offset--;
}
rootMap.set(array, { json: cloneFromDb(array), strategy, originalArray: [...array] });
fetchingStrategyMap.set(array, strategy);
}
async function deleteRow(row, options) {
let strategy = extractStrategy(options, row);
let meta = await getMeta();
let patch = createPatch([row], [], meta);
let body = stringify({ patch, options });
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
await adapter.patch(body);
rootMap.set(row, { strategy });
}
async function saveRow(row, concurrencyOptions, strategy) {
let deduceStrategy;
if (arguments.length < 3)
deduceStrategy = false;
strategy = extractStrategy({ strategy }, row);
strategy = extractFetchingStrategy(row, strategy);
let json = rootMap.get(row)?.json;
if (!json)
return;
let meta = await getMeta();
let patch = createPatch([json], [row], meta);
if (patch.length === 0)
return;
let body = stringify({ patch, options: { ...tableOptions, ...concurrencyOptions, strategy, deduceStrategy } });
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
let { changed, strategy: newStrategy } = await adapter.patch(body);
copyInto(changed, [row]);
rootMap.set(row, { json: cloneFromDb(row), strategy: newStrategy });
}
async function refreshRow(row, strategy) {
clearChangesRow(row);
strategy = extractStrategy({ strategy }, row);
strategy = extractFetchingStrategy(row, strategy);
let meta = await getMeta();
let keyFilter = client.filter;
for (let i = 0; i < meta.keys.length; i++) {
let keyName = meta.keys[i].name;
let keyValue = row[keyName];
keyFilter = keyFilter.and(_table[keyName].eq(keyValue));
}
let rows = await getManyCore(keyFilter, strategy);
for (let p in row) {
delete row[p];
}
if (rows.length === 0)
return;
for (let p in rows[0]) {
row[p] = rows[0][p];
}
rootMap.set(row, { json: cloneFromDb(row), strategy });
fetchingStrategyMap.set(row, strategy);
}
function acceptChangesRow(row) {
const data = rootMap.get(row);
if (!data)
return;
const { strategy } = data;
rootMap.set(row, { json: cloneFromDb(row), strategy });
}
function clearChangesRow(row) {
let json = rootMap.get(row)?.json;
if (!json)
return;
let old = cloneFromDb(json);
for (let p in row) {
delete row[p];
}
for (let p in old) {
row[p] = old[p];
}
}
}
}
function tableProxy() {
let handler = {
get(_target, property,) {
return column(property);
}
};
return new Proxy({}, handler);
}
function aggregate(path, arg) {
const c = {
sum,
count,
avg,
max,
min
};
let handler = {
get(_target, property,) {
if (property in c)
return Reflect.get(...arguments);
else {
subColumn = column(path + '_aggregate');
return column(property);
}
}
};
let subColumn;
const proxy = new Proxy(c, handler);
const result = arg(proxy);
if (subColumn)
return subColumn(result.self());
else
return result;
function sum(fn) {
return column(path + '_aggregate')(fn(column('')).groupSum());
}
function avg(fn) {
return column(path + '_aggregate')(fn(column('')).groupAvg());
}
function max(fn) {
return column(path + '_aggregate')(fn(column('')).groupMax());
}
function min(fn) {
return column(path + '_aggregate')(fn(column('')).groupMin());
}
function count(fn) {
return column(path + '_aggregate')(fn(column('')).groupCount());
}
}
function groupByAggregate(path, arg) {
const c = {
sum,
count,
avg,
max,
min
};
let handler = {
get(_target, property,) {
if (property in c)
return Reflect.get(...arguments);
else {
subColumn = column(path + '_aggregate');
return column(property);
}
}
};
let subColumn;
const proxy = new Proxy(c, handler);
const result = arg(proxy);
if (subColumn)
return subColumn(result.self());
else
return result;
function sum(fn) {
return column(path + '_aggregate')(fn(column('')).sum());
}
function avg(fn) {
return column(path + '_aggregate')(fn(column('')).avg());
}
function max(fn) {
return column(path + '_aggregate')(fn(column('')).max());
}
function min(fn) {
return column(path + '_aggregate')(fn(column('')).min());
}
function count(fn) {
return column(path + '_aggregate')(fn(column('')).count());
}
}
function column(path, ...previous) {
function c() {
let args = [];
for (let i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === 'function')
args[i] = arguments[i](tableProxy(path.split('.').slice(0, -1).join('.')));
else
args[i] = arguments[i];
}
args = previous.concat(Array.prototype.slice.call(args));
let result = { path, args };
let handler = {
get(_target, property) {
if (property === 'toJSON')
return result.toJSON;
else if (property === 'then')
return;
if (property in result)
return Reflect.get(...arguments);
else
return column(property, result);
}
};
return new Proxy(result, handler);
}
let handler = {
get(_target, property) {
if (property === 'toJSON')
return Reflect.get(...arguments);
else if (property === 'then')
return;
else {
const nextPath = path ? path + '.' : '';
return column(nextPath + property);
}
}
};
return new Proxy(c, handler);
}
function onChange(target, onChange) {
let notified = false;
const handler = {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'object' && value !== null) {
return new Proxy(value, handler);
}
return value;
},
set(target, prop, value, receiver) {
if (!notified) {
notified = true;
onChange(JSON.stringify(target));
}
return Reflect.set(target, prop, value, receiver);
},
deleteProperty(target, prop) {
if (!notified) {
notified = true;
onChange(JSON.stringify(target));
}
return Reflect.deleteProperty(target, prop);
}
};
return new Proxy(target, handler);
}
module.exports = rdbClient();