apibara 2.0.0-beta.40 → 2.0.0-beta.42

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.
@@ -0,0 +1,465 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { consola } from "consola";
4
+ import { type ObjectLiteralExpression, Project, SyntaxKind } from "ts-morph";
5
+ import { cyan, green, magenta, yellow } from "./colors";
6
+ import { packageVersions } from "./constants";
7
+ import type { IndexerOptions } from "./types";
8
+ import { checkFileExists, getDnaUrl } from "./utils";
9
+
10
+ export function generatePackageJson(isTypeScript: boolean) {
11
+ return {
12
+ name: "apibara-app",
13
+ version: "0.1.0",
14
+ private: true,
15
+ type: "module",
16
+ scripts: {
17
+ prepare: "apibara prepare",
18
+ dev: "apibara dev",
19
+ start: "apibara start",
20
+ build: "apibara build",
21
+ ...(isTypeScript && { typecheck: "tsc --noEmit" }),
22
+ },
23
+ dependencies: {
24
+ "@apibara/indexer": packageVersions["@apibara/indexer"],
25
+ "@apibara/protocol": packageVersions["@apibara/protocol"],
26
+ apibara: packageVersions.apibara,
27
+ },
28
+ devDependencies: {
29
+ ...(isTypeScript && {
30
+ "@rollup/plugin-typescript":
31
+ packageVersions["@rollup/plugin-typescript"],
32
+ "@types/node": packageVersions["@types/node"],
33
+ typescript: packageVersions.typescript,
34
+ }),
35
+ },
36
+ };
37
+ }
38
+
39
+ export function generateTsConfig() {
40
+ return {
41
+ $schema: "https://json.schemastore.org/tsconfig",
42
+ display: "Default",
43
+ compilerOptions: {
44
+ forceConsistentCasingInFileNames: true,
45
+ target: "ES2022",
46
+ lib: ["ESNext"],
47
+ module: "ESNext",
48
+ moduleResolution: "bundler",
49
+ skipLibCheck: true,
50
+ types: ["node"],
51
+ noEmit: true,
52
+ strict: true,
53
+ baseUrl: ".",
54
+ },
55
+ include: [".", "./.apibara/types"],
56
+ exclude: ["node_modules"],
57
+ };
58
+ }
59
+
60
+ export function generateApibaraConfig(isTypeScript: boolean) {
61
+ return `${isTypeScript ? 'import typescript from "@rollup/plugin-typescript";\nimport type { Plugin } from "apibara/rollup";\n' : ""}import { defineConfig } from "apibara/config";
62
+
63
+ export default defineConfig({
64
+ runtimeConfig: {},${
65
+ isTypeScript
66
+ ? `
67
+ rollupConfig: {
68
+ plugins: [typescript()${isTypeScript ? " as Plugin" : ""}],
69
+ },`
70
+ : ""
71
+ }
72
+ });\n`;
73
+ }
74
+
75
+ export function generateIndexer({
76
+ indexerId,
77
+ storage,
78
+ chain,
79
+ language,
80
+ }: IndexerOptions) {
81
+ return `${
82
+ chain === "ethereum"
83
+ ? `import { EvmStream } from "@apibara/evm";`
84
+ : chain === "beaconchain"
85
+ ? `import { BeaconChainStream } from "@apibara/beaconchain";`
86
+ : chain === "starknet"
87
+ ? `import { StarknetStream } from "@apibara/starknet";`
88
+ : ""
89
+ }
90
+ import { defineIndexer } from "@apibara/indexer";
91
+ ${storage === "postgres" ? `import { drizzleStorage } from "@apibara/plugin-drizzle";` : ""}
92
+ ${language === "typescript" ? `import type { ApibaraRuntimeConfig } from "apibara/types";` : ""}
93
+ import { useLogger } from "@apibara/indexer/plugins";
94
+ ${
95
+ storage === "postgres"
96
+ ? `import { getDrizzlePgDatabase } from "../lib/db";`
97
+ : ""
98
+ }
99
+
100
+
101
+ export default function (runtimeConfig${language === "typescript" ? ": ApibaraRuntimeConfig" : ""}) {
102
+ const indexerId = "${indexerId}";
103
+ const { startingBlock, streamUrl${storage === "postgres" ? ", postgresConnectionString" : ""} } = runtimeConfig[indexerId];
104
+ ${
105
+ storage === "postgres"
106
+ ? "const { db } = getDrizzlePgDatabase(postgresConnectionString);"
107
+ : ""
108
+ }
109
+
110
+ return defineIndexer(${
111
+ chain === "ethereum"
112
+ ? "EvmStream"
113
+ : chain === "beaconchain"
114
+ ? "BeaconChainStream"
115
+ : chain === "starknet"
116
+ ? "StarknetStream"
117
+ : ""
118
+ })({
119
+ streamUrl,
120
+ finality: "accepted",
121
+ startingBlock: BigInt(startingBlock),
122
+ filter: {
123
+ header: "always",
124
+ },
125
+ plugins: [${storage === "postgres" ? "drizzleStorage({ db, persistState: true })" : ""}],
126
+ async transform({ endCursor, finality }) {
127
+ const logger = useLogger();
128
+
129
+ logger.info(
130
+ "Transforming block | orderKey: ",
131
+ endCursor?.orderKey,
132
+ " | finality: ",
133
+ finality
134
+ );
135
+
136
+ ${
137
+ storage === "postgres"
138
+ ? `// Example snippet to insert data into db using drizzle with postgres
139
+ // const { db } = useDrizzleStorage();
140
+ // const { logs } = block;
141
+ // for (const log of logs) {
142
+ // await db.insert(exampleTable).values({
143
+ // number: Number(endCursor?.orderKey),
144
+ // hash: log.transactionHash,
145
+ // });
146
+ // }`
147
+ : ""
148
+ }
149
+ },
150
+ });
151
+ }
152
+ `;
153
+ }
154
+
155
+ export async function createIndexerFile(options: IndexerOptions) {
156
+ const indexerFilePath = path.join(
157
+ options.cwd,
158
+ "indexers",
159
+ `${options.indexerFileId}.indexer.${options.language === "typescript" ? "ts" : "js"}`,
160
+ );
161
+
162
+ const { exists, overwrite } = await checkFileExists(indexerFilePath, {
163
+ askPrompt: true,
164
+ });
165
+
166
+ if (exists && !overwrite) return;
167
+
168
+ const indexerContent = generateIndexer(options);
169
+
170
+ fs.mkdirSync(path.dirname(indexerFilePath), { recursive: true });
171
+ fs.writeFileSync(indexerFilePath, indexerContent);
172
+ }
173
+
174
+ export function updatePackageJson({
175
+ cwd,
176
+ chain,
177
+ storage,
178
+ language,
179
+ }: IndexerOptions) {
180
+ const packageJson = JSON.parse(
181
+ fs.readFileSync(path.join(cwd, "package.json"), "utf8"),
182
+ );
183
+
184
+ if (chain === "ethereum") {
185
+ packageJson.dependencies["@apibara/evm"] = packageVersions["@apibara/evm"];
186
+ } else if (chain === "beaconchain") {
187
+ packageJson.dependencies["@apibara/beaconchain"] =
188
+ packageVersions["@apibara/beaconchain"];
189
+ } else if (chain === "starknet") {
190
+ packageJson.dependencies["@apibara/starknet"] =
191
+ packageVersions["@apibara/starknet"];
192
+ }
193
+
194
+ if (storage === "postgres") {
195
+ packageJson.scripts["drizzle:generate"] = "drizzle-kit generate";
196
+ packageJson.scripts["drizzle:migrate"] = "drizzle-kit migrate";
197
+
198
+ packageJson.dependencies["@apibara/plugin-drizzle"] =
199
+ packageVersions["@apibara/plugin-drizzle"];
200
+
201
+ packageJson.dependencies["drizzle-orm"] = packageVersions["drizzle-orm"];
202
+
203
+ packageJson.dependencies["@electric-sql/pglite"] =
204
+ packageVersions["@electric-sql/pglite"];
205
+
206
+ packageJson.dependencies["drizzle-kit"] = packageVersions["drizzle-kit"];
207
+
208
+ packageJson.dependencies["pg"] = packageVersions["pg"];
209
+
210
+ if (language === "typescript") {
211
+ packageJson.devDependencies["@types/pg"] = packageVersions["@types/pg"];
212
+ }
213
+ }
214
+
215
+ fs.writeFileSync(
216
+ path.join(cwd, "package.json"),
217
+ JSON.stringify(packageJson, null, 2),
218
+ );
219
+ }
220
+
221
+ export function updateApibaraConfigFile({
222
+ indexerId,
223
+ cwd,
224
+ chain,
225
+ storage,
226
+ language,
227
+ network,
228
+ dnaUrl,
229
+ }: IndexerOptions) {
230
+ const pathToConfig = path.join(
231
+ cwd,
232
+ `apibara.config.${language === "typescript" ? "ts" : "js"}`,
233
+ );
234
+
235
+ const runtimeConfigString = `{
236
+ startingBlock: 0,
237
+ streamUrl: "${dnaUrl ?? getDnaUrl(chain, network)}"${
238
+ storage === "postgres"
239
+ ? `,
240
+ postgresConnectionString: process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://${indexerId}"`
241
+ : ""
242
+ }}`;
243
+
244
+ const project = new Project();
245
+ const sourceFile = project.addSourceFileAtPath(pathToConfig);
246
+
247
+ // Find the defineConfig call expression
248
+ const defineConfigCall = sourceFile.getFirstDescendantByKind(
249
+ SyntaxKind.CallExpression,
250
+ );
251
+ if (!defineConfigCall) return;
252
+
253
+ const configObjectExpression =
254
+ defineConfigCall.getArguments()[0] as ObjectLiteralExpression;
255
+
256
+ const runtimeConfigObject =
257
+ configObjectExpression.getProperty("runtimeConfig");
258
+
259
+ if (!runtimeConfigObject) {
260
+ configObjectExpression.addPropertyAssignment({
261
+ name: "runtimeConfig",
262
+ initializer: `{
263
+ "${indexerId}": ${runtimeConfigString}
264
+ }`,
265
+ });
266
+ } else {
267
+ const runtimeConfigProp = runtimeConfigObject.asKindOrThrow(
268
+ SyntaxKind.PropertyAssignment,
269
+ );
270
+ const runtimeConfigObj = runtimeConfigProp
271
+ .getInitializerOrThrow()
272
+ .asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
273
+
274
+ runtimeConfigObj.addPropertyAssignment({
275
+ name: `"${indexerId}"`,
276
+ initializer: runtimeConfigString,
277
+ });
278
+ }
279
+ // Save the changes
280
+ sourceFile.saveSync();
281
+
282
+ sourceFile.formatText({
283
+ tabSize: 2,
284
+ insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: true,
285
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
286
+ });
287
+ }
288
+
289
+ export async function createDrizzleStorageFiles(options: IndexerOptions) {
290
+ const { cwd, language, storage } = options;
291
+
292
+ if (storage !== "postgres") return;
293
+
294
+ const fileExtension = language === "typescript" ? "ts" : "js";
295
+
296
+ /**
297
+ *
298
+ *
299
+ * Drizzle Config File
300
+ *
301
+ *
302
+ */
303
+
304
+ const drizzleConfigFileName = `drizzle.config.${fileExtension}`;
305
+
306
+ // create drizzle.config.ts
307
+ const drizzleConfigPath = path.join(cwd, drizzleConfigFileName);
308
+
309
+ const { exists, overwrite } = await checkFileExists(drizzleConfigPath, {
310
+ askPrompt: true,
311
+ allowIgnore: true,
312
+ });
313
+
314
+ if (!exists || overwrite) {
315
+ const drizzleConfigContent = `${language === "typescript" ? 'import type { Config } from "drizzle-kit";' : ""}
316
+
317
+ export default {
318
+ schema: "./lib/schema.ts",
319
+ out: "./drizzle",
320
+ dialect: "postgresql",
321
+ dbCredentials: {
322
+ url: process.env["POSTGRES_CONNECTION_STRING"] ?? "",
323
+ },
324
+ }${language === "typescript" ? " satisfies Config" : ""};`;
325
+
326
+ fs.writeFileSync(drizzleConfigPath, drizzleConfigContent);
327
+
328
+ consola.success(`Created ${cyan(drizzleConfigFileName)}`);
329
+ }
330
+
331
+ /**
332
+ *
333
+ *
334
+ * Schema File
335
+ *
336
+ *
337
+ */
338
+
339
+ const schemaFileName = `schema.${fileExtension}`;
340
+
341
+ const schemaPath = path.join(cwd, "lib", schemaFileName);
342
+
343
+ const { exists: schemaExists, overwrite: schemaOverwrite } =
344
+ await checkFileExists(schemaPath, {
345
+ askPrompt: true,
346
+ allowIgnore: true,
347
+ fileName: `lib/${schemaFileName}`,
348
+ });
349
+
350
+ if (!schemaExists || schemaOverwrite) {
351
+ const schemaContent = `// --- Add your pg table schemas here ----
352
+
353
+ // import { bigint, pgTable, text, uuid } from "drizzle-orm/pg-core";
354
+
355
+ // export const exampleTable = pgTable("example_table", {
356
+ // id: uuid("id").primaryKey().defaultRandom(),
357
+ // number: bigint("number", { mode: "number" }),
358
+ // hash: text("hash"),
359
+ // });
360
+
361
+ export {};
362
+ `;
363
+
364
+ // create directory if it doesn't exist
365
+ fs.mkdirSync(path.dirname(schemaPath), { recursive: true });
366
+ fs.writeFileSync(schemaPath, schemaContent);
367
+
368
+ consola.success(`Created ${cyan("lib/schema.ts")}`);
369
+ }
370
+
371
+ /**
372
+ *
373
+ *
374
+ * DB File
375
+ *
376
+ *
377
+ */
378
+ const dbFileName = `db.${fileExtension}`;
379
+
380
+ const dbPath = path.join(cwd, "lib", dbFileName);
381
+
382
+ const { exists: dbExists, overwrite: dbOverwrite } = await checkFileExists(
383
+ dbPath,
384
+ {
385
+ askPrompt: true,
386
+ fileName: `lib/${dbFileName}`,
387
+ allowIgnore: true,
388
+ },
389
+ );
390
+
391
+ if (!dbExists || dbOverwrite) {
392
+ const dbContent = `import * as schema from "./schema";
393
+ import { drizzle as nodePgDrizzle } from "drizzle-orm/node-postgres";
394
+ import { drizzle as pgLiteDrizzle } from "drizzle-orm/pglite";
395
+ import pg from "pg";
396
+
397
+
398
+ export function getDrizzlePgDatabase(connectionString${language === "typescript" ? ": string" : ""}) {
399
+ // Create pglite instance
400
+ if (connectionString.includes("memory")) {
401
+ return {
402
+ db: pgLiteDrizzle({
403
+ schema,
404
+ connection: {
405
+ dataDir: connectionString,
406
+ },
407
+ }),
408
+ };
409
+ }
410
+
411
+ // Create node-postgres instance
412
+ const pool = new pg.Pool({
413
+ connectionString,
414
+ });
415
+
416
+ return { db: nodePgDrizzle(pool, { schema }) };
417
+ }`;
418
+
419
+ // create directory if it doesn't exist
420
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
421
+ fs.writeFileSync(dbPath, dbContent);
422
+
423
+ consola.success(`Created ${cyan(`lib/${dbFileName}`)}`);
424
+ }
425
+
426
+ console.log("\n");
427
+
428
+ // If schema file is created, show the example
429
+ if (!schemaExists || schemaOverwrite) {
430
+ consola.info(
431
+ `Make sure to export your pgTables in ${cyan(`lib/${schemaFileName}`)}`,
432
+ );
433
+
434
+ console.log();
435
+
436
+ consola.info(`${magenta("Example:")}
437
+
438
+ ${yellow(`
439
+ ┌──────────────────────────────────────────┐
440
+ │ lib/schema.ts │
441
+ └──────────────────────────────────────────┘
442
+
443
+ import { bigint, pgTable, text, uuid } from "drizzle-orm/pg-core";
444
+
445
+ export const exampleTable = pgTable("example_table", {
446
+ id: uuid("id").primaryKey().defaultRandom(),
447
+ number: bigint("number", { mode: "number" }),
448
+ hash: text("hash"),
449
+ });`)}`);
450
+
451
+ console.log("\n");
452
+ }
453
+
454
+ consola.info(
455
+ `Run ${green(`${options.packageManager} run drizzle:generate`)} & ${green(`${options.packageManager} run drizzle:migrate`)} to generate and apply migrations.`,
456
+ );
457
+ }
458
+
459
+ export async function createStorageRelatedFiles(options: IndexerOptions) {
460
+ const { storage } = options;
461
+
462
+ if (storage === "postgres") {
463
+ await createDrizzleStorageFiles(options);
464
+ }
465
+ }
@@ -0,0 +1,34 @@
1
+ export type ColorFunc = (str: string | number) => string;
2
+
3
+ export type Language = "typescript" | "javascript";
4
+
5
+ export type Chain = "starknet" | "ethereum" | "beaconchain";
6
+
7
+ export type Starknet_Network = "mainnet" | "sepolia";
8
+ export type Ethereum_Network = "mainnet" | "sepolia";
9
+ export type Beaconchain_Network = "mainnet";
10
+
11
+ export type Network =
12
+ | Starknet_Network
13
+ | Ethereum_Network
14
+ | Beaconchain_Network
15
+ | "other";
16
+
17
+ export type Storage = "postgres" | "none";
18
+
19
+ export type IndexerOptions = {
20
+ cwd: string;
21
+ indexerFileId: string;
22
+ indexerId: string;
23
+ chain: Chain;
24
+ network: Network;
25
+ storage: Storage;
26
+ dnaUrl?: string;
27
+ packageManager: string;
28
+ language: Language;
29
+ };
30
+
31
+ export type PkgInfo = {
32
+ name: string;
33
+ version?: string;
34
+ };