Wraps the Graffiti API so that changes made or received in one part of an application are automatically routed to other parts of the application. This is an important tool for building responsive and consistent user interfaces, and is built upon to make the Graffiti Vue Plugin and possibly other front-end libraries in the future.

Specifically, it provides the following synchronize methods for each of the following API methods:

API Method Synchronize Method
get synchronizeGet
discover synchronizeDiscover
recoverOrphans synchronizeRecoverOrphans

Whenever a change is made via put, patch, and delete or received from get, discover, and recoverOrphans, those changes are forwarded to the appropriate synchronize method. Each synchronize method returns an iterator that streams these changes continually until the user calls return on the iterator or breaks out of the loop, allowing for live updates without additional polling.

Example 1: Suppose a user publishes a post using put. If the feed displaying that user's posts is using synchronizeDiscover to listen for changes, then the user's new post will instantly appear in their feed, giving the UI a responsive feel.

Example 2: Suppose one of a user's friends changes their name. As soon as the user's application receives one notice of that change (using get or discover), then synchronizeDiscover listeners can be used to update all instance's of that friend's name in the user's application instantly, providing a consistent user experience.

Hierarchy (View Summary)

Constructors

Properties

Accessors

Methods

CRUD Methods

Query Methods

Session Management

Synchronize Methods

This group contains methods that listen for changes made via put, patch, and delete or fetched from get, discover, and recoverOrphans and then streams appropriate changes to provide a responsive and consistent user experience.

Utilities

Constructors

Properties

ajv_: undefined | Promise<Ajv>
applyPatch_:
    | undefined
    | Promise<
        <T>(
            document: T,
            patch: readonly Operation[],
            validateOperation?: boolean | Validator<T>,
            mutateDocument?: boolean,
            banPrototypeModifications?: boolean,
        ) => PatchResult<T>,
    >
callbacks: Set<GraffitiSynchronizeCallback> = ...
graffiti: Graffiti

Accessors

  • get applyPatch(): Promise<
        <T>(
            document: T,
            patch: readonly Operation[],
            validateOperation?: boolean | Validator<T>,
            mutateDocument?: boolean,
            banPrototypeModifications?: boolean,
        ) => PatchResult<T>,
    >

    Returns Promise<
        <T>(
            document: T,
            patch: readonly Operation[],
            validateOperation?: boolean | Validator<T>,
            mutateDocument?: boolean,
            banPrototypeModifications?: boolean,
        ) => PatchResult<T>,
    >

Methods

  • Streams changes made to any object in any channel and made by any user. You may want to use it in conjuction with GraffitiSyncrhonizeOptions.omniscient to get a global view of all Graffiti objects passing through the system. This is useful for building a client-side cache, for example.

    Be careful using this method. Without additional filters it can expose the user to content out of context.

    Parameters

    Returns GraffitiStream<GraffitiObjectBase>

CRUD Methods

delete: (
    locationOrUri: string | GraffitiLocation,
    session: GraffitiSession,
) => Promise<GraffitiObjectBase> = ...

Deletes an object from a given location. The deleting actor must be the same as the actor that created the object.

If the object does not exist or has already been deleted, GraffitiErrorNotFound is thrown.

Type declaration

get: <Schema extends JSONSchema>(
    locationOrUri: string | GraffitiLocation,
    schema: Schema,
    session?: null | GraffitiSession,
) => Promise<GraffitiObject<Schema>> = ...

Retrieves an object from a given location.

The retrieved object is type-checked against the provided JSON schema otherwise a GraffitiErrorSchemaMismatch is thrown.

If the object existed but has since been deleted, or the retrieving actor was allowed to access the object but now isn't, this method will return the latest version of the object that the actor was allowed to access with its tombstone set to true, so long as that version is still cached.

Otherwise, if the object never existed, or the retrieving actor was never allowed to access it, or if the object was changed long enough ago that its history has been purged from the cache, a GraffitiErrorNotFound is thrown. The rate at which the cache is purged is implementation dependent. See the tombstoneReturn property returned by discover.

Type declaration

patch: (
    patch: GraffitiPatch,
    locationOrUri: string | GraffitiLocation,
    session: GraffitiSession,
) => Promise<GraffitiObjectBase> = ...

Patches an existing object at a given location. The patching actor must be the same as the actor that created the object.

Type declaration

put: <Schema extends JSONSchema>(
    object: GraffitiPutObject<Schema>,
    session: GraffitiSession,
) => Promise<GraffitiObjectBase> = ...

Creates a new object or replaces an existing object. An object can only be replaced by the same actor that created it.

Replacement occurs when the GraffitiLocation properties of the supplied object (name, actor, and source) exactly match the location of an existing object.

Type declaration

Query Methods

channelStats: (session: GraffitiSession) => GraffitiStream<ChannelStats>

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.

Type declaration

discover: <Schema extends JSONSchema>(
    channels: string[],
    schema: Schema,
    session?: null | GraffitiSession,
) => GraffitiStream<GraffitiObject<Schema>, { tombstoneRetention: number }> = ...

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 method must be polled again for new objects.

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 supplied schema is applied.

Since different implementations may fetch data from multiple sources there is no guarentee on the order that objects are returned in. Additionally, the method will return objects that have been deleted but with a tombstone field set to true for cache invalidation purposes. The final return() value of the stream includes a tombstoneRetention property that represents the minimum amount of time, in milliseconds, that an application will retain and return tombstones for objects that have been deleted.

When repolling, the lastModified field can be queried via the schema to only fetch objects that have been modified since the last poll. Such queries should only be done if the time since the last poll is less than the tombstoneRetention value of that poll, otherwise the tombstones for objects that have been deleted may not be returned.

{
"properties": {
"lastModified": {
"minimum": LAST_RETRIEVED_TIME
}
}
}

discover needs to be polled for new data because live updates to an application can be visually distracting or lead to toxic engagement. If and when an application wants real-time updates, such as in a chat application, application authors must be intentional about their polling.

Implementers should be aware that some users may applications may try to poll discover repetitively. You can deal with this by rate limiting or preemptively fetching data via a bidirectional channel, like a WebSocket. Additionally, implementers should probably index the lastModified field to speed up responses to schemas like the one above.

Type declaration

recoverOrphans: <Schema extends JSONSchema>(
    schema: Schema,
    session: GraffitiSession,
) => GraffitiStream<GraffitiObject<Schema>, { tombstoneRetention: number }> = ...

Discovers objects not contained in any channels that were created by the querying actor and match the given JSON Schema. Unlike discover, this method will not return objects created by other users.

This method is not useful for most applications, but necessary for getting a global view of all a user's Graffiti data or debugging channel usage.

It's return value is the same as discover.

Type declaration

Session Management

login: (proposal?: { actor?: string; scope?: {} }) => Promise<void>

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.

Type declaration

    • (proposal?: { actor?: string; scope?: {} }): Promise<void>
    • 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>

logout: (session: GraffitiSession) => 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.

Type declaration

sessionEvents: EventTarget

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

Synchronize Methods

Utilities

locationToUri: (location: GraffitiLocation) => string

Converts a GraffitiLocation object containing a name, actor, and source into a globally unique URI. The form of this URI is implementation dependent.

Its exact inverse is uriToLocation.

uriToLocation: (uri: string) => GraffitiLocation

Parses a globally unique Graffiti URI into a GraffitiLocation object containing a name, actor, and source.

Its exact inverse is locationToUri.