express-generator-typescript 3.0.4 → 3.1.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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +1 -20
  3. package/lib/template/README.md +3 -2
  4. package/lib/template/package.json +5 -5
  5. package/lib/template/src/common/classes/route-errors.ts +0 -13
  6. package/lib/template/src/common/constants/Paths/Paths.ts +29 -0
  7. package/lib/template/src/common/constants/Paths/helpers.ts +29 -0
  8. package/lib/template/src/common/constants/Paths/index.ts +1 -0
  9. package/lib/template/src/common/constants/{environment-consts.ts → env.constants.ts} +13 -15
  10. package/lib/template/src/common/utils/{date-utils.ts → date.utils.ts} +10 -12
  11. package/lib/template/src/common/utils/validators.ts +0 -23
  12. package/lib/template/src/entities/User/module.ts +77 -28
  13. package/lib/template/src/entities/common/entity-base-types.ts +1 -1
  14. package/lib/template/src/main.ts +3 -3
  15. package/lib/template/src/public/scripts/lib/bootstrap.bundle.min.js +0 -1
  16. package/lib/template/src/public/scripts/users.js +19 -10
  17. package/lib/template/src/public/stylesheets/lib/bootstrap.min.css +0 -1
  18. package/lib/template/src/public/stylesheets/users.css +24 -1
  19. package/lib/template/src/repos/MockOrm.ts +2 -2
  20. package/lib/template/src/repos/UserRepo.ts +5 -15
  21. package/lib/template/src/repos/common/database.json +3 -3
  22. package/lib/template/src/{routes/apiRouter.ts → routers/api.router.ts} +3 -11
  23. package/lib/template/src/{routes/UserRoutes.ts → routers/user.router.ts} +37 -46
  24. package/lib/template/src/server.ts +20 -7
  25. package/lib/template/src/views/users.html +8 -0
  26. package/lib/template/tests/support/utils.ts +23 -0
  27. package/lib/template/tests/users.test.ts +99 -50
  28. package/package.json +6 -5
  29. package/lib/template/src/common/constants/Paths.ts +0 -16
  30. package/lib/template/src/entities/User/_internal/validators.ts +0 -47
  31. package/lib/template/src/routes/common/express-types.ts +0 -9
  32. package/lib/template/src/routes/common/parseReq.ts +0 -23
  33. package/lib/template/tests/common/comparators.ts +0 -36
  34. package/lib/template/tests/common/supertest-types.ts +0 -11
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "users": [
3
3
  {
4
- "id": "2HF7W6BZV-23RYN-0GY0E-80X19N",
4
+ "id": "01a12230-a18b-721e-bf21-0ee836fa989d",
5
5
  "name": "Sean Maxwell",
6
6
  "email": "smaxwell@example.com",
7
7
  "created": "2024-03-22T05:14:36.252Z"
8
8
  },
9
9
  {
10
- "id": "56N1CSRF5-SG3XG-M7CEY-TE5G45",
10
+ "id": "01a12230-a18c-7473-b1f0-845016f412d3",
11
11
  "name": "John Smith",
12
12
  "email": "john.smith@example.com",
13
13
  "created": "2024-03-22T05:20:55.079Z"
14
14
  },
15
15
  {
16
- "id": "Q78BTED9D-HBPJQ-GFEYQ-3EVFJF",
16
+ "id": "01a12230-a18c-7473-b1f1-88ed3ea1d5ae",
17
17
  "name": "Gordan Freeman",
18
18
  "email": "nova@prospect.com",
19
19
  "created": "2024-03-22T05:42:18.895Z"
@@ -2,7 +2,7 @@ import { Router } from 'express';
2
2
 
3
3
  import Paths from '@src/common/constants/Paths';
4
4
 
5
- import UserRoutes from './UserRoutes';
5
+ import userRouter from './user.router';
6
6
 
7
7
  // ========================================================================= //
8
8
  // EXEC //
@@ -10,16 +10,8 @@ import UserRoutes from './UserRoutes';
10
10
 
11
11
  const apiRouter = Router();
12
12
 
13
- // ============================ Add `userRouter` =========================== //
14
-
15
- const userRouter = Router();
16
-
17
- userRouter.get(Paths.Users.Get.$path, UserRoutes.getAll);
18
- userRouter.post(Paths.Users.Add.$path, UserRoutes.addOne);
19
- userRouter.put(Paths.Users.Update.$path, UserRoutes.updateOne);
20
- userRouter.delete(Paths.Users.Delete.$path, UserRoutes.deleteOne);
21
-
22
- apiRouter.use(Paths.Users.$path, userRouter);
13
+ // Add more routers as your app grows (e.g. `postsRouter`)
14
+ apiRouter.use(Paths.Users.PATH, userRouter);
23
15
 
24
16
  // ========================================================================= //
25
17
  // EXPORT //
@@ -1,74 +1,65 @@
1
+ import { Router } from 'express';
2
+ import validate from 'express-zod-safe';
3
+
1
4
  import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
5
+ import Paths from '@src/common/constants/Paths';
2
6
  import User from '@src/entities/User';
3
7
  import UserService from '@src/services/UserService';
4
8
 
5
- import type { Req, Res } from './common/express-types';
6
- import parseReq from './common/parseReq';
7
-
8
9
  // ========================================================================= //
9
- // EXEC //
10
+ // CONSTANTS //
10
11
  // ========================================================================= //
11
12
 
12
- const validators = {
13
- add: parseReq({ user: User.isInput }),
14
- update: parseReq({ user: User.isComplete }),
15
- delete: parseReq({ id: User.isId }),
13
+ // Shorthand for the path strings
14
+ const P = {
15
+ GET: Paths.Users.Get.PATH,
16
+ ADD: Paths.Users.Add.PATH,
17
+ UPDATE: Paths.Users.Update.PATH,
18
+ DELETE: Paths.Users.Delete.PATH,
19
+ } as const;
20
+
21
+ // Middlware `validators`
22
+ const V = {
23
+ add: validate({ body: { user: User.Schemas.Input } }),
24
+ update: validate({ body: { user: User.Schemas.Main } }),
25
+ delete: validate({ params: { id: User.Schemas.Id } }),
16
26
  } as const;
17
27
 
18
28
  // ========================================================================= //
19
- // FUNCTIONS //
29
+ // EXEC //
20
30
  // ========================================================================= //
21
31
 
22
- /**
23
- * Get all users.
24
- *
25
- * @route GET /api/users/all
26
- */
27
- async function getAll(_: Req, res: Res) {
32
+ const router = Router();
33
+
34
+ // Get all users
35
+ router.get(P.GET, async (_, res) => {
28
36
  const users = await UserService.getAll();
29
37
  res.status(HttpStatusCodes.OK).json({ users });
30
- }
38
+ });
31
39
 
32
- /**
33
- * Add one user.
34
- *
35
- * @route POST /api/users/add
36
- */
37
- async function addOne(req: Req, res: Res) {
38
- const { user } = validators.add(req.body);
40
+ // Add one user
41
+ router.post(P.ADD, V.add, async (req, res) => {
42
+ const { user } = req.body;
39
43
  const created = await UserService.addOne(user);
40
44
  res.status(HttpStatusCodes.CREATED).json({ user: created });
41
- }
45
+ });
42
46
 
43
- /**
44
- * Update one user.
45
- *
46
- * @route PUT /api/users/update
47
- */
48
- async function updateOne(req: Req, res: Res) {
49
- const { user } = validators.update(req.body);
47
+ // Update one user
48
+ router.put(P.UPDATE, V.update, async (req, res) => {
49
+ const { user } = req.body;
50
50
  await UserService.updateOne(user);
51
51
  res.status(HttpStatusCodes.OK).end();
52
- }
52
+ });
53
53
 
54
- /**
55
- * Delete one user.
56
- *
57
- * @route DELETE /api/users/delete/:id
58
- */
59
- async function deleteOne(req: Req, res: Res) {
60
- const { id } = validators.delete(req.params);
54
+ // Delete one user
55
+ router.delete(P.DELETE, V.delete, async (req, res) => {
56
+ const { id } = req.params;
61
57
  await UserService.deleteOne(id);
62
58
  res.status(HttpStatusCodes.OK).end();
63
- }
59
+ });
64
60
 
65
61
  // ========================================================================= //
66
62
  // EXPORT //
67
63
  // ========================================================================= //
68
64
 
69
- export default {
70
- getAll,
71
- addOne,
72
- updateOne,
73
- deleteOne,
74
- } as const;
65
+ export default router;
@@ -11,9 +11,9 @@ import path from 'path';
11
11
  import { RouteError } from '@src/common/classes/route-errors';
12
12
  import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
13
13
  import Paths from '@src/common/constants/Paths';
14
- import BaseRouter from '@src/routes/apiRouter';
14
+ import BaseRouter from '@src/routers/api.router';
15
15
 
16
- import { EnvVars, NodeEnvs } from './common/constants/environment-consts';
16
+ import { Env, NodeEnvs } from './common/constants/env.constants';
17
17
 
18
18
  // ========================================================================= //
19
19
  // EXEC //
@@ -28,17 +28,17 @@ app.use(express.json());
28
28
  app.use(express.urlencoded({ extended: true }));
29
29
 
30
30
  // Show routes called in console during development
31
- if (EnvVars.NODE_ENV === NodeEnvs.DEVELOPMENT) {
31
+ if (Env.NODE_ENV === NodeEnvs.DEVELOPMENT) {
32
32
  app.use(morgan('dev'));
33
33
  }
34
34
 
35
35
  // Security
36
- if (EnvVars.NODE_ENV === NodeEnvs.PRODUCTION) {
36
+ if (Env.NODE_ENV === NodeEnvs.PRODUCTION) {
37
37
  app.use(helmet());
38
38
  }
39
39
 
40
40
  // Add APIs, must be after middleware
41
- app.use(Paths.$path, BaseRouter);
41
+ app.use(Paths.PATH, BaseRouter);
42
42
 
43
43
  // Add error handler. `_next` must stay: Express only treats 4-arg middleware
44
44
  // as an error handler.
@@ -48,7 +48,20 @@ app.use((err: Error, _: Request, res: Response, _next: NextFunction) => {
48
48
  .status(err.status)
49
49
  .json({ error: err.message, errors: err.errors });
50
50
  }
51
- if (EnvVars.NODE_ENV !== NodeEnvs.TEST) {
51
+ // Body-parser errors are client errors; don't expose the rejected body.
52
+ if ('type' in err) {
53
+ if (err.type === 'entity.parse.failed') {
54
+ return res
55
+ .status(HttpStatusCodes.BAD_REQUEST)
56
+ .json({ error: 'Invalid JSON' });
57
+ }
58
+ if (err.type === 'entity.too.large') {
59
+ return res
60
+ .status(HttpStatusCodes.PAYLOAD_TOO_LARGE)
61
+ .json({ error: 'Request body too large' });
62
+ }
63
+ }
64
+ if (Env.NODE_ENV !== NodeEnvs.TEST) {
52
65
  logger.err(err, true);
53
66
  }
54
67
  return res
@@ -64,7 +77,7 @@ const staticDir = path.join(import.meta.dirname, 'public');
64
77
 
65
78
  // Refresh the browser on changes. Imported lazily because livereload is a
66
79
  // dev dependency and isn't installed in production.
67
- if (EnvVars.NODE_ENV === NodeEnvs.DEVELOPMENT) {
80
+ if (Env.NODE_ENV === NodeEnvs.DEVELOPMENT) {
68
81
  const { setupLiveReload } = await import('@src/common/utils/dev-only');
69
82
  setupLiveReload(app, [staticDir, viewsDir]);
70
83
  }
@@ -3,6 +3,7 @@
3
3
  <!-- Head -->
4
4
  <head>
5
5
  <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
7
  <title>Users</title>
7
8
  <link rel="stylesheet" type="text/css" href="stylesheets/users.css" />
8
9
 
@@ -50,6 +51,13 @@
50
51
  <div id="all-users-anchor"></div>
51
52
  </div>
52
53
 
54
+ <!-- Error Display -->
55
+ <aside class="users-column errors-column" aria-labelledby="errors-heading">
56
+ <div id="errors-heading" class="column-header">Error Messages:</div>
57
+ <pre id="error-display" role="status" aria-live="polite">
58
+ No errors to display.</pre>
59
+ </aside>
60
+
53
61
  <script src="/scripts/HttpClient.js"></script>
54
62
  <script src="/scripts/renderUsers.js"></script>
55
63
  <script src="/scripts/users.js"></script>
@@ -0,0 +1,23 @@
1
+ import type { Response } from 'supertest';
2
+
3
+ // ========================================================================= //
4
+ // TYPES //
5
+ // ========================================================================= //
6
+
7
+ export type TypedRes<T> = Omit<Response, 'body'> & { body: T };
8
+ export type TypedErrRes<T = 'string'> = TypedRes<{ error: T }>;
9
+
10
+ interface ValidationError {
11
+ errors: { message: string }[];
12
+ }
13
+
14
+ // ========================================================================= //
15
+ // HELPERS //
16
+ // ========================================================================= //
17
+
18
+ /**
19
+ * Get the error message from a superTest Response object.
20
+ */
21
+ export function extractErrMsg(res: TypedRes<ValidationError[]>) {
22
+ return res.body[0].errors[0].message;
23
+ }
@@ -1,28 +1,47 @@
1
- import jetid from 'jet-id';
2
-
3
- import { ValidationError } from '@src/common/classes/route-errors';
4
1
  import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
5
2
  import Paths from '@src/common/constants/Paths';
6
3
  import User, { type UserEntity } from '@src/entities/User';
7
4
  import UserRepo, { UserRepoTestOnly } from '@src/repos/UserRepo';
8
5
  import UserService from '@src/services/UserService';
9
6
 
10
- import { compareUserArrays } from './common/comparators';
11
- import type { TestRes } from './common/supertest-types';
12
7
  import { agent } from './support/agent';
8
+ import {
9
+ extractErrMsg,
10
+ type TypedErrRes,
11
+ type TypedRes,
12
+ } from './support/utils';
13
13
 
14
14
  // ========================================================================= //
15
15
  // CONSTANTS //
16
16
  // ========================================================================= //
17
17
 
18
- // Path constants
19
- const PATH_USERS_GET = Paths.Users.Get();
20
- const PATH_USERS_ADD = Paths.Users.Add();
21
- const PATH_USERS_UPDATE = Paths.Users.Update();
22
- const PATH_USERS_DELETE = Paths.Users.Delete.$tmpl;
18
+ // Shorthand the `Paths`
19
+ const P = {
20
+ Get: {
21
+ CALL: Paths.Users.Get.FULL_PATH,
22
+ DISPLAY: Paths.Users.Get.DISPLAY,
23
+ },
24
+ Add: {
25
+ CALL: Paths.Users.Add.FULL_PATH,
26
+ DISPLAY: Paths.Users.Add.DISPLAY,
27
+ },
28
+ Update: {
29
+ CALL: Paths.Users.Update.FULL_PATH,
30
+ DISPLAY: Paths.Users.Update.DISPLAY,
31
+ },
32
+ Delete: {
33
+ CALL: Paths.Users.Delete.format,
34
+ DISPLAY: Paths.Users.Delete.DISPLAY,
35
+ FULL_PATH: Paths.Users.Delete.FULL_PATH,
36
+ },
37
+ } as const;
23
38
 
24
39
  // Errors
25
- const ERR_USER_NOT_FOUND = UserService.Errors.USER_NOT_FOUND;
40
+ const Errors = {
41
+ ...UserService.Errors,
42
+ } as const;
43
+
44
+ // Status codes
26
45
  const { BAD_REQUEST, CREATED, INTERNAL_SERVER_ERROR, OK, NOT_FOUND } =
27
46
  HttpStatusCodes;
28
47
 
@@ -51,15 +70,15 @@ describe('UserRouter', () => {
51
70
  vi.restoreAllMocks();
52
71
  });
53
72
 
54
- // ---- `Get`
55
- describe(`"GET - ${PATH_USERS_GET}"`, () => {
73
+ // --- `Get` ---
74
+ describe(P.Get.DISPLAY, () => {
56
75
  describe('on success', () => {
57
76
  it(`returns all users as JSON with status "${OK}"`, async () => {
58
- const res: TestRes<{ users: UserEntity[] }> =
59
- await agent.get(PATH_USERS_GET);
77
+ const res: TypedRes<{ users: UserEntity[] }> = await agent.get(
78
+ P.Get.CALL,
79
+ );
60
80
  expect(res.status).toBe(OK);
61
- const usersMatch = compareUserArrays(res.body.users, DUMMY_USERS);
62
- expect(usersMatch).toBeTruthy();
81
+ expect(res.body.users).toStrictEqual(DUMMY_USERS);
63
82
  });
64
83
  });
65
84
 
@@ -68,24 +87,24 @@ describe('UserRouter', () => {
68
87
  const error = new Error('boom');
69
88
  const getAllSpy = vi.spyOn(UserService, 'getAll');
70
89
  getAllSpy.mockRejectedValueOnce(error);
71
- const res: TestRes = await agent.get(PATH_USERS_GET);
90
+ const res: TypedErrRes = await agent.get(P.Get.CALL);
72
91
  expect(res.status).toBe(INTERNAL_SERVER_ERROR);
73
92
  expect(res.body.error).toBe('Internal Server Error');
74
93
  });
75
94
  });
76
95
  });
77
96
 
78
- // ---- `Add`
79
- describe(`"POST - ${PATH_USERS_ADD}"`, () => {
97
+ // --- `Add` ---
98
+ describe(P.Add.DISPLAY, () => {
80
99
  describe('on success', () => {
81
100
  it(`persists the user and returns status "${CREATED}"`, async () => {
82
101
  // Same payload shape the front-end sends
83
102
  const input = { name: 'a', email: 'a@a.com' };
84
- const res: TestRes<{ user: UserEntity }> = await agent
85
- .post(PATH_USERS_ADD)
103
+ const res: TypedRes<{ user: UserEntity }> = await agent
104
+ .post(P.Add.CALL)
86
105
  .send({ user: input });
87
106
  expect(res.status).toBe(CREATED);
88
- const isId = User.isId(res.body.user.id);
107
+ const isId = User.is.id(res.body.user.id);
89
108
  expect(isId).toBe(true);
90
109
  const all = await UserRepo.getAll();
91
110
  expect(all).toHaveLength(DUMMY_USERS.length + 1);
@@ -95,33 +114,63 @@ describe('UserRouter', () => {
95
114
 
96
115
  describe('when the user param is missing', () => {
97
116
  it('returns a validation error', async () => {
98
- const res: TestRes = await agent
99
- .post(PATH_USERS_ADD)
100
- .send({ user: null });
117
+ const res = await agent.post(P.Add.CALL).send({ user: null });
101
118
  expect(res.status).toBe(BAD_REQUEST);
102
- expect(res.body.error).toBe(ValidationError.MESSAGE);
103
- expect(res.body.errors?.[0].key).toStrictEqual('user');
119
+ const errMsg = extractErrMsg(res);
120
+ expect(errMsg).toBe('Invalid input: expected object, received null');
104
121
  });
105
122
  });
106
123
 
107
124
  describe('when the name is empty', () => {
108
125
  it(`returns status "${BAD_REQUEST}"`, async () => {
109
- const res: TestRes = await agent
110
- .post(PATH_USERS_ADD)
126
+ const res = await agent
127
+ .post(P.Add.CALL)
111
128
  .send({ user: { name: '', email: 'a@a.com' } });
112
129
  expect(res.status).toBe(BAD_REQUEST);
130
+ // Verify it didn't persist in the database
113
131
  const allUsers = await UserRepo.getAll();
114
132
  expect(allUsers).toHaveLength(DUMMY_USERS.length);
115
133
  });
116
134
  });
135
+
136
+ describe('when the request body cannot be parsed', () => {
137
+ it('returns 400 for malformed JSON without persisting a user', async () => {
138
+ const res: TypedErrRes = await agent
139
+ .post(P.Add.CALL)
140
+ .set('Content-Type', 'application/json')
141
+ .send('{"user":');
142
+ expect(res.status).toBe(BAD_REQUEST);
143
+ expect(res.body).toStrictEqual({ error: 'Invalid JSON' });
144
+ expect(await UserRepo.getAll()).toStrictEqual(DUMMY_USERS);
145
+ });
146
+
147
+ it.each([
148
+ [
149
+ 'application/json',
150
+ JSON.stringify({ user: { name: 'a'.repeat(110000) } }),
151
+ ],
152
+ [
153
+ 'application/x-www-form-urlencoded',
154
+ `user[name]=${'a'.repeat(110000)}`,
155
+ ],
156
+ ])('returns 413 for an oversized %s body', async (contentType, body) => {
157
+ const res: TypedErrRes = await agent
158
+ .post(P.Add.CALL)
159
+ .set('Content-Type', contentType)
160
+ .send(body);
161
+ expect(res.status).toBe(HttpStatusCodes.PAYLOAD_TOO_LARGE);
162
+ expect(res.body).toStrictEqual({ error: 'Request body too large' });
163
+ expect(await UserRepo.getAll()).toStrictEqual(DUMMY_USERS);
164
+ });
165
+ });
117
166
  });
118
167
 
119
- // ---- `Update`
120
- describe(`"PUT - ${PATH_USERS_UPDATE}"`, () => {
168
+ // --- `Update` ---
169
+ describe(P.Update.DISPLAY, () => {
121
170
  describe('on success', () => {
122
171
  it(`saves the change and returns status "${OK}"`, async () => {
123
172
  const user = { ...dbUsers[0], name: 'Bill' };
124
- const res = await agent.put(PATH_USERS_UPDATE).send({ user });
173
+ const res = await agent.put(P.Update.CALL).send({ user });
125
174
  expect(res.status).toBe(OK);
126
175
  const allUsers = await UserRepo.getAll();
127
176
  const saved = allUsers.find((u) => u.id === user.id);
@@ -138,30 +187,29 @@ describe('UserRouter', () => {
138
187
  email: 'a@a.com',
139
188
  id: '5',
140
189
  };
141
- const res: TestRes = await agent.put(PATH_USERS_UPDATE).send({ user });
190
+ const res = await agent.put(P.Update.CALL).send({ user });
142
191
  expect(res.status).toBe(BAD_REQUEST);
143
- expect(res.body.error).toBe(ValidationError.MESSAGE);
144
- expect(res.body.errors?.[0].keyPath).toStrictEqual(['user', 'id']);
192
+ const errMsg = extractErrMsg(res);
193
+ expect(errMsg).toBe(errMsg);
145
194
  });
146
195
  });
147
196
 
148
197
  describe('when the user is not found', () => {
149
198
  it(`returns status "${NOT_FOUND}"`, async () => {
150
- const id = jetid();
151
- const user = User.create({ id, name: 'a', email: 'a@a.com' });
152
- const res: TestRes = await agent.put(PATH_USERS_UPDATE).send({ user });
199
+ const user = User.create({ name: 'a', email: 'a@a.com' });
200
+ const res: TypedErrRes = await agent.put(P.Update.CALL).send({ user });
153
201
  expect(res.status).toBe(NOT_FOUND);
154
- expect(res.body.error).toBe(ERR_USER_NOT_FOUND);
202
+ expect(res.body.error).toBe(Errors.USER_NOT_FOUND);
155
203
  });
156
204
  });
157
205
  });
158
206
 
159
- // ---- `Delete`
160
- describe(`"DELETE - ${PATH_USERS_DELETE}"`, () => {
207
+ // --- `Delete` ---
208
+ describe(P.Delete.DISPLAY, () => {
161
209
  describe('on success', () => {
162
210
  it(`removes the user and returns status "${OK}"`, async () => {
163
211
  const id = dbUsers[0].id;
164
- const path = Paths.Users.Delete({ id });
212
+ const path = P.Delete.CALL(id);
165
213
  const res = await agent.delete(path);
166
214
  expect(res.status).toBe(OK);
167
215
  const persists = await UserRepo.persists(id);
@@ -171,17 +219,17 @@ describe('UserRouter', () => {
171
219
 
172
220
  describe('when the user is not found', () => {
173
221
  it(`returns status "${NOT_FOUND}"`, async () => {
174
- const id = jetid();
175
- const path = Paths.Users.Delete({ id });
176
- const res: TestRes = await agent.delete(path);
222
+ const id = User.genId();
223
+ const path = P.Delete.CALL(id);
224
+ const res: TypedErrRes = await agent.delete(path);
177
225
  expect(res.status).toBe(NOT_FOUND);
178
- expect(res.body.error).toBe(ERR_USER_NOT_FOUND);
226
+ expect(res.body.error).toBe(Errors.USER_NOT_FOUND);
179
227
  });
180
228
  });
181
229
 
182
230
  describe('when the id is invalid', () => {
183
231
  it(`returns status "${BAD_REQUEST}"`, async () => {
184
- const path = Paths.Users.Delete({ id: -1 });
232
+ const path = P.Delete.CALL('asdf');
185
233
  const res = await agent.delete(path);
186
234
  expect(res.status).toBe(BAD_REQUEST);
187
235
  });
@@ -189,9 +237,10 @@ describe('UserRouter', () => {
189
237
 
190
238
  describe('when the id path param is missing', () => {
191
239
  it(`returns status "${BAD_REQUEST}"`, async () => {
192
- const res: TestRes = await agent.delete(PATH_USERS_DELETE);
240
+ const res = await agent.delete(P.Delete.FULL_PATH);
193
241
  expect(res.status).toBe(BAD_REQUEST);
194
- expect(res.body.errors?.[0].key).toStrictEqual('id');
242
+ const errMsg = extractErrMsg(res);
243
+ expect(errMsg).toStrictEqual('Not a valid uuid7 id');
195
244
  });
196
245
  });
197
246
  });
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "express-generator-typescript",
3
- "version": "3.0.4",
3
+ "version": "3.1.0",
4
4
  "description": "Generate new Express applications similar to express-generate which but sets it up to use TypeScript instead",
5
5
  "scripts": {
6
- "commit": "git add -A && git commit -m \"dev commit\"",
7
- "commit:push": "npm run commit && git push",
8
6
  "install:clean": "rm -rf node_modules && rm -rf package-lock.json && npm i",
9
7
  "test": "node --test \"tests/**/*.test.js\"",
10
- "test:e2e": "node scripts/e2e.js"
8
+ "test:e2e": "node scripts/e2e.js",
9
+ "_local:commit": "tsx --tsconfig ../my-tools/tsconfig.json ../my-tools/src/workflows/commit/run.ts",
10
+ "_local:commit:push": "npm run _local:commit -- -p",
11
+ "_local:squash-history": "tsx --tsconfig ../my-tools/tsconfig.json ../my-tools/src/workflows/squash-history/run.ts"
11
12
  },
12
13
  "main": "lib/express-generator-typescript.js",
13
14
  "bin": {
@@ -25,7 +26,7 @@
25
26
  "!lib/template/src/repos/common/database.test.json"
26
27
  ],
27
28
  "engines": {
28
- "node": ">=22.12.0"
29
+ "node": "^22.13.0 || ^24.0.0 || >=26.0.0"
29
30
  },
30
31
  "repository": {
31
32
  "type": "git",
@@ -1,16 +0,0 @@
1
- import jetPaths from 'jet-paths';
2
-
3
- // ========================================================================= //
4
- // EXPORT //
5
- // ========================================================================= //
6
-
7
- export default jetPaths({
8
- $path: '/api',
9
- Users: {
10
- $path: '/users',
11
- Get: '/all',
12
- Add: '/add',
13
- Update: '/update',
14
- Delete: '/delete/:id',
15
- },
16
- } as const);
@@ -1,47 +0,0 @@
1
- import jetid from 'jet-id';
2
- import { isNonEmptyString, isString } from 'jet-validators';
3
- import { parseObject, type Schema, testObject } from 'jet-validators/utils';
4
-
5
- import { isISOString } from '@src/common/utils/date-utils';
6
-
7
- import type { UserEntity, UserInput } from '../types';
8
-
9
- // ========================================================================= //
10
- // FUNCTIONS //
11
- // ========================================================================= //
12
-
13
- const schema: Schema<UserEntity> = {
14
- id: isUserId,
15
- name: isString,
16
- email: isString,
17
- created: isISOString,
18
- };
19
-
20
- /**
21
- * Validate the `User` schema.
22
- */
23
- export const parseUser = parseObject<UserEntity>(schema);
24
-
25
- /**
26
- * For the APIs make sure the right fields are complete
27
- */
28
- export const isCompleteUser = testObject<UserEntity>({
29
- ...schema,
30
- name: isNonEmptyString,
31
- email: isNonEmptyString,
32
- });
33
-
34
- /**
35
- * Validate the fields a client sends to create a user.
36
- */
37
- export const isUserInput = testObject<UserInput>({
38
- name: isNonEmptyString,
39
- email: isNonEmptyString,
40
- });
41
-
42
- /**
43
- * Test if an id is a valid user id.
44
- */
45
- export function isUserId(val: unknown): val is string {
46
- return jetid.test(val);
47
- }
@@ -1,9 +0,0 @@
1
- import type { Request, Response } from 'express';
2
-
3
- // ========================================================================= //
4
- // TYPES //
5
- // ========================================================================= //
6
-
7
- // Body is `unknown` until it has been validated with `parseReq`.
8
- export type Req = Request<Record<string, string>, unknown, unknown>;
9
- export type Res = Response;
@@ -1,23 +0,0 @@
1
- import { parseObject, type Schema } from 'jet-validators/utils';
2
-
3
- import { ValidationError } from '@src/common/classes/route-errors';
4
-
5
- // ========================================================================= //
6
- // FUNCTIONS //
7
- // ========================================================================= //
8
-
9
- /**
10
- * Build a request parser for `schema` that throws a `ValidationError`
11
- * (400) listing every failed field.
12
- */
13
- function parseReq<U extends Schema>(schema: U) {
14
- return parseObject(schema, (errors) => {
15
- throw new ValidationError(errors);
16
- });
17
- }
18
-
19
- // ========================================================================= //
20
- // EXPORT //
21
- // ========================================================================= //
22
-
23
- export default parseReq;