@graffiti-garden/wrapper-synchronize 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -2,9 +2,10 @@ import type Ajv from "ajv";
2
2
  import { Graffiti } from "@graffiti-garden/api";
3
3
  import type {
4
4
  GraffitiSession,
5
- GraffitiObject,
6
5
  JSONSchema,
7
- GraffitiStream,
6
+ GraffitiObjectStream,
7
+ GraffitiObjectStreamContinueEntry,
8
+ GraffitiObjectStreamContinue,
8
9
  } from "@graffiti-garden/api";
9
10
  import type { GraffitiObjectBase } from "@graffiti-garden/api";
10
11
  import { Repeater } from "@repeaterjs/repeater";
@@ -14,13 +15,13 @@ import {
14
15
  compileGraffitiObjectSchema,
15
16
  isActorAllowedGraffitiObject,
16
17
  maskGraffitiObject,
17
- unpackLocationOrUri,
18
+ unpackObjectUrl,
18
19
  } from "@graffiti-garden/implementation-local/utilities";
19
20
  export type * from "@graffiti-garden/api";
20
21
 
21
22
  export type GraffitiSynchronizeCallback = (
22
- oldObject: GraffitiObjectBase,
23
- newObject?: GraffitiObjectBase,
23
+ oldObject: GraffitiObjectStreamContinueEntry<{}>,
24
+ newObject?: GraffitiObjectStreamContinueEntry<{}>,
24
25
  ) => void;
25
26
 
26
27
  export interface GraffitiSynchronizeOptions {
@@ -138,8 +139,10 @@ export class GraffitiSynchronize extends Graffiti {
138
139
  channels: string[],
139
140
  schema: Schema,
140
141
  session?: GraffitiSession | null,
141
- ) {
142
- const repeater: GraffitiStream<GraffitiObject<Schema>> = new Repeater(
142
+ ): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>> {
143
+ const seenUrls = new Set<string>();
144
+
145
+ const repeater = new Repeater<GraffitiObjectStreamContinueEntry<Schema>>(
143
146
  async (push, stop) => {
144
147
  const validate = compileGraffitiObjectSchema(await this.ajv, schema);
145
148
  const callback: GraffitiSynchronizeCallback = (
@@ -147,18 +150,23 @@ export class GraffitiSynchronize extends Graffiti {
147
150
  newObjectRaw,
148
151
  ) => {
149
152
  for (const objectRaw of [newObjectRaw, oldObjectRaw]) {
150
- if (
153
+ if (objectRaw?.tombstone) {
154
+ if (seenUrls.has(objectRaw.object.url)) {
155
+ push(objectRaw);
156
+ }
157
+ } else if (
151
158
  objectRaw &&
152
- matchObject(objectRaw) &&
159
+ matchObject(objectRaw.object) &&
153
160
  (this.options.omniscient ||
154
- isActorAllowedGraffitiObject(objectRaw, session))
161
+ isActorAllowedGraffitiObject(objectRaw.object, session))
155
162
  ) {
156
- const object = { ...objectRaw };
163
+ const object = { ...objectRaw.object };
157
164
  if (!this.options.omniscient) {
158
165
  maskGraffitiObject(object, channels, session);
159
166
  }
160
167
  if (validate(object)) {
161
- push({ value: object });
168
+ push({ object });
169
+ seenUrls.add(object.url);
162
170
  break;
163
171
  }
164
172
  }
@@ -189,7 +197,7 @@ export class GraffitiSynchronize extends Graffiti {
189
197
  */
190
198
  synchronizeDiscover<Schema extends JSONSchema>(
191
199
  ...args: Parameters<typeof Graffiti.prototype.discover<Schema>>
192
- ): GraffitiStream<GraffitiObject<Schema>> {
200
+ ): GraffitiObjectStreamContinue<Schema> {
193
201
  const [channels, schema, session] = args;
194
202
  function matchObject(object: GraffitiObjectBase) {
195
203
  return object.channels.some((channel) => channels.includes(channel));
@@ -210,11 +218,11 @@ export class GraffitiSynchronize extends Graffiti {
210
218
  */
211
219
  synchronizeGet<Schema extends JSONSchema>(
212
220
  ...args: Parameters<typeof Graffiti.prototype.get<Schema>>
213
- ): GraffitiStream<GraffitiObject<Schema>> {
214
- const [locationOrUri, schema, session] = args;
215
- const uri = unpackLocationOrUri(locationOrUri);
221
+ ): GraffitiObjectStreamContinue<Schema> {
222
+ const [objectUrl, schema, session] = args;
223
+ const url = unpackObjectUrl(objectUrl);
216
224
  function matchObject(object: GraffitiObjectBase) {
217
- return object.uri === uri;
225
+ return object.url === url;
218
226
  }
219
227
  return this.synchronize<Schema>(matchObject, [], schema, session);
220
228
  }
@@ -234,7 +242,7 @@ export class GraffitiSynchronize extends Graffiti {
234
242
  */
235
243
  synchronizeRecoverOrphans<Schema extends JSONSchema>(
236
244
  ...args: Parameters<typeof Graffiti.prototype.recoverOrphans<Schema>>
237
- ): GraffitiStream<GraffitiObject<Schema>> {
245
+ ): GraffitiObjectStreamContinue<Schema> {
238
246
  const [schema, session] = args;
239
247
  function matchObject(object: GraffitiObjectBase) {
240
248
  return object.actor === session.actor && object.channels.length === 0;
@@ -253,15 +261,15 @@ export class GraffitiSynchronize extends Graffiti {
253
261
  * expose the user to content out of context.
254
262
  */
255
263
  synchronizeAll<Schema extends JSONSchema>(
256
- schema?: Schema,
264
+ schema: Schema,
257
265
  session?: GraffitiSession | null,
258
- ): GraffitiStream<GraffitiObjectBase> {
259
- return this.synchronize(() => true, [], schema ?? {}, session);
266
+ ): GraffitiObjectStreamContinue<Schema> {
267
+ return this.synchronize<Schema>(() => true, [], schema, session);
260
268
  }
261
269
 
262
270
  protected async synchronizeDispatch(
263
- oldObject: GraffitiObjectBase,
264
- newObject?: GraffitiObjectBase,
271
+ oldObject: GraffitiObjectStreamContinueEntry<{}>,
272
+ newObject?: GraffitiObjectStreamContinueEntry<{}>,
265
273
  waitForListeners = false,
266
274
  ) {
267
275
  for (const callback of this.callbacks) {
@@ -294,7 +302,7 @@ export class GraffitiSynchronize extends Graffiti {
294
302
 
295
303
  get: Graffiti["get"] = async (...args) => {
296
304
  const object = await this.graffiti.get(...args);
297
- this.synchronizeDispatch(object);
305
+ this.synchronizeDispatch({ object });
298
306
  return object;
299
307
  };
300
308
 
@@ -306,45 +314,88 @@ export class GraffitiSynchronize extends Graffiti {
306
314
  value: partialObject.value,
307
315
  channels: partialObject.channels,
308
316
  allowed: partialObject.allowed,
309
- tombstone: false,
310
317
  };
311
- await this.synchronizeDispatch(oldObject, newObject, true);
318
+ await this.synchronizeDispatch(
319
+ {
320
+ tombstone: true,
321
+ object: oldObject,
322
+ },
323
+ {
324
+ object: newObject,
325
+ },
326
+ true,
327
+ );
312
328
  return oldObject;
313
329
  };
314
330
 
315
331
  patch: Graffiti["patch"] = async (...args) => {
316
332
  const oldObject = await this.graffiti.patch(...args);
317
333
  const newObject: GraffitiObjectBase = { ...oldObject };
318
- newObject.tombstone = false;
319
334
  for (const prop of ["value", "channels", "allowed"] as const) {
320
335
  applyGraffitiPatch(await this.applyPatch, prop, args[0], newObject);
321
336
  }
322
- await this.synchronizeDispatch(oldObject, newObject, true);
337
+ await this.synchronizeDispatch(
338
+ {
339
+ tombstone: true,
340
+ object: oldObject,
341
+ },
342
+ {
343
+ object: newObject,
344
+ },
345
+ true,
346
+ );
323
347
  return oldObject;
324
348
  };
325
349
 
326
350
  delete: Graffiti["delete"] = async (...args) => {
327
351
  const oldObject = await this.graffiti.delete(...args);
328
- await this.synchronizeDispatch(oldObject, undefined, true);
352
+ await this.synchronizeDispatch(
353
+ {
354
+ tombstone: true,
355
+ object: oldObject,
356
+ },
357
+ undefined,
358
+ true,
359
+ );
329
360
  return oldObject;
330
361
  };
331
362
 
332
- protected objectStream<Schema extends JSONSchema>(
333
- iterator: ReturnType<typeof Graffiti.prototype.discover<Schema>>,
334
- ) {
335
- const dispatch = this.synchronizeDispatch.bind(this);
336
- const wrapper = async function* () {
337
- let result = await iterator.next();
338
- while (!result.done) {
363
+ protected objectStreamContinue<Schema extends JSONSchema>(
364
+ iterator: GraffitiObjectStreamContinue<Schema>,
365
+ ): GraffitiObjectStreamContinue<Schema> {
366
+ const this_ = this;
367
+ return (async function* () {
368
+ while (true) {
369
+ const result = await iterator.next();
370
+ if (result.done) {
371
+ const { continue: continue_, cursor } = result.value;
372
+ return {
373
+ continue: () => this_.objectStreamContinue<Schema>(continue_()),
374
+ cursor,
375
+ };
376
+ }
339
377
  if (!result.value.error) {
340
- dispatch(result.value.value);
378
+ this_.synchronizeDispatch(
379
+ result.value as GraffitiObjectStreamContinueEntry<{}>,
380
+ );
341
381
  }
342
382
  yield result.value;
343
- result = await iterator.next();
344
383
  }
345
- return result.value;
346
- };
347
- return wrapper();
384
+ })();
385
+ }
386
+
387
+ protected objectStream<Schema extends JSONSchema>(
388
+ iterator: GraffitiObjectStream<Schema>,
389
+ ): GraffitiObjectStream<Schema> {
390
+ const wrapped = this.objectStreamContinue<Schema>(iterator);
391
+ return (async function* () {
392
+ // Filter out the tombstones for type safety
393
+ while (true) {
394
+ const result = await wrapped.next();
395
+ if (result.done) return result.value;
396
+ if (result.value.error || !result.value.tombstone) yield result.value;
397
+ }
398
+ })();
348
399
  }
349
400
 
350
401
  discover: Graffiti["discover"] = (...args) => {
@@ -356,4 +407,9 @@ export class GraffitiSynchronize extends Graffiti {
356
407
  const iterator = this.graffiti.recoverOrphans(...args);
357
408
  return this.objectStream<(typeof args)[0]>(iterator);
358
409
  };
410
+
411
+ continueObjectStream: Graffiti["continueObjectStream"] = (...args) => {
412
+ // TODO!!
413
+ return this.graffiti.continueObjectStream(...args);
414
+ };
359
415
  }