Getting Started with the JavaScript SDK

Edit page
Getting started
Next
Object model

This guide introduces you to the use of the Carbon LDP JavaScript SDK. In it you will find an installation guide and the basic methods needed to start using our platform.

Prerequisite knowledge

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

Additionally, knowledge on the following topics is highly recommended:

  • TypeScript: Carbon LDP's JavaScript SDK is written in TypeScript, a JavaScript superset language, maintained by Microsoft. See: TypeScript.
  • Package Managers: Carbon LDP recommends NPM
  • 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 and follow the steps in the quick start guide to install your Carbon LDP's 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/CarbonLDP.sfx.js"></script>

After that, a variable named CarbonLDP 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.

CarbonLDP Variable as a Namespace

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

import { NotFoundError } from "carbonldp/HTTP/Errors";

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

CarbonLDP.HTTP.Errors.NotFoundError
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 NPM 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 LDP will export at least one named export and you can either make use of selective imports or import all statements to import them. For example:

// This: ( Selective import )
import { Document } from "carbonldp/Document";
let myDocument:Document = Document.create();

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

The semantics behind Carbon LDP's modules are based on the usage of selective imports statements.
We recommend not using import all statements because IDEs will not be able to auto-complete while coding but also because you'll likely end up with long and repetitive statements, like in the example above.

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

import { Errors, RequestService } from "carbonldp/HTTP";

import * as HTTP from "carbonldp/HTTP";

HTTP.Errors === Errors; // true
HTTP.RequestService === RequestService; // 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.

We created boilerplate projects for each of the supported languages. They can serve as a good starting point:

Creating a CarbonLDP object

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 CarbonLDP object. This is the instance you will be working with, and it will help you communicate directly with your platform instance. When creating this object you need to specify the domain where your platform lives, and pass it as a parameter of the CarbonLDP object; other settings can be passed as parameters. You can view the API documentation for more information.

import { CarbonLDP } from "carbonldp/CarbonLDP";

// initialize your CarbonLDP object with the domain where your platform lives
let carbonldp = new CarbonLDP( "http://my-carbonldp-instance.com:8080" );
import { CarbonLDP } from "carbonldp/CarbonLDP";

// initialize your CarbonLDP object with the domain where your platform lives
let carbonldp:CarbonLDP = new CarbonLDP( "http://my-carbonldp-instance.com:8080" );
// initialize your CarbonLDP object with the domain where your platform lives
var carbonldp = new CarbonLDP( "http://my-carbonldp-instance.com:8080" );
Advanced CarbonLDP object creation

Instead of passing a simple string to construct the object, you can also pass a CarbonLDPSettings type of object to configure the CarbonLDP object. This is useful when you want to dynamically configure settings like host, port or SSL.

The properties you can set with the CarbonLDPSettings object are the following:

Let's see an example using the Settings object to create the CarbonLDP object, but this time also specifying the URL that will be used for your Carbon LDP vocabulary.

import { CarbonLDP } from "carbonldp/CarbonLDP";

// The settings object
let settings = {
    host: "my-carbon-instance.com", 	// or localhost
    port: 8083,
    ssl: false,
    vocabulary: "http://schema.org/"
};

// initialize your Carbon object with the domain where your platform lives
let carbonldp = new CarbonLDP( settings );
import { CarbonLDP } from "carbonldp/CarbonLDP";
import { CarbonLDPSettings } from "carbonldp/Settings";

// The settings object
let settings:CarbonLDPSettings = {
    host: "localhost",	// or localhost
    port: 8083,
    ssl: false,
    vocabulary: "http://schema.org/"
};

// initialize your Carbon object with the domain where your platform lives
let carbonldp = new CarbonLDP( settings );
// The settings object
var settings = {
    host: "my-carbon-instance.com", 	// or localhost
    port: 8083,
    ssl: false,
    vocabulary: "http://schema.org/"
};

// initialize your Carbon object with the domain where your platform lives
var carbonldp = new CarbonLDP( settings );

When specifying the vocabulary option, you must pass a string containing the URL to which a document's properties will resolve. This means that whenever you store an object containing the property title, to give an example, Carbon by default, will store it as http://localhost:8083/vocabularies/main/#title but in this case, it will be stored as http://schema.org/#title. Notice how the property has the URL we entered for vocabulary, this is due to the way Carbon stores its data.
To know more about how CarbonLDP stores a/an document's/object's properties, see the section URI's as Identifiers of the Object Model page, or visit the vocabularies section of the Object Schema page.

Creating a document

Now that you have your instance, you can start working with your data. Carbon LDP 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 CarbonLDP instance 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 carbonldp;

// ... initialize your CarbonLDP object

// JavaScript object to be persisted as a CarbonLDP 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" )
        }
    ]
};

carbonldp.documents.createChild( "/", project ).then(
    ( persistedProject ) => {
        console.log( project === persistedProject ); // true
        console.log( project.id ); // document's URI
    }
).catch( console.error );
// ... additional imports
import { PersistedDocument } from "carbonldp/PersistedDocument";

let carbonldp:CarbonLDP;

// ... initialize your CarbonLDP object

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

carbonldp.documents.createChild( "/", project ).then(
    ( persistedProject:Project & PersistedDocument ) => {
    console.log( project === persistedProject ); // true
    console.log( project.id ); // document's URI
}
).catch( console.error );
var carbonldp;

// ... initialize your CarbonLDP object

// JavaScript object to be persisted as a CarbonLDP 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" )
        }
    ]
};

carbonldp.documents.createChild( "/", project ).then(
    function( persistedProject ) {

        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 LDP. Nested objects will also be saved inside of the document, so pretty much any JSON object can be saved as a Carbon LDP 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 object. This type of object represents a saved document in Carbon LDP. 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 LDP 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 LDP when forging the document's URI:

// ...

carbonldp.documents.createChild( "/", project, "My sUp4 Project!" ).then(
    ( persistedProject ) => {
        console.log( persistedProject.id ); // .../my-sup4-project/
    }
).catch( console.error );
// ...

carbonldp.documents.createChild<Project>( "/", project, "My sUp4 Project!" ).then(
    ( persistedProject:Project & PersistedDocument ) => {
        console.log( persistedProject.id ); // .../my-sup4-project/
    }
).catch( console.error );
// ...

carbonldp.documents.createChild( "/", project, "My sUp4 Project!" ).then(
    function( persistedProject ) {
        console.log( persistedProject.id ); // .../my-sup4-project/
    }
).catch( console.error );

Another thing to notice is that the method is called createChild. All documents stored in Carbon LDP 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 our instance has, which is our domain. To see to what that would resolve you can use a resolve method:

// ... imports

let carbonldp;

// ... initialize your CarbonLDP object

console.log( carbonldp.resolve( "/" ) ); // https://your-domain.com/
// ... imports

let carbonldp:CarbonLDP;

// ... initialize your CarbonLDP object

console.log( carbonldp.resolve( "/" ) ); // https://your-domain.com/
var carbonldp;

// ... initialize your CarbonLDP object

console.log( carbonldp.resolve( "/" ) ); // https://your-domain.com/

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 XML representation of it.

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

// ... imports

let carbonldp;

// ... initialize your CarbonLDP object

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

carbonldp.documents.get( projectID ).then(
    ( project ) => {
        console.log( project.name ); // "Project X"
    }
).catch( console.error );
// ... additional imports
import { PersistedDocument } from "carbonldp/PersistedDocument";

let carbonldp:CarbonLDP;

// ... initialize your CarbonLDP object

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

carbonldp.documents.get<Project>( projectID ).then(
    ( project:Project & PersistedDocument ) => {
        console.log( project.name ); // "Project X"
    }
).catch( console.error );
var carbonldp;

// ... initialize your CarbonLDP object

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

carbonldp.documents.get( projectID ).then(
    function( project ) {
        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 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 extends Pointer). These pointers have a resolve method that helps you retrieve their information:

// ... imports

let projectPointer;

// ... document pointer acquisition

projectPointer.resolve().then(
    ( project ) => {
        console.log( project.name ); // "Project X"
    }
).catch( console.error );
// ... additional imports
import { PersistedDocument } from "carbonldp/PersistedDocument";
import { Pointer } from "carbonldp/Pointer";

let projectPointer:Pointer;

// ... document pointer acquisition

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

// ... document pointer acquisition

projectPointer.resolve().then(
    function( project ) {
        console.log( project.name ); // "Project X"
    }
).catch( console.error );

As you can see, Pointer objects have two states, resolved and unresolved. When a pointer isn't resolved it doesn't contain the document's information 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 CarbonLDP can be modified. However, all the changes made 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 properties, fragments, named 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;

// 1) ... document retrieval

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


// 3) Save the changes
project.save().then(
    ( savedProject ) => {
        console.log( savedProject === project ); // true
        return savedProject.refresh();
    }
).then(
    ( refreshedProject ) => {
        console.log( refreshedProject === project ); // true
        // Continue doing stuff...
    }
).catch( console.error );
// ... additional imports
import { PersistedDocument } from "carbonldp/PersistedDocument";

let project:Project & PersistedDocument;

// 1) ... document retrieval

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

// 3) Save the changes
project.save().then(
    ( savedProject ) => {
        console.log( savedProject === project ); // true
        return savedProject.refresh();
    }
).then(
    ( refreshedProject ) => {
        console.log( refreshedProject === project ); // true
        // Continue doing stuff...
    }
).catch( console.error );
var project;

// 1) ... document retrieval

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

// 3) Save the changes
project.save().then(
    function( savedProject ) => {
        console.log( savedProject === project ); // true
        return savedProject.refresh();
    }
).then(
    function( refreshedProject ) => {
        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 LDP 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 saveAndRefresh which 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 carbonldp;

// ... initialize your CarbonLDP object

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

carbonldp.documents.delete( projectID ).then(
    () => {
        // continue doing stuff...
    }
).catch( console.error );
// ... additional imports

let carbonldp:Carbon;

// ... initialize your CarbonLDP object

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

carbonldp.documents.delete( projectID ).then(
    () => {
        // continue doing stuff...
    }
).catch( console.error );
var carbonldp;

// ... initialize your CarbonLDP object

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

carbonldp.documents.delete( projectID ).then(
    function() {
        // continue doing stuff...
    }
).catch( console.error );

Like in "Retrieving a document", a document can also be deleted through the PersistedDocument's delete method:

// ... imports

let project;

// persisted document retrieval

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

let project:Project & PersistedDocument;

// persisted document retrieval

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

// persisted document retrieval

project.delete().then(
    function() {
        // continue doing stuff...
    }
).catch( console.error );

In fact, this pattern can be seen across several methods, where both the documents service and PersistedDocument expose similar methods. For example, createChild is also exposed by a PersistedDocument 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 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 { PersistedDocument } from "carbonldp/PersistedDocument";

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

// ... persisted documents retrieval

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

// ... persisted documents retrieval

project1.relatedProject = project2;

There are also special properties that Carbon LDP 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 LDP must have a parent. When a document is 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 shallow PersistedDocument objects for each of the children documents
  • documents.getChildren: Returns a list of PersistedDocument objects for each of the document children
// ... imports

let carbonldp

// ... initialize your CarbonLDP object

carbonldp.documents.listChildren( "projects/" ).then(
    ( shallowProjects ) => {
        console.log( shallowProjects ); // array of shallow documents
    }
);

carbonldp.documents.getChildren( "projects/" ).then(
    ( projects ) => {
        console.log( projects ); // array of documents
    }
);
// ... additional imports

let carbonldp:Carbon;

// ... initialize your CarbonLDP object

carbonldp.documents.listChildren<Project>( "projects/" ).then(
    ( shallowProjects:( Project & PersistedDocument )[] ) => {
        console.log( shallowProjects ); // array of shallow documents
    }
);

carbonldp.documents.getChildren<Project>( "projects/" ).then(
    ( projects:( Project & PersistedDocument )[] ) => {
        console.log( projects ); // array of documents
    }
);
var carbonldp;

// ... initialize your CarbonLDP object

carbonldp.documents.listChildren( "projects/" ).then(
    function( shallowProjects ) {
        console.log( shallowProjects ); // array of shallow documents
    }
);

carbonldp.documents.getChildren( "projects/" ).then(
    function( projects ) {
        console.log( projects ); // array of documents
    }
);

Like createChild and delete, listChildren and getChildren are also accessible through a PersistedDocument. 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 LDP 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 LDP 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 set in the document that will hold the members 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 shallow PersistedDocument objects for each member
  • getMembers: Like getChildren, returns an array of complete PersistedDocument objects for each member
  • removeMember/removeMembers/removeAllMembers: Removes the specified members (without deleting them)
// ... imports

let carbonldp;

// ... initialize your CarbonLDP object

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(
    () => {
        return project.resolve();
    }
).then(
    ( project ) => {
        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(
    ( shallowTasks ) => {
        console.log( shallowTasks ); // Pointers for task1, task2 and task3

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

        return project.getMembers();
    }
).then(
    ( resolvedTasks ) => {
        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 { PersistedDocument } from "carbonldp/PersistedDocument";
import { Response } from "carbonldp/HTTP/Response";

let carbonldp:CarbonLDP;

// ... initialize your CarbonLDP object

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(
    () => {
        return project.resolve<Project>();
    }
).then(
    ( project:Project & PersistedDocument ) => {
        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(
    ( shallowTasks:PersistedDocument[] ) => {
        console.log( shallowTasks ); // Pointers for task1, task2 and task3

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

        return project.getMembers<Task>();
    }
).then(
    ( resolvedTasks:( Task & PersistedDocument )[] ) => {
        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 carbonldp;

// ... initialize your CarbonLDP object

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() {
        return project.resolve();
    }
).then(
    function( project ) {

        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( shallowTasks ) {
        console.log( shallowTasks ); // Pointers for task1, task2 and task3

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

        return project.getMembers();
    }
).then(
    function( resolvedTasks ) {

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

More on listMembers(), getMembers(), listChildren() and getChildren() can be found here:

Reading documents

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 createAccessPoint method of the documents service or of a PersistedDocument 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( projectMembersAccessPoint, "people" ).then(
    ( persistedProjectMembersAccessPoint ) => {
        console.log( projectMembersAccessPoint === persistedProjectMembersAccessPoint ); // true

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

        return person1.refresh();
    }
).then(
    ( refreshedPerson1 ) => {
        console.log( person1 === refreshedPerson1 ); // true
        console.log( refreshedPerson1.projects ); // Pointer to project
    }
).catch( console.error );
// ... additional imports
import { AccessPointBase } from "carbonldp/AccessPoint";
import { PersistedAccessPoint } from "carbonldp/PersistedAccessPoint";
import { PersistedDocument } from "carbonldp/PersistedDocument";
import { Response } from "carbonldp/HTTP/Response";

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

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

let projectMembersAccessPoint:AccessPointBase = {
    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( projectMembersAccessPoint, "people" ).then(
    ( persistedProjectMembersAccessPoint:PersistedAccessPoint ) => {
        console.log( projectMembersAccessPoint === persistedProjectMembersAccessPoint ); // true

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

        return person1.refresh<Person>();
    }
).then(
    ( refreshedPerson1:Person & PersistedDocument ) => {
        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( projectMembersAccessPoint, "people" ).then(
    function( persistedProjectMembersAccessPoint ) {
        console.log( projectMembersAccessPoint === persistedProjectMembersAccessPoint ); // true

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

        return person1.refresh();
    }
).then(
    function( refreshedPerson1 ) {
        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 { CarbonLDP } from "carbonldp/CarbonLDP";

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

let carbonldp = new CarbonLDP( "http://my-carbonldp-instance.com:8083" );

carbonldp.documents.createChild( "/", project ).then(
    ( _persistedProject ) => {
        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 carbonldp.documents.get( persistedProject.id ); // Can also be `return persistedProject.refresh();`
    }
).then(
    ( persistedProject ) => {
        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 `carbonldp.documents.save( persistedProject );`
    }
).then(
    ( persistedProject ) => {
        console.log( project === persistedProject ); // true

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

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

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

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

        // Refreshing the project so we can check if the tasks were added
        return persistedProject.refresh();
    }
).then(
    ( persistedProject ) => {
        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(
    () => {

        console.log( "All done!" );
    }
).catch( console.error );
import { CarbonLDP } from "carbonldp/CarbonLDP";
import { PersistedAccessPoint } from "carbonldp/PersistedAccessPoint";
import { 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;
let tasksAccessPoint:PersistedAccessPoint;
let persistedTask1:Task & PersistedDocument;
let persistedTask2:Task & PersistedDocument;

let carbonldp:CarbonLDP = new CarbonLDP( "http://localhost:8083" );


carbonldp.documents.createChild<Project>( "/", project ).then(
    ( _persistedProject:Project & PersistedDocument ) => {
        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 carbonldp.documents.get<Project>( persistedProject.id ); // Can also be `return persistedProject.refresh();`
    }
).then(
    ( persistedProject:Project & PersistedDocument ) => {
        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 `carbonldp.documents.save<Project>( persistedProject );`
    }
).then(
    ( persistedProject:Project & PersistedDocument ) => {
        console.log( project === persistedProject ); // true

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

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

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

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

        // Refreshing the project so we can check if the tasks were added
        return persistedProject.refresh<Project>();
    }
).then(
    ( persistedProject:Project & PersistedDocument ) => {
        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<void>[] = [];
        promises.push( persistedProject.delete() );
        promises.push( persistedTask1.delete() );
        promises.push( persistedTask2.delete() );

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

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

var carbonldp = new CarbonLDP( "http://localhost:8083" );

carbonldp.documents.createChild( "/", project ).then(
    function ( _persistedProject ) {
        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 carbonldp.documents.get( persistedProject.id ); // Can also be `return persistedProject.refresh();`
    }
).then(
    function ( persistedProject ) {
        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 `carbonldp.documents.save( persistedProject );`
    }
).then(
    function ( persistedProject ) {
        console.log( project === persistedProject ); // true

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

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

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

        // Adding them to the project through the AccessPoint
        return tasksAccessPoint.addMembers( [ persistedTask1, persistedTask2 ] );
    }
).then(
    function () {
        // Refreshing the project so we can check if the tasks were added
        return persistedProject.refresh();
    }
).then(
    function ( persistedProject ) {
        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 () {

        console.log( "All done!" );
    }
).catch( console.error );

This was a short introduction to what the SDK and Carbon LDP 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.

Getting started
Next
Object model