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.

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

This API is available since Fedify 0.2.0.

Suppose your app has a search box that allows the user to look up a fediverse user by the handle or a post by the URI. In such cases, you need to look up the object from a remote server that your app haven't interacted with yet. The lookupObject() function plays a role in such cases. The following shows an example of looking up an actor object from the handle:

typescript
import { lookupObject } from "@fedify/fedify";

const actor = await lookupObject("@hongminhee@todon.eu");

In the above example, the lookupObject() function queries the remote server's WebFinger endpoint to get the actor's URI from the handle, and then fetches the actor object from the URI.

TIP

The lookupObject() function accepts a fediverse handle without prefix @ as well:

typescript
const actor = await lookupObject("hongminhee@todon.eu");

Also an acct: URI:

typescript
const actor = await lookupObject("acct:hongminhee@todon.eu");

The lookupObject() function is not limited to the actor object. It can look up any object in the Activity Vocabulary. For example, the following shows an example of looking up a Note object from the URI:

typescript
const note = await lookupObject("https://todon.eu/@hongminhee/112060633798771581");

NOTE

Some objects require authentication to look up, such as a Note object with a visibility of followers-only. In such cases, you need to use the Context.getDocumentLoader() method to get an authenticated DocumentLoader object. The lookupObject() function takes the documentLoader option to specify the method to fetch the remote object:

typescript
const documentLoader = await ctx.getDocumentLoader({ handle: "john" });
const note = await lookupObject("...", { documentLoader });

See the Getting an authenticated DocumentLoader section for details.

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();

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
Language tag (BCP 47)LanguageTag
Public key PEMCryptoKey
Units"cm" | "feet" | "inches" | "km" | "m" | "miles" | URL