From d1a9648d8e9b795db10386355acd694142560cca Mon Sep 17 00:00:00 2001 From: Omar Date: Tue, 7 Nov 2023 14:16:55 -0800 Subject: [PATCH] dev server with live-reload (#391) * dev server with live-reload * comments + cleanup * remove accidentally added tsconfig option * dont watch files not in specified glob * revert lockfile to main * integrate example into build service * patch vite-node to remove log * wip vite server working * mostly working * wip using ssr * wip with ssr custom plugin * revert to use vite-node, largely working again * wip * simpler file watcher approach * improve error handling * fix error handling * fix merge conflict * fix eslint error * fix merge conf * update config loading pattern --------- Co-authored-by: typedarray <90073088+0xOlias@users.noreply.github.com> --- examples/art-gobblers/.eslintrc.json | 1 + examples/ethfs/.eslintrc.json | 1 + examples/factory-llama/.eslintrc.json | 1 + examples/friendtech/.eslintrc.json | 4 + examples/friendtech/package.json | 6 +- examples/token-erc20/.eslintrc.json | 1 + examples/token-erc721/.eslintrc.json | 1 + examples/token-reth/.eslintrc.json | 1 + examples/with-docker/.eslintrc.json | 1 + packages/core/package.json | 5 +- packages/core/src/Ponder.ts | 231 ++++++----- .../core/src/_test/art-gobblers/app.test.ts | 14 +- .../_test/art-gobblers/app/ponder.config.ts | 7 +- packages/core/src/_test/ens/app.test.ts | 15 +- .../core/src/_test/ens/app/ponder.config.ts | 7 +- packages/core/src/_test/utils.ts | 7 - packages/core/src/bin/ponder.ts | 33 +- packages/core/src/build/config.ts | 94 ----- packages/core/src/build/functions.ts | 111 +---- packages/core/src/build/service.ts | 379 +++++++++++++----- packages/core/src/build/stacktrace.ts | 119 ++++++ packages/core/src/config/database.ts | 8 +- packages/core/src/config/options.ts | 7 +- packages/core/src/logs/service.ts | 2 +- packages/core/src/utils/debounce.ts | 18 + packages/create-ponder/package.json | 2 +- pnpm-lock.yaml | 281 +------------ 27 files changed, 610 insertions(+), 747 deletions(-) create mode 100644 examples/friendtech/.eslintrc.json delete mode 100644 packages/core/src/build/config.ts create mode 100644 packages/core/src/build/stacktrace.ts create mode 100644 packages/core/src/utils/debounce.ts diff --git a/examples/art-gobblers/.eslintrc.json b/examples/art-gobblers/.eslintrc.json index 359e2bbfa..18b8b3bae 100644 --- a/examples/art-gobblers/.eslintrc.json +++ b/examples/art-gobblers/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "ponder" } diff --git a/examples/ethfs/.eslintrc.json b/examples/ethfs/.eslintrc.json index 359e2bbfa..18b8b3bae 100644 --- a/examples/ethfs/.eslintrc.json +++ b/examples/ethfs/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "ponder" } diff --git a/examples/factory-llama/.eslintrc.json b/examples/factory-llama/.eslintrc.json index 359e2bbfa..18b8b3bae 100644 --- a/examples/factory-llama/.eslintrc.json +++ b/examples/factory-llama/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "ponder" } diff --git a/examples/friendtech/.eslintrc.json b/examples/friendtech/.eslintrc.json new file mode 100644 index 000000000..18b8b3bae --- /dev/null +++ b/examples/friendtech/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "root": true, + "extends": "ponder" +} diff --git a/examples/friendtech/package.json b/examples/friendtech/package.json index da556bebb..b739d59ab 100644 --- a/examples/friendtech/package.json +++ b/examples/friendtech/package.json @@ -4,14 +4,16 @@ "scripts": { "dev": "ponder dev", "start": "ponder start", - "codegen": "ponder codegen" + "codegen": "ponder codegen", + "lint": "eslint ." }, "dependencies": { "@ponder/core": "workspace:*" }, "devDependencies": { - "@types/node": "^18.11.18", "abitype": "^0.8.11", + "eslint": "^8.43.0", + "eslint-config-ponder": "workspace:*", "typescript": "^5.1.3", "viem": "^1.2.6" } diff --git a/examples/token-erc20/.eslintrc.json b/examples/token-erc20/.eslintrc.json index 359e2bbfa..18b8b3bae 100644 --- a/examples/token-erc20/.eslintrc.json +++ b/examples/token-erc20/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "ponder" } diff --git a/examples/token-erc721/.eslintrc.json b/examples/token-erc721/.eslintrc.json index 359e2bbfa..18b8b3bae 100644 --- a/examples/token-erc721/.eslintrc.json +++ b/examples/token-erc721/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "ponder" } diff --git a/examples/token-reth/.eslintrc.json b/examples/token-reth/.eslintrc.json index 359e2bbfa..18b8b3bae 100644 --- a/examples/token-reth/.eslintrc.json +++ b/examples/token-reth/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "ponder" } diff --git a/examples/with-docker/.eslintrc.json b/examples/with-docker/.eslintrc.json index 359e2bbfa..18b8b3bae 100644 --- a/examples/with-docker/.eslintrc.json +++ b/examples/with-docker/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "ponder" } diff --git a/packages/core/package.json b/packages/core/package.json index ae28a3aa8..96224731c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -65,7 +65,9 @@ "retry": "^0.13.1", "stacktrace-parser": "^0.1.10", "tsc-alias": "^1.8.2", - "viem": "^1.2.6" + "viem": "^1.2.6", + "vite": "^4.5.0", + "vite-node": "^0.34.6" }, "devDependencies": { "@types/babel__code-frame": "^7.0.3", @@ -86,7 +88,6 @@ "rimraf": "^5.0.1", "supertest": "^6.3.3", "typescript": "^5.1.3", - "vite": "^4.5.0", "vitest": "^0.34.6" } } diff --git a/packages/core/src/Ponder.ts b/packages/core/src/Ponder.ts index c1c2382e9..5ee252c93 100644 --- a/packages/core/src/Ponder.ts +++ b/packages/core/src/Ponder.ts @@ -3,7 +3,6 @@ import process from "node:process"; import { BuildService } from "@/build/service"; import { CodegenService } from "@/codegen/service"; -import { type ResolvedConfig } from "@/config/config"; import { buildDatabase } from "@/config/database"; import { type Network, buildNetwork } from "@/config/networks"; import { type Options } from "@/config/options"; @@ -24,6 +23,7 @@ import { PostgresUserStore } from "@/user-store/postgres/store"; import { SqliteUserStore } from "@/user-store/sqlite/store"; import { type UserStore } from "@/user-store/store"; +import { hydrateIndexingFunctions } from "./build/functions"; import { buildSources, Source } from "./config/sources"; export type Common = { @@ -36,39 +36,31 @@ export type Common = { export class Ponder { common: Common; - sources: Source[]; + buildService: BuildService; - eventStore: EventStore; - userStore: UserStore; + // Derived config + sources: Source[] = undefined!; - // List of indexing-related services. One per configured network. - networkSyncServices: { + // Sync services + eventStore: EventStore = undefined!; + syncServices: { network: Network; sources: Source[]; - historicalSyncService: HistoricalSyncService; - realtimeSyncService: RealtimeSyncService; - }[] = []; + historical: HistoricalSyncService; + realtime: RealtimeSyncService; + }[] = undefined!; + eventAggregatorService: EventAggregatorService = undefined!; - eventAggregatorService: EventAggregatorService; - indexingService: IndexingService; + // Indexing services + userStore: UserStore = undefined!; + indexingService: IndexingService = undefined!; - serverService: ServerService; - buildService: BuildService; - codegenService: CodegenService; - uiService: UiService; + // Misc services + serverService: ServerService = undefined!; + codegenService: CodegenService = undefined!; + uiService: UiService = undefined!; - constructor({ - options, - config, - eventStore, - userStore, - }: { - options: Options; - config: ResolvedConfig; - // These options are only used for testing. - eventStore?: EventStore; - userStore?: UserStore; - }) { + constructor({ options }: { options: Options }) { const logger = new LoggerService({ level: options.logLevel, dir: options.logDir, @@ -77,9 +69,19 @@ export class Ponder { const metrics = new MetricsService(); const telemetry = new TelemetryService({ options }); - const common = { options, logger, errors, metrics, telemetry }; - this.common = common; + this.common = { options, logger, errors, metrics, telemetry }; + + this.buildService = new BuildService({ common: this.common }); + } + async setup({ + eventStore, + userStore, + }: { + // These options are only used for testing. + eventStore?: EventStore; + userStore?: UserStore; + } = {}) { this.common.logger.debug({ service: "app", msg: `Started using config file: ${path.relative( @@ -88,7 +90,19 @@ export class Ponder { )}`, }); - const database = buildDatabase({ options, config }); + // Initialize the Vite server and Vite Node runner. + await this.buildService.setup(); + + // Load the config file so that we can create initial versions of all services. + // If `config` is undefined, there was an error loading the config. For now, + // we can just exit. No need to call `this.kill()` because no services are set up. + const config = await this.buildService.loadConfig(); + if (!config) { + await this.buildService.kill(); + return; + } + + const database = buildDatabase({ common: this.common, config }); this.eventStore = eventStore ?? (database.kind === "sqlite" @@ -101,15 +115,10 @@ export class Ponder { ? new SqliteUserStore({ db: database.db }) : new PostgresUserStore({ pool: database.pool })); - const networks = config.networks.map((network) => - buildNetwork({ network, common }) - ); - - const sources = buildSources({ config }); - this.sources = sources; + this.sources = buildSources({ config }); const networksToSync = config.networks - .map((network) => buildNetwork({ network, common })) + .map((network) => buildNetwork({ network, common: this.common })) .filter((network) => { const hasEventSources = this.sources.some( (eventSource) => eventSource.network === network.name @@ -117,134 +126,118 @@ export class Ponder { if (!hasEventSources) { this.common.logger.warn({ service: "app", - msg: `No event sources found (network=${network.name})`, + msg: `No contracts found (network=${network.name})`, }); } return hasEventSources; }); + this.syncServices = []; networksToSync.forEach((network) => { - const sourcesForNetwork = sources.filter( + const sourcesForNetwork = this.sources.filter( (logSource) => logSource.network === network.name ); - - this.networkSyncServices.push({ + this.syncServices.push({ network, - sources: sourcesForNetwork, - historicalSyncService: new HistoricalSyncService({ - common, + historical: new HistoricalSyncService({ + common: this.common, eventStore: this.eventStore, network, sources: sourcesForNetwork, }), - realtimeSyncService: new RealtimeSyncService({ - common, + realtime: new RealtimeSyncService({ + common: this.common, eventStore: this.eventStore, network, sources: sourcesForNetwork, }), }); }); - this.eventAggregatorService = new EventAggregatorService({ - common, + common: this.common, eventStore: this.eventStore, - networks, - sources, + networks: networksToSync, + sources: this.sources, }); this.indexingService = new IndexingService({ - common, + common: this.common, eventStore: this.eventStore, userStore: this.userStore, eventAggregatorService: this.eventAggregatorService, - sources, + sources: this.sources, }); this.serverService = new ServerService({ - common, + common: this.common, userStore: this.userStore, }); - this.buildService = new BuildService({ - common, - sources, - }); this.codegenService = new CodegenService({ - common, - sources, + common: this.common, + sources: this.sources, + }); + this.uiService = new UiService({ + common: this.common, + sources: this.sources, }); - this.uiService = new UiService({ common, sources }); - } - async setup() { + // Once all services have been successfully created & started + // using the initial config, register service dependencies. this.registerServiceDependencies(); - // Start the HTTP server. - await this.serverService.start(); - - // These files depend only on ponder.config.ts, so can generate once on setup. - // Note that buildIndexingFunctions depends on the index.ts file being present. + // TODO: Remove once we have the new PonderApp magic. this.codegenService.generateAppFile(); - // Note that this must occur before buildSchema and buildIndexingFunctions. + // One-time setup for some services. await this.eventStore.migrateUp(); + await this.serverService.start(); - // Manually trigger loading schema and indexing functions. Subsequent loads - // are triggered by changes to project files (handled in BuildService). + // Finally, load the schema + indexing functions which will trigger + // the indexing service to reload (for the first time). + await this.buildService.loadIndexingFunctions(); this.buildService.buildSchema(); - await this.buildService.buildIndexingFunctions(); } async dev() { - await this.setup(); - this.common.telemetry.record({ event: "App Started", properties: { command: "ponder dev", - logFilterCount: this.sources.length, + contractCount: this.sources.length, databaseKind: this.eventStore.kind, }, }); await Promise.all( - this.networkSyncServices.map( - async ({ historicalSyncService, realtimeSyncService }) => { - const blockNumbers = await realtimeSyncService.setup(); - await historicalSyncService.setup(blockNumbers); + this.syncServices.map(async ({ historical, realtime }) => { + const blockNumbers = await realtime.setup(); + await historical.setup(blockNumbers); - historicalSyncService.start(); - realtimeSyncService.start(); - } - ) + historical.start(); + await realtime.start(); + }) ); - - this.buildService.watch(); } async start() { - await this.setup(); - this.common.telemetry.record({ event: "App Started", properties: { command: "ponder start", - logFilterCount: this.sources.length, + contractCount: this.sources.length, databaseKind: this.eventStore.kind, }, }); await Promise.all( - this.networkSyncServices.map( - async ({ historicalSyncService, realtimeSyncService }) => { - const blockNumbers = await realtimeSyncService.setup(); - await historicalSyncService.setup(blockNumbers); + this.syncServices.map(async ({ historical, realtime }) => { + const blockNumbers = await realtime.setup(); + await historical.setup(blockNumbers); - historicalSyncService.start(); - realtimeSyncService.start(); - } - ) + historical.start(); + await realtime.start(); + }) ); } @@ -272,12 +265,10 @@ export class Ponder { }); await Promise.all( - this.networkSyncServices.map( - async ({ realtimeSyncService, historicalSyncService }) => { - await realtimeSyncService.kill(); - await historicalSyncService.kill(); - } - ) + this.syncServices.map(async ({ realtime, historical }) => { + await realtime.kill(); + await historical.kill(); + }) ); await this.buildService.kill(); @@ -305,6 +296,8 @@ export class Ponder { }); this.buildService.on("newSchema", async ({ schema, graphqlSchema }) => { + this.common.errors.hasUserError = false; + this.codegenService.generateAppFile({ schema }); this.codegenService.generateSchemaFile({ graphqlSchema }); @@ -317,50 +310,54 @@ export class Ponder { this.buildService.on( "newIndexingFunctions", async ({ indexingFunctions }) => { - await this.indexingService.reset({ indexingFunctions }); + this.common.errors.hasUserError = false; + + // This is jank. Not quite sure where this should go. + const hydrated = hydrateIndexingFunctions({ + rawIndexingFunctions: indexingFunctions, + sources: this.sources, + }); + await this.indexingService.reset({ indexingFunctions: hydrated }); + await this.indexingService.processEvents(); } ); - this.networkSyncServices.forEach((networkSyncService) => { - const { chainId } = networkSyncService.network; - const { historicalSyncService, realtimeSyncService } = networkSyncService; + this.syncServices.forEach(({ network, historical, realtime }) => { + const { chainId } = network; - historicalSyncService.on("historicalCheckpoint", ({ blockTimestamp }) => { + historical.on("historicalCheckpoint", ({ blockTimestamp }) => { this.eventAggregatorService.handleNewHistoricalCheckpoint({ chainId, timestamp: blockTimestamp, }); }); - historicalSyncService.on("syncComplete", () => { + historical.on("syncComplete", () => { this.eventAggregatorService.handleHistoricalSyncComplete({ chainId, }); }); - realtimeSyncService.on("realtimeCheckpoint", ({ blockTimestamp }) => { + realtime.on("realtimeCheckpoint", ({ blockTimestamp }) => { this.eventAggregatorService.handleNewRealtimeCheckpoint({ chainId, timestamp: blockTimestamp, }); }); - realtimeSyncService.on("finalityCheckpoint", ({ blockTimestamp }) => { + realtime.on("finalityCheckpoint", ({ blockTimestamp }) => { this.eventAggregatorService.handleNewFinalityCheckpoint({ chainId, timestamp: blockTimestamp, }); }); - realtimeSyncService.on( - "shallowReorg", - ({ commonAncestorBlockTimestamp }) => { - this.eventAggregatorService.handleReorg({ - commonAncestorTimestamp: commonAncestorBlockTimestamp, - }); - } - ); + realtime.on("shallowReorg", ({ commonAncestorBlockTimestamp }) => { + this.eventAggregatorService.handleReorg({ + commonAncestorTimestamp: commonAncestorBlockTimestamp, + }); + }); }); this.eventAggregatorService.on("newCheckpoint", async () => { diff --git a/packages/core/src/_test/art-gobblers/app.test.ts b/packages/core/src/_test/art-gobblers/app.test.ts index 434a65975..35327950f 100644 --- a/packages/core/src/_test/art-gobblers/app.test.ts +++ b/packages/core/src/_test/art-gobblers/app.test.ts @@ -1,11 +1,8 @@ import { rmSync } from "node:fs"; -import path from "node:path"; import request from "supertest"; import { type TestContext, afterEach, beforeEach, expect, test } from "vitest"; import { setupEventStore, setupUserStore } from "@/_test/setup"; -import { testNetworkConfig } from "@/_test/utils"; -import { buildConfig } from "@/build/config"; import { buildOptions } from "@/config/options"; import { Ponder } from "@/Ponder"; @@ -13,12 +10,6 @@ beforeEach((context) => setupEventStore(context)); beforeEach((context) => setupUserStore(context)); const setup = async ({ context }: { context: TestContext }) => { - const config = await buildConfig({ - configFile: path.resolve("src/_test/art-gobblers/app/ponder.config.ts"), - }); - // Inject proxied anvil chain. - const testConfig = { ...config, networks: [testNetworkConfig] }; - const options = buildOptions({ cliOptions: { rootDir: "./src/_test/art-gobblers/app", @@ -32,9 +23,8 @@ const setup = async ({ context }: { context: TestContext }) => { telemetryDisabled: true, } as const; - const ponder = new Ponder({ - config: testConfig, - options: testOptions, + const ponder = new Ponder({ options: testOptions }); + await ponder.setup({ eventStore: context.eventStore, userStore: context.userStore, }); diff --git a/packages/core/src/_test/art-gobblers/app/ponder.config.ts b/packages/core/src/_test/art-gobblers/app/ponder.config.ts index f460890b3..53a208dd1 100644 --- a/packages/core/src/_test/art-gobblers/app/ponder.config.ts +++ b/packages/core/src/_test/art-gobblers/app/ponder.config.ts @@ -3,10 +3,11 @@ import { http } from "viem"; import { createConfig } from "../../../../dist"; import { ArtGobblersAbi } from "./ArtGobblers.abi"; +const poolId = Number(process.env.VITEST_POOL_ID ?? 1); +const transport = http(`http://127.0.0.1:8545/${poolId}`); + export const config = createConfig({ - networks: [ - { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, - ], + networks: [{ name: "mainnet", chainId: 1, transport }], contracts: [ { name: "ArtGobblers", diff --git a/packages/core/src/_test/ens/app.test.ts b/packages/core/src/_test/ens/app.test.ts index 86de428de..581fe7004 100644 --- a/packages/core/src/_test/ens/app.test.ts +++ b/packages/core/src/_test/ens/app.test.ts @@ -1,11 +1,8 @@ import { rmSync } from "node:fs"; -import path from "node:path"; import request from "supertest"; import { type TestContext, afterEach, beforeEach, expect, test } from "vitest"; import { setupEventStore, setupUserStore } from "@/_test/setup"; -import { testNetworkConfig } from "@/_test/utils"; -import { buildConfig } from "@/build/config"; import { buildOptions } from "@/config/options"; import { Ponder } from "@/Ponder"; @@ -13,18 +10,13 @@ beforeEach((context) => setupEventStore(context)); beforeEach((context) => setupUserStore(context)); const setup = async ({ context }: { context: TestContext }) => { - const config = await buildConfig({ - configFile: path.resolve("src/_test/ens/app/ponder.config.ts"), - }); - // Inject proxied anvil chain. - const testConfig = { ...config, networks: [testNetworkConfig] }; - const options = buildOptions({ cliOptions: { rootDir: "./src/_test/ens/app", configFile: "ponder.config.ts", }, }); + const testOptions = { ...options, uiEnabled: false, @@ -32,9 +24,8 @@ const setup = async ({ context }: { context: TestContext }) => { telemetryDisabled: true, } as const; - const ponder = new Ponder({ - config: testConfig, - options: testOptions, + const ponder = new Ponder({ options: testOptions }); + await ponder.setup({ eventStore: context.eventStore, userStore: context.userStore, }); diff --git a/packages/core/src/_test/ens/app/ponder.config.ts b/packages/core/src/_test/ens/app/ponder.config.ts index c58f7909f..ce1cb8d2e 100644 --- a/packages/core/src/_test/ens/app/ponder.config.ts +++ b/packages/core/src/_test/ens/app/ponder.config.ts @@ -3,10 +3,11 @@ import { http } from "viem"; import { createConfig } from "../../../../dist"; import { BaseRegistrarImplementationAbi } from "./BaseRegistrarImplementation.abi"; +const poolId = Number(process.env.VITEST_POOL_ID ?? 1); +const transport = http(`http://127.0.0.1:8545/${poolId}`); + export const config = createConfig({ - networks: [ - { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, - ], + networks: [{ name: "mainnet", chainId: 1, transport }], contracts: [ { name: "BaseRegistrarImplementation", diff --git a/packages/core/src/_test/utils.ts b/packages/core/src/_test/utils.ts index aef154327..c97e09427 100644 --- a/packages/core/src/_test/utils.ts +++ b/packages/core/src/_test/utils.ts @@ -32,13 +32,6 @@ export const anvil: Chain = { }, }; -export const testNetworkConfig = { - name: "mainnet", - chainId: anvil.id, - transport: http(anvil.rpcUrls.default.http[0]), - pollingInterval: 500, -}; - export const testClient: TestClient<"anvil", HttpTransport, Chain> = createTestClient({ chain: anvil, diff --git a/packages/core/src/bin/ponder.ts b/packages/core/src/bin/ponder.ts index a413486d3..aaa0e94c8 100644 --- a/packages/core/src/bin/ponder.ts +++ b/packages/core/src/bin/ponder.ts @@ -1,9 +1,7 @@ #!/usr/bin/env node import { cac } from "cac"; import dotenv from "dotenv"; -import path from "node:path"; -import { buildConfig } from "@/build/config"; import { buildOptions } from "@/config/options"; import { Ponder } from "@/Ponder"; @@ -36,14 +34,13 @@ cli .action(async (cliOptions: CliOptions) => { if (cliOptions.help) process.exit(0); - const configFile = path.resolve(cliOptions.rootDir, cliOptions.configFile); - const config = await buildConfig({ configFile }); - const options = buildOptions({ cliOptions, configOptions: config.options }); - + const options = buildOptions({ cliOptions }); const devOptions = { ...options, uiEnabled: true }; - const ponder = new Ponder({ config, options: devOptions }); + const ponder = new Ponder({ options: devOptions }); registerKilledProcessListener(() => ponder.kill()); + + await ponder.setup(); await ponder.dev(); }); @@ -52,14 +49,13 @@ cli .action(async (cliOptions: CliOptions) => { if (cliOptions.help) process.exit(0); - const configFile = path.resolve(cliOptions.rootDir, cliOptions.configFile); - const config = await buildConfig({ configFile }); - const options = buildOptions({ cliOptions, configOptions: config.options }); - + const options = buildOptions({ cliOptions }); const startOptions = { ...options, uiEnabled: false }; - const ponder = new Ponder({ config, options: startOptions }); + const ponder = new Ponder({ options: startOptions }); registerKilledProcessListener(() => ponder.kill()); + + await ponder.setup(); await ponder.start(); }); @@ -68,18 +64,17 @@ cli .action(async (cliOptions: CliOptions) => { if (cliOptions.help) process.exit(0); - const configFile = path.resolve(cliOptions.rootDir, cliOptions.configFile); - const config = await buildConfig({ configFile }); - const options = buildOptions({ cliOptions, configOptions: config.options }); - + const options = buildOptions({ cliOptions }); const codegenOptions = { ...options, uiEnabled: false, - logLevel: "silent", - } as const; + logLevel: "silent" as const, + }; - const ponder = new Ponder({ config, options: codegenOptions }); + const ponder = new Ponder({ options: codegenOptions }); registerKilledProcessListener(() => ponder.kill()); + + await ponder.setup(); await ponder.codegen(); }); diff --git a/packages/core/src/build/config.ts b/packages/core/src/build/config.ts deleted file mode 100644 index 626117f89..000000000 --- a/packages/core/src/build/config.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { build, Plugin } from "esbuild"; -import { existsSync, rmSync } from "node:fs"; -import path from "node:path"; - -import { ensureDirExists } from "@/utils/exists"; - -/** - * Fixes issue with createConfig() being built - * - * {@link} https://github.com/evanw/esbuild/issues/1051 - */ -const nativeNodeModulesPlugin: Plugin = { - name: "native-node-modules", - setup(build) { - // If a ".node" file is imported within a module in the "file" namespace, resolve - // it to an absolute path and put it into the "node-file" virtual namespace. - build.onResolve({ filter: /\.node$/, namespace: "file" }, (args) => ({ - path: require.resolve(args.path, { paths: [args.resolveDir] }), - namespace: "node-file", - })); - - // Files in the "node-file" virtual namespace call "require()" on the - // path from esbuild of the ".node" file in the output directory. - build.onLoad({ filter: /.*/, namespace: "node-file" }, (args) => ({ - contents: ` - import path from ${JSON.stringify(args.path)} - try { module.exports = require(path) } - catch {} - `, - })); - - // If a ".node" file is imported within a module in the "node-file" namespace, put - // it in the "file" namespace where esbuild's default loading behavior will handle - // it. It is already an absolute path since we resolved it to one above. - build.onResolve({ filter: /\.node$/, namespace: "node-file" }, (args) => ({ - path: args.path, - namespace: "file", - })); - - // Tell esbuild's default loading behavior to use the "file" loader for - // these ".node" files. - const opts = build.initialOptions; - opts.loader = opts.loader || {}; - opts.loader[".node"] = "file"; - }, -}; - -export const buildConfig = async ({ configFile }: { configFile: string }) => { - if (!existsSync(configFile)) { - throw new Error(`Ponder config file not found, expected: ${configFile}`); - } - - const buildFile = path.join(path.dirname(configFile), "__ponder__.js"); - ensureDirExists(buildFile); - - // Delete the build file before attempting to write it. - rmSync(buildFile, { force: true }); - - try { - await build({ - entryPoints: [configFile], - outfile: buildFile, - platform: "node", - format: "cjs", - plugins: [nativeNodeModulesPlugin], - // Note: Flipped to true in order to be able to import external files into ponder.config.ts - bundle: true, - logLevel: "silent", - }); - - const { default: rawDefault, config } = require(buildFile); - rmSync(buildFile, { force: true }); - - if (!config) { - if (rawDefault) { - throw new Error( - `Ponder config not found. ${path.basename( - configFile - )} must export a variable named "config" (Cannot be a default export)` - ); - } - throw new Error( - `Ponder config not found. ${path.basename( - configFile - )} must export a variable named "config"` - ); - } - - return config; - } catch (err) { - rmSync(buildFile, { force: true }); - throw err; - } -}; diff --git a/packages/core/src/build/functions.ts b/packages/core/src/build/functions.ts index db4817a15..bb2614ee1 100644 --- a/packages/core/src/build/functions.ts +++ b/packages/core/src/build/functions.ts @@ -1,12 +1,6 @@ -import { type Message, build, formatMessagesSync } from "esbuild"; -import glob from "glob"; -import { existsSync, rmSync } from "node:fs"; -import path from "node:path"; -import { replaceTscAliasPaths } from "tsc-alias"; import type { Hex } from "viem"; -import { LogEventMetadata } from "@/config/abi"; -import type { Options } from "@/config/options"; +import type { LogEventMetadata } from "@/config/abi"; import { Source } from "@/config/sources"; import type { Block } from "@/types/block"; import type { Log } from "@/types/log"; @@ -37,7 +31,7 @@ type SetupEventIndexingFunction = ({ context: unknown; }) => Promise | void; -type RawIndexingFunctions = { +export type RawIndexingFunctions = { _meta_?: { setup?: SetupEventIndexingFunction; }; @@ -84,107 +78,6 @@ export class PonderApp< } } -export const buildRawIndexingFunctions = async ({ - options, -}: { - options: Options; -}) => { - const entryAppFilename = path.join(options.generatedDir, "index.ts"); - if (!existsSync(entryAppFilename)) { - throw new Error( - `generated/index.ts file not found, expected: ${entryAppFilename}` - ); - } - - const entryGlob = path.join(options.srcDir, "/**/*.ts"); - const entryFilenames = [...glob.sync(entryGlob), entryAppFilename]; - - const buildDir = path.join(options.ponderDir, "out"); - rmSync(buildDir, { recursive: true, force: true }); - - try { - await build({ - entryPoints: entryFilenames, - outdir: buildDir, - platform: "node", - bundle: false, - format: "cjs", - logLevel: "silent", - sourcemap: "inline", - }); - } catch (err) { - const error = err as Error & { errors: Message[]; warnings: Message[] }; - // Hack to use esbuilds very pretty stack traces when rendering errors to the user. - const stackTraces = formatMessagesSync(error.errors, { - kind: "error", - color: true, - }); - error.stack = stackTraces.join("\n"); - - throw error; - } - - const tsconfigPath = path.join(options.rootDir, "tsconfig.json"); - if (existsSync(tsconfigPath)) { - await replaceTscAliasPaths({ - configFile: tsconfigPath, - outDir: buildDir, - }); - } else { - throw new Error( - `tsconfig.json not found, unable to resolve "@/*" path aliases. Expected at: ${tsconfigPath}` - ); - } - - const outGlob = buildDir + "/**/*.js"; - const outFilenames = glob.sync(outGlob); - - // Remove all out modules from the require cache, because we are loading - // them several times in the same process and need the latest version each time. - // https://ar.al/2021/02/22/cache-busting-in-node.js-dynamic-esm-imports/ - outFilenames.forEach((file) => delete require.cache[require.resolve(file)]); - - const outAppFilename = path.join(buildDir, "generated/index.js"); - - // Require all the user-defined files first. - const outUserFilenames = outFilenames.filter( - (name) => name !== outAppFilename - ); - - const requireErrors = outUserFilenames - .map((file) => { - try { - require(file); - return undefined; - } catch (err) { - return err as Error; - } - }) - .filter((err): err is Error => err !== undefined); - - if (requireErrors.length > 0) { - throw requireErrors[0]; - } - - // Then require the `_app.ts` file to grab the `app` instance. - const result = require(outAppFilename); - - const app = result.ponder; - - if (!app) { - throw new Error(`ponder not exported from generated/index.ts`); - } - if (!(app.constructor.name === "PonderApp")) { - throw new Error(`exported ponder not instanceof PonderApp`); - } - if (app["errors"].length > 0) { - const error = app["errors"][0]; - throw error; - } - - return app["indexingFunctions"] as RawIndexingFunctions; -}; - export type IndexingFunctions = { _meta_: { setup?: { diff --git a/packages/core/src/build/service.ts b/packages/core/src/build/service.ts index 2c8ea3d25..2e1f960f9 100644 --- a/packages/core/src/build/service.ts +++ b/packages/core/src/build/service.ts @@ -1,123 +1,335 @@ -import chokidar from "chokidar"; +/* eslint-disable @typescript-eslint/ban-ts-comment */ import Emittery from "emittery"; +import glob from "glob"; import { GraphQLSchema } from "graphql"; -import { createHash } from "node:crypto"; -import { readFileSync } from "node:fs"; import path from "node:path"; +// @ts-ignore +import type { ViteDevServer } from "vite"; +// @ts-ignore +import type { ViteNodeRunner } from "vite-node/client"; +// @ts-ignore +import type { ViteNodeServer } from "vite-node/server"; -import { Source } from "@/config/sources"; +import type { ResolvedConfig } from "@/config/config"; import { UserError } from "@/errors/user"; import type { Common } from "@/Ponder"; import { buildSchema } from "@/schema/schema"; import type { Schema } from "@/schema/types"; import { buildGqlSchema } from "@/server/graphql/schema"; -import { - type IndexingFunctions, - buildRawIndexingFunctions, - hydrateIndexingFunctions, -} from "./functions"; +import type { RawIndexingFunctions } from "./functions"; import { readGraphqlSchema } from "./schema"; +import { parseViteNodeError, ViteNodeError } from "./stacktrace"; type BuildServiceEvents = { - newConfig: undefined; - newIndexingFunctions: { indexingFunctions: IndexingFunctions }; + newConfig: { config: ResolvedConfig }; + newIndexingFunctions: { indexingFunctions: RawIndexingFunctions }; newSchema: { schema: Schema; graphqlSchema: GraphQLSchema }; }; export class BuildService extends Emittery { private common: Common; - private sources: Source[]; - private closeWatcher?: () => Promise; - private latestFileHashes: Record = {}; + private viteDevServer: ViteDevServer = undefined!; + private viteNodeServer: ViteNodeServer = undefined!; + private viteNodeRunner: ViteNodeRunner = undefined!; - constructor({ common, sources }: { common: Common; sources: Source[] }) { + private indexingFunctions: Record> = {}; + + constructor({ common }: { common: Common }) { super(); this.common = common; - this.sources = sources; + } + + async setup() { + const { createServer } = await import("vite"); + const { ViteNodeServer } = await import("vite-node/server"); + const { installSourcemapsSupport } = await import("vite-node/source-map"); + const { ViteNodeRunner } = await import("vite-node/client"); + const { toFilePath, normalizeModuleId } = await import("vite-node/utils"); + + const viteLogger = { + warnedMessages: new Set(), + loggedErrors: new WeakSet(), + hasWarned: false, + clearScreen() {}, + hasErrorLogged: (error: Error) => viteLogger.loggedErrors.has(error), + info: (msg: string) => { + this.common.logger.trace({ service: "build(vite)", msg }); + }, + warn: (msg: string) => { + viteLogger.hasWarned = true; + this.common.logger.trace({ service: "build(vite)", msg }); + }, + warnOnce: (msg: string) => { + if (viteLogger.warnedMessages.has(msg)) return; + viteLogger.hasWarned = true; + this.common.logger.trace({ service: "build(vite)", msg }); + viteLogger.warnedMessages.add(msg); + }, + error: (msg: string) => { + viteLogger.hasWarned = true; + this.common.logger.trace({ service: "build(vite)", msg }); + }, + }; + + this.viteDevServer = await createServer({ + root: this.common.options.rootDir, + cacheDir: path.join(this.common.options.ponderDir, "vite"), + publicDir: false, + customLogger: viteLogger, + server: { hmr: false }, + plugins: [ + { + name: "ponder:hmr", + transform: (code_) => { + let code = code_; + + // Matches `import { ponder } from "@/generated";` with whitespaces and newlines. + const regex = + /import\s+\{\s*ponder\s*\}\s+from\s+(['"])@\/generated\1\s*;?/g; + if (regex.test(code)) { + // Add shim object to collect user functions. + const shimHeader = ` + export let ponder = { + fns: [], + on(name, fn) { + this.fns.push({ name, fn }); + }, + }; + `; + code = `${shimHeader}\n${code.replace(regex, "")}`; + } + + return code; + }, + }, + ], + }); + + // This is Vite boilerplate (initializes the Rollup container). + await this.viteDevServer.pluginContainer.buildStart({}); + + this.viteNodeServer = new ViteNodeServer(this.viteDevServer); + installSourcemapsSupport({ + getSourceMap: (source) => this.viteNodeServer.getSourceMap(source), + }); + + this.viteNodeRunner = new ViteNodeRunner({ + root: this.viteDevServer.config.root, + fetchModule: (id) => this.viteNodeServer.fetchModule(id, "ssr"), + resolveId: (id, importer) => + this.viteNodeServer.resolveId(id, importer, "ssr"), + }); + + const handleFileChange = async (files_: string[]) => { + const files = files_.map( + (file) => + toFilePath(normalizeModuleId(file), this.common.options.rootDir).path + ); + + // Invalidate all modules that depend on the updated files. + const invalidated = [ + ...this.viteNodeRunner.moduleCache.invalidateDepTree(files), + ]; + + this.common.logger.info({ + service: "build", + msg: `Hot reload ${invalidated + .map((f) => path.relative(this.common.options.rootDir, f)) + .join(", ")}`, + }); + + // Note that the order execution here is intentional. + if (invalidated.includes(this.common.options.configFile)) { + await this.loadConfig(); + } + if (invalidated.includes(this.common.options.schemaFile)) { + await this.loadSchema(); + } + + const srcRegex = new RegExp( + `^${this.common.options.srcDir.replace( + /[.*+?^${}()|[\]\\]/g, + "\\$&" + )}/.*\\.(js|ts)$` + ); + const srcFiles = invalidated.filter((file) => srcRegex.test(file)); + + if (srcFiles.length > 0) { + await this.loadIndexingFunctions({ files: srcFiles }); + } + }; + + // TODO: Consider handling "add" and "unlink" events too. + // TODO: Debounce, de-duplicate, and batch updates. + + this.viteDevServer.watcher.on("change", async (file) => { + const ignoreRegex = new RegExp( + `^${this.common.options.ponderDir.replace( + /[.*+?^${}()|[\]\\]/g, + "\\$&" + )}/.*[^/]$` + ); + if (ignoreRegex.test(file)) return; + + await handleFileChange([file]); + }); } async kill() { - this.closeWatcher?.(); + await this.viteDevServer?.close(); this.common.logger.debug({ service: "build", msg: `Killed build service`, }); } - watch() { - const watchFiles = [ - this.common.options.configFile, - this.common.options.schemaFile, - this.common.options.srcDir, - ]; + async loadConfig() { + const result = await this.executeFile(this.common.options.configFile); - const watcher = chokidar.watch(watchFiles); - this.closeWatcher = async () => { - await watcher.close(); - }; + if (result.error) { + this.handleViteNodeError(result); + return; + } - watcher.on("change", async (filePath) => { - if (filePath === this.common.options.configFile) { - this.emit("newConfig"); - return; - } + const rawConfig = result.exports.config; + const resolvedConfig = ( + typeof rawConfig === "function" ? await rawConfig() : await rawConfig + ) as ResolvedConfig; - if (this.isFileChanged(filePath)) { - const fileName = path.basename(filePath); + // TODO: Validate config lol - this.common.logger.info({ - service: "build", - msg: `Detected change in ${fileName}`, - }); + this.emit("newConfig", { config: resolvedConfig }); - this.common.errors.hasUserError = false; + return resolvedConfig; + } - if (filePath === this.common.options.schemaFile) { - this.buildSchema(); - } else { - await this.buildIndexingFunctions(); - } - } - }); + async loadSchema() { + console.log("loaded schema "); } - async buildIndexingFunctions() { - try { - const rawIndexingFunctions = await buildRawIndexingFunctions({ - options: this.common.options, - }); + async loadIndexingFunctions({ files: files_ }: { files?: string[] } = {}) { + const files = + files_ ?? + glob.sync( + path.join(this.common.options.srcDir, "**/*.{js,cjs,mjs,ts,mts}") + ); - const indexingFunctions = hydrateIndexingFunctions({ - rawIndexingFunctions, - sources: this.sources, - }); + const results = await Promise.all( + files.map((file) => this.executeFile(file)) + ); - if (Object.values(indexingFunctions.eventSources).length === 0) { - this.common.logger.warn({ - service: "build", - msg: "No indexing functions found", - }); - } + const errorResults = results.filter( + (r): r is { file: string; error: ViteNodeError } => r.error !== undefined + ); + if (errorResults.length > 0) { + this.handleViteNodeError(errorResults[0]); + return; + } - this.emit("newIndexingFunctions", { indexingFunctions }); - } catch (error_) { - const error = error_ as Error; + const successResults = results.filter( + (r): r is { file: string; exports: any } => r.exports !== undefined + ); - // TODO: Build the UserError object within readIndexingFunctions, check instanceof, - // then log/submit as-is if it's already a UserError. - const message = `Error during build: ${error.message}`; - const userError = new UserError(message, { - stack: error.stack, - }); + for (const result of successResults) { + const { file, exports } = result; - this.common.logger.error({ + const fns = (exports?.ponder?.fns ?? []) as { name: string; fn: any }[]; + + const fnsForFile: Record = {}; + for (const { name, fn } of fns) fnsForFile[name] = fn; + + // Override the indexing functions for this file. + this.indexingFunctions[file] = fnsForFile; + } + + // After adding all new indexing functions, validate that the user + // has not registered two functions for the same event. + const eventNameSet = new Set(); + for (const file of Object.keys(this.indexingFunctions)) { + for (const eventName of Object.keys(this.indexingFunctions[file])) { + if (eventNameSet.has(eventName)) { + throw new Error( + `Cannot register two indexing functions for one event '${eventName}' in '${file}'` + ); + } + eventNameSet.add(eventName); + } + } + + if (eventNameSet.size === 0) { + this.common.logger.warn({ service: "build", - error: userError, + msg: `No indexing functions were registered`, }); - this.common.errors.submitUserError({ error: userError }); + return; + } + + // TODO: Update this to be less awful. + const rawIndexingFunctions: RawIndexingFunctions = { eventSources: {} }; + for (const file of Object.keys(this.indexingFunctions)) { + for (const [fullName, fn] of Object.entries( + this.indexingFunctions[file] + )) { + if (fullName === "setup") { + rawIndexingFunctions._meta_ ||= {}; + rawIndexingFunctions._meta_.setup = fn; + } else { + const [eventSourceName, eventName] = fullName.split(":"); + if (!eventSourceName || !eventName) + throw new Error(`Invalid event name: ${fullName}`); + rawIndexingFunctions.eventSources[eventSourceName] ||= {}; + if (rawIndexingFunctions.eventSources[eventSourceName][eventName]) + throw new Error( + `Cannot add multiple handler functions for event: ${name}` + ); + rawIndexingFunctions.eventSources[eventSourceName][eventName] = fn; + } + } } + + this.emit("newIndexingFunctions", { + indexingFunctions: rawIndexingFunctions, + }); + } + + private async executeFile(file: string) { + try { + const exports = await this.viteNodeRunner.executeFile(file); + return { file, exports }; + } catch (error_) { + const error = parseViteNodeError(error_ as Error); + return { file, error }; + } + } + + private handleViteNodeError({ + file, + error, + }: { + file: string; + error: ViteNodeError; + }) { + const verb = + error.name === "ESBuildTransformError" + ? "transforming" + : error.name === "ESBuildBuildError" || + error.name === "ESBuildContextError" + ? "building" + : "executing"; + + this.common.logger.error({ + service: "build", + msg: `Error while ${verb} ${path.relative( + this.common.options.rootDir, + file + )}`, + error: error, + }); + + // TODO: Fix this error handling approach. + this.common.errors.submitUserError({ error }); } buildSchema() { @@ -150,25 +362,4 @@ export class BuildService extends Emittery { return undefined; } } - - private isFileChanged(filePath: string) { - // TODO: I think this throws if the file being watched gets deleted while - // the development server is running. Should handle this case gracefully. - try { - const content = readFileSync(filePath, "utf-8"); - const hash = createHash("md5").update(content).digest("hex"); - - const prevHash = this.latestFileHashes[filePath]; - this.latestFileHashes[filePath] = hash; - if (!prevHash) { - // If there is no previous hash, this file is being changed for the first time. - return true; - } else { - // If there is a previous hash, check if the content hash has changed. - return prevHash !== hash; - } - } catch (e) { - return true; - } - } } diff --git a/packages/core/src/build/stacktrace.ts b/packages/core/src/build/stacktrace.ts new file mode 100644 index 000000000..d683ea903 --- /dev/null +++ b/packages/core/src/build/stacktrace.ts @@ -0,0 +1,119 @@ +import { codeFrameColumns } from "@babel/code-frame"; +import { readFileSync } from "node:fs"; +import { parse as parseStackTrace } from "stacktrace-parser"; + +class ESBuildTransformError extends Error { + override name = "ESBuildTransformError"; +} + +class ESBuildBuildError extends Error { + override name = "ESBuildBuildError"; +} + +class ESBuildContextError extends Error { + override name = "ESBuildContextError"; +} + +export type ViteNodeError = + | ESBuildTransformError + | ESBuildBuildError + | ESBuildContextError + | Error; + +export function parseViteNodeError(error: Error): ViteNodeError { + let resolvedError: ViteNodeError; + + if (/^(Transform failed|Build failed|Context failed)/.test(error.message)) { + // Handle ESBuild errors based on this error message construction logic: + // https://github.com/evanw/esbuild/blob/4e11b50fe3178ed0a78c077df78788d66304d379/lib/shared/common.ts#L1659 + const errorKind = error.message.split(" with ")[0] as + | "Transform failed" + | "Build failed" + | "Context failed"; + const innerError = error.message + .split("\n") + .slice(1) + .map((message) => { + let location: string | undefined = undefined; + let detail: string | undefined = undefined; + if (message.includes(": ERROR: ")) { + // /path/to/file.ts:11:9: ERROR: Expected ")" but found ";" + const s = message.split(": ERROR: "); + location = s[0]; + detail = s[1]; + } else { + // error: some error without a location + detail = message.slice(7); + } + return { location, detail }; + })[0]; + + // If we aren't able to extract an inner error, just return the original. + if (!innerError) return error; + + resolvedError = + errorKind === "Transform failed" + ? new ESBuildTransformError(innerError.detail) + : errorKind === "Build failed" + ? new ESBuildBuildError(innerError.detail) + : new ESBuildContextError(innerError.detail); + if (innerError.location) + resolvedError.stack = ` at ${innerError.location}`; + } else { + // Handle ViteNode vm.runModuleInContext execution errors. + // If there is no stack, we can't detect what kind of error this is. + if (!error.stack) return error; + + const stackFrames = parseStackTrace(error.stack); + + // Filter for only user-land stack frames. + const userStackFrames = []; + for (const rawStackFrame of stackFrames) { + if (rawStackFrame.methodName.includes("ViteNodeRunner.runModule")) break; + userStackFrames.push(rawStackFrame); + } + + const userStack = userStackFrames + .map(({ file, lineNumber, column, methodName }) => { + const prefix = ` at`; + const path = `${file}${lineNumber !== null ? `:${lineNumber}` : ""}${ + column !== null ? `:${column}` : "" + }`; + if (methodName === null || methodName === "") { + return `${prefix} ${path}`; + } else { + return `${prefix} ${methodName} (${path})`; + } + }) + .join("\n"); + + resolvedError = error; + resolvedError.stack = userStack; + } + + // Attempt to build a code frame for the top of the user stack. + if (!resolvedError.stack) return error; + const userStackFrames = parseStackTrace(resolvedError.stack); + + let codeFrame: string | undefined = undefined; + for (const { file, lineNumber, column } of userStackFrames) { + if (file !== null && lineNumber !== null) { + try { + const sourceFileContents = readFileSync(file, { encoding: "utf-8" }); + codeFrame = codeFrameColumns( + sourceFileContents, + { start: { line: lineNumber, column: column ?? undefined } }, + { highlightCode: true } + ); + break; + } catch (err) { + // No-op. + } + } + } + + resolvedError.stack = `${resolvedError.name}: ${resolvedError.message}\n${resolvedError.stack}`; + if (codeFrame) resolvedError.stack = `${resolvedError.stack}\n${codeFrame}`; + + return resolvedError; +} diff --git a/packages/core/src/config/database.ts b/packages/core/src/config/database.ts index 2366bfbeb..85e79024a 100644 --- a/packages/core/src/config/database.ts +++ b/packages/core/src/config/database.ts @@ -3,9 +3,9 @@ import path from "node:path"; import pg, { Client, DatabaseError, Pool } from "pg"; import type { ResolvedConfig } from "@/config/config"; -import type { Options } from "@/config/options"; import { PostgresError } from "@/errors/postgres"; import { SqliteError } from "@/errors/sqlite"; +import type { Common } from "@/Ponder"; import { ensureDirExists } from "@/utils/exists"; export interface SqliteDb { @@ -87,15 +87,15 @@ export const patchSqliteDatabase = ({ db }: { db: any }) => { }; export const buildDatabase = ({ - options, + common, config, }: { - options: Options; + common: Common; config: ResolvedConfig; }): Database => { let resolvedDatabaseConfig: NonNullable; - const defaultSqliteFilename = path.join(options.ponderDir, "cache.db"); + const defaultSqliteFilename = path.join(common.options.ponderDir, "cache.db"); if (config.database) { if (config.database.kind === "postgres") { diff --git a/packages/core/src/config/options.ts b/packages/core/src/config/options.ts index d67114a91..083c67dea 100644 --- a/packages/core/src/config/options.ts +++ b/packages/core/src/config/options.ts @@ -3,8 +3,6 @@ import type { LevelWithSilent } from "pino"; import type { CliOptions } from "@/bin/ponder"; -import type { ResolvedConfig } from "./config"; - export type Options = { configFile: string; schemaFile: string; @@ -26,10 +24,8 @@ export type Options = { export const buildOptions = ({ cliOptions, - configOptions = {}, }: { cliOptions: CliOptions; - configOptions?: ResolvedConfig["options"]; }): Options => { const railwayHealthcheckTimeout = process.env.RAILWAY_HEALTHCHECK_TIMEOUT_SEC ? Math.max(Number(process.env.RAILWAY_HEALTHCHECK_TIMEOUT_SEC) - 5, 0) // Add 5 seconds of buffer. @@ -54,8 +50,7 @@ export const buildOptions = ({ logDir: ".ponder/logs", port: Number(process.env.PORT ?? 42069), - maxHealthcheckDuration: - configOptions?.maxHealthcheckDuration ?? railwayHealthcheckTimeout ?? 240, + maxHealthcheckDuration: railwayHealthcheckTimeout ?? 240, telemetryUrl: "https://ponder.sh/api/telemetry", telemetryDisabled: Boolean(process.env.PONDER_TELEMETRY_DISABLED), diff --git a/packages/core/src/logs/service.ts b/packages/core/src/logs/service.ts index 4e1b3e85e..f868d099a 100644 --- a/packages/core/src/logs/service.ts +++ b/packages/core/src/logs/service.ts @@ -57,7 +57,7 @@ export class LoggerService { fatal = (options: LogOptions & { error?: Error }) => { this.logger.fatal(options); }; - error = (options: LogOptions & { error: Error }) => { + error = (options: LogOptions & { error: Error; msg?: string }) => { this.logger.error(options); }; warn = (options: LogOptions & { msg: string }) => { diff --git a/packages/core/src/utils/debounce.ts b/packages/core/src/utils/debounce.ts new file mode 100644 index 000000000..42f980b5d --- /dev/null +++ b/packages/core/src/utils/debounce.ts @@ -0,0 +1,18 @@ +type Fn = (...args: TA) => TR; + +/** + * Creates a debounced function that delays invoking `fun` until `ms` milliseconds + * have passed since the last invocation of the debounced function. + * + * `fun` is invoked with the last arguments passed to the debounced function. + * + * Derived from `froebel/debounce`. + * https://github.com/MathisBullinger/froebel/blob/main/debounce.ts + */ +export function debounce(ms: number, fun: T) { + let toId: any; + return (...args: Parameters) => { + clearTimeout(toId); + toId = setTimeout(() => fun(...args), ms); + }; +} diff --git a/packages/create-ponder/package.json b/packages/create-ponder/package.json index 9b6f45639..cd64fa087 100644 --- a/packages/create-ponder/package.json +++ b/packages/create-ponder/package.json @@ -43,6 +43,6 @@ "dotenv": "^16.0.1", "tsc-alias": "^1.8.2", "typescript": "^5.1.3", - "vitest": "^0.29.2" + "vitest": "^0.34.6" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 322480909..0070c595b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -201,12 +201,15 @@ importers: specifier: workspace:* version: link:../../packages/core devDependencies: - '@types/node': - specifier: ^18.11.18 - version: 18.16.18 abitype: specifier: ^0.8.11 version: 0.8.11(typescript@5.1.3) + eslint: + specifier: ^8.43.0 + version: 8.43.0 + eslint-config-ponder: + specifier: workspace:* + version: link:../../packages/eslint-config-ponder typescript: specifier: ^5.1.3 version: 5.1.3 @@ -406,6 +409,12 @@ importers: viem: specifier: ^1.2.6 version: 1.2.6(typescript@5.1.3) + vite: + specifier: ^4.5.0 + version: 4.5.0(@types/node@18.16.18) + vite-node: + specifier: ^0.34.6 + version: 0.34.6(@types/node@18.16.18) devDependencies: '@types/babel__code-frame': specifier: ^7.0.3 @@ -461,9 +470,6 @@ importers: typescript: specifier: ^5.1.3 version: 5.1.3 - vite: - specifier: ^4.5.0 - version: 4.5.0(@types/node@18.16.18) vitest: specifier: ^0.34.6 version: 0.34.6 @@ -523,8 +529,8 @@ importers: specifier: ^5.1.3 version: 5.1.3 vitest: - specifier: ^0.29.2 - version: 0.29.2 + specifier: ^0.34.6 + version: 0.34.6 packages/eslint-config-ponder: dependencies: @@ -2035,7 +2041,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-arm64@0.18.4: @@ -2053,7 +2058,6 @@ packages: cpu: [arm] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-arm@0.18.4: @@ -2071,7 +2075,6 @@ packages: cpu: [x64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-x64@0.18.4: @@ -2089,7 +2092,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/darwin-arm64@0.18.4: @@ -2107,7 +2109,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/darwin-x64@0.18.4: @@ -2125,7 +2126,6 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-arm64@0.18.4: @@ -2143,7 +2143,6 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-x64@0.18.4: @@ -2161,7 +2160,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm64@0.18.4: @@ -2179,7 +2177,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm@0.18.4: @@ -2197,7 +2194,6 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ia32@0.18.4: @@ -2224,7 +2220,6 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-loong64@0.18.4: @@ -2242,7 +2237,6 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-mips64el@0.18.4: @@ -2260,7 +2254,6 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ppc64@0.18.4: @@ -2278,7 +2271,6 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-riscv64@0.18.4: @@ -2296,7 +2288,6 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-s390x@0.18.4: @@ -2314,7 +2305,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-x64@0.18.4: @@ -2332,7 +2322,6 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true - dev: true optional: true /@esbuild/netbsd-x64@0.18.4: @@ -2350,7 +2339,6 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true - dev: true optional: true /@esbuild/openbsd-x64@0.18.4: @@ -2368,7 +2356,6 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true - dev: true optional: true /@esbuild/sunos-x64@0.18.4: @@ -2386,7 +2373,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-arm64@0.18.4: @@ -2404,7 +2390,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-ia32@0.18.4: @@ -2422,7 +2407,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-x64@0.18.4: @@ -3511,12 +3495,6 @@ packages: '@types/chai': 4.3.4 dev: true - /@types/chai-subset@1.3.4: - resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==} - dependencies: - '@types/chai': 4.3.9 - dev: true - /@types/chai@4.3.4: resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} dev: true @@ -3687,7 +3665,6 @@ packages: /@types/node@18.16.18: resolution: {integrity: sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==} - dev: true /@types/node@8.10.66: resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} @@ -4127,14 +4104,6 @@ packages: - utf-8-validate dev: true - /@vitest/expect@0.29.2: - resolution: {integrity: sha512-wjrdHB2ANTch3XKRhjWZN0UueFocH0cQbi2tR5Jtq60Nb3YOSmakjdAvUa2JFBu/o8Vjhj5cYbcMXkZxn1NzmA==} - dependencies: - '@vitest/spy': 0.29.2 - '@vitest/utils': 0.29.2 - chai: 4.3.7 - dev: true - /@vitest/expect@0.34.6: resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} dependencies: @@ -4143,14 +4112,6 @@ packages: chai: 4.3.10 dev: true - /@vitest/runner@0.29.2: - resolution: {integrity: sha512-A1P65f5+6ru36AyHWORhuQBJrOOcmDuhzl5RsaMNFe2jEkoj0faEszQS4CtPU/LxUYVIazlUtZTY0OEZmyZBnA==} - dependencies: - '@vitest/utils': 0.29.2 - p-limit: 4.0.0 - pathe: 1.1.0 - dev: true - /@vitest/runner@0.34.6: resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} dependencies: @@ -4167,28 +4128,12 @@ packages: pretty-format: 29.7.0 dev: true - /@vitest/spy@0.29.2: - resolution: {integrity: sha512-Hc44ft5kaAytlGL2PyFwdAsufjbdOvHklwjNy/gy/saRbg9Kfkxfh+PklLm1H2Ib/p586RkQeNFKYuJInUssyw==} - dependencies: - tinyspy: 1.1.1 - dev: true - /@vitest/spy@0.34.6: resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} dependencies: tinyspy: 2.2.0 dev: true - /@vitest/utils@0.29.2: - resolution: {integrity: sha512-F14/Uc+vCdclStS2KEoXJlOLAEyqRhnw0gM27iXw9bMTcyKRPJrQ+rlC6XZ125GIPvvKYMPpVxNhiou6PsEeYQ==} - dependencies: - cli-truncate: 3.1.0 - diff: 5.1.0 - loupe: 2.3.6 - picocolors: 1.0.0 - pretty-format: 27.5.1 - dev: true - /@vitest/utils@0.34.6: resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} dependencies: @@ -4319,22 +4264,11 @@ packages: engines: {node: '>=0.4.0'} dev: true - /acorn-walk@8.3.0: - resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} - engines: {node: '>=0.4.0'} - dev: true - /acorn@8.10.0: resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} engines: {node: '>=0.4.0'} hasBin: true - /acorn@8.11.2: - resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -5045,19 +4979,6 @@ packages: type-detect: 4.0.8 dev: true - /chai@4.3.7: - resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} - engines: {node: '>=4'} - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.2 - deep-eql: 4.1.3 - get-func-name: 2.0.0 - loupe: 2.3.6 - pathval: 1.1.1 - type-detect: 4.0.8 - dev: true - /chalk@2.3.0: resolution: {integrity: sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==} engines: {node: '>=4'} @@ -5115,10 +5036,6 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true - /check-error@1.0.2: - resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} - dev: true - /check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} dependencies: @@ -6082,6 +5999,7 @@ packages: /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} engines: {node: '>=0.3.1'} + dev: false /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -6627,7 +6545,6 @@ packages: '@esbuild/win32-arm64': 0.18.20 '@esbuild/win32-ia32': 0.18.20 '@esbuild/win32-x64': 0.18.20 - dev: true /esbuild@0.18.4: resolution: {integrity: sha512-9rxWV/Cb2DMUXfe9aUsYtqg0KTlw146ElFH22kYeK9KVV1qT082X4lpmiKsa12ePiCcIcB686TQJxaGAa9TFvA==} @@ -7544,10 +7461,6 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: true - /get-func-name@2.0.0: - resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} - dev: true - /get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} dev: true @@ -9188,12 +9101,6 @@ packages: dependencies: js-tokens: 4.0.0 - /loupe@2.3.6: - resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} - dependencies: - get-func-name: 2.0.0 - dev: true - /loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} dependencies: @@ -10066,23 +9973,13 @@ packages: hasBin: true dev: true - /mlly@1.1.1: - resolution: {integrity: sha512-Jnlh4W/aI4GySPo6+DyTN17Q75KKbLTyFK8BrGhjNP4rxuUjbRWhE6gHg3bs33URWAF44FRm7gdQA348i3XxRw==} - dependencies: - acorn: 8.10.0 - pathe: 1.1.0 - pkg-types: 1.0.2 - ufo: 1.1.1 - dev: true - /mlly@1.4.2: resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} dependencies: - acorn: 8.11.2 + acorn: 8.10.0 pathe: 1.1.1 pkg-types: 1.0.3 ufo: 1.3.1 - dev: true /module-alias@2.2.2: resolution: {integrity: sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==} @@ -10815,13 +10712,8 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - /pathe@1.1.0: - resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} - dev: true - /pathe@1.1.1: resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} - dev: true /pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} @@ -10961,21 +10853,12 @@ packages: find-up: 4.1.0 dev: true - /pkg-types@1.0.2: - resolution: {integrity: sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==} - dependencies: - jsonc-parser: 3.2.0 - mlly: 1.1.1 - pathe: 1.1.0 - dev: true - /pkg-types@1.0.3: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} dependencies: jsonc-parser: 3.2.0 mlly: 1.4.2 pathe: 1.1.1 - dev: true /pkg-up@3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} @@ -11026,7 +10909,6 @@ packages: nanoid: 3.3.6 picocolors: 1.0.0 source-map-js: 1.0.2 - dev: true /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} @@ -11097,15 +10979,6 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - /pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - dev: true - /pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -11322,10 +11195,6 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true - /react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - dev: true - /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true @@ -11765,7 +11634,6 @@ packages: hasBin: true optionalDependencies: fsevents: 2.3.3 - dev: true /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -12198,10 +12066,6 @@ packages: engines: {node: '>= 0.8'} dev: false - /std-env@3.3.2: - resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==} - dev: true - /std-env@3.4.3: resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} dev: true @@ -12380,12 +12244,6 @@ packages: acorn: 8.10.0 dev: true - /strip-literal@1.3.0: - resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} - dependencies: - acorn: 8.11.2 - dev: true - /style-to-object@0.4.1: resolution: {integrity: sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==} dependencies: @@ -12663,29 +12521,15 @@ packages: globrex: 0.1.2 dev: true - /tinybench@2.4.0: - resolution: {integrity: sha512-iyziEiyFxX4kyxSp+MtY1oCH/lvjH3PxFN8PGCDeqcZWAJ/i+9y+nL85w99PxVzrIvew/GSkSbDYtiGVa85Afg==} - dev: true - /tinybench@2.5.1: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} dev: true - /tinypool@0.3.1: - resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==} - engines: {node: '>=14.0.0'} - dev: true - /tinypool@0.7.0: resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} engines: {node: '>=14.0.0'} dev: true - /tinyspy@1.1.1: - resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==} - engines: {node: '>=14.0.0'} - dev: true - /tinyspy@2.2.0: resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} engines: {node: '>=14.0.0'} @@ -13025,13 +12869,8 @@ packages: hasBin: true dev: false - /ufo@1.1.1: - resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} - dev: true - /ufo@1.3.1: resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} - dev: true /uint8arrays@3.1.1: resolution: {integrity: sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==} @@ -13390,28 +13229,6 @@ packages: - utf-8-validate - zod - /vite-node@0.29.2(@types/node@18.16.18): - resolution: {integrity: sha512-5oe1z6wzI3gkvc4yOBbDBbgpiWiApvuN4P55E8OI131JGrSuo4X3SOZrNmZYo4R8Zkze/dhi572blX0zc+6SdA==} - engines: {node: '>=v14.16.0'} - hasBin: true - dependencies: - cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) - mlly: 1.1.1 - pathe: 1.1.0 - picocolors: 1.0.0 - vite: 4.5.0(@types/node@18.16.18) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - /vite-node@0.34.6(@types/node@18.16.18): resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} @@ -13432,7 +13249,6 @@ packages: - sugarss - supports-color - terser - dev: true /vite@4.5.0(@types/node@18.16.18): resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} @@ -13468,63 +13284,6 @@ packages: rollup: 3.29.4 optionalDependencies: fsevents: 2.3.3 - dev: true - - /vitest@0.29.2: - resolution: {integrity: sha512-ydK9IGbAvoY8wkg29DQ4ivcVviCaUi3ivuPKfZEVddMTenFHUfB8EEDXQV8+RasEk1ACFLgMUqAaDuQ/Nk+mQA==} - engines: {node: '>=v14.16.0'} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - dependencies: - '@types/chai': 4.3.4 - '@types/chai-subset': 1.3.3 - '@types/node': 18.16.18 - '@vitest/expect': 0.29.2 - '@vitest/runner': 0.29.2 - '@vitest/spy': 0.29.2 - '@vitest/utils': 0.29.2 - acorn: 8.10.0 - acorn-walk: 8.2.0 - cac: 6.7.14 - chai: 4.3.7 - debug: 4.3.4(supports-color@8.1.1) - local-pkg: 0.4.3 - pathe: 1.1.0 - picocolors: 1.0.0 - source-map: 0.6.1 - std-env: 3.3.2 - strip-literal: 1.0.1 - tinybench: 2.4.0 - tinypool: 0.3.1 - tinyspy: 1.1.1 - vite: 4.5.0(@types/node@18.16.18) - vite-node: 0.29.2(@types/node@18.16.18) - why-is-node-running: 2.2.2 - transitivePeerDependencies: - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true /vitest@0.34.6: resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} @@ -13558,15 +13317,15 @@ packages: optional: true dependencies: '@types/chai': 4.3.9 - '@types/chai-subset': 1.3.4 + '@types/chai-subset': 1.3.3 '@types/node': 18.16.18 '@vitest/expect': 0.34.6 '@vitest/runner': 0.34.6 '@vitest/snapshot': 0.34.6 '@vitest/spy': 0.34.6 '@vitest/utils': 0.34.6 - acorn: 8.11.2 - acorn-walk: 8.3.0 + acorn: 8.10.0 + acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.10 debug: 4.3.4(supports-color@8.1.1) @@ -13575,7 +13334,7 @@ packages: pathe: 1.1.1 picocolors: 1.0.0 std-env: 3.4.3 - strip-literal: 1.3.0 + strip-literal: 1.0.1 tinybench: 2.5.1 tinypool: 0.7.0 vite: 4.5.0(@types/node@18.16.18)