Skip to content

Dependencies

Dependencies are small modules that can be replaced/unplugged/override in Idyille.

1. CriteriaBuilder

The CriteriaBuilder serialize the express req.query into something understandable for your actions. Meaning, if one action use an ODM like mongoose, its job will be to serialize the query to make it compliant with the mongoose ODM. If you decide to change the ODM/ORM for a specific action, you will just have to change the criteriaBuilder associated to it.

So far, we provide a default CriteriaBuilder that is capable of serializing any express query into a mongoose query.

1.1 How to use it

The expected format is: http://api.com?criteria=<json_formatted_criteria>

1
2
3
4
5
6
7
8
9
{
    "criteria": {
        "where": {},
        "limit": 0,
        "offset": 0,
        "sort": {},
        "includes": []
    }
}
where parameter

Tip

use where parameter to filter the result of the resource requested.

1
2
3
4
5
6
7
{
    "criteria": {
      "where": {
        "something": "equals something else"
      }
    }
}
sort parameter

Tip

use sort to sort the results, it must be an object,

1
2
3
4
5
6
7
{
    "criteria": {
      "sort": {
        "some.property": -1
      }
    }
}
limit parameter

Tip

use limit to limit the number of results, it must be an integer between 0 and +∞

1
2
3
4
5
{
    "criteria": {
      "limit": 10
    }
}
offset parameter

Tip

use offset to skip a number of entries from the results, it must be an integer between 0 and +∞

1
2
3
4
5
{
    "criteria": {
      "offset": 10
    }
}
includes parameter

Tip

use includes to populate relations of results. it must be an array

1
2
3
[
    "relation1", "relation2"         
]

when you want to fetch nested relation like bar relation of foo relation of root objects

1
2
3
[
    {"foo": "bar"}         
]

works for any depth

1
2
3
4
5
6
7
8
9
[
    {
        "foo": {
            "bar": {
                "baz": "bim"
             }
        }
    }         
]

can fetch mutilple nested relation on the same depth

1
2
3
4
5
6
7
[
    {
        "foo": {
            "bar": ["baz", "fut"]
        }
    }         
]

and you can combine styles

1
2
3
4
5
6
7
8
9
[
    "faz",
    "bun",  
    {
        "fiu": {
            "bar": ["baz", "fut"]
        }
    }         
]

2. Use your own CriteriaBuilder

There is two options to replace the default CriteriaBuilder bundled with Idylle.

2.1 Overriding the default

You can override the default CriteriaBuilder at the dependencies initialization :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const Core = require('idylle').Core;
const app = new Core();

class CustomCriteriaBuilder {
  constructor() {}

  default() {
    return {foo: 'bar'};
  }

  build(query) {
    return this.default();
  }
}

Core.on(Core.events.init.dependencies, () => {
  return {
    critieriaBuilder: CustomCriteriaBuilder
    ...
  }
});

Mandatory methods

Please be careful to implement the default() and build() methods. All action will have a context where the criteria property will come from the build method when called from an HTTP request.
And when one of your action will use another action, the default criteria will be merged to the context passed to the invoked action.

2.2 Override per Action

When building an action we have seen a simple way to do it:

1
2
3
4
5
return Action({
  execute: context => {
      // ....
  }
});
Here the only mandatory property has been the execute property. You can also add a criteriaBuilder property. This property will be used only for the targeted action.

1
2
3
4
5
6
7
8
9
return Action({
  criteriaBuilder: {
      default: () => {foo: 'bar'},
      build: (query) => {foo: query['bar']}  
  },
  execute: context => {
      // ....
  }
});

2. ErrorHandler

The ErrorHandler is in charge of responding to clients when an error occurs. Generally you want to hide this error in production but need the stack trace in developpement.

That is what the default ErrorHandler does on Idylle. Here the code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
module.exports = (error, req, res, next) => {
    const details = reason(error);
    console.error(details);

    if (error.code)
        return res.status(error.code).send(details);

    if (process.env.NODE_ENV !== 'production') {
        return res.status(500).send(details);
    }

    return res.status(500).send();
};

function reason(error) {
    return error.stack ? error.stack : error.reason ? error.reason : typeof error === 'object' ? JSON.stringify(error) : error.toString()
}

2.1 Overriding the ErrorHandler

You can develop and plug your own error handler.

On the dependency initialization events: Core.events.init.dependencies you can override the ErrorHandler.

Let's say you want to modify the behavior of the ErrorHandler to return an error 500 and use the property message of your errors.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const Core = require('idylle').Core;
const app = new Core();

app.on(Core.events.init.dependencies, () => {
  return {
    errorHandler: (error, req, res, next) =>  {
        return res.status(500).send(error.message);
    }
  };
});
...

3. ResponseHandler

The ResponseHandler is used behind the scene the respond to HTTP request. It analyzes the state of the context and the data returned by an action to decide what HTTP code to use and what data to send in the request's body.

Here the default ResponseHandler provided by Idylle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module.exports = (req, res, context, result) => {
    if (!context || !context.meta)
        return res.send(result);

    if (context.meta.state === 'noContent')
        return res.status(204).send();

    if (context.meta.state === 'partial')
        return res.status(206).send(result);

    if (context.meta.state === 'stream')
        return res.download(context.meta.path);

    if (context.meta.state === 'redirect') {
        const code = context.meta.code || 302;
        return res.status(code).send(context.meta.url);
    }

    if (context.meta.state === 'created') {
        if (context.meta.resourceURI)
            res.header('location', context.meta.resourceURI);

        return res.status(201).send();
    }

    return res.send(result);
};

As you can see, nothing magical happens here. You can change the context state by calling methods like context.created(resource), or context.noContent() to update context's state. Then, once the action resolve the promise, depending on the state, the default ResponseHandler will use the data to respond to the HTTP request.

3.1 Overriding the ResponseHandler

You can develop and plug your own response handler.

On the dependency initialization events: Core.events.init.dependencies you can override the ResponseHandler.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const Core = require('idylle').Core;
const app = new Core();

app.on(Core.events.init.dependencies, () => {
  return {
    responseHandler:  (req, res, context, result) => {
        return res.send(result);
    }
  };
});
...

Comments