@didcid/gatekeeper 0.1.3
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/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/cjs/abstract-json-BJMq-iEa.cjs +216 -0
- package/dist/cjs/db/json-cache.cjs +75 -0
- package/dist/cjs/db/json-memory.cjs +29 -0
- package/dist/cjs/db/json.cjs +36 -0
- package/dist/cjs/db/mongo.cjs +242 -0
- package/dist/cjs/db/redis.cjs +291 -0
- package/dist/cjs/db/sqlite.cjs +330 -0
- package/dist/cjs/errors-DQaog-FG.cjs +34 -0
- package/dist/cjs/gatekeeper-client.cjs +369 -0
- package/dist/cjs/gatekeeper.cjs +42212 -0
- package/dist/cjs/index.cjs +12 -0
- package/dist/cjs/node.cjs +40 -0
- package/dist/esm/db/abstract-json.js +212 -0
- package/dist/esm/db/abstract-json.js.map +1 -0
- package/dist/esm/db/json-cache.js +68 -0
- package/dist/esm/db/json-cache.js.map +1 -0
- package/dist/esm/db/json-memory.js +22 -0
- package/dist/esm/db/json-memory.js.map +1 -0
- package/dist/esm/db/json.js +29 -0
- package/dist/esm/db/json.js.map +1 -0
- package/dist/esm/db/mongo.js +236 -0
- package/dist/esm/db/mongo.js.map +1 -0
- package/dist/esm/db/redis.js +285 -0
- package/dist/esm/db/redis.js.map +1 -0
- package/dist/esm/db/sqlite.js +305 -0
- package/dist/esm/db/sqlite.js.map +1 -0
- package/dist/esm/gatekeeper-client.js +363 -0
- package/dist/esm/gatekeeper-client.js.map +1 -0
- package/dist/esm/gatekeeper.js +1090 -0
- package/dist/esm/gatekeeper.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/node.js +8 -0
- package/dist/esm/node.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/db/abstract-json.d.ts +26 -0
- package/dist/types/db/json-cache.d.ts +14 -0
- package/dist/types/db/json-memory.d.ts +9 -0
- package/dist/types/db/json.d.ts +8 -0
- package/dist/types/db/mongo.d.ts +23 -0
- package/dist/types/db/redis.d.ts +29 -0
- package/dist/types/db/sqlite.d.ts +30 -0
- package/dist/types/gatekeeper-client.d.ts +40 -0
- package/dist/types/gatekeeper.d.ts +67 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/node.d.ts +7 -0
- package/dist/types/types.d.ts +226 -0
- package/package.json +128 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var ioredis = require('ioredis');
|
|
6
|
+
var errors = require('../errors-DQaog-FG.cjs');
|
|
7
|
+
|
|
8
|
+
const REDIS_NOT_STARTED_ERROR = 'Redis not started. Call start() first.';
|
|
9
|
+
class DbRedis {
|
|
10
|
+
dbName;
|
|
11
|
+
redis;
|
|
12
|
+
_lock = Promise.resolve();
|
|
13
|
+
runExclusive(fn) {
|
|
14
|
+
const run = async () => await fn();
|
|
15
|
+
const chained = this._lock.then(run, run);
|
|
16
|
+
this._lock = chained.then(() => undefined, () => undefined);
|
|
17
|
+
return chained;
|
|
18
|
+
}
|
|
19
|
+
constructor(dbName) {
|
|
20
|
+
this.dbName = dbName;
|
|
21
|
+
this.redis = null;
|
|
22
|
+
}
|
|
23
|
+
async start() {
|
|
24
|
+
const url = process.env.ARCHON_REDIS_URL || 'redis://localhost:6379';
|
|
25
|
+
this.redis = new ioredis.Redis(url);
|
|
26
|
+
}
|
|
27
|
+
async stop() {
|
|
28
|
+
if (this.redis) {
|
|
29
|
+
await this.redis.quit();
|
|
30
|
+
this.redis = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async resetDb() {
|
|
34
|
+
if (!this.redis) {
|
|
35
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
36
|
+
}
|
|
37
|
+
let cursor = '0';
|
|
38
|
+
let totalDeleted = 0;
|
|
39
|
+
do {
|
|
40
|
+
// Scan for keys that match the pattern
|
|
41
|
+
const [newCursor, keys] = await this.redis.scan(cursor, 'MATCH', `${this.dbName}/*`, 'COUNT', 1000);
|
|
42
|
+
cursor = newCursor;
|
|
43
|
+
if (keys.length > 0) {
|
|
44
|
+
// Delete the keys found
|
|
45
|
+
const deletedCount = await this.redis.del(...keys);
|
|
46
|
+
totalDeleted += deletedCount; // Increment the total count
|
|
47
|
+
}
|
|
48
|
+
} while (cursor !== '0'); // Continue scanning until cursor returns to 0
|
|
49
|
+
return totalDeleted;
|
|
50
|
+
}
|
|
51
|
+
async addEvent(did, event) {
|
|
52
|
+
if (!this.redis) {
|
|
53
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
54
|
+
}
|
|
55
|
+
// Store operation separately if present
|
|
56
|
+
if (event.opid && event.operation) {
|
|
57
|
+
await this.addOperation(event.opid, event.operation);
|
|
58
|
+
}
|
|
59
|
+
const key = this.didKey(did);
|
|
60
|
+
// Strip operation and store only opid reference
|
|
61
|
+
const { operation, ...strippedEvent } = event;
|
|
62
|
+
const val = JSON.stringify(strippedEvent);
|
|
63
|
+
return this.redis.rpush(key, val);
|
|
64
|
+
}
|
|
65
|
+
async setEvents(did, events) {
|
|
66
|
+
if (!this.redis) {
|
|
67
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
68
|
+
}
|
|
69
|
+
const key = this.didKey(did);
|
|
70
|
+
// Update operations in ops store if modified, then strip from events
|
|
71
|
+
const payloads = [];
|
|
72
|
+
for (const event of events) {
|
|
73
|
+
if (event.opid && event.operation) {
|
|
74
|
+
await this.addOperation(event.opid, event.operation);
|
|
75
|
+
}
|
|
76
|
+
const { operation, ...stripped } = event;
|
|
77
|
+
payloads.push(JSON.stringify(stripped));
|
|
78
|
+
}
|
|
79
|
+
await this.runExclusive(async () => {
|
|
80
|
+
const multi = this.redis.multi().del(key);
|
|
81
|
+
if (payloads.length) {
|
|
82
|
+
multi.rpush(key, ...payloads);
|
|
83
|
+
}
|
|
84
|
+
await multi.exec();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
didKey(did) {
|
|
88
|
+
if (!did) {
|
|
89
|
+
throw new errors.InvalidDIDError();
|
|
90
|
+
}
|
|
91
|
+
const id = did.split(':').pop();
|
|
92
|
+
if (!id) {
|
|
93
|
+
throw new errors.InvalidDIDError();
|
|
94
|
+
}
|
|
95
|
+
return `${this.dbName}/dids/${id}`;
|
|
96
|
+
}
|
|
97
|
+
async getEvents(did) {
|
|
98
|
+
if (!this.redis) {
|
|
99
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
100
|
+
}
|
|
101
|
+
const rawEvents = await this.redis.lrange(this.didKey(did), 0, -1);
|
|
102
|
+
const events = rawEvents.map(event => JSON.parse(event));
|
|
103
|
+
// Hydrate operations from ops store
|
|
104
|
+
const hydrated = [];
|
|
105
|
+
for (const event of events) {
|
|
106
|
+
if (event.operation) {
|
|
107
|
+
hydrated.push(event);
|
|
108
|
+
}
|
|
109
|
+
else if (event.opid) {
|
|
110
|
+
const operation = await this.getOperation(event.opid);
|
|
111
|
+
if (operation) {
|
|
112
|
+
hydrated.push({ ...event, operation });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
hydrated.push(event);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
hydrated.push(event);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return hydrated;
|
|
123
|
+
}
|
|
124
|
+
async deleteEvents(did) {
|
|
125
|
+
if (!this.redis) {
|
|
126
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
127
|
+
}
|
|
128
|
+
if (!did) {
|
|
129
|
+
throw new errors.InvalidDIDError();
|
|
130
|
+
}
|
|
131
|
+
return this.redis.del(this.didKey(did));
|
|
132
|
+
}
|
|
133
|
+
async getAllKeys() {
|
|
134
|
+
if (!this.redis) {
|
|
135
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
136
|
+
}
|
|
137
|
+
const keys = await this.redis.keys(`${this.dbName}/dids/*`);
|
|
138
|
+
// Extract the id part from the key
|
|
139
|
+
return keys.map(key => key.split('/').pop() || '');
|
|
140
|
+
}
|
|
141
|
+
queueKey(registry) {
|
|
142
|
+
return `${this.dbName}/registry/${registry}/queue`;
|
|
143
|
+
}
|
|
144
|
+
async queueOperation(registry, op) {
|
|
145
|
+
if (!this.redis) {
|
|
146
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
147
|
+
}
|
|
148
|
+
const queueKey = this.queueKey(registry);
|
|
149
|
+
return this.redis.rpush(queueKey, JSON.stringify(op));
|
|
150
|
+
}
|
|
151
|
+
async getQueue(registry) {
|
|
152
|
+
if (!this.redis) {
|
|
153
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const queueKey = this.queueKey(registry);
|
|
157
|
+
const ops = await this.redis.lrange(queueKey, 0, -1);
|
|
158
|
+
return ops.map(op => JSON.parse(op));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async clearQueue(registry, batch) {
|
|
165
|
+
if (!this.redis) {
|
|
166
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
167
|
+
}
|
|
168
|
+
const hashes = batch
|
|
169
|
+
.map(op => op.signature?.hash)
|
|
170
|
+
.filter((h) => !!h);
|
|
171
|
+
if (hashes.length === 0) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
const key = this.queueKey(registry);
|
|
175
|
+
const script = `
|
|
176
|
+
local key = KEYS[1]
|
|
177
|
+
local n = tonumber(ARGV[1])
|
|
178
|
+
local idx = 2
|
|
179
|
+
local want = {}
|
|
180
|
+
for i=1,n do
|
|
181
|
+
want[ARGV[idx]] = true
|
|
182
|
+
idx = idx + 1
|
|
183
|
+
end
|
|
184
|
+
local list = redis.call('LRANGE', key, 0, -1)
|
|
185
|
+
if #list == 0 then return 0 end
|
|
186
|
+
local keep = {}
|
|
187
|
+
for i=1,#list do
|
|
188
|
+
local ok, obj = pcall(cjson.decode, list[i])
|
|
189
|
+
if ok and obj and obj.signature and obj.signature.hash and want[obj.signature.hash] then
|
|
190
|
+
-- drop
|
|
191
|
+
else
|
|
192
|
+
table.insert(keep, list[i])
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
redis.call('DEL', key)
|
|
196
|
+
if #keep > 0 then
|
|
197
|
+
redis.call('RPUSH', key, unpack(keep))
|
|
198
|
+
end
|
|
199
|
+
return #list - #keep
|
|
200
|
+
`;
|
|
201
|
+
try {
|
|
202
|
+
await this.redis.eval(script, 1, key, hashes.length.toString(), ...hashes);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
console.error(e);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
blockKey(registry, hash) {
|
|
211
|
+
return `${this.dbName}/registry/${registry}/blocks/${hash}`;
|
|
212
|
+
}
|
|
213
|
+
heightMapKey(registry) {
|
|
214
|
+
return `${this.dbName}/registry/${registry}/heightMap`;
|
|
215
|
+
}
|
|
216
|
+
maxHeightKey(registry) {
|
|
217
|
+
return `${this.dbName}/registry/${registry}/maxHeight`;
|
|
218
|
+
}
|
|
219
|
+
operationKey(opid) {
|
|
220
|
+
return `${this.dbName}/ops/${opid}`;
|
|
221
|
+
}
|
|
222
|
+
async addBlock(registry, blockInfo) {
|
|
223
|
+
if (!this.redis)
|
|
224
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
225
|
+
const { hash, height } = blockInfo;
|
|
226
|
+
if (!hash || height == null) {
|
|
227
|
+
throw new Error(`Invalid blockInfo: ${JSON.stringify(blockInfo)}`);
|
|
228
|
+
}
|
|
229
|
+
const blockKey = this.blockKey(registry, hash);
|
|
230
|
+
const heightMapKey = this.heightMapKey(registry);
|
|
231
|
+
const maxHeightKey = this.maxHeightKey(registry);
|
|
232
|
+
const maxHeight = await this.redis.get(maxHeightKey);
|
|
233
|
+
const currentMaxHeight = maxHeight ? parseInt(maxHeight) : -1;
|
|
234
|
+
try {
|
|
235
|
+
await this.redis.multi()
|
|
236
|
+
.set(blockKey, JSON.stringify(blockInfo))
|
|
237
|
+
.hset(heightMapKey, height.toString(), hash)
|
|
238
|
+
.set(maxHeightKey, Math.max(height, currentMaxHeight))
|
|
239
|
+
.exec();
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
async getBlock(registry, blockId) {
|
|
247
|
+
if (!this.redis) {
|
|
248
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
let blockHash;
|
|
252
|
+
if (blockId === undefined) {
|
|
253
|
+
// No blockId provided → get latest by max height
|
|
254
|
+
const maxHeightStr = await this.redis.get(this.maxHeightKey(registry));
|
|
255
|
+
if (!maxHeightStr)
|
|
256
|
+
return null;
|
|
257
|
+
blockId = parseInt(maxHeightStr);
|
|
258
|
+
}
|
|
259
|
+
if (typeof blockId === 'number') {
|
|
260
|
+
const heightMapKey = this.heightMapKey(registry);
|
|
261
|
+
blockHash = await this.redis.hget(heightMapKey, blockId.toString());
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
blockHash = blockId;
|
|
265
|
+
}
|
|
266
|
+
if (!blockHash)
|
|
267
|
+
return null;
|
|
268
|
+
const blockKey = this.blockKey(registry, blockHash);
|
|
269
|
+
const blockInfo = await this.redis.get(blockKey);
|
|
270
|
+
return blockInfo ? JSON.parse(blockInfo) : null;
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async addOperation(opid, op) {
|
|
277
|
+
if (!this.redis) {
|
|
278
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
279
|
+
}
|
|
280
|
+
await this.redis.set(this.operationKey(opid), JSON.stringify(op));
|
|
281
|
+
}
|
|
282
|
+
async getOperation(opid) {
|
|
283
|
+
if (!this.redis) {
|
|
284
|
+
throw new Error(REDIS_NOT_STARTED_ERROR);
|
|
285
|
+
}
|
|
286
|
+
const json = await this.redis.get(this.operationKey(opid));
|
|
287
|
+
return json ? JSON.parse(json) : null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
exports.default = DbRedis;
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var sqlite = require('sqlite');
|
|
6
|
+
var sqlite3 = require('sqlite3');
|
|
7
|
+
var errors = require('../errors-DQaog-FG.cjs');
|
|
8
|
+
|
|
9
|
+
function _interopNamespaceDefault(e) {
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var sqlite__namespace = /*#__PURE__*/_interopNamespaceDefault(sqlite);
|
|
27
|
+
|
|
28
|
+
const SQLITE_NOT_STARTED_ERROR = 'SQLite DB not open. Call start() first.';
|
|
29
|
+
class DbSqlite {
|
|
30
|
+
dbName;
|
|
31
|
+
db;
|
|
32
|
+
constructor(name, dataFolder = 'data') {
|
|
33
|
+
this.dbName = `${dataFolder}/${name}.db`;
|
|
34
|
+
this.db = null;
|
|
35
|
+
}
|
|
36
|
+
_lock = Promise.resolve();
|
|
37
|
+
runExclusive(fn) {
|
|
38
|
+
const run = async () => await fn();
|
|
39
|
+
const chained = this._lock.then(run, run);
|
|
40
|
+
this._lock = chained.then(() => undefined, () => undefined);
|
|
41
|
+
return chained;
|
|
42
|
+
}
|
|
43
|
+
async withTx(fn) {
|
|
44
|
+
if (!this.db) {
|
|
45
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
46
|
+
}
|
|
47
|
+
await this.db.exec('BEGIN IMMEDIATE');
|
|
48
|
+
try {
|
|
49
|
+
const result = await fn();
|
|
50
|
+
await this.db.exec('COMMIT');
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
try {
|
|
55
|
+
await this.db.exec('ROLLBACK');
|
|
56
|
+
}
|
|
57
|
+
catch { }
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
splitSuffix(did) {
|
|
62
|
+
if (!did) {
|
|
63
|
+
throw new errors.InvalidDIDError();
|
|
64
|
+
}
|
|
65
|
+
const suffix = did.split(':').pop();
|
|
66
|
+
if (!suffix) {
|
|
67
|
+
throw new errors.InvalidDIDError();
|
|
68
|
+
}
|
|
69
|
+
return suffix;
|
|
70
|
+
}
|
|
71
|
+
async start() {
|
|
72
|
+
this.db = await sqlite__namespace.open({
|
|
73
|
+
filename: this.dbName,
|
|
74
|
+
driver: sqlite3.Database
|
|
75
|
+
});
|
|
76
|
+
await this.db.exec(`CREATE TABLE IF NOT EXISTS dids (
|
|
77
|
+
id TEXT PRIMARY KEY,
|
|
78
|
+
events TEXT
|
|
79
|
+
)`);
|
|
80
|
+
await this.db.exec(`CREATE TABLE IF NOT EXISTS queue (
|
|
81
|
+
id TEXT PRIMARY KEY,
|
|
82
|
+
ops TEXT
|
|
83
|
+
)`);
|
|
84
|
+
await this.db.exec(`CREATE TABLE IF NOT EXISTS blocks (
|
|
85
|
+
registry TEXT NOT NULL,
|
|
86
|
+
hash TEXT NOT NULL,
|
|
87
|
+
height INTEGER NOT NULL,
|
|
88
|
+
time TEXT NOT NULL,
|
|
89
|
+
txns INTEGER NOT NULL,
|
|
90
|
+
PRIMARY KEY (registry, hash)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_registry_height ON blocks (registry, height);
|
|
94
|
+
`);
|
|
95
|
+
await this.db.exec(`CREATE TABLE IF NOT EXISTS operations (
|
|
96
|
+
opid TEXT PRIMARY KEY,
|
|
97
|
+
operation TEXT
|
|
98
|
+
)`);
|
|
99
|
+
}
|
|
100
|
+
async stop() {
|
|
101
|
+
if (this.db) {
|
|
102
|
+
await this.db.close();
|
|
103
|
+
this.db = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async resetDb() {
|
|
107
|
+
if (!this.db) {
|
|
108
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
109
|
+
}
|
|
110
|
+
await this.runExclusive(async () => {
|
|
111
|
+
await this.withTx(async () => {
|
|
112
|
+
await this.db.run('DELETE FROM dids');
|
|
113
|
+
await this.db.run('DELETE FROM queue');
|
|
114
|
+
await this.db.run('DELETE FROM blocks');
|
|
115
|
+
await this.db.run('DELETE FROM operations');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async addEvent(did, event) {
|
|
120
|
+
if (!did) {
|
|
121
|
+
throw new errors.InvalidDIDError();
|
|
122
|
+
}
|
|
123
|
+
return this.runExclusive(() => this.withTx(async () => {
|
|
124
|
+
const id = this.splitSuffix(did);
|
|
125
|
+
// Store operation separately if present
|
|
126
|
+
if (event.opid && event.operation) {
|
|
127
|
+
await this.addOperationStrict(event.opid, event.operation);
|
|
128
|
+
}
|
|
129
|
+
const events = await this.getEventsStrictRaw(id);
|
|
130
|
+
// Strip operation and store only opid reference
|
|
131
|
+
const { operation, ...strippedEvent } = event;
|
|
132
|
+
events.push(strippedEvent);
|
|
133
|
+
return this.setEventsStrict(id, events);
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
async setEventsStrict(id, events) {
|
|
137
|
+
if (!this.db) {
|
|
138
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
139
|
+
}
|
|
140
|
+
// Update operations in ops table if modified, then strip from events
|
|
141
|
+
const strippedEvents = [];
|
|
142
|
+
for (const event of events) {
|
|
143
|
+
if (event.opid && event.operation) {
|
|
144
|
+
await this.addOperationStrict(event.opid, event.operation);
|
|
145
|
+
}
|
|
146
|
+
const { operation, ...stripped } = event;
|
|
147
|
+
strippedEvents.push(stripped);
|
|
148
|
+
}
|
|
149
|
+
const res = await this.db.run(`INSERT OR REPLACE INTO dids(id, events) VALUES(?, ?)`, id, JSON.stringify(strippedEvents));
|
|
150
|
+
return res.changes ?? 0;
|
|
151
|
+
}
|
|
152
|
+
async addOperationStrict(opid, op) {
|
|
153
|
+
if (!this.db) {
|
|
154
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
155
|
+
}
|
|
156
|
+
await this.db.run(`INSERT OR REPLACE INTO operations(opid, operation) VALUES(?, ?)`, opid, JSON.stringify(op));
|
|
157
|
+
}
|
|
158
|
+
async setEvents(did, events) {
|
|
159
|
+
const id = this.splitSuffix(did);
|
|
160
|
+
return this.runExclusive(() => this.withTx(() => this.setEventsStrict(id, events)));
|
|
161
|
+
}
|
|
162
|
+
async getEventsStrictRaw(id) {
|
|
163
|
+
if (!this.db) {
|
|
164
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
165
|
+
}
|
|
166
|
+
const row = await this.db.get('SELECT events FROM dids WHERE id = ?', id);
|
|
167
|
+
if (!row) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
const events = JSON.parse(row.events);
|
|
171
|
+
if (!Array.isArray(events)) {
|
|
172
|
+
throw new Error('events is not an array');
|
|
173
|
+
}
|
|
174
|
+
return events;
|
|
175
|
+
}
|
|
176
|
+
async hydrateEvents(events) {
|
|
177
|
+
const hydrated = [];
|
|
178
|
+
for (const event of events) {
|
|
179
|
+
if (event.operation) {
|
|
180
|
+
hydrated.push(event);
|
|
181
|
+
}
|
|
182
|
+
else if (event.opid) {
|
|
183
|
+
const operation = await this.getOperation(event.opid);
|
|
184
|
+
if (operation) {
|
|
185
|
+
hydrated.push({ ...event, operation });
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
hydrated.push(event);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
hydrated.push(event);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return hydrated;
|
|
196
|
+
}
|
|
197
|
+
async getEvents(did) {
|
|
198
|
+
if (!this.db) {
|
|
199
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const id = this.splitSuffix(did);
|
|
203
|
+
const events = await this.getEventsStrictRaw(id);
|
|
204
|
+
return this.hydrateEvents(events);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async deleteEvents(did) {
|
|
211
|
+
if (!this.db) {
|
|
212
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
213
|
+
}
|
|
214
|
+
return this.runExclusive(() => this.withTx(async () => {
|
|
215
|
+
const id = this.splitSuffix(did);
|
|
216
|
+
const result = await this.db.run('DELETE FROM dids WHERE id = ?', id);
|
|
217
|
+
return result.changes ?? 0;
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
async queueOperation(registry, op) {
|
|
221
|
+
if (!this.db) {
|
|
222
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
223
|
+
}
|
|
224
|
+
return this.runExclusive(async () => this.withTx(async () => {
|
|
225
|
+
const ops = await this.getQueueStrict(registry);
|
|
226
|
+
ops.push(op);
|
|
227
|
+
await this.db.run(`INSERT OR REPLACE INTO queue(id, ops) VALUES(?, ?)`, registry, JSON.stringify(ops));
|
|
228
|
+
return ops.length;
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
async getQueueStrict(registry) {
|
|
232
|
+
if (!this.db) {
|
|
233
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
234
|
+
}
|
|
235
|
+
const row = await this.db.get('SELECT ops FROM queue WHERE id = ?', registry);
|
|
236
|
+
if (!row) {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
const ops = JSON.parse(row.ops);
|
|
240
|
+
if (!Array.isArray(ops)) {
|
|
241
|
+
throw new Error('queue row malformed: ops is not an array');
|
|
242
|
+
}
|
|
243
|
+
return ops;
|
|
244
|
+
}
|
|
245
|
+
async getQueue(registry) {
|
|
246
|
+
if (!this.db) {
|
|
247
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
return await this.getQueueStrict(registry);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async clearQueue(registry, batch) {
|
|
257
|
+
if (!this.db) {
|
|
258
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
259
|
+
}
|
|
260
|
+
return this.runExclusive(async () => this.withTx(async () => {
|
|
261
|
+
const oldQueue = await this.getQueueStrict(registry);
|
|
262
|
+
const batchHashes = new Set(batch.map(b => b.signature?.hash).filter((h) => h !== undefined));
|
|
263
|
+
const newQueue = oldQueue.filter(item => !batchHashes.has(item.signature?.hash || ''));
|
|
264
|
+
await this.db.run(`INSERT OR REPLACE INTO queue(id, ops) VALUES(?, ?)`, registry, JSON.stringify(newQueue));
|
|
265
|
+
return true;
|
|
266
|
+
}).catch(err => {
|
|
267
|
+
console.error(err);
|
|
268
|
+
return false;
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
async getAllKeys() {
|
|
272
|
+
if (!this.db) {
|
|
273
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
274
|
+
}
|
|
275
|
+
const rows = await this.db.all('SELECT id FROM dids');
|
|
276
|
+
return rows.map(row => row.id);
|
|
277
|
+
}
|
|
278
|
+
async addBlock(registry, blockInfo) {
|
|
279
|
+
if (!this.db) {
|
|
280
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
// Insert or replace the block information
|
|
284
|
+
await this.runExclusive(async () => await this.db.run(`INSERT OR REPLACE INTO blocks (registry, hash, height, time, txns) VALUES (?, ?, ?, ?, ?)`, registry, blockInfo.hash, blockInfo.height, blockInfo.time, 0));
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async getBlock(registry, blockId) {
|
|
292
|
+
if (!this.db) {
|
|
293
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
let blockRow;
|
|
297
|
+
if (blockId === undefined) {
|
|
298
|
+
// Return block with max height
|
|
299
|
+
blockRow = await this.db.get(`SELECT * FROM blocks WHERE registry = ? ORDER BY height DESC LIMIT 1`, registry);
|
|
300
|
+
}
|
|
301
|
+
else if (typeof blockId === 'number') {
|
|
302
|
+
blockRow = await this.db.get(`SELECT * FROM blocks WHERE registry = ? AND height = ?`, registry, blockId);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
blockRow = await this.db.get(`SELECT * FROM blocks WHERE registry = ? AND hash = ?`, registry, blockId);
|
|
306
|
+
}
|
|
307
|
+
return blockRow ?? null;
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async addOperation(opid, op) {
|
|
314
|
+
if (!this.db) {
|
|
315
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
316
|
+
}
|
|
317
|
+
await this.runExclusive(() => this.withTx(async () => {
|
|
318
|
+
await this.db.run(`INSERT OR REPLACE INTO operations(opid, operation) VALUES(?, ?)`, opid, JSON.stringify(op));
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
async getOperation(opid) {
|
|
322
|
+
if (!this.db) {
|
|
323
|
+
throw new Error(SQLITE_NOT_STARTED_ERROR);
|
|
324
|
+
}
|
|
325
|
+
const row = await this.db.get('SELECT operation FROM operations WHERE opid = ?', opid);
|
|
326
|
+
return row ? JSON.parse(row.operation) : null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
exports.default = DbSqlite;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class ArchonError extends Error {
|
|
4
|
+
type;
|
|
5
|
+
detail;
|
|
6
|
+
constructor(type, detail) {
|
|
7
|
+
const message = detail ? `${type}: ${detail}` : type;
|
|
8
|
+
super(message);
|
|
9
|
+
this.type = type;
|
|
10
|
+
this.detail = detail;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
class InvalidDIDError extends ArchonError {
|
|
14
|
+
static type = 'Invalid DID';
|
|
15
|
+
constructor(detail) {
|
|
16
|
+
super(InvalidDIDError.type, detail);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
class InvalidParameterError extends ArchonError {
|
|
20
|
+
static type = 'Invalid parameter';
|
|
21
|
+
constructor(detail) {
|
|
22
|
+
super(InvalidParameterError.type, detail);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
class InvalidOperationError extends ArchonError {
|
|
26
|
+
static type = 'Invalid operation';
|
|
27
|
+
constructor(detail) {
|
|
28
|
+
super(InvalidOperationError.type, detail);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
exports.InvalidDIDError = InvalidDIDError;
|
|
33
|
+
exports.InvalidOperationError = InvalidOperationError;
|
|
34
|
+
exports.InvalidParameterError = InvalidParameterError;
|