univo 0.2.14 → 0.2.16
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/cli.mjs +31 -23
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +223 -138
- package/dist/realtime.d.mts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.mjs +32 -17
- package/dist/rpc.d.mts +6 -2
- package/dist/rpc.d.ts +6 -2
- package/dist/shared/{univo.sqV2c2Yj.mjs → univo.BVikUbk_.mjs} +6 -2
- package/dist/shared/{univo.CzTm3CDN.mjs → univo.CkhykU8v.mjs} +1 -1
- package/dist/shared/{univo.DcmLb3Hn.d.mts → univo.DdHfpTWq.d.mts} +3 -3
- package/dist/shared/{univo.DcmLb3Hn.d.ts → univo.DdHfpTWq.d.ts} +3 -3
- package/dist/transport.d.mts +2 -2
- package/dist/transport.d.ts +2 -2
- package/dist/transport.mjs +2 -2
- package/package.json +3 -2
package/dist/cli.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import open from 'open';
|
|
|
4
4
|
import { config } from 'dotenv';
|
|
5
5
|
import { WebSocket } from 'partysocket';
|
|
6
6
|
import { defineCommand, runMain } from 'citty';
|
|
7
|
-
import { h as http } from './shared/univo.
|
|
8
|
-
import { c as createLogger, v as version,
|
|
7
|
+
import { h as http } from './shared/univo.CkhykU8v.mjs';
|
|
8
|
+
import { c as createLogger, v as version, b as name } from './shared/univo.BVikUbk_.mjs';
|
|
9
9
|
|
|
10
10
|
const log = createLogger({ quiet: false });
|
|
11
11
|
const dev = defineCommand({
|
|
@@ -18,39 +18,47 @@ const dev = defineCommand({
|
|
|
18
18
|
},
|
|
19
19
|
async run(ctx) {
|
|
20
20
|
try {
|
|
21
|
-
if (!ctx.args.url.startsWith("http://"))
|
|
22
|
-
|
|
21
|
+
if (!ctx.args.url.startsWith("http://")) {
|
|
22
|
+
throw new Error("`url` must start with http://");
|
|
23
|
+
}
|
|
24
|
+
config({ quiet: true });
|
|
23
25
|
const localSigningKey = process.env.UNIVO_SIGNING_KEY;
|
|
24
|
-
if (localSigningKey === void 0)
|
|
26
|
+
if (localSigningKey === void 0) {
|
|
27
|
+
throw new Error("Please provide a UNIVO_SIGNING_KEY environment variable");
|
|
28
|
+
}
|
|
25
29
|
const client = http(ctx.args.url, { signingKey: localSigningKey });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
]);
|
|
30
|
+
await client.request({ method: "private_getMetadata", params: [] }).catch(() => {
|
|
31
|
+
throw new Error(`Failed to connect to ${ctx.args.url}. Check that your server is running on the correct port.`);
|
|
32
|
+
});
|
|
30
33
|
const remoteSigningKey = crypto.randomUUID();
|
|
31
34
|
const url = `https://tunnel-univo.app/v1/${crypto.randomUUID()}`;
|
|
32
35
|
const endpoint = {
|
|
33
36
|
url,
|
|
34
|
-
events,
|
|
35
|
-
metadata,
|
|
36
37
|
signing_key: remoteSigningKey
|
|
37
38
|
};
|
|
38
|
-
const
|
|
39
|
-
|
|
39
|
+
const encoder = new TextEncoder();
|
|
40
|
+
const data = encoder.encode(JSON.stringify(endpoint));
|
|
41
|
+
const encoded = data.toBase64({ alphabet: "base64url", omitPadding: true });
|
|
42
|
+
await open(`https://univo.app/dev/${encoded}`);
|
|
40
43
|
const ws$1 = new WebSocket(url, [], { WebSocket: ws });
|
|
41
44
|
ws$1.binaryType = "arraybuffer";
|
|
42
|
-
ws$1.onerror = (event) =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
ws$1.onerror = (event) => {
|
|
46
|
+
log.error(event.message);
|
|
47
|
+
};
|
|
48
|
+
ws$1.onopen = () => {
|
|
49
|
+
log.info(`Dev server started for ${ctx.args.url}`);
|
|
50
|
+
};
|
|
51
|
+
ws$1.onclose = () => {
|
|
52
|
+
log.info(`Dev server closed for ${ctx.args.url}`);
|
|
53
|
+
};
|
|
45
54
|
ws$1.onmessage = async (event) => {
|
|
46
55
|
try {
|
|
47
|
-
const
|
|
48
|
-
log.
|
|
49
|
-
const
|
|
50
|
-
ws$1.send(JSON.stringify(
|
|
51
|
-
} catch
|
|
52
|
-
|
|
53
|
-
ws$1.send(JSON.stringify(response));
|
|
56
|
+
const json = JSON.parse(event.data);
|
|
57
|
+
log.debug(`Received request (${json.id}) for method '${json.method}'`);
|
|
58
|
+
const result = await client.request({ method: json.method, params: json.params });
|
|
59
|
+
ws$1.send(JSON.stringify({ jsonrpc: "2.0", id: json.id, result }));
|
|
60
|
+
} catch {
|
|
61
|
+
ws$1.send(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: 0, message: "Invalid request" } }));
|
|
54
62
|
}
|
|
55
63
|
};
|
|
56
64
|
} catch (error) {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { B as Block, E as Event, a as Filter, H as Head, I as Indexer, M as Metadata, R as Result, i as indexer
|
|
2
|
-
import '
|
|
1
|
+
export { B as Block, E as Event, a as Filter, H as Head, I as Indexer, M as Metadata, R as Result, i as indexer } from './shared/univo.DdHfpTWq.mjs';
|
|
2
|
+
import '@storagesdk/core';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { B as Block, E as Event, a as Filter, H as Head, I as Indexer, M as Metadata, R as Result, i as indexer
|
|
2
|
-
import '
|
|
1
|
+
export { B as Block, E as Event, a as Filter, H as Head, I as Indexer, M as Metadata, R as Result, i as indexer } from './shared/univo.DdHfpTWq.js';
|
|
2
|
+
import '@storagesdk/core';
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,98 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { StorageError } from '@storagesdk/core';
|
|
2
|
+
import { g as getException, c as createException, a as catchException, l as local } from './shared/univo.CkhykU8v.mjs';
|
|
3
|
+
import { c as createLogger, d as decompress, e as decoder, r as retry, v as version, h as hexToNumber, a as isHexEqual, f as normalizeHex, g as compress } from './shared/univo.BVikUbk_.mjs';
|
|
3
4
|
import 'ws';
|
|
4
5
|
import 'partysocket';
|
|
5
6
|
|
|
7
|
+
function createServer(opts) {
|
|
8
|
+
const log = createLogger({ quiet: opts.quiet, prefix: "[server]" });
|
|
9
|
+
const http = async (req) => {
|
|
10
|
+
let body_buffer;
|
|
11
|
+
try {
|
|
12
|
+
body_buffer = await req.arrayBuffer();
|
|
13
|
+
} catch {
|
|
14
|
+
return Response.json(
|
|
15
|
+
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Invalid request body" } },
|
|
16
|
+
//
|
|
17
|
+
{ status: 400 }
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const authorization = req.headers.get("Authorization");
|
|
21
|
+
const authenticated = authorization === `Bearer ${opts.signingKey}`;
|
|
22
|
+
let body_string;
|
|
23
|
+
if (req.headers.get("Content-Encoding") === "gzip") {
|
|
24
|
+
if (!authorization) {
|
|
25
|
+
return Response.json(
|
|
26
|
+
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "No bearer token provided" } },
|
|
27
|
+
//
|
|
28
|
+
{ status: 400 }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (!authenticated) {
|
|
32
|
+
return Response.json(
|
|
33
|
+
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Invalid bearer token" } },
|
|
34
|
+
//
|
|
35
|
+
{ status: 400 }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
body_string = await decompress(body_buffer);
|
|
39
|
+
} else {
|
|
40
|
+
body_string = decoder.decode(body_buffer);
|
|
41
|
+
}
|
|
42
|
+
let json;
|
|
43
|
+
try {
|
|
44
|
+
json = JSON.parse(body_string);
|
|
45
|
+
} catch {
|
|
46
|
+
return Response.json(
|
|
47
|
+
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Malformed JSON request body" } },
|
|
48
|
+
//
|
|
49
|
+
{ status: 400 }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (json.method.startsWith("private_")) {
|
|
53
|
+
if (!authorization) {
|
|
54
|
+
return Response.json(
|
|
55
|
+
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "No bearer token provided" } },
|
|
56
|
+
//
|
|
57
|
+
{ status: 400 }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (authorization !== `Bearer ${opts.signingKey}`) {
|
|
61
|
+
return Response.json(
|
|
62
|
+
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "Invalid bearer token" } },
|
|
63
|
+
//
|
|
64
|
+
{ status: 400 }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const result = await opts.transport.request({ method: json.method, params: json.params });
|
|
70
|
+
return Response.json({ jsonrpc: "2.0", id: json.id, result });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const message = getException(error);
|
|
73
|
+
if (message) {
|
|
74
|
+
log.error(message);
|
|
75
|
+
return Response.json(
|
|
76
|
+
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message } },
|
|
77
|
+
//
|
|
78
|
+
{ status: 400 }
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (error instanceof Error) {
|
|
82
|
+
log.error(error.message);
|
|
83
|
+
}
|
|
84
|
+
return Response.json(
|
|
85
|
+
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "Internal server error" } },
|
|
86
|
+
//
|
|
87
|
+
{ status: 500 }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
http
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
6
96
|
function matchFilter(block, filter) {
|
|
7
97
|
if (!chainValid(block, filter)) return false;
|
|
8
98
|
if (!toBlockValid(block, filter)) return false;
|
|
@@ -48,48 +138,61 @@ function indexer(opts) {
|
|
|
48
138
|
const events_grouped_by_storage_map = /* @__PURE__ */ new Map();
|
|
49
139
|
const metadata = {
|
|
50
140
|
blocks: {
|
|
51
|
-
async
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
141
|
+
async getNextUnfinalizedBlock(chain) {
|
|
142
|
+
const prefix = `blocks/v1/${normalizeHex(chain)}`;
|
|
143
|
+
const keys = await opts.metadataStorage.list({ prefix, limit: 1 });
|
|
144
|
+
const [key] = keys.items;
|
|
145
|
+
if (key === void 0) {
|
|
146
|
+
return null;
|
|
55
147
|
}
|
|
56
|
-
const
|
|
57
|
-
return
|
|
58
|
-
const [__, ___, chain, number, hash] = key.split(":");
|
|
59
|
-
return { chain, number, hash };
|
|
60
|
-
});
|
|
148
|
+
const [_, __, ___, number, hash] = key.path.split("/");
|
|
149
|
+
return { chain, number, hash };
|
|
61
150
|
},
|
|
62
|
-
async
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
151
|
+
async getBlocksByNumber(chain, number) {
|
|
152
|
+
const prefix = `blocks/v1/${normalizeHex(chain)}/${normalizeHex(number, 16)}`;
|
|
153
|
+
const keys = await opts.metadataStorage.list({ prefix });
|
|
154
|
+
if (keys.items.length === 0) {
|
|
155
|
+
return [];
|
|
66
156
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
157
|
+
const blocks = await Promise.all(
|
|
158
|
+
keys.items.map(async (item) => {
|
|
159
|
+
const blob = await opts.metadataStorage.download(item.path, { as: "blob" });
|
|
160
|
+
const block = await decompress(blob);
|
|
161
|
+
const parsed = JSON.parse(block);
|
|
162
|
+
return parsed;
|
|
163
|
+
})
|
|
164
|
+
);
|
|
165
|
+
return blocks;
|
|
166
|
+
},
|
|
167
|
+
async getBlockByNumberAndHash(chain, number, hash) {
|
|
168
|
+
const prefix = `blocks/v1/${normalizeHex(chain)}/${normalizeHex(number, 16)}/${normalizeHex(hash)}`;
|
|
169
|
+
const blob = await opts.metadataStorage.download(prefix, { as: "blob" }).catch((error) => {
|
|
170
|
+
if (error instanceof StorageError) {
|
|
171
|
+
if (error.code === "NotFound") {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
75
174
|
}
|
|
76
|
-
|
|
175
|
+
throw error;
|
|
176
|
+
});
|
|
177
|
+
if (blob === null) {
|
|
178
|
+
return null;
|
|
77
179
|
}
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
return
|
|
180
|
+
const block = await decompress(blob);
|
|
181
|
+
const parsed = JSON.parse(block);
|
|
182
|
+
return parsed;
|
|
81
183
|
},
|
|
82
184
|
async upsert(blocks) {
|
|
83
185
|
if (blocks.length === 0) {
|
|
84
186
|
return;
|
|
85
187
|
}
|
|
86
|
-
await
|
|
87
|
-
blocks.map((block) => {
|
|
188
|
+
await Promise.all(
|
|
189
|
+
blocks.map(async (block) => {
|
|
88
190
|
const chain = normalizeHex(block.eth_chainId);
|
|
89
191
|
const hash = normalizeHex(block.eth_getBlockByNumber.hash);
|
|
90
192
|
const number = normalizeHex(block.eth_getBlockByNumber.number, 16);
|
|
91
|
-
const
|
|
92
|
-
|
|
193
|
+
const prefix = `blocks/v1/${chain}/${number}/${hash}`;
|
|
194
|
+
const compressed = await compress(JSON.stringify(block));
|
|
195
|
+
await opts.metadataStorage.upload(prefix, compressed);
|
|
93
196
|
})
|
|
94
197
|
);
|
|
95
198
|
},
|
|
@@ -102,7 +205,8 @@ function indexer(opts) {
|
|
|
102
205
|
const chain = normalizeHex(block.eth_chainId);
|
|
103
206
|
const hash = normalizeHex(block.eth_getBlockByNumber.hash);
|
|
104
207
|
const number = normalizeHex(block.eth_getBlockByNumber.number, 16);
|
|
105
|
-
|
|
208
|
+
const prefix = `blocks/v1/${chain}/${number}/${hash}`;
|
|
209
|
+
await opts.metadataStorage.delete(prefix);
|
|
106
210
|
})
|
|
107
211
|
);
|
|
108
212
|
}
|
|
@@ -133,8 +237,8 @@ function indexer(opts) {
|
|
|
133
237
|
}
|
|
134
238
|
const GetBlockError = createException("Failed to load block from the provided `getBlock` function");
|
|
135
239
|
const public_getFinalizedHeight = async (chain) => {
|
|
136
|
-
const [
|
|
137
|
-
metadata.blocks.
|
|
240
|
+
const [stored, finalizedBlock] = await Promise.all([
|
|
241
|
+
metadata.blocks.getNextUnfinalizedBlock(chain),
|
|
138
242
|
//
|
|
139
243
|
getBlock({ chain, number: "finalized" })
|
|
140
244
|
]);
|
|
@@ -143,12 +247,68 @@ function indexer(opts) {
|
|
|
143
247
|
throw new Error(GetBlockError);
|
|
144
248
|
}
|
|
145
249
|
const finalizedHeight = hexToNumber(finalizedBlock.eth_getBlockByNumber.number);
|
|
146
|
-
if (stored ===
|
|
250
|
+
if (stored === null) {
|
|
147
251
|
return finalizedHeight;
|
|
148
252
|
}
|
|
149
253
|
const indexerHeight = hexToNumber(stored.number) - 1;
|
|
150
254
|
return Math.min(indexerHeight, finalizedHeight);
|
|
151
255
|
};
|
|
256
|
+
const public_writeUnfinalizedHead = async (head) => {
|
|
257
|
+
log.debug("Received unfinalized head...");
|
|
258
|
+
const blocks_start = Date.now();
|
|
259
|
+
const [finalizedBlock, block] = await Promise.all([
|
|
260
|
+
getBlock({ chain: head.chain, number: "finalized" }),
|
|
261
|
+
getBlock({ chain: head.chain, number: head.number, hash: head.hash })
|
|
262
|
+
]);
|
|
263
|
+
if (block === null) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (finalizedBlock === null) {
|
|
267
|
+
log.debug("Failed to determine finalized height when processing unfinalized heads");
|
|
268
|
+
throw new Error(GetBlockError);
|
|
269
|
+
}
|
|
270
|
+
const finalizedHeight = hexToNumber(finalizedBlock.eth_getBlockByNumber.number);
|
|
271
|
+
if (hexToNumber(head.number) <= finalizedHeight) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
log.debug(`Loaded block in ${Date.now() - blocks_start}ms`);
|
|
275
|
+
const metadata_start = Date.now();
|
|
276
|
+
await metadata.blocks.upsert([block]);
|
|
277
|
+
log.debug(`Persisted block to metadata in ${Date.now() - metadata_start}ms`);
|
|
278
|
+
const events_start = Date.now();
|
|
279
|
+
const promises = events_grouped_by_storage_map.entries().map(async ([storage, grouped_events]) => {
|
|
280
|
+
const batch = [];
|
|
281
|
+
for (const event2 of grouped_events) {
|
|
282
|
+
try {
|
|
283
|
+
if (!event2.filters.some((filter) => matchFilter(block, filter))) {
|
|
284
|
+
log.debug(`Block matches no filters for event ${event2.id}`);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const events = event2.handler(block);
|
|
288
|
+
for (const event3 of events) {
|
|
289
|
+
batch.push(event3);
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
log.error(`Failed to run your 'handler' for event ${event2.id}`);
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (batch.length > 0) {
|
|
297
|
+
const start = Date.now();
|
|
298
|
+
await retry(() => storage.upsert(batch), 2).catch((error) => {
|
|
299
|
+
for (const event2 of grouped_events) {
|
|
300
|
+
log.error(`Failed to run your 'upsert' handler for event ${event2.id}`);
|
|
301
|
+
}
|
|
302
|
+
throw error;
|
|
303
|
+
});
|
|
304
|
+
for (const event2 of grouped_events) {
|
|
305
|
+
log.debug(`Recorded ${batch.length} ${event2.id} in ${Date.now() - start}ms`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
await Promise.all(promises);
|
|
310
|
+
log.debug(`Wrote head in ${Date.now() - events_start}ms`);
|
|
311
|
+
};
|
|
152
312
|
const public_writeUnfinalizedHeads = async (heads) => {
|
|
153
313
|
if (heads.length === 0) {
|
|
154
314
|
return log.debug("No heads received, returning...");
|
|
@@ -231,11 +391,11 @@ function indexer(opts) {
|
|
|
231
391
|
};
|
|
232
392
|
const public_deleteReorganisedHead = async (head) => {
|
|
233
393
|
log.debug(`Received reorganised head ${hexToNumber(head.number)}`);
|
|
234
|
-
const [canonical_block,
|
|
394
|
+
const [canonical_block, stored_block] = await Promise.all([
|
|
235
395
|
getBlock({ chain: head.chain, number: head.number }),
|
|
236
|
-
metadata.blocks.
|
|
396
|
+
metadata.blocks.getBlockByNumberAndHash(head.chain, head.number, head.hash)
|
|
237
397
|
]);
|
|
238
|
-
if (stored_block ===
|
|
398
|
+
if (stored_block === null) {
|
|
239
399
|
return log.debug("Reorganised block never/already processed");
|
|
240
400
|
}
|
|
241
401
|
if (canonical_block === null) {
|
|
@@ -299,8 +459,8 @@ function indexer(opts) {
|
|
|
299
459
|
}
|
|
300
460
|
previousHead = head;
|
|
301
461
|
}
|
|
302
|
-
const [
|
|
303
|
-
metadata.blocks.
|
|
462
|
+
const [unfinalizedMetadataBlock, finalizedBlock] = await Promise.all([
|
|
463
|
+
metadata.blocks.getNextUnfinalizedBlock(chain),
|
|
304
464
|
//
|
|
305
465
|
getBlock({ chain, number: "finalized" })
|
|
306
466
|
]);
|
|
@@ -314,7 +474,7 @@ function indexer(opts) {
|
|
|
314
474
|
throw new Error("Received head greater than chain finalized height");
|
|
315
475
|
}
|
|
316
476
|
}
|
|
317
|
-
if (unfinalizedMetadataBlock ===
|
|
477
|
+
if (unfinalizedMetadataBlock === null) {
|
|
318
478
|
return log.debug("No heads to finalize, returning...");
|
|
319
479
|
}
|
|
320
480
|
const indexerHeight = hexToNumber(unfinalizedMetadataBlock.number) - 1;
|
|
@@ -330,10 +490,12 @@ function indexer(opts) {
|
|
|
330
490
|
return log.debug("No new heads received, returning...");
|
|
331
491
|
}
|
|
332
492
|
if (hexToNumber(firstNewHead.number) !== indexerHeight + 1) {
|
|
333
|
-
throw new Error(
|
|
493
|
+
throw new Error(
|
|
494
|
+
`Client advanced too far (${hexToNumber(firstNewHead.number)}). Heads received are greater than the first new head ${indexerHeight + 1}`
|
|
495
|
+
);
|
|
334
496
|
}
|
|
335
497
|
for (const head of newHeads) {
|
|
336
|
-
const processed = await metadata.blocks.
|
|
498
|
+
const processed = await metadata.blocks.getBlocksByNumber(head.chain, head.number);
|
|
337
499
|
if (processed.length === 0) {
|
|
338
500
|
await writeFinalizedHead(head, null);
|
|
339
501
|
continue;
|
|
@@ -683,10 +845,21 @@ function indexer(opts) {
|
|
|
683
845
|
});
|
|
684
846
|
return { results: results_array, keys: filtered_keys };
|
|
685
847
|
};
|
|
848
|
+
const event = (event2) => {
|
|
849
|
+
if (!/^[A-Za-z0-9_-]+$/.test(event2.id)) {
|
|
850
|
+
throw new Error(`Invalid event id \`${event2.id}\`. Only characters A-Z, a-z, 0-9, underscores, and hyphens are permitted.`);
|
|
851
|
+
}
|
|
852
|
+
all_events.push(event2);
|
|
853
|
+
const group = events_grouped_by_storage_map.get(event2.storage) ?? [];
|
|
854
|
+
group.push(event2);
|
|
855
|
+
events_grouped_by_storage_map.set(event2.storage, group);
|
|
856
|
+
return event2;
|
|
857
|
+
};
|
|
686
858
|
const rpc = {
|
|
687
859
|
request: {
|
|
688
860
|
public_getFinalizedHeight,
|
|
689
861
|
public_writeFinalizedHeads,
|
|
862
|
+
public_writeUnfinalizedHead,
|
|
690
863
|
public_writeUnfinalizedHeads,
|
|
691
864
|
public_deleteReorganisedHead,
|
|
692
865
|
private_getEvents,
|
|
@@ -696,105 +869,17 @@ function indexer(opts) {
|
|
|
696
869
|
},
|
|
697
870
|
subscribe: {}
|
|
698
871
|
};
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
} catch {
|
|
705
|
-
return Response.json(
|
|
706
|
-
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Invalid request body" } },
|
|
707
|
-
//
|
|
708
|
-
{ status: 400 }
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
const authorization = req.headers.get("Authorization");
|
|
712
|
-
const authenticated = authorization === `Bearer ${opts.signingKey}`;
|
|
713
|
-
let body_string;
|
|
714
|
-
if (req.headers.get("Content-Encoding") === "gzip") {
|
|
715
|
-
if (!authorization) {
|
|
716
|
-
return Response.json(
|
|
717
|
-
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "No bearer token provided" } },
|
|
718
|
-
//
|
|
719
|
-
{ status: 400 }
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
if (!authenticated) {
|
|
723
|
-
return Response.json(
|
|
724
|
-
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Invalid bearer token" } },
|
|
725
|
-
//
|
|
726
|
-
{ status: 400 }
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
body_string = await decompress(body_buffer);
|
|
730
|
-
} else {
|
|
731
|
-
body_string = decoder.decode(body_buffer);
|
|
732
|
-
}
|
|
733
|
-
let json;
|
|
734
|
-
try {
|
|
735
|
-
json = JSON.parse(body_string);
|
|
736
|
-
} catch {
|
|
737
|
-
return Response.json(
|
|
738
|
-
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Malformed JSON request body" } },
|
|
739
|
-
//
|
|
740
|
-
{ status: 400 }
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
if (json.method.startsWith("private_")) {
|
|
744
|
-
if (!authorization) {
|
|
745
|
-
return Response.json(
|
|
746
|
-
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "No bearer token provided" } },
|
|
747
|
-
//
|
|
748
|
-
{ status: 400 }
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
if (authorization !== `Bearer ${opts.signingKey}`) {
|
|
752
|
-
return Response.json(
|
|
753
|
-
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "Invalid bearer token" } },
|
|
754
|
-
//
|
|
755
|
-
{ status: 400 }
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
try {
|
|
760
|
-
const result = await transport.request({ method: json.method, params: json.params });
|
|
761
|
-
return Response.json({ jsonrpc: "2.0", id: json.id, result });
|
|
762
|
-
} catch (error) {
|
|
763
|
-
const message = getException(error);
|
|
764
|
-
if (message) {
|
|
765
|
-
log.error(message);
|
|
766
|
-
return Response.json(
|
|
767
|
-
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message } },
|
|
768
|
-
//
|
|
769
|
-
{ status: 400 }
|
|
770
|
-
);
|
|
771
|
-
}
|
|
772
|
-
if (error instanceof Error) {
|
|
773
|
-
log.error(error.message);
|
|
774
|
-
}
|
|
775
|
-
return Response.json(
|
|
776
|
-
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "Internal server error" } },
|
|
777
|
-
//
|
|
778
|
-
{ status: 500 }
|
|
779
|
-
);
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
const event = (event2) => {
|
|
783
|
-
if (!/^[A-Za-z0-9_-]+$/.test(event2.id)) {
|
|
784
|
-
throw new Error(`Invalid event id \`${event2.id}\`. Only characters A-Z, a-z, 0-9, underscores, and hyphens are permitted.`);
|
|
785
|
-
}
|
|
786
|
-
all_events.push(event2);
|
|
787
|
-
const group = events_grouped_by_storage_map.get(event2.storage) ?? [];
|
|
788
|
-
group.push(event2);
|
|
789
|
-
events_grouped_by_storage_map.set(event2.storage, group);
|
|
790
|
-
return event2;
|
|
791
|
-
};
|
|
872
|
+
const server = createServer({
|
|
873
|
+
transport: local(rpc),
|
|
874
|
+
quiet: opts.quiet ?? false,
|
|
875
|
+
signingKey: opts.signingKey
|
|
876
|
+
});
|
|
792
877
|
const indexer2 = {
|
|
793
878
|
...rpc,
|
|
794
879
|
event,
|
|
795
|
-
fetch:
|
|
880
|
+
fetch: server.http
|
|
796
881
|
};
|
|
797
882
|
return indexer2;
|
|
798
883
|
}
|
|
799
884
|
|
|
800
|
-
export { indexer
|
|
885
|
+
export { indexer };
|
package/dist/realtime.d.mts
CHANGED
package/dist/realtime.d.ts
CHANGED
package/dist/realtime.mjs
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
|
-
import { c as createLogger,
|
|
1
|
+
import { c as createLogger, i as iife, h as hexToNumber, n as numberToHex, m as mutex, r as retry, a as isHexEqual } from './shared/univo.BVikUbk_.mjs';
|
|
2
2
|
|
|
3
|
-
const MAX_LENGTH = 1e4;
|
|
4
3
|
function defineBlockchain(opts) {
|
|
5
4
|
const log = createLogger({ quiet: opts.quiet });
|
|
6
5
|
const chain = [];
|
|
6
|
+
function getHeadBlock() {
|
|
7
|
+
const block = chain[chain.length - 1];
|
|
8
|
+
if (block === void 0) {
|
|
9
|
+
throw new Error("Expected non-empty chain when retrieving latest block");
|
|
10
|
+
}
|
|
11
|
+
return block;
|
|
12
|
+
}
|
|
13
|
+
function getTailBlock() {
|
|
14
|
+
const block = chain[0];
|
|
15
|
+
if (block === void 0) {
|
|
16
|
+
throw new Error("Expected non-empty chain when retrieving oldest block");
|
|
17
|
+
}
|
|
18
|
+
return block;
|
|
19
|
+
}
|
|
7
20
|
function setHeadBlock(newBlock) {
|
|
8
21
|
chain.push(newBlock);
|
|
9
22
|
if (opts.onBlockAdded) {
|
|
10
23
|
opts.onBlockAdded(newBlock);
|
|
11
24
|
}
|
|
12
|
-
if (chain.length > MAX_LENGTH) {
|
|
13
|
-
chain.shift();
|
|
14
|
-
}
|
|
15
25
|
}
|
|
16
26
|
function removeHeadBlock() {
|
|
17
27
|
const head = chain.pop();
|
|
@@ -22,25 +32,19 @@ function defineBlockchain(opts) {
|
|
|
22
32
|
opts.onBlockReorganised(head);
|
|
23
33
|
}
|
|
24
34
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
throw new Error("Expected non-empty chain when retrieving latest block");
|
|
35
|
+
async function prune(tail) {
|
|
36
|
+
if (hexToNumber(tail.number) >= hexToNumber(getHeadBlock().number)) {
|
|
37
|
+
throw new Error("Cannot remove head block");
|
|
29
38
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
function getOldestBlock() {
|
|
33
|
-
const block = chain[0];
|
|
34
|
-
if (block === void 0) {
|
|
35
|
-
throw new Error("Expected non-empty chain when retrieving oldest block");
|
|
39
|
+
while (hexToNumber(tail.number) > hexToNumber(getTailBlock().number)) {
|
|
40
|
+
chain.shift();
|
|
36
41
|
}
|
|
37
|
-
return block;
|
|
38
42
|
}
|
|
39
43
|
async function reconcile(newBlock) {
|
|
40
44
|
if (chain.length === 0) {
|
|
41
45
|
return setHeadBlock(newBlock);
|
|
42
46
|
}
|
|
43
|
-
if (hexToNumber(newBlock.number) < hexToNumber(
|
|
47
|
+
if (hexToNumber(newBlock.number) < hexToNumber(getTailBlock().number)) {
|
|
44
48
|
log.info(`Received block ${hexToNumber(newBlock.number)} older than our local chain`);
|
|
45
49
|
throw new Error("Not implemented");
|
|
46
50
|
}
|
|
@@ -74,6 +78,7 @@ function defineBlockchain(opts) {
|
|
|
74
78
|
}
|
|
75
79
|
return {
|
|
76
80
|
chain,
|
|
81
|
+
prune: mutex(prune),
|
|
77
82
|
reconcile: mutex(reconcile)
|
|
78
83
|
};
|
|
79
84
|
}
|
|
@@ -194,6 +199,16 @@ function realtime(opts) {
|
|
|
194
199
|
log.debug(`Delivered ${newHeads.length} finalized head(s)`);
|
|
195
200
|
indexerHeight = hexToNumber(finalizedBlock.number);
|
|
196
201
|
log.debug(`Updated indexer height to ${indexerHeight}`);
|
|
202
|
+
await indexer.prune({
|
|
203
|
+
number: finalizedBlock.number,
|
|
204
|
+
hash: finalizedBlock.hash,
|
|
205
|
+
parent_hash: finalizedBlock.parentHash
|
|
206
|
+
});
|
|
207
|
+
await latest.prune({
|
|
208
|
+
number: finalizedBlock.number,
|
|
209
|
+
hash: finalizedBlock.hash,
|
|
210
|
+
parent_hash: finalizedBlock.parentHash
|
|
211
|
+
});
|
|
197
212
|
} catch (error) {
|
|
198
213
|
if (error instanceof Error) {
|
|
199
214
|
log.error(`Failed to reconcile finalized head: ${error.message}`);
|
package/dist/rpc.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as Head, M as Metadata, F as Flatten, a as Filter, B as Block, R as Result } from './shared/univo.
|
|
2
|
-
import '
|
|
1
|
+
import { H as Head, M as Metadata, F as Flatten, a as Filter, B as Block, R as Result } from './shared/univo.DdHfpTWq.mjs';
|
|
2
|
+
import '@storagesdk/core';
|
|
3
3
|
|
|
4
4
|
type Rpc = {
|
|
5
5
|
/**
|
|
@@ -54,6 +54,10 @@ type IndexerRpc = {
|
|
|
54
54
|
* Accepts a reorganised block and deletes all related events from storage
|
|
55
55
|
*/
|
|
56
56
|
public_deleteReorganisedHead: (head: Head) => Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Accepts an unfinalized head and writes all events to storage
|
|
59
|
+
*/
|
|
60
|
+
public_writeUnfinalizedHead: (head: Head) => Promise<void>;
|
|
57
61
|
/**
|
|
58
62
|
* Accepts a list of the latest block heads and writes all events to storage
|
|
59
63
|
*/
|
package/dist/rpc.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as Head, M as Metadata, F as Flatten, a as Filter, B as Block, R as Result } from './shared/univo.
|
|
2
|
-
import '
|
|
1
|
+
import { H as Head, M as Metadata, F as Flatten, a as Filter, B as Block, R as Result } from './shared/univo.DdHfpTWq.js';
|
|
2
|
+
import '@storagesdk/core';
|
|
3
3
|
|
|
4
4
|
type Rpc = {
|
|
5
5
|
/**
|
|
@@ -54,6 +54,10 @@ type IndexerRpc = {
|
|
|
54
54
|
* Accepts a reorganised block and deletes all related events from storage
|
|
55
55
|
*/
|
|
56
56
|
public_deleteReorganisedHead: (head: Head) => Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Accepts an unfinalized head and writes all events to storage
|
|
59
|
+
*/
|
|
60
|
+
public_writeUnfinalizedHead: (head: Head) => Promise<void>;
|
|
57
61
|
/**
|
|
58
62
|
* Accepts a list of the latest block heads and writes all events to storage
|
|
59
63
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const name = "univo";
|
|
2
|
-
const version = "0.2.
|
|
2
|
+
const version = "0.2.16";
|
|
3
3
|
|
|
4
4
|
function createLogger(opts) {
|
|
5
5
|
let prefix = `[${name}@${version}]`;
|
|
@@ -78,6 +78,10 @@ async function compress(input) {
|
|
|
78
78
|
return new Response(compressed).arrayBuffer();
|
|
79
79
|
}
|
|
80
80
|
async function decompress(input) {
|
|
81
|
+
if (input instanceof Blob) {
|
|
82
|
+
const decompressed2 = input.stream().pipeThrough(new DecompressionStream("gzip"));
|
|
83
|
+
return new Response(decompressed2).text();
|
|
84
|
+
}
|
|
81
85
|
const decompressed = new Blob([input]).stream().pipeThrough(new DecompressionStream("gzip"));
|
|
82
86
|
return new Response(decompressed).text();
|
|
83
87
|
}
|
|
@@ -89,4 +93,4 @@ const normalizeHex = (hex, length) => {
|
|
|
89
93
|
return `0x${hex.slice(2).toLowerCase().padStart(length, "0")}`;
|
|
90
94
|
};
|
|
91
95
|
|
|
92
|
-
export {
|
|
96
|
+
export { isHexEqual as a, name as b, createLogger as c, decompress as d, decoder as e, normalizeHex as f, compress as g, hexToNumber as h, iife as i, raise as j, mutex as m, numberToHex as n, retry as r, version as v };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ws from 'ws';
|
|
2
2
|
import { WebSocket } from 'partysocket';
|
|
3
|
-
import { g as compress, j as raise, c as createLogger } from './univo.
|
|
3
|
+
import { g as compress, j as raise, c as createLogger } from './univo.BVikUbk_.mjs';
|
|
4
4
|
|
|
5
5
|
function createException(message) {
|
|
6
6
|
return `univo:${message}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Storage } from '
|
|
1
|
+
import { Storage } from '@storagesdk/core';
|
|
2
2
|
|
|
3
3
|
type CamelToSnake<T extends string, P extends string = ""> = string extends T ? string : T extends `${infer C0}${infer R}` ? CamelToSnake<R, `${P}${C0 extends Lowercase<C0> ? "" : "_"}${Lowercase<C0>}`> : P;
|
|
4
4
|
type Flatten<T> = {
|
|
@@ -38,7 +38,6 @@ type Filter = {
|
|
|
38
38
|
/** Index blocks that involve this address */
|
|
39
39
|
address?: `0x${string}`;
|
|
40
40
|
};
|
|
41
|
-
declare function matchFilter(block: Block, filter: Filter): boolean;
|
|
42
41
|
/**
|
|
43
42
|
* Events -----------------------------------------------------------------------------------------------------------------------------------
|
|
44
43
|
*/
|
|
@@ -192,6 +191,7 @@ declare function indexer<TBlock extends Block>(opts: IndexerOptions<TBlock>): {
|
|
|
192
191
|
request: {
|
|
193
192
|
public_getFinalizedHeight: (chain: `0x${string}`) => Promise<number>;
|
|
194
193
|
public_deleteReorganisedHead: (head: Head) => Promise<void>;
|
|
194
|
+
public_writeUnfinalizedHead: (head: Head) => Promise<void>;
|
|
195
195
|
public_writeUnfinalizedHeads: (heads: Head[]) => Promise<void>;
|
|
196
196
|
public_writeFinalizedHeads: (heads: Head[]) => Promise<void>;
|
|
197
197
|
private_getMetadata: () => Promise<Metadata>;
|
|
@@ -216,5 +216,5 @@ declare function indexer<TBlock extends Block>(opts: IndexerOptions<TBlock>): {
|
|
|
216
216
|
subscribe: Record<string, never>;
|
|
217
217
|
};
|
|
218
218
|
|
|
219
|
-
export { indexer as i
|
|
219
|
+
export { indexer as i };
|
|
220
220
|
export type { Block as B, Event as E, Flatten as F, Head as H, Indexer as I, Metadata as M, Result as R, Filter as a };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Storage } from '
|
|
1
|
+
import { Storage } from '@storagesdk/core';
|
|
2
2
|
|
|
3
3
|
type CamelToSnake<T extends string, P extends string = ""> = string extends T ? string : T extends `${infer C0}${infer R}` ? CamelToSnake<R, `${P}${C0 extends Lowercase<C0> ? "" : "_"}${Lowercase<C0>}`> : P;
|
|
4
4
|
type Flatten<T> = {
|
|
@@ -38,7 +38,6 @@ type Filter = {
|
|
|
38
38
|
/** Index blocks that involve this address */
|
|
39
39
|
address?: `0x${string}`;
|
|
40
40
|
};
|
|
41
|
-
declare function matchFilter(block: Block, filter: Filter): boolean;
|
|
42
41
|
/**
|
|
43
42
|
* Events -----------------------------------------------------------------------------------------------------------------------------------
|
|
44
43
|
*/
|
|
@@ -192,6 +191,7 @@ declare function indexer<TBlock extends Block>(opts: IndexerOptions<TBlock>): {
|
|
|
192
191
|
request: {
|
|
193
192
|
public_getFinalizedHeight: (chain: `0x${string}`) => Promise<number>;
|
|
194
193
|
public_deleteReorganisedHead: (head: Head) => Promise<void>;
|
|
194
|
+
public_writeUnfinalizedHead: (head: Head) => Promise<void>;
|
|
195
195
|
public_writeUnfinalizedHeads: (heads: Head[]) => Promise<void>;
|
|
196
196
|
public_writeFinalizedHeads: (heads: Head[]) => Promise<void>;
|
|
197
197
|
private_getMetadata: () => Promise<Metadata>;
|
|
@@ -216,5 +216,5 @@ declare function indexer<TBlock extends Block>(opts: IndexerOptions<TBlock>): {
|
|
|
216
216
|
subscribe: Record<string, never>;
|
|
217
217
|
};
|
|
218
218
|
|
|
219
|
-
export { indexer as i
|
|
219
|
+
export { indexer as i };
|
|
220
220
|
export type { Block as B, Event as E, Flatten as F, Head as H, Indexer as I, Metadata as M, Result as R, Filter as a };
|
package/dist/transport.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Rpc } from './rpc.mjs';
|
|
2
|
-
import './shared/univo.
|
|
3
|
-
import '
|
|
2
|
+
import './shared/univo.DdHfpTWq.mjs';
|
|
3
|
+
import '@storagesdk/core';
|
|
4
4
|
|
|
5
5
|
type Protocol = "http" | "wss" | "local";
|
|
6
6
|
type Transport<R extends Rpc, P extends Protocol = Protocol> = {
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Rpc } from './rpc.js';
|
|
2
|
-
import './shared/univo.
|
|
3
|
-
import '
|
|
2
|
+
import './shared/univo.DdHfpTWq.js';
|
|
3
|
+
import '@storagesdk/core';
|
|
4
4
|
|
|
5
5
|
type Protocol = "http" | "wss" | "local";
|
|
6
6
|
type Transport<R extends Rpc, P extends Protocol = Protocol> = {
|
package/dist/transport.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import 'ws';
|
|
2
2
|
import 'partysocket';
|
|
3
|
-
export { h as http, l as local, w as wss } from './shared/univo.
|
|
4
|
-
import './shared/univo.
|
|
3
|
+
export { h as http, l as local, w as wss } from './shared/univo.CkhykU8v.mjs';
|
|
4
|
+
import './shared/univo.BVikUbk_.mjs';
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "univo",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.16",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"vitest": "^3.1.3"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"
|
|
57
|
+
"@storagesdk/core": ">=0.4.2",
|
|
58
|
+
"@storagesdk/adapters": ">=0.6.0"
|
|
58
59
|
}
|
|
59
60
|
}
|