Giter Site home page Giter Site logo

aspect.js's Introduction

Introduction

Build Status

Library for aspect-oriented programming with JavaScript, which takes advantage of ECMAScript 2016 decorators syntax.

NOTE: if you are using aspect.js in a plain JavaScript project that uses @babel/plugin-proposal-decorators, you must set its legacy property to true until #72 is fixed. See this migration note for more information. A sample project using JavaScript with Node >=10.12.0 (or >=8.12.0) & Babel 7.x can be found at https://github.com/matthewadams/aspect.js-babel7-poc. It's a good way to get going quickly with aspect.js in that environment.

For further reading on decorators, take a look at the spec.

Blog post, introduction to the AOP and the library could be found here.

Talk from AngularConnect.

Cutting Angular's Crosscuts

Sample usage

import {beforeMethod, Advised, Metadata} from 'aspect.js';

class LoggerAspect {
  @beforeMethod({
    classNamePattern: /^Article/,
    methodNamePattern: /^(get|set)/
  })
  invokeBeforeMethod(meta: Metadata) {
    // meta.advisedMetadata == { bar: 42 }
    console.log(`Inside of the logger. Called ${meta.className}.${meta.method.name} with args: ${meta.method.args.join(', ')}.`);
  }
}

class Article {
  id: number;
  title: string;
  content: string;
}

@Advised({ bar: 42 })
class ArticleCollection {
  articles: Article[] = [];
  getArticle(id: number) {
    console.log(`Getting article with id: ${id}.`);
    return this.articles.filter(a => {
      return a.id === id;
    }).pop();
  }
  setArticle(article: Article) {
    console.log(`Setting article with id: ${article.id}.`);
    this.articles.push(article);
  }
}

new ArticleCollection().getArticle(1);

// Result:
// Inside of the logger. Called ArticleCollection.getArticle with args: 1.
// Getting article with id: 1.

In case you're using aspect.js in a browser environment the minifier may break the annotated code because of the performed mangling. In order to handle this problem you can use:

class LoggerAspect {
  @beforeMethod({
    classes: [ArticleCollection],
    methods: [ArticleCollection.prototype.getArticle, ArticleCollection.prototype.setArticle]
  })
  invokeBeforeMethod(meta: Metadata) {
    // meta.advisedMetadata == { bar: 42 }
    console.log(`Inside of the logger. Called ${meta.className}.${meta.method.name} with args: ${meta.method.args.join(', ')}.`);
  }
}

class ArticleCollection {
  getArticle(id: number) {...}
  setArticle(article: Article) {...}
}

In this case you can omit the @Advised decorator.

This way, by explicitly listing the classes and methods which should be woven, you can prevent the unwanted effect of mangling.

Demo

git clone https://github.com/mgechev/aop.js --depth 1
npm install -g ts-node
ts-node demo/index.ts

API

The library offers the following combinations of advices and join points:

Method calls

  • beforeMethod(MethodSelector) - invoked before method call
  • afterMethod(MethodSelector) - invoked after method call
  • aroundMethod(MethodSelector) - invoked around method call
  • onThrowOfMethod(MethodSelector) - invoked on throw of method call
  • asyncOnThrowOfMethod(MethodSelector) - invoked on throw of async method call

Static method calls

  • beforeStaticMethod(MethodSelector) - invoked before static method call
  • afterStaticMethod(MethodSelector) - invoked after static method call
  • aroundStaticMethod(MethodSelector) - invoked around static method call
  • onThrowOfStaticMethod(MethodSelector) - invoked on throw of static method call
  • asyncOnThrowOfStaticMethod(MethodSelector) - invoked on throw of async static method call

Accessors

  • beforeSetter(PropertySelector) - invoked before setter call
  • afterSetter(PropertySelector) - invoked after setter call
  • aroundSetter(PropertySelector) - invoked around setter call
  • onThrowOfSetter(PropertySelector) - invoked on throw of setter call
  • asyncOnThrowOfSetter(PropertySelector) - invoked on throw of async setter call
  • beforeGetter(PropertySelector) - invoked before getter call
  • afterGetter(PropertySelector) - invoked after getter call
  • aroundGetter(PropertySelector) - invoked around getter call
  • onThrowOfGetter(PropertySelector) - invoked on throw of getter call
  • asyncOnThrowOfGetter(PropertySelector) - invoked on throw of async getter call

MethodSelector

export interface MethodSelector {
  classNamePattern?: RegExp;
  methodNamePattern?: RegExp;
  classes?: Function[];
  methods?: Function[];
}

PropertySelector

export interface PropertySelector {
  classNamePattern?: RegExp;
  propertyNamePattern?: RegExp;
  classes?: Function[];
  properties?: PropertyDescriptor[];
}

Metadata

export class Metadata {
  public method: MethodMetadata;
  public className: string;
  public advisedMetadata: any;
}

MethodMetadata

export class MethodMetadata {
  public proceed: boolean;
  public name: string;
  public args: any[];
  public context: any;
  public result: any;
  public exception: any;
  public invoke: (...args: any[]) => any;
}

Diagram

Here's a UML class diagram which shows the relations between the individual abstractions:

UML Diagram

Roadmap

  • Tests
  • Type annotations and DTS generation
  • Aspect factories
    • Generic aspects
  • Implement the following advices:
    • Before
    • After
      • Throwing
      • Returning
    • Around
  • Implement the following join points:
    • Method execution
    • Static method execution
    • Filed get
    • Field set

License

MIT

aspect.js's People

Contributors

dependabot[bot] avatar islandman93 avatar jtheoof avatar matthewadams avatar mgechev avatar mrajcok avatar qwertyzw avatar samverschueren avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aspect.js's Issues

Write documentation

A documentation for the implementation is required:

  • Class diagram
  • Documenting the classes responsibilities

`Wove` automatically when `classes` is being used

Would it be possible to automatically wove a target when it is being used in a classes array?

class Article {
  id: number;
  title: string;
  content: string;
}

class ArticleCollection {
  articles: Article[] = [];
  getArticle(id: number) {
    console.log(`Getting article with id: ${id}.`);
    return this.articles.filter(a => {
      return a.id === id;
    }).pop();
  }
  setArticle(article: Article) {
    console.log(`Setting article with id: ${article.id}.`);
    this.articles.push(article);
  }
}
import { ArticleCollection } from './article-collection';

class LoggerAspect {
  @beforeMethod({
    classes: [ArticleCollection],
    methodNamePattern: /^(get|set)/
  })
  invokeBeforeMethod(meta: Metadata) {
    console.log(`Inside of the logger. Called ${meta.className}.${meta.method.name} with args: ${meta.method.args.join(', ')}.`);
  }
}

I'm back and forth on this one (if it would be possible but I don't see a reason why it wouldn't be possible).

The reason I would like this is that in this case the ArticleCollection is not aware at all of something being attached to it. No extra work for the developer to let it work.

The reason I wouldn't like it is because when looking at the code, you can't detect that a method could run side effects. Although, if we would go for it, nothing keeps the developer from adding @Wove() to the class just to make it clear for every other developer working on the project.

Happy to do a PR but wanted to discuss this first.

Not able to call Servlet from Aspect

I had a issue with my aspect where i am trying to call my servlet from @aftermethod through http.get() call, but _http:Http is not getting initialized in aspect and getting the error,
TypeError: cannot read property 'get' of undefined

I have also tried the approach that you mentioned in the comments of issue 22 (#22), but not able to succeed in that.

@mgechev - Please help how can i call servlet from @aftermethod under LoggerAspect.

Has NPM package been updated to contain this PR (feat: automatically weave classes)?

Hi @mgechev,

Thanks for your library. I am just starting to try and use it in a Node app and it seems to work fine.

I was looking though the closed issues and I see this feature -
feat: automatically weave classes
#45

However, when i try and use the classes property mentioned it does nothing.

The package.json file was updated 3 months ago and the merge only occurred 22 days ago..

So question is, has the NPM package been updated?

Regards,
Tarek

Getting "Cannot read property 'value' of undefined" when using aspect.js from Node.js 10.12.0, plain JavaScript project that uses Babel 7

aspect.js is broken when using Node.js in a plain JavaScript project that uses Babel 7; error is below. Babel settings are in the babel section of the package.json. I have a feeling that the issue is related to @babel/plugin-proposal-decorators.

A git repository with reproducible test case is at https://github.com/matthewadams/aspect.js-babel7-poc. You can reproduce the error by cloning the repo and issuing the following command:

npm test

The same error exists when using Node.js 8.12.0.

I'm getting the following error:

/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/aspect.js/src/join_points/static_method.js:61
            pointcut.advice = new constr(target, descriptor.value);
                                                            ^

TypeError: Cannot read property 'value' of undefined
    at /Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/aspect.js/src/join_points/static_method.js:61:61
    at _decorateElement (/Users/matthewadams/dev/aspect.js-babel7-poc/lib/main/aspects/Aspect.js:1:45845)
    at /Users/matthewadams/dev/aspect.js-babel7-poc/lib/main/aspects/Aspect.js:1:44152
    at Array.forEach (<anonymous>)
    at _decorateClass (/Users/matthewadams/dev/aspect.js-babel7-poc/lib/main/aspects/Aspect.js:1:43891)
    at _decorate (/Users/matthewadams/dev/aspect.js-babel7-poc/lib/main/aspects/Aspect.js:1:37108)
    at Object.<anonymous> (/Users/matthewadams/dev/aspect.js-babel7-poc/lib/main/aspects/Aspect.js:2:112)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Module.replacementCompile (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:58:13)
    at Module._compile (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/pirates/lib/index.js:83:24)
    at Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at /Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:62:4
    at newLoader (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/pirates/lib/index.js:88:7)
    at Object.<anonymous> (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:62:4)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Module.require (internal/modules/cjs/loader.js:636:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at Object.<anonymous> (/Users/matthewadams/dev/aspect.js-babel7-poc/lib/main/Thing.js:1:56820)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Module.replacementCompile (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:58:13)
    at Module._compile (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/pirates/lib/index.js:83:24)
    at Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at /Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:62:4
    at newLoader (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/pirates/lib/index.js:88:7)
    at Object.<anonymous> (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:62:4)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Module.require (internal/modules/cjs/loader.js:636:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at Object.<anonymous> (/Users/matthewadams/dev/aspect.js-babel7-poc/lib/test/integration/Thing.spec.js:9:15)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Module.replacementCompile (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:58:13)
    at Module._compile (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/pirates/lib/index.js:83:24)
    at Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at /Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:62:4
    at newLoader (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/pirates/lib/index.js:88:7)
    at Object.<anonymous> (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:62:4)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Module.require (internal/modules/cjs/loader.js:636:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at /Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/mocha/lib/mocha.js:250:27
    at Array.forEach (<anonymous>)
    at Mocha.loadFiles (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/mocha/lib/mocha.js:247:14)
    at Mocha.run (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/mocha/lib/mocha.js:576:10)
    at Object.<anonymous> (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/mocha/bin/_mocha:637:18)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Module.replacementCompile (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:58:13)
    at Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Object.<anonymous> (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/node_modules/append-transform/index.js:62:4)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
    at runMain (/Users/matthewadams/.node-spawn-wrap-38692-42f74de48f81/node:68:10)
    at Function.<anonymous> (/Users/matthewadams/.node-spawn-wrap-38692-42f74de48f81/node:171:5)
    at Object.<anonymous> (/Users/matthewadams/dev/aspect.js-babel7-poc/node_modules/nyc/bin/wrap.js:23:4)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
    at /Users/matthewadams/.node-spawn-wrap-38692-42f74de48f81/node:178:8
    at Object.<anonymous> (/Users/matthewadams/.node-spawn-wrap-38692-42f74de48f81/node:181:3)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
    at startup (internal/bootstrap/node.js:285:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3)

Waiving a Component

Hi,

Thanks for your library.

I am trying to use wove on an Angular2 component and it breaks the component behaviour:

import { LoggerAspect } from './../../aspects/logger.aspect'

@Component({
  selector: 'app-login',
  templateUrl: 'base/dist/app/directives/login/login.component.html',
  // styleUrls: ['app/directives/login/login.component.css'],
  pipes: [TranslatePipe],
  providers: [TwAPIService, HTTP_PROVIDERS],
  directives: [FORM_DIRECTIVES, MD_BUTTON_DIRECTIVES]
})
@Wove(LoggerAspect)
/**
 * Login component. Provides a login form with controlled and
 * emits a User ($event userLogged) on successful login.
 */
export class LoginComponent implements OnInit 

LoggerAspect contains the @beforeMethod annotation and some console.log statements in the same fashion as the demo.

This transpiles just fine but, upon execution, I got EXCEPTION: Error: Uncaught (in promise): TypeError: Cannot read property 'bind' of undefined.

In your opinion, is it doable at all ?

Thanks.

Can't use package name "aspect.js" from Typescript project

TL;DR

tsc fails on importing aspect.js, and @microsoft won't fix it because it's working as intended. Ergo, the project name aspect.js must be changed or its type mappings must be changed to be spec compliant.

L;R

The package name aspect.js is not compliant with the AMD specification.

I filed microsoft/TypeScript#27908, which appears to be a duplicate of microsoft/TypeScript#16444. See microsoft/TypeScript#16444 for more information.

Unfortunately, the following names are taken by similar projects:

I noticed that aspectj is not currently taken, but you might run into trademark issues with the AspectJ eclipse project.

I also noticed that aspectize is not taken, so you may consider that, since it does have the word "aspect" in it, it would be easily found, and it sounds kind of javascript-y.

Implement mangle-safe operations for fields

The MemberSelector does not support mangle-safe annotations. Its interface should change to:

export interface MemberSelector {
  classNamePattern?: RegExp;
  fieldNamePattern?: RegExp;
  classes?: any[];
  fields?: any[];
}

TypeError: Cannot read property 'value' of undefined

On starting the node server I get the following error

my/path/to/nodemodules/node_modules/aspect.js/src/joint_points/method_call.js:62
pointcut.advice = new constr(target, descriptor.value);
^
TypeError: Cannot read property 'value' of undefined

Steps followed:

  1. npm install aspect.js --save

  2. Opened of of my controller.ts and added the following code on top

import {
  Metadata,
  MethodMetadata,
  Wove,
  afterGetter,
  afterMethod,
  afterSetter,
  beforeGetter,
  beforeMethod,
  beforeStaticMethod,
} from 'aspect.js';

class LoggerAspect {
  @beforeMethod({
    classNamePattern: /.*Controller/,
    methodNamePattern: /.*/
  })
  invokeBeforeMethod(meta: Metadata) {
    console.log(`Inside of the logger`);
  }
}

3.Added the decorator in the same file as follows

@Wove()
class MyController {
  mymethod() {}
}
  1. tsc

  2. node app.js

Can't use externalized aspect

Trying to use an aspect defined in an external file & failing.

Failing test can be found at https://github.com/SciSpike/aspect.js/blob/external-aspects/test/advices/sync_advices.spec.ts#L305
(that's in forked repo https://github.com/SciSpike/aspect.js, branch external-aspects).

Code is effectively

// in file external_aspect.ts
import { aroundMethod, Metadata } from "../../lib";
import { expect } from "chai";

export default class ExternalAspect {
  @aroundMethod({ classNamePattern: /.*/, methodNamePattern: /.*/ })
  around(metadata: Metadata) {
    expect(this).to.deep.equal(ExternalAspect.prototype);
    expect(metadata.className).to.equal('Demo');
    expect(metadata.method.name).to.equal('get');
    expect(metadata.method.args).to.deep.equal([42, 1.618]);
    expect(metadata.method.invoke).to.be.a('function');

    metadata.method.proceed = false
    metadata.method.result = 'ExternalAspect'
  }
}

Failing test code is

import ExternalAspect from './external_aspect'

// ...

  describe('AroundAdvice', () => {
  // ...
    it('should invoke the external advice with the appropriate metadata', () => {
      let demo: any;

      @Wove()
      class Demo {
        get(foo: any, bar: any): string { return 'Demo' }
      }

      demo = new Demo();
      expect(demo.get(42, 1.618)).to.equal('ExternalAspect');
    });
  });

// ...

Created similar test using inline aspect that's passing at https://github.com/SciSpike/aspect.js/blob/external-aspects/test/advices/sync_advices.spec.ts#L279.

How to use aspects with dependencies in angular2 projects?

Use case:

  • The logger aspect has post log messages to a REST services
  • When using plain XMLHttpRequest, then the following error will be shown:

Error: This test module uses the component RestartWidgetComponent which is using a "templateUrl", but they were never compiled. Please call "TestBed.compileComponents" before your test. in node_modules/systemjs/dist/system.src.js line 1555 > eval (line 762)

  • Therefore angular2's http service is desired to be used
  • When injecting http, then the following error occurs:

ERROR: 'Unhandled Promise rejection:', 'this.httpService is undefined'

Interceptinn promises

Hi

I've seen afterReject and afterResolve decorators in introduction post of the library here that I guess they are useful to intercept async methods but they are not present in the library. How can I intercept promise rejections?

Thanks

JavaScript syntax extended classes

Hi,
thanks you, this library is great.
Unfortunately I've problems with logging of extended classes.

Depending where I use @wove() only methods off one class are logged (either foo or bar), but I don't get both logged together if constructor is executed. If both classes use wove decorator or only ClassA uses decorator => foo is logged. If only ClassB uses decorator => bar is logged.

file classB.js

import {Wove} from 'aspect.js';

@Wove()
export default class ClassB extends ClassA {

    constructor() {
        this.foo();
        this.bar();
    }

    bar() {
        ...
    }
    ...
}

file classA.js

import {Wove} from 'aspect.js';

@Wove()
export default ClassA {
    foo() {
        ...
    }
    ...
}

file logger.js

import {
  beforeMethod,
} from 'aspect.js';

export default class LoggerAspect {
  @beforeMethod({
      methodNamePattern: /.*/,
      classNamePattern: /.*/
  })
  beforeLogger(meta, ...args) {
      ...
  }
...
}

What is the correct syntax for this case?

Set Up on Browser

I would like to use this library. How do I set it up to use it on the browser? Do I have to download something?

Support Babel 7's @babel/plugin-proposal-decorators in modern mode

[email protected] fails when using @babel/plugin-proposal-decorators@>=7.1.6 in a pure JavaScript project. The workaround is to set the plugin's legacy property to true.

Since the modern (legacy: false) mode of the plugin is now the default and closer to the expected final version of the ECMAScript decorators proposal, aspect.js should be updated accordingly to work with the plugin's modern mode. For backward compatibility, there should probably be an aspect.js setting to opt-in to using the modern decorator mode. The next major version of aspect.js could then be changed to use the modern mode by default.

The project at https://github.com/matthewadams/aspect.js-babel7-poc can be used as a pure JavaScript test client for such an environment for integration testing.

Unknown compiler option 'lib' when running demo.

taji:~/workspace (master) $ npm install -g ts-node
taji:~/workspace (master) $ npm install
taji:~/workspace (master) $ ts-node demo/index.ts

/home/ubuntu/.nvm/versions/node/v7.4.0/lib/node_modules/ts-node/src/index.ts:199
      throw new TSError(formatDiagnostics(configDiagnostics, cwd, ts, 0))
            ^
TSError: โจฏ Unable to compile TypeScript
Unknown compiler option 'lib'. (5023)

I'm running :
ts-node v2.0.0
node v7.4.0

Looking at the tsconfig.json file:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "sourceMap": true,
    "experimentalDecorators": true,
    "outDir": "./dist",
    "lib": ["es2015", "dom"]
  },
  "exclude": [
    "typings/browser.d.ts",
    "typings/browser",
    "dist",
    "node_modules",
    ".vscode"
  ]
}

Thinking the problem is the lib parameter in tsconfig.json. Any ideas?

npm install error

npm install -g babel-node
Error: 'babel-node' is not in the npm registry.

How do I apply aspect to multiple TS modules?

All aspect.js usages examples I managed to find seem to only apply aspect in scope of single TS module: example. Is it for sake of simplicity or is it a technical restriction?

In my case, I wanted to apply an aspect to multiple classes, but I didn't manage to make it work without adding:

import {LoggingAspect} from "./logging.aspect";
LoggingAspect;

to every module. Here's my illustration:

How do I avoid these funny references? I would appreciate any hints.

beforeMethod and afterMethod does not capture constructor calls

Hi,
Kindly, I configured one aspect (called it Logger), as a provider below:

import { Injectable } from '@angular/core';
import {beforeMethod, Metadata, afterMethod} from 'aspect.js';

@Injectable()
export class Logger {
constructor()
{
console.log(Just initialized the logger ...);
}

@beforeMethod({
classNamePattern: /^./,
methodNamePattern: /^.
/
})
invokeBeforeMethod(meta: Metadata)
{
console.log(Before calling ${meta.method.name});
}

@afterMethod({
classNamePattern: /^./,
methodNamePattern: /^.
/
})
invokeAfterMethod(meta: Metadata)
{
console.log(After calling ${meta.method});
}
}

also configured it in the app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { Logger } from '../providers/logger'

@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
Logger,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {

constructor(public logger: Logger){}
}

But I observed Logger does not capture constructors calls. Is anything special required to be able to capture the initialization of objects. Or we don't support constructors calls?

Thank you,

doubt about features

This library actually supports calling synchronous multiple chained aspects?

IE:

@an-aspect-which-check-if-user-is-logged
@an-aspect-which-serializes-the-current-instance-to-json
@an-aspect-which-perform-http-post-to-save-returned-instance-from-the-previous-aspect
[method called saveUser with business logic]

class User {
   ...
   @auth //cross cutting concern
   @serialize //cross cutting concern
   @POSTInstance //cross cutting concern
   saveUser() {
      //output some where 
   }
   ...
}

Proposal: Use term "property" instead of "field"

AFAIK, neither JavaScript nor TypeScript use the term "field"; it's "property". Further, in conventional OOP, the term "member" refers to both fields and methods (which is reinforced even more so in JavaScript, since all objects have keys that can contain data or functions, which are effectively "methods").

I propose to effectively rename, with appropriate backward-compatible deprecations,

  • MemberSelector to PropertySelector,
  • fieldNamePattern to propertyNamePattern,
  • fields to properties, and
  • any other uses of the term "field" to "property" in code & documentation.

After all, the current MemberSelector into alignment with its own current fields property, which is defined to be of type PropertyDescriptor[], which contributes to confusion. Further, the pointcuts beforeGetter, beforeSetter, afterGetter, afterSetter, aroundGetter & aroundSetter, which intercept calls to get xxx () { /* ... */ } & set xxx (value) { /* ... */ } methods. Unless I'm mistaken, you cannot intercept property reads & writes without defining your own property methods with the get & set keywords, respectively. The documentation incorrectly describes this as "field" interception, which is a misnomer.

IMHO, if "field interception" could be supported, then that should be manifested in such a way as to not require the class whose "fields" are being intercepted to not have to define get or set property methods. I don't know if that's even possible right now.

Discussion welcome on this topic.

Proposal: Change name of decorator "Wove" to something better

I find the name Wove nonintuitive for that decorator. The English simple past participle wove doesn't really fit here and causes it to read awkwardly.

Better names, IMHO, include (but are not necessarily limited to):

  • Advised
  • Woven

Once a better name is chosen, backward compatibility could be provided by aliasing Wove and deprecating its usage in favor of the new name.

Sequencing of the decorators calls (multiple advices calling)

Problem

@Wove()
class Example {
    foo() {
        console.log('within foo');
    }
    bar() {
        console.log('within bar');
    }
}

const example = new Example();

setTimeout(() => {
    example.foo();
    example.bar();
}, 1e3);

class Aspect {
    @beforeMethod({ classNamePattern: /^(Example)$/, methodNamePattern: /^foo/})
    beforeFoo() {
        console.log('before foo');
    }

    @afterMethod({ classNamePattern: /^(Example)$/, methodNamePattern: /^bar/})
    afterBar() {
        console.log('before bar');
    }
}

In such code beforeFoo() advice will be executed twice, despite foo() is called once.
It is because of the sequencing of the decorators functions calls (@Wove, @beforeMethod, @afterMethod). If move Example class declaration after Aspect class, it will be no problem (@Wove decorator will be executed after @beforeMethod and @afterMethod).

Possible Solution

As a fast variant it could be solved by returning from woveTarget() func of the joint_points classes if jointpoint was already __woven__. For method_call jointpoint:

protected woveTarget(proto: any, key:string, advice: Advice, woveMetadata: any) {
    // Add this:
    if (proto[key].__woven__) {
        return;
    }

    let className = proto.constructor.name;
    let bak = proto[key];
    let self = this;
    proto[key] = function () {
      let metadata = self.getMetadata(className, key, bak, arguments, this, woveMetadata);
      return advice.wove(bak, metadata);
    };
    proto[key].__woven__ = true;
}

onThrowLogger doesn't handle async exceptions

First of all, thanks for addressing the cross-cutting concerns problem in JS, much appreciate the amazing work.

Here's my code:

const { beforeMethod, afterMethod, onThrowOfMethod, Wove } = require('aspect.js');

class LoggerAspect {
    @beforeMethod({
        methodNamePattern: /.*/,
        classNamePattern: /.*/
    })
    beforeLogger(meta, ...args) {
        console.log(`Starting method ${meta.method.name} with arguments: ${args.join(', ')}`);
    }
    @afterMethod({
        methodNamePattern: /.*/,
        classNamePattern: /.*/
    })
    afterLogger(meta, ...args) {
        console.log(`Successfully completed method ${meta.method.name} with arguments: ${args.join(', ')}`);
    }
    @onThrowOfMethod({
        methodNamePattern: /.*/,
        classNamePattern: /.*/
    })
    onThrowLogger(meta, ...args) {
        console.log(`Error during the invocation of method ${meta.method.name} with arguments: ${args.join(', ')}`);
        console.log('Exception: ', meta.method.exception);
    }
}

@Wove({ bar: 42 })
class MyClass {
    myMethodX(x) {
        console.log(`myMethodX: ${x}.`);
        throw "Catching from myMethodX successfully";
    }
    async myMethodY(y) {
        console.log(`myMethodY: ${y}.`);
        throw "This exception from async myMethodY is not caught and not logged!";
        //return await Promise.reject("This will throw an exception that won't be caught or logged either!");
        //return Promise.reject("This rejection won't be caught or logged either!");
    }
}

const o = new MyClass()

console.log("Will invoke myMethodX with xxx");
o.myMethodX("xxx")
console.log("Will invoke myMethodY with yyy");
o.myMethodY("yyy")

Here's the output:

Will invoke myMethodX with xxx
Starting method myMethodX with arguments: xxx
myMethodX: xxx.
Error during the invocation of method myMethodX with arguments: xxx
Exception:  Catching from myMethodX successfully
Will invoke myMethodY with yyy
Starting method myMethodY with arguments: yyy
myMethodY: yyy.
Successfully completed method myMethodY with arguments: yyy

/usr/local/lib/node_modules/serverless/lib/classes/Error.js:93
    throw new Error(e);
    ^

Error: This exception from async myMethodY is not caught and not logged!
    at module.exports.logError (/usr/local/lib/node_modules/serverless/lib/classes/Error.js:93:11)
    at process.on (/usr/local/lib/node_modules/serverless/bin/serverless:12:41)
    at emitTwo (events.js:106:13)
    at process.emit (events.js:194:7)
    at emitPendingUnhandledRejections (internal/process/promises.js:85:22)
    at runMicrotasksCallback (internal/process/next_tick.js:67:9)
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickDomainCallback (internal/process/next_tick.js:128:9)

The exception in the async method is not caught, instead the following log line is produced:
Successfully completed method myMethodY with arguments: yyy

Is this a bug or a feature?

Not able to Call Http POST Request in LoggerAspect Class

Hi Mgechev,

I have an issue in sending http request from the @onThrowOfMethod, I am not able to see any error in console as well as any request being sent in network tab of developer tools. Kindly help on this.

Thanks,
Mithun

Add travis

I noticed that Travis isn't being used. Would be nice to have it enabled in PR's etc. Have to add a compile step as well. Will do a PR.

Lazy loading of multiple aspects targeting woven class

Problem

In case we lazy-load multiple aspects which aim to alter behavior to already woven target it will not work because we'll check the private flag __woven__ and in case it's set to true we'll skip further woving.

Solution

A better idea is to keep the __woven__ property Set<Aspect> so we can push new elements into the set, each time we wove it with another aspect. We can check if the target has been already woven by given aspect by checking if the aspect is into the set, which is an operation with a constant complexity.

Performance metrics

Hi there,

First of all thanks for this library, it looks like a great approach concerning AOP in JS and TypeScript sure seems to be the best fit for this kind of libs. I am really looking forward evaluating it for my master thesis.

Just one question: Do you have any metrics / benchmarks on the performance overhead of your library?

Thanks for your help

P.S.: If the performance works for me I will use this library, so if you have any tasks concerning the further development don't hesitate to reach out to me, I would be glad to help.

Share `woveMetadata` between advices

Let me first explain my use case as to why I need this. Then I will explain the issue I have.

I have a service that processes a @ngrx/store Action. It creates a command and invokes it. I applied the same logic as in your scalable architecture blogpost. I want to implement an offline strategy and cache my actions first, invoke the command, and if the device is online, remove the action from the storage.

foo.service.ts

@Wove()
@Injectable()
export class FooService extends AsyncService {

    constructor(
        private _gateway: FooGateway,
    ) {
        super();
    }

    process(action: Action): Observable<any> {
        // Create command and invoke
    }
}

foo.service.aspect.ts

class AWSServiceAspect {

    @beforeMethod({
        classNamePattern: /^FooService$/,
        methodNamePattern: /^process$/
    })
    processBeforeMethod(meta: Metadata) {
        const key = new Date().toISOString();

        // Insert the action, being `meta.method.args[0]` into IndexedDB with `key`
    }

    @afterMethod({
        classNamePattern: /^FooService$/,
        methodNamePattern: /^process$/
    })
    processAfterMethod(meta: Metadata) {
        // After the invocation, I should grab the `key` and remove the record from IndexedDB
    }
}

So I store my Action in IndexedDB with the current timestamp as key. After the process method finishes, I have to know which key was used in order to remove it again. I though I could do it with the woveMetadata. Something like this:

    processBeforeMethod(meta: Metadata) {
        const key = new Date().toISOString();

        meta.woveMetadata = {key};
    }

That didn't work as meta.woveMetadata keeps being undefined in the @afterMethod method.

Current solution

Currently, you can solve this by passing an empty object in the @Wove() decorator like this

@Wove()
@Injectable()
export class FooService extends AsyncService {
}

And attach the key to the woveMetadata.

    processBeforeMethod(meta: Metadata) {
        const key = new Date().toISOString();

        meta.woveMetadata.key = key;
    }

I dived into the source code but couldn't find the reason why the first approach didn't work. Although I would think that the meta object is passed by reference, it doesn't look like that's the case.

@deprecated annotation

Hi Minko,
Are you planning to implement the generic decorators? What features you're planning to add here?

I thought of implementing (Sending PR) the @deprecated to console.warn a deprecation warning on the console since it globally used in Aspect oriented languages like: Java.

Any interest in that?
Thanks

Proposal: Replace "MethodMetadata" with "BeforeMethodMetadata" & "AfterMethodMetadata"

The reuse of MethodMetadata in both before & after advice doesn't make sense. In particular, MethodMetadata given to after advice shouldn't contain the properties proceed or invoke. Further, changing the value of proceed has no effect, and calling invoke doesn't make sense in after advice, because the method being intercepted has already been called. Also, neither result nor exception make sense in before advice.

IMHO, there should be two classes:

export class MethodMetadata {
  public name: string;
  public args: any[];
  public context: any;
}

export class BeforeMethodMetadata extends MethodMetadata {
  public proceed: boolean;
  public invoke: (...args: any[]) => any;
}

export class AfterMethodMetadata extends MethodMetadata {
  public result: any;
  public exception: any;
}

AroundMethodMetadata should be considered for around advice.

Efforts to remain backward-compatible should be made where possible.

Discussion welcome on this topic.

Work with mangling

Currently the joint-points are declared with regular expressions:

export interface MethodSelector {
  classNamePattern: RegExp;
  methodNamePattern: RegExp;
}
export interface MemberSelector {
  classNamePattern: RegExp;
  fieldNamePattern: RegExp;
}

This will prevent the library work with mangled production build.

Possible solutions:

  • Introduce "production" mode as part of the build process into which the code will be woven in-place (i.e. we will inject code on the place of the joint point).

    • Drawbacks:
      • Code duplication (given advice needs to be copied each place we use it).
      • Need to generate sourcemaps.
      • Complicated.
  • Pass a list of classes and their methods:

    class Bar {
      foo() {
        return 2;
      }
      baz() {
        return 42;
      }
    } 
    
    Bar.prototype.foo;
    Bar.prototype.baz;
    • Drawbacks:
      • Couples the advice with all the places it is used in more explicitly.
      • Need to alter existing API.
      • More verbose.

Error encountered resolving symbol values statically

After upgrading my dependencies to [email protected]. I started receiving the following error when I do AoT compilation.

Error: Error encountered resolving symbol values statically. Could not resolve aspect.js relative to /Users/sam/Projects/pridiktiv/
frontend/medapp/dist/tmp/app/shared/async-services/aws.async-service.ts.

I import Aspect.js like this

import { Wove } from 'aspect.js';

And I can resolve the issue by importing it like this

import { Wove } from 'aspect.js/aspect.js';

I use your seed project so I upgraded my compile.ahead.prod.ts to the one in the Seed project.

Do you have any ideas as to what is causing this issue? It worked perfectly before I did the upgrade. Don't have any issues with other libraries either.

RFC: Adding aspects

Enhancing method's Woving

Instead of directly adding proxies to the woven methods inside the decorators' bodies we can add metadata, similar to what Angular does with all of its decorators.

Then we can add a thin wrapper, which run-times woves the method lazy, based on the metadata.

Benefits

  • Faster initialization

Drawbacks

  • Slower first method call
  • The improvement is useless when we have only a single pointcut per method

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.