@devmoods/express-extras 0.40.1 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  import pg, { type PoolClient, type PoolConfig, type QueryResult, type QueryResultRow } from 'pg';
2
2
  export declare const escapePostgresIdentifier: typeof pg.escapeIdentifier;
3
3
  export declare const escapePostgresLiteral: typeof pg.escapeLiteral;
4
- export interface PostgresOptions extends PoolConfig {
4
+ export declare const formatSql: (statement: SqlStatement | string) => string;
5
+ export interface PostgresOptions extends PoolConfig, ConnectionOptions {
5
6
  onError?: (error: Error, client: any) => void;
6
7
  url?: PoolConfig['connectionString'];
7
8
  }
@@ -15,14 +16,18 @@ export declare class IntegrityError extends Error {
15
16
  constructor(message: string);
16
17
  }
17
18
  type Value = null | string | number | Date;
19
+ export interface ConnectionOptions {
20
+ onLogStatement?: (statement: SqlStatement | string, values?: unknown[]) => void;
21
+ }
18
22
  export declare class Connection {
23
+ private options;
19
24
  client: PoolClient;
20
- constructor(client: PoolClient);
21
- query<T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]): Promise<QueryResult<T>>;
22
- all<T>(text: string | SQLStatement, values?: Value[]): Promise<T[]>;
23
- insert<T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]): Promise<QueryResult<T>>;
24
- first<T>(text: string | SQLStatement, values?: Value[]): Promise<T | null>;
25
- get<T>(text: string | SQLStatement, values?: Value[]): Promise<T>;
25
+ constructor(client: PoolClient, options: ConnectionOptions);
26
+ query<T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]): Promise<QueryResult<T>>;
27
+ all<T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]): Promise<T[]>;
28
+ insert<T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]): Promise<QueryResult<T>>;
29
+ first<T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]): Promise<T | null>;
30
+ get<T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]): Promise<T>;
26
31
  }
27
32
  export declare class Transaction extends Connection {
28
33
  }
@@ -34,27 +39,27 @@ export declare class Transaction extends Connection {
34
39
  * @param config
35
40
  */
36
41
  export declare function createPostgres({ onError, url, ...config }?: PostgresOptions): {
37
- insert: <T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]) => Promise<QueryResult<T>>;
38
- query: <T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]) => Promise<QueryResult<T>>;
39
- first: <T>(text: string | SQLStatement, values?: Value[]) => Promise<T | null>;
40
- all: <T>(text: string | SQLStatement, values?: Value[]) => Promise<T[]>;
41
- get: <T>(text: string | SQLStatement, values?: Value[]) => Promise<T>;
42
+ insert: <T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]) => Promise<QueryResult<T>>;
43
+ query: <T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]) => Promise<QueryResult<T>>;
44
+ first: <T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]) => Promise<T | null>;
45
+ all: <T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]) => Promise<T[]>;
46
+ get: <T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]) => Promise<T>;
42
47
  withConnection: <T>(callback: (connection: Connection) => Promise<T>) => Promise<T>;
43
48
  transaction: <T>(callback: (connection: Transaction) => Promise<T> | T) => Promise<T>;
44
49
  instance: pg.Pool;
45
50
  clear: (force?: boolean) => Promise<void>;
46
51
  end(): Promise<void>;
47
52
  };
48
- declare class SQLStatement {
53
+ export declare class SqlStatement {
49
54
  stringsAndValues: [string, unknown | undefined][];
50
55
  constructor(strings: string[], values: unknown[]);
51
56
  _extract(strings: string[], values: unknown[]): void;
52
- append(statement: SQLStatement | string): this;
57
+ append(statement: SqlStatement | string): this;
53
58
  get values(): ({} | null)[];
54
59
  get text(): string;
55
60
  }
56
61
  export declare const sql: {
57
- (strings: TemplateStringsArray, ...values: unknown[]): SQLStatement;
62
+ (strings: TemplateStringsArray, ...values: unknown[]): SqlStatement;
58
63
  identifier(name: string): {
59
64
  name: string;
60
65
  raw: boolean;
@@ -63,14 +68,14 @@ export declare const sql: {
63
68
  name: string;
64
69
  raw: boolean;
65
70
  };
66
- literal(name: string): {
71
+ literal(name: string | null): {
67
72
  name: string;
68
73
  raw: boolean;
69
74
  };
70
75
  spreadUpdate: typeof spreadUpdate;
71
76
  spreadInsert: typeof spreadInsert;
72
77
  };
73
- declare function spreadUpdate(data: Record<string, any>): SQLStatement;
74
- declare function spreadInsert<TData extends Record<string, any>>(data: TData | TData[]): SQLStatement;
75
- export declare function joinStatements(statements: SQLStatement[], separator: string): SQLStatement;
78
+ declare function spreadUpdate(data: Record<string, any>): SqlStatement;
79
+ declare function spreadInsert<TData extends Record<string, any>>(data: TData | TData[]): SqlStatement;
80
+ export declare function joinStatements(statements: SqlStatement[], separator: string): SqlStatement;
76
81
  export {};
package/dist/postgres.js CHANGED
@@ -1,7 +1,17 @@
1
1
  import pg, {} from 'pg';
2
+ import { format } from 'sql-formatter';
2
3
  import { readEnv } from './utils.js';
3
4
  export const escapePostgresIdentifier = pg.escapeIdentifier;
4
5
  export const escapePostgresLiteral = pg.escapeLiteral;
6
+ export const formatSql = (statement) => {
7
+ const value = statement instanceof SqlStatement ? statement.text : statement;
8
+ return format(value, {
9
+ language: 'postgresql',
10
+ keywordCase: 'upper',
11
+ tabWidth: 2,
12
+ useTabs: false,
13
+ });
14
+ };
5
15
  export class DoesNotExistError extends Error {
6
16
  constructor(message) {
7
17
  super(message);
@@ -21,11 +31,13 @@ export class IntegrityError extends Error {
21
31
  }
22
32
  }
23
33
  export class Connection {
24
- constructor(client) {
34
+ constructor(client, options) {
35
+ this.options = options;
25
36
  this.client = client;
26
37
  }
27
38
  async query(text, values) {
28
39
  try {
40
+ this.options.onLogStatement?.(text, values);
29
41
  return await this.client.query(text, values);
30
42
  }
31
43
  catch (error) {
@@ -36,7 +48,7 @@ export class Connection {
36
48
  }
37
49
  }
38
50
  async all(text, values) {
39
- const result = await this.client.query(text, values);
51
+ const result = await this.query(text, values);
40
52
  if (result.rows) {
41
53
  return result.rows;
42
54
  /* c8 ignore next 2*/
@@ -47,11 +59,11 @@ export class Connection {
47
59
  return await this.query(text, values);
48
60
  }
49
61
  async first(text, values) {
50
- const result = await this.client.query(text, values);
62
+ const result = await this.query(text, values);
51
63
  return result.rows && result.rows.length ? result.rows[0] : null;
52
64
  }
53
65
  async get(text, values) {
54
- const result = await this.client.query(text, values);
66
+ const result = await this.query(text, values);
55
67
  if (result.rowCount === 0) {
56
68
  throw new DoesNotExistError('Row matching query not found');
57
69
  }
@@ -78,7 +90,7 @@ export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...conf
78
90
  async function withConnection(callback) {
79
91
  const client = await pool.connect();
80
92
  try {
81
- const result = await callback(new Connection(client));
93
+ const result = await callback(new Connection(client, { onLogStatement: config.onLogStatement }));
82
94
  return result;
83
95
  }
84
96
  finally {
@@ -104,7 +116,7 @@ export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...conf
104
116
  const client = await pool.connect();
105
117
  try {
106
118
  await client.query('BEGIN');
107
- const result = await callback(new Transaction(client));
119
+ const result = await callback(new Transaction(client, { onLogStatement: config.onLogStatement }));
108
120
  await client.query('COMMIT');
109
121
  return result;
110
122
  }
@@ -160,7 +172,7 @@ export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...conf
160
172
  const isRaw = (value) => {
161
173
  return (typeof value === 'object' && value !== null && 'raw' in value && !!value.raw);
162
174
  };
163
- class SQLStatement {
175
+ export class SqlStatement {
164
176
  constructor(strings, values) {
165
177
  this.stringsAndValues = [];
166
178
  this._extract(strings, values);
@@ -172,7 +184,7 @@ class SQLStatement {
172
184
  if (isRaw(value)) {
173
185
  this.stringsAndValues.push([string + value.name, undefined]);
174
186
  }
175
- else if (value instanceof SQLStatement) {
187
+ else if (value instanceof SqlStatement) {
176
188
  this.stringsAndValues.push([string, undefined]);
177
189
  this.stringsAndValues.push(...value.stringsAndValues);
178
190
  }
@@ -182,7 +194,7 @@ class SQLStatement {
182
194
  }
183
195
  }
184
196
  append(statement) {
185
- if (statement instanceof SQLStatement) {
197
+ if (statement instanceof SqlStatement) {
186
198
  this.stringsAndValues.push(...statement.stringsAndValues);
187
199
  }
188
200
  else {
@@ -211,14 +223,16 @@ class SQLStatement {
211
223
  }
212
224
  }
213
225
  export const sql = (strings, ...values) => {
214
- return new SQLStatement(Array.from(strings), values);
226
+ return new SqlStatement(Array.from(strings), values);
215
227
  };
216
228
  function spreadUpdate(data) {
217
229
  const entries = Object.entries(data);
218
230
  if (entries.length === 0) {
219
231
  return sql ``;
220
232
  }
221
- return joinStatements(entries.map(([key, value]) => sql `${sql.identifier(key)} = ${value}`), ', ');
233
+ return joinStatements(entries
234
+ .filter(([, value]) => typeof value !== 'undefined')
235
+ .map(([key, value]) => sql `${sql.identifier(key)} = ${value == null ? sql.raw('NULL') : value}`), ', ');
222
236
  }
223
237
  function spreadInsert(data) {
224
238
  const rows = Array.isArray(data) ? data : [data];
@@ -257,6 +271,9 @@ sql.raw = (name) => {
257
271
  * Inject a literal value into the template string
258
272
  */
259
273
  sql.literal = (name) => {
274
+ if (name === null) {
275
+ return { name: 'NULL', raw: true };
276
+ }
260
277
  return { name: escapePostgresLiteral(name), raw: true };
261
278
  };
262
279
  sql.spreadUpdate = spreadUpdate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devmoods/express-extras",
3
- "version": "0.40.1",
3
+ "version": "0.41.0",
4
4
  "description": "Kubernetes-ready utilities for rapid Node.js application development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,31 +20,31 @@
20
20
  "devDependencies": {
21
21
  "@devmoods/eslint-config": "^1.15.0",
22
22
  "@types/koa-compose": "^3.2.8",
23
- "@types/node": "22.9.3",
23
+ "@types/node": "22.10.1",
24
24
  "@types/supertest": "^6.0.2",
25
- "@typescript-eslint/eslint-plugin": "^8.15.0",
26
- "@typescript-eslint/parser": "^8.15.0",
27
- "@vitest/coverage-v8": "^2.1.5",
25
+ "@typescript-eslint/eslint-plugin": "^8.16.0",
26
+ "@typescript-eslint/parser": "^8.16.0",
27
+ "@vitest/coverage-v8": "^2.1.6",
28
28
  "cookiejar": "^2.1.4",
29
29
  "eslint": "^8.57.0",
30
30
  "eslint-plugin-import": "^2.31.0",
31
31
  "eslint-plugin-prettier": "^5.2.1",
32
32
  "faktory-worker": "^4.6.0",
33
- "prettier": "3.3.3",
33
+ "prettier": "3.4.1",
34
34
  "supertest": "7.0.0",
35
35
  "ts-node": "10.9.2",
36
36
  "tsx": "^4.19.2",
37
37
  "typescript": "5.7.2",
38
- "vitest": "^2.1.5",
38
+ "vitest": "^2.1.6",
39
39
  "vitest-fail-on-console": "^0.7.1"
40
40
  },
41
41
  "dependencies": {
42
42
  "@devmoods/fetch": "^4.0.1",
43
- "@sentry/node": "^8.40.0",
44
- "@sentry/profiling-node": "^8.40.0",
43
+ "@sentry/node": "^8.41.0",
44
+ "@sentry/profiling-node": "^8.41.0",
45
45
  "@types/bcrypt": "^5.0.2",
46
46
  "@types/body-parser": "^1.19.5",
47
- "@types/cookie-parser": "^1.4.7",
47
+ "@types/cookie-parser": "^1.4.8",
48
48
  "@types/express": "^5.0.0",
49
49
  "@types/jsonwebtoken": "^9.0.7",
50
50
  "@types/nodemailer": "^6.4.17",
@@ -65,6 +65,7 @@
65
65
  "nodemailer": "^6.9.16",
66
66
  "pg": "^8.13.1",
67
67
  "redis": "^4.7.0",
68
+ "sql-formatter": "^15.4.6",
68
69
  "uuid": "^11.0.3"
69
70
  },
70
71
  "prettier": {