apibara 2.0.0-beta.9 → 2.1.0-beta.3
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/chunks/add.mjs +44 -0
- package/dist/chunks/build.mjs +3 -3
- package/dist/chunks/dev.mjs +22 -18
- package/dist/chunks/init.mjs +37 -0
- package/dist/chunks/prepare.mjs +0 -2
- package/dist/chunks/start.mjs +56 -0
- package/dist/cli/index.mjs +5 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/core/index.mjs +61 -97
- package/dist/create/index.d.mts +17 -0
- package/dist/create/index.d.ts +17 -0
- package/dist/create/index.mjs +981 -0
- package/dist/rollup/index.d.mts +2 -1
- package/dist/rollup/index.d.ts +2 -1
- package/dist/rollup/index.mjs +130 -167
- package/dist/runtime/dev.d.ts +3 -0
- package/dist/runtime/dev.mjs +55 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.mjs +2 -0
- package/dist/runtime/internal/app.d.ts +2 -0
- package/dist/runtime/internal/app.mjs +56 -0
- package/dist/runtime/internal/logger.d.ts +14 -0
- package/dist/runtime/internal/logger.mjs +45 -0
- package/dist/runtime/start.d.ts +3 -0
- package/dist/runtime/start.mjs +41 -0
- package/dist/types/index.d.mts +22 -19
- package/dist/types/index.d.ts +22 -19
- package/package.json +35 -13
- package/runtime-meta.d.ts +2 -0
- package/runtime-meta.mjs +7 -0
- package/src/cli/commands/add.ts +44 -0
- package/src/cli/commands/build.ts +5 -3
- package/src/cli/commands/dev.ts +28 -18
- package/src/cli/commands/init.ts +36 -0
- package/src/cli/commands/prepare.ts +0 -2
- package/src/cli/commands/start.ts +61 -0
- package/src/cli/index.ts +3 -0
- package/src/config/index.ts +5 -4
- package/src/core/apibara.ts +4 -2
- package/src/core/build/build.ts +2 -0
- package/src/core/build/dev.ts +1 -0
- package/src/core/build/error.ts +0 -1
- package/src/core/build/prepare.ts +5 -2
- package/src/core/build/prod.ts +10 -6
- package/src/core/build/types.ts +4 -95
- package/src/core/config/defaults.ts +1 -4
- package/src/core/config/loader.ts +1 -0
- package/src/core/config/resolvers/runtime-config.resolver.ts +1 -1
- package/src/core/config/update.ts +2 -3
- package/src/core/path.ts +11 -0
- package/src/core/scan.ts +40 -0
- package/src/create/add.ts +238 -0
- package/src/create/colors.ts +15 -0
- package/src/create/constants.ts +98 -0
- package/src/create/index.ts +2 -0
- package/src/create/init.ts +175 -0
- package/src/create/templates.ts +468 -0
- package/src/create/types.ts +34 -0
- package/src/create/utils.ts +422 -0
- package/src/rollup/config.ts +67 -189
- package/src/rollup/index.ts +1 -0
- package/src/rollup/plugins/config.ts +12 -0
- package/src/rollup/plugins/esm-shim.ts +69 -0
- package/src/rollup/plugins/indexers.ts +17 -0
- package/src/runtime/dev.ts +64 -0
- package/src/runtime/index.ts +2 -0
- package/src/runtime/internal/app.ts +78 -0
- package/src/runtime/internal/logger.ts +70 -0
- package/src/runtime/start.ts +48 -0
- package/src/types/apibara.ts +8 -0
- package/src/types/config.ts +28 -27
- package/src/types/hooks.ts +1 -0
- package/src/types/virtual/config.d.ts +3 -0
- package/src/types/virtual/indexers.d.ts +10 -0
- package/dist/internal/citty/index.d.mts +0 -1
- package/dist/internal/citty/index.d.ts +0 -1
- package/dist/internal/citty/index.mjs +0 -1
- package/dist/internal/consola/index.d.mts +0 -2
- package/dist/internal/consola/index.d.ts +0 -2
- package/dist/internal/consola/index.mjs +0 -1
- package/src/internal/citty/index.ts +0 -1
- package/src/internal/consola/index.ts +0 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import prompts from "prompts";
|
|
5
|
+
import { addIndexer } from "./add";
|
|
6
|
+
import { cyan, green } from "./colors";
|
|
7
|
+
import {
|
|
8
|
+
generateApibaraConfig,
|
|
9
|
+
generatePackageJson,
|
|
10
|
+
generateTsConfig,
|
|
11
|
+
} from "./templates";
|
|
12
|
+
import type { Language } from "./types";
|
|
13
|
+
import {
|
|
14
|
+
cancelOperation,
|
|
15
|
+
emptyDir,
|
|
16
|
+
formatFile,
|
|
17
|
+
getLanguageFromAlias,
|
|
18
|
+
getPackageManager,
|
|
19
|
+
isEmpty,
|
|
20
|
+
validateLanguage,
|
|
21
|
+
} from "./utils";
|
|
22
|
+
|
|
23
|
+
type Options = {
|
|
24
|
+
argTargetDir: string;
|
|
25
|
+
argLanguage?: string;
|
|
26
|
+
argNoCreateIndexer?: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export async function initializeProject({
|
|
30
|
+
argTargetDir,
|
|
31
|
+
argLanguage,
|
|
32
|
+
argNoCreateIndexer,
|
|
33
|
+
}: Options) {
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
validateLanguage(argLanguage, true);
|
|
36
|
+
|
|
37
|
+
console.log();
|
|
38
|
+
|
|
39
|
+
const result = await prompts(
|
|
40
|
+
[
|
|
41
|
+
{
|
|
42
|
+
type: () =>
|
|
43
|
+
argTargetDir &&
|
|
44
|
+
(!fs.existsSync(argTargetDir) || isEmpty(argTargetDir))
|
|
45
|
+
? null
|
|
46
|
+
: "select",
|
|
47
|
+
name: "overwrite",
|
|
48
|
+
message: () =>
|
|
49
|
+
(argTargetDir === "."
|
|
50
|
+
? "Current directory"
|
|
51
|
+
: `Target directory "${argTargetDir}"`) +
|
|
52
|
+
" is not empty. Please choose how to proceed:",
|
|
53
|
+
initial: 0,
|
|
54
|
+
choices: [
|
|
55
|
+
{
|
|
56
|
+
title: "Cancel operation",
|
|
57
|
+
value: "no",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
title: "Remove existing files and continue",
|
|
61
|
+
value: "yes",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
title: "Ignore files and continue",
|
|
65
|
+
value: "ignore",
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
hint: "\nCurrent Working Directory: " + cwd,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: (_, { overwrite }: { overwrite?: string }) => {
|
|
72
|
+
if (overwrite === "no") {
|
|
73
|
+
cancelOperation();
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
},
|
|
77
|
+
name: "overwriteChecker",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: argLanguage ? null : "select",
|
|
81
|
+
name: "prompt_language",
|
|
82
|
+
message: "Select a language:",
|
|
83
|
+
choices: [
|
|
84
|
+
{
|
|
85
|
+
title: "Typescript",
|
|
86
|
+
value: "typescript",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
title: "Javascript",
|
|
90
|
+
value: "javascript",
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
{
|
|
96
|
+
onCancel: () => {
|
|
97
|
+
cancelOperation();
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const { overwrite, prompt_language } = result as {
|
|
103
|
+
overwrite: "no" | "yes" | "ignore";
|
|
104
|
+
prompt_language: "typescript" | "javascript";
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const root = path.join(cwd, argTargetDir);
|
|
108
|
+
if (overwrite === "yes") {
|
|
109
|
+
emptyDir(root);
|
|
110
|
+
} else if (!fs.existsSync(root)) {
|
|
111
|
+
fs.mkdirSync(root, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const lang: Language = argLanguage
|
|
115
|
+
? getLanguageFromAlias(argLanguage)
|
|
116
|
+
: prompt_language;
|
|
117
|
+
|
|
118
|
+
const isTs = lang === "typescript";
|
|
119
|
+
const configExt = isTs ? "ts" : "js";
|
|
120
|
+
|
|
121
|
+
console.log("\n");
|
|
122
|
+
consola.info(`Initializing project in ${argTargetDir}\n\n`);
|
|
123
|
+
|
|
124
|
+
// Generate package.json
|
|
125
|
+
const packageJsonPath = path.join(root, "package.json");
|
|
126
|
+
const packageJson = generatePackageJson(isTs);
|
|
127
|
+
fs.writeFileSync(
|
|
128
|
+
packageJsonPath,
|
|
129
|
+
JSON.stringify(packageJson, null, 2) + "\n",
|
|
130
|
+
);
|
|
131
|
+
await formatFile(packageJsonPath);
|
|
132
|
+
consola.success("Created ", cyan("package.json"));
|
|
133
|
+
|
|
134
|
+
// Generate tsconfig.json if TypeScript
|
|
135
|
+
if (isTs) {
|
|
136
|
+
const tsConfigPath = path.join(root, "tsconfig.json");
|
|
137
|
+
const tsConfig = generateTsConfig();
|
|
138
|
+
fs.writeFileSync(tsConfigPath, JSON.stringify(tsConfig, null, 2) + "\n");
|
|
139
|
+
await formatFile(tsConfigPath);
|
|
140
|
+
consola.success("Created ", cyan("tsconfig.json"));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const apibaraConfigPath = path.join(root, `apibara.config.${configExt}`);
|
|
144
|
+
// Generate apibara.config
|
|
145
|
+
const apibaraConfig = generateApibaraConfig(isTs);
|
|
146
|
+
fs.writeFileSync(apibaraConfigPath, apibaraConfig);
|
|
147
|
+
await formatFile(apibaraConfigPath);
|
|
148
|
+
consola.success("Created ", cyan(`apibara.config.${configExt}`));
|
|
149
|
+
|
|
150
|
+
// Create "indexers" directory if not exists
|
|
151
|
+
const indexersDir = path.join(root, "indexers");
|
|
152
|
+
if (!fs.existsSync(indexersDir)) {
|
|
153
|
+
fs.mkdirSync(indexersDir, { recursive: true });
|
|
154
|
+
consola.success(`Created ${cyan("indexers")} directory`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log("\n");
|
|
158
|
+
|
|
159
|
+
consola.ready(green("Project initialized successfully"));
|
|
160
|
+
|
|
161
|
+
console.log();
|
|
162
|
+
|
|
163
|
+
if (!argNoCreateIndexer) {
|
|
164
|
+
consola.info("Let's create an indexer\n");
|
|
165
|
+
|
|
166
|
+
await addIndexer({});
|
|
167
|
+
} else {
|
|
168
|
+
const pkgManager = getPackageManager();
|
|
169
|
+
consola.info(
|
|
170
|
+
"Run ",
|
|
171
|
+
green(`${pkgManager.name} run install`),
|
|
172
|
+
" to install all dependencies",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,468 @@
|
|
|
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, formatFile, 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 `import { defineIndexer } from "@apibara/indexer";
|
|
82
|
+
import { useLogger } from "@apibara/indexer/plugins";
|
|
83
|
+
${storage === "postgres" ? `import { drizzleStorage } from "@apibara/plugin-drizzle";` : ""}
|
|
84
|
+
${
|
|
85
|
+
chain === "ethereum"
|
|
86
|
+
? `import { EvmStream } from "@apibara/evm";`
|
|
87
|
+
: chain === "beaconchain"
|
|
88
|
+
? `import { BeaconChainStream } from "@apibara/beaconchain";`
|
|
89
|
+
: chain === "starknet"
|
|
90
|
+
? `import { StarknetStream } from "@apibara/starknet";`
|
|
91
|
+
: ""
|
|
92
|
+
}
|
|
93
|
+
${language === "typescript" ? `import type { ApibaraRuntimeConfig } from "apibara/types";` : ""}
|
|
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
|
+
await formatFile(indexerFilePath);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function updatePackageJson({
|
|
177
|
+
cwd,
|
|
178
|
+
chain,
|
|
179
|
+
storage,
|
|
180
|
+
language,
|
|
181
|
+
}: IndexerOptions) {
|
|
182
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
183
|
+
|
|
184
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
185
|
+
|
|
186
|
+
if (chain === "ethereum") {
|
|
187
|
+
packageJson.dependencies["@apibara/evm"] = packageVersions["@apibara/evm"];
|
|
188
|
+
} else if (chain === "beaconchain") {
|
|
189
|
+
packageJson.dependencies["@apibara/beaconchain"] =
|
|
190
|
+
packageVersions["@apibara/beaconchain"];
|
|
191
|
+
} else if (chain === "starknet") {
|
|
192
|
+
packageJson.dependencies["@apibara/starknet"] =
|
|
193
|
+
packageVersions["@apibara/starknet"];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (storage === "postgres") {
|
|
197
|
+
packageJson.scripts["drizzle:generate"] = "drizzle-kit generate";
|
|
198
|
+
packageJson.scripts["drizzle:migrate"] = "drizzle-kit migrate";
|
|
199
|
+
|
|
200
|
+
packageJson.dependencies["@apibara/plugin-drizzle"] =
|
|
201
|
+
packageVersions["@apibara/plugin-drizzle"];
|
|
202
|
+
|
|
203
|
+
packageJson.dependencies["drizzle-orm"] = packageVersions["drizzle-orm"];
|
|
204
|
+
|
|
205
|
+
packageJson.dependencies["@electric-sql/pglite"] =
|
|
206
|
+
packageVersions["@electric-sql/pglite"];
|
|
207
|
+
|
|
208
|
+
packageJson.dependencies["drizzle-kit"] = packageVersions["drizzle-kit"];
|
|
209
|
+
|
|
210
|
+
packageJson.dependencies["pg"] = packageVersions["pg"];
|
|
211
|
+
|
|
212
|
+
if (language === "typescript") {
|
|
213
|
+
packageJson.devDependencies["@types/pg"] = packageVersions["@types/pg"];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
218
|
+
|
|
219
|
+
await formatFile(packageJsonPath);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function updateApibaraConfigFile({
|
|
223
|
+
indexerId,
|
|
224
|
+
cwd,
|
|
225
|
+
chain,
|
|
226
|
+
storage,
|
|
227
|
+
language,
|
|
228
|
+
network,
|
|
229
|
+
dnaUrl,
|
|
230
|
+
}: IndexerOptions) {
|
|
231
|
+
const pathToConfig = path.join(
|
|
232
|
+
cwd,
|
|
233
|
+
`apibara.config.${language === "typescript" ? "ts" : "js"}`,
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const runtimeConfigString = `{
|
|
237
|
+
startingBlock: 0,
|
|
238
|
+
streamUrl: "${dnaUrl ?? getDnaUrl(chain, network)}"${
|
|
239
|
+
storage === "postgres"
|
|
240
|
+
? `,
|
|
241
|
+
postgresConnectionString: process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://${indexerId}"`
|
|
242
|
+
: ""
|
|
243
|
+
}}`;
|
|
244
|
+
|
|
245
|
+
const project = new Project();
|
|
246
|
+
const sourceFile = project.addSourceFileAtPath(pathToConfig);
|
|
247
|
+
|
|
248
|
+
// Find the defineConfig call expression
|
|
249
|
+
const defineConfigCall = sourceFile.getFirstDescendantByKind(
|
|
250
|
+
SyntaxKind.CallExpression,
|
|
251
|
+
);
|
|
252
|
+
if (!defineConfigCall) return;
|
|
253
|
+
|
|
254
|
+
const configObjectExpression =
|
|
255
|
+
defineConfigCall.getArguments()[0] as ObjectLiteralExpression;
|
|
256
|
+
|
|
257
|
+
const runtimeConfigObject =
|
|
258
|
+
configObjectExpression.getProperty("runtimeConfig");
|
|
259
|
+
|
|
260
|
+
if (!runtimeConfigObject) {
|
|
261
|
+
configObjectExpression.addPropertyAssignment({
|
|
262
|
+
name: "runtimeConfig",
|
|
263
|
+
initializer: `{
|
|
264
|
+
"${indexerId}": ${runtimeConfigString}
|
|
265
|
+
}`,
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
const runtimeConfigProp = runtimeConfigObject.asKindOrThrow(
|
|
269
|
+
SyntaxKind.PropertyAssignment,
|
|
270
|
+
);
|
|
271
|
+
const runtimeConfigObj = runtimeConfigProp
|
|
272
|
+
.getInitializerOrThrow()
|
|
273
|
+
.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
274
|
+
|
|
275
|
+
runtimeConfigObj.addPropertyAssignment({
|
|
276
|
+
name: `"${indexerId}"`,
|
|
277
|
+
initializer: runtimeConfigString,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// Save the changes
|
|
281
|
+
sourceFile.saveSync();
|
|
282
|
+
|
|
283
|
+
await formatFile(pathToConfig);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function createDrizzleStorageFiles(options: IndexerOptions) {
|
|
287
|
+
const { cwd, language, storage } = options;
|
|
288
|
+
|
|
289
|
+
if (storage !== "postgres") return;
|
|
290
|
+
|
|
291
|
+
const fileExtension = language === "typescript" ? "ts" : "js";
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
*
|
|
295
|
+
*
|
|
296
|
+
* Drizzle Config File
|
|
297
|
+
*
|
|
298
|
+
*
|
|
299
|
+
*/
|
|
300
|
+
|
|
301
|
+
const drizzleConfigFileName = `drizzle.config.${fileExtension}`;
|
|
302
|
+
|
|
303
|
+
// create drizzle.config.ts
|
|
304
|
+
const drizzleConfigPath = path.join(cwd, drizzleConfigFileName);
|
|
305
|
+
|
|
306
|
+
const { exists, overwrite } = await checkFileExists(drizzleConfigPath, {
|
|
307
|
+
askPrompt: true,
|
|
308
|
+
allowIgnore: true,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
if (!exists || overwrite) {
|
|
312
|
+
const drizzleConfigContent = `${language === "typescript" ? 'import type { Config } from "drizzle-kit";' : ""}
|
|
313
|
+
|
|
314
|
+
export default {
|
|
315
|
+
schema: "./lib/schema.ts",
|
|
316
|
+
out: "./drizzle",
|
|
317
|
+
dialect: "postgresql",
|
|
318
|
+
dbCredentials: {
|
|
319
|
+
url: process.env["POSTGRES_CONNECTION_STRING"] ?? "",
|
|
320
|
+
},
|
|
321
|
+
}${language === "typescript" ? " satisfies Config" : ""};`;
|
|
322
|
+
|
|
323
|
+
fs.writeFileSync(drizzleConfigPath, drizzleConfigContent);
|
|
324
|
+
|
|
325
|
+
await formatFile(drizzleConfigPath);
|
|
326
|
+
|
|
327
|
+
consola.success(`Created ${cyan(drizzleConfigFileName)}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
*
|
|
332
|
+
*
|
|
333
|
+
* Schema File
|
|
334
|
+
*
|
|
335
|
+
*
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
const schemaFileName = `schema.${fileExtension}`;
|
|
339
|
+
|
|
340
|
+
const schemaPath = path.join(cwd, "lib", schemaFileName);
|
|
341
|
+
|
|
342
|
+
const { exists: schemaExists, overwrite: schemaOverwrite } =
|
|
343
|
+
await checkFileExists(schemaPath, {
|
|
344
|
+
askPrompt: true,
|
|
345
|
+
allowIgnore: true,
|
|
346
|
+
fileName: `lib/${schemaFileName}`,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
if (!schemaExists || schemaOverwrite) {
|
|
350
|
+
const schemaContent = `// --- Add your pg table schemas here ----
|
|
351
|
+
|
|
352
|
+
// import { bigint, pgTable, text, uuid } from "drizzle-orm/pg-core";
|
|
353
|
+
|
|
354
|
+
// export const exampleTable = pgTable("example_table", {
|
|
355
|
+
// id: uuid("id").primaryKey().defaultRandom(),
|
|
356
|
+
// number: bigint("number", { mode: "number" }),
|
|
357
|
+
// hash: text("hash"),
|
|
358
|
+
// });
|
|
359
|
+
|
|
360
|
+
export {};
|
|
361
|
+
`;
|
|
362
|
+
|
|
363
|
+
// create directory if it doesn't exist
|
|
364
|
+
fs.mkdirSync(path.dirname(schemaPath), { recursive: true });
|
|
365
|
+
fs.writeFileSync(schemaPath, schemaContent);
|
|
366
|
+
|
|
367
|
+
await formatFile(schemaPath);
|
|
368
|
+
|
|
369
|
+
consola.success(`Created ${cyan("lib/schema.ts")}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
*
|
|
374
|
+
*
|
|
375
|
+
* DB File
|
|
376
|
+
*
|
|
377
|
+
*
|
|
378
|
+
*/
|
|
379
|
+
const dbFileName = `db.${fileExtension}`;
|
|
380
|
+
|
|
381
|
+
const dbPath = path.join(cwd, "lib", dbFileName);
|
|
382
|
+
|
|
383
|
+
const { exists: dbExists, overwrite: dbOverwrite } = await checkFileExists(
|
|
384
|
+
dbPath,
|
|
385
|
+
{
|
|
386
|
+
askPrompt: true,
|
|
387
|
+
fileName: `lib/${dbFileName}`,
|
|
388
|
+
allowIgnore: true,
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
if (!dbExists || dbOverwrite) {
|
|
393
|
+
const dbContent = `import * as schema from "./schema";
|
|
394
|
+
import { drizzle as nodePgDrizzle } from "drizzle-orm/node-postgres";
|
|
395
|
+
import { drizzle as pgLiteDrizzle } from "drizzle-orm/pglite";
|
|
396
|
+
import pg from "pg";
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
export function getDrizzlePgDatabase(connectionString${language === "typescript" ? ": string" : ""}) {
|
|
400
|
+
// Create pglite instance
|
|
401
|
+
if (connectionString.includes("memory")) {
|
|
402
|
+
return {
|
|
403
|
+
db: pgLiteDrizzle({
|
|
404
|
+
schema,
|
|
405
|
+
connection: {
|
|
406
|
+
dataDir: connectionString,
|
|
407
|
+
},
|
|
408
|
+
}),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Create node-postgres instance
|
|
413
|
+
const pool = new pg.Pool({
|
|
414
|
+
connectionString,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
return { db: nodePgDrizzle(pool, { schema }) };
|
|
418
|
+
}`;
|
|
419
|
+
|
|
420
|
+
// create directory if it doesn't exist
|
|
421
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
422
|
+
fs.writeFileSync(dbPath, dbContent);
|
|
423
|
+
|
|
424
|
+
await formatFile(dbPath);
|
|
425
|
+
|
|
426
|
+
consola.success(`Created ${cyan(`lib/${dbFileName}`)}`);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
console.log("\n");
|
|
430
|
+
|
|
431
|
+
// If schema file is created, show the example
|
|
432
|
+
if (!schemaExists || schemaOverwrite) {
|
|
433
|
+
consola.info(
|
|
434
|
+
`Make sure to export your pgTables in ${cyan(`lib/${schemaFileName}`)}`,
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
console.log();
|
|
438
|
+
|
|
439
|
+
consola.info(`${magenta("Example:")}
|
|
440
|
+
|
|
441
|
+
${yellow(`
|
|
442
|
+
┌──────────────────────────────────────────┐
|
|
443
|
+
│ lib/schema.ts │
|
|
444
|
+
└──────────────────────────────────────────┘
|
|
445
|
+
|
|
446
|
+
import { bigint, pgTable, text, uuid } from "drizzle-orm/pg-core";
|
|
447
|
+
|
|
448
|
+
export const exampleTable = pgTable("example_table", {
|
|
449
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
450
|
+
number: bigint("number", { mode: "number" }),
|
|
451
|
+
hash: text("hash"),
|
|
452
|
+
});`)}`);
|
|
453
|
+
|
|
454
|
+
console.log("\n");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
consola.info(
|
|
458
|
+
`Run ${green(`${options.packageManager} run drizzle:generate`)} & ${green(`${options.packageManager} run drizzle:migrate`)} to generate and apply migrations.`,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export async function createStorageRelatedFiles(options: IndexerOptions) {
|
|
463
|
+
const { storage } = options;
|
|
464
|
+
|
|
465
|
+
if (storage === "postgres") {
|
|
466
|
+
await createDrizzleStorageFiles(options);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
@@ -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
|
+
};
|