@apibara/indexer 2.1.0-beta.3 → 2.1.0-beta.31
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/index.cjs +71 -26
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +61 -16
- package/dist/index.mjs.map +1 -0
- package/dist/internal/index.cjs +1 -0
- package/dist/internal/index.cjs.map +1 -0
- package/dist/internal/index.mjs +1 -0
- package/dist/internal/index.mjs.map +1 -0
- package/dist/internal/plugins.cjs +4 -4
- package/dist/internal/plugins.cjs.map +1 -0
- package/dist/internal/plugins.d.cts +1 -1
- package/dist/internal/plugins.d.mts +1 -1
- package/dist/internal/plugins.d.ts +1 -1
- package/dist/internal/plugins.mjs +2 -2
- package/dist/internal/plugins.mjs.map +1 -0
- package/dist/internal/testing.cjs +25 -10
- package/dist/internal/testing.cjs.map +1 -0
- package/dist/internal/testing.d.cts +5 -3
- package/dist/internal/testing.d.mts +5 -3
- package/dist/internal/testing.d.ts +5 -3
- package/dist/internal/testing.mjs +23 -8
- package/dist/internal/testing.mjs.map +1 -0
- package/dist/plugins/index.cjs +4 -4
- package/dist/plugins/index.cjs.map +1 -0
- package/dist/plugins/index.d.cts +2 -2
- package/dist/plugins/index.d.mts +2 -2
- package/dist/plugins/index.d.ts +2 -2
- package/dist/plugins/index.mjs +4 -4
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/shared/{indexer.fedcd831.d.cts → indexer.27b29a67.d.cts} +3 -4
- package/dist/shared/{indexer.fedcd831.d.mts → indexer.27b29a67.d.mts} +3 -4
- package/dist/shared/{indexer.fedcd831.d.ts → indexer.27b29a67.d.ts} +3 -4
- package/dist/shared/{indexer.077335f3.cjs → indexer.479ae593.cjs} +6 -0
- package/dist/shared/indexer.479ae593.cjs.map +1 -0
- package/dist/shared/{indexer.a55ad619.mjs → indexer.75773ef1.mjs} +6 -1
- package/dist/shared/indexer.75773ef1.mjs.map +1 -0
- package/dist/shared/{indexer.ff25c953.mjs → indexer.98a921a7.mjs} +2 -2
- package/dist/shared/indexer.98a921a7.mjs.map +1 -0
- package/dist/shared/{indexer.2416906c.cjs → indexer.a09fa402.cjs} +4 -4
- package/dist/shared/indexer.a09fa402.cjs.map +1 -0
- package/dist/testing/index.cjs +10 -5
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +4 -3
- package/dist/testing/index.d.mts +4 -3
- package/dist/testing/index.d.ts +4 -3
- package/dist/testing/index.mjs +10 -5
- package/dist/testing/index.mjs.map +1 -0
- package/dist/vcr/index.cjs +3 -1
- package/dist/vcr/index.cjs.map +1 -0
- package/dist/vcr/index.d.cts +1 -1
- package/dist/vcr/index.d.mts +1 -1
- package/dist/vcr/index.d.ts +1 -1
- package/dist/vcr/index.mjs +3 -1
- package/dist/vcr/index.mjs.map +1 -0
- package/package.json +3 -3
- package/src/indexer.ts +49 -15
- package/src/internal/testing.ts +34 -11
- package/src/otel.ts +29 -2
- package/src/testing/index.ts +11 -0
- package/dist/shared/indexer.601ceab0.cjs +0 -7
- package/dist/shared/indexer.9b21ddd2.mjs +0 -5
- package/src/compose.test.ts +0 -76
- package/src/indexer.test.ts +0 -430
package/dist/testing/index.d.cts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { I as IndexerWithStreamConfig } from '../shared/indexer.
|
|
1
|
+
import { I as IndexerWithStreamConfig } from '../shared/indexer.27b29a67.cjs';
|
|
2
2
|
import '@apibara/protocol';
|
|
3
3
|
import 'hookable';
|
|
4
4
|
|
|
5
|
+
type VcrResult = Record<string, unknown>;
|
|
5
6
|
declare function createVcr(): {
|
|
6
7
|
run<TFilter, TBlock>(cassetteName: string, indexerConfig: IndexerWithStreamConfig<TFilter, TBlock>, range: {
|
|
7
8
|
fromBlock: bigint;
|
|
8
9
|
toBlock: bigint;
|
|
9
|
-
}): Promise<
|
|
10
|
+
}): Promise<VcrResult>;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
export { createVcr };
|
|
13
|
+
export { type VcrResult, createVcr };
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { I as IndexerWithStreamConfig } from '../shared/indexer.
|
|
1
|
+
import { I as IndexerWithStreamConfig } from '../shared/indexer.27b29a67.mjs';
|
|
2
2
|
import '@apibara/protocol';
|
|
3
3
|
import 'hookable';
|
|
4
4
|
|
|
5
|
+
type VcrResult = Record<string, unknown>;
|
|
5
6
|
declare function createVcr(): {
|
|
6
7
|
run<TFilter, TBlock>(cassetteName: string, indexerConfig: IndexerWithStreamConfig<TFilter, TBlock>, range: {
|
|
7
8
|
fromBlock: bigint;
|
|
8
9
|
toBlock: bigint;
|
|
9
|
-
}): Promise<
|
|
10
|
+
}): Promise<VcrResult>;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
export { createVcr };
|
|
13
|
+
export { type VcrResult, createVcr };
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { I as IndexerWithStreamConfig } from '../shared/indexer.
|
|
1
|
+
import { I as IndexerWithStreamConfig } from '../shared/indexer.27b29a67.js';
|
|
2
2
|
import '@apibara/protocol';
|
|
3
3
|
import 'hookable';
|
|
4
4
|
|
|
5
|
+
type VcrResult = Record<string, unknown>;
|
|
5
6
|
declare function createVcr(): {
|
|
6
7
|
run<TFilter, TBlock>(cassetteName: string, indexerConfig: IndexerWithStreamConfig<TFilter, TBlock>, range: {
|
|
7
8
|
fromBlock: bigint;
|
|
8
9
|
toBlock: bigint;
|
|
9
|
-
}): Promise<
|
|
10
|
+
}): Promise<VcrResult>;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
export { createVcr };
|
|
13
|
+
export { type VcrResult, createVcr };
|
package/dist/testing/index.mjs
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { createClient } from '@apibara/protocol';
|
|
2
2
|
import ci from 'ci-info';
|
|
3
|
+
import { u as useIndexerContext } from '../shared/indexer.75773ef1.mjs';
|
|
3
4
|
import { createIndexer } from '../index.mjs';
|
|
4
5
|
import { internalContext } from '../internal/plugins.mjs';
|
|
5
|
-
import { l as logger } from '../shared/indexer.
|
|
6
|
+
import { l as logger } from '../shared/indexer.98a921a7.mjs';
|
|
6
7
|
import { isCassetteAvailable, record, replay } from '../vcr/index.mjs';
|
|
8
|
+
import 'node:async_hooks';
|
|
9
|
+
import 'unctx';
|
|
7
10
|
import 'consola';
|
|
8
11
|
import 'hookable';
|
|
9
12
|
import 'node:assert';
|
|
10
|
-
import '../shared/indexer.a55ad619.mjs';
|
|
11
|
-
import 'node:async_hooks';
|
|
12
|
-
import 'unctx';
|
|
13
13
|
import '@opentelemetry/api';
|
|
14
|
-
import '../shared/indexer.9b21ddd2.mjs';
|
|
15
14
|
import 'node:fs/promises';
|
|
16
15
|
import 'node:path';
|
|
17
16
|
import 'node:fs';
|
|
18
17
|
import '@apibara/protocol/testing';
|
|
19
18
|
|
|
20
19
|
function createVcr() {
|
|
20
|
+
let result;
|
|
21
21
|
return {
|
|
22
22
|
async run(cassetteName, indexerConfig, range) {
|
|
23
23
|
const vcrConfig = {
|
|
@@ -41,6 +41,9 @@ function createVcr() {
|
|
|
41
41
|
...indexerConfig.plugins ?? []
|
|
42
42
|
];
|
|
43
43
|
const indexer = createIndexer(indexerConfig);
|
|
44
|
+
indexer.hooks.hook("run:after", () => {
|
|
45
|
+
result = useIndexerContext();
|
|
46
|
+
});
|
|
44
47
|
if (!isCassetteAvailable(vcrConfig, cassetteName)) {
|
|
45
48
|
if (ci.isCI) {
|
|
46
49
|
throw new Error("Cannot record cassette in CI");
|
|
@@ -53,8 +56,10 @@ function createVcr() {
|
|
|
53
56
|
} else {
|
|
54
57
|
await replay(vcrConfig, indexer, cassetteName);
|
|
55
58
|
}
|
|
59
|
+
return result;
|
|
56
60
|
}
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
export { createVcr };
|
|
65
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/testing/index.ts"],"sourcesContent":["import { createClient } from \"@apibara/protocol\";\nimport ci from \"ci-info\";\nimport { useIndexerContext } from \"../context\";\nimport { type IndexerWithStreamConfig, createIndexer } from \"../indexer\";\nimport { type InternalContext, internalContext } from \"../plugins/context\";\nimport { logger } from \"../plugins/logger\";\nimport type { CassetteOptions, VcrConfig } from \"../vcr/config\";\nimport { isCassetteAvailable } from \"../vcr/helper\";\nimport { record } from \"../vcr/record\";\nimport { replay } from \"../vcr/replay\";\n\nexport type VcrResult = Record<string, unknown>;\n\nexport function createVcr() {\n let result: VcrResult;\n\n return {\n async run<TFilter, TBlock>(\n cassetteName: string,\n indexerConfig: IndexerWithStreamConfig<TFilter, TBlock>,\n range: { fromBlock: bigint; toBlock: bigint },\n ) {\n const vcrConfig: VcrConfig = {\n cassetteDir: \"cassettes\",\n };\n\n const cassetteOptions: CassetteOptions = {\n name: cassetteName,\n startingCursor: {\n orderKey: range.fromBlock,\n },\n endingCursor: {\n orderKey: range.toBlock,\n },\n };\n\n indexerConfig.plugins = [\n internalContext({\n indexerName: cassetteName,\n availableIndexers: [cassetteName],\n } as InternalContext),\n logger(),\n ...(indexerConfig.plugins ?? []),\n ];\n\n const indexer = createIndexer(indexerConfig);\n\n indexer.hooks.hook(\"run:after\", () => {\n result = useIndexerContext();\n });\n\n if (!isCassetteAvailable(vcrConfig, cassetteName)) {\n if (ci.isCI) {\n throw new Error(\"Cannot record cassette in CI\");\n }\n\n const client = createClient(\n indexer.streamConfig,\n indexer.options.streamUrl,\n );\n await record(vcrConfig, client, indexer, cassetteOptions);\n } else {\n await replay(vcrConfig, indexer, cassetteName);\n }\n\n return result;\n },\n };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAaO,SAAS,SAAY,GAAA;AAC1B,EAAI,IAAA,MAAA,CAAA;AAEJ,EAAO,OAAA;AAAA,IACL,MAAM,GAAA,CACJ,YACA,EAAA,aAAA,EACA,KACA,EAAA;AACA,MAAA,MAAM,SAAuB,GAAA;AAAA,QAC3B,WAAa,EAAA,WAAA;AAAA,OACf,CAAA;AAEA,MAAA,MAAM,eAAmC,GAAA;AAAA,QACvC,IAAM,EAAA,YAAA;AAAA,QACN,cAAgB,EAAA;AAAA,UACd,UAAU,KAAM,CAAA,SAAA;AAAA,SAClB;AAAA,QACA,YAAc,EAAA;AAAA,UACZ,UAAU,KAAM,CAAA,OAAA;AAAA,SAClB;AAAA,OACF,CAAA;AAEA,MAAA,aAAA,CAAc,OAAU,GAAA;AAAA,QACtB,eAAgB,CAAA;AAAA,UACd,WAAa,EAAA,YAAA;AAAA,UACb,iBAAA,EAAmB,CAAC,YAAY,CAAA;AAAA,SACd,CAAA;AAAA,QACpB,MAAO,EAAA;AAAA,QACP,GAAI,aAAc,CAAA,OAAA,IAAW,EAAC;AAAA,OAChC,CAAA;AAEA,MAAM,MAAA,OAAA,GAAU,cAAc,aAAa,CAAA,CAAA;AAE3C,MAAQ,OAAA,CAAA,KAAA,CAAM,IAAK,CAAA,WAAA,EAAa,MAAM;AACpC,QAAA,MAAA,GAAS,iBAAkB,EAAA,CAAA;AAAA,OAC5B,CAAA,CAAA;AAED,MAAA,IAAI,CAAC,mBAAA,CAAoB,SAAW,EAAA,YAAY,CAAG,EAAA;AACjD,QAAA,IAAI,GAAG,IAAM,EAAA;AACX,UAAM,MAAA,IAAI,MAAM,8BAA8B,CAAA,CAAA;AAAA,SAChD;AAEA,QAAA,MAAM,MAAS,GAAA,YAAA;AAAA,UACb,OAAQ,CAAA,YAAA;AAAA,UACR,QAAQ,OAAQ,CAAA,SAAA;AAAA,SAClB,CAAA;AACA,QAAA,MAAM,MAAO,CAAA,SAAA,EAAW,MAAQ,EAAA,OAAA,EAAS,eAAe,CAAA,CAAA;AAAA,OACnD,MAAA;AACL,QAAM,MAAA,MAAA,CAAO,SAAW,EAAA,OAAA,EAAS,YAAY,CAAA,CAAA;AAAA,OAC/C;AAEA,MAAO,OAAA,MAAA,CAAA;AAAA,KACT;AAAA,GACF,CAAA;AACF;;;;"}
|
package/dist/vcr/index.cjs
CHANGED
|
@@ -9,10 +9,11 @@ const testing = require('@apibara/protocol/testing');
|
|
|
9
9
|
require('@apibara/protocol');
|
|
10
10
|
require('consola');
|
|
11
11
|
require('hookable');
|
|
12
|
-
require('../shared/indexer.
|
|
12
|
+
require('../shared/indexer.479ae593.cjs');
|
|
13
13
|
require('node:async_hooks');
|
|
14
14
|
require('unctx');
|
|
15
15
|
require('@opentelemetry/api');
|
|
16
|
+
require('../internal/plugins.cjs');
|
|
16
17
|
|
|
17
18
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
18
19
|
|
|
@@ -90,3 +91,4 @@ exports.loadCassette = loadCassette;
|
|
|
90
91
|
exports.record = record;
|
|
91
92
|
exports.replay = replay;
|
|
92
93
|
exports.serialize = serialize;
|
|
94
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../src/vcr/helper.ts","../../src/vcr/record.ts","../../src/vcr/replay.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { VcrConfig } from \"./config\";\n\nexport function deserialize(str: string) {\n return JSON.parse(str, (_, value) =>\n typeof value === \"string\" && value.match(/^\\d+n$/)\n ? BigInt(value.slice(0, -1))\n : value,\n );\n}\n\nexport function serialize(obj: Record<string, unknown>): string {\n return JSON.stringify(\n obj,\n (_, value) => (typeof value === \"bigint\" ? `${value.toString()}n` : value),\n \"\\t\",\n );\n}\n\nexport function isCassetteAvailable(\n vcrConfig: VcrConfig,\n cassetteName: string,\n): boolean {\n const filePath = path.join(vcrConfig.cassetteDir, `${cassetteName}.json`);\n return fs.existsSync(filePath);\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Client, StreamDataResponse } from \"@apibara/protocol\";\nimport { type Indexer, run } from \"../indexer\";\nimport type { CassetteOptions, VcrConfig } from \"./config\";\nimport { serialize } from \"./helper\";\n\nexport type CassetteDataType<TFilter, TBlock> = {\n filter: TFilter;\n messages: StreamDataResponse<TBlock>[];\n};\n\nexport async function record<TFilter, TBlock, TTxnParams>(\n vcrConfig: VcrConfig,\n client: Client<TFilter, TBlock>,\n indexer: Indexer<TFilter, TBlock>,\n cassetteOptions: CassetteOptions,\n) {\n const messages: StreamDataResponse<TBlock>[] = [];\n\n indexer.hooks.addHooks({\n \"connect:before\"({ options, request }) {\n request.startingCursor = cassetteOptions.startingCursor;\n options.endingCursor = cassetteOptions.endingCursor;\n },\n message({ message }) {\n messages.push(message);\n },\n async \"run:after\"() {\n const output: CassetteDataType<TFilter, TBlock> = {\n filter: indexer.options.filter,\n messages: messages,\n };\n\n await fs.mkdir(vcrConfig.cassetteDir, { recursive: true });\n\n const filePath = path.join(\n vcrConfig.cassetteDir,\n `${cassetteOptions.name}.json`,\n );\n\n await fs.writeFile(filePath, serialize(output), { flag: \"w\" });\n },\n });\n\n await run(client, indexer);\n}\n","import assert from \"node:assert\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Client } from \"@apibara/protocol\";\nimport { MockClient } from \"@apibara/protocol/testing\";\nimport { type Indexer, run } from \"../indexer\";\nimport { type CassetteDataType, deserialize } from \"../vcr\";\nimport type { VcrConfig } from \"./config\";\n\nexport async function replay<TFilter, TBlock, TTxnParams>(\n vcrConfig: VcrConfig,\n indexer: Indexer<TFilter, TBlock>,\n cassetteName: string,\n) {\n const client = loadCassette<TFilter, TBlock>(vcrConfig, cassetteName);\n await run(client, indexer);\n}\n\nexport function loadCassette<TFilter, TBlock>(\n vcrConfig: VcrConfig,\n cassetteName: string,\n): Client<TFilter, TBlock> {\n const filePath = path.join(vcrConfig.cassetteDir, `${cassetteName}.json`);\n\n const data = fs.readFileSync(filePath, \"utf8\");\n const cassetteData: CassetteDataType<TFilter, TBlock> = deserialize(data);\n\n const { filter, messages } = cassetteData;\n\n return new MockClient<TFilter, TBlock>((request, options) => {\n // Notice that the request filter is an array of filters,\n // so we need to wrap the indexer filter in an array.\n assert.deepStrictEqual(\n request.filter,\n [filter],\n \"Indexer and cassette filter mismatch. Hint: delete the cassette and run again.\",\n );\n\n return messages;\n });\n}\n"],"names":["path","fs","run","MockClient","assert"],"mappings":"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"}
|
package/dist/vcr/index.d.cts
CHANGED
package/dist/vcr/index.d.mts
CHANGED
package/dist/vcr/index.d.ts
CHANGED
package/dist/vcr/index.mjs
CHANGED
|
@@ -7,10 +7,11 @@ import { MockClient } from '@apibara/protocol/testing';
|
|
|
7
7
|
import '@apibara/protocol';
|
|
8
8
|
import 'consola';
|
|
9
9
|
import 'hookable';
|
|
10
|
-
import '../shared/indexer.
|
|
10
|
+
import '../shared/indexer.75773ef1.mjs';
|
|
11
11
|
import 'node:async_hooks';
|
|
12
12
|
import 'unctx';
|
|
13
13
|
import '@opentelemetry/api';
|
|
14
|
+
import '../internal/plugins.mjs';
|
|
14
15
|
|
|
15
16
|
function deserialize(str) {
|
|
16
17
|
return JSON.parse(
|
|
@@ -76,3 +77,4 @@ function loadCassette(vcrConfig, cassetteName) {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
export { deserialize, isCassetteAvailable, loadCassette, record, replay, serialize };
|
|
80
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/vcr/helper.ts","../../src/vcr/record.ts","../../src/vcr/replay.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { VcrConfig } from \"./config\";\n\nexport function deserialize(str: string) {\n return JSON.parse(str, (_, value) =>\n typeof value === \"string\" && value.match(/^\\d+n$/)\n ? BigInt(value.slice(0, -1))\n : value,\n );\n}\n\nexport function serialize(obj: Record<string, unknown>): string {\n return JSON.stringify(\n obj,\n (_, value) => (typeof value === \"bigint\" ? `${value.toString()}n` : value),\n \"\\t\",\n );\n}\n\nexport function isCassetteAvailable(\n vcrConfig: VcrConfig,\n cassetteName: string,\n): boolean {\n const filePath = path.join(vcrConfig.cassetteDir, `${cassetteName}.json`);\n return fs.existsSync(filePath);\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Client, StreamDataResponse } from \"@apibara/protocol\";\nimport { type Indexer, run } from \"../indexer\";\nimport type { CassetteOptions, VcrConfig } from \"./config\";\nimport { serialize } from \"./helper\";\n\nexport type CassetteDataType<TFilter, TBlock> = {\n filter: TFilter;\n messages: StreamDataResponse<TBlock>[];\n};\n\nexport async function record<TFilter, TBlock, TTxnParams>(\n vcrConfig: VcrConfig,\n client: Client<TFilter, TBlock>,\n indexer: Indexer<TFilter, TBlock>,\n cassetteOptions: CassetteOptions,\n) {\n const messages: StreamDataResponse<TBlock>[] = [];\n\n indexer.hooks.addHooks({\n \"connect:before\"({ options, request }) {\n request.startingCursor = cassetteOptions.startingCursor;\n options.endingCursor = cassetteOptions.endingCursor;\n },\n message({ message }) {\n messages.push(message);\n },\n async \"run:after\"() {\n const output: CassetteDataType<TFilter, TBlock> = {\n filter: indexer.options.filter,\n messages: messages,\n };\n\n await fs.mkdir(vcrConfig.cassetteDir, { recursive: true });\n\n const filePath = path.join(\n vcrConfig.cassetteDir,\n `${cassetteOptions.name}.json`,\n );\n\n await fs.writeFile(filePath, serialize(output), { flag: \"w\" });\n },\n });\n\n await run(client, indexer);\n}\n","import assert from \"node:assert\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Client } from \"@apibara/protocol\";\nimport { MockClient } from \"@apibara/protocol/testing\";\nimport { type Indexer, run } from \"../indexer\";\nimport { type CassetteDataType, deserialize } from \"../vcr\";\nimport type { VcrConfig } from \"./config\";\n\nexport async function replay<TFilter, TBlock, TTxnParams>(\n vcrConfig: VcrConfig,\n indexer: Indexer<TFilter, TBlock>,\n cassetteName: string,\n) {\n const client = loadCassette<TFilter, TBlock>(vcrConfig, cassetteName);\n await run(client, indexer);\n}\n\nexport function loadCassette<TFilter, TBlock>(\n vcrConfig: VcrConfig,\n cassetteName: string,\n): Client<TFilter, TBlock> {\n const filePath = path.join(vcrConfig.cassetteDir, `${cassetteName}.json`);\n\n const data = fs.readFileSync(filePath, \"utf8\");\n const cassetteData: CassetteDataType<TFilter, TBlock> = deserialize(data);\n\n const { filter, messages } = cassetteData;\n\n return new MockClient<TFilter, TBlock>((request, options) => {\n // Notice that the request filter is an array of filters,\n // so we need to wrap the indexer filter in an array.\n assert.deepStrictEqual(\n request.filter,\n [filter],\n \"Indexer and cassette filter mismatch. Hint: delete the cassette and run again.\",\n );\n\n return messages;\n });\n}\n"],"names":["fs"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apibara/indexer",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.31",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"vitest": "^1.6.0"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@apibara/protocol": "2.1.0-beta.
|
|
73
|
+
"@apibara/protocol": "2.1.0-beta.31",
|
|
74
74
|
"@opentelemetry/api": "^1.9.0",
|
|
75
75
|
"ci-info": "^4.1.0",
|
|
76
|
-
"consola": "^3.2
|
|
76
|
+
"consola": "^3.4.2",
|
|
77
77
|
"hookable": "^5.5.3",
|
|
78
78
|
"klona": "^2.0.6",
|
|
79
79
|
"nice-grpc": "^2.1.8",
|
package/src/indexer.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Client,
|
|
3
3
|
ClientError,
|
|
4
|
+
type CreateClientOptions,
|
|
4
5
|
type Cursor,
|
|
5
6
|
type DataFinality,
|
|
6
7
|
type Finalize,
|
|
7
|
-
type Heartbeat,
|
|
8
8
|
type Invalidate,
|
|
9
|
+
ServerError,
|
|
9
10
|
Status,
|
|
10
11
|
type StreamConfig,
|
|
11
12
|
type StreamDataOptions,
|
|
@@ -28,8 +29,9 @@ import {
|
|
|
28
29
|
indexerAsyncContext,
|
|
29
30
|
useIndexerContext,
|
|
30
31
|
} from "./context";
|
|
31
|
-
import {
|
|
32
|
+
import { createIndexerMetrics, createTracer } from "./otel";
|
|
32
33
|
import type { IndexerPlugin } from "./plugins";
|
|
34
|
+
import { useInternalContext } from "./plugins/context";
|
|
33
35
|
|
|
34
36
|
export type UseMiddlewareFunction = (
|
|
35
37
|
fn: MiddlewareFunction<IndexerContext>,
|
|
@@ -59,7 +61,7 @@ export interface IndexerHooks<TFilter, TBlock> {
|
|
|
59
61
|
message: ({ message }: { message: StreamDataResponse<TBlock> }) => void;
|
|
60
62
|
"message:invalidate": ({ message }: { message: Invalidate }) => void;
|
|
61
63
|
"message:finalize": ({ message }: { message: Finalize }) => void;
|
|
62
|
-
"message:heartbeat": (
|
|
64
|
+
"message:heartbeat": () => void;
|
|
63
65
|
"message:systemMessage": ({ message }: { message: SystemMessage }) => void;
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -81,6 +83,7 @@ export type IndexerConfig<TFilter, TBlock> = {
|
|
|
81
83
|
streamUrl: string;
|
|
82
84
|
filter: TFilter;
|
|
83
85
|
finality?: DataFinality;
|
|
86
|
+
clientOptions?: CreateClientOptions;
|
|
84
87
|
factory?: ({
|
|
85
88
|
block,
|
|
86
89
|
context,
|
|
@@ -179,13 +182,18 @@ export async function runWithReconnect<TFilter, TBlock>(
|
|
|
179
182
|
|
|
180
183
|
retryCount++;
|
|
181
184
|
|
|
182
|
-
if (error instanceof ClientError) {
|
|
185
|
+
if (error instanceof ClientError || error instanceof ServerError) {
|
|
186
|
+
const isServerError = error instanceof ServerError;
|
|
187
|
+
|
|
183
188
|
if (error.code === Status.INTERNAL) {
|
|
184
189
|
if (retryCount < maxRetries) {
|
|
185
190
|
consola.error(
|
|
186
|
-
|
|
187
|
-
|
|
191
|
+
`Internal ${isServerError ? "server" : "client"} error: ${
|
|
192
|
+
error.message
|
|
193
|
+
}`,
|
|
188
194
|
);
|
|
195
|
+
consola.start("Reconnecting...");
|
|
196
|
+
console.log();
|
|
189
197
|
|
|
190
198
|
// Add jitter to the retry delay to avoid all clients retrying at the same time.
|
|
191
199
|
const delay = Math.random() * (retryDelay * 0.2) + retryDelay;
|
|
@@ -216,8 +224,13 @@ export async function run<TFilter, TBlock>(
|
|
|
216
224
|
const context = useIndexerContext();
|
|
217
225
|
const middleware = await registerMiddleware(indexer);
|
|
218
226
|
|
|
227
|
+
const indexerMetrics = createIndexerMetrics();
|
|
228
|
+
const tracer = createTracer();
|
|
229
|
+
|
|
219
230
|
await indexer.hooks.callHook("run:before");
|
|
220
231
|
|
|
232
|
+
const { indexerName: indexerId } = useInternalContext();
|
|
233
|
+
|
|
221
234
|
const isFactoryMode = indexer.options.factory !== undefined;
|
|
222
235
|
|
|
223
236
|
// Give priority to startingCursor over startingBlock.
|
|
@@ -235,13 +248,13 @@ export async function run<TFilter, TBlock>(
|
|
|
235
248
|
}
|
|
236
249
|
|
|
237
250
|
// if factory mode we add a empty filter at the end of the filter array.
|
|
238
|
-
const request =
|
|
251
|
+
const request = {
|
|
239
252
|
filter: isFactoryMode
|
|
240
253
|
? [indexer.options.filter, {} as TFilter]
|
|
241
254
|
: [indexer.options.filter],
|
|
242
255
|
finality: indexer.options.finality,
|
|
243
256
|
startingCursor,
|
|
244
|
-
}
|
|
257
|
+
} as StreamDataRequest<TFilter>;
|
|
245
258
|
|
|
246
259
|
const options: StreamDataOptions = {};
|
|
247
260
|
|
|
@@ -288,11 +301,19 @@ export async function run<TFilter, TBlock>(
|
|
|
288
301
|
context.endCursor = endCursor;
|
|
289
302
|
context.finality = finality;
|
|
290
303
|
|
|
304
|
+
// Record current block number being processed
|
|
305
|
+
indexerMetrics.currentBlockGauge.record(
|
|
306
|
+
Number(endCursor?.orderKey),
|
|
307
|
+
{
|
|
308
|
+
indexer_id: indexerId,
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
|
|
291
312
|
await middleware(context, async () => {
|
|
292
313
|
let block: TBlock | null;
|
|
293
314
|
|
|
294
315
|
// when factory mode
|
|
295
|
-
if (isFactoryMode) {
|
|
316
|
+
if (isFactoryMode && finality !== "pending") {
|
|
296
317
|
assert(indexer.options.factory !== undefined);
|
|
297
318
|
|
|
298
319
|
const [factoryBlock, mainBlock] = blocks;
|
|
@@ -315,11 +336,11 @@ export async function run<TFilter, TBlock>(
|
|
|
315
336
|
);
|
|
316
337
|
|
|
317
338
|
// create request with new filters
|
|
318
|
-
const request =
|
|
339
|
+
const request = {
|
|
319
340
|
filter: [indexer.options.filter, mainFilter],
|
|
320
341
|
finality: indexer.options.finality,
|
|
321
342
|
startingCursor: cursor,
|
|
322
|
-
}
|
|
343
|
+
} as StreamDataRequest<TFilter>;
|
|
323
344
|
|
|
324
345
|
await indexer.hooks.callHook("connect:factory", {
|
|
325
346
|
request,
|
|
@@ -364,6 +385,11 @@ export async function run<TFilter, TBlock>(
|
|
|
364
385
|
span.end();
|
|
365
386
|
});
|
|
366
387
|
|
|
388
|
+
// Record processed block metric
|
|
389
|
+
indexerMetrics.processedBlockCounter.add(1, {
|
|
390
|
+
indexer_id: indexerId,
|
|
391
|
+
});
|
|
392
|
+
|
|
367
393
|
context.cursor = undefined;
|
|
368
394
|
context.endCursor = undefined;
|
|
369
395
|
context.finality = undefined;
|
|
@@ -372,21 +398,29 @@ export async function run<TFilter, TBlock>(
|
|
|
372
398
|
}
|
|
373
399
|
case "invalidate": {
|
|
374
400
|
await tracer.startActiveSpan("message invalidate", async (span) => {
|
|
375
|
-
|
|
401
|
+
// Record reorg metric
|
|
402
|
+
indexerMetrics.reorgCounter.add(1, {
|
|
403
|
+
indexer_id: indexerId,
|
|
404
|
+
});
|
|
405
|
+
await indexer.hooks.callHook("message:invalidate", {
|
|
406
|
+
message: message.invalidate,
|
|
407
|
+
});
|
|
376
408
|
span.end();
|
|
377
409
|
});
|
|
378
410
|
break;
|
|
379
411
|
}
|
|
380
412
|
case "finalize": {
|
|
381
413
|
await tracer.startActiveSpan("message finalize", async (span) => {
|
|
382
|
-
await indexer.hooks.callHook("message:finalize", {
|
|
414
|
+
await indexer.hooks.callHook("message:finalize", {
|
|
415
|
+
message: message.finalize,
|
|
416
|
+
});
|
|
383
417
|
span.end();
|
|
384
418
|
});
|
|
385
419
|
break;
|
|
386
420
|
}
|
|
387
421
|
case "heartbeat": {
|
|
388
422
|
await tracer.startActiveSpan("message heartbeat", async (span) => {
|
|
389
|
-
await indexer.hooks.callHook("message:heartbeat"
|
|
423
|
+
await indexer.hooks.callHook("message:heartbeat");
|
|
390
424
|
span.end();
|
|
391
425
|
});
|
|
392
426
|
break;
|
|
@@ -409,7 +443,7 @@ export async function run<TFilter, TBlock>(
|
|
|
409
443
|
}
|
|
410
444
|
|
|
411
445
|
await indexer.hooks.callHook("message:systemMessage", {
|
|
412
|
-
message,
|
|
446
|
+
message: message.systemMessage,
|
|
413
447
|
});
|
|
414
448
|
span.end();
|
|
415
449
|
},
|
package/src/internal/testing.ts
CHANGED
|
@@ -5,10 +5,9 @@ import {
|
|
|
5
5
|
MockStream,
|
|
6
6
|
type MockStreamResponse,
|
|
7
7
|
} from "@apibara/protocol/testing";
|
|
8
|
-
|
|
9
8
|
import { useIndexerContext } from "../context";
|
|
10
9
|
import { type IndexerConfig, createIndexer, defineIndexer } from "../indexer";
|
|
11
|
-
import { defineIndexerPlugin } from "../plugins";
|
|
10
|
+
import { defineIndexerPlugin, logger } from "../plugins";
|
|
12
11
|
import { type InternalContext, internalContext } from "./plugins";
|
|
13
12
|
|
|
14
13
|
export type MockMessagesOptions = {
|
|
@@ -20,6 +19,8 @@ export type MockMessagesOptions = {
|
|
|
20
19
|
finalizeToIndex: number;
|
|
21
20
|
finalizeTriggerIndex: number;
|
|
22
21
|
};
|
|
22
|
+
uniqueKey?: boolean;
|
|
23
|
+
baseBlockNumber?: bigint;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
export function generateMockMessages(
|
|
@@ -30,33 +31,50 @@ export function generateMockMessages(
|
|
|
30
31
|
const finalizeAt = options?.finalize;
|
|
31
32
|
const messages: MockStreamResponse[] = [];
|
|
32
33
|
|
|
34
|
+
const baseBlockNumber = options?.baseBlockNumber ?? BigInt(5_000_000);
|
|
35
|
+
|
|
33
36
|
for (let i = 0; i < count; i++) {
|
|
37
|
+
const currentBlockNumber = baseBlockNumber + BigInt(i);
|
|
38
|
+
const uniqueKey = uniqueKeyFromOrderKey(currentBlockNumber);
|
|
34
39
|
if (invalidateAt && i === invalidateAt.invalidateTriggerIndex) {
|
|
40
|
+
const invalidateToBlock =
|
|
41
|
+
baseBlockNumber + BigInt(invalidateAt.invalidateFromIndex);
|
|
35
42
|
messages.push({
|
|
36
43
|
_tag: "invalidate",
|
|
37
44
|
invalidate: {
|
|
38
45
|
cursor: {
|
|
39
|
-
orderKey:
|
|
46
|
+
orderKey: invalidateToBlock,
|
|
47
|
+
uniqueKey: options?.uniqueKey
|
|
48
|
+
? uniqueKeyFromOrderKey(invalidateToBlock)
|
|
49
|
+
: undefined,
|
|
40
50
|
},
|
|
41
|
-
},
|
|
42
|
-
}
|
|
51
|
+
} as Invalidate,
|
|
52
|
+
});
|
|
43
53
|
} else if (finalizeAt && i === finalizeAt.finalizeTriggerIndex) {
|
|
54
|
+
const fianlizedToBlock =
|
|
55
|
+
baseBlockNumber + BigInt(finalizeAt.finalizeToIndex);
|
|
44
56
|
messages.push({
|
|
45
57
|
_tag: "finalize",
|
|
46
58
|
finalize: {
|
|
47
59
|
cursor: {
|
|
48
|
-
orderKey:
|
|
60
|
+
orderKey: fianlizedToBlock,
|
|
61
|
+
uniqueKey: options?.uniqueKey
|
|
62
|
+
? uniqueKeyFromOrderKey(fianlizedToBlock)
|
|
63
|
+
: undefined,
|
|
49
64
|
},
|
|
50
|
-
},
|
|
51
|
-
}
|
|
65
|
+
} as Finalize,
|
|
66
|
+
});
|
|
52
67
|
} else {
|
|
53
68
|
messages.push({
|
|
54
69
|
_tag: "data",
|
|
55
70
|
data: {
|
|
56
|
-
cursor: { orderKey:
|
|
71
|
+
cursor: { orderKey: currentBlockNumber - 1n },
|
|
57
72
|
finality: "accepted",
|
|
58
|
-
data: [{ data: `${
|
|
59
|
-
endCursor: {
|
|
73
|
+
data: [{ data: `${baseBlockNumber + BigInt(i)}` }],
|
|
74
|
+
endCursor: {
|
|
75
|
+
orderKey: currentBlockNumber,
|
|
76
|
+
uniqueKey: options?.uniqueKey ? uniqueKey : undefined,
|
|
77
|
+
},
|
|
60
78
|
production: "backfill",
|
|
61
79
|
},
|
|
62
80
|
});
|
|
@@ -66,6 +84,10 @@ export function generateMockMessages(
|
|
|
66
84
|
return messages;
|
|
67
85
|
}
|
|
68
86
|
|
|
87
|
+
function uniqueKeyFromOrderKey(orderKey: bigint): `0x${string}` {
|
|
88
|
+
return `0xff00${orderKey.toString()}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
69
91
|
type MockIndexerParams = {
|
|
70
92
|
internalContext?: InternalContext;
|
|
71
93
|
override?: Partial<IndexerConfig<MockFilter, MockBlock>>;
|
|
@@ -82,6 +104,7 @@ export function getMockIndexer(params?: MockIndexerParams) {
|
|
|
82
104
|
filter: {},
|
|
83
105
|
async transform() {},
|
|
84
106
|
plugins: [
|
|
107
|
+
logger(),
|
|
85
108
|
internalContext(
|
|
86
109
|
contextParams ??
|
|
87
110
|
({
|
package/src/otel.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
-
import { trace } from "@opentelemetry/api";
|
|
1
|
+
import { metrics, trace } from "@opentelemetry/api";
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export function createTracer() {
|
|
4
|
+
return trace.getTracer("@apibara/indexer");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createIndexerMetrics() {
|
|
8
|
+
const meter = metrics.getMeter("@apibara/indexer");
|
|
9
|
+
|
|
10
|
+
const currentBlockGauge = meter.createGauge("current_block", {
|
|
11
|
+
description: "Current block number being processed",
|
|
12
|
+
unit: "{block}",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const processedBlockCounter = meter.createCounter("processed_blocks", {
|
|
16
|
+
description: "Number of blocks processed",
|
|
17
|
+
unit: "{blocks}",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const reorgCounter = meter.createCounter("reorgs", {
|
|
21
|
+
description: "Number of reorgs (invalidate messages) received",
|
|
22
|
+
unit: "{reorgs}",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
currentBlockGauge,
|
|
27
|
+
processedBlockCounter,
|
|
28
|
+
reorgCounter,
|
|
29
|
+
};
|
|
30
|
+
}
|
package/src/testing/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createClient } from "@apibara/protocol";
|
|
2
2
|
import ci from "ci-info";
|
|
3
|
+
import { useIndexerContext } from "../context";
|
|
3
4
|
import { type IndexerWithStreamConfig, createIndexer } from "../indexer";
|
|
4
5
|
import { type InternalContext, internalContext } from "../plugins/context";
|
|
5
6
|
import { logger } from "../plugins/logger";
|
|
@@ -8,7 +9,11 @@ import { isCassetteAvailable } from "../vcr/helper";
|
|
|
8
9
|
import { record } from "../vcr/record";
|
|
9
10
|
import { replay } from "../vcr/replay";
|
|
10
11
|
|
|
12
|
+
export type VcrResult = Record<string, unknown>;
|
|
13
|
+
|
|
11
14
|
export function createVcr() {
|
|
15
|
+
let result: VcrResult;
|
|
16
|
+
|
|
12
17
|
return {
|
|
13
18
|
async run<TFilter, TBlock>(
|
|
14
19
|
cassetteName: string,
|
|
@@ -40,6 +45,10 @@ export function createVcr() {
|
|
|
40
45
|
|
|
41
46
|
const indexer = createIndexer(indexerConfig);
|
|
42
47
|
|
|
48
|
+
indexer.hooks.hook("run:after", () => {
|
|
49
|
+
result = useIndexerContext();
|
|
50
|
+
});
|
|
51
|
+
|
|
43
52
|
if (!isCassetteAvailable(vcrConfig, cassetteName)) {
|
|
44
53
|
if (ci.isCI) {
|
|
45
54
|
throw new Error("Cannot record cassette in CI");
|
|
@@ -53,6 +62,8 @@ export function createVcr() {
|
|
|
53
62
|
} else {
|
|
54
63
|
await replay(vcrConfig, indexer, cassetteName);
|
|
55
64
|
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
56
67
|
},
|
|
57
68
|
};
|
|
58
69
|
}
|