Repro
const SymCols = Symbol.for('test:cols');
class TBase {
static Symbol = { Columns: SymCols };
[SymCols]: any;
}
const t: any = new TBase();
t[TBase.Symbol.Columns] = { id: { name: 'id' }, name: { name: 'name' } };
console.log('before assign:', t[TBase.Symbol.Columns]);
Object.assign(t, { extraField: 1 });
console.log('after assign:', t[TBase.Symbol.Columns]);
Bun output (correct):
before assign: { id: { name: 'id' }, name: { name: 'name' } }
after assign: { id: { name: 'id' }, name: { name: 'name' } }
Perry output (bug):
before assign: { id: { name: 'id' }, name: { name: 'name' } }
after assign: undefined
Spec
Object.assign(target, ...sources) only enumerates own enumerable string-keyed and Symbol-keyed properties on each source and assigns them to target. Properties already on target (string-keyed or Symbol-keyed) are preserved unless overwritten by a source. Perry retains string-keyed target properties through the assign but drops Symbol-keyed ones.
Why it matters for #488
drizzle-orm's pgTableWithSchema assembles a table instance like this (pg-core/table.js:42-53):
const rawTable = new PgTable(name, schema, baseName);
// ... build columns ...
const table = Object.assign(rawTable, builtColumns); // <-- Object.assign #1
table[Table.Symbol.Columns] = builtColumns; // <-- Symbol-keyed write
table[Table.Symbol.ExtraConfigColumns] = builtColumnsForExtraConfig;
return Object.assign(table, { enableRLS: ... }); // <-- Object.assign #2 drops the Symbols
By the time dialect.buildInsertQuery({ table }) runs (pg-core/dialect.js:358), table[Table.Symbol.Columns] reads as undefined, and the next line (Object.entries(undefined).filter(...)) SIGSEGVs in js_object_entries reading 0x11 as a pointer.
The drizzle case has the writes BEFORE the second Object.assign — even cleaner: any Symbol property the writer set is wiped by the trailing Object.assign(table, { enableRLS }). The repro above mirrors the same shape.
Where I'd start digging
crates/perry-runtime/src/object.rs — js_object_assign / wherever Object.assign target preservation lives. The likely shape is that the implementation copies own string-keyed properties from target to a fresh object, copies sources, and returns the new object — without iterating the target's Symbol-keyed side table (SYMBOL_PROPERTIES or whatever it's called in v0.5.622's groundwork).
Side benefit of fixing this: the Object.assign-loses-class-id bug surfaced earlier in the session (Object.assign'd instance returning false for instanceof PgTable) might be fixable in the same pass — preserve the target's GC class_id and SYMBOL_PROPERTIES side-table when constructing the result.
Workaround
User-side: assign Symbol-keyed properties AFTER all Object.assign calls. drizzle's source order makes this hard to monkey-patch from outside the package; cleanest fix is on perry's side.
Refs
Blocks #488 (drizzle + @perryts/postgres acceptance) along with #588.
After this lands, the next blocker on #488 is whatever fires after dialect.buildInsertQuery succeeds — haven't reached that frame yet.
Repro
Bun output (correct):
Perry output (bug):
Spec
Object.assign(target, ...sources)only enumerates own enumerable string-keyed and Symbol-keyed properties on each source and assigns them to target. Properties already ontarget(string-keyed or Symbol-keyed) are preserved unless overwritten by a source. Perry retains string-keyedtargetproperties through the assign but drops Symbol-keyed ones.Why it matters for #488
drizzle-orm's
pgTableWithSchemaassembles a table instance like this (pg-core/table.js:42-53):By the time
dialect.buildInsertQuery({ table })runs (pg-core/dialect.js:358),table[Table.Symbol.Columns]reads asundefined, and the next line (Object.entries(undefined).filter(...)) SIGSEGVs injs_object_entriesreading0x11as a pointer.The drizzle case has the writes BEFORE the second Object.assign — even cleaner: any Symbol property the writer set is wiped by the trailing
Object.assign(table, { enableRLS }). The repro above mirrors the same shape.Where I'd start digging
crates/perry-runtime/src/object.rs—js_object_assign/ wherever Object.assign target preservation lives. The likely shape is that the implementation copies own string-keyed properties from target to a fresh object, copies sources, and returns the new object — without iterating the target's Symbol-keyed side table (SYMBOL_PROPERTIESor whatever it's called in v0.5.622's groundwork).Side benefit of fixing this: the Object.assign-loses-class-id bug surfaced earlier in the session (
Object.assign'd instance returningfalseforinstanceof PgTable) might be fixable in the same pass — preserve the target's GC class_id and SYMBOL_PROPERTIES side-table when constructing the result.Workaround
User-side: assign Symbol-keyed properties AFTER all
Object.assigncalls. drizzle's source order makes this hard to monkey-patch from outside the package; cleanest fix is on perry's side.Refs
Blocks #488 (drizzle + @perryts/postgres acceptance) along with #588.
After this lands, the next blocker on #488 is whatever fires after
dialect.buildInsertQuerysucceeds — haven't reached that frame yet.