Getting started with the REST API
Prerequisite knowledge
To better understand this guide and its code examples, a working knowledge of the following prerequisite topics is required.
- HTML, CSS, JavaScript: See: Mozilla Developer Network.
- Promises: A standard-based way of handling asynchronous JavaScript logic. See: Promise object(Mozilla Developer Network).
Additionally, knowledge on 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)
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
- 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
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
carbon.setSetting( "domain", "my-carbon-instance.com:8080" );
import Carbon from "carbonldp/Carbon";
let carbon:Carbon = new Carbon(); // platform context
var carbon = new Carbon(); // platform context
carbon.setSetting( "domain", "my-carbon-instance.com:8080" );
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!
}
);
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.
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:
// ... imports
let carbon;
// ... platform context creation and authentication
let myAppContext;
carbon.apps.getContext( "your-app-slug-here/" ).then(
( appContext ) => {
myAppContext = appContext; // retrieving your app context
}
).catch( console.error );
// ... 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 );
var carbon;
// ... platform context creation and authentication
var myAppContext;
carbon.apps.getContext( "your-app-slug-here/" ).then(
function( appContext ) {
myAppContext = appContext; // retrieving your app context
}
).catch( console.error );
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 XML
representation 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 );
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:
- Retrieve the document:
get
using the document id as described in retrieving a document
- Modify the document: Add, modify or delete properties, fragments, named fragments.
- 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 );
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 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 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 );
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 unresolvedPersistedDocument.Class
objects for each of the document childrendocuments.getChildren
: Returns a list of RESOLVEDPersistedDocument.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
}
);
createChild
and delete
, listChildren
and getChildren
are also accessible through a PersistedDocument.Class
. See, there’s a pattern!
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 defaultmembers
)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 documentlistMembers
: LikelistChildren
, returns an array of unresolvedPersistedDocument.Class
objects for each membergetMembers
: LikegetChildren
, returns an array of RESOLVEDPersistedDocument.Class
objects for each memberremoveMember/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 createAccessPoint
method 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 Response 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.