@latticexyz/store-indexer 2.2.18-90aac1d4acce19ac592d47a090732dd11c1c3e7a → 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.
- package/dist/bin/postgres-decoded-indexer.cjs +322 -0
- package/dist/bin/postgres-decoded-indexer.cjs.map +1 -0
- package/dist/bin/postgres-decoded-indexer.d.cts +1 -0
- package/dist/bin/postgres-decoded-indexer.js +92 -1
- package/dist/bin/postgres-decoded-indexer.js.map +1 -1
- package/dist/bin/postgres-frontend.cjs +567 -0
- package/dist/bin/postgres-frontend.cjs.map +1 -0
- package/dist/bin/postgres-frontend.d.cts +1 -0
- package/dist/bin/postgres-frontend.js +258 -5
- package/dist/bin/postgres-frontend.js.map +1 -1
- package/dist/bin/postgres-indexer.cjs +368 -0
- package/dist/bin/postgres-indexer.cjs.map +1 -0
- package/dist/bin/postgres-indexer.d.cts +1 -0
- package/dist/bin/postgres-indexer.js +106 -1
- package/dist/bin/postgres-indexer.js.map +1 -1
- package/dist/bin/sqlite-indexer.cjs +567 -0
- package/dist/bin/sqlite-indexer.cjs.map +1 -0
- package/dist/bin/sqlite-indexer.d.cts +1 -0
- package/dist/bin/sqlite-indexer.js +243 -1
- package/dist/bin/sqlite-indexer.js.map +1 -1
- package/dist/chunk-66BWQNF7.js +38 -0
- package/dist/{chunk-R7HX5BT2.js.map → chunk-66BWQNF7.js.map} +1 -1
- package/dist/chunk-CGE4ONKA.js +44 -0
- package/dist/{chunk-YQ7E5W26.js.map → chunk-CGE4ONKA.js.map} +1 -1
- package/dist/chunk-GDNGJPVT.js +16 -0
- package/dist/{chunk-AYPBOJNL.js.map → chunk-GDNGJPVT.js.map} +1 -1
- package/dist/chunk-L5CWEDU6.js +53 -0
- package/dist/{chunk-O2SDU7EQ.js.map → chunk-L5CWEDU6.js.map} +1 -1
- package/dist/chunk-O4XAWAXU.js +72 -0
- package/dist/{chunk-ED45N3IT.js.map → chunk-O4XAWAXU.js.map} +1 -1
- package/dist/chunk-R7UQFYRA.js +99 -0
- package/dist/{chunk-JDWVOODJ.js.map → chunk-R7UQFYRA.js.map} +1 -1
- package/dist/chunk-SJLOWI5M.js +31 -0
- package/dist/{chunk-7O2ZWWUX.js.map → chunk-SJLOWI5M.js.map} +1 -1
- package/dist/healthcheck-2DQWYXPX.js +7 -0
- package/dist/helloWorld-6IXGINV6.js +7 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -0
- package/dist/metrics-HO5SO4EX.js +7 -0
- package/dist/metrics-HO5SO4EX.js.map +1 -0
- package/package.json +17 -9
- package/dist/chunk-7O2ZWWUX.js +0 -2
- package/dist/chunk-AYPBOJNL.js +0 -2
- package/dist/chunk-ED45N3IT.js +0 -2
- package/dist/chunk-JDWVOODJ.js +0 -2
- package/dist/chunk-O2SDU7EQ.js +0 -7
- package/dist/chunk-R7HX5BT2.js +0 -2
- package/dist/chunk-YQ7E5W26.js +0 -2
- package/dist/healthcheck-57YETUEX.js +0 -2
- package/dist/helloWorld-4VT4FZ7F.js +0 -2
- package/dist/metrics-4BMCDEZZ.js +0 -2
- /package/dist/{healthcheck-57YETUEX.js.map → healthcheck-2DQWYXPX.js.map} +0 -0
- /package/dist/{helloWorld-4VT4FZ7F.js.map → helloWorld-6IXGINV6.js.map} +0 -0
- /package/dist/{metrics-4BMCDEZZ.js.map → index.cjs.map} +0 -0
@@ -0,0 +1,322 @@
|
|
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 __copyProps = (to, from, except, desc) => {
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
11
|
+
for (let key of __getOwnPropNames(from))
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
14
|
+
}
|
15
|
+
return to;
|
16
|
+
};
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
23
|
+
mod
|
24
|
+
));
|
25
|
+
|
26
|
+
// src/bin/postgres-decoded-indexer.ts
|
27
|
+
var import_config = require("dotenv/config");
|
28
|
+
var import_zod2 = require("zod");
|
29
|
+
var import_drizzle_orm = require("drizzle-orm");
|
30
|
+
var import_rxjs = require("rxjs");
|
31
|
+
var import_postgres_js = require("drizzle-orm/postgres-js");
|
32
|
+
var import_postgres = __toESM(require("postgres"), 1);
|
33
|
+
var import_postgres_decoded = require("@latticexyz/store-sync/postgres-decoded");
|
34
|
+
var import_store_sync = require("@latticexyz/store-sync");
|
35
|
+
|
36
|
+
// src/bin/parseEnv.ts
|
37
|
+
var import_viem = require("viem");
|
38
|
+
var import_zod = require("zod");
|
39
|
+
var frontendEnvSchema = import_zod.z.object({
|
40
|
+
HOST: import_zod.z.string().default("0.0.0.0"),
|
41
|
+
PORT: import_zod.z.coerce.number().positive().default(3001)
|
42
|
+
});
|
43
|
+
var indexerEnvSchema = import_zod.z.intersection(
|
44
|
+
import_zod.z.object({
|
45
|
+
FOLLOW_BLOCK_TAG: import_zod.z.enum(["latest", "safe", "finalized"]).default("safe"),
|
46
|
+
START_BLOCK: import_zod.z.coerce.bigint().nonnegative().default(0n),
|
47
|
+
MAX_BLOCK_RANGE: import_zod.z.coerce.bigint().positive().default(1000n),
|
48
|
+
POLLING_INTERVAL: import_zod.z.coerce.number().positive().default(1e3),
|
49
|
+
STORE_ADDRESS: import_zod.z.string().optional().transform((input) => input === "" ? void 0 : input).refine(isHexOrUndefined),
|
50
|
+
INTERNAL__VALIDATE_BLOCK_RANGE: import_zod.z.string().optional().transform((input) => input === "true" || input === "1")
|
51
|
+
}),
|
52
|
+
import_zod.z.union([
|
53
|
+
import_zod.z.object({
|
54
|
+
RPC_HTTP_URL: import_zod.z.string(),
|
55
|
+
RPC_WS_URL: import_zod.z.string().optional()
|
56
|
+
}),
|
57
|
+
import_zod.z.object({
|
58
|
+
RPC_HTTP_URL: import_zod.z.string().optional(),
|
59
|
+
RPC_WS_URL: import_zod.z.string()
|
60
|
+
})
|
61
|
+
])
|
62
|
+
);
|
63
|
+
function parseEnv(envSchema) {
|
64
|
+
try {
|
65
|
+
return envSchema.parse(process.env);
|
66
|
+
} catch (error2) {
|
67
|
+
if (error2 instanceof import_zod.ZodError) {
|
68
|
+
const { ...invalidEnvVars } = error2.format();
|
69
|
+
console.error(`
|
70
|
+
Missing or invalid environment variables:
|
71
|
+
|
72
|
+
${Object.keys(invalidEnvVars).join("\n ")}
|
73
|
+
`);
|
74
|
+
process.exit(1);
|
75
|
+
}
|
76
|
+
throw error2;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
function isHexOrUndefined(input) {
|
80
|
+
return input === void 0 || (0, import_viem.isHex)(input);
|
81
|
+
}
|
82
|
+
|
83
|
+
// src/koa-middleware/sentry.ts
|
84
|
+
var Sentry = __toESM(require("@sentry/node"), 1);
|
85
|
+
var import_profiling_node = require("@sentry/profiling-node");
|
86
|
+
var import_utils = require("@sentry/utils");
|
87
|
+
|
88
|
+
// src/debug.ts
|
89
|
+
var import_debug = __toESM(require("debug"), 1);
|
90
|
+
var debug = (0, import_debug.default)("mud:store-indexer");
|
91
|
+
var error = (0, import_debug.default)("mud:store-indexer");
|
92
|
+
debug.log = console.debug.bind(console);
|
93
|
+
error.log = console.error.bind(console);
|
94
|
+
|
95
|
+
// src/koa-middleware/sentry.ts
|
96
|
+
var import_koa_compose = __toESM(require("koa-compose"), 1);
|
97
|
+
function errorHandler() {
|
98
|
+
return async function errorHandlerMiddleware(ctx, next) {
|
99
|
+
try {
|
100
|
+
await next();
|
101
|
+
} catch (err) {
|
102
|
+
Sentry.withScope((scope) => {
|
103
|
+
scope.addEventProcessor((event) => {
|
104
|
+
return Sentry.addRequestDataToEvent(event, ctx.request);
|
105
|
+
});
|
106
|
+
Sentry.captureException(err);
|
107
|
+
});
|
108
|
+
throw err;
|
109
|
+
}
|
110
|
+
};
|
111
|
+
}
|
112
|
+
function requestHandler() {
|
113
|
+
return async function requestHandlerMiddleware(ctx, next) {
|
114
|
+
await Sentry.runWithAsyncContext(async () => {
|
115
|
+
const hub = Sentry.getCurrentHub();
|
116
|
+
hub.configureScope(
|
117
|
+
(scope) => scope.addEventProcessor(
|
118
|
+
(event) => Sentry.addRequestDataToEvent(event, ctx.request, {
|
119
|
+
include: {
|
120
|
+
user: false
|
121
|
+
}
|
122
|
+
})
|
123
|
+
)
|
124
|
+
);
|
125
|
+
await next();
|
126
|
+
});
|
127
|
+
};
|
128
|
+
}
|
129
|
+
function tracing() {
|
130
|
+
return async function tracingMiddleware(ctx, next) {
|
131
|
+
const reqMethod = (ctx.method || "").toUpperCase();
|
132
|
+
const reqUrl = ctx.url && (0, import_utils.stripUrlQueryAndFragment)(ctx.url);
|
133
|
+
let traceparentData;
|
134
|
+
if (ctx.request.get("sentry-trace")) {
|
135
|
+
traceparentData = Sentry.extractTraceparentData(ctx.request.get("sentry-trace"));
|
136
|
+
}
|
137
|
+
const transaction = Sentry.startTransaction({
|
138
|
+
name: `${reqMethod} ${reqUrl}`,
|
139
|
+
op: "http.server",
|
140
|
+
...traceparentData
|
141
|
+
});
|
142
|
+
ctx.__sentry_transaction = transaction;
|
143
|
+
Sentry.getCurrentHub().configureScope((scope) => {
|
144
|
+
scope.setSpan(transaction);
|
145
|
+
});
|
146
|
+
ctx.res.on("finish", () => {
|
147
|
+
setImmediate(() => {
|
148
|
+
if (ctx._matchedRoute) {
|
149
|
+
const mountPath = ctx.mountPath || "";
|
150
|
+
transaction.setName(`${reqMethod} ${mountPath}${ctx._matchedRoute}`);
|
151
|
+
}
|
152
|
+
transaction.setHttpStatus(ctx.status);
|
153
|
+
transaction.finish();
|
154
|
+
});
|
155
|
+
});
|
156
|
+
await next();
|
157
|
+
};
|
158
|
+
}
|
159
|
+
function sentry(dsn) {
|
160
|
+
debug("Initializing Sentry");
|
161
|
+
Sentry.init({
|
162
|
+
dsn,
|
163
|
+
integrations: [
|
164
|
+
// Automatically instrument Node.js libraries and frameworks
|
165
|
+
...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(),
|
166
|
+
new import_profiling_node.ProfilingIntegration()
|
167
|
+
],
|
168
|
+
// Performance Monitoring
|
169
|
+
tracesSampleRate: 1,
|
170
|
+
// Set sampling rate for profiling - this is relative to tracesSampleRate
|
171
|
+
profilesSampleRate: 1
|
172
|
+
});
|
173
|
+
return (0, import_koa_compose.default)([errorHandler(), requestHandler(), tracing()]);
|
174
|
+
}
|
175
|
+
|
176
|
+
// src/koa-middleware/healthcheck.ts
|
177
|
+
function healthcheck({ isHealthy, isReady } = {}) {
|
178
|
+
return async function healthcheckMiddleware(ctx, next) {
|
179
|
+
if (ctx.path === "/healthz") {
|
180
|
+
if (isHealthy == null || isHealthy()) {
|
181
|
+
ctx.status = 200;
|
182
|
+
ctx.body = "healthy";
|
183
|
+
} else {
|
184
|
+
ctx.status = 503;
|
185
|
+
ctx.body = "not healthy";
|
186
|
+
}
|
187
|
+
return;
|
188
|
+
}
|
189
|
+
if (ctx.path === "/readyz") {
|
190
|
+
if (isReady == null || isReady()) {
|
191
|
+
ctx.status = 200;
|
192
|
+
ctx.body = "ready";
|
193
|
+
} else {
|
194
|
+
ctx.status = 503;
|
195
|
+
ctx.body = "not ready";
|
196
|
+
}
|
197
|
+
return;
|
198
|
+
}
|
199
|
+
await next();
|
200
|
+
};
|
201
|
+
}
|
202
|
+
|
203
|
+
// src/koa-middleware/helloWorld.ts
|
204
|
+
function helloWorld() {
|
205
|
+
return async function helloWorldMiddleware(ctx, next) {
|
206
|
+
if (ctx.path === "/") {
|
207
|
+
ctx.status = 200;
|
208
|
+
ctx.body = "emit HelloWorld();";
|
209
|
+
return;
|
210
|
+
}
|
211
|
+
await next();
|
212
|
+
};
|
213
|
+
}
|
214
|
+
|
215
|
+
// src/bin/getClientOptions.ts
|
216
|
+
var import_viem2 = require("viem");
|
217
|
+
var import_utils2 = require("@latticexyz/common/utils");
|
218
|
+
var import_actions = require("viem/actions");
|
219
|
+
async function getClientOptions(env) {
|
220
|
+
if (env.INTERNAL__VALIDATE_BLOCK_RANGE) {
|
221
|
+
const rpcHttpUrl = env.RPC_HTTP_URL;
|
222
|
+
if (!rpcHttpUrl) {
|
223
|
+
throw new Error("Must provide RPC_HTTP_URL when using INTERNAL__VALIDATE_BLOCK_RANGE.");
|
224
|
+
}
|
225
|
+
const chainId = await (0, import_actions.getChainId)((0, import_viem2.createClient)({ transport: (0, import_viem2.http)(rpcHttpUrl) }));
|
226
|
+
const chain = {
|
227
|
+
id: chainId,
|
228
|
+
name: "Unknown",
|
229
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
230
|
+
rpcUrls: { default: { http: [rpcHttpUrl] } }
|
231
|
+
};
|
232
|
+
return {
|
233
|
+
internal_clientOptions: {
|
234
|
+
chain,
|
235
|
+
pollingInterval: env.POLLING_INTERVAL,
|
236
|
+
validateBlockRange: env.INTERNAL__VALIDATE_BLOCK_RANGE
|
237
|
+
}
|
238
|
+
};
|
239
|
+
}
|
240
|
+
const transport = (0, import_viem2.fallback)(
|
241
|
+
[
|
242
|
+
// prefer WS when specified
|
243
|
+
env.RPC_WS_URL ? (0, import_viem2.webSocket)(env.RPC_WS_URL) : void 0,
|
244
|
+
// otherwise use or fallback to HTTP
|
245
|
+
env.RPC_HTTP_URL ? (0, import_viem2.http)(env.RPC_HTTP_URL) : void 0
|
246
|
+
].filter(import_utils2.isDefined)
|
247
|
+
);
|
248
|
+
const publicClient = (0, import_viem2.createClient)({
|
249
|
+
transport,
|
250
|
+
pollingInterval: env.POLLING_INTERVAL
|
251
|
+
});
|
252
|
+
return { publicClient };
|
253
|
+
}
|
254
|
+
|
255
|
+
// src/bin/postgres-decoded-indexer.ts
|
256
|
+
var import_actions2 = require("viem/actions");
|
257
|
+
var import_block_logs_stream = require("@latticexyz/block-logs-stream");
|
258
|
+
(async () => {
|
259
|
+
const env = parseEnv(
|
260
|
+
import_zod2.z.intersection(
|
261
|
+
indexerEnvSchema,
|
262
|
+
import_zod2.z.object({
|
263
|
+
DATABASE_URL: import_zod2.z.string(),
|
264
|
+
HEALTHCHECK_HOST: import_zod2.z.string().optional(),
|
265
|
+
HEALTHCHECK_PORT: import_zod2.z.coerce.number().optional(),
|
266
|
+
SENTRY_DSN: import_zod2.z.string().optional()
|
267
|
+
})
|
268
|
+
)
|
269
|
+
);
|
270
|
+
const clientOptions = await getClientOptions(env);
|
271
|
+
const chainId = await (0, import_actions2.getChainId)((0, import_block_logs_stream.getRpcClient)(clientOptions));
|
272
|
+
const database = (0, import_postgres_js.drizzle)((0, import_postgres.default)(env.DATABASE_URL, { prepare: false }));
|
273
|
+
const { storageAdapter, tables } = await (0, import_postgres_decoded.createStorageAdapter)({ ...clientOptions, database });
|
274
|
+
let startBlock = env.START_BLOCK;
|
275
|
+
try {
|
276
|
+
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));
|
277
|
+
if (chainState?.blockNumber != null) {
|
278
|
+
startBlock = chainState.blockNumber + 1n;
|
279
|
+
console.log("resuming from block number", startBlock);
|
280
|
+
}
|
281
|
+
} catch (error2) {
|
282
|
+
}
|
283
|
+
const { latestBlockNumber$, storedBlockLogs$ } = await (0, import_store_sync.createStoreSync)({
|
284
|
+
...clientOptions,
|
285
|
+
storageAdapter,
|
286
|
+
followBlockTag: env.FOLLOW_BLOCK_TAG,
|
287
|
+
startBlock,
|
288
|
+
maxBlockRange: env.MAX_BLOCK_RANGE,
|
289
|
+
address: env.STORE_ADDRESS
|
290
|
+
});
|
291
|
+
storedBlockLogs$.subscribe();
|
292
|
+
let isCaughtUp = false;
|
293
|
+
(0, import_rxjs.combineLatest)([latestBlockNumber$, storedBlockLogs$]).pipe(
|
294
|
+
(0, import_rxjs.filter)(
|
295
|
+
([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => latestBlockNumber === lastBlockNumberProcessed
|
296
|
+
),
|
297
|
+
(0, import_rxjs.first)()
|
298
|
+
).subscribe(() => {
|
299
|
+
isCaughtUp = true;
|
300
|
+
console.log("all caught up");
|
301
|
+
});
|
302
|
+
if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) {
|
303
|
+
const { default: Koa } = await import("koa");
|
304
|
+
const { default: cors } = await import("@koa/cors");
|
305
|
+
const server = new Koa();
|
306
|
+
if (env.SENTRY_DSN) {
|
307
|
+
server.use(sentry(env.SENTRY_DSN));
|
308
|
+
}
|
309
|
+
server.use(cors());
|
310
|
+
server.use(
|
311
|
+
healthcheck({
|
312
|
+
isReady: () => isCaughtUp
|
313
|
+
})
|
314
|
+
);
|
315
|
+
server.use(helloWorld());
|
316
|
+
server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT });
|
317
|
+
console.log(
|
318
|
+
`postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`
|
319
|
+
);
|
320
|
+
}
|
321
|
+
})();
|
322
|
+
//# sourceMappingURL=postgres-decoded-indexer.cjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["../../src/bin/postgres-decoded-indexer.ts","../../src/bin/parseEnv.ts","../../src/koa-middleware/sentry.ts","../../src/debug.ts","../../src/koa-middleware/healthcheck.ts","../../src/koa-middleware/helloWorld.ts","../../src/bin/getClientOptions.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 { createStorageAdapter } from \"@latticexyz/store-sync/postgres-decoded\";\nimport { createStoreSync } from \"@latticexyz/store-sync\";\nimport { indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { sentry } from \"../koa-middleware/sentry\";\nimport { healthcheck } from \"../koa-middleware/healthcheck\";\nimport { helloWorld } from \"../koa-middleware/helloWorld\";\nimport { getClientOptions } from \"./getClientOptions\";\nimport { 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 SENTRY_DSN: z.string().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 const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database });\n\n let startBlock = env.START_BLOCK;\n\n // Resume from 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 if (chainState?.blockNumber != null) {\n startBlock = chainState.blockNumber + 1n;\n console.log(\"resuming from block number\", startBlock);\n }\n } catch (error) {\n // ignore errors for now\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\n const server = new Koa();\n\n if (env.SENTRY_DSN) {\n server.use(sentry(env.SENTRY_DSN));\n }\n\n server.use(cors());\n server.use(\n healthcheck({\n isReady: () => isCaughtUp,\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 * as Sentry from \"@sentry/node\";\nimport { ProfilingIntegration } from \"@sentry/profiling-node\";\nimport { stripUrlQueryAndFragment } from \"@sentry/utils\";\nimport { debug } from \"../debug\";\nimport Koa from \"koa\";\nimport compose from \"koa-compose\";\n\nexport function errorHandler(): Koa.Middleware {\n return async function errorHandlerMiddleware(ctx, next) {\n try {\n await next();\n } catch (err) {\n Sentry.withScope((scope) => {\n scope.addEventProcessor((event) => {\n return Sentry.addRequestDataToEvent(event, ctx.request);\n });\n Sentry.captureException(err);\n });\n throw err;\n }\n };\n}\n\nexport function requestHandler(): Koa.Middleware {\n return async function requestHandlerMiddleware(ctx, next) {\n await Sentry.runWithAsyncContext(async () => {\n const hub = Sentry.getCurrentHub();\n hub.configureScope((scope) =>\n scope.addEventProcessor((event) =>\n Sentry.addRequestDataToEvent(event, ctx.request, {\n include: {\n user: false,\n },\n }),\n ),\n );\n await next();\n });\n };\n}\n\nexport function tracing(): Koa.Middleware {\n // creates a Sentry transaction per request\n return async function tracingMiddleware(ctx, next) {\n const reqMethod = (ctx.method || \"\").toUpperCase();\n const reqUrl = ctx.url && stripUrlQueryAndFragment(ctx.url);\n\n // Connect to trace of upstream app\n let traceparentData;\n if (ctx.request.get(\"sentry-trace\")) {\n traceparentData = Sentry.extractTraceparentData(ctx.request.get(\"sentry-trace\"));\n }\n\n const transaction = Sentry.startTransaction({\n name: `${reqMethod} ${reqUrl}`,\n op: \"http.server\",\n ...traceparentData,\n });\n\n ctx.__sentry_transaction = transaction;\n\n // We put the transaction on the scope so users can attach children to it\n Sentry.getCurrentHub().configureScope((scope) => {\n scope.setSpan(transaction);\n });\n\n ctx.res.on(\"finish\", () => {\n // Push `transaction.finish` to the next event loop so open spans have a chance to finish before the transaction closes\n setImmediate(() => {\n // If you're using koa router, set the matched route as transaction name\n if (ctx._matchedRoute) {\n const mountPath = ctx.mountPath || \"\";\n transaction.setName(`${reqMethod} ${mountPath}${ctx._matchedRoute}`);\n }\n\n transaction.setHttpStatus(ctx.status);\n transaction.finish();\n });\n });\n\n await next();\n };\n}\n\nexport function sentry(dsn: string): Koa.Middleware {\n debug(\"Initializing Sentry\");\n Sentry.init({\n dsn,\n integrations: [\n // Automatically instrument Node.js libraries and frameworks\n ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(),\n new ProfilingIntegration(),\n ],\n // Performance Monitoring\n tracesSampleRate: 1.0,\n // Set sampling rate for profiling - this is relative to tracesSampleRate\n profilesSampleRate: 1.0,\n });\n\n return compose([errorHandler(), requestHandler(), tracing()]);\n}\n","import createDebug from \"debug\";\n\nexport const debug = createDebug(\"mud:store-indexer\");\nexport const error = createDebug(\"mud:store-indexer\");\n\n// Pipe debug output to stdout instead of stderr\ndebug.log = console.debug.bind(console);\n\n// Pipe error output to stderr\nerror.log = console.error.bind(console);\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\";\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","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":["import_zod","error","createDebug","compose","import_viem","import_utils","import_actions","postgres","error"]}
|
@@ -0,0 +1 @@
|
|
1
|
+
#!/usr/bin/env node
|
@@ -1,3 +1,94 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
|
-
import
|
2
|
+
import {
|
3
|
+
sentry
|
4
|
+
} from "../chunk-R7UQFYRA.js";
|
5
|
+
import {
|
6
|
+
getClientOptions
|
7
|
+
} from "../chunk-CGE4ONKA.js";
|
8
|
+
import {
|
9
|
+
indexerEnvSchema,
|
10
|
+
parseEnv
|
11
|
+
} from "../chunk-L5CWEDU6.js";
|
12
|
+
import {
|
13
|
+
healthcheck
|
14
|
+
} from "../chunk-SJLOWI5M.js";
|
15
|
+
import {
|
16
|
+
helloWorld
|
17
|
+
} from "../chunk-GDNGJPVT.js";
|
18
|
+
|
19
|
+
// src/bin/postgres-decoded-indexer.ts
|
20
|
+
import "dotenv/config";
|
21
|
+
import { z } from "zod";
|
22
|
+
import { eq } from "drizzle-orm";
|
23
|
+
import { combineLatest, filter, first } from "rxjs";
|
24
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
25
|
+
import postgres from "postgres";
|
26
|
+
import { createStorageAdapter } from "@latticexyz/store-sync/postgres-decoded";
|
27
|
+
import { createStoreSync } from "@latticexyz/store-sync";
|
28
|
+
import { getChainId } from "viem/actions";
|
29
|
+
import { getRpcClient } from "@latticexyz/block-logs-stream";
|
30
|
+
(async () => {
|
31
|
+
const env = parseEnv(
|
32
|
+
z.intersection(
|
33
|
+
indexerEnvSchema,
|
34
|
+
z.object({
|
35
|
+
DATABASE_URL: z.string(),
|
36
|
+
HEALTHCHECK_HOST: z.string().optional(),
|
37
|
+
HEALTHCHECK_PORT: z.coerce.number().optional(),
|
38
|
+
SENTRY_DSN: z.string().optional()
|
39
|
+
})
|
40
|
+
)
|
41
|
+
);
|
42
|
+
const clientOptions = await getClientOptions(env);
|
43
|
+
const chainId = await getChainId(getRpcClient(clientOptions));
|
44
|
+
const database = drizzle(postgres(env.DATABASE_URL, { prepare: false }));
|
45
|
+
const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database });
|
46
|
+
let startBlock = env.START_BLOCK;
|
47
|
+
try {
|
48
|
+
const chainState = await database.select().from(tables.configTable).where(eq(tables.configTable.chainId, chainId)).limit(1).execute().then((rows) => rows.find(() => true));
|
49
|
+
if (chainState?.blockNumber != null) {
|
50
|
+
startBlock = chainState.blockNumber + 1n;
|
51
|
+
console.log("resuming from block number", startBlock);
|
52
|
+
}
|
53
|
+
} catch (error) {
|
54
|
+
}
|
55
|
+
const { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({
|
56
|
+
...clientOptions,
|
57
|
+
storageAdapter,
|
58
|
+
followBlockTag: env.FOLLOW_BLOCK_TAG,
|
59
|
+
startBlock,
|
60
|
+
maxBlockRange: env.MAX_BLOCK_RANGE,
|
61
|
+
address: env.STORE_ADDRESS
|
62
|
+
});
|
63
|
+
storedBlockLogs$.subscribe();
|
64
|
+
let isCaughtUp = false;
|
65
|
+
combineLatest([latestBlockNumber$, storedBlockLogs$]).pipe(
|
66
|
+
filter(
|
67
|
+
([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => latestBlockNumber === lastBlockNumberProcessed
|
68
|
+
),
|
69
|
+
first()
|
70
|
+
).subscribe(() => {
|
71
|
+
isCaughtUp = true;
|
72
|
+
console.log("all caught up");
|
73
|
+
});
|
74
|
+
if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) {
|
75
|
+
const { default: Koa } = await import("koa");
|
76
|
+
const { default: cors } = await import("@koa/cors");
|
77
|
+
const server = new Koa();
|
78
|
+
if (env.SENTRY_DSN) {
|
79
|
+
server.use(sentry(env.SENTRY_DSN));
|
80
|
+
}
|
81
|
+
server.use(cors());
|
82
|
+
server.use(
|
83
|
+
healthcheck({
|
84
|
+
isReady: () => isCaughtUp
|
85
|
+
})
|
86
|
+
);
|
87
|
+
server.use(helloWorld());
|
88
|
+
server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT });
|
89
|
+
console.log(
|
90
|
+
`postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`
|
91
|
+
);
|
92
|
+
}
|
93
|
+
})();
|
3
94
|
//# sourceMappingURL=postgres-decoded-indexer.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../src/bin/postgres-decoded-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 { createStorageAdapter } from \"@latticexyz/store-sync/postgres-decoded\";\nimport { createStoreSync } from \"@latticexyz/store-sync\";\nimport { indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { sentry } from \"../koa-middleware/sentry\";\nimport { healthcheck } from \"../koa-middleware/healthcheck\";\nimport { helloWorld } from \"../koa-middleware/helloWorld\";\nimport { getClientOptions } from \"./getClientOptions\";\nimport { getChainId } from \"viem/actions\";\nimport { getRpcClient } from \"@latticexyz/block-logs-stream\";\n\
|
1
|
+
{"version":3,"sources":["../../src/bin/postgres-decoded-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 { createStorageAdapter } from \"@latticexyz/store-sync/postgres-decoded\";\nimport { createStoreSync } from \"@latticexyz/store-sync\";\nimport { indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { sentry } from \"../koa-middleware/sentry\";\nimport { healthcheck } from \"../koa-middleware/healthcheck\";\nimport { helloWorld } from \"../koa-middleware/helloWorld\";\nimport { getClientOptions } from \"./getClientOptions\";\nimport { 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 SENTRY_DSN: z.string().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 const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database });\n\n let startBlock = env.START_BLOCK;\n\n // Resume from 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 if (chainState?.blockNumber != null) {\n startBlock = chainState.blockNumber + 1n;\n console.log(\"resuming from block number\", startBlock);\n }\n } catch (error) {\n // ignore errors for now\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\n const server = new Koa();\n\n if (env.SENTRY_DSN) {\n server.use(sentry(env.SENTRY_DSN));\n }\n\n server.use(cors());\n server.use(\n healthcheck({\n isReady: () => isCaughtUp,\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":";;;;;;;;;;;;;;;;;;;AACA,OAAO;AACP,SAAS,SAAS;AAClB,SAAS,UAAU;AACnB,SAAS,eAAe,QAAQ,aAAa;AAC7C,SAAS,eAAe;AACxB,OAAO,cAAc;AACrB,SAAS,4BAA4B;AACrC,SAAS,uBAAuB;AAMhC,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAAA,CAI5B,YAA2B;AAC1B,QAAM,MAAM;AAAA,IACV,EAAE;AAAA,MACA;AAAA,MACA,EAAE,OAAO;AAAA,QACP,cAAc,EAAE,OAAO;AAAA,QACvB,kBAAkB,EAAE,OAAO,EAAE,SAAS;AAAA,QACtC,kBAAkB,EAAE,OAAO,OAAO,EAAE,SAAS;AAAA,QAC7C,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,MAClC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM,iBAAiB,GAAG;AAEhD,QAAM,UAAU,MAAM,WAAW,aAAa,aAAa,CAAC;AAC5D,QAAM,WAAW,QAAQ,SAAS,IAAI,cAAc,EAAE,SAAS,MAAM,CAAC,CAAC;AAEvE,QAAM,EAAE,gBAAgB,OAAO,IAAI,MAAM,qBAAqB,EAAE,GAAG,eAAe,SAAS,CAAC;AAE5F,MAAI,aAAa,IAAI;AAIrB,MAAI;AACF,UAAM,aAAa,MAAM,SACtB,OAAO,EACP,KAAK,OAAO,WAAW,EACvB,MAAM,GAAG,OAAO,YAAY,SAAS,OAAO,CAAC,EAC7C,MAAM,CAAC,EACP,QAAQ,EAGR,KAAK,CAAC,SAAS,KAAK,KAAK,MAAM,IAAI,CAAC;AAEvC,QAAI,YAAY,eAAe,MAAM;AACnC,mBAAa,WAAW,cAAc;AACtC,cAAQ,IAAI,8BAA8B,UAAU;AAAA,IACtD;AAAA,EACF,SAAS,OAAO;AAAA,EAEhB;AAEA,QAAM,EAAE,oBAAoB,iBAAiB,IAAI,MAAM,gBAAgB;AAAA,IACrE,GAAG;AAAA,IACH;AAAA,IACA,gBAAgB,IAAI;AAAA,IACpB;AAAA,IACA,eAAe,IAAI;AAAA,IACnB,SAAS,IAAI;AAAA,EACf,CAAC;AAED,mBAAiB,UAAU;AAE3B,MAAI,aAAa;AACjB,gBAAc,CAAC,oBAAoB,gBAAgB,CAAC,EACjD;AAAA,IACC;AAAA,MACE,CAAC,CAAC,mBAAmB,EAAE,aAAa,yBAAyB,CAAC,MAC5D,sBAAsB;AAAA,IAC1B;AAAA,IACA,MAAM;AAAA,EACR,EACC,UAAU,MAAM;AACf,iBAAa;AACb,YAAQ,IAAI,eAAe;AAAA,EAC7B,CAAC;AAEH,MAAI,IAAI,oBAAoB,QAAQ,IAAI,oBAAoB,MAAM;AAChE,UAAM,EAAE,SAAS,IAAI,IAAI,MAAM,OAAO,KAAK;AAC3C,UAAM,EAAE,SAAS,KAAK,IAAI,MAAM,OAAO,WAAW;AAElD,UAAM,SAAS,IAAI,IAAI;AAEvB,QAAI,IAAI,YAAY;AAClB,aAAO,IAAI,OAAO,IAAI,UAAU,CAAC;AAAA,IACnC;AAEA,WAAO,IAAI,KAAK,CAAC;AACjB,WAAO;AAAA,MACL,YAAY;AAAA,QACV,SAAS,MAAM;AAAA,MACjB,CAAC;AAAA,IACH;AACA,WAAO,IAAI,WAAW,CAAC;AAEvB,WAAO,OAAO,EAAE,MAAM,IAAI,kBAAkB,MAAM,IAAI,iBAAiB,CAAC;AACxE,YAAQ;AAAA,MACN,2DAA2D,IAAI,gBAAgB,IAAI,IAAI,gBAAgB;AAAA,IACzG;AAAA,EACF;AACF,GAAG;","names":[]}
|