@graffiti-garden/wrapper-synchronize 0.1.1 → 0.2.1
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/browser/index.js +1 -1
- package/dist/browser/index.js.map +3 -3
- package/dist/cjs/index.js +72 -24
- package/dist/cjs/index.js.map +2 -2
- package/dist/esm/index.js +73 -25
- package/dist/esm/index.js.map +2 -2
- package/dist/index.d.ts +11 -17
- package/dist/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/index.spec.ts +181 -133
- package/src/index.ts +97 -41
package/dist/cjs/index.js
CHANGED
|
@@ -77,18 +77,24 @@ class GraffitiSynchronize extends import_api.Graffiti {
|
|
|
77
77
|
this.sessionEvents = graffiti.sessionEvents;
|
|
78
78
|
}
|
|
79
79
|
synchronize(matchObject, channels, schema, session) {
|
|
80
|
+
const seenUrls = /* @__PURE__ */ new Set();
|
|
80
81
|
const repeater = new import_repeater.Repeater(
|
|
81
82
|
async (push, stop) => {
|
|
82
83
|
const validate = (0, import_utilities.compileGraffitiObjectSchema)(await this.ajv, schema);
|
|
83
84
|
const callback = (oldObjectRaw, newObjectRaw) => {
|
|
84
85
|
for (const objectRaw of [newObjectRaw, oldObjectRaw]) {
|
|
85
|
-
if (objectRaw
|
|
86
|
-
|
|
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 = { ...objectRaw.object };
|
|
87
92
|
if (!this.options.omniscient) {
|
|
88
93
|
(0, import_utilities.maskGraffitiObject)(object, channels, session);
|
|
89
94
|
}
|
|
90
95
|
if (validate(object)) {
|
|
91
|
-
push({
|
|
96
|
+
push({ object });
|
|
97
|
+
seenUrls.add(object.url);
|
|
92
98
|
break;
|
|
93
99
|
}
|
|
94
100
|
}
|
|
@@ -133,10 +139,10 @@ class GraffitiSynchronize extends import_api.Graffiti {
|
|
|
133
139
|
* @group Synchronize Methods
|
|
134
140
|
*/
|
|
135
141
|
synchronizeGet(...args) {
|
|
136
|
-
const [
|
|
137
|
-
const
|
|
142
|
+
const [objectUrl, schema, session] = args;
|
|
143
|
+
const url = (0, import_utilities.unpackObjectUrl)(objectUrl);
|
|
138
144
|
function matchObject(object) {
|
|
139
|
-
return object.url ===
|
|
145
|
+
return object.url === url;
|
|
140
146
|
}
|
|
141
147
|
return this.synchronize(matchObject, [], schema, session);
|
|
142
148
|
}
|
|
@@ -171,7 +177,7 @@ class GraffitiSynchronize extends import_api.Graffiti {
|
|
|
171
177
|
* expose the user to content out of context.
|
|
172
178
|
*/
|
|
173
179
|
synchronizeAll(schema, session) {
|
|
174
|
-
return this.synchronize(() => true, [], schema
|
|
180
|
+
return this.synchronize(() => true, [], schema, session);
|
|
175
181
|
}
|
|
176
182
|
async synchronizeDispatch(oldObject, newObject, waitForListeners = false) {
|
|
177
183
|
for (const callback of this.callbacks) {
|
|
@@ -183,7 +189,7 @@ class GraffitiSynchronize extends import_api.Graffiti {
|
|
|
183
189
|
}
|
|
184
190
|
get = async (...args) => {
|
|
185
191
|
const object = await this.graffiti.get(...args);
|
|
186
|
-
this.synchronizeDispatch(object);
|
|
192
|
+
this.synchronizeDispatch({ object });
|
|
187
193
|
return object;
|
|
188
194
|
};
|
|
189
195
|
put = async (...args) => {
|
|
@@ -193,41 +199,80 @@ class GraffitiSynchronize extends import_api.Graffiti {
|
|
|
193
199
|
...oldObject,
|
|
194
200
|
value: partialObject.value,
|
|
195
201
|
channels: partialObject.channels,
|
|
196
|
-
allowed: partialObject.allowed
|
|
197
|
-
tombstone: false
|
|
202
|
+
allowed: partialObject.allowed
|
|
198
203
|
};
|
|
199
|
-
await this.synchronizeDispatch(
|
|
204
|
+
await this.synchronizeDispatch(
|
|
205
|
+
{
|
|
206
|
+
tombstone: true,
|
|
207
|
+
object: oldObject
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
object: newObject
|
|
211
|
+
},
|
|
212
|
+
true
|
|
213
|
+
);
|
|
200
214
|
return oldObject;
|
|
201
215
|
};
|
|
202
216
|
patch = async (...args) => {
|
|
203
217
|
const oldObject = await this.graffiti.patch(...args);
|
|
204
218
|
const newObject = { ...oldObject };
|
|
205
|
-
newObject.tombstone = false;
|
|
206
219
|
for (const prop of ["value", "channels", "allowed"]) {
|
|
207
220
|
(0, import_utilities.applyGraffitiPatch)(await this.applyPatch, prop, args[0], newObject);
|
|
208
221
|
}
|
|
209
|
-
await this.synchronizeDispatch(
|
|
222
|
+
await this.synchronizeDispatch(
|
|
223
|
+
{
|
|
224
|
+
tombstone: true,
|
|
225
|
+
object: oldObject
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
object: newObject
|
|
229
|
+
},
|
|
230
|
+
true
|
|
231
|
+
);
|
|
210
232
|
return oldObject;
|
|
211
233
|
};
|
|
212
234
|
delete = async (...args) => {
|
|
213
235
|
const oldObject = await this.graffiti.delete(...args);
|
|
214
|
-
await this.synchronizeDispatch(
|
|
236
|
+
await this.synchronizeDispatch(
|
|
237
|
+
{
|
|
238
|
+
tombstone: true,
|
|
239
|
+
object: oldObject
|
|
240
|
+
},
|
|
241
|
+
void 0,
|
|
242
|
+
true
|
|
243
|
+
);
|
|
215
244
|
return oldObject;
|
|
216
245
|
};
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
246
|
+
objectStreamContinue(iterator) {
|
|
247
|
+
const this_ = this;
|
|
248
|
+
return async function* () {
|
|
249
|
+
while (true) {
|
|
250
|
+
const result = await iterator.next();
|
|
251
|
+
if (result.done) {
|
|
252
|
+
const { continue: continue_, cursor } = result.value;
|
|
253
|
+
return {
|
|
254
|
+
continue: () => this_.objectStreamContinue(continue_()),
|
|
255
|
+
cursor
|
|
256
|
+
};
|
|
257
|
+
}
|
|
222
258
|
if (!result.value.error) {
|
|
223
|
-
|
|
259
|
+
this_.synchronizeDispatch(
|
|
260
|
+
result.value
|
|
261
|
+
);
|
|
224
262
|
}
|
|
225
263
|
yield result.value;
|
|
226
|
-
result = await iterator.next();
|
|
227
264
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
265
|
+
}();
|
|
266
|
+
}
|
|
267
|
+
objectStream(iterator) {
|
|
268
|
+
const wrapped = this.objectStreamContinue(iterator);
|
|
269
|
+
return async function* () {
|
|
270
|
+
while (true) {
|
|
271
|
+
const result = await wrapped.next();
|
|
272
|
+
if (result.done) return result.value;
|
|
273
|
+
if (result.value.error || !result.value.tombstone) yield result.value;
|
|
274
|
+
}
|
|
275
|
+
}();
|
|
231
276
|
}
|
|
232
277
|
discover = (...args) => {
|
|
233
278
|
const iterator = this.graffiti.discover(...args);
|
|
@@ -237,5 +282,8 @@ class GraffitiSynchronize extends import_api.Graffiti {
|
|
|
237
282
|
const iterator = this.graffiti.recoverOrphans(...args);
|
|
238
283
|
return this.objectStream(iterator);
|
|
239
284
|
};
|
|
285
|
+
continueObjectStream = (...args) => {
|
|
286
|
+
return this.graffiti.continueObjectStream(...args);
|
|
287
|
+
};
|
|
240
288
|
}
|
|
241
289
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -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 GraffitiObject,\n JSONSchema,\n GraffitiStream,\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 unpackLocationOrUri,\n} from \"@graffiti-garden/implementation-local/utilities\";\nexport type * from \"@graffiti-garden/api\";\n\nexport type GraffitiSynchronizeCallback = (\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\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 ) {\n const repeater: GraffitiStream<GraffitiObject<Schema>> = new Repeater(\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 (\n objectRaw &&\n matchObject(objectRaw) &&\n (this.options.omniscient ||\n isActorAllowedGraffitiObject(objectRaw, session))\n ) {\n const object = { ...objectRaw };\n if (!this.options.omniscient) {\n maskGraffitiObject(object, channels, session);\n }\n if (validate(object)) {\n push({ value: object });\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 ): GraffitiStream<GraffitiObject<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 {@link GraffitiStream}.\n *\n * @group Synchronize Methods\n */\n synchronizeGet<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.get<Schema>>\n ): GraffitiStream<GraffitiObject<Schema>> {\n const [locationOrUri, schema, session] = args;\n const uri = unpackLocationOrUri(locationOrUri);\n function matchObject(object: GraffitiObjectBase) {\n return object.url === uri;\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 ): GraffitiStream<GraffitiObject<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 ): GraffitiStream<GraffitiObjectBase> {\n return this.synchronize(() => true, [], schema ?? {}, session);\n }\n\n protected async synchronizeDispatch(\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\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 tombstone: false,\n };\n await this.synchronizeDispatch(oldObject, newObject, true);\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 newObject.tombstone = false;\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(await this.applyPatch, prop, args[0], newObject);\n }\n await this.synchronizeDispatch(oldObject, newObject, true);\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n await this.synchronizeDispatch(oldObject, undefined, true);\n return oldObject;\n };\n\n protected objectStream<Schema extends JSONSchema>(\n iterator: ReturnType<typeof Graffiti.prototype.discover<Schema>>,\n ) {\n const dispatch = this.synchronizeDispatch.bind(this);\n const wrapper = async function* () {\n let result = await iterator.next();\n while (!result.done) {\n if (!result.value.error) {\n dispatch(result.value.value);\n }\n yield result.value;\n result = await iterator.next();\n }\n return result.value;\n };\n return wrapper();\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"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,iBAAyB;
|
|
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 {@link GraffitiStream}.\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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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/esm/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
compileGraffitiObjectSchema,
|
|
6
6
|
isActorAllowedGraffitiObject,
|
|
7
7
|
maskGraffitiObject,
|
|
8
|
-
|
|
8
|
+
unpackObjectUrl
|
|
9
9
|
} from "@graffiti-garden/implementation-local/utilities";
|
|
10
10
|
class GraffitiSynchronize extends Graffiti {
|
|
11
11
|
ajv_;
|
|
@@ -50,18 +50,24 @@ class GraffitiSynchronize extends Graffiti {
|
|
|
50
50
|
this.sessionEvents = graffiti.sessionEvents;
|
|
51
51
|
}
|
|
52
52
|
synchronize(matchObject, channels, schema, session) {
|
|
53
|
+
const seenUrls = /* @__PURE__ */ new Set();
|
|
53
54
|
const repeater = new Repeater(
|
|
54
55
|
async (push, stop) => {
|
|
55
56
|
const validate = compileGraffitiObjectSchema(await this.ajv, schema);
|
|
56
57
|
const callback = (oldObjectRaw, newObjectRaw) => {
|
|
57
58
|
for (const objectRaw of [newObjectRaw, oldObjectRaw]) {
|
|
58
|
-
if (objectRaw
|
|
59
|
-
|
|
59
|
+
if (objectRaw?.tombstone) {
|
|
60
|
+
if (seenUrls.has(objectRaw.object.url)) {
|
|
61
|
+
push(objectRaw);
|
|
62
|
+
}
|
|
63
|
+
} else if (objectRaw && matchObject(objectRaw.object) && (this.options.omniscient || isActorAllowedGraffitiObject(objectRaw.object, session))) {
|
|
64
|
+
const object = { ...objectRaw.object };
|
|
60
65
|
if (!this.options.omniscient) {
|
|
61
66
|
maskGraffitiObject(object, channels, session);
|
|
62
67
|
}
|
|
63
68
|
if (validate(object)) {
|
|
64
|
-
push({
|
|
69
|
+
push({ object });
|
|
70
|
+
seenUrls.add(object.url);
|
|
65
71
|
break;
|
|
66
72
|
}
|
|
67
73
|
}
|
|
@@ -106,10 +112,10 @@ class GraffitiSynchronize extends Graffiti {
|
|
|
106
112
|
* @group Synchronize Methods
|
|
107
113
|
*/
|
|
108
114
|
synchronizeGet(...args) {
|
|
109
|
-
const [
|
|
110
|
-
const
|
|
115
|
+
const [objectUrl, schema, session] = args;
|
|
116
|
+
const url = unpackObjectUrl(objectUrl);
|
|
111
117
|
function matchObject(object) {
|
|
112
|
-
return object.url ===
|
|
118
|
+
return object.url === url;
|
|
113
119
|
}
|
|
114
120
|
return this.synchronize(matchObject, [], schema, session);
|
|
115
121
|
}
|
|
@@ -144,7 +150,7 @@ class GraffitiSynchronize extends Graffiti {
|
|
|
144
150
|
* expose the user to content out of context.
|
|
145
151
|
*/
|
|
146
152
|
synchronizeAll(schema, session) {
|
|
147
|
-
return this.synchronize(() => true, [], schema
|
|
153
|
+
return this.synchronize(() => true, [], schema, session);
|
|
148
154
|
}
|
|
149
155
|
async synchronizeDispatch(oldObject, newObject, waitForListeners = false) {
|
|
150
156
|
for (const callback of this.callbacks) {
|
|
@@ -156,7 +162,7 @@ class GraffitiSynchronize extends Graffiti {
|
|
|
156
162
|
}
|
|
157
163
|
get = async (...args) => {
|
|
158
164
|
const object = await this.graffiti.get(...args);
|
|
159
|
-
this.synchronizeDispatch(object);
|
|
165
|
+
this.synchronizeDispatch({ object });
|
|
160
166
|
return object;
|
|
161
167
|
};
|
|
162
168
|
put = async (...args) => {
|
|
@@ -166,41 +172,80 @@ class GraffitiSynchronize extends Graffiti {
|
|
|
166
172
|
...oldObject,
|
|
167
173
|
value: partialObject.value,
|
|
168
174
|
channels: partialObject.channels,
|
|
169
|
-
allowed: partialObject.allowed
|
|
170
|
-
tombstone: false
|
|
175
|
+
allowed: partialObject.allowed
|
|
171
176
|
};
|
|
172
|
-
await this.synchronizeDispatch(
|
|
177
|
+
await this.synchronizeDispatch(
|
|
178
|
+
{
|
|
179
|
+
tombstone: true,
|
|
180
|
+
object: oldObject
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
object: newObject
|
|
184
|
+
},
|
|
185
|
+
true
|
|
186
|
+
);
|
|
173
187
|
return oldObject;
|
|
174
188
|
};
|
|
175
189
|
patch = async (...args) => {
|
|
176
190
|
const oldObject = await this.graffiti.patch(...args);
|
|
177
191
|
const newObject = { ...oldObject };
|
|
178
|
-
newObject.tombstone = false;
|
|
179
192
|
for (const prop of ["value", "channels", "allowed"]) {
|
|
180
193
|
applyGraffitiPatch(await this.applyPatch, prop, args[0], newObject);
|
|
181
194
|
}
|
|
182
|
-
await this.synchronizeDispatch(
|
|
195
|
+
await this.synchronizeDispatch(
|
|
196
|
+
{
|
|
197
|
+
tombstone: true,
|
|
198
|
+
object: oldObject
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
object: newObject
|
|
202
|
+
},
|
|
203
|
+
true
|
|
204
|
+
);
|
|
183
205
|
return oldObject;
|
|
184
206
|
};
|
|
185
207
|
delete = async (...args) => {
|
|
186
208
|
const oldObject = await this.graffiti.delete(...args);
|
|
187
|
-
await this.synchronizeDispatch(
|
|
209
|
+
await this.synchronizeDispatch(
|
|
210
|
+
{
|
|
211
|
+
tombstone: true,
|
|
212
|
+
object: oldObject
|
|
213
|
+
},
|
|
214
|
+
void 0,
|
|
215
|
+
true
|
|
216
|
+
);
|
|
188
217
|
return oldObject;
|
|
189
218
|
};
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
219
|
+
objectStreamContinue(iterator) {
|
|
220
|
+
const this_ = this;
|
|
221
|
+
return async function* () {
|
|
222
|
+
while (true) {
|
|
223
|
+
const result = await iterator.next();
|
|
224
|
+
if (result.done) {
|
|
225
|
+
const { continue: continue_, cursor } = result.value;
|
|
226
|
+
return {
|
|
227
|
+
continue: () => this_.objectStreamContinue(continue_()),
|
|
228
|
+
cursor
|
|
229
|
+
};
|
|
230
|
+
}
|
|
195
231
|
if (!result.value.error) {
|
|
196
|
-
|
|
232
|
+
this_.synchronizeDispatch(
|
|
233
|
+
result.value
|
|
234
|
+
);
|
|
197
235
|
}
|
|
198
236
|
yield result.value;
|
|
199
|
-
result = await iterator.next();
|
|
200
237
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
238
|
+
}();
|
|
239
|
+
}
|
|
240
|
+
objectStream(iterator) {
|
|
241
|
+
const wrapped = this.objectStreamContinue(iterator);
|
|
242
|
+
return async function* () {
|
|
243
|
+
while (true) {
|
|
244
|
+
const result = await wrapped.next();
|
|
245
|
+
if (result.done) return result.value;
|
|
246
|
+
if (result.value.error || !result.value.tombstone) yield result.value;
|
|
247
|
+
}
|
|
248
|
+
}();
|
|
204
249
|
}
|
|
205
250
|
discover = (...args) => {
|
|
206
251
|
const iterator = this.graffiti.discover(...args);
|
|
@@ -210,6 +255,9 @@ class GraffitiSynchronize extends Graffiti {
|
|
|
210
255
|
const iterator = this.graffiti.recoverOrphans(...args);
|
|
211
256
|
return this.objectStream(iterator);
|
|
212
257
|
};
|
|
258
|
+
continueObjectStream = (...args) => {
|
|
259
|
+
return this.graffiti.continueObjectStream(...args);
|
|
260
|
+
};
|
|
213
261
|
}
|
|
214
262
|
export {
|
|
215
263
|
GraffitiSynchronize
|
package/dist/esm/index.js.map
CHANGED
|
@@ -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 GraffitiObject,\n JSONSchema,\n GraffitiStream,\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 unpackLocationOrUri,\n} from \"@graffiti-garden/implementation-local/utilities\";\nexport type * from \"@graffiti-garden/api\";\n\nexport type GraffitiSynchronizeCallback = (\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\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 ) {\n const repeater: GraffitiStream<GraffitiObject<Schema>> = new Repeater(\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 (\n objectRaw &&\n matchObject(objectRaw) &&\n (this.options.omniscient ||\n isActorAllowedGraffitiObject(objectRaw, session))\n ) {\n const object = { ...objectRaw };\n if (!this.options.omniscient) {\n maskGraffitiObject(object, channels, session);\n }\n if (validate(object)) {\n push({ value: object });\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 ): GraffitiStream<GraffitiObject<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 {@link GraffitiStream}.\n *\n * @group Synchronize Methods\n */\n synchronizeGet<Schema extends JSONSchema>(\n ...args: Parameters<typeof Graffiti.prototype.get<Schema>>\n ): GraffitiStream<GraffitiObject<Schema>> {\n const [locationOrUri, schema, session] = args;\n const uri = unpackLocationOrUri(locationOrUri);\n function matchObject(object: GraffitiObjectBase) {\n return object.url === uri;\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 ): GraffitiStream<GraffitiObject<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 ): GraffitiStream<GraffitiObjectBase> {\n return this.synchronize(() => true, [], schema ?? {}, session);\n }\n\n protected async synchronizeDispatch(\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\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 tombstone: false,\n };\n await this.synchronizeDispatch(oldObject, newObject, true);\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 newObject.tombstone = false;\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(await this.applyPatch, prop, args[0], newObject);\n }\n await this.synchronizeDispatch(oldObject, newObject, true);\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n await this.synchronizeDispatch(oldObject, undefined, true);\n return oldObject;\n };\n\n protected objectStream<Schema extends JSONSchema>(\n iterator: ReturnType<typeof Graffiti.prototype.discover<Schema>>,\n ) {\n const dispatch = this.synchronizeDispatch.bind(this);\n const wrapper = async function* () {\n let result = await iterator.next();\n while (!result.done) {\n if (!result.value.error) {\n dispatch(result.value.value);\n }\n yield result.value;\n result = await iterator.next();\n }\n return result.value;\n };\n return wrapper();\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"],
|
|
5
|
-
"mappings": "AACA,SAAS,gBAAgB;
|
|
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 {@link GraffitiStream}.\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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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type Ajv from "ajv";
|
|
2
2
|
import { Graffiti } from "@graffiti-garden/api";
|
|
3
|
-
import type { GraffitiSession,
|
|
3
|
+
import type { GraffitiSession, JSONSchema, GraffitiObjectStream, GraffitiObjectStreamContinueEntry, GraffitiObjectStreamContinue } from "@graffiti-garden/api";
|
|
4
4
|
import type { GraffitiObjectBase } from "@graffiti-garden/api";
|
|
5
5
|
import type { applyPatch } from "fast-json-patch";
|
|
6
6
|
export type * from "@graffiti-garden/api";
|
|
7
|
-
export type GraffitiSynchronizeCallback = (oldObject:
|
|
7
|
+
export type GraffitiSynchronizeCallback = (oldObject: GraffitiObjectStreamContinueEntry<{}>, newObject?: GraffitiObjectStreamContinueEntry<{}>) => void;
|
|
8
8
|
export interface GraffitiSynchronizeOptions {
|
|
9
9
|
/**
|
|
10
10
|
* Allows synchronize to listen to all objects, not just those
|
|
@@ -83,7 +83,7 @@ export declare class GraffitiSynchronize extends Graffiti {
|
|
|
83
83
|
* instance to wrap.
|
|
84
84
|
*/
|
|
85
85
|
graffiti: Graffiti, options?: GraffitiSynchronizeOptions);
|
|
86
|
-
protected synchronize<Schema extends JSONSchema>(matchObject: (object: GraffitiObjectBase) => boolean, channels: string[], schema: Schema, session?: GraffitiSession | null):
|
|
86
|
+
protected synchronize<Schema extends JSONSchema>(matchObject: (object: GraffitiObjectBase) => boolean, channels: string[], schema: Schema, session?: GraffitiSession | null): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>>;
|
|
87
87
|
/**
|
|
88
88
|
* This method has the same signature as {@link discover} but listens for
|
|
89
89
|
* changes made via {@link put}, {@link patch}, and {@link delete} or
|
|
@@ -97,7 +97,7 @@ export declare class GraffitiSynchronize extends Graffiti {
|
|
|
97
97
|
*
|
|
98
98
|
* @group Synchronize Methods
|
|
99
99
|
*/
|
|
100
|
-
synchronizeDiscover<Schema extends JSONSchema>(...args: Parameters<typeof Graffiti.prototype.discover<Schema>>):
|
|
100
|
+
synchronizeDiscover<Schema extends JSONSchema>(...args: Parameters<typeof Graffiti.prototype.discover<Schema>>): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>>;
|
|
101
101
|
/**
|
|
102
102
|
* This method has the same signature as {@link get} but
|
|
103
103
|
* listens for changes made via {@link put}, {@link patch}, and {@link delete} or
|
|
@@ -109,7 +109,7 @@ export declare class GraffitiSynchronize extends Graffiti {
|
|
|
109
109
|
*
|
|
110
110
|
* @group Synchronize Methods
|
|
111
111
|
*/
|
|
112
|
-
synchronizeGet<Schema extends JSONSchema>(...args: Parameters<typeof Graffiti.prototype.get<Schema>>):
|
|
112
|
+
synchronizeGet<Schema extends JSONSchema>(...args: Parameters<typeof Graffiti.prototype.get<Schema>>): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>>;
|
|
113
113
|
/**
|
|
114
114
|
* This method has the same signature as {@link recoverOrphans} but
|
|
115
115
|
* listens for changes made via
|
|
@@ -123,7 +123,7 @@ export declare class GraffitiSynchronize extends Graffiti {
|
|
|
123
123
|
*
|
|
124
124
|
* @group Synchronize Methods
|
|
125
125
|
*/
|
|
126
|
-
synchronizeRecoverOrphans<Schema extends JSONSchema>(...args: Parameters<typeof Graffiti.prototype.recoverOrphans<Schema>>):
|
|
126
|
+
synchronizeRecoverOrphans<Schema extends JSONSchema>(...args: Parameters<typeof Graffiti.prototype.recoverOrphans<Schema>>): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>>;
|
|
127
127
|
/**
|
|
128
128
|
* Streams changes made to *any* object in *any* channel
|
|
129
129
|
* and made by *any* user. You may want to use it in conjuction with
|
|
@@ -134,22 +134,16 @@ export declare class GraffitiSynchronize extends Graffiti {
|
|
|
134
134
|
* Be careful using this method. Without additional filters it can
|
|
135
135
|
* expose the user to content out of context.
|
|
136
136
|
*/
|
|
137
|
-
synchronizeAll<Schema extends JSONSchema>(schema
|
|
138
|
-
protected synchronizeDispatch(oldObject:
|
|
137
|
+
synchronizeAll<Schema extends JSONSchema>(schema: Schema, session?: GraffitiSession | null): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>>;
|
|
138
|
+
protected synchronizeDispatch(oldObject: GraffitiObjectStreamContinueEntry<{}>, newObject?: GraffitiObjectStreamContinueEntry<{}>, waitForListeners?: boolean): Promise<void>;
|
|
139
139
|
get: Graffiti["get"];
|
|
140
140
|
put: Graffiti["put"];
|
|
141
141
|
patch: Graffiti["patch"];
|
|
142
142
|
delete: Graffiti["delete"];
|
|
143
|
-
protected
|
|
144
|
-
|
|
145
|
-
origin: string;
|
|
146
|
-
} | {
|
|
147
|
-
error?: undefined;
|
|
148
|
-
value: GraffitiObject<Schema>;
|
|
149
|
-
}, {
|
|
150
|
-
tombstoneRetention: number;
|
|
151
|
-
}, unknown>;
|
|
143
|
+
protected objectStreamContinue<Schema extends JSONSchema>(iterator: GraffitiObjectStreamContinue<Schema>): GraffitiObjectStreamContinue<Schema>;
|
|
144
|
+
protected objectStream<Schema extends JSONSchema>(iterator: GraffitiObjectStream<Schema>): GraffitiObjectStream<Schema>;
|
|
152
145
|
discover: Graffiti["discover"];
|
|
153
146
|
recoverOrphans: Graffiti["recoverOrphans"];
|
|
147
|
+
continueObjectStream: Graffiti["continueObjectStream"];
|
|
154
148
|
}
|
|
155
149
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,KAAK,EACV,eAAe,EACf,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|