@evs-chris/ts-pg-dao
Advanced tools
Comparing version
{ | ||
"name": "@evs-chris/ts-pg-dao", | ||
"version": "0.12.0", | ||
"version": "0.13.0", | ||
"main": "runtime/main.js", | ||
@@ -5,0 +5,0 @@ "typings": "runtime/main.d.ts", |
import * as pg from 'pg'; | ||
declare const cfgprop = "ts-pg-dao"; | ||
/** | ||
@@ -21,3 +22,3 @@ * A thin wrapper on top of pg.Client that provides basic transaction support, | ||
* */ | ||
rollback(savepoint?: SavePoint): Promise<void>; | ||
rollback(savepoint?: SavePoint, err?: Error): Promise<void>; | ||
/** Complete the current transaction for this connection. */ | ||
@@ -38,5 +39,3 @@ commit(): Promise<void>; | ||
*/ | ||
sql<T = any>(string: TemplateStringsArray, ...parts: any[]): Promise<pg.QueryResult & { | ||
rows: T[]; | ||
}>; | ||
sql<T = any>(string: TemplateStringsArray, ...parts: any[]): Promise<pg.QueryResult<T>>; | ||
/** | ||
@@ -52,3 +51,3 @@ * Wrap a string such that it can be included directly in a tagged `sql` template | ||
* */ | ||
onCommit(run: () => void | Promise<void>): Promise<void>; | ||
onCommit(run: ConnectionCallback): void; | ||
/** | ||
@@ -59,9 +58,39 @@ * Execute the given callback immediately after the current transaction | ||
*/ | ||
onRollback(run: () => void | Promise<void>): Promise<void>; | ||
readonly ['ts-pg-dao']: { | ||
attachQueryToError?: boolean; | ||
attachParametersToError?: boolean; | ||
}; | ||
onRollback(run: RollbackCallback): void; | ||
/** | ||
* Execute the given callback immediately after any query returns a result. | ||
* Result callbacks are automatically removed when a connection is released | ||
* back to a pool. | ||
*/ | ||
onResult(run: ResultCallback): HookHandle; | ||
/** | ||
* Execute the given callback immediately after the connection is done. For | ||
* pooled connections, this is after release, and it is after end for | ||
* non-pooled connections. | ||
* End callbacks are automatically removed when a connection is released | ||
* back to a pool. | ||
*/ | ||
onEnd(run: EndCallback): HookHandle; | ||
/** | ||
* Enhanced connection settings and information | ||
*/ | ||
readonly [cfgprop]: EnhanceInfo; | ||
} | ||
export interface EnhanceInfo { | ||
clientStack?: boolean; | ||
attachQueryToError?: boolean; | ||
attachParametersToError?: boolean; | ||
readonly id: number; | ||
} | ||
/** | ||
* A handle to allow cancelling a listener hook. | ||
*/ | ||
export interface HookHandle { | ||
cancel(): void; | ||
} | ||
export declare type ConnectionCallback = (con: Connection, err?: Error) => void; | ||
export declare type RollbackCallback = (con: Connection, err?: Error, savepoint?: SavePoint) => void; | ||
export declare type ResultCallback = (con: Connection, query: string, params: any[], ok: boolean, result: Error | pg.QueryResult, time: number) => void; | ||
export declare type EndCallback = (con: Connection, err?: Error, end?: boolean) => void; | ||
/** | ||
* A savepoint genereated from a transaction. | ||
@@ -73,2 +102,5 @@ */ | ||
export { QueryResult } from 'pg'; | ||
export declare function onEnhance(run: ConnectionCallback): HookHandle; | ||
export declare function onEnd(run: EndCallback): HookHandle; | ||
export declare function onResult(run: ResultCallback): HookHandle; | ||
/** | ||
@@ -75,0 +107,0 @@ * Enhances the given pg.Client with a few convenience methods for managing |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const pg = require("pg"); | ||
const cfgprop = 'ts-pg-dao'; | ||
const __onenhance = []; | ||
function onEnhance(run) { | ||
__onenhance.push(run); | ||
return { | ||
cancel() { | ||
const idx = __onenhance.indexOf(run); | ||
if (!idx) | ||
__onenhance.splice(idx, 1); | ||
} | ||
}; | ||
} | ||
exports.onEnhance = onEnhance; | ||
const __onend = []; | ||
function onEnd(run) { | ||
__onend.push(run); | ||
return { | ||
cancel() { | ||
const idx = __onend.indexOf(run); | ||
if (!idx) | ||
__onend.splice(idx, 1); | ||
} | ||
}; | ||
} | ||
exports.onEnd = onEnd; | ||
const __onresult = []; | ||
function onResult(run) { | ||
__onresult.push(run); | ||
return { | ||
cancel() { | ||
const idx = __onresult.indexOf(run); | ||
if (!idx) | ||
__onresult.splice(idx, 1); | ||
} | ||
}; | ||
} | ||
exports.onResult = onResult; | ||
let count = 0; | ||
/** | ||
@@ -10,22 +48,24 @@ * Enhances the given pg.Client with a few convenience methods for managing | ||
function enhance(client) { | ||
if ('ts-pg-dao' in client) | ||
if (cfgprop in client) | ||
return client; | ||
if (!('connect' in client)) | ||
client = new pg.Client(client); | ||
const res = client; | ||
const config = res['ts-pg-dao'] = { attachQueryToError: true, clientStack: true }; | ||
res.inTransaction = false; | ||
res.begin = begin; | ||
res.savepoint = savepoint; | ||
res.rollback = rollback; | ||
res.commit = commit; | ||
res.transact = transact; | ||
res.sql = sql; | ||
res.lit = lit; | ||
res.onCommit = onCommit; | ||
res.onRollback = onRollback; | ||
const query = res.query; | ||
let lastq; | ||
let lastp; | ||
res.query = function (...args) { | ||
const db = client; | ||
const config = db[cfgprop] = { id: count++, attachQueryToError: true, clientStack: true }; | ||
db.inTransaction = false; | ||
db.begin = begin; | ||
db.savepoint = savepoint; | ||
db.rollback = rollback; | ||
db.commit = commit; | ||
db.transact = transact; | ||
db.sql = sql; | ||
db.lit = lit; | ||
db.onCommit = onCommit; | ||
db.onRollback = onRollback; | ||
db.onResult = _onResult; | ||
db.onEnd = _onEnd; | ||
const query = db.query; | ||
db.query = function (...args) { | ||
let lastq; | ||
let lastp; | ||
if (typeof args[0] === 'string') { | ||
@@ -42,37 +82,94 @@ lastq = args[0]; | ||
const stack = config.clientStack ? new Error() : null; | ||
if (typeof args[args.length - 1] === 'function') { | ||
query.apply(res, args.slice(0, -1).concat([(err, res) => { | ||
if (err && lastq) { | ||
const start = Date.now(); | ||
const cb = typeof args[args.length - 1] === 'function' ? args.pop() : null; | ||
const res = new Promise((ok, fail) => { | ||
query.apply(db, args.concat([(err, res) => { | ||
if (err && lastq && typeof err === 'object') { | ||
if (config.attachQueryToError) | ||
err.query = lastq; | ||
if (config.attachParametersToError) | ||
err.parameters = lastp; | ||
err.parameters = lastq; | ||
} | ||
if (err && stack) | ||
if (err && stack && typeof err === 'object') | ||
err.stack = `${stack.stack}\n--- Driver exception:\n${err.stack}`; | ||
lastq = lastp = null; | ||
args[args.length - 1](err, res); | ||
if (err) { | ||
if (typeof err === 'object' && (config.__onresult || __onresult.length)) | ||
setTimeout(fireResult, 0, db, lastq || 'unknown', lastp || [], false, err, Date.now() - start); | ||
fail(err); | ||
} | ||
else { | ||
if (config.__onresult || __onresult.length) | ||
setTimeout(fireResult, 0, db, lastq || 'unknown', lastp || [], true, res, Date.now() - start); | ||
ok(res); | ||
} | ||
if (cb) | ||
cb(err, res); | ||
}])); | ||
} | ||
else { | ||
return new Promise((ok, fail) => { | ||
query.apply(res, args.concat([(err, res) => { | ||
if (err && lastq) { | ||
if (config.attachQueryToError) | ||
err.query = lastq; | ||
if (config.attachParametersToError) | ||
err.parameters = lastq; | ||
}); | ||
if (!cb) | ||
return res; | ||
}; | ||
const end = db.end; | ||
if (end) { | ||
db.end = function (cb) { | ||
const stack = config.clientStack ? new Error() : null; | ||
const res = new Promise((ok, fail) => { | ||
end.call(db, (err) => { | ||
if (err && stack && typeof err === 'object') | ||
err.stack = `${stack.stack}\n--- Driver exception:\n${err.stack}`; | ||
if (err) | ||
fail(err); | ||
else | ||
ok(); | ||
if (cb) | ||
cb(err); | ||
if (config.__onend) | ||
for (const cb of config.__onend) | ||
try { | ||
cb(db, err, true); | ||
} | ||
catch (_a) { } | ||
if (__onend.length) | ||
for (const cb of __onend) | ||
try { | ||
cb(db, err, true); | ||
} | ||
catch (_b) { } | ||
config.__onend = config.__onresult = config.__oncommit = config.__onrollback = undefined; | ||
}); | ||
}); | ||
if (!cb) | ||
return res; | ||
}; | ||
} | ||
const release = db.release; | ||
if (release) { | ||
db.release = function (err) { | ||
release.call(db, err); | ||
if (config.__onend || __onend.length) { | ||
const stack = config.clientStack ? new Error() : null; | ||
if (err && stack && typeof err === 'object' && 'stack' in err) | ||
stack.stack = `${err.stack}\n--- Release called from:\n${stack.stack}`; | ||
if (config.__onend) | ||
for (const cb of config.__onend) | ||
try { | ||
cb(db, typeof err === 'boolean' ? (err ? stack : undefined) : err); | ||
} | ||
if (err && stack) | ||
err.stack = `${stack.stack}\n--- Driver exception:\n${err.stack}`; | ||
lastq = lastp = null; | ||
if (err) | ||
fail(err); | ||
else | ||
ok(res); | ||
}])); | ||
}); | ||
} | ||
}; | ||
return res; | ||
catch (_a) { } | ||
for (const cb of __onend) | ||
try { | ||
cb(db); | ||
} | ||
catch (_b) { } | ||
config.__onend = config.__onresult = config.__oncommit = config.__onrollback = undefined; | ||
} | ||
}; | ||
} | ||
if (__onenhance) | ||
for (const cb of __onenhance) | ||
try { | ||
cb(db); | ||
} | ||
catch (_a) { } | ||
return db; | ||
} | ||
@@ -94,26 +191,32 @@ exports.enhance = enhance; | ||
} | ||
async function rollback(point) { | ||
async function rollback(point, err) { | ||
if (!this.inTransaction) | ||
throw new Error(`Can't rollback when not in transaction`); | ||
const t = this[cfgprop]; | ||
if (point) { | ||
try { | ||
await this.query(`rollback to ${point.point}`); | ||
// process rollback callbacks | ||
if (Array.isArray(t.__onrollback)) | ||
for (const r of t.__onrollback) | ||
try { | ||
r(this, err, point); | ||
} | ||
catch (_a) { } | ||
return; | ||
} | ||
catch (_a) { } | ||
catch (e) { | ||
await this.rollback(undefined, e); | ||
} | ||
} | ||
await this.query('rollback'); | ||
const t = this; | ||
// process rollback callbacks | ||
if (Array.isArray(t.__rruns)) { | ||
for (const r of t.__rruns) | ||
if (Array.isArray(t.__onrollback)) | ||
for (const r of t.__onrollback) | ||
try { | ||
await r(); | ||
r(this, err); | ||
} | ||
catch (_b) { } | ||
t.__rruns = []; | ||
} | ||
// discard commit callbacks | ||
if (Array.isArray(t.__cruns)) | ||
t.__cruns = []; | ||
// discard callbacks | ||
t.__oncommit = t.__onrollback = undefined; | ||
this.inTransaction = false; | ||
@@ -127,13 +230,10 @@ } | ||
// process commit callbacks | ||
if (Array.isArray(t.__cruns)) { | ||
for (const r of t.__cruns) | ||
if (Array.isArray(t.__oncommit)) | ||
for (const r of t.__oncommit) | ||
try { | ||
await r(); | ||
r(); | ||
} | ||
catch (_a) { } | ||
t.__cruns = []; | ||
} | ||
// discard rollback callbacks | ||
if (Array.isArray(t.__rruns)) | ||
t.__rruns = []; | ||
// discard callbacks | ||
t.__oncommit = t.__onrollback = undefined; | ||
this.inTransaction = false; | ||
@@ -153,3 +253,3 @@ } | ||
if (init) | ||
await this.rollback(); | ||
await this.rollback(undefined, ex); | ||
throw ex; | ||
@@ -172,18 +272,63 @@ } | ||
} | ||
async function onCommit(run) { | ||
function onCommit(run) { | ||
if (!this.inTransaction) | ||
return run(); | ||
return run(this); | ||
else { | ||
const t = this; | ||
(t.__cruns || (t.__cruns = [])).push(run); | ||
const t = this[cfgprop]; | ||
(t.__oncommit || (t.__oncommit = [])).push(run); | ||
} | ||
} | ||
async function onRollback(run) { | ||
function onRollback(run) { | ||
if (!this.inTransaction) | ||
return; | ||
else { | ||
const t = this; | ||
(t.__rruns || (t.__rruns = [])).push(run); | ||
const t = this[cfgprop]; | ||
(t.__onrollback || (t.__onrollback = [])).push(run); | ||
} | ||
} | ||
function _onResult(run) { | ||
const t = this[cfgprop]; | ||
(t.__onresult || (t.__onresult = [])).push(run); | ||
return { | ||
cancel() { | ||
if (!t.__onresult) | ||
return; | ||
const idx = t.__onresult.indexOf(run); | ||
if (!idx) | ||
t.__onresult.splice(idx, 1); | ||
if (t.__onresult.length === 0) | ||
t.__onresult = undefined; | ||
} | ||
}; | ||
} | ||
function fireResult(con, query, params, ok, result, time) { | ||
const t = con[cfgprop]; | ||
if (t && t.__onresult) | ||
for (const c of t.__onresult) | ||
try { | ||
c(con, query, params, ok, result, time); | ||
} | ||
catch (_a) { } | ||
if (__onresult.length) | ||
for (const c of __onresult) | ||
try { | ||
c(con, query, params, ok, result, time); | ||
} | ||
catch (_b) { } | ||
} | ||
function _onEnd(run) { | ||
const t = this[cfgprop]; | ||
(t.__onend || (t.__onend = [])).push(run); | ||
return { | ||
cancel() { | ||
if (!t.__onend) | ||
return; | ||
const idx = t.__onend.indexOf(run); | ||
if (!idx) | ||
t.__onend.splice(idx, 1); | ||
if (t.__onend.length === 0) | ||
t.__onend = undefined; | ||
} | ||
}; | ||
} | ||
class SQL { | ||
@@ -190,0 +335,0 @@ constructor(sql) { |
@@ -6,2 +6,3 @@ "use strict"; | ||
const main_1 = require("./main"); | ||
const index_1 = require("./index"); | ||
function createColumn(c) { | ||
@@ -32,4 +33,2 @@ let sql = `"${c.name}" `; | ||
const qs = res.statements; | ||
const client = new pg.Client(connect); | ||
await client.connect(); | ||
const cache = JSON.parse(await fs.readFile(connect.schemaCacheFile, { encoding: 'utf8' })); | ||
@@ -47,2 +46,4 @@ const schema = { tables: [] }; | ||
log(`Patching ${name}...`); | ||
const client = index_1.enhance(new pg.Client(connect)); | ||
await client.connect(); | ||
try { | ||
@@ -81,3 +82,18 @@ const allCols = (await client.query(main_1.columnQuery)).rows; | ||
if (c.type !== col.type || c.length !== col.length || JSON.stringify(c.precision || []) !== JSON.stringify(col.precision || [])) { | ||
const q = `alter table "${ct.name}" alter column "${col.name}" type ${colType(col)};`; | ||
const typ = colType(col); | ||
let q = `alter table "${ct.name}" alter column "${col.name}" type ${typ};`; | ||
if (col.type === c.type) { | ||
if (col.length !== c.length) { | ||
if (col.length >= c.length) | ||
q += ` -- safe lengthen`; | ||
else | ||
q += ` -- warning, unsafe shorten (${c.length} to ${col.length})`; | ||
} | ||
else if (JSON.stringify(col.precision) !== JSON.stringify(c.precision)) { | ||
if (col.precision[0] >= c.precision[0] && col.precision[1] >= c.precision[1]) | ||
q += ` -- safe add precision`; | ||
else | ||
q += ` -- warning, unsafe remove precision (${c.precision} to ${col.precision})`; | ||
} | ||
} | ||
qs.push(q); | ||
@@ -92,3 +108,9 @@ t.push(q); | ||
if (c.nullable !== col.nullable) { | ||
const q = `alter table "${ct.schema}"."${ct.name}" alter column "${col.name}" ${col.nullable ? 'drop' : 'set'} not null;`; | ||
let q; | ||
if (!col.nullable && col.default) { | ||
q = `update "${ct.schema}"."${ct.name}" set "${col.name}" = ${col.default} where "${col.name}" is null; -- warning, possible data change`; | ||
qs.push(q); | ||
t.push(q); | ||
} | ||
q = `alter table "${ct.schema}"."${ct.name}" alter column "${col.name}" ${col.nullable ? 'drop' : 'set'} not null;`; | ||
qs.push(q); | ||
@@ -95,0 +117,0 @@ t.push(q); |
126708
6.52%2686
8%