Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Can I use this inside a property decorator? #7

Closed
Baumgaer opened this issue Jan 16, 2022 · 8 comments
Closed

Question: Can I use this inside a property decorator? #7

Baumgaer opened this issue Jan 16, 2022 · 8 comments
Labels
enhancement New feature or request implemented question Further information is requested
Milestone

Comments

@Baumgaer
Copy link

Hi there,

first of all this seems to be a very interesting project and maybe it's ver useful.
I wonder if I could use this in property decorators to get the type during runtime. I am planing to build a Mongoose Schema definition based on the class I am writing. For this I think I need to do something like this:

// My class could look like this
export default class Test {

    @Attr() myAttr!: boolean;

}

// The decorator implementation could look like this

export function Attr(options?: OptionsSchema): PropertyDecorator {
    return (target, propertyName) {
        const type = getType<typeof target>();
        const propType = type[propertyName];
        // some magic...
    }
}

Do you think this could work? I am still in preparation of my project so I can't test it yet.
What I want to avoid is something like this:

export default class Test {

    @Attr<Test>() myAttr!: boolean;

}

export function Attr<TType>(options?: OptionsSchema): PropertyDecorator {
    return (target, propertyName) {
        const type = getType<TType>();
        const propType = type[propertyName];
        // some magic...
    }
}
@Hookyns
Copy link
Owner

Hookyns commented Jan 16, 2022

Hello @Eluminati,
thank You for trying at all, it's nice when someone finds this project useful.

I play around with tst-reflect and decorators in this repo: https://github.com/Hookyns/react-typed-di
Class decorators work but property decorators do not,.. it is not implemented,.. but it should be possible to implement that.

I've made this repl with example usage of class decorators.
https://replit.com/@Hookyns/tst-reflect-example-decorators
You can access property decorators from that class decorator and do some stuff. But only decorators' names are captured for this moment so it won't help you much in your case.

PS: This code is responsible for decorators handling:
https://github.com/Hookyns/ts-reflection/blob/1a93314179f739e91fb183dcb72cb95e13ca4aa7/transformer/src/visitors/mainVisitor.ts#L89-L103
https://github.com/Hookyns/ts-reflection/blob/1a93314179f739e91fb183dcb72cb95e13ca4aa7/transformer/src/processDecorator.ts#L31-L47

@Baumgaer
Copy link
Author

Baumgaer commented Jan 21, 2022

OK, I've tryed to play around a litle bit with the package. For some reason I can't get any type information. Here is what I#ve done:

// I am using ts-config-paths-plugin
import CommonExample from "~common/models/Example";
import { Model } from "~client/utils/decorators";

@Model()
export default class Example extends CommonExample {

}

// decorators.ts
/**
 * @reflectDecorator
 */
export function Model<T extends BaseModel>(): ClassDecorator {
    const theType = getType<T>();
    console.log(theType);

    return (target) => {
        const schemaDefinition = Reflect.getMetadata("schemaDefinition", target.prototype);
        console.log(schemaDefinition);
    };
}

// This is just for completenes... uninteresting in this case
export function Attr<T extends BaseModel>(options: Pick<SchemaTypeOptions<keyof T> & ThisType<T>, allowedAttrFields> = {}): PropertyDecorator {
    // TODO:
    //      1. Make immutable on readonly
    //      2. Make enum on union types? Or maybe build own custom validator
    //      3. Make required if not optional
    //      4. Determine "ref" in case of Model
    //      5. Determine "of" in case of Map
    return (target, attributeName) => {
        let schemaDefinition = Reflect.getMetadata("schemaDefinition", target);
        if (!schemaDefinition) {
            schemaDefinition = {};
            Reflect.defineMetadata("schemaDefinition", schemaDefinition, target);
        }
        if (schemaDefinition[attributeName]) throw new Error(`Attribute "${attributeName.toString()}" is already defined`);
        schemaDefinition[attributeName] = options;
    };
}

I am building my app with @ionic/vue. This is my vue.config.js

module.exports = {
    //...
    chainWebpack: (config) => {

        config.module.rule('ts').use('ts-loader').merge({
            options: {
                configFile: TSCONFIG_PATH,
                compiler: "ttypescript"
            }
        });
    }
}

I added the folloging to my compilerOptions in tsconfig.json:

"plugins": [
    {
        "transform": "tst-reflect-transformer"
    }
]

because I am using config extension, I have added the "reflection" field in the corresponding tsconfig.json. Otherwise the transformer can't find that field. I configured reflection.metadata: false.
Could you help me?

If you want to see the whole example, you can clone the repo: https://github.com/Eluminati/boilerplate

then:

npm install
npm run serve

@Hookyns
Copy link
Owner

Hookyns commented Jan 22, 2022

@Eluminati There are several issues I've noticed.

Output of npm run build contains errors thrown by eslint from reflect.ts (file from this package). I fixed those issues in the package.

But the main problem is visible by running npx ttsc in src/client. There is some unknown type for transformer which is an Index Access Type in Mongoose Schema class.
get<K extends keyof SchemaOptions>(key: K): SchemaOptions[K];
Cuz Schema is a return type of your getSchema() method.

I'll update runtime and transformer packages and test it in your project,.. Maybe there are more unknown types like this.

@Hookyns
Copy link
Owner

Hookyns commented Jan 23, 2022

@Eluminati I've made few updates and build of whole project is now OK. JavaScript code generated by running npx ttsc contains everything so it should works.

Please, download the latest versions of both packages - runtime and transformer.

But while running npm run build, some bundling process breaks something..
In dist/js/index.js, there is:

/* harmony import */ var tst_reflect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tst-reflect */ "./node_modules/tst-reflect/reflect.js");


/**
 * @reflectDecorator
 */

function Model(__genericParams__) {
  const theType = (0,tst_reflect__WEBPACK_IMPORTED_MODULE_1__.getType)(); // <<<============ wrong; there should be __genericParams__.T

Correct code generated by Typescript, before bundling:

import { getType } from "tst-reflect";
/**
 * @reflectDecorator
 */
export function Model(__genericParams__) {
    const theType = (__genericParams__ && __genericParams__.T); // <<<========== __genericParams__.T is used
    console.log(theType);
    return (target) => {
        const schemaDefinition = Reflect.getMetadata("schemaDefinition", target.prototype);
        console.log(schemaDefinition);
    };
}

BTW: dist/js/index.js does not even contain generated metadata...

@Baumgaer
Copy link
Author

hey thank you very much. Maybe the generated code is incomplete because of the "transpileOnly" configuration of ts-loader?
It could also be a problem, that the cache-loader is used by vue or a bunch of other loaders. Lets see... I will play around a little bit with the package. =)

@Hookyns
Copy link
Owner

Hookyns commented Jan 23, 2022

hey thank you very much. Maybe the generated code is incomplete because of the "transpileOnly" configuration of ts-loader? It could also be a problem, that the cache-loader is used by vue or a bunch of other loaders. Lets see... I will play around a little bit with the package. =)

Yeap, it can be a hundred different things,..
But I'm sure that you configured it right and transformer is executed because even in dist/js/index.js there is __genericParams__ parameter added into function Model() (your decorator).

@Hookyns Hookyns added enhancement New feature or request question Further information is requested and removed enhancement New feature or request labels Feb 5, 2022
@Hookyns Hookyns added enhancement New feature or request in-progress labels Feb 10, 2022
@Hookyns Hookyns added this to the v1.0.0-alpha milestone Feb 13, 2022
Hookyns added a commit that referenced this issue Feb 13, 2022
### Added
- `getType(val: any)` it is possible to get type of runtime value,

<dl>
<dd>

```typescript
const someValue: unknown = new Animal();
getType(someValue); // > Type<Animal>
```
This works mainly with classes.
Before #29 is implemented, `@reflect()` decorator, `@reflect` JSDoc tag or `getType<OfThatType>()` is required or there will be no Type metadata.

Native types such as Object `{ foo: "", bar: 5 }`, Array `[ {}, true, 5 ]` and primitive types are supported too; those types are recognized and their properties are parsed at runtime.

*Getters and setter of Objects are recognized.*
\
*Classes without metadata will be parsed as Objects.*

</dd>
</dl>

- `Decorator.getArguments(): Array<any>` - constant literal arguments of decorators are captured,
- `Type.isPrimitive()`,
- partial test coverage (~75%) - issue #28,
- `TypeBuilder`,
- decorator can be CallExpression`@decorator()` or (new) just Identifier `@decorator`
- implementation of #7 - custom Property and Method decorators supporting `getType<T>()` of generic parameters, like already supported class decorators.

### Changed
- JSDoc tags @reflectGeneric and @reflectDecorator removed in favor of single @reflect,
- `Property.decorators` changed to `Property.getDecorators()` - to keep it same as `Type.getDecorators()` and `Method.getDecorators()`,
- `Type.flattenInheritedMembers()` support base union and intersection types,
- fixed issue #27,
- fix of some circular dependencies in runtime package,
- few other small bug fixes.
@Hookyns
Copy link
Owner

Hookyns commented Feb 13, 2022

@Eluminati property decorators are working in new version, same as method decorators.

@Hookyns
Copy link
Owner

Hookyns commented Feb 13, 2022

Available from:

tst-reflect@0.7.0-alpha.0
tst-reflect-transformer@0.9.0-beta.2

Version details in CHANGELOG

@Hookyns Hookyns closed this as completed Mar 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request implemented question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants