@graffiti-garden/api 0.1.11 → 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/src/1-api.d.ts +147 -74
- package/dist/src/1-api.d.ts.map +1 -1
- package/dist/src/2-types.d.ts +8 -5
- package/dist/src/2-types.d.ts.map +1 -1
- package/dist/tests/index.js +1 -1
- package/dist/tests/src/channel-stats.d.ts +3 -0
- package/dist/tests/src/channel-stats.d.ts.map +1 -0
- package/dist/tests/src/crud.d.ts.map +1 -1
- package/dist/tests/src/index.d.ts +2 -0
- package/dist/tests/src/index.d.ts.map +1 -1
- package/dist/tests/src/list.d.ts +3 -0
- package/dist/tests/src/list.d.ts.map +1 -0
- package/dist/tests/src/orphans.d.ts +3 -0
- package/dist/tests/src/orphans.d.ts.map +1 -0
- package/dist/tests/src/synchronize.d.ts.map +1 -1
- package/dist/tests/src/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/1-api.ts +157 -78
- package/src/2-types.ts +15 -8
- package/tests/src/channel-stats.ts +121 -0
- package/tests/src/crud.ts +41 -9
- package/tests/src/index.ts +2 -0
- package/tests/src/orphans.ts +70 -0
- package/tests/src/synchronize.ts +107 -13
- package/tests/src/utils.ts +4 -1
package/dist/src/1-api.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { JSONSchema4 } from "json-schema";
|
|
|
14
14
|
*
|
|
15
15
|
* There are several different implementations of this Graffiti API available,
|
|
16
16
|
* including a [federated implementation](https://github.com/graffiti-garden/implementation-federated),
|
|
17
|
+
* that lets users choose where their data is stored,
|
|
17
18
|
* and a [local implementation](https://github.com/graffiti-garden/implementation-local)
|
|
18
19
|
* that can be used for testing and development. In our design of Graffiti, this API is our
|
|
19
20
|
* primary focus as it is the layer that shapes the experience
|
|
@@ -27,22 +28,28 @@ import type { JSONSchema4 } from "json-schema";
|
|
|
27
28
|
*
|
|
28
29
|
* ## Overview
|
|
29
30
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* (e.g. likes, follows) can be interacted with through standard CRUD operations:
|
|
31
|
+
* Graffiti provides applications with methods to create and store data
|
|
32
|
+
* on behalf of their users using standard CRUD operations:
|
|
33
33
|
* {@link put}, {@link get}, {@link patch}, and {@link delete}.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* For interoperability between Graffiti applications, we recommend using established properties from the
|
|
37
|
-
* [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/) when available.
|
|
34
|
+
* This data can represent both social artifacts (e.g. posts, profiles) and
|
|
35
|
+
* activities (e.g. likes, follows) and is stored as JSON.
|
|
38
36
|
*
|
|
39
|
-
* The social aspect of Graffiti comes from the {@link discover}
|
|
37
|
+
* The social aspect of Graffiti comes from the {@link discover} method
|
|
40
38
|
* which allows applications to find objects that other users made.
|
|
41
39
|
* It is a lot like a traditional query operation, but it only
|
|
42
40
|
* returns objects that have been placed in particular
|
|
43
41
|
* {@link GraffitiObjectBase.channels | `channels`}
|
|
44
42
|
* specified by the discovering application.
|
|
45
43
|
*
|
|
44
|
+
* Graffiti builds on well known concepts and standards wherever possible.
|
|
45
|
+
* JSON Objects can be typed with [JSON Schema](https://json-schema.org/) and patches
|
|
46
|
+
* can be applied with [JSON Patch](https://jsonpatch.com).
|
|
47
|
+
* For interoperability between Graffiti applications, we recommend that
|
|
48
|
+
* objects use established properties from the
|
|
49
|
+
* [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/) when available,
|
|
50
|
+
* however it is always possible to create additional properties, contributing
|
|
51
|
+
* to the broader [folksonomy](https://en.wikipedia.org/wiki/Folksonomy).
|
|
52
|
+
*
|
|
46
53
|
* {@link GraffitiObjectBase.channels | `channels`} are one of the major concepts
|
|
47
54
|
* unique to Graffiti along with *interaction relativity*.
|
|
48
55
|
* Channels create boundaries between public spaces and work to prevent
|
|
@@ -222,19 +229,17 @@ import type { JSONSchema4 } from "json-schema";
|
|
|
222
229
|
*
|
|
223
230
|
* ## TODO
|
|
224
231
|
*
|
|
225
|
-
* - Test for listChannels and listOrphans,
|
|
226
232
|
* - Implement scope.
|
|
227
233
|
*
|
|
228
234
|
* @groupDescription CRUD Methods
|
|
229
235
|
* Methods for {@link put | creating}, {@link get | reading}, {@link patch | updating},
|
|
230
236
|
* and {@link delete | deleting} {@link GraffitiObjectBase | Graffiti objects}.
|
|
231
237
|
* @groupDescription Query Methods
|
|
232
|
-
* Methods
|
|
238
|
+
* Methods that retrieve or accumulate information about multiple {@link GraffitiObjectBase | Graffiti objects} at a time.
|
|
233
239
|
* @groupDescription Session Management
|
|
234
240
|
* Methods and properties for logging in and out of a Graffiti implementation.
|
|
235
241
|
* @groupDescription Utilities
|
|
236
|
-
* Methods for for converting Graffiti objects to and from URIs
|
|
237
|
-
* and for finding lost objects.
|
|
242
|
+
* Methods for for converting Graffiti objects to and from URIs.
|
|
238
243
|
*/
|
|
239
244
|
export declare abstract class Graffiti {
|
|
240
245
|
/**
|
|
@@ -297,14 +302,26 @@ export declare abstract class Graffiti {
|
|
|
297
302
|
session: GraffitiSession): Promise<GraffitiObjectBase>;
|
|
298
303
|
/**
|
|
299
304
|
* Retrieves an object from a given location.
|
|
300
|
-
* If no object exists at that location or if the retrieving
|
|
301
|
-
* {@link GraffitiObjectBase.actor | `actor`} is not the creator or included in
|
|
302
|
-
* the object's {@link GraffitiObjectBase.allowed | `allowed`} property,
|
|
303
|
-
* a {@link GraffitiErrorNotFound} is thrown.
|
|
304
305
|
*
|
|
305
|
-
* The retrieved object is
|
|
306
|
+
* The retrieved object is type-checked against the provided [JSON schema](https://json-schema.org/)
|
|
306
307
|
* otherwise a {@link GraffitiErrorSchemaMismatch} is thrown.
|
|
307
308
|
*
|
|
309
|
+
* If the object existed but has since been deleted,
|
|
310
|
+
* or the retrieving {@link GraffitiObjectBase.actor | `actor`}
|
|
311
|
+
* was {@link GraffitiObjectBase.allowed | `allowed`} to access
|
|
312
|
+
* the object but now isn't, this method will return the latest
|
|
313
|
+
* version of the object that the {@link GraffitiObjectBase.actor | `actor`}
|
|
314
|
+
* was allowed to access with its {@link GraffitiObjectBase.tombstone | `tombstone`}
|
|
315
|
+
* set to `true`, so long as that version is still cached.
|
|
316
|
+
*
|
|
317
|
+
* Otherwise, if the object never existed, or the
|
|
318
|
+
* retrieving {@link GraffitiObjectBase.actor | `actor`} was never
|
|
319
|
+
* {@link GraffitiObjectBase.allowed | `allowed`} to access it, or if
|
|
320
|
+
* the object was changed long enough ago that its history has been
|
|
321
|
+
* purged from the cache, a {@link GraffitiErrorNotFound} is thrown.
|
|
322
|
+
* The rate at which the cache is purged is implementation dependent.
|
|
323
|
+
* See the `tombstoneReturn` property returned by {@link discover}.
|
|
324
|
+
*
|
|
308
325
|
* @group CRUD Methods
|
|
309
326
|
*/
|
|
310
327
|
abstract get<Schema extends JSONSchema4>(
|
|
@@ -395,7 +412,7 @@ export declare abstract class Graffiti {
|
|
|
395
412
|
* not specified by the `discover` method will not be revealed. This masking happens
|
|
396
413
|
* before the supplied schema is applied.
|
|
397
414
|
*
|
|
398
|
-
* {@link discover} can be used in conjunction with {@link
|
|
415
|
+
* {@link discover} can be used in conjunction with {@link synchronizeDiscover}
|
|
399
416
|
* to provide a responsive and consistent user experience.
|
|
400
417
|
*
|
|
401
418
|
* Since different implementations may fetch data from multiple sources there is
|
|
@@ -460,94 +477,60 @@ export declare abstract class Graffiti {
|
|
|
460
477
|
tombstoneRetention: number;
|
|
461
478
|
}>;
|
|
462
479
|
/**
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
480
|
+
* Discovers objects **not** contained in any
|
|
481
|
+
* {@link GraffitiObjectBase.channels | `channels`}
|
|
482
|
+
* that were created by the querying {@link GraffitiObjectBase.actor | `actor`}
|
|
483
|
+
* and match the given [JSON Schema](https://json-schema.org).
|
|
484
|
+
* Unlike {@link discover}, this method will not return objects created by other users.
|
|
467
485
|
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
486
|
+
* This method is not useful for most applications, but necessary for
|
|
487
|
+
* getting a global view of all a user's Graffiti data or debugging
|
|
488
|
+
* channel usage.
|
|
471
489
|
*
|
|
472
|
-
*
|
|
473
|
-
* displaying that user's posts is using {@link synchronize} to listen for changes,
|
|
474
|
-
* then the user's new post will instantly appear in their feed, giving the UI a
|
|
475
|
-
* responsive feel.
|
|
476
|
-
*
|
|
477
|
-
* Example 2: Suppose one of a user's friends changes their name. As soon as the
|
|
478
|
-
* user's application receives one notice of that change (using {@link get}
|
|
479
|
-
* or {@link discover}), then {@link synchronize} listeners can be used to update
|
|
480
|
-
* all instance's of that friend's name in the user's application instantly,
|
|
481
|
-
* providing a consistent user experience.
|
|
490
|
+
* It's return value is the same as {@link discover}.
|
|
482
491
|
*
|
|
483
492
|
* @group Query Methods
|
|
484
493
|
*/
|
|
485
|
-
abstract
|
|
486
|
-
/**
|
|
487
|
-
* The {@link GraffitiObjectBase.channels | `channels`} that the objects must be associated with.
|
|
488
|
-
*/
|
|
489
|
-
channels: string[],
|
|
494
|
+
abstract recoverOrphans<Schema extends JSONSchema4>(
|
|
490
495
|
/**
|
|
491
|
-
* A [JSON Schema](https://json-schema.org) that objects must satisfy.
|
|
496
|
+
* A [JSON Schema](https://json-schema.org) that orphaned objects must satisfy.
|
|
492
497
|
*/
|
|
493
498
|
schema: Schema,
|
|
494
499
|
/**
|
|
495
500
|
* An implementation-specific object with information to authenticate the
|
|
496
|
-
* {@link GraffitiObjectBase.actor | `actor`}.
|
|
497
|
-
* only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}
|
|
498
|
-
* property will be returned.
|
|
501
|
+
* {@link GraffitiObjectBase.actor | `actor`}.
|
|
499
502
|
*/
|
|
500
|
-
session
|
|
503
|
+
session: GraffitiSession): GraffitiStream<GraffitiObject<Schema>, {
|
|
504
|
+
tombstoneRetention: number;
|
|
505
|
+
}>;
|
|
501
506
|
/**
|
|
502
|
-
* Returns
|
|
507
|
+
* Returns statistics about all the {@link GraffitiObjectBase.channels | `channels`}
|
|
503
508
|
* that an {@link GraffitiObjectBase.actor | `actor`} has posted to.
|
|
504
509
|
* This is not very useful for most applications, but
|
|
505
510
|
* necessary for certain applications where a user wants a
|
|
506
511
|
* global view of all their Graffiti data or to debug
|
|
507
512
|
* channel usage.
|
|
508
513
|
*
|
|
509
|
-
* @group
|
|
514
|
+
* @group Query Methods
|
|
510
515
|
*
|
|
511
|
-
* @returns A stream
|
|
516
|
+
* @returns A stream of all {@link GraffitiObjectBase.channels | `channel`}s
|
|
512
517
|
* that the {@link GraffitiObjectBase.actor | `actor`} has posted to.
|
|
513
518
|
* The `lastModified` field is the time that the user last modified an
|
|
514
519
|
* object in that channel. The `count` field is the number of objects
|
|
515
520
|
* that the user has posted to that channel.
|
|
521
|
+
* {@link GraffitiObjectBase.tombstone | `tombstone`}d objects are not included
|
|
522
|
+
* in either the `count` or `lastModified` fields.
|
|
516
523
|
*/
|
|
517
|
-
abstract
|
|
524
|
+
abstract channelStats(
|
|
518
525
|
/**
|
|
519
526
|
* An implementation-specific object with information to authenticate the
|
|
520
527
|
* {@link GraffitiObjectBase.actor | `actor`}.
|
|
521
528
|
*/
|
|
522
529
|
session: GraffitiSession): GraffitiStream<{
|
|
523
530
|
channel: string;
|
|
524
|
-
lastModified: number;
|
|
525
531
|
count: number;
|
|
532
|
+
lastModified: number;
|
|
526
533
|
}>;
|
|
527
|
-
/**
|
|
528
|
-
* Returns a list of all {@link GraffitiObjectBase | objects} a user has posted that are
|
|
529
|
-
* not associated with any {@link GraffitiObjectBase.channels | `channel`}, i.e. orphaned objects.
|
|
530
|
-
* This is not very useful for most applications, but
|
|
531
|
-
* necessary for certain applications where a user wants a
|
|
532
|
-
* global view of all their Graffiti data or to debug
|
|
533
|
-
* channel usage.
|
|
534
|
-
*
|
|
535
|
-
* @group Utilities
|
|
536
|
-
*
|
|
537
|
-
* @returns A stream of the {@link GraffitiObjectBase.name | `name`}
|
|
538
|
-
* and {@link GraffitiObjectBase.source | `source`} of the orphaned objects
|
|
539
|
-
* that the {@link GraffitiObjectBase.actor | `actor`} has posted to.
|
|
540
|
-
* The {@link GraffitiObjectBase.lastModified | lastModified} field is the
|
|
541
|
-
* time that the user last modified the orphan.
|
|
542
|
-
*/
|
|
543
|
-
abstract listOrphans(session: GraffitiSession): GraffitiStream<{
|
|
544
|
-
name: string;
|
|
545
|
-
source: string;
|
|
546
|
-
lastModified: string;
|
|
547
|
-
}>;
|
|
548
|
-
/**
|
|
549
|
-
* The age at which a query for a session will be considered expired.
|
|
550
|
-
*/
|
|
551
534
|
/**
|
|
552
535
|
* Begins the login process. Depending on the implementation, this may
|
|
553
536
|
* involve redirecting the user to a login page or opening a popup,
|
|
@@ -615,6 +598,96 @@ export declare abstract class Graffiti {
|
|
|
615
598
|
* @group Session Management
|
|
616
599
|
*/
|
|
617
600
|
abstract readonly sessionEvents: EventTarget;
|
|
601
|
+
/**
|
|
602
|
+
* This method has the same signature as {@link discover} but listens for
|
|
603
|
+
* changes made via {@link put}, {@link patch}, and {@link delete} or
|
|
604
|
+
* fetched from {@link get}, {@link discover}, and {@link recoverOrphans}
|
|
605
|
+
* and then streams appropriate changes to provide a responsive and
|
|
606
|
+
* consistent user experience.
|
|
607
|
+
*
|
|
608
|
+
* Unlike {@link discover}, this method continuously listens for changes
|
|
609
|
+
* and will not terminate unless the user calls the `return` method on the iterator
|
|
610
|
+
* or `break`s out of the loop.
|
|
611
|
+
*
|
|
612
|
+
* Example 1: Suppose a user publishes a post using {@link put}. If the feed
|
|
613
|
+
* displaying that user's posts is using {@link synchronizeDiscover} to listen for changes,
|
|
614
|
+
* then the user's new post will instantly appear in their feed, giving the UI a
|
|
615
|
+
* responsive feel.
|
|
616
|
+
*
|
|
617
|
+
* Example 2: Suppose one of a user's friends changes their name. As soon as the
|
|
618
|
+
* user's application receives one notice of that change (using {@link get}
|
|
619
|
+
* or {@link discover}), then {@link synchronizeDiscover} listeners can be used to update
|
|
620
|
+
* all instance's of that friend's name in the user's application instantly,
|
|
621
|
+
* providing a consistent user experience.
|
|
622
|
+
*
|
|
623
|
+
* @group Synchronize Methods
|
|
624
|
+
*/
|
|
625
|
+
abstract synchronizeDiscover<Schema extends JSONSchema4>(
|
|
626
|
+
/**
|
|
627
|
+
* The {@link GraffitiObjectBase.channels | `channels`} that the objects must be associated with.
|
|
628
|
+
*/
|
|
629
|
+
channels: string[],
|
|
630
|
+
/**
|
|
631
|
+
* A [JSON Schema](https://json-schema.org) that objects must satisfy.
|
|
632
|
+
*/
|
|
633
|
+
schema: Schema,
|
|
634
|
+
/**
|
|
635
|
+
* An implementation-specific object with information to authenticate the
|
|
636
|
+
* {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,
|
|
637
|
+
* only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}
|
|
638
|
+
* property will be returned.
|
|
639
|
+
*/
|
|
640
|
+
session?: GraffitiSession | null): GraffitiStream<GraffitiObject<Schema>>;
|
|
641
|
+
/**
|
|
642
|
+
* This method has the same signature as {@link get} but, like {@link synchronizeDiscover},
|
|
643
|
+
* it listens for changes made via {@link put}, {@link patch}, and {@link delete} or
|
|
644
|
+
* fetched from {@link get}, {@link discover}, and {@link recoverOrphans} and then
|
|
645
|
+
* streams appropriate changes to provide a responsive and consistent user experience.
|
|
646
|
+
*
|
|
647
|
+
* Unlike {@link get}, which returns a single result, this method continuously
|
|
648
|
+
* listens for changes which are output as an asynchronous {@link GraffitiStream}.
|
|
649
|
+
*
|
|
650
|
+
* @group Synchronize Methods
|
|
651
|
+
*/
|
|
652
|
+
abstract synchronizeGet<Schema extends JSONSchema4>(
|
|
653
|
+
/**
|
|
654
|
+
* The location of the object to get.
|
|
655
|
+
*/
|
|
656
|
+
locationOrUri: GraffitiLocation | string,
|
|
657
|
+
/**
|
|
658
|
+
* The JSON schema to validate the retrieved object against.
|
|
659
|
+
*/
|
|
660
|
+
schema: Schema,
|
|
661
|
+
/**
|
|
662
|
+
* An implementation-specific object with information to authenticate the
|
|
663
|
+
* {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,
|
|
664
|
+
* the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}
|
|
665
|
+
* property must be `undefined`.
|
|
666
|
+
*/
|
|
667
|
+
session?: GraffitiSession | null): GraffitiStream<GraffitiObject<Schema>>;
|
|
668
|
+
/**
|
|
669
|
+
* This method has the same signature as {@link recoverOrphans} but,
|
|
670
|
+
* like {@link synchronizeDiscover}, it listens for changes made via
|
|
671
|
+
* {@link put}, {@link patch}, and {@link delete} or fetched from
|
|
672
|
+
* {@link get}, {@link discover}, and {@link recoverOrphans} and then
|
|
673
|
+
* streams appropriate changes to provide a responsive and consistent user experience.
|
|
674
|
+
*
|
|
675
|
+
* Unlike {@link recoverOrphans}, this method continuously listens for changes
|
|
676
|
+
* and will not terminate unless the user calls the `return` method on the iterator
|
|
677
|
+
* or `break`s out of the loop.
|
|
678
|
+
*
|
|
679
|
+
* @group Synchronize Methods
|
|
680
|
+
*/
|
|
681
|
+
abstract synchronizeRecoverOrphans<Schema extends JSONSchema4>(
|
|
682
|
+
/**
|
|
683
|
+
* A [JSON Schema](https://json-schema.org) that orphaned objects must satisfy.
|
|
684
|
+
*/
|
|
685
|
+
schema: Schema,
|
|
686
|
+
/**
|
|
687
|
+
* An implementation-specific object with information to authenticate the
|
|
688
|
+
* {@link GraffitiObjectBase.actor | `actor`}.
|
|
689
|
+
*/
|
|
690
|
+
session: GraffitiSession): GraffitiStream<GraffitiObject<Schema>>;
|
|
618
691
|
}
|
|
619
692
|
/**
|
|
620
693
|
* This is a factory function that produces an instance of
|
package/dist/src/1-api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1-api.d.ts","sourceRoot":"","sources":["../../src/1-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,cAAc,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C
|
|
1
|
+
{"version":3,"file":"1-api.d.ts","sourceRoot":"","sources":["../../src/1-api.ts"],"names":[],"mappings":"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"}
|
package/dist/src/2-types.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export interface GraffitiObjectBase {
|
|
|
57
57
|
* the sender should put their object in the channel of the recipient's {@link actor | `actor`} URI to notify them of the message and also add
|
|
58
58
|
* the recipient's {@link actor | `actor`} URI to the `allowed` array to prevent others from seeing the message.
|
|
59
59
|
*/
|
|
60
|
-
allowed?: string[];
|
|
60
|
+
allowed?: string[] | null;
|
|
61
61
|
/**
|
|
62
62
|
* The URI of the `actor` that {@link Graffiti.put | created } the object. This `actor` also has the unique permission to
|
|
63
63
|
* {@link Graffiti.patch | modify} or {@link Graffiti.delete | delete} the object.
|
|
@@ -221,7 +221,7 @@ export interface GraffitiPatch {
|
|
|
221
221
|
/**
|
|
222
222
|
* This type represents a stream of data that are
|
|
223
223
|
* returned by Graffiti's query-like operations such as
|
|
224
|
-
* {@link Graffiti.discover} and {@link Graffiti.
|
|
224
|
+
* {@link Graffiti.discover} and {@link Graffiti.recoverOrphans}.
|
|
225
225
|
*
|
|
226
226
|
* Errors are returned within the stream rather than as
|
|
227
227
|
* exceptions that would halt the entire stream. This is because
|
|
@@ -273,12 +273,15 @@ export type GraffitiLogoutEvent = CustomEvent<{
|
|
|
273
273
|
* and restore any previously active sessions.
|
|
274
274
|
* Successful session restores will be returned in parallel as
|
|
275
275
|
* their own {@link GraffitiLoginEvent} events.
|
|
276
|
-
*
|
|
277
|
-
*
|
|
276
|
+
*
|
|
277
|
+
* This event optionally returns an `href` property
|
|
278
|
+
* representing the URL the user originated a login request
|
|
279
|
+
* from, which may be useful for redirecting the user back to
|
|
280
|
+
* the page they were on after login.
|
|
278
281
|
* The event name to listen for is `initialized`.
|
|
279
282
|
*/
|
|
280
283
|
export type GraffitiSessionInitializedEvent = CustomEvent<{
|
|
281
284
|
error?: Error;
|
|
282
285
|
href?: string;
|
|
283
|
-
}>;
|
|
286
|
+
} | null | undefined>;
|
|
284
287
|
//# sourceMappingURL=2-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"2-types.d.ts","sourceRoot":"","sources":["../../src/2-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,IAAI,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,KAAK,EAAE,EAAE,CAAC;IAEV;;;;;;;;;;;;OAYG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IAEnB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"2-types.d.ts","sourceRoot":"","sources":["../../src/2-types.ts"],"names":[],"mappings":"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"}
|
package/dist/tests/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{assert as e,describe as a,it as t,expect as o}from"vitest";import{GraffitiErrorInvalidUri as n,GraffitiErrorNotFound as l,GraffitiErrorForbidden as s,GraffitiErrorInvalidSchema as i,GraffitiErrorSchemaMismatch as r,GraffitiErrorPatchTestFailed as c,GraffitiErrorPatchError as u}from"@graffiti-garden/api";function d(){const e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e).map((e=>e.toString(16).padStart(2,"0"))).join("")}function v(){return{value:{[d()]:d()},channels:[d(),d()]}}async function h(a){const t=await a.next();return e(!t.done&&!t.value.error,"result has no value"),t.value.value}const w=e=>{a("URI and location conversion",(()=>{t("location to uri and back",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()},n=a.locationToUri(t),l=a.uriToLocation(n);o(t).toEqual(l)})),t("collision resistance",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()};for(const e of["name","actor","source"]){const n={...t,[e]:d()},l=a.locationToUri(t),s=a.locationToUri(n);o(l).not.toEqual(s)}})),t("random URI should not be a valid location",(async()=>{const a=e();o((()=>a.uriToLocation(""))).toThrow(n)}))}))},p=(e,n,h)=>{a("CRUD",{timeout:2e4},(()=>{t("put, get, delete",(async()=>{const a=e(),t=n(),s={something:"hello, world~ c:"},i=[d(),d()],r=await a.put({value:s,channels:i},t);o(r.value).toEqual({}),o(r.channels).toEqual([]),o(r.allowed).toBeUndefined(),o(r.actor).toEqual(t.actor);const c=await a.get(r,{});o(c.value).toEqual(s),o(c.channels).toEqual([]),o(c.allowed).toBeUndefined(),o(c.name).toEqual(r.name),o(c.actor).toEqual(r.actor),o(c.source).toEqual(r.source),o(c.lastModified).toEqual(r.lastModified);const u={something:"goodbye, world~ :c"},v=await a.put({...r,value:u,channels:[]},t);o(v.value).toEqual(s),o(v.tombstone).toEqual(!0),o(v.name).toEqual(r.name),o(v.actor).toEqual(r.actor),o(v.source).toEqual(r.source),o(v.lastModified).toBeGreaterThanOrEqual(c.lastModified);const h=await a.get(r,{});o(h.value).toEqual(u),o(h.lastModified).toEqual(v.lastModified),o(h.tombstone).toEqual(!1);const w=await a.delete(h,t);o(w.tombstone).toEqual(!0),o(w.value).toEqual(u),o(w.lastModified).toBeGreaterThanOrEqual(v.lastModified),await o(a.get(h,{})).rejects.toThrow(l)})),t("put, get, delete with wrong actor",(async()=>{const a=e(),t=n(),l=h();await o(a.put({value:{},channels:[],actor:l.actor},t)).rejects.toThrow(s);const i=await a.put({value:{},channels:[]},l);await o(a.delete(i,t)).rejects.toThrow(s),await o(a.patch({},i,t)).rejects.toThrow(s)})),t("put and get with schema",(async()=>{const a=e(),t=n(),l={something:"hello",another:42},s=await a.put({value:l,channels:[]},t),i=await a.get(s,{properties:{value:{properties:{something:{type:"string"},another:{type:"integer"}}}}});o(i.value.something).toEqual(l.something),o(i.value.another).toEqual(l.another)})),t("put and get with invalid schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{},channels:[]},t);await o(a.get(l,{properties:{value:{type:"asdf"}}})).rejects.toThrow(i)})),t("put and get with wrong schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{hello:"world"},channels:[]},t);await o(a.get(l,{properties:{value:{properties:{hello:{type:"number"}}}}})).rejects.toThrow(r)})),t("put and get with empty access control",(async()=>{const a=e(),t=n(),l=h(),s={um:"hi"},i=[d()],r=[d()],c=await a.put({value:s,allowed:i,channels:r},t),u=await a.get(c,{},t);o(u.value).toEqual(s),o(u.allowed).toEqual(i),o(u.channels).toEqual(r),await o(a.get(c,{})).rejects.toThrow(),await o(a.get(c,{},l)).rejects.toThrow()})),t("put and get with specific access control",(async()=>{const a=e(),t=n(),l=h(),s={um:"hi"},i=[d(),l.actor,d()],r=[d()],c=await a.put({value:s,allowed:i,channels:r},t),u=await a.get(c,{},t);o(u.value).toEqual(s),o(u.allowed).toEqual(i),o(u.channels).toEqual(r),await o(a.get(c,{})).rejects.toThrow();const v=await a.get(c,{},l);o(v.value).toEqual(s),o(v.allowed).toEqual([l.actor]),o(v.channels).toEqual([])})),t("patch value",(async()=>{const a=e(),t=n(),l={something:"hello, world~ c:"},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something",value:"goodbye, world~ :c"}]},s,t);o(i.value).toEqual(l),o(i.tombstone).toBe(!0);const r=await a.get(s,{});o(r.value).toEqual({something:"goodbye, world~ :c"}),o(i.lastModified).toBe(r.lastModified),await a.delete(s,t)})),t("deep patch",(async()=>{const a=e(),t=n(),l={something:{another:{somethingElse:"hello"}}},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something/another/somethingElse",value:"goodbye"}]},s,t),r=await a.get(s,{});o(i.value).toEqual(l),o(r.value).toEqual({something:{another:{somethingElse:"goodbye"}}})})),t("patch channels",(async()=>{const a=e(),t=n(),l=[d()],s=[d()],i=await a.put({value:{},channels:l},t),r={channels:[{op:"replace",path:"/0",value:s[0]}]},c=await a.patch(r,i,t);o(c.channels).toEqual(l);const u=await a.get(i,{},t);o(u.channels).toEqual(s),await a.delete(i,t)})),t("patch 'increment' with test",(async()=>{const a=e(),t=n(),l=await a.put({value:{counter:1},channels:[]},t),s=await a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:2}]},l,t);o(s.value).toEqual({counter:1});const i=await a.get(s,{properties:{value:{properties:{counter:{type:"integer"}}}}});o(i.value.counter).toEqual(2),await o(a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:3}]},l,t)).rejects.toThrow(c)})),t("invalid patch",(async()=>{const a=e(),t=n(),l=v(),s=await a.put(l,t);await o(a.patch({value:[{op:"add",path:"/root",value:[]},{op:"add",path:"/root/2",value:2}]},s,t)).rejects.toThrow(u)})),t("patch channels to be wrong",(async()=>{const a=e(),t=n(),l=v();l.allowed=[d()];const s=await a.put(l,t),i=[{channels:[{op:"replace",path:"",value:null}]},{channels:[{op:"replace",path:"",value:{}}]},{channels:[{op:"replace",path:"",value:["hello",["hi"]]}]},{channels:[{op:"add",path:"/0",value:1}]},{value:[{op:"replace",path:"",value:"not an object"}]},{value:[{op:"replace",path:"",value:null}]},{value:[{op:"replace",path:"",value:[]}]},{allowed:[{op:"replace",path:"",value:{}}]},{allowed:[{op:"replace",path:"",value:["hello",["hi"]]}]}];for(const e of i)await o(a.patch(e,s,t)).rejects.toThrow(u);const r=await a.get(s,{},t);o(r.value).toEqual(l.value),o(r.channels).toEqual(l.channels),o(r.allowed).toEqual(l.allowed),o(r.lastModified).toEqual(s.lastModified)}))}))},m=(e,n,l)=>{a("synchronize",(()=>{t("get",(async()=>{const a=e(),t=n(),l=v(),s=l.channels.slice(1),i=await a.put(l,t),r=e(),c=r.synchronize(s,{}).next(),u=await r.get(i,{},t),d=(await c).value;if(!d||d.error)throw new Error("Error in synchronize");o(d.value.value).toEqual(l.value),o(d.value.channels).toEqual(s),o(d.value.tombstone).toBe(!1),o(d.value.lastModified).toEqual(u.lastModified)})),t("put",(async()=>{const a=e(),t=n(),l=d(),s=d(),i=d(),r={hello:"world"},c=[l,i],u=await a.put({value:r,channels:c},t),v=a.synchronize([l],{}).next(),h=a.synchronize([s],{}).next(),w=a.synchronize([i],{}).next(),p={goodbye:"world"},m=[s,i];await a.put({...u,value:p,channels:m},t);const E=(await v).value,q=(await h).value,y=(await w).value;if(!E||E.error||!q||q.error||!y||y.error)throw new Error("Error in synchronize");o(E.value.value).toEqual(r),o(E.value.channels).toEqual([l]),o(E.value.tombstone).toBe(!0),o(q.value.value).toEqual(p),o(q.value.channels).toEqual([s]),o(q.value.tombstone).toBe(!1),o(y.value.value).toEqual(p),o(y.value.channels).toEqual([i]),o(y.value.tombstone).toBe(!1),o(E.value.lastModified).toEqual(q.value.lastModified),o(y.value.lastModified).toEqual(q.value.lastModified)})),t("patch",(async()=>{const a=e(),t=n(),l=d(),s=d(),i=d(),r={hello:"world"},c=[l,i],u=await a.put({value:r,channels:c},t),v=a.synchronize([l],{}).next(),h=a.synchronize([s],{}).next(),w=a.synchronize([i],{}).next();await a.patch({value:[{op:"add",path:"/something",value:"new value"}],channels:[{op:"add",path:"/-",value:s},{op:"remove",path:`/${c.indexOf(l)}`}]},u,t);const p=(await v).value,m=(await h).value,E=(await w).value;if(!p||p.error||!m||m.error||!E||E.error)throw new Error("Error in synchronize");const q={...r,something:"new value"};o(p.value.value).toEqual(r),o(p.value.channels).toEqual([l]),o(p.value.tombstone).toBe(!0),o(m.value.value).toEqual(q),o(m.value.channels).toEqual([s]),o(m.value.tombstone).toBe(!1),o(E.value.value).toEqual(q),o(E.value.channels).toEqual([i]),o(E.value.tombstone).toBe(!1),o(p.value.lastModified).toEqual(m.value.lastModified),o(E.value.lastModified).toEqual(m.value.lastModified)})),t("delete",(async()=>{const a=e(),t=n(),l=[d(),d(),d()],s={hello:"world"},i=[d(),...l.slice(1)],r=await a.put({value:s,channels:i},t),c=a.synchronize(l,{}).next();a.delete(r,t);const u=(await c).value;if(!u||u.error)throw new Error("Error in synchronize");o(u.value.tombstone).toBe(!0),o(u.value.value).toEqual(s),o(u.value.channels).toEqual(l.filter((e=>i.includes(e))))})),t("not allowed",(async()=>{const a=e(),t=n(),s=l(),i=[d(),d(),d()],r=i.slice(1),c=a.synchronize(r,{},t).next(),u=a.synchronize(r,{},s).next(),v=a.synchronize(r,{}).next(),h={hello:"world"},w=[d(),s.actor];await a.put({value:h,channels:i,allowed:w},t),await o(Promise.race([v,new Promise(((e,a)=>setTimeout(a,100,"Timeout")))])).rejects.toThrow("Timeout");const p=(await c).value,m=(await u).value;if(!p||p.error||!m||m.error)throw new Error("Error in synchronize");o(p.value.value).toEqual(h),o(p.value.allowed).toEqual(w),o(p.value.channels).toEqual(i),o(m.value.value).toEqual(h),o(m.value.allowed).toEqual([s.actor]),o(m.value.channels).toEqual(r)}))}))},E=(n,l,s)=>{a("discover",{timeout:2e4},(()=>{t("discover nothing",(async()=>{const e=n().discover([],{});o(await e.next()).toHaveProperty("done",!0)})),t("discover single",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=[d(),t.channels[0]],r=e.discover(i,{}),c=await h(r);o(c.value).toEqual(t.value),o(c.channels).toEqual([t.channels[0]]),o(c.allowed).toBeUndefined(),o(c.actor).toEqual(a.actor),o(c.tombstone).toBe(!1),o(c.lastModified).toEqual(s.lastModified);const u=await r.next();o(u.done).toBe(!0)})),t("discover wrong channel",(async()=>{const e=n(),a=l(),t=v();await e.put(t,a);const s=e.discover([d()],{});await o(s.next()).resolves.toHaveProperty("done",!0)})),t("discover not allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),d()];const r=await e.put(i,a),c=e.discover(i.channels,{},a),u=await h(c);o(u.value).toEqual(i.value),o(u.channels).toEqual(i.channels),o(u.allowed).toEqual(i.allowed),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(r.lastModified);const w=e.discover(i.channels,{},t);o(await w.next()).toHaveProperty("done",!0);const p=e.discover(i.channels,{});o(await p.next()).toHaveProperty("done",!0)})),t("discover allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()];const r=await e.put(i,a),c=e.discover(i.channels,{},t),u=await h(c);o(u.value).toEqual(i.value),o(u.allowed).toEqual([t.actor]),o(u.channels).toEqual(i.channels),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(r.lastModified)}));for(const e of["name","actor","lastModified"])t(`discover for ${e}`,(async()=>{const a=n(),t=l(),i=s(),r=v(),c=await a.put(r,t),u=v();u.channels=r.channels,await new Promise((e=>setTimeout(e,20)));const d=await a.put(u,i),w=a.discover(r.channels,{properties:{[e]:{enum:[c[e]]}}}),p=await h(w);o(p.name).toEqual(c.name),o(p.name).not.toEqual(d.name),o(p.value).toEqual(r.value),await o(w.next()).resolves.toHaveProperty("done",!0)}));t("discover with lastModified range",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await new Promise((e=>setTimeout(e,20)));const i=await e.put(t,a);o(s.name).not.toEqual(i.name),o(s.lastModified).toBeLessThan(i.lastModified);const r=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified,exclusiveMinimum:!0}}});o(await r.next()).toHaveProperty("done",!0);const c=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified-.1,exclusiveMinimum:!0}}}),u=await h(c);o(u.name).toEqual(i.name),o(await c.next()).toHaveProperty("done",!0);const d=e.discover(t.channels,{properties:{value:{},lastModified:{minimum:i.lastModified}}}),w=await h(d);o(w.name).toEqual(i.name),o(await d.next()).toHaveProperty("done",!0);const p=e.discover(t.channels,{properties:{lastModified:{minimum:i.lastModified+.1}}});o(await p.next()).toHaveProperty("done",!0);const m=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified,exclusiveMaximum:!0}}});o(await m.next()).toHaveProperty("done",!0);const E=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified+.1,exclusiveMaximum:!0}}}),q=await h(E);o(q.name).toEqual(s.name),o(await E.next()).toHaveProperty("done",!0);const y=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified}}}),f=await h(y);o(f.name).toEqual(s.name),o(await y.next()).toHaveProperty("done",!0);const g=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified-.1}}});o(await g.next()).toHaveProperty("done",!0)})),t("discover schema allowed, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()],await e.put(i,a);const r=e.discover(i.channels,{properties:{allowed:{minItems:3,not:{items:{not:{enum:[t.actor]}}}}}},a),c=await h(r);o(c.value).toEqual(i.value),await o(r.next()).resolves.toHaveProperty("done",!0);const u=e.discover(i.channels,{properties:{allowed:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover(i.channels,{properties:{allowed:{not:{items:{not:{enum:[i.channels[0]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover(i.channels,{properties:{allowed:{maxItems:1,not:{items:{not:{enum:[t.actor]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover schema channels, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.channels=[d(),d(),d()],await e.put(i,a);const r=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3,not:{items:{not:{enum:[i.channels[1]]}}}}}},a),c=await h(r);o(c.value).toEqual(i.value),await o(r.next()).resolves.toHaveProperty("done",!0);const u=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{not:{items:{not:{enum:[i.channels[1]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover([i.channels[0],i.channels[2]],{properties:{allowed:{maxItems:2,not:{items:{not:{enum:[i.channels[2]]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover query for empty allowed",(async()=>{const e=n(),a=l(),t=v(),s={not:{required:["allowed"]}};await e.put(t,a);const i=e.discover(t.channels,s,a),r=await h(i);o(r.value).toEqual(t.value),o(r.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0);const c=v();c.allowed=[],await e.put(c,a);const u=e.discover(c.channels,s,a);await o(u.next()).resolves.toHaveProperty("done",!0)})),t("discover query for values",(async()=>{const a=n(),t=l(),s=v();s.value={test:d()},await a.put(s,t);const i=v();i.channels=s.channels,i.value={test:d(),something:d()},await a.put(i,t);const r=v();r.channels=s.channels,r.value={other:d(),something:d()},await a.put(r,t);const c=new Map;for(const t of["test","something","other"]){let o=0;for await(const n of a.discover(s.channels,{properties:{value:{required:[t]}}}))e(!n.error,"result has error"),t in n.value.value&&o++;c.set(t,o)}o(c.get("test")).toBe(2),o(c.get("something")).toBe(2),o(c.get("other")).toBe(1)})),t("discover for deleted content",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=await e.delete(s,a),r=e.discover(t.channels,{}),c=await h(r);o(c.tombstone).toBe(!0),o(c.value).toEqual(t.value),o(c.channels).toEqual(t.channels),o(c.actor).toEqual(a.actor),o(c.lastModified).toEqual(i.lastModified),await o(r.next()).resolves.toHaveProperty("done",!0)})),t("discover for replaced channels",(async()=>{for(let e=0;e<10;e++){const e=n(),a=l(),t=v(),s=await e.put(t,a),i=v(),r=await e.put({...s,...i},a),c=e.discover(t.channels,{}),u=await h(c);await o(c.next()).resolves.toHaveProperty("done",!0);const d=e.discover(i.channels,{}),w=await h(d);await o(d.next()).resolves.toHaveProperty("done",!0),s.lastModified===r.lastModified?(o(u.tombstone||w.tombstone).toBe(!0),o(u.tombstone&&w.tombstone).toBe(!1)):(o(u.tombstone).toBe(!0),o(u.value).toEqual(t.value),o(u.channels).toEqual(t.channels),o(u.lastModified).toEqual(r.lastModified),o(w.tombstone).toBe(!1),o(w.value).toEqual(i.value),o(w.channels).toEqual(i.channels),o(w.lastModified).toEqual(r.lastModified))}})),t("discover for patched allowed",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await e.patch({allowed:[{op:"add",path:"",value:[]}]},s,a);const i=e.discover(t.channels,{}),r=await h(i);o(r.tombstone).toBe(!0),o(r.value).toEqual(t.value),o(r.channels).toEqual(t.channels),o(r.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0)})),t("put concurrently and discover one",(async()=>{const a=n(),t=l(),s=v();s.name=d();const i=Array(100).fill(0).map((()=>a.put(s,t)));await Promise.all(i);const r=a.discover(s.channels,{});let c=0,u=0;for await(const a of r)e(!a.error,"result has error"),a.value.tombstone?c++:u++;o(c).toBe(99),o(u).toBe(1)}))}))};export{p as graffitiCRUDTests,E as graffitiDiscoverTests,w as graffitiLocationTests,m as graffitiSynchronizeTests};
|
|
1
|
+
import{assert as e,describe as a,it as t,expect as o}from"vitest";import{GraffitiErrorInvalidUri as n,GraffitiErrorNotFound as l,GraffitiErrorForbidden as s,GraffitiErrorInvalidSchema as i,GraffitiErrorSchemaMismatch as c,GraffitiErrorPatchTestFailed as r,GraffitiErrorPatchError as u}from"@graffiti-garden/api";function d(){const e=new Uint8Array(16);crypto.getRandomValues(e);return Array.from(e).map((e=>e.toString(16).padStart(2,"0"))).join("")+"👩🏽❤️💋👩🏻🫱🏼🫲🏿"}function v(){return{value:{[d()]:d()},channels:[d(),d()]}}async function h(a){const t=await a.next();return e(!t.done&&!t.value.error,"result has no value"),t.value.value}const w=e=>{a("URI and location conversion",(()=>{t("location to uri and back",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()},n=a.locationToUri(t),l=a.uriToLocation(n);o(t).toEqual(l)})),t("collision resistance",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()};for(const e of["name","actor","source"]){const n={...t,[e]:d()},l=a.locationToUri(t),s=a.locationToUri(n);o(l).not.toEqual(s)}})),t("random URI should not be a valid location",(async()=>{const a=e();o((()=>a.uriToLocation(""))).toThrow(n)}))}))},p=(e,n,h)=>{a("CRUD",{timeout:2e4},(()=>{t("put, get, delete",(async()=>{const a=e(),t=n(),l={something:"hello, world~ c:"},s=[d(),d()],i=await a.put({value:l,channels:s},t);o(i.value).toEqual({}),o(i.channels).toEqual([]),o(i.allowed).toBeUndefined(),o(i.actor).toEqual(t.actor);const c=await a.get(i,{});o(c.value).toEqual(l),o(c.channels).toEqual([]),o(c.allowed).toBeUndefined(),o(c.name).toEqual(i.name),o(c.actor).toEqual(i.actor),o(c.source).toEqual(i.source),o(c.lastModified).toEqual(i.lastModified);const r={something:"goodbye, world~ :c"},u=await a.put({...i,value:r,channels:[]},t);o(u.value).toEqual(l),o(u.tombstone).toEqual(!0),o(u.name).toEqual(i.name),o(u.actor).toEqual(i.actor),o(u.source).toEqual(i.source),o(u.lastModified).toBeGreaterThan(c.lastModified);const v=await a.get(i,{});o(v.value).toEqual(r),o(v.lastModified).toEqual(u.lastModified),o(v.tombstone).toEqual(!1);const h=await a.delete(v,t);o(h.tombstone).toEqual(!0),o(h.value).toEqual(r),o(h.lastModified).toBeGreaterThan(u.lastModified);const w=await a.get(v,{});o(w).toEqual(h)})),t("get non-existant",(async()=>{const a=e(),t=n(),s=await a.put(v(),t);await o(a.get({...s,name:d()},{})).rejects.toBeInstanceOf(l)})),t("put, get, delete with wrong actor",(async()=>{const a=e(),t=n(),l=h();await o(a.put({value:{},channels:[],actor:l.actor},t)).rejects.toThrow(s);const i=await a.put({value:{},channels:[]},l);await o(a.delete(i,t)).rejects.toThrow(s),await o(a.patch({},i,t)).rejects.toThrow(s)})),t("put and get with schema",(async()=>{const a=e(),t=n(),l={something:"hello",another:42},s=await a.put({value:l,channels:[]},t),i=await a.get(s,{properties:{value:{properties:{something:{type:"string"},another:{type:"integer"}}}}});o(i.value.something).toEqual(l.something),o(i.value.another).toEqual(l.another)})),t("put and get with invalid schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{},channels:[]},t);await o(a.get(l,{properties:{value:{type:"asdf"}}})).rejects.toThrow(i)})),t("put and get with wrong schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{hello:"world"},channels:[]},t);await o(a.get(l,{properties:{value:{properties:{hello:{type:"number"}}}}})).rejects.toThrow(c)})),t("put and get with empty access control",(async()=>{const a=e(),t=n(),s=h(),i={um:"hi"},c=[d()],r=[d()],u=await a.put({value:i,allowed:c,channels:r},t),v=await a.get(u,{},t);o(v.value).toEqual(i),o(v.allowed).toEqual(c),o(v.channels).toEqual(r),await o(a.get(u,{})).rejects.toBeInstanceOf(l),await o(a.get(u,{},s)).rejects.toBeInstanceOf(l)})),t("put and get with specific access control",(async()=>{const a=e(),t=n(),s=h(),i={um:"hi"},c=[d(),s.actor,d()],r=[d()],u=await a.put({value:i,allowed:c,channels:r},t),v=await a.get(u,{},t);o(v.value).toEqual(i),o(v.allowed).toEqual(c),o(v.channels).toEqual(r),await o(a.get(u,{})).rejects.toBeInstanceOf(l);const w=await a.get(u,{},s);o(w.value).toEqual(i),o(w.allowed).toEqual([s.actor]),o(w.channels).toEqual([])})),t("patch value",(async()=>{const a=e(),t=n(),l={something:"hello, world~ c:"},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something",value:"goodbye, world~ :c"}]},s,t);o(i.value).toEqual(l),o(i.tombstone).toBe(!0);const c=await a.get(s,{});o(c.value).toEqual({something:"goodbye, world~ :c"}),o(i.lastModified).toBe(c.lastModified),await a.delete(s,t)})),t("patch deleted object",(async()=>{const a=e(),t=n(),s=await a.put(v(),t);await a.delete(s,t),await o(a.patch({},s,t)).rejects.toBeInstanceOf(l)})),t("deep patch",(async()=>{const a=e(),t=n(),l={something:{another:{somethingElse:"hello"}}},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something/another/somethingElse",value:"goodbye"}]},s,t),c=await a.get(s,{});o(i.value).toEqual(l),o(c.value).toEqual({something:{another:{somethingElse:"goodbye"}}})})),t("patch channels",(async()=>{const a=e(),t=n(),l=[d()],s=[d()],i=await a.put({value:{},channels:l},t),c={channels:[{op:"replace",path:"/0",value:s[0]}]},r=await a.patch(c,i,t);o(r.channels).toEqual(l);const u=await a.get(i,{},t);o(u.channels).toEqual(s),await a.delete(i,t)})),t("patch 'increment' with test",(async()=>{const a=e(),t=n(),l=await a.put({value:{counter:1},channels:[]},t),s=await a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:2}]},l,t);o(s.value).toEqual({counter:1});const i=await a.get(s,{properties:{value:{properties:{counter:{type:"integer"}}}}});o(i.value.counter).toEqual(2),await o(a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:3}]},l,t)).rejects.toThrow(r)})),t("invalid patch",(async()=>{const a=e(),t=n(),l=v(),s=await a.put(l,t);await o(a.patch({value:[{op:"add",path:"/root",value:[]},{op:"add",path:"/root/2",value:2}]},s,t)).rejects.toThrow(u)})),t("patch channels to be wrong",(async()=>{const a=e(),t=n(),l=v();l.allowed=[d()];const s=await a.put(l,t),i=[{channels:[{op:"replace",path:"",value:null}]},{channels:[{op:"replace",path:"",value:{}}]},{channels:[{op:"replace",path:"",value:["hello",["hi"]]}]},{channels:[{op:"add",path:"/0",value:1}]},{value:[{op:"replace",path:"",value:"not an object"}]},{value:[{op:"replace",path:"",value:null}]},{value:[{op:"replace",path:"",value:[]}]},{allowed:[{op:"replace",path:"",value:{}}]},{allowed:[{op:"replace",path:"",value:["hello",["hi"]]}]}];for(const e of i)await o(a.patch(e,s,t)).rejects.toThrow(u);const c=await a.get(s,{},t);o(c.value).toEqual(l.value),o(c.channels).toEqual(l.channels),o(c.allowed).toEqual(l.allowed),o(c.lastModified).toEqual(s.lastModified)}))}))},m=(n,l,s)=>{a("synchronizeDiscover",(()=>{t("get",(async()=>{const e=n(),a=l(),t=v(),s=t.channels.slice(1),i=await e.put(t,a),c=n(),r=c.synchronizeDiscover(s,{}).next(),u=await c.get(i,{},a),d=(await r).value;if(!d||d.error)throw new Error("Error in synchronize");o(d.value.value).toEqual(t.value),o(d.value.channels).toEqual(s),o(d.value.tombstone).toBe(!1),o(d.value.lastModified).toEqual(u.lastModified)})),t("put",(async()=>{const e=n(),a=l(),t=d(),s=d(),i=d(),c={hello:"world"},r=[t,i],u=await e.put({value:c,channels:r},a),v=e.synchronizeDiscover([t],{}).next(),h=e.synchronizeDiscover([s],{}).next(),w=e.synchronizeDiscover([i],{}).next(),p={goodbye:"world"},m=[s,i];await e.put({...u,value:p,channels:m},a);const E=(await v).value,f=(await h).value,q=(await w).value;if(!E||E.error||!f||f.error||!q||q.error)throw new Error("Error in synchronize");o(E.value.value).toEqual(c),o(E.value.channels).toEqual([t]),o(E.value.tombstone).toBe(!0),o(f.value.value).toEqual(p),o(f.value.channels).toEqual([s]),o(f.value.tombstone).toBe(!1),o(q.value.value).toEqual(p),o(q.value.channels).toEqual([i]),o(q.value.tombstone).toBe(!1),o(E.value.lastModified).toEqual(f.value.lastModified),o(q.value.lastModified).toEqual(f.value.lastModified)})),t("patch",(async()=>{const e=n(),a=l(),t=d(),s=d(),i=d(),c={hello:"world"},r=[t,i],u=await e.put({value:c,channels:r},a),v=e.synchronizeDiscover([t],{}).next(),h=e.synchronizeDiscover([s],{}).next(),w=e.synchronizeDiscover([i],{}).next();await e.patch({value:[{op:"add",path:"/something",value:"new value"}],channels:[{op:"add",path:"/-",value:s},{op:"remove",path:`/${r.indexOf(t)}`}]},u,a);const p=(await v).value,m=(await h).value,E=(await w).value;if(!p||p.error||!m||m.error||!E||E.error)throw new Error("Error in synchronize");const f={...c,something:"new value"};o(p.value.value).toEqual(c),o(p.value.channels).toEqual([t]),o(p.value.tombstone).toBe(!0),o(m.value.value).toEqual(f),o(m.value.channels).toEqual([s]),o(m.value.tombstone).toBe(!1),o(E.value.value).toEqual(f),o(E.value.channels).toEqual([i]),o(E.value.tombstone).toBe(!1),o(p.value.lastModified).toEqual(m.value.lastModified),o(E.value.lastModified).toEqual(m.value.lastModified)})),t("delete",(async()=>{const e=n(),a=l(),t=[d(),d(),d()],s={hello:"world"},i=[d(),...t.slice(1)],c=await e.put({value:s,channels:i},a),r=e.synchronizeDiscover(t,{}).next();e.delete(c,a);const u=(await r).value;if(!u||u.error)throw new Error("Error in synchronize");o(u.value.tombstone).toBe(!0),o(u.value.value).toEqual(s),o(u.value.channels).toEqual(t.filter((e=>i.includes(e))))})),t("not allowed",(async()=>{const e=n(),a=l(),t=s(),i=[d(),d(),d()],c=i.slice(1),r=e.synchronizeDiscover(c,{},a).next(),u=e.synchronizeDiscover(c,{},t).next(),v=e.synchronizeDiscover(c,{}).next(),h={hello:"world"},w=[d(),t.actor];await e.put({value:h,channels:i,allowed:w},a),await o(Promise.race([v,new Promise(((e,a)=>setTimeout(a,100,"Timeout")))])).rejects.toThrow("Timeout");const p=(await r).value,m=(await u).value;if(!p||p.error||!m||m.error)throw new Error("Error in synchronize");o(p.value.value).toEqual(h),o(p.value.allowed).toEqual(w),o(p.value.channels).toEqual(i),o(m.value.value).toEqual(h),o(m.value.allowed).toEqual([t.actor]),o(m.value.channels).toEqual(c)}))})),a("synchronizeGet",(()=>{t("replace, delete",(async()=>{const a=n(),t=l(),s=v(),i=await a.put(s,t),c=a.synchronizeGet(i,{}),r=c.next(),u={goodbye:"world"},d=await a.put({...i,value:u},t),h=(await r).value;e(h&&!h.error),o(h.value.value).toEqual(u),o(h.value.actor).toEqual(t.actor),o(h.value.channels).toEqual([]),o(h.value.tombstone).toBe(!1),o(h.value.lastModified).toEqual(d.lastModified),o(h.value.allowed).toBeUndefined();const w=await a.delete(d,t),p=(await c.next()).value;e(p&&!p.error),o(p.value.tombstone).toBe(!0),o(p.value.lastModified).toEqual(w.lastModified),await a.put(v(),t),await o(Promise.race([c.next(),new Promise(((e,a)=>setTimeout(a,100,"Timeout")))])).rejects.toThrow("Timeout")})),t("not allowed",(async()=>{const a=n(),t=l(),i=s(),c=v(),r=await a.put(c,t),u=a.synchronizeGet(r,{},t),d=a.synchronizeGet(r,{},i),h=u.next(),w=d.next(),p={goodbye:"world"},m=await a.put({...r,...c,allowed:[],value:p},t),E=(await h).value,f=(await w).value;e(E&&!E.error),e(f&&!f.error),o(E.value.value).toEqual(p),o(f.value.value).toEqual(c.value),o(E.value.actor).toEqual(t.actor),o(f.value.actor).toEqual(t.actor),o(E.value.channels).toEqual(c.channels),o(f.value.channels).toEqual([]),o(E.value.tombstone).toBe(!1),o(f.value.tombstone).toBe(!0),o(E.value.lastModified).toEqual(m.lastModified),o(f.value.lastModified).toEqual(m.lastModified)}))}))},E=(n,l,s)=>{a("discover",{timeout:2e4},(()=>{t("discover nothing",(async()=>{const e=n().discover([],{});o(await e.next()).toHaveProperty("done",!0)})),t("discover single",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=[d(),t.channels[0]],c=e.discover(i,{}),r=await h(c);o(r.value).toEqual(t.value),o(r.channels).toEqual([t.channels[0]]),o(r.allowed).toBeUndefined(),o(r.actor).toEqual(a.actor),o(r.tombstone).toBe(!1),o(r.lastModified).toEqual(s.lastModified);const u=await c.next();o(u.done).toBe(!0)})),t("discover wrong channel",(async()=>{const e=n(),a=l(),t=v();await e.put(t,a);const s=e.discover([d()],{});await o(s.next()).resolves.toHaveProperty("done",!0)})),t("discover not allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),d()];const c=await e.put(i,a),r=e.discover(i.channels,{},a),u=await h(r);o(u.value).toEqual(i.value),o(u.channels).toEqual(i.channels),o(u.allowed).toEqual(i.allowed),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(c.lastModified);const w=e.discover(i.channels,{},t);o(await w.next()).toHaveProperty("done",!0);const p=e.discover(i.channels,{});o(await p.next()).toHaveProperty("done",!0)})),t("discover allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()];const c=await e.put(i,a),r=e.discover(i.channels,{},t),u=await h(r);o(u.value).toEqual(i.value),o(u.allowed).toEqual([t.actor]),o(u.channels).toEqual(i.channels),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(c.lastModified)}));for(const e of["name","actor","lastModified"])t(`discover for ${e}`,(async()=>{const a=n(),t=l(),i=s(),c=v(),r=await a.put(c,t),u=v();u.channels=c.channels,await new Promise((e=>setTimeout(e,20)));const d=await a.put(u,i),w=a.discover(c.channels,{properties:{[e]:{enum:[r[e]]}}}),p=await h(w);o(p.name).toEqual(r.name),o(p.name).not.toEqual(d.name),o(p.value).toEqual(c.value),await o(w.next()).resolves.toHaveProperty("done",!0)}));t("discover with lastModified range",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await new Promise((e=>setTimeout(e,20)));const i=await e.put(t,a);o(s.name).not.toEqual(i.name),o(s.lastModified).toBeLessThan(i.lastModified);const c=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified,exclusiveMinimum:!0}}});o(await c.next()).toHaveProperty("done",!0);const r=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified-.1,exclusiveMinimum:!0}}}),u=await h(r);o(u.name).toEqual(i.name),o(await r.next()).toHaveProperty("done",!0);const d=e.discover(t.channels,{properties:{value:{},lastModified:{minimum:i.lastModified}}}),w=await h(d);o(w.name).toEqual(i.name),o(await d.next()).toHaveProperty("done",!0);const p=e.discover(t.channels,{properties:{lastModified:{minimum:i.lastModified+.1}}});o(await p.next()).toHaveProperty("done",!0);const m=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified,exclusiveMaximum:!0}}});o(await m.next()).toHaveProperty("done",!0);const E=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified+.1,exclusiveMaximum:!0}}}),f=await h(E);o(f.name).toEqual(s.name),o(await E.next()).toHaveProperty("done",!0);const q=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified}}}),y=await h(q);o(y.name).toEqual(s.name),o(await q.next()).toHaveProperty("done",!0);const M=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified-.1}}});o(await M.next()).toHaveProperty("done",!0)})),t("discover schema allowed, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()],await e.put(i,a);const c=e.discover(i.channels,{properties:{allowed:{minItems:3,not:{items:{not:{enum:[t.actor]}}}}}},a),r=await h(c);o(r.value).toEqual(i.value),await o(c.next()).resolves.toHaveProperty("done",!0);const u=e.discover(i.channels,{properties:{allowed:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover(i.channels,{properties:{allowed:{not:{items:{not:{enum:[i.channels[0]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover(i.channels,{properties:{allowed:{maxItems:1,not:{items:{not:{enum:[t.actor]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover schema channels, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.channels=[d(),d(),d()],await e.put(i,a);const c=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3,not:{items:{not:{enum:[i.channels[1]]}}}}}},a),r=await h(c);o(r.value).toEqual(i.value),await o(c.next()).resolves.toHaveProperty("done",!0);const u=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{not:{items:{not:{enum:[i.channels[1]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover([i.channels[0],i.channels[2]],{properties:{allowed:{maxItems:2,not:{items:{not:{enum:[i.channels[2]]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover query for empty allowed",(async()=>{const e=n(),a=l(),t=v(),s={not:{required:["allowed"]}};await e.put(t,a);const i=e.discover(t.channels,s,a),c=await h(i);o(c.value).toEqual(t.value),o(c.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0);const r=v();r.allowed=[],await e.put(r,a);const u=e.discover(r.channels,s,a);await o(u.next()).resolves.toHaveProperty("done",!0)})),t("discover query for values",(async()=>{const a=n(),t=l(),s=v();s.value={test:d()},await a.put(s,t);const i=v();i.channels=s.channels,i.value={test:d(),something:d()},await a.put(i,t);const c=v();c.channels=s.channels,c.value={other:d(),something:d()},await a.put(c,t);const r=new Map;for(const t of["test","something","other"]){let o=0;for await(const n of a.discover(s.channels,{properties:{value:{required:[t]}}}))e(!n.error,"result has error"),t in n.value.value&&o++;r.set(t,o)}o(r.get("test")).toBe(2),o(r.get("something")).toBe(2),o(r.get("other")).toBe(1)})),t("discover for deleted content",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=await e.delete(s,a),c=e.discover(t.channels,{}),r=await h(c);o(r.tombstone).toBe(!0),o(r.value).toEqual(t.value),o(r.channels).toEqual(t.channels),o(r.actor).toEqual(a.actor),o(r.lastModified).toEqual(i.lastModified),await o(c.next()).resolves.toHaveProperty("done",!0)})),t("discover for replaced channels",(async()=>{for(let e=0;e<10;e++){const e=n(),a=l(),t=v(),s=await e.put(t,a),i=v(),c=await e.put({...s,...i},a),r=e.discover(t.channels,{}),u=await h(r);await o(r.next()).resolves.toHaveProperty("done",!0);const d=e.discover(i.channels,{}),w=await h(d);await o(d.next()).resolves.toHaveProperty("done",!0),s.lastModified===c.lastModified?(o(u.tombstone||w.tombstone).toBe(!0),o(u.tombstone&&w.tombstone).toBe(!1)):(o(u.tombstone).toBe(!0),o(u.value).toEqual(t.value),o(u.channels).toEqual(t.channels),o(u.lastModified).toEqual(c.lastModified),o(w.tombstone).toBe(!1),o(w.value).toEqual(i.value),o(w.channels).toEqual(i.channels),o(w.lastModified).toEqual(c.lastModified))}})),t("discover for patched allowed",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await e.patch({allowed:[{op:"add",path:"",value:[]}]},s,a);const i=e.discover(t.channels,{}),c=await h(i);o(c.tombstone).toBe(!0),o(c.value).toEqual(t.value),o(c.channels).toEqual(t.channels),o(c.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0)})),t("put concurrently and discover one",(async()=>{const a=n(),t=l(),s=v();s.name=d();const i=Array(100).fill(0).map((()=>a.put(s,t)));await Promise.all(i);const c=a.discover(s.channels,{});let r=0,u=0;for await(const a of c)e(!a.error,"result has error"),a.value.tombstone?r++:u++;o(r).toBe(99),o(u).toBe(1)}))}))},f=(e,n,l)=>{a("recoverOrphans",(()=>{t("list orphans",(async()=>{const a=e(),t=n(),l=[],s=a.recoverOrphans({},t);for await(const e of s)e.error||l.push(e.value.name);const i=v();i.channels=[];const c=await a.put(i,t),r=a.recoverOrphans({},t);let u=0;for await(const e of r)e.error||e.value.name===c.name&&(u++,o(e.value.source).toBe(c.source),o(e.value.lastModified).toBe(c.lastModified));o(u).toBe(1)})),t("replaced orphan, no longer",(async()=>{const a=e(),t=n(),l=v();l.channels=[];const s=await a.put(l,t),i=await a.put({...s,...l,channels:[d()]},t);o(i.name).toBe(s.name);const c=a.recoverOrphans({},t);let r=0;for await(const e of c)e.error||e.value.name===s.name&&(r++,o(e.value.tombstone).toBe(!0),o(e.value.lastModified).toBe(i.lastModified),o(e.value.channels).toEqual([]));o(r).toBe(1)}))}))},q=(n,l,s)=>{a("channel stats",(()=>{t("list channels",(async()=>{const e=n(),a=l(),t=new Map,s=e.channelStats(a);for await(const e of s)e.error||t.set(e.value.channel,e.value.count);const i=[d(),d(),d()];for(let t=0;t<3;t++)for(let o=0;o<t+1;o++)await e.put({value:{index:o},channels:i.slice(0,t+1)},a);await e.put({value:{index:3},channels:[i[2]]},a);const c=e.channelStats(a);let r=new Map;for await(const e of c)e.error||r.set(e.value.channel,e.value.count);r=new Map(Array.from(r).filter((([e,a])=>!t.has(e)))),o(r.size).toBe(3),o(r.get(i[0])).toBe(6),o(r.get(i[1])).toBe(5),o(r.get(i[2])).toBe(4)})),t("list channels with deleted channel",(async()=>{const a=n(),t=l(),s=[d(),d(),d()],i=await a.put({value:{index:2},channels:s.slice(1)},t),c=await a.put({value:{index:0},channels:s},t);await a.delete(c,t);const r=await a.put({value:{index:1},channels:s.slice(2)},t),u=a.channelStats(t);let v=0,h=0;for await(const a of u){if(a.error)continue;const{channel:t,count:n,lastModified:l}=a.value;e(t!==s[0],"There should not be an object in channel[0]"),t===s[1]?(o(n).toBe(1),o(l).toBe(i.lastModified),v++):t===s[2]&&(o(n).toBe(2),o(l).toBe(r.lastModified),h++)}o(v).toBe(1),o(h).toBe(1)}))}))};export{p as graffitiCRUDTests,q as graffitiChannelStatsTests,E as graffitiDiscoverTests,w as graffitiLocationTests,f as graffitiOrphanTests,m as graffitiSynchronizeTests};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { GraffitiFactory, GraffitiSession } from "@graffiti-garden/api";
|
|
2
|
+
export declare const graffitiChannelStatsTests: (useGraffiti: GraffitiFactory, useSession1: () => GraffitiSession, useSession2: () => GraffitiSession) => void;
|
|
3
|
+
//# sourceMappingURL=channel-stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-stats.d.ts","sourceRoot":"","sources":["../../../tests/src/channel-stats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,yBAAyB,gBACvB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SAiHnC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../../tests/src/crud.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAW9B,eAAO,MAAM,iBAAiB,gBACf,MAAM,IAAI,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC,eACxD,MAAM,eAAe,eACrB,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../../tests/src/crud.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAW9B,eAAO,MAAM,iBAAiB,gBACf,MAAM,IAAI,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC,eACxD,MAAM,eAAe,eACrB,MAAM,eAAe,SAwenC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../tests/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../tests/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../tests/src/list.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,iBAAiB,gBACf,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SAgBnC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphans.d.ts","sourceRoot":"","sources":["../../../tests/src/orphans.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,mBAAmB,gBACjB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SA8DnC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"synchronize.d.ts","sourceRoot":"","sources":["../../../tests/src/synchronize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,wBAAwB,gBACtB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"synchronize.d.ts","sourceRoot":"","sources":["../../../tests/src/synchronize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,wBAAwB,gBACtB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SAyVnC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../tests/src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE9E,wBAAgB,YAAY,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../tests/src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE9E,wBAAgB,YAAY,IAAI,MAAM,CASrC;AAED,wBAAgB,WAAW;;EAI1B;AAED,wBAAgB,eAAe,IAAI,iBAAiB,CAAC,EAAE,CAAC,CAKvD;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,cAIzE"}
|