Class GraffitiAbstract

This API describes a small but powerful set of methods that can be used to create many different kinds of social media applications, all of which can interoperate. These methods should satisfy all of an application's needs for the communication, storage, and access management of social data. The rest of the application can be built with standard client-side user interface tools to present and interact with the data — no server code necessary. The Typescript source for this API is available at [REDACTED while paper under review].

There are several different implementations of this Graffiti API available, including a federated implementation that lets users choose where their data is stored, and a local implementation that can be used for testing and development. In our design of Graffiti, this API is our primary focus as it is the layer that shapes the experience of developing applications. While different implementations can provide tradeoffs between other important properties (e.g. privacy, security, scalability), those properties are useless if the system as a whole doesn't expose useful functionality to developers.

On the other side of the stack, there is Vue plugin that wraps around this API to provide reactivity. Other high-level libraries will be available in the future.

Graffiti provides applications with methods to create and store data on behalf of their users using standard CRUD operations: put, get, patch, and delete. This data can represent both social artifacts (e.g. posts, profiles) and activities (e.g. likes, follows) and is stored as JSON.

The social aspect of Graffiti comes from the discover method which allows applications to find objects that other users made. It is a lot like a traditional query operation, but it only returns objects that have been placed in particular channels specified by the discovering application.

Graffiti builds on well known concepts and standards wherever possible. JSON Objects can be typed with JSON Schema and patches can be applied with JSON Patch. For interoperability between Graffiti applications, we recommend that objects use established properties from the Activity Vocabulary when available, however it is always possible to create additional properties, contributing to the broader folksonomy.

channels are one of the major concepts unique to Graffiti along with interaction relativity, defined below. Channels create boundaries between public spaces and work to prevent context collapse even in a highly interoperable environment. Interaction relativity means that all interactions between users are actually atomic single-user operations that can be interpreted in different ways, which also supports interoperability and pluralism.

channels are a way for the creators of social data to express the intended audience of their data. When a user creates data using the put method, they can place their data in one or more channels. Content consumers using the discover method will only see data contained in one of the channels they specify.

While many channels may be public, they partition the public into different "contexts", mitigating the phenomenon of context collapse or the "flattening of multiple audiences." Any URI can be used as a channel, and so channels can represent people, comment threads, topics, places (real or virtual), pieces of media, and more.

For example, consider a comment on a post. If we place that comment in the channel represented by the post's URL, then only people viewing the post will know to look in that channel, giving it visibility akin to a comment on a blog post or comment on Instagram (since 2019). If we also place the comment in the channel represented by the commenter's URI (their actor URI), then people viewing the commenter's profile will also see the comment, giving it more visibility, like a reply on Twitter. If we only place the comment in the channel represented by the commenter's URI, then it becomes like a quote tweet (prior to 2020), where the comment is only visible to the commenter's followers but not the audience of the original post.

The channel model differs from other models of communication such as the actor model used by ActivityPub, the protocol underlying Mastodon, or the firehose model used by the AT Protocol, the protocol underlying BlueSky. The actor model is a fusion of direct messaging (like Email) and broadcasting (like RSS) and works well for follow-based communication but struggles to pass information via other rendez-vous. In the actor model, even something as simple as comments can be very tricky and require server "side effects". The firehose model dumps all user data into one public database, which doesn't allow for the carving out of different contexts that we did in our comment example above. In the firehose model a comment will always be visible to both the original post's audience and the commenter's followers.

In some sense, channels provide a sort of "social access control" by forming expectations about the audiences of different online spaces. As a real world analogy, oftentimes support groups, such as alcoholics anonymous, are open to the public but people in those spaces feel comfortable sharing intimate details because they have expectations about the other people attending. If someone malicious went to support groups just to spread people's secrets, they would be shamed for violating these norms. Similarly, in Graffiti, while you could spider public channels like a search engine to find content about a person, revealing that you've done such a thing would be shameful.

Still, social access control is not perfect and so in situations where privacy is important, objects can also be given an allowed list. For example, to send someone a direct message you should put an object representing that message in the channel that represents them (their actor URI), so they can find it, and set the allowed field to only include the recipient, so only they can read it.

Interaction relativity posits that "interaction between two individuals only exists relative to an observer," or equivalently, all interaction is reified. For example, if one user creates a post and another user wants to "like" that post, their like is not modifying the original post, it is simply another data object that points to the post being liked, via its URL.

{
activity: 'like',
target: 'url-of-the-post-i-like',
actor: 'my-user-id'
}

In Graffiti, all interactions including moderation and collaboration are relative. This means that applications can freely choose which interactions they want to express to their users and how. For example, one application could have a single fixed moderator, another could allow users to choose which moderators they would like filter their content like Bluesky's stackable moderation, and another could implement a fully democratic system like PolicyKit. Each of these applications is one interpretation of the underlying refieid user interactions and users can freely switch between them.

Interaction relativy also allows applications to introduce new sorts of interactions without having to coordinate with all the other existing applications, keeping the ecosystem flexible and interoperable. For example, an application could add a "Trust" button to posts and use it assess the truthfulness of posts made on applications across Graffiti. New sorts of interactions like these can be smoothly absorbed by the broader ecosystem as a folksonomy.

Interactivy relativity is realized in Graffiti through two design decisions:

  1. The creators of objects can only modify their own objects. It is important for users to be able to change and delete their own content to respect their right to be forgotten, but beyond self-correction and self-censorship all other interaction is reified. Many interactions can be reified via pointers, as in the "like" example above, and collaborative edits can be refieid via CRDTs.
  2. No one owns channels. Unlike IRC/Slack channels or Matrix rooms, anyone can post to any channel, so long as they know the URI of that channel. It is up to applications to hide content from channels either according to manual filters or in response to user action. For example, a user may create a post with the flag disableReplies. Applications could then filter out any content from the replies channel that the original poster has not specifically approved.

Constructors

CRUD Methods

Query Methods

Methods that retrieve or accumulate information about multiple Graffiti objects at a time.

Session Management

Methods and properties for logging in and out of a Graffiti implementation.

Constructors

CRUD Methods

Query Methods

  • Discovers objects created by any user that are contained in at least one of the given channels and match the given JSON Schema.

    Objects are returned asynchronously as they are discovered but the stream will end once all leads have been exhausted. The GraffitiObjectStream ends by returning a continue method and a cursor string, each of which can be be used to poll for new objects. The continue method preserves the type safety of the stream and the cursor string can be serialized to continue the stream after an application is closed and reopened.

    discover will not return objects that the actor is not allowed to access. If the actor is not the creator of a discovered object, the allowed list will be masked to only contain the querying actor if the allowed list is not undefined (public). Additionally, if the actor is not the creator of a discovered object, any channels not specified by the discover method will not be revealed. This masking happens before the object is validated against the supplied schema.

    Since different implementations may fetch data from multiple sources there is no guarentee on the order that objects are returned in. It is also possible that duplicate objects are returned and their lastModified fields must be used to determine which object is the most recent.

    Type Parameters

    Parameters

    • channels: string[]

      The channels that objects must be associated with.

    • schema: Schema

      A JSON Schema that objects must satisfy.

    • Optionalsession: null | GraffitiSession

      An implementation-specific object with information to authenticate the actor. If no session is provided, only objects that have no allowed property will be returned.

    Returns GraffitiObjectStream<Schema>

    A stream of objects that match the given channels and JSON Schema.

  • Returns statistics about all the channels that an actor has posted to. This is not very useful for most applications, but necessary for certain applications where a user wants a global view of all their Graffiti data or to debug channel usage.

    Like discover, objects are returned asynchronously as they are discovered, the stream will end once all leads have been exhausted.

    Parameters

    • session: GraffitiSession

      An implementation-specific object with information to authenticate the actor.

    Returns GraffitiChannelStatsStream

    A stream of statistics for each channel that the actor has posted to.

  • Continues a GraffitiObjectStream from a given cursor string. The continuation will return new objects that have been created that match the original stream, and also returns the urls of objects that have been deleted, as marked by a tombstone.

    The continuation may also include duplicates of objects that were already returned by the original stream. This is dependent on how much state the underlying implementation maintains.

    The cursor allows the client to serialize the state of the stream and continue it later. However this method loses any typing information that was present in the original stream. For better type safety and when serializing is not necessary, use the continue method instead, which is returned along with the cursor at the end of the original stream.

    Parameters

    Returns GraffitiObjectStreamContinue<{}>

    GraffitiErrorForbidden if the actor provided in the session is not the same as the actor that initiated the original stream.

Session Management

  • Begins the login process. Depending on the implementation, this may involve redirecting the user to a login page or opening a popup, so it should always be called in response to a user action.

    The session object is returned asynchronously via sessionEvents as a GraffitiLoginEvent with event type login.

    Parameters

    • Optionalproposal: { actor?: string; scope?: {} }

      Suggestions for the permissions that the login process should grant. The login process may not provide the exact proposed permissions.

      • Optionalactor?: string

        A suggested actor to login as. For example, if a user tries to edit a post but are not logged in, the interface can infer that they might want to log in as the actor who created the post they are attempting to edit.

        Even if provided, the implementation should allow the user to log in as a different actor if they choose.

      • Optionalscope?: {}

        A yet to be defined permissions scope. An application may use this to indicate the minimum necessary scope needed to operate. For example, it may need to be able read private messages from a certain set of channels, or write messages that follow a particular schema.

        The login process should make it clear what scope an application is requesting and allow the user to enhance or reduce that scope as necessary.

    Returns Promise<void>

  • Begins the logout process. Depending on the implementation, this may involve redirecting the user to a logout page or opening a popup, so it should always be called in response to a user action.

    A confirmation will be returned asynchronously via sessionEvents as a GraffitiLogoutEvent as event type logout.

    Parameters

    Returns Promise<void>

sessionEvents: EventTarget

An event target that can be used to listen for the following events and they're corresponding event types: