Skip to content
LogoLogo

@tevm/effect

npm install --save-dev @tevm/effect

Five thin Effect wrappers around operations the rest of the pipeline does constantly: resolving modules, reading files, parsing JSON, and reporting errors.

These exist so that failures carry a _tag and can be caught selectively — which is exactly what the plugins do when they treat a missing tevm.config.json as a warning while still failing hard on a malformed one.

You do not need Effect in your own code to use the bundler. This package is for people extending the pipeline.

resolveSync(importPath, options) / resolveAsync(importPath, options)

Effect wrappers around the resolve package.

import { resolveSync } from '@tevm/effect'
import { runSync } from 'effect/Effect'
 
const path = runSync(resolveSync('@tevm/contract', { basedir: process.cwd() }))
console.log(path)

ParametersimportPath: string, and options, the resolve package's sync or async options (most usefully basedir).

Returns Effect<string, CouldNotResolveImportError, never> — the resolved absolute path.

Throws (as an Effect failure) CouldNotResolveImportError, whose message names both the import and the directory it was resolved from:

Could not resolve import <path> from <basedir>. Please check your remappings and libraries.

fileExists(path)

import { fileExists } from '@tevm/effect'
import { runPromise } from 'effect/Effect'
 
if (await runPromise(fileExists('./tevm.config.json'))) {
	console.log('found a config')
}

Returns Effect<boolean, never, never>. It cannot fail — a missing file and an unreadable one both come back false. Debug logs record the path and the answer.

parseJson(jsonStr)

import { parseJson } from '@tevm/effect'
import { runSync } from 'effect/Effect'
 
const config = runSync(
	parseJson('{ "foundryProject": true, /* comments are fine */ "libs": ["lib"], }'),
)

Returns Effect<unknown, ParseJsonError, never>.

Throws (as an Effect failure) ParseJsonError, with the underlying parse errors as its cause.

Parsing is done with jsonc-parser, so comments and trailing commas are accepted. That is deliberate: tsconfig.json files routinely contain both, and Tevm config can live inside one.

createRequireEffect(url)

import { createRequireEffect } from '@tevm/effect'
import { flatMap, runPromise } from 'effect/Effect'
 
const solc = await runPromise(
	createRequireEffect(import.meta.url).pipe(flatMap((require) => require('solc'))),
)

Returns Effect<(id: string) => Effect<unknown, RequireError, never>, CreateRequireError, never> — an Effect producing a require function that itself returns Effects.

Throws (as Effect failures) CreateRequireError if the require function cannot be constructed for url, and RequireError if the subsequent require fails.

This exists so CommonJS-only dependencies can be loaded from ESM without the call site having to care which module system it is in.

logAllErrors(e)

import { logAllErrors } from '@tevm/effect'
import { tapError } from 'effect/Effect'
 
const program = someEffect.pipe(tapError(logAllErrors))

Walks the cause chain of an error and logs every link, outermost cause first. Useful because the pipeline wraps errors several layers deep — a LoadConfigError caused by a FoundryNotFoundError caused by an ENOENT is otherwise reported as one unhelpful line.

Returns Effect<void, never, never>.

Error tags

Every error class here carries a literal _tag, which is what makes catchTag work:

Class_tag
CouldNotResolveImportError'CouldNotResolveImportError'
ParseJsonError'ParseJsonError'
CreateRequireError'CreateRequireError'
RequireError'RequireError'
import { catchTag, succeed } from 'effect/Effect'
 
const safe = parseJson(maybeInvalid).pipe(catchTag('ParseJsonError', () => succeed({})))