Mylet Development Guide - GetMyle/Guides GitHub Wiki
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.
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.
Creating simple mylet is as easy as couple of clicks:
- Go to https://dev.getmyle.com
- Click
Create mylet
button - Provide title for the mylet and click
Create
button - 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.
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 viewingbower.json
orpackage.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
-
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 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.
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.
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.
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.
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");
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");
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.
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.
Webpack is used to preprocess and bundle front-end resources.
Output of a webpack job is a folder with the following files:
- JS bundle file, that contains all JS and dependencies combined
- CSS bundle file, that contains all CSS and dependencies combined
- Any other resources referenced from CSS and JS files
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 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
}
-
query
is set query interface -
settings
is user settings interface
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 clientpayload
with status codestatusCode
and headersheaders
-
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 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
}
-
query
is set query interface -
settings
is user settings interface
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 namename
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));
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.
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.
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.
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 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:
- using
db.query
- using a set reference,
db.addSet('setname')
, for example, returns one
See Mylet Set Queries for more information.
Source control is done by means of Git.
By clicking on a Git (on branch)
button you would see some options:
User can see a branch they are currently on as well as remote repo.
Clicking on Manage branches
button user can see the following dialog:
Here user can manage branches.
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.
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.