Getting Started with the JavaScript SDK

Prerequisite knowledge

To better understand this guide and its code examples, a working knowledge of the following prerequisite topics is required.

Additionally, knowledge of the following topics is highly recommended:

  • TypeScript: Carbon’s JavaScript SDK is written in TypeScript, a JavaScript superset language, maintained by Microsoft. See: TypeScript.
  • Package Managers: Carbon recommends JSPM
  • ES2015 Modules: ES2015 modules let you “import” a file from another one so you can modularize your application. See: Modules (Exploring ES6, by Dr. Axel Rauschmayer)
Before starting this guide, please register for our community mailing list and follow the steps in the quick start guide to install your Carbon platform and Workbench, if you have already done so, let the fun begin.

Installing the SDK

Installing the SDK depends on what package manager you are using:

Package manager?

If you are not using a package manager you can clone the source code with the following command:

git clone https://github.com/CarbonLDP/CarbonLDP-JS-SDK.git

It already includes a pre-compiled version of the source code so you can just jump right in.

npm
npm install carbonldp --save
jspm
jspm install npm:carbonldp
# We also need to install one of carbonldp's dependencies
jspm install npm:jsonld
After it has been installed, the way you’ll use it will depend on your development environment:
Browser - Global Window Object

The SDK comes with a bundled version that you can import directly into your HTML:

<script src="--sdk-directory--/dist/bundles/Carbon.sfx.js"></script>

After that, a variable named Carbon will be accessible in the global scope. This variable will serve as both a class constructor and a namespace. It’s usage as a class constructor is the same as when using ES2015 Modules, but its namespace nature is unique to the Global approach.

Carbon Variable as a Namespace

The Carbon object chains together all the Carbon modules, so you can access them even if you are using the Global approach. Each time you see in an example something like:

import * as Errors from "carbonldp/HTTP/Errors";

You can take the portion after carbonldp/, replace the /s with . and append that to the Carbon namespace to access the same module. For example, accessing that module using the global namespace would be:

Carbon.HTTP.Errors
Browser - ES2015 Modules

To be able to use ES2015 modules a transpiler like Babel, Traceur or TypeScript needs to be used. This documentation won’t cover the details of setting one up, but we recommend using JSPM as a package manager, configured to use TypeScript as a transpiler. That way you can use ES2015 features and bundle your dependencies together!

Each module in Carbon will export at least one named export. Some of the modules will also export a default export containing the “main” export of the module. That export will also be available as the named export Class. For example:

// This:
import Document from "carbonldp/Document";
let myDocument:Document = new Document();

// Is the same as this:
import * as Document from "carbonldp/Document";
let myDocument:Document.Class = new Document.Class();

We recommend not using default exports, but we provide them in case you are only using the “main” export of a file.

The semantics behind Carbon’s modules are based on the usage of the import all statement. Still, you can use selective imports but you’ll likely need to rename the import variables like:

import { Factory as DocumentFactory, Class as Document } from "carbonldp/Document";

For each folder inside the Carbon source, there is a module at the same level with the same name that exports everything contained by the folder. Example:

import * as Errors from "carbonldp/HTTP/Errors";
import * as Request from "carbonldp/HTTP/Request";

import * as HTTP from "carbonldp/HTTP";

HTTP.Errors === Errors; // true
HTTP.Request === Request; // true
Next, you need to decide what language (and what version) you are going to use. The SDK can be used with the following ones:
  • TypeScript: a JavaScript superset language, maintained by Microsoft. See: TypeScript.
  • JavaScript ES2015+: JavaScript with the new features introduced in the ES2015 standard. In order for you to use it, you’ll likely need a transpiler like BabelJS or Traceur
  • JavaScript ES5: The good old JS, no arrow functions, let declarations or any of that magic. To use the SDK with this JavaScript version you’ll need to include the following polyfill before including the SDK:
    <script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.min.js"></script>

Our code examples are written in all of these languages.

Creating a platform context

Before you can save data in your platform, you need to create an account. If you haven’t done it, follow the steps in section create an agent of the quick start guide.

 

Let’s start with some basic things you can do, like creating, saving and linking documents; among other things.

First of all, you need to create a Carbon platform context. This context will provide a reference to the Carbon platform instance you are working with (for more about Carbon contexts see: Contexts).

let carbon = new Carbon(); // platform context
import Carbon from "carbonldp/Carbon";

let carbon:Carbon = new Carbon(); // platform context
var carbon = new Carbon(); // platform context
By default, platform contexts point to the instance in carbonldp.com. If you need to change this, you can do it by changing the domain setting:
let carbon = new Carbon(); // platform context
carbon.setSetting( "domain", "my-carbon-instance.com:8080" );
import Carbon from "carbonldp/Carbon";

let carbon:Carbon = new Carbon(); // platform context
carbon.setSetting( "domain", "my-carbon-instance.com:8080" );
var carbon = new Carbon(); // platform context
carbon.setSetting( "domain", "my-carbon-instance.com:8080" );

Authenticating

After creating the platform context you need to tell the platform who you are by authenticating yourself (if you haven’t created an account yet, follow the steps in the section create an agent of the quick start guide ).

All authentication/authorization related actions are done through the auth service every context has. This service provides an authenticate method where you can pass your credentials:

// ... imports

let carbon;

// ... platform context creation

carbon.auth.authenticate( "your@email.com", "YouRP455word" ).then(
    ( token ) => {
        console.log( carbon.auth.authenticatedAgent ); // Yourself!
    }
);
// ... additional imports
import Carbon from "carbonldp/Carbon";
import * as Token from "carbonldp/Auth/Token";

let carbon:Carbon;

// ... platform context creation

carbon.auth.authenticate( "your@email.com", "YouRP455word" ).then(
    ( token:Token.Class ) => {
        console.log( carbon.auth.authenticatedAgent ); // Yourself!
    }
);
var carbon;

// ... platform context creation

carbon.auth.authenticate( "your@email.com", "YouRP455word" ).then(
    function( token ) {
        console.log( carbon.auth.authenticatedAgent ); // Yourself!
    }
);
It’s important to notice that you are authenticated in that specific context (and its child contexts). You could create additional platform contexts (or other type of contexts) and authenticate on them with other credentials.

Retrieving an app context

Carbon stores your data in “applications” which are separate containers of information. Data can be linked between applications, but its management is treated separately.

 

Before continuing, you need to create your first application, if you haven’t done it, follow the steps in the section create an application of the quick start guide (remember to note your application slug, because we’ll use it later).

 

To access your application data, you need an application context that references it, on which you will be working on. To retrieve it, you can use the apps service of the platform context:

// ... additional imports
import Carbon from "carbonldp/Carbon";
import * as Token from "carbonldp/Auth/Token";

let carbon:Carbon;

// ... platform context creation

carbon.auth.authenticate( "your@email.com", "YouRP455word" ).then(
    ( token:Token.Class ) => {
        console.log( carbon.auth.authenticatedAgent ); // Yourself!
    }
);
// … additional imports
import Carbon from “carbonldp/Carbon”
import * as App from “carbonldp/App”;

let carbon:Carbon;

// ... platform context creation and authentication

let myAppContext:App.Context;

carbon.apps.getContext( "your-app-slug-here/" ).then(
    ( appContext:App.Context ) => {
        myAppContext = appContext; // retrieving your app context
    }
).catch( console.error );
// ... additional imports
import Carbon from "carbonldp/Carbon";
import * as Token from "carbonldp/Auth/Token";

let carbon:Carbon;

// ... platform context creation

carbon.auth.authenticate( "your@email.com", "YouRP455word" ).then(
    ( token:Token.Class ) => {
        console.log( carbon.auth.authenticatedAgent ); // Yourself!
    }
);
The apps.getContext() method returns a promise that when resolved, will give you the app context you need. Be sure to replace your-app-slug-here/ with your app slug.This context is a child of the platform context you created. Because of that, any setting or authentication information you saved in your platform context, is accessible (and will be used) by this application context (for more see the Inheritance section of Contexts).

Creating a document

Now that you have your app context, you can start working with your app’s data. Carbon stores data in documents, which are basically normal JavaScript objects identified by a URI (Unique Resource Identifier). These documents are composed by properties, fragments and named fragments. For more information see: JavaScript SDK Object model.

Any carbon context has a documents service that helps you manage your documents. To create one you need to call the method createChild of this service:

// ... imports

let myAppContext;

// ... app context retrieval

// JavaScript object to be persisted as a Carbon document
let project = {
    name: "Project X",
    tasks: [
        {
            name: "Task 1",
            dueDate: new Date( "2016-04-02" )
        },
        {
            name: "Task 2",
            dueDate: new Date( "2016-04-08" )
        }
    ]
};

myAppContext.documents.createChild( "/", project ).then(
    ( [ persistedProject, response ] ) => {
        console.log( project === persistedProject ); // true
        console.log( project.id ); // document's URI
    }
).catch( console.error );
// ... additional imports
import * as App from "carbonldp/App";
import * as Response from "carbonldp/HTTP/Response";
import * as PersistedDocument from "carbonldp/PersistedDocument";

let myAppContext:App.Context;

// ... app context retrieval

// JavaScript object to be persisted as a Carbon document
let project:Project = { // Project being a business specific interface defined somewhere
    name: "Project X",
    tasks: [
        {
            name: "Task 1",
            dueDate: new Date( "2016-04-02" )
        },
        {
            name: "Task 2",
            dueDate: new Date( "2016-04-08" )
        }
    ]
};

myAppContext.documents.createChild<Project>( "/", project ).then(
    ( [ persistedProject, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( project === persistedProject ); // true
        console.log( persistedProject.id ); // document's URI
    }
).catch( console.error );
var myAppContext;

// ... app context retrieval

// JavaScript object to be persisted as a Carbon document
var project = {
    name: "Project X",
    tasks: [
        {
            name: "Task 1",
            dueDate: new Date( "2016-04-02" )
        },
        {
            name: "Task 2",
            dueDate: new Date( "2016-04-08" )
        }
    ]
};

myAppContext.documents.createChild( "/", project ).then(
    function( result ) {
        var persistedProject = result[ 0 ];
        var response = result[ 1 ];

        console.log( project === persistedProject ); // true
        console.log( project.id ); // document's URI
    }
).catch( console.error );
After executing that, the information inside of project will have been persisted in Carbon. Nested objects will also be saved inside of the document, so pretty much any JSON object can be saved as a Carbon document (as long as the JSON’s information is contained inside of an object, not an array).

Notice that the promise returned by createChild is returning a PersistedDocument.Class object. This type of object represent a saved document in Carbon. It may or may not have the latest server information. You can make sure that you have its latest changes by using its refresh method.

If you’ve just executed this, you’ll notice that the document’s id (or URI) ends with something like 1264563453436/. Carbon automatically assigns URIs to newly created documents. If you want to customize the ending portion of the URI, a third parameter can be passed to the createChild method. This parameter is called slug and it serves as a suggestion for Carbon when forging the document’s URI:

// ...

myAppContext.documents.createChild( "/", project, "My sUp4 Project!" ).then(
    ( [ persistedProject, response ] ) => {
        console.log( persistedProject.id ); // .../my-supa-project/
    }
).catch( console.error );
// ...
myAppContext.documents.createChild<Project>( "/", project, "My sUp4 Project!" ).then(
( [ persistedProject, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
console.log( persistedProject.id ); // .../my-supa-project/
}
).catch( console.error );
// ...

myAppContext.documents.createChild( "/", project, "My sUp4 Project!" ).then(
    function( result ) {
        var persistedProject = result[ 0 ];
        var response = result[ 1 ];

        console.log( persistedProject.id ); // .../my-supa-project/
    }
).catch( console.error );
Another thing to notice is that the method is called createChild. All documents stored in Carbon are children of another document. In this case we are specifying our newly created document’s parent through the first parameter "/". That string represents a relative URI, one that can be resolved against something else. In this case it is being resolved to the base URI the context has. To see to what that would resolve you can use a context resolve method:
// ... imports

let myAppContext;

// ... app context retrieval

console.log( myAppContext.resolve( "" ) ); // https://.../your-app-slug/
// ... additional imports
import * as App from "carbonldp/App";

let myAppContext:App.Context;

// ... app context retrieval

console.log( myAppContext.resolve( "" ) ); // https://.../your-app-slug/
var myAppContext;

// ... app context retrieval

console.log( myAppContext.resolve( "" ) ); // https://.../your-app-slug/

Retrieving a document

Documents can be retrieved by their URIs or by following other documents’ links. In fact, you can paste the id you just retrieved when persisting your first document in your browser and you should see an XMLrepresentation of it.

Like creating documents, retrieving them can be done using the documents service inside of any Carbon context. To do so, you can use its get method:

// ... imports

let myAppContext;

// ... app context retrieval

// ID of the Carbon document you want to retrieve
let projectID = "https://example.com/your-document-id/";

myAppContext.documents.get( projectID ).then(
    ( [ project, response ] ) => {
        console.log( project.name ); // "Project X"
    }
).catch( console.error );
// ... additional imports
import * as App from "carbonldp/App";
import * as Response from "carbonldp/HTTP/Response";
import * as PersistedDocument from "carbonldp/PersistedDocument";

let myAppContext:App.Context;

// ... app context retrieval

// ID of the Carbon document you want to retrieve
let projectID:string = "https://example.com/your-document-id/";

myAppContext.documents.get<Project>( projectID ).then(
    ( [ project, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( project.name ); // "Project X"
    }
).catch( console.error );
var myAppContext;

// ... app context retrieval

// ID of the Carbon document you want to retrieve
var projectID = "https://example.com/your-document-id/";

myAppContext.documents.get( projectID ).then(
    function( result ) {
        var project = result[ 0 ];
        var response = result[ 1 ];

        console.log( project.name ); // "Project X"
    }
).catch( console.error );
In this example we used the id of the document, but there are times that instead of having the id, you have a Pointer.Class object. This can happen for example when a document points to another document through a property, or as soon as you create a document and receive a pointer back (PersistedDocument.Class extends Pointer.Class). These pointers have a resolve method that helps you retrieve their information:
// ... imports

let projectPointer;

// ... document pointer acquisition

projectPointer.resolve().then(
    ( [ project, response ] ) => {
        console.log( project.name ); // "Project X"
    }
).catch( console.error );
// ... additional imports
import * as Response from "carbonldp/HTTP/Response";
import * as PersistedDocument from "carbonldp/PersistedDocument";
import * as Pointer from "carbonldp/Pointer";

let projectPointer:Pointer.Class;

// ... document pointer acquisition

projectPointer.resolve<Project>().then(
    ( [ project, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( project.name ); // "Project X"
    }
).catch( console.error );
var projectPointer;

// ... document pointer acquisition

projectPointer.resolve().then(
    function( result ) {
        var project = result[ 0 ];
        var response = result[ 1 ];

        console.log( project.name ); // "Project X"
    }
).catch( console.error );
As you can see, Pointer.Class objects have two states, resolved and unresolved. When a pointer isn’t resolved it doesn’t contain the document’s information (or it may be just out of sync with the server) but it can be used to link other documents to it as we’ll see later. You can also use its resolve method to retrieve its information.

Modifying and saving a document

All documents in Carbon can be modified. However all the changes done to a document, are done in memory. To persist the modifications the document needs to be saved.

To modify a document you should:

  1. Retrieve the document:
    1. get using the document id as described in retrieving a document
  2. Modify the document: Add, modify or delete propertiesfragmentsnamed fragments.
  3. Save the changes through document.save method.

Document properties can be added/modified/removed as with any normal JavaScript object.

// ... imports

let project;

// ... document retrieval

project.name = "Project's New Name";
project.task.push( {
    name: "Task 3",
    dueDate: new Date( "2016-05-02" )
} );
project.description = {
    type: "markdown",
    content: "# Some description here"
};

project.save().then(
    ( [ savedProject, response ] ) => {
        console.log( savedProject === project ); // true
        return savedProject.refresh();
    }
).then(
    ( [ refreshedProject, response ] ) => {
        console.log( refreshedProject === project ); // true
        // Continue doing stuff...
    }
).catch( console.error );
// ... additional imports
import * as PersistedDocument from "carbonldp/PersistedDocument";

let project:Project & PersistedDocument.Class;

// ... document retrieval

project.name = "Project's New Name";
project.task.push( {
    name: "Task 3",
    dueDate: new Date( "2016-05-02" )
} );
project.description = {
    type: "markdown",
    content: "# Some description here"
};

project.save<Project>().then(
    ( [ savedProject, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( savedProject === project ); // true
        return savedProject.refresh<Project>();
    }
).then(
    ( [ refreshedProject, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( refreshedProject === project ); // true
        // Continue doing stuff...
    }
).catch( console.error );
var project;

// ... document retrieval

project.name = "Project's New Name";
project.task.push( {
    name: "Task 3",
    dueDate: new Date( "2016-05-02" )
} );
project.description = {
    type: "markdown",
    content: "# Some description here"
};

project.save().then(
    function( result ) => {
        var savedProject = result[ 0 ];
        var response = result[ 0 ];

        console.log( savedProject === project ); // true
        return savedProject.refresh();
    }
).then(
    function( result ) => {
        var refreshedProject = result[ 0 ];
        var response = result[ 0 ];

        console.log( refreshedProject === project ); // true
        // Continue doing stuff...
    }
).catch( console.error );
Notice that we used the save method and immediately after, we called refresh. Remember that every persisted document has this method and it helps you sync the in memory representation of the document with the server’s information. In this case we used it because after the document was saved, Carbon could have added or modified some of its properties (noticeably properties like modified).This is a very common thing to do, therefore persisted documents also have the method saveAndRefreshwhich does both things at once.

Deleting a document

As when creating and retrieving documents, the documents service can help you delete a persisted document through its delete method. To do so, you need to provide the document’s URI:

// ... imports

let myAppContext;

// ... app context retrieval

// ID of the Carbon document you want to delete
let projectID = "https://example.com/your-document-id/";

myAppContext.documents.delete( projectID ).then(
    ( response ) => {
        // continue doing stuff...
    }
).catch( console.error );
// ... additional imports
import * as App from "carbonldp/App";
import * as Response from "carbonldp/HTTP/Response";

let myAppContext:App.Context;

// ... app context retrieval

// ID of the Carbon document you want to delete
let projectID:string = "https://example.com/your-document-id/";

myAppContext.documents.delete( projectID ).then(
    ( response:Response.Class ) => {
        // continue doing stuff...
    }
).catch( console.error );
var myAppContext;

// ... app context retrieval

// ID of the Carbon document you want to delete
var projectID = "https://example.com/your-document-id/";

myAppContext.documents.delete( projectID ).then(
    function( response ) {
        // continue doing stuff...
    }
).catch( console.error );
Like in “Retrieving a document”, a document can also be deleted through the PersistedDocument.Class‘s delete method:
// ... imports

let project;

// persisted document retrieval

project.delete().then(
    ( response ) => {
        // continue doing stuff...
    }
).catch( console.error );
// ... additional imports
import * as PersistedDocument from "carbonldp/PersistedDocument";
import * as Response from "carbonldp/HTTP/Response";

let project:Project & PersistedDocument.Class;

// persisted document retrieval

project.delete().then(
    ( response:Response.Class ) => {
        // continue doing stuff...
    }
).catch( console.error );
var project;

// persisted document retrieval

project.delete().then(
    function( response ) {
        // continue doing stuff...
    }
).catch( console.error );
In fact, this pattern can be seen across several methods, where both the documents service and PersistedDocument.Class expose similar methods. For example, createChild is also exposed by a PersistedDocument.Class object and allows you to create a direct child of that document. Basically, if you don’t have a resolved reference of the document, use documents service. But if you already have one, you can call the action through the PersistedDocument.Class directly (which may make code more readable, and in some cases more efficient).

Linking documents

One of the core features of Carbon LDP™, is linking data (after all, it’s a Linked Data Platform). Documents can be linked to other documents through normal JavaScript properties:

// ... imports

let project1;
let project2;

// ... persisted documents retrieval

project1.relatedProject = project2;
// ... additional imports
import * as PersistedDocument from "carbonldp/PersistedDocument";

let project1:Project & PersistedDocument.Class;
let project2:Project & PersistedDocument.Class;

// ... persisted documents retrieval

project1.relatedProject = project2;
var project1;
var project2;

// ... persisted documents retrieval

project1.relatedProject = project2;
There are also special properties Carbon handles for you. The most important ones are contains, which represents the document parent-child relation, and members which represents membership relation.

Parent-child relation

Like we said before, every document that is saved in Carbon must have a parent. When a document gets created it’s automatically added to the array contained in its parent contains property. The SDK gives you convenient methods to handle this array, such as:

  • documents.listChildren: Returns a list of unresolved PersistedDocument.Class objects for each of the document children
  • documents.getChildren: Returns a list of RESOLVED PersistedDocument.Class objects for each of the document children
¡// ... imports

let myAppContext;

// ... app context retrieval

myAppContext.documents.listChildren( "projects/" ).then(
    ( [ unresolvedProjects, response ] ) => {
        console.log( projectPointers ); // array of unresolved pointers
    }
);

myAppContext.documents.getChildren( "projects/" ).then(
    ( [ resolvedProjects, response ] ) => {
        console.log( projects ); // array of resolved pointers
    }
);
// ... additional imports
import * as App from "carbonldp/App";

let myAppContext:App.Context;

// ... app context retrieval

myAppContext.documents.listChildren<Project>( "projects/" ).then(
    ( [ unresolvedProjects, response ] : [ ( Project & PersistedDocument.Class )[], HTTP.Response.Class]) => {
        console.log( unresolvedProjects ); // array of unresolved pointers
    }
);

myAppContext.documents.getChildren<Project>( "projects/" ).then(
    ( [ resolvedProjects, response ] : [ ( Project & PersistedDocument.Class )[], HTTP.Response.Class] ) => {
        console.log( resolvedProjects ); // array of resolved pointers
    }
);
var myAppContext;

// ... app context retrieval

myAppContext.documents.listChildren( "projects/" ).then(
    function( [ unresolvedProjects, response ] ) {
        var unresolvedProjects = result[ 0 ];
        var response = result[ 1 ];

        console.log( projectPointers ); // array of unresolved pointers
    }
);

myAppContext.documents.getChildren( "projects/" ).then(
    function( [ resolvedProjects, response ] ) {
        var resolvedProjects = result[ 0 ];
        var response = result[ 1 ];

        console.log( projects ); // array of resolved pointers
    }
);
Like createChild and delete, listChildren and getChildren are also accessible through PersistedDocument.Class. See, there’s a pattern!

 

The last example could show you weird results. The documents service caches pointers, so listing children of a document while at the same time retrieving them will return you the same objects.

 

It’s worth mentioning that parent-child relations are considered hard links. They are directly related to the structure of the data. Meaning, if you delete the relation, the child gets deleted too. By consequence, if you delete a parent, all of its children will also be deleted (and the children of its children, and so on).

Membership Relation

Because parent-child relations are hard links, it is not recommended to use them in your app logic. Instead, Carbon helps you manage another type of relation, considered as a soft link, membership relations.

The meaning of a membership relation is up to your application logic to define it. It can mean things like, a project’s list of tasks, a person’s friends, a person’s assets, etc. You must be thinking “well I can already do that with normal JavaScript properties, what are the advantages of treating them as ‘membership relations’?”. It’s true that you can manually manage lists, but to do so, you would need to manage the whole document. By configuring a property to act as a “membership relation”, you enable things like retrieving the list without retrieving the complete document, adding new elements without retrieving the list, etc.

All carbon documents can maintain a list of members. The relation between the document and its members can be configured, through the properties:

  • hasMemberRelation: Configures the property that will hold the array of members (by default members)
  • isMemberOfRelation: Configures the property that each member will have, that links back to the document they are member of (none by default)

These properties are configured in the document that will maintain the member array. Members can be added, removed, listed or retrieved, through the following methods in the documents service (and the document itself too):

  • addMember/addMembers: Adds the provided pointers as members of the document
  • listMembers: Like listChildren, returns an array of unresolved PersistedDocument.Class objects for each member
  • getMembers: Like getChildren, returns an array of RESOLVED PersistedDocument.Class objects for each member
  • removeMember/removeMembers/removeAllMembers: Removes the specified members (without deleting them)
// ... imports

let myAppContext;

// ... app context retrieval

let project = {
    name: "Important project",

    hasMemberRelation: tasks,
    isMemberOfRelation: project
};

let task1 = { name: "Task 1" };
let task2 = { name: "Task 2" };
let task3 = { name: "Task 3" };

// ... project, task1, task2 and task3 persistence (createChild, etc)

project.addMembers( [ task1, task2, task3 ] ).then(
    ( response ) => {
        return project.resolve();
    }
).then(
    ( [ project, response ] ) => {
        console.log( project.tasks ); // Pointers for task1, task2 and task3. Added because of the configured hasMemberRelation
        console.log( project.tasks.indexOf( task1 ) !== -1 ); // true (remember, pointers are cached and reused)
        console.log( project.tasks.indexOf( task2 ) !== -1 ); // true
        console.log( project.tasks.indexOf( task3 ) !== -1 ); // true

        return project.listMembers();
    }
).then(
    ( [ unresolvedTasks, response ] ) => {
        console.log( unresolvedTasks ); // Pointers for task1, task2 and task3

        return project.removeMember( task2 );
    }
).then(
    ( response ) => {
        console.log( "Task 2 is no longer a member of the project" );

        return project.getMembers();
    }
).then(
    ( [ resolvedTasks, response ] ) => {
        console.log( resolvedTasks ); // task1 and task3 documents
        console.log( task[0].project ); // pointer to project. Added due to the configured isMemberOfRelation
    }
).catch( console.error );
// ... additional imports
import * as App from "carbonldp/App";
import * as PersistedDocument from "carbonldp/PersistedDocument";
import * as Response from "carbonldp/HTTP/Response";

let myAppContext:App.Context;

// ... app context retrieval

let project:Project = {
    name: "Important project",

    hasMemberRelation: "tasks",
    isMemberOfRelation: "project"
};

let task1:Task = { name: "Task 1" }; // Being "Task" a business specific interface defined somewhere
let task2:Task = { name: "Task 2" };
let task3:Task = { name: "Task 3" };

// ... project, task1, task2 and task3 persistence (createChild, etc)

project.addMembers( [ task1, task2, task3 ] ).then(
    ( response:Response.Class ) => {
        return project.resolve<Project>();
    }
).then(
    ( [ project, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( project.tasks ); // Pointers for task1, task2 and task3. Added because of the configured hasMemberRelation
        console.log( project.tasks.indexOf( task1 ) !== -1 ); // true (remember, pointers are cached and reused)
        console.log( project.tasks.indexOf( task2 ) !== -1 ); // true
        console.log( project.tasks.indexOf( task3 ) !== -1 ); // true

        return project.listMembers<Task>();
    }
).then(
    ( [ unresolvedTasks, response ]:[ Task & PersistedDocument.Class, Response.Class ] ) => {
        console.log( unresolvedTasks ); // Pointers for task1, task2 and task3

        return project.removeMember( task2 );
    }
).then(
    ( response:Response.Class ) => {
        console.log( "Task 2 is no longer a member of the project" );

        return project.getMembers<Task>();
    }
).then(
    ( [ resolvedTasks, response ]:[ Task & PersistedDocument.Class, Response.Class ] ) => {
        console.log( resolvedTasks ); // task1 and task3 documents
        console.log( task[0].project ); // pointer to project. Added due to the configured isMemberOfRelation
    }
).catch( console.error );
var myAppContext;

// ... app context retrieval

var project = {
    name: "Important project",

    hasMemberRelation: tasks,
    isMemberOfRelation: project
};

var task1 = { name: "Task 1" };
var task2 = { name: "Task 2" };
var task3 = { name: "Task 3" };

// ... project, task1, task2 and task3 persistence (createChild, etc)

project.addMembers( [ task1, task2, task3 ] ).then(
    function( response ) {
        return project.resolve();
    }
).then(
    function( result ) {
        var project = result[ 0 ];
        var response = result[ 1 ];

        console.log( project.tasks ); // Pointers for task1, task2 and task3. Added because of the configured hasMemberRelation
        console.log( project.tasks.indexOf( task1 ) !== -1 ); // true (remember, pointers are cached and reused)
        console.log( project.tasks.indexOf( task2 ) !== -1 ); // true
        console.log( project.tasks.indexOf( task3 ) !== -1 ); // true

        return project.listMembers();
    }
).then(
    function( result ) {
        var unresolvedTasks = result[ 0 ];
        var response = result[ 1 ];

        console.log( unresolvedTasks ); // Pointers for task1, task2 and task3

        return project.removeMember( task2 );
    }
).then(
    function( response ) {
        console.log( "Task 2 is no longer a member of the project" );

        return project.getMembers();
    }
).then(
    function( result ) {
        var resolvedTasks = result[ 0 ];
        var response = result[ 1 ];

        console.log( resolvedTasks ); // task1 and task3 documents
        console.log( task[0].project ); // pointer to project. Added due to the configured isMemberOfRelation
    }
).catch( console.error );

Creating an access point

Membership relations have a limitation though, a document can only maintain one list through one property. So what happens when you want to manage members of another property? For example, what happens if you want to manage a list of people that are members of the project? Well, this can be accomplished using access points.

Access points are special documents whose purpose is to maintain a list of members of another document. Any document can have any number of access points, removing the limitation we talked about.

Like normal documents, access points can be customized by specifying a hasMemberRelation and an isMemberOfRelation. To create an access point for a document you can use the createAccessPointmethod of the documents service or of a PersistedDocument.Class object.

// ... imports

let project;
let person1;
let person2;

// ... project, person1 and person2 document retrieval

let projectMembersAccessPoint = {
    hasMemberRelation: "people", // property used to store the member list in the accessPoint's document
    isMemberOfRelation: "projects" // property used to link back members to the document they are members of
};

project.createAccessPoint( "people", projectMembersAccessPoint ).then(
    ( [ persistedProjectMembersAccessPoint, response ] ) => {
        console.log( projectMembersAccessPoint === persistedProjectMembersAccessPoint ); // true

        return persistedProjectMembersAccessPoint.addMembers( [ person1, person2 ] );
    }
).then(
    ( response ) => {
        return project.refresh<Project>();
    }
).then(
    ( [ refreshedProject, response ] ) => {
        console.log( project === refreshedProject ); // true
        console.log( refreshedProject.people ); // Pointers to person1 and person2

        return person1.refresh<Person>();
    }
).then(
    ( [ refreshedPerson1, response ] ) => {
        console.log( person1 === refreshedPerson1 ); // true
        console.log( refreshedPerson1.projects ); // Pointer to project
    }
).catch( console.error );
// ... additional imports
import * as AccessPoint from "carbonldp/AccessPoint";
import * as PersistedAccessPoint from "carbonldp/PersistedAccessPoint";
import * as PersistedDocument from "carbonldp/PersistedDocument";

let project:Project & PersistedDocument.Class;
let person1:Person & PersistedDocument.Class;
let person2:Person & PersistedDocument.Class;

// ... project, person1 and person2 document retrieval

let projectMembersAccessPoint:AccessPoint = {
    hasMemberRelation: "people", // property used to store the member list in the accessPoint's document
    isMemberOfRelation: "projects" // property used to link back members to the document they are members of
};

project.createAccessPoint( "people", projectMembersAccessPoint ).then(
    ( [ persistedProjectMembersAccessPoint, response ]:[ PersistedAccessPoint.Class, Response.Class ] ) => {
        console.log( projectMembersAccessPoint === persistedProjectMembersAccessPoint ); // true

        return persistedProjectMembersAccessPoint.addMembers( [ person1, person2 ] );
    }
).then(
    ( response:Response.Class ) => {
        return project.refresh<Project>();
    }
).then(
    ( [ refreshedProject, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( project === refreshedProject ); // true
        console.log( refreshedProject.people ); // Pointers to person1 and person2

        return person1.refresh<Person>();
    }
).then(
    ( [ refreshedPerson1, response ]:[ Person & PersistedDocument.Class, Response.Class ] ) => {
        console.log( person1 === refreshedPerson1 ); // true
        console.log( refreshedPerson1.projects ); // Pointer to project
    }
).catch( console.error );
// ... imports

var project;
var person1;
var person2;

// ... project, person1 and person2 document retrieval

var projectMembersAccessPoint = {
    hasMemberRelation: "people", // property used to store the member list in the accessPoint's document
    isMemberOfRelation: "projects" // property used to link back members to the document they are members of
};

project.createAccessPoint( "people", projectMembersAccessPoint ).then(
    function( result ) {
        var persistedProjectMembersAccessPoint = result[ 0 ];
        var response = result[ 1 ];

        console.log( projectMembersAccessPoint === persistedProjectMembersAccessPoint ); // true

        return persistedProjectMembersAccessPoint.addMembers( [ person1, person2 ] );
    }
).then(
    function( response ) {
        return project.refresh<Project>();
    }
).then(
    function( result ) {
        var refreshedProject = result[ 0 ];
        var response = result[ 1 ];

        console.log( project === refreshedProject ); // true
        console.log( refreshedProject.people ); // Pointers to person1 and person2

        return person1.refresh<Person>();
    }
).then(
    function( result ) {
        var refreshedPerson1 = result[ 0 ];
        var response = result[ 1 ];

        console.log( person1 === refreshedPerson1 ); // true
        console.log( refreshedPerson1.projects ); // Pointer to project
    }
).catch( console.error );

Putting it all together

Here’s an example of all the actions we talked about joined together into a single script:

¡import Carbon from "carbonldp/Carbon";

let project = {
    name: "Project X"
};
let persistedProject;
let tasksAccessPoint;
let persistedTask1;
let persistedTask2;

let carbon = new Carbon();
carbon.setSetting( "domain", "localhost:8083" );
carbon.setSetting( "http.ssl", false ); // Turning off HTTPS (only needed if your server isn't using it)

let appContext;

carbon.auth.authenticate( "your@email.com", "YouRP455word" ).then(
    ( token ) => {
        console.log( `Hello ${ carbon.auth.authenticatedAgent.name }!` );

        // Retrieving the application context
        return carbon.apps.getContext( "your-app-slug-here/" );
    }
).then(
    ( _appContext ) => {
        appContext = _appContext;

        // Creating a document
        return appContext.documents.createChild( "/", project );
    }
).then(
    ( [ _persistedProject, response ] ) => {
        persistedProject = _persistedProject;

        console.log( project === persistedProject ); // true
        console.log( persistedProject.id ); // document's URI
        console.log( "created" in persistedProject ); // false - The project was persisted but it's latest state hasn't been retrieved from the server

        // Read the just created document
        return appContext.documents.get( persistedProject.id ); // Can also be `return persistedProject.refresh();`
    }
).then(
    ( [ persistedProject, response ] ) => {
        console.log( project === persistedProject ); // true - The same object was updated with its latest properties
        console.log( persistedProject.created ); // Creation date

        persistedProject.description = {
            type: "markdown",
            content: "# Some description here"
        };

        // Save the latest state to the server
        return persistedProject.save(); // Can also be `appContext.documents.save<Project>( persistedProject );`
    }
).then(
    ( [ persistedProject, response ] ) => {
        console.log( project === persistedProject ); // true

        // Creating an access point for the project to manage tasks
        return persistedProject.createAccessPoint( {
            hasMemberRelation: "tasks"
        }, "tasks" );
    }
).then(
    ( [ _tasksAccessPoint, response ] ) => {
        tasksAccessPoint = _tasksAccessPoint;

        // Creating a task so we can add it to the project later on
        return appContext.documents.createChild( "/", {
            name: "Do something",
            dueDate: new Date()
        } );
    }
).then(
    ( [ persistedTask, response ] ) => {
        persistedTask1 = persistedTask;

        // Creating another task so we can add it to the project later on
        return appContext.documents.createChild( "/", {
            name: "Do something else",
            dueDate: new Date()
        } );
    }
).then(
    ( [ persistedTask, response ] ) => {
        persistedTask2 = persistedTask;

        // Adding them to the project through the AccessPoint
        return tasksAccessPoint.addMembers( [ persistedTask1, persistedTask2 ] );
    }
).then(
    ( response ) => {

        // Refreshing the project so we can check if the tasks were added
        return persistedProject.refresh();
    }
).then(
    ( [ persistedProject, response ] ) => {
        console.log( project === persistedProject ); // true - Still the same object
        console.log( "tasks" in persistedProject ); // true
        console.log( persistedProject.tasks.indexOf( persistedTask1 ) !== - 1 ); // true - There's the first task
        console.log( persistedProject.tasks.indexOf( persistedTask2 ) !== - 1 ); // true - There's the other task

        // Deleting everything
        let promises = [];
        promises.push( persistedProject.delete() );
        promises.push( persistedTask1.delete() );
        promises.push( persistedTask2.delete() );

        return Promise.all( promises );
    }
).then(
    ( responses ) => {

        console.log( "All done!" );
    }
).catch( console.error );
import Carbon from "carbonldp/Carbon";
import * as Token from "carbonldp/Auth/Token";
import * as App from "carbonldp/App";
import * as Responsae from "carbonldp/HTTP/Response";
import * as PersistedAccessPoint from "carbonldp/PersistedAccessPoint";
import * as PersistedDocument from "carbonldp/PersistedDocument";

interface Task {
    name:string;
    dueDate:Date;
}

interface Project {
    name:string;
    description?:{
        type:string;
        content:string;
    };
    tasks?:Task[];  // If there are no tasks this property won't exist
}

let project:Project = {
    name: "Project X"
};
let persistedProject:Project & PersistedDocument.Class;
let tasksAccessPoint:PersistedAccessPoint.Class;
let persistedTask1:Task & PersistedDocument.Class;
let persistedTask2:Task & PersistedDocument.Class;

let carbon:Carbon = new Carbon();
carbon.setSetting( "domain", "localhost:8083" );
carbon.setSetting( "http.ssl", false ); // Turning off HTTPS (only needed if your server isn't using it)

let appContext:App.Context;

carbon.auth.authenticate( "your@email.com", "YouRP455word" ).then(
    ( token:Token.Class ) => {
        console.log( `Hello ${ carbon.auth.authenticatedAgent.name }!` );

        // Retrieving the application context
        return carbon.apps.getContext( "your-app-slug-here/" );
    }
).then(
    ( _appContext:App.Context ) => {
        appContext = _appContext;

        // Creating a document
        return appContext.documents.createChild<Project>( "/", project );
    }
).then(
    ( [ _persistedProject, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        persistedProject = _persistedProject;

        console.log( project === persistedProject ); // true
        console.log( persistedProject.id ); // document's URI
        console.log( "created" in persistedProject ); // false - The project was persisted but it's latest state hasn't been retrieved from the server

        // Read the just created document
        return appContext.documents.get<Project>( persistedProject.id ); // Can also be `return persistedProject.refresh();`
    }
).then(
    ( [ persistedProject, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( project === persistedProject ); // true - The same object was updated with its latest properties
        console.log( persistedProject.created ); // Creation date

        persistedProject.description = {
            type: "markdown",
            content: "# Some description here"
        };

        // Save the latest state to the server
        return persistedProject.save<Project>(); // Can also be `appContext.documents.save<Project>( persistedProject );`
    }
).then(
    ( [ persistedProject, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( project === persistedProject ); // true

        // Creating an access point for the project to manage tasks
        return persistedProject.createAccessPoint( {
            hasMemberRelation: "tasks"
        }, "tasks" );
    }
).then(
    ( [ _tasksAccessPoint, response ]:[ PersistedAccessPoint.Class, Response.Class ] ) => {
        tasksAccessPoint = _tasksAccessPoint;

        // Creating a task so we can add it to the project later on
        return appContext.documents.createChild<Task>( "/", {
            name: "Do something",
            dueDate: new Date()
        } );
    }
).then(
    ( [ persistedTask, response ]:[ Task & PersistedDocument.Class, Response.Class ] ) => {
        persistedTask1 = persistedTask;

        // Creating another task so we can add it to the project later on
        return appContext.documents.createChild<Task>( "/", {
            name: "Do something else",
            dueDate: new Date()
        } );
    }
).then(
    ( [ persistedTask, response ]:[ Task & PersistedDocument.Class, Response.Class ] ) => {
        persistedTask2 = persistedTask;

        // Adding them to the project through the AccessPoint
        return tasksAccessPoint.addMembers( [ persistedTask1, persistedTask2 ] );
    }
).then(
    ( response:Response.Class ) => {

        // Refreshing the project so we can check if the tasks were added
        return persistedProject.refresh<Project>();
    }
).then(
    ( [ persistedProject, response ]:[ Project & PersistedDocument.Class, Response.Class ] ) => {
        console.log( project === persistedProject ); // true - Still the same object
        console.log( "tasks" in persistedProject ); // true
        console.log( persistedProject.tasks.indexOf( persistedTask1 ) !== - 1 ); // true - There's the first task
        console.log( persistedProject.tasks.indexOf( persistedTask2 ) !== - 1 ); // true - There's the other task

        // Deleting everything
        let promises:Promise<Response.Class>[] = [];
        promises.push( persistedProject.delete() );
        promises.push( persistedTask1.delete() );
        promises.push( persistedTask2.delete() );

        return Promise.all( promises );
    }
).then(
    ( responses:Response.Class[] ) => {

        console.log( "All done!" );
    }
).catch( console.error );
var project = {
    name: "Project X"
};
var persistedProject;
var tasksAccessPoint;
var persistedTask1;
var persistedTask2;

var carbon = new Carbon();
carbon.setSetting( "domain", "localhost:8083" );
carbon.setSetting( "http.ssl", false ); // Turning off HTTPS (only needed if your server isn't using it)

var appContext;

carbon.auth.authenticate( "your@email.com", "YouRP455word" ).then(
    function ( token ) {
        console.log( `Hello ${ carbon.auth.authenticatedAgent.name }!` );

        // Retrieving the application context
        return carbon.apps.getContext( "your-app-slug-here/" );
    }
).then(
    function ( _appContext ) {
        appContext = _appContext;

        // Creating a document
        return appContext.documents.createChild( "/", project );
    }
).then(
    function ( results ) {
        persistedProject = results[ 0 ];
        var response = results[ 1 ]

        console.log( project === persistedProject ); // true
        console.log( persistedProject.id ); // document's URI
        console.log( "created" in persistedProject ); // false - The project was persisted but it's latest state hasn't been retrieved from the server

        // Read the just created document
        return appContext.documents.get( persistedProject.id ); // Can also be `return persistedProject.refresh();`
    }
).then(
    function ( results ) {
        persistedProject = results[ 0 ]; // This is not actually needed because the reference will be the same
        var response = results[ 1 ];

        console.log( project === persistedProject ); // true - The same object was updated with its latest properties
        console.log( persistedProject.created ); // Creation date

        persistedProject.description = {
            type: "markdown",
            content: "# Some description here"
        };

        // Save the latest state to the server
        return persistedProject.save(); // Can also be `appContext.documents.save<Project>( persistedProject );`
    }
).then(
    function ( results ) {
        persistedProject = results[ 0 ]; // This is not actually needed because the reference will be the same
        var response = results[ 1 ];

        console.log( project === persistedProject ); // true

        // Creating an access point for the project to manage tasks
        return persistedProject.createAccessPoint( {
            hasMemberRelation: "tasks"
        }, "tasks" );
    }
).then(
    function ( results ) {
        tasksAccessPoint = results[ 0 ];
        var response = results[ 1 ];

        // Creating a task so we can add it to the project later on
        return appContext.documents.createChild( "/", {
            name: "Do something",
            dueDate: new Date()
        } );
    }
).then(
    function ( results ) {
        persistedTask1 = results[ 0 ];
        var response = results[ 1 ];

        // Creating another task so we can add it to the project later on
        return appContext.documents.createChild( "/", {
            name: "Do something else",
            dueDate: new Date()
        } );
    }
).then(
    function ( results ) {
        persistedTask2 = results[ 0 ];
        var response = results[ 1 ];

        // Adding them to the project through the AccessPoint
        return tasksAccessPoint.addMembers( [ persistedTask1, persistedTask2 ] );
    }
).then(
    function ( response ) {

        // Refreshing the project so we can check if the tasks were added
        return persistedProject.refresh();
    }
).then(
    function ( results ) {
        persistedProject = results[ 0 ]; // This is not actually needed because the reference will be the same
        var response = results[ 1 ];

        console.log( project === persistedProject ); // true - Still the same object
        console.log( "tasks" in persistedProject ); // true
        console.log( persistedProject.tasks.indexOf( persistedTask1 ) !== - 1 ); // true - There's the first task
        console.log( persistedProject.tasks.indexOf( persistedTask2 ) !== - 1 ); // true - There's the other task

        // Deleting everything
        var promises = [];
        promises.push( persistedProject.delete() );
        promises.push( persistedTask1.delete() );
        promises.push( persistedTask2.delete() );

        return Promise.all( promises );
    }
).then(
    function ( responses ) {

        console.log( "All done!" );
    }
).catch( console.error );
This was a short introduction to what the SDK and Carbon can do. You should be able to develop simple applications after reading this document, but to really master the inner works of the SDK you can continue reading about the Object Model.