@graffiti-garden/api 0.6.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/1-api.ts CHANGED
@@ -2,250 +2,153 @@ import type {
2
2
  GraffitiObjectUrl,
3
3
  GraffitiObject,
4
4
  GraffitiObjectBase,
5
- GraffitiPatch,
6
5
  GraffitiSession,
7
- GraffitiPutObject,
6
+ GraffitiPostObject,
8
7
  GraffitiObjectStream,
9
- ChannelStats,
10
- GraffitiChannelStatsStream,
11
8
  GraffitiObjectStreamContinue,
12
9
  } from "./2-types";
13
10
  import type { JSONSchema } from "json-schema-to-ts";
14
11
 
15
12
  /**
16
13
  * This API describes a small but powerful set of methods that
17
- * can be used to create many different kinds of social media applications,
18
- * all of which can interoperate.
19
- * These methods should satisfy all of an application's needs for
14
+ * can be used to create many different kinds of social applications,
15
+ * from applications like Twitter, to Messenger, to Wikipedia, to many more new designs.
16
+ * See the [Graffiti project website](https://graffiti.garden)
17
+ * for links to example applications. Additionally, apps built on top
18
+ * of the API interoperate with each other so you can seamlessly switch
19
+ * between apps without losing your friends or data.
20
+ *
21
+ * These API methods should satisfy all of an application's needs for
20
22
  * the communication, storage, and access management of social data.
21
23
  * The rest of the application can be built with standard client-side
22
- * user interface tools to present and interact with the data
23
- * no server code necessary.
24
- * The Typescript source for this API is available at
25
- * [graffiti-garden/api](https://github.com/graffiti-garden/api).
24
+ * user interface tools to present and interact with that data—no server code necessary!
25
+ *
26
+ * The Typescript code for this API is [open source on Github](https://github.com/graffiti-garden/api).
26
27
  *
27
28
  * There are several different implementations of this Graffiti API available,
28
- * including a [federated implementation](https://github.com/graffiti-garden/implementation-federated),
29
- * that lets users choose where their data is stored,
29
+ * including a [federated implementation](https://github.com/graffiti-garden/implementation-remote),
30
+ * that lets people choose where their data is stored (you do not need to host your own server)
30
31
  * and a [local implementation](https://github.com/graffiti-garden/implementation-local)
31
- * that can be used for testing and development. In our design of Graffiti, this API is our
32
- * primary focus as it is the layer that shapes the experience
33
- * of developing applications. While different implementations can provide tradeoffs between
34
- * other important properties (e.g. privacy, security, scalability), those properties
35
- * are useless if the system as a whole doesn't expose useful functionality to developers.
36
- *
37
- * On the other side of the stack, there is [Vue plugin](https://github.com/graffiti-garden/wrapper-vue/)
38
- * that wraps around this API to provide reactivity. Other high-level libraries
39
- * will be available in the future.
40
- *
41
- * ## Overview
42
- *
43
- * Graffiti provides applications with methods to create and store data
44
- * on behalf of their users using standard CRUD operations:
45
- * {@link put}, {@link get}, {@link patch}, and {@link delete}.
46
- * This data can represent both social artifacts (e.g. posts, profiles) and
47
- * activities (e.g. likes, follows) and is stored as JSON.
48
- *
49
- * The social aspect of Graffiti comes from the {@link discover} method
50
- * which allows applications to find objects that other users made.
51
- * It is a lot like a traditional query operation, but it only
52
- * returns objects that have been placed in particular
53
- * {@link GraffitiObjectBase.channels | `channels`}
54
- * specified by the discovering application.
55
- *
56
- * Graffiti builds on well known concepts and standards wherever possible.
57
- * JSON Objects can be typed with [JSON Schema](https://json-schema.org/) and patches
58
- * can be applied with [JSON Patch](https://jsonpatch.com).
59
- * For interoperability between Graffiti applications, we recommend that
60
- * objects use established properties from the
61
- * [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/) when available,
62
- * however it is always possible to create additional properties, contributing
63
- * to the broader [folksonomy](https://en.wikipedia.org/wiki/Folksonomy).
64
- *
65
- * {@link GraffitiObjectBase.channels | `channels`} are one of the major concepts
66
- * unique to Graffiti along with *interaction relativity*, defined below.
67
- * Channels create boundaries between public spaces and work to prevent
68
- * [context collapse](https://en.wikipedia.org/wiki/Context_collapse)
69
- * even in a highly interoperable environment.
70
- * Interaction relativity means that all interactions between users are
71
- * actually atomic single-user operations that can be interpreted in different ways,
72
- * which also supports interoperability and pluralism.
32
+ * that can be used for testing and development. Different implementations can
33
+ * be swapped-in in the future without changing the API or any of the apps built on
34
+ * top of it. In fact, we're working on an end-to-end encrypted version now!
35
+ * [Follow Theia on BlueSky for updates](https://bsky.app/profile/theias.place).
73
36
  *
74
- * ### Channels
37
+ * On the other side of the stack, there is [Vue plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)
38
+ * that wraps around this API to provide reactivity. Other plugin frameworks
39
+ * and high-level libraries will be available in the future.
75
40
  *
76
- * {@link GraffitiObjectBase.channels | `channels`}
77
- * are a way for the creators of social data to express the intended audience of their
78
- * data. When a user creates data using the {@link put} method, they
79
- * can place their data in one or more channels.
80
- * Content consumers using the {@link discover} method will only see data
81
- * contained in one of the channels they specify.
41
+ * ## API Overview
82
42
  *
83
- * While many channels may be public, they partition
84
- * the public into different "contexts", mitigating the
85
- * phenomenon of [context collapse](https://en.wikipedia.org/wiki/Context_collapse) or the "flattening of multiple audiences."
86
- * Any [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) can be used as a channel, and so channels can represent people,
87
- * comment threads, topics, places (real or virtual), pieces of media, and more.
43
+ * The Graffiti API provides applications with methods for {@link login} and {@link logout},
44
+ * methods to interact with data objects using standard database operations ({@link post}, {@link get}, and {@link delete}),
45
+ * and a method to {@link discover} data objects created by others.
46
+ * These data objects have a couple structured properties:
47
+ * - {@link GraffitiObjectBase.url | `url`} (string): A globally unique identifier and locator for the object.
48
+ * - {@link GraffitiObjectBase.actor | `actor`} (string): An unforgeable identifier for the creator of the object.
49
+ * - {@link GraffitiObjectBase.allowed | `allowed`} (string[] | undefined): An array of the actors who are allowed to access the object (undefined for public objects).
50
+ * - {@link GraffitiObjectBase.channels | `channels`} (string[]): An array of the *contexts* in which the object should appear.
88
51
  *
89
- * For example, consider a comment on a post. If we place that comment in the channel
90
- * represented by the post's URL, then only people viewing the post will know to
91
- * look in that channel, giving it visibility akin to a comment on a blog post
92
- * or comment on Instagram ([since 2019](https://www.buzzfeednews.com/article/katienotopoulos/instagrams-following-activity-tab-is-going-away)).
93
- * If we also place the comment in the channel represented by the commenter's URI (their
94
- * {@link GraffitiObjectBase.actor | `actor` URI}), then people viewing the commenter's profile
95
- * will also see the comment, giving it more visibility, like a reply on Twitter.
96
- * If we *only* place the comment in the channel represented by the commenter's URI, then
97
- * it becomes like a quote tweet ([prior to 2020](https://x.com/Support/status/1300555325750292480)),
98
- * where the comment is only visible to the commenter's followers but not the audience
99
- * of the original post.
100
- *
101
- * The channel model differs from other models of communication such as the
102
- * [actor model](https://www.w3.org/TR/activitypub/#Overview) used by ActivityPub,
103
- * the protocol underlying Mastodon, or the [firehose model](https://bsky.social/about/blog/5-5-2023-federation-architecture)
104
- * used by the AT Protocol, the protocol underlying BlueSky.
105
- * The actor model is a fusion of direct messaging (like Email) and broadcasting
106
- * (like RSS) and works well for follow-based communication but struggles
107
- * to pass information via other rendez-vous.
108
- * In the actor model, even something as simple as comments can be
109
- * [very tricky and require server "side effects"](https://seb.jambor.dev/posts/understanding-activitypub-part-3-the-state-of-mastodon/).
110
- * The firehose model dumps all user data into one public database,
111
- * which doesn't allow for the carving out of different contexts that we did in our comment
112
- * example above. In the firehose model a comment will always be visible to *both* the original post's audience and
113
- * the commenter's followers.
114
- *
115
- * In some sense, channels provide a sort of "social access control" by forming
116
- * expectations about the audiences of different online spaces.
117
- * As a real world analogy, oftentimes support groups, such as alcoholics
118
- * anonymous, are open to the public but people in those spaces feel comfortable sharing intimate details
119
- * because they have expectations about the other people attending.
120
- * If someone malicious went to support groups just to spread people's secrets,
121
- * they would be shamed for violating these norms.
122
- * Similarly, in Graffiti, while you could spider public channels like a search engine
123
- * to find content about a person, revealing that you've done such a thing
124
- * would be shameful.
52
+ * All other data is stored in the object's unstructured {@link GraffitiObjectBase.value | `value`} property.
53
+ * This data can be used to represent social artifacts (e.g. posts, profiles) and activities (e.g. likes, follows).
54
+ * For example, a post might have the value:
55
+
56
+ * ```js
57
+ * {
58
+ * title: "My First Post",
59
+ * content: "Hello, world!",
60
+ * published: 1630483200000
61
+ * }
62
+ * ```
125
63
  *
126
- * Still, social access control is not perfect and so in situations where privacy is important,
127
- * objects can also be given
128
- * an {@link GraffitiObjectBase.allowed | `allowed`} list.
129
- * For example, to send someone a direct message you should put an object representing
130
- * that message in the channel that represents them (their {@link GraffitiObjectBase.actor | `actor` URI}),
131
- * so they can find it, *and* set the `allowed` field to only include the recipient,
132
- * so only they can read it.
64
+ * a profile might have the value:
133
65
  *
134
- * ### Interaction relativity
66
+ * ```js
67
+ * {
68
+ * name: "Theia Henderson",
69
+ * pronouns: "she/her",
70
+ * describes: "did:web:theias.place" // Theia's actor ID
71
+ * }
72
+ * ```
135
73
  *
136
- * Interaction relativity posits that "interaction between two individuals only
137
- * exists relative to an observer," or equivalently, all interaction is [reified](https://en.wikipedia.org/wiki/Reification_(computer_science)).
138
- * For example, if one user creates a post and another user wants to "like" that post,
139
- * their like is not modifying the original post, it is simply another data object that points
140
- * to the post being liked, via its {@link GraffitiObjectBase.url | URL}.
74
+ * and a "Like" might have the value:
141
75
  *
142
- * ```json
76
+ * ```js
143
77
  * {
144
- * activity: 'like',
145
- * target: 'url-of-the-post-i-like',
146
- * actor: 'my-user-id'
78
+ * activity: "Like",
79
+ * target: "graffiti:remote:pod.graffiti.garden/12345" // The URL of the graffiti object being liked
147
80
  * }
148
81
  * ```
149
82
  *
150
- * In Graffiti, all interactions including *moderation* and *collaboration* are relative.
151
- * This means that applications can freely choose which interactions
152
- * they want to express to their users and how.
153
- * For example, one application could have a single fixed moderator,
154
- * another could allow users to choose which moderators they would like filter their content
155
- * like [Bluesky's stackable moderation](https://bsky.social/about/blog/03-12-2024-stackable-moderation),
156
- * and another could implement a fully democratic system like [PolicyKit](https://policykit.org/).
157
- * Each of these applications is one interpretation of the underlying refieid user interactions and
158
- * users can freely switch between them.
83
+ * New social artifacts and activities can be easily created, simply
84
+ * by creating new objects with appropriate properties. Despite the lack of
85
+ * structure, we expect Graffiti object properties to adhere to a "[folksonomy](https://en.wikipedia.org/wiki/Folksonomy)",
86
+ * similar to hashtags. Any string can be used as a hashtag on Twitter,
87
+ * but there is social value in using the same hashtags at other people and
88
+ * so a structure naturally emerges. Similarly, Graffiti objects
89
+ * can have arbitrary properties but if people use the same properties as each other,
90
+ * their apps will interoperate, which has social value.
159
91
  *
160
- * Interaction relativy also allows applications to introduce new sorts of interactions
161
- * without having to coordinate with all the other existing applications,
162
- * keeping the ecosystem flexible and interoperable.
163
- * For example, an application could [add a "Trust" button to posts](https://social.cs.washington.edu/pub_details.html?id=trustnet)
164
- * and use it assess the truthfulness of posts made on applications across Graffiti.
165
- * New sorts of interactions like these can be smoothly absorbed by the broader ecosystem
166
- * as a [folksonomy](https://en.wikipedia.org/wiki/Folksonomy).
92
+ * For a more complete and detailed overview of Graffiti's design, please
93
+ * refer to [this section of the Graffiti paper](https://dl.acm.org/doi/10.1145/3746059.3747627#sec-3),
94
+ * published in ACM UIST 2025. The paper also overviews {@link GraffitiObjectBase.channels | `channels`},
95
+ * which are Graffiti's means of organizing data contextually, and a concept called "total reification",
96
+ * which handles explains how moderation, collaboration, and other interactions are managed.
167
97
  *
168
- * Interactivy relativity is realized in Graffiti through two design decisions:
169
- * 1. The creators of objects can only modify their own objects. It is important for
170
- * users to be able to change and delete their own content to respect their
171
- * [right to be forgotten](https://en.wikipedia.org/wiki/Right_to_be_forgotten),
172
- * but beyond self-correction and self-censorship all other interaction is reified.
173
- * Many interactions can be reified via pointers, as in the "like" example above, and collaborative
174
- * edits can be refieid via [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type).
175
- * 2. No one owns channels. Unlike IRC/Slack channels or [Matrix rooms](https://matrix.org/docs/matrix-concepts/rooms_and_events/),
176
- * anyone can post to any channel, so long as they know the URI of that channel.
177
- * It is up to applications to hide content from channels either according to manual
178
- * filters or in response to user action.
179
- * For example, a user may create a post with the flag `disableReplies`.
180
- * Applications could then filter out any content from the replies channel
181
- * that the original poster has not specifically approved.
182
- *
183
- * @groupDescription CRUD Methods
184
- * Methods for {@link put | creating}, {@link get | reading}, {@link patch | updating},
98
+ * @groupDescription 1 - Single-Object Methods
99
+ * Methods for {@link post | creating}, {@link get | reading},
185
100
  * and {@link delete | deleting} {@link GraffitiObjectBase | Graffiti objects}.
186
- * @groupDescription Query Methods
101
+ * @groupDescription 2 - Multi-Object Methods
187
102
  * Methods that retrieve or accumulate information about multiple {@link GraffitiObjectBase | Graffiti objects} at a time.
188
- * @groupDescription Session Management
189
- * Methods and properties for logging in and out of a Graffiti implementation.
103
+ * @groupDescription 3 - Media Methods
104
+ * Methods for {@link postMedia | creating}, {@link getMedia | reading},
105
+ * and {@link deleteMedia | deleting} media data.
106
+ * @groupDescription 4 - Session Management
107
+ * Methods and properties for logging in and out.
190
108
  */
191
109
  export abstract class Graffiti {
192
110
  /**
193
- * Creates a new {@link GraffitiObjectBase | object} or replaces an existing object.
194
- * An object can only be replaced by the same {@link GraffitiObjectBase.actor | `actor`}
195
- * that created it.
196
- *
197
- * Replacement occurs when the {@link GraffitiObjectBase.url | `url`} of
198
- * the replaced object exactly matches an existing object's URL.
199
- *
200
- * @throws {@link GraffitiErrorNotFound} if a {@link GraffitiObjectBase.url | `url`}
201
- * is provided that has not been created yet or the {@link GraffitiObjectBase.actor | `actor`}
202
- * is not {@link GraffitiObjectBase.allowed | `allowed`} to see it.
203
- *
204
- * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}
205
- * is not the same `actor` as the one who created the object.
111
+ * Creates a new {@link GraffitiObjectBase | object}.
206
112
  *
207
- * @returns The object that was replaced if one one exists, otherwise an object with
208
- * with an empty {@link GraffitiObjectBase.value | `value`},
209
- * {@link GraffitiObjectBase.channels | `channels`}, and {@link GraffitiObjectBase.allowed | `allowed`}
210
- * list.
211
- * The {@link GraffitiObjectBase.lastModified | `lastModified`} property of the returned object
212
- * will be updated to the time of replacement/creation.
113
+ * @returns Returns the object that has been posted, complete with its
114
+ * assigned {@link GraffitiObjectBase.url | `url`} and
115
+ * {@link GraffitiObjectBase.actor | `actor`}.
213
116
  *
214
- * @group CRUD Methods
117
+ * @group 1 - Single-Object Methods
215
118
  */
216
- abstract put<Schema extends JSONSchema>(
119
+ abstract post<Schema extends JSONSchema>(
217
120
  /**
218
- * The object to be put. This object is statically type-checked against the [JSON schema](https://json-schema.org/) that can be optionally provided
219
- * as the generic type parameter. We highly recommend providing a schema to
220
- * ensure that the PUT object matches subsequent {@link get} or {@link discover}
121
+ * An object to post. This object is statically type-checked against the [JSON schema](https://json-schema.org/) that can be optionally provided
122
+ * as the generic type parameter. It is recommended to a schema to
123
+ * ensure that the posted object matches subsequent {@link get} or {@link discover}
221
124
  * methods.
222
125
  */
223
- object: GraffitiPutObject<Schema>,
126
+ object: GraffitiPostObject<Schema>,
224
127
  /**
225
128
  * An implementation-specific object with information to authenticate the
226
129
  * {@link GraffitiObjectBase.actor | `actor`}.
227
130
  */
228
131
  session: GraffitiSession,
229
- ): Promise<GraffitiObjectBase>;
132
+ ): Promise<GraffitiObject<Schema>>;
230
133
 
231
134
  /**
232
- * Retrieves an object from a given {@link GraffitiObjectBase.url | `url`}.
233
- *
234
- * The retrieved object is type-checked against the provided [JSON schema](https://json-schema.org/)
235
- * otherwise a {@link GraffitiErrorSchemaMismatch} is thrown.
135
+ * Retrieves an object from a given {@link GraffitiObjectBase.url | `url`} matching
136
+ * the provided `schema`.
236
137
  *
237
138
  * If the retreiving {@link GraffitiObjectBase.actor | `actor`} is not
238
139
  * the object's `actor`,
239
140
  * the object's {@link GraffitiObjectBase.allowed | `allowed`} and
240
141
  * {@link GraffitiObjectBase.channels | `channels`} properties are
241
- * not revealed, similar to a BCC.
142
+ * not revealed, similar to a BCC email.
143
+ *
144
+ * @returns Returns the retrieved object.
242
145
  *
243
- * @throws {@link GraffitiErrorNotFound} if the object does not exist, has been deleted, or the user is not
146
+ * @throws {@link GraffitiErrorNotFound} if the object does not exist, has been deleted, or the actor is not
244
147
  * {@link GraffitiObjectBase.allowed | `allowed`} to access it.
245
148
  *
246
149
  * @throws {@link GraffitiErrorSchemaMismatch} if the retrieved object does not match the provided schema.
247
150
  *
248
- * @group CRUD Methods
151
+ * @group 1 - Single-Object Methods
249
152
  */
250
153
  abstract get<Schema extends JSONSchema>(
251
154
  /**
@@ -266,61 +169,18 @@ export abstract class Graffiti {
266
169
  ): Promise<GraffitiObject<Schema>>;
267
170
 
268
171
  /**
269
- * Patches an existing object at a given {@link GraffitiObjectBase.url | `url`}.
270
- * The patching {@link GraffitiObjectBase.actor | `actor`} must be the same as the
271
- * `actor` that created the object.
272
- *
273
- * @returns The original object prior to the patch with its
274
- * {@link GraffitiObjectBase.lastModified | `lastModified`}
275
- * property updated to the time of deletion.
276
- *
277
- * @throws {@link GraffitiErrorNotFound} if the object does not exist, has already been deleted,
278
- * or the user is not {@link GraffitiObjectBase.allowed | `allowed`} to access it.
279
- *
280
- * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}
281
- * is not the same `actor` as the one who created the object.
282
- *
283
- * @group CRUD Methods
284
- */
285
- abstract patch(
286
- /**
287
- * A collection of [JSON Patch](https://jsonpatch.com) operations
288
- * to apply to the object. See {@link GraffitiPatch} for more information.
289
- */
290
- patch: GraffitiPatch,
291
- /**
292
- * The location of the object to patch.
293
- */
294
- url: string | GraffitiObjectUrl,
295
- /**
296
- * An implementation-specific object with information to authenticate the
297
- * {@link GraffitiObjectBase.actor | `actor`}.
298
- */
299
- session: GraffitiSession,
300
- ): Promise<GraffitiObjectBase>;
301
-
302
- /**
303
- * Deletes an object from a given {@link GraffitiObjectBase.url | `url`}.
172
+ * Deletes an object from a given {@link GraffitiObjectBase.url | `url`}
173
+ * that had previously been {@link post | `post`ed}.
304
174
  * The deleting {@link GraffitiObjectBase.actor | `actor`} must be the same as the
305
175
  * `actor` that created the object.
306
176
  *
307
- * It is not possible to re-{@link put} an object that has been deleted
308
- * to ensure a user's [right to be forgotten](https://en.wikipedia.org/wiki/Right_to_be_forgotten).
309
- * In cases where deleting and restoring an object is useful, an object's
310
- * {@link GraffitiObjectBase.allowed | `allowed`} property can be set to
311
- * an empty list to hide it from all users except the creator.
312
- *
313
- * @returns The object that was deleted with its
314
- * {@link GraffitiObjectBase.lastModified | `lastModified`}
315
- * property updated to the time of deletion.
316
- *
317
- * @throws {@link GraffitiErrorNotFound} if the object does not exist, has already been deleted,
318
- * or the user is not {@link GraffitiObjectBase.allowed | `allowed`} to access it.
177
+ * @throws {@link GraffitiErrorNotFound} if the object does not exist, has already been deleted,
178
+ * or the actor is not {@link GraffitiObjectBase.allowed | `allowed`} to access it.
319
179
  *
320
180
  * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}
321
181
  * is not the same `actor` as the one who created the object.
322
182
  *
323
- * @group CRUD Methods
183
+ * @group 1 - Single-Object Methods
324
184
  */
325
185
  abstract delete(
326
186
  /**
@@ -332,10 +192,10 @@ export abstract class Graffiti {
332
192
  * {@link GraffitiObjectBase.actor | `actor`}.
333
193
  */
334
194
  session: GraffitiSession,
335
- ): Promise<GraffitiObjectBase>;
195
+ ): Promise<void>;
336
196
 
337
197
  /**
338
- * Discovers objects created by any user that are contained
198
+ * Discovers objects created by any actor that are contained
339
199
  * in at least one of the given {@link GraffitiObjectBase.channels | `channels`}
340
200
  * and match the given [JSON Schema](https://json-schema.org).
341
201
  *
@@ -349,7 +209,7 @@ export abstract class Graffiti {
349
209
  * string can be serialized to continue the stream after an application is closed
350
210
  * and reopened.
351
211
  *
352
- * `discover` will not return objects that the {@link GraffitiObjectBase.actor | `actor`}
212
+ * `discover` will not return objects that the querying {@link GraffitiObjectBase.actor | `actor`}
353
213
  * is not {@link GraffitiObjectBase.allowed | `allowed`} to access.
354
214
  * If the `actor` is not the creator of a discovered object,
355
215
  * the allowed list will be masked to only contain the querying actor if the
@@ -360,14 +220,11 @@ export abstract class Graffiti {
360
220
  *
361
221
  * Since different implementations may fetch data from multiple sources there is
362
222
  * no guarentee on the order that objects are returned in.
363
- * It is also possible that duplicate objects are returned and their
364
- * {@link GraffitiObjectBase.lastModified | `lastModified`} fields must be used
365
- * to determine which object is the most recent.
366
223
  *
367
- * @returns A stream of objects that match the given {@link GraffitiObjectBase.channels | `channels`}
224
+ * @returns Returns a stream of objects that match the given {@link GraffitiObjectBase.channels | `channels`}
368
225
  * and [JSON Schema](https://json-schema.org).
369
226
  *
370
- * @group Query Methods
227
+ * @group 2 - Multi-Object Methods
371
228
  */
372
229
  abstract discover<Schema extends JSONSchema>(
373
230
  /**
@@ -388,148 +245,156 @@ export abstract class Graffiti {
388
245
  ): GraffitiObjectStream<Schema>;
389
246
 
390
247
  /**
391
- * Discovers objects **not** contained in any
392
- * {@link GraffitiObjectBase.channels | `channels`}
393
- * that were created by the querying {@link GraffitiObjectBase.actor | `actor`}
394
- * and match the given [JSON Schema](https://json-schema.org).
395
- * Unlike {@link discover}, this method will not return objects created by other users.
248
+ * Continues a {@link GraffitiObjectStream} from a given
249
+ * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string.
250
+ * The continuation will return new objects that have been {@link post | `post`ed}
251
+ * that match the original stream, and also returns the
252
+ * {@link GraffitiObjectBase.url | `url`}s of objects that
253
+ * have been {@link delete | `delete`d}, as marked by a `tombstone`.
396
254
  *
397
- * This method is not useful for most applications, but necessary for
398
- * getting a global view of all a user's Graffiti data or debugging
399
- * channel usage.
255
+ * The `cursor` allows the client to
256
+ * serialize the state of the stream and continue it later.
257
+ * However this method loses any typing information that was
258
+ * present in the original stream. For better type safety
259
+ * and when serializing is not necessary, use the
260
+ * {@link GraffitiObjectStreamReturn.continue | `continue`} method
261
+ * instead, which is returned along with the `cursor` at the
262
+ * end of the original stream.
400
263
  *
401
- * Like {@link discover}, objects are returned asynchronously as they are discovered,
402
- * the stream will end once all leads have been exhausted, and the stream
403
- * can be continued using the {@link GraffitiObjectStreamReturn.continue | `continue`}
404
- * method or {@link GraffitiObjectStreamReturn.cursor | `cursor`} string.
264
+ * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}
265
+ * provided in the `session` is not the same as the `actor`
266
+ * that initiated the original stream.
405
267
  *
406
- * @returns A stream of objects created by the querying {@link GraffitiObjectBase.actor | `actor`}
407
- * that do not belong to any {@link GraffitiObjectBase.channels | `channels`}
408
- * and match the given [JSON Schema](https://json-schema.org).
268
+ * @group 2 - Multi-Object Methods
269
+ */
270
+ abstract continueDiscover(
271
+ cursor: string,
272
+ session?: GraffitiSession | null,
273
+ ): GraffitiObjectStreamContinue<{}>;
274
+
275
+ /**
276
+ * Uploads media data, such as an image or video.
277
+ *
278
+ * Unlike structured {@link GraffitiObjectBase | objects},
279
+ * media is not indexed for {@link discover | `discover`y} and
280
+ * must be retrieved by its exact URL using {@link getMedia}
409
281
  *
410
- * @group Query Methods
282
+ * @returns The URL that the media was posted to.
283
+ *
284
+ * @group 3 - Media Methods
411
285
  */
412
- abstract recoverOrphans<Schema extends JSONSchema>(
286
+ abstract postMedia(
413
287
  /**
414
- * A [JSON Schema](https://json-schema.org) that orphaned objects must satisfy.
288
+ * The binary data of the media to be uploaded,
289
+ * along with its [media type](https://www.iana.org/assignments/media-types/media-types.xhtml),
290
+ * formatted as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob).
415
291
  */
416
- schema: Schema,
292
+ media: Blob,
417
293
  /**
418
294
  * An implementation-specific object with information to authenticate the
419
295
  * {@link GraffitiObjectBase.actor | `actor`}.
420
296
  */
421
297
  session: GraffitiSession,
422
- ): GraffitiObjectStream<Schema>;
298
+ ): Promise<string>;
423
299
 
424
300
  /**
425
- * Returns statistics about all the {@link GraffitiObjectBase.channels | `channels`}
426
- * that an {@link GraffitiObjectBase.actor | `actor`} has posted to.
427
- * This is not very useful for most applications, but
428
- * necessary for certain applications where a user wants a
429
- * global view of all their Graffiti data or to debug
430
- * channel usage.
301
+ * Deletes media previously {@link postMedia | `post`ed} to a given URL.
431
302
  *
432
- * Like {@link discover}, objects are returned asynchronously as they are discovered,
433
- * the stream will end once all leads have been exhausted.
303
+ * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.
434
304
  *
435
- * @group Query Methods
305
+ * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}
306
+ * provided in the `session` is not the same as the `actor` that {@link postMedia | `post`ed}
307
+ * the media.
436
308
  *
437
- * @returns A stream of statistics for each {@link GraffitiObjectBase.channels | `channel`}
438
- * that the {@link GraffitiObjectBase.actor | `actor`} has posted to.
309
+ * @group 3 - Media Methods
439
310
  */
440
- abstract channelStats(
311
+ abstract deleteMedia(
312
+ /**
313
+ * A globally unique identifier and locator for the media.
314
+ */
315
+ mediaUrl: string,
441
316
  /**
442
317
  * An implementation-specific object with information to authenticate the
443
318
  * {@link GraffitiObjectBase.actor | `actor`}.
444
319
  */
445
320
  session: GraffitiSession,
446
- ): GraffitiChannelStatsStream;
321
+ ): Promise<void>;
447
322
 
448
323
  /**
449
- * Continues a {@link GraffitiObjectStream} from a given
450
- * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string.
451
- * The continuation will return new objects that have been created
452
- * that match the original stream, and also returns the
453
- * {@link GraffitiObjectBase.url | `url`}s of objects that
454
- * have been deleted, as marked by a `tombstone`.
324
+ * Retrieves media from the given media URL, adhering to the given requirements.
455
325
  *
456
- * The continuation may also include duplicates of objects that
457
- * were already returned by the original stream. This is dependent
458
- * on how much state the underlying implementation maintains.
326
+ * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.
459
327
  *
460
- * The `cursor` allows the client to
461
- * serialize the state of the stream and continue it later.
462
- * However this method loses any typing information that was
463
- * present in the original stream. For better type safety
464
- * and when serializing is not necessary, use the
465
- * {@link GraffitiObjectStreamReturn.continue | `continue`} method
466
- * instead, which is returned along with the `cursor` at the
467
- * end of the original stream.
328
+ * @throws {@link GraffitiErrorTooLarge} if the media exceeds the given `maxBytes`.
468
329
  *
469
- * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}
470
- * provided in the `session` is not the same as the `actor`
471
- * that initiated the original stream.
330
+ * @throws {@link GraffitiErrorNotAcceptable} if the media does not match the given
331
+ * `accept` specification.
332
+ *
333
+ * @returns The URL of the retrieved media, as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
334
+ * and the {@link GraffitiObjectBase.actor | `actor`} that posted it.
472
335
  *
473
- * @group Query Methods
336
+ * @group 3 - Media Methods
474
337
  */
475
- abstract continueObjectStream(
476
- cursor: string,
477
- session?: GraffitiSession | null,
478
- ): GraffitiObjectStreamContinue<{}>;
338
+ abstract getMedia(
339
+ /**
340
+ * A globally unique identifier and locator for the media.
341
+ */
342
+ mediaUrl: string,
343
+ /**
344
+ * An optional set of requirements the retrieved media must meet.
345
+ */
346
+ requirements?: {
347
+ /**
348
+ * A list of acceptable media types for the retrieved media,
349
+ * formatted as like an [HTTP Accept header](https://httpwg.org/specs/rfc9110.html#field.accept)
350
+ */
351
+ accept?: string;
352
+ /**
353
+ * The maximum size, in bytes, of the media.
354
+ */
355
+ maxBytes?: number;
356
+ },
357
+ ): Promise<{
358
+ media: Blob;
359
+ actor: string;
360
+ }>;
479
361
 
480
362
  /**
481
363
  * Begins the login process. Depending on the implementation, this may
482
- * involve redirecting the user to a login page or opening a popup,
483
- * so it should always be called in response to a user action.
364
+ * involve redirecting to a login page or opening a popup,
365
+ * so it should always be called in response to a gesture, such as clicking
366
+ * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).
484
367
  *
485
368
  * The {@link GraffitiSession | session} object is returned
486
369
  * asynchronously via {@link Graffiti.sessionEvents | sessionEvents}
487
370
  * as a {@link GraffitiLoginEvent} with event type `login`.
488
371
  *
489
- * @group Session Management
372
+ * @group 4 - Session Management
490
373
  */
491
374
  abstract login(
492
375
  /**
493
- * Suggestions for the permissions that the
494
- * login process should grant. The login process may not
495
- * provide the exact proposed permissions.
376
+ * A suggested actor to login as. For example, if a user tries to
377
+ * edit a post but are not logged in, the interface can infer that
378
+ * they might want to log in as the actor who created the post
379
+ * they are attempting to edit.
380
+ *
381
+ * Even if provided, the implementation should allow the user
382
+ * to log in as a different actor if they choose.
496
383
  */
497
- proposal?: {
498
- /**
499
- * A suggested actor to login as. For example, if a user tries to
500
- * edit a post but are not logged in, the interface can infer that
501
- * they might want to log in as the actor who created the post
502
- * they are attempting to edit.
503
- *
504
- * Even if provided, the implementation should allow the user
505
- * to log in as a different actor if they choose.
506
- */
507
- actor?: string;
508
- /**
509
- * A yet to be defined permissions scope. An application may use
510
- * this to indicate the minimum necessary scope needed to
511
- * operate. For example, it may need to be able read private
512
- * messages from a certain set of channels, or write messages that
513
- * follow a particular schema.
514
- *
515
- * The login process should make it clear what scope an application
516
- * is requesting and allow the user to enhance or reduce that
517
- * scope as necessary.
518
- */
519
- scope?: {};
520
- },
384
+ actor?: string,
521
385
  ): Promise<void>;
522
386
 
523
387
  /**
524
- * Begins the logout process. Depending on the implementation, this may
388
+ * Begins the logout process for a particular {@link GraffitiSession | session}. Depending on the implementation, this may
525
389
  * involve redirecting the user to a logout page or opening a popup,
526
- * so it should always be called in response to a user action.
390
+ * so it should always be called in response to a gesture, such as clicking
391
+ * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).
527
392
  *
528
393
  * A confirmation will be returned asynchronously via
529
394
  * {@link Graffiti.sessionEvents | sessionEvents}
530
395
  * as a {@link GraffitiLogoutEvent} as event type `logout`.
531
396
  *
532
- * @group Session Management
397
+ * @group 4 - Session Management
533
398
  */
534
399
  abstract logout(
535
400
  /**
@@ -540,12 +405,12 @@ export abstract class Graffiti {
540
405
 
541
406
  /**
542
407
  * An event target that can be used to listen for the following
543
- * events and they're corresponding event types:
408
+ * events and their corresponding event types:
544
409
  * - `login` - {@link GraffitiLoginEvent}
545
410
  * - `logout` - {@link GraffitiLogoutEvent}
546
411
  * - `initialized` - {@link GraffitiSessionInitializedEvent}
547
412
  *
548
- * @group Session Management
413
+ * @group 4 - Session Management
549
414
  */
550
415
  abstract readonly sessionEvents: EventTarget;
551
416
  }