I'm having a problem where a certain import fails to work when compiling directly, but works okay after saving the source on VS Code.
If I delete the node_modules/.cache
folder and execute the command:
yarn serve
It compiles okay, but then the browser gives out an error at runtime:
Uncaught TypeError: getParamNames is not a function
at eval (webpack-internal:///../api/src/clients/base.ts:116)
at Array.forEach (<anonymous>)
at exports.Remotable (webpack-internal:///../api/src/clients/base.ts:114)
at DecorateConstructor (webpack-internal:///../api/node_modules/reflect-metadata/Reflect.js:541)
at Object.decorate (webpack-internal:///../api/node_modules/reflect-metadata/Reflect.js:130)
at Module.__decorate (webpack-internal:///../api/node_modules/tslib/tslib.es6.js:78)
at eval (webpack-internal:///../api/src/clients/order.ts:26)
at Object.../api/src/clients/order.ts (app.js:909)
at __webpack_require__ (app.js:785)
at fn (app.js:151)
If you keep compiling the application without changing anything, the error repeats, but then, if you edit base.ts
on VS Code, change the import to something wrong, then change it back, it compiles, and even if you close the server and start it again, the error does not repeat.
If you delete node_modules/.cache
again, or wait for it to expire, the cycle restarts.
base.ts:
/*eslint prefer-spread: "off"*/
/* eslint-disable no-empty */
import * as getParamNames from 'get-param-names';
import * as NamedRouter from 'named-routes';
import { fromPairs, has, get, isString } from 'lodash';
import { Config } from '../../config';
import { RestClient } from 'typed-rest-client';
import 'reflect-metadata'
import { classToPlain } from 'class-transformer';
export interface ApiResult<T> {
status?: string;
data?: T;
error?;
}
// ATSTODO: Implementar conversão de retorno para classes específicas
export const LocalClient = (service) => {
return (Client) => {
const ParentClass = Object.getPrototypeOf(Client);
Object.getOwnPropertyNames(ParentClass.prototype).filter(s => s !== 'constructor').forEach(propName => {
if (ParentClass.prototype[propName] instanceof Function) {
if (has(Client.prototype, propName)) {
// Já tem uma implementação específica
return;
}
Client.prototype[propName] = (...args: any[]) => {
return service[propName].apply(service, args);
}
}
});
}
}
export const getHost = () => {
if (Config.dbConfig.port) {
return Config.dbConfig.host + ':' + Config.dbConfig.port;
} else {
return Config.dbConfig.host;
}
}
class RemotabeManager {
public metadata = {};
private getTargetName(target: any): string {
return isString(target) ? target : target.constructor.name;
}
public createTarget = (target: any) => {
const name = this.getTargetName(target);
let targetMetadata = this.metadata[name];
if (!targetMetadata) {
targetMetadata = {
name,
target,
methods: {}
}
this.metadata[name] = targetMetadata;
}
return targetMetadata;
}
public getTarget = (target: any) => {
const name = this.getTargetName(target);
return this.metadata[name];
}
public forMethod(target: any, propertyKey: string | symbol) {
const methods = this.createTarget(target).methods;
let method = methods[propertyKey];
if (!method) {
method = {
name: propertyKey,
path: `/rpc/${String(propertyKey)}`,
parameters: []
};
methods[propertyKey] = method;
}
return method;
}
public registerParam(target: any, propertyKey: string | symbol, parameterIndex: number, value) {
const method = this.forMethod(target, propertyKey);
const existingInfo = method.parameters[parameterIndex] || {};
method.parameters[parameterIndex] = { ...value, ...existingInfo };
}
}
const remotableMetadata = new RemotabeManager();
/**
* Decorador
* @param constructor
*/
export const Remotable = (constructor) => {
Object.getOwnPropertyNames(constructor.prototype)
.filter(s => s !== 'constructor'&& constructor.prototype[s] instanceof Function)
.forEach(name => {
const method = constructor.prototype[name];
getParamNames(method).forEach((parameterName, parameterIndex) =>
remotableMetadata.registerParam(constructor.prototype, name, parameterIndex, { name: parameterName }));
});
}
// ATSTODO: Implementar tratamento de erros
// ATSTODO: Implementar suporte a outros métodos além de GET
/**
* Decorator
* @param Client
*/
export const Controller = (client, service) => {
return (Server) => {
const metadata = remotableMetadata.getTarget(client);
if (!metadata) {
throw new Error(`Não encontrou os metadados para o client ${client}`);
}
Object.entries(metadata.methods).forEach(([methodName, info]) => {
if (has(Server.prototype, methodName)) {
// Já existe uma implementação específica do método
return;
}
const method = service[methodName];
if (!method) {
throw new Error(`Método não encontrado: ${methodName}`);
}
Server.prototype[methodName] = async (req, res, next) => {
try {
const params = get(info, 'parameters').map(({ name }) => req.params[name] || req.query[name]);
const result = await method.apply(service, params);
res.status(200).json({
status: 'success',
data: classToPlain(result)
});
} catch (error) {
next(error);
}
};
});
}
}
/**
* Decorator
* @param clientInterface
*/
export const RemoteClient = (clientInterface, baseUrl) => {
const namedRouter = new NamedRouter();
return (Client) => {
const metadata = remotableMetadata.getTarget(clientInterface);
if (!metadata) {
throw new Error(`Não encontrou os metadados para o client ${clientInterface}`);
}
const restClient = new RestClient('resulth-web', getHost());
Object.entries(metadata.methods).forEach(([methodName, info]) => {
if (has(Client.prototype, methodName)) {
// Já existe uma implementação específica do método
return;
}
const routeName = `autoClient.${metadata.name}.${methodName}`;
// eslint-disable-next-line
namedRouter.add('get', (info as any).path, (req, res, next) => {}, { name: routeName });
Client.prototype[methodName] = async (...params) => {
const paramsObj = fromPairs(get(info, 'parameters').map(({ name }, idx) => [name, params[idx]]));
const url = namedRouter.build(routeName, paramsObj);
const searchParams = new URLSearchParams();
Object.entries(paramsObj).forEach(([k, v]) => v && searchParams.append(k, String(v)));
const fullPath = `/api/v1/${baseUrl}/${url}?${searchParams}`;
const res = await restClient.get<ApiResult<any>>(fullPath);
return res.result.data;
}
});
}
}
/**
* Decorador
* @param path
*/
export const Path = (path: string) => {
return (target: any, propertyKey: string) => {
remotableMetadata.forMethod(target, propertyKey).path = path;
}
}
/**
* Decorador
* @param name
*/
export const Param = (name: string) => {
return (target: any, propertyKey: string | symbol, parameterIndex: number) => {
remotableMetadata.registerParam(target, propertyKey, parameterIndex, { name });
}
}
package.json:
{
"name": "framework-ats",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"@rauschma/stringio": "^1.4.0",
"@types/lodash": "^4.14.149",
"ajv-i18n": "^3.5.0",
"core-js": "^3.4.4",
"lodash": "^4.17.15",
"node-firebird": "^0.8.9",
"typed-rest-client": "^1.7.1",
"typescript-ioc": "^1.2.6",
"v-money": "^0.8.1",
"vue": "^2.6.10",
"vue-class-component": "^7.2.2",
"vue-form-json-schema": "^2.5.0",
"vue-property-decorator": "^8.3.0",
"vue-router": "^3.1.5",
"vue-the-mask": "^0.11.1",
"vuetify": "^2.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-plugin-router": "^4.1.0",
"@vue/cli-plugin-typescript": "^4.2.2",
"@vue/cli-service": "^4.1.0",
"@vue/eslint-config-typescript": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^2.19.0",
"@typescript-eslint/parser": "^2.19.0",
"babel-eslint": "^10.0.3",
"electron": "^6.0.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"material-design-icons-iconfont": "^5.0.1",
"sass": "^1.19.0",
"sass-loader": "^8.0.0",
"typescript": "~3.7.5",
"vue-cli-plugin-electron-builder": "^1.4.4",
"vue-cli-plugin-vuetify": "^2.0.4",
"vue-template-compiler": "^2.6.10",
"vuetify-loader": "^1.3.0"
}
}
tsconfig.json:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"typeRoots": [
"./node_modules/@types",
"./node_modules/vuetify/types"
],
"types": [
"webpack-env",
"vuetify"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
vue.config.js:
module.exports = {
"devServer": {
"disableHostCheck": true
},
"transpileDependencies": [
"vuetify"
]
}
babel.config.js:
module.exports = {
presets: [
[
'@vue/cli-plugin-babel/preset',
{
targets: {
node: 'current',
},
},
]
],
}
Addendum (2020-03-20)
Managed to make it work partially by to creating an explicit type declaration:
import getParamNames = require('get-param-names');
declare function getParamNames(o: any): string[];
export = getParamNames;
And also changed the import from import * as getParamNames from 'get-param-names';
to import getParamNames from 'get-param-names';
; this worked okay for the frontend, which is built through vue-cli
, but not for the backend, which is built through ts-node-dev
:
ts-node-dev --respawn -- src/index.ts
This gives this error on the backend:
[INFO] 08:15:21 Restarting: D:\Java\framework-ats\api\src\clients\base.ts has been modified
Using ts-node version 8.6.2, typescript version 3.8.2
TypeError: get_param_names_1.default is not a function
at Object.getOwnPropertyNames.filter.forEach.name (D:\Java\framework-ats\api\src\clients\base.ts:107:26)
at Array.forEach (<anonymous>)
at exports.Remotable (D:\Java\framework-ats\api\src\clients\base.ts:105:10)
at DecorateConstructor (D:\Java\framework-ats\api\node_modules\reflect-metadata\Reflect.js:541:33)
at Object.decorate (D:\Java\framework-ats\api\node_modules\reflect-metadata\Reflect.js:130:24)
at __decorate (D:\Java\framework-ats\api\src\clients\product.ts:4:92)
at Object.<anonymous> (D:\Java\framework-ats\api\src\clients\product.ts:7:36)
at Module._compile (internal/modules/cjs/loader.js:778:30)
at Module._compile (D:\Java\framework-ats\api\node_modules\source-map-support\source-map-support.js:541:25)
at Module.m._compile (C:\Users\HAROLD~1\AppData\Local\Temp\ts-node-dev-hook-8308269448535168.js:57:25)
[ERROR] 08:15:21 TypeError: get_param_names_1.default is not a function
BTW, the bug seems very similar to those issues: