Skip to content

Vocabulary

One of the key features of Fedify library is that it provides a collection of type-safe objects that represent the Activity Vocabulary and the vendor-specific extensions.

There are tons of objects in the Activity Vocabulary, and it's not practical to list all of them here. Instead, we'll show a few examples of the objects that are available in the library: Create, Note, and Person. For the full list of the objects, please refer to the API reference.

Instantiation

You can instantiate an object by calling the constructor function with an object that contains the properties of the object. The following shows an example of instantiating a Create object:

typescript
import { 
Create
,
Note
} from "@fedify/fedify";
const
create
= new
Create
({
id
: new
URL
("https://example.com/activities/123"),
actor
: new
URL
("https://example.com/users/alice"),
object
: new
Note
({
id
: new
URL
("https://example.com/notes/456"),
content
: "Hello, world!",
published
: Temporal.
Instant
.
from
("2024-01-01T00:00:00Z"),
}), });

Note that every URI is represented as a URL object. This is for distinguishing the URIs from the other strings.

TIP

You can instantiate an object from a JSON-LD document by calling the fromJsonLd() method of the object. See the JSON-LD section for details.

Properties

Every object in the Activity Vocabulary has a set of properties. The properties are categorized into the following types:

  • Functional or non-functional
  • Scalar or non-scalar

Functional properties are the properties that contain zero or a single value, while non-functional properties are the properties that contain zero or multiple values.

Scalar properties can contain only scalar values (e.g., string, number, boolean, URI), while non-scalar properties can contain both scalar and non-scalar values. Objects like Create, Note, and Person are non-scalar values. Non-scalar properties can contain either objects or URIs (object ID) of the objects.

Depending on the category of the property, the accessors of the property are different. The following table shows examples of the accessors:

FunctionalNon-functional
ScalarObject.publishedObject.name/names
Non-scalarPerson.inboxId/getInbox()Activity.actorId/actorIds/getActor()/getActors()

Some non-functional properties have both singular and plural accessors for the sake of convenience. In such cases, the singular accessors return the first value of the property, while the plural accessors return all values of the property.

Object IDs and remote objects

Every object in the Activity Vocabulary has an id property, which is the URI of the object. It is used to identify and dereference the object.

For example, the following two objects are equivalent (where dereferencing URI https://example.com/notes/456 returns the Note object):

typescript
const 
a
= new
Create
({
id
: new
URL
("https://example.com/activities/123"),
actor
: new
URL
("https://example.com/users/alice"),
object
: new
Note
({
id
: new
URL
("https://example.com/notes/456"),
content
: "Hello, world!",
published
: Temporal.
Instant
.
from
("2024-01-01T00:00:00Z"),
}), }); const
b
= new
Create
({
actor
: new
URL
("https://example.com/users/alice"),
object
: new
URL
("https://example.com/notes/456"),
});

How are the two objects equivalent? Because for the both objects, getObject() returns the equivalent Note object. Such get*() methods for non-scalar properties are called dereferencing accessors. Under the hood, the get*() methods fetch the remote object from the URI and return the object if no cache hit. In the above example, the await a.getObject() immediately returns the Note object because it's already instantiated, while the await b.getObject() fetches the remote object from the URI and returns the Note object.

If you only need the object ID without fetching the remote object, you can use the *Id/*Ids accessors instead of dereferencing accessors. In the same manner, both a.objectId and b.objectId return the equivalent URI.

TIP

Dereferencing accessors take option documentLoader to specify the method to fetch the remote object. By default, it uses the default document loader which utilizes th fetch() API.

If you want to implement your own document loader, see the DocumentLoader interface in the API reference.

See the Getting a DocumentLoader section for details.

Property hydration

The get*() accessor methods for non-scalar properties automatically populate the property with the remote object when the methods are called even if the property internally contains only the URI of the object. This is called property hydration.

For example, the following code hydrates the object property of the Create object:

typescript
const 
create
= new
Create
({
object
: new
URL
(
"https://hollo.social/@fedify/0191e4f3-6b08-7003-9d33-f07d1e33d7b4", ), }); // Hydrates the `object` property: const
note
= await
create
.
getObject
();
// Returns the hydrated `object` property; therefore, the following code does // not make any HTTP request: const
note2
= await
create
.
getObject
();

Hydrating the property also affects the JSON-LD representation of the object.

For example, since the following code does not hydrate the object property, the JSON-LD representation of the Create object has the object property with the URI of the object:

typescript
const 
create
= new
Create
({
object
: new
URL
(
"https://hollo.social/@fedify/0191e4f3-6b08-7003-9d33-f07d1e33d7b4", ), }); const
jsonLd
= await
create
.
toJsonLd
();
console
.
log
(
JSON
.
stringify
(
jsonLd
));

The above code outputs the following JSON-LD document ("@context" is simplified for readability):

json
{
  "@context": ["https://www.w3.org/ns/activitystreams"],
  "type": "Create",
  "object": "https://hollo.social/@fedify/0191e4f3-6b08-7003-9d33-f07d1e33d7b4"
}

However, if the property is once hydrated, the JSON-LD representation of the object has the object property with the full object:

typescript
// Hydrates the `object` property:
await 
create
.
getObject
();
const
jsonLd
= await
create
.
toJsonLd
();
console
.
log
(
JSON
.
stringify
(
jsonLd
));

The above code outputs the following JSON-LD document ("@context" and some attributes are simplified or omitted for readability):

json
{
  "type": "Create",
  "@context": ["https://www.w3.org/ns/activitystreams"],
  "object": {
    "id": "https://hollo.social/@fedify/0191e4f3-6b08-7003-9d33-f07d1e33d7b4",
    "type": "Note",
    "attributedTo": "https://hollo.social/@fedify",
    "content": "<p>...</p>\n",
    "published": "2024-09-12T06:37:23.593Z",
    "sensitive": false,
    "tag": [],
    "to": "as:Public",
    "url": "https://hollo.social/@fedify/0191e4f3-6b08-7003-9d33-f07d1e33d7b4"
  }
}

Immutability

Every object in the Activity Vocabulary is represented as an immutable object. This means that you cannot change the properties of the object after the object is instantiated. This is for ensuring the consistency of the objects and the safety of the objects in the concurrent environment.

In order to change the properties of the object, you need to clone the object with the new properties. Fortunately, the objects have a clone() method that takes an object with the new properties and returns a new object with the new properties. The following shows an example of changing the content property of a Note object:

typescript
import { 
LanguageString
,
Note
} from "@fedify/fedify";
const
noteInEnglish
= new
Note
({
id
: new
URL
("https://example.com/notes/123"),
content
: new
LanguageString
("Hello, world!", "en"),
published
: Temporal.
Now
.
instant
(),
}); const
noteInChinese
=
noteInEnglish
.
clone
({
content
: new
LanguageString
("你好,世界!", "zh"),
});

Parameters of the clone() method share the same type with parameters of the constructor.

Looking up remote objects

See the Looking up remote objects section in the Context docs.

Traversing remote collections

See the Traversing remote collections section in the Context docs.

JSON-LD

Under the hood, every object in the Activity Vocabulary is represented as a JSON-LD document. The JSON-LD document is a JSON object that contains the properties of the object. The following shows an example of the JSON-LD representation of the Create object:

json
{
  "@context": "https://www.w3.org/ns/activitystreams",
  "type": "Create",
  "id": "https://example.com/activities/123",
  "actor": "https://example.com/users/alice",
  "object": {
    "type": "Note",
    "id": "https://example.com/notes/456",
    "content": "Hello, world!",
    "published": "2024-01-01T00:00:00Z"
  }
}

If you want to instantiate an object from a JSON-LD document, you can use the fromJsonLd() method of the object. The following shows an example of instantiating a Create object from the JSON-LD document:

typescript
import { 
Create
} from "@fedify/fedify";
const
create
= await
Create
.
fromJsonLd
({
"@context": "https://www.w3.org/ns/activitystreams", "type": "Create", "id": "https://example.com/activities/123", "actor": "https://example.com/users/alice", "object": { "type": "Note", "id": "https://example.com/notes/456", "content": "Hello, world!", "published": "2024-01-01T00:00:00Z" } });

Note that the fromJsonLd() method can parse a subtype as well. For example, since Create is a subtype of Activity, the Activity.fromJsonLd() method can parse a Create object as well:

typescript
import { 
Activity
} from "@fedify/fedify";
const
create
= await
Activity
.
fromJsonLd
({
"@context": "https://www.w3.org/ns/activitystreams", "type": "Create", "id": "https://example.com/activities/123", "actor": "https://example.com/users/alice", "object": { "type": "Note", "id": "https://example.com/notes/456", "content": "Hello, world!", "published": "2024-01-01T00:00:00Z" } });

On the other way around, you can use the toJsonLd() method to get the JSON-LD representation of the object:

typescript
const 
jsonLd
= await
create
.
toJsonLd
();

By default, the toJsonLd() method returns the JSON-LD document which is neither compacted nor expanded. Instead, it processes the JSON-LD document without the proper JSON-LD processor for efficiency.

The toJsonLd() method takes some options to customize the JSON-LD document. For example, you can compact the JSON-LD document with a custom context. In this case, the toJsonLd() method returns the compacted JSON-LD document which is processed by the proper JSON-LD processor:

typescript
const 
jsonLd
= await
create
.
toJsonLd
({
format
: "compact",
context
: "https://example.com/context",
});

TIP

Why are the fromJsonLd() and toJsonLd() methods asynchronous? Because both methods may fetch remote documents under the hood in order to compact/expand a JSON-LD document. In fact, like the dereferencing accessors, both fromJsonLd() and toJsonLd() methods take option documentLoader to specify the method to fetch the remote document.

See the Getting a DocumentLoader section for details.

Scalar types

The Activity Vocabulary has a few scalar types that are used as the values of the properties. The following table shows the scalar types and their corresponding TypeScript types:

Scalar typeTypeScript type
xsd:booleanboolean
xsd:integernumber
xsd:nonNegativeIntegernumber
xsd:floatnumber
xsd:stringstring
xsd:anyURIURL
xsd:dateTimeTemporal.Instant
xsd:durationTemporal.Duration
rdf:langStringLanguageString
w3id:cryptosuiteString"eddsa-jcs-2022"
w3id:multibaseUint8Array
Language tag (BCP 47)LanguageTag
Public key PEMCryptoKey
Public key MultibaseCryptoKey
Proof purpose"assertionMethod" | "authentication" | "capabilityInvocation" | "capabilityDelegation" | "keyAgreement"
Units"cm" | "feet" | "inches" | "km" | "m" | "miles" | URL