@graffiti-garden/wrapper-synchronize 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/cjs/index.js CHANGED
@@ -88,7 +88,9 @@ class GraffitiSynchronize extends import_api.Graffiti {
88
88
  push(objectRaw);
89
89
  }
90
90
  } else if (objectRaw && matchObject(objectRaw.object) && (this.options.omniscient || (0, import_utilities.isActorAllowedGraffitiObject)(objectRaw.object, session))) {
91
- const object = { ...objectRaw.object };
91
+ const object = JSON.parse(
92
+ JSON.stringify(objectRaw.object)
93
+ );
92
94
  if (!this.options.omniscient) {
93
95
  (0, import_utilities.maskGraffitiObject)(object, channels, session);
94
96
  }
@@ -105,7 +107,9 @@ class GraffitiSynchronize extends import_api.Graffiti {
105
107
  this.callbacks.delete(callback);
106
108
  }
107
109
  );
108
- return repeater;
110
+ return (async function* () {
111
+ for await (const i of repeater) yield i;
112
+ })();
109
113
  }
110
114
  /**
111
115
  * This method has the same signature as {@link discover} but listens for
@@ -176,6 +180,8 @@ class GraffitiSynchronize extends import_api.Graffiti {
176
180
  *
177
181
  * Be careful using this method. Without additional filters it can
178
182
  * expose the user to content out of context.
183
+ *
184
+ * @group Synchronize Methods
179
185
  */
180
186
  synchronizeAll(schema, session) {
181
187
  return this.synchronize(() => true, [], schema, session);
@@ -246,7 +252,7 @@ class GraffitiSynchronize extends import_api.Graffiti {
246
252
  };
247
253
  objectStreamContinue(iterator) {
248
254
  const this_ = this;
249
- return async function* () {
255
+ return (async function* () {
250
256
  while (true) {
251
257
  const result = await iterator.next();
252
258
  if (result.done) {
@@ -263,17 +269,17 @@ class GraffitiSynchronize extends import_api.Graffiti {
263
269
  }
264
270
  yield result.value;
265
271
  }
266
- }();
272
+ })();
267
273
  }
268
274
  objectStream(iterator) {
269
275
  const wrapped = this.objectStreamContinue(iterator);
270
- return async function* () {
276
+ return (async function* () {
271
277
  while (true) {
272
278
  const result = await wrapped.next();
273
279
  if (result.done) return result.value;
274
280
  if (result.value.error || !result.value.tombstone) yield result.value;
275
281
  }
276
- }();
282
+ })();
277
283
  }
278
284
  discover = (...args) => {
279
285
  const iterator = this.graffiti.discover(...args);
@@ -284,7 +290,8 @@ class GraffitiSynchronize extends import_api.Graffiti {
284
290
  return this.objectStream(iterator);
285
291
  };
286
292
  continueObjectStream = (...args) => {
287
- return this.graffiti.continueObjectStream(...args);
293
+ const iterator = this.graffiti.continueObjectStream(...args);
294
+ return this.objectStreamContinue(iterator);
288
295
  };
289
296
  }
290
297
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import type Ajv from \"ajv\";\nimport { Graffiti } from \"@graffiti-garden/api\";\nimport type {\n GraffitiSession,\n JSONSchema,\n GraffitiObjectStream,\n GraffitiObjectStreamContinueEntry,\n GraffitiObjectStreamContinue,\n} from \"@graffiti-garden/api\";\nimport type { GraffitiObjectBase } from \"@graffiti-garden/api\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport type { applyPatch } from \"fast-json-patch\";\nimport {\n applyGraffitiPatch,\n compileGraffitiObjectSchema,\n isActorAllowedGraffitiObject,\n maskGraffitiObject,\n unpackObjectUrl,\n} from \"@graffiti-garden/implementation-local/utilities\";\nexport type * from \"@graffiti-garden/api\";\n\nexport type GraffitiSynchronizeCallback = (\n oldObject: GraffitiObjectStreamContinueEntry<{}>,\n newObject?: GraffitiObjectStreamContinueEntry<{}>,\n) => void;\n\nexport interface GraffitiSynchronizeOptions {\n /**\n * Allows synchronize to listen to all objects, not just those\n * that the session is allowed to see. This is useful to get a\n * global view of all Graffiti objects passsing through the system,\n * for example to build a client-side cache. Additional mechanisms\n * should be in place to ensure that users do not see objects or\n * properties they are not allowed to see.\n *\n * Default: `false`\n */\n omniscient?: boolean;\n}\n\n/**\n * Wraps the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * so that changes made or received in one part of an application\n * are automatically routed to other parts of the application.\n * This is an important tool for building responsive\n * and consistent user interfaces, and is built upon to make\n * the [Graffiti Vue Plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)\n * and possibly other front-end libraries in the future.\n *\n * Specifically, it provides the following *synchronize*\n * methods for each of the following API methods:\n *\n * | API Method | Synchronize Method |\n * |------------|--------------------|\n * | {@link get} | {@link synchronizeGet} |\n * | {@link discover} | {@link synchronizeDiscover} |\n * | {@link recoverOrphans} | {@link synchronizeRecoverOrphans} |\n *\n * Whenever a change is made via {@link put}, {@link patch}, and {@link delete} or\n * received from {@link get}, {@link discover}, and {@link recoverOrphans},\n * those changes are forwarded to the appropriate synchronize method.\n * Each synchronize method returns an iterator that streams these changes\n * continually until the user calls `return` on the iterator or `break`s out of the loop,\n * allowing for live updates without additional polling.\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 * @groupDescription Synchronize Methods\n * This group contains methods that listen 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 */\nexport class GraffitiSynchronize extends Graffiti {\n protected ajv_: Promise<Ajv> | undefined;\n protected applyPatch_: Promise<typeof applyPatch> | undefined;\n protected readonly graffiti: Graffiti;\n protected readonly callbacks = new Set<GraffitiSynchronizeCallback>();\n protected readonly options: GraffitiSynchronizeOptions;\n\n channelStats: Graffiti[\"channelStats\"];\n login: Graffiti[\"login\"];\n logout: Graffiti[\"logout\"];\n sessionEvents: Graffiti[\"sessionEvents\"];\n\n get ajv() {\n if (!this.ajv_) {\n this.ajv_ = (async () => {\n const { default: Ajv } = await import(\"ajv\");\n return new Ajv({ strict: false });\n })();\n }\n return this.ajv_;\n }\n\n get applyPatch() {\n if (!this.applyPatch_) {\n this.applyPatch_ = (async () => {\n const { applyPatch } = await import(\"fast-json-patch\");\n return applyPatch;\n })();\n }\n return this.applyPatch_;\n }\n\n /**\n * Wraps a Graffiti API instance to provide the synchronize methods.\n * The GraffitiSyncrhonize class rather than the Graffiti class\n * must be used for all functions for the synchronize methods to work.\n */\n constructor(\n /**\n * The [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * instance to wrap.\n */\n graffiti: Graffiti,\n options?: GraffitiSynchronizeOptions,\n ) {\n super();\n this.options = options ?? {};\n this.graffiti = graffiti;\n this.channelStats = graffiti.channelStats.bind(graffiti);\n this.login = graffiti.login.bind(graffiti);\n this.logout = graffiti.logout.bind(graffiti);\n this.sessionEvents = graffiti.sessionEvents;\n }\n\n protected synchronize<Schema extends JSONSchema>(\n matchObject: (object: GraffitiObjectBase) => boolean,\n channels: string[],\n schema: Schema,\n session?: GraffitiSession | null,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const seenUrls = new Set<string>();\n\n const repeater = new Repeater<GraffitiObjectStreamContinueEntry<Schema>>(\n async (push, stop) => {\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n const callback: GraffitiSynchronizeCallback = (\n oldObjectRaw,\n newObjectRaw,\n ) => {\n for (const objectRaw of [newObjectRaw, oldObjectRaw]) {\n if (objectRaw?.tombstone) {\n if (seenUrls.has(objectRaw.object.url)) {\n push(objectRaw);\n }\n } else if (\n objectRaw &&\n matchObject(objectRaw.object) &&\n (this.options.omniscient ||\n isActorAllowedGraffitiObject(objectRaw.object, session))\n ) {\n const object = { ...objectRaw.object };\n if (!this.options.omniscient) {\n maskGraffitiObject(object, channels, session);\n }\n if (validate(object)) {\n push({ object });\n seenUrls.add(object.url);\n break;\n }\n }\n }\n };\n\n this.callbacks.add(callback);\n await stop;\n this.callbacks.delete(callback);\n },\n );\n\n return repeater;\n }\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 * @group Synchronize Methods\n */\n synchronizeDiscover<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.discover<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [channels, schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.channels.some((channel) => channels.includes(channel));\n }\n return this.synchronize<Schema>(matchObject, channels, schema, session);\n }\n\n /**\n * This method has the same signature as {@link get} but\n * 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 stream, similar\n * to {@link discover}.\n *\n * @group Synchronize Methods\n */\n synchronizeGet<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.get<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [objectUrl, schema, session] = args;\n const url = unpackObjectUrl(objectUrl);\n function matchObject(object: GraffitiObjectBase) {\n return object.url === url;\n }\n return this.synchronize<Schema>(matchObject, [], schema, session);\n }\n\n /**\n * This method has the same signature as {@link recoverOrphans} but\n * 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 synchronizeRecoverOrphans<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.recoverOrphans<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.actor === session.actor && object.channels.length === 0;\n }\n return this.synchronize<Schema>(matchObject, [], schema, session);\n }\n\n /**\n * Streams changes made to *any* object in *any* channel\n * and made by *any* user. You may want to use it in conjuction with\n * {@link GraffitiSynchronizeOptions.omniscient} to get a global view\n * of all Graffiti objects passing through the system. This is useful\n * for building a client-side cache, for example.\n *\n * Be careful using this method. Without additional filters it can\n * expose the user to content out of context.\n */\n synchronizeAll<Schema extends JSONSchema>(\n schema: Schema,\n session?: GraffitiSession | null,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n return this.synchronize<Schema>(() => true, [], schema, session);\n }\n\n protected async synchronizeDispatch(\n oldObject: GraffitiObjectStreamContinueEntry<{}>,\n newObject?: GraffitiObjectStreamContinueEntry<{}>,\n waitForListeners = false,\n ) {\n for (const callback of this.callbacks) {\n callback(oldObject, newObject);\n }\n if (waitForListeners) {\n // Wait for the listeners to receive\n // their objects, before returning the operation\n // that triggered them.\n //\n // This is important for mutators (put, patch, delete)\n // to ensure the application state has been updated\n // everywhere before returning, giving consistent\n // feedback to the user that the operation has completed.\n //\n // The opposite is true for accessors (get, discover, recoverOrphans),\n // where it is a weird user experience to call `get`\n // in one place and have the application update\n // somewhere else first. It is also less efficient.\n //\n // The hack is simply to await one \"macro task cycle\".\n // We need to wait for this cycle rather than using\n // `await push` in the callback, because it turns out\n // that `await push` won't resolve until the following\n // .next() call of the iterator, so if only\n // one .next() is called, this dispatch will hang.\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const object = await this.graffiti.get(...args);\n this.synchronizeDispatch({ object });\n return object;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const oldObject = await this.graffiti.put<{}>(...args);\n const partialObject = args[0];\n const newObject: GraffitiObjectBase = {\n ...oldObject,\n value: partialObject.value,\n channels: partialObject.channels,\n allowed: partialObject.allowed,\n };\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n {\n object: newObject,\n },\n true,\n );\n return oldObject;\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const oldObject = await this.graffiti.patch(...args);\n const newObject: GraffitiObjectBase = { ...oldObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(await this.applyPatch, prop, args[0], newObject);\n }\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n {\n object: newObject,\n },\n true,\n );\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n undefined,\n true,\n );\n return oldObject;\n };\n\n protected objectStreamContinue<Schema extends JSONSchema>(\n iterator: GraffitiObjectStreamContinue<Schema>,\n ): GraffitiObjectStreamContinue<Schema> {\n const this_ = this;\n return (async function* () {\n while (true) {\n const result = await iterator.next();\n if (result.done) {\n const { continue: continue_, cursor } = result.value;\n return {\n continue: () => this_.objectStreamContinue<Schema>(continue_()),\n cursor,\n };\n }\n if (!result.value.error) {\n this_.synchronizeDispatch(\n result.value as GraffitiObjectStreamContinueEntry<{}>,\n );\n }\n yield result.value;\n }\n })();\n }\n\n protected objectStream<Schema extends JSONSchema>(\n iterator: GraffitiObjectStream<Schema>,\n ): GraffitiObjectStream<Schema> {\n const wrapped = this.objectStreamContinue<Schema>(iterator);\n return (async function* () {\n // Filter out the tombstones for type safety\n while (true) {\n const result = await wrapped.next();\n if (result.done) return result.value;\n if (result.value.error || !result.value.tombstone) yield result.value;\n }\n })();\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const iterator = this.graffiti.discover(...args);\n return this.objectStream<(typeof args)[1]>(iterator);\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (...args) => {\n const iterator = this.graffiti.recoverOrphans(...args);\n return this.objectStream<(typeof args)[0]>(iterator);\n };\n\n continueObjectStream: Graffiti[\"continueObjectStream\"] = (...args) => {\n // TODO!!\n return this.graffiti.continueObjectStream(...args);\n };\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,iBAAyB;AASzB,sBAAyB;AAEzB,uBAMO;AAgEA,MAAM,4BAA4B,oBAAS;AAAA,EACtC;AAAA,EACA;AAAA,EACS;AAAA,EACA,YAAY,oBAAI,IAAiC;AAAA,EACjD;AAAA,EAEnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,IAAI,MAAM;AACR,QAAI,CAAC,KAAK,MAAM;AACd,WAAK,QAAQ,YAAY;AACvB,cAAM,EAAE,SAAS,IAAI,IAAI,MAAM,OAAO,KAAK;AAC3C,eAAO,IAAI,IAAI,EAAE,QAAQ,MAAM,CAAC;AAAA,MAClC,GAAG;AAAA,IACL;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,aAAa;AACf,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,eAAe,YAAY;AAC9B,cAAM,EAAE,WAAW,IAAI,MAAM,OAAO,iBAAiB;AACrD,eAAO;AAAA,MACT,GAAG;AAAA,IACL;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAKE,UACA,SACA;AACA,UAAM;AACN,SAAK,UAAU,WAAW,CAAC;AAC3B,SAAK,WAAW;AAChB,SAAK,eAAe,SAAS,aAAa,KAAK,QAAQ;AACvD,SAAK,QAAQ,SAAS,MAAM,KAAK,QAAQ;AACzC,SAAK,SAAS,SAAS,OAAO,KAAK,QAAQ;AAC3C,SAAK,gBAAgB,SAAS;AAAA,EAChC;AAAA,EAEU,YACR,aACA,UACA,QACA,SAC2D;AAC3D,UAAM,WAAW,oBAAI,IAAY;AAEjC,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,SAAS;AACpB,cAAM,eAAW,8CAA4B,MAAM,KAAK,KAAK,MAAM;AACnE,cAAM,WAAwC,CAC5C,cACA,iBACG;AACH,qBAAW,aAAa,CAAC,cAAc,YAAY,GAAG;AACpD,gBAAI,WAAW,WAAW;AACxB,kBAAI,SAAS,IAAI,UAAU,OAAO,GAAG,GAAG;AACtC,qBAAK,SAAS;AAAA,cAChB;AAAA,YACF,WACE,aACA,YAAY,UAAU,MAAM,MAC3B,KAAK,QAAQ,kBACZ,+CAA6B,UAAU,QAAQ,OAAO,IACxD;AACA,oBAAM,SAAS,EAAE,GAAG,UAAU,OAAO;AACrC,kBAAI,CAAC,KAAK,QAAQ,YAAY;AAC5B,yDAAmB,QAAQ,UAAU,OAAO;AAAA,cAC9C;AACA,kBAAI,SAAS,MAAM,GAAG;AACpB,qBAAK,EAAE,OAAO,CAAC;AACf,yBAAS,IAAI,OAAO,GAAG;AACvB;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,aAAK,UAAU,IAAI,QAAQ;AAC3B,cAAM;AACN,aAAK,UAAU,OAAO,QAAQ;AAAA,MAChC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,uBACK,MACwD;AAC3D,UAAM,CAAC,UAAU,QAAQ,OAAO,IAAI;AACpC,aAAS,YAAY,QAA4B;AAC/C,aAAO,OAAO,SAAS,KAAK,CAAC,YAAY,SAAS,SAAS,OAAO,CAAC;AAAA,IACrE;AACA,WAAO,KAAK,YAAoB,aAAa,UAAU,QAAQ,OAAO;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,kBACK,MACwD;AAC3D,UAAM,CAAC,WAAW,QAAQ,OAAO,IAAI;AACrC,UAAM,UAAM,kCAAgB,SAAS;AACrC,aAAS,YAAY,QAA4B;AAC/C,aAAO,OAAO,QAAQ;AAAA,IACxB;AACA,WAAO,KAAK,YAAoB,aAAa,CAAC,GAAG,QAAQ,OAAO;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,6BACK,MACwD;AAC3D,UAAM,CAAC,QAAQ,OAAO,IAAI;AAC1B,aAAS,YAAY,QAA4B;AAC/C,aAAO,OAAO,UAAU,QAAQ,SAAS,OAAO,SAAS,WAAW;AAAA,IACtE;AACA,WAAO,KAAK,YAAoB,aAAa,CAAC,GAAG,QAAQ,OAAO;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,eACE,QACA,SAC2D;AAC3D,WAAO,KAAK,YAAoB,MAAM,MAAM,CAAC,GAAG,QAAQ,OAAO;AAAA,EACjE;AAAA,EAEA,MAAgB,oBACd,WACA,WACA,mBAAmB,OACnB;AACA,eAAW,YAAY,KAAK,WAAW;AACrC,eAAS,WAAW,SAAS;AAAA,IAC/B;AACA,QAAI,kBAAkB;AAqBpB,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,CAAC,CAAC;AAAA,IACvD;AAAA,EACF;AAAA,EAEA,MAAuB,UAAU,SAAS;AACxC,UAAM,SAAS,MAAM,KAAK,SAAS,IAAI,GAAG,IAAI;AAC9C,SAAK,oBAAoB,EAAE,OAAO,CAAC;AACnC,WAAO;AAAA,EACT;AAAA,EAEA,MAAuB,UAAU,SAAS;AACxC,UAAM,YAAY,MAAM,KAAK,SAAS,IAAQ,GAAG,IAAI;AACrD,UAAM,gBAAgB,KAAK,CAAC;AAC5B,UAAM,YAAgC;AAAA,MACpC,GAAG;AAAA,MACH,OAAO,cAAc;AAAA,MACrB,UAAU,cAAc;AAAA,MACxB,SAAS,cAAc;AAAA,IACzB;AACA,UAAM,KAAK;AAAA,MACT;AAAA,QACE,WAAW;AAAA,QACX,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,QACE,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,QAA2B,UAAU,SAAS;AAC5C,UAAM,YAAY,MAAM,KAAK,SAAS,MAAM,GAAG,IAAI;AACnD,UAAM,YAAgC,EAAE,GAAG,UAAU;AACrD,eAAW,QAAQ,CAAC,SAAS,YAAY,SAAS,GAAY;AAC5D,+CAAmB,MAAM,KAAK,YAAY,MAAM,KAAK,CAAC,GAAG,SAAS;AAAA,IACpE;AACA,UAAM,KAAK;AAAA,MACT;AAAA,QACE,WAAW;AAAA,QACX,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,QACE,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,SAA6B,UAAU,SAAS;AAC9C,UAAM,YAAY,MAAM,KAAK,SAAS,OAAO,GAAG,IAAI;AACpD,UAAM,KAAK;AAAA,MACT;AAAA,QACE,WAAW;AAAA,QACX,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEU,qBACR,UACsC;AACtC,UAAM,QAAQ;AACd,WAAQ,mBAAmB;AACzB,aAAO,MAAM;AACX,cAAM,SAAS,MAAM,SAAS,KAAK;AACnC,YAAI,OAAO,MAAM;AACf,gBAAM,EAAE,UAAU,WAAW,OAAO,IAAI,OAAO;AAC/C,iBAAO;AAAA,YACL,UAAU,MAAM,MAAM,qBAA6B,UAAU,CAAC;AAAA,YAC9D;AAAA,UACF;AAAA,QACF;AACA,YAAI,CAAC,OAAO,MAAM,OAAO;AACvB,gBAAM;AAAA,YACJ,OAAO;AAAA,UACT;AAAA,QACF;AACA,cAAM,OAAO;AAAA,MACf;AAAA,IACF,EAAG;AAAA,EACL;AAAA,EAEU,aACR,UAC8B;AAC9B,UAAM,UAAU,KAAK,qBAA6B,QAAQ;AAC1D,WAAQ,mBAAmB;AAEzB,aAAO,MAAM;AACX,cAAM,SAAS,MAAM,QAAQ,KAAK;AAClC,YAAI,OAAO,KAAM,QAAO,OAAO;AAC/B,YAAI,OAAO,MAAM,SAAS,CAAC,OAAO,MAAM,UAAW,OAAM,OAAO;AAAA,MAClE;AAAA,IACF,EAAG;AAAA,EACL;AAAA,EAEA,WAAiC,IAAI,SAAS;AAC5C,UAAM,WAAW,KAAK,SAAS,SAAS,GAAG,IAAI;AAC/C,WAAO,KAAK,aAA+B,QAAQ;AAAA,EACrD;AAAA,EAEA,iBAA6C,IAAI,SAAS;AACxD,UAAM,WAAW,KAAK,SAAS,eAAe,GAAG,IAAI;AACrD,WAAO,KAAK,aAA+B,QAAQ;AAAA,EACrD;AAAA,EAEA,uBAAyD,IAAI,SAAS;AAEpE,WAAO,KAAK,SAAS,qBAAqB,GAAG,IAAI;AAAA,EACnD;AACF;",
4
+ "sourcesContent": ["import type Ajv from \"ajv\";\nimport { Graffiti } from \"@graffiti-garden/api\";\nimport type {\n GraffitiSession,\n JSONSchema,\n GraffitiObjectStream,\n GraffitiObjectStreamContinueEntry,\n GraffitiObjectStreamContinue,\n GraffitiObject,\n} from \"@graffiti-garden/api\";\nimport type { GraffitiObjectBase } from \"@graffiti-garden/api\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport type { applyPatch } from \"fast-json-patch\";\nimport {\n applyGraffitiPatch,\n compileGraffitiObjectSchema,\n isActorAllowedGraffitiObject,\n maskGraffitiObject,\n unpackObjectUrl,\n} from \"@graffiti-garden/implementation-local/utilities\";\nexport type * from \"@graffiti-garden/api\";\n\nexport type GraffitiSynchronizeCallback = (\n oldObject: GraffitiObjectStreamContinueEntry<{}>,\n newObject?: GraffitiObjectStreamContinueEntry<{}>,\n) => void;\n\nexport interface GraffitiSynchronizeOptions {\n /**\n * Allows synchronize to listen to all objects, not just those\n * that the session is allowed to see. This is useful to get a\n * global view of all Graffiti objects passsing through the system,\n * for example to build a client-side cache. Additional mechanisms\n * should be in place to ensure that users do not see objects or\n * properties they are not allowed to see.\n *\n * Default: `false`\n */\n omniscient?: boolean;\n}\n\n/**\n * Wraps the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * so that changes made or received in one part of an application\n * are automatically routed to other parts of the application.\n * This is an important tool for building responsive\n * and consistent user interfaces, and is built upon to make\n * the [Graffiti Vue Plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)\n * and possibly other front-end libraries in the future.\n *\n * [See a live example](/example).\n *\n * Specifically, this library provides the following *synchronize*\n * methods to correspond with each of the following Graffiti API methods:\n *\n * | API Method | Synchronize Method |\n * |------------|--------------------|\n * | {@link get} | {@link synchronizeGet} |\n * | {@link discover} | {@link synchronizeDiscover} |\n * | {@link recoverOrphans} | {@link synchronizeRecoverOrphans} |\n *\n * Whenever a change is made via {@link put}, {@link patch}, and {@link delete} or\n * received from {@link get}, {@link discover}, and {@link recoverOrphans},\n * those changes are forwarded to the appropriate synchronize method.\n * Each synchronize method returns an iterator that streams these changes\n * continually until the user calls `return` on the iterator or `break`s out of the loop,\n * allowing for live updates without additional polling.\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 * The source code for this library is [available on GitHub](https://github.com/graffiti-garden/wrapper-synchronize/).\n *\n * @groupDescription Synchronize Methods\n * This group contains methods that listen 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 */\nexport class GraffitiSynchronize extends Graffiti {\n protected ajv_: Promise<Ajv> | undefined;\n protected applyPatch_: Promise<typeof applyPatch> | undefined;\n protected readonly graffiti: Graffiti;\n protected readonly callbacks = new Set<GraffitiSynchronizeCallback>();\n protected readonly options: GraffitiSynchronizeOptions;\n\n channelStats: Graffiti[\"channelStats\"];\n login: Graffiti[\"login\"];\n logout: Graffiti[\"logout\"];\n sessionEvents: Graffiti[\"sessionEvents\"];\n\n protected get ajv() {\n if (!this.ajv_) {\n this.ajv_ = (async () => {\n const { default: Ajv } = await import(\"ajv\");\n return new Ajv({ strict: false });\n })();\n }\n return this.ajv_;\n }\n\n protected get applyPatch() {\n if (!this.applyPatch_) {\n this.applyPatch_ = (async () => {\n const { applyPatch } = await import(\"fast-json-patch\");\n return applyPatch;\n })();\n }\n return this.applyPatch_;\n }\n\n /**\n * Wraps a Graffiti API instance to provide the synchronize methods.\n * The GraffitiSyncrhonize class rather than the Graffiti class\n * must be used for all functions for the synchronize methods to work.\n */\n constructor(\n /**\n * The [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * instance to wrap.\n */\n graffiti: Graffiti,\n options?: GraffitiSynchronizeOptions,\n ) {\n super();\n this.options = options ?? {};\n this.graffiti = graffiti;\n this.channelStats = graffiti.channelStats.bind(graffiti);\n this.login = graffiti.login.bind(graffiti);\n this.logout = graffiti.logout.bind(graffiti);\n this.sessionEvents = graffiti.sessionEvents;\n }\n\n protected synchronize<Schema extends JSONSchema>(\n matchObject: (object: GraffitiObjectBase) => boolean,\n channels: string[],\n schema: Schema,\n session?: GraffitiSession | null,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const seenUrls = new Set<string>();\n\n const repeater = new Repeater<GraffitiObjectStreamContinueEntry<Schema>>(\n async (push, stop) => {\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n const callback: GraffitiSynchronizeCallback = (\n oldObjectRaw,\n newObjectRaw,\n ) => {\n for (const objectRaw of [newObjectRaw, oldObjectRaw]) {\n if (objectRaw?.tombstone) {\n if (seenUrls.has(objectRaw.object.url)) {\n push(objectRaw);\n }\n } else if (\n objectRaw &&\n matchObject(objectRaw.object) &&\n (this.options.omniscient ||\n isActorAllowedGraffitiObject(objectRaw.object, session))\n ) {\n // Deep clone the object to prevent mutation\n const object = JSON.parse(\n JSON.stringify(objectRaw.object),\n ) as GraffitiObject<{}>;\n if (!this.options.omniscient) {\n maskGraffitiObject(object, channels, session);\n }\n if (validate(object)) {\n push({ object });\n seenUrls.add(object.url);\n break;\n }\n }\n }\n };\n\n this.callbacks.add(callback);\n await stop;\n this.callbacks.delete(callback);\n },\n );\n\n return (async function* () {\n for await (const i of repeater) yield i;\n })();\n }\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 * @group Synchronize Methods\n */\n synchronizeDiscover<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.discover<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [channels, schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.channels.some((channel) => channels.includes(channel));\n }\n return this.synchronize<Schema>(matchObject, channels, schema, session);\n }\n\n /**\n * This method has the same signature as {@link get} but\n * 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 stream, similar\n * to {@link discover}.\n *\n * @group Synchronize Methods\n */\n synchronizeGet<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.get<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [objectUrl, schema, session] = args;\n const url = unpackObjectUrl(objectUrl);\n function matchObject(object: GraffitiObjectBase) {\n return object.url === url;\n }\n return this.synchronize<Schema>(matchObject, [], schema, session);\n }\n\n /**\n * This method has the same signature as {@link recoverOrphans} but\n * 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 synchronizeRecoverOrphans<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.recoverOrphans<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.actor === session.actor && object.channels.length === 0;\n }\n return this.synchronize<Schema>(matchObject, [], schema, session);\n }\n\n /**\n * Streams changes made to *any* object in *any* channel\n * and made by *any* user. You may want to use it in conjuction with\n * {@link GraffitiSynchronizeOptions.omniscient} to get a global view\n * of all Graffiti objects passing through the system. This is useful\n * for building a client-side cache, for example.\n *\n * Be careful using this method. Without additional filters it can\n * expose the user to content out of context.\n *\n * @group Synchronize Methods\n */\n synchronizeAll<Schema extends JSONSchema>(\n schema: Schema,\n session?: GraffitiSession | null,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n return this.synchronize<Schema>(() => true, [], schema, session);\n }\n\n protected async synchronizeDispatch(\n oldObject: GraffitiObjectStreamContinueEntry<{}>,\n newObject?: GraffitiObjectStreamContinueEntry<{}>,\n waitForListeners = false,\n ) {\n for (const callback of this.callbacks) {\n callback(oldObject, newObject);\n }\n if (waitForListeners) {\n // Wait for the listeners to receive\n // their objects, before returning the operation\n // that triggered them.\n //\n // This is important for mutators (put, patch, delete)\n // to ensure the application state has been updated\n // everywhere before returning, giving consistent\n // feedback to the user that the operation has completed.\n //\n // The opposite is true for accessors (get, discover, recoverOrphans),\n // where it is a weird user experience to call `get`\n // in one place and have the application update\n // somewhere else first. It is also less efficient.\n //\n // The hack is simply to await one \"macro task cycle\".\n // We need to wait for this cycle rather than using\n // `await push` in the callback, because it turns out\n // that `await push` won't resolve until the following\n // .next() call of the iterator, so if only\n // one .next() is called, this dispatch will hang.\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const object = await this.graffiti.get(...args);\n this.synchronizeDispatch({ object });\n return object;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const oldObject = await this.graffiti.put<{}>(...args);\n const partialObject = args[0];\n const newObject: GraffitiObjectBase = {\n ...oldObject,\n value: partialObject.value,\n channels: partialObject.channels,\n allowed: partialObject.allowed,\n };\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n {\n object: newObject,\n },\n true,\n );\n return oldObject;\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const oldObject = await this.graffiti.patch(...args);\n const newObject: GraffitiObjectBase = { ...oldObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(await this.applyPatch, prop, args[0], newObject);\n }\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n {\n object: newObject,\n },\n true,\n );\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n undefined,\n true,\n );\n return oldObject;\n };\n\n protected objectStreamContinue<Schema extends JSONSchema>(\n iterator: GraffitiObjectStreamContinue<Schema>,\n ): GraffitiObjectStreamContinue<Schema> {\n const this_ = this;\n return (async function* () {\n while (true) {\n const result = await iterator.next();\n if (result.done) {\n const { continue: continue_, cursor } = result.value;\n return {\n continue: () => this_.objectStreamContinue<Schema>(continue_()),\n cursor,\n };\n }\n if (!result.value.error) {\n this_.synchronizeDispatch(\n result.value as GraffitiObjectStreamContinueEntry<{}>,\n );\n }\n yield result.value;\n }\n })();\n }\n\n protected objectStream<Schema extends JSONSchema>(\n iterator: GraffitiObjectStream<Schema>,\n ): GraffitiObjectStream<Schema> {\n const wrapped = this.objectStreamContinue<Schema>(iterator);\n return (async function* () {\n // Filter out the tombstones for type safety\n while (true) {\n const result = await wrapped.next();\n if (result.done) return result.value;\n if (result.value.error || !result.value.tombstone) yield result.value;\n }\n })();\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const iterator = this.graffiti.discover(...args);\n return this.objectStream<(typeof args)[1]>(iterator);\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (...args) => {\n const iterator = this.graffiti.recoverOrphans(...args);\n return this.objectStream<(typeof args)[0]>(iterator);\n };\n\n continueObjectStream: Graffiti[\"continueObjectStream\"] = (...args) => {\n const iterator = this.graffiti.continueObjectStream(...args);\n return this.objectStreamContinue<{}>(iterator);\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/esm/index.js CHANGED
@@ -61,7 +61,9 @@ class GraffitiSynchronize extends Graffiti {
61
61
  push(objectRaw);
62
62
  }
63
63
  } else if (objectRaw && matchObject(objectRaw.object) && (this.options.omniscient || isActorAllowedGraffitiObject(objectRaw.object, session))) {
64
- const object = { ...objectRaw.object };
64
+ const object = JSON.parse(
65
+ JSON.stringify(objectRaw.object)
66
+ );
65
67
  if (!this.options.omniscient) {
66
68
  maskGraffitiObject(object, channels, session);
67
69
  }
@@ -78,7 +80,9 @@ class GraffitiSynchronize extends Graffiti {
78
80
  this.callbacks.delete(callback);
79
81
  }
80
82
  );
81
- return repeater;
83
+ return (async function* () {
84
+ for await (const i of repeater) yield i;
85
+ })();
82
86
  }
83
87
  /**
84
88
  * This method has the same signature as {@link discover} but listens for
@@ -149,6 +153,8 @@ class GraffitiSynchronize extends Graffiti {
149
153
  *
150
154
  * Be careful using this method. Without additional filters it can
151
155
  * expose the user to content out of context.
156
+ *
157
+ * @group Synchronize Methods
152
158
  */
153
159
  synchronizeAll(schema, session) {
154
160
  return this.synchronize(() => true, [], schema, session);
@@ -219,7 +225,7 @@ class GraffitiSynchronize extends Graffiti {
219
225
  };
220
226
  objectStreamContinue(iterator) {
221
227
  const this_ = this;
222
- return async function* () {
228
+ return (async function* () {
223
229
  while (true) {
224
230
  const result = await iterator.next();
225
231
  if (result.done) {
@@ -236,17 +242,17 @@ class GraffitiSynchronize extends Graffiti {
236
242
  }
237
243
  yield result.value;
238
244
  }
239
- }();
245
+ })();
240
246
  }
241
247
  objectStream(iterator) {
242
248
  const wrapped = this.objectStreamContinue(iterator);
243
- return async function* () {
249
+ return (async function* () {
244
250
  while (true) {
245
251
  const result = await wrapped.next();
246
252
  if (result.done) return result.value;
247
253
  if (result.value.error || !result.value.tombstone) yield result.value;
248
254
  }
249
- }();
255
+ })();
250
256
  }
251
257
  discover = (...args) => {
252
258
  const iterator = this.graffiti.discover(...args);
@@ -257,7 +263,8 @@ class GraffitiSynchronize extends Graffiti {
257
263
  return this.objectStream(iterator);
258
264
  };
259
265
  continueObjectStream = (...args) => {
260
- return this.graffiti.continueObjectStream(...args);
266
+ const iterator = this.graffiti.continueObjectStream(...args);
267
+ return this.objectStreamContinue(iterator);
261
268
  };
262
269
  }
263
270
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import type Ajv from \"ajv\";\nimport { Graffiti } from \"@graffiti-garden/api\";\nimport type {\n GraffitiSession,\n JSONSchema,\n GraffitiObjectStream,\n GraffitiObjectStreamContinueEntry,\n GraffitiObjectStreamContinue,\n} from \"@graffiti-garden/api\";\nimport type { GraffitiObjectBase } from \"@graffiti-garden/api\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport type { applyPatch } from \"fast-json-patch\";\nimport {\n applyGraffitiPatch,\n compileGraffitiObjectSchema,\n isActorAllowedGraffitiObject,\n maskGraffitiObject,\n unpackObjectUrl,\n} from \"@graffiti-garden/implementation-local/utilities\";\nexport type * from \"@graffiti-garden/api\";\n\nexport type GraffitiSynchronizeCallback = (\n oldObject: GraffitiObjectStreamContinueEntry<{}>,\n newObject?: GraffitiObjectStreamContinueEntry<{}>,\n) => void;\n\nexport interface GraffitiSynchronizeOptions {\n /**\n * Allows synchronize to listen to all objects, not just those\n * that the session is allowed to see. This is useful to get a\n * global view of all Graffiti objects passsing through the system,\n * for example to build a client-side cache. Additional mechanisms\n * should be in place to ensure that users do not see objects or\n * properties they are not allowed to see.\n *\n * Default: `false`\n */\n omniscient?: boolean;\n}\n\n/**\n * Wraps the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * so that changes made or received in one part of an application\n * are automatically routed to other parts of the application.\n * This is an important tool for building responsive\n * and consistent user interfaces, and is built upon to make\n * the [Graffiti Vue Plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)\n * and possibly other front-end libraries in the future.\n *\n * Specifically, it provides the following *synchronize*\n * methods for each of the following API methods:\n *\n * | API Method | Synchronize Method |\n * |------------|--------------------|\n * | {@link get} | {@link synchronizeGet} |\n * | {@link discover} | {@link synchronizeDiscover} |\n * | {@link recoverOrphans} | {@link synchronizeRecoverOrphans} |\n *\n * Whenever a change is made via {@link put}, {@link patch}, and {@link delete} or\n * received from {@link get}, {@link discover}, and {@link recoverOrphans},\n * those changes are forwarded to the appropriate synchronize method.\n * Each synchronize method returns an iterator that streams these changes\n * continually until the user calls `return` on the iterator or `break`s out of the loop,\n * allowing for live updates without additional polling.\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 * @groupDescription Synchronize Methods\n * This group contains methods that listen 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 */\nexport class GraffitiSynchronize extends Graffiti {\n protected ajv_: Promise<Ajv> | undefined;\n protected applyPatch_: Promise<typeof applyPatch> | undefined;\n protected readonly graffiti: Graffiti;\n protected readonly callbacks = new Set<GraffitiSynchronizeCallback>();\n protected readonly options: GraffitiSynchronizeOptions;\n\n channelStats: Graffiti[\"channelStats\"];\n login: Graffiti[\"login\"];\n logout: Graffiti[\"logout\"];\n sessionEvents: Graffiti[\"sessionEvents\"];\n\n get ajv() {\n if (!this.ajv_) {\n this.ajv_ = (async () => {\n const { default: Ajv } = await import(\"ajv\");\n return new Ajv({ strict: false });\n })();\n }\n return this.ajv_;\n }\n\n get applyPatch() {\n if (!this.applyPatch_) {\n this.applyPatch_ = (async () => {\n const { applyPatch } = await import(\"fast-json-patch\");\n return applyPatch;\n })();\n }\n return this.applyPatch_;\n }\n\n /**\n * Wraps a Graffiti API instance to provide the synchronize methods.\n * The GraffitiSyncrhonize class rather than the Graffiti class\n * must be used for all functions for the synchronize methods to work.\n */\n constructor(\n /**\n * The [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * instance to wrap.\n */\n graffiti: Graffiti,\n options?: GraffitiSynchronizeOptions,\n ) {\n super();\n this.options = options ?? {};\n this.graffiti = graffiti;\n this.channelStats = graffiti.channelStats.bind(graffiti);\n this.login = graffiti.login.bind(graffiti);\n this.logout = graffiti.logout.bind(graffiti);\n this.sessionEvents = graffiti.sessionEvents;\n }\n\n protected synchronize<Schema extends JSONSchema>(\n matchObject: (object: GraffitiObjectBase) => boolean,\n channels: string[],\n schema: Schema,\n session?: GraffitiSession | null,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const seenUrls = new Set<string>();\n\n const repeater = new Repeater<GraffitiObjectStreamContinueEntry<Schema>>(\n async (push, stop) => {\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n const callback: GraffitiSynchronizeCallback = (\n oldObjectRaw,\n newObjectRaw,\n ) => {\n for (const objectRaw of [newObjectRaw, oldObjectRaw]) {\n if (objectRaw?.tombstone) {\n if (seenUrls.has(objectRaw.object.url)) {\n push(objectRaw);\n }\n } else if (\n objectRaw &&\n matchObject(objectRaw.object) &&\n (this.options.omniscient ||\n isActorAllowedGraffitiObject(objectRaw.object, session))\n ) {\n const object = { ...objectRaw.object };\n if (!this.options.omniscient) {\n maskGraffitiObject(object, channels, session);\n }\n if (validate(object)) {\n push({ object });\n seenUrls.add(object.url);\n break;\n }\n }\n }\n };\n\n this.callbacks.add(callback);\n await stop;\n this.callbacks.delete(callback);\n },\n );\n\n return repeater;\n }\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 * @group Synchronize Methods\n */\n synchronizeDiscover<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.discover<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [channels, schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.channels.some((channel) => channels.includes(channel));\n }\n return this.synchronize<Schema>(matchObject, channels, schema, session);\n }\n\n /**\n * This method has the same signature as {@link get} but\n * 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 stream, similar\n * to {@link discover}.\n *\n * @group Synchronize Methods\n */\n synchronizeGet<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.get<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [objectUrl, schema, session] = args;\n const url = unpackObjectUrl(objectUrl);\n function matchObject(object: GraffitiObjectBase) {\n return object.url === url;\n }\n return this.synchronize<Schema>(matchObject, [], schema, session);\n }\n\n /**\n * This method has the same signature as {@link recoverOrphans} but\n * 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 synchronizeRecoverOrphans<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.recoverOrphans<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.actor === session.actor && object.channels.length === 0;\n }\n return this.synchronize<Schema>(matchObject, [], schema, session);\n }\n\n /**\n * Streams changes made to *any* object in *any* channel\n * and made by *any* user. You may want to use it in conjuction with\n * {@link GraffitiSynchronizeOptions.omniscient} to get a global view\n * of all Graffiti objects passing through the system. This is useful\n * for building a client-side cache, for example.\n *\n * Be careful using this method. Without additional filters it can\n * expose the user to content out of context.\n */\n synchronizeAll<Schema extends JSONSchema>(\n schema: Schema,\n session?: GraffitiSession | null,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n return this.synchronize<Schema>(() => true, [], schema, session);\n }\n\n protected async synchronizeDispatch(\n oldObject: GraffitiObjectStreamContinueEntry<{}>,\n newObject?: GraffitiObjectStreamContinueEntry<{}>,\n waitForListeners = false,\n ) {\n for (const callback of this.callbacks) {\n callback(oldObject, newObject);\n }\n if (waitForListeners) {\n // Wait for the listeners to receive\n // their objects, before returning the operation\n // that triggered them.\n //\n // This is important for mutators (put, patch, delete)\n // to ensure the application state has been updated\n // everywhere before returning, giving consistent\n // feedback to the user that the operation has completed.\n //\n // The opposite is true for accessors (get, discover, recoverOrphans),\n // where it is a weird user experience to call `get`\n // in one place and have the application update\n // somewhere else first. It is also less efficient.\n //\n // The hack is simply to await one \"macro task cycle\".\n // We need to wait for this cycle rather than using\n // `await push` in the callback, because it turns out\n // that `await push` won't resolve until the following\n // .next() call of the iterator, so if only\n // one .next() is called, this dispatch will hang.\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const object = await this.graffiti.get(...args);\n this.synchronizeDispatch({ object });\n return object;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const oldObject = await this.graffiti.put<{}>(...args);\n const partialObject = args[0];\n const newObject: GraffitiObjectBase = {\n ...oldObject,\n value: partialObject.value,\n channels: partialObject.channels,\n allowed: partialObject.allowed,\n };\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n {\n object: newObject,\n },\n true,\n );\n return oldObject;\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const oldObject = await this.graffiti.patch(...args);\n const newObject: GraffitiObjectBase = { ...oldObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(await this.applyPatch, prop, args[0], newObject);\n }\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n {\n object: newObject,\n },\n true,\n );\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n undefined,\n true,\n );\n return oldObject;\n };\n\n protected objectStreamContinue<Schema extends JSONSchema>(\n iterator: GraffitiObjectStreamContinue<Schema>,\n ): GraffitiObjectStreamContinue<Schema> {\n const this_ = this;\n return (async function* () {\n while (true) {\n const result = await iterator.next();\n if (result.done) {\n const { continue: continue_, cursor } = result.value;\n return {\n continue: () => this_.objectStreamContinue<Schema>(continue_()),\n cursor,\n };\n }\n if (!result.value.error) {\n this_.synchronizeDispatch(\n result.value as GraffitiObjectStreamContinueEntry<{}>,\n );\n }\n yield result.value;\n }\n })();\n }\n\n protected objectStream<Schema extends JSONSchema>(\n iterator: GraffitiObjectStream<Schema>,\n ): GraffitiObjectStream<Schema> {\n const wrapped = this.objectStreamContinue<Schema>(iterator);\n return (async function* () {\n // Filter out the tombstones for type safety\n while (true) {\n const result = await wrapped.next();\n if (result.done) return result.value;\n if (result.value.error || !result.value.tombstone) yield result.value;\n }\n })();\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const iterator = this.graffiti.discover(...args);\n return this.objectStream<(typeof args)[1]>(iterator);\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (...args) => {\n const iterator = this.graffiti.recoverOrphans(...args);\n return this.objectStream<(typeof args)[0]>(iterator);\n };\n\n continueObjectStream: Graffiti[\"continueObjectStream\"] = (...args) => {\n // TODO!!\n return this.graffiti.continueObjectStream(...args);\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type Ajv from \"ajv\";\nimport { Graffiti } from \"@graffiti-garden/api\";\nimport type {\n GraffitiSession,\n JSONSchema,\n GraffitiObjectStream,\n GraffitiObjectStreamContinueEntry,\n GraffitiObjectStreamContinue,\n GraffitiObject,\n} from \"@graffiti-garden/api\";\nimport type { GraffitiObjectBase } from \"@graffiti-garden/api\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport type { applyPatch } from \"fast-json-patch\";\nimport {\n applyGraffitiPatch,\n compileGraffitiObjectSchema,\n isActorAllowedGraffitiObject,\n maskGraffitiObject,\n unpackObjectUrl,\n} from \"@graffiti-garden/implementation-local/utilities\";\nexport type * from \"@graffiti-garden/api\";\n\nexport type GraffitiSynchronizeCallback = (\n oldObject: GraffitiObjectStreamContinueEntry<{}>,\n newObject?: GraffitiObjectStreamContinueEntry<{}>,\n) => void;\n\nexport interface GraffitiSynchronizeOptions {\n /**\n * Allows synchronize to listen to all objects, not just those\n * that the session is allowed to see. This is useful to get a\n * global view of all Graffiti objects passsing through the system,\n * for example to build a client-side cache. Additional mechanisms\n * should be in place to ensure that users do not see objects or\n * properties they are not allowed to see.\n *\n * Default: `false`\n */\n omniscient?: boolean;\n}\n\n/**\n * Wraps the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * so that changes made or received in one part of an application\n * are automatically routed to other parts of the application.\n * This is an important tool for building responsive\n * and consistent user interfaces, and is built upon to make\n * the [Graffiti Vue Plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)\n * and possibly other front-end libraries in the future.\n *\n * [See a live example](/example).\n *\n * Specifically, this library provides the following *synchronize*\n * methods to correspond with each of the following Graffiti API methods:\n *\n * | API Method | Synchronize Method |\n * |------------|--------------------|\n * | {@link get} | {@link synchronizeGet} |\n * | {@link discover} | {@link synchronizeDiscover} |\n * | {@link recoverOrphans} | {@link synchronizeRecoverOrphans} |\n *\n * Whenever a change is made via {@link put}, {@link patch}, and {@link delete} or\n * received from {@link get}, {@link discover}, and {@link recoverOrphans},\n * those changes are forwarded to the appropriate synchronize method.\n * Each synchronize method returns an iterator that streams these changes\n * continually until the user calls `return` on the iterator or `break`s out of the loop,\n * allowing for live updates without additional polling.\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 * The source code for this library is [available on GitHub](https://github.com/graffiti-garden/wrapper-synchronize/).\n *\n * @groupDescription Synchronize Methods\n * This group contains methods that listen 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 */\nexport class GraffitiSynchronize extends Graffiti {\n protected ajv_: Promise<Ajv> | undefined;\n protected applyPatch_: Promise<typeof applyPatch> | undefined;\n protected readonly graffiti: Graffiti;\n protected readonly callbacks = new Set<GraffitiSynchronizeCallback>();\n protected readonly options: GraffitiSynchronizeOptions;\n\n channelStats: Graffiti[\"channelStats\"];\n login: Graffiti[\"login\"];\n logout: Graffiti[\"logout\"];\n sessionEvents: Graffiti[\"sessionEvents\"];\n\n protected get ajv() {\n if (!this.ajv_) {\n this.ajv_ = (async () => {\n const { default: Ajv } = await import(\"ajv\");\n return new Ajv({ strict: false });\n })();\n }\n return this.ajv_;\n }\n\n protected get applyPatch() {\n if (!this.applyPatch_) {\n this.applyPatch_ = (async () => {\n const { applyPatch } = await import(\"fast-json-patch\");\n return applyPatch;\n })();\n }\n return this.applyPatch_;\n }\n\n /**\n * Wraps a Graffiti API instance to provide the synchronize methods.\n * The GraffitiSyncrhonize class rather than the Graffiti class\n * must be used for all functions for the synchronize methods to work.\n */\n constructor(\n /**\n * The [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * instance to wrap.\n */\n graffiti: Graffiti,\n options?: GraffitiSynchronizeOptions,\n ) {\n super();\n this.options = options ?? {};\n this.graffiti = graffiti;\n this.channelStats = graffiti.channelStats.bind(graffiti);\n this.login = graffiti.login.bind(graffiti);\n this.logout = graffiti.logout.bind(graffiti);\n this.sessionEvents = graffiti.sessionEvents;\n }\n\n protected synchronize<Schema extends JSONSchema>(\n matchObject: (object: GraffitiObjectBase) => boolean,\n channels: string[],\n schema: Schema,\n session?: GraffitiSession | null,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const seenUrls = new Set<string>();\n\n const repeater = new Repeater<GraffitiObjectStreamContinueEntry<Schema>>(\n async (push, stop) => {\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n const callback: GraffitiSynchronizeCallback = (\n oldObjectRaw,\n newObjectRaw,\n ) => {\n for (const objectRaw of [newObjectRaw, oldObjectRaw]) {\n if (objectRaw?.tombstone) {\n if (seenUrls.has(objectRaw.object.url)) {\n push(objectRaw);\n }\n } else if (\n objectRaw &&\n matchObject(objectRaw.object) &&\n (this.options.omniscient ||\n isActorAllowedGraffitiObject(objectRaw.object, session))\n ) {\n // Deep clone the object to prevent mutation\n const object = JSON.parse(\n JSON.stringify(objectRaw.object),\n ) as GraffitiObject<{}>;\n if (!this.options.omniscient) {\n maskGraffitiObject(object, channels, session);\n }\n if (validate(object)) {\n push({ object });\n seenUrls.add(object.url);\n break;\n }\n }\n }\n };\n\n this.callbacks.add(callback);\n await stop;\n this.callbacks.delete(callback);\n },\n );\n\n return (async function* () {\n for await (const i of repeater) yield i;\n })();\n }\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 * @group Synchronize Methods\n */\n synchronizeDiscover<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.discover<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [channels, schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.channels.some((channel) => channels.includes(channel));\n }\n return this.synchronize<Schema>(matchObject, channels, schema, session);\n }\n\n /**\n * This method has the same signature as {@link get} but\n * 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 stream, similar\n * to {@link discover}.\n *\n * @group Synchronize Methods\n */\n synchronizeGet<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.get<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [objectUrl, schema, session] = args;\n const url = unpackObjectUrl(objectUrl);\n function matchObject(object: GraffitiObjectBase) {\n return object.url === url;\n }\n return this.synchronize<Schema>(matchObject, [], schema, session);\n }\n\n /**\n * This method has the same signature as {@link recoverOrphans} but\n * 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 synchronizeRecoverOrphans<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.recoverOrphans<Schema>>\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const [schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.actor === session.actor && object.channels.length === 0;\n }\n return this.synchronize<Schema>(matchObject, [], schema, session);\n }\n\n /**\n * Streams changes made to *any* object in *any* channel\n * and made by *any* user. You may want to use it in conjuction with\n * {@link GraffitiSynchronizeOptions.omniscient} to get a global view\n * of all Graffiti objects passing through the system. This is useful\n * for building a client-side cache, for example.\n *\n * Be careful using this method. Without additional filters it can\n * expose the user to content out of context.\n *\n * @group Synchronize Methods\n */\n synchronizeAll<Schema extends JSONSchema>(\n schema: Schema,\n session?: GraffitiSession | null,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n return this.synchronize<Schema>(() => true, [], schema, session);\n }\n\n protected async synchronizeDispatch(\n oldObject: GraffitiObjectStreamContinueEntry<{}>,\n newObject?: GraffitiObjectStreamContinueEntry<{}>,\n waitForListeners = false,\n ) {\n for (const callback of this.callbacks) {\n callback(oldObject, newObject);\n }\n if (waitForListeners) {\n // Wait for the listeners to receive\n // their objects, before returning the operation\n // that triggered them.\n //\n // This is important for mutators (put, patch, delete)\n // to ensure the application state has been updated\n // everywhere before returning, giving consistent\n // feedback to the user that the operation has completed.\n //\n // The opposite is true for accessors (get, discover, recoverOrphans),\n // where it is a weird user experience to call `get`\n // in one place and have the application update\n // somewhere else first. It is also less efficient.\n //\n // The hack is simply to await one \"macro task cycle\".\n // We need to wait for this cycle rather than using\n // `await push` in the callback, because it turns out\n // that `await push` won't resolve until the following\n // .next() call of the iterator, so if only\n // one .next() is called, this dispatch will hang.\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const object = await this.graffiti.get(...args);\n this.synchronizeDispatch({ object });\n return object;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const oldObject = await this.graffiti.put<{}>(...args);\n const partialObject = args[0];\n const newObject: GraffitiObjectBase = {\n ...oldObject,\n value: partialObject.value,\n channels: partialObject.channels,\n allowed: partialObject.allowed,\n };\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n {\n object: newObject,\n },\n true,\n );\n return oldObject;\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const oldObject = await this.graffiti.patch(...args);\n const newObject: GraffitiObjectBase = { ...oldObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(await this.applyPatch, prop, args[0], newObject);\n }\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n {\n object: newObject,\n },\n true,\n );\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n await this.synchronizeDispatch(\n {\n tombstone: true,\n object: oldObject,\n },\n undefined,\n true,\n );\n return oldObject;\n };\n\n protected objectStreamContinue<Schema extends JSONSchema>(\n iterator: GraffitiObjectStreamContinue<Schema>,\n ): GraffitiObjectStreamContinue<Schema> {\n const this_ = this;\n return (async function* () {\n while (true) {\n const result = await iterator.next();\n if (result.done) {\n const { continue: continue_, cursor } = result.value;\n return {\n continue: () => this_.objectStreamContinue<Schema>(continue_()),\n cursor,\n };\n }\n if (!result.value.error) {\n this_.synchronizeDispatch(\n result.value as GraffitiObjectStreamContinueEntry<{}>,\n );\n }\n yield result.value;\n }\n })();\n }\n\n protected objectStream<Schema extends JSONSchema>(\n iterator: GraffitiObjectStream<Schema>,\n ): GraffitiObjectStream<Schema> {\n const wrapped = this.objectStreamContinue<Schema>(iterator);\n return (async function* () {\n // Filter out the tombstones for type safety\n while (true) {\n const result = await wrapped.next();\n if (result.done) return result.value;\n if (result.value.error || !result.value.tombstone) yield result.value;\n }\n })();\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const iterator = this.graffiti.discover(...args);\n return this.objectStream<(typeof args)[1]>(iterator);\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (...args) => {\n const iterator = this.graffiti.recoverOrphans(...args);\n return this.objectStream<(typeof args)[0]>(iterator);\n };\n\n continueObjectStream: Graffiti[\"continueObjectStream\"] = (...args) => {\n const iterator = this.graffiti.continueObjectStream(...args);\n return this.objectStreamContinue<{}>(iterator);\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/index.d.ts CHANGED
@@ -27,8 +27,10 @@ export interface GraffitiSynchronizeOptions {
27
27
  * the [Graffiti Vue Plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)
28
28
  * and possibly other front-end libraries in the future.
29
29
  *
30
- * Specifically, it provides the following *synchronize*
31
- * methods for each of the following API methods:
30
+ * [See a live example](/example).
31
+ *
32
+ * Specifically, this library provides the following *synchronize*
33
+ * methods to correspond with each of the following Graffiti API methods:
32
34
  *
33
35
  * | API Method | Synchronize Method |
34
36
  * |------------|--------------------|
@@ -54,6 +56,8 @@ export interface GraffitiSynchronizeOptions {
54
56
  * all instance's of that friend's name in the user's application instantly,
55
57
  * providing a consistent user experience.
56
58
  *
59
+ * The source code for this library is [available on GitHub](https://github.com/graffiti-garden/wrapper-synchronize/).
60
+ *
57
61
  * @groupDescription Synchronize Methods
58
62
  * This group contains methods that listen for changes made via
59
63
  * {@link put}, {@link patch}, and {@link delete} or fetched from
@@ -70,8 +74,8 @@ export declare class GraffitiSynchronize extends Graffiti {
70
74
  login: Graffiti["login"];
71
75
  logout: Graffiti["logout"];
72
76
  sessionEvents: Graffiti["sessionEvents"];
73
- get ajv(): Promise<Ajv>;
74
- get applyPatch(): Promise<typeof applyPatch>;
77
+ protected get ajv(): Promise<Ajv>;
78
+ protected get applyPatch(): Promise<typeof applyPatch>;
75
79
  /**
76
80
  * Wraps a Graffiti API instance to provide the synchronize methods.
77
81
  * The GraffitiSyncrhonize class rather than the Graffiti class
@@ -134,6 +138,8 @@ export declare class GraffitiSynchronize extends Graffiti {
134
138
  *
135
139
  * Be careful using this method. Without additional filters it can
136
140
  * expose the user to content out of context.
141
+ *
142
+ * @group Synchronize Methods
137
143
  */
138
144
  synchronizeAll<Schema extends JSONSchema>(schema: Schema, session?: GraffitiSession | null): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>>;
139
145
  protected synchronizeDispatch(oldObject: GraffitiObjectStreamContinueEntry<{}>, newObject?: GraffitiObjectStreamContinueEntry<{}>, waitForListeners?: boolean): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graffiti-garden/wrapper-synchronize",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Internal synchronization for the Graffiti API",
5
5
  "types": "./dist/index.d.ts",
6
6
  "module": "./dist/esm/index.js",
@@ -24,7 +24,7 @@
24
24
  "test:coverage": "vitest --coverage",
25
25
  "build:types": "tsc --declaration --emitDeclarationOnly",
26
26
  "build:js": "tsx esbuild.config.mts",
27
- "build:docs": "typedoc --options typedoc.json",
27
+ "build:docs": "typedoc --options typedoc.json && cp -r example docs",
28
28
  "build": "rm -rf dist && npm run build:types && npm run build:js && npm run build:docs",
29
29
  "prepublishOnly": "npm update && npm test && npm run build"
30
30
  },
@@ -45,19 +45,19 @@
45
45
  },
46
46
  "homepage": "https://sync.graffiti.garden/classes/GraffitiSynchronize.html",
47
47
  "devDependencies": {
48
- "@types/node": "^22.13.1",
49
- "@vitest/coverage-v8": "^3.0.6",
50
- "tsx": "^4.19.2",
51
- "typedoc": "^0.27.6",
52
- "typescript": "^5.7.3",
53
- "vitest": "^3.0.5"
48
+ "@types/node": "^24.7.2",
49
+ "@vitest/coverage-v8": "^3.2.4",
50
+ "esbuild-plugin-polyfill-node": "^0.3.0",
51
+ "tsx": "^4.20.6",
52
+ "typedoc": "^0.28.14",
53
+ "typescript": "^5.9.3",
54
+ "vitest": "^3.2.4"
54
55
  },
55
56
  "dependencies": {
56
- "@graffiti-garden/api": "^0.6.2",
57
- "@graffiti-garden/implementation-local": "^0.6.2",
57
+ "@graffiti-garden/api": "^0.6.4",
58
+ "@graffiti-garden/implementation-local": "^0.6.4",
58
59
  "@repeaterjs/repeater": "^3.0.6",
59
60
  "ajv": "^8.17.1",
60
- "esbuild-plugin-polyfill-node": "^0.3.0",
61
61
  "fast-json-patch": "^3.1.1"
62
62
  }
63
63
  }
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  GraffitiObjectStream,
7
7
  GraffitiObjectStreamContinueEntry,
8
8
  GraffitiObjectStreamContinue,
9
+ GraffitiObject,
9
10
  } from "@graffiti-garden/api";
10
11
  import type { GraffitiObjectBase } from "@graffiti-garden/api";
11
12
  import { Repeater } from "@repeaterjs/repeater";
@@ -47,8 +48,10 @@ export interface GraffitiSynchronizeOptions {
47
48
  * the [Graffiti Vue Plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)
48
49
  * and possibly other front-end libraries in the future.
49
50
  *
50
- * Specifically, it provides the following *synchronize*
51
- * methods for each of the following API methods:
51
+ * [See a live example](/example).
52
+ *
53
+ * Specifically, this library provides the following *synchronize*
54
+ * methods to correspond with each of the following Graffiti API methods:
52
55
  *
53
56
  * | API Method | Synchronize Method |
54
57
  * |------------|--------------------|
@@ -74,6 +77,8 @@ export interface GraffitiSynchronizeOptions {
74
77
  * all instance's of that friend's name in the user's application instantly,
75
78
  * providing a consistent user experience.
76
79
  *
80
+ * The source code for this library is [available on GitHub](https://github.com/graffiti-garden/wrapper-synchronize/).
81
+ *
77
82
  * @groupDescription Synchronize Methods
78
83
  * This group contains methods that listen for changes made via
79
84
  * {@link put}, {@link patch}, and {@link delete} or fetched from
@@ -92,7 +97,7 @@ export class GraffitiSynchronize extends Graffiti {
92
97
  logout: Graffiti["logout"];
93
98
  sessionEvents: Graffiti["sessionEvents"];
94
99
 
95
- get ajv() {
100
+ protected get ajv() {
96
101
  if (!this.ajv_) {
97
102
  this.ajv_ = (async () => {
98
103
  const { default: Ajv } = await import("ajv");
@@ -102,7 +107,7 @@ export class GraffitiSynchronize extends Graffiti {
102
107
  return this.ajv_;
103
108
  }
104
109
 
105
- get applyPatch() {
110
+ protected get applyPatch() {
106
111
  if (!this.applyPatch_) {
107
112
  this.applyPatch_ = (async () => {
108
113
  const { applyPatch } = await import("fast-json-patch");
@@ -160,7 +165,10 @@ export class GraffitiSynchronize extends Graffiti {
160
165
  (this.options.omniscient ||
161
166
  isActorAllowedGraffitiObject(objectRaw.object, session))
162
167
  ) {
163
- const object = { ...objectRaw.object };
168
+ // Deep clone the object to prevent mutation
169
+ const object = JSON.parse(
170
+ JSON.stringify(objectRaw.object),
171
+ ) as GraffitiObject<{}>;
164
172
  if (!this.options.omniscient) {
165
173
  maskGraffitiObject(object, channels, session);
166
174
  }
@@ -179,7 +187,9 @@ export class GraffitiSynchronize extends Graffiti {
179
187
  },
180
188
  );
181
189
 
182
- return repeater;
190
+ return (async function* () {
191
+ for await (const i of repeater) yield i;
192
+ })();
183
193
  }
184
194
 
185
195
  /**
@@ -260,6 +270,8 @@ export class GraffitiSynchronize extends Graffiti {
260
270
  *
261
271
  * Be careful using this method. Without additional filters it can
262
272
  * expose the user to content out of context.
273
+ *
274
+ * @group Synchronize Methods
263
275
  */
264
276
  synchronizeAll<Schema extends JSONSchema>(
265
277
  schema: Schema,
@@ -410,7 +422,7 @@ export class GraffitiSynchronize extends Graffiti {
410
422
  };
411
423
 
412
424
  continueObjectStream: Graffiti["continueObjectStream"] = (...args) => {
413
- // TODO!!
414
- return this.graffiti.continueObjectStream(...args);
425
+ const iterator = this.graffiti.continueObjectStream(...args);
426
+ return this.objectStreamContinue<{}>(iterator);
415
427
  };
416
428
  }