Writing and Reading Facts and Data

Factern allows writing of a rich hierarchy of facts and data. Information facts or "nodes" are written under an entity node. Data associated with the information is then written to some data storage.

Branch vs non-Branch Fields

Every piece of information written to factern must be associated with a field type. If the field type is a branch then the information can have no data, but can be used to parent other information. Otherwise, if the field type is not a branch, the information must have data.

Data Storage

Information can be written to any storage to which you have access. If not specified, information data is written to Factern's Default Storage.

Writing some Facts

The write API is the typical way information is written to Factern. The write always specifies an entity node for rooting the facts, together with a specification of associations between field types and data.

In this example, we write a person's profile under the entity 00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A and specify the information using a JSON document that combines fields with data.

POST /write
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "document": {
        "FullName": "John Watson",
        "FullAddress": "221b Baker Street, London, UK"
    }
}

This creates two information nodes under the entity, one having field type FullName and data "John Watson", the other having field type FullAddress and data "221b Baker Street, London, UK".

If the field type is a branch the JSON document gets a bit more structured. In this example, the Address field type is branch and parents three other fields, Street, City and Country.

POST /write
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "document": {
        "FullName": "John Watson",
        "Address": {
            "Street": "221b Baker Street",
            "City": "London",
            "Country": "UK"
        }
    }
}

Reading Facts

The read API is for reading structured information back from Factern.

The read specifies the entity node that roots the facts, together with a template of the field types of the data expected to be returned.

In this example, we read a person's profile under the entity 00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A and specify that we want the values for the field types FullName and FullAddress.

POST /read
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "template": [
        "FullName",
        "FullAddress"
    ]
}
Response
[
{
    "readItem": {
        "data": "John  Watson",
        "nodeId": "00000000DD15095A49A56DFF7F9F3C3D18092B7513505394",
        "fieldId": "00000000119385347C3E5723B094E4271AF01FD5540AC54A"
    },
    "status": 200
},
{
    "readItem": {
        "data": "221b Baker Street, London, UK",
        "nodeId": "0000000054C843F50DC0736A7B42E116CF3B0834EBFAD1F6",
        "fieldId": "00000000B23B9F544B8B8A3A72B0629E03FEDB6746CCAF38"
    },
    "status": 200
}
]

Note the use of the property template rather than document for specifying the fields. A document combines fields with data, whereas a template only specifies the field structure of the facts.

Like the document in the write, the template can express a rich hierarchy of fields.

POST /read
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "template": [
        "FullName",
        {"Address": [
            "Street",
            "City",
            "Country"
        ]}
    ]
}

Note that the template can specify fields that do not exist under the entity. Only the fields that actually exist are returned. Also, only desired fields need to be specified. You do not have to specify all fields under the entity

Writing using an Inline Template

Like the read API the write can accept an inline template. In this case, the values are specified in a separate values properties consisting of an array of the values that corresponds to the same order corresponding as the fields appear in the template.

POST /write
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "template": [
        "FullName",
        "FullAddress"
    ],
    "values": [
        "John Watson",
        "221b Baker Street, London, UK"
    ]
}

Creating Templates

Templates can be created and treated as stand-alone facts. It includes a list or hierarchy of fields, and the id of the template can be used in API calls that otherwise take inline templates.

Templates are named facts, and hence the template can be identified using an FRN.

POST /createtemplate
{
    "parentId": "00000000C320A7BFD903745851915F6C33D20634E36C37C6",
    "name": "PersonProfile",
    "description": "A person's relevant data",
    "memberIds": [
        "FullName",
        "FullAddress"
    ]
}

This template can be referenced either using its node id, or using one of the following FRNs.

Description FRI
Full FRI frn:template:0000000009C2602130DEC995888A2CE624A7FCA1513E1B48:PersonProfile
Login understood frn:template::PersonProfile
Short FRI :PersonProfile

Writing using a Template

Rather than using a document or specifying an inline template, the templateId can be used in the write API.

POST /write
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "templateId": "00000000DF08A004639A8EF503F75FFC31A842D2F8CD225B",
    "values": [
        "John Watson",
        "221b Baker Street, London, UK"
    ]
}

or using the template FRN:

POST /write
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "templateId": "PersonProfile",
    "values": [
        "John Watson",
        "221b Baker Street, London, UK"
    ]
}

Reading using a Template

The read API can also use a templateId rather than an inline template.

POST /read
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "templateId": "00000000DF08A004639A8EF503F75FFC31A842D2F8CD225B"
}

or using the template FRN:

POST /read
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "templateId": "PersonProfile",
}

Updating Facts

Facts in Factern are immutable, so when you update a fact you are really just adding a new version of the fact.

That being said, what does it mean to use the write API to write a fact of a particular field type, and then later write a new value of the same field type? The answer is that it depends on the field type.

Consider writing a FullName field using value "John Watson".

POST /write
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "document": {
        "FullName": "John Watson"
    }
}

Later we write to the same empty again using the FullName field, using value "Dr. Watson".

POST /write
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "document": {
        "FullName": "Dr. Watson"
    }
}

So what is the result of the following read?

POST /read
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "template": [
        "FullName"
    ]
}

In fact, now we have two FullName fields for the entity:

Response
[{
    "readItem": {
        "nodes": [{
            "data": "John Watson",
            "nodeId": "000000006285CB8DD2C44D5C21B51525F65659BA083514DF",
            "fieldId": "00000000119385347C3E5723B094E4271AF01FD5540AC54A"
        },{
            "data": "Dr. Watson",
            "nodeId": "00000000CD864CF2515A5873BEE92E658AF0B7BAAF8E430B",
            "fieldId": "00000000119385347C3E5723B094E4271AF01FD5540AC54A"
        }],
        "fieldId": "00000000119385347C3E5723B094E4271AF01FD5540AC54A"
    },
    "status": 200
}]

But, really, is that we wanted? Should an entity have two full names?

Factern field types include a property uniqueByParent to indicate whether a node should parent more than one information node of that field type.

For example, if we had created the FullName field as

POST /createfield
{
    "parentId": "00000000C320A7BFD903745851915F6C33D20634E36C37C6",
    "name": "FullName",
    "uniqueByParent": true
}

Then the response to the read

POST /read
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "template": [
        "FullName"
    ]
}

would be

Response
[{
    "readItem": {
        "data": "Dr. Watson",
        "nodeId": "00000000DD15095A49A56DFF7F9F3C3D18092B7513505394",
        "fieldId": "00000000119385347C3E5723B094E4271AF01FD5540AC54A"
    },
    "status": 200
}]

That is, only the most recently set value of the field is returned.

Deleting Facts

Facts in Factern are immutable, so when you delete a fact you are really just adding a new version "deleted" version to the information node.

To delete a fact, you need the facts node id. Note the response to a read API calls

Response
[{
    "readItem": {
        "data": "Dr. Watson",
        "nodeId": "00000000DD15095A49A56DFF7F9F3C3D18092B7513505394",
        "fieldId": "00000000119385347C3E5723B094E4271AF01FD5540AC54A"
    },
    "status": 200
}]

The nodeId property indicates the id of the information node. To delete that node the deletenode API call is used.

Example:

POST /deletenode
{
    "nodeId": "00000000DD15095A49A56DFF7F9F3C3D18092B7513505394"
}

Obliterating Facts

Although facts in Factern are immutable, data is not. To delete the data associated with an information node, the obliterate API call is used.

Example:

POST /obliterate
{
    "nodeId": "00000000DD15095A49A56DFF7F9F3C3D18092B7513505394"
}

Version History

We have shown how new versions of a node are created, and, because of Factern's immutability, that node versions are never deleted.

If you know the node id of an information node, you can list all versions of the node using history API call.

For example

POST /history
{
    "nodeId": "00000000DD15095A49A56DFF7F9F3C3D18092B7513505394"
}

Specifying External Storage

Unless otherwise specified, data is written to Factern's Default Store. To write elsewhere, the id of the storage must be specified.

To specify that data is to be written to a store, you must either have the id of the store, or have its FRN.

Note that since the storage id is stored with an information node, the read API does not have to include any information about storage.

Specifying the Default Storage Id on a Write

To specify the default store on a particular write, specify the defaultStorageId property.

For example, here we write the data to the "MyExampleStore" Storage.

POST /write
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "defaultStorageId": "MyExampleStore"
    "document": {
        "FullName": "John Watson",
        "Address": {
            "Street": "221b Baker Street",
            "City": "London",
            "Country": "UK"
        }
    }
}

Specifying the Storage Id on a Field

To specify the storage of individual information nodes, the storageId property must be specified.

In this example, only the "Street" data is written to the "MyExampleStore" Storage.

POST /write
{
    "nodeId": "00000000404AE4402DF969ACE2C2DA9133A23EA48773AF8A",
    "document": {
        "FullName": "John Watson",
        "Address": [
            "Street": {
                "#data": "221b Baker Street"
                "#storageId": "MyExampleStore"
            },
            "City": "London",
            "Country": "UK"
        ]
    }
}