diff --git a/package-lock.json b/package-lock.json index 40f61d8..5aab674 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hirosystems/api-toolkit", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@hirosystems/api-toolkit", - "version": "1.0.0", + "version": "1.1.0", "license": "Apache 2.0", "dependencies": { "node-pg-migrate": "^6.2.2", diff --git a/package.json b/package.json index da4cf3b..b5ad485 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hirosystems/api-toolkit", - "version": "1.0.0", + "version": "1.1.0", "description": "API development toolkit", "main": "./dist/index.js", "typings": "./dist/index.d.ts", diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..f40375d --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,7 @@ +export const isDevEnv = process.env.NODE_ENV === 'development'; +export const isTestEnv = process.env.NODE_ENV === 'test'; +export const isProdEnv = + process.env.NODE_ENV === 'production' || + process.env.NODE_ENV === 'prod' || + !process.env.NODE_ENV || + (!isTestEnv && !isDevEnv); diff --git a/src/index.ts b/src/index.ts index 33b34e9..dd78b72 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +export * from './helpers'; export * from './logger'; export * from './postgres'; export * from './server-version'; diff --git a/src/postgres/base-pg-store.ts b/src/postgres/base-pg-store.ts index 8e0256b..5327ee0 100644 --- a/src/postgres/base-pg-store.ts +++ b/src/postgres/base-pg-store.ts @@ -1,5 +1,6 @@ import { AsyncLocalStorage } from 'async_hooks'; import { PgSqlClient } from '.'; +import { isProdEnv } from '../helpers'; /** * AsyncLocalStorage used to determine if the current async context is running inside a SQL @@ -73,4 +74,44 @@ export class BasePgStore { ): Promise> { return this.sqlTransaction(callback, false); } + + /** + * Refreshes a materialized view concurrently depending on the current environment. + * @param viewName - Materialized view name + */ + async refreshMaterializedView(viewName: string): Promise { + await this.sql`REFRESH MATERIALIZED VIEW ${ + isProdEnv ? this.sql`CONCURRENTLY` : this.sql`` + } ${this.sql(viewName)}`; + } +} + +/** + * Base module that extends PgStore functionality and allows organizing queries in separate files. + */ +export class BasePgStoreModule { + private readonly parent: BasePgStore; + + constructor(db: BasePgStore) { + this.parent = db; + } + + protected get sql(): PgSqlClient { + return this.parent.sql; + } + + async sqlTransaction( + callback: (sql: PgSqlClient) => T | Promise, + readOnly = true + ): Promise> { + return this.parent.sqlTransaction(callback, readOnly); + } + async sqlWriteTransaction( + callback: (sql: PgSqlClient) => T | Promise + ): Promise> { + return this.sqlTransaction(callback, false); + } + async refreshMaterializedView(viewName: string): Promise { + return this.parent.refreshMaterializedView(viewName); + } } diff --git a/src/postgres/connection.ts b/src/postgres/connection.ts index 50986bc..3e89b41 100644 --- a/src/postgres/connection.ts +++ b/src/postgres/connection.ts @@ -15,7 +15,7 @@ export type PgConnectionVars = { user?: string; password?: string; host?: string; - port?: string; + port?: number; schema?: string; ssl?: boolean; application_name?: string; @@ -24,8 +24,13 @@ export type PgConnectionVars = { export type PgConnectionArgs = PgConnectionUri | PgConnectionVars; /** Postgres connection options */ export type PgConnectionOptions = { + /** Time to wait before automatically closing an idle connection (s) */ idleTimeout?: number; + /** Maximum allowed duration of any statement (ms) */ + statementTimeout?: number; + /** Maximum time a connection can exist (s) */ maxLifetime?: number; + /** Max number of connections */ poolMax?: number; }; @@ -49,7 +54,7 @@ export function standardizedConnectionArgs( user: process.env.PGUSER, password: process.env.PGPASSWORD, host: process.env.PGHOST, - port: process.env.PGPORT ?? '5432', + port: parseInt(process.env.PGPORT ?? '5432'), ssl: true, application_name: `${appName}:${appUsage}`, }; @@ -150,7 +155,7 @@ export function getPostgres({ user: args.user, password: args.password, host: args.host, - port: args.port ? parseInt(args.port) : undefined, + port: args.port, ssl: args.ssl, idle_timeout: connectionConfig?.idleTimeout, max_lifetime: connectionConfig?.maxLifetime, @@ -159,6 +164,7 @@ export function getPostgres({ connection: { application_name: args.application_name, search_path: args.schema, + statement_timeout: connectionConfig?.statementTimeout?.toString(), }, }); } diff --git a/src/postgres/migrations.ts b/src/postgres/migrations.ts index ae1a10f..2f40d35 100644 --- a/src/postgres/migrations.ts +++ b/src/postgres/migrations.ts @@ -25,7 +25,7 @@ export async function runMigrations( ? args : { host: args.host, - port: args.port ? parseInt(args.port) : undefined, + port: args.port, user: args.user, password: args.password, database: args.database,