From b8d21e63e3265a65eec85051d49728d432bd68b9 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Thu, 9 Jan 2025 18:22:35 -0500 Subject: [PATCH 1/2] inject clients into api file --- packages/core/src/_test/setup.ts | 2 +- packages/core/src/bin/commands/dev.ts | 18 +++-- packages/core/src/bin/commands/list.ts | 4 +- packages/core/src/bin/commands/serve.ts | 36 ++++++++-- packages/core/src/bin/commands/start.ts | 19 +++-- packages/core/src/bin/utils/run.test.ts | 4 +- packages/core/src/build/index.ts | 78 ++++++++++----------- packages/core/src/build/plugin.ts | 14 +++- packages/core/src/client/index.ts | 2 +- packages/core/src/database/index.test.ts | 50 ++++++------- packages/core/src/database/index.ts | 89 ++++++++++++------------ packages/core/src/drizzle/onchain.ts | 6 +- packages/core/src/internal/types.ts | 5 +- packages/core/src/types.d.ts | 6 ++ 14 files changed, 199 insertions(+), 134 deletions(-) diff --git a/packages/core/src/_test/setup.ts b/packages/core/src/_test/setup.ts index 38a61472b..02c69713a 100644 --- a/packages/core/src/_test/setup.ts +++ b/packages/core/src/_test/setup.ts @@ -200,9 +200,9 @@ export async function setupDatabaseServices( const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: config.schema, diff --git a/packages/core/src/bin/commands/dev.ts b/packages/core/src/bin/commands/dev.ts index 7da865095..5872b5b4a 100644 --- a/packages/core/src/bin/commands/dev.ts +++ b/packages/core/src/bin/commands/dev.ts @@ -10,6 +10,7 @@ import { createLogger } from "@/internal/logger.js"; import { MetricsService } from "@/internal/metrics.js"; import { buildOptions } from "@/internal/options.js"; import { buildPayload, createTelemetry } from "@/internal/telemetry.js"; +import type { IndexingBuild } from "@/internal/types.js"; import { createUi } from "@/ui/index.js"; import { type Result, mergeResults } from "@/utils/result.js"; import { createQueue } from "@ponder/common"; @@ -118,7 +119,7 @@ export async function dev({ cliOptions }: { cliOptions: CliOptions }) { return; } - const schemaResult = await build.executeSchema(); + const schemaResult = await build.executeSchema({ namespace }); if (schemaResult.status === "error") { buildQueue.add({ status: "error", @@ -168,12 +169,19 @@ export async function dev({ cliOptions }: { cliOptions: CliOptions }) { }); return; } + indexingBuild = indexingBuildResult.result; - database = await createDatabase({ common, preBuild, schemaBuild }); + database = await createDatabase({ + common, + namespace, + preBuild, + schemaBuild, + }); await database.migrate(indexingBuildResult.result); listenConnection = await database.getListenConnection(); const apiResult = await build.executeApi({ + indexingBuild, database, listenConnection, }); @@ -240,6 +248,7 @@ export async function dev({ cliOptions }: { cliOptions: CliOptions }) { metrics.resetApiMetrics(); const apiResult = await build.executeApi({ + indexingBuild: indexingBuild!, database: database!, listenConnection: listenConnection!, }); @@ -275,11 +284,12 @@ export async function dev({ cliOptions }: { cliOptions: CliOptions }) { }, }); + let indexingBuild: IndexingBuild | undefined; let database: Database | undefined; let listenConnection: ListenConnection | undefined; - build.initNamespace({ isSchemaRequired: false }); - build.initNamespace({ isSchemaRequired: false }); + const namespace = + cliOptions.schema ?? process.env.DATABASE_SCHEMA ?? "public"; build.startDev({ onReload: (kind) => { diff --git a/packages/core/src/bin/commands/list.ts b/packages/core/src/bin/commands/list.ts index 5cab775b2..ff12bba43 100644 --- a/packages/core/src/bin/commands/list.ts +++ b/packages/core/src/bin/commands/list.ts @@ -44,8 +44,6 @@ export async function list({ cliOptions }: { cliOptions: CliOptions }) { const shutdown = setupShutdown({ common, cleanup }); - build.initNamespace({ isSchemaRequired: false }); - const configResult = await build.executeConfig(); if (configResult.status === "error") { await shutdown({ reason: "Failed intial build", code: 1 }); @@ -61,6 +59,8 @@ export async function list({ cliOptions }: { cliOptions: CliOptions }) { const database = await createDatabase({ common, + // Note: `namespace` is not used in this command + namespace: "public", preBuild: buildResult.result, schemaBuild: emptySchemaBuild, }); diff --git a/packages/core/src/bin/commands/serve.ts b/packages/core/src/bin/commands/serve.ts index 072ea47b0..c18f352f1 100644 --- a/packages/core/src/bin/commands/serve.ts +++ b/packages/core/src/bin/commands/serve.ts @@ -50,7 +50,7 @@ export async function serve({ cliOptions }: { cliOptions: CliOptions }) { }; const shutdown = setupShutdown({ common, cleanup }); - const namespaceResult = build.initNamespace({ isSchemaRequired: true }); + const namespaceResult = build.namespaceCompile(); if (namespaceResult.status === "error") { await shutdown({ reason: "Failed to initialize namespace", code: 1 }); @@ -63,7 +63,9 @@ export async function serve({ cliOptions }: { cliOptions: CliOptions }) { return cleanup; } - const schemaResult = await build.executeSchema(); + const schemaResult = await build.executeSchema({ + namespace: namespaceResult.result, + }); if (schemaResult.status === "error") { await shutdown({ reason: "Failed intial build", code: 1 }); return cleanup; @@ -89,11 +91,37 @@ export async function serve({ cliOptions }: { cliOptions: CliOptions }) { return cleanup; } - const database = await createDatabase({ common, preBuild, schemaBuild }); + const indexingResult = await build.executeIndexingFunctions(); + if (indexingResult.status === "error") { + await shutdown({ reason: "Failed intial build", code: 1 }); + return cleanup; + } + + const indexingBuildResult = await build.compileIndexing({ + configResult: configResult.result, + schemaResult: schemaResult.result, + indexingResult: indexingResult.result, + }); + + if (indexingBuildResult.status === "error") { + await shutdown({ reason: "Failed intial build", code: 1 }); + return cleanup; + } + + const database = await createDatabase({ + common, + namespace: namespaceResult.result, + preBuild, + schemaBuild, + }); // Note: this assumes that the _ponder_status table exists const listenConnection = await database.getListenConnection(); - const apiResult = await build.executeApi({ database, listenConnection }); + const apiResult = await build.executeApi({ + indexingBuild: indexingBuildResult.result, + database, + listenConnection, + }); if (apiResult.status === "error") { await shutdown({ reason: "Failed intial build", code: 1 }); return cleanup; diff --git a/packages/core/src/bin/commands/start.ts b/packages/core/src/bin/commands/start.ts index 85878bf8d..8cd42950f 100644 --- a/packages/core/src/bin/commands/start.ts +++ b/packages/core/src/bin/commands/start.ts @@ -67,7 +67,7 @@ export async function start({ cliOptions }: { cliOptions: CliOptions }) { const shutdown = setupShutdown({ common, cleanup }); - const namespaceResult = build.initNamespace({ isSchemaRequired: true }); + const namespaceResult = build.namespaceCompile(); if (namespaceResult.status === "error") { await shutdown({ reason: "Failed to initialize namespace", code: 1 }); return cleanup; @@ -79,7 +79,9 @@ export async function start({ cliOptions }: { cliOptions: CliOptions }) { return cleanup; } - const schemaResult = await build.executeSchema(); + const schemaResult = await build.executeSchema({ + namespace: namespaceResult.result, + }); if (schemaResult.status === "error") { await shutdown({ reason: "Failed intial build", code: 1 }); return cleanup; @@ -114,11 +116,20 @@ export async function start({ cliOptions }: { cliOptions: CliOptions }) { return cleanup; } - database = await createDatabase({ common, preBuild, schemaBuild }); + database = await createDatabase({ + common, + namespace: namespaceResult.result, + preBuild, + schemaBuild, + }); await database.migrate(indexingBuildResult.result); const listenConnection = await database.getListenConnection(); - const apiResult = await build.executeApi({ database, listenConnection }); + const apiResult = await build.executeApi({ + indexingBuild: indexingBuildResult.result, + database, + listenConnection, + }); if (apiResult.status === "error") { await shutdown({ reason: "Failed intial build", code: 1 }); return cleanup; diff --git a/packages/core/src/bin/utils/run.test.ts b/packages/core/src/bin/utils/run.test.ts index 27e866b35..a11a26f0c 100644 --- a/packages/core/src/bin/utils/run.test.ts +++ b/packages/core/src/bin/utils/run.test.ts @@ -64,9 +64,9 @@ test("run() setup", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema, @@ -129,9 +129,9 @@ test("run() setup error", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema, diff --git a/packages/core/src/build/index.ts b/packages/core/src/build/index.ts index e8e94940f..8ba8ca2e5 100644 --- a/packages/core/src/build/index.ts +++ b/packages/core/src/build/index.ts @@ -9,12 +9,12 @@ import { BuildError } from "@/internal/errors.js"; import type { ApiBuild, IndexingBuild, + NamespaceBuild, PreBuild, RawIndexingFunctions, Schema, SchemaBuild, } from "@/internal/types.js"; -import type { Drizzle } from "@/types/db.js"; import { getNextAvailablePort } from "@/utils/port.js"; import type { Result } from "@/utils/result.js"; import { serialize } from "@/utils/serialize.js"; @@ -33,8 +33,9 @@ import { safeBuildSchema } from "./schema.js"; import { parseViteNodeError } from "./stacktrace.js"; declare global { - var PONDER_DATABASE_SCHEMA: string | undefined; - var PONDER_READONLY_DB: Drizzle; + var PONDER_NAMESPACE_BUILD: NamespaceBuild; + var PONDER_INDEXING_BUILD: IndexingBuild; + var PONDER_DATABASE: Database; var PONDER_LISTEN_CONNECTION: ListenConnection; } @@ -49,14 +50,17 @@ type IndexingResult = Result<{ type ApiResult = Result<{ app: Hono }>; export type Build = { - initNamespace: (params: { isSchemaRequired: boolean }) => Result; executeConfig: () => Promise; - executeSchema: () => Promise; + executeSchema: (params: { + namespace: NamespaceBuild; + }) => Promise; executeIndexingFunctions: () => Promise; executeApi: (params: { + indexingBuild: IndexingBuild; database: Database; listenConnection: ListenConnection; }) => Promise; + namespaceCompile: () => Result; preCompile: (params: { config: Config }) => Result; compileSchema: (params: { schema: Schema }) => Result; compileIndexing: (params: { @@ -80,8 +84,6 @@ export const createBuild = async ({ common: Common; cliOptions: CliOptions; }): Promise => { - let namespace: string | undefined; - const escapeRegex = /[.*+?^${}()|[\]\\]/g; const escapedIndexingDir = common.options.indexingDir @@ -159,34 +161,6 @@ export const createBuild = async ({ }; const build = { - initNamespace: ({ isSchemaRequired }) => { - if (isSchemaRequired) { - if ( - cliOptions.schema === undefined && - process.env.DATABASE_SCHEMA === undefined - ) { - const error = new BuildError( - "Database schema required. Specify with 'DATABASE_SCHEMA' env var or '--schema' CLI flag. Read more: https://ponder.sh/docs/getting-started/database#database-schema", - ); - error.stack = undefined; - common.logger.error({ - service: "build", - msg: "Failed build", - error, - }); - return { status: "error", error } as const; - } - - namespace = cliOptions.schema ?? process.env.DATABASE_SCHEMA; - } else { - namespace = - cliOptions.schema ?? process.env.DATABASE_SCHEMA ?? "public"; - } - - global.PONDER_DATABASE_SCHEMA = namespace; - - return { status: "success" } as const; - }, async executeConfig(): Promise { const executeResult = await executeFile({ file: common.options.configFile, @@ -214,7 +188,8 @@ export const createBuild = async ({ result: { config, contentHash }, } as const; }, - async executeSchema(): Promise { + async executeSchema({ namespace }): Promise { + global.PONDER_NAMESPACE_BUILD = namespace; const executeResult = await executeFile({ file: common.options.schemaFile, }); @@ -296,8 +271,13 @@ export const createBuild = async ({ }, }; }, - async executeApi({ database, listenConnection }): Promise { - global.PONDER_READONLY_DB = database.qb.drizzleReadonly; + async executeApi({ + indexingBuild, + database, + listenConnection, + }): Promise { + global.PONDER_INDEXING_BUILD = indexingBuild; + global.PONDER_DATABASE = database; global.PONDER_LISTEN_CONNECTION = listenConnection; if (!fs.existsSync(common.options.apiFile)) { @@ -352,6 +332,27 @@ export const createBuild = async ({ result: { app }, }; }, + namespaceCompile() { + if ( + cliOptions.schema === undefined && + process.env.DATABASE_SCHEMA === undefined + ) { + const error = new BuildError( + "Database schema required. Specify with 'DATABASE_SCHEMA' env var or '--schema' CLI flag. Read more: https://ponder.sh/docs/getting-started/database#database-schema", + ); + error.stack = undefined; + common.logger.error({ + service: "build", + msg: "Failed build", + error, + }); + return { status: "error", error } as const; + } + return { + status: "success", + result: cliOptions.schema ?? process.env.DATABASE_SCHEMA!, + } as const; + }, preCompile({ config }): Result { const preBuild = safeBuildPre({ config, @@ -375,7 +376,6 @@ export const createBuild = async ({ status: "success", result: { databaseConfig: preBuild.databaseConfig, - namespace: namespace!, }, } as const; }, diff --git a/packages/core/src/build/plugin.ts b/packages/core/src/build/plugin.ts index 0bc8ac019..8e7727bed 100644 --- a/packages/core/src/build/plugin.ts +++ b/packages/core/src/build/plugin.ts @@ -16,7 +16,19 @@ export * from "${schemaPath}"; export default schema; `; -const apiModule = () => `export const db = global.PONDER_READONLY_DB; +const apiModule = () => `import { createPublicClient } from "viem"; + +const clients = {}; + +for (const network of global.PONDER_INDEXING_BUILD.networks) { + clients[network.chainId] = createPublicClient({ + chain: network.chain, + transport: network.transport + }) +} + +export const db = global.PONDER_DATABASE.qb.drizzleReadonly; +export { clients }; `; export const vitePluginPonder = (options: Common["options"]): Plugin => { diff --git a/packages/core/src/client/index.ts b/packages/core/src/client/index.ts index b06698643..b9da0c494 100644 --- a/packages/core/src/client/index.ts +++ b/packages/core/src/client/index.ts @@ -39,7 +39,7 @@ export const client = ({ db }: { db: ReadonlyDrizzle }) => { let queryPromise: Promise; - const channel = `${global.PONDER_DATABASE_SCHEMA}_status_channel`; + const channel = `${global.PONDER_NAMESPACE_BUILD}_status_channel`; if (listenConnection.dialect === "pglite") { queryPromise = listenConnection.connection.query(`LISTEN ${channel}`); diff --git a/packages/core/src/database/index.test.ts b/packages/core/src/database/index.test.ts index ce3f57e8b..325682239 100644 --- a/packages/core/src/database/index.test.ts +++ b/packages/core/src/database/index.test.ts @@ -38,9 +38,9 @@ test("createDatabase() readonly", async (context) => { if (context.databaseConfig.kind === "pglite_test") return; const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -74,9 +74,9 @@ test("createDatabase() search path", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "Ponder", preBuild: { databaseConfig: context.databaseConfig, - namespace: "Ponder", }, schemaBuild: { schema: { account: schemaAccount }, @@ -99,9 +99,9 @@ test("createDatabase() search path", async (context) => { test("migrate() succeeds with empty schema", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -149,9 +149,9 @@ test("migrate() with empty schema creates tables and enums", async (context) => const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account, kyle, mood, user }, @@ -178,9 +178,9 @@ test("migrate() with empty schema creates tables and enums", async (context) => test("migrate() throws with schema used", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -192,9 +192,9 @@ test("migrate() throws with schema used", async (context) => { const databaseTwo = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -221,9 +221,9 @@ test("migrate() throws with schema used after waiting for lock", async (context) const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -235,9 +235,9 @@ test("migrate() throws with schema used after waiting for lock", async (context) const databaseTwo = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -258,9 +258,9 @@ test("migrate() throws with schema used after waiting for lock", async (context) test("migrate() succeeds with crash recovery", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -279,9 +279,9 @@ test("migrate() succeeds with crash recovery", async (context) => { const databaseTwo = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -312,9 +312,9 @@ test("migrate() succeeds with crash recovery after waiting for lock", async (con const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -326,9 +326,9 @@ test("migrate() succeeds with crash recovery after waiting for lock", async (con const databaseTwo = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -346,9 +346,9 @@ test("migrate() succeeds with crash recovery after waiting for lock", async (con test("recoverCheckpoint() with crash recovery reverts rows", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -393,9 +393,9 @@ test("recoverCheckpoint() with crash recovery reverts rows", async (context) => const databaseTwo = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -439,9 +439,9 @@ test("recoverCheckpoint() with crash recovery drops indexes and triggers", async const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -462,9 +462,9 @@ test("recoverCheckpoint() with crash recovery drops indexes and triggers", async const databaseTwo = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -488,9 +488,9 @@ test("heartbeat updates the heartbeat_at value", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -527,9 +527,9 @@ test("heartbeat updates the heartbeat_at value", async (context) => { test("finalize()", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -599,9 +599,9 @@ test("finalize()", async (context) => { test("unlock()", async (context) => { let database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -615,9 +615,9 @@ test("unlock()", async (context) => { database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -651,9 +651,9 @@ test("createIndexes()", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -674,9 +674,9 @@ test("createIndexes()", async (context) => { test("createTriggers()", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -719,9 +719,9 @@ test("createTriggers()", async (context) => { test("createTriggers() duplicate", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -740,9 +740,9 @@ test("createTriggers() duplicate", async (context) => { test("complete()", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, @@ -788,9 +788,9 @@ test("complete()", async (context) => { test("revert()", async (context) => { const database = await createDatabase({ common: context.common, + namespace: "public", preBuild: { databaseConfig: context.databaseConfig, - namespace: "public", }, schemaBuild: { schema: { account }, diff --git a/packages/core/src/database/index.ts b/packages/core/src/database/index.ts index 82d6c8e9b..9ae2ef79d 100644 --- a/packages/core/src/database/index.ts +++ b/packages/core/src/database/index.ts @@ -4,6 +4,7 @@ import type { Common } from "@/internal/common.js"; import { IgnorableError, NonRetryableError } from "@/internal/errors.js"; import type { IndexingBuild, + NamespaceBuild, PreBuild, Schema, SchemaBuild, @@ -127,10 +128,12 @@ type QueryBuilder = { export const createDatabase = async ({ common, + namespace, preBuild, schemaBuild, }: { common: Common; + namespace: NamespaceBuild; preBuild: PreBuild; schemaBuild: Omit; }): Promise => { @@ -148,7 +151,7 @@ export const createDatabase = async ({ common.logger.info({ service: "database", - msg: `Using database schema '${preBuild.namespace}'`, + msg: `Using database schema '${namespace}'`, }); if (dialect === "pglite" || dialect === "pglite_test") { @@ -161,10 +164,8 @@ export const createDatabase = async ({ const kyselyDialect = new KyselyPGlite(driver.instance).dialect; - await driver.instance.query( - `CREATE SCHEMA IF NOT EXISTS "${preBuild.namespace}"`, - ); - await driver.instance.query(`SET search_path TO "${preBuild.namespace}"`); + await driver.instance.query(`CREATE SCHEMA IF NOT EXISTS "${namespace}"`); + await driver.instance.query(`SET search_path TO "${namespace}"`); qb = { internal: new Kysely({ @@ -176,7 +177,7 @@ export const createDatabase = async ({ }); } }, - plugins: [new WithSchemaPlugin(preBuild.namespace)], + plugins: [new WithSchemaPlugin(namespace)], }), user: new Kysely({ dialect: kyselyDialect, @@ -187,7 +188,7 @@ export const createDatabase = async ({ }); } }, - plugins: [new WithSchemaPlugin(preBuild.namespace)], + plugins: [new WithSchemaPlugin(namespace)], }), readonly: new Kysely({ dialect: kyselyDialect, @@ -198,7 +199,7 @@ export const createDatabase = async ({ }); } }, - plugins: [new WithSchemaPlugin(preBuild.namespace)], + plugins: [new WithSchemaPlugin(namespace)], }), sync: new Kysely({ dialect: kyselyDialect, @@ -233,7 +234,7 @@ export const createDatabase = async ({ const internal = createPool( { ...preBuild.databaseConfig.poolConfig, - application_name: `${preBuild.namespace}_internal`, + application_name: `${namespace}_internal`, max: internalMax, statement_timeout: 10 * 60 * 1000, // 10 minutes to accommodate slow sync store migrations. }, @@ -246,10 +247,10 @@ export const createDatabase = async ({ const role = connection.database === undefined - ? `ponder_readonly_${preBuild.namespace}` - : `ponder_readonly_${connection.database}_${preBuild.namespace}`; + ? `ponder_readonly_${namespace}` + : `ponder_readonly_${connection.database}_${namespace}`; - await internal.query(`CREATE SCHEMA IF NOT EXISTS "${preBuild.namespace}"`); + await internal.query(`CREATE SCHEMA IF NOT EXISTS "${namespace}"`); const hasRole = await internal .query("SELECT FROM pg_roles WHERE rolname = $1", [role]) .then(({ rows }) => rows[0]); @@ -261,17 +262,15 @@ export const createDatabase = async ({ await internal.query( `GRANT CONNECT ON DATABASE "${connection.database}" TO "${role}"`, ); + await internal.query(`GRANT USAGE ON SCHEMA "${namespace}" TO "${role}"`); await internal.query( - `GRANT USAGE ON SCHEMA "${preBuild.namespace}" TO "${role}"`, - ); - await internal.query( - `GRANT SELECT ON ALL TABLES IN SCHEMA "${preBuild.namespace}" TO "${role}"`, + `GRANT SELECT ON ALL TABLES IN SCHEMA "${namespace}" TO "${role}"`, ); await internal.query( - `ALTER DEFAULT PRIVILEGES IN SCHEMA "${preBuild.namespace}" GRANT SELECT ON TABLES TO "${role}"`, + `ALTER DEFAULT PRIVILEGES IN SCHEMA "${namespace}" GRANT SELECT ON TABLES TO "${role}"`, ); await internal.query( - `ALTER ROLE "${role}" SET search_path TO "${preBuild.namespace}"`, + `ALTER ROLE "${role}" SET search_path TO "${namespace}"`, ); await internal.query(`ALTER ROLE "${role}" SET statement_timeout TO '1s'`); await internal.query(`ALTER ROLE "${role}" SET work_mem TO '1MB'`); @@ -281,7 +280,7 @@ export const createDatabase = async ({ user: createPool( { ...preBuild.databaseConfig.poolConfig, - application_name: `${preBuild.namespace}_user`, + application_name: `${namespace}_user`, max: userMax, }, common.logger, @@ -290,7 +289,7 @@ export const createDatabase = async ({ { ...preBuild.databaseConfig.poolConfig, connectionString: undefined, - application_name: `${preBuild.namespace}_readonly`, + application_name: `${namespace}_readonly`, max: readonlyMax, user: role, password: "pw", @@ -320,7 +319,7 @@ export const createDatabase = async ({ }); } }, - plugins: [new WithSchemaPlugin(preBuild.namespace)], + plugins: [new WithSchemaPlugin(namespace)], }), user: new Kysely({ dialect: new PostgresDialect({ pool: driver.user }), @@ -331,7 +330,7 @@ export const createDatabase = async ({ }); } }, - plugins: [new WithSchemaPlugin(preBuild.namespace)], + plugins: [new WithSchemaPlugin(namespace)], }), readonly: new Kysely({ dialect: new PostgresDialect({ pool: driver.readonly }), @@ -342,7 +341,7 @@ export const createDatabase = async ({ }); } }, - plugins: [new WithSchemaPlugin(preBuild.namespace)], + plugins: [new WithSchemaPlugin(namespace)], }), sync: new Kysely({ dialect: new PostgresDialect({ pool: driver.sync }), @@ -643,7 +642,7 @@ export const createDatabase = async ({ // @ts-ignore .select("schema") // @ts-ignore - .where("namespace", "=", preBuild.namespace) + .where("namespace", "=", namespace) .executeTakeFirst() .then((schema: any | undefined) => schema === undefined @@ -664,7 +663,7 @@ export const createDatabase = async ({ // @ts-ignore .deleteFrom("namespace_lock") // @ts-ignore - .where("namespace", "=", preBuild.namespace) + .where("namespace", "=", namespace) .execute(); if (namespaceCount!.count === 1) { @@ -697,7 +696,7 @@ export const createDatabase = async ({ // @ts-ignore .where("table_name", "=", "_ponder_meta") // @ts-ignore - .where("table_schema", "=", preBuild.namespace) + .where("table_schema", "=", namespace) .executeTakeFirst() .then((table) => table !== undefined); @@ -721,7 +720,7 @@ export const createDatabase = async ({ ) ) { throw new NonRetryableError( - `Migration failed: Schema '${preBuild.namespace}' has an active app`, + `Migration failed: Schema '${namespace}' has an active app`, ); } @@ -806,7 +805,7 @@ export const createDatabase = async ({ const error = _error as Error; if (!error.message.includes("already exists")) throw error; const e = new NonRetryableError( - `Unable to create table '${preBuild.namespace}'.'${schemaBuild.statements.tables.json[i]!.tableName}' because a table with that name already exists.`, + `Unable to create table '${namespace}'.'${schemaBuild.statements.tables.json[i]!.tableName}' because a table with that name already exists.`, ); e.stack = undefined; throw e; @@ -827,7 +826,7 @@ export const createDatabase = async ({ const error = _error as Error; if (!error.message.includes("already exists")) throw error; const e = new NonRetryableError( - `Unable to create enum '${preBuild.namespace}'.'${schemaBuild.statements.enums.json[i]!.name}' because an enum with that name already exists.`, + `Unable to create enum '${namespace}'.'${schemaBuild.statements.enums.json[i]!.name}' because an enum with that name already exists.`, ); e.stack = undefined; throw e; @@ -917,7 +916,7 @@ export const createDatabase = async ({ previousApp!.build_id !== buildId ) { const error = new NonRetryableError( - `Schema '${preBuild.namespace}' was previously used by a different Ponder app. Drop the schema first, or use a different schema. Read more: https://ponder.sh/docs/getting-started/database#database-schema`, + `Schema '${namespace}' was previously used by a different Ponder app. Drop the schema first, or use a different schema. Read more: https://ponder.sh/docs/getting-started/database#database-schema`, ); error.stack = undefined; throw error; @@ -944,7 +943,7 @@ export const createDatabase = async ({ common.logger.info({ service: "database", - msg: `Detected crash recovery for build '${buildId}' in schema '${preBuild.namespace}' last active ${formatEta(Date.now() - previousApp!.heartbeat_at)} ago`, + msg: `Detected crash recovery for build '${buildId}' in schema '${namespace}' last active ${formatEta(Date.now() - previousApp!.heartbeat_at)} ago`, }); } @@ -962,11 +961,11 @@ export const createDatabase = async ({ const duration = result.expiry - Date.now(); common.logger.warn({ service: "database", - msg: `Schema '${preBuild.namespace}' is locked by a different Ponder app`, + msg: `Schema '${namespace}' is locked by a different Ponder app`, }); common.logger.warn({ service: "database", - msg: `Waiting ${formatEta(duration)} for lock on schema '${preBuild.namespace} to expire...`, + msg: `Waiting ${formatEta(duration)} for lock on schema '${namespace} to expire...`, }); await wait(duration); @@ -974,7 +973,7 @@ export const createDatabase = async ({ result = await attempt(); if (result.status === "locked") { const error = new NonRetryableError( - `Failed to acquire lock on schema '${preBuild.namespace}'. A different Ponder app is actively using this schema.`, + `Failed to acquire lock on schema '${namespace}'. A different Ponder app is actively using this schema.`, ); error.stack = undefined; throw error; @@ -1011,8 +1010,8 @@ export const createDatabase = async ({ }, async getListenConnection() { const trigger = "status_trigger"; - const notification = `${preBuild.namespace}_status_notify()`; - const channel = `${preBuild.namespace}_status_channel`; + const notification = `${namespace}_status_notify()`; + const channel = `${namespace}_status_channel`; await this.wrap({ method: "getListenConnection" }, async () => { await sql @@ -1032,7 +1031,7 @@ export const createDatabase = async ({ .raw(` CREATE OR REPLACE TRIGGER ${trigger} AFTER INSERT OR UPDATE OR DELETE - ON "${preBuild.namespace}"._ponder_status + ON "${namespace}"._ponder_status FOR EACH STATEMENT EXECUTE PROCEDURE ${notification};`) .execute(qb.internal); @@ -1068,7 +1067,7 @@ export const createDatabase = async ({ for (const tableName of getTableNames(schemaBuild.schema)) { await sql .raw( - `TRUNCATE TABLE "${preBuild.namespace}"."${tableName.sql}", "${preBuild.namespace}"."${tableName.reorg}" CASCADE`, + `TRUNCATE TABLE "${namespace}"."${tableName.sql}", "${namespace}"."${tableName.reorg}" CASCADE`, ) .execute(tx); } @@ -1090,7 +1089,7 @@ export const createDatabase = async ({ for (const tableName of getTableNames(schemaBuild.schema)) { await sql .raw( - `DROP TRIGGER IF EXISTS "${tableName.trigger}" ON "${preBuild.namespace}"."${tableName.sql}"`, + `DROP TRIGGER IF EXISTS "${tableName.trigger}" ON "${namespace}"."${tableName.sql}"`, ) .execute(tx); } @@ -1104,7 +1103,7 @@ export const createDatabase = async ({ .execute(); common.logger.info({ service: "database", - msg: `Dropped index '${indexStatement.data.name}' in schema '${preBuild.namespace}'`, + msg: `Dropped index '${indexStatement.data.name}' in schema '${namespace}'`, }); } @@ -1141,13 +1140,13 @@ CREATE OR REPLACE FUNCTION ${tableName.triggerFn} RETURNS TRIGGER AS $$ BEGIN IF TG_OP = 'INSERT' THEN - INSERT INTO "${preBuild.namespace}"."${tableName.reorg}" (${columnNames.join(",")}, operation, checkpoint) + INSERT INTO "${namespace}"."${tableName.reorg}" (${columnNames.join(",")}, operation, checkpoint) VALUES (${columnNames.map((name) => `NEW.${name}`).join(",")}, 0, '${encodeCheckpoint(maxCheckpoint)}'); ELSIF TG_OP = 'UPDATE' THEN - INSERT INTO "${preBuild.namespace}"."${tableName.reorg}" (${columnNames.join(",")}, operation, checkpoint) + INSERT INTO "${namespace}"."${tableName.reorg}" (${columnNames.join(",")}, operation, checkpoint) VALUES (${columnNames.map((name) => `OLD.${name}`).join(",")}, 1, '${encodeCheckpoint(maxCheckpoint)}'); ELSIF TG_OP = 'DELETE' THEN - INSERT INTO "${preBuild.namespace}"."${tableName.reorg}" (${columnNames.join(",")}, operation, checkpoint) + INSERT INTO "${namespace}"."${tableName.reorg}" (${columnNames.join(",")}, operation, checkpoint) VALUES (${columnNames.map((name) => `OLD.${name}`).join(",")}, 2, '${encodeCheckpoint(maxCheckpoint)}'); END IF; RETURN NULL; @@ -1159,7 +1158,7 @@ $$ LANGUAGE plpgsql await sql .raw(` CREATE OR REPLACE TRIGGER "${tableName.trigger}" - AFTER INSERT OR UPDATE OR DELETE ON "${preBuild.namespace}"."${tableName.sql}" + AFTER INSERT OR UPDATE OR DELETE ON "${namespace}"."${tableName.sql}" FOR EACH ROW EXECUTE FUNCTION ${tableName.triggerFn}; `) .execute(qb.internal); @@ -1171,7 +1170,7 @@ $$ LANGUAGE plpgsql for (const tableName of getTableNames(schemaBuild.schema)) { await sql .raw( - `DROP TRIGGER IF EXISTS "${tableName.trigger}" ON "${preBuild.namespace}"."${tableName.sql}"`, + `DROP TRIGGER IF EXISTS "${tableName.trigger}" ON "${namespace}"."${tableName.sql}"`, ) .execute(qb.internal); } diff --git a/packages/core/src/drizzle/onchain.ts b/packages/core/src/drizzle/onchain.ts index 3c67d34db..b24824aed 100644 --- a/packages/core/src/drizzle/onchain.ts +++ b/packages/core/src/drizzle/onchain.ts @@ -149,8 +149,7 @@ export const onchainTable = < extra: extra; dialect: "pg"; }> => { - const schema = - typeof global === "undefined" ? undefined : global.PONDER_DATABASE_SCHEMA; + const schema = global?.PONDER_NAMESPACE_BUILD; const table = pgTableWithSchema(name, columns, extraConfig as any, schema); // @ts-ignore @@ -182,8 +181,7 @@ export const onchainEnum = >( enumName: string, values: T | Writable, ): OnchainEnum> & { [onchain]: true } => { - const schema = - typeof global === "undefined" ? undefined : global.PONDER_DATABASE_SCHEMA; + const schema = global?.PONDER_NAMESPACE_BUILD; const e = pgEnumWithSchema(enumName, values, schema); // @ts-ignore diff --git a/packages/core/src/internal/types.ts b/packages/core/src/internal/types.ts index f7959dcb0..430916111 100644 --- a/packages/core/src/internal/types.ts +++ b/packages/core/src/internal/types.ts @@ -255,12 +255,13 @@ export type Schema = { [name: string]: unknown }; // Build artifacts +/** Database schema name. */ +export type NamespaceBuild = string; + /** Consolidated CLI, env vars, and config. */ export type PreBuild = { /** Database type and configuration */ databaseConfig: DatabaseConfig; - /** Database schema */ - namespace: string; }; export type SchemaBuild = { diff --git a/packages/core/src/types.d.ts b/packages/core/src/types.d.ts index 9b0198d81..4b530ff09 100644 --- a/packages/core/src/types.d.ts +++ b/packages/core/src/types.d.ts @@ -26,7 +26,13 @@ declare module "ponder:schema" { declare module "ponder:api" { import type { ReadonlyDrizzle } from "ponder"; + import type { PublicClient } from "viem"; + type schema = typeof import("ponder:internal").schema; + type config = typeof import("ponder:internal").config; export const db: ReadonlyDrizzle; + export const clients: { + [chainId in config["default"]["networks"][keyof config["default"]["networks"]]["chainId"]]: PublicClient; + }; } From 260f7fa03d7eda871c64c877a127004f08de5050 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 10 Jan 2025 12:32:28 -0500 Subject: [PATCH 2/2] fix plugin --- packages/core/src/build/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/build/plugin.ts b/packages/core/src/build/plugin.ts index 8e7727bed..f7fa6e257 100644 --- a/packages/core/src/build/plugin.ts +++ b/packages/core/src/build/plugin.ts @@ -23,7 +23,7 @@ const clients = {}; for (const network of global.PONDER_INDEXING_BUILD.networks) { clients[network.chainId] = createPublicClient({ chain: network.chain, - transport: network.transport + transport: () => network.transport }) }