@decaf-ts/for-postgres
Version:
template for ts projects
886 lines • 126 kB
JavaScript
"use strict";
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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostgresAdapter = void 0;
exports.createdByOnPostgresCreateUpdate = createdByOnPostgresCreateUpdate;
const core_1 = require("@decaf-ts/core");
const constants_1 = require("./constants.cjs");
const db_decorators_1 = require("@decaf-ts/db-decorators");
require("reflect-metadata");
const decorator_validation_1 = require("@decaf-ts/decorator-validation");
const errors_1 = require("./errors.cjs");
const query_1 = require("./query/index.cjs");
const pg_1 = require("pg");
const sequences_1 = require("./sequences/index.cjs");
const indexes_1 = require("./indexes/index.cjs");
const reflection_1 = require("@decaf-ts/reflection");
const PostgresRepository_1 = require("./PostgresRepository.cjs");
const logging_1 = require("@decaf-ts/logging");
const PostgresDispatch_1 = require("./PostgresDispatch.cjs");
const utils_1 = require("./utils.cjs");
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 db_decorators_1.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
*/
class PostgresAdapter extends core_1.Adapter {
constructor(pool, alias) {
super(pool, constants_1.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 = (0, db_decorators_1.findPrimaryKey)(new model()).id;
newObj.ignoredValidationProperties = (f.ignoredValidationProperties ? f.ignoredValidationProperties : []).concat(pk);
}
return Object.assign(f, newObj);
}
Dispatch() {
return new PostgresDispatch_1.PostgresDispatch();
}
repository() {
return PostgresRepository_1.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 query_1.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 sequences_1.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 = core_1.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 = (0, indexes_1.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 === core_1.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" ? decorator_validation_1.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[core_1.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 db_decorators_1.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 db_decorators_1.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 db_decorators_1.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 db_decorators_1.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 db_decorators_1.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 db_decorators_1.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 db_decorators_1.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 db_decorators_1.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(constants_1.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 db_decorators_1.BaseError)
return err;
const code = typeof err === "string" ? err : err.message;
if (code.match(/duplicate key|already exists/g))
return new db_decorators_1.ConflictError(code);
if (code.match(/does not exist|not found/g))
return new db_decorators_1.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 db_decorators_1.ConflictError(reason);
// Object not found errors
case "42P01": // undefined_table
case "42703": // undefined_column
return new db_decorators_1.NotFoundError(reason);
// Invalid object definition
case "42P16": // invalid_table_definition
return new errors_1.IndexError(err);
// Connection errors
default:
if (code.toString().match(/ECONNREFUSED/g))
return new core_1.ConnectionError(err);
return new db_decorators_1.InternalError(err);
}
}
static async connect(config) {
return new pg_1.Pool(config);
}
static async createDatabase(pool, dbName) {
const log = logging_1.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_1.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 = decorator_validation_1.Model.get(type);
if (m) {
const mm = new m();
const type = reflection_1.Reflection.getTypeFromDecorator(mm, (0, db_decorators_1.findPrimaryKey)(mm).id);
return {
model: m,
pkType: type,
};
}
throw new db_decorators_1.InternalError(`Unsupported type: ${type}`);
}
}
}
static parseValidationToPostgres(prop, type, isPk, key, options) {
switch (key) {
case decorator_validation_1.ValidationKeys.REQUIRED:
return "NOT NULL";
case decorator_validation_1.ValidationKeys.MAX_LENGTH:
if (isPk || !options || type.toLowerCase() !== "string") {
return "";
}
return `(${options[decorator_validation_1.ValidationKeys.MAX_LENGTH]})`;
case decorator_validation_1.ValidationKeys.MIN_LENGTH:
return `CONSTRAINT ${prop}_min_length_check CHECK (LENGTH(${prop}) >= ${options[decorator_validation_1.ValidationKeys.MIN_LENGTH]})`;
case decorator_validation_1.ValidationKeys.PATTERN:
case decorator_validation_1.ValidationKeys.URL:
case decorator_validation_1.ValidationKeys.EMAIL:
return `CONSTRAINT ${prop}_pattern_check CHECK (${prop} ~ '${(0, utils_1.convertJsRegexToPostgres)(options[decorator_validation_1.ValidationKeys.PATTERN])}')`;
case decorator_validation_1.ValidationKeys.TYPE:
case decorator_validation_1.ValidationKeys.DATE:
return "";
case decorator_validation_1.ValidationKeys.MIN:
return `CONSTRAINT ${prop}_${key}_check CHECK (${prop} >= ${options[decorator_validation_1.ValidationKeys.MIN]})`;
case decorator_validation_1.ValidationKeys.MAX:
return `CONSTRAINT ${prop}_${key}_check CHECK (${prop} <= ${options[decorator_validation_1.ValidationKeys.MAX]})`;
case decorator_validation_1.ValidationKeys.PASSWORD:
default:
throw new db_decorators_1.InternalError(`Unsupported type: ${key}`);
}
}
static parseRelationsToPostgres(prop, clazz, pk, key, options) {
const tableName = core_1.Repository.table(clazz);
const { cascade } = options;
const cascadeStr = `${cascade.update ? " ON UPDATE CASCADE" : ""}${cascade.delete ? " ON DELETE CASCADE" : ""}`;
switch (`relations${key}`) {
case core_1.PersistenceKeys.ONE_TO_ONE:
return `FOREIGN KEY (${prop}) REFERENCES ${tableName}(${pk})${cascadeStr}`;
default:
throw new db_decorators_1.InternalError(`Unsupported operation: ${key}`);
}
}
static async createTable(pool, model) {
const result = {};
const m = new model();
const tableName = core_1.Repository.table(model);
const { id } = (0, db_decorators_1.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 = core_1.Repository.column(m, prop.toString());
const allDecs = reflection_1.Reflection.getPropertyDecorators(decorator_validation_1.ValidationKeys.REFLECT, m, prop.toString(), false, true);
const decoratorData = allDecs.decorators.reduce((accum, el) => {
const { key, props } = el;
if (key === decorator_validation_1.ModelKeys.TYPE && !accum[decorator_validation_1.ValidationKeys.TYPE]) {
accum[decorator_validation_1.ValidationKeys.TYPE] = {
customTypes: [props.name],
message: decorator_validation_1.DEFAULT_ERROR_MESSAGES.TYPE,
description: "defines the accepted types for the attribute",
};
}
else if (key !== decorator_validation_1.ValidationKeys.TYPE) {
// do nothing. we can only support basis ctypes at this time
accum[key] = props;
}
return accum;
}, {});
const dbDecs = reflection_1.Reflection.getPropertyDecorators(core_1.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[decorator_validation_1.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 = (0, db_decorators_1.findPrimaryKey)(m);
typeStr = this.parseTypeToPostgres(parsedType.pkType, false, true);
await this.createTable(pool, childClass);
}
catch (e) {
throw new db_decorators_1.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, decorator_validation_1.ValidationKeys.MAX_LENGTH, decoratorData[decorator_validation_1.ValidationKeys.MAX_LENGTH] || {
[decorator_validation_1.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]) => ![decorator_validation_1.ValidationKeys.TYPE, decorator_validation_1.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 db_decorators_1.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 = core_1.Repository.key(core_1.PersistenceKeys.CREATED_BY);
const updatedByKey = core_1.Repository.key(core_1.PersistenceKeys.UPDATED_BY);
const pkKey = core_1.Repository.key(db_decorators_1.DBKeys.ID);
const uniqueKey = core_1.Repository.key(db_decorators_1.DBKeys.UNIQUE);
decorator_validation_1.Decoration.flavouredAs(constants_1.PostgresFlavour)
.for(pkKey)
.define((0, decorator_validation_1.required)(), (0, db_decorators_1.readonly)(), (0, decorator_validation_1.propMetadata)(pkKey, core_1.DefaultSequenceOptions))
.apply();
decorator_validation_1.Decoration.flavouredAs(constants_1.PostgresFlavour)
.for(uniqueKey)
.define((0, decorator_validation_1.propMetadata)(uniqueKey, {}))
.apply();
decorator_validation_1.Decoration.flavouredAs(constants_1.PostgresFlavour)
.for(createdByKey)
.define((0, db_decorators_1.onCreate)(createdByOnPostgresCreateUpdate), (0, decorator_validation_1.propMetadata)(createdByKey, {}))
.apply();
decorator_validation_1.Decoration.flavouredAs(constants_1.PostgresFlavour)
.for(updatedByKey)
.define((0, db_decorators_1.onCreate)(createdByOnPostgresCreateUpdate), (0, decorator_validation_1.propMetadata)(updatedByKey, {}))
.apply();
}
}
exports.PostgresAdapter = PostgresAdapter;
__decorate([
(0, core_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", query_1.PostgresStatement)
], PostgresAdapter.prototype, "Statement", null);
__decorate([
(0, core_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], PostgresAdapter.prototype, "Sequence", null);
__decorate([
(0, core_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], PostgresAdapter.prototype, "index", null);
__decorate([
(0, core_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Boolean]),
__metadata("design:returntype", Promise)
], PostgresAdapter.prototype, "raw", null);
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9hZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQXNEQSwwRUFvQkM7QUExRUQseUNBVXdCO0FBQ3hCLCtDQUFrRTtBQUNsRSwyREFXaUM7QUFDakMsNEJBQTBCO0FBQzFCLHlFQWdCd0M7QUFDeEMseUNBQXNDO0FBQ3RDLDZDQUE0QztBQUM1QywyQkFBK0Q7QUFDL0QscURBQStDO0FBQy9DLGlEQUE0QztBQUU1QyxxREFBa0Q7QUFDbEQsaUVBQTBEO0FBQzFELCtDQUE0QztBQUM1Qyw2REFBc0Q7QUFDdEQsdUNBQW1EO0FBRTVDLEtBQUssVUFBVSwrQkFBK0IsQ0FNbkQsT0FBK0IsRUFDL0IsSUFBTyxFQUNQLEdBQVksRUFDWixLQUFRO0lBRVIsSUFBSSxDQUFDO1FBQ0gsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNqQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsSUFBcUIsQ0FBQztRQUNuQyw2REFBNkQ7SUFDL0QsQ0FBQztJQUFDLE9BQU8sQ0FBVSxFQUFFLENBQUM7UUFDcEIsTUFBTSxJQUFJLDZCQUFhLENBQ3JCLGdFQUFnRSxDQUNqRSxDQUFDO0lBQ0osQ0FBQztBQUNILENBQUM7QUFFRDs7Ozs7Ozs7OztHQVVHO0FBQ0gsTUFBYSxlQUFnQixTQUFRLGNBS3BDO0lBQ0MsWUFBWSxJQUFVLEVBQUUsS0FBYztRQUNwQyxLQUFLLENBQUMsSUFBSSxFQUFFLDJCQUFlLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUVrQixLQUFLLENBQUMsS0FBSyxDQUM1QixTQUF3QixFQUN4QixLQUFxQixFQUNyQixLQUE2QjtRQUU3QixNQUFNLENBQUMsR0FBRyxNQUFNLEtBQUssQ0FBQyxLQUFLLENBQUMsU0FBUyxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQztRQUNyRCxNQUFN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