@devmoods/express-extras 0.31.0 → 0.33.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.
- package/dist/cache.d.ts +1 -1
- package/dist/cache.js +2 -2
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +45 -0
- package/dist/email.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/listen.d.ts +0 -1
- package/dist/listen.js +1 -1
- package/dist/logger.d.ts +6 -7
- package/dist/logger.js +8 -10
- package/dist/manage.d.ts +4 -1
- package/dist/manage.js +29 -1
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.js +2 -4
- package/dist/migrate.d.ts +4 -2
- package/dist/migrate.js +52 -73
- package/dist/postgres.d.ts +7 -7
- package/dist/postgres.js +3 -3
- package/dist/routes.js +1 -1
- package/dist/sentry.d.ts +2 -62
- package/dist/sentry.js +4 -24
- package/dist/utils.d.ts +0 -1
- package/package.json +36 -51
- package/bin/migrate +0 -5
- package/bin/routes +0 -5
- package/dist/cli/migrate.d.ts +0 -1
- package/dist/cli/migrate.js +0 -3
- package/dist/cli/routes.d.ts +0 -1
- package/dist/cli/routes.js +0 -10
package/dist/cache.d.ts
CHANGED
|
@@ -12,6 +12,6 @@ export declare function createMemoryCacheAdapter(): CacheAdapter;
|
|
|
12
12
|
export declare function createCache(adapter: CacheAdapter): {
|
|
13
13
|
set: (key: string, value: any, expires?: number | null | undefined) => Promise<void>;
|
|
14
14
|
get: (key: string) => Promise<any>;
|
|
15
|
-
clear: (key?: string
|
|
15
|
+
clear: (key?: string) => Promise<void>;
|
|
16
16
|
wrap: <T>(key: string, expiresMs: number | null | undefined, fn: () => Promise<T>) => Promise<T>;
|
|
17
17
|
};
|
package/dist/cache.js
CHANGED
|
@@ -18,8 +18,8 @@ export function createRedisCacheAdapter(redis, options = {}) {
|
|
|
18
18
|
await redis.del(createKey(key));
|
|
19
19
|
}
|
|
20
20
|
const keys = await redis.keys(`${keyPrefix}*`);
|
|
21
|
-
for (const
|
|
22
|
-
await redis.del(
|
|
21
|
+
for (const existingKey of keys) {
|
|
22
|
+
await redis.del(existingKey);
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type WriteStream } from 'node:tty';
|
|
2
|
+
export interface CLIOptions {
|
|
3
|
+
printStackTraces?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare class CLI {
|
|
6
|
+
private options;
|
|
7
|
+
constructor(options?: CLIOptions);
|
|
8
|
+
writeLine(message: string, output?: WriteStream): void;
|
|
9
|
+
greenLine(message: string, output?: WriteStream): void;
|
|
10
|
+
redLine(message: string, output?: WriteStream): void;
|
|
11
|
+
readInput(question: string): Promise<string>;
|
|
12
|
+
wrap(action: (...args: any[]) => void | Promise<void>): (...args: any[]) => Promise<void>;
|
|
13
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { stdin, stdout, stderr } from 'node:process';
|
|
2
|
+
import * as readline from 'node:readline';
|
|
3
|
+
import {} from 'node:tty';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { startTimer } from './utils.js';
|
|
6
|
+
export class CLI {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
writeLine(message, output = stdout) {
|
|
11
|
+
output.write(`${message}`);
|
|
12
|
+
}
|
|
13
|
+
greenLine(message, output = stdout) {
|
|
14
|
+
output.write(`${chalk.green(message)}\n`);
|
|
15
|
+
}
|
|
16
|
+
redLine(message, output = stderr) {
|
|
17
|
+
output.write(`${chalk.red(message)}\n`);
|
|
18
|
+
}
|
|
19
|
+
/* v8 ignore next 10 */
|
|
20
|
+
readInput(question) {
|
|
21
|
+
const rl = readline.createInterface({ input: stdin, output: stderr });
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
rl.question(question, (answer) => {
|
|
24
|
+
resolve(answer);
|
|
25
|
+
rl.close();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
wrap(action) {
|
|
30
|
+
return async (...args) => {
|
|
31
|
+
const timer = startTimer();
|
|
32
|
+
try {
|
|
33
|
+
await action(...args);
|
|
34
|
+
this.greenLine(`Command succeeded in ${timer.getDuration().toFixed(4)}s`);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
this.redLine(error.message);
|
|
38
|
+
if (this.options.printStackTraces) {
|
|
39
|
+
this.writeLine('\nStack trace:\n', stderr);
|
|
40
|
+
this.redLine(error.stack);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/email.d.ts
CHANGED
|
@@ -12,6 +12,6 @@ export interface SendEmailOptions extends nodemailer.SendMailOptions {
|
|
|
12
12
|
}
|
|
13
13
|
export declare function createEmail({ host, port, user, pass, senderName, senderAddress, }: EmailOptions): {
|
|
14
14
|
send: ({ to, subject, message, ...rest }: SendEmailOptions) => Promise<void>;
|
|
15
|
-
transporter: nodemailer.Transporter<import("nodemailer/lib/smtp-transport/index.js").SentMessageInfo>;
|
|
15
|
+
transporter: nodemailer.Transporter<import("nodemailer/lib/smtp-transport/index.js").SentMessageInfo, import("nodemailer/lib/smtp-transport/index.js").Options>;
|
|
16
16
|
readonly outbox: Record<string, any>[];
|
|
17
17
|
};
|
package/dist/errors.js
CHANGED
|
@@ -64,7 +64,7 @@ function _errorHandler({ onError }) {
|
|
|
64
64
|
*/
|
|
65
65
|
export function errorHandler({ onError }) {
|
|
66
66
|
return errors([
|
|
67
|
-
Sentry.
|
|
67
|
+
Sentry.expressErrorHandler({
|
|
68
68
|
shouldHandleError(error) {
|
|
69
69
|
if (error instanceof ApiError && error.status < 500) {
|
|
70
70
|
return false;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/listen.d.ts
CHANGED
package/dist/listen.js
CHANGED
|
@@ -104,7 +104,7 @@ export function createExpressServer(app, options = {}) {
|
|
|
104
104
|
const meta = await options.healthCheck?.();
|
|
105
105
|
return { status: 'ok', meta, timestamp };
|
|
106
106
|
}
|
|
107
|
-
catch
|
|
107
|
+
catch {
|
|
108
108
|
return [503, { status: 'error', timestamp }];
|
|
109
109
|
}
|
|
110
110
|
}));
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
import { type Duplex } from 'node:stream';
|
|
3
|
-
import { type CreateSentry } from './sentry.js';
|
|
4
1
|
export type LoggerContext = Record<string, any>;
|
|
2
|
+
export interface LoggerOutputWriter {
|
|
3
|
+
write(chunk: any): boolean;
|
|
4
|
+
}
|
|
5
5
|
export interface LoggerOptions {
|
|
6
6
|
/**
|
|
7
7
|
* Name of the logger
|
|
@@ -12,9 +12,8 @@ export interface LoggerOptions {
|
|
|
12
12
|
*/
|
|
13
13
|
getContext?: () => LoggerContext;
|
|
14
14
|
level?: string;
|
|
15
|
-
output?:
|
|
15
|
+
output?: LoggerOutputWriter;
|
|
16
16
|
}
|
|
17
|
-
export declare function attachSentryClientToLoggers(sentryClient: CreateSentry['client']): void;
|
|
18
17
|
export declare function setGlobalLoggerContext(getContext: () => LoggerContext): void;
|
|
19
18
|
export type Logger = ReturnType<typeof createLogger>;
|
|
20
19
|
export declare function createLogger({ name, getContext, level, output, }: LoggerOptions): {
|
|
@@ -29,6 +28,6 @@ export declare function getLoggers(): Map<string, {
|
|
|
29
28
|
debug(message: string, context?: LoggerContext): void;
|
|
30
29
|
info(message: string, context?: LoggerContext): void;
|
|
31
30
|
warn(message: string, context?: LoggerContext): void;
|
|
32
|
-
error(error: Error | null, message?: string
|
|
33
|
-
fatal(error: Error | null, message?: string
|
|
31
|
+
error(error: Error | null, message?: string, context?: LoggerContext): void;
|
|
32
|
+
fatal(error: Error | null, message?: string, context?: LoggerContext): void;
|
|
34
33
|
}>;
|
package/dist/logger.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as os from 'node:os';
|
|
2
|
-
import
|
|
2
|
+
import * as Sentry from '@sentry/node';
|
|
3
3
|
import { useRequestId } from './context.js';
|
|
4
|
-
import {} from './sentry.js';
|
|
5
4
|
import { readEnv } from './utils.js';
|
|
6
5
|
const LEVELS = {
|
|
7
6
|
TRACE: 10,
|
|
@@ -13,16 +12,14 @@ const LEVELS = {
|
|
|
13
12
|
};
|
|
14
13
|
const LEVELS_TO_NAME = Object.fromEntries(Object.entries(LEVELS).map(([name, level]) => [level, name.toLowerCase()]));
|
|
15
14
|
const _loggers = new Map();
|
|
16
|
-
let _sentryClient;
|
|
17
|
-
export function attachSentryClientToLoggers(sentryClient) {
|
|
18
|
-
_sentryClient = sentryClient;
|
|
19
|
-
}
|
|
20
15
|
let _globalGetContext;
|
|
21
16
|
export function setGlobalLoggerContext(getContext) {
|
|
22
17
|
_globalGetContext = getContext;
|
|
23
18
|
}
|
|
24
19
|
function defaultGetContext() {
|
|
20
|
+
const nameArgIndex = process.argv.indexOf('--name');
|
|
25
21
|
return {
|
|
22
|
+
app: nameArgIndex > -1 ? process.argv.at(nameArgIndex + 1) : undefined,
|
|
26
23
|
requestId: useRequestId(),
|
|
27
24
|
..._globalGetContext?.(),
|
|
28
25
|
};
|
|
@@ -45,6 +42,7 @@ export function createLogger({ name, getContext, level = readEnv('LOG_LEVEL', 'I
|
|
|
45
42
|
...context,
|
|
46
43
|
};
|
|
47
44
|
}
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
48
46
|
function log(level, message, context) {
|
|
49
47
|
const payload = JSON.stringify({
|
|
50
48
|
level: LEVELS_TO_NAME[level],
|
|
@@ -73,8 +71,8 @@ export function createLogger({ name, getContext, level = readEnv('LOG_LEVEL', 'I
|
|
|
73
71
|
},
|
|
74
72
|
error(error, message, context = {}) {
|
|
75
73
|
const finalContext = getFinalContext(context);
|
|
76
|
-
if (
|
|
77
|
-
|
|
74
|
+
if (Sentry.isInitialized()) {
|
|
75
|
+
Sentry.captureException(error, {
|
|
78
76
|
tags: finalContext,
|
|
79
77
|
});
|
|
80
78
|
}
|
|
@@ -85,8 +83,8 @@ export function createLogger({ name, getContext, level = readEnv('LOG_LEVEL', 'I
|
|
|
85
83
|
},
|
|
86
84
|
fatal(error, message, context = {}) {
|
|
87
85
|
const finalContext = getFinalContext(context);
|
|
88
|
-
if (
|
|
89
|
-
|
|
86
|
+
if (Sentry.isInitialized()) {
|
|
87
|
+
Sentry.captureException(error, {
|
|
90
88
|
tags: finalContext,
|
|
91
89
|
});
|
|
92
90
|
}
|
package/dist/manage.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { program } from 'commander';
|
|
2
|
+
import { type Express } from 'express';
|
|
3
|
+
import { CLI } from './cli.js';
|
|
2
4
|
type Command = typeof program.command;
|
|
3
|
-
export declare function registerManagementCommand(register: (command: Command) => void): void;
|
|
5
|
+
export declare function registerManagementCommand(register: (command: Command, cli: CLI) => void): void;
|
|
4
6
|
export declare function runManagementCommands(): Promise<void>;
|
|
7
|
+
export declare function registerDefaultCommands(app: Express | null): void;
|
|
5
8
|
export {};
|
package/dist/manage.js
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
import { program } from 'commander';
|
|
2
|
+
import {} from 'express';
|
|
3
|
+
import { parseExpressApp } from 'express-route-parser';
|
|
4
|
+
import { hashPassword } from './auth.js';
|
|
5
|
+
import { CLI } from './cli.js';
|
|
6
|
+
import { asCli } from './migrate.js';
|
|
7
|
+
program.option('--verbose', 'Verbose mode');
|
|
2
8
|
export function registerManagementCommand(register) {
|
|
3
|
-
|
|
9
|
+
const cli = new CLI({ printStackTraces: process.argv.includes('--verbose') });
|
|
10
|
+
register(program.command.bind(program), cli);
|
|
4
11
|
}
|
|
5
12
|
export async function runManagementCommands() {
|
|
6
13
|
await program.parseAsync(process.argv);
|
|
7
14
|
}
|
|
15
|
+
export function registerDefaultCommands(app) {
|
|
16
|
+
registerManagementCommand((command, cli) => {
|
|
17
|
+
const migrate = command('migrate');
|
|
18
|
+
asCli(migrate, cli);
|
|
19
|
+
});
|
|
20
|
+
registerManagementCommand((command, cli) => command('routes').action(cli.wrap(async () => {
|
|
21
|
+
if (!app)
|
|
22
|
+
return;
|
|
23
|
+
const routes = parseExpressApp(app);
|
|
24
|
+
for (const route of routes) {
|
|
25
|
+
cli.writeLine(`${route.method.toLocaleUpperCase().padEnd(5)} ${route.path}\n`);
|
|
26
|
+
}
|
|
27
|
+
})));
|
|
28
|
+
registerManagementCommand((command, cli) => {
|
|
29
|
+
command('password-hash').action(cli.wrap(async () => {
|
|
30
|
+
const password = await cli.readInput('Type a password: ');
|
|
31
|
+
const hash = await hashPassword(password);
|
|
32
|
+
cli.writeLine(`${hash}\n`);
|
|
33
|
+
}));
|
|
34
|
+
});
|
|
35
|
+
}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import bodyParser from 'body-parser';
|
|
2
|
-
import { type RequestHandler } from 'express';
|
|
2
|
+
import { type NextFunction, type RequestHandler, type Request, type Response } from 'express';
|
|
3
3
|
type UrlencodedParser = typeof bodyParser.urlencoded;
|
|
4
4
|
type JsonParser = typeof bodyParser.json;
|
|
5
5
|
export declare const urlencodedParser: UrlencodedParser;
|
|
@@ -36,4 +36,4 @@ export declare function after(): RequestHandler;
|
|
|
36
36
|
/**
|
|
37
37
|
* Alias of `route()`
|
|
38
38
|
*/
|
|
39
|
-
export declare function asyncMiddleware<RequestBody, RequestQuery = object>(fn:
|
|
39
|
+
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>;
|
package/dist/middleware.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as Sentry from '@sentry/node';
|
|
2
1
|
import bodyParser from 'body-parser';
|
|
3
2
|
import { compose } from 'compose-middleware';
|
|
4
3
|
import cookieParser from 'cookie-parser';
|
|
@@ -19,8 +18,6 @@ export function before({ cookies = false, helmet: useHelmet = true, json: useJso
|
|
|
19
18
|
return compose([
|
|
20
19
|
withRequestContext(),
|
|
21
20
|
logRequestDuration(),
|
|
22
|
-
Sentry.Handlers.requestHandler(),
|
|
23
|
-
Sentry.Handlers.tracingHandler(),
|
|
24
21
|
useHelmet &&
|
|
25
22
|
helmet({
|
|
26
23
|
contentSecurityPolicy: {
|
|
@@ -97,7 +94,8 @@ function analyticsProxy() {
|
|
|
97
94
|
return (req, res, next) => {
|
|
98
95
|
if (req.originalUrl === '/analytics.js') {
|
|
99
96
|
res.setHeader('Content-type', 'text/javascript');
|
|
100
|
-
|
|
97
|
+
res.status(200).end('// no-op analytics proxy');
|
|
98
|
+
return;
|
|
101
99
|
}
|
|
102
100
|
next();
|
|
103
101
|
};
|
package/dist/migrate.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type Command } from 'commander';
|
|
2
|
+
import { type CLI } from './cli.js';
|
|
1
3
|
import { type Connection, createPostgres } from './postgres.js';
|
|
2
4
|
interface Migration {
|
|
3
5
|
name: string;
|
|
@@ -9,8 +11,7 @@ interface MigrationFileAPI {
|
|
|
9
11
|
up(fn: any): Promise<void> | Promise<void> | string | undefined;
|
|
10
12
|
down(fn: any): Promise<string> | Promise<void> | string | undefined;
|
|
11
13
|
}
|
|
12
|
-
export declare
|
|
13
|
-
export declare function runCli(input: string[], flags: Record<string, any>, showHelp?: () => void): Promise<number>;
|
|
14
|
+
export declare const asCli: (command: Command, cli: CLI) => void;
|
|
14
15
|
export interface MigratorOptions {
|
|
15
16
|
postgres?: ReturnType<typeof createPostgres>;
|
|
16
17
|
migrationTableName?: string;
|
|
@@ -24,6 +25,7 @@ export declare class Migrator {
|
|
|
24
25
|
private filenameRegExp;
|
|
25
26
|
private verbose;
|
|
26
27
|
constructor({ postgres, pathToMigrations, migrationTableName, verbose, }?: MigratorOptions);
|
|
28
|
+
end(): Promise<void>;
|
|
27
29
|
init(): Promise<boolean>;
|
|
28
30
|
isInstalled(): Promise<boolean>;
|
|
29
31
|
create(name: string | null | undefined, format?: 'ts' | 'sql'): Promise<string>;
|
package/dist/migrate.js
CHANGED
|
@@ -1,85 +1,58 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import
|
|
4
|
+
import {} from 'commander';
|
|
5
|
+
import {} from './cli.js';
|
|
5
6
|
import { createPostgres } from './postgres.js';
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
$ migrate down
|
|
12
|
-
$ migrate create <name> [--typescript]
|
|
13
|
-
`, {
|
|
14
|
-
description: 'PostgreSQL migration tool',
|
|
15
|
-
importMeta: import.meta,
|
|
16
|
-
});
|
|
17
|
-
let exitCode = 0;
|
|
18
|
-
try {
|
|
19
|
-
exitCode = await runCli(cli.input, cli.flags,
|
|
20
|
-
/* c8 ignore next 2 */
|
|
21
|
-
() => cli.showHelp());
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
if (error instanceof CliError) {
|
|
25
|
-
exitCode = error.code;
|
|
7
|
+
export const asCli = (command, cli) => {
|
|
8
|
+
command.command('up').action(cli.wrap(async () => {
|
|
9
|
+
const migrator = new Migrator();
|
|
10
|
+
try {
|
|
11
|
+
await migrator.runMigrations('up');
|
|
26
12
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const action = args.shift();
|
|
43
|
-
function isValidAction(action) {
|
|
44
|
-
return action != null && ['init', 'up', 'down', 'create'].includes(action);
|
|
45
|
-
}
|
|
46
|
-
const migrator = new Migrator({ verbose });
|
|
47
|
-
async function run(action, ...args) {
|
|
13
|
+
finally {
|
|
14
|
+
await migrator.end();
|
|
15
|
+
}
|
|
16
|
+
}));
|
|
17
|
+
command.command('down').action(cli.wrap(async () => {
|
|
18
|
+
const migrator = new Migrator();
|
|
19
|
+
try {
|
|
20
|
+
await migrator.runMigrations('down');
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
await migrator.end();
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
command.command('init').action(cli.wrap(async () => {
|
|
27
|
+
const migrator = new Migrator();
|
|
48
28
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
29
|
+
const created = await migrator.init();
|
|
30
|
+
if (created) {
|
|
31
|
+
cli.greenLine('Migrations table was successfully setup.');
|
|
52
32
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
case 'down':
|
|
56
|
-
await migrator.runMigrations(action);
|
|
57
|
-
break;
|
|
58
|
-
case 'init': {
|
|
59
|
-
const created = await migrator.init();
|
|
60
|
-
if (created) {
|
|
61
|
-
migrator.log(chalk.green('Migrations table was successfully setup.'));
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
migrator.log(chalk.green('Migrations table already existing.'));
|
|
65
|
-
}
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
case 'create': {
|
|
69
|
-
const createdMigration = await migrator.create(args[0], flags.typescript ? 'ts' : 'sql');
|
|
70
|
-
migrator.log(chalk.green('Created migration', createdMigration));
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
33
|
+
else {
|
|
34
|
+
cli.greenLine('Migrations table already existing.');
|
|
73
35
|
}
|
|
74
|
-
return 0;
|
|
75
36
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
throw new CliError(error.message, 1);
|
|
37
|
+
finally {
|
|
38
|
+
await migrator.end();
|
|
79
39
|
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
40
|
+
}));
|
|
41
|
+
command
|
|
42
|
+
.command('create')
|
|
43
|
+
.argument('<string>', 'name of migration')
|
|
44
|
+
.option('--typescript', 'use typescript')
|
|
45
|
+
.action(cli.wrap(async (name, flags) => {
|
|
46
|
+
const migrator = new Migrator();
|
|
47
|
+
try {
|
|
48
|
+
const createdMigration = await migrator.create(name, flags.typescript ? 'ts' : 'sql');
|
|
49
|
+
migrator.log(chalk.green('Created migration', createdMigration));
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await migrator.end();
|
|
53
|
+
}
|
|
54
|
+
}));
|
|
55
|
+
};
|
|
83
56
|
export class Migrator {
|
|
84
57
|
constructor({ postgres = createPostgres(), pathToMigrations = path.resolve(process.cwd(), 'db', 'migrations'), migrationTableName = 'migrations', verbose = true, } = {}) {
|
|
85
58
|
this.filenameRegExp = /(\d+)_(.*)\.(js|ts|mjs|mts|sql)/;
|
|
@@ -88,6 +61,9 @@ export class Migrator {
|
|
|
88
61
|
this.migrationTableName = migrationTableName;
|
|
89
62
|
this.verbose = verbose;
|
|
90
63
|
}
|
|
64
|
+
end() {
|
|
65
|
+
return this.postgres.end();
|
|
66
|
+
}
|
|
91
67
|
async init() {
|
|
92
68
|
if (await this.isInstalled()) {
|
|
93
69
|
return false;
|
|
@@ -108,7 +84,7 @@ export class Migrator {
|
|
|
108
84
|
await fs.stat(this.pathToMigrations);
|
|
109
85
|
return true;
|
|
110
86
|
}
|
|
111
|
-
catch
|
|
87
|
+
catch {
|
|
112
88
|
return false;
|
|
113
89
|
}
|
|
114
90
|
}
|
|
@@ -116,6 +92,9 @@ export class Migrator {
|
|
|
116
92
|
if (!name) {
|
|
117
93
|
throw new Error('Missing positional argument <name>');
|
|
118
94
|
}
|
|
95
|
+
if (!(await this.isInstalled())) {
|
|
96
|
+
await this.init();
|
|
97
|
+
}
|
|
119
98
|
const existingMigrations = await this.getMigrations();
|
|
120
99
|
const lastVersion = existingMigrations.length
|
|
121
100
|
? existingMigrations[existingMigrations.length - 1].version
|
package/dist/postgres.d.ts
CHANGED
|
@@ -33,13 +33,13 @@ export declare class Transaction extends Connection {
|
|
|
33
33
|
* @param config
|
|
34
34
|
*/
|
|
35
35
|
export declare function createPostgres({ onError, url, ...config }?: PostgresOptions): {
|
|
36
|
-
insert: <T extends
|
|
37
|
-
query: <
|
|
38
|
-
first: <
|
|
39
|
-
all: <
|
|
40
|
-
get: <
|
|
41
|
-
withConnection: <
|
|
42
|
-
transaction: <
|
|
36
|
+
insert: <T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]) => Promise<QueryResult<T>>;
|
|
37
|
+
query: <T extends QueryResultRow>(text: string | SQLStatement, values?: Value[]) => Promise<QueryResult<T>>;
|
|
38
|
+
first: <T>(text: string | SQLStatement, values?: Value[]) => Promise<T | null>;
|
|
39
|
+
all: <T>(text: string | SQLStatement, values?: Value[]) => Promise<T[]>;
|
|
40
|
+
get: <T>(text: string | SQLStatement, values?: Value[]) => Promise<T>;
|
|
41
|
+
withConnection: <T>(callback: (connection: Connection) => Promise<T>) => Promise<T>;
|
|
42
|
+
transaction: <T>(callback: (connection: Transaction) => Promise<T> | T) => Promise<T>;
|
|
43
43
|
instance: pg.Pool;
|
|
44
44
|
clear: (force?: boolean) => Promise<void>;
|
|
45
45
|
end(): Promise<void>;
|
package/dist/postgres.js
CHANGED
|
@@ -71,9 +71,9 @@ export class Transaction extends Connection {
|
|
|
71
71
|
*/
|
|
72
72
|
export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...config } = {}) {
|
|
73
73
|
const pool = new pg.Pool({ connectionString: url, ...config });
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
74
|
+
pool.on('error', (e, client) => {
|
|
75
|
+
onError?.(e, client);
|
|
76
|
+
});
|
|
77
77
|
async function withConnection(callback) {
|
|
78
78
|
const client = await pool.connect();
|
|
79
79
|
try {
|
package/dist/routes.js
CHANGED
|
@@ -2,7 +2,7 @@ import {} from 'express';
|
|
|
2
2
|
import { parseExpressApp } from 'express-route-parser';
|
|
3
3
|
import { route } from './middleware.js';
|
|
4
4
|
export function createDocsRouter(app, extras = {}) {
|
|
5
|
-
return route(() => {
|
|
5
|
+
return route(async () => {
|
|
6
6
|
return {
|
|
7
7
|
...extras,
|
|
8
8
|
routes: parseExpressApp(app),
|
package/dist/sentry.d.ts
CHANGED
|
@@ -1,65 +1,5 @@
|
|
|
1
|
-
import * as Sentry from '@sentry/node';
|
|
2
|
-
import { type Application } from 'express';
|
|
1
|
+
import type * as Sentry from '@sentry/node';
|
|
3
2
|
export type { Sentry };
|
|
4
3
|
export interface SentryOptions extends Sentry.NodeOptions {
|
|
5
4
|
}
|
|
6
|
-
export
|
|
7
|
-
/**
|
|
8
|
-
* Create a pre-configured Sentry client
|
|
9
|
-
*/
|
|
10
|
-
export declare function createSentry(options?: SentryOptions): {
|
|
11
|
-
client: typeof Sentry;
|
|
12
|
-
init(app: Application | null, getOptions?: (defaultOptions: Sentry.NodeOptions) => Sentry.NodeOptions): void;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Construct sensible Sentry default options for an Express-based app.
|
|
16
|
-
*/
|
|
17
|
-
export declare function createSentryOptions(app: Application | null, options?: SentryOptions): {
|
|
18
|
-
defaultIntegrations?: false | import("@sentry/types").Integration[] | undefined;
|
|
19
|
-
integrations: import("@sentry/types").Integration[] | ((integrations: import("@sentry/types").Integration[]) => import("@sentry/types").Integration[]) | undefined;
|
|
20
|
-
transport?: ((transportOptions: import("@sentry/node/types/transports/http.js").NodeTransportOptions) => import("@sentry/types").Transport) | undefined;
|
|
21
|
-
stackParser?: import("@sentry/types").StackParser | import("@sentry/types").StackLineParser[] | undefined;
|
|
22
|
-
release?: string | undefined;
|
|
23
|
-
debug?: boolean | undefined;
|
|
24
|
-
enabled?: boolean | undefined;
|
|
25
|
-
attachStacktrace?: boolean | undefined;
|
|
26
|
-
autoSessionTracking?: boolean | undefined;
|
|
27
|
-
sendClientReports?: boolean | undefined;
|
|
28
|
-
dsn: string | undefined;
|
|
29
|
-
environment?: string | undefined;
|
|
30
|
-
dist?: string | undefined;
|
|
31
|
-
instrumenter?: import("@sentry/types").Instrumenter | undefined;
|
|
32
|
-
transportOptions?: Partial<import("@sentry/node/types/transports/http.js").NodeTransportOptions> | undefined;
|
|
33
|
-
tracesSampleRate: number;
|
|
34
|
-
enableTracing?: boolean | undefined;
|
|
35
|
-
initialScope?: import("@sentry/types").CaptureContext | undefined;
|
|
36
|
-
maxBreadcrumbs?: number | undefined;
|
|
37
|
-
sampleRate?: number | undefined;
|
|
38
|
-
maxValueLength?: number | undefined;
|
|
39
|
-
normalizeDepth?: number | undefined;
|
|
40
|
-
normalizeMaxBreadth?: number | undefined;
|
|
41
|
-
shutdownTimeout?: number | undefined;
|
|
42
|
-
ignoreErrors?: (string | RegExp)[] | undefined;
|
|
43
|
-
ignoreTransactions?: (string | RegExp)[] | undefined;
|
|
44
|
-
tunnel?: string | undefined;
|
|
45
|
-
sendDefaultPii?: boolean | undefined;
|
|
46
|
-
_metadata?: import("@sentry/types").SdkMetadata | undefined;
|
|
47
|
-
_experiments?: {
|
|
48
|
-
[key: string]: any;
|
|
49
|
-
} | undefined;
|
|
50
|
-
allowUrls?: (string | RegExp)[] | undefined;
|
|
51
|
-
denyUrls?: (string | RegExp)[] | undefined;
|
|
52
|
-
tracePropagationTargets?: import("@sentry/types").TracePropagationTargets | undefined;
|
|
53
|
-
tracesSampler?: ((samplingContext: import("@sentry/types").SamplingContext) => number | boolean) | undefined;
|
|
54
|
-
beforeSend?: ((event: import("@sentry/types").ErrorEvent, hint: Sentry.EventHint) => Sentry.Event | PromiseLike<Sentry.Event | null> | null) | undefined;
|
|
55
|
-
beforeSendTransaction?: ((event: import("@sentry/types").TransactionEvent, hint: Sentry.EventHint) => Sentry.Event | PromiseLike<Sentry.Event | null> | null) | undefined;
|
|
56
|
-
beforeBreadcrumb?: ((breadcrumb: Sentry.Breadcrumb, hint?: Sentry.BreadcrumbHint | undefined) => Sentry.Breadcrumb | null) | undefined;
|
|
57
|
-
profilesSampleRate?: number | undefined;
|
|
58
|
-
profilesSampler?: ((samplingContext: import("@sentry/types").SamplingContext) => number | boolean) | undefined;
|
|
59
|
-
serverName?: string | undefined;
|
|
60
|
-
includeLocalVariables?: boolean | undefined;
|
|
61
|
-
clientClass?: typeof Sentry.NodeClient | undefined;
|
|
62
|
-
spotlight?: string | boolean | undefined;
|
|
63
|
-
shouldCreateSpanForRequest?(this: void, url: string): boolean;
|
|
64
|
-
onFatalError?(this: void, error: Error): void;
|
|
65
|
-
};
|
|
5
|
+
export declare function sentryOptions(options?: SentryOptions): SentryOptions;
|
package/dist/sentry.js
CHANGED
|
@@ -1,31 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {} from 'express';
|
|
3
|
-
import { attachSentryClientToLoggers } from './logger.js';
|
|
1
|
+
import { nodeProfilingIntegration } from '@sentry/profiling-node';
|
|
4
2
|
import { readEnv } from './utils.js';
|
|
5
|
-
|
|
6
|
-
* Create a pre-configured Sentry client
|
|
7
|
-
*/
|
|
8
|
-
export function createSentry(options = {}) {
|
|
9
|
-
const client = Sentry;
|
|
10
|
-
return {
|
|
11
|
-
client,
|
|
12
|
-
init(app, getOptions = (defaultOptions) => defaultOptions) {
|
|
13
|
-
attachSentryClientToLoggers(client);
|
|
14
|
-
Sentry.init(getOptions(createSentryOptions(app, options)));
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Construct sensible Sentry default options for an Express-based app.
|
|
20
|
-
*/
|
|
21
|
-
export function createSentryOptions(app, options = {}) {
|
|
3
|
+
export function sentryOptions(options = {}) {
|
|
22
4
|
return {
|
|
23
5
|
dsn: readEnv('SENTRY_DSN'),
|
|
24
|
-
integrations: [
|
|
25
|
-
new Sentry.Integrations.Http({ tracing: true }),
|
|
26
|
-
app && new Sentry.Integrations.Express({ app }),
|
|
27
|
-
].filter(Boolean),
|
|
28
6
|
tracesSampleRate: 1.0,
|
|
7
|
+
profilesSampleRate: 1.0,
|
|
8
|
+
integrations: [nodeProfilingIntegration()],
|
|
29
9
|
...options,
|
|
30
10
|
};
|
|
31
11
|
}
|
package/dist/utils.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
-
"bin": {
|
|
9
|
-
"migrate": "bin/migrate"
|
|
10
|
-
},
|
|
11
8
|
"author": {
|
|
12
9
|
"name": "hanse",
|
|
13
10
|
"email": "hanse@koren.im"
|
|
@@ -17,77 +14,65 @@
|
|
|
17
14
|
"url": "https://github.com/devmoods/express-extras.git"
|
|
18
15
|
},
|
|
19
16
|
"files": [
|
|
20
|
-
"
|
|
21
|
-
"dist",
|
|
22
|
-
"README.md"
|
|
17
|
+
"dist"
|
|
23
18
|
],
|
|
24
19
|
"license": "ISC",
|
|
25
20
|
"devDependencies": {
|
|
26
|
-
"@devmoods/eslint-config": "^1.
|
|
21
|
+
"@devmoods/eslint-config": "^1.14.0",
|
|
27
22
|
"@types/koa-compose": "^3.2.8",
|
|
28
|
-
"@types/node": "
|
|
23
|
+
"@types/node": "22.7.5",
|
|
29
24
|
"@types/supertest": "^6.0.2",
|
|
30
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
31
|
-
"@typescript-eslint/parser": "^
|
|
32
|
-
"@vitest/coverage-v8": "^1.
|
|
25
|
+
"@typescript-eslint/eslint-plugin": "^8.8.1",
|
|
26
|
+
"@typescript-eslint/parser": "^8.8.1",
|
|
27
|
+
"@vitest/coverage-v8": "^2.1.2",
|
|
33
28
|
"chalk": "^5.3.0",
|
|
34
29
|
"eslint": "^8.57.0",
|
|
35
|
-
"eslint-plugin-import": "^2.
|
|
36
|
-
"eslint-plugin-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"eslint-plugin-react-hooks": "^4.6.0",
|
|
40
|
-
"faktory-worker": "^4.5.1",
|
|
41
|
-
"prettier": "3.3.1",
|
|
30
|
+
"eslint-plugin-import": "^2.31.0",
|
|
31
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
32
|
+
"faktory-worker": "^4.6.0",
|
|
33
|
+
"prettier": "3.3.3",
|
|
42
34
|
"supertest": "7.0.0",
|
|
43
35
|
"ts-node": "10.9.2",
|
|
44
|
-
"typescript": "5.
|
|
45
|
-
"vitest": "^1.
|
|
36
|
+
"typescript": "5.6.3",
|
|
37
|
+
"vitest": "^2.1.2"
|
|
46
38
|
},
|
|
47
39
|
"dependencies": {
|
|
48
40
|
"@devmoods/fetch": "^4.0.1",
|
|
49
|
-
"@sentry/node": "^
|
|
41
|
+
"@sentry/node": "^8.34.0",
|
|
42
|
+
"@sentry/profiling-node": "^8.34.0",
|
|
50
43
|
"@types/bcrypt": "^5.0.2",
|
|
51
44
|
"@types/body-parser": "^1.19.5",
|
|
52
45
|
"@types/cookie-parser": "^1.4.7",
|
|
53
|
-
"@types/express": "^
|
|
54
|
-
"@types/jsonwebtoken": "^9.0.
|
|
55
|
-
"@types/nodemailer": "^6.4.
|
|
56
|
-
"@types/pg": "^8.11.
|
|
57
|
-
"@types/superagent": "^8.1.
|
|
58
|
-
"@types/uuid": "^
|
|
59
|
-
"ajv": "^8.
|
|
46
|
+
"@types/express": "^5.0.0",
|
|
47
|
+
"@types/jsonwebtoken": "^9.0.7",
|
|
48
|
+
"@types/nodemailer": "^6.4.16",
|
|
49
|
+
"@types/pg": "^8.11.10",
|
|
50
|
+
"@types/superagent": "^8.1.9",
|
|
51
|
+
"@types/uuid": "^10.0.0",
|
|
52
|
+
"ajv": "^8.17.1",
|
|
60
53
|
"bcrypt": "^5.1.1",
|
|
61
|
-
"body-parser": "^1.20.
|
|
54
|
+
"body-parser": "^1.20.3",
|
|
62
55
|
"commander": "^12.1.0",
|
|
63
56
|
"compose-middleware": "^5.0.1",
|
|
64
|
-
"cookie-parser": "^1.4.
|
|
57
|
+
"cookie-parser": "^1.4.7",
|
|
65
58
|
"dotenv": "^16.4.5",
|
|
66
|
-
"express": "^4.
|
|
59
|
+
"express": "^4.21.1",
|
|
67
60
|
"express-route-parser": "^1.0.5",
|
|
68
|
-
"helmet": "^
|
|
61
|
+
"helmet": "^8.0.0",
|
|
69
62
|
"jsonwebtoken": "^9.0.2",
|
|
70
63
|
"meow": "^13.2.0",
|
|
71
|
-
"nodemailer": "^6.9.
|
|
72
|
-
"pg": "^8.
|
|
73
|
-
"redis": "^4.
|
|
64
|
+
"nodemailer": "^6.9.15",
|
|
65
|
+
"pg": "^8.13.0",
|
|
66
|
+
"redis": "^4.7.0",
|
|
74
67
|
"sql-template-strings": "^2.2.2",
|
|
75
|
-
"uuid": "^
|
|
76
|
-
"vitest-fail-on-console": "^0.7.
|
|
68
|
+
"uuid": "^10.0.0",
|
|
69
|
+
"vitest-fail-on-console": "^0.7.1"
|
|
77
70
|
},
|
|
78
71
|
"prettier": {
|
|
79
72
|
"singleQuote": true
|
|
80
73
|
},
|
|
81
74
|
"eslintConfig": {
|
|
82
|
-
"extends": "@devmoods/eslint-config"
|
|
83
|
-
"parserOptions": {
|
|
84
|
-
"project": "./tsconfig.json"
|
|
85
|
-
},
|
|
86
|
-
"rules": {
|
|
87
|
-
"@typescript-eslint/consistent-type-imports": "warn",
|
|
88
|
-
"@typescript-eslint/no-shadow": "off",
|
|
89
|
-
"react-hooks/rules-of-hooks": "off"
|
|
90
|
-
}
|
|
75
|
+
"extends": "@devmoods/eslint-config"
|
|
91
76
|
},
|
|
92
77
|
"peerDependencies": {
|
|
93
78
|
"faktory-worker": "^4.5.1"
|
|
@@ -96,11 +81,11 @@
|
|
|
96
81
|
"node": ">=20.3.0 <23"
|
|
97
82
|
},
|
|
98
83
|
"scripts": {
|
|
99
|
-
"start": "ts-node
|
|
100
|
-
"start:worker": "ts-node
|
|
84
|
+
"start": "node --no-warnings --enable-source-maps --loader ts-node/esm example/src/index.ts --name example",
|
|
85
|
+
"start:worker": "node --no-warnings --enable-source-maps --loader ts-node/esm example/worker.ts",
|
|
101
86
|
"clean": "rm -rf dist",
|
|
102
|
-
"lint": "eslint src",
|
|
103
|
-
"test": "./test.sh
|
|
87
|
+
"lint": "eslint src example",
|
|
88
|
+
"test": "./test.sh",
|
|
104
89
|
"build": "pnpm run clean && tsc --project tsconfig.build.json",
|
|
105
90
|
"format": "prettier --write '**/*.{js,css,md,ts,tsx}'"
|
|
106
91
|
}
|
package/bin/migrate
DELETED
package/bin/routes
DELETED
package/dist/cli/migrate.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
package/dist/cli/migrate.js
DELETED
package/dist/cli/routes.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/cli/routes.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { createFetch } from '@devmoods/fetch';
|
|
2
|
-
const port = process.env.PORT ?? 3000;
|
|
3
|
-
const fetch = createFetch({
|
|
4
|
-
getRootUrl: () => `http://localhost:${port}`,
|
|
5
|
-
getRequestId: () => 'routes',
|
|
6
|
-
});
|
|
7
|
-
const response = await fetch('/docs');
|
|
8
|
-
for (const route of response.jsonData.routes) {
|
|
9
|
-
process.stdout.write(`${route.method.toLocaleUpperCase().padEnd(5)} ${route.path}\n`);
|
|
10
|
-
}
|