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

crudely-typed

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

crudely-typed - npm Package Compare versions

Comparing version 0.1.0 to 0.1.1

8

dist/src/index.d.ts

@@ -17,3 +17,3 @@ export interface QueryResult {

constructor(schema: SchemaT, tableName: keyof SchemaT);
select<Cols extends null | keyof TableT = null, WhereCols extends keyof TableT | SQLAny<keyof TableT & string> = never, Joins extends Record<string, keyof TableT> = never, IsSingular extends boolean = false>(opts?: {
select<Cols extends null | keyof TableT = null, WhereCols extends keyof TableT | SQLAny<keyof TableT & string> = never, Joins extends Record<string, keyof LooseKey3<SchemaT, Table, 'foreignKeys'>> = never, IsSingular extends boolean = false>(opts?: {
columns?: Cols[];

@@ -25,3 +25,3 @@ where?: WhereCols[];

}): (...args: [Extract<WhereCols, string>, WhereCols extends SQLAny<infer C> ? C : never] extends [never, never] ? [db: Queryable] : [db: Queryable, where: Resolve<Resolve<Pick<LooseKey3<SchemaT, Table, "$type">, Extract<WhereCols, string> & keyof LooseKey3<SchemaT, Table, "$type">>> & { [K in (WhereCols extends SQLAny<infer C> ? C : never) & string]: Set<LooseKey3<SchemaT, Table, "$type">[K & keyof LooseKey3<SchemaT, Table, "$type">]> | readonly LooseKey3<SchemaT, Table, "$type">[K & keyof LooseKey3<SchemaT, Table, "$type">][]; }>]) => [Cols, Joins] extends [null, never] ? Promise<Result<LooseKey3<SchemaT, Table, "$type">, IsSingular>> : [Cols] extends [null] ? Promise<Result<LooseKey3<SchemaT, Table, "$type"> & Resolve<Join<LooseKey<SchemaT, Table>, Joins>>, IsSingular>> : Promise<Result<Resolve<Resolve<Pick<LooseKey3<SchemaT, Table, "$type">, Cols & keyof LooseKey3<SchemaT, Table, "$type">>> & Join<LooseKey<SchemaT, Table>, Joins>>, IsSingular>>;
selectByPrimaryKey<Cols extends null | keyof TableT = null, Joins extends Record<string, keyof TableT> = never>(opts?: {
selectByPrimaryKey<Cols extends null | keyof TableT = null, Joins extends Record<string, keyof LooseKey3<SchemaT, Table, 'foreignKeys'>> = never>(opts?: {
columns?: Cols[];

@@ -32,6 +32,6 @@ join?: Joins;

disallowColumns?: DisallowedColumns[];
}): (db: Queryable, row: Omit<LooseKey3<SchemaT, Table, "$input">, DisallowedColumns & keyof LooseKey3<SchemaT, Table, "$input">>) => Promise<LooseKey3<SchemaT, Table, "$type">>;
}): (db: Queryable, row: [DisallowedColumns] extends [never] ? LooseKey3<SchemaT, Table, "$input"> : Omit<LooseKey3<SchemaT, Table, "$input">, DisallowedColumns & keyof LooseKey3<SchemaT, Table, "$input">>) => Promise<LooseKey3<SchemaT, Table, "$type">>;
insertMultiple<DisallowedColumns extends keyof TableT = never>(opts?: {
disallowColumns?: DisallowedColumns[];
}): (db: Queryable, rows: Omit<LooseKey3<SchemaT, Table, "$input">, DisallowedColumns & keyof LooseKey3<SchemaT, Table, "$input">>[]) => Promise<LooseKey3<SchemaT, Table, "$type">[]>;
}): (db: Queryable, rows: [DisallowedColumns] extends [never] ? readonly LooseKey3<SchemaT, Table, "$input">[] : readonly Omit<LooseKey3<SchemaT, Table, "$input">, DisallowedColumns & keyof LooseKey3<SchemaT, Table, "$input">>[]) => Promise<LooseKey3<SchemaT, Table, "$type">[]>;
update<SetCols extends null | keyof TableT = null, WhereCols extends keyof TableT | SQLAny<keyof TableT & string> = never, IsSingular extends boolean = false>(opts?: {

@@ -38,0 +38,0 @@ set?: SetCols[];

"use strict";
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-explicit-any */
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.any = exports.TableBuilder = exports.TypedSQL = void 0;
var TypedSQL = /** @class */ (function () {
function TypedSQL(schema) {
class TypedSQL {
constructor(schema) {
this.schema = schema;
}
TypedSQL.prototype.table = function (tableName) {
table(tableName) {
return new TableBuilder(this.schema, tableName);
};
return TypedSQL;
}());
}
}
exports.TypedSQL = TypedSQL;
var TableBuilder = /** @class */ (function () {
function TableBuilder(schema, tableName) {
class TableBuilder {
constructor(schema, tableName) {
this.schema = schema;
this.tableName = tableName;
}
TableBuilder.prototype.select = function (opts) {
select(opts) {
var _a, _b, _c, _d, _e;
var where = ((_a = opts === null || opts === void 0 ? void 0 : opts.where) !== null && _a !== void 0 ? _a : []);
var whereCols = where.filter(function (col) { return !isSQLAny(col); });
var whereAnyCols = where.filter(isSQLAny);
const where = ((_a = opts === null || opts === void 0 ? void 0 : opts.where) !== null && _a !== void 0 ? _a : []);
const whereCols = where.filter(col => !isSQLAny(col));
const whereAnyCols = where.filter(isSQLAny);
// TODO: eliminate Select<> entirely.
return new Select(this.schema[this.tableName], this.tableName, ((_b = opts === null || opts === void 0 ? void 0 : opts.columns) !== null && _b !== void 0 ? _b : null), whereCols, whereAnyCols, ((_c = opts === null || opts === void 0 ? void 0 : opts.orderBy) !== null && _c !== void 0 ? _c : null), ((_d = opts === null || opts === void 0 ? void 0 : opts.join) !== null && _d !== void 0 ? _d : null), (_e = opts === null || opts === void 0 ? void 0 : opts.limitOne) !== null && _e !== void 0 ? _e : false).build();
};
}
// TODO: disallow this method if primaryKey=null
TableBuilder.prototype.selectByPrimaryKey = function (opts) {
return this.select(__assign(__assign({}, opts), { where: [this.schema[this.tableName].primaryKey], limitOne: true }));
};
TableBuilder.prototype.insert = function (opts) {
selectByPrimaryKey(opts) {
return this.select(Object.assign(Object.assign({}, opts), { where: [this.schema[this.tableName].primaryKey], limitOne: true }));
}
insert(opts) {
var _a;
return new Insert(this.schema[this.tableName], this.tableName, ((_a = opts === null || opts === void 0 ? void 0 : opts.disallowColumns) !== null && _a !== void 0 ? _a : null)).build();
};
TableBuilder.prototype.insertMultiple = function (opts) {
}
insertMultiple(opts) {
var _a;
return new InsertMultiple(this.schema[this.tableName], this.tableName, ((_a = opts === null || opts === void 0 ? void 0 : opts.disallowColumns) !== null && _a !== void 0 ? _a : null)).build();
};
TableBuilder.prototype.update = function (opts) {
}
// TODO: this should include a disallowColumns for dynamic set
update(opts) {
var _a, _b, _c;
var where = ((_a = opts === null || opts === void 0 ? void 0 : opts.where) !== null && _a !== void 0 ? _a : []);
var whereCols = where.filter(function (col) { return !isSQLAny(col); });
var whereAnyCols = where.filter(isSQLAny);
const where = ((_a = opts === null || opts === void 0 ? void 0 : opts.where) !== null && _a !== void 0 ? _a : []);
const whereCols = where.filter(col => !isSQLAny(col));
const whereAnyCols = where.filter(isSQLAny);
return new Update(this.tableName, whereCols, whereAnyCols, ((_b = opts === null || opts === void 0 ? void 0 : opts.set) !== null && _b !== void 0 ? _b : null), ((_c = opts === null || opts === void 0 ? void 0 : opts.limitOne) !== null && _c !== void 0 ? _c : false)).build();
};
TableBuilder.prototype.updateByPrimaryKey = function (opts) {
return this.update(__assign(__assign({}, opts), { where: [this.schema[this.tableName].primaryKey], limitOne: true }));
};
TableBuilder.prototype.delete = function (opts) {
}
updateByPrimaryKey(opts) {
return this.update(Object.assign(Object.assign({}, opts), { where: [this.schema[this.tableName].primaryKey], limitOne: true }));
}
delete(opts) {
var _a, _b;
var where = ((_a = opts === null || opts === void 0 ? void 0 : opts.where) !== null && _a !== void 0 ? _a : []);
var whereCols = where.filter(function (col) { return !isSQLAny(col); });
var whereAnyCols = where.filter(isSQLAny);
const where = ((_a = opts === null || opts === void 0 ? void 0 : opts.where) !== null && _a !== void 0 ? _a : []);
const whereCols = where.filter(col => !isSQLAny(col));
const whereAnyCols = where.filter(isSQLAny);
return new Delete(this.tableName, whereCols, whereAnyCols, ((_b = opts === null || opts === void 0 ? void 0 : opts.limitOne) !== null && _b !== void 0 ? _b : false)).build();
};
TableBuilder.prototype.deleteByPrimaryKey = function () {
}
deleteByPrimaryKey() {
return this.delete({

@@ -110,5 +61,4 @@ where: [this.schema[this.tableName].primaryKey],

});
};
return TableBuilder;
}());
}
}
exports.TableBuilder = TableBuilder;

@@ -122,4 +72,4 @@ function any(column) {

}
var Select = /** @class */ (function () {
function Select(tableSchema, table, cols, whereCols, whereAnyCols, order, joins, isSingular) {
class Select {
constructor(tableSchema, table, cols, whereCols, whereAnyCols, order, joins, isSingular) {
this.tableSchema = tableSchema;

@@ -134,92 +84,71 @@ this.table = table;

}
Select.prototype.build = function () {
var _this = this;
var what = ['*'];
build() {
let what = ['*'];
if (this.cols) {
what = this.cols;
}
var query = "SELECT ".concat(what.join(', '), " FROM ").concat(this.table);
var joins = [];
let query = `SELECT ${what.join(', ')} FROM ${this.table}`;
let joins = [];
if (this.joins) {
var joinNameCols = Object.entries(this.joins);
query = "SELECT ".concat(what.map(function (c) { return "t1.".concat(c); }).join(', '), ", ");
const joinNameCols = Object.entries(this.joins);
query = `SELECT ${what.map(c => `t1.${c}`).join(', ')}, `;
query += joinNameCols
.map(function (_a, i) {
var joinName = _a[0];
return "to_jsonb(t".concat(i + 2, ".*) as ").concat(joinName);
})
.map(([joinName], i) => `to_jsonb(t${i + 2}.*) as ${joinName}`)
.join(', ');
query += " FROM ".concat(this.table, " as t1");
joins = joinNameCols.map(function (_a, i) {
var _ = _a[0], col = _a[1];
var fkey = _this.tableSchema.foreignKeys[col];
var n = i + 2;
return " JOIN ".concat(fkey.table, " AS t").concat(n, " ON t1.").concat(col, " = t").concat(n, ".").concat(fkey.column);
query += ` FROM ${this.table} as t1`;
joins = joinNameCols.map(([_, col], i) => {
const fkey = this.tableSchema.foreignKeys[col];
const n = i + 2;
return ` JOIN ${fkey.table} AS t${n} ON t1.${col} = t${n}.${fkey.column}`;
});
query += joins.join('');
}
var whereKeys = [];
var whereClauses = [];
var tab = this.joins ? 't1.' : '';
const whereKeys = [];
const whereClauses = [];
const tab = this.joins ? 't1.' : '';
if (this.whereCols) {
for (var _i = 0, _a = this.whereCols; _i < _a.length; _i++) {
var col = _a[_i];
for (const col of this.whereCols) {
whereKeys.push(col);
var n = whereKeys.length;
const n = whereKeys.length;
// XXX pg-promise requires a cast here for UUID columns (${tab}${col}::text)
// while node-postgres does not require it.
whereClauses.push("".concat(tab).concat(col, " = $").concat(n));
whereClauses.push(`${tab}${col} = $${n}`);
}
}
if (this.whereAnyCols) {
for (var _b = 0, _c = this.whereAnyCols; _b < _c.length; _b++) {
var anyCol = _c[_b];
var col = anyCol.__any;
for (const anyCol of this.whereAnyCols) {
const col = anyCol.__any;
whereKeys.push(col);
var n = whereKeys.length;
whereClauses.push("".concat(tab).concat(col, " = ANY($").concat(n, ")"));
const n = whereKeys.length;
whereClauses.push(`${tab}${col} = ANY($${n})`);
}
}
if (whereClauses.length) {
query += " WHERE ".concat(whereClauses.join(' AND '));
query += ` WHERE ${whereClauses.join(' AND ')}`;
}
if (this.order) {
var orderClause = this.order.map(function (_a) {
var col = _a[0], dir = _a[1];
return "".concat(col, " ").concat(dir);
});
query += " ORDER BY ".concat(orderClause);
const orderClause = this.order.map(([col, dir]) => `${col} ${dir}`);
query += ` ORDER BY ${orderClause}`;
}
return function (db, whereObj) { return __awaiter(_this, void 0, void 0, function () {
var where, result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
where = whereKeys.map(function (col) {
return whereObj[col] instanceof Set
? Array.from(whereObj[col])
: whereObj[col];
});
return [4 /*yield*/, db.query(query, where)];
case 1:
result = _a.sent();
if (this.isSingular) {
if (result.rowCount === 0) {
return [2 /*return*/, null];
}
else if (result.rowCount === 1) {
return [2 /*return*/, result.rows[0]];
}
// TODO: is it helpful or harmful to add a LIMIT 1 to the query?
throw new Error('Got multiple results for singular query');
}
return [2 /*return*/, result.rows];
return async (db, whereObj) => {
const where = whereKeys.map(col => whereObj[col] instanceof Set
? Array.from(whereObj[col])
: whereObj[col]);
const result = await db.query(query, where);
if (this.isSingular) {
if (result.rowCount === 0) {
return null;
}
});
}); };
};
return Select;
}());
var Insert = /** @class */ (function () {
function Insert(tableSchema, table, disallowedColumns) {
else if (result.rowCount === 1) {
return result.rows[0];
}
// TODO: is it helpful or harmful to add a LIMIT 1 to the query?
throw new Error('Got multiple results for singular query');
}
return result.rows;
};
}
}
class Insert {
constructor(tableSchema, table, disallowedColumns) {
this.tableSchema = tableSchema;

@@ -229,42 +158,33 @@ this.table = table;

}
Insert.prototype.build = function () {
var _this = this;
build() {
// TODO: define an interface for this
var allColumns = this.tableSchema.columns;
var disallowedColumns = this.disallowedColumns;
var allowedColumns = disallowedColumns
? allColumns.filter(function (col) { return !disallowedColumns.includes(col); })
const allColumns = this.tableSchema.columns;
const disallowedColumns = this.disallowedColumns;
const allowedColumns = disallowedColumns
? allColumns.filter(col => !disallowedColumns.includes(col))
: allColumns;
return function (db, obj) { return __awaiter(_this, void 0, void 0, function () {
var illegalCols, keys, placeholders, colsSql, placeholderSql, query, vals, result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (disallowedColumns) {
illegalCols = disallowedColumns.filter(function (col) { return obj[col] !== undefined; });
if (illegalCols.length > 0) {
throw new Error("Cannot insert disallowed column(s) ".concat(illegalCols));
}
}
keys = allowedColumns.filter(function (col) { return obj[col] !== undefined; });
placeholders = keys.map(function (_col, i) { return "$".concat(i + 1); });
colsSql = keys.join(', ');
placeholderSql = placeholders.join(', ');
query = "INSERT INTO ".concat(this.table, "(").concat(colsSql, ") VALUES (").concat(placeholderSql, ") RETURNING *");
vals = keys.map(function (col) { return obj[col]; });
return [4 /*yield*/, db.query(query, vals)];
case 1:
result = _a.sent();
if (result.rowCount === 0) {
return [2 /*return*/, null]; // should be an error?
}
return [2 /*return*/, result.rows[0]];
return async (db, obj) => {
if (disallowedColumns) {
const illegalCols = disallowedColumns.filter(col => obj[col] !== undefined);
if (illegalCols.length > 0) {
throw new Error(`Cannot insert disallowed column(s) ${illegalCols}`);
}
});
}); };
};
return Insert;
}());
var InsertMultiple = /** @class */ (function () {
function InsertMultiple(tableSchema, table, disallowedColumns) {
}
const keys = allowedColumns.filter(col => obj[col] !== undefined);
const placeholders = keys.map((_col, i) => `$${i + 1}`);
// TODO: quoting for table / column names everywhere
const colsSql = keys.join(', ');
const placeholderSql = placeholders.join(', ');
const query = `INSERT INTO ${this.table}(${colsSql}) VALUES (${placeholderSql}) RETURNING *`;
const vals = keys.map(col => obj[col]);
const result = await db.query(query, vals);
if (result.rowCount === 0) {
return null; // should be an error?
}
return result.rows[0];
};
}
}
class InsertMultiple {
constructor(tableSchema, table, disallowedColumns) {
this.tableSchema = tableSchema;

@@ -274,53 +194,39 @@ this.table = table;

}
InsertMultiple.prototype.build = function () {
var _this = this;
var allColumns = this.tableSchema.columns;
var disallowedColumns = this.disallowedColumns;
var allowedColumns = disallowedColumns
? allColumns.filter(function (col) { return !disallowedColumns.includes(col); })
build() {
const allColumns = this.tableSchema.columns;
const disallowedColumns = this.disallowedColumns;
const allowedColumns = disallowedColumns
? allColumns.filter(col => !disallowedColumns.includes(col))
: allColumns;
return function (db, rows) { return __awaiter(_this, void 0, void 0, function () {
var illegalCols, keys, colsSql, placeholder, insertSqls, vals, _loop_1, _i, rows_1, row, placeholderSql, query, result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (disallowedColumns) {
illegalCols = disallowedColumns.filter(function (col) {
return rows.some(function (row) { return row[col] !== undefined; });
});
if (illegalCols.length > 0) {
throw new Error("Cannot insert disallowed column(s) ".concat(illegalCols));
}
}
if (rows.length === 0) {
return [2 /*return*/, []]; // TODO(danvk): consider throwing in this case
}
keys = allowedColumns.filter(function (col) { return rows[0][col] !== undefined; });
colsSql = keys.join(', ');
placeholder = 1;
insertSqls = [];
vals = [];
_loop_1 = function (row) {
insertSqls.push('(' + keys.map(function (_col, i) { return "$".concat(placeholder + i); }).join(',') + ')');
placeholder += keys.length;
vals = vals.concat(keys.map(function (k) { return row[k]; }));
};
for (_i = 0, rows_1 = rows; _i < rows_1.length; _i++) {
row = rows_1[_i];
_loop_1(row);
}
placeholderSql = insertSqls.join(', ');
query = "INSERT INTO ".concat(this.table, "(").concat(colsSql, ") VALUES ").concat(placeholderSql, " RETURNING *");
return [4 /*yield*/, db.query(query, vals)];
case 1:
result = _a.sent();
return [2 /*return*/, result.rows];
return async (db, rows) => {
if (disallowedColumns) {
const illegalCols = disallowedColumns.filter(col => rows.some(row => row[col] !== undefined));
if (illegalCols.length > 0) {
throw new Error(`Cannot insert disallowed column(s) ${illegalCols}`);
}
});
}); };
};
return InsertMultiple;
}());
var Update = /** @class */ (function () {
function Update(table, whereCols, whereAnyCols, setCols, isSingular) {
}
if (rows.length === 0) {
return []; // TODO(danvk): consider throwing in this case
}
const keys = allowedColumns.filter(col => rows[0][col] !== undefined);
const colsSql = keys.join(', ');
let placeholder = 1;
const insertSqls = [];
let vals = [];
for (const row of rows) {
insertSqls.push('(' + keys.map((_col, i) => `$${placeholder + i}`).join(',') + ')');
placeholder += keys.length;
vals = vals.concat(keys.map(k => row[k]));
}
// TODO: quoting for table / column names everywhere
const placeholderSql = insertSqls.join(', ');
// TODO: some ability to control 'returning' would be especially useful here.
const query = `INSERT INTO ${this.table}(${colsSql}) VALUES ${placeholderSql} RETURNING *`;
const result = await db.query(query, vals);
return result.rows;
};
}
}
class Update {
constructor(table, whereCols, whereAnyCols, setCols, isSingular) {
this.table = table;

@@ -332,69 +238,51 @@ this.whereCols = whereCols;

}
Update.prototype.clone = function () {
return new Update(this.table, this.whereCols, this.whereAnyCols, this.setCols, this.isSingular);
};
Update.prototype.build = function () {
var _this = this;
var placeholder = 1;
var setKeys = [];
var setClauses = [];
var setCols = this.setCols;
build() {
let placeholder = 1;
const setKeys = [];
const setClauses = [];
const setCols = this.setCols;
if (setCols) {
for (var _i = 0, setCols_1 = setCols; _i < setCols_1.length; _i++) {
var col = setCols_1[_i];
for (const col of setCols) {
setKeys.push(col);
var n = placeholder++;
setClauses.push("".concat(col, " = $").concat(n));
const n = placeholder++;
setClauses.push(`${col} = $${n}`);
}
}
var whereKeys = [];
var whereClauses = [];
const whereKeys = [];
const whereClauses = [];
if (this.whereCols) {
for (var _a = 0, _b = this.whereCols; _a < _b.length; _a++) {
var col = _b[_a];
for (const col of this.whereCols) {
whereKeys.push(col);
var n = placeholder++;
whereClauses.push("".concat(col, " = $").concat(n));
const n = placeholder++;
whereClauses.push(`${col} = $${n}`);
}
}
if (this.whereAnyCols) {
for (var _c = 0, _d = this.whereAnyCols; _c < _d.length; _c++) {
var anyCol = _d[_c];
var col = anyCol.__any;
for (const anyCol of this.whereAnyCols) {
const col = anyCol.__any;
whereKeys.push(col);
var n = placeholder++;
whereClauses.push("".concat(col, " = ANY($").concat(n, ")"));
const n = placeholder++;
whereClauses.push(`${col} = ANY($${n})`);
}
}
var whereClause = whereClauses.length
? " WHERE ".concat(whereClauses.join(' AND '))
const whereClause = whereClauses.length
? ` WHERE ${whereClauses.join(' AND ')}`
: '';
var limitClause = ''; // this.isSingular ? ' LIMIT 1' : '';
const limitClause = ''; // this.isSingular ? ' LIMIT 1' : '';
if (setCols) {
// In this case the query can be determined in advance
var query_1 = setCols
? "UPDATE ".concat(this.table, " SET ").concat(setClauses.join(', ')).concat(whereClause).concat(limitClause, " RETURNING *")
: null;
return function (db, whereObj, updateObj) { return __awaiter(_this, void 0, void 0, function () {
var vals, result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
vals = setCols
.map(function (col) { return updateObj[col]; })
.concat(whereKeys.map(function (col) {
return whereObj[col] instanceof Set
? Array.from(whereObj[col])
: whereObj[col];
}));
return [4 /*yield*/, db.query(query_1, vals)];
case 1:
result = _a.sent();
if (this.isSingular) {
return [2 /*return*/, result.rowCount === 0 ? null : result.rows[0]];
}
return [2 /*return*/, result.rows];
}
});
}); };
const setSql = setClauses.join(', ');
const query = `UPDATE ${this.table} SET ${setSql}${whereClause}${limitClause} RETURNING *`;
return async (db, whereObj, updateObj) => {
const vals = setCols
.map(col => updateObj[col])
.concat(whereKeys.map(col => whereObj[col] instanceof Set
? Array.from(whereObj[col])
: whereObj[col]));
const result = await db.query(query, vals);
if (this.isSingular) {
return result.rowCount === 0 ? null : result.rows[0];
}
return result.rows;
};
}

@@ -404,42 +292,30 @@ // In this case the query is dynamic.

// TODO: major shadowing bugs here
return function (db, whereObj, updateObj) { return __awaiter(_this, void 0, void 0, function () {
var dynamicSetCols, vals, dynamicPlaceholder, dynamicSetKeys, dynamicSetClauses, _i, dynamicSetCols_1, col, n, query, result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
dynamicSetCols = Object.keys(updateObj);
vals = whereKeys
.map(function (col) {
return whereObj[col] instanceof Set
? Array.from(whereObj[col])
: whereObj[col];
})
.concat(dynamicSetCols.map(function (col) { return updateObj[col]; }));
dynamicPlaceholder = placeholder;
dynamicSetKeys = [];
dynamicSetClauses = [];
for (_i = 0, dynamicSetCols_1 = dynamicSetCols; _i < dynamicSetCols_1.length; _i++) {
col = dynamicSetCols_1[_i];
dynamicSetKeys.push(col);
n = dynamicPlaceholder++;
dynamicSetClauses.push("".concat(col, " = $").concat(n));
}
query = dynamicSetCols
? "UPDATE ".concat(this.table, " SET ").concat(dynamicSetClauses.join(', ')).concat(whereClause).concat(limitClause, " RETURNING *")
: null;
return [4 /*yield*/, db.query(query, vals)];
case 1:
result = _a.sent();
if (this.isSingular) {
return [2 /*return*/, result.rowCount === 0 ? null : result.rows[0]];
}
return [2 /*return*/, result.rows];
}
});
}); };
};
return Update;
}());
var Delete = /** @class */ (function () {
function Delete(table, whereCols, whereAnyCols, isSingular) {
return async (db, whereObj, updateObj) => {
// TODO: maybe better to get this from the schema?
const dynamicSetCols = Object.keys(updateObj);
const vals = whereKeys
.map(col => whereObj[col] instanceof Set
? Array.from(whereObj[col])
: whereObj[col])
.concat(dynamicSetCols.map(col => updateObj[col]));
let dynamicPlaceholder = placeholder;
const dynamicSetKeys = [];
const dynamicSetClauses = [];
for (const col of dynamicSetCols) {
dynamicSetKeys.push(col);
const n = dynamicPlaceholder++;
dynamicSetClauses.push(`${col} = $${n}`);
}
const setSql = dynamicSetClauses.join(', ');
const query = `UPDATE ${this.table} SET ${setSql}${whereClause}${limitClause} RETURNING *`;
const result = await db.query(query, vals);
if (this.isSingular) {
return result.rowCount === 0 ? null : result.rows[0];
}
return result.rows;
};
}
}
class Delete {
constructor(table, whereCols, whereAnyCols, isSingular) {
this.table = table;

@@ -450,55 +326,38 @@ this.whereCols = whereCols;

}
Delete.prototype.clone = function () {
return new Delete(this.table, this.whereCols, this.whereAnyCols, this.isSingular);
};
Delete.prototype.build = function () {
var _this = this;
var placeholder = 1;
var whereKeys = [];
var whereClauses = [];
build() {
let placeholder = 1;
const whereKeys = [];
const whereClauses = [];
if (this.whereCols) {
for (var _i = 0, _a = this.whereCols; _i < _a.length; _i++) {
var col = _a[_i];
for (const col of this.whereCols) {
whereKeys.push(col);
var n = placeholder++;
whereClauses.push("".concat(col, " = $").concat(n));
const n = placeholder++;
whereClauses.push(`${col} = $${n}`);
}
}
if (this.whereAnyCols) {
for (var _b = 0, _c = this.whereAnyCols; _b < _c.length; _b++) {
var anyCol = _c[_b];
var col = anyCol.__any;
for (const anyCol of this.whereAnyCols) {
const col = anyCol.__any;
whereKeys.push(col);
var n = placeholder++;
whereClauses.push("".concat(col, " = ANY($").concat(n, ")"));
const n = placeholder++;
whereClauses.push(`${col} = ANY($${n})`);
}
}
var whereClause = whereClauses.length
? " WHERE ".concat(whereClauses.join(' AND '))
const whereClause = whereClauses.length
? ` WHERE ${whereClauses.join(' AND ')}`
: '';
var limitClause = ''; // this.isSingular ? ' LIMIT 1' : '';
var query = "DELETE FROM ".concat(this.table).concat(whereClause).concat(limitClause, " RETURNING *");
return function (db, whereObj) { return __awaiter(_this, void 0, void 0, function () {
var vals, result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
vals = whereKeys.map(function (col) {
return whereObj[col] instanceof Set
? Array.from(whereObj[col])
: whereObj[col];
});
return [4 /*yield*/, db.query(query, vals)];
case 1:
result = _a.sent();
if (this.isSingular) {
return [2 /*return*/, result.rowCount === 0 ? null : result.rows[0]];
}
return [2 /*return*/, result.rows];
}
});
}); };
};
return Delete;
}());
const limitClause = ''; // this.isSingular ? ' LIMIT 1' : '';
const query = `DELETE FROM ${this.table}${whereClause}${limitClause} RETURNING *`;
return async (db, whereObj) => {
const vals = whereKeys.map(col => whereObj[col] instanceof Set
? Array.from(whereObj[col])
: whereObj[col]);
const result = await db.query(query, vals);
if (this.isSingular) {
return result.rowCount === 0 ? null : result.rows[0];
}
return result.rows;
};
}
}
//# sourceMappingURL=index.js.map
{
"name": "crudely-typed",
"version": "0.1.0",
"version": "0.1.1",
"description": "Simple \"everyday CRUD\" Postgres queries with perfect TypeScript types",

@@ -16,3 +16,4 @@ "main": "dist/src/index.js",

"clean": "rm -rf dist",
"coverage": "jest --coverage"
"coverage": "jest --coverage",
"prepublishOnly": "tsc"
},

@@ -29,8 +30,11 @@ "files": [

"eslint": "^8.12.0",
"eslint-plugin-expect-type": "^0.2.0",
"jest": "^27.5.1",
"pg": "^8.7.3",
"pg-to-ts": "^4.1.0",
"prettier": "^2.6.2",
"ts-jest": "^27.1.4",
"typescript": "^4.6.3"
}
},
"dependencies": {}
}
# Crudely Typed
[![codecov](https://codecov.io/gh/danvk/crudely-typed/branch/main/graph/badge.svg?token=2C0SU9X0EM)](https://codecov.io/gh/danvk/crudely-typed)
Simple "everyday CRUD" Postgres queries with perfect TypeScript types.
Zero dependencies. Designed to work with [pg-to-ts][] and [node-postgres][].
## Quickstart
Install [pg-to-ts][] and this library, and generate a schema file:
npm install -D pg-to-ts
npm install crudely-typed
pg-to-ts generate -c $POSTGRES_URL --output src/dbschema.ts
Then generate your queries using a `TypedSQL` instance:
```ts
// src/demo.ts
import {TypedSQL} from 'crudely-typed';
import {tables} from './dbschema';
const typedSql = new TypedSQL(tables);
const getDocById = typedSql.table('docs').selectByPrimaryKey();
// ^? const getDocById: (db: Queryable, where: { id: string }) => Promise<Doc | null>
```
Crudely Typed supports the basic create / read / update / delete queries. See
[API](#api) for details. Crudely Typed is _not_ a full-fledged query builder,
nor does it aspire to be. See [FAQ](#faq) for more on this.
## API
### TypedSQL
Everything starts with a `TypedSQL` instance, which you construct from the
`tables` export of a [pg-to-ts][] DB schema. There are many schema generators
derived from the old SchemaTS project, but crudely-typed specifically requires
pg-to-ts schemas because they have just the type references it needs.
```ts
import {TypedSQL} from 'crudely-typed';
import {tables} from './dbschema'; // <-- output of pg-to-ts
const typedSql = new TypedSQL(tables);
```
### table
From a `TypedSQL` instance, you can produce a `TableBuilder` object for any of
your tables:
```ts
const usersTable = typedSql.table('users');
```
The remaining functions in crudely-typed are all defined on this table object.
Each of the functions comes in regular and `ByPrimaryKey` variants, e.g.
`table.select()` and `table.selectByPrimaryKey()`.
### table.select
```ts
table.select(): (db: Queryable) => Promise<Row[]>
```
with no parameters, this is select all in the order returned by the database.
```ts
table.select(options: {
where?: (Column | SQLAny<Column>)[],
columns?: Column[],
orderBy?: [col: Column, order: 'ASC' | 'DESC'][];
limitOne?: boolean;
join?: {
[resultingColumnName: string]: Column
};
}): (db: Queryable, where: ...) => Promise<...>
```
Looking at each option individually:
- `where` adds a `WHERE` clause to the query:
```ts
const docsTable = typedSql.table('docs');
const getDocsByAuthor = docsTable.select({where: ['author']});
// ^? const getDocsByAuthor: (db: Queryable, where: {author: string}) => Promise<Doc[]>
```
If you specify multiple where clauses, they'll be combined with `AND`.
You may also specify an `ANY` clause to match one of many values.
See [Where clasues](#where-clauses), below.
- `columns` restricts the set of columns that are retrieved (by default all
columns are retrieved, i.e. `SELECT *`). You can use this to avoid fetching
large, unneeded columns.
```ts
const docsTable = typedSql.table('docs');
const getTitles = docsTable.select({columns: ['title']});
// ^? const getTitles: (db: Queryable) => Promise<{title: string}[]>
```
- `orderBy` sorts the output, i.e. it adds an `ORDER BY` clause to the query.
Adding an `orderBy` clause does not affect the type of the `select`.
```ts
const docsTable = typedSql.table('docs');
const getDocs = docsTable.select({orderBy: [['author', 'ASC']]});
// ^? const getTitles: (db: Queryable) => Promise<Doc[]>
```
- `limitOne` adds a `LIMIT 1` clause to the query, so that it always returns
either zero or one row. This changes the return type from `T[]` to `T | null`.
```ts
const docsTable = typedSql.table('docs');
const getTitle = docsTable.select({where: ['title'], limitOne: true});
// ^? const getTitle: (
// db: Queryable,
// where: {title: string}
// ) => Promise<Doc | null>
```
- `join` adds 1-1 joins to the query for columns that are foreign keys into
other tables. The row from the joined table comes back as an object under
the property name that you specify. You may specify multiple joins, though
they cannot be nested and they must all be 1-1.
```ts
const docsTable = typedSql.table('docs');
const getDocs = docsTable.select({
join: {
author: 'author_id',
publisher: 'publisher_id',
}
});
// ^? const getDocs: (
// db: Queryable
// ) => Promise<(Doc & {author: Author; publisher: Publisher })[]>
```
You don't need to specify the joined table or its type; crudely-typed has all
the information it needs from the dbschema. If you specify a set of columns to
select with `columns`, the foreign key need not be one of those columns.
### table.selectByPrimaryKey
There's a helper for the common case of selecting by primary key:
```ts
const docsTable = typedSql.table('docs');
const getDocById = docsTable.selectByPrimaryKey();
// ^? const getDocById: (
// db: Queryable,
// where: { id: string }
// ) => Promise<Doc | null>
```
This is exactly equivalent to `docsTable.select({where: ['id'], limitOne: true})`
but saves you some typing.
You may use the `columns` and `join` and with `selectByPrimaryKey`:
```ts
const getDocById = docsTable.selectByPrimaryKey({
columns: ['title'],
join: { author: 'author_id' }
});
const doc = await getDocById(db, {id: 'doc-id'});
// ^? const doc: {title: string; author: Author} | null
```
### table.insert
```ts
table.insert(): (db: Queryable, row: RowInput) => Promise<Row>
```
This generates a dynamic `INSERT` query based on the properties of `row`.
The `RowInput` type models the required and optional columns in the table.
If an optional property is omitted from `row`, then it will be set to its
default value and observable in the returned `Row`. If a required property
is omitted, you'll get a type error.
```ts
const insertDoc = docsTable.insert();
const doc = await insertDoc({author: 'Mark Twain', title: 'Huckleberry Finn'});
// ^? const doc: Doc
```
It's sometimes desirable to prevent certain columns from being set, e.g. the
primary key. This can be enforced with the `disallowColumns` option:
```ts
const insertDoc = docsTable.insert({ disallowColumns: ['id'] });
// ^? const insertDoc: (db: Queryable, row: Omit<DocInput, 'id'>) => Promise<Doc>
insertDoc({id: 'some id'});
// ~~ type error!
const doc = await insertDoc({author: 'Mark Twain', title: 'Huckleberry Finn'});
// ^? const doc: Doc
```
### table.insertMultiple
This is indentical to `insert` but allows multiple rows to be inserted with a
single query.
```ts
const docsTable = typedSql.table('docs');
const insertDocs = docsTable.insertMultiple();
// ^? const insertDocs: (
// db: Queryable,
// rows: readonly DocInput[]
// ) => Promise<Row[]>
const docs = await insertDocs([
{title: 'Huckleberry Finn', author: 'Mark Twain'},
{title: 'Poor Richard', author: 'Ben Franklin'}
]);
```
`insertMultiple` also supports `disallowColumns`, just like `insert`.
### table.update
```ts
table.update({
where?: (Column | SQLAny<Column>)[],
set?: Column[],
limitOne?: boolean,
}): (db: Queryable, where: ..., set: ...) => Promise<...>
```
With a `where` and a `set` clause, this updates specific columns on specific rows. All affected rows are returned:
```ts
const docsTable = typedSql.table('docs');
const setYear = docsTable.update({where: ['title'], set: ['year']});
// ^? const setYear: (
// db: Queryable,
// where: {title: string},
// set: {year: number}
// ) => Promise<Doc[]>
const newDocs = setYear(db, {title: 'Huck Finn'}, {year: 1872});
// ^? const newDocs: Promise<Doc[]>
```
Without a `set` clause, this performs a dynamic update based on the param:
```ts
const update = docsTable.update({where: ['title']});
// ^? const update: (
// db: Queryable,
// where: {title: string},
// set: Partial<Doc>
// ) => Promise<Doc[]>
const newDocs = setYear(db, {title: 'Huck Finn'}, {year: 1872});
// ^? const newDocs: Promise<Doc[]>
```
The `where` clause can include multiple columns, in which case it operates as
an `AND`, and can support `ANY` clauses.
See [Where clasues](#where-clauses), below.
Without a `where` clause, this updates all rows in the table.
If you pass `limitOne: true`, at most one row will be updated and the function
will return `T | null` instead of `T[]`:
```ts
const update = docsTable.update({where: ['title'], limitOne: true});
// ^? const update: (
// db: Queryable,
// where: {title: string},
// set: Partial<Doc>
// ) => Promise<Doc | null>
const newDoc = setYear(db, {title: 'Huck Finn'}, {year: 1872});
// ^? const newDocs: Promise<Doc>
```
### table.updateByPrimaryKey
This is a shortcut for updating a row by its table's primary key:
```ts
const update = docsTable.updateByPrimaryKey(set: ['year']);
// ^? const update: (
// db: Queryable,
// where: {id: string},
// set: {year: number}
// ) => Promise<Doc | null>
const newDoc = setYear(db, {id: 'isbn-123'}, {year: 1872});
// ^? const newDoc: Promise<Doc>
```
If you pass a `set` option, then this updates a fixed set of columns.
If you don't, it's dynamic based on its parameter, just like `update`.
### table.delete
```ts
table.delete(options: {
where?: (Column | SQLAny<Column>)[];
limitOne?: boolean;
}): (db: Queryable, where: ...) => Promise<...>
```
The `where` clause for `delete` works exactly as it does for `select`. It may
be set to an array of columns or `ANY` clauses. See
[Where clauses](#where-clauses), below.
```ts
const docsTable = typedDb.table('docs');
const deleteByTitle = docsTable.delete({ where: ['title'] });
// ^? const deleteByTitle: (db: Queryable, where: {title: string}) => Promise<Doc[]>
```
The `delete` function returns the rows that it deletes (if any). As with
`select`, if you pass `limitOne: true` then it will return `T | null` instead
of `T[]`:
```ts
const docsTable = typedDb.table('docs');
const deleteByTitle = docsTable.delete({ where: ['title'], limitOne: true });
// ^? const deleteByTitle: (db: Queryable, where: {title: string}) => Promise<Doc | null>
```
If you don't specify a `where` clause or `limitOne`, this acts as "delete all".
### table.deleteByPrimaryKey
This is a helper for the common case where you want to delete rows by their
primary key:
```ts
const docsTable = typedDb.table('docs');
const deleteDoc = docsTable.deleteByPrimaryKey();
// ^? const deleteDoc: (db: Queryable, where: {id: string}) => Promise<Doc | null>
```
This is exactly equivalent to `docsTable.delete({ where: ['id'], limitOne: true })`.
## Queryable
All generated functions take a `Queryable` object as their first parameter:
```ts
interface Queryable {
query(sql: string, ...args: any[]): Promise<Result>;
}
```
The `Client` and `Pool` classes from `pg` conform to this and can be used with
crudely-typed.
## Where clauses
TODO:
- [ ] Publish to npm
- [ ] Write docs
- [ ] Set up CI
- [ ] Split select unit/type tests
- [ ] Check types in type tests
- [ ] Write type tests for:
- [ ] Update
- [ ] Delete
- [ ] Insert
- [ ] Add unit tests to check that all queries are idempotent
- [x] Settle on name (crudely-typed it is!)
- [x] Test in cityci (requires publishing new pg-to-ts with `$type`)
- Multiple columns are `AND`ed
- You can generate `ANY` matchers
- You can generate a mix
For issues:
## Joins
- [ ] Limit joins to join-able columns
- [ ] Support both node-postgres _and_ pg-promise
- [ ] Add upsert
- [ ] Prevent omitting non-optional columns in insert / insertMultiple
TODO
From cityci testing:
## FAQ
- [ ] Should insertMultiple with zero rows throw?
- **Isn't this just a query builder?**
- **Why does crudely-typed generate functions instead of running them?**
- **Can you add support for X?**
Probably not! The goal of this library is to handle the simplest queries for
you with perfect types and a minimum of fuss. Supporting every SQL query is
absolutely not a goal. At some point you should just write SQL (see below).
- **What should I do for complex queries?**
- **Why not use PgTyped for all my queries?**
- **Why not use an ORM?**
- **What's with the name?**
CRUD is short for Create, Read, Update, Delete. I wanted something that had
"crud" in the name but didn't sound like "crud". "crudely" fit the bill. It's
also a play on [DefinitelyTyped][dt] and is a bit tongue in cheek since the
types in this library are anything but crude.
## How this works
The `tables` object that `pg-to-ts` outputs includes all the TypeScript types
and runtime values needed to generate well-typed queries. From there it's just
a bunch of TypeScript generics that should be entirely invisible to you, the
user. See [`index.ts`](/src/index.ts) for all the details. The following blog
posts may be helpful for understanding some of the techniques being used:
- intersect what you have
- unionize/objectify
- display of type
- currying and classes
[pg-to-ts]: https://github.com/danvk/pg-to-ts
[node-postgres]: https://github.com/brianc/node-postgres
[dt]: https://github.com/DefinitelyTyped/DefinitelyTyped

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