@seed-ship/mcp-ui-solid 6.7.0 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,14 @@
6
6
  */
7
7
 
8
8
  import { describe, it, expect, vi } from 'vitest'
9
- import { validateComponent, validateChartComponent, getIframeSandbox, validateIframeDomain } from './validation'
9
+ import {
10
+ validateComponent,
11
+ validateChartComponent,
12
+ validatePayloadSize,
13
+ getIframeSandbox,
14
+ validateIframeDomain,
15
+ DEFAULT_RESOURCE_LIMITS,
16
+ } from './validation'
10
17
  import type { UIComponent, ComponentType } from '../types'
11
18
 
12
19
  /** Helper to create a minimal valid UIComponent for testing */
@@ -191,6 +198,77 @@ describe('component-specific validation', () => {
191
198
  })
192
199
  })
193
200
 
201
+ describe('validatePayloadSize — payload size guard (v6.8.0: 50KB → 512KB)', () => {
202
+ /**
203
+ * Build a `map` component whose JSON payload is at least `targetBytes`,
204
+ * by appending valid GeoJSON Point features to a FeatureCollection.
205
+ * Deterministic — no randomness, no clock.
206
+ */
207
+ function mapComponentOfSize(targetBytes: number): UIComponent {
208
+ const component = makeComponent('map', {
209
+ center: { lat: 48.8566, lng: 2.3522 },
210
+ zoom: 12,
211
+ geojson: { type: 'FeatureCollection', features: [] as unknown[] },
212
+ })
213
+ const features = (component.params as any).geojson.features as unknown[]
214
+ while (JSON.stringify(component).length < targetBytes) {
215
+ // Grow in batches to keep the size loop cheap.
216
+ for (let i = 0; i < 200; i++) {
217
+ features.push({
218
+ type: 'Feature',
219
+ geometry: { type: 'Point', coordinates: [2.3522, 48.8566] },
220
+ properties: { i: features.length },
221
+ })
222
+ }
223
+ }
224
+ return component
225
+ }
226
+
227
+ it('the default ceiling is 512KB', () => {
228
+ expect(DEFAULT_RESOURCE_LIMITS.maxPayloadSize).toBe(512 * 1024)
229
+ })
230
+
231
+ it('accepts a map with valid GeoJSON ~350KB (rejected under the old 50KB/256KB limits)', () => {
232
+ const component = mapComponentOfSize(350 * 1024)
233
+ const size = JSON.stringify(component).length
234
+ expect(size).toBeGreaterThan(256 * 1024) // exceeds the interim 256KB ceiling
235
+ expect(size).toBeLessThan(512 * 1024) // under the NEW 512KB limit
236
+
237
+ expect(validatePayloadSize(component).valid).toBe(true)
238
+ // Full component validation also passes — no PAYLOAD_TOO_LARGE.
239
+ const result = validateComponent(component)
240
+ expect(result.errors?.some((e) => e.code === 'PAYLOAD_TOO_LARGE')).toBeFalsy()
241
+ expect(result.valid).toBe(true)
242
+ })
243
+
244
+ it('still rejects a map payload over 512KB', () => {
245
+ const component = mapComponentOfSize(600 * 1024)
246
+ expect(JSON.stringify(component).length).toBeGreaterThan(512 * 1024)
247
+
248
+ const sizeResult = validatePayloadSize(component)
249
+ expect(sizeResult.valid).toBe(false)
250
+ expect(sizeResult.errors?.[0].code).toBe('PAYLOAD_TOO_LARGE')
251
+
252
+ expect(validateComponent(component).errors?.some((e) => e.code === 'PAYLOAD_TOO_LARGE')).toBe(
253
+ true
254
+ )
255
+ })
256
+
257
+ it('still rejects an oversized NON-map payload (guard-rail intact for every type)', () => {
258
+ const component = makeComponent('text', { content: 'x'.repeat(600 * 1024) })
259
+ const result = validatePayloadSize(component)
260
+ expect(result.valid).toBe(false)
261
+ expect(result.errors?.[0].code).toBe('PAYLOAD_TOO_LARGE')
262
+ })
263
+
264
+ it('honours a caller-supplied lower limit (validation is not disabled)', () => {
265
+ // A consumer passing stricter limits keeps full control.
266
+ const component = mapComponentOfSize(60 * 1024)
267
+ const strict = { ...DEFAULT_RESOURCE_LIMITS, maxPayloadSize: 50 * 1024 }
268
+ expect(validatePayloadSize(component, strict).valid).toBe(false)
269
+ })
270
+ })
271
+
194
272
  describe('validateChartComponent — scatter/bubble/time-series', () => {
195
273
  it('validates scatter chart without labels', () => {
196
274
  const result = validateChartComponent({
@@ -122,7 +122,16 @@ function mapZodIssuesToErrors(
122
122
  export const DEFAULT_RESOURCE_LIMITS: ResourceLimits = {
123
123
  maxDataPoints: 1000,
124
124
  maxTableRows: 100,
125
- maxPayloadSize: 50 * 1024, // 50KB
125
+ // v6.8.0 raised 50KB → 512KB. The single payload-size guard is shared by
126
+ // every component type ; 50KB rejected otherwise-valid `map` components
127
+ // carrying a realistic `params.geojson` FeatureCollection (a dense
128
+ // multi-feature map — e.g. a département-wide choropleth — runs 300-500KB
129
+ // even after reasonable geometry simplification). 512KB leaves real
130
+ // headroom for that while still rejecting runaway payloads ; genuinely
131
+ // large datasets belong in vector tiles (PMTiles), not inline GeoJSON.
132
+ // The guard itself (`validatePayloadSize`) is unchanged — only the
133
+ // default ceiling moved.
134
+ maxPayloadSize: 512 * 1024, // 512KB
126
135
  renderTimeout: 5000, // 5 seconds
127
136
  }
128
137
 
@@ -75,7 +75,7 @@ export interface ResourceLimits {
75
75
  maxTableRows: number
76
76
 
77
77
  /**
78
- * Max payload size in bytes (default: 50KB)
78
+ * Max payload size in bytes (default: 512KB)
79
79
  */
80
80
  maxPayloadSize: number
81
81