@devmoods/express-extras 0.72.1 → 0.73.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/auth.js CHANGED
@@ -5,7 +5,7 @@ import { InvalidToken, PermissionDenied } from './auth/errors.js';
5
5
  import { createAccessToken, decodeAccessToken, extractAccessTokenFromRequest, setAccessTokenCookie, tokenSchema, } from './auth/token.js';
6
6
  import {} from './auth/types.js';
7
7
  import { setRequestContext, useRequestContext } from './context.js';
8
- import { Forbidden, NotFound, Unauthorized } from './errors.js';
8
+ import { Forbidden, Unauthorized } from './errors.js';
9
9
  import { getLogger } from './logger.js';
10
10
  import { createRouter } from './router.js';
11
11
  import { jsonSchema } from './validate.js';
@@ -77,33 +77,22 @@ export function createAuth({ getUserById, filterUser, tokenStorage, providers, l
77
77
  const user = useCurrentUser();
78
78
  return filterUser(user);
79
79
  });
80
- router.get('/logout', {}, async (req, res) => {
80
+ router.post('/logout', {}, async (req, res) => {
81
81
  await logout(req, res);
82
82
  res.redirect(loginUrl);
83
83
  });
84
- /**
85
- * Check that a reset token is valid.
86
- */
87
84
  router.get('/reset/:token', {
88
85
  params: jsonSchema({
89
86
  type: 'object',
90
87
  properties: { token: tokenSchema },
91
88
  required: ['token'],
92
89
  }),
93
- }, async (req) => {
94
- const { token } = req.params;
95
- const value = await tokenStorage.get(token);
96
- if (!value) {
97
- logger.info('invalid reset token', { token });
98
- throw NotFound();
99
- }
100
- return 200;
101
- });
102
- router.get('/token/refresh', {}, middleware(false), async (req, res) => {
90
+ }, async () => 200);
91
+ router.post('/token/refresh', {}, middleware(false), async (req, res) => {
103
92
  const user = useCurrentUser();
104
93
  if (!user) {
105
94
  setAccessTokenCookie(res, null);
106
- throw Unauthorized();
95
+ return { token: null, user: null };
107
96
  }
108
97
  const { token } = await providerContext.login(req, res, user, true);
109
98
  logger.info('refreshed token');
package/dist/config.js CHANGED
@@ -14,7 +14,7 @@ const app = define({
14
14
  SENTRY_DSN: t.string(),
15
15
  SENTRY_TRACES_SAMPLE_RATE: t.number(0.3),
16
16
  SENTRY_PROFILES_SAMPLE_RATE: t.number(0.3),
17
- SENTRY_EVENT_LOOP_THRESHOLD: t.number(300),
17
+ SENTRY_EVENT_LOOP_THRESHOLD: t.number(750),
18
18
  LOG_LEVEL: t.string(),
19
19
  LOG_SQL: t.boolean(),
20
20
  NODE_ENV: t.string(),
package/dist/errors.d.ts CHANGED
@@ -35,8 +35,6 @@ export interface ErrorHandlerOptions {
35
35
  }
36
36
  /**
37
37
  * Generic error handler for JSON-based apps.
38
- *
39
- * Composed together with the Sentry error handler.
40
38
  */
41
39
  export declare function errorHandler({ onError }?: ErrorHandlerOptions): ErrorHandler<any, any, void>;
42
40
  export {};
package/dist/errors.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { STATUS_CODES } from 'node:http';
2
2
  import { DoesNotExistError, IntegrityError } from '@devmoods/postgres';
3
- import * as Sentry from '@sentry/node';
4
3
  import {} from 'express';
5
4
  import { InvalidToken } from './auth/errors.js';
6
5
  import { errors } from './composeMiddleware.js';
@@ -66,8 +65,6 @@ function _errorHandler({ onError }) {
66
65
  }
67
66
  /**
68
67
  * Generic error handler for JSON-based apps.
69
- *
70
- * Composed together with the Sentry error handler.
71
68
  */
72
69
  export function errorHandler({ onError } = {}) {
73
70
  const logger = getLogger();
@@ -82,14 +79,6 @@ export function errorHandler({ onError } = {}) {
82
79
  }
83
80
  };
84
81
  return errors([
85
- Sentry.expressErrorHandler({
86
- shouldHandleError(error) {
87
- if (error instanceof ApiError && error.status < 500) {
88
- return false;
89
- }
90
- return process.env.NODE_ENV === 'production';
91
- },
92
- }),
93
82
  _errorHandler({
94
83
  onError: onError || _defaultErrorHandler,
95
84
  }),
package/dist/index.d.ts CHANGED
@@ -19,7 +19,6 @@ export * from './rate-limit.js';
19
19
  export * from './redis.js';
20
20
  export * from './router.js';
21
21
  export * from './routes.js';
22
- export * from './sentry.js';
23
22
  export * from './slack.js';
24
23
  export * from './utils.js';
25
24
  export * from './validate.js';
package/dist/index.js CHANGED
@@ -20,7 +20,6 @@ export * from './rate-limit.js';
20
20
  export * from './redis.js';
21
21
  export * from './router.js';
22
22
  export * from './routes.js';
23
- export * from './sentry.js';
24
23
  export * from './slack.js';
25
24
  export * from './utils.js';
26
25
  export * from './validate.js';
package/dist/listen.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface ExpressServerOptions {
14
14
  onStart?(): Promise<void> | void;
15
15
  onShutdown?(): Promise<void> | void;
16
16
  beforeShutdown?(): Promise<void> | void;
17
+ afterShutdown?(): Promise<void> | void;
17
18
  healthCheck?(type?: 'live' | 'ready'): Promise<unknown>;
18
19
  gracePeriodMs?: number;
19
20
  }
package/dist/listen.js CHANGED
@@ -126,6 +126,7 @@ export function createExpressServer(app, options = {}) {
126
126
  duration: timer.getDuration(),
127
127
  gracefully,
128
128
  });
129
+ await options.afterShutdown?.();
129
130
  process.exit(0);
130
131
  }
131
132
  catch (error) {
package/dist/logger.d.ts CHANGED
@@ -1,20 +1,12 @@
1
1
  import { type Writable } from 'node:stream';
2
+ import { type LoggerAdapter } from '@devmoods/observability';
2
3
  export type LoggerContext = Record<string, any>;
3
- export interface LoggerAdapter {
4
- captureError(error: Error | null, attributes?: Record<string, any>): void;
5
- write(chunk: Record<string, any>): boolean;
6
- }
7
4
  export declare class JsonStreamAdapter implements LoggerAdapter {
8
5
  #private;
9
6
  constructor(stream: Writable);
10
7
  captureError(error: Error, attributes?: Record<string, any>): void;
11
8
  write(log: Record<string, any>): boolean;
12
9
  }
13
- export declare class SentryLoggerAdapter implements LoggerAdapter {
14
- constructor();
15
- captureError(error: Error | null, attributes?: Record<string, any>): void;
16
- write(log: Record<string, any>): boolean;
17
- }
18
10
  export interface LoggerOptions {
19
11
  /**
20
12
  * Name of the logger
@@ -36,6 +28,8 @@ export interface LoggerOptions {
36
28
  export declare function setGlobalLoggerContext(getContext: () => LoggerContext): void;
37
29
  export declare function clearGlobalLoggerContext(): void;
38
30
  export type Logger = ReturnType<typeof createLogger>;
31
+ export declare function addDefaultLoggerOutput(output: LoggerAdapter): void;
32
+ export declare function clearDefaultLoggerOutputs(): void;
39
33
  export declare function createLogger({ name, getContext, level, outputs }: LoggerOptions): {
40
34
  debug(message: string, context?: LoggerContext): void;
41
35
  info(message: string, context?: LoggerContext): void;
package/dist/logger.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as os from 'node:os';
2
2
  import {} from 'node:stream';
3
3
  import { config } from '@devmoods/config';
4
- import * as Sentry from '@sentry/node';
4
+ import {} from '@devmoods/observability';
5
5
  export class JsonStreamAdapter {
6
6
  #stream;
7
7
  constructor(stream) {
@@ -14,30 +14,6 @@ export class JsonStreamAdapter {
14
14
  return this.#stream.write(chunk);
15
15
  }
16
16
  }
17
- export class SentryLoggerAdapter {
18
- constructor() { }
19
- captureError(error, attributes) {
20
- /* v8 ignore next -- @preserve */
21
- if (error == null) {
22
- return;
23
- }
24
- /* v8 ignore next -- @preserve */
25
- if (Sentry.isInitialized()) {
26
- Sentry.captureException(error, {
27
- tags: attributes,
28
- });
29
- }
30
- }
31
- write(log) {
32
- const { message, ...attributes } = log;
33
- /* v8 ignore next -- @preserve */
34
- if (Sentry.isInitialized()) {
35
- const fn = log.level;
36
- Sentry.logger[fn](message, attributes);
37
- }
38
- return true;
39
- }
40
- }
41
17
  const LEVELS = {
42
18
  TRACE: 10,
43
19
  DEBUG: 20,
@@ -73,7 +49,19 @@ function baseContext() {
73
49
  }
74
50
  return globalContext;
75
51
  }
76
- export function createLogger({ name, getContext, level = config.LOG_LEVEL, outputs = [new JsonStreamAdapter(process.stdout), new SentryLoggerAdapter()], }) {
52
+ const defaultLoggerOutputs = [
53
+ new JsonStreamAdapter(process.stdout),
54
+ ];
55
+ export function addDefaultLoggerOutput(output) {
56
+ if (defaultLoggerOutputs.includes(output)) {
57
+ return;
58
+ }
59
+ defaultLoggerOutputs.push(output);
60
+ }
61
+ export function clearDefaultLoggerOutputs() {
62
+ defaultLoggerOutputs.length = 0;
63
+ }
64
+ export function createLogger({ name, getContext, level = config.LOG_LEVEL, outputs = defaultLoggerOutputs, }) {
77
65
  if (_loggers.has(name)) {
78
66
  throw new Error(`A logger named ${name} already exists`);
79
67
  }
package/dist/vite/ssr.js CHANGED
@@ -1,3 +1,11 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
1
9
  import * as fs from 'node:fs/promises';
2
10
  import * as path from 'node:path';
3
11
  import { config } from '@devmoods/config';
@@ -38,7 +46,7 @@ export async function createSsrMiddleware({ root = process.cwd(), getInitialData
38
46
  render = (await vite.ssrLoadModule('/src/entry-server.tsx')).render;
39
47
  }
40
48
  else {
41
- render = (await import(path.join(root, 'dist/server/entry-server.js'))).render;
49
+ render = (await import(__rewriteRelativeImportExtension(path.join(root, 'dist/server/entry-server.js')))).render;
42
50
  }
43
51
  const rendered = await render(createFetchRequest(req, res), {
44
52
  useDevServer,
@@ -117,15 +125,17 @@ async function getAssetLinks(manifestPath, input, publicPath = '/') {
117
125
  // Collect all transitively imported chunk keys so we can gather their CSS too
118
126
  const visited = new Set();
119
127
  const collectImports = (key) => {
128
+ /* c8 ignore start */
120
129
  if (visited.has(key)) {
121
130
  return;
122
131
  }
132
+ /* c8 ignore end */
123
133
  visited.add(key);
124
- const chunk = manifest[key];
125
- if (!chunk) {
134
+ const manifestValue = manifest[key];
135
+ if (!manifestValue) {
126
136
  return;
127
137
  }
128
- for (const importKey of chunk.imports ?? []) {
138
+ for (const importKey of manifestValue.imports ?? []) {
129
139
  collectImports(importKey);
130
140
  }
131
141
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devmoods/express-extras",
3
- "version": "0.72.1",
3
+ "version": "0.73.0",
4
4
  "description": "Kubernetes-ready utilities for rapid Node.js application development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -68,15 +68,12 @@
68
68
  "vite": "^8.0.8"
69
69
  },
70
70
  "dependencies": {
71
- "@devmoods/config": "0.2.1",
71
+ "@devmoods/config": "0.2.3",
72
72
  "@devmoods/fetch": "^4.3.0",
73
- "@devmoods/observability": "0.1.2",
74
- "@devmoods/postgres": "0.4.0",
75
- "@hey-api/openapi-ts": "^0.95.0",
76
- "@rolldown/plugin-babel": "^0.2.2",
77
- "@sentry/node": "^10.47.0",
78
- "@sentry/node-native": "^10.47.0",
79
- "@sentry/profiling-node": "^10.47.0",
73
+ "@devmoods/observability": "0.2.0",
74
+ "@devmoods/postgres": "0.5.0",
75
+ "@hey-api/openapi-ts": "^0.96.0",
76
+ "@rolldown/plugin-babel": "^0.2.3",
80
77
  "@standard-schema/spec": "^1.1.0",
81
78
  "@types/bcrypt": "^6.0.0",
82
79
  "@types/cookie-parser": "^1.4.10",
@@ -91,7 +88,7 @@
91
88
  "chalk": "^5.6.2",
92
89
  "commander": "^14.0.3",
93
90
  "cookie-parser": "^1.4.7",
94
- "dotenv": "^17.4.1",
91
+ "dotenv": "^17.4.2",
95
92
  "express": "^4.21.2",
96
93
  "google-auth-library": "^10.6.2",
97
94
  "helmet": "^8.1.0",
@@ -104,7 +101,7 @@
104
101
  "vitest-fail-on-console": "^0.10.1"
105
102
  },
106
103
  "peerDependencies": {
107
- "@vitejs/plugin-react": ">=4.3.4",
104
+ "@vitejs/plugin-react": ">=6.0.1",
108
105
  "arktype": ">=2.0.0",
109
106
  "express": ">=4.21.2 <5.0.0",
110
107
  "faktory-worker": ">=4.5.1",
@@ -113,5 +110,5 @@
113
110
  "engines": {
114
111
  "node": ">=20 <26"
115
112
  },
116
- "gitHead": "04f69fb0aafc073cc734a0cfc9d278b5850d4ec2"
113
+ "gitHead": "ea4f51756aca98bd050cf98128bf4d4b3da1bf38"
117
114
  }
package/dist/sentry.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import type * as Sentry from '@sentry/node';
2
- export type { Sentry };
3
- export interface SentryOptions extends Sentry.NodeOptions {
4
- }
5
- export declare function sentryOptions(options?: SentryOptions): Promise<SentryOptions>;
package/dist/sentry.js DELETED
@@ -1,38 +0,0 @@
1
- import { config } from '@devmoods/config';
2
- /**
3
- * Safely imports native Sentry integrations if available.
4
- * Returns null if native modules are not available (e.g., on unsupported Node.js versions).
5
- */
6
- async function getNativeIntegrations() {
7
- const integrations = [];
8
- /* v8 ignore next -- @preserve */
9
- try {
10
- const { nodeProfilingIntegration } = await import('@sentry/profiling-node');
11
- integrations.push(nodeProfilingIntegration());
12
- }
13
- catch {
14
- // Native profiling integration not available
15
- }
16
- /* v8 ignore next -- @preserve */
17
- try {
18
- const { eventLoopBlockIntegration } = await import('@sentry/node-native');
19
- integrations.push(eventLoopBlockIntegration({
20
- threshold: config.SENTRY_EVENT_LOOP_THRESHOLD,
21
- }));
22
- }
23
- catch {
24
- // Native event loop integration not available
25
- }
26
- return integrations;
27
- }
28
- export async function sentryOptions(options = {}) {
29
- const nativeIntegrations = await getNativeIntegrations();
30
- return {
31
- dsn: config.SENTRY_DSN,
32
- tracesSampleRate: config.SENTRY_TRACES_SAMPLE_RATE,
33
- profilesSampleRate: config.SENTRY_PROFILES_SAMPLE_RATE,
34
- integrations: nativeIntegrations,
35
- enableLogs: true,
36
- ...options,
37
- };
38
- }