@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.
- package/CHANGELOG.md +21 -0
- package/dist/services/validation.cjs +11 -2
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +11 -2
- package/dist/services/validation.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/services/validation.test.ts +79 -1
- package/src/services/validation.ts +10 -1
- package/src/types/index.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { describe, it, expect, vi } from 'vitest'
|
|
9
|
-
import {
|
|
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
|
-
|
|
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
|
|