From 9fc42e80bc36f81eb161a3a269ee0c26384b49c4 Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Fri, 28 Aug 2026 13:06:50 +0800 Subject: [PATCH] fix(database): preserve JSON field setters --- .../fields/rich-text-sanitization.test.ts | 35 +++++++++++++++++++ .../core/database/src/fields/json-field.ts | 15 +++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/core/database/src/__tests__/fields/rich-text-sanitization.test.ts b/packages/core/database/src/__tests__/fields/rich-text-sanitization.test.ts index e8bdad2186a..9c6393c4d8b 100644 --- a/packages/core/database/src/__tests__/fields/rich-text-sanitization.test.ts +++ b/packages/core/database/src/__tests__/fields/rich-text-sanitization.test.ts @@ -14,6 +14,10 @@ const fieldContext = { collection: {}, database: { inDialect: () => false, + sequelize: { + getDialect: () => 'sqlite', + normalizeDataType: (dataType: unknown) => dataType, + }, }, }; @@ -44,6 +48,37 @@ describe('rich text field sanitization', () => { expect(field.setter(value)).toBe(value); }); + it('does not install a Sequelize setter on ordinary JSON fields', () => { + const customSetter = vi.fn(); + const field = new JsonField({ type: 'json', name: 'content', set: customSetter }, fieldContext as never); + + expect(field.additionalSequelizeOptions()).toEqual({}); + expect(field.toSequelize().set).toBe(customSetter); + }); + + it('preserves a rich text JSON custom setter and sanitizes its output', () => { + const customSetter = vi.fn(function ( + this: { setDataValue: (name: string, value: unknown) => void }, + value: unknown, + ) { + this.setDataValue('content', `${value}`); + }); + const field = new JsonField( + { type: 'json', name: 'content', interface: 'richText', set: customSetter }, + fieldContext as never, + ); + const values = new Map(); + const model = { + getDataValue: (name: string) => values.get(name), + setDataValue: (name: string, value: unknown) => values.set(name, value), + }; + + field.toSequelize().set.call(model, '

safe

'); + + expect(customSetter).toHaveBeenCalledWith('

safe

'); + expect(values.get('content')).toBe('

safe

'); + }); + it('leaves non-string JSON rich text values unchanged', () => { const field = new JsonField({ type: 'json', name: 'content', interface: 'richText' }, fieldContext as never); const value = { delta: [{ insert: '' }] }; diff --git a/packages/core/database/src/fields/json-field.ts b/packages/core/database/src/fields/json-field.ts index d0a2f70f232..7d8fcb16ab9 100644 --- a/packages/core/database/src/fields/json-field.ts +++ b/packages/core/database/src/fields/json-field.ts @@ -30,11 +30,24 @@ export class JsonField extends Field { } additionalSequelizeOptions() { - const { name } = this.options; + if (this.options.interface !== 'richText') { + return {}; + } + + const { name, set: originalSetter } = this.options; const normalizeValue = (value: unknown) => this.normalizeValue(value); return { set(value) { + if (typeof originalSetter === 'function') { + originalSetter.call(this, normalizeValue(value)); + const currentValue = this.getDataValue(name); + const normalizedValue = normalizeValue(currentValue); + if (!Object.is(normalizedValue, currentValue)) { + this.setDataValue(name, normalizedValue); + } + return; + } this.setDataValue(name, normalizeValue(value)); }, };