apibara 2.1.0-beta.1 → 2.1.0-beta.10

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 (51) hide show
  1. package/dist/chunks/add.mjs +12 -7
  2. package/dist/chunks/dev.mjs +23 -5
  3. package/dist/chunks/init.mjs +3 -7
  4. package/dist/core/index.mjs +69 -40
  5. package/dist/create/index.d.mts +2 -1
  6. package/dist/create/index.d.ts +2 -1
  7. package/dist/create/index.mjs +185 -121
  8. package/dist/rolldown/index.d.mts +7 -0
  9. package/dist/rolldown/index.d.ts +7 -0
  10. package/dist/rolldown/index.mjs +90 -0
  11. package/dist/runtime/dev.mjs +3 -0
  12. package/dist/runtime/internal/app.d.ts +1 -1
  13. package/dist/runtime/internal/app.mjs +11 -3
  14. package/dist/runtime/start.mjs +5 -0
  15. package/dist/types/index.d.mts +18 -15
  16. package/dist/types/index.d.ts +18 -15
  17. package/package.json +12 -15
  18. package/src/cli/commands/add.ts +12 -6
  19. package/src/cli/commands/dev.ts +26 -5
  20. package/src/cli/commands/init.ts +3 -7
  21. package/src/core/build/build.ts +13 -5
  22. package/src/core/build/dev.ts +44 -23
  23. package/src/core/build/error.ts +9 -14
  24. package/src/core/build/prod.ts +15 -10
  25. package/src/core/build/types.ts +8 -0
  26. package/src/core/config/defaults.ts +3 -0
  27. package/src/core/config/update.ts +1 -1
  28. package/src/create/add.ts +26 -12
  29. package/src/create/constants.ts +9 -10
  30. package/src/create/init.ts +28 -14
  31. package/src/create/templates.ts +154 -118
  32. package/src/create/utils.ts +10 -0
  33. package/src/rolldown/config.ts +83 -0
  34. package/src/rolldown/index.ts +2 -0
  35. package/src/{rollup → rolldown}/plugins/config.ts +2 -1
  36. package/src/{rollup → rolldown}/plugins/indexers.ts +3 -3
  37. package/src/runtime/dev.ts +3 -0
  38. package/src/runtime/internal/app.ts +13 -5
  39. package/src/runtime/start.ts +5 -0
  40. package/src/types/config.ts +12 -7
  41. package/src/types/hooks.ts +8 -5
  42. package/src/types/index.ts +1 -1
  43. package/src/types/rolldown.ts +5 -0
  44. package/src/types/virtual/indexers.d.ts +4 -1
  45. package/dist/rollup/index.d.mts +0 -6
  46. package/dist/rollup/index.d.ts +0 -6
  47. package/dist/rollup/index.mjs +0 -150
  48. package/src/rollup/config.ts +0 -87
  49. package/src/rollup/index.ts +0 -2
  50. package/src/rollup/plugins/esm-shim.ts +0 -69
  51. package/src/types/rollup.ts +0 -8
package/src/create/add.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import consola from "consola";
2
3
  import prompts from "prompts";
3
4
  import { cyan, red, reset } from "./colors";
@@ -18,6 +19,7 @@ import {
18
19
  import type { Chain, IndexerOptions, Network, Storage } from "./types";
19
20
  import {
20
21
  cancelOperation,
22
+ checkFileExists,
21
23
  convertKebabToCamelCase,
22
24
  getApibaraConfigLanguage,
23
25
  getPackageManager,
@@ -35,6 +37,7 @@ type Options = {
35
37
  argNetwork?: string;
36
38
  argStorage?: string;
37
39
  argDnaUrl?: string;
40
+ argRootDir?: string;
38
41
  };
39
42
 
40
43
  export async function addIndexer({
@@ -43,8 +46,10 @@ export async function addIndexer({
43
46
  argNetwork,
44
47
  argStorage,
45
48
  argDnaUrl,
49
+ argRootDir,
46
50
  }: Options) {
47
- const configExists = hasApibaraConfig(process.cwd());
51
+ const cwd = path.join(process.cwd(), argRootDir ?? ".");
52
+ const configExists = hasApibaraConfig(cwd);
48
53
 
49
54
  if (!configExists) {
50
55
  consola.error("No apibara.config found in the current directory.");
@@ -72,7 +77,7 @@ export async function addIndexer({
72
77
  }
73
78
  }
74
79
 
75
- const language = getApibaraConfigLanguage(process.cwd());
80
+ const language = getApibaraConfigLanguage(cwd);
76
81
 
77
82
  validateIndexerId(argIndexerId, true);
78
83
  validateChain(argChain, true);
@@ -88,8 +93,19 @@ export async function addIndexer({
88
93
  message: reset("Indexer ID:"),
89
94
  initial: argIndexerId ?? "my-indexer",
90
95
  validate: (id) =>
91
- validateIndexerId(id) ||
92
- "Invalid indexer ID cannot be empty and must be in kebab-case format",
96
+ validateIndexerId(id)
97
+ ? checkFileExists(
98
+ path.join(
99
+ cwd,
100
+ "indexers",
101
+ `${id}.indexer.${language === "typescript" ? "ts" : "js"}`,
102
+ ),
103
+ ).then(({ exists }) =>
104
+ exists
105
+ ? `Indexer ${cyan(`${id}.indexer.${language === "typescript" ? "ts" : "js"}`)} already exists`
106
+ : true,
107
+ )
108
+ : "Invalid indexer ID, it cannot be empty and must be in kebab-case format",
93
109
  },
94
110
  {
95
111
  type: argChain ? null : "select",
@@ -184,7 +200,7 @@ export async function addIndexer({
184
200
  const pkgManager = getPackageManager();
185
201
 
186
202
  const options: IndexerOptions = {
187
- cwd: process.cwd(),
203
+ cwd: cwd,
188
204
  indexerFileId,
189
205
  indexerId: convertKebabToCamelCase(indexerFileId),
190
206
  chain: (argChain as Chain) ?? prompt_chain?.name!,
@@ -195,13 +211,13 @@ export async function addIndexer({
195
211
  packageManager: pkgManager.name,
196
212
  };
197
213
 
198
- updateApibaraConfigFile(options);
214
+ await updateApibaraConfigFile(options);
199
215
 
200
216
  consola.success(
201
217
  `Updated ${cyan("apibara.config." + (language === "typescript" ? "ts" : "js"))}`,
202
218
  );
203
219
 
204
- updatePackageJson(options);
220
+ await updatePackageJson(options);
205
221
 
206
222
  consola.success(`Updated ${cyan("package.json")}`);
207
223
 
@@ -215,11 +231,9 @@ export async function addIndexer({
215
231
 
216
232
  console.log();
217
233
 
234
+ const baseCommand = `${options.packageManager} install`;
235
+ const tsCommand = `${baseCommand} && ${options.packageManager} run prepare`;
218
236
  consola.info(
219
- `Before running the indexer, run ${cyan(`${options.packageManager} run install`)}${
220
- language === "typescript"
221
- ? " & " + cyan(`${options.packageManager} run prepare`)
222
- : ""
223
- }`,
237
+ `Before running the indexer, run ${cyan(language === "typescript" ? tsCommand : baseCommand)}`,
224
238
  );
225
239
  }
@@ -66,17 +66,17 @@ export const storages: StorageDataType[] = [
66
66
 
67
67
  export const packageVersions = {
68
68
  // Required Dependencies
69
- apibara: "^2.1.0-beta.1",
70
- "@apibara/indexer": "^2.1.0-beta.1",
71
- "@apibara/protocol": "^2.1.0-beta.1",
69
+ apibara: "next",
70
+ "@apibara/indexer": "next",
71
+ "@apibara/protocol": "next",
72
72
  // Chain Dependencies
73
- "@apibara/evm": "^2.1.0-beta.1",
74
- "@apibara/beaconchain": "^2.1.0-beta.1",
75
- "@apibara/starknet": "^2.1.0-beta.1",
73
+ "@apibara/evm": "next",
74
+ "@apibara/beaconchain": "next",
75
+ "@apibara/starknet": "next",
76
76
  // Storage Dependencies
77
- "@apibara/plugin-drizzle": "^2.1.0-beta.1",
78
- "@apibara/plugin-mongo": "^2.1.0-beta.1",
79
- "@apibara/plugin-sqlite": "^2.1.0-beta.1",
77
+ "@apibara/plugin-drizzle": "next",
78
+ "@apibara/plugin-mongo": "next",
79
+ "@apibara/plugin-sqlite": "next",
80
80
  // Postgres Dependencies
81
81
  "@electric-sql/pglite": "^0.2.17",
82
82
  "drizzle-orm": "^0.37.0",
@@ -85,7 +85,6 @@ export const packageVersions = {
85
85
  "drizzle-kit": "^0.29.0",
86
86
  // Typescript Dependencies
87
87
  typescript: "^5.6.2",
88
- "@rollup/plugin-typescript": "^11.1.6",
89
88
  "@types/node": "^20.5.2",
90
89
  };
91
90
 
@@ -5,6 +5,7 @@ import prompts from "prompts";
5
5
  import { addIndexer } from "./add";
6
6
  import { cyan, green } from "./colors";
7
7
  import {
8
+ createGitIgnoreFile,
8
9
  generateApibaraConfig,
9
10
  generatePackageJson,
10
11
  generateTsConfig,
@@ -13,6 +14,7 @@ import type { Language } from "./types";
13
14
  import {
14
15
  cancelOperation,
15
16
  emptyDir,
17
+ formatFile,
16
18
  getLanguageFromAlias,
17
19
  getPackageManager,
18
20
  isEmpty,
@@ -121,30 +123,42 @@ export async function initializeProject({
121
123
  consola.info(`Initializing project in ${argTargetDir}\n\n`);
122
124
 
123
125
  // Generate package.json
126
+ const packageJsonPath = path.join(root, "package.json");
124
127
  const packageJson = generatePackageJson(isTs);
125
128
  fs.writeFileSync(
126
- path.join(root, "package.json"),
129
+ packageJsonPath,
127
130
  JSON.stringify(packageJson, null, 2) + "\n",
128
131
  );
129
- consola.success("Created ", cyan("package.json"));
132
+ await formatFile(packageJsonPath);
133
+ consola.success("Created", cyan("package.json"));
130
134
 
131
135
  // Generate tsconfig.json if TypeScript
132
136
  if (isTs) {
137
+ const tsConfigPath = path.join(root, "tsconfig.json");
133
138
  const tsConfig = generateTsConfig();
134
- fs.writeFileSync(
135
- path.join(root, "tsconfig.json"),
136
- JSON.stringify(tsConfig, null, 2) + "\n",
137
- );
138
- consola.success("Created ", cyan("tsconfig.json"));
139
+ fs.writeFileSync(tsConfigPath, JSON.stringify(tsConfig, null, 2) + "\n");
140
+ await formatFile(tsConfigPath);
141
+ consola.success("Created", cyan("tsconfig.json"));
139
142
  }
140
143
 
144
+ const apibaraConfigPath = path.join(root, `apibara.config.${configExt}`);
141
145
  // Generate apibara.config
142
146
  const apibaraConfig = generateApibaraConfig(isTs);
143
- fs.writeFileSync(
144
- path.join(root, `apibara.config.${configExt}`),
145
- apibaraConfig,
146
- );
147
- consola.success("Created ", cyan(`apibara.config.${configExt}`), "\n\n");
147
+ fs.writeFileSync(apibaraConfigPath, apibaraConfig);
148
+ await formatFile(apibaraConfigPath);
149
+ consola.success("Created", cyan(`apibara.config.${configExt}`));
150
+
151
+ // Create "indexers" directory if not exists
152
+ const indexersDir = path.join(root, "indexers");
153
+ if (!fs.existsSync(indexersDir)) {
154
+ fs.mkdirSync(indexersDir, { recursive: true });
155
+ consola.success(`Created ${cyan("indexers")} directory`);
156
+ }
157
+
158
+ await createGitIgnoreFile(root);
159
+
160
+ console.log("\n");
161
+
148
162
  consola.ready(green("Project initialized successfully"));
149
163
 
150
164
  console.log();
@@ -152,12 +166,12 @@ export async function initializeProject({
152
166
  if (!argNoCreateIndexer) {
153
167
  consola.info("Let's create an indexer\n");
154
168
 
155
- await addIndexer({});
169
+ await addIndexer({ argRootDir: argTargetDir });
156
170
  } else {
157
171
  const pkgManager = getPackageManager();
158
172
  consola.info(
159
173
  "Run ",
160
- green(`${pkgManager.name} run install`),
174
+ green(`${pkgManager.name} install`),
161
175
  " to install all dependencies",
162
176
  );
163
177
  }
@@ -1,11 +1,12 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { consola } from "consola";
4
+ import prompts from "prompts";
4
5
  import { type ObjectLiteralExpression, Project, SyntaxKind } from "ts-morph";
5
6
  import { cyan, green, magenta, yellow } from "./colors";
6
7
  import { packageVersions } from "./constants";
7
8
  import type { IndexerOptions } from "./types";
8
- import { checkFileExists, getDnaUrl } from "./utils";
9
+ import { checkFileExists, formatFile, getDnaUrl } from "./utils";
9
10
 
10
11
  export function generatePackageJson(isTypeScript: boolean) {
11
12
  return {
@@ -14,7 +15,7 @@ export function generatePackageJson(isTypeScript: boolean) {
14
15
  private: true,
15
16
  type: "module",
16
17
  scripts: {
17
- prepare: "apibara prepare",
18
+ ...(isTypeScript && { prepare: "apibara prepare" }),
18
19
  dev: "apibara dev",
19
20
  start: "apibara start",
20
21
  build: "apibara build",
@@ -27,8 +28,6 @@ export function generatePackageJson(isTypeScript: boolean) {
27
28
  },
28
29
  devDependencies: {
29
30
  ...(isTypeScript && {
30
- "@rollup/plugin-typescript":
31
- packageVersions["@rollup/plugin-typescript"],
32
31
  "@types/node": packageVersions["@types/node"],
33
32
  typescript: packageVersions.typescript,
34
33
  }),
@@ -58,17 +57,10 @@ export function generateTsConfig() {
58
57
  }
59
58
 
60
59
  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";
60
+ return `import { defineConfig } from "apibara/config";
62
61
 
63
62
  export default defineConfig({
64
- runtimeConfig: {},${
65
- isTypeScript
66
- ? `
67
- rollupConfig: {
68
- plugins: [typescript()${isTypeScript ? " as Plugin" : ""}],
69
- },`
70
- : ""
71
- }
63
+ runtimeConfig: {},
72
64
  });\n`;
73
65
  }
74
66
 
@@ -78,24 +70,21 @@ export function generateIndexer({
78
70
  chain,
79
71
  language,
80
72
  }: 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";` : ""}
73
+ return `import { defineIndexer } from "@apibara/indexer";
93
74
  import { useLogger } from "@apibara/indexer/plugins";
75
+ ${storage === "postgres" ? `import { drizzleStorage } from "@apibara/plugin-drizzle";` : ""}
76
+ ${storage === "postgres" ? `import { drizzle } from "@apibara/plugin-drizzle";` : ""}
94
77
  ${
95
- storage === "postgres"
96
- ? `import { getDrizzlePgDatabase } from "../lib/db";`
97
- : ""
78
+ chain === "ethereum"
79
+ ? `import { EvmStream } from "@apibara/evm";`
80
+ : chain === "beaconchain"
81
+ ? `import { BeaconChainStream } from "@apibara/beaconchain";`
82
+ : chain === "starknet"
83
+ ? `import { StarknetStream } from "@apibara/starknet";`
84
+ : ""
98
85
  }
86
+ ${language === "typescript" ? `import type { ApibaraRuntimeConfig } from "apibara/types";` : ""}
87
+ ${storage === "postgres" ? `import * as schema from "../lib/schema";` : ""}
99
88
 
100
89
 
101
90
  export default function (runtimeConfig${language === "typescript" ? ": ApibaraRuntimeConfig" : ""}) {
@@ -103,7 +92,10 @@ export default function (runtimeConfig${language === "typescript" ? ": ApibaraRu
103
92
  const { startingBlock, streamUrl${storage === "postgres" ? ", postgresConnectionString" : ""} } = runtimeConfig[indexerId];
104
93
  ${
105
94
  storage === "postgres"
106
- ? "const { db } = getDrizzlePgDatabase(postgresConnectionString);"
95
+ ? `const db = drizzle({
96
+ schema,
97
+ connectionString: postgresConnectionString,
98
+ });`
107
99
  : ""
108
100
  }
109
101
 
@@ -122,7 +114,7 @@ export default function (runtimeConfig${language === "typescript" ? ": ApibaraRu
122
114
  filter: {
123
115
  header: "always",
124
116
  },
125
- plugins: [${storage === "postgres" ? "drizzleStorage({ db, persistState: true })" : ""}],
117
+ plugins: [${storage === "postgres" ? "drizzleStorage({ db, migrate: { migrationsFolder: './drizzle' } })" : ""}],
126
118
  async transform({ endCursor, finality }) {
127
119
  const logger = useLogger();
128
120
 
@@ -136,14 +128,12 @@ export default function (runtimeConfig${language === "typescript" ? ": ApibaraRu
136
128
  ${
137
129
  storage === "postgres"
138
130
  ? `// 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
- // }`
131
+ // const { db: database } = useDrizzleStorage();
132
+
133
+ // await database.insert(schema.cursorTable).values({
134
+ // endCursor: Number(endCursor?.orderKey),
135
+ // uniqueKey: \`\${endCursor?.uniqueKey}\`,
136
+ // });`
147
137
  : ""
148
138
  }
149
139
  },
@@ -169,17 +159,19 @@ export async function createIndexerFile(options: IndexerOptions) {
169
159
 
170
160
  fs.mkdirSync(path.dirname(indexerFilePath), { recursive: true });
171
161
  fs.writeFileSync(indexerFilePath, indexerContent);
162
+
163
+ await formatFile(indexerFilePath);
172
164
  }
173
165
 
174
- export function updatePackageJson({
166
+ export async function updatePackageJson({
175
167
  cwd,
176
168
  chain,
177
169
  storage,
178
170
  language,
179
171
  }: IndexerOptions) {
180
- const packageJson = JSON.parse(
181
- fs.readFileSync(path.join(cwd, "package.json"), "utf8"),
182
- );
172
+ const packageJsonPath = path.join(cwd, "package.json");
173
+
174
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
183
175
 
184
176
  if (chain === "ethereum") {
185
177
  packageJson.dependencies["@apibara/evm"] = packageVersions["@apibara/evm"];
@@ -212,13 +204,12 @@ export function updatePackageJson({
212
204
  }
213
205
  }
214
206
 
215
- fs.writeFileSync(
216
- path.join(cwd, "package.json"),
217
- JSON.stringify(packageJson, null, 2),
218
- );
207
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
208
+
209
+ await formatFile(packageJsonPath);
219
210
  }
220
211
 
221
- export function updateApibaraConfigFile({
212
+ export async function updateApibaraConfigFile({
222
213
  indexerId,
223
214
  cwd,
224
215
  chain,
@@ -279,15 +270,11 @@ export function updateApibaraConfigFile({
279
270
  // Save the changes
280
271
  sourceFile.saveSync();
281
272
 
282
- sourceFile.formatText({
283
- tabSize: 2,
284
- insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: true,
285
- insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
286
- });
273
+ await formatFile(pathToConfig);
287
274
  }
288
275
 
289
276
  export async function createDrizzleStorageFiles(options: IndexerOptions) {
290
- const { cwd, language, storage } = options;
277
+ const { cwd, language, storage, indexerId } = options;
291
278
 
292
279
  if (storage !== "postgres") return;
293
280
 
@@ -315,16 +302,18 @@ export async function createDrizzleStorageFiles(options: IndexerOptions) {
315
302
  const drizzleConfigContent = `${language === "typescript" ? 'import type { Config } from "drizzle-kit";' : ""}
316
303
 
317
304
  export default {
318
- schema: "./lib/schema.ts",
305
+ schema: "./lib/schema.${fileExtension}",
319
306
  out: "./drizzle",
320
307
  dialect: "postgresql",
321
308
  dbCredentials: {
322
- url: process.env["POSTGRES_CONNECTION_STRING"] ?? "",
309
+ url: process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://${indexerId}",
323
310
  },
324
311
  }${language === "typescript" ? " satisfies Config" : ""};`;
325
312
 
326
313
  fs.writeFileSync(drizzleConfigPath, drizzleConfigContent);
327
314
 
315
+ await formatFile(drizzleConfigPath);
316
+
328
317
  consola.success(`Created ${cyan(drizzleConfigFileName)}`);
329
318
  }
330
319
 
@@ -348,14 +337,14 @@ export default {
348
337
  });
349
338
 
350
339
  if (!schemaExists || schemaOverwrite) {
351
- const schemaContent = `// --- Add your pg table schemas here ----
340
+ const schemaContent = `// --- Add your pg table schemas here ----
352
341
 
353
342
  // import { bigint, pgTable, text, uuid } from "drizzle-orm/pg-core";
354
343
 
355
- // export const exampleTable = pgTable("example_table", {
344
+ // export const cursorTable = pgTable("cursor_table", {
356
345
  // id: uuid("id").primaryKey().defaultRandom(),
357
- // number: bigint("number", { mode: "number" }),
358
- // hash: text("hash"),
346
+ // endCursor: bigint("end_cursor", { mode: "number" }),
347
+ // uniqueKey: text("unique_key"),
359
348
  // });
360
349
 
361
350
  export {};
@@ -365,62 +354,9 @@ export {};
365
354
  fs.mkdirSync(path.dirname(schemaPath), { recursive: true });
366
355
  fs.writeFileSync(schemaPath, schemaContent);
367
356
 
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);
357
+ await formatFile(schemaPath);
381
358
 
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}`)}`);
359
+ consola.success(`Created ${cyan("lib/schema.ts")}`);
424
360
  }
425
361
 
426
362
  console.log("\n");
@@ -442,17 +378,17 @@ ${yellow(`
442
378
 
443
379
  import { bigint, pgTable, text, uuid } from "drizzle-orm/pg-core";
444
380
 
445
- export const exampleTable = pgTable("example_table", {
381
+ export const cursorTable = pgTable("cursor_table", {
446
382
  id: uuid("id").primaryKey().defaultRandom(),
447
- number: bigint("number", { mode: "number" }),
448
- hash: text("hash"),
383
+ endCursor: bigint("end_cursor", { mode: "number" }),
384
+ uniqueKey: text("unique_key"),
449
385
  });`)}`);
450
386
 
451
387
  console.log("\n");
452
388
  }
453
389
 
454
390
  consola.info(
455
- `Run ${green(`${options.packageManager} run drizzle:generate`)} & ${green(`${options.packageManager} run drizzle:migrate`)} to generate and apply migrations.`,
391
+ `Run ${green(`${options.packageManager}${options.packageManager === "npm" ? " run" : ""} drizzle:generate`)} & ${green(`${options.packageManager}${options.packageManager === "npm" ? " run" : ""} drizzle:migrate`)} to generate and apply migrations.`,
456
392
  );
457
393
  }
458
394
 
@@ -463,3 +399,103 @@ export async function createStorageRelatedFiles(options: IndexerOptions) {
463
399
  await createDrizzleStorageFiles(options);
464
400
  }
465
401
  }
402
+
403
+ const gitIgnoreItems: {
404
+ isRecommended: boolean;
405
+ description?: string;
406
+ value: string;
407
+ }[] = [
408
+ {
409
+ isRecommended: false,
410
+ value: "node_modules",
411
+ },
412
+ {
413
+ isRecommended: false,
414
+ value: "dist",
415
+ },
416
+ {
417
+ isRecommended: true,
418
+ description: "build and dev files of apibara",
419
+ value: ".apibara",
420
+ },
421
+ {
422
+ isRecommended: false,
423
+ value: ".env",
424
+ },
425
+ {
426
+ isRecommended: false,
427
+ description: "for mac users",
428
+ value: ".DS_Store",
429
+ },
430
+ ];
431
+
432
+ export async function createGitIgnoreFile(cwd: string) {
433
+ const gitIgnorePath = path.join(cwd, ".gitignore");
434
+
435
+ if (fs.existsSync(gitIgnorePath)) {
436
+ const result = await prompts([
437
+ {
438
+ type: "select",
439
+ name: "overwrite",
440
+ message: `${cyan(".gitignore")} already exists. Please choose how to proceed:`,
441
+ initial: 0,
442
+ choices: [
443
+ {
444
+ title: "Choose items to append in your .gitignore",
445
+ value: "append",
446
+ },
447
+ {
448
+ title: "Keep original",
449
+ value: "ignore",
450
+ },
451
+ {
452
+ title: "Overwrite",
453
+ value: "overwrite",
454
+ },
455
+ ],
456
+ },
457
+ {
458
+ type: (overwrite: "append" | "ignore" | "overwrite") =>
459
+ overwrite === "append" ? "multiselect" : null,
460
+ name: "ignoreItems",
461
+ message: "Choose items to append in your .gitignore",
462
+ choices: gitIgnoreItems.map((item) => ({
463
+ title: `${yellow(item.value)}${
464
+ item.description ? ` - ${item.description}` : ""
465
+ }${item.isRecommended ? ` ${green("(recommended)")}` : ""}`,
466
+ value: item.value,
467
+ })),
468
+ },
469
+ ]);
470
+
471
+ const { overwrite, ignoreItems } = result as {
472
+ overwrite: "append" | "ignore" | "overwrite";
473
+ ignoreItems: string[];
474
+ };
475
+
476
+ if (overwrite === "append" && ignoreItems.length > 0) {
477
+ const gitIgnoreContent = fs.readFileSync(gitIgnorePath, "utf8");
478
+ fs.writeFileSync(
479
+ gitIgnorePath,
480
+ `${gitIgnoreContent}\n${result.ignoreItems.join("\n")}`,
481
+ );
482
+ consola.success(`Updated ${cyan(".gitignore")}`);
483
+ return;
484
+ }
485
+
486
+ if (overwrite === "overwrite") {
487
+ fs.writeFileSync(
488
+ gitIgnorePath,
489
+ gitIgnoreItems.map((item) => item.value).join("\n"),
490
+ );
491
+ consola.success(`Updated ${cyan(".gitignore")}`);
492
+ return;
493
+ }
494
+ }
495
+
496
+ fs.writeFileSync(
497
+ gitIgnorePath,
498
+ gitIgnoreItems.map((item) => item.value).join("\n"),
499
+ );
500
+ consola.success(`Created ${cyan(".gitignore")}`);
501
+ }
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path, { basename } from "node:path";
3
+ import * as prettier from "prettier";
3
4
  import prompts from "prompts";
4
5
  import { blue, cyan, red, yellow } from "./colors";
5
6
  import { dnaUrls, networks } from "./constants";
@@ -410,3 +411,12 @@ function pkgFromUserAgent(userAgent: string | undefined): PkgInfo | undefined {
410
411
  version: pkgSpecArr[1],
411
412
  };
412
413
  }
414
+
415
+ export async function formatFile(path: string) {
416
+ const file = fs.readFileSync(path, "utf8");
417
+ const formatted = await prettier.format(file, {
418
+ filepath: path,
419
+ tabWidth: 2,
420
+ });
421
+ fs.writeFileSync(path, formatted);
422
+ }