express-generator-typescript 3.0.4 → 3.2.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 (48) hide show
  1. package/README.md +10 -11
  2. package/bin/cli.js +1 -20
  3. package/lib/template/.prettierrc.json +1 -1
  4. package/lib/template/README.md +13 -15
  5. package/lib/template/eslint.config.ts +10 -1
  6. package/lib/template/gitignore +0 -1
  7. package/lib/template/package.json +19 -29
  8. package/lib/template/src/common/classes/route-errors.ts +1 -14
  9. package/lib/template/src/common/constants/HttpStatusCodes.ts +1 -1
  10. package/lib/template/src/common/constants/Paths/Paths.ts +29 -0
  11. package/lib/template/src/common/constants/Paths/helpers.ts +29 -0
  12. package/lib/template/src/common/constants/Paths/index.ts +1 -0
  13. package/lib/template/src/common/constants/{environment-consts.ts → env.constants.ts} +13 -15
  14. package/lib/template/src/common/utils/{date-utils.ts → date.utils.ts} +10 -12
  15. package/lib/template/src/common/utils/dev-only.ts +1 -1
  16. package/lib/template/src/common/utils/validators.ts +0 -23
  17. package/lib/template/src/entities/User/index.ts +2 -2
  18. package/lib/template/src/entities/User/module.ts +77 -28
  19. package/lib/template/src/entities/User/types.ts +1 -1
  20. package/lib/template/src/entities/common/entity-base-types.ts +1 -1
  21. package/lib/template/src/main.ts +4 -4
  22. package/lib/template/src/public/scripts/lib/bootstrap.bundle.min.js +0 -1
  23. package/lib/template/src/public/scripts/users.js +19 -10
  24. package/lib/template/src/public/stylesheets/lib/bootstrap.min.css +0 -1
  25. package/lib/template/src/public/stylesheets/users.css +24 -1
  26. package/lib/template/src/repos/MockOrm.ts +6 -6
  27. package/lib/template/src/repos/UserRepo.ts +6 -16
  28. package/lib/template/src/repos/common/database.json +3 -3
  29. package/lib/template/src/{routes/apiRouter.ts → routers/api.router.ts} +4 -12
  30. package/lib/template/src/routers/user.router.ts +65 -0
  31. package/lib/template/src/server.ts +24 -11
  32. package/lib/template/src/services/UserService.ts +6 -3
  33. package/lib/template/src/views/users.html +8 -0
  34. package/lib/template/tests/frontend.test.ts +28 -17
  35. package/lib/template/tests/support/agent.ts +4 -4
  36. package/lib/template/tests/support/utils.ts +23 -0
  37. package/lib/template/tests/users.test.ts +136 -83
  38. package/lib/template/tsconfig.json +10 -13
  39. package/package.json +6 -6
  40. package/lib/template/src/common/constants/Paths.ts +0 -16
  41. package/lib/template/src/entities/User/_internal/validators.ts +0 -47
  42. package/lib/template/src/routes/UserRoutes.ts +0 -74
  43. package/lib/template/src/routes/common/express-types.ts +0 -9
  44. package/lib/template/src/routes/common/parseReq.ts +0 -23
  45. package/lib/template/tests/common/comparators.ts +0 -36
  46. package/lib/template/tests/common/supertest-types.ts +0 -11
  47. package/lib/template/tsconfig.prod.json +0 -30
  48. package/lib/template/vitest.config.ts +0 -25
@@ -1,6 +1,22 @@
1
- import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
2
3
 
3
- import { agent } from './support/agent';
4
+ import HttpStatusCodes from '#src/common/constants/HttpStatusCodes.ts';
5
+
6
+ import { agent } from './support/agent.ts';
7
+
8
+ // ========================================================================= //
9
+ // CONSTANTS //
10
+ // ========================================================================= //
11
+
12
+ const STATIC_ASSETS = [
13
+ '/scripts/HttpClient.js',
14
+ '/scripts/renderUsers.js',
15
+ '/scripts/users.js',
16
+ '/scripts/lib/bootstrap.bundle.min.js',
17
+ '/stylesheets/users.css',
18
+ '/stylesheets/lib/bootstrap.min.css',
19
+ ] as const;
4
20
 
5
21
  // ========================================================================= //
6
22
  // TESTS //
@@ -9,25 +25,20 @@ import { agent } from './support/agent';
9
25
  describe('Front-end content', () => {
10
26
  it('should redirect "/" to the users page.', async () => {
11
27
  const res = await agent.get('/');
12
- expect(res.status).toBe(HttpStatusCodes.FOUND);
13
- expect(res.headers.location).toBe('/users');
28
+ assert.equal(res.status, HttpStatusCodes.FOUND);
29
+ assert.equal(res.headers.location, '/users');
14
30
  });
15
31
 
16
32
  it('should serve the users page.', async () => {
17
33
  const res = await agent.get('/users');
18
- expect(res.status).toBe(HttpStatusCodes.OK);
19
- expect(res.headers['content-type']).toMatch(/html/);
34
+ assert.equal(res.status, HttpStatusCodes.OK);
35
+ assert.match(res.headers['content-type'], /html/);
20
36
  });
21
37
 
22
- it.each([
23
- '/scripts/HttpClient.js',
24
- '/scripts/renderUsers.js',
25
- '/scripts/users.js',
26
- '/scripts/lib/bootstrap.bundle.min.js',
27
- '/stylesheets/users.css',
28
- '/stylesheets/lib/bootstrap.min.css',
29
- ])('should serve the static asset "%s".', async (asset) => {
30
- const res = await agent.get(asset);
31
- expect(res.status).toBe(HttpStatusCodes.OK);
32
- });
38
+ for (const asset of STATIC_ASSETS) {
39
+ it(`should serve the static asset "${asset}".`, async () => {
40
+ const res = await agent.get(asset);
41
+ assert.equal(res.status, HttpStatusCodes.OK);
42
+ });
43
+ }
33
44
  });
@@ -1,9 +1,9 @@
1
+ import { before } from 'node:test';
1
2
  import supertest, { type Test } from 'supertest';
2
3
  import type TestAgent from 'supertest/lib/agent.js';
3
- import { beforeAll } from 'vitest';
4
4
 
5
- import MockOrm from '@src/repos/MockOrm';
6
- import app from '@src/server';
5
+ import MockOrm from '#src/repos/MockOrm.ts';
6
+ import app from '#src/server.ts';
7
7
 
8
8
  // ========================================================================= //
9
9
  // EXEC //
@@ -11,7 +11,7 @@ import app from '@src/server';
11
11
 
12
12
  let agent: TestAgent<Test>;
13
13
 
14
- beforeAll(async () => {
14
+ before(async () => {
15
15
  agent = supertest.agent(app);
16
16
  await MockOrm.cleanDb();
17
17
  });
@@ -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,50 @@
1
- import jetid from 'jet-id';
2
-
3
- import { ValidationError } from '@src/common/classes/route-errors';
4
- import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
5
- import Paths from '@src/common/constants/Paths';
6
- import User, { type UserEntity } from '@src/entities/User';
7
- import UserRepo, { UserRepoTestOnly } from '@src/repos/UserRepo';
8
- import UserService from '@src/services/UserService';
9
-
10
- import { compareUserArrays } from './common/comparators';
11
- import type { TestRes } from './common/supertest-types';
12
- import { agent } from './support/agent';
1
+ import assert from 'node:assert/strict';
2
+ import { beforeEach, describe, it } from 'node:test';
3
+
4
+ import HttpStatusCodes from '#src/common/constants/HttpStatusCodes.ts';
5
+ import Paths from '#src/common/constants/Paths/index.ts';
6
+ import User, { type UserEntity } from '#src/entities/User/index.ts';
7
+ import UserRepo, { UserRepoTestOnly } from '#src/repos/UserRepo.ts';
8
+ import UserService from '#src/services/UserService.ts';
9
+
10
+ import { agent } from './support/agent.ts';
11
+ import {
12
+ extractErrMsg,
13
+ type TypedErrRes,
14
+ type TypedRes,
15
+ } from './support/utils.ts';
13
16
 
14
17
  // ========================================================================= //
15
18
  // CONSTANTS //
16
19
  // ========================================================================= //
17
20
 
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;
21
+ // Shorthand the `Paths`
22
+ const P = {
23
+ Get: {
24
+ CALL: Paths.Users.Get.FULL_PATH,
25
+ DISPLAY: Paths.Users.Get.DISPLAY,
26
+ },
27
+ Add: {
28
+ CALL: Paths.Users.Add.FULL_PATH,
29
+ DISPLAY: Paths.Users.Add.DISPLAY,
30
+ },
31
+ Update: {
32
+ CALL: Paths.Users.Update.FULL_PATH,
33
+ DISPLAY: Paths.Users.Update.DISPLAY,
34
+ },
35
+ Delete: {
36
+ CALL: Paths.Users.Delete.format,
37
+ DISPLAY: Paths.Users.Delete.DISPLAY,
38
+ FULL_PATH: Paths.Users.Delete.FULL_PATH,
39
+ },
40
+ } as const;
23
41
 
24
42
  // Errors
25
- const ERR_USER_NOT_FOUND = UserService.Errors.USER_NOT_FOUND;
43
+ const Errors = {
44
+ ...UserService.Errors,
45
+ } as const;
46
+
47
+ // Status codes
26
48
  const { BAD_REQUEST, CREATED, INTERNAL_SERVER_ERROR, OK, NOT_FOUND } =
27
49
  HttpStatusCodes;
28
50
 
@@ -47,85 +69,116 @@ describe('UserRouter', () => {
47
69
  dbUsers = await UserRepoTestOnly.insertMultiple(DUMMY_USERS);
48
70
  });
49
71
 
50
- afterEach(() => {
51
- vi.restoreAllMocks();
52
- });
53
-
54
- // ---- `Get`
55
- describe(`"GET - ${PATH_USERS_GET}"`, () => {
72
+ // --- `Get` ---
73
+ describe(P.Get.DISPLAY, () => {
56
74
  describe('on success', () => {
57
75
  it(`returns all users as JSON with status "${OK}"`, async () => {
58
- const res: TestRes<{ users: UserEntity[] }> =
59
- await agent.get(PATH_USERS_GET);
60
- expect(res.status).toBe(OK);
61
- const usersMatch = compareUserArrays(res.body.users, DUMMY_USERS);
62
- expect(usersMatch).toBeTruthy();
76
+ const res: TypedRes<{ users: UserEntity[] }> = await agent.get(
77
+ P.Get.CALL,
78
+ );
79
+ assert.equal(res.status, OK);
80
+ assert.deepEqual(res.body.users, DUMMY_USERS);
63
81
  });
64
82
  });
65
83
 
66
84
  describe('when an unexpected error is thrown', () => {
67
- it(`returns status "${INTERNAL_SERVER_ERROR}"`, async () => {
85
+ it(`returns status "${INTERNAL_SERVER_ERROR}"`, async (t) => {
68
86
  const error = new Error('boom');
69
- const getAllSpy = vi.spyOn(UserService, 'getAll');
70
- getAllSpy.mockRejectedValueOnce(error);
71
- const res: TestRes = await agent.get(PATH_USERS_GET);
72
- expect(res.status).toBe(INTERNAL_SERVER_ERROR);
73
- expect(res.body.error).toBe('Internal Server Error');
87
+ // Mocks made through `t.mock` are restored when the test ends
88
+ t.mock.method(UserService, 'getAll', () => Promise.reject(error));
89
+ const res: TypedErrRes = await agent.get(P.Get.CALL);
90
+ assert.equal(res.status, INTERNAL_SERVER_ERROR);
91
+ assert.equal(res.body.error, 'Internal Server Error');
74
92
  });
75
93
  });
76
94
  });
77
95
 
78
- // ---- `Add`
79
- describe(`"POST - ${PATH_USERS_ADD}"`, () => {
96
+ // --- `Add` ---
97
+ describe(P.Add.DISPLAY, () => {
80
98
  describe('on success', () => {
81
99
  it(`persists the user and returns status "${CREATED}"`, async () => {
82
100
  // Same payload shape the front-end sends
83
101
  const input = { name: 'a', email: 'a@a.com' };
84
- const res: TestRes<{ user: UserEntity }> = await agent
85
- .post(PATH_USERS_ADD)
102
+ const res: TypedRes<{ user: UserEntity }> = await agent
103
+ .post(P.Add.CALL)
86
104
  .send({ user: input });
87
- expect(res.status).toBe(CREATED);
88
- const isId = User.isId(res.body.user.id);
89
- expect(isId).toBe(true);
105
+ assert.equal(res.status, CREATED);
106
+ const isId = User.is.id(res.body.user.id);
107
+ assert.equal(isId, true);
90
108
  const all = await UserRepo.getAll();
91
- expect(all).toHaveLength(DUMMY_USERS.length + 1);
92
- expect(all).toContainEqual(res.body.user);
109
+ assert.equal(all.length, DUMMY_USERS.length + 1);
110
+ const saved = all.find((u) => u.id === res.body.user.id);
111
+ assert.deepEqual(saved, res.body.user);
93
112
  });
94
113
  });
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 });
101
- expect(res.status).toBe(BAD_REQUEST);
102
- expect(res.body.error).toBe(ValidationError.MESSAGE);
103
- expect(res.body.errors?.[0].key).toStrictEqual('user');
117
+ const res = await agent.post(P.Add.CALL).send({ user: null });
118
+ assert.equal(res.status, BAD_REQUEST);
119
+ const errMsg = extractErrMsg(res);
120
+ assert.equal(errMsg, '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
- expect(res.status).toBe(BAD_REQUEST);
129
+ assert.equal(res.status, BAD_REQUEST);
130
+ // Verify it didn't persist in the database
113
131
  const allUsers = await UserRepo.getAll();
114
- expect(allUsers).toHaveLength(DUMMY_USERS.length);
132
+ assert.equal(allUsers.length, 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
+ assert.equal(res.status, BAD_REQUEST);
143
+ assert.deepEqual(res.body, { error: 'Invalid JSON' });
144
+ assert.deepEqual(await UserRepo.getAll(), DUMMY_USERS);
145
+ });
146
+
147
+ const oversizedBodies = [
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
+ ] as const;
157
+
158
+ for (const [contentType, body] of oversizedBodies) {
159
+ it(`returns 413 for an oversized ${contentType} body`, async () => {
160
+ const res: TypedErrRes = await agent
161
+ .post(P.Add.CALL)
162
+ .set('Content-Type', contentType)
163
+ .send(body);
164
+ assert.equal(res.status, HttpStatusCodes.PAYLOAD_TOO_LARGE);
165
+ assert.deepEqual(res.body, { error: 'Request body too large' });
166
+ assert.deepEqual(await UserRepo.getAll(), DUMMY_USERS);
167
+ });
168
+ }
169
+ });
117
170
  });
118
171
 
119
- // ---- `Update`
120
- describe(`"PUT - ${PATH_USERS_UPDATE}"`, () => {
172
+ // --- `Update` ---
173
+ describe(P.Update.DISPLAY, () => {
121
174
  describe('on success', () => {
122
175
  it(`saves the change and returns status "${OK}"`, async () => {
123
176
  const user = { ...dbUsers[0], name: 'Bill' };
124
- const res = await agent.put(PATH_USERS_UPDATE).send({ user });
125
- expect(res.status).toBe(OK);
177
+ const res = await agent.put(P.Update.CALL).send({ user });
178
+ assert.equal(res.status, OK);
126
179
  const allUsers = await UserRepo.getAll();
127
180
  const saved = allUsers.find((u) => u.id === user.id);
128
- expect(saved?.name).toBe('Bill');
181
+ assert.equal(saved?.name, 'Bill');
129
182
  });
130
183
  });
131
184
 
@@ -138,60 +191,60 @@ describe('UserRouter', () => {
138
191
  email: 'a@a.com',
139
192
  id: '5',
140
193
  };
141
- const res: TestRes = await agent.put(PATH_USERS_UPDATE).send({ user });
142
- expect(res.status).toBe(BAD_REQUEST);
143
- expect(res.body.error).toBe(ValidationError.MESSAGE);
144
- expect(res.body.errors?.[0].keyPath).toStrictEqual(['user', 'id']);
194
+ const res = await agent.put(P.Update.CALL).send({ user });
195
+ assert.equal(res.status, BAD_REQUEST);
196
+ const errMsg = extractErrMsg(res);
197
+ assert.equal(errMsg, 'Not a valid uuid7 id');
145
198
  });
146
199
  });
147
200
 
148
201
  describe('when the user is not found', () => {
149
202
  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 });
153
- expect(res.status).toBe(NOT_FOUND);
154
- expect(res.body.error).toBe(ERR_USER_NOT_FOUND);
203
+ const user = User.create({ name: 'a', email: 'a@a.com' });
204
+ const res: TypedErrRes = await agent.put(P.Update.CALL).send({ user });
205
+ assert.equal(res.status, NOT_FOUND);
206
+ assert.equal(res.body.error, Errors.USER_NOT_FOUND);
155
207
  });
156
208
  });
157
209
  });
158
210
 
159
- // ---- `Delete`
160
- describe(`"DELETE - ${PATH_USERS_DELETE}"`, () => {
211
+ // --- `Delete` ---
212
+ describe(P.Delete.DISPLAY, () => {
161
213
  describe('on success', () => {
162
214
  it(`removes the user and returns status "${OK}"`, async () => {
163
215
  const id = dbUsers[0].id;
164
- const path = Paths.Users.Delete({ id });
216
+ const path = P.Delete.CALL(id);
165
217
  const res = await agent.delete(path);
166
- expect(res.status).toBe(OK);
218
+ assert.equal(res.status, OK);
167
219
  const persists = await UserRepo.persists(id);
168
- expect(persists).toBe(false);
220
+ assert.equal(persists, false);
169
221
  });
170
222
  });
171
223
 
172
224
  describe('when the user is not found', () => {
173
225
  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);
177
- expect(res.status).toBe(NOT_FOUND);
178
- expect(res.body.error).toBe(ERR_USER_NOT_FOUND);
226
+ const id = User.genId();
227
+ const path = P.Delete.CALL(id);
228
+ const res: TypedErrRes = await agent.delete(path);
229
+ assert.equal(res.status, NOT_FOUND);
230
+ assert.equal(res.body.error, Errors.USER_NOT_FOUND);
179
231
  });
180
232
  });
181
233
 
182
234
  describe('when the id is invalid', () => {
183
235
  it(`returns status "${BAD_REQUEST}"`, async () => {
184
- const path = Paths.Users.Delete({ id: -1 });
236
+ const path = P.Delete.CALL('asdf');
185
237
  const res = await agent.delete(path);
186
- expect(res.status).toBe(BAD_REQUEST);
238
+ assert.equal(res.status, BAD_REQUEST);
187
239
  });
188
240
  });
189
241
 
190
242
  describe('when the id path param is missing', () => {
191
243
  it(`returns status "${BAD_REQUEST}"`, async () => {
192
- const res: TestRes = await agent.delete(PATH_USERS_DELETE);
193
- expect(res.status).toBe(BAD_REQUEST);
194
- expect(res.body.errors?.[0].key).toStrictEqual('id');
244
+ const res = await agent.delete(P.Delete.FULL_PATH);
245
+ assert.equal(res.status, BAD_REQUEST);
246
+ const errMsg = extractErrMsg(res);
247
+ assert.equal(errMsg, 'Not a valid uuid7 id');
195
248
  });
196
249
  });
197
250
  });
@@ -1,26 +1,23 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
- "module": "esnext",
5
- "moduleResolution": "bundler",
4
+ "module": "nodenext",
6
5
  "esModuleInterop": true,
7
6
  "lib": ["ES2022"],
8
7
  "strict": true,
9
8
  "skipLibCheck": true,
10
9
  "forceConsistentCasingInFileNames": true,
11
- "paths": {
12
- "@src/*": ["./src/*"]
13
- },
14
- // tsx transpiles one file at a time, so type-only imports must say so
10
+ // Node runs the .ts files directly and tsc only typechecks, so imports
11
+ // name the .ts files
12
+ "noEmit": true,
13
+ "allowImportingTsExtensions": true,
14
+ // Node strips types one file at a time, so type-only imports must say so
15
+ // and syntax that needs compiling (enums, namespaces...) isn't allowed
15
16
  "verbatimModuleSyntax": true,
17
+ "erasableSyntaxOnly": true,
16
18
  "useUnknownInCatchVariables": false,
17
- "types": ["node", "vitest/globals"]
19
+ "types": ["node"]
18
20
  },
19
- "include": [
20
- "src/**/*.ts",
21
- "tests/**/*.ts",
22
- "eslint.config.ts",
23
- "vitest.config.ts"
24
- ],
21
+ "include": ["src/**/*.ts", "tests/**/*.ts", "eslint.config.ts"],
25
22
  "exclude": ["src/public/*"]
26
23
  }
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "express-generator-typescript",
3
- "version": "3.0.4",
3
+ "version": "3.2.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": "node ../my-tools/src/workflows/commit/run.ts",
10
+ "_local:commit:push": "npm run _local:commit -- -p",
11
+ "_local:squash-history": "node ../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.18.0 || ^24.0.0 || >=26.0.0"
29
30
  },
30
31
  "repository": {
31
32
  "type": "git",
@@ -53,7 +54,6 @@
53
54
  "express5",
54
55
  "server",
55
56
  "tooling",
56
- "vitest",
57
57
  "eslint"
58
58
  ],
59
59
  "author": "sean maxwell",
@@ -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,74 +0,0 @@
1
- import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
2
- import User from '@src/entities/User';
3
- import UserService from '@src/services/UserService';
4
-
5
- import type { Req, Res } from './common/express-types';
6
- import parseReq from './common/parseReq';
7
-
8
- // ========================================================================= //
9
- // EXEC //
10
- // ========================================================================= //
11
-
12
- const validators = {
13
- add: parseReq({ user: User.isInput }),
14
- update: parseReq({ user: User.isComplete }),
15
- delete: parseReq({ id: User.isId }),
16
- } as const;
17
-
18
- // ========================================================================= //
19
- // FUNCTIONS //
20
- // ========================================================================= //
21
-
22
- /**
23
- * Get all users.
24
- *
25
- * @route GET /api/users/all
26
- */
27
- async function getAll(_: Req, res: Res) {
28
- const users = await UserService.getAll();
29
- res.status(HttpStatusCodes.OK).json({ users });
30
- }
31
-
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);
39
- const created = await UserService.addOne(user);
40
- res.status(HttpStatusCodes.CREATED).json({ user: created });
41
- }
42
-
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);
50
- await UserService.updateOne(user);
51
- res.status(HttpStatusCodes.OK).end();
52
- }
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);
61
- await UserService.deleteOne(id);
62
- res.status(HttpStatusCodes.OK).end();
63
- }
64
-
65
- // ========================================================================= //
66
- // EXPORT //
67
- // ========================================================================= //
68
-
69
- export default {
70
- getAll,
71
- addOne,
72
- updateOne,
73
- deleteOne,
74
- } as const;
@@ -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;