@graffiti-garden/wrapper-synchronize 0.2.3 → 1.0.2

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
@@ -33,17 +33,19 @@ __export(index_exports, {
33
33
  module.exports = __toCommonJS(index_exports);
34
34
  var import_api = require("@graffiti-garden/api");
35
35
  var import_repeater = require("@repeaterjs/repeater");
36
- var import_utilities = require("@graffiti-garden/implementation-local/utilities");
37
- class GraffitiSynchronize extends import_api.Graffiti {
36
+ class GraffitiSynchronize {
38
37
  ajv_;
39
- applyPatch_;
40
38
  graffiti;
41
39
  callbacks = /* @__PURE__ */ new Set();
42
40
  options;
43
- channelStats;
44
41
  login;
45
42
  logout;
46
43
  sessionEvents;
44
+ postMedia;
45
+ getMedia;
46
+ deleteMedia;
47
+ actorToHandle;
48
+ handleToActor;
47
49
  get ajv() {
48
50
  if (!this.ajv_) {
49
51
  this.ajv_ = (async () => {
@@ -53,52 +55,42 @@ class GraffitiSynchronize extends import_api.Graffiti {
53
55
  }
54
56
  return this.ajv_;
55
57
  }
56
- get applyPatch() {
57
- if (!this.applyPatch_) {
58
- this.applyPatch_ = (async () => {
59
- const { applyPatch } = await import("fast-json-patch");
60
- return applyPatch;
61
- })();
62
- }
63
- return this.applyPatch_;
64
- }
65
58
  /**
66
59
  * Wraps a Graffiti API instance to provide the synchronize methods.
67
60
  * The GraffitiSyncrhonize class rather than the Graffiti class
68
61
  * must be used for all functions for the synchronize methods to work.
69
62
  */
70
63
  constructor(graffiti, options) {
71
- super();
72
64
  this.options = options ?? {};
73
65
  this.graffiti = graffiti;
74
- this.channelStats = graffiti.channelStats.bind(graffiti);
75
66
  this.login = graffiti.login.bind(graffiti);
76
67
  this.logout = graffiti.logout.bind(graffiti);
77
68
  this.sessionEvents = graffiti.sessionEvents;
69
+ this.postMedia = graffiti.postMedia.bind(graffiti);
70
+ this.getMedia = graffiti.getMedia.bind(graffiti);
71
+ this.deleteMedia = graffiti.deleteMedia.bind(graffiti);
72
+ this.actorToHandle = graffiti.actorToHandle.bind(graffiti);
73
+ this.handleToActor = graffiti.handleToActor.bind(graffiti);
78
74
  }
79
- synchronize(matchObject, channels, schema, session) {
80
- const seenUrls = /* @__PURE__ */ new Set();
75
+ synchronize(matchObject, channels, schema, session, seenUrls = /* @__PURE__ */ new Set()) {
81
76
  const repeater = new import_repeater.Repeater(
82
77
  async (push, stop) => {
83
- const validate = (0, import_utilities.compileGraffitiObjectSchema)(await this.ajv, schema);
84
- const callback = (oldObjectRaw, newObjectRaw) => {
85
- for (const objectRaw of [newObjectRaw, oldObjectRaw]) {
86
- if (objectRaw?.tombstone) {
87
- if (seenUrls.has(objectRaw.object.url)) {
88
- push(objectRaw);
89
- }
90
- } else if (objectRaw && matchObject(objectRaw.object) && (this.options.omniscient || (0, import_utilities.isActorAllowedGraffitiObject)(objectRaw.object, session))) {
91
- const object = JSON.parse(
92
- JSON.stringify(objectRaw.object)
93
- );
94
- if (!this.options.omniscient) {
95
- (0, import_utilities.maskGraffitiObject)(object, channels, session);
96
- }
97
- if (validate(object)) {
98
- push({ object });
99
- seenUrls.add(object.url);
100
- break;
101
- }
78
+ const validate = (0, import_api.compileGraffitiObjectSchema)(await this.ajv, schema);
79
+ const callback = (objectUpdate) => {
80
+ if (objectUpdate?.tombstone) {
81
+ if (seenUrls.has(objectUpdate.object.url)) {
82
+ push(objectUpdate);
83
+ }
84
+ } else if (objectUpdate && matchObject(objectUpdate.object) && (this.options.omniscient || (0, import_api.isActorAllowedGraffitiObject)(objectUpdate.object, session))) {
85
+ const object = JSON.parse(
86
+ JSON.stringify(objectUpdate.object)
87
+ );
88
+ if (!this.options.omniscient) {
89
+ (0, import_api.maskGraffitiObject)(object, channels, session);
90
+ }
91
+ if (validate(object)) {
92
+ push({ object });
93
+ seenUrls.add(object.url);
102
94
  }
103
95
  }
104
96
  };
@@ -107,12 +99,14 @@ class GraffitiSynchronize extends import_api.Graffiti {
107
99
  this.callbacks.delete(callback);
108
100
  }
109
101
  );
110
- return repeater;
102
+ return (async function* () {
103
+ for await (const i of repeater) yield i;
104
+ })();
111
105
  }
112
106
  /**
113
107
  * This method has the same signature as {@link discover} but listens for
114
- * changes made via {@link put}, {@link patch}, and {@link delete} or
115
- * fetched from {@link get}, {@link discover}, and {@link recoverOrphans}
108
+ * changes made via {@link post} and {@link delete} or
109
+ * fetched from {@link get}, {@link discover}, and {@link continueDiscover}
116
110
  * and then streams appropriate changes to provide a responsive and
117
111
  * consistent user experience.
118
112
  *
@@ -120,10 +114,9 @@ class GraffitiSynchronize extends import_api.Graffiti {
120
114
  * and will not terminate unless the user calls the `return` method on the iterator
121
115
  * or `break`s out of the loop.
122
116
  *
123
- * @group Synchronize Methods
117
+ * @group 0 - Synchronize Methods
124
118
  */
125
- synchronizeDiscover(...args) {
126
- const [channels, schema, session] = args;
119
+ synchronizeDiscover(channels, schema, session) {
127
120
  function matchObject(object) {
128
121
  return object.channels.some((channel) => channels.includes(channel));
129
122
  }
@@ -131,43 +124,28 @@ class GraffitiSynchronize extends import_api.Graffiti {
131
124
  }
132
125
  /**
133
126
  * This method has the same signature as {@link get} but
134
- * listens for changes made via {@link put}, {@link patch}, and {@link delete} or
135
- * fetched from {@link get}, {@link discover}, and {@link recoverOrphans} and then
127
+ * listens for changes made via {@link post}, and {@link delete} or
128
+ * fetched from {@link get}, {@link discover}, and {@link continueDiscover} and then
136
129
  * streams appropriate changes to provide a responsive and consistent user experience.
137
130
  *
138
131
  * Unlike {@link get}, which returns a single result, this method continuously
139
132
  * listens for changes which are output as an asynchronous stream, similar
140
133
  * to {@link discover}.
141
134
  *
142
- * @group Synchronize Methods
135
+ * @group 0 - Synchronize Methods
143
136
  */
144
- synchronizeGet(...args) {
145
- const [objectUrl, schema, session] = args;
146
- const url = (0, import_utilities.unpackObjectUrl)(objectUrl);
137
+ synchronizeGet(objectUrl, schema, session) {
138
+ const url = (0, import_api.unpackObjectUrl)(objectUrl);
147
139
  function matchObject(object) {
148
140
  return object.url === url;
149
141
  }
150
- return this.synchronize(matchObject, [], schema, session);
151
- }
152
- /**
153
- * This method has the same signature as {@link recoverOrphans} but
154
- * listens for changes made via
155
- * {@link put}, {@link patch}, and {@link delete} or fetched from
156
- * {@link get}, {@link discover}, and {@link recoverOrphans} and then
157
- * streams appropriate changes to provide a responsive and consistent user experience.
158
- *
159
- * Unlike {@link recoverOrphans}, this method continuously listens for changes
160
- * and will not terminate unless the user calls the `return` method on the iterator
161
- * or `break`s out of the loop.
162
- *
163
- * @group Synchronize Methods
164
- */
165
- synchronizeRecoverOrphans(...args) {
166
- const [schema, session] = args;
167
- function matchObject(object) {
168
- return object.actor === session.actor && object.channels.length === 0;
169
- }
170
- return this.synchronize(matchObject, [], schema, session);
142
+ return this.synchronize(
143
+ matchObject,
144
+ [],
145
+ schema,
146
+ session,
147
+ /* @__PURE__ */ new Set([url])
148
+ );
171
149
  }
172
150
  /**
173
151
  * Streams changes made to *any* object in *any* channel
@@ -178,115 +156,92 @@ class GraffitiSynchronize extends import_api.Graffiti {
178
156
  *
179
157
  * Be careful using this method. Without additional filters it can
180
158
  * expose the user to content out of context.
159
+ *
160
+ * @group 0 - Synchronize Methods
181
161
  */
182
162
  synchronizeAll(schema, session) {
183
163
  return this.synchronize(() => true, [], schema, session);
184
164
  }
185
- async synchronizeDispatch(oldObject, newObject, waitForListeners = false) {
165
+ async synchronizeDispatch(objectUpdate, waitForListeners = false) {
186
166
  for (const callback of this.callbacks) {
187
- callback(oldObject, newObject);
167
+ callback(objectUpdate);
188
168
  }
189
169
  if (waitForListeners) {
190
170
  await new Promise((resolve) => setTimeout(resolve, 0));
191
171
  }
192
172
  }
193
173
  get = async (...args) => {
194
- const object = await this.graffiti.get(...args);
195
- this.synchronizeDispatch({ object });
174
+ try {
175
+ const object = await this.graffiti.get(...args);
176
+ this.synchronizeDispatch({ object });
177
+ return object;
178
+ } catch (error) {
179
+ if (error instanceof import_api.GraffitiErrorNotFound) {
180
+ this.synchronizeDispatch({
181
+ tombstone: true,
182
+ object: { url: (0, import_api.unpackObjectUrl)(args[0]) }
183
+ });
184
+ }
185
+ throw error;
186
+ }
187
+ };
188
+ post = async (...args) => {
189
+ const object = await this.graffiti.post(...args);
190
+ await this.synchronizeDispatch({ object }, true);
196
191
  return object;
197
192
  };
198
- put = async (...args) => {
199
- const oldObject = await this.graffiti.put(...args);
200
- const partialObject = args[0];
201
- const newObject = {
202
- ...oldObject,
203
- value: partialObject.value,
204
- channels: partialObject.channels,
205
- allowed: partialObject.allowed
193
+ delete = async (...args) => {
194
+ const update = {
195
+ tombstone: true,
196
+ object: { url: (0, import_api.unpackObjectUrl)(args[0]) }
206
197
  };
207
- await this.synchronizeDispatch(
208
- {
209
- tombstone: true,
210
- object: oldObject
211
- },
212
- {
213
- object: newObject
214
- },
215
- true
216
- );
217
- return oldObject;
218
- };
219
- patch = async (...args) => {
220
- const oldObject = await this.graffiti.patch(...args);
221
- const newObject = { ...oldObject };
222
- for (const prop of ["value", "channels", "allowed"]) {
223
- (0, import_utilities.applyGraffitiPatch)(await this.applyPatch, prop, args[0], newObject);
198
+ try {
199
+ const oldObject = await this.graffiti.delete(...args);
200
+ await this.synchronizeDispatch(update, true);
201
+ return oldObject;
202
+ } catch (error) {
203
+ if (error instanceof import_api.GraffitiErrorNotFound) {
204
+ await this.synchronizeDispatch(update, true);
205
+ }
206
+ throw error;
224
207
  }
225
- await this.synchronizeDispatch(
226
- {
227
- tombstone: true,
228
- object: oldObject
229
- },
230
- {
231
- object: newObject
232
- },
233
- true
234
- );
235
- return oldObject;
236
- };
237
- delete = async (...args) => {
238
- const oldObject = await this.graffiti.delete(...args);
239
- await this.synchronizeDispatch(
240
- {
241
- tombstone: true,
242
- object: oldObject
243
- },
244
- void 0,
245
- true
246
- );
247
- return oldObject;
248
208
  };
249
209
  objectStreamContinue(iterator) {
250
210
  const this_ = this;
251
- return async function* () {
211
+ return (async function* () {
252
212
  while (true) {
253
213
  const result = await iterator.next();
254
214
  if (result.done) {
255
215
  const { continue: continue_, cursor } = result.value;
256
216
  return {
257
- continue: () => this_.objectStreamContinue(continue_()),
217
+ continue: (session) => this_.objectStreamContinue(continue_(session)),
258
218
  cursor
259
219
  };
260
220
  }
261
221
  if (!result.value.error) {
262
- this_.synchronizeDispatch(
263
- result.value
264
- );
222
+ this_.synchronizeDispatch(result.value);
265
223
  }
266
224
  yield result.value;
267
225
  }
268
- }();
226
+ })();
269
227
  }
270
228
  objectStream(iterator) {
271
229
  const wrapped = this.objectStreamContinue(iterator);
272
- return async function* () {
230
+ return (async function* () {
273
231
  while (true) {
274
232
  const result = await wrapped.next();
275
233
  if (result.done) return result.value;
276
234
  if (result.value.error || !result.value.tombstone) yield result.value;
277
235
  }
278
- }();
236
+ })();
279
237
  }
280
238
  discover = (...args) => {
281
239
  const iterator = this.graffiti.discover(...args);
282
240
  return this.objectStream(iterator);
283
241
  };
284
- recoverOrphans = (...args) => {
285
- const iterator = this.graffiti.recoverOrphans(...args);
286
- return this.objectStream(iterator);
287
- };
288
- continueObjectStream = (...args) => {
289
- return this.graffiti.continueObjectStream(...args);
242
+ continueDiscover = (...args) => {
243
+ const iterator = this.graffiti.continueDiscover(...args);
244
+ return this.objectStreamContinue(iterator);
290
245
  };
291
246
  }
292
247
  //# 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 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 * 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 // 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 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 type {\n Graffiti,\n GraffitiSession,\n JSONSchema,\n GraffitiObjectBase,\n GraffitiObjectStream,\n GraffitiObjectStreamContinueEntry,\n GraffitiObjectStreamContinue,\n GraffitiObjectUrl,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n compileGraffitiObjectSchema,\n isActorAllowedGraffitiObject,\n maskGraffitiObject,\n unpackObjectUrl,\n} from \"@graffiti-garden/api\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nexport type * from \"@graffiti-garden/api\";\n\nexport type GraffitiSynchronizeCallback = (\n object: 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 *\n * Whenever a change is made via {@link post} and {@link delete} or\n * received from {@link get}, {@link discover}, and {@link continueDiscover},\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 post}. 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 * Additionally, the library supplies a {@link synchronizeAll} method that can be used\n * to stream all the Graffiti changes that an application is aware of, which can be used\n * for caching or history building.\n *\n * The source code for this library is [available on GitHub](https://github.com/graffiti-garden/wrapper-synchronize/).\n *\n * @groupDescription 0 - Synchronize Methods\n * This group contains methods that listen for changes made via\n * {@link post}, and {@link delete} or fetched from\n * {@link get}, {@link discover}, or {@link continueDiscover} and then\n * streams appropriate changes to provide a responsive and consistent user experience.\n */\nexport class GraffitiSynchronize implements Graffiti {\n protected ajv_: Promise<Ajv> | undefined;\n protected readonly graffiti: Graffiti;\n protected readonly callbacks = new Set<GraffitiSynchronizeCallback>();\n protected readonly options: GraffitiSynchronizeOptions;\n\n login: Graffiti[\"login\"];\n logout: Graffiti[\"logout\"];\n sessionEvents: Graffiti[\"sessionEvents\"];\n postMedia: Graffiti[\"postMedia\"];\n getMedia: Graffiti[\"getMedia\"];\n deleteMedia: Graffiti[\"deleteMedia\"];\n actorToHandle: Graffiti[\"actorToHandle\"];\n handleToActor: Graffiti[\"handleToActor\"];\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 /**\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 this.options = options ?? {};\n this.graffiti = graffiti;\n this.login = graffiti.login.bind(graffiti);\n this.logout = graffiti.logout.bind(graffiti);\n this.sessionEvents = graffiti.sessionEvents;\n this.postMedia = graffiti.postMedia.bind(graffiti);\n this.getMedia = graffiti.getMedia.bind(graffiti);\n this.deleteMedia = graffiti.deleteMedia.bind(graffiti);\n this.actorToHandle = graffiti.actorToHandle.bind(graffiti);\n this.handleToActor = graffiti.handleToActor.bind(graffiti);\n }\n\n protected synchronize<Schema extends JSONSchema>(\n matchObject: (object: GraffitiObjectBase) => boolean,\n channels: string[],\n schema: Schema,\n session?: GraffitiSession | null,\n seenUrls: Set<string> = new Set<string>(),\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const repeater = new Repeater<GraffitiObjectStreamContinueEntry<Schema>>(\n async (push, stop) => {\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n const callback: GraffitiSynchronizeCallback = (objectUpdate) => {\n if (objectUpdate?.tombstone) {\n if (seenUrls.has(objectUpdate.object.url)) {\n push(objectUpdate);\n }\n } else if (\n objectUpdate &&\n matchObject(objectUpdate.object) &&\n (this.options.omniscient ||\n isActorAllowedGraffitiObject(objectUpdate.object, session))\n ) {\n // Deep clone the object to prevent mutation\n const object = JSON.parse(\n JSON.stringify(objectUpdate.object),\n ) as GraffitiObjectBase;\n if (!this.options.omniscient) {\n maskGraffitiObject(object, channels, session);\n }\n if (validate(object)) {\n push({ object });\n seenUrls.add(object.url);\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 post} and {@link delete} or\n * fetched from {@link get}, {@link discover}, and {@link continueDiscover}\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 0 - Synchronize Methods\n */\n synchronizeDiscover<Schema extends JSONSchema>(\n channels: string[],\n schema: Schema,\n session?: GraffitiSession | null,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\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 post}, and {@link delete} or\n * fetched from {@link get}, {@link discover}, and {@link continueDiscover} 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 0 - Synchronize Methods\n */\n synchronizeGet<Schema extends JSONSchema>(\n objectUrl: string | GraffitiObjectUrl,\n schema: Schema,\n session?: GraffitiSession | null | undefined,\n ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {\n const url = unpackObjectUrl(objectUrl);\n function matchObject(object: GraffitiObjectBase) {\n return object.url === url;\n }\n return this.synchronize<Schema>(\n matchObject,\n [],\n schema,\n session,\n new Set<string>([url]),\n );\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 0 - 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 objectUpdate: GraffitiObjectStreamContinueEntry<{}>,\n waitForListeners = false,\n ) {\n for (const callback of this.callbacks) {\n callback(objectUpdate);\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 try {\n const object = await this.graffiti.get(...args);\n this.synchronizeDispatch({ object });\n return object;\n } catch (error) {\n if (error instanceof GraffitiErrorNotFound) {\n this.synchronizeDispatch({\n tombstone: true,\n object: { url: unpackObjectUrl(args[0]) },\n });\n }\n throw error;\n }\n };\n\n post: Graffiti[\"post\"] = async (...args) => {\n // @ts-ignore\n const object = await this.graffiti.post(...args);\n await this.synchronizeDispatch({ object }, true);\n return object;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const update = {\n tombstone: true,\n object: { url: unpackObjectUrl(args[0]) },\n } as const;\n try {\n const oldObject = await this.graffiti.delete(...args);\n await this.synchronizeDispatch(update, true);\n return oldObject;\n } catch (error) {\n if (error instanceof GraffitiErrorNotFound) {\n await this.synchronizeDispatch(update, true);\n }\n throw error;\n }\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: (session?: GraffitiSession | null) =>\n this_.objectStreamContinue<Schema>(continue_(session)),\n cursor,\n };\n }\n if (!result.value.error) {\n this_.synchronizeDispatch(result.value);\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 continueDiscover: Graffiti[\"continueDiscover\"] = (...args) => {\n const iterator = this.graffiti.continueDiscover(...args);\n return this.objectStreamContinue<{}>(iterator);\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }