@happy0/flumeview-level-custom 1.0.0 → 2.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/package.json +1 -1
- package/src/flumeView.js +104 -0
- package/src/index.js +6 -163
- package/src/mapperBuilder.js +37 -0
- package/src/metadata.js +16 -0
- package/test/e2e.test.js +236 -0
- package/test/index.test.js +32 -29
- package/vitest.config.ts +1 -0
package/package.json
CHANGED
package/src/flumeView.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const Obv = require("obv");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const charwise = require("charwise");
|
|
4
|
+
const { Level } = require("level");
|
|
5
|
+
const pull = require("pull-stream");
|
|
6
|
+
const { createPullStream } = require("./stream/read");
|
|
7
|
+
const { METADATA_KEY } = require("./metadata");
|
|
8
|
+
|
|
9
|
+
module.exports = function (version, createWriter, filter) {
|
|
10
|
+
return function (log, name) {
|
|
11
|
+
const dir = path.dirname(log.filename);
|
|
12
|
+
|
|
13
|
+
const since = Obv();
|
|
14
|
+
const db = createLevelDb(dir, log, name);
|
|
15
|
+
let closed = false;
|
|
16
|
+
|
|
17
|
+
startConsumingLog(db, since, version);
|
|
18
|
+
|
|
19
|
+
let writer;
|
|
20
|
+
|
|
21
|
+
function destroy(cb) {
|
|
22
|
+
cb = cb || (() => {});
|
|
23
|
+
since.set(-1);
|
|
24
|
+
db.clear().then(cb);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function startConsumingLog(db, since) {
|
|
28
|
+
const value = await db.get(METADATA_KEY, { keyEncoding: "utf8" });
|
|
29
|
+
|
|
30
|
+
if (!value) {
|
|
31
|
+
// No existing entry, we're consuming the log from the start
|
|
32
|
+
since.set(-1);
|
|
33
|
+
} else if (value.version === version) {
|
|
34
|
+
// The version has not changed, resume consuming the log from where we left off
|
|
35
|
+
// last time
|
|
36
|
+
since.set(value.since);
|
|
37
|
+
} else {
|
|
38
|
+
// version has changed, wipe db and start over.
|
|
39
|
+
destroy(() => {});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
// flumeview interface
|
|
45
|
+
methods: { get: "async", read: "source" },
|
|
46
|
+
since: since,
|
|
47
|
+
createSink: function (cb) {
|
|
48
|
+
writer = createWriter(db, since, version, cb);
|
|
49
|
+
|
|
50
|
+
if (!filter) {
|
|
51
|
+
return writer;
|
|
52
|
+
} else {
|
|
53
|
+
return pull(
|
|
54
|
+
pull.filter((item) => filter(item.value)),
|
|
55
|
+
writer,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
destroy: destroy,
|
|
60
|
+
close: function (cb) {
|
|
61
|
+
cb = cb || (() => {});
|
|
62
|
+
|
|
63
|
+
closed = true;
|
|
64
|
+
|
|
65
|
+
if (writer) {
|
|
66
|
+
writer.abort(() => db.close().then(cb));
|
|
67
|
+
} else {
|
|
68
|
+
db.close().then(cb);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
get: function (key, cb) {
|
|
72
|
+
db.get(key).then((result) => {
|
|
73
|
+
if (result === undefined) {
|
|
74
|
+
cb(result);
|
|
75
|
+
} else {
|
|
76
|
+
cb(null, result);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
*
|
|
82
|
+
* @param {*} opts The level DB stream key range options ( see 'level-read-stream' library ) as well as an optional 'live' parameter to indicate whether the
|
|
83
|
+
* stream should stay live an emit live updates. IF live, a {sync:true} object will be emitted when the stream is about to emit
|
|
84
|
+
* live values and each item will have a 'type' field (put, del) indicating whether the database operation type as well as the key
|
|
85
|
+
* and value
|
|
86
|
+
* @returns a pull-stream source of database entries satisfying the input range
|
|
87
|
+
*/
|
|
88
|
+
read: function (opts) {
|
|
89
|
+
return createPullStream(db, opts);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function createLevelDb(dir, log, name) {
|
|
96
|
+
if (!log.filename) {
|
|
97
|
+
throw new Error("flumeview-level-custom can only be used with a file log");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new Level(path.join(dir, name), {
|
|
101
|
+
keyEncoding: charwise,
|
|
102
|
+
valueEncoding: "json",
|
|
103
|
+
});
|
|
104
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,164 +1,7 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const charwise = require("charwise");
|
|
5
|
-
const { Level } = require("level");
|
|
6
|
-
const pull = require("pull-stream");
|
|
7
|
-
const { createPullStream } = require("./stream/read");
|
|
1
|
+
const flumeViewLevelCustom = require("./flumeView");
|
|
2
|
+
const mapperBuilder = require("./mapperBuilder");
|
|
3
|
+
const metadata = require("./metadata");
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {*} version the version of the flumeview we're building. If this value differs to the value stored in the view's metadata, we
|
|
16
|
-
* will rebuild the index from scratch.
|
|
17
|
-
* @param {*} asyncMapper A function that takes a level database and the current flumelog item value as parameters. It should return an array of
|
|
18
|
-
* batch operation objects to be written to the database. It's important that these are returned rather than inserted via the db parameter so that the
|
|
19
|
-
* flumeview metadata can be updated at the same time as the new entries with the current log position atomically.
|
|
20
|
-
* @param {*} filter Optional function to filter out items in the logstream by prior to mapping them to database entries
|
|
21
|
-
* @returns
|
|
22
|
-
*/
|
|
23
|
-
module.exports = function (version, asyncMapper, filter) {
|
|
24
|
-
return function (log, name) {
|
|
25
|
-
const dir = path.dirname(log.filename);
|
|
26
|
-
|
|
27
|
-
const since = Obv();
|
|
28
|
-
const db = createLevelDb(dir, log, name);
|
|
29
|
-
let closed = false;
|
|
30
|
-
|
|
31
|
-
startConsumingLog(db, since, version);
|
|
32
|
-
|
|
33
|
-
let writer;
|
|
34
|
-
|
|
35
|
-
async function mapEntry(item) {
|
|
36
|
-
const items = await asyncMapper(db, item);
|
|
37
|
-
return items;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function createWriter(db, streamEndCb) {
|
|
41
|
-
return Write(
|
|
42
|
-
function write(batch, cb) {
|
|
43
|
-
if (closed) {
|
|
44
|
-
return cb(new Error("database closed while index was building"));
|
|
45
|
-
} else if (batch.length !== 1) {
|
|
46
|
-
// For v1 we're only exposing an interface that allows one item to be processed at a time.
|
|
47
|
-
// For future versions we may consider batching optimisations
|
|
48
|
-
throw new Error(
|
|
49
|
-
"Batch should never have more or less than one item.",
|
|
50
|
-
);
|
|
51
|
-
} else {
|
|
52
|
-
const item = batch[0];
|
|
53
|
-
|
|
54
|
-
const metadata = {
|
|
55
|
-
key: METADATA_KEY,
|
|
56
|
-
value: { version: version, since: item.seq },
|
|
57
|
-
valueEncoding: "json",
|
|
58
|
-
keyEncoding: "utf8",
|
|
59
|
-
type: "put",
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const update = mapEntry(item.value);
|
|
63
|
-
|
|
64
|
-
update
|
|
65
|
-
.then((items) => {
|
|
66
|
-
return db.batch([metadata, ...items]);
|
|
67
|
-
})
|
|
68
|
-
.then((_) => {
|
|
69
|
-
since.set(item.seq);
|
|
70
|
-
cb();
|
|
71
|
-
})
|
|
72
|
-
.catch((e) => console.error(e));
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
null,
|
|
76
|
-
MAX_ITEM_BUFFER_SIZE,
|
|
77
|
-
streamEndCb,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function destroy(cb) {
|
|
82
|
-
cb = cb || (() => {});
|
|
83
|
-
since.set(-1);
|
|
84
|
-
db.clear().then(cb);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function startConsumingLog(db, since) {
|
|
88
|
-
const value = await db.get(METADATA_KEY, { keyEncoding: "utf8" });
|
|
89
|
-
|
|
90
|
-
if (!value) {
|
|
91
|
-
// No existing entry, we're consuming the log from the start
|
|
92
|
-
since.set(-1);
|
|
93
|
-
} else if (value.version === version) {
|
|
94
|
-
// The version has not changed, resume consuming the log from where we left off
|
|
95
|
-
// last time
|
|
96
|
-
since.set(value.since);
|
|
97
|
-
} else {
|
|
98
|
-
// version has changed, wipe db and start over.
|
|
99
|
-
destroy(() => {});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
// flumeview interface
|
|
105
|
-
methods: { get: "async", read: "source" },
|
|
106
|
-
since: since,
|
|
107
|
-
createSink: function (cb) {
|
|
108
|
-
writer = createWriter(db, cb);
|
|
109
|
-
|
|
110
|
-
if (!filter) {
|
|
111
|
-
return writer;
|
|
112
|
-
} else {
|
|
113
|
-
return pull(
|
|
114
|
-
pull.filter((item) => filter(item.value)),
|
|
115
|
-
writer,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
destroy: destroy,
|
|
120
|
-
close: function (cb) {
|
|
121
|
-
cb = cb || (() => {});
|
|
122
|
-
|
|
123
|
-
closed = true;
|
|
124
|
-
|
|
125
|
-
if (writer) {
|
|
126
|
-
writer.abort(() => db.close().then(cb));
|
|
127
|
-
} else {
|
|
128
|
-
db.close().then(cb);
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
get: function (key, cb) {
|
|
132
|
-
db.get(key).then((result) => {
|
|
133
|
-
if (result === undefined) {
|
|
134
|
-
cb(result);
|
|
135
|
-
} else {
|
|
136
|
-
cb(null, result);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
},
|
|
140
|
-
/**
|
|
141
|
-
*
|
|
142
|
-
* @param {*} opts The level DB stream key range options ( see 'level-read-stream' library ) as well as an optional 'live' parameter to indicate whether the
|
|
143
|
-
* stream should stay live an emit live updates. IF live, a {sync:true} object will be emitted when the stream is about to emit
|
|
144
|
-
* live values and each item will have a 'type' field (put, del) indicating whether the database operation type as well as the key
|
|
145
|
-
* and value
|
|
146
|
-
* @returns a pull-stream source of database entries satisfying the input range
|
|
147
|
-
*/
|
|
148
|
-
read: function (opts) {
|
|
149
|
-
return createPullStream(db, opts);
|
|
150
|
-
},
|
|
151
|
-
};
|
|
152
|
-
};
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
function createLevelDb(dir, log, name) {
|
|
156
|
-
if (!log.filename) {
|
|
157
|
-
throw new Error("flumeview-level-custom can only be used with a file log");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return new Level(path.join(dir, name), {
|
|
161
|
-
keyEncoding: charwise,
|
|
162
|
-
valueEncoding: "json",
|
|
163
|
-
});
|
|
164
|
-
}
|
|
5
|
+
module.exports.FlumeViewLevelCustom = flumeViewLevelCustom;
|
|
6
|
+
module.exports.mapperBuilder = mapperBuilder;
|
|
7
|
+
module.exports.metadata = metadata;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const Write = require("pull-write");
|
|
2
|
+
const { createMetadataUpdateItem } = require("./metadata");
|
|
3
|
+
|
|
4
|
+
module.exports.sequentialMapper = function (asyncMapper) {
|
|
5
|
+
const MAX_ITEM_BUFFER_SIZE = 0;
|
|
6
|
+
|
|
7
|
+
return function (db, since, version, streamEndCb) {
|
|
8
|
+
return Write(
|
|
9
|
+
function write(batch, cb) {
|
|
10
|
+
if (batch.length !== 1) {
|
|
11
|
+
cb(
|
|
12
|
+
new Error(
|
|
13
|
+
"Batch should never have more or less than one item for sequential mapper.",
|
|
14
|
+
),
|
|
15
|
+
);
|
|
16
|
+
} else {
|
|
17
|
+
const item = batch[0];
|
|
18
|
+
const metadata = createMetadataUpdateItem(version, item.seq);
|
|
19
|
+
const update = asyncMapper(db, item.value);
|
|
20
|
+
|
|
21
|
+
Promise.resolve(update)
|
|
22
|
+
.then((items) => {
|
|
23
|
+
return db.batch([metadata, ...items]);
|
|
24
|
+
})
|
|
25
|
+
.then((_) => {
|
|
26
|
+
since.set(item.seq);
|
|
27
|
+
cb();
|
|
28
|
+
})
|
|
29
|
+
.catch((e) => console.error(e));
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
null,
|
|
33
|
+
MAX_ITEM_BUFFER_SIZE,
|
|
34
|
+
streamEndCb,
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
};
|
package/src/metadata.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const METADATA_KEY = "\x00";
|
|
2
|
+
|
|
3
|
+
function createMetadataUpdateBatchItem(version, seq) {
|
|
4
|
+
return {
|
|
5
|
+
key: METADATA_KEY,
|
|
6
|
+
value: { version: version, since: seq },
|
|
7
|
+
valueEncoding: "json",
|
|
8
|
+
keyEncoding: "utf8",
|
|
9
|
+
type: "put",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
METADATA_KEY,
|
|
15
|
+
createMetadataUpdateItem: createMetadataUpdateBatchItem,
|
|
16
|
+
};
|
package/test/e2e.test.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
const { FlumeViewCustom, mapperBuilder } = require("../src/index");
|
|
2
|
+
const Flume = require("flumedb");
|
|
3
|
+
const util = require("util");
|
|
4
|
+
const pull = require("pull-stream");
|
|
5
|
+
const Log = require("flumelog-offset");
|
|
6
|
+
const codec = require("flumecodec");
|
|
7
|
+
const { rmSync } = require("fs");
|
|
8
|
+
|
|
9
|
+
function pullCollectPromise(source) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
return pull(
|
|
12
|
+
source,
|
|
13
|
+
pull.collect((err, result) => {
|
|
14
|
+
if (result) {
|
|
15
|
+
resolve(result);
|
|
16
|
+
} else {
|
|
17
|
+
reject(err);
|
|
18
|
+
}
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function chessView(flumeDb) {
|
|
25
|
+
async function chessMapper(db, item) {
|
|
26
|
+
// Simplified - in a really scuttlebutt environment we'd have to deal with invite acceptances / game ends arriving
|
|
27
|
+
// out of order from different user's feeds
|
|
28
|
+
|
|
29
|
+
if (item.value.content.type === "chess_invite") {
|
|
30
|
+
const gameId = item.key;
|
|
31
|
+
|
|
32
|
+
const entry = {
|
|
33
|
+
id: gameId,
|
|
34
|
+
inviter: item.value.author,
|
|
35
|
+
invitee: item.value.content.inviting,
|
|
36
|
+
white:
|
|
37
|
+
item.value.content.myColor === "white"
|
|
38
|
+
? item.value.author
|
|
39
|
+
: item.value.content.inviting,
|
|
40
|
+
black:
|
|
41
|
+
item.value.content.myColor === "black"
|
|
42
|
+
? item.value.author
|
|
43
|
+
: item.value.content.inviting,
|
|
44
|
+
state: "invite",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return [
|
|
48
|
+
{
|
|
49
|
+
type: "put",
|
|
50
|
+
key: `/games/${gameId}`,
|
|
51
|
+
value: entry,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: "put",
|
|
55
|
+
key: `/player/inviter/${entry.inviter}/id/${gameId}`,
|
|
56
|
+
value: entry,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: "put",
|
|
60
|
+
key: `/player/invited/${entry.invitee}/id/${gameId}`,
|
|
61
|
+
value: entry,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
} else if (item.value.content.type === "chess_invite_accept") {
|
|
65
|
+
const gameId = item.value.content.root;
|
|
66
|
+
const existingEntry = await db.get(`/games/${gameId}`);
|
|
67
|
+
|
|
68
|
+
existingEntry.state = "accepted";
|
|
69
|
+
|
|
70
|
+
const deletions = [
|
|
71
|
+
{
|
|
72
|
+
type: "del",
|
|
73
|
+
key: `/player/inviter/${existingEntry.inviter}/id/${gameId}`,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: "del",
|
|
77
|
+
key: `/player/invited/${existingEntry.invitee}/id/${gameId}`,
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const insertions = [
|
|
82
|
+
{ type: "put", key: `/games/${gameId}`, value: existingEntry },
|
|
83
|
+
{
|
|
84
|
+
type: "put",
|
|
85
|
+
key: `/player/live_game/${existingEntry.inviter}/game/${gameId}`,
|
|
86
|
+
value: existingEntry,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: "put",
|
|
90
|
+
key: `/player/live_game/${existingEntry.invitee}/game/${gameId}`,
|
|
91
|
+
value: existingEntry,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
return [...deletions, ...insertions];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const chessFlumeView = FlumeViewCustom(
|
|
100
|
+
1,
|
|
101
|
+
mapperBuilder.sequentialMapper(chessMapper),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const db = flumeDb.use("flumeviewCustom", chessFlumeView);
|
|
105
|
+
|
|
106
|
+
const chessView = db["flumeviewCustom"];
|
|
107
|
+
const append = util.promisify(db.append);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
append: (item) => {
|
|
111
|
+
return append(item);
|
|
112
|
+
},
|
|
113
|
+
getOutboundInvites: function (inviter) {
|
|
114
|
+
return chessView.read({
|
|
115
|
+
gt: `/player/inviter/${inviter}/`,
|
|
116
|
+
lt: `/player/inviter/${inviter}/\uffff`,
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
getInboundInvites: function (invitee) {
|
|
120
|
+
return chessView.read({
|
|
121
|
+
gt: `/player/invited/${invitee}/`,
|
|
122
|
+
lt: `/player/invited/${invitee}/\uffff`,
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
getGamesInProgress: function (playerId) {
|
|
126
|
+
return chessView.read({
|
|
127
|
+
gt: `/player/live_game/${playerId}/`,
|
|
128
|
+
lt: `/player/live_game/${playerId}/\uffff`,
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
describe("End to end scenario", () => {
|
|
135
|
+
let db;
|
|
136
|
+
|
|
137
|
+
afterEach(async () => {
|
|
138
|
+
if (db) {
|
|
139
|
+
db.close(() => {});
|
|
140
|
+
db = null;
|
|
141
|
+
rmSync("/tmp/flumeview-level-custom", { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("Chess game index scenario invites (non-live)", async () => {
|
|
146
|
+
db = Flume(
|
|
147
|
+
Log("/tmp/flumeview-level-custom/log2.offset", 1024, codec.json),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const chess = chessView(db);
|
|
151
|
+
|
|
152
|
+
const items = [
|
|
153
|
+
{
|
|
154
|
+
key: "%Zf7y1aoZ/51P5IzF6GEvXNRkBqs+X7r+hG5djhQfwpM=.sha256",
|
|
155
|
+
value: {
|
|
156
|
+
previous: "%EA7jGV3XotJ3qQRMwpDbswj2xfHkEFCbZtSiXfr6d6E=.sha256",
|
|
157
|
+
sequence: 19942,
|
|
158
|
+
author: "@RJ09Kfs3neEZPrbpbWVDxkN92x9moe3aPusOMOc4S2I=.ed25519",
|
|
159
|
+
timestamp: 1791495457292,
|
|
160
|
+
hash: "sha256",
|
|
161
|
+
content: {
|
|
162
|
+
type: "chess_invite",
|
|
163
|
+
inviting: "@STgkEO48Kucd57i7x0vBqfkz/euvt2XdglwF6gZLtdk=.ed25519",
|
|
164
|
+
myColor: "black",
|
|
165
|
+
branch: [
|
|
166
|
+
"%AxUibLnrkZrzA6bk15LpGGDepPZOR2nEn24Kql0YYho=.sha256",
|
|
167
|
+
"%t/8R6p+0Hkq7vDacYwiMOoKNzRf9DD2Ukhiyhxmsroc=.sha256",
|
|
168
|
+
"%01ksKwoAAkrY+bjYdP1WCgtZHHGGcZ8bXmEoYnUlyn4=.sha256",
|
|
169
|
+
"%9oK3ltfnvH9/7h5sxD6Cu+IYoSXcOddTMPJ5L6dLsUM=.sha256",
|
|
170
|
+
"%ldUBk14tkOXNoLECOP9BYDyCyBh4p9KvfHAIzpwcK9k=.sha256",
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
signature:
|
|
174
|
+
"LSq6Ul3o71i7QlPANr3xEhuazhHhPyByLoCGtxv+Nhu37VeI35UOIjCXGBfUNl+9c4f54y6XRNZXS4/ctQvhAg==.sig.ed25519",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
key: "%GpeF1QW5NEiyMDSnotDYoU/2YDFAVVVNYJDleKpnhtw=.sha256",
|
|
179
|
+
value: {
|
|
180
|
+
previous: "%sat8SZr1QshItA3IZK4e6221k8i+3NOf0JLH6PlgIzw=.sha256",
|
|
181
|
+
sequence: 5561,
|
|
182
|
+
author: "@STgkEO48Kucd57i7x0vBqfkz/euvt2XdglwF6gZLtdk=.ed25519",
|
|
183
|
+
timestamp: 1793634892038,
|
|
184
|
+
hash: "sha256",
|
|
185
|
+
content: {
|
|
186
|
+
type: "chess_invite_accept",
|
|
187
|
+
root: "%Zf7y1aoZ/51P5IzF6GEvXNRkBqs+X7r+hG5djhQfwpM=.sha256",
|
|
188
|
+
branch: "%Zf7y1aoZ/51P5IzF6GEvXNRkBqs+X7r+hG5djhQfwpM=.sha256",
|
|
189
|
+
},
|
|
190
|
+
signature:
|
|
191
|
+
"S/ZvvE9VNtkcI6vMJmijlwU8kDAjKLlWhAMenA/tZQRs1MOsDnl3AySfNMAkNEIJw4pUP0//EeTUOFPQgECwCA==.sig.ed25519",
|
|
192
|
+
},
|
|
193
|
+
timestamp: 1793639323422,
|
|
194
|
+
rts: 1793634892038,
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
await chess.append(items[0]);
|
|
199
|
+
|
|
200
|
+
const source = chess.getInboundInvites(
|
|
201
|
+
"@STgkEO48Kucd57i7x0vBqfkz/euvt2XdglwF6gZLtdk=.ed25519",
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const result = await pullCollectPromise(source);
|
|
205
|
+
|
|
206
|
+
expect(result.length).toStrictEqual(1);
|
|
207
|
+
|
|
208
|
+
const invitedSource = chess.getOutboundInvites(
|
|
209
|
+
"@RJ09Kfs3neEZPrbpbWVDxkN92x9moe3aPusOMOc4S2I=.ed25519",
|
|
210
|
+
);
|
|
211
|
+
const invitedResult = await pullCollectPromise(invitedSource);
|
|
212
|
+
expect(invitedResult.length).toStrictEqual(1);
|
|
213
|
+
|
|
214
|
+
const inProgressSource = chess.getGamesInProgress(
|
|
215
|
+
"@RJ09Kfs3neEZPrbpbWVDxkN92x9moe3aPusOMOc4S2I=.ed25519",
|
|
216
|
+
);
|
|
217
|
+
const inProgressResult = await pullCollectPromise(inProgressSource);
|
|
218
|
+
expect(inProgressResult.length).toStrictEqual(0);
|
|
219
|
+
|
|
220
|
+
// After invite acceptance the invites should disappear and there should be an 'in progress' result
|
|
221
|
+
await chess.append(items[1]);
|
|
222
|
+
|
|
223
|
+
const invitedSource2 = chess.getInboundInvites(
|
|
224
|
+
"@RJ09Kfs3neEZPrbpbWVDxkN92x9moe3aPusOMOc4S2I=.ed25519",
|
|
225
|
+
);
|
|
226
|
+
const invitedResult2 = await pullCollectPromise(invitedSource2);
|
|
227
|
+
expect(invitedResult2.length).toStrictEqual(0);
|
|
228
|
+
|
|
229
|
+
const inProgressSource2 = chess.getGamesInProgress(
|
|
230
|
+
"@RJ09Kfs3neEZPrbpbWVDxkN92x9moe3aPusOMOc4S2I=.ed25519",
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const inProgressResult2 = await pullCollectPromise(inProgressSource2);
|
|
234
|
+
expect(inProgressResult2.length).toStrictEqual(1);
|
|
235
|
+
});
|
|
236
|
+
});
|
package/test/index.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
1
|
+
const Log = require("flumelog-offset");
|
|
2
|
+
const { mapperBuilder, FlumeViewCustom } = require("../src");
|
|
3
3
|
const Flume = require("flumedb");
|
|
4
4
|
const codec = require("flumecodec");
|
|
5
5
|
const util = require("util");
|
|
@@ -8,25 +8,33 @@ const pull = require("pull-stream");
|
|
|
8
8
|
const Aborter = require("pull-abortable");
|
|
9
9
|
|
|
10
10
|
describe("flumeview-level-custom flumeview builder tests", () => {
|
|
11
|
-
function
|
|
12
|
-
return
|
|
11
|
+
function defaultWriter() {
|
|
12
|
+
return mapperBuilder.sequentialMapper(defaultMapper);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createWriter(mapper) {
|
|
16
|
+
return mapperBuilder.sequentialMapper(mapper);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function defaultMapper(db, item) {
|
|
20
|
+
return Promise.resolve([{ type: "put", key: item.id, value: item.value }]);
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
let db = null;
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
if (db) {
|
|
27
|
+
db.close(() => {});
|
|
28
|
+
db = null;
|
|
29
|
+
rmSync("/tmp/flumeview-level-custom", { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
25
32
|
|
|
33
|
+
describe("get", () => {
|
|
26
34
|
test("Test without optional filter function parameter set", async () => {
|
|
27
35
|
db = Flume(
|
|
28
36
|
Log("/tmp/flumeview-level-custom/log2.offset", 1024, codec.json),
|
|
29
|
-
).use("flumeviewCustom",
|
|
37
|
+
).use("flumeviewCustom", FlumeViewCustom(1, defaultWriter()));
|
|
30
38
|
|
|
31
39
|
const get = util.promisify(db["flumeviewCustom"].get);
|
|
32
40
|
const append = util.promisify(db.append);
|
|
@@ -43,7 +51,7 @@ describe("flumeview-level-custom flumeview builder tests", () => {
|
|
|
43
51
|
|
|
44
52
|
db = Flume(
|
|
45
53
|
Log("/tmp/flumeview-level-custom/log.offset", 1024, codec.json),
|
|
46
|
-
).use("flumeviewCustom",
|
|
54
|
+
).use("flumeviewCustom", FlumeViewCustom(1, defaultWriter(), filter));
|
|
47
55
|
|
|
48
56
|
const append = util.promisify(db.append);
|
|
49
57
|
const get = util.promisify(db["flumeviewCustom"].get);
|
|
@@ -63,18 +71,10 @@ describe("flumeview-level-custom flumeview builder tests", () => {
|
|
|
63
71
|
});
|
|
64
72
|
|
|
65
73
|
describe("read", () => {
|
|
66
|
-
afterEach(async () => {
|
|
67
|
-
if (db) {
|
|
68
|
-
db.close(() => {});
|
|
69
|
-
db = null;
|
|
70
|
-
rmSync("/tmp/flumeview-level-custom", { recursive: true, force: true });
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
74
|
test("non-live read", async () => {
|
|
75
75
|
db = Flume(
|
|
76
76
|
Log("/tmp/flumeview-level-custom/log.offset", 1024, codec.json),
|
|
77
|
-
).use("flumeviewCustom",
|
|
77
|
+
).use("flumeviewCustom", FlumeViewCustom(1, defaultWriter()));
|
|
78
78
|
|
|
79
79
|
const append = util.promisify(db.append);
|
|
80
80
|
const read = db["flumeviewCustom"].read;
|
|
@@ -111,7 +111,7 @@ describe("flumeview-level-custom flumeview builder tests", () => {
|
|
|
111
111
|
test("Read live stream", async () => {
|
|
112
112
|
db = Flume(
|
|
113
113
|
Log("/tmp/flumeview-level-custom/log.offset", 1024, codec.json),
|
|
114
|
-
).use("flumeviewCustom",
|
|
114
|
+
).use("flumeviewCustom", FlumeViewCustom(1, defaultWriter()));
|
|
115
115
|
|
|
116
116
|
const append = util.promisify(db.append);
|
|
117
117
|
const read = db["flumeviewCustom"].read;
|
|
@@ -173,7 +173,7 @@ describe("flumeview-level-custom flumeview builder tests", () => {
|
|
|
173
173
|
|
|
174
174
|
db = Flume(
|
|
175
175
|
Log("/tmp/flumeview-level-custom/log.offset", 1024, codec.json),
|
|
176
|
-
).use("flumeviewCustom",
|
|
176
|
+
).use("flumeviewCustom", FlumeViewCustom(1, createWriter(mapper)));
|
|
177
177
|
|
|
178
178
|
const append = util.promisify(db.append);
|
|
179
179
|
const read = db["flumeviewCustom"].read;
|
|
@@ -247,7 +247,7 @@ describe("flumeview-level-custom flumeview builder tests", () => {
|
|
|
247
247
|
test("Starts from last offset reached on startup", async () => {
|
|
248
248
|
db = Flume(
|
|
249
249
|
Log("/tmp/flumeview-level-custom/log2.offset", 1024, codec.json),
|
|
250
|
-
).use("flumeviewCustom",
|
|
250
|
+
).use("flumeviewCustom", FlumeViewCustom(1, defaultWriter()));
|
|
251
251
|
|
|
252
252
|
const get = util.promisify(db["flumeviewCustom"].get);
|
|
253
253
|
const append = util.promisify(db.append);
|
|
@@ -274,7 +274,7 @@ describe("flumeview-level-custom flumeview builder tests", () => {
|
|
|
274
274
|
|
|
275
275
|
db = Flume(
|
|
276
276
|
Log("/tmp/flumeview-level-custom/log2.offset", 1024, codec.json),
|
|
277
|
-
).use("flumeviewCustom",
|
|
277
|
+
).use("flumeviewCustom", FlumeViewCustom(1, defaultWriter()));
|
|
278
278
|
|
|
279
279
|
const read = db["flumeviewCustom"].read;
|
|
280
280
|
|
|
@@ -322,7 +322,7 @@ describe("flumeview-level-custom flumeview builder tests", () => {
|
|
|
322
322
|
|
|
323
323
|
db = Flume(
|
|
324
324
|
Log("/tmp/flumeview-level-custom/log2.offset", 1024, codec.json),
|
|
325
|
-
).use("flumeviewCustom",
|
|
325
|
+
).use("flumeviewCustom", FlumeViewCustom(1, defaultWriter()));
|
|
326
326
|
|
|
327
327
|
const get = util.promisify(db["flumeviewCustom"].get);
|
|
328
328
|
const append = util.promisify(db.append);
|
|
@@ -337,7 +337,10 @@ describe("flumeview-level-custom flumeview builder tests", () => {
|
|
|
337
337
|
|
|
338
338
|
db = Flume(
|
|
339
339
|
Log("/tmp/flumeview-level-custom/log2.offset", 1024, codec.json),
|
|
340
|
-
).use(
|
|
340
|
+
).use(
|
|
341
|
+
"flumeviewCustom",
|
|
342
|
+
FlumeViewCustom(2, createWriter(newSchemaMapper)),
|
|
343
|
+
);
|
|
341
344
|
|
|
342
345
|
const get2 = util.promisify(db["flumeviewCustom"].get);
|
|
343
346
|
|