Mylet Development Guide - GetMyle/Guides GitHub Wiki

Overview

What is mylet?

Mylet is a custom user app that can be run in context of user's MYLE account in smart-phone and in browser and also perform some lightweight server code.

You can think of a mylet as of a tool that transforms user's data into desired output. Users install mylets through MYLE market.

What skills are required to develop a mylet?

A mylet consists of front-end and back-end:

  • front-end is HTML, CSS, JavaScript and anything that can be run in browser
  • back-end is run by node.js

As it's seen major development time is spent writing JavaScript code.

Get Started!

Creating first mylet

Creating simple mylet is as easy as couple of clicks:

  1. Go to https://dev.getmyle.com
  2. Click Create mylet button
  3. Provide title for the mylet and click Create button
  4. On constructor page click Build & Run toolbar button

Congratulations you have just created and run your first mylet! Let's take a closer look at mylet structure.

Development environment

Here is how IDE looks like:

IDE is split into three main parts:

  • main toolbar:
    • Build & Run - builds and opens mylet in new window
    • Run Last Build - runs last mylet build in new window
    • Db - opens up database tools
    • Publish - builds and publishes current mylet version to market
    • Git (on branch) - shows source control options
    • View Output - shows last output from hook or controller during mylet execution during development
  • navigation pane on left hand side
  • content pane on right hand side that shows content of an active item in navigation pane and has own toolbar depending on the item:
    • Edit - when in document view mode, switch to document editing mode
    • Rename - renames current document or folder name
    • Delete - deletes current document or folder
    • Done - when in editing mode, switches back to view mode
    • Save - when in editing mode, saves current document
    • Install package - when viewing bower.json or package.json is used to install a dependency
    • Add file - when in folder mode, allows to create empty document or upload another one
    • Add folder - when in folder mode, creates sub-folder

Mylet files

Let's walk through project files of default mylet template:

  • api/controller.js - entry point for mylet controller - nodejs code that is executed when specific mylet URL is requested
  • api/hook.js - entry point for mylet hook - nodejs code that is executed when new record appears
  • views/index.js - entry point for UI JavaScript
  • views/index.ejs - mylet HTML page; currently EJS templates are used
  • views/layout.ejs - layout EJS template page.
  • views/index.css - UI CSS styles
  • icon.jpg - mylet icon; see Mylet configuration for how to change icon file name
  • bower.json - bower configuration file; see Managing client side dependencies
  • mylet.dev.json - development only [mylet configuration file]((#mylet-configuration)
  • mylet.json - mylet configuration file
  • package.json - npm configuration file; see Managing server side dependencies

Mylet configuration

Configuration file

Mylet configuration is setup via mylet.json file. Take a look at it's schema with default values:

{
  "title": undefined,
  "keywords": undefined,
  "icon": "icon.jpg",
  "controllerJs": "api/controller.js",
  "hookJs": "api/hook.js",
  "indexJs": "views/index.js",
  "indexCss": "views/index.css",
  "indexView": "views/index.ejs",
  "migrationsDir": "migrations",
  "config": undefined
}

Where

  • title - mylet title

  • keywords - an object with language-keywords map to trigger hook execution. Example:

    {
        "eng-USA": ["keyword 1", "keyword 2"],
        "rus-RUS": "ключевое слово 1"
    }
  • icon - path to mylet icon

  • controllerJs - path to a controller file

  • hookJs - path to a hook file

  • indexJs - path to entry point of UI JavaScript

  • indexCss - path to UI CSS file

  • indexView - path to entry point of HTML

  • migrationsDir - path to a directory where DB schema files are placed

  • config - an object representing mylet custom configuration data

See controller and hook documentation for a way how to access config object.

config object is not accessible from UI. You can use controller to pass config values to client side.

config object is read-only, any changes to it from controller/hook are not persisted.

Development configuration

There is also support of mylet.dev.json file, that have the same schema. All properties in mylet.json are overridden with ones from mylet.dev.json when in development. Published mylets don't have such file.

Front-end

Overview

Default mylet template uses Framework 7 as a UI library. It is not a mandatory and can be changed. More templates are coming...

Front-end files by default are put into views folder.

Page

By default, starting page is index.ejs. This is EJS template with mylet global variable that have the following properties:

  • styleUrl - path to a built CSS bundle
  • scriptUrl - path to a built JS bundle
  • controllerUrl - controller URL
  • url - mylet URL
  • title - mylet title
  • name - mylet identifier

Take a look at views/layout.ejs for example of what default layout renders.

Style

Styles, by default, are defined in views/index.css file. Styles can be split into several files. They can be combined using @import directive:

@import "file1.css"; /* file local to current one */

Bower is used to manage front-end dependencies. To import a style from bower_components folder, use the following approach:

@import "~ionicons/css/ionicons.css"; /* a file from bower_components folder */

Styles are preprocessed and combined into a bundle during build step. A path to the bundle is available in page EJS template in style global variable.

NOTE: it is required to require() starting CSS file somewhere from a JS file in order to build CSS bundle.

require("./index.css");

Script

By default, entry point script module is views/index.js. Different script modules are referenced using CommonJS approach:

var a = require('./a');

Bower is used to manage front-end dependencies. When referencing a module from bower_components folder, you can skip specifying bower_components in reference path.

var m1 = require("m1");				// for well-formed package
var m2 = require("aFolder/m2");		// for some specific script file in a package

All JS modules that are references from index.js are bundled into single file. Reference to this file is available in page EJS template in script global variable.

NOTE: it is required to require() starting CSS file somewhere from a JS file in order to build CSS bundle:

require("./index.css");

Integrations with server

The only way for UI to interact with server is by means of controller.

To fire a controller endpoint do an HTTP request to window.__controller_url_path_name URL.

Managing front-end dependencies

Bower is used for client side dependencies.

In order to add a dependency, select bower.json file and click Install package toolbar button. You have to type package name to be installed. Package name can also have version number and other options applicable to package names in bower install command line utility.

Every time new package is installed all unused packages are pruned.

To remove a package, just switch bower.js to edit mode and remove corresponding line.

Packaging

Webpack is used to preprocess and bundle front-end resources.

Output of a webpack job is a folder with the following files:

  1. JS bundle file, that contains all JS and dependencies combined
  2. CSS bundle file, that contains all CSS and dependencies combined
  3. Any other resources referenced from CSS and JS files

Back-end

Overview and limitations

node.js is used for mylet back-end. Back-end code is used to perform lightweight code depending on a triggering action:

  • if controller URL is hit, then controller code handles the request
  • if new record appeared in a user account, then hook code handles that

Back-end code code has limitations:

  • code execution is limited to 1 minute
  • (disk, network, memory limitations?, more to come...)

Current version of NodeJS is v5.0.0.

Controller

Controller is a NodeJS module that handles requests to a specific mylet URL.

The URL can be retrieved on client side from global __controller_url_path_name. Example of a request to a controller:

$.getJSON(window.__controller_url_path_name);

Here is an example of a simple controller:

module.exports = function (request, mylet, query, settings) {
    return Promise.resolve({ foo: 'bar' });
};

Where:

  • request is
{
   "httpVersion": '',    // the HTTP version sent by the client
   "method": '',         // The request HTTP method as a string
   "url": {
       "protocol": '',   // the request protocol, lowercased
       "slashes": true,  // the protocol requires slashes after the colon
       "auth": '',       // the authentication information portion of a URL
       "host": '',       // the full lowercased host portion of the URL, including port information
       "port": '',       // the port number portion of the host
       "hostname": '',   // just the lowercased hostname portion of the host
       "hash": '',       // the 'fragment' portion of the URL including the pound-sign
       "search": '',     // the 'query string' portion of the URL, including the leading question mark
       "query": {},      // the querystring-parsed object
       "pathname": '',   // the path section of the URL, that comes after the host and before the query, including the initial slash if present. No decoding is performed
       "path": '',       // Concatenation of pathname and search. No decoding is performed
       "href": ''        // the full URL that was originally parsed. Both the protocol and host are lowercased
   },
   "headers": {},        // the request headers object
   "rawHeaders": [],     // the raw request headers list exactly as they were received
   "payload": '' || {}   // the request payload
}
  • mylet is
{
   "name": "",                     // mylet identifier
   "title": "",                    // mylet title
   "keywords": [] || undefined,    // mylet keywords
   "config": {} || undefined,      // configuration object
   "url": "",                      // mylet URL
   "controllerUrl": "",            // mylet controller URL
   "dir": ""                       // mylet dir
}

A controller must return a Promise object. This is a way to let mylet engine know about completion of the controller task.

A value full-filled in the controller promise is send back to client. Here are possible values:

  • { statusCode: 200, headers: {}, payload: 'foo' } - so called raw response object is used to send to client payload with status code statusCode and headers headers
  • undefined is tranlsated to { statusCode: 200 }
  • string is translated to { statusCode: 200, headers: { 'content-type': 'text/plain' }, payload: 'string value' }
  • buffer is translated to { statusCode: 200, headers: { 'content-type': 'application/octet-stream', payload: 'buffer converted to string' }
  • error object is translated to Boom object
  • any other value is tranlsated to { statusCode: 200, headers: { 'content-type': 'application/json' }, payload: JSON.stringify(obj) }

There is also built-in helper module controller-response that is wrapper around building promisified response. Here is example of usage:

const response = require('controller-response');

module.exports = function (request, mylet) {
    return response.status(201).json({ foo: 'bar' });
};

Hook

Hook is a NodeJS code that is executed once new user record appears. Hook is similar to controller, but doesn't have request parameter in its execution context, but instead it has record parameter.

Here is example of logging of newly appeared record:

module.exports = function (record, mylet, query, settings) {
    console.log(record);
    return Promise.resolve();
};

Where:

  • record is
{
   "time": "",             // record time
   "timezoneOffset": 0,    // timezone offset (values on west are negative)
   "raw": "",              // record raw phrase
   "trigger": "",          // a keyword that triggered the hook
   "phrase": "",           // record phrase with trimmed trigger keyword
   "buffer": Buffer,       // raw audio buffer
   "location": {
       "longitude": 0,
       "latitude": 0,
       "altitude": 0
   }
}
  • mylet is
{
   "name": "",                     // mylet identifier
   "title": "",                    // mylet title
   "keywords": [] || undefined,    // mylet keywords
   "config": {} || undefined,      // configuration object
   "url": "",                      // mylet URL
   "controllerUrl": "",            // mylet controller URL
   "dir": ""                       // mylet dir
}

Accessing user settings

User settings can be accessed by means of settings argument of a controller/hook/migration function:

settings interface has the following methods:

  • get(name) - reads a setting with given name from server and returns completion promise
  • set(name, value) - stores a setting with name name and returns completion promise
settings.set("email", "[email protected]")
    .then(() => console.log("email is saved"));

settings.get("email")
    .then(value => console.log("received email:", value));

Managing back-end dependencies

Npm dependencies are used for back-end.

In order to add a dependency, select package.json file and click Install package toolbar button. You have to type package name to be installed. Package name can also have version number and other options applicable to package names in npm install command line utility.

Every time new package is installed all unused packages are pruned.

To remove a package, just switch package.js to edit mode and remove corresponding line.

__NOTE: Sometimes it happens that Install package method doesn't update package.json file. In this case you have to provide dependency manually.

Mylet database

Overview

Each mylet has a database which is a collection of sets that is organized so that its contents can easily be accessed, managed, and updated.

A database set is a collection of JSON documents.

Migrations

A migration is a script to update a database. This is the only way to add, remove or rename a set.

Migrations are usually placed in migrations directory. This can be changed in mylet.json.

Each migration script has to export a function that performs a migration. The function has the following parameters:

  • db - is a reference to database
  • mylet - mylet object
  • data - object that is read from a file with the same name as current migration script but with extension '.json'; the file has to contain valid JSON object
  • settings is user settings interface

Here is an example of a migration script:

module.exports = function (db, mylet, data, settings) {
    return Promise.all([
        db.addSet('history'),
        db.addSet('contactList')
    ]);
};

The code above creates two sets: history and contactList.

Migration script files have strict naming convention - they have to have numeric names, so each subsequent script name is greater then previous one. This is needed to help mylet engine to see what migrations are missing for a current mylet installation.

Tools

Mylet IDE provides a convenient way to play with a mylet database during development.

  • Drop all - deletes all sets
  • Import data... - uploads a JSON file with structure:
{
    "set1": [{}, {}],
    "set2": [{}]
}
  • Export data - downloads current database as a JSON file
  • Migrate - performs migrations from migrations directory

Database queries

Database queries allow to add, remove, rename and update database sets.

All database queries are done through first argument migration script. Here are available methods:

  • addSet(name) - adds a set, result object allows to query the set (see Mylet Set Queries)

  • renameSet(oldName, newName) - renames a set, result object allows to query the set (see Mylet Set Queries)

  • removeSet(name) - removes a set

  • query(name) - queries a set (see Mylet Set Queries)

  • import - imports JS object with the following schema:

{ "set1": [{}, {}], "set2": [{}] }


- `export` - exports mylet database into a JS object
- `list` - list available set names
- `dropAll` - deletes sets and their data


### Set queries

Set queries is subset of database queries that only perform CRUD operations on a set. So no database schema changes (i.e. adding/removing sets) possible.

`query` interface is accessed through 3rd argument of a controller/hook function.

query('food')
   .then(food => console.log(food));

Note in a migration script set queries are accessed differently:

  1. using db.query
  2. using a set reference, db.addSet('setname'), for example, returns one

See Mylet Set Queries for more information.

Collaboration and source control

Overview

Source control is done by means of Git.

By clicking on a Git (on branch) button you would see some options:

image

User can see a branch they are currently on as well as remote repo.

Managing branches

Clicking on Manage branches button user can see the following dialog:

image

Here user can manage branches.

Using remotes

Currently only BitBucket is supported as remote.

On Source Control screen there are options two push and pull:

  • Commit and Push - commits current changes to current branch and pushes them to remote repo. NOTE: force push is done.
  • Pull and Reset - pulls changes from remote and resets current branch to remote one.

NOTE: use these tools with caution because we currently don't support merging capabilities.

Publishing

Once mylet is finished and ready for publishing developers click Publish button on main toolbar to send a request to publish the mylet to market.

Once approved, the mylet will be available for users.

⚠️ **GitHub.com Fallback** ⚠️