UNPKG

sql-client

Version:

A dirt-simple SQL client abstraction (currently) supporting PostgreSQL, MySQL and SQLite.

629 lines (580 loc) 23.4 kB
// Generated by CoffeeScript 2.6.0 (function() { var SQLClient, SQLClientPool, Transaction, splice = [].splice, indexOf = [].indexOf; SQLClient = require('./sql-client').SQLClient; SQLClientPool = (function() { class SQLClientPool { create_transaction() { return new Transaction(this); } create(callback) { var client; client = new SQLClient(...this.sql_options, this.factory); return client.connect((err) => { return callback(err, client); }); } activate(client, callback) { return callback(null, client); } validate(client, callback) { var ref; if ((client != null) && ((client.pooled_at == null) || (((ref = this.pool_options) != null ? ref.max_age : void 0) == null) || ((Date.now() - client.pooled_at) < this.pool_options.max_age))) { return callback(null, true, client); } else { return callback(null, false, client); } } passivate(client, callback) { return callback(null, client); } destroy(client, callback) { if (client != null) { return client.disconnect(callback); } else { return callback(); } } constructor(...sql_options) { var factory, ref; ref = sql_options, [...sql_options] = ref, [factory] = splice.call(sql_options, -1); this.create_transaction = this.create_transaction.bind(this); this.create = this.create.bind(this); this.activate = this.activate.bind(this); this.validate = this.validate.bind(this); this.passivate = this.passivate.bind(this); this.destroy = this.destroy.bind(this); this.open = this.open.bind(this); this.close = this.close.bind(this); // borrow, execute, return, callback this.execute = this.execute.bind(this); this.borrow = this.borrow.bind(this); this.return = this.return.bind(this); // CONFIGUATION OPTIONS: // - `min_idle` - minimum number of idle connections in an "empty" pool // - `max_idle` - maximum number of idle connections in a "full" pool // - `max_active` - maximum number of connections active at one time // - `when_exhausted` - what to do when max_active is reached (`grow`,`block`,`fail`), // - `max_wait` - when `when_exhausted` is `block` max time (in millis) to wait before failure, use < 0 for no maximum // - `wait_interval` - when `when_exhausted` is `BLOCK`, amount of time (in millis) to wait before rechecking if connections are available // - `max_retries` - number of times to attempt to create another new connection when a newly created connection is invalid; when `null` no retry attempts will be made; when < 0 an infinite number of retries will be attempted // - `retry_interval` - when `max_retries` is > 0, amount of time (in millis) to wait before retrying // - `max_age` - when a positive integer, connections that have been idle for `max_age` milliseconds will be considered invalid and eligable for eviction // - `eviction_run_interval` - when a positive integer, the number of milliseconds between eviction runs; during an eviction run idle connections will be tested for validity and if invalid, evicted from the pool // - `eviction_run_length` - when a positive integer, the maxiumum number of connections to examine per eviction run (when not set, all idle connections will be evamined during each eviction run) // - `unref_eviction_runner` - unless `false`, `unref` (https://nodejs.org/api/timers.html#timers_unref) will be called on the eviction run interval timer this._config = this._config.bind(this); this._reconfig = this._reconfig.bind(this); this._evict = this._evict.bind(this); this._eviction_run = this._eviction_run.bind(this); this._prepopulate = this._prepopulate.bind(this); this._borrow_n = this._borrow_n.bind(this); this._return_n = this._return_n.bind(this); this._activate_and_validate_or_destroy = this._activate_and_validate_or_destroy.bind(this); this.sql_options = sql_options; this.factory = factory; this.open(function() { return void 0; }); } open(opts, callback) { if ((callback == null) && typeof opts === 'function') { callback = opts; opts = null; } if (typeof callback !== 'function') { throw new Error(this.MESSAGES.INVALID_ARGUMENT); } else { return this._config(opts, (err) => { this.pool_is_open = true; return typeof callback === "function" ? callback(err) : void 0; }); } } close(callback) { if ((callback != null) && typeof callback !== 'function') { throw new Error(this.MESSAGES.INVALID_ARGUMENT); } else { this.pool_is_open = false; if (this.eviction_runner != null) { this.eviction_runner.ref(); clearTimeout(this.eviction_runner); this.eviction_runner = null; } if (this.pool.length > 0) { return this.destroy(this.pool.shift(), () => { return this.close(callback); }); } else { if (this.active > 0) { return typeof callback === "function" ? callback(new Error(this.MESSAGES.CLOSED_WITH_ACTIVE)) : void 0; } else { return typeof callback === "function" ? callback() : void 0; } } } } execute(sql, bindvars, callback) { if (typeof bindvars === 'function' && (callback == null)) { callback = bindvars; bindvars = []; } return this.borrow((err, client) => { if (err != null) { return callback(err); } else if (client == null) { return callback(new Error("non-null client expected")); } else { return client.execute(sql, bindvars, (...response) => { return this.return(client, () => { return callback(...response); }); }); } }); } borrow(callback, blocked_since = null, retry_count = 0) { var client; if (typeof callback !== 'function') { throw new Error(this.MESSAGES.INVALID_ARGUMENT); } else { if (!this.pool_is_open) { return callback(new Error(this.MESSAGES.POOL_NOT_OPEN)); } else { if (this.active >= this.pool_options.max_active && this.pool_options.when_exhausted === 'fail') { return callback(new Error(this.MESSAGES.EXHAUSTED)); } else if (this.active >= this.pool_options.max_active && this.pool_options.when_exhausted === 'block') { if ((blocked_since != null) && (Date.now() - blocked_since) >= this.pool_options.max_wait) { return callback(new Error(this.MESSAGES.MAX_WAIT)); } else { if (blocked_since == null) { blocked_since = Date.now(); } return setTimeout((() => { return this.borrow(callback, blocked_since, retry_count); }), this.pool_options.wait_interval); } } else if (this.pool.length > 0) { client = this.pool.shift(); return this._activate_and_validate_or_destroy(client, (err, valid, client) => { if (err != null) { return callback(err); } else if (!valid) { return this.borrow(callback); } else { client.pooled_at = null; client.borrowed_at = Date.now(); this.active++; return callback(null, client); } }); } else { return this.create((err, client) => { if (err != null) { return callback(err); } else { return this._activate_and_validate_or_destroy(client, (err, valid, client) => { var ref; if (err != null) { return callback(err); } else if (!valid) { if ((this.pool_options.max_retries != null) && this.pool_options.max_retries > retry_count) { return setTimeout((() => { return this.borrow(callback, blocked_since, retry_count + 1); }), (ref = this.pool_options.retry_interval) != null ? ref : 0); } else { return callback(new Error(this.MESSAGES.INVALID)); } } else { client.pooled_at = null; client.borrowed_at = Date.now(); this.active++; return callback(null, client); } }); } }); } } } } return(client, callback) { if (((client == null) && (callback == null)) || ((callback != null) && typeof callback !== 'function')) { throw new Error(this.MESSAGES.INVALID_ARGUMENT); } else if (client == null) { return callback(new Error(this.MESSAGES.NULL_RETURNED)); } else if (this.active <= 0) { return callback(new Error(this.MESSAGES.TOO_MANY_RETURNED)); } else { this.returned++; this.active--; if (client != null) { return this.passivate(client, (err, client) => { if (err) { return callback(err); } else { client.pooled_at = Date.now(); client.borrowed_at = null; if (this.pool.length >= this.pool_options.max_idle) { return this.destroy(client, callback); } else { this.pool.push(client); return typeof callback === "function" ? callback() : void 0; } } }); } } } _config(opts, callback) { var i, keys, len, new_opts, prop, ref, ref1; if (opts == null) { opts = {}; } new_opts = this._clone(this.pool_options); keys = Object.keys(opts); ref = ['min_idle', 'max_idle', 'max_active', 'when_exhausted', 'max_wait', 'wait_interval', 'max_age', 'eviction_run_interval', 'eviction_run_length', 'unref_eviction_runner', 'max_retries', 'retry_interval']; for (i = 0, len = ref.length; i < len; i++) { prop = ref[i]; if (indexOf.call(keys, prop) >= 0) { new_opts[prop] = opts[prop]; } } if ((new_opts.max_retries != null) && (typeof new_opts.max_retries !== 'number' || new_opts.max_retries <= 0)) { new_opts.max_retries = null; } if (new_opts.max_retries != null) { if ((new_opts.retry_interval != null) && (typeof new_opts.retry_interval !== 'number' || new_opts.retry_interval <= 0)) { new_opts.retry_interval = 0; } else if (new_opts.retry_interval == null) { new_opts.retry_interval = this.DEFAULT_RETRY_INTERVAL; } } if (typeof new_opts.max_idle === 'number' && new_opts.max_idle < 0) { new_opts.max_idle = Number.MAX_VALUE; } else if (typeof new_opts.max_idle !== 'number') { new_opts.max_idle = 0; } if (typeof new_opts.min_idle !== 'number' || new_opts.min_idle < 0) { new_opts.min_idle = 0; } if (new_opts.min_idle > new_opts.max_idle) { new_opts.min_idle = new_opts.max_idle; } if (typeof new_opts.max_active !== 'number' || new_opts.max_active < 0) { new_opts.max_active = Number.MAX_VALUE; } if (typeof new_opts.max_wait !== 'number' || new_opts.max_wait < 0) { new_opts.max_wait = Number.MAX_VALUE; } if (typeof new_opts.wait_interval !== 'number' || new_opts.wait_interval < 0) { new_opts.wait_interval = this.DEFAULT_WAIT_INTERVAL; } if ((ref1 = new_opts.when_exhausted) !== 'grow' && ref1 !== 'block' && ref1 !== 'fail') { new_opts.when_exhausted = 'grow'; } if ((new_opts.max_age == null) || new_opts.max_age < 0) { new_opts.max_age = Number.MAX_VALUE; } this.pool_options = new_opts; return this._reconfig(callback); } _reconfig(callback) { if (this.eviction_runner != null) { this.eviction_runner.ref(); clearTimeout(this.eviction_runner); this.eviction_runner = null; } return this._evict((err) => { if (err != null) { return typeof callback === "function" ? callback(err) : void 0; } else { return this._prepopulate((...x) => { var ref, ref1; if ((((ref = this.pool_options) != null ? ref.eviction_run_interval : void 0) != null) && this.pool_options.eviction_run_interval > 0) { this.eviction_runner = setInterval(this._eviction_run, this.pool_options.eviction_run_interval); if (((ref1 = this.pool_options) != null ? ref1.unref_eviction_runner : void 0) !== false) { this.eviction_runner.unref(); } } return callback(...x); }); } }); } _evict(callback) { return this._eviction_run(0, callback); } _eviction_run(num_to_check, callback) { var client, new_pool, num_checked; if (typeof num_to_check === 'function' && (callback == null)) { callback = num_to_check; num_to_check = null; } new_pool = []; num_checked = 0; while (this.pool.length > 0) { client = this.pool.shift(); if ((num_to_check == null) || num_to_check <= 0 || num_checked < num_to_check) { num_checked += 1; if (new_pool.length < this.pool_options.max_idle && this._is_valid(client)) { new_pool.push(client); } else { client.disconnect(); } } else { new_pool.push(client); } } this.pool = new_pool; return typeof callback === "function" ? callback() : void 0; } _prepopulate(callback) { var n; n = this.pool_options.min_idle - this.pool.length; if (n > 0) { return this._borrow_n(n, [], (err, borrowed) => { if (err != null) { return callback(err); } else { return this._return_n(borrowed, callback); } }); } else { return callback(); } } _borrow_n(n, borrowed, callback) { if (typeof n !== 'number' || !Array.isArray(borrowed)) { return callback(new Error(this.MESSAGES.INTERNAL_ERROR)); } else { if (n > borrowed.length) { return this.borrow((err, client) => { if (client != null) { borrowed.push(client); } if (err != null) { return this._return_n(borrowed, () => { return callback(err); }); } else { return this._borrow_n(n, borrowed, callback); } }); } else { return callback(null, borrowed); } } } _return_n(borrowed, callback) { var client; if (!Array.isArray(borrowed)) { return callback(new Error(this.MESSAGES.INTERNAL_ERROR)); } else if (borrowed.length > 0) { client = borrowed.shift(); return this.return(client, () => { return this._return_n(borrowed, callback); }); } else { return callback(null); } } _activate_and_validate_or_destroy(client, callback) { return this.activate(client, (err, client) => { if (err != null) { if (client != null) { return this.destroy(client, () => { return callback(err, false, null); }); } else { return callback(err, false, null); } } else { return this.validate(client, (err, valid, client) => { if (err != null) { if (client != null) { return this.destroy(client, () => { return callback(err, false, null); }); } else { return callback(err, false, null); } } else if (!valid) { return this.destroy(client, () => { return callback(null, false, null); }); } else { return callback(null, true, client); } }); } }); } _clone(map) { var cloned, n, v; if (map == null) { return null; } else { cloned = {}; for (n in map) { v = map[n]; cloned[n] = v; } return cloned; } } }; SQLClientPool.prototype.MESSAGES = { POOL_NOT_OPEN: "The pool is not open; please call 'open' before invoking this method.", TOO_MANY_RETURNED: "More clients have been returned to the pool than were active. A client may have been returned twice.", EXHAUSTED: "The maxiumum number of clients are already active; cannot obtain a new client.", MAX_WAIT: "The maxiumum number of clients are already active and the maximum wait time has been exceeded; cannot obtain a new client.", INVALID: "Unable to create a valid client.", INTERNAL_ERROR: "Internal error.", INVALID_ARGUMENT: "Invalid argument.", NULL_RETURNED: "A null object was returned.", CLOSED_WITH_ACTIVE: "The pool was closed, but some clients remain active (were never returned)." }; SQLClientPool.prototype.DEFAULT_WAIT_INTERVAL = 50; SQLClientPool.prototype.DEFAULT_RETRY_INTERVAL = 50; SQLClientPool.prototype.pool = []; SQLClientPool.prototype.pool_options = {}; SQLClientPool.prototype.pool_is_open = false; SQLClientPool.prototype.borrowed = 0; SQLClientPool.prototype.returned = 0; SQLClientPool.prototype.active = 0; SQLClientPool.prototype.eviction_runner = null; return SQLClientPool; }).call(this); Transaction = class Transaction { constructor(pool_or_client) { this.begin = this.begin.bind(this); this._end = this._end.bind(this); this.rollback = this.rollback.bind(this); this.commit = this.commit.bind(this); this.rollback_and_end = this.rollback_and_end.bind(this); this.commit_and_end = this.commit_and_end.bind(this); this._borrow = this._borrow.bind(this); this.execute = this.execute.bind(this); if ((pool_or_client != null) && pool_or_client instanceof SQLClientPool) { this._pool = pool_or_client; } else if ((pool_or_client != null) && pool_or_client instanceof SQLClient) { this._client = pool_or_client; } else { throw new Error(`Expected SQLClientPool or SQLClient instance, found ${pool_or_client}`); } } begin(callback) { return this.execute("BEGIN", [], (...r) => { this._began = true; return callback(...r); }); } _end(command, options, callback) { var maybe_return; if (typeof options === "function" && (callback == null)) { [callback, options] = [options, callback]; } maybe_return = (cb) => { var ref; this._ended = true; if ((this._pool != null) && (this._client != null)) { return this._pool.return(this._client, () => { this._pool = null; this._client = null; return typeof cb === "function" ? cb() : void 0; }); } else if ((this._client != null) && ((ref = this._client) != null ? ref._auto_disconnect_on_transaction_end : void 0)) { this._client.disconnect(options, cb); return this._client = null; } else { return typeof cb === "function" ? cb() : void 0; } }; if (this._began) { return this.execute(command, [], (err) => { return maybe_return(() => { return callback(err); }); }); } else { return maybe_return(() => { return callback(); }); } } rollback(options, callback) { if (typeof options === "function" && (callback == null)) { [callback, options] = [options, callback]; } if (this._ended) { return callback(new Error("This transaction has already been closed.")); } else { return this._end("ROLLBACK", options, callback); } } commit(options, callback) { if (typeof options === "function" && (callback == null)) { [callback, options] = [options, callback]; } if (this._ended) { return callback(new Error("This transaction has already been closed.")); } else { return this._end("COMMIT", options, callback); } } rollback_and_end(callback) { return this.rollback({ end_pg_pool: true }, callback); } commit_and_end(callback) { return this.commit({ end_pg_pool: true }, callback); } _borrow(callback) { if (this._client != null) { if (this._client.connected_at == null) { return this._client.connect((err) => { this._client._auto_disconnect_on_transaction_end = true; return callback(err, this._client); }); } else { return callback(null, this._client); } } else if (this._pool == null) { return callback(new Error("No SQLClient or SQLClientPool from which to obtain a connection. This transaction may have already been closed.")); } else { return this._pool.borrow((err, client) => { if (err != null) { return callback(err); } else { this._client = client; return callback(null, client); } }); } } execute(sql, bindvars, callback) { if (typeof bindvars === 'function' && (callback == null)) { [callback, bindvars] = [bindvars, callback]; } if (this._ended) { return callback(new Error("This transaction has already been closed.")); } else { return this._borrow((err, client) => { if (err != null) { return callback(err); } else { return client.execute(sql, bindvars, callback); } }); } } }; exports.SQLClientPool = SQLClientPool; exports.Transaction = Transaction; }).call(this);