@graffiti-garden/api 0.6.4 → 1.0.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/README.md +0 -7
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +4 -4
- package/dist/src/1-api.d.ts +172 -178
- package/dist/src/1-api.d.ts.map +1 -1
- package/dist/src/2-types.d.ts +39 -152
- package/dist/src/2-types.d.ts.map +1 -1
- package/dist/src/3-errors.d.ts +2 -8
- package/dist/src/3-errors.d.ts.map +1 -1
- package/dist/src/4-utilities.d.ts +8 -0
- package/dist/src/4-utilities.d.ts.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/tests/crud.d.ts +1 -1
- package/dist/tests/crud.d.ts.map +1 -1
- package/dist/tests/discover.d.ts +1 -1
- package/dist/tests/discover.d.ts.map +1 -1
- package/dist/tests/index.d.ts +1 -2
- package/dist/tests/index.d.ts.map +1 -1
- package/dist/tests/media.d.ts +3 -0
- package/dist/tests/media.d.ts.map +1 -0
- package/dist/tests/utils.d.ts +3 -3
- package/dist/tests/utils.d.ts.map +1 -1
- package/dist/tests.mjs +207 -790
- package/dist/tests.mjs.map +4 -4
- package/package.json +6 -6
- package/src/1-api.ts +182 -186
- package/src/2-types.ts +42 -157
- package/src/3-errors.ts +6 -22
- package/src/4-utilities.ts +65 -0
- package/src/index.ts +1 -0
- package/tests/crud.ts +39 -332
- package/tests/discover.ts +51 -349
- package/tests/index.ts +1 -2
- package/tests/media.ts +158 -0
- package/tests/utils.ts +4 -4
- package/dist/tests/channel-stats.d.ts +0 -3
- package/dist/tests/channel-stats.d.ts.map +0 -1
- package/dist/tests/orphans.d.ts +0 -3
- package/dist/tests/orphans.d.ts.map +0 -1
- package/tests/channel-stats.ts +0 -129
- package/tests/orphans.ts +0 -128
package/tests/media.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GraffitiErrorNotAcceptable,
|
|
3
|
+
GraffitiErrorNotFound,
|
|
4
|
+
GraffitiErrorTooLarge,
|
|
5
|
+
type Graffiti,
|
|
6
|
+
type GraffitiSession,
|
|
7
|
+
} from "@graffiti-garden/api";
|
|
8
|
+
import { it, expect, describe, beforeAll } from "vitest";
|
|
9
|
+
import { randomString } from "./utils";
|
|
10
|
+
|
|
11
|
+
export const graffitiMediaTests = (
|
|
12
|
+
useGraffiti: () => Pick<Graffiti, "postMedia" | "getMedia" | "deleteMedia">,
|
|
13
|
+
useSession1: () => GraffitiSession | Promise<GraffitiSession>,
|
|
14
|
+
useSession2: () => GraffitiSession | Promise<GraffitiSession>,
|
|
15
|
+
) => {
|
|
16
|
+
describe.concurrent(
|
|
17
|
+
"media",
|
|
18
|
+
{
|
|
19
|
+
timeout: 20000,
|
|
20
|
+
},
|
|
21
|
+
() => {
|
|
22
|
+
let graffiti: ReturnType<typeof useGraffiti>;
|
|
23
|
+
let session: GraffitiSession;
|
|
24
|
+
let session1: GraffitiSession;
|
|
25
|
+
let session2: GraffitiSession;
|
|
26
|
+
beforeAll(async () => {
|
|
27
|
+
graffiti = useGraffiti();
|
|
28
|
+
session1 = await useSession1();
|
|
29
|
+
session = session1;
|
|
30
|
+
session2 = await useSession2();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("post, get, delete media", async () => {
|
|
34
|
+
// Post media
|
|
35
|
+
const text = randomString();
|
|
36
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
37
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
38
|
+
|
|
39
|
+
// Get the media back
|
|
40
|
+
const media = await graffiti.getMedia(mediaUrl, {});
|
|
41
|
+
expect(await media.data.text()).toEqual(text);
|
|
42
|
+
expect(media.data.type).toEqual("text/plain");
|
|
43
|
+
expect(media.allowed).toBeUndefined();
|
|
44
|
+
expect(media.actor).toBe(session.actor);
|
|
45
|
+
|
|
46
|
+
// Delete the media
|
|
47
|
+
await graffiti.deleteMedia(mediaUrl, session);
|
|
48
|
+
|
|
49
|
+
// Try to get the media again
|
|
50
|
+
await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(
|
|
51
|
+
GraffitiErrorNotFound,
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("acceptable type", async () => {
|
|
56
|
+
const text = randomString();
|
|
57
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
58
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
59
|
+
|
|
60
|
+
const media = await graffiti.getMedia(mediaUrl, {
|
|
61
|
+
accept: "text/*",
|
|
62
|
+
});
|
|
63
|
+
expect(await media.data.text()).toEqual(text);
|
|
64
|
+
expect(media.data.type).toEqual("text/plain");
|
|
65
|
+
expect(media.allowed).toBeUndefined();
|
|
66
|
+
expect(media.actor).toBe(session.actor);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("unacceptable type", async () => {
|
|
70
|
+
const text = randomString();
|
|
71
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
72
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
73
|
+
|
|
74
|
+
await expect(
|
|
75
|
+
graffiti.getMedia(mediaUrl, {
|
|
76
|
+
accept: "image/*",
|
|
77
|
+
}),
|
|
78
|
+
).rejects.toThrow(GraffitiErrorNotAcceptable);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("acceptable size", async () => {
|
|
82
|
+
const text = randomString();
|
|
83
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
84
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
85
|
+
|
|
86
|
+
const media = await graffiti.getMedia(mediaUrl, {
|
|
87
|
+
maxBytes: data.size,
|
|
88
|
+
});
|
|
89
|
+
expect(await media.data.text()).toEqual(text);
|
|
90
|
+
expect(media.data.type).toEqual("text/plain");
|
|
91
|
+
expect(media.allowed).toBeUndefined();
|
|
92
|
+
expect(media.actor).toBe(session.actor);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("unacceptable size", async () => {
|
|
96
|
+
const text = randomString();
|
|
97
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
98
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
99
|
+
|
|
100
|
+
await expect(
|
|
101
|
+
graffiti.getMedia(mediaUrl, {
|
|
102
|
+
maxBytes: data.size - 1,
|
|
103
|
+
}),
|
|
104
|
+
).rejects.toThrow(GraffitiErrorTooLarge);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("empty allowed", async () => {
|
|
108
|
+
const text = randomString();
|
|
109
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
110
|
+
const allowed: string[] = [];
|
|
111
|
+
const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);
|
|
112
|
+
|
|
113
|
+
// Get it with the authorized user
|
|
114
|
+
const media = await graffiti.getMedia(mediaUrl, {}, session1);
|
|
115
|
+
expect(await media.data.text()).toEqual(text);
|
|
116
|
+
expect(media.data.type).toEqual("text/plain");
|
|
117
|
+
expect(media.allowed).toEqual([]);
|
|
118
|
+
expect(media.actor).toBe(session1.actor);
|
|
119
|
+
|
|
120
|
+
// Get it with the unauthorized user
|
|
121
|
+
await expect(graffiti.getMedia(mediaUrl, {}, session2)).rejects.toThrow(
|
|
122
|
+
GraffitiErrorNotFound,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Get it without authorization
|
|
126
|
+
await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(
|
|
127
|
+
GraffitiErrorNotFound,
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("allowed", async () => {
|
|
132
|
+
const text = randomString();
|
|
133
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
134
|
+
const allowed = [randomString(), session2.actor, randomString()];
|
|
135
|
+
const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);
|
|
136
|
+
|
|
137
|
+
// Get it with the authorized user
|
|
138
|
+
const media = await graffiti.getMedia(mediaUrl, {}, session1);
|
|
139
|
+
expect(await media.data.text()).toEqual(text);
|
|
140
|
+
expect(media.data.type).toEqual("text/plain");
|
|
141
|
+
expect(media.allowed).toEqual(allowed);
|
|
142
|
+
expect(media.actor).toBe(session1.actor);
|
|
143
|
+
|
|
144
|
+
// Get it with the allowed user
|
|
145
|
+
const media2 = await graffiti.getMedia(mediaUrl, {}, session2);
|
|
146
|
+
expect(await media2.data.text()).toEqual(text);
|
|
147
|
+
expect(media2.data.type).toEqual("text/plain");
|
|
148
|
+
expect(media2.allowed).toEqual([session2.actor]);
|
|
149
|
+
expect(media2.actor).toBe(session1.actor);
|
|
150
|
+
|
|
151
|
+
// Get it without authorization
|
|
152
|
+
await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(
|
|
153
|
+
GraffitiErrorNotFound,
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
};
|
package/tests/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assert } from "vitest";
|
|
2
2
|
import type {
|
|
3
|
-
|
|
3
|
+
GraffitiPostObject,
|
|
4
4
|
GraffitiObjectStream,
|
|
5
5
|
JSONSchema,
|
|
6
6
|
GraffitiObject,
|
|
@@ -27,7 +27,7 @@ export function randomValue() {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export function
|
|
30
|
+
export function randomPostObject(): GraffitiPostObject<{}> {
|
|
31
31
|
return {
|
|
32
32
|
value: randomValue(),
|
|
33
33
|
channels: [randomString(), randomString()],
|
|
@@ -44,13 +44,13 @@ export async function nextStreamValue<Schema extends JSONSchema>(
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export function continueStream<Schema extends JSONSchema>(
|
|
47
|
-
graffiti: Pick<Graffiti, "
|
|
47
|
+
graffiti: Pick<Graffiti, "continueDiscover">,
|
|
48
48
|
streamReturn: GraffitiObjectStreamReturn<Schema>,
|
|
49
49
|
type: "cursor" | "continue",
|
|
50
50
|
session?: GraffitiSession | null,
|
|
51
51
|
): GraffitiObjectStreamContinue<Schema> {
|
|
52
52
|
if (type === "cursor") {
|
|
53
|
-
return graffiti.
|
|
53
|
+
return graffiti.continueDiscover(
|
|
54
54
|
streamReturn.cursor,
|
|
55
55
|
session,
|
|
56
56
|
) as unknown as GraffitiObjectStreamContinue<Schema>;
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { Graffiti, GraffitiSession } from "@graffiti-garden/api";
|
|
2
|
-
export declare const graffitiChannelStatsTests: (useGraffiti: () => Pick<Graffiti, "channelStats" | "put" | "delete" | "patch">, useSession1: () => GraffitiSession | Promise<GraffitiSession>, useSession2: () => GraffitiSession | Promise<GraffitiSession>) => void;
|
|
3
|
-
//# sourceMappingURL=channel-stats.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"channel-stats.d.ts","sourceRoot":"","sources":["../../tests/channel-stats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGtE,eAAO,MAAM,yBAAyB,GACpC,aAAa,MAAM,IAAI,CACrB,QAAQ,EACR,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAC5C,EACD,aAAa,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,EAC7D,aAAa,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,SAsH9D,CAAC"}
|
package/dist/tests/orphans.d.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { Graffiti, GraffitiSession } from "@graffiti-garden/api";
|
|
2
|
-
export declare const graffitiOrphanTests: (useGraffiti: () => Pick<Graffiti, "recoverOrphans" | "put" | "delete" | "patch" | "continueObjectStream">, useSession1: () => GraffitiSession | Promise<GraffitiSession>, useSession2: () => GraffitiSession | Promise<GraffitiSession>) => void;
|
|
3
|
-
//# sourceMappingURL=orphans.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"orphans.d.ts","sourceRoot":"","sources":["../../tests/orphans.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAQtE,eAAO,MAAM,mBAAmB,GAC9B,aAAa,MAAM,IAAI,CACrB,QAAQ,EACR,gBAAgB,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,sBAAsB,CACvE,EACD,aAAa,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,EAC7D,aAAa,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,SAgH9D,CAAC"}
|
package/tests/channel-stats.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { it, expect, describe, assert, beforeAll } from "vitest";
|
|
2
|
-
import type { Graffiti, GraffitiSession } from "@graffiti-garden/api";
|
|
3
|
-
import { randomString } from "./utils";
|
|
4
|
-
|
|
5
|
-
export const graffitiChannelStatsTests = (
|
|
6
|
-
useGraffiti: () => Pick<
|
|
7
|
-
Graffiti,
|
|
8
|
-
"channelStats" | "put" | "delete" | "patch"
|
|
9
|
-
>,
|
|
10
|
-
useSession1: () => GraffitiSession | Promise<GraffitiSession>,
|
|
11
|
-
useSession2: () => GraffitiSession | Promise<GraffitiSession>,
|
|
12
|
-
) => {
|
|
13
|
-
describe("channel stats", { timeout: 20000 }, () => {
|
|
14
|
-
let graffiti: ReturnType<typeof useGraffiti>;
|
|
15
|
-
let session: GraffitiSession;
|
|
16
|
-
let session1: GraffitiSession;
|
|
17
|
-
let session2: GraffitiSession;
|
|
18
|
-
beforeAll(async () => {
|
|
19
|
-
graffiti = useGraffiti();
|
|
20
|
-
session1 = await useSession1();
|
|
21
|
-
session = session1;
|
|
22
|
-
session2 = await useSession2();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("list channels", async () => {
|
|
26
|
-
const existingChannels: Map<string, number> = new Map();
|
|
27
|
-
const channelIterator1 = graffiti.channelStats(session);
|
|
28
|
-
for await (const channel of channelIterator1) {
|
|
29
|
-
if (channel.error) continue;
|
|
30
|
-
existingChannels.set(channel.value.channel, channel.value.count);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const channels = [randomString(), randomString(), randomString()];
|
|
34
|
-
|
|
35
|
-
// Add one value to channels[0],
|
|
36
|
-
// two values to both channels[0] and channels[1],
|
|
37
|
-
// three values to all channels
|
|
38
|
-
// one value to channels[2]
|
|
39
|
-
for (let i = 0; i < 3; i++) {
|
|
40
|
-
for (let j = 0; j < i + 1; j++) {
|
|
41
|
-
await graffiti.put<{}>(
|
|
42
|
-
{
|
|
43
|
-
value: {
|
|
44
|
-
index: j,
|
|
45
|
-
},
|
|
46
|
-
channels: channels.slice(0, i + 1),
|
|
47
|
-
},
|
|
48
|
-
session,
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
await graffiti.put<{}>(
|
|
53
|
-
{ value: { index: 3 }, channels: [channels[2]] },
|
|
54
|
-
session,
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const channelIterator2 = graffiti.channelStats(session);
|
|
58
|
-
let newChannels: Map<string, number> = new Map();
|
|
59
|
-
for await (const channel of channelIterator2) {
|
|
60
|
-
if (channel.error) continue;
|
|
61
|
-
newChannels.set(channel.value.channel, channel.value.count);
|
|
62
|
-
}
|
|
63
|
-
// Filter out existing channels
|
|
64
|
-
newChannels = new Map(
|
|
65
|
-
Array.from(newChannels).filter(
|
|
66
|
-
([channel, count]) => !existingChannels.has(channel),
|
|
67
|
-
),
|
|
68
|
-
);
|
|
69
|
-
expect(newChannels.size).toBe(3);
|
|
70
|
-
expect(newChannels.get(channels[0])).toBe(6);
|
|
71
|
-
expect(newChannels.get(channels[1])).toBe(5);
|
|
72
|
-
expect(newChannels.get(channels[2])).toBe(4);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("list channels with deleted channel", async () => {
|
|
76
|
-
const channels = [randomString(), randomString(), randomString()];
|
|
77
|
-
|
|
78
|
-
// Add an item with two channels
|
|
79
|
-
const before = await graffiti.put<{}>(
|
|
80
|
-
{
|
|
81
|
-
value: { index: 2 },
|
|
82
|
-
channels: channels.slice(1),
|
|
83
|
-
},
|
|
84
|
-
session,
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
// Add an item with all channels
|
|
88
|
-
const first = await graffiti.put<{}>(
|
|
89
|
-
{ value: { index: 0 }, channels },
|
|
90
|
-
session,
|
|
91
|
-
);
|
|
92
|
-
// But then delete it
|
|
93
|
-
await graffiti.delete(first, session);
|
|
94
|
-
|
|
95
|
-
// Create a new object with only one channel
|
|
96
|
-
const second = await graffiti.put<{}>(
|
|
97
|
-
{
|
|
98
|
-
value: { index: 1 },
|
|
99
|
-
channels: channels.slice(2),
|
|
100
|
-
},
|
|
101
|
-
session,
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const channelIterator = graffiti.channelStats(session);
|
|
105
|
-
|
|
106
|
-
let got1 = 0;
|
|
107
|
-
let got2 = 0;
|
|
108
|
-
for await (const result of channelIterator) {
|
|
109
|
-
if (result.error) continue;
|
|
110
|
-
const { channel, count, lastModified } = result.value;
|
|
111
|
-
assert(
|
|
112
|
-
channel !== channels[0],
|
|
113
|
-
"There should not be an object in channel[0]",
|
|
114
|
-
);
|
|
115
|
-
if (channel === channels[1]) {
|
|
116
|
-
expect(count).toBe(1);
|
|
117
|
-
expect(lastModified).toBe(before.lastModified);
|
|
118
|
-
got1++;
|
|
119
|
-
} else if (channel === channels[2]) {
|
|
120
|
-
expect(count).toBe(2);
|
|
121
|
-
expect(lastModified).toBe(second.lastModified);
|
|
122
|
-
got2++;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
expect(got1).toBe(1);
|
|
126
|
-
expect(got2).toBe(1);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
};
|
package/tests/orphans.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { it, expect, describe, assert, beforeAll } from "vitest";
|
|
2
|
-
import type { Graffiti, GraffitiSession } from "@graffiti-garden/api";
|
|
3
|
-
import {
|
|
4
|
-
randomPutObject,
|
|
5
|
-
randomString,
|
|
6
|
-
nextStreamValue,
|
|
7
|
-
continueStream,
|
|
8
|
-
} from "./utils";
|
|
9
|
-
|
|
10
|
-
export const graffitiOrphanTests = (
|
|
11
|
-
useGraffiti: () => Pick<
|
|
12
|
-
Graffiti,
|
|
13
|
-
"recoverOrphans" | "put" | "delete" | "patch" | "continueObjectStream"
|
|
14
|
-
>,
|
|
15
|
-
useSession1: () => GraffitiSession | Promise<GraffitiSession>,
|
|
16
|
-
useSession2: () => GraffitiSession | Promise<GraffitiSession>,
|
|
17
|
-
) => {
|
|
18
|
-
describe("recoverOrphans", { timeout: 20000 }, () => {
|
|
19
|
-
let graffiti: ReturnType<typeof useGraffiti>;
|
|
20
|
-
let session: GraffitiSession;
|
|
21
|
-
let session1: GraffitiSession;
|
|
22
|
-
let session2: GraffitiSession;
|
|
23
|
-
beforeAll(async () => {
|
|
24
|
-
graffiti = useGraffiti();
|
|
25
|
-
session1 = await useSession1();
|
|
26
|
-
session = session1;
|
|
27
|
-
session2 = await useSession2();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("list orphans", async () => {
|
|
31
|
-
const existingOrphans: string[] = [];
|
|
32
|
-
const orphanIterator1 = graffiti.recoverOrphans({}, session);
|
|
33
|
-
for await (const orphan of orphanIterator1) {
|
|
34
|
-
if (orphan.error) continue;
|
|
35
|
-
existingOrphans.push(orphan.object.url);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const object = randomPutObject();
|
|
39
|
-
object.channels = [];
|
|
40
|
-
const putted = await graffiti.put<{}>(object, session);
|
|
41
|
-
const orphanIterator2 = graffiti.recoverOrphans({}, session);
|
|
42
|
-
let numResults = 0;
|
|
43
|
-
for await (const orphan of orphanIterator2) {
|
|
44
|
-
if (orphan.error) continue;
|
|
45
|
-
assert(!orphan.tombstone, "orphan is tombstone");
|
|
46
|
-
if (orphan.object.url === putted.url) {
|
|
47
|
-
numResults++;
|
|
48
|
-
expect(orphan.object.lastModified).toBe(putted.lastModified);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
expect(numResults).toBe(1);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
for (const continueType of ["continue", "cursor"] as const) {
|
|
55
|
-
describe(`continue orphans with ${continueType}`, () => {
|
|
56
|
-
it("replaced orphan, no longer", async () => {
|
|
57
|
-
const object = randomPutObject();
|
|
58
|
-
object.channels = [];
|
|
59
|
-
const putOrphan = await graffiti.put<{}>(object, session);
|
|
60
|
-
|
|
61
|
-
// Wait for the put to be processed
|
|
62
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
63
|
-
|
|
64
|
-
expect(Object.keys(object.value).length).toBeGreaterThanOrEqual(1);
|
|
65
|
-
expect(Object.keys(object.value)[0]).toBeTypeOf("string");
|
|
66
|
-
const iterator1 = graffiti.recoverOrphans<{}>(
|
|
67
|
-
{
|
|
68
|
-
properties: {
|
|
69
|
-
value: {
|
|
70
|
-
properties: {
|
|
71
|
-
[Object.keys(object.value)[0]]: {
|
|
72
|
-
type: "string",
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
required: [Object.keys(object.value)[0]],
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
session,
|
|
80
|
-
);
|
|
81
|
-
const value1 = await nextStreamValue<{}>(iterator1);
|
|
82
|
-
expect(value1.value).toEqual(object.value);
|
|
83
|
-
const returnValue = await iterator1.next();
|
|
84
|
-
assert(returnValue.done, "value2 is not done");
|
|
85
|
-
|
|
86
|
-
const putNotOrphan = await graffiti.put<{}>(
|
|
87
|
-
{
|
|
88
|
-
...putOrphan,
|
|
89
|
-
...object,
|
|
90
|
-
channels: [randomString()],
|
|
91
|
-
},
|
|
92
|
-
session,
|
|
93
|
-
);
|
|
94
|
-
expect(putNotOrphan.url).toBe(putOrphan.url);
|
|
95
|
-
expect(putNotOrphan.lastModified).toBeGreaterThan(
|
|
96
|
-
putOrphan.lastModified,
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// The tombstone will not appear to a fresh iterator
|
|
100
|
-
const orphanIterator = graffiti.recoverOrphans({}, session);
|
|
101
|
-
let numResults = 0;
|
|
102
|
-
for await (const orphan of orphanIterator) {
|
|
103
|
-
if (orphan.error) continue;
|
|
104
|
-
if (orphan.object.url === putOrphan.url) {
|
|
105
|
-
numResults++;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
expect(numResults).toBe(0);
|
|
109
|
-
|
|
110
|
-
const iterator2 = continueStream<{}>(
|
|
111
|
-
graffiti,
|
|
112
|
-
returnValue.value,
|
|
113
|
-
continueType,
|
|
114
|
-
session,
|
|
115
|
-
);
|
|
116
|
-
const value2 = await iterator2.next();
|
|
117
|
-
assert(
|
|
118
|
-
!value2.done && !value2.value.error,
|
|
119
|
-
"value2 is done or has error",
|
|
120
|
-
);
|
|
121
|
-
assert(value2.value.tombstone, "value2 is not tombstone");
|
|
122
|
-
expect(value2.value.object.url).toBe(putOrphan.url);
|
|
123
|
-
await expect(iterator2.next()).resolves.toHaveProperty("done", true);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
};
|