Potion
![Dependency Status](https://gemnasium.com/badges/github.com/biosustain/potion-node.svg)
A TypeScript client for APIs written in Flask-Potion.
Installation
You can install this package via npm, but it is advised to use it with JSPM:
$(node bin)/jspm install potion=npm:potion-client
Usage
Before you use this package, make sure you include reflect-metadata and a shim for ES6/7 features (core-js has the most comprehensive collection of shims and I advise using it).
Furthermore, this package has multiple implementations available, it can be used as:
Note that any routes created with Route.<method>
and the following methods on Item
return a Promise:
.save()
.update()
.destroy()
.query()
.fetch()
Standalone
Using the package requires you to have a ES6 env setup with either JSPM or SystemJS alone (or any loader that can load commonjs packages).
You will first need to load Potion
and create an instance of it in order to be able to register any API endpoints:
import {
ItemCache,
Potion,
Item
} from 'potion';
class CustomCache<T> implements ItemCache<T> {
get(key: string): T {
}
put(key: string, item: T): T {
}
remove(key: string): {
}
}
let potion = new Potion({
host: 'http://localhost:8080',
prefix: '/api'
cache: new CustomCache<Item>()
});
Now the API endpoints can be registered either using the @potion.registerAs()
class decorator:
@potion.registerAs('/user')
class User extends Item {}
Or by using the potion.register()
method:
class User extends Item {}
potion.register('/user', User);
If there are some instance or static routes for an API endpoint that you wish to register, this can be done using:
import {Route} from 'potion';
class User extends Item {
static names = Route.GET('/names');
groups = Route.GET('/groups');
}
Furthermore, if you'd like to set some of the properties to read only (meaning that any requests to the backend will omit those properties), you can do it either directly on the resource using property decorators:
import {Item, readonly} from 'potion';
class User extends Item {
@readonly
age;
}
Or you can do it when the resource is registered:
import {Item} from 'potion';
@potion.registerAs('/user', {
readonly: ['age']
})
class User extends Item {
age;
}
Finally, to use the endpoints that were just created:
let user = User.fetch(1);
user
.then((user) => user.groups()})
.then((groups) => {
console.log(groups);
});
let names = User.names();
let users = User.query();
user.then((john) => {
john.update({name: 'John Doe'});
});
user.then((john) => {
john.destroy();
});
let jane = new User({name: 'Jane Doe'});
jane.save();
When using the .query()
method, you can provide additional params:
let users = User.query({
where: {name: 'John'},
sort: {name: false},
perPage: 50,
page: 1
});
let freshUsers = User.query(null, {
paginate: true,
cache: false
});
Furthermore, all Route methods (besides DELETE
) accept additional params as well:
class Car extends Item {
static readEngines = Route.GET('/engines');
writeSpecs = Route.POST('/specs');
}
let car = Car.fetch(1);
let engines = Car.engines({where: {type: 'Diesel'}}, {paginate: true});
car.then((car) => car.writeSpecs({
engine: {type: 'Electric'}
}));
AngularJS
If you decide to use this package as a AngularJS module, there are a few differences from the standalone version, but the API does not change. Use the following example as a starting point:
import angular from 'angular';
import {Item, Route, potion} from 'potion/angular';
angular
.module('myApp', [
potion.name
])
.config(['potionProvider', (potionProvider) => {
potionProvider.config({prefix: ''});
}])
.factory('User', ['potion', (potion) => {
class User extends Item {
name: string;
static readNames = Route.GET('/names');
readAttributes = Route.GET('/attributes');
}
return potion.register('/user', User);
}])
.controller('MyAppController', ['User', (User) => {
let user = User.fetch(1);
user
.then((user) => user.readAttributes()})
.then((attrs) => {
console.log(attrs);
});
let names = User.readNames();
let users = User.query();
user.then((john) => {
john.update({name: 'John Doe'});
});
user.then((john) => {
john.destroy();
});
let jane = new User({name: 'Jane Doe'});
jane.save();
}]);
Angular 2
Using the package in an Angular 2 app is very similar to the above, as in there are no API changes, but a few differences in the way resources are registered:
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app.module.ts';
platformBrowserDynamic().bootstrapModule(AppModule);
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {HttpModule} from '@angular/http';
import {AppComponent} from './app.component';
import {resources} from './app.resources';
@NgModule({
imports: [
BrowserModule,
HttpModule,
resources
],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
import {PotionResources, PotionModule} from 'potion-client/@angular';
import {Engine, Car} from './vehicle';
const appResources: PotionResources = {
'/engine': Engine,
'/car': [Car, {
readonly: ['production']
}]
};
export const resources = PotionModule.forRoot(appResources);
import {Component} from '@angular/core';
import {Car} from './vehicle';
@Component({
selector: 'my-app',
template: '<h1>My Angular 2 App</h1>'
})
export class AppComponent {
constructor() {
let car = new Car({brand: 'Opel'});
car.save();
}
}
import {Item} from 'potion/@angular';
export class Engine extends Item {}
export class Car extends Item {}
NOTE: It is important that HttpModule
is provided as Potion
uses Http
as default http engine to make requests.
If you wish to override either one of the config values or provide your own http engine, you can use the following:
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {HttpModule} from '@angular/http';
import {POTION_HTTP, POTION_CONFIG, PotionHttp} from 'potion/@angular';
import {resources} from './app.resources';
class MyHttp implements PotionHttp {}
@NgModule({
imports: [
BrowserModule,
HttpModule,
resources,
...
],
providers: [
{
provide: POTION_CONFIG,
useValue: {
prefix: '/api'
}
},
{
provide: POTION_HTTP,
useClass: MyHttp
}
],
...
})
export class AppModule {}
Note that you can still register new resources at a later point by using the Potion
instance (though I advise against it):
import {Component} from '@angular/core';
import {Potion, Item} from 'potion/@angular';
export class Engine extends Item {}
@Component({
selector: 'my-app',
template: '<h1>My Angular 2 App</h1>'
})
class AppComponent {
constructor(potion: Potion) {
potion.register('/engine', Engine);
}
}
Contribute
Clone the repository git clone https://github.com/biosustain/potion-node
, install all the deps (npm install
) and start hacking.
Make sure that the builds and tests will run successfully, before you make a pull request. Follow the next steps:
- use
make build
to build the .ts
files and see if any errors have occurred; - run the tests using
make test
(if you wish to run tests on file change, use $(npm bin)/karma start karma.config.ts
.); - lint the code with
make lint
.
Note: If you add/remove files, make sure to edit the "include"
field in tsconfig.json
:
{
"include": [
"src/@angular.ts",
"src/angular.ts",
"src/core.ts",
"src/fetch.ts",
"src/utils.ts"
]
}
NOTE: If you use the prepublish
hook, it will also execute every time you do a npm install
(see scripts).
Thus, it is advised to use the command below to publish and build artifacts before publish, otherwise the TS compiler will throw errors.
Use the following command to publish the package:
make publish