Skip to content

sql-kysely: Proxy invariant error on frozen Kysely insert-value nodes #6112

@Ethan826

Description

@Ethan826

Description

The recursive Proxy wrapper in @effect/sql-kysely's effectifyWith (in packages/sql-kysely/src/internal/patch.ts) violates the JS Proxy invariant when it wraps properties of frozen objects created by Kysely's internal AST node constructors.

Error

TypeError: Proxy handler's 'get' result of a non-configurable and non-writable property should be the same value as the target's property
    at get (patch.js:33)
    at compileList (default-query-compiler.js:136)
    at visitValues (default-query-compiler.js:223)
    at visitNode (operation-node-visitor.js:109)
    at visitInsertQuery (default-query-compiler.js:195)
    at visitNode (operation-node-visitor.js:109)
    at compileQuery (default-query-compiler.js:24)
    at <anonymous> (patch.js:49)

Root cause

Two mechanisms conflict:

  1. Kysely freezes its AST nodes via Object.freeze() (e.g., ValuesNode.create returns freeze({ kind: 'ValuesNode', values: freeze(values) })). This makes properties non-configurable and non-writable.

  2. effectifyWith wraps every non-function property recursively (line 39 of patch.ts):

    return effectifyWith(target[prop], commit, whitelist);

    This creates a new Proxy each time, but the JS spec requires that a Proxy's get trap must return the identical value for non-configurable, non-writable properties.

Reproduction

function effectifyWith(obj) {
  if (typeof obj !== "object" || obj === null) return obj;
  return new Proxy(obj, {
    get(target, prop) {
      if (typeof target[prop] === "function") {
        return (...args) => effectifyWith(target[prop].call(target, ...args));
      }
      return effectifyWith(target[prop]); // violates invariant on frozen props
    },
  });
}

const frozen = Object.freeze({ kind: "ValuesNode", values: Object.freeze([1]) });
const proxied = effectifyWith(frozen);
proxied.values; // throws TypeError

Trigger in practice

A multi-row insertInto().values([...]) where Kysely's ValuesNode.create freezes the values array. During .compile(), the query compiler traverses the AST and accesses frozen properties through the Proxy, triggering the invariant error.

Suggested fix

Add a descriptor check in the get trap to return frozen properties as-is:

get(target, prop) {
  const desc = Object.getOwnPropertyDescriptor(target, prop);
  if (desc && !desc.configurable && !desc.writable) {
    return target[prop]; // must return original value per Proxy invariant
  }
  // ... existing effectify logic
}

Alternatively, check Object.isFrozen(target) and skip wrapping entirely for frozen objects, since they are internal AST nodes that should never be effectified.

Environment

  • @effect/sql-kysely: 0.45.0
  • kysely: 0.28.11
  • Runtime: Bun 1.3.6

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions