@decaf-ts/for-postgres
Version:
template for ts projects
881 lines • 125 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { Adapter, PersistenceKeys, ConnectionError, Repository, DefaultSequenceOptions, final, } from "@decaf-ts/core";
import { PostgresFlavour, reservedAttributes } from "./constants.js";
import { BaseError, ConflictError, DBKeys, findPrimaryKey, InternalError, NotFoundError, onCreate, readonly, } from "@decaf-ts/db-decorators";
import "reflect-metadata";
import { Decoration, DEFAULT_ERROR_MESSAGES, Model, ModelKeys, propMetadata, required, ValidationKeys, } from "@decaf-ts/decorator-validation";
import { IndexError } from "./errors.js";
import { PostgresStatement } from "./query/index.js";
import { Pool } from "pg";
import { PostgresSequence } from "./sequences/index.js";
import { generateIndexes } from "./indexes/index.js";
import { Reflection } from "@decaf-ts/reflection";
import { PostgresRepository } from "./PostgresRepository.js";
import { Logging } from "@decaf-ts/logging";
import { PostgresDispatch } from "./PostgresDispatch.js";
import { convertJsRegexToPostgres } from "./utils.js";
export async function createdByOnPostgresCreateUpdate(context, data, key, model) {
try {
const user = context.get("user");
model[key] = user;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}
catch (e) {
throw new InternalError("No User found in context. Please provide a user in the context");
}
}
/**
* @description Abstract adapter for Postgres database operations
* @summary Provides a base implementation for Postgres database operations, including CRUD operations, sequence management, and error handling
* @template Y - The scope type
* @template F - The repository flags type
* @template C - The context type
* @param {Y} scope - The scope for the adapter
* @param {string} flavour - The flavour of the adapter
* @param {string} [alias] - Optional alias for the adapter
* @class PostgresAdapter
*/
export class PostgresAdapter extends Adapter {
constructor(pool, alias) {
super(pool, PostgresFlavour, alias);
}
async flags(operation, model, flags) {
const f = await super.flags(operation, model, flags);
const newObj = {
user: (await PostgresAdapter.getCurrentUser(this.native)),
};
if (operation === "create" || operation === "update") {
const pk = findPrimaryKey(new model()).id;
newObj.ignoredValidationProperties = (f.ignoredValidationProperties ? f.ignoredValidationProperties : []).concat(pk);
}
return Object.assign(f, newObj);
}
Dispatch() {
return new PostgresDispatch();
}
repository() {
return PostgresRepository;
}
/**
* @description Creates a new Postgres statement for querying
* @summary Factory method that creates a new PostgresStatement instance for building queries
* @template M - The model type
* @return {PostgresStatement<M, any>} A new PostgresStatement instance
*/
Statement() {
return new PostgresStatement(this);
}
/**
* @description Creates a new PostgreSQL sequence
* @summary Factory method that creates a new PostgreSQLSequence instance for managing sequences
* @param {SequenceOptions} options - The options for the sequence
* @return {Promise<Sequence>} A promise that resolves to a new Sequence instance
*/
async Sequence(options) {
return new PostgresSequence(options, this);
}
/**
* @description Initializes the adapter by creating indexes for all managed models
* @summary Sets up the necessary database indexes for all models managed by this adapter
* @return {Promise<void>} A promise that resolves when initialization is complete
*/
async initialize() {
const managedModels = Adapter.models(this.flavour);
return this.index(...managedModels);
}
/**
* @description Creates indexes for the given models
* @summary Abstract method that must be implemented to create database indexes for the specified models
* @template M - The model type
* @param {...Constructor<M>} models - The model constructors to create indexes for
* @return {Promise<void>} A promise that resolves when all indexes are created
*/
async index(...models) {
const indexes = generateIndexes(models);
const client = await this.native.connect();
try {
await client.query("BEGIN");
for (const index of indexes) {
await client.query(index.query, index.values);
}
await client.query("COMMIT");
}
catch (e) {
await client.query("ROLLBACK");
throw this.parseError(e);
}
finally {
client.release();
}
}
/**
* @description Executes a raw SQL query against the database
* @summary Abstract method that must be implemented to execute raw SQL queries
* @template R - The result type
* @param {PostgresQuery} q - The query to execute
* @param {boolean} rowsOnly - Whether to return only the rows or the full response
* @return {Promise<R>} A promise that resolves to the query result
*/
async raw(q, rowsOnly) {
const client = await this.native.connect();
try {
const { query, values } = q;
const response = await client.query(query, values);
if (rowsOnly)
return response.rows;
return response;
}
catch (e) {
throw this.parseError(e);
}
finally {
client.release();
}
}
prepare(model, pk) {
const prepared = super.prepare(model, pk);
prepared.record = Object.entries(prepared.record).reduce((accum, [key, value]) => {
if (key === PersistenceKeys.METADATA ||
this.isReserved(key) ||
key === pk)
return accum;
if (value === undefined) {
return accum;
}
if (value instanceof Date) {
value = new Date(value.getTime());
}
else {
switch (typeof value) {
case "string":
value = `${value}`;
break;
default:
//do nothing;
}
}
accum[key] = value;
return accum;
}, {});
return prepared;
}
revert(obj, clazz, pk, id, transient) {
const log = this.log.for(this.revert);
const ob = {};
ob[pk] = id || obj[pk];
const m = (typeof clazz === "string" ? Model.build(ob, clazz) : new clazz(ob));
log.silly(`Rebuilding model ${m.constructor.name} id ${id}`);
const result = Object.keys(m).reduce((accum, key) => {
accum[key] = obj[Repository.column(accum, key)];
return accum;
}, m);
if (transient) {
log.verbose(`re-adding transient properties: ${Object.keys(transient).join(", ")}`);
Object.entries(transient).forEach(([key, val]) => {
if (key in result)
throw new InternalError(`Transient property ${key} already exists on model ${m.constructor.name}. should be impossible`);
result[key] = val;
});
}
return result;
}
/**
* @description Creates a new record in the database
* @summary Abstract method that must be implemented to create a new record
* @param {string} tableName - The name of the table
* @param {string|number} id - The ID of the record
* @param {Record<string, any>} model - The model to create
* @param {...any[]} args - Additional arguments
* @return {Promise<Record<string, any>>} A promise that resolves to the created record
*/
async create(tableName, id, model,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...args) {
const values = Object.values(model);
const sql = `INSERT INTO ${tableName} (${Object.keys(model)}) VALUES (${values.map((_, i) => `$${i + 1}`)}) RETURNING *`;
const response = await this.raw({ query: sql, values: values }, false);
const { rows } = response;
return rows[0];
}
/**
* @description Reads a record from the database
* @summary Abstract method that must be implemented to read a record
* @param {string} tableName - The name of the table
* @param {string|number} id - The ID of the record
* @param {string} pk - primary key colum
* @return {Promise<Record<string, any>>} A promise that resolves to the read record
*/
async read(tableName, id, pk) {
const sql = `SELECT * FROM ${tableName} as t WHERE t.${pk} = $1`;
const result = await this.raw({ query: sql, values: [id] }, false);
if (result.rowCount === 0)
throw new NotFoundError(`Record with id: ${id} not found in table ${tableName}`);
return result.rows[0];
}
/**
* @description Updates a record in the database
* @summary Abstract method that must be implemented to update a record
* @param {string} tableName - The name of the table
* @param {string|number} id - The ID of the record
* @param {Record<string, any>} model - The model to update
* @param {string} pk - Additional arguments
* @return A promise that resolves to the updated record
*/
async update(tableName, id, model,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...args) {
const values = Object.values(model);
const sql = `UPDATE ${tableName}
SET ${Object.keys(model)
.map((f, i) => `${f} = $${i + 1}`)
.join(", ")}
WHERE id = $${values.length + 1}
RETURNING *;`;
const response = await this.raw({ query: sql, values: [...values, id] }, false);
if (response.rowCount === 0) {
throw new NotFoundError(`Record with id: ${id} not found in table ${tableName}`);
}
const { rows } = response;
return rows[0];
}
/**
* @description Deletes a record from the database
* @summary Abstract method that must be implemented to delete a record
* @param {string} tableName - The name of the table
* @param {string|number} id - The ID of the record
* @param {string} pk - Additional arguments
* @return A promise that resolves to the deleted record
*/
async delete(tableName, id, pk,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...args) {
const sql = `
DELETE FROM ${tableName}
WHERE ${pk} = $1
RETURNING *
`;
const result = await this.raw({
query: sql,
values: [id],
}, false);
if (result.rowCount === 0) {
throw new NotFoundError(`Record with id: ${id} not found in table ${tableName}`);
}
return result.rows[0];
}
async createAll(tableName, id, model,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...args) {
const columns = Object.keys(model[0]);
const valuePlaceholders = model
.map((_, recordIndex) => `(${columns
.map((_, colIndex) => `$${recordIndex * columns.length + colIndex + 1}`)
.join(", ")})`)
.join(", ");
const values = model.flatMap((record) => Object.values(record));
const q = `INSERT INTO ${tableName} (${columns.join(", ")})
VALUES ${valuePlaceholders}
RETURNING *;
`;
const result = await this.raw({
query: q,
values: values,
}, false);
return result.rows;
}
async readAll(tableName, id, pk,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...args) {
if (!id.length)
return [];
const sql = `
SELECT *
FROM ${tableName}
WHERE ${pk} = ANY($1)
ORDER BY array_position($1::${typeof id[0] === "number" ? "integer" : "text"}[], ${pk})`;
const result = await this.raw({
query: sql,
values: [id],
}, false);
// If we didn't find all requested records, throw an error
if (result.rows.length !== id.length) {
const foundIds = result.rows.map((row) => row[pk]);
const missingIds = id.filter((id) => !foundIds.includes(id));
throw new NotFoundError(`Records with ids: ${missingIds.join(", ")} not found in table ${tableName}`);
}
return result.rows;
}
async updateAll(tableName, ids, model, pk,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...args) {
if (!ids.length)
return [];
if (ids.length !== model.length) {
throw new InternalError("Number of IDs must match number of records");
}
// Create values array and get column names from first record
const columns = Object.keys(model[0]);
const values = [];
let placeholderIndex = 1;
// Generate value lists for each record
const valueLists = model
.map((record, i) => {
const recordValues = columns.map((col) => {
values.push(record[col]);
if (record[col] instanceof Date) {
return `$${placeholderIndex++}::timestamp`;
}
return `$${placeholderIndex++}`;
});
return `(${ids[i]}, ${recordValues.join(", ")})`;
})
.join(", ");
const sql = `
UPDATE ${tableName} AS t SET
${columns.map((col) => `${col} = c.${col}`).join(",\n ")}
FROM (VALUES ${valueLists}) AS c(id, ${columns.join(", ")})
WHERE t.${pk} = c.id
RETURNING *`;
const result = await this.raw({
query: sql,
values,
}, false);
// Verify all records were updated
if (result.rows.length !== ids.length) {
const foundIds = result.rows.map((row) => row[pk]);
const missingIds = ids.filter((id) => !foundIds.includes(id));
throw new NotFoundError(`Records with ids: ${missingIds.join(", ")} not found in table ${tableName}`);
}
// Return updated records in the same order as input
return ids.map((id) => result.rows.find((row) => row[pk].toString() === id.toString()));
}
async deleteAll(tableName, ids, pk,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...args) {
if (!ids.length)
return [];
// First fetch the records that will be deleted (for returning them later)
const fetchSql = `
SELECT *
FROM ${tableName}
WHERE ${pk} = ANY($1)
ORDER BY array_position($1::${typeof ids[0] === "number" ? "integer" : "text"}[], ${pk})`;
const fetchResult = await this.raw({
query: fetchSql,
values: [ids],
}, false);
if (fetchResult.rows.length !== ids.length) {
const foundIds = fetchResult.rows.map((row) => row[pk]);
const missingIds = ids.filter((id) => !foundIds.includes(id));
throw new NotFoundError(`Records with ids: ${missingIds.join(", ")} not found in table ${tableName}`);
}
const deleteSql = `
DELETE FROM ${tableName}
WHERE ${pk} = ANY($1)`;
await this.raw({
query: deleteSql,
values: [ids],
}, false);
return ids.map((id) => fetchResult.rows.find((row) => row[pk].toString() === id.toString()));
}
/**
* @description Parses an error and converts it to a BaseError
* @summary Converts various error types to appropriate BaseError subtypes
* @param {Error|string} err - The error to parse
* @param {string} [reason] - Optional reason for the error
* @return {BaseError} The parsed error as a BaseError
*/
parseError(err, reason) {
return PostgresAdapter.parseError(err, reason);
}
/**
* @description Checks if an attribute is reserved
* @summary Determines if an attribute name is reserved in PostgreSQL
* @param {string} attr - The attribute name to check
* @return {boolean} True if the attribute is reserved, false otherwise
*/
isReserved(attr) {
return !!attr.match(reservedAttributes);
}
/**
* @description Static method to parse an error and convert it to a BaseError
* @summary Converts various error types to appropriate BaseError subtypes based on PostgreSQL error codes and messages
* @param {Error|string} err - The error to parse
* @param {string} [reason] - Optional reason for the error
* @return {BaseError} The parsed error as a BaseError
* @mermaid
* sequenceDiagram
* participant Caller
* participant parseError
* participant ErrorTypes
*
* Caller->>parseError: err, reason
* Note over parseError: Check if err is already a BaseError
* alt err is BaseError
* parseError-->>Caller: return err
* else err is string
* Note over parseError: Extract code from string
* alt code matches "duplicate key|already exists"
* parseError->>ErrorTypes: new ConflictError(code)
* ErrorTypes-->>Caller: ConflictError
* else code matches "does not exist|not found"
* parseError->>ErrorTypes: new NotFoundError(code)
* ErrorTypes-->>Caller: NotFoundError
* end
* else err has code property
* Note over parseError: Extract code and reason
* else
* Note over parseError: Use err.message as code
* end
*
* Note over parseError: Switch on PostgreSQL error code
* alt code is 23505 (unique_violation)
* parseError->>ErrorTypes: new ConflictError(reason)
* ErrorTypes-->>Caller: ConflictError
* else code is 23503 (foreign_key_violation)
* parseError->>ErrorTypes: new ConflictError(reason)
* ErrorTypes-->>Caller: ConflictError
* else code is 42P01 (undefined_table)
* parseError->>ErrorTypes: new NotFoundError(reason)
* ErrorTypes-->>Caller: NotFoundError
* else code is 42703 (undefined_column)
* parseError->>ErrorTypes: new NotFoundError(reason)
* ErrorTypes-->>Caller: NotFoundError
* else code is 42P07 (duplicate_table)
* parseError->>ErrorTypes: new ConflictError(reason)
* ErrorTypes-->>Caller: ConflictError
* else code is 42P16 (invalid_table_definition)
* parseError->>ErrorTypes: new IndexError(err)
* ErrorTypes-->>Caller: IndexError
* else code matches "ECONNREFUSED"
* parseError->>ErrorTypes: new ConnectionError(err)
* ErrorTypes-->>Caller: ConnectionError
* else
* parseError->>ErrorTypes: new InternalError(err)
* ErrorTypes-->>Caller: InternalError
* end
*/
static parseError(err, reason) {
if (err instanceof BaseError)
return err;
const code = typeof err === "string" ? err : err.message;
if (code.match(/duplicate key|already exists/g))
return new ConflictError(code);
if (code.match(/does not exist|not found/g))
return new NotFoundError(code);
// PostgreSQL error codes: https://www.postgresql.org/docs/current/errcodes-appendix.html
switch (code.toString()) {
// Integrity constraint violations
case "23505": // unique_violation
case "23503": // foreign_key_violation
case "42P07": // duplicate_table
return new ConflictError(reason);
// Object not found errors
case "42P01": // undefined_table
case "42703": // undefined_column
return new NotFoundError(reason);
// Invalid object definition
case "42P16": // invalid_table_definition
return new IndexError(err);
// Connection errors
default:
if (code.toString().match(/ECONNREFUSED/g))
return new ConnectionError(err);
return new InternalError(err);
}
}
static async connect(config) {
return new Pool(config);
}
static async createDatabase(pool, dbName) {
const log = Logging.for(this.createDatabase);
log.verbose(`Creating database ${dbName}`);
const client = await pool.connect();
try {
await client.query({
name: `create-database`,
text: `CREATE DATABASE ${dbName}`,
});
log.info(`Created database ${dbName}`);
}
catch (e) {
throw this.parseError(e);
}
finally {
client.release();
}
}
static async createNotifyFunction(pool, user) {
const log = Logging.for(this.createNotifyFunction);
log.verbose(`Creating notify function`);
const client = await pool.connect();
try {
await client.query(`CREATE OR REPLACE FUNCTION notify_table_changes()
RETURNS trigger AS $$
BEGIN
PERFORM pg_notify(
'table_changes',
json_build_object(
'table', TG_TABLE_NAME,
'action', TG_OP,
'data', row_to_json(NEW),
'old_data', row_to_json(OLD)
)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER
;`);
await client.query(`ALTER FUNCTION notify_table_changes() OWNER TO ${user};`);
await client.query(`
GRANT EXECUTE ON FUNCTION notify_table_changes() TO public;
`);
log.info(`Created notify function`);
}
catch (e) {
throw this.parseError(e);
}
finally {
client.release();
}
}
static async deleteDatabase(pool, dbName, user) {
const client = await pool.connect();
try {
if (user)
await client.query({
name: `delete-owned-by`,
text: `DROP OWNED BY ${user} CASCADE;`,
});
await client.query({
name: `delete-database`,
text: `DROP DATABASE ${dbName}`,
});
}
catch (e) {
throw this.parseError(e);
}
finally {
client.release();
}
}
static async createUser(pool, dbName, user, password) {
const client = await pool.connect();
try {
await client.query(`CREATE USER ${user} WITH PASSWORD '${password}'`);
await client.query(`GRANT CONNECT ON DATABASE ${dbName} TO ${user}`);
await client.query(`GRANT USAGE ON SCHEMA public TO ${user}`);
await client.query(`GRANT CREATE ON SCHEMA public TO ${user}`);
await client.query(`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ${user}`);
await client.query(`GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ${user}`);
await client.query(`GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO ${user}`);
await client.query(`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO ${user}`);
await client.query(`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON SEQUENCES TO ${user}`);
}
catch (e) {
throw this.parseError(e);
}
finally {
client.release();
}
}
static async deleteUser(pool, user, admin) {
const client = await pool.connect();
try {
await client.query(`REASSIGN OWNED BY ${user} TO ${admin}`);
await client.query(`REVOKE ALL ON ALL TABLES IN SCHEMA public FROM ${user}`);
await client.query(`REVOKE ALL ON SCHEMA public FROM ${user}`);
await client.query(`REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM ${user}`);
await client.query(`REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public FROM ${user}`);
await client.query(`ALTER DEFAULT PRIVILEGES FOR ROLE ${admin} IN SCHEMA public REVOKE ALL ON TABLES FROM ${user}`);
await client.query(`ALTER DEFAULT PRIVILEGES FOR ROLE ${admin} IN SCHEMA public REVOKE ALL ON SEQUENCES FROM ${user};`);
await client.query(`ALTER DEFAULT PRIVILEGES FOR ROLE ${admin} IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM ${user}`);
await client.query(`DROP OWNED BY ${user} CASCADE`);
await client.query(`DROP USER IF EXISTS "${user}"`);
}
catch (e) {
throw this.parseError(e);
}
finally {
client.release();
}
}
static parseTypeToPostgres(type, isPk, isFk = false) {
switch (type.toLowerCase()) {
case "string":
return isPk ? "TEXT PRIMARY KEY" : isFk ? "TEXT" : "VARCHAR";
case "number":
return isPk ? "SERIAL PRIMARY KEY" : "INTEGER";
case "boolean":
return "BOOLEAN";
case "date":
return "TIMESTAMP";
case "bigint":
return isPk ? "BIGINT PRIMARY KEY" : "BIGINT";
default: {
const m = Model.get(type);
if (m) {
const mm = new m();
const type = Reflection.getTypeFromDecorator(mm, findPrimaryKey(mm).id);
return {
model: m,
pkType: type,
};
}
throw new InternalError(`Unsupported type: ${type}`);
}
}
}
static parseValidationToPostgres(prop, type, isPk, key, options) {
switch (key) {
case ValidationKeys.REQUIRED:
return "NOT NULL";
case ValidationKeys.MAX_LENGTH:
if (isPk || !options || type.toLowerCase() !== "string") {
return "";
}
return `(${options[ValidationKeys.MAX_LENGTH]})`;
case ValidationKeys.MIN_LENGTH:
return `CONSTRAINT ${prop}_min_length_check CHECK (LENGTH(${prop}) >= ${options[ValidationKeys.MIN_LENGTH]})`;
case ValidationKeys.PATTERN:
case ValidationKeys.URL:
case ValidationKeys.EMAIL:
return `CONSTRAINT ${prop}_pattern_check CHECK (${prop} ~ '${convertJsRegexToPostgres(options[ValidationKeys.PATTERN])}')`;
case ValidationKeys.TYPE:
case ValidationKeys.DATE:
return "";
case ValidationKeys.MIN:
return `CONSTRAINT ${prop}_${key}_check CHECK (${prop} >= ${options[ValidationKeys.MIN]})`;
case ValidationKeys.MAX:
return `CONSTRAINT ${prop}_${key}_check CHECK (${prop} <= ${options[ValidationKeys.MAX]})`;
case ValidationKeys.PASSWORD:
default:
throw new InternalError(`Unsupported type: ${key}`);
}
}
static parseRelationsToPostgres(prop, clazz, pk, key, options) {
const tableName = Repository.table(clazz);
const { cascade } = options;
const cascadeStr = `${cascade.update ? " ON UPDATE CASCADE" : ""}${cascade.delete ? " ON DELETE CASCADE" : ""}`;
switch (`relations${key}`) {
case PersistenceKeys.ONE_TO_ONE:
return `FOREIGN KEY (${prop}) REFERENCES ${tableName}(${pk})${cascadeStr}`;
default:
throw new InternalError(`Unsupported operation: ${key}`);
}
}
static async createTable(pool, model) {
const result = {};
const m = new model();
const tableName = Repository.table(model);
const { id } = findPrimaryKey(m);
let isPk, column;
const properties = Object.getOwnPropertyNames(m);
for (const prop of properties) {
if (typeof this[prop] === "function" ||
prop.toString().startsWith("_") ||
prop === "constructor") {
continue;
}
isPk = prop === id;
column = Repository.column(m, prop.toString());
const allDecs = Reflection.getPropertyDecorators(ValidationKeys.REFLECT, m, prop.toString(), false, true);
const decoratorData = allDecs.decorators.reduce((accum, el) => {
const { key, props } = el;
if (key === ModelKeys.TYPE && !accum[ValidationKeys.TYPE]) {
accum[ValidationKeys.TYPE] = {
customTypes: [props.name],
message: DEFAULT_ERROR_MESSAGES.TYPE,
description: "defines the accepted types for the attribute",
};
}
else if (key !== ValidationKeys.TYPE) {
// do nothing. we can only support basis ctypes at this time
accum[key] = props;
}
return accum;
}, {});
const dbDecs = Reflection.getPropertyDecorators(Repository.key("relations"), m, prop.toString(), true, true);
const query = [];
const constraints = [];
const foreignKeys = [];
let typeData = undefined;
let childClass = undefined;
let childPk;
if (Object.keys(decoratorData).length) {
typeData = decoratorData[ValidationKeys.TYPE];
if (!typeData) {
throw new Error(`Missing type information`);
}
let parsedType = this.parseTypeToPostgres(typeData.customTypes[0], isPk);
if (typeof parsedType === "string") {
parsedType = { model: parsedType };
}
let typeStr = parsedType.model;
if (typeof typeStr !== "string") {
if (Array.isArray(typeStr)) {
console.log(typeStr);
}
// continue;
// const res: Record<string, PostgresTableSpec> = await this.createTable(pool, typeStr);
try {
childClass = parsedType.model;
const m = new childClass();
childPk = findPrimaryKey(m);
typeStr = this.parseTypeToPostgres(parsedType.pkType, false, true);
await this.createTable(pool, childClass);
}
catch (e) {
throw new InternalError(`Error creating table for ${typeStr}: ${e}`);
}
// const tbl = Repository.table(typeStr);
// foreignKeys.push(`FOREIGN KEY (${prop as string}) REFERENCES ${tbl}(${pk as string})`);
}
const validationStr = this.parseValidationToPostgres(column, typeData.customTypes[0], isPk, ValidationKeys.MAX_LENGTH, decoratorData[ValidationKeys.MAX_LENGTH] || {
[ValidationKeys.MAX_LENGTH]: 255,
});
const q = `${column} ${typeStr}${validationStr}`;
if (isPk) {
query.unshift(q);
}
else {
query.push(q);
}
for (const [key, props] of Object.entries(decoratorData).filter(([k]) => ![ValidationKeys.TYPE, ValidationKeys.MAX_LENGTH].includes(k))) {
const validation = this.parseValidationToPostgres(column, typeData.customTypes[0], isPk, key, props);
if (validation.startsWith("CONSTRAINT")) {
constraints.push(validation);
}
else {
if (validation) {
query.push(validation);
}
}
}
}
if (dbDecs && dbDecs.decorators.length) {
if (!typeData)
throw new Error(`Missing type information`);
for (const decorator of dbDecs.decorators) {
const { key, props } = decorator;
const validation = this.parseRelationsToPostgres(column, childClass, childPk.id, key, props);
if (validation.startsWith("FOREIGN")) {
foreignKeys.push(validation);
}
else {
throw new InternalError(`Unsupported relation: ${key}`);
}
}
}
result[prop.toString()] = {
query: query.join(" "),
values: [],
primaryKey: isPk,
constraints: constraints,
foreignKeys: foreignKeys,
};
}
const client = await pool.connect();
const values = Object.values(result);
const query = values.map((r) => r.query).join(",\n");
const constraints = values
.filter((c) => !!c.constraints.length)
.map((r) => r.constraints)
.join(",\n");
const foreignKeys = values
.filter((c) => !!c.foreignKeys.length)
.map((r) => r.foreignKeys)
.join(",\n");
const vals = [query, constraints];
if (foreignKeys) {
vals.push(foreignKeys);
}
const queryString = `CREATE TABLE ${tableName} (${vals.filter((v) => !!v).join(",\n")})`;
try {
await client.query(queryString);
await client.query(`CREATE TRIGGER notify_changes_${tableName}
AFTER INSERT OR UPDATE OR DELETE ON ${tableName}
FOR EACH ROW
EXECUTE FUNCTION notify_table_changes();`);
}
catch (e) {
throw this.parseError(e);
}
finally {
client.release();
}
return result;
}
static async getCurrentUser(pool) {
const client = await pool.connect();
const queryString = `SELECT CURRENT_USER;`;
try {
const result = await client.query({
name: `get-current-user`,
text: queryString,
});
return result.rows[0].current_user;
}
catch (e) {
throw this.parseError(e);
}
finally {
client.release();
}
}
static decoration() {
const createdByKey = Repository.key(PersistenceKeys.CREATED_BY);
const updatedByKey = Repository.key(PersistenceKeys.UPDATED_BY);
const pkKey = Repository.key(DBKeys.ID);
const uniqueKey = Repository.key(DBKeys.UNIQUE);
Decoration.flavouredAs(PostgresFlavour)
.for(pkKey)
.define(required(), readonly(), propMetadata(pkKey, DefaultSequenceOptions))
.apply();
Decoration.flavouredAs(PostgresFlavour)
.for(uniqueKey)
.define(propMetadata(uniqueKey, {}))
.apply();
Decoration.flavouredAs(PostgresFlavour)
.for(createdByKey)
.define(onCreate(createdByOnPostgresCreateUpdate), propMetadata(createdByKey, {}))
.apply();
Decoration.flavouredAs(PostgresFlavour)
.for(updatedByKey)
.define(onCreate(createdByOnPostgresCreateUpdate), propMetadata(updatedByKey, {}))
.apply();
}
}
__decorate([
final(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", PostgresStatement)
], PostgresAdapter.prototype, "Statement", null);
__decorate([
final(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], PostgresAdapter.prototype, "Sequence", null);
__decorate([
final(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], PostgresAdapter.prototype, "index", null);
__decorate([
final(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Boolean]),
__metadata("design:returntype", Promise)
], PostgresAdapter.prototype, "raw", null);
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBLE9BQU8sRUFDTCxPQUFPLEVBR1AsZUFBZSxFQUNmLGVBQWUsRUFDZixVQUFVLEVBRVYsc0JBQXNCLEVBQ3RCLEtBQUssR0FDTixNQUFNLGdCQUFnQixDQUFDO0FBQ3hCLE9BQU8sRUFBRSxlQUFlLEVBQUUsa0JBQWtCLEVBQUUsdUJBQW9CO0FBQ2xFLE9BQU8sRUFDTCxTQUFTLEVBQ1QsYUFBYSxFQUViLE1BQU0sRUFDTixjQUFjLEVBQ2QsYUFBYSxFQUNiLGFBQWEsRUFDYixRQUFRLEVBRVIsUUFBUSxHQUNULE1BQU0seUJBQXlCLENBQUM7QUFDakMsT0FBTyxrQkFBa0IsQ0FBQztBQUMxQixPQUFPLEVBRUwsVUFBVSxFQUNWLHNCQUFzQixFQUt0QixLQUFLLEVBQ0wsU0FBUyxFQUVULFlBQVksRUFDWixRQUFRLEVBRVIsY0FBYyxHQUVmLE1BQU0sZ0NBQWdDLENBQUM7QUFDeEMsT0FBTyxFQUFFLFVBQVUsRUFBRSxvQkFBaUI7QUFDdEMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLHlCQUFnQjtBQUM1QyxPQUFPLEVBQUUsSUFBSSxFQUF1QyxNQUFNLElBQUksQ0FBQztBQUMvRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsNkJBQW9CO0FBQy9DLE9BQU8sRUFBRSxlQUFlLEVBQUUsMkJBQWtCO0FBRTVDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsZ0NBQTZCO0FBQzFELE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUM1QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsOEJBQTJCO0FBQ3RELE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxtQkFBZ0I7QUFFbkQsTUFBTSxDQUFDLEtBQUssVUFBVSwrQkFBK0Is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