@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 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 | undefined) => Promise<void>;
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 key of keys) {
22
- await redis.del(key);
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.Handlers.errorHandler({
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
@@ -1,5 +1,6 @@
1
1
  export * from './auth.js';
2
2
  export * from './cache.js';
3
+ export * from './cli.js';
3
4
  export * from './context.js';
4
5
  export * from './email.js';
5
6
  export * from './errors.js';
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
+ /* v8 ignore next 100 */
1
2
  export * from './auth.js';
2
3
  export * from './cache.js';
4
+ export * from './cli.js';
3
5
  export * from './context.js';
4
6
  export * from './email.js';
5
7
  export * from './errors.js';
package/dist/listen.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import { type Server } from 'node:http';
3
2
  import { type Application } from 'express';
4
3
  interface StoppableServer extends Server {
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 (error) {
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?: Duplex;
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 | undefined, context?: LoggerContext): void;
33
- fatal(error: Error | null, message?: string | undefined, context?: LoggerContext): void;
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 {} from 'node:stream';
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 (_sentryClient != null) {
77
- _sentryClient.captureException(error, {
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 (_sentryClient != null) {
89
- _sentryClient.captureException(error, {
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
- register(program.command.bind(program));
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
+ }
@@ -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: RequestHandler<Record<string, string>, any, RequestBody, RequestQuery>): RequestHandler<Record<string, string>, any, RequestBody, RequestQuery>;
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>;
@@ -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
- return res.status(200).end('// no-op analytics proxy');
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 function cli(): Promise<void>;
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 meow from 'meow';
4
+ import {} from 'commander';
5
+ import {} from './cli.js';
5
6
  import { createPostgres } from './postgres.js';
6
- export async function cli() {
7
- const cli = meow(`
8
- Usage
9
- $ migrate init
10
- $ migrate up
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
- finally {
29
- process.exit(exitCode);
30
- }
31
- }
32
- class CliError extends Error {
33
- constructor(message, code) {
34
- super(message);
35
- this.code = code;
36
- this.code = code;
37
- }
38
- }
39
- export async function runCli(input, flags, showHelp) {
40
- const verbose = !flags.silent;
41
- const args = input.slice();
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
- if (!isValidAction(action)) {
50
- showHelp?.();
51
- throw new CliError(`Invalid action ${action}`, 2);
29
+ const created = await migrator.init();
30
+ if (created) {
31
+ cli.greenLine('Migrations table was successfully setup.');
52
32
  }
53
- switch (action) {
54
- case 'up':
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
- catch (error) {
77
- console.error(chalk.red(error.message));
78
- throw new CliError(error.message, 1);
37
+ finally {
38
+ await migrator.end();
79
39
  }
80
- }
81
- return run(action, ...args);
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 (error) {
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
@@ -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 pg.QueryResultRow>(text: string | SQLStatement, values?: Value[] | undefined) => Promise<pg.QueryResult<T>>;
37
- query: <T_1 extends pg.QueryResultRow>(text: string | SQLStatement, values?: Value[] | undefined) => Promise<pg.QueryResult<T_1>>;
38
- first: <T_2>(text: string | SQLStatement, values?: Value[] | undefined) => Promise<T_2 | null>;
39
- all: <T_3>(text: string | SQLStatement, values?: Value[] | undefined) => Promise<T_3[]>;
40
- get: <T_4>(text: string | SQLStatement, values?: Value[] | undefined) => Promise<T_4>;
41
- withConnection: <T_5>(callback: (connection: Connection) => Promise<T_5>) => Promise<T_5>;
42
- transaction: <T_6>(callback: (connection: Transaction) => T_6 | Promise<T_6>) => Promise<T_6>;
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
- if (onError) {
75
- pool.on('error', onError);
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 type CreateSentry = ReturnType<typeof createSentry>;
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 * as Sentry from '@sentry/node';
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
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import { type EventEmitter } from 'node:events';
3
2
  /**
4
3
  * Start a high resolution timer
package/package.json CHANGED
@@ -1,13 +1,10 @@
1
1
  {
2
2
  "name": "@devmoods/express-extras",
3
- "version": "0.31.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
- "bin",
21
- "dist",
22
- "README.md"
17
+ "dist"
23
18
  ],
24
19
  "license": "ISC",
25
20
  "devDependencies": {
26
- "@devmoods/eslint-config": "^1.13.1",
21
+ "@devmoods/eslint-config": "^1.14.0",
27
22
  "@types/koa-compose": "^3.2.8",
28
- "@types/node": "20.14.2",
23
+ "@types/node": "22.7.5",
29
24
  "@types/supertest": "^6.0.2",
30
- "@typescript-eslint/eslint-plugin": "^7.12.0",
31
- "@typescript-eslint/parser": "^7.12.0",
32
- "@vitest/coverage-v8": "^1.3.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.29.1",
36
- "eslint-plugin-jsx-a11y": "^6.8.0",
37
- "eslint-plugin-prettier": "^5.1.3",
38
- "eslint-plugin-react": "^7.34.2",
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.4.5",
45
- "vitest": "^1.3.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": "^7.116.0",
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": "^4.17.21",
54
- "@types/jsonwebtoken": "^9.0.6",
55
- "@types/nodemailer": "^6.4.14",
56
- "@types/pg": "^8.11.2",
57
- "@types/superagent": "^8.1.4",
58
- "@types/uuid": "^9.0.8",
59
- "ajv": "^8.16.0",
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.2",
54
+ "body-parser": "^1.20.3",
62
55
  "commander": "^12.1.0",
63
56
  "compose-middleware": "^5.0.1",
64
- "cookie-parser": "^1.4.6",
57
+ "cookie-parser": "^1.4.7",
65
58
  "dotenv": "^16.4.5",
66
- "express": "^4.18.3",
59
+ "express": "^4.21.1",
67
60
  "express-route-parser": "^1.0.5",
68
- "helmet": "^7.1.0",
61
+ "helmet": "^8.0.0",
69
62
  "jsonwebtoken": "^9.0.2",
70
63
  "meow": "^13.2.0",
71
- "nodemailer": "^6.9.11",
72
- "pg": "^8.12.0",
73
- "redis": "^4.6.13",
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": "^9.0.1",
76
- "vitest-fail-on-console": "^0.7.0"
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 --esm example/index.ts",
100
- "start:worker": "ts-node --esm example/worker.ts",
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 src",
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
@@ -1,5 +0,0 @@
1
- #/bin/bash
2
-
3
- set -e
4
-
5
- node --no-warnings --loader ts-node/esm node_modules/@devmoods/express-extras/dist/cli/migrate.js ${*:1}
package/bin/routes DELETED
@@ -1,5 +0,0 @@
1
- #/bin/bash
2
-
3
- set -e
4
-
5
- node_modules/.bin/ts-node --esm node_modules/@devmoods/express-extras/dist/cli/routes.js ${*:1}
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1,3 +0,0 @@
1
- import 'dotenv/config';
2
- import { cli } from '../migrate.js';
3
- cli();
@@ -1 +0,0 @@
1
- export {};
@@ -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
- }