UNPKG

@decaf-ts/for-postgres

Version:
886 lines 126 kB
"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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