@devmoods/express-extras 0.39.1 → 0.40.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.
@@ -35,7 +35,11 @@ export declare function notFound(): RequestHandler;
35
35
  * Register middlewares that must come after the app routes.
36
36
  */
37
37
  export declare function after(): RequestHandler;
38
+ type ResponseHeaders = Record<string, string>;
39
+ type AnyResponseBody = Record<string, any> | string;
40
+ type HttpResponseCode = number;
41
+ type ResponseWrapper<TResponseBody extends AnyResponseBody> = TResponseBody | HttpResponseCode | [HttpResponseCode, TResponseBody] | [HttpResponseCode, TResponseBody, ResponseHeaders];
38
42
  /**
39
43
  * Alias of `route()`
40
44
  */
41
- export declare function asyncMiddleware<RequestBody, RequestQuery = object, ResponseBody = any>(fn: (req: Request<Record<string, string>, ResponseBody, RequestBody, RequestQuery, object>, res: Response<ResponseBody, object>, next: NextFunction) => ResponseBody | Promise<ResponseBody>): RequestHandler<Record<string, string>, any, RequestBody, RequestQuery>;
45
+ export declare function asyncMiddleware<RequestBody, RequestQuery = object, ResponseBody extends AnyResponseBody = object>(fn: (req: Request<Record<string, string>, ResponseBody, RequestBody, RequestQuery, object>, res: Response<ResponseBody, object>, next: NextFunction) => ResponseWrapper<ResponseBody> | Promise<ResponseWrapper<ResponseBody>>): RequestHandler<Record<string, string>, ResponseBody, RequestBody, RequestQuery>;
@@ -1,5 +1,4 @@
1
- import pg, { type PoolClient, type PoolConfig, type QueryResult, type QueryResultRow } from 'pg';
2
- import { SQL, type SQLStatement } from 'sql-template-strings';
1
+ import pg, { escapeIdentifier, escapeLiteral, type PoolClient, type PoolConfig, type QueryResult, type QueryResultRow } from 'pg';
3
2
  export interface PostgresOptions extends PoolConfig {
4
3
  onError?: (error: Error, client: any) => void;
5
4
  url?: PoolConfig['connectionString'];
@@ -44,5 +43,32 @@ export declare function createPostgres({ onError, url, ...config }?: PostgresOpt
44
43
  clear: (force?: boolean) => Promise<void>;
45
44
  end(): Promise<void>;
46
45
  };
47
- export declare const sql: typeof SQL;
48
- export {};
46
+ declare class SQLStatement {
47
+ stringsAndValues: [string, unknown | undefined][];
48
+ constructor(strings: string[], values: unknown[]);
49
+ _extract(strings: string[], values: unknown[]): void;
50
+ append(statement: SQLStatement | string): this;
51
+ get values(): ({} | null)[];
52
+ get text(): string;
53
+ }
54
+ export declare const sql: {
55
+ (strings: TemplateStringsArray, ...values: unknown[]): SQLStatement;
56
+ identifier(name: string): {
57
+ name: string;
58
+ raw: boolean;
59
+ };
60
+ raw(name: string): {
61
+ name: string;
62
+ raw: boolean;
63
+ };
64
+ literal(name: string): {
65
+ name: string;
66
+ raw: boolean;
67
+ };
68
+ spreadUpdate: typeof spreadUpdate;
69
+ spreadInsert: typeof spreadInsert;
70
+ };
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 };
package/dist/postgres.js CHANGED
@@ -1,5 +1,4 @@
1
- import pg, {} from 'pg';
2
- import { SQL } from 'sql-template-strings';
1
+ import pg, { escapeIdentifier, escapeLiteral, } from 'pg';
3
2
  import { readEnv } from './utils.js';
4
3
  export class DoesNotExistError extends Error {
5
4
  constructor(message) {
@@ -156,4 +155,119 @@ export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...conf
156
155
  },
157
156
  };
158
157
  }
159
- export const sql = SQL;
158
+ const isRaw = (value) => {
159
+ return (typeof value === 'object' && value !== null && 'raw' in value && !!value.raw);
160
+ };
161
+ class SQLStatement {
162
+ constructor(strings, values) {
163
+ this.stringsAndValues = [];
164
+ this._extract(strings, values);
165
+ }
166
+ _extract(strings, values) {
167
+ for (let i = 0; i < strings.length; i++) {
168
+ const string = strings[i];
169
+ const value = values[i];
170
+ if (isRaw(value)) {
171
+ this.stringsAndValues.push([string + value.name, undefined]);
172
+ }
173
+ else if (value instanceof SQLStatement) {
174
+ this.stringsAndValues.push([string, undefined]);
175
+ this.stringsAndValues.push(...value.stringsAndValues);
176
+ }
177
+ else {
178
+ this.stringsAndValues.push([string, value]);
179
+ }
180
+ }
181
+ }
182
+ append(statement) {
183
+ if (statement instanceof SQLStatement) {
184
+ this.stringsAndValues.push(...statement.stringsAndValues);
185
+ }
186
+ else {
187
+ this.stringsAndValues.push([statement, undefined]);
188
+ }
189
+ return this;
190
+ }
191
+ get values() {
192
+ return this.stringsAndValues
193
+ .map(([, value]) => value)
194
+ .filter((x) => x !== undefined);
195
+ }
196
+ get text() {
197
+ let i = 1;
198
+ let result = '';
199
+ for (const [string, value] of this.stringsAndValues) {
200
+ if (value == null) {
201
+ result += string;
202
+ }
203
+ else {
204
+ result += `${string}$${i}`;
205
+ i += 1;
206
+ }
207
+ }
208
+ return result;
209
+ }
210
+ }
211
+ export const sql = (strings, ...values) => {
212
+ return new SQLStatement(Array.from(strings), values);
213
+ };
214
+ function spreadUpdate(data) {
215
+ const entries = Object.entries(data);
216
+ if (entries.length === 0) {
217
+ return sql ``;
218
+ }
219
+ return joinStatements(entries.map(([key, value]) => sql `${sql.identifier(key)} = ${value}`), ', ');
220
+ }
221
+ function spreadInsert(data) {
222
+ const rows = Array.isArray(data) ? data : [data];
223
+ if (rows.length === 0) {
224
+ return sql ``;
225
+ }
226
+ const statement = sql `(`;
227
+ statement.append(Object.keys(rows[0])
228
+ .map((k) => escapeIdentifier(k))
229
+ .join(', '));
230
+ statement.append(sql `) VALUES `);
231
+ const toInsert = [];
232
+ for (const row of rows) {
233
+ const values = Object.values(row);
234
+ if (values.length === 0) {
235
+ continue;
236
+ }
237
+ toInsert.push(sql `(${joinStatements(values.map((value) => sql `${value ?? sql.raw('NULL')}`), ', ')})`);
238
+ }
239
+ statement.append(joinStatements(toInsert, ', '));
240
+ return statement;
241
+ }
242
+ /**
243
+ * Inject an identifier into the template string
244
+ */
245
+ sql.identifier = (name) => {
246
+ return { name: escapeIdentifier(name), raw: true };
247
+ };
248
+ /**
249
+ * Inject a raw value into the template string
250
+ */
251
+ sql.raw = (name) => {
252
+ return { name, raw: true };
253
+ };
254
+ /**
255
+ * Inject a literal value into the template string
256
+ */
257
+ sql.literal = (name) => {
258
+ return { name: escapeLiteral(name), raw: true };
259
+ };
260
+ sql.spreadUpdate = spreadUpdate;
261
+ sql.spreadInsert = spreadInsert;
262
+ export function joinStatements(statements, separator) {
263
+ let i = 0;
264
+ const statement = sql ``;
265
+ for (const o of statements) {
266
+ if (i++ !== 0) {
267
+ statement.append(separator);
268
+ }
269
+ statement.append(o);
270
+ }
271
+ return statement;
272
+ }
273
+ export { escapeIdentifier, escapeLiteral };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devmoods/express-extras",
3
- "version": "0.39.1",
3
+ "version": "0.40.0",
4
4
  "description": "Kubernetes-ready utilities for rapid Node.js application development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,11 +20,11 @@
20
20
  "devDependencies": {
21
21
  "@devmoods/eslint-config": "^1.15.0",
22
22
  "@types/koa-compose": "^3.2.8",
23
- "@types/node": "22.8.6",
23
+ "@types/node": "22.9.3",
24
24
  "@types/supertest": "^6.0.2",
25
- "@typescript-eslint/eslint-plugin": "^8.12.2",
26
- "@typescript-eslint/parser": "^8.12.2",
27
- "@vitest/coverage-v8": "^2.1.4",
25
+ "@typescript-eslint/eslint-plugin": "^8.15.0",
26
+ "@typescript-eslint/parser": "^8.15.0",
27
+ "@vitest/coverage-v8": "^2.1.5",
28
28
  "cookiejar": "^2.1.4",
29
29
  "eslint": "^8.57.0",
30
30
  "eslint-plugin-import": "^2.31.0",
@@ -34,20 +34,20 @@
34
34
  "supertest": "7.0.0",
35
35
  "ts-node": "10.9.2",
36
36
  "tsx": "^4.19.2",
37
- "typescript": "5.6.3",
38
- "vitest": "^2.1.4",
37
+ "typescript": "5.7.2",
38
+ "vitest": "^2.1.5",
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.36.0",
44
- "@sentry/profiling-node": "^8.36.0",
43
+ "@sentry/node": "^8.40.0",
44
+ "@sentry/profiling-node": "^8.40.0",
45
45
  "@types/bcrypt": "^5.0.2",
46
46
  "@types/body-parser": "^1.19.5",
47
47
  "@types/cookie-parser": "^1.4.7",
48
48
  "@types/express": "^5.0.0",
49
49
  "@types/jsonwebtoken": "^9.0.7",
50
- "@types/nodemailer": "^6.4.16",
50
+ "@types/nodemailer": "^6.4.17",
51
51
  "@types/pg": "^8.11.10",
52
52
  "@types/superagent": "^8.1.9",
53
53
  "@types/uuid": "^10.0.0",
@@ -65,8 +65,7 @@
65
65
  "nodemailer": "^6.9.16",
66
66
  "pg": "^8.13.1",
67
67
  "redis": "^4.7.0",
68
- "sql-template-strings": "^2.2.2",
69
- "uuid": "^11.0.2"
68
+ "uuid": "^11.0.3"
70
69
  },
71
70
  "prettier": {
72
71
  "singleQuote": true