diff --git a/.changeset/plain-trams-kneel.md b/.changeset/plain-trams-kneel.md new file mode 100644 index 0000000000..a75daeea57 --- /dev/null +++ b/.changeset/plain-trams-kneel.md @@ -0,0 +1,6 @@ +--- +'@envelop/core': patch +--- + +Fix getters and setters being stripped away. The value was copied at plugin creation instead of +copying the getter and setter (if any). diff --git a/packages/core/src/plugin-with-state.ts b/packages/core/src/plugin-with-state.ts index 370a540b93..270c4a17c4 100644 --- a/packages/core/src/plugin-with-state.ts +++ b/packages/core/src/plugin-with-state.ts @@ -62,9 +62,16 @@ export function withState< function addStateGetters(src: any) { const result: any = {}; - for (const [hookName, hook] of Object.entries(src) as any) { + // Use the property descriptors to keep potential getters and setters, or not enumerable props + const properties = Object.entries(Object.getOwnPropertyDescriptors(src)); + for (const [hookName, descriptor] of properties) { + const hook = descriptor.value; if (typeof hook !== 'function') { - result[hookName] = hook; + descriptor.get &&= () => src[hookName]; + descriptor.set &&= value => { + src[hookName] = value; + }; + Object.defineProperty(result, hookName, descriptor); } else { result[hookName] = { [hook.name](payload: any, ...args: any[]) { @@ -88,11 +95,11 @@ export function withState< return result; } - const { instrumentation, ...hooks } = pluginFactory(getState as any); + const plugin = pluginFactory(getState as any); - const pluginWithState = addStateGetters(hooks); - if (instrumentation) { - pluginWithState.instrumentation = addStateGetters(instrumentation); + const pluginWithState = addStateGetters(plugin); + if (plugin.instrumentation) { + pluginWithState.instrumentation = addStateGetters(plugin.instrumentation); } return pluginWithState as P; diff --git a/packages/core/test/plugin-with-state.spec.ts b/packages/core/test/plugin-with-state.spec.ts index 66f1ddd52e..0d10f0afc0 100644 --- a/packages/core/test/plugin-with-state.spec.ts +++ b/packages/core/test/plugin-with-state.spec.ts @@ -77,7 +77,35 @@ describe('pluginWithState', () => { ); }); - describe('instruments', () => { + it('should allow to have getters and setters', () => { + const called = { getter: false, setter: false }; + const plugin = withState<{ value: string; instrumentation?: never }>(() => ({ + get value() { + called.getter = true; + return 'test'; + }, + set value(value) { + called.setter = true; + expect(value).toBe('value'); + }, + })); + + plugin.value = 'value'; + expect(plugin.value).toBe('test'); + expect(called).toEqual({ getter: true, setter: true }); + }); + + it('should allow to have not enumerable', () => { + const plugin = {}; + Object.defineProperty(plugin, 'hidden', { value: 'test' }); + + const pluginWithState = withState(() => plugin); + // @ts-expect-error + expect(pluginWithState['hidden']).toBe('test'); + expect(Object.keys(plugin)).toEqual([]); + }); + + describe('instrumentation', () => { const plugin = withState(() => ({ instrumentation: { hook: (...args: any[]): any => args } })); it('should add request state', () => {