express-generator-typescript 2.5.5 → 2.5.7
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/lib/express-generator-typescript.js +1 -1
- package/lib/project-files/eslint.config.ts +0 -1
- package/lib/project-files/package.json +7 -6
- package/lib/project-files/spec/support/index.ts +18 -0
- package/lib/project-files/spec/tests/users.spec.ts +28 -8
- package/lib/project-files/src/common/Env.ts +1 -1
- package/lib/project-files/src/models/User.ts +6 -4
- package/lib/project-files/src/routes/UserRoutes.ts +5 -2
- package/lib/project-files/src/routes/common/index.ts +5 -5
- package/lib/project-files/src/util/schema.ts +6 -4
- package/lib/project-files/src/util/validators.ts +4 -321
- package/lib/project-files/tsconfig.json +2 -3
- package/lib/project-files/tsconfig.prod.json +2 -3
- package/package.json +1 -1
- package/lib/project-files/config.js +0 -18
- package/lib/project-files/spec/support/apiCb.ts +0 -88
|
@@ -13,7 +13,7 @@ const PROJECT_FOLDER_PATH = './project-files';
|
|
|
13
13
|
// Project-folder dependencies
|
|
14
14
|
const DEPENDENCIES = 'express dotenv morgan cookie-parser jet-logger ' +
|
|
15
15
|
'module-alias express-async-errors helmet jsonfile inserturlparams ' +
|
|
16
|
-
'jet-paths dayjs jet-schema jet-env ',
|
|
16
|
+
'jet-paths dayjs jet-schema jet-env jet-validators',
|
|
17
17
|
DEV_DEPENDENCIES = 'ts-node typescript nodemon find jasmine supertest ' +
|
|
18
18
|
'@types/node @types/express @types/jasmine @types/find @types/morgan ' +
|
|
19
19
|
'@types/cookie-parser @types/supertest fs-extra tsconfig-paths jiti @swc/core ' +
|
|
@@ -4,7 +4,6 @@ import stylisticJs from '@stylistic/eslint-plugin-js';
|
|
|
4
4
|
import stylisticTs from '@stylistic/eslint-plugin-ts';
|
|
5
5
|
import nodePlugin from 'eslint-plugin-n';
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
export default tseslint.config(
|
|
9
8
|
eslint.configs.recommended,
|
|
10
9
|
nodePlugin.configs['flat/recommended-script'],
|
|
@@ -18,21 +18,22 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"cookie-parser": "^1.4.6",
|
|
20
20
|
"dayjs": "^1.11.13",
|
|
21
|
-
"dotenv": "^16.
|
|
21
|
+
"dotenv": "^16.4.5",
|
|
22
22
|
"express": "^4.18.1",
|
|
23
23
|
"express-async-errors": "^3.1.1",
|
|
24
24
|
"helmet": "^6.0.0",
|
|
25
|
-
"inserturlparams": "^
|
|
25
|
+
"inserturlparams": "^2.0.5",
|
|
26
26
|
"jet-env": "^1.1.4",
|
|
27
|
-
"jet-logger": "^
|
|
28
|
-
"jet-paths": "^1.0
|
|
27
|
+
"jet-logger": "^2.0.1",
|
|
28
|
+
"jet-paths": "^1.1.0",
|
|
29
29
|
"jet-schema": "^1.4.2",
|
|
30
|
+
"jet-validators": "^1.0.6",
|
|
30
31
|
"jsonfile": "^6.1.0",
|
|
31
32
|
"module-alias": "^2.2.2",
|
|
32
33
|
"morgan": "^1.10.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@eslint/js": "^9.
|
|
36
|
+
"@eslint/js": "^9.16.0",
|
|
36
37
|
"@stylistic/eslint-plugin-js": "^2.11.0",
|
|
37
38
|
"@stylistic/eslint-plugin-ts": "^2.8.0",
|
|
38
39
|
"@swc/core": "^1.9.3",
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"@types/node": "^18.7.14",
|
|
49
50
|
"@types/supertest": "^6.0.2",
|
|
50
51
|
"eslint": "^9.11.1",
|
|
51
|
-
"eslint-plugin-n": "^17.
|
|
52
|
+
"eslint-plugin-n": "^17.14.0",
|
|
52
53
|
"find": "^0.3.0",
|
|
53
54
|
"fs-extra": "^10.1.0",
|
|
54
55
|
"jasmine": "^4.3.0",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isValidDate } from 'jet-validators';
|
|
2
|
+
import { traverseObject } from 'jet-validators/utils';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Send a date object over an API will convert it to a string, so for jasmine
|
|
7
|
+
* tests to pass we need to convert them back to date objects.
|
|
8
|
+
*/
|
|
9
|
+
export function convertValidDates(toConvert: string | string[]) {
|
|
10
|
+
if (!Array.isArray(toConvert)) {
|
|
11
|
+
toConvert = [toConvert];
|
|
12
|
+
}
|
|
13
|
+
return traverseObject((prop, value, parentObj) => {
|
|
14
|
+
if (toConvert.includes(prop) && isValidDate(value)) {
|
|
15
|
+
parentObj[prop] = new Date(value);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import supertest, { Test } from 'supertest';
|
|
2
2
|
import TestAgent from 'supertest/lib/agent';
|
|
3
3
|
import insertUrlParams from 'inserturlparams';
|
|
4
|
+
import { safeJsonParse } from 'jet-validators/utils';
|
|
5
|
+
import logger from 'jet-logger';
|
|
4
6
|
|
|
5
7
|
import app from '@src/server';
|
|
6
8
|
|
|
7
9
|
import UserRepo from '@src/repos/UserRepo';
|
|
8
10
|
import User, { IUser } from '@src/models/User';
|
|
9
11
|
import { USER_NOT_FOUND_ERR } from '@src/services/UserService';
|
|
10
|
-
import { safeJsonParse } from '@src/util/validators';
|
|
11
12
|
|
|
12
13
|
import HttpStatusCodes from '@src/common/HttpStatusCodes';
|
|
13
14
|
import { IValidationErrFormat, ValidationErr } from '@src/common/route-errors';
|
|
14
15
|
|
|
15
16
|
import Paths from 'spec/support/Paths';
|
|
16
|
-
import
|
|
17
|
-
import { TApiCb } from 'spec/types/misc';
|
|
17
|
+
import { convertValidDates } from 'spec/support';
|
|
18
|
+
import { TApiCb, TRes } from 'spec/types/misc';
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
// **** Setup **** //
|
|
22
|
+
|
|
20
23
|
// Dummy users for GET req
|
|
21
24
|
const getDummyUsers = () => [
|
|
22
25
|
User.new({ name: 'Sean Maxwell', email: 'sean.maxwell@gmail.com' }),
|
|
@@ -24,8 +27,25 @@ const getDummyUsers = () => [
|
|
|
24
27
|
User.new({ name: 'Gordan Freeman', email: 'gordan.freeman@gmail.com' }),
|
|
25
28
|
];
|
|
26
29
|
|
|
30
|
+
// Convert "created" prop back to Date object
|
|
31
|
+
const createdKeyToDate = convertValidDates('created');
|
|
32
|
+
|
|
33
|
+
// Setup wrap-callback function
|
|
34
|
+
const wrapCb = (cb: TApiCb) => (err: Error, res: TRes) => {
|
|
35
|
+
if (!!err) {
|
|
36
|
+
logger.err(err);
|
|
37
|
+
}
|
|
38
|
+
if (!!res.body.user) {
|
|
39
|
+
createdKeyToDate(res.body.user);
|
|
40
|
+
} else if (!!res.body.users) {
|
|
41
|
+
res.body.users?.map(user => createdKeyToDate(user));
|
|
42
|
+
}
|
|
43
|
+
return cb(res);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
// **** Run Tests **** //
|
|
27
48
|
|
|
28
|
-
// Tests
|
|
29
49
|
describe('UserRouter', () => {
|
|
30
50
|
|
|
31
51
|
let agent: TestAgent<Test>;
|
|
@@ -43,7 +63,7 @@ describe('UserRouter', () => {
|
|
|
43
63
|
const api = (cb: TApiCb) =>
|
|
44
64
|
agent
|
|
45
65
|
.get(Paths.Users.Get)
|
|
46
|
-
.end(
|
|
66
|
+
.end(wrapCb(cb));
|
|
47
67
|
|
|
48
68
|
// Success
|
|
49
69
|
it('should return a JSON object with all the users and a status code ' +
|
|
@@ -70,7 +90,7 @@ describe('UserRouter', () => {
|
|
|
70
90
|
agent
|
|
71
91
|
.post(Paths.Users.Add)
|
|
72
92
|
.send({ user })
|
|
73
|
-
.end(
|
|
93
|
+
.end(wrapCb(cb));
|
|
74
94
|
|
|
75
95
|
// Test add user success
|
|
76
96
|
it(`should return a status code of "${HttpStatusCodes.CREATED}" if the ` +
|
|
@@ -109,7 +129,7 @@ describe('UserRouter', () => {
|
|
|
109
129
|
agent
|
|
110
130
|
.put(Paths.Users.Update)
|
|
111
131
|
.send({ user })
|
|
112
|
-
.end(
|
|
132
|
+
.end(wrapCb(cb));
|
|
113
133
|
|
|
114
134
|
// Success
|
|
115
135
|
it(`should return a status code of "${HttpStatusCodes.OK}" if the ` +
|
|
@@ -154,7 +174,7 @@ describe('UserRouter', () => {
|
|
|
154
174
|
const callApi = (id: number, cb: TApiCb) =>
|
|
155
175
|
agent
|
|
156
176
|
.delete(insertUrlParams(Paths.Users.Delete, { id }))
|
|
157
|
-
.end(
|
|
177
|
+
.end(wrapCb(cb));
|
|
158
178
|
|
|
159
179
|
// Success
|
|
160
180
|
it(`should return a status code of "${HttpStatusCodes.OK}" if the ` +
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { isString } from 'jet-validators';
|
|
2
|
+
|
|
1
3
|
import schema from '@src/util/schema';
|
|
2
|
-
import {
|
|
4
|
+
import { isRelationalKey } from '@src/util/validators';
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
export interface IUser {
|
|
@@ -10,10 +12,10 @@ export interface IUser {
|
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
const User = schema<IUser>({
|
|
13
|
-
id:
|
|
14
|
-
name:
|
|
15
|
+
id: isRelationalKey,
|
|
16
|
+
name: isString,
|
|
15
17
|
created: Date,
|
|
16
|
-
email:
|
|
18
|
+
email: isString,
|
|
17
19
|
});
|
|
18
20
|
|
|
19
21
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { isNumber } from 'jet-validators';
|
|
2
|
+
import { transform } from 'jet-validators/utils';
|
|
3
|
+
|
|
1
4
|
import HttpStatusCodes from '@src/common/HttpStatusCodes';
|
|
2
5
|
import UserService from '@src/services/UserService';
|
|
3
6
|
import User from '@src/models/User';
|
|
4
|
-
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
import { parseReq, IReq, IRes } from './common';
|
|
7
10
|
|
|
@@ -11,7 +14,7 @@ import { parseReq, IReq, IRes } from './common';
|
|
|
11
14
|
const Validators = {
|
|
12
15
|
add: parseReq({ user: User.test }),
|
|
13
16
|
update: parseReq({ user: User.test }),
|
|
14
|
-
delete: parseReq({ id: transform(Number,
|
|
17
|
+
delete: parseReq({ id: transform(Number, isNumber) }),
|
|
15
18
|
} as const;
|
|
16
19
|
|
|
17
20
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Response, Request } from 'express';
|
|
2
|
+
import { parseObject, TSchema } from 'jet-validators/utils';
|
|
2
3
|
|
|
3
|
-
import { parseObj, TSchema } from '@src/util/validators';
|
|
4
4
|
import { ValidationErr } from '@src/common/route-errors';
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
// **** Types **** //
|
|
8
8
|
|
|
9
|
-
type
|
|
10
|
-
export type IReq = Request<
|
|
11
|
-
export type IRes = Response<unknown,
|
|
9
|
+
type TRecord = Record<string, unknown>;
|
|
10
|
+
export type IReq = Request<TRecord, void, TRecord, TRecord>;
|
|
11
|
+
export type IRes = Response<unknown, TRecord>;
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
// **** Functions **** //
|
|
@@ -17,7 +17,7 @@ export type IRes = Response<unknown, TObj>;
|
|
|
17
17
|
* Parse a Request object property and throw a Validation error if it fails.
|
|
18
18
|
*/
|
|
19
19
|
export function parseReq<U extends TSchema>(schema: U) {
|
|
20
|
-
return
|
|
20
|
+
return parseObject<U>(schema, _parseReqOnError);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import jetSchema from 'jet-schema';
|
|
2
|
-
import {
|
|
2
|
+
import { isNumber, isString, isBoolean } from 'jet-validators';
|
|
3
|
+
import { isRelationalKey } from './validators';
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
export default jetSchema({
|
|
6
7
|
globals: [
|
|
7
|
-
{ vf:
|
|
8
|
-
{ vf:
|
|
9
|
-
{ vf:
|
|
8
|
+
{ vf: isRelationalKey, default: -1 },
|
|
9
|
+
{ vf: isNumber, default: 0 },
|
|
10
|
+
{ vf: isString, default: '' },
|
|
11
|
+
{ vf: isBoolean, default: false },
|
|
10
12
|
],
|
|
11
13
|
});
|
|
@@ -1,326 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
* NOTE: These functions were copied from here:
|
|
3
|
-
* https://github.com/seanpmaxwell/ts-validators/blob/master/src/validators.ts
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/* eslint-disable max-len */
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// **** Types **** //
|
|
10
|
-
|
|
11
|
-
type TBasicObj = Record<string, unknown>;
|
|
12
|
-
type TEnum = Record<string, string | number>;
|
|
13
|
-
|
|
14
|
-
// Add modifiers
|
|
15
|
-
type AddNull<T, N> = (N extends true ? T | null : T);
|
|
16
|
-
type AddNullables<T, O, N> = (O extends true ? AddNull<T, N> | undefined : AddNull<T, N>);
|
|
17
|
-
type AddMods<T, O, N, A> = A extends true ? AddNullables<T[], O, N> : AddNullables<T, O, N>;
|
|
18
|
-
export type TValidateWithTransform<T> = (arg: unknown, cb?: (arg: T) => void) => arg is T;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// **** Functions **** //
|
|
22
|
-
|
|
23
|
-
// Nullables
|
|
24
|
-
export const isUndef = ((arg: unknown): arg is undefined => arg === undefined);
|
|
25
|
-
export const isNull = ((arg: unknown): arg is null => arg === null);
|
|
26
|
-
|
|
27
|
-
// Base types
|
|
28
|
-
export const isStr = _checkType<string>('string');
|
|
29
|
-
export const isNum = _checkType<number>('number');
|
|
30
|
-
export const isBool = _checkType<boolean>('boolean');
|
|
31
|
-
export const isObj = _checkType<object>('object');
|
|
32
|
-
|
|
33
|
-
// Misc
|
|
34
|
-
export const parseObj = <U extends TSchema>(arg: U, onError?: TParseOnError<false>) =>
|
|
35
|
-
_parseObj<U, false, false, false>(arg, false, false, false, onError);
|
|
36
|
-
export const isEnumVal = <T>(arg: T) => _isEnumVal<T, false, false>(arg, false, false);
|
|
37
|
-
|
|
38
|
-
// Util
|
|
39
|
-
export const transform = _transform;
|
|
40
|
-
export const safeJsonParse = _safeJsonParse;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// **** Helpers **** //
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Check if unknown is a valid enum object.
|
|
47
|
-
* NOTE: this does not work for mixed enums see: "eslint@typescript-eslint/no-mixed-enums"
|
|
48
|
-
*/
|
|
49
|
-
function _isEnum(arg: unknown): arg is TEnum {
|
|
50
|
-
// Check is non-array object
|
|
51
|
-
if (!(isObj(arg) && !Array.isArray(arg))) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
// Check if string or number enum
|
|
55
|
-
const param = (arg as TBasicObj),
|
|
56
|
-
keys = Object.keys(param),
|
|
57
|
-
middle = Math.floor(keys.length / 2);
|
|
58
|
-
// ** String Enum ** //
|
|
59
|
-
if (!isNum(param[keys[middle]])) {
|
|
60
|
-
return _checkObjEntries(arg, (key, val) => {
|
|
61
|
-
return isStr(key) && isStr(val);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
// ** Number Enum ** //
|
|
65
|
-
// Enum key length will always be even
|
|
66
|
-
if (keys.length % 2 !== 0) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
// Check key/values
|
|
70
|
-
for (let i = 0; i < middle; i++) {
|
|
71
|
-
const thisKey = keys[i],
|
|
72
|
-
thisVal = param[thisKey],
|
|
73
|
-
thatKey = keys[i + middle],
|
|
74
|
-
thatVal = param[thatKey];
|
|
75
|
-
if (!(thisVal === thatKey && thisKey === String(thatVal))) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// Return
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Check is value satisfies enum.
|
|
85
|
-
*/
|
|
86
|
-
function _isEnumVal<T,
|
|
87
|
-
O extends boolean,
|
|
88
|
-
N extends boolean
|
|
89
|
-
>(
|
|
90
|
-
enumArg: T,
|
|
91
|
-
optional: O,
|
|
92
|
-
nullable: N,
|
|
93
|
-
): ((arg: unknown) => arg is AddNullables<T[keyof T], O, N>) {
|
|
94
|
-
// Check is enum
|
|
95
|
-
if (!_isEnum(enumArg)) {
|
|
96
|
-
throw Error('Item to check from must be an enum.');
|
|
97
|
-
}
|
|
98
|
-
// Get keys
|
|
99
|
-
let resp = Object.keys(enumArg).reduce((arr: unknown[], key) => {
|
|
100
|
-
if (!arr.includes(key)) {
|
|
101
|
-
arr.push(enumArg[key]);
|
|
102
|
-
}
|
|
103
|
-
return arr;
|
|
104
|
-
}, []);
|
|
105
|
-
// Check if string or number enum
|
|
106
|
-
if (isNum(enumArg[resp[0] as string])) {
|
|
107
|
-
resp = resp.map(item => enumArg[item as string]);
|
|
108
|
-
}
|
|
109
|
-
// Return validator function
|
|
110
|
-
return (arg: unknown): arg is AddNullables<T[keyof T], O, N> => {
|
|
111
|
-
if (isUndef(arg)) {
|
|
112
|
-
return !!optional;
|
|
113
|
-
}
|
|
114
|
-
if (isNull(arg)) {
|
|
115
|
-
return !!nullable;
|
|
116
|
-
}
|
|
117
|
-
return resp.some(val => arg === val);
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Do a validator callback function for each object key/value pair.
|
|
123
|
-
*/
|
|
124
|
-
function _checkObjEntries(
|
|
125
|
-
val: unknown,
|
|
126
|
-
cb: (key: string, val: unknown) => boolean,
|
|
127
|
-
): val is NonNullable<object> {
|
|
128
|
-
if (isObj(val)) {
|
|
129
|
-
for (const entry of Object.entries(val)) {
|
|
130
|
-
if (!cb(entry[0], entry[1])) {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
1
|
+
import { isNumber } from 'jet-validators';
|
|
137
2
|
|
|
138
|
-
/**
|
|
139
|
-
* Wrapper to check basic type.
|
|
140
|
-
*/
|
|
141
|
-
function _checkType<T>(type: string) {
|
|
142
|
-
return (arg: unknown): arg is T => {
|
|
143
|
-
return typeof arg === type && (type === 'object' ? (arg !== null) : true);
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Transform a value before checking it.
|
|
149
|
-
*/
|
|
150
|
-
function _transform<T>(
|
|
151
|
-
transFn: (arg: unknown) => T,
|
|
152
|
-
vldt: ((arg: unknown) => arg is T),
|
|
153
|
-
): TValidateWithTransform<T> {
|
|
154
|
-
return (arg: unknown, cb?: (arg: T) => void): arg is T => {
|
|
155
|
-
if (arg !== undefined) {
|
|
156
|
-
arg = transFn(arg);
|
|
157
|
-
}
|
|
158
|
-
cb?.(arg as T);
|
|
159
|
-
return vldt(arg);
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Safe JSON parse
|
|
165
|
-
*/
|
|
166
|
-
function _safeJsonParse<T>(arg: unknown): T {
|
|
167
|
-
if (isStr(arg)) {
|
|
168
|
-
return JSON.parse(arg) as T;
|
|
169
|
-
} else {
|
|
170
|
-
throw Error('JSON parse argument must be a string');
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// **** Parse Object **** //
|
|
176
|
-
|
|
177
|
-
export interface TSchema {
|
|
178
|
-
[key: string]: TValidateWithTransform<unknown> | TSchema;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
type TInferParseRes<U, O, N, A, Schema = TInferParseResHelper<U>> = (
|
|
182
|
-
AddMods<Schema, O, N, A>
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
type TInferParseResHelper<U> = {
|
|
186
|
-
[K in keyof U]: (
|
|
187
|
-
U[K] extends TValidateWithTransform<infer X>
|
|
188
|
-
? X
|
|
189
|
-
: U[K] extends TSchema
|
|
190
|
-
? TInferParseResHelper<U[K]>
|
|
191
|
-
: never
|
|
192
|
-
);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
type TParseOnError<A> = (
|
|
196
|
-
A extends true
|
|
197
|
-
? ((property?: string, value?: unknown, index?: number, caughtErr?: unknown) => void)
|
|
198
|
-
: ((property?: string, value?: unknown, caughtErr?: unknown) => void)
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* validates an object schema, calls an error function is supplied one, returns
|
|
203
|
-
* "undefined" if the parse fails, and works recursively too.
|
|
204
|
-
*/
|
|
205
|
-
function _parseObj<
|
|
206
|
-
U extends TSchema,
|
|
207
|
-
O extends boolean,
|
|
208
|
-
N extends boolean,
|
|
209
|
-
A extends boolean,
|
|
210
|
-
>(
|
|
211
|
-
schema: U,
|
|
212
|
-
optional: O,
|
|
213
|
-
nullable: N,
|
|
214
|
-
isArr: A,
|
|
215
|
-
onError?: TParseOnError<A>,
|
|
216
|
-
) {
|
|
217
|
-
return (arg: unknown) => _parseObjCore(
|
|
218
|
-
!!optional,
|
|
219
|
-
!!nullable,
|
|
220
|
-
!!isArr,
|
|
221
|
-
schema,
|
|
222
|
-
arg,
|
|
223
|
-
onError,
|
|
224
|
-
) as TInferParseRes<U, O, N, A>;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Validate the schema.
|
|
229
|
-
*/
|
|
230
|
-
function _parseObjCore<A>(
|
|
231
|
-
optional: boolean,
|
|
232
|
-
nullable: boolean,
|
|
233
|
-
isArr: A,
|
|
234
|
-
schema: TSchema,
|
|
235
|
-
arg: unknown,
|
|
236
|
-
onError?: TParseOnError<A>,
|
|
237
|
-
) {
|
|
238
|
-
// Check "undefined"
|
|
239
|
-
if (arg === undefined) {
|
|
240
|
-
if (!optional) {
|
|
241
|
-
onError?.('object value was undefined but not optional', arg);
|
|
242
|
-
return undefined;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
// Check "null"
|
|
246
|
-
if (arg === null) {
|
|
247
|
-
if (!nullable) {
|
|
248
|
-
onError?.('object value was null but not nullable', arg);
|
|
249
|
-
return undefined;
|
|
250
|
-
}
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
// Check "array"
|
|
254
|
-
if (isArr) {
|
|
255
|
-
if (!Array.isArray(arg)) {
|
|
256
|
-
onError?.('object not an array', arg);
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
// Iterate array
|
|
260
|
-
const resp = [];
|
|
261
|
-
for (let i = 0; i < arg.length; i++) {
|
|
262
|
-
const item: unknown = arg[i];
|
|
263
|
-
const parsedItem = _parseObjCoreHelper(schema, item, (prop, val, caughtErr) => {
|
|
264
|
-
onError?.(prop, val, i, caughtErr);
|
|
265
|
-
});
|
|
266
|
-
if (parsedItem === undefined) {
|
|
267
|
-
return undefined;
|
|
268
|
-
} else {
|
|
269
|
-
resp.push(parsedItem);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return resp;
|
|
273
|
-
// Default
|
|
274
|
-
} else {
|
|
275
|
-
return _parseObjCoreHelper(schema, arg, onError as TParseOnError<false>);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
3
|
|
|
279
4
|
/**
|
|
280
|
-
*
|
|
281
|
-
* object using the schema.
|
|
5
|
+
* Database relational key.
|
|
282
6
|
*/
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
arg: unknown,
|
|
286
|
-
onError?: TParseOnError<false>,
|
|
287
|
-
): unknown {
|
|
288
|
-
if (!isObj(arg)) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
const retVal = (arg as TBasicObj);
|
|
292
|
-
for (const key in schema) {
|
|
293
|
-
const schemaProp = schema[key],
|
|
294
|
-
val = retVal[key];
|
|
295
|
-
// Nested object
|
|
296
|
-
if (typeof schemaProp === 'object') {
|
|
297
|
-
const childVal = _parseObjCoreHelper(schemaProp, val, onError);
|
|
298
|
-
if (childVal === undefined) {
|
|
299
|
-
return undefined;
|
|
300
|
-
}
|
|
301
|
-
// Run validator
|
|
302
|
-
} else if (typeof schemaProp === 'function') {
|
|
303
|
-
try {
|
|
304
|
-
if (!schemaProp(val, (tval: unknown) => {
|
|
305
|
-
retVal[key] = tval;
|
|
306
|
-
})) {
|
|
307
|
-
return onError?.(key, val);
|
|
308
|
-
}
|
|
309
|
-
} catch (err) {
|
|
310
|
-
if (err instanceof Error) {
|
|
311
|
-
return onError?.(key, val, err.message);
|
|
312
|
-
} else {
|
|
313
|
-
return onError?.(key, val, err);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
// Purse keys not in schema
|
|
319
|
-
for (const key in retVal) {
|
|
320
|
-
if (!(key in schema)) {
|
|
321
|
-
Reflect.deleteProperty(arg, key);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
// Return
|
|
325
|
-
return retVal;
|
|
7
|
+
export function isRelationalKey(arg: unknown): arg is number {
|
|
8
|
+
return isNumber(arg) && arg >= -1;
|
|
326
9
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-generator-typescript",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.7",
|
|
4
4
|
"description": "Generate new Express applications similar to express-generate which but sets it up to use TypeScript instead",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"express-generator-typescript": "node bin/cli.js",
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const path_1 = __importDefault(require("path"));
|
|
7
|
-
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
|
-
const module_alias_1 = __importDefault(require("module-alias"));
|
|
9
|
-
const NODE_ENV = (process.env.NODE_ENV ?? 'development');
|
|
10
|
-
const result2 = dotenv_1.default.config({
|
|
11
|
-
path: path_1.default.join(__dirname, `./config/.env.${NODE_ENV}`),
|
|
12
|
-
});
|
|
13
|
-
if (result2.error) {
|
|
14
|
-
throw result2.error;
|
|
15
|
-
}
|
|
16
|
-
if (__filename.endsWith('js')) {
|
|
17
|
-
module_alias_1.default.addAlias('@src', __dirname + '/dist');
|
|
18
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { CallbackHandler } from 'supertest';
|
|
2
|
-
import dayjs, { Dayjs } from 'dayjs';
|
|
3
|
-
import logger from 'jet-logger';
|
|
4
|
-
|
|
5
|
-
import { TApiCb, TRes } from 'spec/types/misc';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* API callback function.
|
|
10
|
-
*/
|
|
11
|
-
function apiCb(
|
|
12
|
-
cb: TApiCb,
|
|
13
|
-
dateParam = 'created',
|
|
14
|
-
printErr?: boolean,
|
|
15
|
-
): CallbackHandler {
|
|
16
|
-
return (err: Error, res: TRes) => {
|
|
17
|
-
if (printErr) {
|
|
18
|
-
logger.err(err);
|
|
19
|
-
}
|
|
20
|
-
_strToDate(res.body, dateParam);
|
|
21
|
-
return cb(res);
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* When date objects get sent through supertest they are converted to
|
|
27
|
-
* iso-strings. This will cause "toEqual()"" tests to fail, so we need to
|
|
28
|
-
* convert them back to Date objects.
|
|
29
|
-
*/
|
|
30
|
-
function _strToDate(
|
|
31
|
-
param: unknown,
|
|
32
|
-
prop: string,
|
|
33
|
-
): void {
|
|
34
|
-
return _iterate(param, prop);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Interate object recursively and convert string-dates to "Date" objects.
|
|
39
|
-
*/
|
|
40
|
-
function _iterate(param: unknown, prop: string): void {
|
|
41
|
-
if (!param || typeof param !== 'object') {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const paramF = param as Record<string, unknown>;
|
|
45
|
-
// For Arrays
|
|
46
|
-
if (Array.isArray(paramF)) {
|
|
47
|
-
for (const item of paramF) {
|
|
48
|
-
_iterate(item, prop);
|
|
49
|
-
}
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
// Check valid string or Date object. If undefined just skip
|
|
53
|
-
const val = paramF[prop];
|
|
54
|
-
if (val !== undefined && !_isValidDate(val)) {
|
|
55
|
-
throw new Error('Property must be a valid date-string or Date() object');
|
|
56
|
-
}
|
|
57
|
-
// Convert and iterate
|
|
58
|
-
if (typeof val !== 'undefined') {
|
|
59
|
-
paramF[prop] = new Date(val as string | Date);
|
|
60
|
-
}
|
|
61
|
-
for (const key in paramF) {
|
|
62
|
-
const oval = paramF[key];
|
|
63
|
-
if (typeof oval === 'object' && key !== prop) {
|
|
64
|
-
_iterate(oval, prop);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Is date a valid date.
|
|
71
|
-
*/
|
|
72
|
-
function _isValidDate(date: unknown): boolean {
|
|
73
|
-
if (
|
|
74
|
-
typeof date === 'string' ||
|
|
75
|
-
(date instanceof Date) ||
|
|
76
|
-
(date instanceof Dayjs) ||
|
|
77
|
-
typeof date === 'number'
|
|
78
|
-
) {
|
|
79
|
-
return dayjs(date).isValid();
|
|
80
|
-
} else {
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// **** Export default **** //
|
|
87
|
-
|
|
88
|
-
export default apiCb;
|