Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

wyseman

Package Overview
Dependencies
Maintainers
1
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

wyseman - npm Package Compare versions

Comparing version 1.0.18 to 1.0.19

lib/client.js

1

lib/dbclient.js

@@ -9,2 +9,3 @@ //Low level connection to PostgreSQL database

//X- Split low level code out from UI module
//- First time you run a source build on a db build from canned schema, it fails once, then fixes itself
//- If schema initialization SQL fails, and we created the DB, should we then delete it?

@@ -11,0 +12,0 @@ //- If Postgres restarted, we lose our connection. Is there a way to recover/reconnect?

3

lib/index.js

@@ -6,3 +6,4 @@ //Copyright WyattERP.org; See license in root of this package

Wyseman: require('./wyseman.js'),
dbClient: require('./dbclient.js')
dbClient: require('./dbclient.js'),
Client: require('./client.js')
}

@@ -8,13 +8,7 @@ //Manage the connection between a User Interface and the backend database

//X- Allow separate instance of all variables per ws connection
//- First time you run a source build on a db build from canned schema, it fails once, then fixes itself
//- Should any of the %s's in pg-format really be %L's?
//- Allow to specify 'returning' fields to insert, update overriding default *
//- Handle warnings from the database, in addition to errors? (like if row count = 0)
//- Query to return meta-data for table joins
//- How to handle module-specific control functions?
//- Return query promise in case no callback given?
//- Restructure code as explained in: https://github.com/websockets/ws/issues/377#issuecomment-462152231
//-
//- Extend module to also listen for clients on regular TCP socket
//- ? Restructure code as explained in: https://github.com/websockets/ws/issues/377#issuecomment-462152231
const DbClient = require('./dbclient.js') //PostgreSQL
const Handler = require('./handler.js') //JSON..SQL
const Ws = require('ws') //Web sockets

@@ -24,7 +18,6 @@ const Https = require('https')

const Url = require('url')
const Format = require('pg-format') //String formatting/escaping
const Base64 = require('base64-js')
const Crypto = require('crypto')
const Net = require('net')
const Opers = ['=', '!=', '<', '<=', '>', '>=', '~', 'diff', 'in', 'isnull', 'notnull', 'true']
const PemHeader = "-----BEGIN PUBLIC KEY-----\n"

@@ -38,392 +31,158 @@ const PemFooter = "\n-----END PUBLIC KEY-----"

module.exports = class Wyseman {
constructor(dbConf, wsConf, adminConf) {
let { port, credentials, actions, dispatch, expApp} = wsConf
, log = this.log = dbConf.log || wsConf.log || adminConf.log || require('./log')
, context = {db:null, control:null, actions, dispatch, expApp, log}
, server = credentials ? Https.createServer(credentials) : Http.createServer()
, adminDB = new DbClient(adminConf) //Admin access to the DB
, validateToken = (user, token, pub, listen, payload, cb) => { //Validate user with one-time login token
log.debug("Request to validate:", user, "tok:", token)
adminDB.query('select base.validate_token($1,$2,$3) as valid', [user, token, pub], (err, res)=>{
if (err) log.error("Error validating user:", user, token, err)
let valid = (!err && res && res.rows && res.rows.length >= 1) ? res.rows[0].valid : false
if (valid) Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options
log.debug(" valid result:", valid)
cb(valid)
})
}
, validateSignature = (user, sign, message, listen, payload, cb) => { //Validate a user with an existing key
log.trace("Validate:", user, sign, message)
adminDB.query('select conn_pub from base.ent_v where username = $1', [user], (err, res)=>{
if (err) log.error("Error getting user connection key:", user, err)
let pubKey = (!err && res && res.rows && res.rows.length >= 1) ? res.rows[0].conn_pub : null
, valid = false //Assume failure
log.trace(" public key:", pubKey, res ? res.rows : null)
if (pubKey && sign) { //We have the public key from the DB and the signed hash from the client
let rawKey = Buffer.from(pubKey, 'hex') //Hex-to-binary
, rawSig = Buffer.from(sign, 'hex')
, key = PemHeader + Base64.fromByteArray(rawKey) + PemFooter //Raw-to-PEM
, verify = Crypto.createVerify('SHA256') //Make a verifier
log.trace(" user public:", user, key)
verify.update(message) //Give it our message
valid = verify.verify(Object.assign({key}, VerifyTpt), rawSig) //And check it
if (valid) Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options
}
log.debug(" valid:", valid)
cb(valid)
})
}
, wss = new Ws.Server({ //Initiate a new websocket connection
constructor(dbConf, sockConf, adminConf) {
let { websock, sock, actions, dispatch, expApp } = sockConf
, { port, credentials, delta } = websock
, server = credentials ? Https.createServer(credentials) : Http.createServer() //websocket rides on this server
, wsport = port
this.log = dbConf.log || sockConf.log || adminConf.log || require('./log') //Try to find a logger
this.adminDB = new DbClient(adminConf) //Open Admin connection to the DB
this.maxDelta = delta
//For future noise-protocol connection:
// if (sock)
// Net.createServer(sock => this.sockConnect(sock)).listen(sock)
if (wsport) {
let wss = new Ws.Server({ //Initiate a new websocket server
server,
clientTracking: true,
verifyClient: function(info, cb) {
log.debug("verifyClient:", info)
let { origin, req, secure } = info
, query = Url.parse(req.url, true).query
, { user, db, sign, date, token, pub } = query
, listen = db ? JSON.parse(Buffer.from(db,'hex').toString()) : null
, payload = req.WysemanPayload = {} //Custom Wyseman data to pass back to connection
log.trace("Checking client:", origin, "cb:", !!cb, "q:", query, "s:", secure, "IP:", req.connection.remoteAddress, "listen:", listen, typeof(listen))
if (user && token && pub)
validateToken(user, token, pub, listen, payload, (valid)=>{
cb(valid, 403, 'Invalid Login') //Tell websocket whether or not to connect
})
else if (user && sign && date) {
let message = JSON.stringify({ip: req.connection.remoteAddress, cookie: req.headers.cookie, userAgent: req.headers['user-agent'], date})
, now = new Date()
, msgDate = new Date(date)
log.debug("Check dates:", now, msgDate, wsConf, "Time delta:", now - msgDate)
if (wsConf.delta && Math.abs(now - msgDate) > wsConf.delta)
cb(false, 400, 'Invalid Date Stamp')
else validateSignature(user, sign, message, listen, payload, (valid)=>{
cb(valid, 403, 'Invalid Login') //Tell websocket whether or not to connect
})
} else if (user && !secure) {
Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options
cb(true) //On an insecure/debug web connection
} else
cb(false, 401, 'No login credentials') //tell websocket not to connect
},
verifyClient: (info, cb) => {
try {this.verifyClient(info, cb)} catch(e) {
this.log.error("Verifying client:", e)
}
}
})
log.info("Wyseman listening:", port)
if (port) server.listen(port)
this.log.info("Wyseman listening on websocket:", wsport)
wss.on('connection', (ws, req) => { //When connection from view client is open
let payload = req.WysemanPayload
, config = Object.assign({}, dbConf, payload) //user,listen passed from verifyClient
, ctx = Object.assign({}, context) //Private copy for this instance
log.verbose("WS Connected; User:", config.user, config)
if (!config.user) return //Shouldn't be able to get here without a username
ctx.db = new DbClient(config, (channel, message, mine) => {
let data = JSON.parse(message)
this.log.trace("Async notify from DB:", channel, data, mine)
ws.send(JSON.stringify({action: 'notify', channel, data}), err => {
if (err) this.log.error(err)
if (!wss) return
server.listen(wsport)
wss.on('connection', (ws, req) => { //When connection from view client is open
let payload = req.WysemanPayload
, config = Object.assign({}, dbConf, payload) //user,listen was passed to us from verifyClient
this.log.verbose("WS Connected; User:", config.user, config)
if (!config.user) return //Shouldn't be able to get here without a username
let db = new DbClient(config, (channel, message, mine) => {
let data = JSON.parse(message)
this.log.trace("Async notify from DB:", channel, data, mine)
ws.send(JSON.stringify({action: 'notify', channel, data}), err => {
if (err) this.log.error(err)
})
})
, handler = new Handler({db, control:null, actions, dispatch, expApp, log:this.log})
this.log.trace("Wyseman connection conf:", "Client WS port:", wsport)
ws.on('close', (code, reason) => {
this.log.debug("Wyseman socket connection closed:", code, reason)
db.disconnect() //Free up this DB connection
})
})
this.log.trace("Wyseman connection conf:", "Client port:", port)
ws.on('close', (code, reason) => {
this.log.debug("Wyseman socket connection closed:", code, reason)
ctx.db.disconnect() //Free up this DB connection
})
ws.on('message', (imsg) => { //When message received from client
this.log.trace("Incoming Wyseman message:" + imsg + ";")
let packet = JSON.parse(imsg)
this.handler(packet, ctx, (omsg) => { //Handle/control an incoming packet
let jmsg = JSON.stringify(omsg)
ws.on('message', (imsg) => { //When message received from client
this.log.trace("Incoming Wyseman message:" + imsg + ";")
let packet = JSON.parse(imsg)
handler.handle(packet, (omsg) => { //Handle/control an incoming packet
let jmsg = JSON.stringify(omsg)
//this.log.trace('Sending back:', JSON.stringify(omsg, null, 2))
ws.send(jmsg, err => { //Send a reply back to the client
if (err) this.log.error(err)
ws.send(jmsg, err => { //Send a reply back to the client
if (err) this.log.error(err)
})
})
})
})
this.log.debug("Connected clients: ", wss.clients.size)
}) //wss.on connection
}) //wss.on connection
} //if (wsport)
} //constructor
// Log an error and generate an error object
// -------------------------------------------------------------------
error(message, err = 'unknown') {
let ret = { message }, prefix = '!wylib.data.'
this.log.trace("Query error " + ret.message + ": " + err)
if (typeof err == 'string') {
if (err && err.split('.').length == 1) ret.code = prefix + err
} else if (typeof err == 'object') {
if (err.constraint && err.constraint.match(/^!/)) {
ret.code = err.constraint
} else if (err.code) {
ret.code = prefix + err.code
} else {
ret.code = prefix + 'unknown'
}
if (err.message) ret.message += (': ' + err.message)
if (err.detail) ret.detail = err.detail
}
return ret
}
// Handle an incoming packet from the view client
// -------------------------------------------------------------------
handler(msg, ctx, sender) {
this.log.trace("Wyseman packet handler, msg:", JSON.stringify(msg))
let {id, view, action} = msg
if (!view) return
if (action == 'lang') {
action = 'tuple';
Object.assign(msg, {fields: ['title','help','columns','messages'], table: 'wm.table_lang', where: {obj: view, language: msg.language || 'en'}})
} else if (msg.action == 'meta') {
action = 'tuple'
Object.assign(msg, {fields: ['obj','pkey','cols','columns','styles','fkeys'], table: 'wm.table_meta', where: {obj: view}})
}
let {table, params, fields, where, order} = msg, argtypes
//this.log.debug(" From msg, table:", table, " view:", view, "order: ", order)
let [sch, tab] = (table || view).split('.') //Split into schema and table
if (!tab) {tab = sch; sch = 'public'} //Default to public if no schema specified
;([tab, argtypes] = tab.split(/[\(\)]/)) //In case table is specified as a function
this.log.trace(" tab:", tab, " argtypes:", argtypes)
table = Format.ident(sch) + '.' + Format.ident(tab)
if (argtypes) argtypes = argtypes.split(',')
let tuples = 1, result = {query: null, parms: [], error: null}
try { switch (action) {
case 'tuple':
this.buildSelect(result, {fields, table, argtypes, params, where}); break;
case 'select':
this.buildSelect(result, {fields, table, argtypes, params, where, order})
tuples = null; break;
case 'update':
this.buildUpdate(result, fields, table, where); break;
case 'insert':
this.buildInsert(result, fields, table); break;
case 'delete':
this.buildDelete(result, table, where)
tuples = 0; break;
default:
if (!ctx.control && ctx.dispatch)
ctx.control = new ctx.dispatch(ctx.expApp, ctx.db, ctx.actions, ctx.log) //Start a controller just in time
if (ctx.control && ctx.control.handle && ctx.control.handle(msg, sender)) return
result.error = this.error('unknown action: ' + action, 'badAction')
}} catch(e) {
result.error = this.error('parsing: ' + e.message)
this.log.error(e.message, e.stack)
}
if (result.error) {
sender({error: result.error, id, view, action})
return
}
let { query, parms } = result
if (!query) return //Ignore null queries (result of an error in query builder)
parms.forEach((p,x)=>{ //node-pg tries to convert JS arrays to PS arrays. We don't want that.
if (typeof p == 'object') parms[x] = JSON.stringify(p)
})
ctx.db.query(query, parms, (err, res) => { //Run the user's query
this.log.trace(" query:", query, "parms:", parms, "tuples:", tuples, "Err:", err)
if (err) {
msg.error = this.error("from database", err)
} else if (!res) {
msg.error = this.error("no result")
} else if (tuples && res.rowCount != tuples) {
msg.error = this.error("unexpected rows: " + res.rowCount + ' != ' + tuples, "badTuples")
}
if (res && res.rows) {
if (tuples == 1) msg.data = res.rows[0]
else if (tuples == null || tuples > 0) msg.data = res.rows
else if (tuples == 0) msg.data = null
}
if (sender) sender(msg)
})
}
//Validate a user who is presenting a one-time connection token
// -----------------------------------------------------------------------------
buildSelect(res, spec) {
let { fields, table, argtypes, params, where, order} = spec
this.log.trace("BuildSelect", fields, table, argtypes, params, where, order)
let wh = '', ord = ''
, whereText = this.buildWhere(where, res)
, ordText = this.buildOrder(order, res)
if (where && whereText) wh = ' where ' + whereText
if (order && ordText) ord = ' order by ' + ordText
if (params) { //If selecting from a function
let i = res.parms.length + 1, plist = []
params.forEach(param => { //Form parameter list
plist.push(Format("$%s%s", i++, argtypes && argtypes.length > 0 ? '::'+argtypes.shift() : ''))
res.parms.push(param)
})
table = table + "(" + plist.join(',') + ")" //And attach the parameter list to the end of the table to make the function call
}
if (fields && fields != "*") fields = Format.ident(fields)
res.query = Format('select %s%s%s%s;', fields ? fields + ' from ' : '', table, wh, ord)
this.log.trace("buildSelect:", res.query, "parms:", res.parms)
}
// -----------------------------------------------------------------------------
buildInsert(res, fields, table) {
let i = res.parms.length + 1 //Starting parameter number
let flist = []
let plist = []
Object.keys(fields).forEach(fld => {
if (fld === null || fld === undefined) {res.error = this.error("invalid null or undefined field", 'badFieldName'); return null}
flist.push(Format("%I", fld))
plist.push(Format("$%s", i++))
res.parms.push(fields[fld])
validateToken(user, token, pub, listen, payload, cb) {
this.log.debug("Request to validate:", user, "tok:", token)
this.adminDB.query('select base.validate_token($1,$2,$3) as valid', [user, token, pub], (err, res) => {
if (err) this.log.error("Error validating user:", user, token, err)
let valid = (!err && res && res.rows && res.rows.length >= 1) ? res.rows[0].valid : false
if (valid) Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options
this.log.debug(" valid result:", valid)
cb(valid)
})
if (flist.length <= 0) {
res.error = this.error("empty insert", 'badInsert'); return null
}
res.query = Format('insert into %s (%s) values (%s) returning *;', table, flist.join(', '), plist.join(', '))
this.log.trace("buildInsert:", res.query, "\n parms:", res.parms)
}
//Validate a user who has an existing key
// -----------------------------------------------------------------------------
buildUpdate(res, fields, table, where) {
let wh = this.buildWhere(where, res),
i = res.parms.length + 1 //Starting parameter number
if (!where || !wh || res.parms.length <= 0) {
res.error = this.error("empty where clause", 'badWhere'); return
}
let flist = []
Object.keys(fields).forEach(fld => {
if (fld === null || fld === undefined) {res.error = this.error("invalid null or undefined field", 'badFieldName'); return null}
flist.push(Format("%I = $%s", fld, i++))
res.parms.push(fields[fld])
})
if (flist.length <= 0) {
res.error = this.error("empty update", "badUpdate"); return null
}
res.query = Format('update %s set %s where %s returning *;', table, flist.join(', '), wh)
this.log.trace("buildUpdate:", res.query, "\n parms:", res.parms)
validateSignature(user, sign, message, listen, payload, cb) {
this.log.debug("Validate:", user, sign, message)
this.adminDB.query('select conn_pub from base.ent_v where username = $1', [user], (err, res) => { try {
if (err) this.log.error("Error getting user connection key:", user, err)
let pubKey = (!err && res && res.rows && res.rows.length >= 1) ? res.rows[0].conn_pub : null
, valid = false //Assume failure
this.log.trace(" public key:", pubKey, res ? res.rows : null)
if (pubKey && sign) { //We have the public key from the DB and the signed hash from the client
let rawKey = Buffer.from(pubKey, 'hex') //Hex-to-binary
, rawSig = Buffer.from(sign, 'hex')
, key = PemHeader + Base64.fromByteArray(rawKey) + PemFooter //Raw-to-PEM
, verify = Crypto.createVerify('SHA256') //Make a verifier
this.log.trace(" user public:", user, key)
verify.update(message) //Give it our message
valid = verify.verify(Object.assign({key}, VerifyTpt), rawSig) //And check it
if (valid) Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options
}
this.log.debug(" valid:", valid)
cb(valid)
} catch (e) {
this.log.debug("Validating signature:", e)
}})
}
//Validate a user who is trying to connect
// -----------------------------------------------------------------------------
buildDelete(res, table, where) {
let wh = this.buildWhere(where, res)
if (!where || !wh || res.parms.length <= 0) {
res.error = this.error("unbounded delete", 'badDelete'); return null
}
res.query = Format('delete from %s where %s;', table, wh)
//this.log.trace("buildDelete:", query, "\n parms:", res.parms)
}
verifyClient(info, cb) {
this.log.debug("verifyClient:", info.req.headers)
let { origin, req, secure } = info
, query = Url.parse(req.url, true).query
, { user, db, sign, date, token, pub } = query
, listen = db ? JSON.parse(Buffer.from(db,'hex').toString()) : null
, payload = req.WysemanPayload = {} //Custom Wyseman data to pass back to connection
// Create a where clause from a JSON structure
// -----------------------------------------------------------------------------
buildWhere(logic, res) {
//this.log.trace("Logic:", logic, typeof logic)
if (!logic) return null
let i = res.parms.length + 1 //Starting parameter number
if (Array.isArray(logic)) { //Compact form, each element is: field <oper> value, anded together
let clauses = [];
logic.forEach(log => {
let [ left, oper, right ] = Array.isArray(log) ? log : log.split(' ')
//this.log.trace("Left:", left, "Oper:", oper, "Right:", right)
if (left === null || left === undefined) {res.error = this.error("invalid null or undefined left hand side", 'badLeftSide'); return null}
if (!Opers.includes(oper)) {res.error = this.error("invalid operator: " + oper, 'badOperator'); return null}
if (oper == 'diff') oper = 'is distinct from'
if (oper == 'isnull' || oper == 'notnull')
clauses.push(Format("%I %s", left, oper))
else if (oper == 'true')
clauses.push(Format("%I", left))
else {
clauses.push(Format("%I %s $%s", left, oper, i++))
res.parms.push(right)
}
this.log.trace("Checking client:", origin, "cb:", !!cb, "q:", query, "s:", secure, "IP:", req.connection.remoteAddress, "pub:", pub)
if (user && token && pub) //User connecting with a token
this.validateToken(user, token, pub, listen, payload, (valid)=>{
cb(valid, 403, 'Invalid Login') //Tell websocket whether or not to connect
})
return clauses.join(' and ')
} else if ('items' in logic) { //Logic list syntax
if (!('and' in logic)) logic.and = true //Default to 'and' combiner
let clauses = []; logic.items.forEach((item) => {
let clause = this.buildWhere(item, res)
if (clause) clauses.push(clause)
else if (user && sign && date) { //User has a signature
let message = JSON.stringify({ip: req.connection.remoteAddress, cookie: req.headers.cookie, userAgent: req.headers['user-agent'], date})
, now = new Date()
, msgDate = new Date(date)
this.log.debug("Check dates:", now, msgDate, this.maxDelta, "Time delta:", now - msgDate)
if (this.maxDelta && Math.abs(now - msgDate) > this.maxDelta)
cb(false, 400, 'Invalid Date Stamp')
else this.validateSignature(user, sign, message, listen, payload, (valid)=>{
cb(valid, 403, 'Invalid Login') //Tell websocket whether or not to connect
})
return clauses.join(logic.and ? ' and ' : ' or ')
} else if ('left' in logic) { //Logic clause syntax
if (logic.oper == 'nop') return null
let oper = logic.oper || '='
if (logic.left === null || logic.left === undefined) {res.error = this.error("invalid null or undefined field", 'badFieldName'); return null}
if (!Opers.includes(oper)) {res.error = this.error("invalid operator: " + oper, 'badOperator'); return null}
if (logic.oper == 'diff') logic.oper = 'is distinct from'
if (logic.oper == 'isnull' || logic.oper == 'notnull') {
return Format("%s(%I %s)", logic.not ? 'not ' : '', logic.left, logic.oper)
} else if (user && !secure) {
Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options
cb(true) //On an insecure/debug web connection
} else if (logic.oper == 'true') { //LHS only
return Format("%s%I", logic.not ? 'not ' : '', logic.left)
} else if (logic.oper == 'in') { //LHS in array or set
if (logic.entry) { //RHS is explicit
let right = logic.entry
, notter = logic.not ? 'not ' : ''
if (Array.isArray(right)) //Map array sub-elements to strings
right = right.map(el=>(Array.isArray(el) ? el.join('~') : el))
if (typeof right == 'string') right = right.split(/[ ,]+/) //Comma separated list
if (Array.isArray(logic.left)) { //Matching multiple fields against array sub-elements
let left = logic.left.map(el=>(Format("%I", el))).join("||'~'||") //Map LHS to tilde joined string
return Format("%s(%s in (%L))", notter, left, right)
}
return Format("%s(%I in (%L))", notter, logic.left, right)
} else if (logic.right) { //RHS is a DB field
return Format("%s(%I = any(%I))", logic.not ? 'not ' : '', logic.left, logic.right)
} else {
res.error = this.error("invalid or null right side", 'badRight'); return null
}
} else {
res.parms.push(logic.entry || logic.right || '')
return Format("%s(%I %s $%s)", logic.not ? 'not ' : '', logic.left, oper, i++)
}
} else if (typeof logic == 'object') { //Compact form, each key is an = clause, anded together
let clauses = [];
Object.keys(logic).forEach((key) => {
clauses.push(Format("%I = $%s", key, i++))
res.parms.push(logic[key])
})
return clauses.join(' and ')
}
res.error = this.error("mangled logic: " + logic, 'badLogic')
return null
} else
cb(false, 401, 'No login credentials') //tell websocket not to connect
}
// Create a field ordering clause from a JSON structure
//Service an incoming connection on a TCP socket
// -----------------------------------------------------------------------------
buildOrder(order, res) {
this.log.trace("Order:", order, typeof order)
if (!order) return null
if (!Array.isArray(order)) order = [order]
let ords = [];
order.forEach(el => {
if (typeof el == 'object') {
let col = el.field || el.column || el.columnId
if (col === null || col === undefined) {res.error = this.error("invalid null or undefined field", 'badFieldName'); return null}
ords.push(Format("%I %s", el.field || el.column || el.columnId, (el.asc || el.sortAsc) ? 'asc' : 'desc'))
} else if (typeof el == 'number') {
ords.push(Format("%s", el))
} else if (typeof el == 'string') {
ords.push(Format("%I", el))
}
sockConnect(ss) {
this.log.debug("Wyseman socket connection")
ss.on('end', () => {
this.log.debug("Wyseman socket disconnect")
})
return ords.length > 0 ? ords.join(', ') : null
ss.on('error', err => {
this.log.debug("Wyseman socket error:", err)
})
ss.on('data', data => {
let msg = data.toString().trim()
this.log.debug("Wyseman socket data:", msg)
})
}
} //class Wyseman
// -----------------------------------------------------------------------------
// def tables_ref(tab, refme=false) //Return tables that are referenced (pointed to) by the specified table
// #Port_me //If refme true, return tables that reference the specified table
// end
// -----------------------------------------------------------------------------
// def columns_fk(tab, ftab) //Return the fk columns in a table and the pk columns they point to in a foreign table
// #Port_me
// end
{
"name": "wyseman",
"version": "1.0.18",
"version": "1.0.19",
"description": "PostgreSQL Schema Manager with Javascript, Ruby, TCL API",

@@ -33,10 +33,11 @@ "main": "lib/index.js",

"dependencies": {
"base64-js": "^1.3.1",
"pg": "^8.0.3",
"base64-js": "^1.5.1",
"node-fetch": "^2.6.1",
"pg": "^8.5.1",
"pg-format": "^1.0.4",
"ws": "^7.2.5"
"ws": "^7.4.3"
},
"devDependencies": {
"mocha": "^7.1.2"
"mocha": "^7.2.0"
}
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc