UNPKG

@decaf-ts/for-postgres

Version:
881 lines 125 kB
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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