@devmoods/express-extras 0.56.0 → 0.57.1

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/README.md CHANGED
@@ -1,33 +1,101 @@
1
1
  # @devmoods/express-extras
2
2
 
3
- > Kubernetes-ready utilities for rapid Node.js application development
3
+ > Express evolved: Type-safe routing, elegant transactions, and production-ready patterns for modern Node.js
4
4
 
5
5
  _Extremely opinionated_ set of utilities for cranking out production-ready Node.js apps in a timely manner. We assume you are using PostgreSQL as the main database, Redis for data you don't want in Postgres and [Faktory](https://contribsys.com/faktory/) for background jobs.
6
6
 
7
7
  - Only works with [ECMAScript Modules (ESM)](https://nodejs.org/api/esm.html)
8
- - Thin layer on top of [Express](https://expressjs.com) with better support for `async`/`await`
8
+ - Thin layer on top of [Express](https://expressjs.com) with better support for `async`/`await` and type safety.
9
+
10
+ ```typescript
11
+ const router = createRouter();
12
+
13
+ router.put(
14
+ '/posts/:id',
15
+ {
16
+ request: type({ title: 'string' }),
17
+ response: type({ id: 'number', title: 'string' }),
18
+ },
19
+ async (req) => {
20
+ await delay(1000);
21
+ return {
22
+ id: req.params.id,
23
+ ...req.body,
24
+ };
25
+ },
26
+ );
27
+ ```
28
+
29
+ - Clean graceful shutdown procedures and built-in health checks. Runs perfectly in Kubernetes.
9
30
  - PostgreSQL with transactions and migrations
31
+
32
+ ```bash
33
+ bin/dx manage migrate up
34
+ ```
35
+
36
+ ```typescript
37
+ await postgres.transaction(() => {
38
+ const post = await postgres.get<{ title: string }>(
39
+ sql`SELECT title FROM posts WHERE id = ${id} FOR UPDATE`,
40
+ );
41
+
42
+ await postgres.query(
43
+ sql`UPDATE posts SET ${sql.spreadUpdate({ title: post.title.split('').reverse().join('') })} WHERE id = ${id}`,
44
+ );
45
+
46
+ try {
47
+ // Nested transactions with savepoints
48
+ await postgres.transaction(() => {
49
+ })
50
+ } catch () {
51
+ }
52
+
53
+ throw new Error('please rollback');
54
+ });
55
+ ```
56
+
57
+ - CSRF protection
58
+ - Built in auth backends! Email+password, passwordless, easy to extend with more
59
+ - OpenAPI v3 generator
10
60
  - Redis
11
- - Slack (and Discord) client for sending operational notifications
61
+ - Slack/Discord client for sending operational notifications
12
62
  - CLI commands inspired by Django's management commands
13
- - Delayed jobs with [Faktory](https://contribsys.com/faktory/)
14
- - Validation of HTTP requests using [JSON Schema](https://json-schema.org/)s
63
+
64
+ ```bash
65
+ bin/dx manage --help
66
+ ```
67
+
68
+ ```typescript
69
+ registerManagementCommand((command, cli) => {
70
+ command('hello').action(() => {
71
+ cli.writeLine('Hello World');
72
+ });
73
+ });
74
+ ```
75
+
76
+ - Delayed/async jobs with [Faktory](https://contribsys.com/faktory/)
77
+ - Validation of HTTP requests using [Standard Schema](https://github.com/standard-schema/standard-schema), including [`jsonSchema()`](https://json-schema.org/) and [arktype](https://arktype.io/).
15
78
  - Logging helpers to write well-formatted contextual JSON logs to `stdout`
79
+
80
+ ```typescript
81
+ const logger = getLogger();
82
+
83
+ logger.info('hello world', { foo: 'bar', baz: 10 });
84
+ // {"level":"info","hostname":"machine.local","pid":43149,"name":"default","message":"hello world","foo":"bar","baz":10,"time":"2024-01-02T17:25:28.610Z","v":1}
85
+ ```
86
+
16
87
  - Request local context using [AsyncLocalStorage](https://nodejs.org/api/async_context.html) (e.g. request ids)
17
88
  - [RFC 7807 Problem Details for HTTP APIs](https://tools.ietf.org/html/rfc7807) by default
18
- - Works best with the [pnpm](https://pnpm.io/) package manager
19
89
  - Uses [Sentry](https://sentry.io/) for external error monitoring
20
- - Inspired by [The Twelve-Factor App](https://12factor.net/)
21
- - Graceful shutdown
22
- - Read configuration from `.env` files
90
+ - [The Twelve-Factor App](https://12factor.net/)
23
91
 
24
92
  ## Goals
25
93
 
26
- - Time-to-market
94
+ - Time-to-market and top-notch developer experience.
27
95
 
28
96
  ## Non-goals
29
97
 
30
- - _Encourage deviating from the chosen path_
98
+ - Support for multiple different database vendors and job processing systems.
31
99
 
32
100
  ## Example
33
101
 
@@ -105,6 +105,7 @@ export function setAccessTokenCookie(res, token) {
105
105
  }
106
106
  else {
107
107
  res.cookie(ACCESS_TOKEN_COOKIE_NAME, token, {
108
+ expires: new Date(Date.now() + config.value.AUTH_TOKEN_EXPIRES_IN * 1000),
108
109
  httpOnly: true,
109
110
  sameSite,
110
111
  secure: process.env.NODE_ENV === 'production',
package/dist/router.d.ts CHANGED
@@ -19,19 +19,19 @@ export declare class TypedRouter {
19
19
  get<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
20
20
  ...RequestHandler[],
21
21
  TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
22
- ]): void;
22
+ ]): this;
23
23
  post<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
24
24
  ...RequestHandler[],
25
25
  TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
26
- ]): void;
26
+ ]): this;
27
27
  put<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
28
28
  ...RequestHandler[],
29
29
  TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
30
- ]): void;
30
+ ]): this;
31
31
  delete<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
32
32
  ...RequestHandler[],
33
33
  TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
34
- ]): void;
34
+ ]): this;
35
35
  use(...middleware: RequestHandler[]): this;
36
36
  use(...middleware: [string, ...RequestHandler[]]): this;
37
37
  getRouter(): import("express-serve-static-core").Router;
package/dist/router.js CHANGED
@@ -23,6 +23,7 @@ export class TypedRouter {
23
23
  query: validation.query,
24
24
  response: validation.response,
25
25
  }), ...middleware, asyncMiddleware(handler));
26
+ return this;
26
27
  }
27
28
  use(...middleware) {
28
29
  this.router.use(...middleware);
package/dist/validate.js CHANGED
@@ -104,6 +104,7 @@ export function jsonSchema(schema) {
104
104
  const ajv = new Ajv({
105
105
  allErrors: true,
106
106
  validateFormats: false,
107
+ coerceTypes: true,
107
108
  });
108
109
  ajv.addKeyword('$enumNames');
109
110
  ajv.addKeyword('$bitiforms');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devmoods/express-extras",
3
- "version": "0.56.0",
3
+ "version": "0.57.1",
4
4
  "description": "Kubernetes-ready utilities for rapid Node.js application development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,7 +44,7 @@
44
44
  ],
45
45
  "license": "ISC",
46
46
  "devDependencies": {
47
- "@devmoods/eslint-config": "^2.6.0",
47
+ "@devmoods/eslint-config": "^2.7.0",
48
48
  "@types/koa-compose": "^3.2.8",
49
49
  "@types/node": "22.13.10",
50
50
  "@types/supertest": "^6.0.2",