From c2748bc7fdf5b26930285535156cc85b57f1b892 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 00:54:01 +0000 Subject: [PATCH 1/3] Replace custom deepEqual with dequal/lite Drop the hand-rolled deep equality utility in favor of dequal/lite, which is well-tested and handles cases the custom one didn't (Date, RegExp, NaN, Array.length mismatches, etc). Externalize dequal in ESM/CJS so consumers dedupe the dependency, but bundle it into the UMD output so the script-tag distribution stays self-contained. --- packages/plugin-sdk/package.json | 3 + packages/plugin-sdk/src/react/hooks.ts | 4 +- .../src/utils/__tests__/deepEqual.test.ts | 91 ------------------- packages/plugin-sdk/src/utils/deepEqual.ts | 23 ----- packages/plugin-sdk/tsdown.config.ts | 8 ++ yarn.lock | 1 + 6 files changed, 14 insertions(+), 116 deletions(-) delete mode 100644 packages/plugin-sdk/src/utils/__tests__/deepEqual.test.ts delete mode 100644 packages/plugin-sdk/src/utils/deepEqual.ts diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index 5098326..36830b2 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -14,6 +14,9 @@ "test:watch": "vitest", "types": "yarn g:types" }, + "dependencies": { + "dequal": "^2.0.3" + }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, diff --git a/packages/plugin-sdk/src/react/hooks.ts b/packages/plugin-sdk/src/react/hooks.ts index adfd0b1..f3b9700 100644 --- a/packages/plugin-sdk/src/react/hooks.ts +++ b/packages/plugin-sdk/src/react/hooks.ts @@ -1,3 +1,4 @@ +import { dequal } from 'dequal/lite'; import * as React from 'react'; import { @@ -10,7 +11,6 @@ import { PluginStyle, UrlParameter, } from '../types'; -import { deepEqual } from '../utils/deepEqual'; import { PluginContext } from './Context'; @@ -34,7 +34,7 @@ export function useEditorPanelConfig( React.useEffect(() => { if (nextOptions == null) return; - if (!deepEqual(nextOptions, optionsRef.current)) { + if (!dequal(nextOptions, optionsRef.current)) { client.config.configureEditorPanel(nextOptions); optionsRef.current = nextOptions; } diff --git a/packages/plugin-sdk/src/utils/__tests__/deepEqual.test.ts b/packages/plugin-sdk/src/utils/__tests__/deepEqual.test.ts deleted file mode 100644 index a57783c..0000000 --- a/packages/plugin-sdk/src/utils/__tests__/deepEqual.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { deepEqual } from '../deepEqual'; - -describe('deepEqual', () => { - describe('primitive comparisons', () => { - it('returns true for strictly equal primitives', () => { - expect(deepEqual(1, 1)).toBe(true); - expect(deepEqual('a', 'a')).toBe(true); - expect(deepEqual(true, true)).toBe(true); - expect(deepEqual(false, false)).toBe(true); - expect(deepEqual(null, null)).toBe(true); - expect(deepEqual(undefined, undefined)).toBe(true); - }); - - it('returns true for the same reference', () => { - const sym = Symbol('x'); - expect(deepEqual(sym, sym)).toBe(true); - - const obj = { a: 1 }; - expect(deepEqual(obj, obj)).toBe(true); - }); - - it('returns falsy for non-equal primitives', () => { - expect(deepEqual(1, 2)).toBeFalsy(); - expect(deepEqual('a', 'b')).toBeFalsy(); - expect(deepEqual(true, false)).toBeFalsy(); - expect(deepEqual(null, undefined)).toBeFalsy(); - expect(deepEqual(0, '0')).toBeFalsy(); - }); - }); - - describe('object comparisons', () => { - it('returns true for two empty objects', () => { - expect(deepEqual({}, {})).toBe(true); - }); - - it('returns true for shallowly equal objects', () => { - expect(deepEqual({ a: 1, b: 'x' }, { a: 1, b: 'x' })).toBe(true); - }); - - it('returns false for objects with different key counts', () => { - expect(deepEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false); - expect(deepEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false); - }); - - it('returns false for objects with the same keys but different values', () => { - expect(deepEqual({ a: 1 }, { a: 2 })).toBe(false); - }); - - it('returns true for deeply nested equal objects', () => { - expect( - deepEqual( - { a: { b: { c: [1, 2, 3] } } }, - { a: { b: { c: [1, 2, 3] } } }, - ), - ).toBe(true); - }); - - it('returns false for deeply nested objects that differ', () => { - expect( - deepEqual( - { a: { b: { c: [1, 2, 3] } } }, - { a: { b: { c: [1, 2, 4] } } }, - ), - ).toBe(false); - }); - - it('compares arrays as objects', () => { - expect(deepEqual([1, 2, 3], [1, 2, 3])).toBe(true); - expect(deepEqual([1, 2], [1, 2, 3])).toBe(false); - expect(deepEqual([1, 2, 3], [3, 2, 1])).toBe(false); - }); - }); - - describe('mixed type comparisons', () => { - it('returns falsy when comparing an object against null', () => { - expect(deepEqual({}, null)).toBeFalsy(); - expect(deepEqual(null, {})).toBeFalsy(); - }); - - it('returns falsy when comparing an object against a primitive', () => { - expect(deepEqual({}, 1)).toBeFalsy(); - expect(deepEqual(1, {})).toBeFalsy(); - expect(deepEqual([], 'a')).toBeFalsy(); - }); - - it('returns falsy when comparing an object against undefined', () => { - expect(deepEqual({}, undefined)).toBeFalsy(); - expect(deepEqual(undefined, {})).toBeFalsy(); - }); - }); -}); diff --git a/packages/plugin-sdk/src/utils/deepEqual.ts b/packages/plugin-sdk/src/utils/deepEqual.ts deleted file mode 100644 index 06210f4..0000000 --- a/packages/plugin-sdk/src/utils/deepEqual.ts +++ /dev/null @@ -1,23 +0,0 @@ -function isObject(obj: any) { - if (typeof obj === 'object' && obj != null) { - return true; - } else { - return false; - } -} - -export function deepEqual(obj1: any, obj2: any) { - if (obj1 === obj2) { - return true; - } else if (isObject(obj1) && isObject(obj2)) { - if (Object.keys(obj1).length !== Object.keys(obj2).length) { - return false; - } - for (const prop in obj1) { - if (!deepEqual(obj1[prop], obj2[prop])) { - return false; - } - } - return true; - } -} diff --git a/packages/plugin-sdk/tsdown.config.ts b/packages/plugin-sdk/tsdown.config.ts index fdb5930..85d6f50 100644 --- a/packages/plugin-sdk/tsdown.config.ts +++ b/packages/plugin-sdk/tsdown.config.ts @@ -11,5 +11,13 @@ export default mergeConfig( define: { __VERSION__: JSON.stringify(packageJson.version), }, + format: { + umd: { + deps: { + alwaysBundle: id => id === 'dequal' || id.startsWith('dequal/'), + skipNodeModulesBundle: false, + }, + }, + }, }), ); diff --git a/yarn.lock b/yarn.lock index 6620614..62cd342 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1230,6 +1230,7 @@ __metadata: version: 0.0.0-use.local resolution: "@sigmacomputing/plugin@workspace:packages/plugin-sdk" dependencies: + dequal: "npm:^2.0.3" tsdown: "npm:^0.21.10" vitest: "npm:^4.1.5" peerDependencies: From 1a42520ca423e5d09cdba04bf28362498de70455 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 01:00:55 +0000 Subject: [PATCH 2/3] Drop redundant skipNodeModulesBundle: false default --- packages/plugin-sdk/tsdown.config.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/plugin-sdk/tsdown.config.ts b/packages/plugin-sdk/tsdown.config.ts index 85d6f50..026fa0e 100644 --- a/packages/plugin-sdk/tsdown.config.ts +++ b/packages/plugin-sdk/tsdown.config.ts @@ -1,4 +1,8 @@ -import { defineConfig, mergeConfig } from 'tsdown'; +import { + defineConfig, + mergeConfig, + type ResolvedDepsConfig, +} from 'tsdown'; // @ts-ignore - base config is defined outside of this package import baseConfig from '../../tsdown.base.ts'; @@ -14,9 +18,9 @@ export default mergeConfig( format: { umd: { deps: { - alwaysBundle: id => id === 'dequal' || id.startsWith('dequal/'), - skipNodeModulesBundle: false, - }, + alwaysBundle: (id: string) => + id === 'dequal' || id.startsWith('dequal/'), + } as unknown as ResolvedDepsConfig, }, }, }), From 5406903ca8c4de1a4d467be9ba8a26c82456ce6f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 01:08:24 +0000 Subject: [PATCH 3/3] Revert: keep skipNodeModulesBundle: false to avoid type cast --- packages/plugin-sdk/tsdown.config.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/plugin-sdk/tsdown.config.ts b/packages/plugin-sdk/tsdown.config.ts index 026fa0e..85d6f50 100644 --- a/packages/plugin-sdk/tsdown.config.ts +++ b/packages/plugin-sdk/tsdown.config.ts @@ -1,8 +1,4 @@ -import { - defineConfig, - mergeConfig, - type ResolvedDepsConfig, -} from 'tsdown'; +import { defineConfig, mergeConfig } from 'tsdown'; // @ts-ignore - base config is defined outside of this package import baseConfig from '../../tsdown.base.ts'; @@ -18,9 +14,9 @@ export default mergeConfig( format: { umd: { deps: { - alwaysBundle: (id: string) => - id === 'dequal' || id.startsWith('dequal/'), - } as unknown as ResolvedDepsConfig, + alwaysBundle: id => id === 'dequal' || id.startsWith('dequal/'), + skipNodeModulesBundle: false, + }, }, }, }),