From 6c63b1635d78af8d790b7ff0fd1762120d4d5ba5 Mon Sep 17 00:00:00 2001 From: Andrew Dodson Date: Tue, 8 Sep 2026 05:22:27 +0100 Subject: [PATCH 1/2] fix(ts): allow Dare class methods to be enumerated --- src/index.ts | 8 ++++ src/mysql57.ts | 4 ++ src/postgres16.ts | 4 ++ src/sqlite.ts | 4 ++ src/utils/make_methods_enumerable.ts | 28 +++++++++++ test/specs/enumerability.spec.ts | 72 ++++++++++++++++++++++++++++ 6 files changed, 120 insertions(+) create mode 100644 src/utils/make_methods_enumerable.ts create mode 100644 test/specs/enumerability.spec.ts diff --git a/src/index.ts b/src/index.ts index fd328b3..cc34d20 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,8 @@ import getFieldAttributes from './utils/field_attributes.ts'; import extend from './utils/extend.ts'; +import makeMethodsEnumerable from './utils/make_methods_enumerable.ts'; + import clone from 'tricks/object/clone.js'; import format_request from './format_request.ts'; @@ -1440,6 +1442,12 @@ export default class Dare { } } +/* + * ES class methods are non-enumerable by default, + * restore the enumerable behaviour of the former prototype assignments + */ +makeMethodsEnumerable(Dare); + /** * Engine, database engine */ diff --git a/src/mysql57.ts b/src/mysql57.ts index 5c3611f..f35ec2b 100644 --- a/src/mysql57.ts +++ b/src/mysql57.ts @@ -1,4 +1,5 @@ import Dare, {type QueryOptions} from './index.ts'; +import makeMethodsEnumerable from './utils/make_methods_enumerable.ts'; import semverCompare from 'semver-compare'; /** @@ -82,6 +83,9 @@ class MySQL57Dare extends Dare { } } +// Restore the enumerable behaviour of the former prototype assignments +makeMethodsEnumerable(MySQL57Dare); + /** * Default engine for MySQL 5.7 */ diff --git a/src/postgres16.ts b/src/postgres16.ts index 9726a0e..bb9cbde 100644 --- a/src/postgres16.ts +++ b/src/postgres16.ts @@ -1,5 +1,6 @@ import SQL, {Sql, join} from 'sql-template-tag'; import Dare, {type QueryOptions} from './index.ts'; +import makeMethodsEnumerable from './utils/make_methods_enumerable.ts'; /** * PostgresDare @@ -104,6 +105,9 @@ class PostgresDare extends Dare { } } +// Restore the enumerable behaviour of the former prototype assignments +makeMethodsEnumerable(PostgresDare); + /** * Default engine for Postgres */ diff --git a/src/sqlite.ts b/src/sqlite.ts index a1d0ef1..0685901 100644 --- a/src/sqlite.ts +++ b/src/sqlite.ts @@ -1,5 +1,6 @@ import SQL, {Sql, raw} from 'sql-template-tag'; import Dare, {type QueryOptions} from './index.ts'; +import makeMethodsEnumerable from './utils/make_methods_enumerable.ts'; /** * SQLiteDare @@ -126,6 +127,9 @@ class SQLiteDare extends Dare { } } +// Restore the enumerable behaviour of the former prototype assignments +makeMethodsEnumerable(SQLiteDare); + /** * Default engine for SQLite */ diff --git a/src/utils/make_methods_enumerable.ts b/src/utils/make_methods_enumerable.ts new file mode 100644 index 0000000..8681349 --- /dev/null +++ b/src/utils/make_methods_enumerable.ts @@ -0,0 +1,28 @@ +/** + * Make the prototype methods of a class enumerable + * ES class methods are non-enumerable by default, whereas the previous + * `Class.prototype.method = function` assignments were enumerable. + * This restores the original enumerable behaviour. + * @param Class - Class whose prototype methods should be made enumerable + */ +export default function makeMethodsEnumerable(Class: { + prototype: object; +}): void { + for (const name of Object.getOwnPropertyNames(Class.prototype)) { + if (name === 'constructor') { + continue; + } + + const descriptor = Object.getOwnPropertyDescriptor( + Class.prototype, + name + ); + + if (descriptor?.enumerable === false) { + Object.defineProperty(Class.prototype, name, { + ...descriptor, + enumerable: true, + }); + } + } +} diff --git a/test/specs/enumerability.spec.ts b/test/specs/enumerability.spec.ts new file mode 100644 index 0000000..3f47d58 --- /dev/null +++ b/test/specs/enumerability.spec.ts @@ -0,0 +1,72 @@ +import assert from 'node:assert'; +import {describe, it} from 'node:test'; +import Dare from '../../src/index.ts'; +import MySQL57Dare from '../../src/mysql57.ts'; +import PostgresDare from '../../src/postgres16.ts'; +import SQLiteDare from '../../src/sqlite.ts'; + +/* + * ES class methods are non-enumerable by default + * Dare restores the enumerable behaviour of the former prototype assignments + * via makeMethodsEnumerable, so instance methods remain visible to `for...in` + */ +describe('enumerability', () => { + const engines = { + Dare, + MySQL57Dare, + PostgresDare, + SQLiteDare, + }; + + for (const [name, Klass] of Object.entries(engines)) { + describe(name, () => { + it('should define enumerable prototype methods', () => { + const methods = Object.getOwnPropertyNames( + Klass.prototype + ).filter(prop => prop !== 'constructor'); + + assert.ok(methods.length, 'has prototype methods'); + + for (const method of methods) { + const descriptor = Object.getOwnPropertyDescriptor( + Klass.prototype, + method + ); + assert.strictEqual( + descriptor?.enumerable, + true, + `${name}.prototype.${method} should be enumerable` + ); + } + }); + + it('should expose methods on instances via for...in', () => { + const instance = new Klass(); + + const enumerated = []; + for (const prop in instance) { + if (typeof instance[prop] === 'function') { + enumerated.push(prop); + } + } + + assert.ok( + enumerated.includes('get'), + 'get is enumerable on the instance' + ); + assert.ok( + enumerated.includes('post'), + 'post is enumerable on the instance' + ); + }); + + it('should keep the constructor non-enumerable', () => { + const descriptor = Object.getOwnPropertyDescriptor( + Klass.prototype, + 'constructor' + ); + assert.strictEqual(descriptor?.enumerable, false); + }); + }); + } +}); From c55bde17328fa18d65eb1165c5b8ef7bcfe4fdf8 Mon Sep 17 00:00:00 2001 From: Andrew Dodson Date: Tue, 8 Sep 2026 05:36:02 +0100 Subject: [PATCH 2/2] fix(ts): patch request body accepts only single item --- src/index.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index cc34d20..2cfa318 100644 --- a/src/index.ts +++ b/src/index.ts @@ -441,8 +441,13 @@ export type GetRequestOptions = Omit< */ export type PatchRequestOptions = Omit< RequestOptions, - 'fields' | 'groupby' | 'query' ->; + 'fields' | 'groupby' | 'query' | 'body' +> & { + /** + * Body containing new data + */ + body?: Record; +}; /** * Dare.post Request Options @@ -1035,9 +1040,9 @@ export default class Dare { * @returns Affected Rows statement */ async patch( - table: string | PatchRequestOptions, - filter?: Record, - body?: Record, + table: PatchRequestOptions['table'] | PatchRequestOptions, + filter?: PatchRequestOptions['filter'], + body?: PatchRequestOptions['body'], options: Omit = {} ): Promise { const opts: QueryOptions =