@devmoods/express-extras 0.40.0 → 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,5 +1,8 @@
1
- import pg, { escapeIdentifier, escapeLiteral, type PoolClient, type PoolConfig, type QueryResult, type QueryResultRow } from 'pg';
2
- export interface PostgresOptions extends PoolConfig {
1
+ import pg, { type PoolClient, type PoolConfig, type QueryResult, type QueryResultRow } from 'pg';
2
+ export declare const escapePostgresIdentifier: typeof pg.escapeIdentifier;
3
+ export declare const escapePostgresLiteral: typeof pg.escapeLiteral;
4
+ export declare const formatSql: (statement: SqlStatement | string) => string;
5
+ export interface PostgresOptions extends PoolConfig, ConnectionOptions {
3
6
  onError?: (error: Error, client: any) => void;
4
7
  url?: PoolConfig['connectionString'];
5
8
  }
@@ -13,14 +16,18 @@ export declare class IntegrityError extends Error {
13
16
  constructor(message: string);
14
17
  }
15
18
  type Value = null | string | number | Date;
19
+ export interface ConnectionOptions {
20
+ onLogStatement?: (statement: SqlStatement | string, values?: unknown[]) => void;
21
+ }
16
22
  export declare class Connection {
23
+ private options;
17
24
  client: PoolClient;
18
- constructor(client: PoolClient);
19
- query<T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]): Promise<QueryResult<T>>;
20
- all<T>(text: string | SQLStatement, values?: Value[]): Promise<T[]>;
21
- insert<T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]): Promise<QueryResult<T>>;
22
- first<T>(text: string | SQLStatement, values?: Value[]): Promise<T | null>;
23
- 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>;
24
31
  }
25
32
  export declare class Transaction extends Connection {
26
33
  }
@@ -32,27 +39,27 @@ export declare class Transaction extends Connection {
32
39
  * @param config
33
40
  */
34
41
  export declare function createPostgres({ onError, url, ...config }?: PostgresOptions): {
35
- insert: <T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]) => Promise<QueryResult<T>>;
36
- query: <T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]) => Promise<QueryResult<T>>;
37
- first: <T>(text: string | SQLStatement, values?: Value[]) => Promise<T | null>;
38
- all: <T>(text: string | SQLStatement, values?: Value[]) => Promise<T[]>;
39
- 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>;
40
47
  withConnection: <T>(callback: (connection: Connection) => Promise<T>) => Promise<T>;
41
48
  transaction: <T>(callback: (connection: Transaction) => Promise<T> | T) => Promise<T>;
42
49
  instance: pg.Pool;
43
50
  clear: (force?: boolean) => Promise<void>;
44
51
  end(): Promise<void>;
45
52
  };
46
- declare class SQLStatement {
53
+ export declare class SqlStatement {
47
54
  stringsAndValues: [string, unknown | undefined][];
48
55
  constructor(strings: string[], values: unknown[]);
49
56
  _extract(strings: string[], values: unknown[]): void;
50
- append(statement: SQLStatement | string): this;
57
+ append(statement: SqlStatement | string): this;
51
58
  get values(): ({} | null)[];
52
59
  get text(): string;
53
60
  }
54
61
  export declare const sql: {
55
- (strings: TemplateStringsArray, ...values: unknown[]): SQLStatement;
62
+ (strings: TemplateStringsArray, ...values: unknown[]): SqlStatement;
56
63
  identifier(name: string): {
57
64
  name: string;
58
65
  raw: boolean;
@@ -61,14 +68,14 @@ export declare const sql: {
61
68
  name: string;
62
69
  raw: boolean;
63
70
  };
64
- literal(name: string): {
71
+ literal(name: string | null): {
65
72
  name: string;
66
73
  raw: boolean;
67
74
  };
68
75
  spreadUpdate: typeof spreadUpdate;
69
76
  spreadInsert: typeof spreadInsert;
70
77
  };
71
- declare function spreadUpdate(data: Record<string, any>): SQLStatement;
72
- declare function spreadInsert<TData extends Record<string, any>>(data: TData | TData[]): SQLStatement;
73
- export declare function joinStatements(statements: SQLStatement[], separator: string): SQLStatement;
74
- export { escapeIdentifier, escapeLiteral };
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;
81
+ export {};
package/dist/postgres.js CHANGED
@@ -1,5 +1,17 @@
1
- import pg, { escapeIdentifier, escapeLiteral, } from 'pg';
1
+ import pg, {} from 'pg';
2
+ import { format } from 'sql-formatter';
2
3
  import { readEnv } from './utils.js';
4
+ export const escapePostgresIdentifier = pg.escapeIdentifier;
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
+ };
3
15
  export class DoesNotExistError extends Error {
4
16
  constructor(message) {
5
17
  super(message);
@@ -19,11 +31,13 @@ export class IntegrityError extends Error {
19
31
  }
20
32
  }
21
33
  export class Connection {
22
- constructor(client) {
34
+ constructor(client, options) {
35
+ this.options = options;
23
36
  this.client = client;
24
37
  }
25
38
  async query(text, values) {
26
39
  try {
40
+ this.options.onLogStatement?.(text, values);
27
41
  return await this.client.query(text, values);
28
42
  }
29
43
  catch (error) {
@@ -34,7 +48,7 @@ export class Connection {
34
48
  }
35
49
  }
36
50
  async all(text, values) {
37
- const result = await this.client.query(text, values);
51
+ const result = await this.query(text, values);
38
52
  if (result.rows) {
39
53
  return result.rows;
40
54
  /* c8 ignore next 2*/
@@ -45,11 +59,11 @@ export class Connection {
45
59
  return await this.query(text, values);
46
60
  }
47
61
  async first(text, values) {
48
- const result = await this.client.query(text, values);
62
+ const result = await this.query(text, values);
49
63
  return result.rows && result.rows.length ? result.rows[0] : null;
50
64
  }
51
65
  async get(text, values) {
52
- const result = await this.client.query(text, values);
66
+ const result = await this.query(text, values);
53
67
  if (result.rowCount === 0) {
54
68
  throw new DoesNotExistError('Row matching query not found');
55
69
  }
@@ -76,7 +90,7 @@ export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...conf
76
90
  async function withConnection(callback) {
77
91
  const client = await pool.connect();
78
92
  try {
79
- const result = await callback(new Connection(client));
93
+ const result = await callback(new Connection(client, { onLogStatement: config.onLogStatement }));
80
94
  return result;
81
95
  }
82
96
  finally {
@@ -102,7 +116,7 @@ export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...conf
102
116
  const client = await pool.connect();
103
117
  try {
104
118
  await client.query('BEGIN');
105
- const result = await callback(new Transaction(client));
119
+ const result = await callback(new Transaction(client, { onLogStatement: config.onLogStatement }));
106
120
  await client.query('COMMIT');
107
121
  return result;
108
122
  }
@@ -158,7 +172,7 @@ export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...conf
158
172
  const isRaw = (value) => {
159
173
  return (typeof value === 'object' && value !== null && 'raw' in value && !!value.raw);
160
174
  };
161
- class SQLStatement {
175
+ export class SqlStatement {
162
176
  constructor(strings, values) {
163
177
  this.stringsAndValues = [];
164
178
  this._extract(strings, values);
@@ -170,7 +184,7 @@ class SQLStatement {
170
184
  if (isRaw(value)) {
171
185
  this.stringsAndValues.push([string + value.name, undefined]);
172
186
  }
173
- else if (value instanceof SQLStatement) {
187
+ else if (value instanceof SqlStatement) {
174
188
  this.stringsAndValues.push([string, undefined]);
175
189
  this.stringsAndValues.push(...value.stringsAndValues);
176
190
  }
@@ -180,7 +194,7 @@ class SQLStatement {
180
194
  }
181
195
  }
182
196
  append(statement) {
183
- if (statement instanceof SQLStatement) {
197
+ if (statement instanceof SqlStatement) {
184
198
  this.stringsAndValues.push(...statement.stringsAndValues);
185
199
  }
186
200
  else {
@@ -209,14 +223,16 @@ class SQLStatement {
209
223
  }
210
224
  }
211
225
  export const sql = (strings, ...values) => {
212
- return new SQLStatement(Array.from(strings), values);
226
+ return new SqlStatement(Array.from(strings), values);
213
227
  };
214
228
  function spreadUpdate(data) {
215
229
  const entries = Object.entries(data);
216
230
  if (entries.length === 0) {
217
231
  return sql ``;
218
232
  }
219
- 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}`), ', ');
220
236
  }
221
237
  function spreadInsert(data) {
222
238
  const rows = Array.isArray(data) ? data : [data];
@@ -225,7 +241,7 @@ function spreadInsert(data) {
225
241
  }
226
242
  const statement = sql `(`;
227
243
  statement.append(Object.keys(rows[0])
228
- .map((k) => escapeIdentifier(k))
244
+ .map((k) => escapePostgresIdentifier(k))
229
245
  .join(', '));
230
246
  statement.append(sql `) VALUES `);
231
247
  const toInsert = [];
@@ -243,7 +259,7 @@ function spreadInsert(data) {
243
259
  * Inject an identifier into the template string
244
260
  */
245
261
  sql.identifier = (name) => {
246
- return { name: escapeIdentifier(name), raw: true };
262
+ return { name: escapePostgresIdentifier(name), raw: true };
247
263
  };
248
264
  /**
249
265
  * Inject a raw value into the template string
@@ -255,7 +271,10 @@ sql.raw = (name) => {
255
271
  * Inject a literal value into the template string
256
272
  */
257
273
  sql.literal = (name) => {
258
- return { name: escapeLiteral(name), raw: true };
274
+ if (name === null) {
275
+ return { name: 'NULL', raw: true };
276
+ }
277
+ return { name: escapePostgresLiteral(name), raw: true };
259
278
  };
260
279
  sql.spreadUpdate = spreadUpdate;
261
280
  sql.spreadInsert = spreadInsert;
@@ -270,4 +289,3 @@ export function joinStatements(statements, separator) {
270
289
  }
271
290
  return statement;
272
291
  }
273
- export { escapeIdentifier, escapeLiteral };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devmoods/express-extras",
3
- "version": "0.40.0",
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": {