As follows: The method defined with declare disappears when compiled to js.
I want him to compile into the following method:
// Declare global variables
// Determine the variable type and return boolean
export declare const isString: (val: unknown) => val is string
export declare const isNumber: (val: unknown) => val is number
class ElementPlusError extends Error {
constructor (m: string) {
super(m)
this.name = 'ElementPlusError'
}
}
// TODO: eslint Function overloading error, need to modify eslint configuration
export function debugWarn(err: Error): void
// eslint-disable-next-line
export function debugWarn(scope: string, message: string): void
// eslint-disable-next-line
export function debugWarn (scope: string | Error, message?: string): void {
if (process.env.NODE_ENV !== 'production') {
const error: Error = isString(scope)
? new ElementPlusError(`[${scope}] ${message}`)
: scope
// eslint-disable-next-line no-console
console.warn(error)
}
}
I want him to compile into the following method:
const isNumber = (val) => typeof val === "number"
Related
I'm trying to pass my service to an instance of a class that I pass to a method decorator.
Here's the service:
#Injectable()
export class EntryService {
constructor(
#InjectRepository(EntryEntity)
private readonly entryRepository: Repository<EntryEntity>,
#InjectRepository(ImageEntity)
private readonly imageRepository: Repository<ImageEntity>,
private readonly awsService: AwsService,
private readonly connection: Connection,
private readonly categoriesService: CategoriesService,
private readonly cacheService: CacheService,
private readonly usersService: UserService,
private readonly imagesService: ImagesService,
private readonly notificationService: NotificationsService,
) {}
#RecordEntryOperation(new CreateOperation(this))
public async create(createEntryDto: CreateEntryBodyDto): Promise<Entry> {
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await queryRunner.commitTransaction();
// more code
} catch (err) {
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
}
The thing here is that I need to use EntryService inside that class I pass to the RecordEntryOperation decorator.
The decorator (not fully implemented yet):
export const RecordEntryOperation = (operation: Operation) => {
return (target: object, key: string | symbol, descriptor: PropertyDescriptor) => {
const original = descriptor.value;
descriptor.value = async function(...args: any[]) {
const response = await original.apply(this, args);
console.log(`operation.execute()`, await operation.execute());
return response;
};
};
};
The CreateOperation class looks like this (not fully implemented yet):
export class CreateOperation extends Operation {
constructor(public entryService: EntryService) { super(); }
public async execute(): Promise<any> {
return this.entryService.someEntryServiceOperation();
}
}
The error I'm getting reads as follows:
Argument of type 'typeof globalThis' is not assignable to parameter of type 'EntryService'.
Type 'typeof globalThis' is missing the following properties from type 'EntryService': entryRepository, imageRepository, awsService, and 53 more.
I don't fully understand what this error is about. I suspect that it means that the this passed to the CreateOperation class does not have all these dependencies injected into the service by the dependency injector.
I tried different things, but to no avail. Seems like I don't completely understand what is going on.
Any ideas?
What would be the right way to structure the code then?
The problem is the following line:
#RecordEntryOperation(new CreateOperation(this))
this does not refer to the instance of EntryService as you might expect, instead it refers to the globalThis (that this actually refers to the current module), thus the error. What you could do is to change your Operation-class a bit and pass the entryService to the execute method.
export class CreateOperation extends Operation {
constructor() { super(); }
public async execute(entryService: EntryService): Promise<any> {
return entryService.someEntryServiceOperation();
}
}
Then you can do the following in your decorator:
export const RecordEntryOperation = (OperationType: typeof CreateOperation) => {
return (target: object, key: string | symbol, descriptor: PropertyDescriptor) => {
const operation = new OperationType();
const original = descriptor.value;
descriptor.value = async function(...args: any[]) {
const response = await original.apply(this, args);
console.log(`operation.execute()`, await operation.execute(this));
return response;
};
};
};
Then use it with:
#RecordEntryOperation(CreateOperation)
public async create(createEntryDto: CreateEntryBodyDto): Promise<Entry> { .. }
I'm trying to create a template file from an object where the key can be either a string or a function that returns a string:
export const createDynamicTemplate = (
templateParams: CreateDynamicTemplateParams
) => {
const { template, projectPath = '', param = '' } = templateParams
const updatedTemplateArr = Object.keys(template).map((key: string) => {
return {
[key]: {
filePath: `${projectPath}/${key}`,
template: typeof template[key] === 'function' ?
template[key](param) : template[key],
},
}
})
const updatedTemplate = Object.assign({}, ...updatedTemplateArr)
return updatedTemplate
}
My interfaces are:
export interface TemplateObject {
[key: string]: string
}
export interface FunctionalTemplateObject {
[key: string]: (param: string) => void
}
export interface CreateDynamicTemplateParams {
template: FunctionalTemplateObject | TemplateObject
projectPath: string
param: string
}
It keeps throwing this error in createDynamicTemplate though:
This expression is not callable.
Not all constituents of type 'string | ((param: string) => void)' are callable.
Type 'string' has no call signatures.
What am I doing wrong here?
Checking the type of a child property of a variable which references an object will not narrow the type of the variable-object. You can save the value at the key in a separate variable first, then check that value to narrow its type:
const updatedTemplateArr = Object.keys(template).map((key: string) => {
const item = template[key];
if (typeof item === 'function') {
return {
[key]: {
filePath: `${projectPath}/${key}`,
template: item(param),
},
}
}
})
Or, even better, use Object.entries to get the key and value at once. (Also note that there's no need to note the type of the .map parameters - TS can infer it automatically just fine)
const updatedTemplateArr = Object.entries(template).map(([key, value]) => {
if (typeof value === 'function') {
return {
[key]: {
filePath: `${projectPath}/${key}`,
template: value(param),
},
}
}
})
I'm writing decorators for the following class method:
export default class API {
...
public async request(url_stub: string, options: any = {}): Promise<any> {
console.log(this)
const url = this.join_url(url_stub);
...
}
}
The functions run as expected when no decorators are applied, but when I apply one of the following decorators:
export function log_func(_target: any,
name: string,
descriptor: PropertyDescriptor): PropertyDescriptor {
const original_function = descriptor.value;
descriptor.value = (... args: any[]) => {
const parameters = args.map((a) => JSON.stringify(a)).join();
const result = original_function.apply(this, args);
const result_str = JSON.stringify(result);
console.log(`Call: ${name}(${parameters}) => ${result_str}`);
return result;
}
return descriptor;
}
export function uri_encode(parameter_index?: number) {
return (_target: any,
name: string,
descriptor: PropertyDescriptor): PropertyDescriptor => {
const original_function = descriptor.value;
descriptor.value = (... args: any[]) => {
args = args.map((arg, index) => {
if (parameter_index === undefined || index === parameter_index) {
arg = encodeURI(arg);
}
return arg;
});
const result = original_function.apply(this, args);
return result;
}
return descriptor;
}
}
as such:
#uri_encode(0)
#log_func
public async request(url_stub: string, options: any = {}): Promise<any> {
this inside the class method is now undefined. I'm guessing this is because the method is technically being called from outside the context of the class.
Is there a flaw in my design, or is this what I should expect? If so is there a way for me to retain the context while still using decorators?
The problem was in my decorator. Apparently modifying the original descriptor value with a () => {} function was the problem. Changing it to function () {} made it work.
God knows why.
I have a JavaScript function in Node module like this (simplified):
index.js:
var BadRequestError = require('./error')
Guard.prototype = {
/**
*
* #param {String} property
* #return {_middleware}
*/
check: function (property) {
const _middleware = function _middleware (req, res, next) {
const ok = property === 'ping'
next(!ok ? new BadRequestError('doing_it_wrong', { message: `you're doing it wrong`}) : null)
}
return _middleware
}
}
module.exports = function (options) {
return new Guard(options)
}
error.js:
module.exports = function BadRequestError (code, error) {
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = error.message
this.code = code
this.status = 400
this.inner = error
}
util.inherits(module.exports, Error)
The usage is as follows:
test.js:
var guard = require('../index')({
someProperty: 'test',
})
var req = {}
guard.check('ping')(req, res, function (err) {
assert.deepStrictEqual(null, err)
})
I tried to create TypeScript typings for it using several approaches but none seems to work:
index.d.ts:
export declare interface IGuardOptions {
property: string
}
exporting via class declaration:
export declare class Guard {
constructor(options: IGuardOptions)
check(required: string | Array<string>): any
}
exporting via interface declaration:
export declare interface Guard {
check(property: string): any
}
export declare type GuardConstructor = new(options: IGuardOptions) => Guard
It's not clear what you mean by "not working" since you are not writing any Typescript code, just declarations, so I will assume that test.js should be test.ts to answer your question.
You'll want to create a type declaration along side the original Javascript source (like you have already done), but you need to make sure that the structure is equivalent to the original module. Your Typescript declarations indicate that there are multiple exports while the Javascript implementations only have a single export.
error.d.ts
declare class BadRequestError extends Error {
code: string;
status: number;
inner: Error;
constructor(code: string, error: Error);
}
export = BadRequestError;
index.d.ts
import {Handler} from 'express';
declare interface GuardOptions {
someProperty: string;
}
declare class Guard {
constructor(options: GuardOptions);
check(property: string): Handler;
}
declare function guardFactory(options: GuardOptions): Guard;
export = guardFactory;
test.ts
import {Request, Response} from 'express';
import guardFactory = require('./index');
const guard = guardFactory({
someProperty: 'test',
});
const middleware = guard.check('ping');
const req = {} as Request;
const res = {} as Response;
const next = function (err) {
assert.deepStrictEqual(null, err);
};
middleware(req, res, next);
After server rebuild, compiller creates instanse in included api controller here:
NewController.ts
import express = require("express");
import INew = require("../interface/INew");
import NewRepository = require("../repositories/NewRepository");
class NewController {
private _newRepository: INew;
constructor() {
this._newRepository = new NewRepository();
this._newRepository.findById(5);
}
retrieve(req: express.Request, res: express.Response): void {
try {
console.log('-----------retrieve--------------------');
this._newRepository.findById(2);
}
catch (e) {
console.log(e);
}
}
}
Object.seal(NewController);
export = NewController;
constructor works: i see console message:
-------------NewRepository------------------
5 'RESULT'
NewRepository.ts:
import INew = require("../interface/INew");
import bluebird = require("bluebird");
class NewRepository implements INew {
sd: string;
constructor() {
console.log('-------------NewRepository------------------');
}
findById(id: number): void {
setTimeout(function () {
console.log(id, 'RESULT');
}, 3000);
}
}
export = NewRepository;
INew.ts
interface INew {
findById: (id: number) => void;
sd: string;
}
export = INew;
Buut when i use controller's method 'retrieve', visit rout '/new' then i get error [TypeError: Cannot read property '_newRepository' of undefined] instead : 2 'RESULT'
Angular 2 helps me with routing:
.............
getCarsRestful(): Promise<New[]> {
console.log('-------------------------------');
return this.http.get('api/new')
.toPromise()
.then(response => response.json())
.catch(this.handleError);
}
...................
and execute backend:
NewRoutes.ts
import express = require("express");
import NewController = require('../controllers/NewController');
var router = express.Router();
class NewRoutes {
private _newController: NewController;
constructor() {
this._newController = new NewController()
}
get routes() {
var controller = this._newController;
router.get("/new", controller.retrieve);
return router;
}
}
Object.seal(NewRoutes);
export = NewRoutes;
my created instanse '_newRepository' doesn't exist already, why? i get console log:
-----------retrieve--------------------
[TypeError: Cannot read property '_newRepository' of undefined]
Help please, how to make 'singltone' in ts
i don't want to create it in every controller's method, though, that works:
.................
retrieve(req: express.Request, res: express.Response): void {
try {
var _newRepository: INew;
_newRepository = new NewRepository();
_newRepository.findById(2);
.............
Try explicitly set this in router config:
router.get("/new", controller.retrieve.bind(controller));