relational-json
v3.3.3
Published
Relation data model for JSON objects
Downloads
25
Maintainers
Readme
relational-json makes working with complex data simpler than ever.
Getting started
Install relational-json
npm install relational-json --save
Require
Relational-json is quite straight-forward. You require relational-json into your source:
var rJSON = require("relational-json");
and you create a db based on a schema you provide:
var db = rJSON(schema);
How it works
Relational-json wraps your primitive data (strings, numbers, booleans) in an immutable & relational structure. The format is just like JSON, except your data can reference other parts of your data tree. This is done dynamically, thanks to ES5 object getters & setters. Each object created inside relational-json is "pure", in the sense that it has no native prototype. This allows us to leverage JavaScripts prototypical inheritance in very elegant ways.
relational-json is JSON with conscience.
Why use it
When building web applications you'll be dealing with a wide variety of data and multiple data sources. Managing application state updates can quickly become problematic, especially when certain data branches rely on other branches. When you update a value, you have to make sure the change propagates everywhere. Relational-json makes all of that simpler:
- It eliminates data duplication
- It simplifies data updates (you only ever need to modify data in 1 place, since there are no duplicates)
- It encapsulates your data, data manipulation logic and restricts certain operations (such as wrong datatype in a given field)
- It's extremely portable, since the data uses the well-known JSON syntax.
- It makes no environment assumptions (can be used in browser, on node, etc.)
Limitation
Because of it's referential nature, JSON.stringify()
does not work as expected. This is due to infinite nesting, which makes you
jump from prototype to child and back again, infinitely, if you don't control the data traversal.
(we provide a simple utility, which entirely mitigates the issue)
Also, relational-json is IE9+ compatible, but cannot be shimmed or polyfilled for older browsers.
The schema
Relational-json expects a javascript-object schema representing the data tree you wish to use. Below is a full break down of the schema notation:
{
tableName: {
primary: "field to use as unique identifier. Field must be in fields object.",
fields: {
fieldName: {
allowNull: "can the value be set to null (true / false)",
dataType: "datatype of the field (string, integer, float, date, time, datetime, boolean)",
defaultValue: "value to use if none provided at creation time"
}
},
extends: {
table: "tableName that is a parent of this table",
localField: "own field that references parent table",
foreignField: "parent table field that localField is matched with"
},
aggregates: [
{
table: "tableName that is aggregated",
alias: "property alias to use for relation",
localField: "local field used to build aggregate relations",
foreignField: "foreign field used in aggregate relation",
cardinality: "(single / many), a single relation provides a direct relation to another object. A many relation creates an array of objects"
}
]
}
}
Table fields can also be written in shorthand. This creates a field that has no defaultValue and cannot be null:
tableName: {
fields: {
id: "integer",
name: "string"
}
}
Schema relations
extends
Extends is an inheritance pattern. It signifies that a table's rows are the child of another table's rows. Concretely, every row object created in this table will use a row object from a parent table as prototype.
The extends
relationship is also reflected in the parent tables' rows, which will contain a property to the child row. This will allow you to traverse your data trees in both directions (up ancestors and down descendants).
extends example
var schema = {
Vehicle: {
primary: "id",
fields: {
id: "integer",
year: "integer",
maker: { allowNull: true, dataType: "string" }
}
},
Car: {
primary: "id",
fields: {
id: "integer",
model: "string"
},
extends: { table: "Vehicle", localField: "id", foreignField: "id" }
}
};
var db = rJSON(schema);
db.Car.post({
id: 1,
year: 2001,
maker: "Toyota",
model: "Camry"
});
db.Car.get(1);
/* own props
{
id: 1,
model: "Camry"
}
*/
// prototype props
db.Car.get(1).year; // 2001
db.Car.get(1).maker; // "Toyota"
db.Vehicle.get(1);
/*
{
id: 1,
year: 2001,
maker: "Toyota",
Car: {
id: 1,
model: "Camry"
}
}
*/
aggregates
This is a composite relation between tables, not inheritance-based. Concretely, if table A "aggregates" table B, rows from table A will have a property pointing to one row (object) or many rows (array) from table B, based on the relations cardinality.
Unlike extends
, aggregate does not affect the prototype chain.
aggregates example (single)
var schema = {
Vehicle: {
primary: "id",
fields: {
id: "integer",
year: "integer",
maker: { allowNull: true, dataType: "string" }
}
},
Client: {
primary: "id",
fields: {
id: "integer",
name: "string",
vehicle_id: { allowNull: true, dataType: "integer" }
},
aggregates: [
{ table: "Vehicle", alias: "Vehicle", localField: "vehicle_id", foreignField: "id", cardinality: "single" }
]
}
};
var db = rJSON(schema);
db.Vehicle.post({
id: 1,
year: 2001,
maker: "hyundai"
});
db.Client.post({
id: 2,
name: "bob the builder",
vehicle_id: 1
});
db.Client.get(2);
/*
{
id: 2,
name: "bob the builder",
Vehicle: {
id: 1,
year: 2001,
maker: "hyundai"
}
}
*/
Table : object
Kind: global typedef
Summary: Once relational-json parsed your schema, it will return your relational database, which will be a collection of all the Tables in your data.
Each Table uses an internal Dictionary to store, retrieve and manipulate it's data (rows).
Tables are essentially the interface through which you manipulate data.
- Table : object
- .meta : object
- .get() ⇒ object | Array.<object>
- .post(d) ⇒ object
- .put(d, pkValue) ⇒ object
- .delete(id) ⇒ object
table.meta : object
Kind: instance property of Table
Summary: partial interface into the Table's inner details
Properties
| Name | Type | Description | | --- | --- | --- | | name | string | Table's property key in the relational-json database | | pk | string | name of the Table's primary field | | primary | string | alias of Table.meta.pk | | aliasMap | object | hashmap of the the table's rows' properties pointing to other tables in the relational-json database | | ownRequiredFields | Array.<string> | list of all own required fields, which must have a value at all times | | allRequiredFields | Array.<string> | list of all required fields, including own & ancestors' required fields |
table.get() ⇒ object | Array.<object>
Kind: instance method of Table
Summary: returns row data matching the provided arguments on their primary field value
Returns: object | Array.<object> - if no argument is provided, it returns an array of all data within the table.
if 1 argument is provided, it returns that row object
if many arguments are provided, it returns an array containing those row objects
table.post(d) ⇒ object
Kind: instance method of Table
Summary: creates a new row of data
Returns: object - row instance created
| Param | Type | Description | | --- | --- | --- | | d | object | data bundle, must contain all required fields |
table.put(d, pkValue) ⇒ object
Kind: instance method of Table
Summary: modifies a data row, by re-creating the row merged with the new data
Returns: object - newly created row
| Param | Type | Default | Description | | --- | --- | --- | --- | | d | object | | field:value object of data to modify | | pkValue | * | d.primary | primary value to find row to modify |
table.delete(id) ⇒ object
Kind: instance method of Table
Summary: recursively deletes the target row and it's children (if any)
Returns: object - deleted row
| Param | Type | Description | | --- | --- | --- | | id | * | primary field value of row to delete |