@graffiti-garden/api 0.2.2 → 0.2.4
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/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +7 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +7 -1
- package/dist/src/2-types.d.ts +1 -1
- package/dist/tests/channel-stats.d.ts.map +1 -0
- package/dist/tests/crud.d.ts.map +1 -0
- package/dist/tests/discover.d.ts.map +1 -0
- package/dist/tests/index.d.ts.map +1 -0
- package/dist/tests/location.d.ts.map +1 -0
- package/dist/tests/orphans.d.ts.map +1 -0
- package/dist/tests/synchronize.d.ts.map +1 -0
- package/dist/tests/utils.d.ts.map +1 -0
- package/dist/tests.js +2 -0
- package/dist/tests.js.map +7 -0
- package/package.json +12 -13
- package/src/2-types.ts +1 -1
- package/dist/tests/index.js +0 -2
- package/dist/tests/index.js.map +0 -1
- package/dist/tests/src/channel-stats.d.ts.map +0 -1
- package/dist/tests/src/crud.d.ts.map +0 -1
- package/dist/tests/src/discover.d.ts.map +0 -1
- package/dist/tests/src/index.d.ts.map +0 -1
- package/dist/tests/src/list.d.ts +0 -3
- package/dist/tests/src/list.d.ts.map +0 -1
- package/dist/tests/src/location.d.ts.map +0 -1
- package/dist/tests/src/orphans.d.ts.map +0 -1
- package/dist/tests/src/synchronize.d.ts.map +0 -1
- package/dist/tests/src/utils.d.ts.map +0 -1
- package/dist/tests/tests/src/crud.d.ts +0 -3
- package/dist/tests/tests/src/crud.d.ts.map +0 -1
- package/dist/tests/tests/src/discover.d.ts +0 -3
- package/dist/tests/tests/src/discover.d.ts.map +0 -1
- package/dist/tests/tests/src/index.d.ts +0 -5
- package/dist/tests/tests/src/index.d.ts.map +0 -1
- package/dist/tests/tests/src/location.d.ts +0 -3
- package/dist/tests/tests/src/location.d.ts.map +0 -1
- package/dist/tests/tests/src/synchronize.d.ts +0 -3
- package/dist/tests/tests/src/synchronize.d.ts.map +0 -1
- package/dist/tests/tests/src/utils.d.ts +0 -8
- package/dist/tests/tests/src/utils.d.ts.map +0 -1
- package/tests/rollup.config.ts +0 -19
- package/tests/tsconfig.json +0 -16
- /package/dist/tests/{src/channel-stats.d.ts → channel-stats.d.ts} +0 -0
- /package/dist/tests/{src/crud.d.ts → crud.d.ts} +0 -0
- /package/dist/tests/{src/discover.d.ts → discover.d.ts} +0 -0
- /package/dist/tests/{src/index.d.ts → index.d.ts} +0 -0
- /package/dist/tests/{src/location.d.ts → location.d.ts} +0 -0
- /package/dist/tests/{src/orphans.d.ts → orphans.d.ts} +0 -0
- /package/dist/tests/{src/synchronize.d.ts → synchronize.d.ts} +0 -0
- /package/dist/tests/{src/utils.d.ts → utils.d.ts} +0 -0
- /package/tests/{src/channel-stats.ts → channel-stats.ts} +0 -0
- /package/tests/{src/crud.ts → crud.ts} +0 -0
- /package/tests/{src/discover.ts → discover.ts} +0 -0
- /package/tests/{src/index.ts → index.ts} +0 -0
- /package/tests/{src/location.ts → location.ts} +0 -0
- /package/tests/{src/orphans.ts → orphans.ts} +0 -0
- /package/tests/{src/synchronize.ts → synchronize.ts} +0 -0
- /package/tests/{src/utils.ts → utils.ts} +0 -0
package/dist/index.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";var i=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var u=(e,t)=>{for(var s in t)i(e,s,{get:t[s],enumerable:!0})},x=(e,t,s,b)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of O(t))!l.call(e,r)&&r!==s&&i(e,r,{get:()=>t[r],enumerable:!(b=G(t,r))||b.enumerable});return e};var j=e=>x(i({},"__esModule",{value:!0}),e);var y={};u(y,{Graffiti:()=>a,GraffitiErrorForbidden:()=>c,GraffitiErrorInvalidSchema:()=>f,GraffitiErrorInvalidUri:()=>S,GraffitiErrorNotFound:()=>n,GraffitiErrorPatchError:()=>p,GraffitiErrorPatchTestFailed:()=>h,GraffitiErrorSchemaMismatch:()=>m,GraffitiErrorUnauthorized:()=>o});module.exports=j(y);var a=class{objectToUri(t){return this.locationToUri(t)}};var o=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorUnauthorized",Object.setPrototypeOf(this,e.prototype)}},c=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorForbidden",Object.setPrototypeOf(this,e.prototype)}},n=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorNotFound",Object.setPrototypeOf(this,e.prototype)}},f=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorInvalidSchema",Object.setPrototypeOf(this,e.prototype)}},m=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorSchemaMismatch",Object.setPrototypeOf(this,e.prototype)}},h=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorPatchTestFailed",Object.setPrototypeOf(this,e.prototype)}},p=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorPatchError",Object.setPrototypeOf(this,e.prototype)}},S=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorInvalidUri",Object.setPrototypeOf(this,e.prototype)}};
|
|
2
2
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/1-api.ts", "../src/3-errors.ts"],
|
|
4
|
+
"sourcesContent": ["export * from \"./1-api\";\nexport * from \"./2-types\";\nexport * from \"./3-errors\";\nexport type { JSONSchema4 } from \"json-schema\";\n", "import type {\n GraffitiLocation,\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiPatch,\n GraffitiSession,\n GraffitiPutObject,\n GraffitiStream,\n ChannelStats,\n} from \"./2-types\";\nimport type { JSONSchema4 } from \"json-schema\";\n\n/**\n * This API describes a small but powerful set of methods that\n * can be used to create many different kinds of social media applications,\n * all of which can interoperate.\n * These methods should satisfy all of an application's needs for\n * the communication, storage, and access management of social data.\n * The rest of the application can be built with standard client-side\n * user interface tools to present and interact with the data \u2014\n * no server code necessary.\n * The Typescript source for this API is available at\n * [graffiti-garden/api](https://github.com/graffiti-garden/api).\n *\n * There are several different implementations of this Graffiti API available,\n * including a [federated implementation](https://github.com/graffiti-garden/implementation-federated),\n * that lets users choose where their data is stored,\n * and a [local implementation](https://github.com/graffiti-garden/implementation-local)\n * that can be used for testing and development. In our design of Graffiti, this API is our\n * primary focus as it is the layer that shapes the experience\n * of developing applications. While different implementations can provide tradeoffs between\n * other important properties (e.g. privacy, security, scalability), those properties\n * are useless if the system as a whole doesn't expose useful functionality to developers.\n *\n * On the other side of the stack, there is [Vue plugin](https://github.com/graffiti-garden/wrapper-vue/)\n * that wraps around this API to provide reactivity. Other high-level libraries\n * will be available in the future.\n *\n * ## Overview\n *\n * Graffiti provides applications with methods to create and store data\n * on behalf of their users using standard CRUD operations:\n * {@link put}, {@link get}, {@link patch}, and {@link delete}.\n * This data can represent both social artifacts (e.g. posts, profiles) and\n * activities (e.g. likes, follows) and is stored as JSON.\n *\n * The social aspect of Graffiti comes from the {@link discover} method\n * which allows applications to find objects that other users made.\n * It is a lot like a traditional query operation, but it only\n * returns objects that have been placed in particular\n * {@link GraffitiObjectBase.channels | `channels`}\n * specified by the discovering application.\n *\n * Graffiti builds on well known concepts and standards wherever possible.\n * JSON Objects can be typed with [JSON Schema](https://json-schema.org/) and patches\n * can be applied with [JSON Patch](https://jsonpatch.com).\n * For interoperability between Graffiti applications, we recommend that\n * objects use established properties from the\n * [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/) when available,\n * however it is always possible to create additional properties, contributing\n * to the broader [folksonomy](https://en.wikipedia.org/wiki/Folksonomy).\n *\n * {@link GraffitiObjectBase.channels | `channels`} are one of the major concepts\n * unique to Graffiti along with *interaction relativity*.\n * Channels create boundaries between public spaces and work to prevent\n * [context collapse](https://en.wikipedia.org/wiki/Context_collapse)\n * even in a highly interoperable environment.\n * Interaction relativity means that all interactions between users are\n * actually atomic single-user operations that can be interpreted in different ways,\n * which also supports interoperability and pluralism.\n *\n * ### Channels\n *\n * {@link GraffitiObjectBase.channels | `channels`}\n * are a way for the creators of social data to express the intended audience of their\n * data. When a user creates data using the {@link put} method, they\n * can place their data in one or more channels.\n * Content consumers using the {@link discover} method will only see data\n * contained in one of the channels they specify.\n *\n * While many channels may be public, they partition\n * the public into different \"contexts\", mitigating the\n * phenomenon of [context collapse](https://en.wikipedia.org/wiki/Context_collapse) or the \"flattening of multiple audiences.\"\n * Any [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) can be used as a channel, and so channels can represent people,\n * comment threads, topics, places (real or virtual), pieces of media, and more.\n *\n * For example, consider a comment on a post. If we place that comment in the channel\n * represented by the post's URI, then only people viewing the post will know to\n * look in that channel, giving it visibility akin to a comment on a blog post\n * or comment on Instagram ([since 2019](https://www.buzzfeednews.com/article/katienotopoulos/instagrams-following-activity-tab-is-going-away)).\n * If we also place the comment in the channel represented by the commenter's URI (their\n * {@link GraffitiObjectBase.actor | `actor` URI}), then people viewing the commenter's profile\n * will also see the comment, giving it more visibility, like a reply on Twitter.\n * If we *only* place the comment in the channel represented by the commenter's URI, then\n * it becomes like a quote tweet ([prior to 2020](https://x.com/Support/status/1300555325750292480)),\n * where the comment is only visible to the commenter's followers but not the audience\n * of the original post.\n *\n * The channel model differs from other models of communication such as the\n * [actor model](https://www.w3.org/TR/activitypub/#Overview) used by ActivityPub,\n * the protocol underlying Mastodon, or the [firehose model](https://bsky.social/about/blog/5-5-2023-federation-architecture)\n * used by the AT Protocol, the protocol underlying BlueSky.\n * The actor model is a fusion of direct messaging (like Email) and broadcasting\n * (like RSS) and works well for follow-based communication but struggles\n * to pass information via other rendez-vous.\n * In the actor model, even something as simple as comments can be\n * [very tricky and require server \"side effects\"](https://seb.jambor.dev/posts/understanding-activitypub-part-3-the-state-of-mastodon/).\n * The firehose model dumps all user data into one public database,\n * which doesn't allow for the carving out of different contexts that we did in our comment\n * example above. In the firehose model a comment will always be visible to *both* the original post's audience and\n * the commenter's followers.\n *\n * In some sense, channels provide a sort of \"social access control\" by forming\n * expectations about the audiences of different online spaces.\n * As a real world analogy, oftentimes support groups, such as alcoholics\n * anonymous, are open to the public but people in those spaces feel comfortable sharing intimate details\n * because they have expectations about the other people attending.\n * If someone malicious went to support groups just to spread people's secrets,\n * they would be shamed for violating these norms.\n * Similarly, in Graffiti, while you could spider public channels like a search engine\n * to find content about a person, revealing that you've done such a thing\n * would be shameful.\n *\n * Still, social access control is not perfect and so in situations where privacy is important,\n * objects can also be given\n * an {@link GraffitiObjectBase.allowed | `allowed`} list.\n * For example, to send someone a direct message you should put an object representing\n * that message in the channel that represents them (their {@link GraffitiObjectBase.actor | `actor` URI}),\n * so they can find it, *and* set the `allowed` field to only include the recipient,\n * so only they can read it.\n *\n * ### Interaction relativity\n *\n * Interaction relativity posits that \"interaction between two individuals only\n * exists relative to an observer,\" or equivalently, all interaction is [reified](https://en.wikipedia.org/wiki/Reification_(computer_science)).\n * For example, if one user creates a post and another user wants to \"like\" that post,\n * their like is not modifying the original post, it is simply another data object that points\n * to the post being liked, via its {@link locationToUri | URI}.\n *\n * ```json\n * {\n * activity: 'like',\n * target: 'uri-of-the-post-i-like',\n * actor: 'my-user-id'\n * }\n * ```\n *\n * In Graffiti, all interactions including *moderation* and *collaboration* are relative.\n * This means that applications can freely choose which interactions\n * they want to express to their users and how.\n * For example, one application could have a single fixed moderator,\n * another could allow users to choose which moderators they would like filter their content\n * like [Bluesky's stackable moderation](https://bsky.social/about/blog/03-12-2024-stackable-moderation),\n * and another could implement a fully democratic system like [PolicyKit](https://policykit.org/).\n * Each of these applications is one interpretation of the underlying refieid user interactions and\n * users can freely switch between them.\n *\n * Interaction relativy also allows applications to introduce new sorts of interactions\n * without having to coordinate with all the other existing applications,\n * keeping the ecosystem flexible and interoperable.\n * For example, an application could [add a \"Trust\" button to posts](https://social.cs.washington.edu/pub_details.html?id=trustnet)\n * and use it assess the truthfulness of posts made on applications across Graffiti.\n * New sorts of interactions like these can be smoothly absorbed by the broader ecosystem\n * as a [folksonomy](https://en.wikipedia.org/wiki/Folksonomy).\n *\n * Interactivy relativity is realized in Graffiti through two design decisions:\n * 1. The creators of objects can only modify their own objects. It is important for\n * users to be able to change and delete their own content to respect their\n * [right to be forgotten](https://en.wikipedia.org/wiki/Right_to_be_forgotten),\n * but beyond self-correction and self-censorship all other interaction is reified.\n * Many interactions can be reified via pointers, as in the \"like\" example above, and collaborative\n * edits can be refieid via [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type).\n * 2. No one owns channels. Unlike IRC/Slack channels or [Matrix rooms](https://matrix.org/docs/matrix-concepts/rooms_and_events/),\n * anyone can post to any channel, so long as they know the URI of that channel.\n * It is up to applications to hide content from channels either according to manual\n * filters or in response to user action.\n * For example, a user may create a post with the flag `disableReplies`.\n * Applications could then filter out any content from the replies channel\n * that the original poster has not specifically approved.\n *\n * ## Implementing the API\n *\n * To implement the API, first install it:\n *\n * ```bash\n * npm install @graffiti-garden/api\n * ```\n *\n * Then create a class that extends the `Graffiti` class and implement the abstract methods.\n *\n * ```typescript\n * import { Graffiti } from \"@graffiti-garden/api\";\n *\n * class MyGraffitiImplementation extends Graffiti {\n * // Implement the abstract methods here\n * }\n * ```\n * ### Testing\n *\n * We have written a number of unit tests written with [vitest](https://vitest.dev/)\n * that can be used to verify implementations of the API.\n * To use them, create a test file in that ends in `*.spec.ts` and format it as follows:\n *\n * ```typescript\n * import { graffitiCRUDTests } from \"@graffiti-garden/api/tests\";\n *\n * const useGraffiti = () => new MyGraffitiImplementation();\n * // Fill in with implementation-specific information\n * // to provide to valid actor sessions for the tests\n * // to use as identities.\n * const useSession1 = () => ({ actor: \"someone\" });\n * const useSession2 = () => ({ actor: \"someoneelse\" });\n *\n * // Run the tests\n * graffitiCRUDTests(useGraffiti, useSession1, useSession2);\n * ```\n *\n * Then run the tests in the root of your directory with:\n *\n * ```bash\n * npx vitest\n * ```\n *\n * ## Building the Documentation\n *\n * To build the [TypeDoc](https://typedoc.org/) documentation, run the following commands:\n *\n * ```bash\n * npm run install\n * npm run docs\n * ```\n *\n * Then run a local server to view the documentation:\n *\n * ```bash\n * cd docs\n * npx http-server\n * ```\n *\n * ## TODO\n *\n * - Implement scope.\n *\n * @groupDescription CRUD Methods\n * Methods for {@link put | creating}, {@link get | reading}, {@link patch | updating},\n * and {@link delete | deleting} {@link GraffitiObjectBase | Graffiti objects}.\n * @groupDescription Query Methods\n * Methods that retrieve or accumulate information about multiple {@link GraffitiObjectBase | Graffiti objects} at a time.\n * @groupDescription Session Management\n * Methods and properties for logging in and out of a Graffiti implementation.\n * @groupDescription Utilities\n * Methods for for converting Graffiti objects to and from URIs.\n */\nexport abstract class Graffiti {\n /**\n * Converts a {@link GraffitiLocation} object containing a\n * {@link GraffitiObjectBase.name | `name`}, {@link GraffitiObjectBase.actor | `actor`},\n * and {@link GraffitiObjectBase.source | `source`} into a globally unique URI.\n * The form of this URI is implementation dependent.\n *\n * Its exact inverse is {@link uriToLocation}.\n *\n * @group Utilities\n */\n abstract locationToUri(location: GraffitiLocation): string;\n\n /**\n * Parses a globally unique Graffiti URI into a {@link GraffitiLocation}\n * object containing a {@link GraffitiObjectBase.name | `name`},\n * {@link GraffitiObjectBase.actor | `actor`}, and {@link GraffitiObjectBase.source | `source`}.\n *\n * Its exact inverse is {@link locationToUri}.\n *\n * @group Utilities\n */\n abstract uriToLocation(uri: string): GraffitiLocation;\n\n /**\n * An alias of {@link locationToUri}\n *\n * @group Utilities\n */\n objectToUri(object: GraffitiObjectBase) {\n return this.locationToUri(object);\n }\n\n /**\n * Creates a new {@link GraffitiObjectBase | object} or replaces an existing object.\n * An object can only be replaced by the same {@link GraffitiObjectBase.actor | `actor`}\n * that created it.\n *\n * Replacement occurs when the {@link GraffitiLocation} properties of the supplied object\n * ({@link GraffitiObjectBase.name | `name`}, {@link GraffitiObjectBase.actor | `actor`},\n * and {@link GraffitiObjectBase.source | `source`}) exactly match the location of an existing object.\n *\n * @returns The object that was replaced if one exists or an object with\n * with a `null` {@link GraffitiObjectBase.value | `value`} if this method\n * created a new object.\n * The object will have a {@link GraffitiObjectBase.tombstone | `tombstone`}\n * field set to `true` and a {@link GraffitiObjectBase.lastModified | `lastModified`}\n * field updated to the time of replacement/creation.\n *\n * @group CRUD Methods\n */\n abstract put<Schema>(\n /**\n * The object to be put. This object is statically type-checked against the [JSON schema](https://json-schema.org/) that can be optionally provided\n * as the generic type parameter. We highly recommend providing a schema to\n * ensure that the PUT object matches subsequent {@link get} or {@link discover}\n * methods.\n */\n object: GraffitiPutObject<Schema>,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Retrieves an object from a given location.\n *\n * The retrieved object is type-checked against the provided [JSON schema](https://json-schema.org/)\n * otherwise a {@link GraffitiErrorSchemaMismatch} is thrown.\n *\n * If the object existed but has since been deleted,\n * or the retrieving {@link GraffitiObjectBase.actor | `actor`}\n * was {@link GraffitiObjectBase.allowed | `allowed`} to access\n * the object but now isn't, this method will return the latest\n * version of the object that the {@link GraffitiObjectBase.actor | `actor`}\n * was allowed to access with its {@link GraffitiObjectBase.tombstone | `tombstone`}\n * set to `true`, so long as that version is still cached.\n *\n * Otherwise, if the object never existed, or the\n * retrieving {@link GraffitiObjectBase.actor | `actor`} was never\n * {@link GraffitiObjectBase.allowed | `allowed`} to access it, or if\n * the object was changed long enough ago that its history has been\n * purged from the cache, a {@link GraffitiErrorNotFound} is thrown.\n * The rate at which the cache is purged is implementation dependent.\n * See the `tombstoneReturn` property returned by {@link discover}.\n *\n * @group CRUD Methods\n */\n abstract get<Schema extends JSONSchema4>(\n /**\n * The location of the object to get.\n */\n locationOrUri: GraffitiLocation | string,\n /**\n * The JSON schema to validate the retrieved object against.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}\n * property must be `undefined`.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Patches an existing object at a given location.\n * The patching {@link GraffitiObjectBase.actor | `actor`} must be the same as the\n * `actor` that created the object.\n *\n * @returns The object that was deleted if one exists or an object with\n * with a `null` {@link GraffitiObjectBase.value | `value`} otherwise.\n * The object will have a {@link GraffitiObjectBase.tombstone | `tombstone`}\n * field set to `true` and a {@link GraffitiObjectBase.lastModified | `lastModified`}\n * field updated to the time of deletion.\n *\n * @group CRUD Methods\n */\n abstract patch(\n /**\n * A collection of [JSON Patch](https://jsonpatch.com) operations\n * to apply to the object. See {@link GraffitiPatch} for more information.\n */\n patch: GraffitiPatch,\n /**\n * The location of the object to patch.\n */\n locationOrUri: GraffitiLocation | string,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Deletes an object from a given location.\n * The deleting {@link GraffitiObjectBase.actor | `actor`} must be the same as the\n * `actor` that created the object.\n *\n * If the object does not exist or has already been deleted,\n * {@link GraffitiErrorNotFound} is thrown.\n *\n * @returns The object that was deleted if one exists or an object with\n * with a `null` {@link GraffitiObjectBase.value | `value`} otherwise.\n * The object will have a {@link GraffitiObjectBase.tombstone | `tombstone`}\n * field set to `true` and a {@link GraffitiObjectBase.lastModified | `lastModified`}\n * field updated to the time of deletion.\n *\n * @group CRUD Methods\n */\n abstract delete(\n /**\n * The location of the object to delete.\n */\n locationOrUri: GraffitiLocation | string,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Discovers objects created by any user that are contained\n * in at least one of the given {@link GraffitiObjectBase.channels | `channels`}\n * and match the given [JSON Schema](https://json-schema.org).\n *\n * Objects are returned asynchronously as they are discovered but the stream\n * will end once all leads have been exhausted.\n * The method must be polled again for new objects.\n *\n * `discover` will not return objects that the {@link GraffitiObjectBase.actor | `actor`}\n * is not {@link GraffitiObjectBase.allowed | `allowed`} to access.\n * If the actor is not the creator of a discovered object,\n * the allowed list will be masked to only contain the querying actor if the\n * allowed list is not `undefined` (public). Additionally, if the actor is not the\n * creator of a discovered object, any {@link GraffitiObjectBase.channels | `channels`}\n * not specified by the `discover` method will not be revealed. This masking happens\n * before the supplied schema is applied.\n *\n * {@link discover} can be used in conjunction with {@link synchronizeDiscover}\n * to provide a responsive and consistent user experience.\n *\n * Since different implementations may fetch data from multiple sources there is\n * no guarentee on the order that objects are returned in. Additionally, the method\n * will return objects that have been deleted but with a\n * {@link GraffitiObjectBase.tombstone | `tombstone`} field set to `true` for\n * cache invalidation purposes.\n * The final `return()` value of the stream includes a `tombstoneRetention`\n * property that represents the minimum amount of time,\n * in milliseconds, that an application will retain and return tombstones for objects that\n * have been deleted.\n *\n * When repolling, the {@link GraffitiObjectBase.lastModified | `lastModified`}\n * field can be queried via the schema to\n * only fetch objects that have been modified since the last poll.\n * Such queries should only be done if the time since the last poll\n * is less than the `tombstoneRetention` value of that poll, otherwise the tombstones\n * for objects that have been deleted may not be returned.\n *\n * ```json\n * {\n * \"properties\": {\n * \"lastModified\": {\n * \"minimum\": LAST_RETRIEVED_TIME\n * }\n * }\n * }\n * ```\n *\n * `discover` needs to be polled for new data because live updates to\n * an application can be visually distracting or lead to toxic engagement.\n * If and when an application wants real-time updates, such as in a chat\n * application, application authors must be intentional about their polling.\n *\n * Implementers should be aware that some users may applications may try to poll\n * {@link discover} repetitively. You can deal with this by rate limiting or\n * preemptively fetching data via a bidirectional channel, like a WebSocket.\n * Additionally, implementers should probably index the `lastModified` field\n * to speed up responses to schemas like the one above.\n *\n * @returns A stream of objects that match the given {@link GraffitiObjectBase.channels | `channels`}\n * and [JSON Schema](https://json-schema.org).\n *\n * @group Query Methods\n */\n abstract discover<Schema extends JSONSchema4>(\n /**\n * The {@link GraffitiObjectBase.channels | `channels`} that objects must be associated with.\n */\n channels: string[],\n /**\n * A [JSON Schema](https://json-schema.org) that objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}\n * property will be returned.\n */\n session?: GraffitiSession | null,\n ): GraffitiStream<\n GraffitiObject<Schema>,\n {\n tombstoneRetention: number;\n }\n >;\n\n /**\n * Discovers objects **not** contained in any\n * {@link GraffitiObjectBase.channels | `channels`}\n * that were created by the querying {@link GraffitiObjectBase.actor | `actor`}\n * and match the given [JSON Schema](https://json-schema.org).\n * Unlike {@link discover}, this method will not return objects created by other users.\n *\n * This method is not useful for most applications, but necessary for\n * getting a global view of all a user's Graffiti data or debugging\n * channel usage.\n *\n * It's return value is the same as {@link discover}.\n *\n * @group Query Methods\n */\n abstract recoverOrphans<Schema extends JSONSchema4>(\n /**\n * A [JSON Schema](https://json-schema.org) that orphaned objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): GraffitiStream<\n GraffitiObject<Schema>,\n {\n tombstoneRetention: number;\n }\n >;\n\n /**\n * Returns statistics about all the {@link GraffitiObjectBase.channels | `channels`}\n * that an {@link GraffitiObjectBase.actor | `actor`} has posted to.\n * This is not very useful for most applications, but\n * necessary for certain applications where a user wants a\n * global view of all their Graffiti data or to debug\n * channel usage.\n *\n * @group Query Methods\n *\n * @returns A stream of statistics for each {@link GraffitiObjectBase.channels | `channel`}\n * that the {@link GraffitiObjectBase.actor | `actor`} has posted to.\n */\n abstract channelStats(\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): GraffitiStream<ChannelStats>;\n\n /**\n * Begins the login process. Depending on the implementation, this may\n * involve redirecting the user to a login page or opening a popup,\n * so it should always be called in response to a user action.\n *\n * The {@link GraffitiSession | session} object is returned\n * asynchronously via {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLoginEvent} with event type `login`.\n *\n * @group Session Management\n */\n abstract login(\n /**\n * Suggestions for the permissions that the\n * login process should grant. The login process may not\n * provide the exact proposed permissions.\n */\n proposal?: {\n /**\n * A suggested actor to login as. For example, if a user tries to\n * edit a post but are not logged in, the interface can infer that\n * they might want to log in as the actor who created the post\n * they are attempting to edit.\n *\n * Even if provided, the implementation should allow the user\n * to log in as a different actor if they choose.\n */\n actor?: string;\n /**\n * A yet to be defined permissions scope. An application may use\n * this to indicate the minimum necessary scope needed to\n * operate. For example, it may need to be able read private\n * messages from a certain set of channels, or write messages that\n * follow a particular schema.\n *\n * The login process should make it clear what scope an application\n * is requesting and allow the user to enhance or reduce that\n * scope as necessary.\n */\n scope?: {};\n },\n ): Promise<void>;\n\n /**\n * Begins the logout process. Depending on the implementation, this may\n * involve redirecting the user to a logout page or opening a popup,\n * so it should always be called in response to a user action.\n *\n * A confirmation will be returned asynchronously via\n * {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLogoutEvent} as event type `logout`.\n *\n * @group Session Management\n */\n abstract logout(\n /**\n * The {@link GraffitiSession | session} object to logout.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * An event target that can be used to listen for the following\n * events and they're corresponding event types:\n * - `login` - {@link GraffitiLoginEvent}\n * - `logout` - {@link GraffitiLogoutEvent}\n * - `initialized` - {@link GraffitiSessionInitializedEvent}\n *\n * @group Session Management\n */\n abstract readonly sessionEvents: EventTarget;\n\n /**\n * This method has the same signature as {@link discover} but listens for\n * changes made via {@link put}, {@link patch}, and {@link delete} or\n * fetched from {@link get}, {@link discover}, and {@link recoverOrphans}\n * and then streams appropriate changes to provide a responsive and\n * consistent user experience.\n *\n * Unlike {@link discover}, this method continuously listens for changes\n * and will not terminate unless the user calls the `return` method on the iterator\n * or `break`s out of the loop.\n *\n * Example 1: Suppose a user publishes a post using {@link put}. If the feed\n * displaying that user's posts is using {@link synchronizeDiscover} to listen for changes,\n * then the user's new post will instantly appear in their feed, giving the UI a\n * responsive feel.\n *\n * Example 2: Suppose one of a user's friends changes their name. As soon as the\n * user's application receives one notice of that change (using {@link get}\n * or {@link discover}), then {@link synchronizeDiscover} listeners can be used to update\n * all instance's of that friend's name in the user's application instantly,\n * providing a consistent user experience.\n *\n * @group Synchronize Methods\n */\n abstract synchronizeDiscover<Schema extends JSONSchema4>(\n /**\n * The {@link GraffitiObjectBase.channels | `channels`} that the objects must be associated with.\n */\n channels: string[],\n /**\n * A [JSON Schema](https://json-schema.org) that objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}\n * property will be returned.\n */\n session?: GraffitiSession | null,\n ): GraffitiStream<GraffitiObject<Schema>>;\n\n /**\n * This method has the same signature as {@link get} but, like {@link synchronizeDiscover},\n * it listens for changes made via {@link put}, {@link patch}, and {@link delete} or\n * fetched from {@link get}, {@link discover}, and {@link recoverOrphans} and then\n * streams appropriate changes to provide a responsive and consistent user experience.\n *\n * Unlike {@link get}, which returns a single result, this method continuously\n * listens for changes which are output as an asynchronous {@link GraffitiStream}.\n *\n * @group Synchronize Methods\n */\n abstract synchronizeGet<Schema extends JSONSchema4>(\n /**\n * The location of the object to get.\n */\n locationOrUri: GraffitiLocation | string,\n /**\n * The JSON schema to validate the retrieved object against.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}\n * property must be `undefined`.\n */\n session?: GraffitiSession | null,\n ): GraffitiStream<GraffitiObject<Schema>>;\n\n /**\n * This method has the same signature as {@link recoverOrphans} but,\n * like {@link synchronizeDiscover}, it listens for changes made via\n * {@link put}, {@link patch}, and {@link delete} or fetched from\n * {@link get}, {@link discover}, and {@link recoverOrphans} and then\n * streams appropriate changes to provide a responsive and consistent user experience.\n *\n * Unlike {@link recoverOrphans}, this method continuously listens for changes\n * and will not terminate unless the user calls the `return` method on the iterator\n * or `break`s out of the loop.\n *\n * @group Synchronize Methods\n */\n abstract synchronizeRecoverOrphans<Schema extends JSONSchema4>(\n /**\n * A [JSON Schema](https://json-schema.org) that orphaned objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): GraffitiStream<GraffitiObject<Schema>>;\n}\n\n/**\n * This is a factory function that produces an instance of\n * the {@link Graffiti} class. Since the Graffiti class is\n * abstract, factory functions provide an easy way to\n * swap out different implementations.\n */\nexport type GraffitiFactory = () => Graffiti;\n", "export class GraffitiErrorUnauthorized extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorUnauthorized\";\n Object.setPrototypeOf(this, GraffitiErrorUnauthorized.prototype);\n }\n}\n\nexport class GraffitiErrorForbidden extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorForbidden\";\n Object.setPrototypeOf(this, GraffitiErrorForbidden.prototype);\n }\n}\n\nexport class GraffitiErrorNotFound extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorNotFound\";\n Object.setPrototypeOf(this, GraffitiErrorNotFound.prototype);\n }\n}\n\nexport class GraffitiErrorInvalidSchema extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorInvalidSchema\";\n Object.setPrototypeOf(this, GraffitiErrorInvalidSchema.prototype);\n }\n}\n\nexport class GraffitiErrorSchemaMismatch extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorSchemaMismatch\";\n Object.setPrototypeOf(this, GraffitiErrorSchemaMismatch.prototype);\n }\n}\n\nexport class GraffitiErrorPatchTestFailed extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorPatchTestFailed\";\n Object.setPrototypeOf(this, GraffitiErrorPatchTestFailed.prototype);\n }\n}\n\nexport class GraffitiErrorPatchError extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorPatchError\";\n Object.setPrototypeOf(this, GraffitiErrorPatchError.prototype);\n }\n}\n\nexport class GraffitiErrorInvalidUri extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorInvalidUri\";\n Object.setPrototypeOf(this, GraffitiErrorInvalidUri.prototype);\n }\n}\n"],
|
|
5
|
+
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,cAAAE,EAAA,2BAAAC,EAAA,+BAAAC,EAAA,4BAAAC,EAAA,0BAAAC,EAAA,4BAAAC,EAAA,iCAAAC,EAAA,gCAAAC,EAAA,8BAAAC,IAAA,eAAAC,EAAAX,GC6PO,IAAeY,EAAf,KAAwB,CA6B7B,YAAYC,EAA4B,CACtC,OAAO,KAAK,cAAcA,CAAM,CAClC,CA0bF,ECttBO,IAAMC,EAAN,MAAMC,UAAkC,KAAM,CACnD,YAAYC,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,4BACZ,OAAO,eAAe,KAAMD,EAA0B,SAAS,CACjE,CACF,EAEaE,EAAN,MAAMC,UAA+B,KAAM,CAChD,YAAYF,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,yBACZ,OAAO,eAAe,KAAME,EAAuB,SAAS,CAC9D,CACF,EAEaC,EAAN,MAAMC,UAA8B,KAAM,CAC/C,YAAYJ,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,wBACZ,OAAO,eAAe,KAAMI,EAAsB,SAAS,CAC7D,CACF,EAEaC,EAAN,MAAMC,UAAmC,KAAM,CACpD,YAAYN,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,6BACZ,OAAO,eAAe,KAAMM,EAA2B,SAAS,CAClE,CACF,EAEaC,EAAN,MAAMC,UAAoC,KAAM,CACrD,YAAYR,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,8BACZ,OAAO,eAAe,KAAMQ,EAA4B,SAAS,CACnE,CACF,EAEaC,EAAN,MAAMC,UAAqC,KAAM,CACtD,YAAYV,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,+BACZ,OAAO,eAAe,KAAMU,EAA6B,SAAS,CACpE,CACF,EAEaC,EAAN,MAAMC,UAAgC,KAAM,CACjD,YAAYZ,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,0BACZ,OAAO,eAAe,KAAMY,EAAwB,SAAS,CAC/D,CACF,EAEaC,EAAN,MAAMC,UAAgC,KAAM,CACjD,YAAYd,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,0BACZ,OAAO,eAAe,KAAMc,EAAwB,SAAS,CAC/D,CACF",
|
|
6
|
+
"names": ["src_exports", "__export", "Graffiti", "GraffitiErrorForbidden", "GraffitiErrorInvalidSchema", "GraffitiErrorInvalidUri", "GraffitiErrorNotFound", "GraffitiErrorPatchError", "GraffitiErrorPatchTestFailed", "GraffitiErrorSchemaMismatch", "GraffitiErrorUnauthorized", "__toCommonJS", "Graffiti", "object", "GraffitiErrorUnauthorized", "_GraffitiErrorUnauthorized", "message", "GraffitiErrorForbidden", "_GraffitiErrorForbidden", "GraffitiErrorNotFound", "_GraffitiErrorNotFound", "GraffitiErrorInvalidSchema", "_GraffitiErrorInvalidSchema", "GraffitiErrorSchemaMismatch", "_GraffitiErrorSchemaMismatch", "GraffitiErrorPatchTestFailed", "_GraffitiErrorPatchTestFailed", "GraffitiErrorPatchError", "_GraffitiErrorPatchError", "GraffitiErrorInvalidUri", "_GraffitiErrorInvalidUri"]
|
|
7
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
var r=class{objectToUri(t){return this.locationToUri(t)}};var s=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorUnauthorized",Object.setPrototypeOf(this,e.prototype)}},i=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorForbidden",Object.setPrototypeOf(this,e.prototype)}},a=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorNotFound",Object.setPrototypeOf(this,e.prototype)}},o=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorInvalidSchema",Object.setPrototypeOf(this,e.prototype)}},c=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorSchemaMismatch",Object.setPrototypeOf(this,e.prototype)}},n=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorPatchTestFailed",Object.setPrototypeOf(this,e.prototype)}},f=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorPatchError",Object.setPrototypeOf(this,e.prototype)}},m=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorInvalidUri",Object.setPrototypeOf(this,e.prototype)}};export{r as Graffiti,i as GraffitiErrorForbidden,o as GraffitiErrorInvalidSchema,m as GraffitiErrorInvalidUri,a as GraffitiErrorNotFound,f as GraffitiErrorPatchError,n as GraffitiErrorPatchTestFailed,c as GraffitiErrorSchemaMismatch,s as GraffitiErrorUnauthorized};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/1-api.ts", "../src/3-errors.ts"],
|
|
4
|
+
"sourcesContent": ["import type {\n GraffitiLocation,\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiPatch,\n GraffitiSession,\n GraffitiPutObject,\n GraffitiStream,\n ChannelStats,\n} from \"./2-types\";\nimport type { JSONSchema4 } from \"json-schema\";\n\n/**\n * This API describes a small but powerful set of methods that\n * can be used to create many different kinds of social media applications,\n * all of which can interoperate.\n * These methods should satisfy all of an application's needs for\n * the communication, storage, and access management of social data.\n * The rest of the application can be built with standard client-side\n * user interface tools to present and interact with the data \u2014\n * no server code necessary.\n * The Typescript source for this API is available at\n * [graffiti-garden/api](https://github.com/graffiti-garden/api).\n *\n * There are several different implementations of this Graffiti API available,\n * including a [federated implementation](https://github.com/graffiti-garden/implementation-federated),\n * that lets users choose where their data is stored,\n * and a [local implementation](https://github.com/graffiti-garden/implementation-local)\n * that can be used for testing and development. In our design of Graffiti, this API is our\n * primary focus as it is the layer that shapes the experience\n * of developing applications. While different implementations can provide tradeoffs between\n * other important properties (e.g. privacy, security, scalability), those properties\n * are useless if the system as a whole doesn't expose useful functionality to developers.\n *\n * On the other side of the stack, there is [Vue plugin](https://github.com/graffiti-garden/wrapper-vue/)\n * that wraps around this API to provide reactivity. Other high-level libraries\n * will be available in the future.\n *\n * ## Overview\n *\n * Graffiti provides applications with methods to create and store data\n * on behalf of their users using standard CRUD operations:\n * {@link put}, {@link get}, {@link patch}, and {@link delete}.\n * This data can represent both social artifacts (e.g. posts, profiles) and\n * activities (e.g. likes, follows) and is stored as JSON.\n *\n * The social aspect of Graffiti comes from the {@link discover} method\n * which allows applications to find objects that other users made.\n * It is a lot like a traditional query operation, but it only\n * returns objects that have been placed in particular\n * {@link GraffitiObjectBase.channels | `channels`}\n * specified by the discovering application.\n *\n * Graffiti builds on well known concepts and standards wherever possible.\n * JSON Objects can be typed with [JSON Schema](https://json-schema.org/) and patches\n * can be applied with [JSON Patch](https://jsonpatch.com).\n * For interoperability between Graffiti applications, we recommend that\n * objects use established properties from the\n * [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/) when available,\n * however it is always possible to create additional properties, contributing\n * to the broader [folksonomy](https://en.wikipedia.org/wiki/Folksonomy).\n *\n * {@link GraffitiObjectBase.channels | `channels`} are one of the major concepts\n * unique to Graffiti along with *interaction relativity*.\n * Channels create boundaries between public spaces and work to prevent\n * [context collapse](https://en.wikipedia.org/wiki/Context_collapse)\n * even in a highly interoperable environment.\n * Interaction relativity means that all interactions between users are\n * actually atomic single-user operations that can be interpreted in different ways,\n * which also supports interoperability and pluralism.\n *\n * ### Channels\n *\n * {@link GraffitiObjectBase.channels | `channels`}\n * are a way for the creators of social data to express the intended audience of their\n * data. When a user creates data using the {@link put} method, they\n * can place their data in one or more channels.\n * Content consumers using the {@link discover} method will only see data\n * contained in one of the channels they specify.\n *\n * While many channels may be public, they partition\n * the public into different \"contexts\", mitigating the\n * phenomenon of [context collapse](https://en.wikipedia.org/wiki/Context_collapse) or the \"flattening of multiple audiences.\"\n * Any [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) can be used as a channel, and so channels can represent people,\n * comment threads, topics, places (real or virtual), pieces of media, and more.\n *\n * For example, consider a comment on a post. If we place that comment in the channel\n * represented by the post's URI, then only people viewing the post will know to\n * look in that channel, giving it visibility akin to a comment on a blog post\n * or comment on Instagram ([since 2019](https://www.buzzfeednews.com/article/katienotopoulos/instagrams-following-activity-tab-is-going-away)).\n * If we also place the comment in the channel represented by the commenter's URI (their\n * {@link GraffitiObjectBase.actor | `actor` URI}), then people viewing the commenter's profile\n * will also see the comment, giving it more visibility, like a reply on Twitter.\n * If we *only* place the comment in the channel represented by the commenter's URI, then\n * it becomes like a quote tweet ([prior to 2020](https://x.com/Support/status/1300555325750292480)),\n * where the comment is only visible to the commenter's followers but not the audience\n * of the original post.\n *\n * The channel model differs from other models of communication such as the\n * [actor model](https://www.w3.org/TR/activitypub/#Overview) used by ActivityPub,\n * the protocol underlying Mastodon, or the [firehose model](https://bsky.social/about/blog/5-5-2023-federation-architecture)\n * used by the AT Protocol, the protocol underlying BlueSky.\n * The actor model is a fusion of direct messaging (like Email) and broadcasting\n * (like RSS) and works well for follow-based communication but struggles\n * to pass information via other rendez-vous.\n * In the actor model, even something as simple as comments can be\n * [very tricky and require server \"side effects\"](https://seb.jambor.dev/posts/understanding-activitypub-part-3-the-state-of-mastodon/).\n * The firehose model dumps all user data into one public database,\n * which doesn't allow for the carving out of different contexts that we did in our comment\n * example above. In the firehose model a comment will always be visible to *both* the original post's audience and\n * the commenter's followers.\n *\n * In some sense, channels provide a sort of \"social access control\" by forming\n * expectations about the audiences of different online spaces.\n * As a real world analogy, oftentimes support groups, such as alcoholics\n * anonymous, are open to the public but people in those spaces feel comfortable sharing intimate details\n * because they have expectations about the other people attending.\n * If someone malicious went to support groups just to spread people's secrets,\n * they would be shamed for violating these norms.\n * Similarly, in Graffiti, while you could spider public channels like a search engine\n * to find content about a person, revealing that you've done such a thing\n * would be shameful.\n *\n * Still, social access control is not perfect and so in situations where privacy is important,\n * objects can also be given\n * an {@link GraffitiObjectBase.allowed | `allowed`} list.\n * For example, to send someone a direct message you should put an object representing\n * that message in the channel that represents them (their {@link GraffitiObjectBase.actor | `actor` URI}),\n * so they can find it, *and* set the `allowed` field to only include the recipient,\n * so only they can read it.\n *\n * ### Interaction relativity\n *\n * Interaction relativity posits that \"interaction between two individuals only\n * exists relative to an observer,\" or equivalently, all interaction is [reified](https://en.wikipedia.org/wiki/Reification_(computer_science)).\n * For example, if one user creates a post and another user wants to \"like\" that post,\n * their like is not modifying the original post, it is simply another data object that points\n * to the post being liked, via its {@link locationToUri | URI}.\n *\n * ```json\n * {\n * activity: 'like',\n * target: 'uri-of-the-post-i-like',\n * actor: 'my-user-id'\n * }\n * ```\n *\n * In Graffiti, all interactions including *moderation* and *collaboration* are relative.\n * This means that applications can freely choose which interactions\n * they want to express to their users and how.\n * For example, one application could have a single fixed moderator,\n * another could allow users to choose which moderators they would like filter their content\n * like [Bluesky's stackable moderation](https://bsky.social/about/blog/03-12-2024-stackable-moderation),\n * and another could implement a fully democratic system like [PolicyKit](https://policykit.org/).\n * Each of these applications is one interpretation of the underlying refieid user interactions and\n * users can freely switch between them.\n *\n * Interaction relativy also allows applications to introduce new sorts of interactions\n * without having to coordinate with all the other existing applications,\n * keeping the ecosystem flexible and interoperable.\n * For example, an application could [add a \"Trust\" button to posts](https://social.cs.washington.edu/pub_details.html?id=trustnet)\n * and use it assess the truthfulness of posts made on applications across Graffiti.\n * New sorts of interactions like these can be smoothly absorbed by the broader ecosystem\n * as a [folksonomy](https://en.wikipedia.org/wiki/Folksonomy).\n *\n * Interactivy relativity is realized in Graffiti through two design decisions:\n * 1. The creators of objects can only modify their own objects. It is important for\n * users to be able to change and delete their own content to respect their\n * [right to be forgotten](https://en.wikipedia.org/wiki/Right_to_be_forgotten),\n * but beyond self-correction and self-censorship all other interaction is reified.\n * Many interactions can be reified via pointers, as in the \"like\" example above, and collaborative\n * edits can be refieid via [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type).\n * 2. No one owns channels. Unlike IRC/Slack channels or [Matrix rooms](https://matrix.org/docs/matrix-concepts/rooms_and_events/),\n * anyone can post to any channel, so long as they know the URI of that channel.\n * It is up to applications to hide content from channels either according to manual\n * filters or in response to user action.\n * For example, a user may create a post with the flag `disableReplies`.\n * Applications could then filter out any content from the replies channel\n * that the original poster has not specifically approved.\n *\n * ## Implementing the API\n *\n * To implement the API, first install it:\n *\n * ```bash\n * npm install @graffiti-garden/api\n * ```\n *\n * Then create a class that extends the `Graffiti` class and implement the abstract methods.\n *\n * ```typescript\n * import { Graffiti } from \"@graffiti-garden/api\";\n *\n * class MyGraffitiImplementation extends Graffiti {\n * // Implement the abstract methods here\n * }\n * ```\n * ### Testing\n *\n * We have written a number of unit tests written with [vitest](https://vitest.dev/)\n * that can be used to verify implementations of the API.\n * To use them, create a test file in that ends in `*.spec.ts` and format it as follows:\n *\n * ```typescript\n * import { graffitiCRUDTests } from \"@graffiti-garden/api/tests\";\n *\n * const useGraffiti = () => new MyGraffitiImplementation();\n * // Fill in with implementation-specific information\n * // to provide to valid actor sessions for the tests\n * // to use as identities.\n * const useSession1 = () => ({ actor: \"someone\" });\n * const useSession2 = () => ({ actor: \"someoneelse\" });\n *\n * // Run the tests\n * graffitiCRUDTests(useGraffiti, useSession1, useSession2);\n * ```\n *\n * Then run the tests in the root of your directory with:\n *\n * ```bash\n * npx vitest\n * ```\n *\n * ## Building the Documentation\n *\n * To build the [TypeDoc](https://typedoc.org/) documentation, run the following commands:\n *\n * ```bash\n * npm run install\n * npm run docs\n * ```\n *\n * Then run a local server to view the documentation:\n *\n * ```bash\n * cd docs\n * npx http-server\n * ```\n *\n * ## TODO\n *\n * - Implement scope.\n *\n * @groupDescription CRUD Methods\n * Methods for {@link put | creating}, {@link get | reading}, {@link patch | updating},\n * and {@link delete | deleting} {@link GraffitiObjectBase | Graffiti objects}.\n * @groupDescription Query Methods\n * Methods that retrieve or accumulate information about multiple {@link GraffitiObjectBase | Graffiti objects} at a time.\n * @groupDescription Session Management\n * Methods and properties for logging in and out of a Graffiti implementation.\n * @groupDescription Utilities\n * Methods for for converting Graffiti objects to and from URIs.\n */\nexport abstract class Graffiti {\n /**\n * Converts a {@link GraffitiLocation} object containing a\n * {@link GraffitiObjectBase.name | `name`}, {@link GraffitiObjectBase.actor | `actor`},\n * and {@link GraffitiObjectBase.source | `source`} into a globally unique URI.\n * The form of this URI is implementation dependent.\n *\n * Its exact inverse is {@link uriToLocation}.\n *\n * @group Utilities\n */\n abstract locationToUri(location: GraffitiLocation): string;\n\n /**\n * Parses a globally unique Graffiti URI into a {@link GraffitiLocation}\n * object containing a {@link GraffitiObjectBase.name | `name`},\n * {@link GraffitiObjectBase.actor | `actor`}, and {@link GraffitiObjectBase.source | `source`}.\n *\n * Its exact inverse is {@link locationToUri}.\n *\n * @group Utilities\n */\n abstract uriToLocation(uri: string): GraffitiLocation;\n\n /**\n * An alias of {@link locationToUri}\n *\n * @group Utilities\n */\n objectToUri(object: GraffitiObjectBase) {\n return this.locationToUri(object);\n }\n\n /**\n * Creates a new {@link GraffitiObjectBase | object} or replaces an existing object.\n * An object can only be replaced by the same {@link GraffitiObjectBase.actor | `actor`}\n * that created it.\n *\n * Replacement occurs when the {@link GraffitiLocation} properties of the supplied object\n * ({@link GraffitiObjectBase.name | `name`}, {@link GraffitiObjectBase.actor | `actor`},\n * and {@link GraffitiObjectBase.source | `source`}) exactly match the location of an existing object.\n *\n * @returns The object that was replaced if one exists or an object with\n * with a `null` {@link GraffitiObjectBase.value | `value`} if this method\n * created a new object.\n * The object will have a {@link GraffitiObjectBase.tombstone | `tombstone`}\n * field set to `true` and a {@link GraffitiObjectBase.lastModified | `lastModified`}\n * field updated to the time of replacement/creation.\n *\n * @group CRUD Methods\n */\n abstract put<Schema>(\n /**\n * The object to be put. This object is statically type-checked against the [JSON schema](https://json-schema.org/) that can be optionally provided\n * as the generic type parameter. We highly recommend providing a schema to\n * ensure that the PUT object matches subsequent {@link get} or {@link discover}\n * methods.\n */\n object: GraffitiPutObject<Schema>,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Retrieves an object from a given location.\n *\n * The retrieved object is type-checked against the provided [JSON schema](https://json-schema.org/)\n * otherwise a {@link GraffitiErrorSchemaMismatch} is thrown.\n *\n * If the object existed but has since been deleted,\n * or the retrieving {@link GraffitiObjectBase.actor | `actor`}\n * was {@link GraffitiObjectBase.allowed | `allowed`} to access\n * the object but now isn't, this method will return the latest\n * version of the object that the {@link GraffitiObjectBase.actor | `actor`}\n * was allowed to access with its {@link GraffitiObjectBase.tombstone | `tombstone`}\n * set to `true`, so long as that version is still cached.\n *\n * Otherwise, if the object never existed, or the\n * retrieving {@link GraffitiObjectBase.actor | `actor`} was never\n * {@link GraffitiObjectBase.allowed | `allowed`} to access it, or if\n * the object was changed long enough ago that its history has been\n * purged from the cache, a {@link GraffitiErrorNotFound} is thrown.\n * The rate at which the cache is purged is implementation dependent.\n * See the `tombstoneReturn` property returned by {@link discover}.\n *\n * @group CRUD Methods\n */\n abstract get<Schema extends JSONSchema4>(\n /**\n * The location of the object to get.\n */\n locationOrUri: GraffitiLocation | string,\n /**\n * The JSON schema to validate the retrieved object against.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}\n * property must be `undefined`.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Patches an existing object at a given location.\n * The patching {@link GraffitiObjectBase.actor | `actor`} must be the same as the\n * `actor` that created the object.\n *\n * @returns The object that was deleted if one exists or an object with\n * with a `null` {@link GraffitiObjectBase.value | `value`} otherwise.\n * The object will have a {@link GraffitiObjectBase.tombstone | `tombstone`}\n * field set to `true` and a {@link GraffitiObjectBase.lastModified | `lastModified`}\n * field updated to the time of deletion.\n *\n * @group CRUD Methods\n */\n abstract patch(\n /**\n * A collection of [JSON Patch](https://jsonpatch.com) operations\n * to apply to the object. See {@link GraffitiPatch} for more information.\n */\n patch: GraffitiPatch,\n /**\n * The location of the object to patch.\n */\n locationOrUri: GraffitiLocation | string,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Deletes an object from a given location.\n * The deleting {@link GraffitiObjectBase.actor | `actor`} must be the same as the\n * `actor` that created the object.\n *\n * If the object does not exist or has already been deleted,\n * {@link GraffitiErrorNotFound} is thrown.\n *\n * @returns The object that was deleted if one exists or an object with\n * with a `null` {@link GraffitiObjectBase.value | `value`} otherwise.\n * The object will have a {@link GraffitiObjectBase.tombstone | `tombstone`}\n * field set to `true` and a {@link GraffitiObjectBase.lastModified | `lastModified`}\n * field updated to the time of deletion.\n *\n * @group CRUD Methods\n */\n abstract delete(\n /**\n * The location of the object to delete.\n */\n locationOrUri: GraffitiLocation | string,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Discovers objects created by any user that are contained\n * in at least one of the given {@link GraffitiObjectBase.channels | `channels`}\n * and match the given [JSON Schema](https://json-schema.org).\n *\n * Objects are returned asynchronously as they are discovered but the stream\n * will end once all leads have been exhausted.\n * The method must be polled again for new objects.\n *\n * `discover` will not return objects that the {@link GraffitiObjectBase.actor | `actor`}\n * is not {@link GraffitiObjectBase.allowed | `allowed`} to access.\n * If the actor is not the creator of a discovered object,\n * the allowed list will be masked to only contain the querying actor if the\n * allowed list is not `undefined` (public). Additionally, if the actor is not the\n * creator of a discovered object, any {@link GraffitiObjectBase.channels | `channels`}\n * not specified by the `discover` method will not be revealed. This masking happens\n * before the supplied schema is applied.\n *\n * {@link discover} can be used in conjunction with {@link synchronizeDiscover}\n * to provide a responsive and consistent user experience.\n *\n * Since different implementations may fetch data from multiple sources there is\n * no guarentee on the order that objects are returned in. Additionally, the method\n * will return objects that have been deleted but with a\n * {@link GraffitiObjectBase.tombstone | `tombstone`} field set to `true` for\n * cache invalidation purposes.\n * The final `return()` value of the stream includes a `tombstoneRetention`\n * property that represents the minimum amount of time,\n * in milliseconds, that an application will retain and return tombstones for objects that\n * have been deleted.\n *\n * When repolling, the {@link GraffitiObjectBase.lastModified | `lastModified`}\n * field can be queried via the schema to\n * only fetch objects that have been modified since the last poll.\n * Such queries should only be done if the time since the last poll\n * is less than the `tombstoneRetention` value of that poll, otherwise the tombstones\n * for objects that have been deleted may not be returned.\n *\n * ```json\n * {\n * \"properties\": {\n * \"lastModified\": {\n * \"minimum\": LAST_RETRIEVED_TIME\n * }\n * }\n * }\n * ```\n *\n * `discover` needs to be polled for new data because live updates to\n * an application can be visually distracting or lead to toxic engagement.\n * If and when an application wants real-time updates, such as in a chat\n * application, application authors must be intentional about their polling.\n *\n * Implementers should be aware that some users may applications may try to poll\n * {@link discover} repetitively. You can deal with this by rate limiting or\n * preemptively fetching data via a bidirectional channel, like a WebSocket.\n * Additionally, implementers should probably index the `lastModified` field\n * to speed up responses to schemas like the one above.\n *\n * @returns A stream of objects that match the given {@link GraffitiObjectBase.channels | `channels`}\n * and [JSON Schema](https://json-schema.org).\n *\n * @group Query Methods\n */\n abstract discover<Schema extends JSONSchema4>(\n /**\n * The {@link GraffitiObjectBase.channels | `channels`} that objects must be associated with.\n */\n channels: string[],\n /**\n * A [JSON Schema](https://json-schema.org) that objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}\n * property will be returned.\n */\n session?: GraffitiSession | null,\n ): GraffitiStream<\n GraffitiObject<Schema>,\n {\n tombstoneRetention: number;\n }\n >;\n\n /**\n * Discovers objects **not** contained in any\n * {@link GraffitiObjectBase.channels | `channels`}\n * that were created by the querying {@link GraffitiObjectBase.actor | `actor`}\n * and match the given [JSON Schema](https://json-schema.org).\n * Unlike {@link discover}, this method will not return objects created by other users.\n *\n * This method is not useful for most applications, but necessary for\n * getting a global view of all a user's Graffiti data or debugging\n * channel usage.\n *\n * It's return value is the same as {@link discover}.\n *\n * @group Query Methods\n */\n abstract recoverOrphans<Schema extends JSONSchema4>(\n /**\n * A [JSON Schema](https://json-schema.org) that orphaned objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): GraffitiStream<\n GraffitiObject<Schema>,\n {\n tombstoneRetention: number;\n }\n >;\n\n /**\n * Returns statistics about all the {@link GraffitiObjectBase.channels | `channels`}\n * that an {@link GraffitiObjectBase.actor | `actor`} has posted to.\n * This is not very useful for most applications, but\n * necessary for certain applications where a user wants a\n * global view of all their Graffiti data or to debug\n * channel usage.\n *\n * @group Query Methods\n *\n * @returns A stream of statistics for each {@link GraffitiObjectBase.channels | `channel`}\n * that the {@link GraffitiObjectBase.actor | `actor`} has posted to.\n */\n abstract channelStats(\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): GraffitiStream<ChannelStats>;\n\n /**\n * Begins the login process. Depending on the implementation, this may\n * involve redirecting the user to a login page or opening a popup,\n * so it should always be called in response to a user action.\n *\n * The {@link GraffitiSession | session} object is returned\n * asynchronously via {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLoginEvent} with event type `login`.\n *\n * @group Session Management\n */\n abstract login(\n /**\n * Suggestions for the permissions that the\n * login process should grant. The login process may not\n * provide the exact proposed permissions.\n */\n proposal?: {\n /**\n * A suggested actor to login as. For example, if a user tries to\n * edit a post but are not logged in, the interface can infer that\n * they might want to log in as the actor who created the post\n * they are attempting to edit.\n *\n * Even if provided, the implementation should allow the user\n * to log in as a different actor if they choose.\n */\n actor?: string;\n /**\n * A yet to be defined permissions scope. An application may use\n * this to indicate the minimum necessary scope needed to\n * operate. For example, it may need to be able read private\n * messages from a certain set of channels, or write messages that\n * follow a particular schema.\n *\n * The login process should make it clear what scope an application\n * is requesting and allow the user to enhance or reduce that\n * scope as necessary.\n */\n scope?: {};\n },\n ): Promise<void>;\n\n /**\n * Begins the logout process. Depending on the implementation, this may\n * involve redirecting the user to a logout page or opening a popup,\n * so it should always be called in response to a user action.\n *\n * A confirmation will be returned asynchronously via\n * {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLogoutEvent} as event type `logout`.\n *\n * @group Session Management\n */\n abstract logout(\n /**\n * The {@link GraffitiSession | session} object to logout.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * An event target that can be used to listen for the following\n * events and they're corresponding event types:\n * - `login` - {@link GraffitiLoginEvent}\n * - `logout` - {@link GraffitiLogoutEvent}\n * - `initialized` - {@link GraffitiSessionInitializedEvent}\n *\n * @group Session Management\n */\n abstract readonly sessionEvents: EventTarget;\n\n /**\n * This method has the same signature as {@link discover} but listens for\n * changes made via {@link put}, {@link patch}, and {@link delete} or\n * fetched from {@link get}, {@link discover}, and {@link recoverOrphans}\n * and then streams appropriate changes to provide a responsive and\n * consistent user experience.\n *\n * Unlike {@link discover}, this method continuously listens for changes\n * and will not terminate unless the user calls the `return` method on the iterator\n * or `break`s out of the loop.\n *\n * Example 1: Suppose a user publishes a post using {@link put}. If the feed\n * displaying that user's posts is using {@link synchronizeDiscover} to listen for changes,\n * then the user's new post will instantly appear in their feed, giving the UI a\n * responsive feel.\n *\n * Example 2: Suppose one of a user's friends changes their name. As soon as the\n * user's application receives one notice of that change (using {@link get}\n * or {@link discover}), then {@link synchronizeDiscover} listeners can be used to update\n * all instance's of that friend's name in the user's application instantly,\n * providing a consistent user experience.\n *\n * @group Synchronize Methods\n */\n abstract synchronizeDiscover<Schema extends JSONSchema4>(\n /**\n * The {@link GraffitiObjectBase.channels | `channels`} that the objects must be associated with.\n */\n channels: string[],\n /**\n * A [JSON Schema](https://json-schema.org) that objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}\n * property will be returned.\n */\n session?: GraffitiSession | null,\n ): GraffitiStream<GraffitiObject<Schema>>;\n\n /**\n * This method has the same signature as {@link get} but, like {@link synchronizeDiscover},\n * it listens for changes made via {@link put}, {@link patch}, and {@link delete} or\n * fetched from {@link get}, {@link discover}, and {@link recoverOrphans} and then\n * streams appropriate changes to provide a responsive and consistent user experience.\n *\n * Unlike {@link get}, which returns a single result, this method continuously\n * listens for changes which are output as an asynchronous {@link GraffitiStream}.\n *\n * @group Synchronize Methods\n */\n abstract synchronizeGet<Schema extends JSONSchema4>(\n /**\n * The location of the object to get.\n */\n locationOrUri: GraffitiLocation | string,\n /**\n * The JSON schema to validate the retrieved object against.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}\n * property must be `undefined`.\n */\n session?: GraffitiSession | null,\n ): GraffitiStream<GraffitiObject<Schema>>;\n\n /**\n * This method has the same signature as {@link recoverOrphans} but,\n * like {@link synchronizeDiscover}, it listens for changes made via\n * {@link put}, {@link patch}, and {@link delete} or fetched from\n * {@link get}, {@link discover}, and {@link recoverOrphans} and then\n * streams appropriate changes to provide a responsive and consistent user experience.\n *\n * Unlike {@link recoverOrphans}, this method continuously listens for changes\n * and will not terminate unless the user calls the `return` method on the iterator\n * or `break`s out of the loop.\n *\n * @group Synchronize Methods\n */\n abstract synchronizeRecoverOrphans<Schema extends JSONSchema4>(\n /**\n * A [JSON Schema](https://json-schema.org) that orphaned objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): GraffitiStream<GraffitiObject<Schema>>;\n}\n\n/**\n * This is a factory function that produces an instance of\n * the {@link Graffiti} class. Since the Graffiti class is\n * abstract, factory functions provide an easy way to\n * swap out different implementations.\n */\nexport type GraffitiFactory = () => Graffiti;\n", "export class GraffitiErrorUnauthorized extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorUnauthorized\";\n Object.setPrototypeOf(this, GraffitiErrorUnauthorized.prototype);\n }\n}\n\nexport class GraffitiErrorForbidden extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorForbidden\";\n Object.setPrototypeOf(this, GraffitiErrorForbidden.prototype);\n }\n}\n\nexport class GraffitiErrorNotFound extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorNotFound\";\n Object.setPrototypeOf(this, GraffitiErrorNotFound.prototype);\n }\n}\n\nexport class GraffitiErrorInvalidSchema extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorInvalidSchema\";\n Object.setPrototypeOf(this, GraffitiErrorInvalidSchema.prototype);\n }\n}\n\nexport class GraffitiErrorSchemaMismatch extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorSchemaMismatch\";\n Object.setPrototypeOf(this, GraffitiErrorSchemaMismatch.prototype);\n }\n}\n\nexport class GraffitiErrorPatchTestFailed extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorPatchTestFailed\";\n Object.setPrototypeOf(this, GraffitiErrorPatchTestFailed.prototype);\n }\n}\n\nexport class GraffitiErrorPatchError extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorPatchError\";\n Object.setPrototypeOf(this, GraffitiErrorPatchError.prototype);\n }\n}\n\nexport class GraffitiErrorInvalidUri extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorInvalidUri\";\n Object.setPrototypeOf(this, GraffitiErrorInvalidUri.prototype);\n }\n}\n"],
|
|
5
|
+
"mappings": "AA6PO,IAAeA,EAAf,KAAwB,CA6B7B,YAAYC,EAA4B,CACtC,OAAO,KAAK,cAAcA,CAAM,CAClC,CA0bF,ECttBO,IAAMC,EAAN,MAAMC,UAAkC,KAAM,CACnD,YAAYC,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,4BACZ,OAAO,eAAe,KAAMD,EAA0B,SAAS,CACjE,CACF,EAEaE,EAAN,MAAMC,UAA+B,KAAM,CAChD,YAAYF,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,yBACZ,OAAO,eAAe,KAAME,EAAuB,SAAS,CAC9D,CACF,EAEaC,EAAN,MAAMC,UAA8B,KAAM,CAC/C,YAAYJ,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,wBACZ,OAAO,eAAe,KAAMI,EAAsB,SAAS,CAC7D,CACF,EAEaC,EAAN,MAAMC,UAAmC,KAAM,CACpD,YAAYN,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,6BACZ,OAAO,eAAe,KAAMM,EAA2B,SAAS,CAClE,CACF,EAEaC,EAAN,MAAMC,UAAoC,KAAM,CACrD,YAAYR,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,8BACZ,OAAO,eAAe,KAAMQ,EAA4B,SAAS,CACnE,CACF,EAEaC,EAAN,MAAMC,UAAqC,KAAM,CACtD,YAAYV,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,+BACZ,OAAO,eAAe,KAAMU,EAA6B,SAAS,CACpE,CACF,EAEaC,EAAN,MAAMC,UAAgC,KAAM,CACjD,YAAYZ,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,0BACZ,OAAO,eAAe,KAAMY,EAAwB,SAAS,CAC/D,CACF,EAEaC,EAAN,MAAMC,UAAgC,KAAM,CACjD,YAAYd,EAAkB,CAC5B,MAAMA,CAAO,EACb,KAAK,KAAO,0BACZ,OAAO,eAAe,KAAMc,EAAwB,SAAS,CAC/D,CACF",
|
|
6
|
+
"names": ["Graffiti", "object", "GraffitiErrorUnauthorized", "_GraffitiErrorUnauthorized", "message", "GraffitiErrorForbidden", "_GraffitiErrorForbidden", "GraffitiErrorNotFound", "_GraffitiErrorNotFound", "GraffitiErrorInvalidSchema", "_GraffitiErrorInvalidSchema", "GraffitiErrorSchemaMismatch", "_GraffitiErrorSchemaMismatch", "GraffitiErrorPatchTestFailed", "_GraffitiErrorPatchTestFailed", "GraffitiErrorPatchError", "_GraffitiErrorPatchError", "GraffitiErrorInvalidUri", "_GraffitiErrorInvalidUri"]
|
|
7
|
+
}
|
package/dist/src/2-types.d.ts
CHANGED
|
@@ -257,7 +257,7 @@ export type ChannelStats = {
|
|
|
257
257
|
*/
|
|
258
258
|
count: number;
|
|
259
259
|
/**
|
|
260
|
-
* The time that the actor {@link
|
|
260
|
+
* The time that the actor {@link GraffitiObjectBase.lastModified | last modified} an object in the channel,
|
|
261
261
|
* measured in milliseconds since January 1, 1970.
|
|
262
262
|
* {@link GraffitiObjectBase.tombstone | Tombstone}d objects do not effect this modification time.
|
|
263
263
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-stats.d.ts","sourceRoot":"","sources":["../../tests/channel-stats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGtE,eAAO,MAAM,yBAAyB,gBACvB,MAAM,IAAI,CACrB,QAAQ,EACR,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAC5C,eACY,MAAM,eAAe,eACrB,MAAM,eAAe,SAiHnC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../tests/crud.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAW9B,eAAO,MAAM,iBAAiB,gBACf,MAAM,IAAI,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC,eACxD,MAAM,eAAe,eACrB,MAAM,eAAe,SAwenC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../tests/discover.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAG9B,eAAO,MAAM,qBAAqB,gBACnB,MAAM,IAAI,CAAC,QAAQ,EAAE,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC,eAC7D,MAAM,eAAe,eACrB,MAAM,eAAe,SA0kBnC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../tests/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"location.d.ts","sourceRoot":"","sources":["../../tests/location.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIrD,eAAO,MAAM,qBAAqB,gBACnB,MAAM,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CAAC,SAmCrE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphans.d.ts","sourceRoot":"","sources":["../../tests/orphans.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGtE,eAAO,MAAM,mBAAmB,gBACjB,MAAM,IAAI,CACrB,QAAQ,EACR,gBAAgB,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAC9C,eACY,MAAM,eAAe,eACrB,MAAM,eAAe,SA8DnC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synchronize.d.ts","sourceRoot":"","sources":["../../tests/synchronize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,wBAAwB,gBACtB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SAyVnC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../tests/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE9E,wBAAgB,YAAY,IAAI,MAAM,CASrC;AAED,wBAAgB,WAAW;;EAI1B;AAED,wBAAgB,eAAe,IAAI,iBAAiB,CAAC,EAAE,CAAC,CAKvD;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,cAIzE"}
|
package/dist/tests.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{it as z,expect as C,describe as L}from"vitest";import{GraffitiErrorInvalidUri as J}from"@graffiti-garden/api";import{assert as N}from"vitest";function u(){let h=new Uint8Array(16);return crypto.getRandomValues(h),Array.from(h).map(q=>q.toString(16).padStart(2,"0")).join("")+"\u{1F469}\u{1F3FD}\u200D\u2764\uFE0F\u200D\u{1F48B}\u200D\u{1F469}\u{1F3FB}\u{1FAF1}\u{1F3FC}\u200D\u{1FAF2}\u{1F3FF}"}function F(){return{[u()]:u()}}function w(){return{value:F(),channels:[u(),u()]}}async function y(h){let p=await h.next();return N(!p.done&&!p.value.error,"result has no value"),p.value.value}var st=h=>{L("URI and location conversion",()=>{z("location to uri and back",async()=>{let p=h(),q={name:u(),actor:u(),source:u()},t=p.locationToUri(q),o=p.uriToLocation(t);C(q).toEqual(o)}),z("collision resistance",async()=>{let p=h(),q={name:u(),actor:u(),source:u()};for(let t of["name","actor","source"]){let o={...q,[t]:u()},e=p.locationToUri(q),a=p.locationToUri(o);C(e).not.toEqual(a)}}),z("random URI should not be a valid location",async()=>{let p=h();C(()=>p.uriToLocation("")).toThrow(J)})})};import{it as x,expect as d,describe as $}from"vitest";import{GraffitiErrorNotFound as O,GraffitiErrorSchemaMismatch as K,GraffitiErrorInvalidSchema as Q,GraffitiErrorForbidden as S,GraffitiErrorPatchTestFailed as W,GraffitiErrorPatchError as R}from"@graffiti-garden/api";var ut=(h,p,q)=>{$("CRUD",{timeout:2e4},()=>{x("put, get, delete",async()=>{let t=h(),o=p(),e={something:"hello, world~ c:"},a=[u(),u()],n=await t.put({value:e,channels:a},o);d(n.value).toEqual({}),d(n.channels).toEqual([]),d(n.allowed).toBeUndefined(),d(n.actor).toEqual(o.actor);let r=await t.get(n,{});d(r.value).toEqual(e),d(r.channels).toEqual([]),d(r.allowed).toBeUndefined(),d(r.name).toEqual(n.name),d(r.actor).toEqual(n.actor),d(r.source).toEqual(n.source),d(r.lastModified).toEqual(n.lastModified);let s={something:"goodbye, world~ :c"},l=await t.put({...n,value:s,channels:[]},o);d(l.value).toEqual(e),d(l.tombstone).toEqual(!0),d(l.name).toEqual(n.name),d(l.actor).toEqual(n.actor),d(l.source).toEqual(n.source),d(l.lastModified).toBeGreaterThan(r.lastModified);let i=await t.get(n,{});d(i.value).toEqual(s),d(i.lastModified).toEqual(l.lastModified),d(i.tombstone).toEqual(!1);let m=await t.delete(i,o);d(m.tombstone).toEqual(!0),d(m.value).toEqual(s),d(m.lastModified).toBeGreaterThan(l.lastModified);let f=await t.get(i,{});d(f).toEqual(m)}),x("get non-existant",async()=>{let t=h(),o=p(),e=await t.put(w(),o);await d(t.get({...e,name:u()},{})).rejects.toBeInstanceOf(O)}),x("put, get, delete with wrong actor",async()=>{let t=h(),o=p(),e=q();await d(t.put({value:{},channels:[],actor:e.actor},o)).rejects.toThrow(S);let a=await t.put({value:{},channels:[]},e);await d(t.delete(a,o)).rejects.toThrow(S),await d(t.patch({},a,o)).rejects.toThrow(S)}),x("put and get with schema",async()=>{let t=h(),o=p(),e={properties:{value:{properties:{something:{type:"string"},another:{type:"integer"}}}}},a={something:"hello",another:42},n=await t.put({value:a,channels:[]},o),r=await t.get(n,e);d(r.value.something).toEqual(a.something),d(r.value.another).toEqual(a.another)}),x("put and get with invalid schema",async()=>{let t=h(),o=p(),e=await t.put({value:{},channels:[]},o);await d(t.get(e,{properties:{value:{type:"asdf"}}})).rejects.toThrow(Q)}),x("put and get with wrong schema",async()=>{let t=h(),o=p(),e=await t.put({value:{hello:"world"},channels:[]},o);await d(t.get(e,{properties:{value:{properties:{hello:{type:"number"}}}}})).rejects.toThrow(K)}),x("put and get with empty access control",async()=>{let t=h(),o=p(),e=q(),a={um:"hi"},n=[u()],r=[u()],s=await t.put({value:a,allowed:n,channels:r},o),l=await t.get(s,{},o);d(l.value).toEqual(a),d(l.allowed).toEqual(n),d(l.channels).toEqual(r),await d(t.get(s,{})).rejects.toBeInstanceOf(O),await d(t.get(s,{},e)).rejects.toBeInstanceOf(O)}),x("put and get with specific access control",async()=>{let t=h(),o=p(),e=q(),a={um:"hi"},n=[u(),e.actor,u()],r=[u()],s=await t.put({value:a,allowed:n,channels:r},o),l=await t.get(s,{},o);d(l.value).toEqual(a),d(l.allowed).toEqual(n),d(l.channels).toEqual(r),await d(t.get(s,{})).rejects.toBeInstanceOf(O);let i=await t.get(s,{},e);d(i.value).toEqual(a),d(i.allowed).toEqual([e.actor]),d(i.channels).toEqual([])}),x("patch value",async()=>{let t=h(),o=p(),e={something:"hello, world~ c:"},a=await t.put({value:e,channels:[]},o),n={value:[{op:"replace",path:"/something",value:"goodbye, world~ :c"}]},r=await t.patch(n,a,o);d(r.value).toEqual(e),d(r.tombstone).toBe(!0);let s=await t.get(a,{});d(s.value).toEqual({something:"goodbye, world~ :c"}),d(r.lastModified).toBe(s.lastModified),await t.delete(a,o)}),x("patch deleted object",async()=>{let t=h(),o=p(),e=await t.put(w(),o),a=await t.delete(e,o);await d(t.patch({},e,o)).rejects.toBeInstanceOf(O)}),x("deep patch",async()=>{let t=h(),o=p(),e={something:{another:{somethingElse:"hello"}}},a=await t.put({value:e,channels:[]},o),n=await t.patch({value:[{op:"replace",path:"/something/another/somethingElse",value:"goodbye"}]},a,o),r=await t.get(a,{});d(n.value).toEqual(e),d(r.value).toEqual({something:{another:{somethingElse:"goodbye"}}})}),x("patch channels",async()=>{let t=h(),o=p(),e=[u()],a=[u()],n=await t.put({value:{},channels:e},o),r={channels:[{op:"replace",path:"/0",value:a[0]}]},s=await t.patch(r,n,o);d(s.channels).toEqual(e);let l=await t.get(n,{},o);d(l.channels).toEqual(a),await t.delete(n,o)}),x("patch 'increment' with test",async()=>{let t=h(),o=p(),e=await t.put({value:{counter:1},channels:[]},o),a=await t.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:2}]},e,o);d(a.value).toEqual({counter:1});let n=await t.get(a,{properties:{value:{properties:{counter:{type:"integer"}}}}});d(n.value.counter).toEqual(2),await d(t.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:3}]},e,o)).rejects.toThrow(W)}),x("invalid patch",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o);await d(t.patch({value:[{op:"add",path:"/root",value:[]},{op:"add",path:"/root/2",value:2}]},a,o)).rejects.toThrow(R)}),x("patch channels to be wrong",async()=>{let t=h(),o=p(),e=w();e.allowed=[u()];let a=await t.put(e,o),n=[{channels:[{op:"replace",path:"",value:null}]},{channels:[{op:"replace",path:"",value:{}}]},{channels:[{op:"replace",path:"",value:["hello",["hi"]]}]},{channels:[{op:"add",path:"/0",value:1}]},{value:[{op:"replace",path:"",value:"not an object"}]},{value:[{op:"replace",path:"",value:null}]},{value:[{op:"replace",path:"",value:[]}]},{allowed:[{op:"replace",path:"",value:{}}]},{allowed:[{op:"replace",path:"",value:["hello",["hi"]]}]}];for(let s of n)await d(t.patch(s,a,o)).rejects.toThrow(R);let r=await t.get(a,{},o);d(r.value).toEqual(e.value),d(r.channels).toEqual(e.channels),d(r.allowed).toEqual(e.allowed),d(r.lastModified).toEqual(a.lastModified)})})};import{it as I,expect as v,describe as D,assert as H}from"vitest";var vt=(h,p,q)=>{D("synchronizeDiscover",()=>{I("get",async()=>{let t=h(),o=p(),e=w(),a=e.channels.slice(1),n=await t.put(e,o),r=h(),s=r.synchronizeDiscover(a,{}).next(),l=await r.get(n,{},o),i=(await s).value;if(!i||i.error)throw new Error("Error in synchronize");v(i.value.value).toEqual(e.value),v(i.value.channels).toEqual(a),v(i.value.tombstone).toBe(!1),v(i.value.lastModified).toEqual(l.lastModified)}),I("put",async()=>{let t=h(),o=p(),e=u(),a=u(),n=u(),r={hello:"world"},s=[e,n],l=await t.put({value:r,channels:s},o),i=t.synchronizeDiscover([e],{}).next(),m=t.synchronizeDiscover([a],{}).next(),f=t.synchronizeDiscover([n],{}).next(),g={goodbye:"world"},E=[a,n];await t.put({...l,value:g,channels:E},o);let b=(await i).value,B=(await m).value,j=(await f).value;if(!b||b.error||!B||B.error||!j||j.error)throw new Error("Error in synchronize");v(b.value.value).toEqual(r),v(b.value.channels).toEqual([e]),v(b.value.tombstone).toBe(!0),v(B.value.value).toEqual(g),v(B.value.channels).toEqual([a]),v(B.value.tombstone).toBe(!1),v(j.value.value).toEqual(g),v(j.value.channels).toEqual([n]),v(j.value.tombstone).toBe(!1),v(b.value.lastModified).toEqual(B.value.lastModified),v(j.value.lastModified).toEqual(B.value.lastModified)}),I("patch",async()=>{let t=h(),o=p(),e=u(),a=u(),n=u(),r={hello:"world"},s=[e,n],l=await t.put({value:r,channels:s},o),i=t.synchronizeDiscover([e],{}).next(),m=t.synchronizeDiscover([a],{}).next(),f=t.synchronizeDiscover([n],{}).next();await t.patch({value:[{op:"add",path:"/something",value:"new value"}],channels:[{op:"add",path:"/-",value:a},{op:"remove",path:`/${s.indexOf(e)}`}]},l,o);let g=(await i).value,E=(await m).value,b=(await f).value;if(!g||g.error||!E||E.error||!b||b.error)throw new Error("Error in synchronize");let B={...r,something:"new value"},j=[n,a];v(g.value.value).toEqual(r),v(g.value.channels).toEqual([e]),v(g.value.tombstone).toBe(!0),v(E.value.value).toEqual(B),v(E.value.channels).toEqual([a]),v(E.value.tombstone).toBe(!1),v(b.value.value).toEqual(B),v(b.value.channels).toEqual([n]),v(b.value.tombstone).toBe(!1),v(g.value.lastModified).toEqual(E.value.lastModified),v(b.value.lastModified).toEqual(E.value.lastModified)}),I("delete",async()=>{let t=h(),o=p(),e=[u(),u(),u()],a={hello:"world"},n=[u(),...e.slice(1)],r=await t.put({value:a,channels:n},o),s=t.synchronizeDiscover(e,{}).next();t.delete(r,o);let l=(await s).value;if(!l||l.error)throw new Error("Error in synchronize");v(l.value.tombstone).toBe(!0),v(l.value.value).toEqual(a),v(l.value.channels).toEqual(e.filter(i=>n.includes(i)))}),I("not allowed",async()=>{let t=h(),o=p(),e=q(),a=[u(),u(),u()],n=a.slice(1),r=t.synchronizeDiscover(n,{},o).next(),s=t.synchronizeDiscover(n,{},e).next(),l=t.synchronizeDiscover(n,{}).next(),i={hello:"world"},m=[u(),e.actor];await t.put({value:i,channels:a,allowed:m},o),await v(Promise.race([l,new Promise((E,b)=>setTimeout(b,100,"Timeout"))])).rejects.toThrow("Timeout");let f=(await r).value,g=(await s).value;if(!f||f.error||!g||g.error)throw new Error("Error in synchronize");v(f.value.value).toEqual(i),v(f.value.allowed).toEqual(m),v(f.value.channels).toEqual(a),v(g.value.value).toEqual(i),v(g.value.allowed).toEqual([e.actor]),v(g.value.channels).toEqual(n)})}),D("synchronizeGet",()=>{I("replace, delete",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o),n=t.synchronizeGet(a,{}),r=n.next(),s={goodbye:"world"},l=await t.put({...a,value:s},o),i=(await r).value;H(i&&!i.error),v(i.value.value).toEqual(s),v(i.value.actor).toEqual(o.actor),v(i.value.channels).toEqual([]),v(i.value.tombstone).toBe(!1),v(i.value.lastModified).toEqual(l.lastModified),v(i.value.allowed).toBeUndefined();let m=await t.delete(l,o),f=(await n.next()).value;H(f&&!f.error),v(f.value.tombstone).toBe(!0),v(f.value.lastModified).toEqual(m.lastModified),await t.put(w(),o),await v(Promise.race([n.next(),new Promise((g,E)=>setTimeout(E,100,"Timeout"))])).rejects.toThrow("Timeout")}),I("not allowed",async()=>{let t=h(),o=p(),e=q(),a=w(),n=await t.put(a,o),r=t.synchronizeGet(n,{},o),s=t.synchronizeGet(n,{},e),l=r.next(),i=s.next(),m={goodbye:"world"},f=await t.put({...n,...a,allowed:[],value:m},o),g=(await l).value,E=(await i).value;H(g&&!g.error),H(E&&!E.error),v(g.value.value).toEqual(m),v(E.value.value).toEqual(a.value),v(g.value.actor).toEqual(o.actor),v(E.value.actor).toEqual(o.actor),v(g.value.channels).toEqual(a.channels),v(E.value.channels).toEqual([]),v(g.value.tombstone).toBe(!1),v(E.value.tombstone).toBe(!0),v(g.value.lastModified).toEqual(f.lastModified),v(E.value.lastModified).toEqual(f.lastModified)})})};import{it as M,expect as c,describe as X,assert as U}from"vitest";var gt=(h,p,q)=>{X("discover",{timeout:2e4},()=>{M("discover nothing",async()=>{let o=h().discover([],{});c(await o.next()).toHaveProperty("done",!0)}),M("discover single",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o),n=[u(),e.channels[0]],r=t.discover(n,{}),s=await y(r);c(s.value).toEqual(e.value),c(s.channels).toEqual([e.channels[0]]),c(s.allowed).toBeUndefined(),c(s.actor).toEqual(o.actor),c(s.tombstone).toBe(!1),c(s.lastModified).toEqual(a.lastModified);let l=await r.next();c(l.done).toBe(!0)}),M("discover wrong channel",async()=>{let t=h(),o=p(),e=w();await t.put(e,o);let a=t.discover([u()],{});await c(a.next()).resolves.toHaveProperty("done",!0)}),M("discover not allowed",async()=>{let t=h(),o=p(),e=q(),a=w();a.allowed=[u(),u()];let n=await t.put(a,o),r=t.discover(a.channels,{},o),s=await y(r);c(s.value).toEqual(a.value),c(s.channels).toEqual(a.channels),c(s.allowed).toEqual(a.allowed),c(s.actor).toEqual(o.actor),c(s.tombstone).toBe(!1),c(s.lastModified).toEqual(n.lastModified);let l=t.discover(a.channels,{},e);c(await l.next()).toHaveProperty("done",!0);let i=t.discover(a.channels,{});c(await i.next()).toHaveProperty("done",!0)}),M("discover allowed",async()=>{let t=h(),o=p(),e=q(),a=w();a.allowed=[u(),e.actor,u()];let n=await t.put(a,o),r=t.discover(a.channels,{},e),s=await y(r);c(s.value).toEqual(a.value),c(s.allowed).toEqual([e.actor]),c(s.channels).toEqual(a.channels),c(s.actor).toEqual(o.actor),c(s.tombstone).toBe(!1),c(s.lastModified).toEqual(n.lastModified)});for(let t of["name","actor","lastModified"])M(`discover for ${t}`,async()=>{let o=h(),e=p(),a=q(),n=w(),r=await o.put(n,e),s=w();s.channels=n.channels,await new Promise(f=>setTimeout(f,20));let l=await o.put(s,a),i=o.discover(n.channels,{properties:{[t]:{enum:[r[t]]}}}),m=await y(i);c(m.name).toEqual(r.name),c(m.name).not.toEqual(l.name),c(m.value).toEqual(n.value),await c(i.next()).resolves.toHaveProperty("done",!0)});M("discover with lastModified range",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o);await new Promise(G=>setTimeout(G,20));let n=await t.put(e,o);c(a.name).not.toEqual(n.name),c(a.lastModified).toBeLessThan(n.lastModified);let r=t.discover([e.channels[0]],{properties:{lastModified:{minimum:n.lastModified,exclusiveMinimum:!0}}});c(await r.next()).toHaveProperty("done",!0);let s=t.discover([e.channels[0]],{properties:{lastModified:{minimum:n.lastModified-.1,exclusiveMinimum:!0}}}),l=await y(s);c(l.name).toEqual(n.name),c(await s.next()).toHaveProperty("done",!0);let i=t.discover(e.channels,{properties:{value:{},lastModified:{minimum:n.lastModified}}}),m=await y(i);c(m.name).toEqual(n.name),c(await i.next()).toHaveProperty("done",!0);let f=t.discover(e.channels,{properties:{lastModified:{minimum:n.lastModified+.1}}});c(await f.next()).toHaveProperty("done",!0);let g=t.discover(e.channels,{properties:{lastModified:{maximum:a.lastModified,exclusiveMaximum:!0}}});c(await g.next()).toHaveProperty("done",!0);let E=t.discover(e.channels,{properties:{lastModified:{maximum:a.lastModified+.1,exclusiveMaximum:!0}}}),b=await y(E);c(b.name).toEqual(a.name),c(await E.next()).toHaveProperty("done",!0);let B=t.discover(e.channels,{properties:{lastModified:{maximum:a.lastModified}}}),j=await y(B);c(j.name).toEqual(a.name),c(await B.next()).toHaveProperty("done",!0);let A=t.discover(e.channels,{properties:{lastModified:{maximum:a.lastModified-.1}}});c(await A.next()).toHaveProperty("done",!0)}),M("discover schema allowed, as and not as owner",async()=>{let t=h(),o=p(),e=q(),a=w();a.allowed=[u(),e.actor,u()],await t.put(a,o);let n=t.discover(a.channels,{properties:{allowed:{minItems:3,not:{items:{not:{enum:[e.actor]}}}}}},o),r=await y(n);c(r.value).toEqual(a.value),await c(n.next()).resolves.toHaveProperty("done",!0);let s=t.discover(a.channels,{properties:{allowed:{minItems:3}}},e);await c(s.next()).resolves.toHaveProperty("done",!0);let l=t.discover(a.channels,{properties:{allowed:{not:{items:{not:{enum:[a.channels[0]]}}}}}},e);await c(l.next()).resolves.toHaveProperty("done",!0);let i=t.discover(a.channels,{properties:{allowed:{maxItems:1,not:{items:{not:{enum:[e.actor]}}}}}},e),m=await y(i);c(m.value).toEqual(a.value),await c(i.next()).resolves.toHaveProperty("done",!0)}),M("discover schema channels, as and not as owner",async()=>{let t=h(),o=p(),e=q(),a=w();a.channels=[u(),u(),u()],await t.put(a,o);let n=t.discover([a.channels[0],a.channels[2]],{properties:{channels:{minItems:3,not:{items:{not:{enum:[a.channels[1]]}}}}}},o),r=await y(n);c(r.value).toEqual(a.value),await c(n.next()).resolves.toHaveProperty("done",!0);let s=t.discover([a.channels[0],a.channels[2]],{properties:{channels:{minItems:3}}},e);await c(s.next()).resolves.toHaveProperty("done",!0);let l=t.discover([a.channels[0],a.channels[2]],{properties:{channels:{not:{items:{not:{enum:[a.channels[1]]}}}}}},e);await c(l.next()).resolves.toHaveProperty("done",!0);let i=t.discover([a.channels[0],a.channels[2]],{properties:{allowed:{maxItems:2,not:{items:{not:{enum:[a.channels[2]]}}}}}},e),m=await y(i);c(m.value).toEqual(a.value),await c(i.next()).resolves.toHaveProperty("done",!0)}),M("discover query for empty allowed",async()=>{let t=h(),o=p(),e=w(),a={not:{required:["allowed"]}};await t.put(e,o);let n=t.discover(e.channels,a,o),r=await y(n);c(r.value).toEqual(e.value),c(r.allowed).toBeUndefined(),await c(n.next()).resolves.toHaveProperty("done",!0);let s=w();s.allowed=[],await t.put(s,o);let l=t.discover(s.channels,a,o);await c(l.next()).resolves.toHaveProperty("done",!0)}),M("discover query for values",async()=>{let t=h(),o=p(),e=w();e.value={test:u()},await t.put(e,o);let a=w();a.channels=e.channels,a.value={test:u(),something:u()},await t.put(a,o);let n=w();n.channels=e.channels,n.value={other:u(),something:u()},await t.put(n,o);let r=new Map;for(let s of["test","something","other"]){let l=0;for await(let i of t.discover(e.channels,{properties:{value:{required:[s]}}}))U(!i.error,"result has error"),s in i.value.value&&l++;r.set(s,l)}c(r.get("test")).toBe(2),c(r.get("something")).toBe(2),c(r.get("other")).toBe(1)}),M("discover for deleted content",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o),n=await t.delete(a,o),r=t.discover(e.channels,{}),s=await y(r);c(s.tombstone).toBe(!0),c(s.value).toEqual(e.value),c(s.channels).toEqual(e.channels),c(s.actor).toEqual(o.actor),c(s.lastModified).toEqual(n.lastModified),await c(r.next()).resolves.toHaveProperty("done",!0)}),M("discover for replaced channels",async()=>{for(let t=0;t<10;t++){let o=h(),e=p(),a=w(),n=await o.put(a,e),r=w(),s=await o.put({...n,...r},e),l=o.discover(a.channels,{}),i=await y(l);await c(l.next()).resolves.toHaveProperty("done",!0);let m=o.discover(r.channels,{}),f=await y(m);await c(m.next()).resolves.toHaveProperty("done",!0),n.lastModified===s.lastModified?(c(i.tombstone||f.tombstone).toBe(!0),c(i.tombstone&&f.tombstone).toBe(!1)):(c(i.tombstone).toBe(!0),c(i.value).toEqual(a.value),c(i.channels).toEqual(a.channels),c(i.lastModified).toEqual(s.lastModified),c(f.tombstone).toBe(!1),c(f.value).toEqual(r.value),c(f.channels).toEqual(r.channels),c(f.lastModified).toEqual(s.lastModified))}}),M("discover for patched allowed",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o);await t.patch({allowed:[{op:"add",path:"",value:[]}]},a,o);let n=t.discover(e.channels,{}),r=await y(n);c(r.tombstone).toBe(!0),c(r.value).toEqual(e.value),c(r.channels).toEqual(e.channels),c(r.allowed).toBeUndefined(),await c(n.next()).resolves.toHaveProperty("done",!0)}),M("put concurrently and discover one",async()=>{let t=h(),o=p(),e=w();e.name=u();let a=Array(100).fill(0).map(()=>t.put(e,o));await Promise.all(a);let n=t.discover(e.channels,{}),r=0,s=0;for await(let l of n)U(!l.error,"result has error"),l.value.tombstone?r++:s++;c(r).toBe(99),c(s).toBe(1)})})};import{it as V,expect as T,describe as Y}from"vitest";var xt=(h,p,q)=>{Y("recoverOrphans",()=>{V("list orphans",async()=>{let t=h(),o=p(),e=[],a=t.recoverOrphans({},o);for await(let i of a)i.error||e.push(i.value.name);let n=w();n.channels=[];let r=await t.put(n,o),s=t.recoverOrphans({},o),l=0;for await(let i of s)i.error||i.value.name===r.name&&(l++,T(i.value.source).toBe(r.source),T(i.value.lastModified).toBe(r.lastModified));T(l).toBe(1)}),V("replaced orphan, no longer",async()=>{let t=h(),o=p(),e=w();e.channels=[];let a=await t.put(e,o),n=await t.put({...a,...e,channels:[u()]},o);T(n.name).toBe(a.name);let r=t.recoverOrphans({},o),s=0;for await(let l of r)l.error||l.value.name===a.name&&(s++,T(l.value.tombstone).toBe(!0),T(l.value.lastModified).toBe(n.lastModified),T(l.value.channels).toEqual([]));T(s).toBe(1)})})};import{it as k,expect as P,describe as Z,assert as _}from"vitest";var Tt=(h,p,q)=>{Z("channel stats",()=>{k("list channels",async()=>{let t=h(),o=p(),e=new Map,a=t.channelStats(o);for await(let l of a)l.error||e.set(l.value.channel,l.value.count);let n=[u(),u(),u()];for(let l=0;l<3;l++)for(let i=0;i<l+1;i++)await t.put({value:{index:i},channels:n.slice(0,l+1)},o);await t.put({value:{index:3},channels:[n[2]]},o);let r=t.channelStats(o),s=new Map;for await(let l of r)l.error||s.set(l.value.channel,l.value.count);s=new Map(Array.from(s).filter(([l,i])=>!e.has(l))),P(s.size).toBe(3),P(s.get(n[0])).toBe(6),P(s.get(n[1])).toBe(5),P(s.get(n[2])).toBe(4)}),k("list channels with deleted channel",async()=>{let t=h(),o=p(),e=[u(),u(),u()],a=await t.put({value:{index:2},channels:e.slice(1)},o),n=await t.put({value:{index:0},channels:e},o);await t.delete(n,o);let r=await t.put({value:{index:1},channels:e.slice(2)},o),s=t.channelStats(o),l=0,i=0;for await(let m of s){if(m.error)continue;let{channel:f,count:g,lastModified:E}=m.value;_(f!==e[0],"There should not be an object in channel[0]"),f===e[1]?(P(g).toBe(1),P(E).toBe(a.lastModified),l++):f===e[2]&&(P(g).toBe(2),P(E).toBe(r.lastModified),i++)}P(l).toBe(1),P(i).toBe(1)})})};export{ut as graffitiCRUDTests,Tt as graffitiChannelStatsTests,gt as graffitiDiscoverTests,st as graffitiLocationTests,xt as graffitiOrphanTests,vt as graffitiSynchronizeTests};
|
|
2
|
+
//# sourceMappingURL=tests.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../tests/location.ts", "../tests/utils.ts", "../tests/crud.ts", "../tests/synchronize.ts", "../tests/discover.ts", "../tests/orphans.ts", "../tests/channel-stats.ts"],
|
|
4
|
+
"sourcesContent": ["import { it, expect, describe } from \"vitest\";\nimport type { Graffiti } from \"@graffiti-garden/api\";\nimport { GraffitiErrorInvalidUri } from \"@graffiti-garden/api\";\nimport { randomString } from \"./utils\";\n\nexport const graffitiLocationTests = (\n useGraffiti: () => Pick<Graffiti, \"locationToUri\" | \"uriToLocation\">,\n) => {\n describe(\"URI and location conversion\", () => {\n it(\"location to uri and back\", async () => {\n const graffiti = useGraffiti();\n const location = {\n name: randomString(),\n actor: randomString(),\n source: randomString(),\n };\n const uri = graffiti.locationToUri(location);\n const location2 = graffiti.uriToLocation(uri);\n expect(location).toEqual(location2);\n });\n\n it(\"collision resistance\", async () => {\n const graffiti = useGraffiti();\n const location1 = {\n name: randomString(),\n actor: randomString(),\n source: randomString(),\n };\n for (const prop of [\"name\", \"actor\", \"source\"] as const) {\n const location2 = { ...location1, [prop]: randomString() };\n const uri1 = graffiti.locationToUri(location1);\n const uri2 = graffiti.locationToUri(location2);\n expect(uri1).not.toEqual(uri2);\n }\n });\n\n it(\"random URI should not be a valid location\", async () => {\n const graffiti = useGraffiti();\n expect(() => graffiti.uriToLocation(\"\")).toThrow(GraffitiErrorInvalidUri);\n });\n });\n};\n", "import { assert } from \"vitest\";\nimport type { GraffitiPutObject, GraffitiStream } from \"@graffiti-garden/api\";\n\nexport function randomString(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n const str = Array.from(array)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n // check for unicode support\n return str + \"\uD83D\uDC69\uD83C\uDFFD\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D\uD83D\uDC69\uD83C\uDFFB\uD83E\uDEF1\uD83C\uDFFC\u200D\uD83E\uDEF2\uD83C\uDFFF\";\n}\n\nexport function randomValue() {\n return {\n [randomString()]: randomString(),\n };\n}\n\nexport function randomPutObject(): GraffitiPutObject<{}> {\n return {\n value: randomValue(),\n channels: [randomString(), randomString()],\n };\n}\n\nexport async function nextStreamValue<S, T>(iterator: GraffitiStream<S, T>) {\n const result = await iterator.next();\n assert(!result.done && !result.value.error, \"result has no value\");\n return result.value.value;\n}\n", "import { it, expect, describe } from \"vitest\";\nimport type {\n Graffiti,\n GraffitiSession,\n GraffitiPatch,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorInvalidSchema,\n GraffitiErrorForbidden,\n GraffitiErrorPatchTestFailed,\n GraffitiErrorPatchError,\n} from \"@graffiti-garden/api\";\nimport { randomPutObject, randomString } from \"./utils\";\n\nexport const graffitiCRUDTests = (\n useGraffiti: () => Pick<Graffiti, \"put\" | \"get\" | \"delete\" | \"patch\">,\n useSession1: () => GraffitiSession,\n useSession2: () => GraffitiSession,\n) => {\n describe(\n \"CRUD\",\n {\n timeout: 20000,\n },\n () => {\n it(\"put, get, delete\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n const value = {\n something: \"hello, world~ c:\",\n };\n const channels = [randomString(), randomString()];\n\n // Put the object\n const previous = await graffiti.put({ value, channels }, session);\n expect(previous.value).toEqual({});\n expect(previous.channels).toEqual([]);\n expect(previous.allowed).toBeUndefined();\n expect(previous.actor).toEqual(session.actor);\n\n // Get it back\n const gotten = await graffiti.get(previous, {});\n expect(gotten.value).toEqual(value);\n expect(gotten.channels).toEqual([]);\n expect(gotten.allowed).toBeUndefined();\n expect(gotten.name).toEqual(previous.name);\n expect(gotten.actor).toEqual(previous.actor);\n expect(gotten.source).toEqual(previous.source);\n expect(gotten.lastModified).toEqual(previous.lastModified);\n\n // Replace it\n const newValue = {\n something: \"goodbye, world~ :c\",\n };\n const beforeReplaced = await graffiti.put(\n { ...previous, value: newValue, channels: [] },\n session,\n );\n expect(beforeReplaced.value).toEqual(value);\n expect(beforeReplaced.tombstone).toEqual(true);\n expect(beforeReplaced.name).toEqual(previous.name);\n expect(beforeReplaced.actor).toEqual(previous.actor);\n expect(beforeReplaced.source).toEqual(previous.source);\n expect(beforeReplaced.lastModified).toBeGreaterThan(\n gotten.lastModified,\n );\n\n // Get it again\n const afterReplaced = await graffiti.get(previous, {});\n expect(afterReplaced.value).toEqual(newValue);\n expect(afterReplaced.lastModified).toEqual(beforeReplaced.lastModified);\n expect(afterReplaced.tombstone).toEqual(false);\n\n // Delete it\n const beforeDeleted = await graffiti.delete(afterReplaced, session);\n expect(beforeDeleted.tombstone).toEqual(true);\n expect(beforeDeleted.value).toEqual(newValue);\n expect(beforeDeleted.lastModified).toBeGreaterThan(\n beforeReplaced.lastModified,\n );\n\n // Get a tombstone\n const final = await graffiti.get(afterReplaced, {});\n expect(final).toEqual(beforeDeleted);\n });\n\n it(\"get non-existant\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const putted = await graffiti.put(randomPutObject(), session);\n await expect(\n graffiti.get(\n {\n ...putted,\n name: randomString(),\n },\n {},\n ),\n ).rejects.toBeInstanceOf(GraffitiErrorNotFound);\n });\n\n it(\"put, get, delete with wrong actor\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n await expect(\n graffiti.put(\n { value: {}, channels: [], actor: session2.actor },\n session1,\n ),\n ).rejects.toThrow(GraffitiErrorForbidden);\n\n const putted = await graffiti.put(\n { value: {}, channels: [] },\n session2,\n );\n\n await expect(graffiti.delete(putted, session1)).rejects.toThrow(\n GraffitiErrorForbidden,\n );\n\n await expect(graffiti.patch({}, putted, session1)).rejects.toThrow(\n GraffitiErrorForbidden,\n );\n });\n\n it(\"put and get with schema\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const schema = {\n properties: {\n value: {\n properties: {\n something: {\n type: \"string\",\n },\n another: {\n type: \"integer\",\n },\n },\n },\n },\n } as const;\n\n const goodValue = {\n something: \"hello\",\n another: 42,\n } as const;\n\n const putted = await graffiti.put<typeof schema>(\n {\n value: goodValue,\n channels: [],\n },\n session,\n );\n\n const gotten = await graffiti.get(putted, schema);\n expect(gotten.value.something).toEqual(goodValue.something);\n expect(gotten.value.another).toEqual(goodValue.another);\n });\n\n it(\"put and get with invalid schema\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const putted = await graffiti.put({ value: {}, channels: [] }, session);\n await expect(\n graffiti.get(putted, {\n properties: {\n value: {\n //@ts-ignore\n type: \"asdf\",\n },\n },\n }),\n ).rejects.toThrow(GraffitiErrorInvalidSchema);\n });\n\n it(\"put and get with wrong schema\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const putted = await graffiti.put(\n {\n value: {\n hello: \"world\",\n },\n channels: [],\n },\n session,\n );\n\n await expect(\n graffiti.get(putted, {\n properties: {\n value: {\n properties: {\n hello: {\n type: \"number\",\n },\n },\n },\n },\n }),\n ).rejects.toThrow(GraffitiErrorSchemaMismatch);\n });\n\n it(\"put and get with empty access control\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n const value = {\n um: \"hi\",\n };\n const allowed = [randomString()];\n const channels = [randomString()];\n const putted = await graffiti.put(\n { value, allowed, channels },\n session1,\n );\n\n // Get it with authenticated session\n const gotten = await graffiti.get(putted, {}, session1);\n expect(gotten.value).toEqual(value);\n expect(gotten.allowed).toEqual(allowed);\n expect(gotten.channels).toEqual(channels);\n\n // But not without session\n await expect(graffiti.get(putted, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n // Or the wrong session\n await expect(graffiti.get(putted, {}, session2)).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"put and get with specific access control\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n const value = {\n um: \"hi\",\n };\n const allowed = [randomString(), session2.actor, randomString()];\n const channels = [randomString()];\n const putted = await graffiti.put(\n {\n value,\n allowed,\n channels,\n },\n session1,\n );\n\n // Get it with authenticated session\n const gotten = await graffiti.get(putted, {}, session1);\n expect(gotten.value).toEqual(value);\n expect(gotten.allowed).toEqual(allowed);\n expect(gotten.channels).toEqual(channels);\n\n // But not without session\n await expect(graffiti.get(putted, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n const gotten2 = await graffiti.get(putted, {}, session2);\n expect(gotten2.value).toEqual(value);\n // They should only see that is is private to them\n expect(gotten2.allowed).toEqual([session2.actor]);\n // And not see any channels\n expect(gotten2.channels).toEqual([]);\n });\n\n it(\"patch value\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const value = {\n something: \"hello, world~ c:\",\n };\n const putted = await graffiti.put({ value, channels: [] }, session);\n\n const patch: GraffitiPatch = {\n value: [\n { op: \"replace\", path: \"/something\", value: \"goodbye, world~ :c\" },\n ],\n };\n const beforePatched = await graffiti.patch(patch, putted, session);\n expect(beforePatched.value).toEqual(value);\n expect(beforePatched.tombstone).toBe(true);\n\n const gotten = await graffiti.get(putted, {});\n expect(gotten.value).toEqual({\n something: \"goodbye, world~ :c\",\n });\n expect(beforePatched.lastModified).toBe(gotten.lastModified);\n\n await graffiti.delete(putted, session);\n });\n\n it(\"patch deleted object\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const putted = await graffiti.put(randomPutObject(), session);\n const deleted = await graffiti.delete(putted, session);\n await expect(\n graffiti.patch({}, putted, session),\n ).rejects.toBeInstanceOf(GraffitiErrorNotFound);\n });\n\n it(\"deep patch\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const value = {\n something: {\n another: {\n somethingElse: \"hello\",\n },\n },\n };\n const putted = await graffiti.put(\n { value: value, channels: [] },\n session,\n );\n\n const beforePatch = await graffiti.patch(\n {\n value: [\n {\n op: \"replace\",\n path: \"/something/another/somethingElse\",\n value: \"goodbye\",\n },\n ],\n },\n putted,\n session,\n );\n const gotten = await graffiti.get(putted, {});\n\n expect(beforePatch.value).toEqual(value);\n expect(gotten.value).toEqual({\n something: {\n another: {\n somethingElse: \"goodbye\",\n },\n },\n });\n });\n\n it(\"patch channels\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const channelsBefore = [randomString()];\n const channelsAfter = [randomString()];\n\n const putted = await graffiti.put(\n { value: {}, channels: channelsBefore },\n session,\n );\n\n const patch: GraffitiPatch = {\n channels: [{ op: \"replace\", path: \"/0\", value: channelsAfter[0] }],\n };\n const patched = await graffiti.patch(patch, putted, session);\n expect(patched.channels).toEqual(channelsBefore);\n const gotten = await graffiti.get(putted, {}, session);\n expect(gotten.channels).toEqual(channelsAfter);\n await graffiti.delete(putted, session);\n });\n\n it(\"patch 'increment' with test\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const putted = await graffiti.put(\n {\n value: {\n counter: 1,\n },\n channels: [],\n },\n session,\n );\n\n const previous = await graffiti.patch(\n {\n value: [\n { op: \"test\", path: \"/counter\", value: 1 },\n { op: \"replace\", path: \"/counter\", value: 2 },\n ],\n },\n putted,\n session,\n );\n expect(previous.value).toEqual({ counter: 1 });\n const result = await graffiti.get(previous, {\n properties: {\n value: {\n properties: {\n counter: {\n type: \"integer\",\n },\n },\n },\n },\n });\n expect(result.value.counter).toEqual(2);\n\n await expect(\n graffiti.patch(\n {\n value: [\n { op: \"test\", path: \"/counter\", value: 1 },\n { op: \"replace\", path: \"/counter\", value: 3 },\n ],\n },\n putted,\n session,\n ),\n ).rejects.toThrow(GraffitiErrorPatchTestFailed);\n });\n\n it(\"invalid patch\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n const object = randomPutObject();\n const putted = await graffiti.put(object, session);\n\n await expect(\n graffiti.patch(\n {\n value: [\n { op: \"add\", path: \"/root\", value: [] },\n { op: \"add\", path: \"/root/2\", value: 2 }, // out of bounds\n ],\n },\n putted,\n session,\n ),\n ).rejects.toThrow(GraffitiErrorPatchError);\n });\n\n it(\"patch channels to be wrong\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n const object = randomPutObject();\n object.allowed = [randomString()];\n const putted = await graffiti.put(object, session);\n\n const patches: GraffitiPatch[] = [\n {\n channels: [{ op: \"replace\", path: \"\", value: null }],\n },\n {\n channels: [{ op: \"replace\", path: \"\", value: {} }],\n },\n {\n channels: [{ op: \"replace\", path: \"\", value: [\"hello\", [\"hi\"]] }],\n },\n {\n channels: [{ op: \"add\", path: \"/0\", value: 1 }],\n },\n {\n value: [{ op: \"replace\", path: \"\", value: \"not an object\" }],\n },\n {\n value: [{ op: \"replace\", path: \"\", value: null }],\n },\n {\n value: [{ op: \"replace\", path: \"\", value: [] }],\n },\n {\n allowed: [{ op: \"replace\", path: \"\", value: {} }],\n },\n {\n allowed: [{ op: \"replace\", path: \"\", value: [\"hello\", [\"hi\"]] }],\n },\n ];\n\n for (const patch of patches) {\n await expect(graffiti.patch(patch, putted, session)).rejects.toThrow(\n GraffitiErrorPatchError,\n );\n }\n\n const gotten = await graffiti.get(putted, {}, session);\n expect(gotten.value).toEqual(object.value);\n expect(gotten.channels).toEqual(object.channels);\n expect(gotten.allowed).toEqual(object.allowed);\n expect(gotten.lastModified).toEqual(putted.lastModified);\n });\n },\n );\n};\n", "import { it, expect, describe, assert } from \"vitest\";\nimport type { GraffitiFactory, GraffitiSession } from \"@graffiti-garden/api\";\nimport { randomPutObject, randomString } from \"./utils\";\n\nexport const graffitiSynchronizeTests = (\n useGraffiti: GraffitiFactory,\n useSession1: () => GraffitiSession,\n useSession2: () => GraffitiSession,\n) => {\n describe(\"synchronizeDiscover\", () => {\n it(\"get\", async () => {\n const graffiti1 = useGraffiti();\n const session = useSession1();\n\n const object = randomPutObject();\n const channels = object.channels.slice(1);\n const putted = await graffiti1.put(object, session);\n\n const graffiti2 = useGraffiti();\n const next = graffiti2.synchronizeDiscover(channels, {}).next();\n const gotten = await graffiti2.get(putted, {}, session);\n\n const result = (await next).value;\n if (!result || result.error) {\n throw new Error(\"Error in synchronize\");\n }\n expect(result.value.value).toEqual(object.value);\n expect(result.value.channels).toEqual(channels);\n expect(result.value.tombstone).toBe(false);\n expect(result.value.lastModified).toEqual(gotten.lastModified);\n });\n\n it(\"put\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const beforeChannel = randomString();\n const afterChannel = randomString();\n const sharedChannel = randomString();\n\n const oldValue = { hello: \"world\" };\n const oldChannels = [beforeChannel, sharedChannel];\n const putted = await graffiti.put(\n {\n value: oldValue,\n channels: oldChannels,\n },\n session,\n );\n\n // Start listening for changes...\n const before = graffiti.synchronizeDiscover([beforeChannel], {}).next();\n const after = graffiti.synchronizeDiscover([afterChannel], {}).next();\n const shared = graffiti.synchronizeDiscover([sharedChannel], {}).next();\n\n // Replace the object\n const newValue = { goodbye: \"world\" };\n const newChannels = [afterChannel, sharedChannel];\n await graffiti.put(\n {\n ...putted,\n value: newValue,\n channels: newChannels,\n },\n session,\n );\n\n const beforeResult = (await before).value;\n const afterResult = (await after).value;\n const sharedResult = (await shared).value;\n if (\n !beforeResult ||\n beforeResult.error ||\n !afterResult ||\n afterResult.error ||\n !sharedResult ||\n sharedResult.error\n ) {\n throw new Error(\"Error in synchronize\");\n }\n\n expect(beforeResult.value.value).toEqual(oldValue);\n expect(beforeResult.value.channels).toEqual([beforeChannel]);\n expect(beforeResult.value.tombstone).toBe(true);\n expect(afterResult.value.value).toEqual(newValue);\n expect(afterResult.value.channels).toEqual([afterChannel]);\n expect(afterResult.value.tombstone).toBe(false);\n expect(sharedResult.value.value).toEqual(newValue);\n expect(sharedResult.value.channels).toEqual([sharedChannel]);\n expect(sharedResult.value.tombstone).toBe(false);\n expect(beforeResult.value.lastModified).toEqual(\n afterResult.value.lastModified,\n );\n expect(sharedResult.value.lastModified).toEqual(\n afterResult.value.lastModified,\n );\n });\n\n it(\"patch\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const beforeChannel = randomString();\n const afterChannel = randomString();\n const sharedChannel = randomString();\n\n const oldValue = { hello: \"world\" };\n const oldChannels = [beforeChannel, sharedChannel];\n const putted = await graffiti.put(\n {\n value: oldValue,\n channels: oldChannels,\n },\n session,\n );\n\n // Start listening for changes...\n const before = graffiti.synchronizeDiscover([beforeChannel], {}).next();\n const after = graffiti.synchronizeDiscover([afterChannel], {}).next();\n const shared = graffiti.synchronizeDiscover([sharedChannel], {}).next();\n\n await graffiti.patch(\n {\n value: [\n {\n op: \"add\",\n path: \"/something\",\n value: \"new value\",\n },\n ],\n channels: [\n {\n op: \"add\",\n path: \"/-\",\n value: afterChannel,\n },\n {\n op: \"remove\",\n path: `/${oldChannels.indexOf(beforeChannel)}`,\n },\n ],\n },\n putted,\n session,\n );\n\n const beforeResult = (await before).value;\n const afterResult = (await after).value;\n const sharedResult = (await shared).value;\n if (\n !beforeResult ||\n beforeResult.error ||\n !afterResult ||\n afterResult.error ||\n !sharedResult ||\n sharedResult.error\n ) {\n throw new Error(\"Error in synchronize\");\n }\n\n const newValue = { ...oldValue, something: \"new value\" };\n const newChannels = [sharedChannel, afterChannel];\n expect(beforeResult.value.value).toEqual(oldValue);\n expect(beforeResult.value.channels).toEqual([beforeChannel]);\n expect(beforeResult.value.tombstone).toBe(true);\n expect(afterResult.value.value).toEqual(newValue);\n expect(afterResult.value.channels).toEqual([afterChannel]);\n expect(afterResult.value.tombstone).toBe(false);\n expect(sharedResult.value.value).toEqual(newValue);\n expect(sharedResult.value.channels).toEqual([sharedChannel]);\n expect(sharedResult.value.tombstone).toBe(false);\n expect(beforeResult.value.lastModified).toEqual(\n afterResult.value.lastModified,\n );\n expect(sharedResult.value.lastModified).toEqual(\n afterResult.value.lastModified,\n );\n });\n\n it(\"delete\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const channels = [randomString(), randomString(), randomString()];\n\n const oldValue = { hello: \"world\" };\n const oldChannels = [randomString(), ...channels.slice(1)];\n const putted = await graffiti.put(\n {\n value: oldValue,\n channels: oldChannels,\n },\n session,\n );\n\n const next = graffiti.synchronizeDiscover(channels, {}).next();\n\n graffiti.delete(putted, session);\n\n const result = (await next).value;\n if (!result || result.error) {\n throw new Error(\"Error in synchronize\");\n }\n expect(result.value.tombstone).toBe(true);\n expect(result.value.value).toEqual(oldValue);\n expect(result.value.channels).toEqual(\n channels.filter((c) => oldChannels.includes(c)),\n );\n });\n\n it(\"not allowed\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n const allChannels = [randomString(), randomString(), randomString()];\n const channels = allChannels.slice(1);\n\n const creatorNext = graffiti\n .synchronizeDiscover(channels, {}, session1)\n .next();\n const allowedNext = graffiti\n .synchronizeDiscover(channels, {}, session2)\n .next();\n const noSession = graffiti.synchronizeDiscover(channels, {}).next();\n\n const value = {\n hello: \"world\",\n };\n const allowed = [randomString(), session2.actor];\n await graffiti.put({ value, channels: allChannels, allowed }, session1);\n\n // Expect no session to time out!\n await expect(\n Promise.race([\n noSession,\n new Promise((resolve, rejects) =>\n setTimeout(rejects, 100, \"Timeout\"),\n ),\n ]),\n ).rejects.toThrow(\"Timeout\");\n\n const creatorResult = (await creatorNext).value;\n const allowedResult = (await allowedNext).value;\n\n if (\n !creatorResult ||\n creatorResult.error ||\n !allowedResult ||\n allowedResult.error\n ) {\n throw new Error(\"Error in synchronize\");\n }\n\n expect(creatorResult.value.value).toEqual(value);\n expect(creatorResult.value.allowed).toEqual(allowed);\n expect(creatorResult.value.channels).toEqual(allChannels);\n expect(allowedResult.value.value).toEqual(value);\n expect(allowedResult.value.allowed).toEqual([session2.actor]);\n expect(allowedResult.value.channels).toEqual(channels);\n });\n });\n\n describe(\"synchronizeGet\", () => {\n it(\"replace, delete\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const object = randomPutObject();\n const putted = await graffiti.put(object, session);\n\n const iterator = graffiti.synchronizeGet(putted, {});\n const next = iterator.next();\n\n // Change the object\n const newValue = { goodbye: \"world\" };\n const putted2 = await graffiti.put(\n {\n ...putted,\n value: newValue,\n },\n session,\n );\n\n const result = (await next).value;\n assert(result && !result.error);\n\n expect(result.value.value).toEqual(newValue);\n expect(result.value.actor).toEqual(session.actor);\n expect(result.value.channels).toEqual([]);\n expect(result.value.tombstone).toBe(false);\n expect(result.value.lastModified).toEqual(putted2.lastModified);\n expect(result.value.allowed).toBeUndefined();\n\n // Delete the object\n const deleted = await graffiti.delete(putted2, session);\n const result2 = (await iterator.next()).value;\n assert(result2 && !result2.error);\n expect(result2.value.tombstone).toBe(true);\n expect(result2.value.lastModified).toEqual(deleted.lastModified);\n\n // Put something else\n await graffiti.put(randomPutObject(), session);\n await expect(\n Promise.race([\n iterator.next(),\n new Promise((resolve, reject) => setTimeout(reject, 100, \"Timeout\")),\n ]),\n ).rejects.toThrow(\"Timeout\");\n });\n\n it(\"not allowed\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n const object = randomPutObject();\n const putted = await graffiti.put(object, session1);\n\n const iterator1 = graffiti.synchronizeGet(putted, {}, session1);\n const iterator2 = graffiti.synchronizeGet(putted, {}, session2);\n\n const next1 = iterator1.next();\n const next2 = iterator2.next();\n\n const newValue = { goodbye: \"world\" };\n const putted2 = await graffiti.put(\n {\n ...putted,\n ...object,\n allowed: [],\n value: newValue,\n },\n session1,\n );\n const result1 = (await next1).value;\n const result2 = (await next2).value;\n assert(result1 && !result1.error);\n assert(result2 && !result2.error);\n\n expect(result1.value.value).toEqual(newValue);\n expect(result2.value.value).toEqual(object.value);\n expect(result1.value.actor).toEqual(session1.actor);\n expect(result2.value.actor).toEqual(session1.actor);\n expect(result1.value.channels).toEqual(object.channels);\n expect(result2.value.channels).toEqual([]);\n expect(result1.value.tombstone).toBe(false);\n expect(result2.value.tombstone).toBe(true);\n expect(result1.value.lastModified).toEqual(putted2.lastModified);\n expect(result2.value.lastModified).toEqual(putted2.lastModified);\n });\n });\n};\n", "import { it, expect, describe, assert } from \"vitest\";\nimport type {\n Graffiti,\n GraffitiSession,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport { randomString, nextStreamValue, randomPutObject } from \"./utils\";\n\nexport const graffitiDiscoverTests = (\n useGraffiti: () => Pick<Graffiti, \"discover\" | \"put\" | \"delete\" | \"patch\">,\n useSession1: () => GraffitiSession,\n useSession2: () => GraffitiSession,\n) => {\n describe(\"discover\", { timeout: 20000 }, () => {\n it(\"discover nothing\", async () => {\n const graffiti = useGraffiti();\n const iterator = graffiti.discover([], {});\n expect(await iterator.next()).toHaveProperty(\"done\", true);\n });\n\n it(\"discover single\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n const object = randomPutObject();\n\n const putted = await graffiti.put(object, session);\n\n const queryChannels = [randomString(), object.channels[0]];\n const iterator = graffiti.discover(queryChannels, {});\n const value = await nextStreamValue(iterator);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual([object.channels[0]]);\n expect(value.allowed).toBeUndefined();\n expect(value.actor).toEqual(session.actor);\n expect(value.tombstone).toBe(false);\n expect(value.lastModified).toEqual(putted.lastModified);\n const result2 = await iterator.next();\n expect(result2.done).toBe(true);\n });\n\n it(\"discover wrong channel\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n const object = randomPutObject();\n await graffiti.put(object, session);\n const iterator = graffiti.discover([randomString()], {});\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover not allowed\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n const object = randomPutObject();\n object.allowed = [randomString(), randomString()];\n const putted = await graffiti.put(object, session1);\n\n const iteratorSession1 = graffiti.discover(object.channels, {}, session1);\n const value = await nextStreamValue(iteratorSession1);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual(object.channels);\n expect(value.allowed).toEqual(object.allowed);\n expect(value.actor).toEqual(session1.actor);\n expect(value.tombstone).toBe(false);\n expect(value.lastModified).toEqual(putted.lastModified);\n\n const iteratorSession2 = graffiti.discover(object.channels, {}, session2);\n expect(await iteratorSession2.next()).toHaveProperty(\"done\", true);\n\n const iteratorNoSession = graffiti.discover(object.channels, {});\n expect(await iteratorNoSession.next()).toHaveProperty(\"done\", true);\n });\n\n it(\"discover allowed\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n const object = randomPutObject();\n object.allowed = [randomString(), session2.actor, randomString()];\n const putted = await graffiti.put(object, session1);\n\n const iteratorSession2 = graffiti.discover(object.channels, {}, session2);\n const value = await nextStreamValue(iteratorSession2);\n expect(value.value).toEqual(object.value);\n expect(value.allowed).toEqual([session2.actor]);\n expect(value.channels).toEqual(object.channels);\n expect(value.actor).toEqual(session1.actor);\n expect(value.tombstone).toBe(false);\n expect(value.lastModified).toEqual(putted.lastModified);\n });\n\n for (const prop of [\"name\", \"actor\", \"lastModified\"] as const) {\n it(`discover for ${prop}`, async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n const object1 = randomPutObject();\n const putted1 = await graffiti.put(object1, session1);\n\n const object2 = randomPutObject();\n object2.channels = object1.channels;\n // Make sure the lastModified is different for the query\n await new Promise((r) => setTimeout(r, 20));\n const putted2 = await graffiti.put(object2, session2);\n\n const iterator = graffiti.discover(object1.channels, {\n properties: {\n [prop]: {\n enum: [putted1[prop]],\n },\n },\n });\n\n const value = await nextStreamValue(iterator);\n expect(value.name).toEqual(putted1.name);\n expect(value.name).not.toEqual(putted2.name);\n expect(value.value).toEqual(object1.value);\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n }\n\n it(\"discover with lastModified range\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const object = randomPutObject();\n const putted1 = await graffiti.put(object, session);\n // Make sure the lastModified is different\n await new Promise((r) => setTimeout(r, 20));\n const putted2 = await graffiti.put(object, session);\n\n expect(putted1.name).not.toEqual(putted2.name);\n expect(putted1.lastModified).toBeLessThan(putted2.lastModified);\n\n const gtIterator = graffiti.discover([object.channels[0]], {\n properties: {\n lastModified: {\n minimum: putted2.lastModified,\n exclusiveMinimum: true,\n },\n },\n });\n expect(await gtIterator.next()).toHaveProperty(\"done\", true);\n const gtIteratorEpsilon = graffiti.discover([object.channels[0]], {\n properties: {\n lastModified: {\n minimum: putted2.lastModified - 0.1,\n exclusiveMinimum: true,\n },\n },\n });\n const value1 = await nextStreamValue(gtIteratorEpsilon);\n expect(value1.name).toEqual(putted2.name);\n expect(await gtIteratorEpsilon.next()).toHaveProperty(\"done\", true);\n const gteIterator = graffiti.discover(object.channels, {\n properties: {\n value: {},\n lastModified: {\n minimum: putted2.lastModified,\n },\n },\n });\n const value = await nextStreamValue(gteIterator);\n expect(value.name).toEqual(putted2.name);\n expect(await gteIterator.next()).toHaveProperty(\"done\", true);\n const gteIteratorEpsilon = graffiti.discover(object.channels, {\n properties: {\n lastModified: {\n minimum: putted2.lastModified + 0.1,\n },\n },\n });\n expect(await gteIteratorEpsilon.next()).toHaveProperty(\"done\", true);\n\n const ltIterator = graffiti.discover(object.channels, {\n properties: {\n lastModified: {\n maximum: putted1.lastModified,\n exclusiveMaximum: true,\n },\n },\n });\n expect(await ltIterator.next()).toHaveProperty(\"done\", true);\n\n const ltIteratorEpsilon = graffiti.discover(object.channels, {\n properties: {\n lastModified: {\n maximum: putted1.lastModified + 0.1,\n exclusiveMaximum: true,\n },\n },\n });\n const value3 = await nextStreamValue(ltIteratorEpsilon);\n expect(value3.name).toEqual(putted1.name);\n expect(await ltIteratorEpsilon.next()).toHaveProperty(\"done\", true);\n\n const lteIterator = graffiti.discover(object.channels, {\n properties: {\n lastModified: {\n maximum: putted1.lastModified,\n },\n },\n });\n const value2 = await nextStreamValue(lteIterator);\n expect(value2.name).toEqual(putted1.name);\n expect(await lteIterator.next()).toHaveProperty(\"done\", true);\n\n const lteIteratorEpsilon = graffiti.discover(object.channels, {\n properties: {\n lastModified: {\n maximum: putted1.lastModified - 0.1,\n },\n },\n });\n expect(await lteIteratorEpsilon.next()).toHaveProperty(\"done\", true);\n });\n\n it(\"discover schema allowed, as and not as owner\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n const object = randomPutObject();\n object.allowed = [randomString(), session2.actor, randomString()];\n await graffiti.put(object, session1);\n\n const iteratorSession1 = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n minItems: 3,\n // Make sure session2.actor is in the allow list\n not: {\n items: {\n not: {\n enum: [session2.actor],\n },\n },\n },\n },\n },\n },\n session1,\n );\n const value = await nextStreamValue(iteratorSession1);\n expect(value.value).toEqual(object.value);\n await expect(iteratorSession1.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n\n const iteratorSession2BigAllow = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n minItems: 3,\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2BigAllow.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2PeekOther = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n not: {\n items: {\n not: {\n enum: [object.channels[0]],\n },\n },\n },\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2PeekOther.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2SmallAllowPeekSelf = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n maxItems: 1,\n not: {\n items: {\n not: {\n enum: [session2.actor],\n },\n },\n },\n },\n },\n },\n session2,\n );\n const value2 = await nextStreamValue(iteratorSession2SmallAllowPeekSelf);\n expect(value2.value).toEqual(object.value);\n await expect(\n iteratorSession2SmallAllowPeekSelf.next(),\n ).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover schema channels, as and not as owner\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n const session2 = useSession2();\n\n const object = randomPutObject();\n object.channels = [randomString(), randomString(), randomString()];\n await graffiti.put(object, session1);\n\n const iteratorSession1 = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n minItems: 3,\n // Make sure session2.actor is in the allow list\n not: {\n items: {\n not: {\n enum: [object.channels[1]],\n },\n },\n },\n },\n },\n },\n session1,\n );\n const value = await nextStreamValue(iteratorSession1);\n expect(value.value).toEqual(object.value);\n await expect(iteratorSession1.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n\n const iteratorSession2BigAllow = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n minItems: 3,\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2BigAllow.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2PeekOther = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n not: {\n items: {\n not: {\n enum: [object.channels[1]],\n },\n },\n },\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2PeekOther.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2SmallAllowPeekSelf = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n allowed: {\n maxItems: 2,\n not: {\n items: {\n not: {\n enum: [object.channels[2]],\n },\n },\n },\n },\n },\n },\n session2,\n );\n const value2 = await nextStreamValue(iteratorSession2SmallAllowPeekSelf);\n expect(value2.value).toEqual(object.value);\n await expect(\n iteratorSession2SmallAllowPeekSelf.next(),\n ).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover query for empty allowed\", async () => {\n const graffiti = useGraffiti();\n const session1 = useSession1();\n\n const publicO = randomPutObject();\n\n const publicSchema = {\n not: {\n required: [\"allowed\"],\n },\n } satisfies JSONSchema4;\n\n await graffiti.put(publicO, session1);\n const iterator = graffiti.discover(\n publicO.channels,\n publicSchema,\n session1,\n );\n const value = await nextStreamValue(iterator);\n expect(value.value).toEqual(publicO.value);\n expect(value.allowed).toBeUndefined();\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n\n const restricted = randomPutObject();\n restricted.allowed = [];\n await graffiti.put(restricted, session1);\n const iterator2 = graffiti.discover(\n restricted.channels,\n publicSchema,\n session1,\n );\n await expect(iterator2.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover query for values\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const object1 = randomPutObject();\n object1.value = { test: randomString() };\n await graffiti.put(object1, session);\n\n const object2 = randomPutObject();\n object2.channels = object1.channels;\n object2.value = { test: randomString(), something: randomString() };\n await graffiti.put(object2, session);\n\n const object3 = randomPutObject();\n object3.channels = object1.channels;\n object3.value = { other: randomString(), something: randomString() };\n await graffiti.put(object3, session);\n\n const counts = new Map<string, number>();\n for (const property of [\"test\", \"something\", \"other\"] as const) {\n let count = 0;\n for await (const result of graffiti.discover(object1.channels, {\n properties: {\n value: {\n required: [property],\n },\n },\n })) {\n assert(!result.error, \"result has error\");\n if (property in result.value.value) {\n count++;\n }\n }\n counts.set(property, count);\n }\n\n expect(counts.get(\"test\")).toBe(2);\n expect(counts.get(\"something\")).toBe(2);\n expect(counts.get(\"other\")).toBe(1);\n });\n\n it(\"discover for deleted content\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const object = randomPutObject();\n const putted = await graffiti.put(object, session);\n const deleted = await graffiti.delete(putted, session);\n\n const iterator = graffiti.discover(object.channels, {});\n const value = await nextStreamValue(iterator);\n expect(value.tombstone).toBe(true);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual(object.channels);\n expect(value.actor).toEqual(session.actor);\n expect(value.lastModified).toEqual(deleted.lastModified);\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover for replaced channels\", async () => {\n // Do this a bunch to check for concurrency issues\n for (let i = 0; i < 10; i++) {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const object1 = randomPutObject();\n const putted = await graffiti.put(object1, session);\n const object2 = randomPutObject();\n const replaced = await graffiti.put(\n {\n ...putted,\n ...object2,\n },\n session,\n );\n\n const iterator1 = graffiti.discover(object1.channels, {});\n const value1 = await nextStreamValue(iterator1);\n await expect(iterator1.next()).resolves.toHaveProperty(\"done\", true);\n\n const iterator2 = graffiti.discover(object2.channels, {});\n const value2 = await nextStreamValue(iterator2);\n await expect(iterator2.next()).resolves.toHaveProperty(\"done\", true);\n\n // If they have the same timestamp, except\n // only one to have a tombstone\n if (putted.lastModified === replaced.lastModified) {\n expect(value1.tombstone || value2.tombstone).toBe(true);\n expect(value1.tombstone && value2.tombstone).toBe(false);\n } else {\n expect(value1.tombstone).toBe(true);\n expect(value1.value).toEqual(object1.value);\n expect(value1.channels).toEqual(object1.channels);\n expect(value1.lastModified).toEqual(replaced.lastModified);\n\n expect(value2.tombstone).toBe(false);\n expect(value2.value).toEqual(object2.value);\n expect(value2.channels).toEqual(object2.channels);\n expect(value2.lastModified).toEqual(replaced.lastModified);\n }\n }\n });\n\n it(\"discover for patched allowed\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n const object = randomPutObject();\n const putted = await graffiti.put(object, session);\n await graffiti.patch(\n {\n allowed: [{ op: \"add\", path: \"\", value: [] }],\n },\n putted,\n session,\n );\n const iterator = graffiti.discover(object.channels, {});\n const value = await nextStreamValue(iterator);\n expect(value.tombstone).toBe(true);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual(object.channels);\n expect(value.allowed).toBeUndefined();\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"put concurrently and discover one\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const object = randomPutObject();\n object.name = randomString();\n\n const putPromises = Array(100)\n .fill(0)\n .map(() => graffiti.put(object, session));\n await Promise.all(putPromises);\n\n const iterator = graffiti.discover(object.channels, {});\n let tombstoneCount = 0;\n let valueCount = 0;\n for await (const result of iterator) {\n assert(!result.error, \"result has error\");\n if (result.value.tombstone) {\n tombstoneCount++;\n } else {\n valueCount++;\n }\n }\n expect(tombstoneCount).toBe(99);\n expect(valueCount).toBe(1);\n });\n });\n};\n", "import { it, expect, describe, assert } from \"vitest\";\nimport type { Graffiti, GraffitiSession } from \"@graffiti-garden/api\";\nimport { randomPutObject, randomString } from \"./utils\";\n\nexport const graffitiOrphanTests = (\n useGraffiti: () => Pick<\n Graffiti,\n \"recoverOrphans\" | \"put\" | \"delete\" | \"patch\"\n >,\n useSession1: () => GraffitiSession,\n useSession2: () => GraffitiSession,\n) => {\n describe(\"recoverOrphans\", () => {\n it(\"list orphans\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const existingOrphans: string[] = [];\n const orphanIterator1 = graffiti.recoverOrphans({}, session);\n for await (const orphan of orphanIterator1) {\n if (orphan.error) continue;\n existingOrphans.push(orphan.value.name);\n }\n\n const object = randomPutObject();\n object.channels = [];\n const putted = await graffiti.put(object, session);\n const orphanIterator2 = graffiti.recoverOrphans({}, session);\n let numResults = 0;\n for await (const orphan of orphanIterator2) {\n if (orphan.error) continue;\n if (orphan.value.name === putted.name) {\n numResults++;\n expect(orphan.value.source).toBe(putted.source);\n expect(orphan.value.lastModified).toBe(putted.lastModified);\n }\n }\n expect(numResults).toBe(1);\n });\n\n it(\"replaced orphan, no longer\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const object = randomPutObject();\n object.channels = [];\n const putOrphan = await graffiti.put(object, session);\n\n const putNotOrphan = await graffiti.put(\n {\n ...putOrphan,\n ...object,\n channels: [randomString()],\n },\n session,\n );\n expect(putNotOrphan.name).toBe(putOrphan.name);\n\n const orphanIterator = graffiti.recoverOrphans({}, session);\n let numResults = 0;\n for await (const orphan of orphanIterator) {\n if (orphan.error) continue;\n if (orphan.value.name === putOrphan.name) {\n numResults++;\n expect(orphan.value.tombstone).toBe(true);\n expect(orphan.value.lastModified).toBe(putNotOrphan.lastModified);\n expect(orphan.value.channels).toEqual([]);\n }\n }\n expect(numResults).toBe(1);\n });\n });\n};\n", "import { it, expect, describe, assert } from \"vitest\";\nimport type { Graffiti, GraffitiSession } from \"@graffiti-garden/api\";\nimport { randomPutObject, randomString } from \"./utils\";\n\nexport const graffitiChannelStatsTests = (\n useGraffiti: () => Pick<\n Graffiti,\n \"channelStats\" | \"put\" | \"delete\" | \"patch\"\n >,\n useSession1: () => GraffitiSession,\n useSession2: () => GraffitiSession,\n) => {\n describe(\"channel stats\", () => {\n it(\"list channels\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const existingChannels: Map<string, number> = new Map();\n const channelIterator1 = graffiti.channelStats(session);\n for await (const channel of channelIterator1) {\n if (channel.error) continue;\n existingChannels.set(channel.value.channel, channel.value.count);\n }\n\n const channels = [randomString(), randomString(), randomString()];\n\n // Add one value to channels[0],\n // two values to both channels[0] and channels[1],\n // three values to all channels\n // one value to channels[2]\n for (let i = 0; i < 3; i++) {\n for (let j = 0; j < i + 1; j++) {\n await graffiti.put(\n {\n value: {\n index: j,\n },\n channels: channels.slice(0, i + 1),\n },\n session,\n );\n }\n }\n await graffiti.put(\n { value: { index: 3 }, channels: [channels[2]] },\n session,\n );\n\n const channelIterator2 = graffiti.channelStats(session);\n let newChannels: Map<string, number> = new Map();\n for await (const channel of channelIterator2) {\n if (channel.error) continue;\n newChannels.set(channel.value.channel, channel.value.count);\n }\n // Filter out existing channels\n newChannels = new Map(\n Array.from(newChannels).filter(\n ([channel, count]) => !existingChannels.has(channel),\n ),\n );\n expect(newChannels.size).toBe(3);\n expect(newChannels.get(channels[0])).toBe(6);\n expect(newChannels.get(channels[1])).toBe(5);\n expect(newChannels.get(channels[2])).toBe(4);\n });\n\n it(\"list channels with deleted channel\", async () => {\n const graffiti = useGraffiti();\n const session = useSession1();\n\n const channels = [randomString(), randomString(), randomString()];\n\n // Add an item with two channels\n const before = await graffiti.put(\n {\n value: { index: 2 },\n channels: channels.slice(1),\n },\n session,\n );\n\n // Add an item with all channels\n const first = await graffiti.put(\n { value: { index: 0 }, channels },\n session,\n );\n // But then delete it\n await graffiti.delete(first, session);\n\n // Create a new object with only one channel\n const second = await graffiti.put(\n {\n value: { index: 1 },\n channels: channels.slice(2),\n },\n session,\n );\n\n const channelIterator = graffiti.channelStats(session);\n\n let got1 = 0;\n let got2 = 0;\n for await (const result of channelIterator) {\n if (result.error) continue;\n const { channel, count, lastModified } = result.value;\n assert(\n channel !== channels[0],\n \"There should not be an object in channel[0]\",\n );\n if (channel === channels[1]) {\n expect(count).toBe(1);\n expect(lastModified).toBe(before.lastModified);\n got1++;\n } else if (channel === channels[2]) {\n expect(count).toBe(2);\n expect(lastModified).toBe(second.lastModified);\n got2++;\n }\n }\n expect(got1).toBe(1);\n expect(got2).toBe(1);\n });\n });\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["it", "expect", "describe", "GraffitiErrorInvalidUri", "assert", "randomString", "array", "b", "randomValue", "randomPutObject", "nextStreamValue", "iterator", "result", "graffitiLocationTests", "useGraffiti", "describe", "it", "graffiti", "location", "randomString", "uri", "location2", "expect", "location1", "prop", "uri1", "uri2", "GraffitiErrorInvalidUri", "it", "expect", "describe", "GraffitiErrorNotFound", "GraffitiErrorSchemaMismatch", "GraffitiErrorInvalidSchema", "GraffitiErrorForbidden", "GraffitiErrorPatchTestFailed", "GraffitiErrorPatchError", "graffitiCRUDTests", "useGraffiti", "useSession1", "useSession2", "describe", "it", "graffiti", "session", "value", "channels", "randomString", "previous", "expect", "gotten", "newValue", "beforeReplaced", "afterReplaced", "beforeDeleted", "final", "putted", "randomPutObject", "GraffitiErrorNotFound", "session1", "session2", "GraffitiErrorForbidden", "schema", "goodValue", "GraffitiErrorInvalidSchema", "GraffitiErrorSchemaMismatch", "allowed", "gotten2", "patch", "beforePatched", "deleted", "beforePatch", "channelsBefore", "channelsAfter", "patched", "result", "GraffitiErrorPatchTestFailed", "object", "GraffitiErrorPatchError", "patches", "it", "expect", "describe", "assert", "graffitiSynchronizeTests", "useGraffiti", "useSession1", "useSession2", "describe", "it", "graffiti1", "session", "object", "randomPutObject", "channels", "putted", "graffiti2", "next", "gotten", "result", "expect", "graffiti", "beforeChannel", "randomString", "afterChannel", "sharedChannel", "oldValue", "oldChannels", "before", "after", "shared", "newValue", "newChannels", "beforeResult", "afterResult", "sharedResult", "c", "session1", "session2", "allChannels", "creatorNext", "allowedNext", "noSession", "value", "allowed", "resolve", "rejects", "creatorResult", "allowedResult", "iterator", "putted2", "assert", "deleted", "result2", "reject", "iterator1", "iterator2", "next1", "next2", "result1", "it", "expect", "describe", "assert", "graffitiDiscoverTests", "useGraffiti", "useSession1", "useSession2", "describe", "it", "iterator", "expect", "graffiti", "session", "object", "randomPutObject", "putted", "queryChannels", "randomString", "value", "nextStreamValue", "result2", "session1", "session2", "iteratorSession1", "iteratorSession2", "iteratorNoSession", "prop", "object1", "putted1", "object2", "r", "putted2", "gtIterator", "gtIteratorEpsilon", "value1", "gteIterator", "gteIteratorEpsilon", "ltIterator", "ltIteratorEpsilon", "value3", "lteIterator", "value2", "lteIteratorEpsilon", "iteratorSession2BigAllow", "iteratorSession2PeekOther", "iteratorSession2SmallAllowPeekSelf", "publicO", "publicSchema", "restricted", "iterator2", "object3", "counts", "property", "count", "result", "assert", "deleted", "i", "replaced", "iterator1", "putPromises", "tombstoneCount", "valueCount", "it", "expect", "describe", "graffitiOrphanTests", "useGraffiti", "useSession1", "useSession2", "describe", "it", "graffiti", "session", "existingOrphans", "orphanIterator1", "orphan", "object", "randomPutObject", "putted", "orphanIterator2", "numResults", "expect", "putOrphan", "putNotOrphan", "randomString", "orphanIterator", "it", "expect", "describe", "assert", "graffitiChannelStatsTests", "useGraffiti", "useSession1", "useSession2", "describe", "it", "graffiti", "session", "existingChannels", "channelIterator1", "channel", "channels", "randomString", "i", "j", "channelIterator2", "newChannels", "count", "expect", "before", "first", "second", "channelIterator", "got1", "got2", "result", "lastModified", "assert"]
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graffiti-garden/api",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "The heart of Graffiti",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"types": "./dist/src/index.d.ts",
|
|
6
7
|
"module": "./dist/index.js",
|
|
7
8
|
"main": "./dist/index.cjs.js",
|
|
@@ -9,7 +10,6 @@
|
|
|
9
10
|
".": {
|
|
10
11
|
"import": {
|
|
11
12
|
"types": "./dist/src/index.d.ts",
|
|
12
|
-
"node": "./dist/index.cjs.js",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
14
|
},
|
|
15
15
|
"require": {
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"./tests": {
|
|
21
|
-
"types": "./dist/tests/
|
|
22
|
-
"default": "./dist/tests
|
|
21
|
+
"types": "./dist/tests/index.d.ts",
|
|
22
|
+
"default": "./dist/tests.js"
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"author": "Theia Henderson",
|
|
33
33
|
"license": "GPL-3.0-or-later",
|
|
34
34
|
"scripts": {
|
|
35
|
+
"build:types": "tsc --declaration --emitDeclarationOnly",
|
|
36
|
+
"build:js": "tsx esbuild.config.ts",
|
|
37
|
+
"build": "rm -rf dist && npm run build:types && npm run build:js",
|
|
35
38
|
"docs": "typedoc --options typedoc.json",
|
|
36
|
-
"build-api": "rollup -c rollup.config.ts --configPlugin rollup-plugin-typescript2",
|
|
37
|
-
"build-tests": "cd tests && rollup -c rollup.config.ts --configPlugin rollup-plugin-typescript2",
|
|
38
|
-
"build": "npm run build-api && npm run build-tests",
|
|
39
39
|
"prepublishOnly": "npm update && npm run build"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
@@ -47,12 +47,11 @@
|
|
|
47
47
|
},
|
|
48
48
|
"homepage": "https://api.graffiti.garden/classes/Graffiti.html",
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"vitest": "^2.1.8"
|
|
50
|
+
"@types/node": "^22.13.1",
|
|
51
|
+
"tsx": "^4.19.2",
|
|
52
|
+
"typedoc": "^0.27.6",
|
|
53
|
+
"typescript": "^5.7.3",
|
|
54
|
+
"vitest": "^2.1.9"
|
|
56
55
|
},
|
|
57
56
|
"dependencies": {
|
|
58
57
|
"@types/json-schema": "^7.0.15",
|
package/src/2-types.ts
CHANGED
|
@@ -286,7 +286,7 @@ export type ChannelStats = {
|
|
|
286
286
|
*/
|
|
287
287
|
count: number;
|
|
288
288
|
/**
|
|
289
|
-
* The time that the actor {@link
|
|
289
|
+
* The time that the actor {@link GraffitiObjectBase.lastModified | last modified} an object in the channel,
|
|
290
290
|
* measured in milliseconds since January 1, 1970.
|
|
291
291
|
* {@link GraffitiObjectBase.tombstone | Tombstone}d objects do not effect this modification time.
|
|
292
292
|
*/
|
package/dist/tests/index.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{assert as e,describe as a,it as t,expect as o}from"vitest";import{GraffitiErrorInvalidUri as n,GraffitiErrorNotFound as l,GraffitiErrorForbidden as s,GraffitiErrorInvalidSchema as i,GraffitiErrorSchemaMismatch as c,GraffitiErrorPatchTestFailed as r,GraffitiErrorPatchError as u}from"@graffiti-garden/api";function d(){const e=new Uint8Array(16);crypto.getRandomValues(e);return Array.from(e).map((e=>e.toString(16).padStart(2,"0"))).join("")+"👩🏽❤️💋👩🏻🫱🏼🫲🏿"}function v(){return{value:{[d()]:d()},channels:[d(),d()]}}async function h(a){const t=await a.next();return e(!t.done&&!t.value.error,"result has no value"),t.value.value}const w=e=>{a("URI and location conversion",(()=>{t("location to uri and back",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()},n=a.locationToUri(t),l=a.uriToLocation(n);o(t).toEqual(l)})),t("collision resistance",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()};for(const e of["name","actor","source"]){const n={...t,[e]:d()},l=a.locationToUri(t),s=a.locationToUri(n);o(l).not.toEqual(s)}})),t("random URI should not be a valid location",(async()=>{const a=e();o((()=>a.uriToLocation(""))).toThrow(n)}))}))},p=(e,n,h)=>{a("CRUD",{timeout:2e4},(()=>{t("put, get, delete",(async()=>{const a=e(),t=n(),l={something:"hello, world~ c:"},s=[d(),d()],i=await a.put({value:l,channels:s},t);o(i.value).toEqual({}),o(i.channels).toEqual([]),o(i.allowed).toBeUndefined(),o(i.actor).toEqual(t.actor);const c=await a.get(i,{});o(c.value).toEqual(l),o(c.channels).toEqual([]),o(c.allowed).toBeUndefined(),o(c.name).toEqual(i.name),o(c.actor).toEqual(i.actor),o(c.source).toEqual(i.source),o(c.lastModified).toEqual(i.lastModified);const r={something:"goodbye, world~ :c"},u=await a.put({...i,value:r,channels:[]},t);o(u.value).toEqual(l),o(u.tombstone).toEqual(!0),o(u.name).toEqual(i.name),o(u.actor).toEqual(i.actor),o(u.source).toEqual(i.source),o(u.lastModified).toBeGreaterThan(c.lastModified);const v=await a.get(i,{});o(v.value).toEqual(r),o(v.lastModified).toEqual(u.lastModified),o(v.tombstone).toEqual(!1);const h=await a.delete(v,t);o(h.tombstone).toEqual(!0),o(h.value).toEqual(r),o(h.lastModified).toBeGreaterThan(u.lastModified);const w=await a.get(v,{});o(w).toEqual(h)})),t("get non-existant",(async()=>{const a=e(),t=n(),s=await a.put(v(),t);await o(a.get({...s,name:d()},{})).rejects.toBeInstanceOf(l)})),t("put, get, delete with wrong actor",(async()=>{const a=e(),t=n(),l=h();await o(a.put({value:{},channels:[],actor:l.actor},t)).rejects.toThrow(s);const i=await a.put({value:{},channels:[]},l);await o(a.delete(i,t)).rejects.toThrow(s),await o(a.patch({},i,t)).rejects.toThrow(s)})),t("put and get with schema",(async()=>{const a=e(),t=n(),l={something:"hello",another:42},s=await a.put({value:l,channels:[]},t),i=await a.get(s,{properties:{value:{properties:{something:{type:"string"},another:{type:"integer"}}}}});o(i.value.something).toEqual(l.something),o(i.value.another).toEqual(l.another)})),t("put and get with invalid schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{},channels:[]},t);await o(a.get(l,{properties:{value:{type:"asdf"}}})).rejects.toThrow(i)})),t("put and get with wrong schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{hello:"world"},channels:[]},t);await o(a.get(l,{properties:{value:{properties:{hello:{type:"number"}}}}})).rejects.toThrow(c)})),t("put and get with empty access control",(async()=>{const a=e(),t=n(),s=h(),i={um:"hi"},c=[d()],r=[d()],u=await a.put({value:i,allowed:c,channels:r},t),v=await a.get(u,{},t);o(v.value).toEqual(i),o(v.allowed).toEqual(c),o(v.channels).toEqual(r),await o(a.get(u,{})).rejects.toBeInstanceOf(l),await o(a.get(u,{},s)).rejects.toBeInstanceOf(l)})),t("put and get with specific access control",(async()=>{const a=e(),t=n(),s=h(),i={um:"hi"},c=[d(),s.actor,d()],r=[d()],u=await a.put({value:i,allowed:c,channels:r},t),v=await a.get(u,{},t);o(v.value).toEqual(i),o(v.allowed).toEqual(c),o(v.channels).toEqual(r),await o(a.get(u,{})).rejects.toBeInstanceOf(l);const w=await a.get(u,{},s);o(w.value).toEqual(i),o(w.allowed).toEqual([s.actor]),o(w.channels).toEqual([])})),t("patch value",(async()=>{const a=e(),t=n(),l={something:"hello, world~ c:"},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something",value:"goodbye, world~ :c"}]},s,t);o(i.value).toEqual(l),o(i.tombstone).toBe(!0);const c=await a.get(s,{});o(c.value).toEqual({something:"goodbye, world~ :c"}),o(i.lastModified).toBe(c.lastModified),await a.delete(s,t)})),t("patch deleted object",(async()=>{const a=e(),t=n(),s=await a.put(v(),t);await a.delete(s,t),await o(a.patch({},s,t)).rejects.toBeInstanceOf(l)})),t("deep patch",(async()=>{const a=e(),t=n(),l={something:{another:{somethingElse:"hello"}}},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something/another/somethingElse",value:"goodbye"}]},s,t),c=await a.get(s,{});o(i.value).toEqual(l),o(c.value).toEqual({something:{another:{somethingElse:"goodbye"}}})})),t("patch channels",(async()=>{const a=e(),t=n(),l=[d()],s=[d()],i=await a.put({value:{},channels:l},t),c={channels:[{op:"replace",path:"/0",value:s[0]}]},r=await a.patch(c,i,t);o(r.channels).toEqual(l);const u=await a.get(i,{},t);o(u.channels).toEqual(s),await a.delete(i,t)})),t("patch 'increment' with test",(async()=>{const a=e(),t=n(),l=await a.put({value:{counter:1},channels:[]},t),s=await a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:2}]},l,t);o(s.value).toEqual({counter:1});const i=await a.get(s,{properties:{value:{properties:{counter:{type:"integer"}}}}});o(i.value.counter).toEqual(2),await o(a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:3}]},l,t)).rejects.toThrow(r)})),t("invalid patch",(async()=>{const a=e(),t=n(),l=v(),s=await a.put(l,t);await o(a.patch({value:[{op:"add",path:"/root",value:[]},{op:"add",path:"/root/2",value:2}]},s,t)).rejects.toThrow(u)})),t("patch channels to be wrong",(async()=>{const a=e(),t=n(),l=v();l.allowed=[d()];const s=await a.put(l,t),i=[{channels:[{op:"replace",path:"",value:null}]},{channels:[{op:"replace",path:"",value:{}}]},{channels:[{op:"replace",path:"",value:["hello",["hi"]]}]},{channels:[{op:"add",path:"/0",value:1}]},{value:[{op:"replace",path:"",value:"not an object"}]},{value:[{op:"replace",path:"",value:null}]},{value:[{op:"replace",path:"",value:[]}]},{allowed:[{op:"replace",path:"",value:{}}]},{allowed:[{op:"replace",path:"",value:["hello",["hi"]]}]}];for(const e of i)await o(a.patch(e,s,t)).rejects.toThrow(u);const c=await a.get(s,{},t);o(c.value).toEqual(l.value),o(c.channels).toEqual(l.channels),o(c.allowed).toEqual(l.allowed),o(c.lastModified).toEqual(s.lastModified)}))}))},m=(n,l,s)=>{a("synchronizeDiscover",(()=>{t("get",(async()=>{const e=n(),a=l(),t=v(),s=t.channels.slice(1),i=await e.put(t,a),c=n(),r=c.synchronizeDiscover(s,{}).next(),u=await c.get(i,{},a),d=(await r).value;if(!d||d.error)throw new Error("Error in synchronize");o(d.value.value).toEqual(t.value),o(d.value.channels).toEqual(s),o(d.value.tombstone).toBe(!1),o(d.value.lastModified).toEqual(u.lastModified)})),t("put",(async()=>{const e=n(),a=l(),t=d(),s=d(),i=d(),c={hello:"world"},r=[t,i],u=await e.put({value:c,channels:r},a),v=e.synchronizeDiscover([t],{}).next(),h=e.synchronizeDiscover([s],{}).next(),w=e.synchronizeDiscover([i],{}).next(),p={goodbye:"world"},m=[s,i];await e.put({...u,value:p,channels:m},a);const E=(await v).value,f=(await h).value,q=(await w).value;if(!E||E.error||!f||f.error||!q||q.error)throw new Error("Error in synchronize");o(E.value.value).toEqual(c),o(E.value.channels).toEqual([t]),o(E.value.tombstone).toBe(!0),o(f.value.value).toEqual(p),o(f.value.channels).toEqual([s]),o(f.value.tombstone).toBe(!1),o(q.value.value).toEqual(p),o(q.value.channels).toEqual([i]),o(q.value.tombstone).toBe(!1),o(E.value.lastModified).toEqual(f.value.lastModified),o(q.value.lastModified).toEqual(f.value.lastModified)})),t("patch",(async()=>{const e=n(),a=l(),t=d(),s=d(),i=d(),c={hello:"world"},r=[t,i],u=await e.put({value:c,channels:r},a),v=e.synchronizeDiscover([t],{}).next(),h=e.synchronizeDiscover([s],{}).next(),w=e.synchronizeDiscover([i],{}).next();await e.patch({value:[{op:"add",path:"/something",value:"new value"}],channels:[{op:"add",path:"/-",value:s},{op:"remove",path:`/${r.indexOf(t)}`}]},u,a);const p=(await v).value,m=(await h).value,E=(await w).value;if(!p||p.error||!m||m.error||!E||E.error)throw new Error("Error in synchronize");const f={...c,something:"new value"};o(p.value.value).toEqual(c),o(p.value.channels).toEqual([t]),o(p.value.tombstone).toBe(!0),o(m.value.value).toEqual(f),o(m.value.channels).toEqual([s]),o(m.value.tombstone).toBe(!1),o(E.value.value).toEqual(f),o(E.value.channels).toEqual([i]),o(E.value.tombstone).toBe(!1),o(p.value.lastModified).toEqual(m.value.lastModified),o(E.value.lastModified).toEqual(m.value.lastModified)})),t("delete",(async()=>{const e=n(),a=l(),t=[d(),d(),d()],s={hello:"world"},i=[d(),...t.slice(1)],c=await e.put({value:s,channels:i},a),r=e.synchronizeDiscover(t,{}).next();e.delete(c,a);const u=(await r).value;if(!u||u.error)throw new Error("Error in synchronize");o(u.value.tombstone).toBe(!0),o(u.value.value).toEqual(s),o(u.value.channels).toEqual(t.filter((e=>i.includes(e))))})),t("not allowed",(async()=>{const e=n(),a=l(),t=s(),i=[d(),d(),d()],c=i.slice(1),r=e.synchronizeDiscover(c,{},a).next(),u=e.synchronizeDiscover(c,{},t).next(),v=e.synchronizeDiscover(c,{}).next(),h={hello:"world"},w=[d(),t.actor];await e.put({value:h,channels:i,allowed:w},a),await o(Promise.race([v,new Promise(((e,a)=>setTimeout(a,100,"Timeout")))])).rejects.toThrow("Timeout");const p=(await r).value,m=(await u).value;if(!p||p.error||!m||m.error)throw new Error("Error in synchronize");o(p.value.value).toEqual(h),o(p.value.allowed).toEqual(w),o(p.value.channels).toEqual(i),o(m.value.value).toEqual(h),o(m.value.allowed).toEqual([t.actor]),o(m.value.channels).toEqual(c)}))})),a("synchronizeGet",(()=>{t("replace, delete",(async()=>{const a=n(),t=l(),s=v(),i=await a.put(s,t),c=a.synchronizeGet(i,{}),r=c.next(),u={goodbye:"world"},d=await a.put({...i,value:u},t),h=(await r).value;e(h&&!h.error),o(h.value.value).toEqual(u),o(h.value.actor).toEqual(t.actor),o(h.value.channels).toEqual([]),o(h.value.tombstone).toBe(!1),o(h.value.lastModified).toEqual(d.lastModified),o(h.value.allowed).toBeUndefined();const w=await a.delete(d,t),p=(await c.next()).value;e(p&&!p.error),o(p.value.tombstone).toBe(!0),o(p.value.lastModified).toEqual(w.lastModified),await a.put(v(),t),await o(Promise.race([c.next(),new Promise(((e,a)=>setTimeout(a,100,"Timeout")))])).rejects.toThrow("Timeout")})),t("not allowed",(async()=>{const a=n(),t=l(),i=s(),c=v(),r=await a.put(c,t),u=a.synchronizeGet(r,{},t),d=a.synchronizeGet(r,{},i),h=u.next(),w=d.next(),p={goodbye:"world"},m=await a.put({...r,...c,allowed:[],value:p},t),E=(await h).value,f=(await w).value;e(E&&!E.error),e(f&&!f.error),o(E.value.value).toEqual(p),o(f.value.value).toEqual(c.value),o(E.value.actor).toEqual(t.actor),o(f.value.actor).toEqual(t.actor),o(E.value.channels).toEqual(c.channels),o(f.value.channels).toEqual([]),o(E.value.tombstone).toBe(!1),o(f.value.tombstone).toBe(!0),o(E.value.lastModified).toEqual(m.lastModified),o(f.value.lastModified).toEqual(m.lastModified)}))}))},E=(n,l,s)=>{a("discover",{timeout:2e4},(()=>{t("discover nothing",(async()=>{const e=n().discover([],{});o(await e.next()).toHaveProperty("done",!0)})),t("discover single",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=[d(),t.channels[0]],c=e.discover(i,{}),r=await h(c);o(r.value).toEqual(t.value),o(r.channels).toEqual([t.channels[0]]),o(r.allowed).toBeUndefined(),o(r.actor).toEqual(a.actor),o(r.tombstone).toBe(!1),o(r.lastModified).toEqual(s.lastModified);const u=await c.next();o(u.done).toBe(!0)})),t("discover wrong channel",(async()=>{const e=n(),a=l(),t=v();await e.put(t,a);const s=e.discover([d()],{});await o(s.next()).resolves.toHaveProperty("done",!0)})),t("discover not allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),d()];const c=await e.put(i,a),r=e.discover(i.channels,{},a),u=await h(r);o(u.value).toEqual(i.value),o(u.channels).toEqual(i.channels),o(u.allowed).toEqual(i.allowed),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(c.lastModified);const w=e.discover(i.channels,{},t);o(await w.next()).toHaveProperty("done",!0);const p=e.discover(i.channels,{});o(await p.next()).toHaveProperty("done",!0)})),t("discover allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()];const c=await e.put(i,a),r=e.discover(i.channels,{},t),u=await h(r);o(u.value).toEqual(i.value),o(u.allowed).toEqual([t.actor]),o(u.channels).toEqual(i.channels),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(c.lastModified)}));for(const e of["name","actor","lastModified"])t(`discover for ${e}`,(async()=>{const a=n(),t=l(),i=s(),c=v(),r=await a.put(c,t),u=v();u.channels=c.channels,await new Promise((e=>setTimeout(e,20)));const d=await a.put(u,i),w=a.discover(c.channels,{properties:{[e]:{enum:[r[e]]}}}),p=await h(w);o(p.name).toEqual(r.name),o(p.name).not.toEqual(d.name),o(p.value).toEqual(c.value),await o(w.next()).resolves.toHaveProperty("done",!0)}));t("discover with lastModified range",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await new Promise((e=>setTimeout(e,20)));const i=await e.put(t,a);o(s.name).not.toEqual(i.name),o(s.lastModified).toBeLessThan(i.lastModified);const c=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified,exclusiveMinimum:!0}}});o(await c.next()).toHaveProperty("done",!0);const r=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified-.1,exclusiveMinimum:!0}}}),u=await h(r);o(u.name).toEqual(i.name),o(await r.next()).toHaveProperty("done",!0);const d=e.discover(t.channels,{properties:{value:{},lastModified:{minimum:i.lastModified}}}),w=await h(d);o(w.name).toEqual(i.name),o(await d.next()).toHaveProperty("done",!0);const p=e.discover(t.channels,{properties:{lastModified:{minimum:i.lastModified+.1}}});o(await p.next()).toHaveProperty("done",!0);const m=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified,exclusiveMaximum:!0}}});o(await m.next()).toHaveProperty("done",!0);const E=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified+.1,exclusiveMaximum:!0}}}),f=await h(E);o(f.name).toEqual(s.name),o(await E.next()).toHaveProperty("done",!0);const q=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified}}}),y=await h(q);o(y.name).toEqual(s.name),o(await q.next()).toHaveProperty("done",!0);const M=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified-.1}}});o(await M.next()).toHaveProperty("done",!0)})),t("discover schema allowed, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()],await e.put(i,a);const c=e.discover(i.channels,{properties:{allowed:{minItems:3,not:{items:{not:{enum:[t.actor]}}}}}},a),r=await h(c);o(r.value).toEqual(i.value),await o(c.next()).resolves.toHaveProperty("done",!0);const u=e.discover(i.channels,{properties:{allowed:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover(i.channels,{properties:{allowed:{not:{items:{not:{enum:[i.channels[0]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover(i.channels,{properties:{allowed:{maxItems:1,not:{items:{not:{enum:[t.actor]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover schema channels, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.channels=[d(),d(),d()],await e.put(i,a);const c=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3,not:{items:{not:{enum:[i.channels[1]]}}}}}},a),r=await h(c);o(r.value).toEqual(i.value),await o(c.next()).resolves.toHaveProperty("done",!0);const u=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{not:{items:{not:{enum:[i.channels[1]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover([i.channels[0],i.channels[2]],{properties:{allowed:{maxItems:2,not:{items:{not:{enum:[i.channels[2]]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover query for empty allowed",(async()=>{const e=n(),a=l(),t=v(),s={not:{required:["allowed"]}};await e.put(t,a);const i=e.discover(t.channels,s,a),c=await h(i);o(c.value).toEqual(t.value),o(c.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0);const r=v();r.allowed=[],await e.put(r,a);const u=e.discover(r.channels,s,a);await o(u.next()).resolves.toHaveProperty("done",!0)})),t("discover query for values",(async()=>{const a=n(),t=l(),s=v();s.value={test:d()},await a.put(s,t);const i=v();i.channels=s.channels,i.value={test:d(),something:d()},await a.put(i,t);const c=v();c.channels=s.channels,c.value={other:d(),something:d()},await a.put(c,t);const r=new Map;for(const t of["test","something","other"]){let o=0;for await(const n of a.discover(s.channels,{properties:{value:{required:[t]}}}))e(!n.error,"result has error"),t in n.value.value&&o++;r.set(t,o)}o(r.get("test")).toBe(2),o(r.get("something")).toBe(2),o(r.get("other")).toBe(1)})),t("discover for deleted content",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=await e.delete(s,a),c=e.discover(t.channels,{}),r=await h(c);o(r.tombstone).toBe(!0),o(r.value).toEqual(t.value),o(r.channels).toEqual(t.channels),o(r.actor).toEqual(a.actor),o(r.lastModified).toEqual(i.lastModified),await o(c.next()).resolves.toHaveProperty("done",!0)})),t("discover for replaced channels",(async()=>{for(let e=0;e<10;e++){const e=n(),a=l(),t=v(),s=await e.put(t,a),i=v(),c=await e.put({...s,...i},a),r=e.discover(t.channels,{}),u=await h(r);await o(r.next()).resolves.toHaveProperty("done",!0);const d=e.discover(i.channels,{}),w=await h(d);await o(d.next()).resolves.toHaveProperty("done",!0),s.lastModified===c.lastModified?(o(u.tombstone||w.tombstone).toBe(!0),o(u.tombstone&&w.tombstone).toBe(!1)):(o(u.tombstone).toBe(!0),o(u.value).toEqual(t.value),o(u.channels).toEqual(t.channels),o(u.lastModified).toEqual(c.lastModified),o(w.tombstone).toBe(!1),o(w.value).toEqual(i.value),o(w.channels).toEqual(i.channels),o(w.lastModified).toEqual(c.lastModified))}})),t("discover for patched allowed",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await e.patch({allowed:[{op:"add",path:"",value:[]}]},s,a);const i=e.discover(t.channels,{}),c=await h(i);o(c.tombstone).toBe(!0),o(c.value).toEqual(t.value),o(c.channels).toEqual(t.channels),o(c.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0)})),t("put concurrently and discover one",(async()=>{const a=n(),t=l(),s=v();s.name=d();const i=Array(100).fill(0).map((()=>a.put(s,t)));await Promise.all(i);const c=a.discover(s.channels,{});let r=0,u=0;for await(const a of c)e(!a.error,"result has error"),a.value.tombstone?r++:u++;o(r).toBe(99),o(u).toBe(1)}))}))},f=(e,n,l)=>{a("recoverOrphans",(()=>{t("list orphans",(async()=>{const a=e(),t=n(),l=[],s=a.recoverOrphans({},t);for await(const e of s)e.error||l.push(e.value.name);const i=v();i.channels=[];const c=await a.put(i,t),r=a.recoverOrphans({},t);let u=0;for await(const e of r)e.error||e.value.name===c.name&&(u++,o(e.value.source).toBe(c.source),o(e.value.lastModified).toBe(c.lastModified));o(u).toBe(1)})),t("replaced orphan, no longer",(async()=>{const a=e(),t=n(),l=v();l.channels=[];const s=await a.put(l,t),i=await a.put({...s,...l,channels:[d()]},t);o(i.name).toBe(s.name);const c=a.recoverOrphans({},t);let r=0;for await(const e of c)e.error||e.value.name===s.name&&(r++,o(e.value.tombstone).toBe(!0),o(e.value.lastModified).toBe(i.lastModified),o(e.value.channels).toEqual([]));o(r).toBe(1)}))}))},q=(n,l,s)=>{a("channel stats",(()=>{t("list channels",(async()=>{const e=n(),a=l(),t=new Map,s=e.channelStats(a);for await(const e of s)e.error||t.set(e.value.channel,e.value.count);const i=[d(),d(),d()];for(let t=0;t<3;t++)for(let o=0;o<t+1;o++)await e.put({value:{index:o},channels:i.slice(0,t+1)},a);await e.put({value:{index:3},channels:[i[2]]},a);const c=e.channelStats(a);let r=new Map;for await(const e of c)e.error||r.set(e.value.channel,e.value.count);r=new Map(Array.from(r).filter((([e,a])=>!t.has(e)))),o(r.size).toBe(3),o(r.get(i[0])).toBe(6),o(r.get(i[1])).toBe(5),o(r.get(i[2])).toBe(4)})),t("list channels with deleted channel",(async()=>{const a=n(),t=l(),s=[d(),d(),d()],i=await a.put({value:{index:2},channels:s.slice(1)},t),c=await a.put({value:{index:0},channels:s},t);await a.delete(c,t);const r=await a.put({value:{index:1},channels:s.slice(2)},t),u=a.channelStats(t);let v=0,h=0;for await(const a of u){if(a.error)continue;const{channel:t,count:n,lastModified:l}=a.value;e(t!==s[0],"There should not be an object in channel[0]"),t===s[1]?(o(n).toBe(1),o(l).toBe(i.lastModified),v++):t===s[2]&&(o(n).toBe(2),o(l).toBe(r.lastModified),h++)}o(v).toBe(1),o(h).toBe(1)}))}))};export{p as graffitiCRUDTests,q as graffitiChannelStatsTests,E as graffitiDiscoverTests,w as graffitiLocationTests,f as graffitiOrphanTests,m as graffitiSynchronizeTests};
|
|
2
|
-
//# sourceMappingURL=index.js.map
|
package/dist/tests/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"channel-stats.d.ts","sourceRoot":"","sources":["../../../tests/src/channel-stats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGtE,eAAO,MAAM,yBAAyB,gBACvB,MAAM,IAAI,CACrB,QAAQ,EACR,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAC5C,eACY,MAAM,eAAe,eACrB,MAAM,eAAe,SAiHnC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../../tests/src/crud.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAW9B,eAAO,MAAM,iBAAiB,gBACf,MAAM,IAAI,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC,eACxD,MAAM,eAAe,eACrB,MAAM,eAAe,SAwenC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../tests/src/discover.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAG9B,eAAO,MAAM,qBAAqB,gBACnB,MAAM,IAAI,CAAC,QAAQ,EAAE,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC,eAC7D,MAAM,eAAe,eACrB,MAAM,eAAe,SA0kBnC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../tests/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC"}
|
package/dist/tests/src/list.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../tests/src/list.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,iBAAiB,gBACf,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SAgBnC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"location.d.ts","sourceRoot":"","sources":["../../../tests/src/location.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIrD,eAAO,MAAM,qBAAqB,gBACnB,MAAM,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CAAC,SAmCrE,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"orphans.d.ts","sourceRoot":"","sources":["../../../tests/src/orphans.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGtE,eAAO,MAAM,mBAAmB,gBACjB,MAAM,IAAI,CACrB,QAAQ,EACR,gBAAgB,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAC9C,eACY,MAAM,eAAe,eACrB,MAAM,eAAe,SA8DnC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"synchronize.d.ts","sourceRoot":"","sources":["../../../tests/src/synchronize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,wBAAwB,gBACtB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SAyVnC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../tests/src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE9E,wBAAgB,YAAY,IAAI,MAAM,CASrC;AAED,wBAAgB,WAAW;;EAI1B;AAED,wBAAgB,eAAe,IAAI,iBAAiB,CAAC,EAAE,CAAC,CAKvD;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,cAIzE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../../../tests/src/crud.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EAQrB,MAAM,sBAAsB,CAAC;AAG9B,eAAO,MAAM,iBAAiB,gBACf,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SAucnC,CAAC"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { GraffitiFactory, GraffitiSession } from "@graffiti-garden/api";
|
|
2
|
-
export declare const graffitiDiscoverTests: (useGraffiti: GraffitiFactory, useSession1: () => GraffitiSession, useSession2: () => GraffitiSession) => void;
|
|
3
|
-
//# sourceMappingURL=discover.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../../tests/src/discover.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAG9B,eAAO,MAAM,qBAAqB,gBACnB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SA0kBnC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../tests/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"location.d.ts","sourceRoot":"","sources":["../../../../tests/src/location.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAG9B,eAAO,MAAM,qBAAqB,gBAAiB,eAAe,SAkCjE,CAAC"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { GraffitiFactory, GraffitiSession } from "@graffiti-garden/api";
|
|
2
|
-
export declare const graffitiSynchronizeTests: (useGraffiti: GraffitiFactory, useSession1: () => GraffitiSession, useSession2: () => GraffitiSession) => void;
|
|
3
|
-
//# sourceMappingURL=synchronize.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"synchronize.d.ts","sourceRoot":"","sources":["../../../../tests/src/synchronize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,wBAAwB,gBACtB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SA2PnC,CAAC"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { GraffitiPutObject, GraffitiStream } from "@graffiti-garden/api";
|
|
2
|
-
export declare function randomString(): string;
|
|
3
|
-
export declare function randomValue(): {
|
|
4
|
-
[x: string]: string;
|
|
5
|
-
};
|
|
6
|
-
export declare function randomPutObject(): GraffitiPutObject<{}>;
|
|
7
|
-
export declare function nextStreamValue<S, T>(iterator: GraffitiStream<S, T>): Promise<S>;
|
|
8
|
-
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../tests/src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE9E,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED,wBAAgB,WAAW;;EAI1B;AAED,wBAAgB,eAAe,IAAI,iBAAiB,CAAC,EAAE,CAAC,CAKvD;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,cAIzE"}
|
package/tests/rollup.config.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import typescript from "rollup-plugin-typescript2";
|
|
2
|
-
import terser from "@rollup/plugin-terser";
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
input: "src/index.ts",
|
|
6
|
-
output: {
|
|
7
|
-
file: "../dist/tests/index.js",
|
|
8
|
-
format: "es",
|
|
9
|
-
sourcemap: true,
|
|
10
|
-
},
|
|
11
|
-
external: ["vitest", "@graffiti-garden/api"],
|
|
12
|
-
plugins: [
|
|
13
|
-
typescript({
|
|
14
|
-
tsconfig: "tsconfig.json",
|
|
15
|
-
useTsconfigDeclarationDir: true,
|
|
16
|
-
}),
|
|
17
|
-
terser(),
|
|
18
|
-
],
|
|
19
|
-
};
|
package/tests/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "esnext",
|
|
4
|
-
"module": "esnext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"resolveJsonModule": true,
|
|
9
|
-
"verbatimModuleSyntax": true,
|
|
10
|
-
"declaration": true,
|
|
11
|
-
"declarationMap": true,
|
|
12
|
-
"declarationDir": "../dist/tests",
|
|
13
|
-
"rootDir": "."
|
|
14
|
-
},
|
|
15
|
-
"include": ["src"]
|
|
16
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|