Rapid Vue app development with a lightweight framework.
Quadrant Transit is a web app development framework that glues your front-end Vue data models to your backend database with minimal configuration. No middleware coding required, Quadrant Transit does it for you. Think thick-client development on a modern stack with a lot less coding - a "low code" framework designed for intelligent coders who just want to save time and avoid repitition. Quadrant Transit is ideal for internal corporate apps that need to be built quickly with minimal fuss.
Transit consists of two parts: a server-side webserver and a client-side javascript library. The webserver connects to your database(s) and auto-generates APIs that enable standard CRUD operations against tables, read operations against views, and execution of procedures and functions. The client-side library connects live Vue objects to the server API; it generates Vue mixins that create mutable Vue data objects that automatically read data from your database (via the auto-generated server APIs) and synchronize client-side mutations back to the server. For more scripted non-Vue applications, a simpler javascript API wrapper library is also available.
transit = require("quadrant.transit");
try {
await transit.startServer(configFileOrObject);
} catch(err) {
console.log(err.stack || err);
console.error("Error while starting server, see message above. Exiting.");
}
{
// required: the server's port number
"port": <int>,
// required: the location of static content served by the web server
"contentDir": <string>,
// optional: Is made available via the URL /v1/$clientConfig.js
// The transitClientConfig global variable contains this value.
"clientConfig": <any>,
// optional: configuration of the identity provider
"identity": {
// required, Auth0|Simple: the name of the identity provider
"driver": <string>
// required: configuration that varies based on the driver
"config": {
// see Identity Drivers below.
}
},
// required: an array of API namespace (e.g. database) configurations
"namespaces": [
{
// required: the namespace prefix for the auto-generated API
// The API is available at /v1/<namespace>
"name": <string>,
// required, SQLServer: the name of the namespace driver
"driver": <string>,
// optional: array of role names from the identity provider
// that are allowed to use this namespace.
"allowedRoles": [<string>],
// required: configuration that varies based on the driver
"driverConfig": {
// see Namespace Drivers below.
},
// optional: list of paths to custom API extensions to add to this namespace.
// see Namespace Extensions below.
"extensions": [<string>]
}
]
}
SQLServer:
// required: the username to log in as
"userName": <string>,
// required: the password to log in with
"password": <string>,
// required: the server host to connect to
"server": <string>,
// required: options to pass into the Tedious connection library
"options": <object>
// required: the Auth0 library pem configuration value
"pem": <string>,
// required: the Auth0 library audience configuration value
"audience": <string>,
// required: the Auth0 library issuer configuration value
"issuer": <string>,
// required: the Auth0 library algorithms configuration value
"algorithms": [<string>],
// optional: the object key containing the list of roles for this user
// in auth objects returned by Auth0
"rolesKey": <string>
Documentation forthcoming.
Documentation forthcoming.
Documentation forthcoming.
<script src="/v1/transit.js"></script>
Load all namespaces to which this user has access:
Transit.loadNamespaceList(login.accessToken)
.then(function(list) {
// use the the list to pick a namespace
}).catch(function(err) {
// whoopsie
});
Load a specific namespace to connect to:
Transit.loadNamespace(namespace:string).then(function(ns) {
// configure Vue here
}).catch(function(err) {
// whoopsie
});
Configure authentication:
ns.authToken = authToken;
Handle authentication errors:
ns.onautherror = function(resumeCallback) {
PerformMyCustomAuthentication().then(function(authToken) {
resumeCallback(authToken);
}
}
Access the simple JavaScript API:
var api = ns.api;
Create a Vue mixin for this namespace, which creates a myTable result object that corresponds to the my_table_name in the database:
var mixin = bcns.vueMixin({
myTable: {
// optional string name of the database entity we're synced with,
// default is the key for this object (i.e. "myTable")
entity: "my_table_name",
// optional boolean that indicates the entity will contain only
// one row, or that only the first row should be used. When true,
// the Vue result object will be of type object rather than array.
// The default is false.
single: false,
// optional boolean that indicates the entity should not be queried
// automatically on page load. When true, the entity will only be
// loaded when load() is called manually or if the args are computed
// and a dependency is triggered.
// The default is false.
wait: false,
// optional boolean indicating that changes to the result object
// may be synchronized back to the underlying database table
// whenever save() is called. Only applicable to table entities.
// In order to be editable, the underlying table must have a primary key.
// The default is false.
editable: true,
// optional boolean indicating that any changes to the result
// object should be synchronized with the underlying database
// table automatically. Only valid for table entities that
// are editable.
// The default is false.
autosave?: boolean,
// optional object or function that serve as arguments for the entity.
// See "The Args Object" below.
// If this is a function, the function acts as a Vue computed -
// the this refers to the Vue instance, and it is reactive
// such that the entity is automatically re-queried
// anytime dependencies within the function change.
args: objectOrFunction,
// optional function that is executed whenever the entity is queried
// and the result object updated
onload: myOnloadFunction,
// optional function that is executed whenever the entity is saved
// (i.e. edits flushed to the underlying database table)
onsave: myOnsaveFunction,
// optional function that is executed when an entity fails
// to load correctly. Takes a single string parameter describing the error.
onloaderror: myOnloadErrorFunction,
// optional function that is executed when an entity fails
// to save correctly. Takes a single string parameter describing the error.
onsaveerror: myOnsaveErrorFunction,
})
var app = new Vue({
el: '#app',
mixins: [mixin],
// etc...
});
Manually load the myTable result object:
ns.load(app.myTable).then(function() {
// this == app
console.log("myTable has " + this.myTable.length + " rows");
});
// the parameter to load can also be a string:
ns.load("myTable");
Modify the myTable result object:
app.myTable[0].myColumn = "changed!";
app.myTable.push({
myColumn: "new!";
});
Synchronize edits to myTable back to the underlying database:
ns.save(app.myTable);
// or...
ns.save("myTable");
Alternatively, revert your client-side edits to myTable:
ns.revert(app.myTable);
// or...
ns.revert("myTable");
The args object represents the arguments for entity retrieval. Its form/type will differ based on the type of entity being queried.
The args object is initialized via the vueMixin method as described above. Also, the args object is available within the vue object by appending the mixin name with "Args", i.e. app.myTableArgs. This Args object is reactive such that modifications to it will automatically reload the entity from the server (unless the args is initialized as a function, in which case its value is computed automatically as described above).)
Table and view args:
{
// optional list of column names to include in the result object's rows.
// If not present, all columns are returned.
columns: ["columName1", "columnName2"],
// optional object that filters out objects returned in the result object.
// Filtering occurs at the server.
filter: {
// include only rows where columnName1 === value1
columnName1: value1,
// include only rows where columnName2's value is contained in the given list
columnName2: [value2, value3, value4]
},
// optional array alternative to the above that allows for more
// fine-grained filtering
filter: [
// include only rows where columnName1 < value1
["columnName1", "<", value1],
// include only rows where columnName2 is null
["columnName2", "is", null]
// supported operators: "=", "!=", "<>", "<", "<=", ">", ">=", "!<", "!>", "like", "not like", "is", "in"
],
// optional array indicating initial sort order (sorting performed server-side)
// e.g. sort by columnName1 ascending, then by columnName2 descending
sort: [
"columnName1",
"!columnName2"
],
// optional options to further control the query behavior
options: {
// Enables response paging by specifying the maximum number of
// rows to return in a page.
// Total number of pages is available in the Status object, see below.
// When paging, it is recommended you specify sort criteria
// to ensure stable page results.
pageSize: int,
// For paged results, specifies the index of the page to fetch.
// Page numbering starts with 1 so they can be more conveniently
// displayed in the UI. The total number of pages is availabe in
// the Status object, see below. If this number is less than or
// equal to zero, pageIndex 1 is used. If this number is greater
// than the total number of pages, an empty result will be returned.
// When paging, it is recommended you specify sort crieteria
// to ensure stable page results.
pageIndex: int,
}
}
Procedure and function args:
{
argName1: value1,
argName2: value2
}
The result object is a Vue-reactive object that contains results of a queried entity. It is an array of row objects, where each row object contains column names as keys mapping to their corresponding row values. There are two exceptions to this pattern:
-
If the mixin is configured with single: true, then the result object will be not be an array, and will instead be a single row object.
-
If the underlying database entity is a (non-table) function, then the returned row objects will consist of a single key/value pair where the key is "result".
The result object is given the same name as the mixin, i.e. app.myTable.
The status object provides information about how queries are performing. These values are particularly useful for updating the user interface in a stateful vue-like way, such as displaying a loading spinner while fetching from the server.
The Status object is available in vue by appending "Status" to the mixin name, i.e. app.myTableStatus. It has the following members, all of which are read-only:
// Status object:
{
// Is true if this entity has not yet fetched its results from the server.
unused: boolean,
// Is true if the result object has changed and requires saving.
// Only applicable for editable entities.
edited: boolean,
// Is true when the entity is in process of fetching from the server.
loading: boolean,
// Is true when the entity is in process of persisting changes back to
// the server. Only applicable for editable entities.
saving: boolean,
// Is true once connection to the server has finished.
complete: boolean,
// Contains an error message if an error occurs when communicating with
// the server, or null if no error occurs. If an error occurs, this value
// is reset to null once the next attempt is made to communicate with the server.
error: string,
// Indicates the total number of pages available when results are paged
// (see pageSize and pageIndex above). Value is 1 when results aren't paged.
pageCount: int,
}
Documentation forthcoming.
Documentation forthcoming.
Copyright 2019 Quadrant LLC. (http://quadrant.agency/) All rights reserved.
Quadrant Transit is distributed without license for evaluation purposes only.
We hope you enjoy it and would love to hear your feedback. At Quadrant we recognize that your time is valuable, so we don't beat around the bush. We're a small business finding our way in the world, which is the only reason we ask that you get in touch. Please don't hesitate to reach out.
Quadrant Transit is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.