-
-
Notifications
You must be signed in to change notification settings - Fork 530
Description
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:
-
Kysely freezes its AST nodes via
Object.freeze()(e.g.,ValuesNode.createreturnsfreeze({ kind: 'ValuesNode', values: freeze(values) })). This makes properties non-configurable and non-writable. -
effectifyWithwraps every non-function property recursively (line 39 ofpatch.ts):return effectifyWith(target[prop], commit, whitelist);
This creates a new Proxy each time, but the JS spec requires that a Proxy's
gettrap 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 TypeErrorTrigger 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.0kysely: 0.28.11- Runtime: Bun 1.3.6
Related
- From Discord: Resolving Kysely Integration Issues in Effect with SqlResolver #3299 -- added
compileto the method whitelist, which fixed a different manifestation of the same underlying Proxy issue, but did not address frozen non-function properties.