Documentation‎ > ‎

Object Model and Storage

Persevere offers very flexible storage of JSON objects. You can store schema-free JSON data if you like and optionally make use of a flexible schema system to ensure data integrity when that's desirable for your application.

Classes

Persevere stores objects by class. Class definitions and instances are accessible through URLs. There are several important URLs for accessing classes:
  • /Class/ClassName - this returns the schema for the class and is used to access basic information about the class. As in JavaScript, there is a prototype object that can be used to define properties and methods on the prototype of all objects in the class.
  • /ClassName/ - this is used to retrieve all the instances of a class, so it's rare that you'll want to use this URL alone. However, you may frequently want to use this URL with a JSONQuery/JSONPath suffix to do queries.
  • /ClassName/id - this is used to retrieve a class instance by id.

Creating New Classes

There are several ways to define new classes:
  • from the Explorer - from the Class view, click on the New Class button and step through the prompts.  This will ultimately end up generating a config file in your WEB-INF/config directory called generated.js.  You can rename this, relocate it within the config dir, and edit it if you want, as long as you update the id value in the file itself to match the file name.  
  • create a new .js file on the filesystem in the WEB-INF/jslib directory.  You can then define classes in JavaScript.
    Class({
      id:"MyClass",
      extends: Object, // not necessary if just extending Object
      prototype: {
      },
      properties: {
      },
      methods: {
      }
    });

    When a file is edited, it the file is automatically reloaded. For large scale application development using these class definitions is the recommended approach.
  • via HTTP POST.  The body should be a JSON document that specifies the name of the class as id and optionally specifies a class to extend as extends.  For example:

    POST /Class/ HTTP/1.1

    {"id":"MyNewTable","extends":{"$ref":"Object"}

Extending/Subclassing Classes

Classes, can "extend" other classes, meaning all instances of the subclass must be valid by any schema defined by the superclass, and will be members of the superclass. An instance of the subclass will exist in queries of both the superclass and subclass. However, direct instances of the superclass will not appear in the subclass.

Subclassing also affects prototype chains - a subclass will have a prototype object that delegates to the prototype object of the superclass. In other words, If you define methods for the superclass's objects, those methods will be available on the subclass's objects as well (unless they are overridden). To extend another class, simply refer to the class with the extends property:
MySuperClass  = Class({
  id:"MySuperClass",
  ...
});
Class({
  id:"MyClass",
  extends: MySuperClass
 ..
});

Class Definitions

In Persevere, classes and their structure and methods are defined using JSON Schema which provide validation and integrity for persisted objects. Every class has a corresponding schema, which can be accessed at /Class/ClassName. The schema can be used to constrain property value types for all the object instances on a table. The schema also allow methods and method signatures to be defined that are available on all instances. See the JSON Schema documentation for more information.
A class schema can be accessed and edited both on the filesystem in the WEB-INF/config directory, and dynamically via HTTP POST.

Querying

Persevere implements an JSONQuery which extends JSONPath and has been greatly expanded with numerous additional features for more powerful querying capabilities.  A JSONQuery starts with object URL that is indicates the list being queried. JSONQuery can then be used to find objects by different property values using various operators. For example:
/Customer/?lastName='Smith'
The JSONQuery documentation provides more in-depth description of the querying capabilities.

Persisting Methods/Functions

In Persevere, you can use functions like any other value and they can be stored in properties just like any other value as long as the object is backed by a store that supports functions, and the default dynamic object database in Persevere does support functions. Typically, you will want to add functions/methods to the prototype object for a class/table. For example you could create a method getFullName that is available for all Customer objects by sending:

PUT /Customer.prototype HTTP/1.1
Content-Length: 30

{"getFullName":function() { return this.firstName + " " + this.lastName; }}

JavaScriptDB

JavaScriptDB is Persevere's default storage engine. JavaScriptDB supports storing a wide array of data types and allows objects to be stored with any structure. JavaScriptDB records a full history of all object changes. Changes are made available in the Transaction table. Each Transaction instance corresponds to a successfully committed transaction, and holds the state of the objects that were changed.

By default JavaScriptDB writes data to the disk using standard writes, allowing the OS to use high-speed write-back caching. JavaScriptDB also supports high-integrity synchronized disk writes, for situations where transactions should be verified to be written to the disk before returning. High-integrity synchronized writes can be configured by setting the writeIntegrity property in the data source configuration to true:

"sources":[
{"name": "MyClass",
"writeIntegrity": true,
"schema":{...

Generally, high-integrity writes are about 50% slower than normal writes.

JavaScriptDB also automatically indexes properties on objects using adaptive and on-demand indexing with background processing. By default, when an object is changed (created, modified, or deleted) all properties of objects are set to be indexed in background process (so as not to slow down the transaction). If a large number of objects (greater than 2000) are created with a given property, and no queries are made against that query, JavaScriptDB will stop automatically indexing that property for each modification. However, if a query is made against that property in the future, JavaScriptDB will bring the index up to date at that point.

Indexing can also be manually controlled from schemas. Property definitions may include a index attribute that defines whether or not a property should be indexed. This will override Persevere's default adaptive indexing. If index is set to true, the index will be updated on every object update. If set to false, the index will not be updated. For example:

schema: {
properties: {
someProperty: {index: true, type: "string",
...
By default JavaScriptDB assigns numerical auto-incremented ids. However, you can also assign string ids to objects. JavaScriptDB can also be configured to assign UUIDs instead of incrementing numbers. To configure for UUID assignment, add a useUUIDs: true to the data source config:
"sources":[
{"name": "MyClass",
"useUUIDs": true,
"schema":{...

Resetting the Database

During development it may be useful to occasionally "reset" the database to the initial conditions. This can be done by deleting the directories within /WEB-INF/data. When these are deleted and Persevere is restarted, Persevere will automatically recreate the database to the initial state.

Subpages (1): Class Definitions