@graffiti-garden/api 0.6.3 → 1.0.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/dist/tests.mjs CHANGED
@@ -1,14 +1,3 @@
1
- // tests/crud.ts
2
- import { it, expect, describe, beforeAll } from "vitest";
3
- import {
4
- GraffitiErrorNotFound,
5
- GraffitiErrorSchemaMismatch,
6
- GraffitiErrorInvalidSchema,
7
- GraffitiErrorForbidden,
8
- GraffitiErrorPatchTestFailed,
9
- GraffitiErrorPatchError
10
- } from "@graffiti-garden/api";
11
-
12
1
  // tests/utils.ts
13
2
  import { assert } from "vitest";
14
3
  function randomString() {
@@ -22,7 +11,7 @@ function randomValue() {
22
11
  [randomString()]: randomString()
23
12
  };
24
13
  }
25
- function randomPutObject() {
14
+ function randomPostObject() {
26
15
  return {
27
16
  value: randomValue(),
28
17
  channels: [randomString(), randomString()]
@@ -36,7 +25,7 @@ async function nextStreamValue(iterator) {
36
25
  }
37
26
  function continueStream(graffiti, streamReturn, type, session) {
38
27
  if (type === "cursor") {
39
- return graffiti.continueObjectStream(
28
+ return graffiti.continueDiscover(
40
29
  streamReturn.cursor,
41
30
  session
42
31
  );
@@ -44,1265 +33,10 @@ function continueStream(graffiti, streamReturn, type, session) {
44
33
  return streamReturn.continue();
45
34
  }
46
35
  }
47
-
48
- // tests/crud.ts
49
- var graffitiCRUDTests = (useGraffiti, useSession1, useSession2) => {
50
- describe.concurrent(
51
- "CRUD",
52
- {
53
- timeout: 2e4
54
- },
55
- () => {
56
- let graffiti;
57
- let session;
58
- let session1;
59
- let session2;
60
- beforeAll(async () => {
61
- graffiti = useGraffiti();
62
- session1 = await useSession1();
63
- session = session1;
64
- session2 = await useSession2();
65
- });
66
- it("put, get, delete", async () => {
67
- const value = {
68
- something: "hello, world~ c:"
69
- };
70
- const channels = [randomString(), randomString()];
71
- const previous = await graffiti.put({ value, channels }, session);
72
- expect(previous.value).toEqual({});
73
- expect(previous.channels).toEqual([]);
74
- expect(previous.allowed).toEqual([]);
75
- expect(previous.actor).toEqual(session.actor);
76
- const gotten = await graffiti.get(previous, {});
77
- expect(gotten.value).toEqual(value);
78
- expect(gotten.channels).toEqual([]);
79
- expect(gotten.allowed).toBeUndefined();
80
- expect(gotten.url).toEqual(previous.url);
81
- expect(gotten.actor).toEqual(previous.actor);
82
- expect(gotten.lastModified).toEqual(previous.lastModified);
83
- const newValue = {
84
- something: "goodbye, world~ :c"
85
- };
86
- const beforeReplaced = await graffiti.put(
87
- {
88
- url: previous.url,
89
- value: newValue,
90
- channels: []
91
- },
92
- session
93
- );
94
- expect(beforeReplaced.value).toEqual(value);
95
- expect(beforeReplaced.url).toEqual(previous.url);
96
- expect(beforeReplaced.actor).toEqual(previous.actor);
97
- expect(beforeReplaced.lastModified).toBeGreaterThanOrEqual(
98
- gotten.lastModified
99
- );
100
- const afterReplaced = await graffiti.get(previous, {});
101
- expect(afterReplaced.value).toEqual(newValue);
102
- expect(afterReplaced.lastModified).toEqual(beforeReplaced.lastModified);
103
- const beforeDeleted = await graffiti.delete(afterReplaced, session);
104
- expect(beforeDeleted.value).toEqual(newValue);
105
- expect(beforeDeleted.lastModified).toBeGreaterThanOrEqual(
106
- beforeReplaced.lastModified
107
- );
108
- await expect(graffiti.get(afterReplaced, {})).rejects.toBeInstanceOf(
109
- GraffitiErrorNotFound
110
- );
111
- await expect(graffiti.delete(beforeDeleted, session)).rejects.toThrow(
112
- GraffitiErrorNotFound
113
- );
114
- await expect(
115
- graffiti.put(
116
- { url: beforeDeleted.url, value: {}, channels: [] },
117
- session
118
- )
119
- ).rejects.toThrow(GraffitiErrorNotFound);
120
- });
121
- it("put, delete, patch with wrong actor", async () => {
122
- await expect(
123
- graffiti.put(
124
- { value: {}, channels: [], actor: session2.actor },
125
- session1
126
- )
127
- ).rejects.toThrow(GraffitiErrorForbidden);
128
- const putted = await graffiti.put(
129
- { value: {}, channels: [] },
130
- session2
131
- );
132
- await expect(
133
- graffiti.put(
134
- {
135
- url: putted.url,
136
- value: {},
137
- channels: []
138
- },
139
- session1
140
- )
141
- ).rejects.toThrow(GraffitiErrorForbidden);
142
- await expect(graffiti.delete(putted, session1)).rejects.toThrow(
143
- GraffitiErrorForbidden
144
- );
145
- await expect(graffiti.patch({}, putted, session1)).rejects.toThrow(
146
- GraffitiErrorForbidden
147
- );
148
- });
149
- it("put, patch, delete object that is not allowed", async () => {
150
- const putted = await graffiti.put(
151
- {
152
- value: {},
153
- channels: [],
154
- allowed: []
155
- },
156
- session1
157
- );
158
- await expect(
159
- graffiti.put(
160
- {
161
- url: putted.url,
162
- value: {},
163
- channels: []
164
- },
165
- session2
166
- )
167
- ).rejects.toThrow(GraffitiErrorNotFound);
168
- await expect(graffiti.patch({}, putted, session2)).rejects.toThrow(
169
- GraffitiErrorNotFound
170
- );
171
- await expect(graffiti.delete(putted, session2)).rejects.toThrow(
172
- GraffitiErrorNotFound
173
- );
174
- });
175
- it("put and get with schema", async () => {
176
- const schema = {
177
- properties: {
178
- value: {
179
- properties: {
180
- something: {
181
- type: "string"
182
- },
183
- another: {
184
- type: "array",
185
- items: {
186
- type: "number"
187
- }
188
- },
189
- deeper: {
190
- type: "object",
191
- properties: {
192
- deepProp: {
193
- type: "string"
194
- }
195
- },
196
- required: ["deepProp"]
197
- }
198
- },
199
- required: ["another", "deeper"]
200
- }
201
- }
202
- };
203
- const goodValue = {
204
- something: "hello",
205
- another: [1, 2, 3],
206
- deeper: {
207
- deepProp: "hello"
208
- }
209
- };
210
- const putted = await graffiti.put(
211
- {
212
- value: goodValue,
213
- channels: []
214
- },
215
- session
216
- );
217
- const gotten = await graffiti.get(putted, schema);
218
- expect(gotten.value.something).toEqual(goodValue.something);
219
- expect(gotten.value.another).toEqual(goodValue.another);
220
- expect(gotten.value.another[0]).toEqual(1);
221
- expect(gotten.value.deeper.deepProp).toEqual(goodValue.deeper.deepProp);
222
- });
223
- it("put and get with invalid schema", async () => {
224
- const putted = await graffiti.put(
225
- { value: {}, channels: [] },
226
- session
227
- );
228
- await expect(
229
- graffiti.get(putted, {
230
- properties: {
231
- value: {
232
- //@ts-ignore
233
- type: "asdf"
234
- }
235
- }
236
- })
237
- ).rejects.toThrow(GraffitiErrorInvalidSchema);
238
- });
239
- it("put and get with wrong schema", async () => {
240
- const putted = await graffiti.put(
241
- {
242
- value: {
243
- hello: "world"
244
- },
245
- channels: []
246
- },
247
- session
248
- );
249
- await expect(
250
- graffiti.get(putted, {
251
- properties: {
252
- value: {
253
- properties: {
254
- hello: {
255
- type: "number"
256
- }
257
- }
258
- }
259
- }
260
- })
261
- ).rejects.toThrow(GraffitiErrorSchemaMismatch);
262
- });
263
- it("put and get with empty access control", async () => {
264
- const value = {
265
- um: "hi"
266
- };
267
- const allowed = [randomString()];
268
- const channels = [randomString()];
269
- const putted = await graffiti.put(
270
- { value, allowed, channels },
271
- session1
272
- );
273
- const gotten = await graffiti.get(putted, {}, session1);
274
- expect(gotten.value).toEqual(value);
275
- expect(gotten.allowed).toEqual(allowed);
276
- expect(gotten.channels).toEqual(channels);
277
- await expect(graffiti.get(putted, {})).rejects.toBeInstanceOf(
278
- GraffitiErrorNotFound
279
- );
280
- await expect(graffiti.get(putted, {}, session2)).rejects.toBeInstanceOf(
281
- GraffitiErrorNotFound
282
- );
283
- });
284
- it("put and get with specific access control", async () => {
285
- const value = {
286
- um: "hi"
287
- };
288
- const allowed = [randomString(), session2.actor, randomString()];
289
- const channels = [randomString()];
290
- const putted = await graffiti.put(
291
- {
292
- value,
293
- allowed,
294
- channels
295
- },
296
- session1
297
- );
298
- const gotten = await graffiti.get(putted, {}, session1);
299
- expect(gotten.value).toEqual(value);
300
- expect(gotten.allowed).toEqual(allowed);
301
- expect(gotten.channels).toEqual(channels);
302
- await expect(graffiti.get(putted, {})).rejects.toBeInstanceOf(
303
- GraffitiErrorNotFound
304
- );
305
- const gotten2 = await graffiti.get(putted, {}, session2);
306
- expect(gotten2.value).toEqual(value);
307
- expect(gotten2.allowed).toEqual([session2.actor]);
308
- expect(gotten2.channels).toEqual([]);
309
- });
310
- it("patch value", async () => {
311
- const value = {
312
- something: "hello, world~ c:"
313
- };
314
- const putted = await graffiti.put({ value, channels: [] }, session);
315
- await new Promise((resolve) => setTimeout(resolve, 10));
316
- const patch = {
317
- value: [
318
- { op: "replace", path: "/something", value: "goodbye, world~ :c" }
319
- ]
320
- };
321
- const beforePatched = await graffiti.patch(patch, putted, session);
322
- expect(beforePatched.value).toEqual(value);
323
- expect(beforePatched.lastModified).toBeGreaterThan(putted.lastModified);
324
- const gotten = await graffiti.get(putted, {});
325
- expect(gotten.value).toEqual({
326
- something: "goodbye, world~ :c"
327
- });
328
- expect(beforePatched.lastModified).toBe(gotten.lastModified);
329
- await graffiti.delete(putted, session);
330
- });
331
- it("patch deleted object", async () => {
332
- const putted = await graffiti.put(randomPutObject(), session);
333
- const deleted = await graffiti.delete(putted, session);
334
- await expect(
335
- graffiti.patch({}, putted, session)
336
- ).rejects.toBeInstanceOf(GraffitiErrorNotFound);
337
- });
338
- it("deep patch", async () => {
339
- const value = {
340
- something: {
341
- another: {
342
- somethingElse: "hello"
343
- }
344
- }
345
- };
346
- const putted = await graffiti.put(
347
- { value, channels: [] },
348
- session
349
- );
350
- const beforePatch = await graffiti.patch(
351
- {
352
- value: [
353
- {
354
- op: "replace",
355
- path: "/something/another/somethingElse",
356
- value: "goodbye"
357
- }
358
- ]
359
- },
360
- putted,
361
- session
362
- );
363
- const gotten = await graffiti.get(putted, {});
364
- expect(beforePatch.value).toEqual(value);
365
- expect(gotten.value).toEqual({
366
- something: {
367
- another: {
368
- somethingElse: "goodbye"
369
- }
370
- }
371
- });
372
- });
373
- it("patch channels", async () => {
374
- const channelsBefore = [randomString()];
375
- const channelsAfter = [randomString()];
376
- const putted = await graffiti.put(
377
- { value: {}, channels: channelsBefore },
378
- session
379
- );
380
- const patch = {
381
- channels: [{ op: "replace", path: "/0", value: channelsAfter[0] }]
382
- };
383
- const patched = await graffiti.patch(patch, putted, session);
384
- expect(patched.channels).toEqual(channelsBefore);
385
- const gotten = await graffiti.get(putted, {}, session);
386
- expect(gotten.channels).toEqual(channelsAfter);
387
- await graffiti.delete(putted, session);
388
- });
389
- it("patch 'increment' with test", async () => {
390
- const putted = await graffiti.put(
391
- {
392
- value: {
393
- counter: 1
394
- },
395
- channels: []
396
- },
397
- session
398
- );
399
- const previous = await graffiti.patch(
400
- {
401
- value: [
402
- { op: "test", path: "/counter", value: 1 },
403
- { op: "replace", path: "/counter", value: 2 }
404
- ]
405
- },
406
- putted,
407
- session
408
- );
409
- expect(previous.value).toEqual({ counter: 1 });
410
- const result = await graffiti.get(previous, {
411
- properties: {
412
- value: {
413
- properties: {
414
- counter: {
415
- type: "integer"
416
- }
417
- }
418
- }
419
- }
420
- });
421
- expect(result.value.counter).toEqual(2);
422
- await expect(
423
- graffiti.patch(
424
- {
425
- value: [
426
- { op: "test", path: "/counter", value: 1 },
427
- { op: "replace", path: "/counter", value: 3 }
428
- ]
429
- },
430
- putted,
431
- session
432
- )
433
- ).rejects.toThrow(GraffitiErrorPatchTestFailed);
434
- });
435
- it("invalid patch", async () => {
436
- const object = randomPutObject();
437
- const putted = await graffiti.put(object, session);
438
- await expect(
439
- graffiti.patch(
440
- {
441
- value: [
442
- { op: "add", path: "/root", value: [] },
443
- { op: "add", path: "/root/2", value: 2 }
444
- // out of bounds
445
- ]
446
- },
447
- putted,
448
- session
449
- )
450
- ).rejects.toThrow(GraffitiErrorPatchError);
451
- });
452
- it("patch channels to be wrong", async () => {
453
- const object = randomPutObject();
454
- object.allowed = [randomString()];
455
- const putted = await graffiti.put(object, session);
456
- const patches = [
457
- {
458
- channels: [{ op: "replace", path: "", value: null }]
459
- },
460
- {
461
- channels: [{ op: "replace", path: "", value: {} }]
462
- },
463
- {
464
- channels: [{ op: "replace", path: "", value: ["hello", ["hi"]] }]
465
- },
466
- {
467
- channels: [{ op: "add", path: "/0", value: 1 }]
468
- },
469
- {
470
- value: [{ op: "replace", path: "", value: "not an object" }]
471
- },
472
- {
473
- value: [{ op: "replace", path: "", value: null }]
474
- },
475
- {
476
- value: [{ op: "replace", path: "", value: [] }]
477
- },
478
- {
479
- allowed: [{ op: "replace", path: "", value: {} }]
480
- },
481
- {
482
- allowed: [{ op: "replace", path: "", value: ["hello", ["hi"]] }]
483
- }
484
- ];
485
- for (const patch of patches) {
486
- await expect(graffiti.patch(patch, putted, session)).rejects.toThrow(
487
- GraffitiErrorPatchError
488
- );
489
- }
490
- const gotten = await graffiti.get(putted, {}, session);
491
- expect(gotten.value).toEqual(object.value);
492
- expect(gotten.channels).toEqual(object.channels);
493
- expect(gotten.allowed).toEqual(object.allowed);
494
- expect(gotten.lastModified).toEqual(putted.lastModified);
495
- });
496
- }
497
- );
498
- };
499
-
500
- // tests/discover.ts
501
- import { it as it2, expect as expect2, describe as describe2, assert as assert2, beforeAll as beforeAll2 } from "vitest";
502
- var graffitiDiscoverTests = (useGraffiti, useSession1, useSession2) => {
503
- describe2.concurrent("discover", { timeout: 2e4 }, () => {
504
- let graffiti;
505
- let session;
506
- let session1;
507
- let session2;
508
- beforeAll2(async () => {
509
- graffiti = useGraffiti();
510
- session1 = await useSession1();
511
- session = session1;
512
- session2 = await useSession2();
513
- });
514
- it2("discover nothing", async () => {
515
- const iterator = graffiti.discover([], {});
516
- expect2(await iterator.next()).toHaveProperty("done", true);
517
- });
518
- it2("discover single", async () => {
519
- const object = randomPutObject();
520
- const putted = await graffiti.put(object, session);
521
- const queryChannels = [randomString(), object.channels[0]];
522
- const iterator = graffiti.discover(queryChannels, {});
523
- const value = await nextStreamValue(iterator);
524
- expect2(value.value).toEqual(object.value);
525
- expect2(value.channels).toEqual([object.channels[0]]);
526
- expect2(value.allowed).toBeUndefined();
527
- expect2(value.actor).toEqual(session.actor);
528
- expect2(value.lastModified).toEqual(putted.lastModified);
529
- const result2 = await iterator.next();
530
- expect2(result2.done).toBe(true);
531
- });
532
- it2("discover wrong channel", async () => {
533
- const object = randomPutObject();
534
- await graffiti.put(object, session);
535
- const iterator = graffiti.discover([randomString()], {});
536
- await expect2(iterator.next()).resolves.toHaveProperty("done", true);
537
- });
538
- it2("discover not allowed", async () => {
539
- const object = randomPutObject();
540
- object.allowed = [randomString(), randomString()];
541
- const putted = await graffiti.put(object, session1);
542
- const iteratorSession1 = graffiti.discover(
543
- object.channels,
544
- {},
545
- session1
546
- );
547
- const value = await nextStreamValue(iteratorSession1);
548
- expect2(value.value).toEqual(object.value);
549
- expect2(value.channels).toEqual(object.channels);
550
- expect2(value.allowed).toEqual(object.allowed);
551
- expect2(value.actor).toEqual(session1.actor);
552
- expect2(value.lastModified).toEqual(putted.lastModified);
553
- const iteratorSession2 = graffiti.discover(object.channels, {}, session2);
554
- expect2(await iteratorSession2.next()).toHaveProperty("done", true);
555
- const iteratorNoSession = graffiti.discover(object.channels, {});
556
- expect2(await iteratorNoSession.next()).toHaveProperty("done", true);
557
- });
558
- it2("discover allowed", async () => {
559
- const object = randomPutObject();
560
- object.allowed = [randomString(), session2.actor, randomString()];
561
- const putted = await graffiti.put(object, session1);
562
- const iteratorSession2 = graffiti.discover(
563
- object.channels,
564
- {},
565
- session2
566
- );
567
- const value = await nextStreamValue(iteratorSession2);
568
- expect2(value.value).toEqual(object.value);
569
- expect2(value.allowed).toEqual([session2.actor]);
570
- expect2(value.channels).toEqual(object.channels);
571
- expect2(value.actor).toEqual(session1.actor);
572
- expect2(value.lastModified).toEqual(putted.lastModified);
573
- });
574
- for (const prop of ["actor", "lastModified"]) {
575
- it2(`discover for ${prop}`, async () => {
576
- const object1 = randomPutObject();
577
- const putted1 = await graffiti.put(object1, session1);
578
- const object2 = randomPutObject();
579
- object2.channels = object1.channels;
580
- await new Promise((r) => setTimeout(r, 20));
581
- const putted2 = await graffiti.put(object2, session2);
582
- const iterator = graffiti.discover(object1.channels, {
583
- properties: {
584
- [prop]: {
585
- enum: [putted1[prop]]
586
- }
587
- }
588
- });
589
- const value = await nextStreamValue(iterator);
590
- expect2(value.url).toEqual(putted1.url);
591
- expect2(value.url).not.toEqual(putted2.url);
592
- expect2(value.value).toEqual(object1.value);
593
- await expect2(iterator.next()).resolves.toHaveProperty("done", true);
594
- });
595
- }
596
- it2("discover with lastModified range", async () => {
597
- const object = randomPutObject();
598
- const putted1 = await graffiti.put(object, session);
599
- await new Promise((r) => setTimeout(r, 20));
600
- const putted2 = await graffiti.put(object, session);
601
- expect2(putted1.url).not.toEqual(putted2.url);
602
- expect2(putted1.lastModified).toBeLessThan(putted2.lastModified);
603
- const gtIterator = graffiti.discover([object.channels[0]], {
604
- properties: {
605
- lastModified: {
606
- exclusiveMinimum: putted2.lastModified
607
- }
608
- }
609
- });
610
- expect2(await gtIterator.next()).toHaveProperty("done", true);
611
- const gtIteratorEpsilon = graffiti.discover([object.channels[0]], {
612
- properties: {
613
- lastModified: {
614
- exclusiveMinimum: putted2.lastModified - 0.1
615
- }
616
- }
617
- });
618
- const value1 = await nextStreamValue(gtIteratorEpsilon);
619
- expect2(value1.url).toEqual(putted2.url);
620
- expect2(await gtIteratorEpsilon.next()).toHaveProperty("done", true);
621
- const gteIterator = graffiti.discover(object.channels, {
622
- properties: {
623
- value: {},
624
- lastModified: {
625
- minimum: putted2.lastModified
626
- }
627
- }
628
- });
629
- const value = await nextStreamValue(gteIterator);
630
- expect2(value.url).toEqual(putted2.url);
631
- expect2(await gteIterator.next()).toHaveProperty("done", true);
632
- const gteIteratorEpsilon = graffiti.discover(object.channels, {
633
- properties: {
634
- lastModified: {
635
- minimum: putted2.lastModified + 0.1
636
- }
637
- }
638
- });
639
- expect2(await gteIteratorEpsilon.next()).toHaveProperty("done", true);
640
- const ltIterator = graffiti.discover(object.channels, {
641
- properties: {
642
- lastModified: {
643
- exclusiveMaximum: putted1.lastModified
644
- }
645
- }
646
- });
647
- expect2(await ltIterator.next()).toHaveProperty("done", true);
648
- const ltIteratorEpsilon = graffiti.discover(object.channels, {
649
- properties: {
650
- lastModified: {
651
- exclusiveMaximum: putted1.lastModified + 0.1
652
- }
653
- }
654
- });
655
- const value3 = await nextStreamValue(ltIteratorEpsilon);
656
- expect2(value3.url).toEqual(putted1.url);
657
- expect2(await ltIteratorEpsilon.next()).toHaveProperty("done", true);
658
- const lteIterator = graffiti.discover(object.channels, {
659
- properties: {
660
- lastModified: {
661
- maximum: putted1.lastModified
662
- }
663
- }
664
- });
665
- const value2 = await nextStreamValue(lteIterator);
666
- expect2(value2.url).toEqual(putted1.url);
667
- expect2(await lteIterator.next()).toHaveProperty("done", true);
668
- const lteIteratorEpsilon = graffiti.discover(object.channels, {
669
- properties: {
670
- lastModified: {
671
- maximum: putted1.lastModified - 0.1
672
- }
673
- }
674
- });
675
- expect2(await lteIteratorEpsilon.next()).toHaveProperty("done", true);
676
- });
677
- it2("discover schema allowed, as and not as owner", async () => {
678
- const object = randomPutObject();
679
- object.allowed = [randomString(), session2.actor, randomString()];
680
- await graffiti.put(object, session1);
681
- const iteratorSession1 = graffiti.discover(
682
- object.channels,
683
- {
684
- properties: {
685
- allowed: {
686
- minItems: 3,
687
- // Make sure session2.actor is in the allow list
688
- not: {
689
- items: {
690
- not: {
691
- enum: [session2.actor]
692
- }
693
- }
694
- }
695
- }
696
- }
697
- },
698
- session1
699
- );
700
- const value = await nextStreamValue(iteratorSession1);
701
- expect2(value.value).toEqual(object.value);
702
- await expect2(iteratorSession1.next()).resolves.toHaveProperty(
703
- "done",
704
- true
705
- );
706
- const iteratorSession2BigAllow = graffiti.discover(
707
- object.channels,
708
- {
709
- properties: {
710
- allowed: {
711
- minItems: 3
712
- }
713
- }
714
- },
715
- session2
716
- );
717
- await expect2(iteratorSession2BigAllow.next()).resolves.toHaveProperty(
718
- "done",
719
- true
720
- );
721
- const iteratorSession2PeekOther = graffiti.discover(
722
- object.channels,
723
- {
724
- properties: {
725
- allowed: {
726
- not: {
727
- items: {
728
- not: {
729
- enum: [object.channels[0]]
730
- }
731
- }
732
- }
733
- }
734
- }
735
- },
736
- session2
737
- );
738
- await expect2(iteratorSession2PeekOther.next()).resolves.toHaveProperty(
739
- "done",
740
- true
741
- );
742
- const iteratorSession2SmallAllowPeekSelf = graffiti.discover(
743
- object.channels,
744
- {
745
- properties: {
746
- allowed: {
747
- maxItems: 1,
748
- not: {
749
- items: {
750
- not: {
751
- enum: [session2.actor]
752
- }
753
- }
754
- }
755
- }
756
- }
757
- },
758
- session2
759
- );
760
- const value2 = await nextStreamValue(
761
- iteratorSession2SmallAllowPeekSelf
762
- );
763
- expect2(value2.value).toEqual(object.value);
764
- await expect2(
765
- iteratorSession2SmallAllowPeekSelf.next()
766
- ).resolves.toHaveProperty("done", true);
767
- });
768
- it2("discover schema channels, as and not as owner", async () => {
769
- const object = randomPutObject();
770
- object.channels = [randomString(), randomString(), randomString()];
771
- await graffiti.put(object, session1);
772
- const iteratorSession1 = graffiti.discover(
773
- [object.channels[0], object.channels[2]],
774
- {
775
- properties: {
776
- channels: {
777
- minItems: 3,
778
- // Make sure session2.actor is in the allow list
779
- not: {
780
- items: {
781
- not: {
782
- enum: [object.channels[1]]
783
- }
784
- }
785
- }
786
- }
787
- }
788
- },
789
- session1
790
- );
791
- const value = await nextStreamValue(iteratorSession1);
792
- expect2(value.value).toEqual(object.value);
793
- await expect2(iteratorSession1.next()).resolves.toHaveProperty(
794
- "done",
795
- true
796
- );
797
- const iteratorSession2BigAllow = graffiti.discover(
798
- [object.channels[0], object.channels[2]],
799
- {
800
- properties: {
801
- channels: {
802
- minItems: 3
803
- }
804
- }
805
- },
806
- session2
807
- );
808
- await expect2(iteratorSession2BigAllow.next()).resolves.toHaveProperty(
809
- "done",
810
- true
811
- );
812
- const iteratorSession2PeekOther = graffiti.discover(
813
- [object.channels[0], object.channels[2]],
814
- {
815
- properties: {
816
- channels: {
817
- not: {
818
- items: {
819
- not: {
820
- enum: [object.channels[1]]
821
- }
822
- }
823
- }
824
- }
825
- }
826
- },
827
- session2
828
- );
829
- await expect2(iteratorSession2PeekOther.next()).resolves.toHaveProperty(
830
- "done",
831
- true
832
- );
833
- const iteratorSession2SmallAllowPeekSelf = graffiti.discover(
834
- [object.channels[0], object.channels[2]],
835
- {
836
- properties: {
837
- allowed: {
838
- maxItems: 2,
839
- not: {
840
- items: {
841
- not: {
842
- enum: [object.channels[2]]
843
- }
844
- }
845
- }
846
- }
847
- }
848
- },
849
- session2
850
- );
851
- const value2 = await nextStreamValue(
852
- iteratorSession2SmallAllowPeekSelf
853
- );
854
- expect2(value2.value).toEqual(object.value);
855
- await expect2(
856
- iteratorSession2SmallAllowPeekSelf.next()
857
- ).resolves.toHaveProperty("done", true);
858
- });
859
- it2("discover query for empty allowed", async () => {
860
- const publicO = randomPutObject();
861
- const publicSchema = {
862
- not: {
863
- required: ["allowed"]
864
- }
865
- };
866
- await graffiti.put(publicO, session1);
867
- const iterator = graffiti.discover(
868
- publicO.channels,
869
- publicSchema,
870
- session1
871
- );
872
- const value = await nextStreamValue(iterator);
873
- expect2(value.value).toEqual(publicO.value);
874
- expect2(value.allowed).toBeUndefined();
875
- await expect2(iterator.next()).resolves.toHaveProperty("done", true);
876
- const restricted = randomPutObject();
877
- restricted.allowed = [];
878
- await graffiti.put(restricted, session1);
879
- const iterator2 = graffiti.discover(
880
- restricted.channels,
881
- publicSchema,
882
- session1
883
- );
884
- await expect2(iterator2.next()).resolves.toHaveProperty("done", true);
885
- });
886
- it2("discover query for values", async () => {
887
- const object1 = randomPutObject();
888
- object1.value = { test: randomString() };
889
- await graffiti.put(object1, session);
890
- const object2 = randomPutObject();
891
- object2.channels = object1.channels;
892
- object2.value = { test: randomString(), something: randomString() };
893
- await graffiti.put(object2, session);
894
- const object3 = randomPutObject();
895
- object3.channels = object1.channels;
896
- object3.value = { other: randomString(), something: randomString() };
897
- await graffiti.put(object3, session);
898
- const counts = /* @__PURE__ */ new Map();
899
- for (const property of ["test", "something", "other"]) {
900
- let count = 0;
901
- for await (const result of graffiti.discover(object1.channels, {
902
- properties: {
903
- value: {
904
- required: [property]
905
- }
906
- }
907
- })) {
908
- assert2(!result.error, "result has error");
909
- if (property in result.object.value) {
910
- count++;
911
- }
912
- }
913
- counts.set(property, count);
914
- }
915
- expect2(counts.get("test")).toBe(2);
916
- expect2(counts.get("something")).toBe(2);
917
- expect2(counts.get("other")).toBe(1);
918
- });
919
- for (const continueType of ["cursor", "continue"]) {
920
- describe2(`continue discover with ${continueType}`, () => {
921
- it2("discover for deleted content", async () => {
922
- const object = randomPutObject();
923
- const putted = await graffiti.put(object, session);
924
- const iterator1 = graffiti.discover(object.channels, {});
925
- const value1 = await nextStreamValue(iterator1);
926
- expect2(value1.value).toEqual(object.value);
927
- const returnValue = await iterator1.next();
928
- assert2(returnValue.done, "value2 is not done");
929
- const deleted = await graffiti.delete(putted, session);
930
- const iterator = graffiti.discover(object.channels, {});
931
- await expect2(iterator.next()).resolves.toHaveProperty("done", true);
932
- const tombIterator = continueStream(
933
- graffiti,
934
- returnValue.value,
935
- continueType
936
- );
937
- const value = await tombIterator.next();
938
- assert2(!value.done && !value.value.error, "value is done");
939
- assert2(value.value.tombstone, "value is not tombstone");
940
- expect2(value.value.object.url).toEqual(putted.url);
941
- await expect2(tombIterator.next()).resolves.toHaveProperty(
942
- "done",
943
- true
944
- );
945
- });
946
- it2("discover for replaced channels", async () => {
947
- async function runTest() {
948
- const object1 = randomPutObject();
949
- const putted = await graffiti.put(object1, session);
950
- const iterator3 = graffiti.discover(object1.channels, {});
951
- const value3 = await nextStreamValue(iterator3);
952
- expect2(value3.value).toEqual(object1.value);
953
- const returnValue = await iterator3.next();
954
- assert2(returnValue.done, "value2 is not done");
955
- const object2 = randomPutObject();
956
- const replaced = await graffiti.put(
957
- {
958
- ...object2,
959
- url: putted.url
960
- },
961
- session
962
- );
963
- const iterator1 = graffiti.discover(object1.channels, {});
964
- const iterator2 = graffiti.discover(object2.channels, {});
965
- const tombIterator = continueStream(
966
- graffiti,
967
- returnValue.value,
968
- continueType
969
- );
970
- if (putted.lastModified === replaced.lastModified) {
971
- const value1 = await iterator1.next();
972
- const value22 = await iterator2.next();
973
- const value32 = await tombIterator.next();
974
- expect2(value1.done || value22.done).toBe(true);
975
- expect2(value1.done && value22.done).toBe(false);
976
- assert2(!value32.done && !value32.value.error, "value is done");
977
- expect2(value32.value.tombstone || value22.done).toBe(true);
978
- expect2(value32.value.tombstone && value22.done).toBe(false);
979
- return;
980
- }
981
- const value5 = await iterator1.next();
982
- assert2(value5.done, "value5 is not done");
983
- const value4 = await tombIterator.next();
984
- assert2(!value4.done && !value4.value.error, "value is done");
985
- assert2(value4.value.tombstone, "value is not tombstone");
986
- expect2(value4.value.object.url).toEqual(putted.url);
987
- expect2(value4.value.object.lastModified).toEqual(
988
- replaced.lastModified
989
- );
990
- const value2 = await nextStreamValue(iterator2);
991
- await expect2(iterator2.next()).resolves.toHaveProperty(
992
- "done",
993
- true
994
- );
995
- expect2(value2.value).toEqual(object2.value);
996
- expect2(value2.channels).toEqual(object2.channels);
997
- expect2(value2.lastModified).toEqual(replaced.lastModified);
998
- const patched = await graffiti.patch(
999
- {
1000
- channels: [
1001
- { op: "replace", path: "", value: object1.channels }
1002
- ]
1003
- },
1004
- replaced,
1005
- session
1006
- );
1007
- const tombIterator2 = continueStream(
1008
- graffiti,
1009
- value5.value,
1010
- continueType
1011
- );
1012
- let result;
1013
- for await (const value of tombIterator2) {
1014
- if (value.error) continue;
1015
- if (!result) {
1016
- result = value;
1017
- continue;
1018
- }
1019
- if (value.object.lastModified > result.object.lastModified || value.object.lastModified === result.object.lastModified && !value.tombstone && result.tombstone) {
1020
- result = value;
1021
- }
1022
- }
1023
- assert2(result, "result is not defined");
1024
- assert2(!result.tombstone, "result is tombstone");
1025
- expect2(result.object.url).toEqual(replaced.url);
1026
- expect2(result.object.lastModified).toEqual(patched.lastModified);
1027
- expect2(result.object.channels).toEqual(object1.channels);
1028
- expect2(result.object.value).toEqual(object2.value);
1029
- }
1030
- await Promise.allSettled(Array.from({ length: 20 }, () => runTest()));
1031
- });
1032
- it2("discover for patched allowed", async () => {
1033
- const object = randomPutObject();
1034
- const putted = await graffiti.put(object, session);
1035
- const iterator1 = graffiti.discover(object.channels, {});
1036
- const value1 = await nextStreamValue(iterator1);
1037
- expect2(value1.value).toEqual(object.value);
1038
- const returnValue = await iterator1.next();
1039
- assert2(returnValue.done, "value2 is not done");
1040
- await graffiti.patch(
1041
- {
1042
- allowed: [{ op: "add", path: "", value: [] }]
1043
- },
1044
- putted,
1045
- session
1046
- );
1047
- const iterator2 = graffiti.discover(object.channels, {});
1048
- expect2(await iterator2.next()).toHaveProperty("done", true);
1049
- const iterator = continueStream(
1050
- graffiti,
1051
- returnValue.value,
1052
- continueType
1053
- );
1054
- const value = await iterator.next();
1055
- assert2(!value.done && !value.value.error, "value is done");
1056
- assert2(value.value.tombstone, "value is not tombstone");
1057
- expect2(value.value.object.url).toEqual(putted.url);
1058
- await expect2(iterator.next()).resolves.toHaveProperty("done", true);
1059
- });
1060
- it2("put concurrently and discover one", async () => {
1061
- const object = randomPutObject();
1062
- const putted = await graffiti.put(object, session);
1063
- const putPromises = Array(99).fill(0).map(
1064
- () => graffiti.put(
1065
- {
1066
- ...object,
1067
- url: putted.url
1068
- },
1069
- session
1070
- )
1071
- );
1072
- await Promise.all(putPromises);
1073
- const iterator = graffiti.discover(object.channels, {});
1074
- let tombstoneCount = 0;
1075
- let valueCount = 0;
1076
- for await (const result of iterator) {
1077
- assert2(!result.error, "result has error");
1078
- if (result.tombstone) {
1079
- tombstoneCount++;
1080
- } else {
1081
- valueCount++;
1082
- }
1083
- }
1084
- expect2(tombstoneCount).toBe(0);
1085
- expect2(valueCount).toBe(1);
1086
- });
1087
- });
1088
- }
1089
- });
1090
- };
1091
-
1092
- // tests/orphans.ts
1093
- import { it as it3, expect as expect3, describe as describe3, assert as assert3, beforeAll as beforeAll3 } from "vitest";
1094
- var graffitiOrphanTests = (useGraffiti, useSession1, useSession2) => {
1095
- describe3("recoverOrphans", { timeout: 2e4 }, () => {
1096
- let graffiti;
1097
- let session;
1098
- let session1;
1099
- let session2;
1100
- beforeAll3(async () => {
1101
- graffiti = useGraffiti();
1102
- session1 = await useSession1();
1103
- session = session1;
1104
- session2 = await useSession2();
1105
- });
1106
- it3("list orphans", async () => {
1107
- const existingOrphans = [];
1108
- const orphanIterator1 = graffiti.recoverOrphans({}, session);
1109
- for await (const orphan of orphanIterator1) {
1110
- if (orphan.error) continue;
1111
- existingOrphans.push(orphan.object.url);
1112
- }
1113
- const object = randomPutObject();
1114
- object.channels = [];
1115
- const putted = await graffiti.put(object, session);
1116
- const orphanIterator2 = graffiti.recoverOrphans({}, session);
1117
- let numResults = 0;
1118
- for await (const orphan of orphanIterator2) {
1119
- if (orphan.error) continue;
1120
- assert3(!orphan.tombstone, "orphan is tombstone");
1121
- if (orphan.object.url === putted.url) {
1122
- numResults++;
1123
- expect3(orphan.object.lastModified).toBe(putted.lastModified);
1124
- }
1125
- }
1126
- expect3(numResults).toBe(1);
1127
- });
1128
- for (const continueType of ["continue", "cursor"]) {
1129
- describe3(`continue orphans with ${continueType}`, () => {
1130
- it3("replaced orphan, no longer", async () => {
1131
- const object = randomPutObject();
1132
- object.channels = [];
1133
- const putOrphan = await graffiti.put(object, session);
1134
- await new Promise((resolve) => setTimeout(resolve, 10));
1135
- expect3(Object.keys(object.value).length).toBeGreaterThanOrEqual(1);
1136
- expect3(Object.keys(object.value)[0]).toBeTypeOf("string");
1137
- const iterator1 = graffiti.recoverOrphans(
1138
- {
1139
- properties: {
1140
- value: {
1141
- properties: {
1142
- [Object.keys(object.value)[0]]: {
1143
- type: "string"
1144
- }
1145
- },
1146
- required: [Object.keys(object.value)[0]]
1147
- }
1148
- }
1149
- },
1150
- session
1151
- );
1152
- const value1 = await nextStreamValue(iterator1);
1153
- expect3(value1.value).toEqual(object.value);
1154
- const returnValue = await iterator1.next();
1155
- assert3(returnValue.done, "value2 is not done");
1156
- const putNotOrphan = await graffiti.put(
1157
- {
1158
- ...putOrphan,
1159
- ...object,
1160
- channels: [randomString()]
1161
- },
1162
- session
1163
- );
1164
- expect3(putNotOrphan.url).toBe(putOrphan.url);
1165
- expect3(putNotOrphan.lastModified).toBeGreaterThan(
1166
- putOrphan.lastModified
1167
- );
1168
- const orphanIterator = graffiti.recoverOrphans({}, session);
1169
- let numResults = 0;
1170
- for await (const orphan of orphanIterator) {
1171
- if (orphan.error) continue;
1172
- if (orphan.object.url === putOrphan.url) {
1173
- numResults++;
1174
- }
1175
- }
1176
- expect3(numResults).toBe(0);
1177
- const iterator2 = continueStream(
1178
- graffiti,
1179
- returnValue.value,
1180
- continueType,
1181
- session
1182
- );
1183
- const value2 = await iterator2.next();
1184
- assert3(
1185
- !value2.done && !value2.value.error,
1186
- "value2 is done or has error"
1187
- );
1188
- assert3(value2.value.tombstone, "value2 is not tombstone");
1189
- expect3(value2.value.object.url).toBe(putOrphan.url);
1190
- await expect3(iterator2.next()).resolves.toHaveProperty("done", true);
1191
- });
1192
- });
1193
- }
1194
- });
1195
- };
1196
-
1197
- // tests/channel-stats.ts
1198
- import { it as it4, expect as expect4, describe as describe4, assert as assert4, beforeAll as beforeAll4 } from "vitest";
1199
- var graffitiChannelStatsTests = (useGraffiti, useSession1, useSession2) => {
1200
- describe4("channel stats", { timeout: 2e4 }, () => {
1201
- let graffiti;
1202
- let session;
1203
- let session1;
1204
- let session2;
1205
- beforeAll4(async () => {
1206
- graffiti = useGraffiti();
1207
- session1 = await useSession1();
1208
- session = session1;
1209
- session2 = await useSession2();
1210
- });
1211
- it4("list channels", async () => {
1212
- const existingChannels = /* @__PURE__ */ new Map();
1213
- const channelIterator1 = graffiti.channelStats(session);
1214
- for await (const channel of channelIterator1) {
1215
- if (channel.error) continue;
1216
- existingChannels.set(channel.value.channel, channel.value.count);
1217
- }
1218
- const channels = [randomString(), randomString(), randomString()];
1219
- for (let i = 0; i < 3; i++) {
1220
- for (let j = 0; j < i + 1; j++) {
1221
- await graffiti.put(
1222
- {
1223
- value: {
1224
- index: j
1225
- },
1226
- channels: channels.slice(0, i + 1)
1227
- },
1228
- session
1229
- );
1230
- }
1231
- }
1232
- await graffiti.put(
1233
- { value: { index: 3 }, channels: [channels[2]] },
1234
- session
1235
- );
1236
- const channelIterator2 = graffiti.channelStats(session);
1237
- let newChannels = /* @__PURE__ */ new Map();
1238
- for await (const channel of channelIterator2) {
1239
- if (channel.error) continue;
1240
- newChannels.set(channel.value.channel, channel.value.count);
1241
- }
1242
- newChannels = new Map(
1243
- Array.from(newChannels).filter(
1244
- ([channel, count]) => !existingChannels.has(channel)
1245
- )
1246
- );
1247
- expect4(newChannels.size).toBe(3);
1248
- expect4(newChannels.get(channels[0])).toBe(6);
1249
- expect4(newChannels.get(channels[1])).toBe(5);
1250
- expect4(newChannels.get(channels[2])).toBe(4);
1251
- });
1252
- it4("list channels with deleted channel", async () => {
1253
- const channels = [randomString(), randomString(), randomString()];
1254
- const before = await graffiti.put(
1255
- {
1256
- value: { index: 2 },
1257
- channels: channels.slice(1)
1258
- },
1259
- session
1260
- );
1261
- const first = await graffiti.put(
1262
- { value: { index: 0 }, channels },
1263
- session
1264
- );
1265
- await graffiti.delete(first, session);
1266
- const second = await graffiti.put(
1267
- {
1268
- value: { index: 1 },
1269
- channels: channels.slice(2)
1270
- },
1271
- session
1272
- );
1273
- const channelIterator = graffiti.channelStats(session);
1274
- let got1 = 0;
1275
- let got2 = 0;
1276
- for await (const result of channelIterator) {
1277
- if (result.error) continue;
1278
- const { channel, count, lastModified } = result.value;
1279
- assert4(
1280
- channel !== channels[0],
1281
- "There should not be an object in channel[0]"
1282
- );
1283
- if (channel === channels[1]) {
1284
- expect4(count).toBe(1);
1285
- expect4(lastModified).toBe(before.lastModified);
1286
- got1++;
1287
- } else if (channel === channels[2]) {
1288
- expect4(count).toBe(2);
1289
- expect4(lastModified).toBe(second.lastModified);
1290
- got2++;
1291
- }
1292
- }
1293
- expect4(got1).toBe(1);
1294
- expect4(got2).toBe(1);
1295
- });
1296
- });
1297
- };
1298
36
  export {
1299
37
  continueStream,
1300
- graffitiCRUDTests,
1301
- graffitiChannelStatsTests,
1302
- graffitiDiscoverTests,
1303
- graffitiOrphanTests,
1304
38
  nextStreamValue,
1305
- randomPutObject,
39
+ randomPostObject,
1306
40
  randomString,
1307
41
  randomValue
1308
42
  };