UNPKG

@resin/pinejs

Version:

Pine.js is a sophisticated rules-driven API engine that enables you to define rules in a structured subset of English. Those rules are used in order for Pine.js to generate a database schema and the associated [OData](http://www.odata.org/) API. This make

133 lines (99 loc) 6.17 kB
# Advanced Pinejs use This guide assumes you have already read through the [getting started guide](./GettingStarted.md) and the [project configuration guide](./ProjectConfig.md). In this tutorial we will iterate on the previous model to add a custom validation step when creating a new device. To achieve this we can use (Hooks)[./Hooks.md], they allow us to execute custom server code whenever an API call is made. ## Custom Sever Code To add custom functionality to our module we will have to provide a setup function, this will be called during server startup, and will take care of initializing everything as we need it. After following the [getting started guide](./GettingStarted.md), we should have a `.sbvr` file with our model, and a `config.json` file with our configuration, the only change we have to do for now, is to add the name of the file where we will write our custom server code, to the configuration. Go ahead and make an empty `example.coffee` file, pine can load custom code written in both Javascript or CoffeeScript, however we will stick to CoffeeScript for this guide. This is how your files should look at the end of this step. `src/example.sbvr` ``` Vocabulary: example Term: name Concept Type: Short Text (Type) Term: note Concept Type: Text (Type) Term: type Concept Type: Short Text (Type) Term: device Fact Type: device has name Necessity: each device has at most one name. Fact Type: device has note Necessity: each device has at most one note. Fact Type: device has type Necessity: each device has exactly one type. ``` `src/config.json` ```json { "models": [{ "modelName": "Example", "modelFile": "example.sbvr", "apiRoot": "example", "customServerCode": "example.coffee" }], "users": [{ "username": "guest", "password": " ", "permissions": [ "resource.all" ] }] } ``` Now that we have taken care of the necessary plumbing we can start to write some actual code. Lets assume we want to enforce that each device that is created, has a unique pair of name and devtype. This way we can have multiple devices with the same name as long as they correspond to different underlying dev types. We could enforce this directly from the SBVR model, but we will do so with a custom hook just to show how easy and effective adding a hook can be. In our `example.coffee` file we will export a setup function with the following signature ```coffee-script exports.setup = (app, sbvrUtils, db, callback) -> ``` This function will be called during pines initialization; inside the `setup` function we can run any custom code, in our case this will be a hook (refer to [Hooks](./Hooks.md) for more information). The last argument passed is a callback to signal the setup is complete; since the callback is wrapped into a promise, we also have to option to ignore it and directly return a promise from the `setup` function. The setup will fail if the returned promise resolves to an error and succeed otherwise. There are many options for when a hook must run, in our case, we want to run the validation step right after pine parses the request as this is the earliest moment we will have access to the information we will need. To do so, we can add a `POSTPARSE` hook: this hook should only run on `POST` requests which attempt to create devices. This translates to the following function ```coffee-script sbvrUtils.addHook 'POST', 'example', 'device', POSTPARSE: ({ req, request, api }) -> validate(request, api) ``` Now we are just left with needing to define our own validation step: in general this can be either synchronous or asynchronous. In case our hook needs to perform an async action we simply return a promise from it, pine will take care of waiting for the result of this promise before processing a request further. Recall we want to enforce that, when creating a device, the pair of name and devtype, is unique to the device being created. To check for this condition we can directly query the device resource to see if any device comes up with the same pair of name and devtype. The api object that is passed in the `POSTPARSE` callback is a special object generated by pinejs. It can be used to query the model: here it will be bound to the model the hook is referring to and will automatically inherit the user privileges. We can use this to check if the model contains a device with the same name/devtype combination. ```coffee-script validate = (request, api) -> api.get resource: request.resourceName options: $filter: name: request.values.name devtype: request.values.devtype .then (result) -> if result?.length > 0 throw new Error('Each device must have a different name/devtype pair') ``` `resourceName` will contain the name of the resource being accessed by the request, since we specified the hook to only run when requests are made against the `device` resource this will actually be fixed. The query we run will select all devices where name and devtype match the ones we found in the request values, if any result is found this means that the pair is already taken. To report this to the user we can simply throw an Error with the desired message and pine will relay it back to the end user with an appropriate status code. This is the full source code for the tutorial. ```coffee-script exports.setup = (app, sbvrUtils, db, callback) -> sbvrUtils.addHook 'POST', 'example', 'device', POSTPARSE: ({ req, request, api }) -> validate(request, api) validate = (request, api) -> api.get resource: request.resourceName options: $filter: name: request.values.name devtype: request.values.devtype .then (result) -> if result?.length > 0 throw new Error('Each device must have a different name/devtype pair') callback() ``` ### Where to go from here: * Learn about migrations that you can execute prior to Pine.js executing a given sbvr model: [Migrations.md](./Migrations.md) * Learn more about what you can do in the setup function from the documentation [CustomServerCode.md](./CustomServerCode.md)