Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@trigo/fsm

Package Overview
Dependencies
Maintainers
3
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@trigo/fsm - npm Package Compare versions

Comparing version
3.0.0
to
3.0.1
+49
lib/parse-transition-api.js
'use strict';
function getNested(theObject, path) {
try {
const separator = '.';
return path
.replace('[', separator).replace(']', '')
.split(separator)
.reduce(
(obj, property) => obj[property], theObject,
);
} catch (err) {
return undefined;
}
}
const replaceFromParams = (path, params, ctx) => {
let href = path;
Object.keys(params).forEach((key) => {
const value = getNested(ctx, params[key]);
if (value === undefined) {
throw new Error(`Cannot resolve param: "${key}" data path: "${params[key]}"`);
}
href = href.replace(new RegExp(`{${key}}`, 'g'), value);
});
return href;
};
module.exports = ({ api, ctx }) => {
if (!api) throw new Error('Argument "api" missing');
if (!api.path) throw new Error('Argument "api.path" missing');
const method = api.method || 'get';
let href = api.path;
if (ctx && ctx.api && ctx.api.params) {
href = replaceFromParams(href, ctx.api.params, ctx);
}
if (api.params) {
href = replaceFromParams(href, api.params, ctx);
}
return {
href,
method,
};
};
'use strict';
const parseApi = require('./parse-transition-api');
const { expect } = require('chai');
describe('parse transition api', () => {
it('throws with missing "api"', () => {
expect(() => parseApi({ })).to.throw('Argument "api" missing');
});
it('throws with missing "api.path"', () => {
expect(() => parseApi({ api: { method: 'patch' } })).to.throw('Argument "api.path" missing');
});
it('returns corrct object', () => {
const res = parseApi({ api: {
path: '/test',
method: 'patch',
} });
expect(res).to.eql({
href: '/test',
method: 'patch',
});
});
it('resolves params from ctx object', () => {
expect(parseApi({
api: {
path: '/test/{resId}',
method: 'patch',
params: {
resId: 'data.event.resId',
},
},
ctx: {
data: {
event: {
resId: '42',
},
},
},
})).to.eql({
href: '/test/42',
method: 'patch',
});
});
it('throws error when param cannot be resolved', () => {
expect(() => parseApi({
api: {
path: '/test/{resId}',
method: 'patch',
params: {
resId: 'data.event.resId',
},
},
ctx: {
data: {
event: {
},
},
},
})).to.throw('Cannot resolve param: "resId" data path: "data.event.resId"');
});
});
+1
-1

@@ -1,1 +0,1 @@

v7.6.0
v8.1.4

@@ -1,2 +0,2 @@

FROM trigo/node-base:7.6-yarn-lib
FROM trigo/node-base:8.1-yarn-lib

@@ -11,2 +11,3 @@ 'use strict';

const getAllTakenNames = require('./get-all-taken-names');
const parseTrasitionApi = require('./parse-transition-api');

@@ -26,3 +27,2 @@ const callIfSet = async (handler, ctx, args) => {

class FSM {
/**

@@ -61,3 +61,3 @@ * Returns the composit state tool used to parse and build state strings from objects

*/
constructor({ initialState, transitions, data, saveState, willChangeState, didChangeState, willSaveState, didSaveState, eventHandler }) {
constructor({ initialState, transitions, data, saveState, willChangeState, didChangeState, willSaveState, didSaveState, eventHandler, api }) {
this._state = '__uninitialized__';

@@ -73,2 +73,3 @@ this._transitions = [];

this._trasitionFunctionNames = [];
this._api = api || {};

@@ -139,2 +140,32 @@ if (transitions) {

/**
* Get rest API links for all currently available transitions than define the
* "restApi" object
*/
restApi() {
const api = {};
if (this._api && this._api.self) {
api.self = parseTrasitionApi({
api: this._api.self,
ctx: {
data: this._data,
api: this._api,
},
});
}
findPossibleTransitions(this.state, this._transitions)
.filter(t => t.api)
.forEach((t) => {
api[t.name] = parseTrasitionApi({
api: t.api,
ctx: {
data: this._data,
api: this._api,
},
});
});
return api;
}
/**
* Execute a transition

@@ -244,3 +275,3 @@ *

result[beforeHandler] = await callIfSet(this._eventHandler[beforeHandler], ctx, args);
// console.log(`Change state: "${from}" => "${to}"`)
// console.log(`Change state: "${from}" => "${to}"`)
this._state = to;

@@ -247,0 +278,0 @@

@@ -593,2 +593,146 @@ 'use strict';

});
describe('transition REST API', () => {
let fsm, cfg;
beforeEach(() => {
cfg = {
initialState: 'a',
transitions: [{
name: 't1',
from: '*',
to: 'a',
api: {
path: '/entity/trans',
method: 'patch',
},
}, {
name: 't2',
from: '*',
to: 'a',
api: {
path: '/entity/{resId}/{trans}/{subId}',
method: 'patch',
params: {
resId: 'data.resId',
subId: 'data._embedded.event.resId',
},
},
}, {
name: 't3',
from: '*',
to: 'a',
}, {
name: 't4',
from: '*',
to: 'a',
api: {
path: '/{entity}/{resId}/{trans}/{subId}',
method: 'patch',
params: {
resId: 'data.resId',
subId: 'data._embedded.event.resId',
},
},
}],
saveState: () => {},
willChangeState: () => bb.delay(5),
data: {
resId: '42',
_embedded: {
event: {
resId: '22',
},
},
},
};
});
it('exposes "restApi()" function', async () => {
fsm = new FSM(cfg);
expect(fsm.restApi).to.be.a('function');
});
it('filters transitions without "api" property', async () => {
fsm = new FSM(cfg);
const r = fsm.restApi();
expect(r.t1).to.exist;
expect(r.t2).to.exist;
expect(r.t3).not.to.exist;
});
it('returns parsed api object', async () => {
fsm = new FSM(cfg);
const r = fsm.restApi();
expect(r.t1).to.eql({
href: '/entity/trans',
method: 'patch',
});
});
it('returns "self" when declared', async () => {
fsm = new FSM(Object.assign({}, cfg, {
api: {
self: {
path: '/entity/{resId}',
},
params: {
resId: 'data.resId',
},
},
}));
const r = fsm.restApi();
expect(r.self).to.eql({
href: '/entity/42',
method: 'get',
});
});
it('can mix global & transition local params in same route', () => {
fsm = new FSM(Object.assign({}, cfg, {
api: {
data: {
entity: 'events',
},
self: {
path: '/{entity}/{resId}',
},
params: {
entity: 'api.data.entity',
resId: 'data.resId',
},
},
}));
const r = fsm.restApi();
expect(r.t4).to.eql({
href: '/events/42/{trans}/22',
method: 'patch',
});
});
it('can declare static params data in "api.data" object', () => {
fsm = new FSM(Object.assign({}, cfg, {
api: {
data: {
entity: 'events',
},
self: {
path: '/{entity}/{resId}',
},
params: {
entity: 'api.data.entity',
resId: 'data.resId',
},
},
}));
const r = fsm.restApi();
expect(r.self).to.eql({
href: '/events/42',
method: 'get',
});
});
});
});

@@ -12,19 +12,19 @@ SHELL=/bin/bash

install:
yarn install
@yarn install
clean:
rm -rf node_modules/
@rm -rf node_modules/
test:
yarn test
@yarn test
.PHONY: docs
docs:
esdoc
@esdoc
build: .
docker-compose -f docker-compose.test.yml build
@docker-compose -f docker-compose.test.yml build
lint:
yarn lint
@yarn lint

@@ -56,6 +56,6 @@ ci-lint: build

dev-inf-up:
docker-compose -f docker-compose.dev-inf.yml up -d
@docker-compose -f docker-compose.dev-inf.yml up -d
dev-inf-down:
docker-compose -f docker-compose.dev-inf.yml down
@docker-compose -f docker-compose.dev-inf.yml down
:
{
"name": "@trigo/fsm",
"version": "3.0.0",
"version": "3.0.1",
"description": "FSM - Finite State Machine",

@@ -15,11 +15,11 @@ "main": "index.js",

"bluebird": "^3.5.0",
"chai": "^3.5.0",
"chai": "^4.0.2",
"esdoc": "^0.5.2",
"eslint": "^3.17.1",
"eslint-config-airbnb-base": "^11.1.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-mocha": "^4.9.0",
"mocha": "^3.2.0",
"eslint": "^4.1.1",
"eslint-config-airbnb-base": "^11.2.0",
"eslint-plugin-import": "^2.6.1",
"eslint-plugin-mocha": "^4.11.0",
"mocha": "^3.4.2",
"nodemon": "^1.11.0",
"nyc": "^10.2.0"
"nyc": "^11.0.3"
},

@@ -26,0 +26,0 @@ "nyc": {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display