@latticexyz/store-indexer 2.2.18-8d0ce55e964e646a1c804c401df01c4deb866f30 → 2.2.18-9fa07c8489f1fbf167d0db01cd9aaa645a29c8e2

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.
Files changed (54) hide show
  1. package/dist/bin/postgres-decoded-indexer.cjs +322 -0
  2. package/dist/bin/postgres-decoded-indexer.cjs.map +1 -0
  3. package/dist/bin/postgres-decoded-indexer.d.cts +1 -0
  4. package/dist/bin/postgres-decoded-indexer.js +92 -1
  5. package/dist/bin/postgres-decoded-indexer.js.map +1 -1
  6. package/dist/bin/postgres-frontend.cjs +567 -0
  7. package/dist/bin/postgres-frontend.cjs.map +1 -0
  8. package/dist/bin/postgres-frontend.d.cts +1 -0
  9. package/dist/bin/postgres-frontend.js +258 -5
  10. package/dist/bin/postgres-frontend.js.map +1 -1
  11. package/dist/bin/postgres-indexer.cjs +368 -0
  12. package/dist/bin/postgres-indexer.cjs.map +1 -0
  13. package/dist/bin/postgres-indexer.d.cts +1 -0
  14. package/dist/bin/postgres-indexer.js +106 -1
  15. package/dist/bin/postgres-indexer.js.map +1 -1
  16. package/dist/bin/sqlite-indexer.cjs +567 -0
  17. package/dist/bin/sqlite-indexer.cjs.map +1 -0
  18. package/dist/bin/sqlite-indexer.d.cts +1 -0
  19. package/dist/bin/sqlite-indexer.js +243 -1
  20. package/dist/bin/sqlite-indexer.js.map +1 -1
  21. package/dist/chunk-66BWQNF7.js +38 -0
  22. package/dist/{chunk-R7HX5BT2.js.map → chunk-66BWQNF7.js.map} +1 -1
  23. package/dist/chunk-CGE4ONKA.js +44 -0
  24. package/dist/{chunk-YQ7E5W26.js.map → chunk-CGE4ONKA.js.map} +1 -1
  25. package/dist/chunk-GDNGJPVT.js +16 -0
  26. package/dist/{chunk-AYPBOJNL.js.map → chunk-GDNGJPVT.js.map} +1 -1
  27. package/dist/chunk-L5CWEDU6.js +53 -0
  28. package/dist/{chunk-O2SDU7EQ.js.map → chunk-L5CWEDU6.js.map} +1 -1
  29. package/dist/chunk-O4XAWAXU.js +72 -0
  30. package/dist/{chunk-ED45N3IT.js.map → chunk-O4XAWAXU.js.map} +1 -1
  31. package/dist/chunk-R7UQFYRA.js +99 -0
  32. package/dist/{chunk-JDWVOODJ.js.map → chunk-R7UQFYRA.js.map} +1 -1
  33. package/dist/chunk-SJLOWI5M.js +31 -0
  34. package/dist/{chunk-7O2ZWWUX.js.map → chunk-SJLOWI5M.js.map} +1 -1
  35. package/dist/healthcheck-2DQWYXPX.js +7 -0
  36. package/dist/helloWorld-6IXGINV6.js +7 -0
  37. package/dist/index.cjs +2 -0
  38. package/dist/index.d.cts +2 -0
  39. package/dist/metrics-HO5SO4EX.js +7 -0
  40. package/dist/metrics-HO5SO4EX.js.map +1 -0
  41. package/package.json +16 -7
  42. package/dist/chunk-7O2ZWWUX.js +0 -2
  43. package/dist/chunk-AYPBOJNL.js +0 -2
  44. package/dist/chunk-ED45N3IT.js +0 -2
  45. package/dist/chunk-JDWVOODJ.js +0 -2
  46. package/dist/chunk-O2SDU7EQ.js +0 -7
  47. package/dist/chunk-R7HX5BT2.js +0 -2
  48. package/dist/chunk-YQ7E5W26.js +0 -2
  49. package/dist/healthcheck-57YETUEX.js +0 -2
  50. package/dist/helloWorld-4VT4FZ7F.js +0 -2
  51. package/dist/metrics-4BMCDEZZ.js +0 -2
  52. /package/dist/{healthcheck-57YETUEX.js.map → healthcheck-2DQWYXPX.js.map} +0 -0
  53. /package/dist/{helloWorld-4VT4FZ7F.js.map → helloWorld-6IXGINV6.js.map} +0 -0
  54. /package/dist/{metrics-4BMCDEZZ.js.map → index.cjs.map} +0 -0
@@ -0,0 +1,368 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ // ../../node_modules/.pnpm/tsup@8.3.0_@microsoft+api-extractor@7.47.7_@types+node@20.17.16__jiti@1.21.6_postcss@8.5.1_ts_rihtmhm6tp3cagz6w7ivhbdyn4/node_modules/tsup/assets/cjs_shims.js
34
+ var init_cjs_shims = __esm({
35
+ "../../node_modules/.pnpm/tsup@8.3.0_@microsoft+api-extractor@7.47.7_@types+node@20.17.16__jiti@1.21.6_postcss@8.5.1_ts_rihtmhm6tp3cagz6w7ivhbdyn4/node_modules/tsup/assets/cjs_shims.js"() {
36
+ "use strict";
37
+ }
38
+ });
39
+
40
+ // src/koa-middleware/healthcheck.ts
41
+ var healthcheck_exports = {};
42
+ __export(healthcheck_exports, {
43
+ healthcheck: () => healthcheck
44
+ });
45
+ function healthcheck({ isHealthy, isReady } = {}) {
46
+ return async function healthcheckMiddleware(ctx, next) {
47
+ if (ctx.path === "/healthz") {
48
+ if (isHealthy == null || isHealthy()) {
49
+ ctx.status = 200;
50
+ ctx.body = "healthy";
51
+ } else {
52
+ ctx.status = 503;
53
+ ctx.body = "not healthy";
54
+ }
55
+ return;
56
+ }
57
+ if (ctx.path === "/readyz") {
58
+ if (isReady == null || isReady()) {
59
+ ctx.status = 200;
60
+ ctx.body = "ready";
61
+ } else {
62
+ ctx.status = 503;
63
+ ctx.body = "not ready";
64
+ }
65
+ return;
66
+ }
67
+ await next();
68
+ };
69
+ }
70
+ var init_healthcheck = __esm({
71
+ "src/koa-middleware/healthcheck.ts"() {
72
+ "use strict";
73
+ init_cjs_shims();
74
+ }
75
+ });
76
+
77
+ // src/koa-middleware/metrics.ts
78
+ var metrics_exports = {};
79
+ __export(metrics_exports, {
80
+ metrics: () => metrics
81
+ });
82
+ function metrics({
83
+ isHealthy,
84
+ isReady,
85
+ getLatestStoredBlockNumber,
86
+ getDistanceFromFollowBlock,
87
+ followBlockTag
88
+ } = {}) {
89
+ import_prom_client.default.collectDefaultMetrics();
90
+ if (isHealthy != null) {
91
+ new import_prom_client.default.Gauge({
92
+ name: "health_status",
93
+ help: "Health status (0 = unhealthy, 1 = healthy)",
94
+ collect() {
95
+ this.set(Number(isHealthy()));
96
+ }
97
+ });
98
+ }
99
+ if (isReady != null) {
100
+ new import_prom_client.default.Gauge({
101
+ name: "readiness_status",
102
+ help: "Readiness status (whether the service is ready to receive requests, 0 = not ready, 1 = ready)",
103
+ collect() {
104
+ this.set(Number(isReady()));
105
+ }
106
+ });
107
+ }
108
+ if (getLatestStoredBlockNumber != null) {
109
+ new import_prom_client.default.Gauge({
110
+ name: "latest_stored_block_number",
111
+ help: "Latest block number stored in the database",
112
+ async collect() {
113
+ this.set(Number(await getLatestStoredBlockNumber()));
114
+ }
115
+ });
116
+ }
117
+ if (followBlockTag != null) {
118
+ const blockTagGauge = new import_prom_client.default.Gauge({
119
+ name: "follow_block_tag",
120
+ help: "Block tag the indexer is following (0 = finalized, 1 = safe, 2 = latest)"
121
+ });
122
+ const blockTagToValue = {
123
+ finalized: 0,
124
+ safe: 1,
125
+ latest: 2
126
+ };
127
+ blockTagGauge.set(blockTagToValue[followBlockTag]);
128
+ }
129
+ if (getDistanceFromFollowBlock != null) {
130
+ new import_prom_client.default.Gauge({
131
+ name: "distance_from_follow_block",
132
+ help: "Block distance from the block tag this the indexer is following",
133
+ async collect() {
134
+ this.set(Number(await getDistanceFromFollowBlock()));
135
+ }
136
+ });
137
+ }
138
+ return async function metricsMiddleware(ctx, next) {
139
+ if (ctx.path === "/metrics") {
140
+ ctx.status = 200;
141
+ ctx.body = await import_prom_client.default.register.metrics();
142
+ return;
143
+ }
144
+ await next();
145
+ };
146
+ }
147
+ var import_prom_client;
148
+ var init_metrics = __esm({
149
+ "src/koa-middleware/metrics.ts"() {
150
+ "use strict";
151
+ init_cjs_shims();
152
+ import_prom_client = __toESM(require("prom-client"), 1);
153
+ }
154
+ });
155
+
156
+ // src/koa-middleware/helloWorld.ts
157
+ var helloWorld_exports = {};
158
+ __export(helloWorld_exports, {
159
+ helloWorld: () => helloWorld
160
+ });
161
+ function helloWorld() {
162
+ return async function helloWorldMiddleware(ctx, next) {
163
+ if (ctx.path === "/") {
164
+ ctx.status = 200;
165
+ ctx.body = "emit HelloWorld();";
166
+ return;
167
+ }
168
+ await next();
169
+ };
170
+ }
171
+ var init_helloWorld = __esm({
172
+ "src/koa-middleware/helloWorld.ts"() {
173
+ "use strict";
174
+ init_cjs_shims();
175
+ }
176
+ });
177
+
178
+ // src/bin/postgres-indexer.ts
179
+ init_cjs_shims();
180
+ var import_config = require("dotenv/config");
181
+ var import_zod2 = require("zod");
182
+ var import_drizzle_orm = require("drizzle-orm");
183
+ var import_rxjs = require("rxjs");
184
+ var import_postgres_js = require("drizzle-orm/postgres-js");
185
+ var import_postgres = __toESM(require("postgres"), 1);
186
+ var import_postgres2 = require("@latticexyz/store-sync/postgres");
187
+ var import_store_sync = require("@latticexyz/store-sync");
188
+
189
+ // src/bin/parseEnv.ts
190
+ init_cjs_shims();
191
+ var import_viem = require("viem");
192
+ var import_zod = require("zod");
193
+ var frontendEnvSchema = import_zod.z.object({
194
+ HOST: import_zod.z.string().default("0.0.0.0"),
195
+ PORT: import_zod.z.coerce.number().positive().default(3001)
196
+ });
197
+ var indexerEnvSchema = import_zod.z.intersection(
198
+ import_zod.z.object({
199
+ FOLLOW_BLOCK_TAG: import_zod.z.enum(["latest", "safe", "finalized"]).default("safe"),
200
+ START_BLOCK: import_zod.z.coerce.bigint().nonnegative().default(0n),
201
+ MAX_BLOCK_RANGE: import_zod.z.coerce.bigint().positive().default(1000n),
202
+ POLLING_INTERVAL: import_zod.z.coerce.number().positive().default(1e3),
203
+ STORE_ADDRESS: import_zod.z.string().optional().transform((input) => input === "" ? void 0 : input).refine(isHexOrUndefined),
204
+ INTERNAL__VALIDATE_BLOCK_RANGE: import_zod.z.string().optional().transform((input) => input === "true" || input === "1")
205
+ }),
206
+ import_zod.z.union([
207
+ import_zod.z.object({
208
+ RPC_HTTP_URL: import_zod.z.string(),
209
+ RPC_WS_URL: import_zod.z.string().optional()
210
+ }),
211
+ import_zod.z.object({
212
+ RPC_HTTP_URL: import_zod.z.string().optional(),
213
+ RPC_WS_URL: import_zod.z.string()
214
+ })
215
+ ])
216
+ );
217
+ function parseEnv(envSchema) {
218
+ try {
219
+ return envSchema.parse(process.env);
220
+ } catch (error) {
221
+ if (error instanceof import_zod.ZodError) {
222
+ const { ...invalidEnvVars } = error.format();
223
+ console.error(`
224
+ Missing or invalid environment variables:
225
+
226
+ ${Object.keys(invalidEnvVars).join("\n ")}
227
+ `);
228
+ process.exit(1);
229
+ }
230
+ throw error;
231
+ }
232
+ }
233
+ function isHexOrUndefined(input) {
234
+ return input === void 0 || (0, import_viem.isHex)(input);
235
+ }
236
+
237
+ // src/bin/getClientOptions.ts
238
+ init_cjs_shims();
239
+ var import_viem2 = require("viem");
240
+ var import_utils = require("@latticexyz/common/utils");
241
+ var import_actions = require("viem/actions");
242
+ async function getClientOptions(env) {
243
+ if (env.INTERNAL__VALIDATE_BLOCK_RANGE) {
244
+ const rpcHttpUrl = env.RPC_HTTP_URL;
245
+ if (!rpcHttpUrl) {
246
+ throw new Error("Must provide RPC_HTTP_URL when using INTERNAL__VALIDATE_BLOCK_RANGE.");
247
+ }
248
+ const chainId = await (0, import_actions.getChainId)((0, import_viem2.createClient)({ transport: (0, import_viem2.http)(rpcHttpUrl) }));
249
+ const chain = {
250
+ id: chainId,
251
+ name: "Unknown",
252
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
253
+ rpcUrls: { default: { http: [rpcHttpUrl] } }
254
+ };
255
+ return {
256
+ internal_clientOptions: {
257
+ chain,
258
+ pollingInterval: env.POLLING_INTERVAL,
259
+ validateBlockRange: env.INTERNAL__VALIDATE_BLOCK_RANGE
260
+ }
261
+ };
262
+ }
263
+ const transport = (0, import_viem2.fallback)(
264
+ [
265
+ // prefer WS when specified
266
+ env.RPC_WS_URL ? (0, import_viem2.webSocket)(env.RPC_WS_URL) : void 0,
267
+ // otherwise use or fallback to HTTP
268
+ env.RPC_HTTP_URL ? (0, import_viem2.http)(env.RPC_HTTP_URL) : void 0
269
+ ].filter(import_utils.isDefined)
270
+ );
271
+ const publicClient = (0, import_viem2.createClient)({
272
+ transport,
273
+ pollingInterval: env.POLLING_INTERVAL
274
+ });
275
+ return { publicClient };
276
+ }
277
+
278
+ // src/bin/postgres-indexer.ts
279
+ var import_actions2 = require("viem/actions");
280
+ var import_block_logs_stream = require("@latticexyz/block-logs-stream");
281
+ (async () => {
282
+ const env = parseEnv(
283
+ import_zod2.z.intersection(
284
+ indexerEnvSchema,
285
+ import_zod2.z.object({
286
+ DATABASE_URL: import_zod2.z.string(),
287
+ HEALTHCHECK_HOST: import_zod2.z.string().optional(),
288
+ HEALTHCHECK_PORT: import_zod2.z.coerce.number().optional()
289
+ })
290
+ )
291
+ );
292
+ const clientOptions = await getClientOptions(env);
293
+ const chainId = await (0, import_actions2.getChainId)((0, import_block_logs_stream.getRpcClient)(clientOptions));
294
+ const database = (0, import_postgres_js.drizzle)((0, import_postgres.default)(env.DATABASE_URL, { prepare: false }));
295
+ if (await (0, import_postgres2.shouldCleanDatabase)(database, chainId)) {
296
+ console.log("outdated database detected, clearing data to start fresh");
297
+ await (0, import_postgres2.cleanDatabase)(database);
298
+ }
299
+ const { storageAdapter, tables } = await (0, import_postgres2.createStorageAdapter)({ ...clientOptions, database });
300
+ let startBlock = env.START_BLOCK;
301
+ async function getLatestStoredBlockNumber() {
302
+ try {
303
+ const chainState = await database.select().from(tables.configTable).where((0, import_drizzle_orm.eq)(tables.configTable.chainId, chainId)).limit(1).execute().then((rows) => rows.find(() => true));
304
+ return chainState?.blockNumber;
305
+ } catch (error) {
306
+ }
307
+ }
308
+ async function getDistanceFromFollowBlock() {
309
+ const [latestStoredBlockNumber2, latestFollowBlock] = await Promise.all([
310
+ getLatestStoredBlockNumber(),
311
+ (0, import_actions2.getBlock)((0, import_block_logs_stream.getRpcClient)(clientOptions), { blockTag: env.FOLLOW_BLOCK_TAG })
312
+ ]);
313
+ return latestFollowBlock.number - (latestStoredBlockNumber2 ?? -1n);
314
+ }
315
+ const latestStoredBlockNumber = await getLatestStoredBlockNumber();
316
+ if (latestStoredBlockNumber != null) {
317
+ startBlock = latestStoredBlockNumber + 1n;
318
+ console.log("resuming from block number", startBlock);
319
+ }
320
+ const { latestBlockNumber$, storedBlockLogs$ } = await (0, import_store_sync.createStoreSync)({
321
+ ...clientOptions,
322
+ storageAdapter,
323
+ followBlockTag: env.FOLLOW_BLOCK_TAG,
324
+ startBlock,
325
+ maxBlockRange: env.MAX_BLOCK_RANGE,
326
+ address: env.STORE_ADDRESS
327
+ });
328
+ storedBlockLogs$.subscribe();
329
+ let isCaughtUp = false;
330
+ (0, import_rxjs.combineLatest)([latestBlockNumber$, storedBlockLogs$]).pipe(
331
+ (0, import_rxjs.filter)(
332
+ ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => latestBlockNumber === lastBlockNumberProcessed
333
+ ),
334
+ (0, import_rxjs.first)()
335
+ ).subscribe(() => {
336
+ isCaughtUp = true;
337
+ console.log("all caught up");
338
+ });
339
+ if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) {
340
+ const { default: Koa } = await import("koa");
341
+ const { default: cors } = await import("@koa/cors");
342
+ const { healthcheck: healthcheck2 } = await Promise.resolve().then(() => (init_healthcheck(), healthcheck_exports));
343
+ const { metrics: metrics2 } = await Promise.resolve().then(() => (init_metrics(), metrics_exports));
344
+ const { helloWorld: helloWorld2 } = await Promise.resolve().then(() => (init_helloWorld(), helloWorld_exports));
345
+ const server = new Koa();
346
+ server.use(cors());
347
+ server.use(
348
+ healthcheck2({
349
+ isReady: () => isCaughtUp
350
+ })
351
+ );
352
+ server.use(
353
+ metrics2({
354
+ isHealthy: () => true,
355
+ isReady: () => isCaughtUp,
356
+ getLatestStoredBlockNumber,
357
+ getDistanceFromFollowBlock,
358
+ followBlockTag: env.FOLLOW_BLOCK_TAG
359
+ })
360
+ );
361
+ server.use(helloWorld2());
362
+ server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT });
363
+ console.log(
364
+ `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`
365
+ );
366
+ }
367
+ })();
368
+ //# sourceMappingURL=postgres-indexer.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../node_modules/.pnpm/tsup@8.3.0_@microsoft+api-extractor@7.47.7_@types+node@20.17.16__jiti@1.21.6_postcss@8.5.1_ts_rihtmhm6tp3cagz6w7ivhbdyn4/node_modules/tsup/assets/cjs_shims.js","../../src/koa-middleware/healthcheck.ts","../../src/koa-middleware/metrics.ts","../../src/koa-middleware/helloWorld.ts","../../src/bin/postgres-indexer.ts","../../src/bin/parseEnv.ts","../../src/bin/getClientOptions.ts"],"sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL(`file:${__filename}`).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import { Middleware } from \"koa\";\n\ntype HealthcheckOptions = {\n isHealthy?: () => boolean;\n isReady?: () => boolean;\n};\n\n/**\n * Middleware to add Kubernetes healthcheck endpoints\n */\nexport function healthcheck({ isHealthy, isReady }: HealthcheckOptions = {}): Middleware {\n return async function healthcheckMiddleware(ctx, next): Promise<void> {\n if (ctx.path === \"/healthz\") {\n if (isHealthy == null || isHealthy()) {\n ctx.status = 200;\n ctx.body = \"healthy\";\n } else {\n ctx.status = 503;\n ctx.body = \"not healthy\";\n }\n return;\n }\n\n if (ctx.path === \"/readyz\") {\n if (isReady == null || isReady()) {\n ctx.status = 200;\n ctx.body = \"ready\";\n } else {\n ctx.status = 503;\n ctx.body = \"not ready\";\n }\n return;\n }\n\n await next();\n };\n}\n","import { Middleware } from \"koa\";\nimport promClient from \"prom-client\";\n\ntype MetricsOptions = {\n isHealthy?: () => boolean;\n isReady?: () => boolean;\n getLatestStoredBlockNumber?: () => Promise<bigint | undefined>;\n getDistanceFromFollowBlock?: () => Promise<bigint>;\n followBlockTag?: \"latest\" | \"safe\" | \"finalized\";\n};\n\n/**\n * Middleware to add Prometheus metrics endpoints\n */\nexport function metrics({\n isHealthy,\n isReady,\n getLatestStoredBlockNumber,\n getDistanceFromFollowBlock,\n followBlockTag,\n}: MetricsOptions = {}): Middleware {\n promClient.collectDefaultMetrics();\n if (isHealthy != null) {\n new promClient.Gauge({\n name: \"health_status\",\n help: \"Health status (0 = unhealthy, 1 = healthy)\",\n collect(): void {\n this.set(Number(isHealthy()));\n },\n });\n }\n\n if (isReady != null) {\n new promClient.Gauge({\n name: \"readiness_status\",\n help: \"Readiness status (whether the service is ready to receive requests, 0 = not ready, 1 = ready)\",\n collect(): void {\n this.set(Number(isReady()));\n },\n });\n }\n\n if (getLatestStoredBlockNumber != null) {\n new promClient.Gauge({\n name: \"latest_stored_block_number\",\n help: \"Latest block number stored in the database\",\n async collect(): Promise<void> {\n this.set(Number(await getLatestStoredBlockNumber()));\n },\n });\n }\n\n if (followBlockTag != null) {\n const blockTagGauge = new promClient.Gauge({\n name: \"follow_block_tag\",\n help: \"Block tag the indexer is following (0 = finalized, 1 = safe, 2 = latest)\",\n });\n const blockTagToValue = {\n finalized: 0,\n safe: 1,\n latest: 2,\n };\n blockTagGauge.set(blockTagToValue[followBlockTag]);\n }\n\n if (getDistanceFromFollowBlock != null) {\n new promClient.Gauge({\n name: \"distance_from_follow_block\",\n help: \"Block distance from the block tag this the indexer is following\",\n async collect(): Promise<void> {\n this.set(Number(await getDistanceFromFollowBlock()));\n },\n });\n }\n\n return async function metricsMiddleware(ctx, next): Promise<void> {\n if (ctx.path === \"/metrics\") {\n ctx.status = 200;\n ctx.body = await promClient.register.metrics();\n return;\n }\n\n await next();\n };\n}\n","import { Middleware } from \"koa\";\n\nexport function helloWorld(): Middleware {\n return async function helloWorldMiddleware(ctx, next): Promise<void> {\n if (ctx.path === \"/\") {\n ctx.status = 200;\n ctx.body = \"emit HelloWorld();\";\n return;\n }\n await next();\n };\n}\n","#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { z } from \"zod\";\nimport { eq } from \"drizzle-orm\";\nimport { combineLatest, filter, first } from \"rxjs\";\nimport { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\nimport { cleanDatabase, createStorageAdapter, shouldCleanDatabase } from \"@latticexyz/store-sync/postgres\";\nimport { createStoreSync } from \"@latticexyz/store-sync\";\nimport { indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { getClientOptions } from \"./getClientOptions\";\nimport { getBlock, getChainId } from \"viem/actions\";\nimport { getRpcClient } from \"@latticexyz/block-logs-stream\";\n\n// Workaround for:\n// Top-level await is currently not supported with the \"cjs\" output format\n(async (): Promise<void> => {\n const env = parseEnv(\n z.intersection(\n indexerEnvSchema,\n z.object({\n DATABASE_URL: z.string(),\n HEALTHCHECK_HOST: z.string().optional(),\n HEALTHCHECK_PORT: z.coerce.number().optional(),\n }),\n ),\n );\n\n const clientOptions = await getClientOptions(env);\n\n const chainId = await getChainId(getRpcClient(clientOptions));\n const database = drizzle(postgres(env.DATABASE_URL, { prepare: false }));\n\n if (await shouldCleanDatabase(database, chainId)) {\n console.log(\"outdated database detected, clearing data to start fresh\");\n await cleanDatabase(database);\n }\n\n const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database });\n\n let startBlock = env.START_BLOCK;\n\n async function getLatestStoredBlockNumber(): Promise<bigint | undefined> {\n // Fetch latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error.\n // TODO: query if the DB exists instead of try/catch\n try {\n const chainState = await database\n .select()\n .from(tables.configTable)\n .where(eq(tables.configTable.chainId, chainId))\n .limit(1)\n .execute()\n // Get the first record in a way that returns a possible `undefined`\n // TODO: move this to `.findFirst` after upgrading drizzle or `rows[0]` after enabling `noUncheckedIndexedAccess: true`\n .then((rows) => rows.find(() => true));\n\n return chainState?.blockNumber;\n } catch (error) {\n // ignore errors for now\n }\n }\n\n async function getDistanceFromFollowBlock(): Promise<bigint> {\n const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([\n getLatestStoredBlockNumber(),\n getBlock(getRpcClient(clientOptions), { blockTag: env.FOLLOW_BLOCK_TAG }),\n ]);\n return latestFollowBlock.number - (latestStoredBlockNumber ?? -1n);\n }\n\n const latestStoredBlockNumber = await getLatestStoredBlockNumber();\n if (latestStoredBlockNumber != null) {\n startBlock = latestStoredBlockNumber + 1n;\n console.log(\"resuming from block number\", startBlock);\n }\n\n const { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({\n ...clientOptions,\n storageAdapter,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n startBlock,\n maxBlockRange: env.MAX_BLOCK_RANGE,\n address: env.STORE_ADDRESS,\n });\n\n storedBlockLogs$.subscribe();\n\n let isCaughtUp = false;\n combineLatest([latestBlockNumber$, storedBlockLogs$])\n .pipe(\n filter(\n ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) =>\n latestBlockNumber === lastBlockNumberProcessed,\n ),\n first(),\n )\n .subscribe(() => {\n isCaughtUp = true;\n console.log(\"all caught up\");\n });\n\n if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) {\n const { default: Koa } = await import(\"koa\");\n const { default: cors } = await import(\"@koa/cors\");\n const { healthcheck } = await import(\"../koa-middleware/healthcheck\");\n const { metrics } = await import(\"../koa-middleware/metrics\");\n const { helloWorld } = await import(\"../koa-middleware/helloWorld\");\n\n const server = new Koa();\n\n server.use(cors());\n server.use(\n healthcheck({\n isReady: () => isCaughtUp,\n }),\n );\n server.use(\n metrics({\n isHealthy: () => true,\n isReady: () => isCaughtUp,\n getLatestStoredBlockNumber,\n getDistanceFromFollowBlock,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n }),\n );\n server.use(helloWorld());\n\n server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT });\n console.log(\n `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`,\n );\n }\n})();\n","import { Hex, isHex } from \"viem\";\nimport { z, ZodError, ZodTypeAny } from \"zod\";\n\nexport const frontendEnvSchema = z.object({\n HOST: z.string().default(\"0.0.0.0\"),\n PORT: z.coerce.number().positive().default(3001),\n});\n\nexport const indexerEnvSchema = z.intersection(\n z.object({\n FOLLOW_BLOCK_TAG: z.enum([\"latest\", \"safe\", \"finalized\"]).default(\"safe\"),\n START_BLOCK: z.coerce.bigint().nonnegative().default(0n),\n MAX_BLOCK_RANGE: z.coerce.bigint().positive().default(1000n),\n POLLING_INTERVAL: z.coerce.number().positive().default(1000),\n STORE_ADDRESS: z\n .string()\n .optional()\n .transform((input) => (input === \"\" ? undefined : input))\n .refine(isHexOrUndefined),\n INTERNAL__VALIDATE_BLOCK_RANGE: z\n .string()\n .optional()\n .transform((input) => input === \"true\" || input === \"1\"),\n }),\n z.union([\n z.object({\n RPC_HTTP_URL: z.string(),\n RPC_WS_URL: z.string().optional(),\n }),\n z.object({\n RPC_HTTP_URL: z.string().optional(),\n RPC_WS_URL: z.string(),\n }),\n ]),\n);\n\nexport function parseEnv<TSchema extends ZodTypeAny>(envSchema: TSchema): z.infer<TSchema> {\n try {\n return envSchema.parse(process.env);\n } catch (error) {\n if (error instanceof ZodError) {\n const { ...invalidEnvVars } = error.format();\n console.error(`\\nMissing or invalid environment variables:\\n\\n ${Object.keys(invalidEnvVars).join(\"\\n \")}\\n`);\n process.exit(1);\n }\n throw error;\n }\n}\n\nfunction isHexOrUndefined(input: unknown): input is Hex | undefined {\n return input === undefined || isHex(input);\n}\n","import { z } from \"zod\";\nimport { indexerEnvSchema } from \"./parseEnv\";\nimport { GetRpcClientOptions } from \"@latticexyz/block-logs-stream\";\nimport { Chain, createClient, fallback, http, webSocket } from \"viem\";\nimport { isDefined } from \"@latticexyz/common/utils\";\nimport { getChainId } from \"viem/actions\";\n\nexport async function getClientOptions(env: z.infer<typeof indexerEnvSchema>): Promise<GetRpcClientOptions> {\n if (env.INTERNAL__VALIDATE_BLOCK_RANGE) {\n const rpcHttpUrl = env.RPC_HTTP_URL;\n if (!rpcHttpUrl) {\n throw new Error(\"Must provide RPC_HTTP_URL when using INTERNAL__VALIDATE_BLOCK_RANGE.\");\n }\n\n const chainId = await getChainId(createClient({ transport: http(rpcHttpUrl) }));\n\n // Mock a chain config so we can use in client options\n const chain = {\n id: chainId,\n name: \"Unknown\",\n nativeCurrency: { decimals: 18, name: \"Ether\", symbol: \"ETH\" },\n rpcUrls: { default: { http: [rpcHttpUrl] } },\n } satisfies Chain;\n\n return {\n internal_clientOptions: {\n chain,\n pollingInterval: env.POLLING_INTERVAL,\n validateBlockRange: env.INTERNAL__VALIDATE_BLOCK_RANGE,\n },\n };\n }\n\n const transport = fallback(\n [\n // prefer WS when specified\n env.RPC_WS_URL ? webSocket(env.RPC_WS_URL) : undefined,\n // otherwise use or fallback to HTTP\n env.RPC_HTTP_URL ? http(env.RPC_HTTP_URL) : undefined,\n ].filter(isDefined),\n );\n\n const publicClient = createClient({\n transport,\n pollingInterval: env.POLLING_INTERVAL,\n });\n\n return { publicClient };\n}\n"],"mappings":"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","names":["promClient","import_zod","import_postgres","import_viem","import_actions","postgres","latestStoredBlockNumber","healthcheck","metrics","helloWorld"]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -1,3 +1,108 @@
1
1
  #!/usr/bin/env node
2
- import{a as p}from"../chunk-YQ7E5W26.js";import{b as m,c as u}from"../chunk-O2SDU7EQ.js";import"dotenv/config";import{z as r}from"zod";import{eq as h}from"drizzle-orm";import{combineLatest as A,filter as E,first as O}from"rxjs";import{drizzle as w}from"drizzle-orm/postgres-js";import _ from"postgres";import{cleanDatabase as B,createStorageAdapter as S,shouldCleanDatabase as k}from"@latticexyz/store-sync/postgres";import{createStoreSync as K}from"@latticexyz/store-sync";import{getBlock as R,getChainId as y}from"viem/actions";import{getRpcClient as g}from"@latticexyz/block-logs-stream";var t=u(r.intersection(m,r.object({DATABASE_URL:r.string(),HEALTHCHECK_HOST:r.string().optional(),HEALTHCHECK_PORT:r.coerce.number().optional()}))),n=await p(t),b=await y(g(n)),i=w(_(t.DATABASE_URL,{prepare:!1}));await k(i,b)&&(console.log("outdated database detected, clearing data to start fresh"),await B(i));var{storageAdapter:D,tables:d}=await S({...n,database:i}),s=t.START_BLOCK;async function c(){try{return(await i.select().from(d.configTable).where(h(d.configTable.chainId,b)).limit(1).execute().then(o=>o.find(()=>!0)))?.blockNumber}catch{}}async function N(){let[e,o]=await Promise.all([c(),R(g(n),{blockTag:t.FOLLOW_BLOCK_TAG})]);return o.number-(e??-1n)}var f=await c();f!=null&&(s=f+1n,console.log("resuming from block number",s));var{latestBlockNumber$:P,storedBlockLogs$:H}=await K({...n,storageAdapter:D,followBlockTag:t.FOLLOW_BLOCK_TAG,startBlock:s,maxBlockRange:t.MAX_BLOCK_RANGE,address:t.STORE_ADDRESS});H.subscribe();var l=!1;A([P,H]).pipe(E(([e,{blockNumber:o}])=>e===o),O()).subscribe(()=>{l=!0,console.log("all caught up")});if(t.HEALTHCHECK_HOST!=null||t.HEALTHCHECK_PORT!=null){let{default:e}=await import("koa"),{default:o}=await import("@koa/cors"),{healthcheck:T}=await import("../healthcheck-57YETUEX.js"),{metrics:C}=await import("../metrics-4BMCDEZZ.js"),{helloWorld:L}=await import("../helloWorld-4VT4FZ7F.js"),a=new e;a.use(o()),a.use(T({isReady:()=>l})),a.use(C({isHealthy:()=>!0,isReady:()=>l,getLatestStoredBlockNumber:c,getDistanceFromFollowBlock:N,followBlockTag:t.FOLLOW_BLOCK_TAG})),a.use(L()),a.listen({host:t.HEALTHCHECK_HOST,port:t.HEALTHCHECK_PORT}),console.log(`postgres indexer healthcheck server listening on http://${t.HEALTHCHECK_HOST}:${t.HEALTHCHECK_PORT}`)}
2
+ import {
3
+ getClientOptions
4
+ } from "../chunk-CGE4ONKA.js";
5
+ import {
6
+ indexerEnvSchema,
7
+ parseEnv
8
+ } from "../chunk-L5CWEDU6.js";
9
+
10
+ // src/bin/postgres-indexer.ts
11
+ import "dotenv/config";
12
+ import { z } from "zod";
13
+ import { eq } from "drizzle-orm";
14
+ import { combineLatest, filter, first } from "rxjs";
15
+ import { drizzle } from "drizzle-orm/postgres-js";
16
+ import postgres from "postgres";
17
+ import { cleanDatabase, createStorageAdapter, shouldCleanDatabase } from "@latticexyz/store-sync/postgres";
18
+ import { createStoreSync } from "@latticexyz/store-sync";
19
+ import { getBlock, getChainId } from "viem/actions";
20
+ import { getRpcClient } from "@latticexyz/block-logs-stream";
21
+ (async () => {
22
+ const env = parseEnv(
23
+ z.intersection(
24
+ indexerEnvSchema,
25
+ z.object({
26
+ DATABASE_URL: z.string(),
27
+ HEALTHCHECK_HOST: z.string().optional(),
28
+ HEALTHCHECK_PORT: z.coerce.number().optional()
29
+ })
30
+ )
31
+ );
32
+ const clientOptions = await getClientOptions(env);
33
+ const chainId = await getChainId(getRpcClient(clientOptions));
34
+ const database = drizzle(postgres(env.DATABASE_URL, { prepare: false }));
35
+ if (await shouldCleanDatabase(database, chainId)) {
36
+ console.log("outdated database detected, clearing data to start fresh");
37
+ await cleanDatabase(database);
38
+ }
39
+ const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database });
40
+ let startBlock = env.START_BLOCK;
41
+ async function getLatestStoredBlockNumber() {
42
+ try {
43
+ const chainState = await database.select().from(tables.configTable).where(eq(tables.configTable.chainId, chainId)).limit(1).execute().then((rows) => rows.find(() => true));
44
+ return chainState?.blockNumber;
45
+ } catch (error) {
46
+ }
47
+ }
48
+ async function getDistanceFromFollowBlock() {
49
+ const [latestStoredBlockNumber2, latestFollowBlock] = await Promise.all([
50
+ getLatestStoredBlockNumber(),
51
+ getBlock(getRpcClient(clientOptions), { blockTag: env.FOLLOW_BLOCK_TAG })
52
+ ]);
53
+ return latestFollowBlock.number - (latestStoredBlockNumber2 ?? -1n);
54
+ }
55
+ const latestStoredBlockNumber = await getLatestStoredBlockNumber();
56
+ if (latestStoredBlockNumber != null) {
57
+ startBlock = latestStoredBlockNumber + 1n;
58
+ console.log("resuming from block number", startBlock);
59
+ }
60
+ const { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({
61
+ ...clientOptions,
62
+ storageAdapter,
63
+ followBlockTag: env.FOLLOW_BLOCK_TAG,
64
+ startBlock,
65
+ maxBlockRange: env.MAX_BLOCK_RANGE,
66
+ address: env.STORE_ADDRESS
67
+ });
68
+ storedBlockLogs$.subscribe();
69
+ let isCaughtUp = false;
70
+ combineLatest([latestBlockNumber$, storedBlockLogs$]).pipe(
71
+ filter(
72
+ ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => latestBlockNumber === lastBlockNumberProcessed
73
+ ),
74
+ first()
75
+ ).subscribe(() => {
76
+ isCaughtUp = true;
77
+ console.log("all caught up");
78
+ });
79
+ if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) {
80
+ const { default: Koa } = await import("koa");
81
+ const { default: cors } = await import("@koa/cors");
82
+ const { healthcheck } = await import("../healthcheck-2DQWYXPX.js");
83
+ const { metrics } = await import("../metrics-HO5SO4EX.js");
84
+ const { helloWorld } = await import("../helloWorld-6IXGINV6.js");
85
+ const server = new Koa();
86
+ server.use(cors());
87
+ server.use(
88
+ healthcheck({
89
+ isReady: () => isCaughtUp
90
+ })
91
+ );
92
+ server.use(
93
+ metrics({
94
+ isHealthy: () => true,
95
+ isReady: () => isCaughtUp,
96
+ getLatestStoredBlockNumber,
97
+ getDistanceFromFollowBlock,
98
+ followBlockTag: env.FOLLOW_BLOCK_TAG
99
+ })
100
+ );
101
+ server.use(helloWorld());
102
+ server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT });
103
+ console.log(
104
+ `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`
105
+ );
106
+ }
107
+ })();
3
108
  //# sourceMappingURL=postgres-indexer.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bin/postgres-indexer.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { z } from \"zod\";\nimport { eq } from \"drizzle-orm\";\nimport { combineLatest, filter, first } from \"rxjs\";\nimport { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\nimport { cleanDatabase, createStorageAdapter, shouldCleanDatabase } from \"@latticexyz/store-sync/postgres\";\nimport { createStoreSync } from \"@latticexyz/store-sync\";\nimport { indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { getClientOptions } from \"./getClientOptions\";\nimport { getBlock, getChainId } from \"viem/actions\";\nimport { getRpcClient } from \"@latticexyz/block-logs-stream\";\n\nconst env = parseEnv(\n z.intersection(\n indexerEnvSchema,\n z.object({\n DATABASE_URL: z.string(),\n HEALTHCHECK_HOST: z.string().optional(),\n HEALTHCHECK_PORT: z.coerce.number().optional(),\n }),\n ),\n);\n\nconst clientOptions = await getClientOptions(env);\n\nconst chainId = await getChainId(getRpcClient(clientOptions));\nconst database = drizzle(postgres(env.DATABASE_URL, { prepare: false }));\n\nif (await shouldCleanDatabase(database, chainId)) {\n console.log(\"outdated database detected, clearing data to start fresh\");\n await cleanDatabase(database);\n}\n\nconst { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database });\n\nlet startBlock = env.START_BLOCK;\n\nasync function getLatestStoredBlockNumber(): Promise<bigint | undefined> {\n // Fetch latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error.\n // TODO: query if the DB exists instead of try/catch\n try {\n const chainState = await database\n .select()\n .from(tables.configTable)\n .where(eq(tables.configTable.chainId, chainId))\n .limit(1)\n .execute()\n // Get the first record in a way that returns a possible `undefined`\n // TODO: move this to `.findFirst` after upgrading drizzle or `rows[0]` after enabling `noUncheckedIndexedAccess: true`\n .then((rows) => rows.find(() => true));\n\n return chainState?.blockNumber;\n } catch (error) {\n // ignore errors for now\n }\n}\n\nasync function getDistanceFromFollowBlock(): Promise<bigint> {\n const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([\n getLatestStoredBlockNumber(),\n getBlock(getRpcClient(clientOptions), { blockTag: env.FOLLOW_BLOCK_TAG }),\n ]);\n return latestFollowBlock.number - (latestStoredBlockNumber ?? -1n);\n}\n\nconst latestStoredBlockNumber = await getLatestStoredBlockNumber();\nif (latestStoredBlockNumber != null) {\n startBlock = latestStoredBlockNumber + 1n;\n console.log(\"resuming from block number\", startBlock);\n}\n\nconst { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({\n ...clientOptions,\n storageAdapter,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n startBlock,\n maxBlockRange: env.MAX_BLOCK_RANGE,\n address: env.STORE_ADDRESS,\n});\n\nstoredBlockLogs$.subscribe();\n\nlet isCaughtUp = false;\ncombineLatest([latestBlockNumber$, storedBlockLogs$])\n .pipe(\n filter(\n ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) =>\n latestBlockNumber === lastBlockNumberProcessed,\n ),\n first(),\n )\n .subscribe(() => {\n isCaughtUp = true;\n console.log(\"all caught up\");\n });\n\nif (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) {\n const { default: Koa } = await import(\"koa\");\n const { default: cors } = await import(\"@koa/cors\");\n const { healthcheck } = await import(\"../koa-middleware/healthcheck\");\n const { metrics } = await import(\"../koa-middleware/metrics\");\n const { helloWorld } = await import(\"../koa-middleware/helloWorld\");\n\n const server = new Koa();\n\n server.use(cors());\n server.use(\n healthcheck({\n isReady: () => isCaughtUp,\n }),\n );\n server.use(\n metrics({\n isHealthy: () => true,\n isReady: () => isCaughtUp,\n getLatestStoredBlockNumber,\n getDistanceFromFollowBlock,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n }),\n );\n server.use(helloWorld());\n\n server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT });\n console.log(\n `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`,\n );\n}\n"],"mappings":"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","names":["z","eq","combineLatest","filter","first","drizzle","postgres","cleanDatabase","createStorageAdapter","shouldCleanDatabase","createStoreSync","getBlock","getChainId","getRpcClient","env","parseEnv","z","indexerEnvSchema","clientOptions","getClientOptions","chainId","database","drizzle","postgres","shouldCleanDatabase","cleanDatabase","storageAdapter","tables","createStorageAdapter","startBlock","getLatestStoredBlockNumber","eq","rows","getDistanceFromFollowBlock","latestStoredBlockNumber","latestFollowBlock","latestBlockNumber$","storedBlockLogs$","createStoreSync","isCaughtUp","combineLatest","filter","latestBlockNumber","lastBlockNumberProcessed","first","Koa","cors","healthcheck","metrics","helloWorld","server"]}
1
+ {"version":3,"sources":["../../src/bin/postgres-indexer.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { z } from \"zod\";\nimport { eq } from \"drizzle-orm\";\nimport { combineLatest, filter, first } from \"rxjs\";\nimport { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\nimport { cleanDatabase, createStorageAdapter, shouldCleanDatabase } from \"@latticexyz/store-sync/postgres\";\nimport { createStoreSync } from \"@latticexyz/store-sync\";\nimport { indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { getClientOptions } from \"./getClientOptions\";\nimport { getBlock, getChainId } from \"viem/actions\";\nimport { getRpcClient } from \"@latticexyz/block-logs-stream\";\n\n// Workaround for:\n// Top-level await is currently not supported with the \"cjs\" output format\n(async (): Promise<void> => {\n const env = parseEnv(\n z.intersection(\n indexerEnvSchema,\n z.object({\n DATABASE_URL: z.string(),\n HEALTHCHECK_HOST: z.string().optional(),\n HEALTHCHECK_PORT: z.coerce.number().optional(),\n }),\n ),\n );\n\n const clientOptions = await getClientOptions(env);\n\n const chainId = await getChainId(getRpcClient(clientOptions));\n const database = drizzle(postgres(env.DATABASE_URL, { prepare: false }));\n\n if (await shouldCleanDatabase(database, chainId)) {\n console.log(\"outdated database detected, clearing data to start fresh\");\n await cleanDatabase(database);\n }\n\n const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database });\n\n let startBlock = env.START_BLOCK;\n\n async function getLatestStoredBlockNumber(): Promise<bigint | undefined> {\n // Fetch latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error.\n // TODO: query if the DB exists instead of try/catch\n try {\n const chainState = await database\n .select()\n .from(tables.configTable)\n .where(eq(tables.configTable.chainId, chainId))\n .limit(1)\n .execute()\n // Get the first record in a way that returns a possible `undefined`\n // TODO: move this to `.findFirst` after upgrading drizzle or `rows[0]` after enabling `noUncheckedIndexedAccess: true`\n .then((rows) => rows.find(() => true));\n\n return chainState?.blockNumber;\n } catch (error) {\n // ignore errors for now\n }\n }\n\n async function getDistanceFromFollowBlock(): Promise<bigint> {\n const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([\n getLatestStoredBlockNumber(),\n getBlock(getRpcClient(clientOptions), { blockTag: env.FOLLOW_BLOCK_TAG }),\n ]);\n return latestFollowBlock.number - (latestStoredBlockNumber ?? -1n);\n }\n\n const latestStoredBlockNumber = await getLatestStoredBlockNumber();\n if (latestStoredBlockNumber != null) {\n startBlock = latestStoredBlockNumber + 1n;\n console.log(\"resuming from block number\", startBlock);\n }\n\n const { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({\n ...clientOptions,\n storageAdapter,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n startBlock,\n maxBlockRange: env.MAX_BLOCK_RANGE,\n address: env.STORE_ADDRESS,\n });\n\n storedBlockLogs$.subscribe();\n\n let isCaughtUp = false;\n combineLatest([latestBlockNumber$, storedBlockLogs$])\n .pipe(\n filter(\n ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) =>\n latestBlockNumber === lastBlockNumberProcessed,\n ),\n first(),\n )\n .subscribe(() => {\n isCaughtUp = true;\n console.log(\"all caught up\");\n });\n\n if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) {\n const { default: Koa } = await import(\"koa\");\n const { default: cors } = await import(\"@koa/cors\");\n const { healthcheck } = await import(\"../koa-middleware/healthcheck\");\n const { metrics } = await import(\"../koa-middleware/metrics\");\n const { helloWorld } = await import(\"../koa-middleware/helloWorld\");\n\n const server = new Koa();\n\n server.use(cors());\n server.use(\n healthcheck({\n isReady: () => isCaughtUp,\n }),\n );\n server.use(\n metrics({\n isHealthy: () => true,\n isReady: () => isCaughtUp,\n getLatestStoredBlockNumber,\n getDistanceFromFollowBlock,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n }),\n );\n server.use(helloWorld());\n\n server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT });\n console.log(\n `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`,\n );\n }\n})();\n"],"mappings":"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","names":["latestStoredBlockNumber"]}