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 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.CzTm3CDN.mjs';
8
- import { c as createLogger, v as version, f as name } from './shared/univo.sqV2c2Yj.mjs';
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://")) throw new Error("`url` must start with http://");
22
- config();
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) throw new Error("Please provide a UNIVO_SIGNING_KEY environment variable");
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
- const [events, metadata] = await Promise.all([
27
- client.request({ method: "private_getEvents", params: [] }),
28
- client.request({ method: "private_getMetadata", params: [] })
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 data = btoa(JSON.stringify(endpoint));
39
- await open(`https://univo.app/dev/${data}`);
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) => log.error(event.message);
43
- ws$1.onopen = () => log.info(`Dev server started for ${ctx.args.url}`);
44
- ws$1.onclose = () => log.info(`Dev server closed for ${ctx.args.url}`);
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 request = JSON.parse(event.data);
48
- log.info(`Received request ${request.id}:${request.method}`);
49
- const response = await client.request(request);
50
- ws$1.send(JSON.stringify(response));
51
- } catch (error) {
52
- const response = { jsonrpc: "2.0", id: null, error: { code: 0, message: "Invalid request" } };
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, m as matchFilter } from './shared/univo.DcmLb3Hn.mjs';
2
- import 'unstorage';
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, m as matchFilter } from './shared/univo.DcmLb3Hn.js';
2
- import 'unstorage';
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 { c as createException, a as catchException, l as local, g as getException } from './shared/univo.CzTm3CDN.mjs';
2
- import { c as createLogger, r as retry, v as version, h as hexToNumber, i as isHexEqual, d as decompress, a as decoder, n as normalizeHex } from './shared/univo.sqV2c2Yj.mjs';
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 list(head) {
52
- let prefix = `/blocks/v1/${normalizeHex(head.chain)}`;
53
- if (typeof head.number === "string") {
54
- prefix += `/${normalizeHex(head.number, 16)}`;
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 keys = await opts.metadataStorage.getKeys(prefix);
57
- return keys.map((key) => {
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 get(head) {
63
- let prefix = `/blocks/v1/${head.chain.toLowerCase()}`;
64
- if (typeof head.number === "string") {
65
- prefix += `/${normalizeHex(head.number, 16)}`;
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
- if (typeof head.hash === "string") {
68
- if (head.number === void 0) {
69
- throw new Error("Requested block by hash without specifying a block number");
70
- }
71
- prefix += `/${head.hash.toLowerCase()}`;
72
- const block = await opts.metadataStorage.getItem(prefix);
73
- if (block === null) {
74
- return [];
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
- return [block];
175
+ throw error;
176
+ });
177
+ if (blob === null) {
178
+ return null;
77
179
  }
78
- const keys = await opts.metadataStorage.getKeys(prefix);
79
- const results = await opts.metadataStorage.getItems(keys);
80
- return results.map((result) => result.value);
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 opts.metadataStorage.setItems(
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 key = `/blocks/v1/${chain}/${number}/${hash}`;
92
- return { key, value: block };
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
- await opts.metadataStorage.del(`/blocks/v1/${chain}/${number}/${hash}`);
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 [[stored], finalizedBlock] = await Promise.all([
137
- metadata.blocks.list({ chain }),
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 === void 0) {
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, [stored_block]] = await Promise.all([
394
+ const [canonical_block, stored_block] = await Promise.all([
235
395
  getBlock({ chain: head.chain, number: head.number }),
236
- metadata.blocks.get({ chain: head.chain, number: head.number, hash: head.hash })
396
+ metadata.blocks.getBlockByNumberAndHash(head.chain, head.number, head.hash)
237
397
  ]);
238
- if (stored_block === void 0) {
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 [[unfinalizedMetadataBlock], finalizedBlock] = await Promise.all([
303
- metadata.blocks.list({ chain }),
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 === void 0) {
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("Client advanced too far. Heads received are greater than the indexer height");
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.get({ chain: head.chain, number: head.number });
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 transport = local(rpc);
700
- const handler = async (req) => {
701
- let body_buffer;
702
- try {
703
- body_buffer = await req.arrayBuffer();
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: handler
880
+ fetch: server.http
796
881
  };
797
882
  return indexer2;
798
883
  }
799
884
 
800
- export { indexer, matchFilter };
885
+ export { indexer };
@@ -1,7 +1,7 @@
1
1
  import { NodeRpc, IndexerRpc } from './rpc.mjs';
2
2
  import { Transport } from './transport.mjs';
3
- import './shared/univo.DcmLb3Hn.mjs';
4
- import 'unstorage';
3
+ import './shared/univo.DdHfpTWq.mjs';
4
+ import '@storagesdk/core';
5
5
 
6
6
  type RealtimeOptions = {
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  import { NodeRpc, IndexerRpc } from './rpc.js';
2
2
  import { Transport } from './transport.js';
3
- import './shared/univo.DcmLb3Hn.js';
4
- import 'unstorage';
3
+ import './shared/univo.DdHfpTWq.js';
4
+ import '@storagesdk/core';
5
5
 
6
6
  type RealtimeOptions = {
7
7
  /**
package/dist/realtime.mjs CHANGED
@@ -1,17 +1,27 @@
1
- import { c as createLogger, b as iife, h as hexToNumber, e as numberToHex, m as mutex, r as retry, i as isHexEqual } from './shared/univo.sqV2c2Yj.mjs';
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 getHeadBlock() {
26
- const block = chain[chain.length - 1];
27
- if (block === void 0) {
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
- return block;
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(getOldestBlock().number)) {
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.DcmLb3Hn.mjs';
2
- import 'unstorage';
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.DcmLb3Hn.js';
2
- import 'unstorage';
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.14";
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 { decoder as a, iife as b, createLogger as c, decompress as d, numberToHex as e, name as f, compress as g, hexToNumber as h, isHexEqual as i, raise as j, mutex as m, normalizeHex as n, retry as r, version as v };
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.sqV2c2Yj.mjs';
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 'unstorage';
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, matchFilter as m };
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 'unstorage';
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, matchFilter as m };
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,6 +1,6 @@
1
1
  import { Rpc } from './rpc.mjs';
2
- import './shared/univo.DcmLb3Hn.mjs';
3
- import 'unstorage';
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> = {
@@ -1,6 +1,6 @@
1
1
  import { Rpc } from './rpc.js';
2
- import './shared/univo.DcmLb3Hn.js';
3
- import 'unstorage';
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> = {
@@ -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.CzTm3CDN.mjs';
4
- import './shared/univo.sqV2c2Yj.mjs';
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.14",
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
- "unstorage": ">=1"
57
+ "@storagesdk/core": ">=0.4.2",
58
+ "@storagesdk/adapters": ">=0.6.0"
58
59
  }
59
60
  }