Add initial version of dijkstra backend cloudron image

This commit is contained in:
2020-10-12 11:27:15 +02:00
commit 4f5db9ab26
4209 changed files with 448228 additions and 0 deletions

454
node_modules/knex/lib/dialects/oracledb/index.js generated vendored Normal file
View File

@@ -0,0 +1,454 @@
// Oracledb Client
// -------
const _ = require('lodash');
const inherits = require('inherits');
const QueryCompiler = require('./query/compiler');
const ColumnCompiler = require('./schema/columncompiler');
const { BlobHelper, ReturningHelper, isConnectionError } = require('./utils');
const Bluebird = require('bluebird');
const stream = require('stream');
const { promisify } = require('util');
const Transaction = require('./transaction');
const Client_Oracle = require('../oracle');
const Oracle_Formatter = require('../oracle/formatter');
function Client_Oracledb() {
Client_Oracle.apply(this, arguments);
// Node.js only have 4 background threads by default, oracledb needs one by connection
if (this.driver) {
process.env.UV_THREADPOOL_SIZE = process.env.UV_THREADPOOL_SIZE || 1;
process.env.UV_THREADPOOL_SIZE =
parseInt(process.env.UV_THREADPOOL_SIZE) + this.driver.poolMax;
}
}
inherits(Client_Oracledb, Client_Oracle);
Client_Oracledb.prototype.driverName = 'oracledb';
Client_Oracledb.prototype._driver = function() {
const client = this;
const oracledb = require('oracledb');
client.fetchAsString = [];
if (this.config.fetchAsString && _.isArray(this.config.fetchAsString)) {
this.config.fetchAsString.forEach(function(type) {
if (!_.isString(type)) return;
type = type.toUpperCase();
if (oracledb[type]) {
if (type !== 'NUMBER' && type !== 'DATE' && type !== 'CLOB') {
this.logger.warn(
'Only "date", "number" and "clob" are supported for fetchAsString'
);
}
client.fetchAsString.push(oracledb[type]);
}
});
}
return oracledb;
};
Client_Oracledb.prototype.queryCompiler = function() {
return new QueryCompiler(this, ...arguments);
};
Client_Oracledb.prototype.columnCompiler = function() {
return new ColumnCompiler(this, ...arguments);
};
Client_Oracledb.prototype.formatter = function() {
return new Oracledb_Formatter(this, ...arguments);
};
Client_Oracledb.prototype.transaction = function() {
return new Transaction(this, ...arguments);
};
Client_Oracledb.prototype.prepBindings = function(bindings) {
return _.map(bindings, (value) => {
if (value instanceof BlobHelper && this.driver) {
return { type: this.driver.BLOB, dir: this.driver.BIND_OUT };
// Returning helper always use ROWID as string
} else if (value instanceof ReturningHelper && this.driver) {
return { type: this.driver.STRING, dir: this.driver.BIND_OUT };
} else if (typeof value === 'boolean') {
return value ? 1 : 0;
}
return value;
});
};
// Get a raw connection, called by the `pool` whenever a new
// connection needs to be added to the pool.
Client_Oracledb.prototype.acquireRawConnection = function() {
const client = this;
const asyncConnection = new Bluebird(function(resolver, rejecter) {
// If external authentication don't have to worry about username/password and
// if not need to set the username and password
const oracleDbConfig = client.connectionSettings.externalAuth
? { externalAuth: client.connectionSettings.externalAuth }
: {
user: client.connectionSettings.user,
password: client.connectionSettings.password,
};
// In the case of external authentication connection string will be given
oracleDbConfig.connectString =
client.connectionSettings.connectString ||
client.connectionSettings.host + '/' + client.connectionSettings.database;
if (client.connectionSettings.prefetchRowCount) {
oracleDbConfig.prefetchRows = client.connectionSettings.prefetchRowCount;
}
if (!_.isUndefined(client.connectionSettings.stmtCacheSize)) {
oracleDbConfig.stmtCacheSize = client.connectionSettings.stmtCacheSize;
}
client.driver.fetchAsString = client.fetchAsString;
client.driver.getConnection(oracleDbConfig, function(err, connection) {
if (err) {
return rejecter(err);
}
connection.commitAsync = function() {
return new Bluebird((commitResolve, commitReject) => {
if (connection.isTransaction) {
return commitResolve();
}
this.commit(function(err) {
if (err) {
return commitReject(err);
}
commitResolve();
});
});
};
connection.rollbackAsync = function() {
return new Bluebird((rollbackResolve, rollbackReject) => {
this.rollback(function(err) {
if (err) {
return rollbackReject(err);
}
rollbackResolve();
});
});
};
const fetchAsync = promisify(function(sql, bindParams, options, cb) {
options = options || {};
options.outFormat =
client.driver.OUT_FORMAT_OBJECT || client.driver.OBJECT;
if (!options.outFormat) {
throw new Error('not found oracledb.outFormat constants');
}
if (options.resultSet) {
connection.execute(sql, bindParams || [], options, function(
err,
result
) {
if (err) {
if (isConnectionError(err)) {
connection.close().catch(function(err) {});
connection.__knex__disposed = err;
}
return cb(err);
}
const fetchResult = { rows: [], resultSet: result.resultSet };
const numRows = 100;
const fetchRowsFromRS = function(connection, resultSet, numRows) {
resultSet.getRows(numRows, function(err, rows) {
if (err) {
if (isConnectionError(err)) {
connection.close().catch(function(err) {});
connection.__knex__disposed = err;
}
resultSet.close(function() {
return cb(err);
});
} else if (rows.length === 0) {
return cb(null, fetchResult);
} else if (rows.length > 0) {
if (rows.length === numRows) {
fetchResult.rows = fetchResult.rows.concat(rows);
fetchRowsFromRS(connection, resultSet, numRows);
} else {
fetchResult.rows = fetchResult.rows.concat(rows);
return cb(null, fetchResult);
}
}
});
};
fetchRowsFromRS(connection, result.resultSet, numRows);
});
} else {
connection.execute(sql, bindParams || [], options, function(
err,
result
) {
if (err) {
// dispose the connection on connection error
if (isConnectionError(err)) {
connection.close().catch(function(err) {});
connection.__knex__disposed = err;
}
return cb(err);
}
return cb(null, result);
});
}
});
connection.executeAsync = function(sql, bindParams, options) {
// Read all lob
return fetchAsync(sql, bindParams, options).then(async (results) => {
const closeResultSet = () => {
return results.resultSet
? promisify(results.resultSet.close).call(results.resultSet)
: Promise.resolve();
};
// Collect LOBs to read
const lobs = [];
if (results.rows) {
if (Array.isArray(results.rows)) {
for (let i = 0; i < results.rows.length; i++) {
// Iterate through the rows
const row = results.rows[i];
for (const column in row) {
if (row[column] instanceof stream.Readable) {
lobs.push({ index: i, key: column, stream: row[column] });
}
}
}
}
}
try {
for (const lob of lobs) {
// todo should be fetchAsString/fetchAsBuffer polyfill only
results.rows[lob.index][lob.key] = await lobProcessing(
lob.stream
);
}
} catch (e) {
await closeResultSet().catch(() => {});
throw e;
}
await closeResultSet();
return results;
});
};
resolver(connection);
});
});
return asyncConnection;
};
// Used to explicitly close a connection, called internally by the pool
// when a connection times out or the pool is shutdown.
Client_Oracledb.prototype.destroyRawConnection = function(connection) {
return connection.release();
};
// Runs the query on the specified connection, providing the bindings
// and any other necessary prep work.
Client_Oracledb.prototype._query = function(connection, obj) {
if (!obj.sql) throw new Error('The query is empty');
const options = { autoCommit: false };
if (obj.method === 'select') {
options.resultSet = true;
}
return Bluebird.resolve(
connection.executeAsync(obj.sql, obj.bindings, options)
).then(async function(response) {
// Flatten outBinds
let outBinds = _.flatten(response.outBinds);
obj.response = response.rows || [];
obj.rowsAffected = response.rows
? response.rows.rowsAffected
: response.rowsAffected;
//added for outBind parameter
if (obj.method === 'raw' && outBinds.length > 0) {
return {
response: outBinds,
};
}
if (obj.method === 'update') {
const modifiedRowsCount = obj.rowsAffected.length || obj.rowsAffected;
const updatedObjOutBinding = [];
const updatedOutBinds = [];
const updateOutBinds = (i) =>
function(value, index) {
const OutBindsOffset = index * modifiedRowsCount;
updatedOutBinds.push(outBinds[i + OutBindsOffset]);
};
for (let i = 0; i < modifiedRowsCount; i++) {
updatedObjOutBinding.push(obj.outBinding[0]);
_.each(obj.outBinding[0], updateOutBinds(i));
}
outBinds = updatedOutBinds;
obj.outBinding = updatedObjOutBinding;
}
if (!obj.returning && outBinds.length === 0) {
await connection.commitAsync();
return obj;
}
const rowIds = [];
let offset = 0;
for (let line = 0; line < obj.outBinding.length; line++) {
const ret = obj.outBinding[line];
offset =
offset +
(obj.outBinding[line - 1] ? obj.outBinding[line - 1].length : 0);
for (let index = 0; index < ret.length; index++) {
const out = ret[index];
await new Promise(function(bindResolver, bindRejecter) {
if (out instanceof BlobHelper) {
const blob = outBinds[index + offset];
if (out.returning) {
obj.response[line] = obj.response[line] || {};
obj.response[line][out.columnName] = out.value;
}
blob.on('error', function(err) {
bindRejecter(err);
});
blob.on('finish', function() {
bindResolver();
});
blob.write(out.value);
blob.end();
} else if (obj.outBinding[line][index] === 'ROWID') {
rowIds.push(outBinds[index + offset]);
bindResolver();
} else {
obj.response[line] = obj.response[line] || {};
obj.response[line][out] = outBinds[index + offset];
bindResolver();
}
});
}
}
return connection.commitAsync().then(function() {
if (obj.returningSql) {
return connection
.executeAsync(obj.returningSql(), rowIds, { resultSet: true })
.then(function(response) {
obj.response = response.rows;
return obj;
});
}
return obj;
});
});
};
/**
* @param stream
* @param {'string' | 'buffer'} type
*/
function readStream(stream, type) {
return new Promise((resolve, reject) => {
let data = type === 'string' ? '' : Buffer.alloc(0);
stream.on('error', function(err) {
reject(err);
});
stream.on('data', function(chunk) {
if (type === 'string') {
data += chunk;
} else {
data = Buffer.concat([data, chunk]);
}
});
stream.on('end', function() {
resolve(data);
});
});
}
// Process the response as returned from the query.
Client_Oracledb.prototype.processResponse = function(obj, runner) {
let response = obj.response;
const method = obj.method;
if (obj.output) {
return obj.output.call(runner, response);
}
switch (method) {
case 'select':
case 'pluck':
case 'first':
if (obj.method === 'pluck') {
response = _.map(response, obj.pluck);
}
return obj.method === 'first' ? response[0] : response;
case 'insert':
case 'del':
case 'update':
case 'counter':
if (obj.returning && !_.isEmpty(obj.returning)) {
if (obj.returning.length === 1 && obj.returning[0] !== '*') {
return _.flatten(_.map(response, _.values));
}
return response;
} else if (!_.isUndefined(obj.rowsAffected)) {
return obj.rowsAffected;
} else {
return 1;
}
default:
return response;
}
};
const lobProcessing = function(stream) {
const oracledb = require('oracledb');
/**
* @type 'string' | 'buffer'
*/
let type;
if (stream.type) {
// v1.2-v4
if (stream.type === oracledb.BLOB) {
type = 'buffer';
} else if (stream.type === oracledb.CLOB) {
type = 'string';
}
} else if (stream.iLob) {
// v1
if (stream.iLob.type === oracledb.CLOB) {
type = 'string';
} else if (stream.iLob.type === oracledb.BLOB) {
type = 'buffer';
}
} else {
throw new Error('Unrecognized oracledb lob stream type');
}
if (type === 'string') {
stream.setEncoding('utf-8');
}
return readStream(stream, type);
};
class Oracledb_Formatter extends Oracle_Formatter {
// Checks whether a value is a function... if it is, we compile it
// otherwise we check whether it's a raw
parameter(value) {
if (typeof value === 'function') {
return this.outputQuery(this.compileCallback(value), true);
} else if (value instanceof BlobHelper) {
return 'EMPTY_BLOB()';
}
return this.unwrapRaw(value, true) || '?';
}
}
module.exports = Client_Oracledb;

View File

@@ -0,0 +1,360 @@
const _ = require('lodash');
const inherits = require('inherits');
const Oracle_Compiler = require('../../oracle/query/compiler');
const ReturningHelper = require('../utils').ReturningHelper;
const BlobHelper = require('../utils').BlobHelper;
function Oracledb_Compiler(client, builder) {
Oracle_Compiler.call(this, client, builder);
}
inherits(Oracledb_Compiler, Oracle_Compiler);
_.assign(Oracledb_Compiler.prototype, {
// Compiles an "insert" query, allowing for multiple
// inserts using a single query statement.
insert: function() {
const self = this;
const outBindPrep = this._prepOutbindings(
this.single.insert,
this.single.returning
);
const outBinding = outBindPrep.outBinding;
const returning = outBindPrep.returning;
const insertValues = outBindPrep.values;
if (
Array.isArray(insertValues) &&
insertValues.length === 1 &&
_.isEmpty(insertValues[0])
) {
return this._addReturningToSqlAndConvert(
'insert into ' +
this.tableName +
' (' +
this.formatter.wrap(this.single.returning) +
') values (default)',
outBinding[0],
this.tableName,
returning
);
}
if (
_.isEmpty(this.single.insert) &&
typeof this.single.insert !== 'function'
) {
return '';
}
const insertData = this._prepInsert(insertValues);
const sql = {};
if (_.isString(insertData)) {
return this._addReturningToSqlAndConvert(
'insert into ' + this.tableName + ' ' + insertData,
outBinding[0],
this.tableName,
returning
);
}
if (insertData.values.length === 1) {
return this._addReturningToSqlAndConvert(
'insert into ' +
this.tableName +
' (' +
this.formatter.columnize(insertData.columns) +
') values (' +
this.formatter.parameterize(insertData.values[0]) +
')',
outBinding[0],
this.tableName,
returning
);
}
const insertDefaultsOnly = insertData.columns.length === 0;
sql.returning = returning;
sql.sql =
'begin ' +
_.map(insertData.values, function(value, index) {
const parameterizedValues = !insertDefaultsOnly
? self.formatter.parameterize(value, self.client.valueForUndefined)
: '';
let subSql = 'insert into ' + self.tableName;
if (insertDefaultsOnly) {
// No columns given so only the default value
subSql +=
' (' +
self.formatter.wrap(self.single.returning) +
') values (default)';
} else {
subSql +=
' (' +
self.formatter.columnize(insertData.columns) +
') values (' +
parameterizedValues +
')';
}
let returningClause = '';
let intoClause = '';
// ToDo review if this code is still needed or could be dropped
// eslint-disable-next-line no-unused-vars
let usingClause = '';
let outClause = '';
_.each(value, function(val) {
if (!(val instanceof BlobHelper)) {
usingClause += ' ?,';
}
});
usingClause = usingClause.slice(0, -1);
// Build returning and into clauses
_.each(outBinding[index], function(ret) {
const columnName = ret.columnName || ret;
returningClause += self.formatter.wrap(columnName) + ',';
intoClause += ' ?,';
outClause += ' out ?,';
// Add Helpers to bindings
if (ret instanceof BlobHelper) {
return self.formatter.bindings.push(ret);
}
self.formatter.bindings.push(new ReturningHelper(columnName));
});
// Strip last comma
returningClause = returningClause.slice(0, -1);
intoClause = intoClause.slice(0, -1);
outClause = outClause.slice(0, -1);
if (returningClause && intoClause) {
subSql += ' returning ' + returningClause + ' into' + intoClause;
}
// Pre bind position because subSql is an execute immediate parameter
// later position binding will only convert the ? params
subSql = self.formatter.client.positionBindings(subSql);
const parameterizedValuesWithoutDefaultAndBlob = parameterizedValues
.replace('DEFAULT, ', '')
.replace(', DEFAULT', '')
.replace('EMPTY_BLOB(), ', '')
.replace(', EMPTY_BLOB()', '');
return (
"execute immediate '" +
subSql.replace(/'/g, "''") +
(parameterizedValuesWithoutDefaultAndBlob || value
? "' using "
: '') +
parameterizedValuesWithoutDefaultAndBlob +
(parameterizedValuesWithoutDefaultAndBlob && outClause ? ',' : '') +
outClause +
';'
);
}).join(' ') +
'end;';
sql.outBinding = outBinding;
if (returning[0] === '*') {
// Generate select statement with special order by
// to keep the order because 'in (..)' may change the order
sql.returningSql = function() {
return (
'select * from ' +
self.tableName +
' where ROWID in (' +
this.outBinding
.map(function(v, i) {
return ':' + (i + 1);
})
.join(', ') +
')' +
' order by case ROWID ' +
this.outBinding
.map(function(v, i) {
return 'when CHARTOROWID(:' + (i + 1) + ') then ' + i;
})
.join(' ') +
' end'
);
};
}
return sql;
},
_addReturningToSqlAndConvert: function(
sql,
outBinding,
tableName,
returning
) {
const self = this;
const res = {
sql: sql,
};
if (!outBinding) {
return res;
}
const returningValues = Array.isArray(outBinding)
? outBinding
: [outBinding];
let returningClause = '';
let intoClause = '';
// Build returning and into clauses
_.each(returningValues, function(ret) {
const columnName = ret.columnName || ret;
returningClause += self.formatter.wrap(columnName) + ',';
intoClause += '?,';
// Add Helpers to bindings
if (ret instanceof BlobHelper) {
return self.formatter.bindings.push(ret);
}
self.formatter.bindings.push(new ReturningHelper(columnName));
});
res.sql = sql;
// Strip last comma
returningClause = returningClause.slice(0, -1);
intoClause = intoClause.slice(0, -1);
if (returningClause && intoClause) {
res.sql += ' returning ' + returningClause + ' into ' + intoClause;
}
res.outBinding = [outBinding];
if (returning[0] === '*') {
res.returningSql = function() {
return 'select * from ' + self.tableName + ' where ROWID = :1';
};
}
res.returning = returning;
return res;
},
_prepOutbindings: function(paramValues, paramReturning) {
const result = {};
let params = paramValues || [];
let returning = paramReturning || [];
if (!Array.isArray(params) && _.isPlainObject(paramValues)) {
params = [params];
}
// Always wrap returning argument in array
if (returning && !Array.isArray(returning)) {
returning = [returning];
}
const outBinding = [];
// Handle Buffer value as Blob
_.each(params, function(values, index) {
if (returning[0] === '*') {
outBinding[index] = ['ROWID'];
} else {
outBinding[index] = _.clone(returning);
}
_.each(values, function(value, key) {
if (value instanceof Buffer) {
values[key] = new BlobHelper(key, value);
// Delete blob duplicate in returning
const blobIndex = outBinding[index].indexOf(key);
if (blobIndex >= 0) {
outBinding[index].splice(blobIndex, 1);
values[key].returning = true;
}
outBinding[index].push(values[key]);
}
if (_.isUndefined(value)) {
delete params[index][key];
}
});
});
result.returning = returning;
result.outBinding = outBinding;
result.values = params;
return result;
},
update: function() {
const self = this;
const sql = {};
const outBindPrep = this._prepOutbindings(
this.single.update || this.single.counter,
this.single.returning
);
const outBinding = outBindPrep.outBinding;
const returning = outBindPrep.returning;
const updates = this._prepUpdate(this.single.update);
const where = this.where();
let returningClause = '';
let intoClause = '';
if (_.isEmpty(updates) && typeof this.single.update !== 'function') {
return '';
}
// Build returning and into clauses
_.each(outBinding, function(out) {
_.each(out, function(ret) {
const columnName = ret.columnName || ret;
returningClause += self.formatter.wrap(columnName) + ',';
intoClause += ' ?,';
// Add Helpers to bindings
if (ret instanceof BlobHelper) {
return self.formatter.bindings.push(ret);
}
self.formatter.bindings.push(new ReturningHelper(columnName));
});
});
// Strip last comma
returningClause = returningClause.slice(0, -1);
intoClause = intoClause.slice(0, -1);
sql.outBinding = outBinding;
sql.returning = returning;
sql.sql =
'update ' +
this.tableName +
' set ' +
updates.join(', ') +
(where ? ' ' + where : '');
if (outBinding.length && !_.isEmpty(outBinding[0])) {
sql.sql += ' returning ' + returningClause + ' into' + intoClause;
}
if (returning[0] === '*') {
sql.returningSql = function() {
let sql = 'select * from ' + self.tableName;
const modifiedRowsCount = this.rowsAffected.length || this.rowsAffected;
let returningSqlIn = ' where ROWID in (';
let returningSqlOrderBy = ') order by case ROWID ';
// Needs special order by because in(...) change result order
for (let i = 0; i < modifiedRowsCount; i++) {
if (this.returning[0] === '*') {
returningSqlIn += ':' + (i + 1) + ', ';
returningSqlOrderBy +=
'when CHARTOROWID(:' + (i + 1) + ') then ' + i + ' ';
}
}
if (this.returning[0] === '*') {
this.returning = this.returning.slice(0, -1);
returningSqlIn = returningSqlIn.slice(0, -2);
returningSqlOrderBy = returningSqlOrderBy.slice(0, -1);
}
return (sql += returningSqlIn + returningSqlOrderBy + ' end');
};
}
return sql;
},
});
module.exports = Oracledb_Compiler;

View File

@@ -0,0 +1,36 @@
const inherits = require('inherits');
const ColumnCompiler_Oracle = require('../../oracle/schema/columncompiler');
const { isObject } = require('lodash');
function ColumnCompiler_Oracledb() {
ColumnCompiler_Oracle.apply(this, arguments);
}
inherits(ColumnCompiler_Oracledb, ColumnCompiler_Oracle);
Object.assign(ColumnCompiler_Oracledb.prototype, {
time: 'timestamp with local time zone',
datetime: function(withoutTz) {
let useTz;
if (isObject(withoutTz)) {
({ useTz } = withoutTz);
} else {
useTz = !withoutTz;
}
return useTz ? 'timestamp with local time zone' : 'timestamp';
},
timestamp: function(withoutTz) {
let useTz;
if (isObject(withoutTz)) {
({ useTz } = withoutTz);
} else {
useTz = !withoutTz;
}
return useTz ? 'timestamp with local time zone' : 'timestamp';
},
});
module.exports = ColumnCompiler_Oracledb;

87
node_modules/knex/lib/dialects/oracledb/transaction.js generated vendored Normal file
View File

@@ -0,0 +1,87 @@
const { isUndefined } = require('lodash');
const Bluebird = require('bluebird');
const Transaction = require('../../transaction');
const debugTx = require('debug')('knex:tx');
module.exports = class Oracle_Transaction extends Transaction {
// disable autocommit to allow correct behavior (default is true)
begin() {
return Bluebird.resolve();
}
commit(conn, value) {
this._completed = true;
return conn
.commitAsync()
.then(() => value)
.then(this._resolver, this._rejecter);
}
release(conn, value) {
return this._resolver(value);
}
rollback(conn, err) {
const self = this;
this._completed = true;
debugTx('%s: rolling back', this.txid);
return conn
.rollbackAsync()
.timeout(5000)
.catch(Bluebird.TimeoutError, function(e) {
self._rejecter(e);
})
.then(function() {
if (isUndefined(err)) {
if (self.doNotRejectOnRollback) {
self._resolver();
return;
}
err = new Error(`Transaction rejected with non-error: ${err}`);
}
self._rejecter(err);
});
}
savepoint(conn) {
return this.query(conn, `SAVEPOINT ${this.txid}`);
}
acquireConnection(config, cb) {
const configConnection = config && config.connection;
const t = this;
return new Bluebird((resolve, reject) => {
try {
this.client
.acquireConnection()
.then((cnx) => {
cnx.__knexTxId = this.txid;
cnx.isTransaction = true;
resolve(cnx);
})
.catch(reject);
} catch (e) {
reject(e);
}
}).then(async (connection) => {
try {
return await cb(connection);
} finally {
debugTx('%s: releasing connection', this.txid);
connection.isTransaction = false;
try {
await connection.commitAsync();
} catch (err) {
t._rejecter(err);
} finally {
if (!configConnection) {
await t.client.releaseConnection(connection);
} else {
debugTx('%s: not releasing external connection', t.txid);
}
}
}
});
}
};

14
node_modules/knex/lib/dialects/oracledb/utils.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
const Utils = require('../oracle/utils');
function BlobHelper(columnName, value) {
this.columnName = columnName;
this.value = value;
this.returning = false;
}
BlobHelper.prototype.toString = function() {
return '[object BlobHelper:' + this.columnName + ']';
};
Utils.BlobHelper = BlobHelper;
module.exports = Utils;