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:
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:
Functional | Non-functional | |
---|---|---|
Scalar | Object.published | Object.name /names |
Non-scalar | Person.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):
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:
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:
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:
const actor = await lookupObject("hongminhee@todon.eu");
Also an acct:
URI:
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:
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:
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:
{
"@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:
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:
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:
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 type | TypeScript type |
---|---|
xsd:boolean | boolean |
xsd:integer | number |
xsd:nonNegativeInteger | number |
xsd:float | number |
xsd:string | string |
xsd:anyURI | URL |
xsd:dateTime | Temporal.Instant |
xsd:duration | Temporal.Duration |
rdf:langString | LanguageString |
Language tag (BCP 47) | LanguageTag |
Public key PEM | CryptoKey |
Units | "cm" | "feet" | "inches" | "km" | "m" | "miles" | URL |