Kysely: Proxy invariant violation: `effectifyWith` fails for non-configurable properties by johanneskares · Pull Request #5982 · Effect-TS/effect

Type

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Description

Bug Description

When using @effect/sql-kysely with Effect.fn, a TypeError is thrown due to JavaScript Proxy invariant violations. The error occurs because the effectifyWith proxy handler doesn't properly pass through non-configurable properties.

Error Messages

TypeError: 'get' on proxy: property 'Symbol(effect/Hash)' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value
TypeError: 'get' on proxy: property 'node' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value

Root Cause

The effectifyWith function in @effect/sql-kysely creates a Proxy that wraps Kysely query builders to make them Effect-compatible. However, the proxy's get handler violates a fundamental JavaScript Proxy invariant:

If the target object property is a non-configurable, non-writable data property, the proxy's get trap must return the same value as the target property.

The current implementation recursively wraps all property accesses, including:

  1. Symbol properties (like Symbol(effect/Hash))
  2. Non-configurable properties (like node on Kysely's internal AST)

When Effect.fn internally compares or hashes the returned value, it accesses these Symbol properties. The proxy returns a wrapped version instead of the actual value, triggering the invariant violation.

Reproduction

import { Effect } from "effect";
import { SqlKysely } from "@effect/sql-kysely";

// This works fine
const workingQuery = Effect.gen(function* () {
  const db = yield* SqlKysely;
  return yield* db.selectFrom("users").selectAll();
});

// This fails with proxy invariant violation
const brokenQuery = Effect.fn("brokenQuery")(function* () {
  const db = yield* SqlKysely;
  return yield* db.selectFrom("users").selectAll();
});

The bug only manifests when using Effect.fn because it performs internal operations (like hashing for tracing) that access Symbol properties on the returned Effect.

  • Related Issue 3299