diff --git a/apps/website/components/vseed/VSeedRender.tsx b/apps/website/components/vseed/VSeedRender.tsx index 9d682f477a..206c05adac 100644 --- a/apps/website/components/vseed/VSeedRender.tsx +++ b/apps/website/components/vseed/VSeedRender.tsx @@ -70,8 +70,10 @@ export const VSeedRender = withVisible((props: { vseed: VSeed }) => { const builderRef = useRef(null) const dark = useDark() const selectedDimValueRef = useRef([]) + const hasValidDataset = Array.isArray(vseed.dataset) && vseed.dataset.length > 0 + useEffect(() => { - if (!ref.current) { + if (!hasValidDataset || !ref.current) { return } const theme = dark ? 'dark' : 'light' @@ -189,7 +191,23 @@ export const VSeedRender = withVisible((props: { vseed: VSeed }) => { window.table = tableInstance return () => tableInstance.release() } - }, [vseed, dark]) + }, [vseed, dark, hasValidDataset]) + + if (!hasValidDataset) { + return ( +
+ 暂无数据 +
+ ) + } return (
` | - | -| **undoManager** | `UndoManager` | - | | **chartType** | `ChartTypeBuilder` | - | | **measures** | `MeasuresBuilder` | - | | **dimensions** | `DimensionsBuilder` | - | @@ -15,6 +14,7 @@ | **theme** | `ThemeBuilder` | - | | **locale** | `LocaleBuilder` | - | | **limit** | `LimitBuilder` | - | +| **undoManager** | `UndoManager` | - | ## 方法 @@ -38,23 +38,28 @@ constructor(doc: Y.Doc) **定义**: ```typescript -applyUpdate(update: Uint8Array) +applyUpdate(update: Uint8Array, transactionOrigin: any): void ``` +**返回**: `void` + **参数**: | 参数 | 类型 | 说明 | | --- | --- | --- | | `update` | Uint8Array | - | +| `transactionOrigin` | any | - | ### encodeStateAsUpdate **定义**: ```typescript -encodeStateAsUpdate(targetStateVector: Uint8Array) +encodeStateAsUpdate(targetStateVector: Uint8Array): Uint8Array ``` +**返回**: `Uint8Array` + **参数**: | 参数 | 类型 | 说明 | @@ -91,6 +96,16 @@ build(): VBIDSL **返回**: `VBIDSL` +### isEmpty + +**定义**: + +```typescript +isEmpty(): boolean +``` + +**返回**: `boolean` + ### getSchema **定义**: diff --git a/apps/website/docs/zh-CN/vbi/api/dimensions.md b/apps/website/docs/zh-CN/vbi/api/dimensions.md index 680f602b2a..8a0a0126eb 100644 --- a/apps/website/docs/zh-CN/vbi/api/dimensions.md +++ b/apps/website/docs/zh-CN/vbi/api/dimensions.md @@ -11,14 +11,14 @@ **定义**: ```typescript -constructor(_doc: Y.Doc, dsl: Y.Map) +constructor(doc: Y.Doc, dsl: Y.Map) ``` **参数**: | 参数 | 类型 | 说明 | | --- | --- | --- | -| `_doc` | Y.Doc | - | +| `doc` | Y.Doc | - | | `dsl` | Y.Map | - | ### add @@ -42,12 +42,12 @@ add(field: string, callback: (node: DimensionNodeBuilder) => void): DimensionsBu ### remove -删除指定字段的维度 +删除指定 ID 的维度 **定义**: ```typescript -remove(field: VBIDimension['field']): DimensionsBuilder +remove(id: string): DimensionsBuilder ``` **返回**: `DimensionsBuilder` @@ -56,16 +56,16 @@ remove(field: VBIDimension['field']): DimensionsBuilder | 参数 | 类型 | 说明 | | --- | --- | --- | -| `field` | VBIDimension['field'] | - 字段名 | +| `id` | string | - 维度 ID | ### update -更新指定维度字段的配置 +更新指定维度 ID 的配置 **定义**: ```typescript -update(field: string, callback: (node: DimensionNodeBuilder) => void): DimensionsBuilder +update(id: string, callback: (node: DimensionNodeBuilder) => void): DimensionsBuilder ``` **返回**: `DimensionsBuilder` @@ -74,17 +74,17 @@ update(field: string, callback: (node: DimensionNodeBuilder) => void): Dimension | 参数 | 类型 | 说明 | | --- | --- | --- | -| `field` | string | - 字段名 | +| `id` | string | - 维度 ID | | `callback` | (node: DimensionNodeBuilder) => void | - 回调函数 | ### find -根据字段名查找维度 +按回调条件查找第一个维度,行为与 Array.find 一致 **定义**: ```typescript -find(field: VBIDimension['field']): DimensionNodeBuilder | undefined +find(predicate: (node: DimensionNodeBuilder, index: number) => boolean): DimensionNodeBuilder | undefined ``` **返回**: `DimensionNodeBuilder \| undefined` @@ -93,7 +93,7 @@ find(field: VBIDimension['field']): DimensionNodeBuilder | undefined | 参数 | 类型 | 说明 | | --- | --- | --- | -| `field` | VBIDimension['field'] | - 字段名 | +| `predicate` | (node: DimensionNodeBuilder, index: number) => boolean | - 查找条件 | ### findAll diff --git a/apps/website/docs/zh-CN/vbi/api/dimensions/dimension-node.md b/apps/website/docs/zh-CN/vbi/api/dimensions/dimension-node.md index 1ec3fed720..97ca259a14 100644 --- a/apps/website/docs/zh-CN/vbi/api/dimensions/dimension-node.md +++ b/apps/website/docs/zh-CN/vbi/api/dimensions/dimension-node.md @@ -18,6 +18,18 @@ constructor(yMap: Y.Map) | --- | --- | --- | | `yMap` | Y.Map | - | +### getId + +获取节点 ID + +**定义**: + +```typescript +getId(): string +``` + +**返回**: `string` + ### getField 获取字段名 diff --git a/apps/website/docs/zh-CN/vbi/api/having-filter.md b/apps/website/docs/zh-CN/vbi/api/having-filter.md index 0d4c1593a0..ce22f99f44 100644 --- a/apps/website/docs/zh-CN/vbi/api/having-filter.md +++ b/apps/website/docs/zh-CN/vbi/api/having-filter.md @@ -127,12 +127,12 @@ remove(idOrIndex: string | number): HavingFilterBuilder ### find -根据 ID 查找条件(过滤或分组) +按回调条件查找第一个条件(过滤或分组),行为与 Array.find 一致 **定义**: ```typescript -find(id: string): HavingFilterNodeBuilder | HavingGroupBuilder | undefined +find(predicate: (entry: HavingFilterNodeBuilder | HavingGroupBuilder, index: number) => boolean): HavingFilterNodeBuilder | HavingGroupBuilder | undefined ``` **返回**: `HavingFilterNodeBuilder \| HavingGroupBuilder \| undefined` @@ -141,7 +141,7 @@ find(id: string): HavingFilterNodeBuilder | HavingGroupBuilder | undefined | 参数 | 类型 | 说明 | | --- | --- | --- | -| `id` | string | - ID | +| `predicate` | (entry: HavingFilterNodeBuilder \| HavingGroupBuilder, index: number) => boolean | - 查找条件 | ### clear diff --git a/apps/website/docs/zh-CN/vbi/api/measures.md b/apps/website/docs/zh-CN/vbi/api/measures.md index 7aa9b32b9a..4e5f885472 100644 --- a/apps/website/docs/zh-CN/vbi/api/measures.md +++ b/apps/website/docs/zh-CN/vbi/api/measures.md @@ -11,14 +11,14 @@ **定义**: ```typescript -constructor(_doc: Y.Doc, dsl: Y.Map) +constructor(doc: Y.Doc, dsl: Y.Map) ``` **参数**: | 参数 | 类型 | 说明 | | --- | --- | --- | -| `_doc` | Y.Doc | - | +| `doc` | Y.Doc | - | | `dsl` | Y.Map | - | ### add @@ -42,12 +42,12 @@ add(field: string, callback: (node: MeasureNodeBuilder) => void): MeasuresBuilde ### remove -删除指定字段的度量 +删除指定 ID 的度量 **定义**: ```typescript -remove(field: VBIMeasure['field']): MeasuresBuilder +remove(id: string): MeasuresBuilder ``` **返回**: `MeasuresBuilder` @@ -56,7 +56,7 @@ remove(field: VBIMeasure['field']): MeasuresBuilder | 参数 | 类型 | 说明 | | --- | --- | --- | -| `field` | VBIMeasure['field'] | - 字段名 | +| `id` | string | - 度量 ID | ### update @@ -65,7 +65,7 @@ remove(field: VBIMeasure['field']): MeasuresBuilder **定义**: ```typescript -update(field: string, callback: (node: MeasureNodeBuilder) => void): MeasuresBuilder +update(id: string, callback: (node: MeasureNodeBuilder) => void): MeasuresBuilder ``` **返回**: `MeasuresBuilder` @@ -74,17 +74,17 @@ update(field: string, callback: (node: MeasureNodeBuilder) => void): MeasuresBui | 参数 | 类型 | 说明 | | --- | --- | --- | -| `field` | string | - 字段名 | +| `id` | string | - 度量 ID | | `callback` | (node: MeasureNodeBuilder) => void | - 回调函数 | ### find -根据字段名查找度量 +按回调条件查找第一个度量,行为与 Array.find 一致 **定义**: ```typescript -find(field: VBIMeasure['field']): MeasureNodeBuilder | undefined +find(predicate: (node: MeasureNodeBuilder, index: number) => boolean): MeasureNodeBuilder | undefined ``` **返回**: `MeasureNodeBuilder \| undefined` @@ -93,7 +93,7 @@ find(field: VBIMeasure['field']): MeasureNodeBuilder | undefined | 参数 | 类型 | 说明 | | --- | --- | --- | -| `field` | VBIMeasure['field'] | - 字段名 | +| `predicate` | (node: MeasureNodeBuilder, index: number) => boolean | - 查找条件 | ### findAll diff --git a/apps/website/docs/zh-CN/vbi/api/measures/measure-node.md b/apps/website/docs/zh-CN/vbi/api/measures/measure-node.md index 7e081ae3eb..aa085a9e89 100644 --- a/apps/website/docs/zh-CN/vbi/api/measures/measure-node.md +++ b/apps/website/docs/zh-CN/vbi/api/measures/measure-node.md @@ -18,6 +18,18 @@ constructor(yMap: Y.Map) | --- | --- | --- | | `yMap` | Y.Map | - | +### getId + +获取节点 ID + +**定义**: + +```typescript +getId(): string +``` + +**返回**: `string` + ### getField 获取字段名 diff --git a/apps/website/docs/zh-CN/vbi/api/where-filter.md b/apps/website/docs/zh-CN/vbi/api/where-filter.md index 7e089821af..682f5ca0a0 100644 --- a/apps/website/docs/zh-CN/vbi/api/where-filter.md +++ b/apps/website/docs/zh-CN/vbi/api/where-filter.md @@ -127,12 +127,12 @@ remove(idOrIndex: string | number): WhereFilterBuilder ### find -根据 ID 查找条件(过滤或分组) +按回调条件查找第一个条件(过滤或分组),行为与 Array.find 一致 **定义**: ```typescript -find(id: string): WhereFilterNodeBuilder | WhereGroupBuilder | undefined +find(predicate: (entry: WhereFilterNodeBuilder | WhereGroupBuilder, index: number) => boolean): WhereFilterNodeBuilder | WhereGroupBuilder | undefined ``` **返回**: `WhereFilterNodeBuilder \| WhereGroupBuilder \| undefined` @@ -141,7 +141,7 @@ find(id: string): WhereFilterNodeBuilder | WhereGroupBuilder | undefined | 参数 | 类型 | 说明 | | --- | --- | --- | -| `id` | string | - ID | +| `predicate` | (entry: WhereFilterNodeBuilder \| WhereGroupBuilder, index: number) => boolean | - 查找条件 | ### clear diff --git a/apps/website/docs/zh-CN/vbi/examples/dimensions.mdx b/apps/website/docs/zh-CN/vbi/examples/dimensions.mdx index ac8d29ba2a..589db84981 100644 --- a/apps/website/docs/zh-CN/vbi/examples/dimensions.mdx +++ b/apps/website/docs/zh-CN/vbi/examples/dimensions.mdx @@ -35,9 +35,12 @@ export default () => { builder.dimensions.add('product_type', (node) => { node.setAlias('商品类型') }) - builder.dimensions.update('product_type', (node) => { - node.setAlias('产品类型') - }) + const dimensionId = builder.dimensions.find((node) => node.getField() === 'product_type')?.getId() + if (dimensionId) { + builder.dimensions.update(dimensionId, (node) => { + node.setAlias('产品类型') + }) + } } applyBuilder(builder) @@ -128,10 +131,13 @@ export default () => { }) const applyBuilder = (builder: VBIBuilder) => { - builder.dimensions.update('product_type', (node) => { - node.setAlias('待删除的产品类型') - }) - builder.dimensions.remove('product_type') + const dimensionId = builder.dimensions.toJSON().find((item) => item.field === 'product_type')?.id + if (dimensionId) { + builder.dimensions.update(dimensionId, (node) => { + node.setAlias('待删除的产品类型') + }) + builder.dimensions.remove(dimensionId) + } } applyBuilder(builder) @@ -175,11 +181,14 @@ export default () => { }) const applyBuilder = (builder: VBIBuilder) => { - const dimension = builder.dimensions.find('product_type') - if (dimension) { - dimension.setAlias('待调整的产品类型') + const dimensionId = builder.dimensions.toJSON().find((item) => item.field === 'product_type')?.id + if (dimensionId) { + const dimension = builder.dimensions.find((node) => node.getId() === dimensionId) + if (dimension) { + dimension.setAlias('待调整的产品类型') + } + builder.dimensions.update(dimensionId, (n) => n.setAlias('新产品类型')) } - builder.dimensions.update('product_type', (n) => n.setAlias('新产品类型')) } applyBuilder(builder) diff --git a/apps/website/docs/zh-CN/vbi/examples/measures.mdx b/apps/website/docs/zh-CN/vbi/examples/measures.mdx index 26093b3e7e..5999ba9a81 100644 --- a/apps/website/docs/zh-CN/vbi/examples/measures.mdx +++ b/apps/website/docs/zh-CN/vbi/examples/measures.mdx @@ -35,9 +35,12 @@ export default () => { builder.measures.add('sales', (node) => { node.setAlias('原销售额') }) - builder.measures.update('sales', (node) => { - node.setAlias('销售额').setAggregate({ func: 'sum' }) - }) + const measureId = builder.measures.find((node) => node.getField() === 'sales')?.getId() + if (measureId) { + builder.measures.update(measureId, (node) => { + node.setAlias('销售额').setAggregate({ func: 'sum' }) + }) + } } applyBuilder(builder) @@ -82,7 +85,10 @@ export default () => { const applyBuilder = (builder: VBIBuilder) => { builder.measures.add('sales', (n) => n.setAlias('销售额')) - builder.measures.update('sales', (n) => n.setEncoding('yAxis').setAggregate({ func: 'sum' })) + const measureId = builder.measures.find((node) => node.getField() === 'sales')?.getId() + if (measureId) { + builder.measures.update(measureId, (n) => n.setEncoding('yAxis').setAggregate({ func: 'sum' })) + } } applyBuilder(builder) @@ -129,8 +135,11 @@ export default () => { }) const applyBuilder = (builder: VBIBuilder) => { - builder.measures.update('sales', (n) => n.setAlias('待移除的销售额')) - builder.measures.remove('sales') + const measureId = builder.measures.toJSON().find((item) => item.field === 'sales')?.id + if (measureId) { + builder.measures.update(measureId, (n) => n.setAlias('待移除的销售额')) + builder.measures.remove(measureId) + } } applyBuilder(builder) @@ -174,11 +183,14 @@ export default () => { }) const applyBuilder = (builder: VBIBuilder) => { - const measure = builder.measures.find('sales') - if (measure) { - measure.setAlias('待调整销售额').setEncoding('yAxis') + const measureId = builder.measures.toJSON().find((item) => item.field === 'sales')?.id + if (measureId) { + const measure = builder.measures.find((node) => node.getId() === measureId) + if (measure) { + measure.setAlias('待调整销售额').setEncoding('yAxis') + } + builder.measures.update(measureId, (n) => n.setAlias('新销售额').setAggregate({ func: 'avg' })) } - builder.measures.update('sales', (n) => n.setAlias('新销售额').setAggregate({ func: 'avg' })) } applyBuilder(builder) diff --git a/apps/website/package.json b/apps/website/package.json index 24281887e7..6eab6fbff7 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -12,7 +12,7 @@ "preview": "rspress preview" }, "dependencies": { - "@rspress/core": "2.0.2", + "@rspress/core": "2.0.5", "@visactor/vbi": "workspace:*", "@visactor/vchart": "2.0.15", "@visactor/vquery": "workspace:*", @@ -27,13 +27,14 @@ }, "devDependencies": { "@eslint/js": "9.39.0", - "@rspress/plugin-playground": "2.0.2", - "@rspress/plugin-preview": "2.0.2", + "@rspress/plugin-llms": "2.0.5", + "@rspress/plugin-playground": "2.0.5", + "@rspress/plugin-preview": "2.0.5", "@types/node": "24.10.1", "@types/react": "19.2.7", "eslint": "9.39.0", "globals": "16.4.0", - "typescript": "^5.9.3", + "typescript": "5.9.3", "typescript-eslint": "8.48.0" } } diff --git a/apps/website/rspress.config.ts b/apps/website/rspress.config.ts index ef86313243..dfb12c8b8d 100644 --- a/apps/website/rspress.config.ts +++ b/apps/website/rspress.config.ts @@ -1,4 +1,5 @@ import * as path from 'node:path' +import { pluginLlms } from '@rspress/plugin-llms' import { pluginPlayground } from '@rspress/plugin-playground' import { pluginPreview } from '@rspress/plugin-preview' import { defineConfig } from '@rspress/core' @@ -10,6 +11,7 @@ export default defineConfig({ base: '/VBI/', globalStyles: path.join(__dirname, 'components/styles/index.css'), plugins: [ + pluginLlms(), pluginPreview(), pluginPlayground({ include: [ diff --git a/packages/vbi/src/builder/features/dimensions/dim-builder.ts b/packages/vbi/src/builder/features/dimensions/dim-builder.ts index 31e4ad8312..c9a34be0ab 100644 --- a/packages/vbi/src/builder/features/dimensions/dim-builder.ts +++ b/packages/vbi/src/builder/features/dimensions/dim-builder.ts @@ -1,14 +1,21 @@ import * as Y from 'yjs' import type { ObserveCallback, VBIDimension, VBIDimensionGroup, VBIDimensionTree } from 'src/types' import { DimensionNodeBuilder } from './dim-node-builder' +import { id } from 'src/utils' +import { getOrCreateDimensions, locateDimensionIndexById, normalizeDimensionNodeIds } from './dimension-utils' /** * @description 维度构建器,用于添加、修改、删除维度配置。维度是数据的分类字段,如:时间、地区、产品类别 */ export class DimensionsBuilder { private dsl: Y.Map - constructor(_doc: Y.Doc, dsl: Y.Map) { + constructor(doc: Y.Doc, dsl: Y.Map) { this.dsl = dsl + + doc.transact(() => { + const dimensions = getOrCreateDimensions(this.dsl) + normalizeDimensionNodeIds(dimensions) + }) } /** @@ -18,6 +25,7 @@ export class DimensionsBuilder { */ add(field: string, callback: (node: DimensionNodeBuilder) => void): DimensionsBuilder { const dimension: VBIDimension = { + id: id.uuid(), alias: field, field, } @@ -27,7 +35,8 @@ export class DimensionsBuilder { yMap.set(key, value) } - this.dsl.get('dimensions').push([yMap]) + const dimensions = getOrCreateDimensions(this.dsl) + dimensions.push([yMap]) const node = new DimensionNodeBuilder(yMap) callback(node) @@ -35,29 +44,29 @@ export class DimensionsBuilder { } /** - * @description 删除指定字段的维度 - * @param field - 字段名 + * @description 删除指定 ID 的维度 + * @param id - 维度 ID */ - remove(field: VBIDimension['field']): DimensionsBuilder { - const dimensions = this.dsl.get('dimensions') - const index = dimensions.toArray().findIndex((item: any) => item.get('field') === field) + remove(id: string): DimensionsBuilder { + const dimensions = getOrCreateDimensions(this.dsl) + const index = locateDimensionIndexById(dimensions, id) if (index !== -1) { - this.dsl.get('dimensions').delete(index, 1) + dimensions.delete(index, 1) } return this } /** - * @description 更新指定维度字段的配置 - * @param field - 字段名 + * @description 更新指定维度 ID 的配置 + * @param id - 维度 ID * @param callback - 回调函数 */ - update(field: string, callback: (node: DimensionNodeBuilder) => void): DimensionsBuilder { - const dimensions = this.dsl.get('dimensions') as Y.Array - const index = dimensions.toArray().findIndex((item: any) => item.get('field') === field) + update(id: string, callback: (node: DimensionNodeBuilder) => void): DimensionsBuilder { + const dimensions = getOrCreateDimensions(this.dsl) + const index = locateDimensionIndexById(dimensions, id) if (index === -1) { - throw new Error(`Dimension with field "${field}" not found`) + throw new Error(`Dimension with id "${id}" not found`) } const dimensionYMap = dimensions.get(index) @@ -67,25 +76,26 @@ export class DimensionsBuilder { } /** - * @description 根据字段名查找维度 - * @param field - 字段名 + * @description 按回调条件查找第一个维度,行为与 Array.find 一致 + * @param predicate - 查找条件 */ - find(field: VBIDimension['field']): DimensionNodeBuilder | undefined { - const dimensions = this.dsl.get('dimensions') as Y.Array - const index = dimensions.toArray().findIndex((item: any) => item.get('field') === field) - - if (index === -1) { - return undefined + find(predicate: (node: DimensionNodeBuilder, index: number) => boolean): DimensionNodeBuilder | undefined { + const dimensions = getOrCreateDimensions(this.dsl) + const items = dimensions.toArray() as Y.Map[] + for (let index = 0; index < items.length; index++) { + const node = new DimensionNodeBuilder(items[index]) + if (predicate(node, index)) { + return node + } } - - return new DimensionNodeBuilder(dimensions.get(index)) + return undefined } /** * @description 获取所有维度 */ findAll(): DimensionNodeBuilder[] { - const dimensions = this.dsl.get('dimensions') as Y.Array + const dimensions = getOrCreateDimensions(this.dsl) return dimensions.toArray().map((yMap: any) => new DimensionNodeBuilder(yMap)) } @@ -93,7 +103,8 @@ export class DimensionsBuilder { * @description 导出所有维度为 JSON 数组 */ toJSON(): VBIDimension[] { - return this.dsl.get('dimensions').toJSON() as VBIDimension[] + const dimensions = getOrCreateDimensions(this.dsl) + return dimensions.toJSON() as VBIDimension[] } /** @@ -102,9 +113,10 @@ export class DimensionsBuilder { * @returns 取消监听的函数 */ observe(callback: ObserveCallback): () => void { - this.dsl.get('dimensions').observe(callback) + const dimensions = getOrCreateDimensions(this.dsl) + dimensions.observe(callback as any) return () => { - this.dsl.get('dimensions').unobserve(callback) + dimensions.unobserve(callback as any) } } diff --git a/packages/vbi/src/builder/features/dimensions/dim-node-builder.ts b/packages/vbi/src/builder/features/dimensions/dim-node-builder.ts index 21208dc04f..43a00da205 100644 --- a/packages/vbi/src/builder/features/dimensions/dim-node-builder.ts +++ b/packages/vbi/src/builder/features/dimensions/dim-node-builder.ts @@ -7,6 +7,13 @@ import { VBIDimension } from '../../../types' export class DimensionNodeBuilder { constructor(private yMap: Y.Map) {} + /** + * @description 获取节点 ID + */ + getId(): string { + return this.yMap.get('id') + } + /** * @description 获取字段名 */ diff --git a/packages/vbi/src/builder/features/dimensions/dimension-utils.ts b/packages/vbi/src/builder/features/dimensions/dimension-utils.ts new file mode 100644 index 0000000000..ab215b5cae --- /dev/null +++ b/packages/vbi/src/builder/features/dimensions/dimension-utils.ts @@ -0,0 +1,25 @@ +import * as Y from 'yjs' +import { id } from 'src/utils' + +export const getOrCreateDimensions = (dsl: Y.Map): Y.Array => { + const dimensions = dsl.get('dimensions') + if (dimensions instanceof Y.Array) { + return dimensions + } + + const yDimensions = new Y.Array() + dsl.set('dimensions', yDimensions) + return yDimensions +} + +export const normalizeDimensionNodeIds = (dimensions: Y.Array) => { + dimensions.toArray().forEach((item: any) => { + if (item instanceof Y.Map && typeof item.get('field') === 'string' && !item.get('id')) { + item.set('id', id.uuid()) + } + }) +} + +export const locateDimensionIndexById = (dimensions: Y.Array, dimensionId: string): number => { + return dimensions.toArray().findIndex((item: any) => item.get('id') === dimensionId) +} diff --git a/packages/vbi/src/builder/features/havingFilter/having-builder.ts b/packages/vbi/src/builder/features/havingFilter/having-builder.ts index e2fc10a042..5af43881ce 100644 --- a/packages/vbi/src/builder/features/havingFilter/having-builder.ts +++ b/packages/vbi/src/builder/features/havingFilter/having-builder.ts @@ -136,22 +136,35 @@ export class HavingFilterBuilder { } /** - * @description 根据 ID 查找条件(过滤或分组) - * @param id - ID + * @description 按回调条件查找第一个条件(过滤或分组),行为与 Array.find 一致 + * @param predicate - 查找条件 */ - find(id: string): HavingFilterNodeBuilder | HavingGroupBuilder | undefined { - const conditions = this.getConditions() - const match = findEntry(conditions, id) - const yMap = match?.item - - if (!yMap) { + find( + predicate: (entry: HavingFilterNodeBuilder | HavingGroupBuilder, index: number) => boolean, + ): HavingFilterNodeBuilder | HavingGroupBuilder | undefined { + const traverse = (collection: Y.Array): HavingFilterNodeBuilder | HavingGroupBuilder | undefined => { + const items = collection.toArray() as Y.Map[] + for (let index = 0; index < items.length; index++) { + const yMap = items[index] + const entry = HavingFilterBuilder.isGroup(yMap) + ? new HavingGroupBuilder(yMap) + : new HavingFilterNodeBuilder(yMap) + + if (predicate(entry, index)) { + return entry + } + + if (HavingFilterBuilder.isGroup(yMap)) { + const nestedCollection = yMap.get('conditions') as Y.Array + const nestedMatch = traverse(nestedCollection) + if (nestedMatch) { + return nestedMatch + } + } + } return undefined } - - if (HavingFilterBuilder.isGroup(yMap)) { - return new HavingGroupBuilder(yMap) - } - return new HavingFilterNodeBuilder(yMap) + return traverse(this.getConditions()) } /** diff --git a/packages/vbi/src/builder/features/measures/mea-builder.ts b/packages/vbi/src/builder/features/measures/mea-builder.ts index 4012ddd58a..291e799dcd 100644 --- a/packages/vbi/src/builder/features/measures/mea-builder.ts +++ b/packages/vbi/src/builder/features/measures/mea-builder.ts @@ -1,14 +1,21 @@ import * as Y from 'yjs' import type { ObserveCallback, VBIMeasure, VBIMeasureGroup, VBIMeasureTree } from 'src/types' import { MeasureNodeBuilder } from './mea-node-builder' +import { id } from 'src/utils' +import { getOrCreateMeasures, locateMeasureIndexById, normalizeMeasureNodeIds } from './measure-utils' /** * @description 度量构建器,用于添加、修改、删除度量配置。度量是数据的数值字段,如:销售额、利润、数量 */ export class MeasuresBuilder { private dsl: Y.Map - constructor(_doc: Y.Doc, dsl: Y.Map) { + constructor(doc: Y.Doc, dsl: Y.Map) { this.dsl = dsl + + doc.transact(() => { + const measures = getOrCreateMeasures(this.dsl) + normalizeMeasureNodeIds(measures) + }) } /** @@ -18,6 +25,7 @@ export class MeasuresBuilder { */ add(field: string, callback: (node: MeasureNodeBuilder) => void): MeasuresBuilder { const measure: VBIMeasure = { + id: id.uuid(), alias: field, field, encoding: 'yAxis', @@ -29,7 +37,8 @@ export class MeasuresBuilder { for (const [key, value] of Object.entries(measure)) { yMap.set(key, value) } - this.dsl.get('measures').push([yMap]) + const measures = getOrCreateMeasures(this.dsl) + measures.push([yMap]) const node = new MeasureNodeBuilder(yMap) @@ -38,29 +47,29 @@ export class MeasuresBuilder { } /** - * @description 删除指定字段的度量 - * @param field - 字段名 + * @description 删除指定 ID 的度量 + * @param id - 度量 ID */ - remove(field: VBIMeasure['field']): MeasuresBuilder { - const measures = this.dsl.get('measures') - const index = measures.toArray().findIndex((item: any) => item.get('field') === field) + remove(id: string): MeasuresBuilder { + const measures = getOrCreateMeasures(this.dsl) + const index = locateMeasureIndexById(measures, id) if (index !== -1) { - this.dsl.get('measures').delete(index, 1) + measures.delete(index, 1) } return this } /** * @description 更新度量配置 - * @param field - 字段名 + * @param id - 度量 ID * @param callback - 回调函数 */ - update(field: string, callback: (node: MeasureNodeBuilder) => void): MeasuresBuilder { - const measures = this.dsl.get('measures') as Y.Array - const index = measures.toArray().findIndex((item: any) => item.get('field') === field) + update(id: string, callback: (node: MeasureNodeBuilder) => void): MeasuresBuilder { + const measures = getOrCreateMeasures(this.dsl) + const index = locateMeasureIndexById(measures, id) if (index === -1) { - throw new Error(`Measure with field "${field}" not found`) + throw new Error(`Measure with id "${id}" not found`) } const measureYMap = measures.get(index) @@ -70,25 +79,26 @@ export class MeasuresBuilder { } /** - * @description 根据字段名查找度量 - * @param field - 字段名 + * @description 按回调条件查找第一个度量,行为与 Array.find 一致 + * @param predicate - 查找条件 */ - find(field: VBIMeasure['field']): MeasureNodeBuilder | undefined { - const measures = this.dsl.get('measures') as Y.Array - const index = measures.toArray().findIndex((item: any) => item.get('field') === field) - - if (index === -1) { - return undefined + find(predicate: (node: MeasureNodeBuilder, index: number) => boolean): MeasureNodeBuilder | undefined { + const measures = getOrCreateMeasures(this.dsl) + const items = measures.toArray() as Y.Map[] + for (let index = 0; index < items.length; index++) { + const node = new MeasureNodeBuilder(items[index]) + if (predicate(node, index)) { + return node + } } - - return new MeasureNodeBuilder(measures.get(index)) + return undefined } /** * @description 获取所有度量 */ findAll(): MeasureNodeBuilder[] { - const measures = this.dsl.get('measures') as Y.Array + const measures = getOrCreateMeasures(this.dsl) return measures.toArray().map((yMap: any) => new MeasureNodeBuilder(yMap)) } @@ -96,7 +106,8 @@ export class MeasuresBuilder { * @description 导出所有度量为 JSON 数组 */ toJSON(): VBIMeasure[] { - return this.dsl.get('measures').toJSON() as VBIMeasure[] + const measures = getOrCreateMeasures(this.dsl) + return measures.toJSON() as VBIMeasure[] } /** @@ -109,9 +120,10 @@ export class MeasuresBuilder { * @returns 取消监听的函数 */ observe(callback: ObserveCallback): () => void { - this.dsl.get('measures').observe(callback) + const measures = getOrCreateMeasures(this.dsl) + measures.observe(callback as any) return () => { - this.dsl.get('measures').unobserve(callback) + measures.unobserve(callback as any) } } diff --git a/packages/vbi/src/builder/features/measures/mea-node-builder.ts b/packages/vbi/src/builder/features/measures/mea-node-builder.ts index eb5d37d102..dd148c3f68 100644 --- a/packages/vbi/src/builder/features/measures/mea-node-builder.ts +++ b/packages/vbi/src/builder/features/measures/mea-node-builder.ts @@ -7,6 +7,13 @@ import { VBIMeasure } from '../../../types' export class MeasureNodeBuilder { constructor(private yMap: Y.Map) {} + /** + * @description 获取节点 ID + */ + getId(): string { + return this.yMap.get('id') + } + /** * @description 获取字段名 */ diff --git a/packages/vbi/src/builder/features/measures/measure-utils.ts b/packages/vbi/src/builder/features/measures/measure-utils.ts new file mode 100644 index 0000000000..f6ac36f313 --- /dev/null +++ b/packages/vbi/src/builder/features/measures/measure-utils.ts @@ -0,0 +1,25 @@ +import * as Y from 'yjs' +import { id } from 'src/utils' + +export const getOrCreateMeasures = (dsl: Y.Map): Y.Array => { + const measures = dsl.get('measures') + if (measures instanceof Y.Array) { + return measures + } + + const yMeasures = new Y.Array() + dsl.set('measures', yMeasures) + return yMeasures +} + +export const normalizeMeasureNodeIds = (measures: Y.Array) => { + measures.toArray().forEach((item: any) => { + if (item instanceof Y.Map && typeof item.get('field') === 'string' && !item.get('id')) { + item.set('id', id.uuid()) + } + }) +} + +export const locateMeasureIndexById = (measures: Y.Array, measureId: string): number => { + return measures.toArray().findIndex((item: any) => item.get('id') === measureId) +} diff --git a/packages/vbi/src/builder/features/whereFilter/where-builder.ts b/packages/vbi/src/builder/features/whereFilter/where-builder.ts index f1f9866bfd..338abd3e8f 100644 --- a/packages/vbi/src/builder/features/whereFilter/where-builder.ts +++ b/packages/vbi/src/builder/features/whereFilter/where-builder.ts @@ -136,22 +136,33 @@ export class WhereFilterBuilder { } /** - * @description 根据 ID 查找条件(过滤或分组) - * @param id - ID + * @description 按回调条件查找第一个条件(过滤或分组),行为与 Array.find 一致 + * @param predicate - 查找条件 */ - find(id: string): WhereFilterNodeBuilder | WhereGroupBuilder | undefined { - const conditions = this.getConditions() - const match = findEntry(conditions, id) - const yMap = match?.item - - if (!yMap) { + find( + predicate: (entry: WhereFilterNodeBuilder | WhereGroupBuilder, index: number) => boolean, + ): WhereFilterNodeBuilder | WhereGroupBuilder | undefined { + const traverse = (collection: Y.Array): WhereFilterNodeBuilder | WhereGroupBuilder | undefined => { + const items = collection.toArray() as Y.Map[] + for (let index = 0; index < items.length; index++) { + const yMap = items[index] + const entry = WhereFilterBuilder.isGroup(yMap) ? new WhereGroupBuilder(yMap) : new WhereFilterNodeBuilder(yMap) + + if (predicate(entry, index)) { + return entry + } + + if (WhereFilterBuilder.isGroup(yMap)) { + const nestedCollection = yMap.get('conditions') as Y.Array + const nestedMatch = traverse(nestedCollection) + if (nestedMatch) { + return nestedMatch + } + } + } return undefined } - - if (WhereFilterBuilder.isGroup(yMap)) { - return new WhereGroupBuilder(yMap) - } - return new WhereFilterNodeBuilder(yMap) + return traverse(this.getConditions()) } /** diff --git a/packages/vbi/src/builder/index.ts b/packages/vbi/src/builder/index.ts index 48c2a4ec8a..f7e7d270a8 100644 --- a/packages/vbi/src/builder/index.ts +++ b/packages/vbi/src/builder/index.ts @@ -1,5 +1,5 @@ export { VBIBuilder } from './vbi-builder' -export { VBI } from './vbi' +export { VBI } from '../vbi' export { MeasuresBuilder, DimensionsBuilder, diff --git a/packages/vbi/src/builder/modules/apply-update.ts b/packages/vbi/src/builder/modules/apply-update.ts new file mode 100644 index 0000000000..f31a17aee1 --- /dev/null +++ b/packages/vbi/src/builder/modules/apply-update.ts @@ -0,0 +1,5 @@ +import * as Y from 'yjs' + +export const applyUpdateToDoc = (doc: Y.Doc, update: Uint8Array, transactionOrigin?: any) => { + Y.applyUpdate(doc, update, transactionOrigin) +} diff --git a/packages/vbi/src/builder/modules/build-vquery.ts b/packages/vbi/src/builder/modules/build-vquery.ts new file mode 100644 index 0000000000..4ae5d22d5a --- /dev/null +++ b/packages/vbi/src/builder/modules/build-vquery.ts @@ -0,0 +1,10 @@ +import * as Y from 'yjs' +import type { VQueryDSL } from '@visactor/vquery' +import type { VBIBuilder } from '../vbi-builder' +import { buildVQuery as buildVQueryPipeline } from 'src/pipeline' +import { buildVBIDSL } from './build' + +export const buildVQueryDSL = (dsl: Y.Map, builder: VBIBuilder): VQueryDSL => { + const vbiDSL = buildVBIDSL(dsl) + return buildVQueryPipeline(vbiDSL, builder) +} diff --git a/packages/vbi/src/builder/modules/build-vseed.ts b/packages/vbi/src/builder/modules/build-vseed.ts new file mode 100644 index 0000000000..36c33421c7 --- /dev/null +++ b/packages/vbi/src/builder/modules/build-vseed.ts @@ -0,0 +1,23 @@ +import type { VSeedDSL } from '@visactor/vseed' +import type { VQueryDSL } from '@visactor/vquery' +import type { VBIDSL } from 'src/types' +import { getConnector } from '../connector' + +export interface BuildVSeedInput { + vbiDSL: VBIDSL + queryDSL: VQueryDSL +} + +export const buildVSeedDSL = async ({ vbiDSL, queryDSL }: BuildVSeedInput): Promise => { + const connectorId = vbiDSL.connectorId + const connector = await getConnector(connectorId) + const schema = await connector.discoverSchema() + const queryResult = await connector.query({ queryDSL, schema, connectorId }) + + return { + chartType: vbiDSL.chartType, + dataset: queryResult.dataset, + theme: vbiDSL.theme, + locale: vbiDSL.locale, + } as VSeedDSL +} diff --git a/packages/vbi/src/builder/modules/build.ts b/packages/vbi/src/builder/modules/build.ts new file mode 100644 index 0000000000..4c423570bf --- /dev/null +++ b/packages/vbi/src/builder/modules/build.ts @@ -0,0 +1,6 @@ +import * as Y from 'yjs' +import type { VBIDSL } from 'src/types' + +export const buildVBIDSL = (dsl: Y.Map): VBIDSL => { + return dsl.toJSON() as VBIDSL +} diff --git a/packages/vbi/src/builder/modules/create-builder-features.ts b/packages/vbi/src/builder/modules/create-builder-features.ts new file mode 100644 index 0000000000..4a77f78241 --- /dev/null +++ b/packages/vbi/src/builder/modules/create-builder-features.ts @@ -0,0 +1,38 @@ +import * as Y from 'yjs' +import { + DimensionsBuilder, + MeasuresBuilder, + HavingFilterBuilder, + WhereFilterBuilder, + ChartTypeBuilder, + ThemeBuilder, + LocaleBuilder, + LimitBuilder, + UndoManager, +} from '../features' + +export interface BuilderFeatureInstances { + chartType: ChartTypeBuilder + measures: MeasuresBuilder + dimensions: DimensionsBuilder + havingFilter: HavingFilterBuilder + whereFilter: WhereFilterBuilder + theme: ThemeBuilder + locale: LocaleBuilder + limit: LimitBuilder + undoManager: UndoManager +} + +export const createBuilderFeatures = (doc: Y.Doc, dsl: Y.Map): BuilderFeatureInstances => { + return { + undoManager: new UndoManager(dsl), + chartType: new ChartTypeBuilder(doc, dsl), + measures: new MeasuresBuilder(doc, dsl), + dimensions: new DimensionsBuilder(doc, dsl), + havingFilter: new HavingFilterBuilder(doc, dsl), + whereFilter: new WhereFilterBuilder(doc, dsl), + theme: new ThemeBuilder(doc, dsl), + locale: new LocaleBuilder(doc, dsl), + limit: new LimitBuilder(doc, dsl), + } +} diff --git a/packages/vbi/src/builder/modules/encode-state-as-update.ts b/packages/vbi/src/builder/modules/encode-state-as-update.ts new file mode 100644 index 0000000000..f39e3a5048 --- /dev/null +++ b/packages/vbi/src/builder/modules/encode-state-as-update.ts @@ -0,0 +1,5 @@ +import * as Y from 'yjs' + +export const encodeDocStateAsUpdate = (doc: Y.Doc, targetStateVector?: Uint8Array) => { + return Y.encodeStateAsUpdate(doc, targetStateVector) +} diff --git a/packages/vbi/src/builder/modules/get-schema.ts b/packages/vbi/src/builder/modules/get-schema.ts new file mode 100644 index 0000000000..5104068881 --- /dev/null +++ b/packages/vbi/src/builder/modules/get-schema.ts @@ -0,0 +1,8 @@ +import * as Y from 'yjs' +import { getConnector } from '../connector' + +export const getBuilderSchema = async (dsl: Y.Map) => { + const connectorId = dsl.get('connectorId') + const connector = await getConnector(connectorId) + return connector.discoverSchema() +} diff --git a/packages/vbi/src/builder/modules/index.ts b/packages/vbi/src/builder/modules/index.ts new file mode 100644 index 0000000000..11710c0844 --- /dev/null +++ b/packages/vbi/src/builder/modules/index.ts @@ -0,0 +1,8 @@ +export { applyUpdateToDoc } from './apply-update' +export { encodeDocStateAsUpdate } from './encode-state-as-update' +export { buildVBIDSL } from './build' +export { buildVQueryDSL } from './build-vquery' +export { buildVSeedDSL } from './build-vseed' +export { isEmptyVBIDSL } from './is-empty' +export { getBuilderSchema } from './get-schema' +export { createBuilderFeatures } from './create-builder-features' diff --git a/packages/vbi/src/builder/modules/is-empty.ts b/packages/vbi/src/builder/modules/is-empty.ts new file mode 100644 index 0000000000..d242c7f6ef --- /dev/null +++ b/packages/vbi/src/builder/modules/is-empty.ts @@ -0,0 +1,19 @@ +import * as Y from 'yjs' + +const getCollectionLength = (value: unknown): number => { + if (value instanceof Y.Array) { + return value.length + } + + if (Array.isArray(value)) { + return value.length + } + + return 0 +} + +export const isEmptyVBIDSL = (dsl: Y.Map): boolean => { + const dimensionsLength = getCollectionLength(dsl.get('dimensions')) + const measuresLength = getCollectionLength(dsl.get('measures')) + return dimensionsLength === 0 && measuresLength === 0 +} diff --git a/packages/vbi/src/builder/vbi-builder.ts b/packages/vbi/src/builder/vbi-builder.ts index 8536bb1bd2..9d427aebf5 100644 --- a/packages/vbi/src/builder/vbi-builder.ts +++ b/packages/vbi/src/builder/vbi-builder.ts @@ -1,7 +1,7 @@ import * as Y from 'yjs' -import { VSeedDSL } from '@visactor/vseed' -import { VQueryDSL } from '@visactor/vquery' +import type { VSeedDSL } from '@visactor/vseed' +import type { VQueryDSL } from '@visactor/vquery' import { DimensionsBuilder, MeasuresBuilder, @@ -14,14 +14,21 @@ import { UndoManager, } from './features' -import { VBIDSL, VBIBuilderInterface } from 'src/types' -import { buildVQuery } from 'src/pipeline' -import { getConnector } from './connector' +import type { VBIDSL, VBIBuilderInterface } from 'src/types' +import { + applyUpdateToDoc, + encodeDocStateAsUpdate, + buildVBIDSL, + buildVQueryDSL, + buildVSeedDSL, + isEmptyVBIDSL, + getBuilderSchema, + createBuilderFeatures, +} from './modules' export class VBIBuilder implements VBIBuilderInterface { public doc: Y.Doc public dsl: Y.Map - public undoManager: UndoManager public chartType: ChartTypeBuilder public measures: MeasuresBuilder @@ -31,60 +38,43 @@ export class VBIBuilder implements VBIBuilderInterface { public theme: ThemeBuilder public locale: LocaleBuilder public limit: LimitBuilder + public undoManager: UndoManager constructor(doc: Y.Doc) { this.doc = doc this.dsl = doc.getMap('dsl') as Y.Map - this.undoManager = new UndoManager(this.dsl) - this.chartType = new ChartTypeBuilder(doc, this.dsl) - this.measures = new MeasuresBuilder(doc, this.dsl) - this.dimensions = new DimensionsBuilder(doc, this.dsl) - this.havingFilter = new HavingFilterBuilder(doc, this.dsl) - this.whereFilter = new WhereFilterBuilder(doc, this.dsl) - this.theme = new ThemeBuilder(doc, this.dsl) - this.locale = new LocaleBuilder(doc, this.dsl) - this.limit = new LimitBuilder(doc, this.dsl) + const features = createBuilderFeatures(doc, this.dsl) + this.undoManager = features.undoManager + this.chartType = features.chartType + this.measures = features.measures + this.dimensions = features.dimensions + this.havingFilter = features.havingFilter + this.whereFilter = features.whereFilter + this.theme = features.theme + this.locale = features.locale + this.limit = features.limit } - public applyUpdate(update: Uint8Array) { - Y.applyUpdate(this.doc, update) + public applyUpdate = (update: Uint8Array, transactionOrigin?: any) => { + return applyUpdateToDoc(this.doc, update, transactionOrigin) } - public encodeStateAsUpdate(targetStateVector?: Uint8Array) { - return Y.encodeStateAsUpdate(this.doc, targetStateVector) + public encodeStateAsUpdate = (targetStateVector?: Uint8Array) => { + return encodeDocStateAsUpdate(this.doc, targetStateVector) } public buildVSeed = async (): Promise => { const vbiDSL = this.build() - const connectorId = vbiDSL.connectorId - const connector = await getConnector(vbiDSL.connectorId) - const queryDSL = this.buildVQuery() - const schema = await connector.discoverSchema() - const queryResult = await connector.query({ queryDSL, schema, connectorId }) - - return { - chartType: vbiDSL.chartType, - dataset: queryResult.dataset, - theme: vbiDSL.theme, - locale: vbiDSL.locale, - } as VSeedDSL + return buildVSeedDSL({ vbiDSL, queryDSL }) } - public buildVQuery = (): VQueryDSL => { - const vbiDSL = this.build() - return buildVQuery(vbiDSL, this) - } + public buildVQuery = (): VQueryDSL => buildVQueryDSL(this.dsl, this) - public build = (): VBIDSL => { - return this.dsl.toJSON() as VBIDSL - } + public build = (): VBIDSL => buildVBIDSL(this.dsl) - public getSchema = async () => { - const connectorId = this.dsl.get('connectorId') - const con = await getConnector(connectorId) - const result = await con.discoverSchema() - return result - } + public isEmpty = (): boolean => isEmptyVBIDSL(this.dsl) + + public getSchema = async () => getBuilderSchema(this.dsl) } diff --git a/packages/vbi/src/builder/vbi.ts b/packages/vbi/src/builder/vbi.ts deleted file mode 100644 index bf1e140b10..0000000000 --- a/packages/vbi/src/builder/vbi.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { VBIConnectorId } from 'src/types/connector/connector' -import { VBIDSL, VBIDSLInput } from 'src/types' -import { VBIBuilder } from './vbi-builder' -import { connectorMap, getConnector, registerConnector } from './connector' -import { id } from 'src/utils' -import { createWhereGroup } from './features/whereFilter/where-utils' -import { createHavingGroup } from './features/havingFilter/having-utils' - -import * as Y from 'yjs' - -const createVBI = () => { - return { - connectorMap: connectorMap, - registerConnector: registerConnector, - getConnector: getConnector, - generateEmptyDSL: (connectorId: VBIConnectorId): VBIDSL => { - return { - connectorId: connectorId, - chartType: 'table', - measures: [], - dimensions: [], - whereFilter: { - id: 'root', - op: 'and', - conditions: [], - }, - havingFilter: { - id: 'root', - op: 'and', - conditions: [], - }, - theme: 'light', - locale: 'zh-CN', - version: 0, - } - }, - from: (vbi: VBIDSLInput) => { - const doc = new Y.Doc() - const dsl = doc.getMap('dsl') - - doc.transact(() => { - if (vbi.connectorId) dsl.set('connectorId', vbi.connectorId) - if (vbi.chartType) dsl.set('chartType', vbi.chartType) - if (vbi.theme) dsl.set('theme', vbi.theme) - if (vbi.limit) dsl.set('limit', vbi.limit) - if (vbi.locale) dsl.set('locale', vbi.locale) - if (vbi.version !== undefined) dsl.set('version', vbi.version) - - // Initialize arrays - convert plain arrays to Y.Array if needed - const toYMap = (obj: any, ensureId = false): Y.Map => { - const yMap = new Y.Map() - if (ensureId && !obj.id) { - yMap.set('id', id.uuid()) - } - for (const [key, value] of Object.entries(obj)) { - if (key === 'conditions' && Array.isArray(value)) { - const yArr = new Y.Array() - ;(value as any[]).forEach((child: any) => { - if (child instanceof Y.Map) { - yArr.push([child]) - } else if (typeof child === 'object' && child !== null) { - yArr.push([toYMap(child, true)]) - } else { - yArr.push([child]) - } - }) - yMap.set(key, yArr) - } else { - yMap.set(key, value) - } - } - return yMap - } - - const ensureYArray = (arr: any, ensureId = false) => { - if (!arr) return new Y.Array() - if (arr instanceof Y.Array) return arr - const yArr = new Y.Array() - // Convert plain objects to Y.Map - arr.forEach((item: any) => { - if (item instanceof Y.Map) { - yArr.push([item]) - } else if (typeof item === 'object' && item !== null) { - yArr.push([toYMap(item, ensureId)]) - } else { - yArr.push([item]) - } - }) - return yArr - } - - const whereFilter = (vbi.whereFilter ?? { - id: 'root', - op: 'and', - conditions: [], - }) as Y.Map | { id?: string; op?: 'and' | 'or'; conditions?: any } - const whereGroup = whereFilter instanceof Y.Map ? whereFilter : createWhereGroup() - if (whereFilter instanceof Y.Map) { - if (!(whereGroup.get('conditions') instanceof Y.Array)) { - whereGroup.set('conditions', new Y.Array()) - } - if (!whereGroup.get('id')) { - whereGroup.set('id', 'root') - } - if (!whereGroup.get('op')) { - whereGroup.set('op', 'and') - } - } else { - whereGroup.set('id', whereFilter.id ?? 'root') - whereGroup.set('op', whereFilter.op ?? 'and') - whereGroup.set('conditions', ensureYArray(whereFilter.conditions, true)) - } - - dsl.set('whereFilter', whereGroup) - const havingFilter = (vbi.havingFilter ?? { - id: 'root', - op: 'and', - conditions: [], - }) as Y.Map | { id?: string; op?: 'and' | 'or'; conditions?: any } - const havingGroup = havingFilter instanceof Y.Map ? havingFilter : createHavingGroup() - if (havingFilter instanceof Y.Map) { - if (!(havingGroup.get('conditions') instanceof Y.Array)) { - havingGroup.set('conditions', new Y.Array()) - } - if (!havingGroup.get('id')) { - havingGroup.set('id', 'root') - } - if (!havingGroup.get('op')) { - havingGroup.set('op', 'and') - } - } else { - havingGroup.set('id', havingFilter.id ?? 'root') - havingGroup.set('op', havingFilter.op ?? 'and') - havingGroup.set('conditions', ensureYArray(havingFilter.conditions, true)) - } - - dsl.set('havingFilter', havingGroup) - dsl.set('measures', ensureYArray(vbi.measures)) - dsl.set('dimensions', ensureYArray(vbi.dimensions)) - }) - - return new VBIBuilder(doc) - }, - } -} - -export const VBI = createVBI() diff --git a/packages/vbi/src/index.ts b/packages/vbi/src/index.ts index 450d4719ea..4e33b6c123 100644 --- a/packages/vbi/src/index.ts +++ b/packages/vbi/src/index.ts @@ -1,4 +1,5 @@ -export { VBIBuilder, VBI } from './builder' +export { VBI } from './vbi' +export { VBIBuilder } from './builder' export { MeasuresBuilder, DimensionsBuilder, diff --git a/packages/vbi/src/types/builder/VBIInterface.ts b/packages/vbi/src/types/builder/VBIInterface.ts index d6d250fbf0..ab0045f48f 100644 --- a/packages/vbi/src/types/builder/VBIInterface.ts +++ b/packages/vbi/src/types/builder/VBIInterface.ts @@ -34,4 +34,5 @@ export interface VBIBuilderInterface { buildVSeed: () => Promise buildVQuery: () => VQueryDSL build: () => VBIDSL + isEmpty: () => boolean } diff --git a/packages/vbi/src/types/dsl/dimensions/dimensions.ts b/packages/vbi/src/types/dsl/dimensions/dimensions.ts index 28a48f7538..d002b2f818 100644 --- a/packages/vbi/src/types/dsl/dimensions/dimensions.ts +++ b/packages/vbi/src/types/dsl/dimensions/dimensions.ts @@ -1,6 +1,7 @@ import { z } from 'zod' export const zVBIDimensionSchema = z.object({ + id: z.string(), field: z.string(), alias: z.string(), }) diff --git a/packages/vbi/src/types/dsl/measures/measures.ts b/packages/vbi/src/types/dsl/measures/measures.ts index 27df910b63..e1b7a73302 100644 --- a/packages/vbi/src/types/dsl/measures/measures.ts +++ b/packages/vbi/src/types/dsl/measures/measures.ts @@ -2,6 +2,7 @@ import { z } from 'zod' import { zAggregate } from './aggregate' export const zVBIMeasure = z.object({ + id: z.string(), field: z.string(), alias: z.string(), encoding: z.enum(['yAxis', 'xAxis', 'color', 'label', 'tooltip', 'size']), diff --git a/packages/vbi/src/vbi.ts b/packages/vbi/src/vbi.ts new file mode 100644 index 0000000000..9c723ea46d --- /dev/null +++ b/packages/vbi/src/vbi.ts @@ -0,0 +1,3 @@ +import { createVBI } from './vbi/create-vbi' + +export const VBI = createVBI() diff --git a/packages/vbi/src/vbi/create-vbi.ts b/packages/vbi/src/vbi/create-vbi.ts new file mode 100644 index 0000000000..0890a72c71 --- /dev/null +++ b/packages/vbi/src/vbi/create-vbi.ts @@ -0,0 +1,14 @@ +import { connectorMap, getConnector, registerConnector } from 'src/builder/connector' +import { fromVBIDSLInput } from './from/from-vbi-dsl-input' +import { generateEmptyDSL } from './generate-empty-dsl' + +export const createVBI = () => { + return { + connectorMap, + registerConnector, + getConnector, + generateEmptyDSL, + from: fromVBIDSLInput, + create: fromVBIDSLInput, + } +} diff --git a/packages/vbi/src/vbi/from/from-vbi-dsl-input.ts b/packages/vbi/src/vbi/from/from-vbi-dsl-input.ts new file mode 100644 index 0000000000..76af76303e --- /dev/null +++ b/packages/vbi/src/vbi/from/from-vbi-dsl-input.ts @@ -0,0 +1,22 @@ +import * as Y from 'yjs' +import type { VBIDSLInput } from 'src/types' +import { VBIBuilder } from 'src/builder' +import { ensureYArray } from '../normalize/ensure-y-array' +import { ensureWhereGroup } from '../normalize/ensure-where-group' +import { ensureHavingGroup } from '../normalize/ensure-having-group' +import { setBaseDSLFields } from './set-base-dsl-fields' + +export const fromVBIDSLInput = (vbi: VBIDSLInput) => { + const doc = new Y.Doc() + const dsl = doc.getMap('dsl') + + doc.transact(() => { + setBaseDSLFields(dsl, vbi) + dsl.set('whereFilter', ensureWhereGroup(vbi.whereFilter)) + dsl.set('havingFilter', ensureHavingGroup(vbi.havingFilter)) + dsl.set('measures', ensureYArray(vbi.measures, 'field')) + dsl.set('dimensions', ensureYArray(vbi.dimensions, 'field')) + }) + + return new VBIBuilder(doc) +} diff --git a/packages/vbi/src/vbi/from/set-base-dsl-fields.ts b/packages/vbi/src/vbi/from/set-base-dsl-fields.ts new file mode 100644 index 0000000000..f4df55fd23 --- /dev/null +++ b/packages/vbi/src/vbi/from/set-base-dsl-fields.ts @@ -0,0 +1,11 @@ +import * as Y from 'yjs' +import type { VBIDSLInput } from 'src/types' + +export const setBaseDSLFields = (dsl: Y.Map, vbi: VBIDSLInput) => { + if (vbi.connectorId) dsl.set('connectorId', vbi.connectorId) + if (vbi.chartType) dsl.set('chartType', vbi.chartType) + if (vbi.theme) dsl.set('theme', vbi.theme) + if (vbi.limit) dsl.set('limit', vbi.limit) + if (vbi.locale) dsl.set('locale', vbi.locale) + if (vbi.version !== undefined) dsl.set('version', vbi.version) +} diff --git a/packages/vbi/src/vbi/generate-empty-dsl.ts b/packages/vbi/src/vbi/generate-empty-dsl.ts new file mode 100644 index 0000000000..f31ea0c95b --- /dev/null +++ b/packages/vbi/src/vbi/generate-empty-dsl.ts @@ -0,0 +1,24 @@ +import type { VBIConnectorId } from 'src/types/connector/connector' +import type { VBIDSL } from 'src/types' + +export const generateEmptyDSL = (connectorId: VBIConnectorId): VBIDSL => { + return { + connectorId, + chartType: 'table', + measures: [], + dimensions: [], + whereFilter: { + id: 'root', + op: 'and', + conditions: [], + }, + havingFilter: { + id: 'root', + op: 'and', + conditions: [], + }, + theme: 'light', + locale: 'zh-CN', + version: 0, + } +} diff --git a/packages/vbi/src/vbi/normalize/ensure-having-group.ts b/packages/vbi/src/vbi/normalize/ensure-having-group.ts new file mode 100644 index 0000000000..848125d118 --- /dev/null +++ b/packages/vbi/src/vbi/normalize/ensure-having-group.ts @@ -0,0 +1,41 @@ +import * as Y from 'yjs' +import { createHavingGroup } from 'src/builder/features/havingFilter/having-utils' +import { ensureYArray } from './ensure-y-array' +import type { FilterGroupInput } from './types' + +const getDefaultHavingFilter = (): FilterGroupInput => { + return { + id: 'root', + op: 'and', + conditions: [], + } +} + +const isFilterGroupInput = (value: unknown): value is FilterGroupInput => { + return typeof value === 'object' && value !== null +} + +export const ensureHavingGroup = (havingFilter?: unknown): Y.Map => { + const sourceHavingFilter = + havingFilter instanceof Y.Map || isFilterGroupInput(havingFilter) ? havingFilter : getDefaultHavingFilter() + const havingGroup = sourceHavingFilter instanceof Y.Map ? sourceHavingFilter : createHavingGroup() + + if (sourceHavingFilter instanceof Y.Map) { + if (!(havingGroup.get('conditions') instanceof Y.Array)) { + havingGroup.set('conditions', new Y.Array()) + } + if (!havingGroup.get('id')) { + havingGroup.set('id', 'root') + } + if (!havingGroup.get('op')) { + havingGroup.set('op', 'and') + } + return havingGroup + } + + havingGroup.set('id', sourceHavingFilter.id ?? 'root') + havingGroup.set('op', sourceHavingFilter.op ?? 'and') + havingGroup.set('conditions', ensureYArray(sourceHavingFilter.conditions, true)) + + return havingGroup +} diff --git a/packages/vbi/src/vbi/normalize/ensure-where-group.ts b/packages/vbi/src/vbi/normalize/ensure-where-group.ts new file mode 100644 index 0000000000..acde5746ba --- /dev/null +++ b/packages/vbi/src/vbi/normalize/ensure-where-group.ts @@ -0,0 +1,41 @@ +import * as Y from 'yjs' +import { createWhereGroup } from 'src/builder/features/whereFilter/where-utils' +import { ensureYArray } from './ensure-y-array' +import type { FilterGroupInput } from './types' + +const getDefaultWhereFilter = (): FilterGroupInput => { + return { + id: 'root', + op: 'and', + conditions: [], + } +} + +const isFilterGroupInput = (value: unknown): value is FilterGroupInput => { + return typeof value === 'object' && value !== null +} + +export const ensureWhereGroup = (whereFilter?: unknown): Y.Map => { + const sourceWhereFilter = + whereFilter instanceof Y.Map || isFilterGroupInput(whereFilter) ? whereFilter : getDefaultWhereFilter() + const whereGroup = sourceWhereFilter instanceof Y.Map ? sourceWhereFilter : createWhereGroup() + + if (sourceWhereFilter instanceof Y.Map) { + if (!(whereGroup.get('conditions') instanceof Y.Array)) { + whereGroup.set('conditions', new Y.Array()) + } + if (!whereGroup.get('id')) { + whereGroup.set('id', 'root') + } + if (!whereGroup.get('op')) { + whereGroup.set('op', 'and') + } + return whereGroup + } + + whereGroup.set('id', sourceWhereFilter.id ?? 'root') + whereGroup.set('op', sourceWhereFilter.op ?? 'and') + whereGroup.set('conditions', ensureYArray(sourceWhereFilter.conditions, true)) + + return whereGroup +} diff --git a/packages/vbi/src/vbi/normalize/ensure-y-array.ts b/packages/vbi/src/vbi/normalize/ensure-y-array.ts new file mode 100644 index 0000000000..b11a0cc1ea --- /dev/null +++ b/packages/vbi/src/vbi/normalize/ensure-y-array.ts @@ -0,0 +1,27 @@ +import * as Y from 'yjs' +import { toYMap, type EnsureIdMode } from './to-y-map' + +export const ensureYArray = (arr: any, ensureId: EnsureIdMode = false): Y.Array => { + if (!arr) { + return new Y.Array() + } + + if (arr instanceof Y.Array) { + return arr + } + + const yArr = new Y.Array() + for (const item of arr as any[]) { + if (item instanceof Y.Map) { + yArr.push([item]) + continue + } + if (typeof item === 'object' && item !== null) { + yArr.push([toYMap(item, ensureId)]) + continue + } + yArr.push([item]) + } + + return yArr +} diff --git a/packages/vbi/src/vbi/normalize/to-y-map.ts b/packages/vbi/src/vbi/normalize/to-y-map.ts new file mode 100644 index 0000000000..d189d8207c --- /dev/null +++ b/packages/vbi/src/vbi/normalize/to-y-map.ts @@ -0,0 +1,47 @@ +import * as Y from 'yjs' +import { id } from 'src/utils' + +export type EnsureIdMode = boolean | 'field' + +const shouldEnsureIdForObject = (obj: Record, ensureId: EnsureIdMode): boolean => { + if (ensureId === true) { + return true + } + + if (ensureId === 'field') { + return typeof obj.field === 'string' + } + + return false +} + +export const toYMap = (obj: Record, ensureId: EnsureIdMode = false): Y.Map => { + const yMap = new Y.Map() + + if (shouldEnsureIdForObject(obj, ensureId) && !obj.id) { + yMap.set('id', id.uuid()) + } + + for (const [key, value] of Object.entries(obj)) { + if ((key === 'conditions' || key === 'children') && Array.isArray(value)) { + const yArr = new Y.Array() + for (const child of value) { + if (child instanceof Y.Map) { + yArr.push([child]) + continue + } + if (typeof child === 'object' && child !== null) { + yArr.push([toYMap(child as Record, ensureId)]) + continue + } + yArr.push([child]) + } + yMap.set(key, yArr) + continue + } + + yMap.set(key, value) + } + + return yMap +} diff --git a/packages/vbi/src/vbi/normalize/types.ts b/packages/vbi/src/vbi/normalize/types.ts new file mode 100644 index 0000000000..2aef79a48c --- /dev/null +++ b/packages/vbi/src/vbi/normalize/types.ts @@ -0,0 +1,5 @@ +export interface FilterGroupInput { + id?: string + op?: 'and' | 'or' + conditions?: any[] +} diff --git a/packages/vbi/tests/builder/builder.test.ts b/packages/vbi/tests/builder/builder.test.ts index af8f079e01..05992b500c 100644 --- a/packages/vbi/tests/builder/builder.test.ts +++ b/packages/vbi/tests/builder/builder.test.ts @@ -15,6 +15,7 @@ describe('VBI', () => { expect(builder.build()).toEqual({ dimensions: [ { + id: 'id-2', alias: 'Area', field: 'area', }, @@ -23,6 +24,7 @@ describe('VBI', () => { havingFilter: { id: 'root', op: 'and', conditions: [] }, measures: [ { + id: 'id-1', aggregate: { func: 'max', }, @@ -33,4 +35,21 @@ describe('VBI', () => { ], }) }) + + test('isEmpty', () => { + const builder = VBI.from({} as VBIDSL) + expect(builder.isEmpty()).toBe(true) + + builder.dimensions.add('area', () => {}) + expect(builder.isEmpty()).toBe(false) + + const dimensionId = builder.dimensions.find((node) => node.getField() === 'area')?.getId() + if (dimensionId) { + builder.dimensions.remove(dimensionId) + } + expect(builder.isEmpty()).toBe(true) + + builder.measures.add('sales', () => {}) + expect(builder.isEmpty()).toBe(false) + }) }) diff --git a/packages/vbi/tests/builder/features/dimensions.test.ts b/packages/vbi/tests/builder/features/dimensions.test.ts index 04c3ff3773..1687792fca 100644 --- a/packages/vbi/tests/builder/features/dimensions.test.ts +++ b/packages/vbi/tests/builder/features/dimensions.test.ts @@ -13,6 +13,7 @@ describe('DimensionsBuilder', () => { expect(builder.build()).toEqual({ dimensions: [ { + id: 'id-1', alias: '类别', field: 'category', }, @@ -37,10 +38,12 @@ describe('DimensionsBuilder', () => { expect(builder.build()).toEqual({ dimensions: [ { + id: 'id-1', alias: '地区', field: 'region', }, { + id: 'id-2', alias: '城市', field: 'city', }, @@ -60,9 +63,9 @@ describe('DimensionsBuilder', () => { } as VBIDSL const builder = VBI.from(dsl) - builder.dimensions.remove('category') + builder.dimensions.remove('id-1') - expect(builder.build().dimensions).toEqual([{ field: 'region', alias: '地区' }]) + expect(builder.build().dimensions).toEqual([{ id: 'id-2', field: 'region', alias: '地区' }]) }) test('removeDimension not found', () => { @@ -71,18 +74,18 @@ describe('DimensionsBuilder', () => { builder.dimensions.remove('notExist') - expect(builder.build().dimensions).toEqual([{ field: 'category', alias: '类别' }]) + expect(builder.build().dimensions).toEqual([{ id: 'id-1', field: 'category', alias: '类别' }]) }) test('updateDimension', () => { const dsl = { dimensions: [{ field: 'category', alias: '类别' }] } as VBIDSL const builder = VBI.from(dsl) - builder.dimensions.update('category', (node) => { + builder.dimensions.update('id-1', (node) => { node.setAlias('新类别') }) - expect(builder.build().dimensions).toEqual([{ field: 'category', alias: '新类别' }]) + expect(builder.build().dimensions).toEqual([{ id: 'id-1', field: 'category', alias: '新类别' }]) }) test('updateDimension throws error if not found', () => { @@ -93,7 +96,7 @@ describe('DimensionsBuilder', () => { builder.dimensions.update('notExist', (node) => { node.setAlias('新类别') }) - }).toThrow('Dimension with field "notExist" not found') + }).toThrow('Dimension with id "notExist" not found') }) test('findDimension', () => { @@ -105,17 +108,17 @@ describe('DimensionsBuilder', () => { } as VBIDSL const builder = VBI.from(dsl) - const node = builder.dimensions.find('category') + const node = builder.dimensions.find((node) => node.getId() === 'id-1') expect(node?.getField()).toBe('category') - expect(node?.toJSON()).toEqual({ field: 'category', alias: '类别' }) + expect(node?.toJSON()).toEqual({ id: 'id-1', field: 'category', alias: '类别' }) }) test('findDimension returns undefined if not found', () => { const dsl = {} as VBIDSL const builder = VBI.from(dsl) - const node = builder.dimensions.find('notExist') + const node = builder.dimensions.find((node) => node.getId() === 'notExist') expect(node).toBeUndefined() }) @@ -157,8 +160,8 @@ describe('DimensionsBuilder', () => { const json = builder.dimensions.toJSON() expect(json).toEqual([ - { field: 'category', alias: '类别' }, - { field: 'region', alias: '地区' }, + { id: 'id-1', field: 'category', alias: '类别' }, + { id: 'id-2', field: 'region', alias: '地区' }, ]) }) @@ -197,7 +200,7 @@ describe('DimensionsBuilder', () => { }) test('isDimensionNode', () => { - const node = { field: 'category', alias: '类别' } + const node = { id: 'id-1', field: 'category', alias: '类别' } const group = { field: 'group1', children: [] } expect(DimensionsBuilder.isDimensionNode(node)).toBe(true) @@ -205,7 +208,7 @@ describe('DimensionsBuilder', () => { }) test('isDimensionGroup', () => { - const node = { field: 'category', alias: '类别' } + const node = { id: 'id-1', field: 'category', alias: '类别' } const group = { field: 'group1', children: [] } expect(DimensionsBuilder.isDimensionGroup(node)).toBe(false) @@ -216,16 +219,25 @@ describe('DimensionsBuilder', () => { const dsl = { dimensions: [{ field: 'category', alias: '类别' }] } as VBIDSL const builder = VBI.from(dsl) - const node = builder.dimensions.find('category') + const node = builder.dimensions.find((node) => node.getId() === 'id-1') expect(node?.getField()).toBe('category') }) + test('DimensionNodeBuilder getId', () => { + const dsl = { dimensions: [{ field: 'category', alias: '类别' }] } as VBIDSL + const builder = VBI.from(dsl) + + const node = builder.dimensions.find((node) => node.getId() === 'id-1') + + expect(node?.getId()).toBe('id-1') + }) + test('DimensionNodeBuilder setAlias', () => { const dsl = { dimensions: [{ field: 'category', alias: '类别' }] } as VBIDSL const builder = VBI.from(dsl) - const node = builder.dimensions.find('category') + const node = builder.dimensions.find((node) => node.getId() === 'id-1') node?.setAlias('新类别') expect(builder.dimensions.toJSON()[0].alias).toBe('新类别') @@ -235,10 +247,10 @@ describe('DimensionsBuilder', () => { const dsl = { dimensions: [{ field: 'category', alias: '类别' }] } as VBIDSL const builder = VBI.from(dsl) - const node = builder.dimensions.find('category') + const node = builder.dimensions.find((node) => node.getId() === 'id-1') const json = node?.toJSON() - expect(json).toEqual({ field: 'category', alias: '类别' }) + expect(json).toEqual({ id: 'id-1', field: 'category', alias: '类别' }) }) test('chained add operations', () => { diff --git a/packages/vbi/tests/builder/features/filterCoverage.test.ts b/packages/vbi/tests/builder/features/filterCoverage.test.ts index dd8713c4a3..57e9cca4a3 100644 --- a/packages/vbi/tests/builder/features/filterCoverage.test.ts +++ b/packages/vbi/tests/builder/features/filterCoverage.test.ts @@ -54,7 +54,7 @@ describe('Where filter internals', () => { group.add('city', (node) => node.setOperator('eq').setValue('杭州')) }) - const group = builder.whereFilter.find('id-1') as any + const group = builder.whereFilter.find((entry) => entry.getId() === 'id-1') as any group.remove('id-2') group.remove(99) @@ -132,7 +132,7 @@ describe('Having filter internals', () => { group.add('利润', (node) => node.setOperator('gt').setValue(200)) }) - const group = builder.havingFilter.find('id-1') as any + const group = builder.havingFilter.find((entry) => entry.getId() === 'id-1') as any group.remove('id-2') group.remove(99) @@ -149,7 +149,7 @@ describe('Having filter internals', () => { node.setOperator('gte').setValue(1000) }) - const node = builder.havingFilter.find('id-1') as any + const node = builder.havingFilter.find((entry) => entry.getId() === 'id-1') as any expect(node.toJSON()).toEqual({ id: 'id-1', field: '销售额', op: 'gte', value: 1000 }) }) diff --git a/packages/vbi/tests/builder/features/havingFilter.test.ts b/packages/vbi/tests/builder/features/havingFilter.test.ts index 0be771e373..2488639bab 100644 --- a/packages/vbi/tests/builder/features/havingFilter.test.ts +++ b/packages/vbi/tests/builder/features/havingFilter.test.ts @@ -129,7 +129,7 @@ describe('HavingFilterBuilder', () => { }) const filterId = builder.havingFilter.toJSON().conditions[0].id! - const found = builder.havingFilter.find(filterId) + const found = builder.havingFilter.find((entry) => entry.getId() === filterId) expect(found).toBeInstanceOf(HavingFilterNodeBuilder) expect((found as HavingFilterNodeBuilder).getField()).toBe('sales') @@ -138,7 +138,7 @@ describe('HavingFilterBuilder', () => { test('find returns undefined if not found', () => { const builder = VBI.from({} as VBIDSL) - expect(builder.havingFilter.find('non-existent')).toBeUndefined() + expect(builder.havingFilter.find((entry) => entry.getId() === 'non-existent')).toBeUndefined() }) test('clear removes all filters', () => { @@ -195,7 +195,7 @@ describe('HavingFilterBuilder', () => { builder.havingFilter.add('sales', (node) => node.setOperator('gt').setValue(1000)) const filterId = builder.havingFilter.toJSON().conditions[0].id! - const node = builder.havingFilter.find(filterId) as HavingFilterNodeBuilder + const node = builder.havingFilter.find((entry) => entry.getId() === filterId) as HavingFilterNodeBuilder expect(node.getId()).toBe(filterId) }) @@ -205,7 +205,7 @@ describe('HavingFilterBuilder', () => { builder.havingFilter.add('sales', (node) => node.setOperator('gt').setValue(1000)) const filterId = builder.havingFilter.toJSON().conditions[0].id! - const node = builder.havingFilter.find(filterId) as HavingFilterNodeBuilder + const node = builder.havingFilter.find((entry) => entry.getId() === filterId) as HavingFilterNodeBuilder expect(node.getOperator()).toBe('gt') }) @@ -270,7 +270,7 @@ describe('HavingGroupBuilder', () => { }) const groupId = builder.havingFilter.toJSON().conditions[0].id! - const found = builder.havingFilter.find(groupId) + const found = builder.havingFilter.find((entry) => entry.getId() === groupId) expect(found).toBeInstanceOf(HavingGroupBuilder) }) @@ -344,7 +344,7 @@ describe('HavingGroupBuilder', () => { }) const groupId = builder.havingFilter.toJSON().conditions[0].id! - const groupFound = builder.havingFilter.find(groupId) as HavingGroupBuilder + const groupFound = builder.havingFilter.find((entry) => entry.getId() === groupId) as HavingGroupBuilder const conditions = groupFound.toJSON().conditions const salesId = conditions[0].id! @@ -363,7 +363,7 @@ describe('HavingGroupBuilder', () => { }) const groupId = builder.havingFilter.toJSON().conditions[0].id! - const groupFound = builder.havingFilter.find(groupId) as HavingGroupBuilder + const groupFound = builder.havingFilter.find((entry) => entry.getId() === groupId) as HavingGroupBuilder groupFound.remove(0) @@ -379,7 +379,7 @@ describe('HavingGroupBuilder', () => { }) const groupId = builder.havingFilter.toJSON().conditions[0].id! - const groupFound = builder.havingFilter.find(groupId) as HavingGroupBuilder + const groupFound = builder.havingFilter.find((entry) => entry.getId() === groupId) as HavingGroupBuilder groupFound.remove('missing-id') @@ -396,7 +396,7 @@ describe('HavingGroupBuilder', () => { }) const groupId = builder.havingFilter.toJSON().conditions[0].id! - const groupFound = builder.havingFilter.find(groupId) as HavingGroupBuilder + const groupFound = builder.havingFilter.find((entry) => entry.getId() === groupId) as HavingGroupBuilder groupFound.clear() @@ -410,7 +410,7 @@ describe('HavingGroupBuilder', () => { }) const groupId = builder.havingFilter.toJSON().conditions[0].id! - const groupFound = builder.havingFilter.find(groupId) as HavingGroupBuilder + const groupFound = builder.havingFilter.find((entry) => entry.getId() === groupId) as HavingGroupBuilder expect(groupFound.getId()).toBe(groupId) expect(groupFound.getOperator()).toBe('or') diff --git a/packages/vbi/tests/builder/features/measures.test.ts b/packages/vbi/tests/builder/features/measures.test.ts index 137d175a65..3ad8ce084f 100644 --- a/packages/vbi/tests/builder/features/measures.test.ts +++ b/packages/vbi/tests/builder/features/measures.test.ts @@ -16,6 +16,7 @@ describe('MeasuresBuilder', () => { havingFilter: { id: 'root', op: 'and', conditions: [] }, measures: [ { + id: 'id-1', aggregate: { func: 'max', }, @@ -44,6 +45,7 @@ describe('MeasuresBuilder', () => { havingFilter: { id: 'root', op: 'and', conditions: [] }, measures: [ { + id: 'id-1', aggregate: { func: 'sum', }, @@ -52,6 +54,7 @@ describe('MeasuresBuilder', () => { field: 'sales', }, { + id: 'id-2', aggregate: { func: 'min', }, @@ -72,9 +75,11 @@ describe('MeasuresBuilder', () => { } as VBIDSL const builder = VBI.from(dsl) - builder.measures.remove('sales') + builder.measures.remove('id-1') - expect(builder.build().measures).toEqual([{ field: 'orders', alias: '订单数', aggregate: { func: 'count' } }]) + expect(builder.build().measures).toEqual([ + { id: 'id-2', field: 'orders', alias: '订单数', aggregate: { func: 'count' } }, + ]) }) test('removeMeasure not found', () => { @@ -83,18 +88,20 @@ describe('MeasuresBuilder', () => { builder.measures.remove('notExist') - expect(builder.build().measures).toEqual([{ field: 'sales', alias: '销售额' }]) + expect(builder.build().measures).toEqual([{ id: 'id-1', field: 'sales', alias: '销售额' }]) }) test('updateMeasure', () => { const dsl = { measures: [{ field: 'sales', alias: '销售额', aggregate: { func: 'sum' } }] } as VBIDSL const builder = VBI.from(dsl) - builder.measures.update('sales', (node) => { + builder.measures.update('id-1', (node) => { node.setAlias('新销售额').setAggregate({ func: 'avg' }) }) - expect(builder.build().measures).toEqual([{ field: 'sales', alias: '新销售额', aggregate: { func: 'avg' } }]) + expect(builder.build().measures).toEqual([ + { id: 'id-1', field: 'sales', alias: '新销售额', aggregate: { func: 'avg' } }, + ]) }) test('updateMeasure throws error if not found', () => { @@ -105,7 +112,7 @@ describe('MeasuresBuilder', () => { builder.measures.update('notExist', (node) => { node.setAlias('新销售额') }) - }).toThrow('Measure with field "notExist" not found') + }).toThrow('Measure with id "notExist" not found') }) test('findMeasure', () => { @@ -117,17 +124,17 @@ describe('MeasuresBuilder', () => { } as VBIDSL const builder = VBI.from(dsl) - const node = builder.measures.find('sales') + const node = builder.measures.find((node) => node.getId() === 'id-1') expect(node?.getField()).toBe('sales') - expect(node?.toJSON()).toEqual({ field: 'sales', alias: '销售额' }) + expect(node?.toJSON()).toEqual({ id: 'id-1', field: 'sales', alias: '销售额' }) }) test('findMeasure returns undefined if not found', () => { const dsl = {} as VBIDSL const builder = VBI.from(dsl) - const node = builder.measures.find('notExist') + const node = builder.measures.find((node) => node.getId() === 'notExist') expect(node).toBeUndefined() }) @@ -169,8 +176,8 @@ describe('MeasuresBuilder', () => { const json = builder.measures.toJSON() expect(json).toEqual([ - { field: 'sales', alias: '销售额' }, - { field: 'orders', alias: '订单数' }, + { id: 'id-1', field: 'sales', alias: '销售额' }, + { id: 'id-2', field: 'orders', alias: '订单数' }, ]) }) @@ -228,16 +235,25 @@ describe('MeasuresBuilder', () => { const dsl = { measures: [{ field: 'sales', alias: '销售额' }] } as VBIDSL const builder = VBI.from(dsl) - const node = builder.measures.find('sales') + const node = builder.measures.find((node) => node.getId() === 'id-1') expect(node?.getField()).toBe('sales') }) + test('MeasureNodeBuilder getId', () => { + const dsl = { measures: [{ field: 'sales', alias: '销售额' }] } as VBIDSL + const builder = VBI.from(dsl) + + const node = builder.measures.find((node) => node.getId() === 'id-1') + + expect(node?.getId()).toBe('id-1') + }) + test('MeasureNodeBuilder setAlias', () => { const dsl = { measures: [{ field: 'sales', alias: '销售额' }] } as VBIDSL const builder = VBI.from(dsl) - const node = builder.measures.find('sales') + const node = builder.measures.find((node) => node.getId() === 'id-1') node?.setAlias('新销售额') expect(builder.measures.toJSON()[0].alias).toBe('新销售额') @@ -247,7 +263,7 @@ describe('MeasuresBuilder', () => { const dsl = { measures: [{ field: 'sales', alias: '销售额' }] } as VBIDSL const builder = VBI.from(dsl) - const node = builder.measures.find('sales') + const node = builder.measures.find((node) => node.getId() === 'id-1') node?.setEncoding('xAxis') expect(builder.measures.toJSON()[0].encoding).toBe('xAxis') @@ -257,7 +273,7 @@ describe('MeasuresBuilder', () => { const dsl = { measures: [{ field: 'sales', alias: '销售额' }] } as VBIDSL const builder = VBI.from(dsl) - const node = builder.measures.find('sales') + const node = builder.measures.find((node) => node.getId() === 'id-1') node?.setAggregate({ func: 'avg' }) expect(builder.measures.toJSON()[0].aggregate).toEqual({ func: 'avg' }) @@ -267,10 +283,10 @@ describe('MeasuresBuilder', () => { const dsl = { measures: [{ field: 'sales', alias: '销售额' }] } as VBIDSL const builder = VBI.from(dsl) - const node = builder.measures.find('sales') + const node = builder.measures.find((node) => node.getId() === 'id-1') const json = node?.toJSON() - expect(json).toEqual({ field: 'sales', alias: '销售额' }) + expect(json).toEqual({ id: 'id-1', field: 'sales', alias: '销售额' }) }) test('chained add operations', () => { diff --git a/packages/vbi/tests/builder/features/whereFilter.test.ts b/packages/vbi/tests/builder/features/whereFilter.test.ts index d90faec92f..aab6b0f123 100644 --- a/packages/vbi/tests/builder/features/whereFilter.test.ts +++ b/packages/vbi/tests/builder/features/whereFilter.test.ts @@ -187,7 +187,7 @@ describe('WhereFilterBuilder', () => { .add('category', (node) => node.setOperator('eq').setValue('Electronics')) .add('region', (node) => node.setOperator('eq').setValue('Beijing')) - const node = builder.whereFilter.find('id-1') + const node = builder.whereFilter.find((entry) => entry.getId() === 'id-1') expect(node).toBeDefined() expect((node as any).getField()).toBe('category') @@ -207,7 +207,7 @@ describe('WhereFilterBuilder', () => { group.add('region', (node) => node.setOperator('eq').setValue('Beijing')) }) - const node = builder.whereFilter.find('id-2') + const node = builder.whereFilter.find((entry) => entry.getId() === 'id-2') expect(node).toBeDefined() expect((node as any).getField()).toBe('region') @@ -223,7 +223,7 @@ describe('WhereFilterBuilder', () => { const dsl = {} as VBIDSL const builder = VBI.from(dsl) - const node = builder.whereFilter.find('not-exist') + const node = builder.whereFilter.find((entry) => entry.getId() === 'not-exist') expect(node).toBeUndefined() }) @@ -318,7 +318,7 @@ describe('WhereFilterBuilder', () => { const builder = VBI.from(dsl) builder.whereFilter.add('category', (node) => node.setOperator('eq').setValue('Electronics')) - const node = builder.whereFilter.find('id-1') + const node = builder.whereFilter.find((entry) => entry.getId() === 'id-1') expect((node as any).getId()).toBe('id-1') }) @@ -327,7 +327,7 @@ describe('WhereFilterBuilder', () => { const builder = VBI.from(dsl) builder.whereFilter.add('category', (node) => node.setOperator('eq').setValue('Electronics')) - const node = builder.whereFilter.find('id-1') + const node = builder.whereFilter.find((entry) => entry.getId() === 'id-1') expect((node as any).getField()).toBe('category') }) @@ -336,7 +336,7 @@ describe('WhereFilterBuilder', () => { const builder = VBI.from(dsl) builder.whereFilter.add('category', (node) => node.setOperator('eq').setValue('Electronics')) - const node = builder.whereFilter.find('id-1') + const node = builder.whereFilter.find((entry) => entry.getId() === 'id-1') expect((node as any).getOperator()).toBe('eq') }) @@ -345,7 +345,7 @@ describe('WhereFilterBuilder', () => { const builder = VBI.from(dsl) builder.whereFilter.add('category', (node) => node.setOperator('eq').setValue('Electronics')) - const node = builder.whereFilter.find('id-1') + const node = builder.whereFilter.find((entry) => entry.getId() === 'id-1') ;(node as any).setOperator('in') expect((builder.whereFilter.toJSON().conditions[0] as VBIFilter).op).toBe('in') @@ -356,7 +356,7 @@ describe('WhereFilterBuilder', () => { const builder = VBI.from(dsl) builder.whereFilter.add('category', (node) => node.setOperator('eq').setValue('Electronics')) - const node = builder.whereFilter.find('id-1') + const node = builder.whereFilter.find((entry) => entry.getId() === 'id-1') ;(node as any).setValue(['Electronics', 'Books']) expect((builder.whereFilter.toJSON().conditions[0] as VBIFilter).value).toEqual(['Electronics', 'Books']) @@ -367,7 +367,7 @@ describe('WhereFilterBuilder', () => { const builder = VBI.from(dsl) builder.whereFilter.add('category', (node) => node.setOperator('eq').setValue('Electronics')) - const node = builder.whereFilter.find('id-1') + const node = builder.whereFilter.find((entry) => entry.getId() === 'id-1') expect((node as any).toJSON()).toEqual({ id: 'id-1', field: 'category', @@ -422,8 +422,8 @@ describe('WhereFilterBuilder', () => { const builder = VBI.from({ ...VBI.generateEmptyDSL('demo'), chartType: 'column', - dimensions: [{ field: 'category', alias: 'category' }], - measures: [{ field: 'sales', alias: 'sales', encoding: 'yAxis', aggregate: { func: 'sum' } }], + dimensions: [{ id: 'id-1', field: 'category', alias: 'category' }], + measures: [{ id: 'id-2', field: 'sales', alias: 'sales', encoding: 'yAxis', aggregate: { func: 'sum' } }], whereFilter: { id: 'root', op: 'and', conditions: [] }, havingFilter: { id: 'root', op: 'and', conditions: [] }, version: 1, @@ -543,7 +543,7 @@ describe('WhereGroupBuilder', () => { builder.whereFilter.addGroup('or', () => {}) - const group = builder.whereFilter.find('id-1') + const group = builder.whereFilter.find((entry) => entry.getId() === 'id-1') expect(group).toBeDefined() expect((group as any).getOperator()).toBe('or') }) @@ -554,7 +554,7 @@ describe('WhereGroupBuilder', () => { builder.whereFilter.addGroup('or', () => {}) - const group = builder.whereFilter.find('id-1') + const group = builder.whereFilter.find((entry) => entry.getId() === 'id-1') expect((group as any).getId()).toBe('id-1') expect((group as any).getConditions()).toBeInstanceOf(Y.Array) }) diff --git a/packages/vbi/tests/builder/filters.test.ts b/packages/vbi/tests/builder/filters.test.ts index a255ea6266..9fda3e04e6 100644 --- a/packages/vbi/tests/builder/filters.test.ts +++ b/packages/vbi/tests/builder/filters.test.ts @@ -155,8 +155,8 @@ describe('WhereFilterBuilder', () => { const whereFilter = builder.whereFilter.toJSON().conditions expect(whereFilter).toHaveLength(2) - const node1 = builder.whereFilter.find('id-1') - const node2 = builder.whereFilter.find('id-2') + const node1 = builder.whereFilter.find((entry) => entry.getId() === 'id-1') + const node2 = builder.whereFilter.find((entry) => entry.getId() === 'id-2') expect((node1 as any).toJSON().field).toBe('sales') expect((node2 as any).toJSON().field).toBe('region') }) @@ -220,7 +220,7 @@ describe('WhereFilterBuilder', () => { node.setOperator('eq').setValue('Beijing') }) - const found = builder.whereFilter.find('id-2') + const found = builder.whereFilter.find((entry) => entry.getId() === 'id-2') expect((found as any).toJSON()).toEqual({ id: 'id-2', field: 'region', @@ -228,7 +228,7 @@ describe('WhereFilterBuilder', () => { value: 'Beijing', }) - const notFound = builder.whereFilter.find('nonexistent') + const notFound = builder.whereFilter.find((entry) => entry.getId() === 'nonexistent') expect(notFound).toBeUndefined() }) }) diff --git a/packages/vbi/tests/builder/yjs.test.ts b/packages/vbi/tests/builder/yjs.test.ts index 0b5c69cdd6..f1ff045b57 100644 --- a/packages/vbi/tests/builder/yjs.test.ts +++ b/packages/vbi/tests/builder/yjs.test.ts @@ -22,6 +22,7 @@ describe('VBI YJS Integration', () => { havingFilter: { id: 'root', op: 'and', conditions: [] }, measures: [ { + id: 'id-1', aggregate: { func: 'sum', }, @@ -57,6 +58,7 @@ test('encodeStateAsUpdate', () => { havingFilter: { id: 'root', op: 'and', conditions: [] }, measures: [ { + id: 'id-1', aggregate: { func: 'max', }, diff --git a/packages/vbi/tests/examples/chartType/chartType.test.ts b/packages/vbi/tests/examples/chartType/chartType.test.ts index 1edd27f097..debb4b97a2 100644 --- a/packages/vbi/tests/examples/chartType/chartType.test.ts +++ b/packages/vbi/tests/examples/chartType/chartType.test.ts @@ -58,6 +58,7 @@ describe('ChartType', () => { { "alias": "订单日期", "field": "order_date", + "id": "id-2", }, ], "havingFilter": { @@ -75,6 +76,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -216,6 +218,7 @@ describe('ChartType', () => { { "alias": "产品类型", "field": "product_type", + "id": "id-2", }, ], "havingFilter": { @@ -233,6 +236,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "xAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -346,6 +350,7 @@ describe('ChartType', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -363,6 +368,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -504,6 +510,7 @@ describe('ChartType', () => { { "alias": "区域", "field": "area", + "id": "id-2", }, ], "havingFilter": { @@ -521,6 +528,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -646,6 +654,7 @@ describe('ChartType', () => { { "alias": "客户类型", "field": "customer_type", + "id": "id-2", }, ], "havingFilter": { @@ -663,6 +672,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "size", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -776,6 +786,7 @@ describe('ChartType', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -793,6 +804,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -934,6 +946,7 @@ describe('ChartType', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -951,6 +964,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -1092,6 +1106,7 @@ describe('ChartType', () => { { "alias": "区域", "field": "area", + "id": "id-2", }, ], "havingFilter": { @@ -1109,6 +1124,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "size", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -1234,6 +1250,7 @@ describe('ChartType', () => { { "alias": "城市", "field": "city", + "id": "id-2", }, ], "havingFilter": { @@ -1251,6 +1268,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "size", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -1407,6 +1425,7 @@ describe('ChartType', () => { "alias": "销售额", "encoding": "xAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -1415,6 +1434,7 @@ describe('ChartType', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", diff --git a/packages/vbi/tests/examples/dimensions/add-dimension.json b/packages/vbi/tests/examples/dimensions/add-dimension.json index 9f9aba31d6..a3764099be 100644 --- a/packages/vbi/tests/examples/dimensions/add-dimension.json +++ b/packages/vbi/tests/examples/dimensions/add-dimension.json @@ -17,5 +17,5 @@ "measures": [], "limit": 20 }, - "code": "const applyBuilder = (builder: VBIBuilder) => {\n builder.dimensions.add('product_type', node => {\n node.setAlias('商品类型')\n })\n builder.dimensions.update('product_type', node => {\n node.setAlias('产品类型')\n })\n}" + "code": "const applyBuilder = (builder: VBIBuilder) => {\n builder.dimensions.add('product_type', node => {\n node.setAlias('商品类型')\n })\n const dimensionId = builder.dimensions.find(node => node.getField() === 'product_type')?.getId()\n if (dimensionId) {\n builder.dimensions.update(dimensionId, node => {\n node.setAlias('产品类型')\n })\n }\n}" } diff --git a/packages/vbi/tests/examples/dimensions/dimensions.test.ts b/packages/vbi/tests/examples/dimensions/dimensions.test.ts index fcb3c20e3c..f9b6680a15 100644 --- a/packages/vbi/tests/examples/dimensions/dimensions.test.ts +++ b/packages/vbi/tests/examples/dimensions/dimensions.test.ts @@ -33,9 +33,12 @@ describe('Dimensions', () => { builder.dimensions.add('product_type', (node) => { node.setAlias('商品类型') }) - builder.dimensions.update('product_type', (node) => { - node.setAlias('产品类型') - }) + const dimensionId = builder.dimensions.find((node) => node.getField() === 'product_type')?.getId() + if (dimensionId) { + builder.dimensions.update(dimensionId, (node) => { + node.setAlias('产品类型') + }) + } } applyBuilder(builder) @@ -49,6 +52,7 @@ describe('Dimensions', () => { { "alias": "产品类型", "field": "product_type", + "id": "id-1", }, ], "havingFilter": { @@ -146,10 +150,12 @@ describe('Dimensions', () => { { "alias": "产品类型", "field": "product_type", + "id": "id-1", }, { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -318,10 +324,13 @@ describe('Dimensions', () => { // Apply custom builder code const applyBuilder = (builder: VBIBuilder) => { - builder.dimensions.update('product_type', (node) => { - node.setAlias('待删除的产品类型') - }) - builder.dimensions.remove('product_type') + const dimensionId = builder.dimensions.toJSON().find((item) => item.field === 'product_type')?.id + if (dimensionId) { + builder.dimensions.update(dimensionId, (node) => { + node.setAlias('待删除的产品类型') + }) + builder.dimensions.remove(dimensionId) + } } applyBuilder(builder) @@ -335,6 +344,7 @@ describe('Dimensions', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -474,11 +484,14 @@ describe('Dimensions', () => { // Apply custom builder code const applyBuilder = (builder: VBIBuilder) => { - const dimension = builder.dimensions.find('product_type') - if (dimension) { - dimension.setAlias('待调整的产品类型') + const dimensionId = builder.dimensions.toJSON().find((item) => item.field === 'product_type')?.id + if (dimensionId) { + const dimension = builder.dimensions.find((node) => node.getId() === dimensionId) + if (dimension) { + dimension.setAlias('待调整的产品类型') + } + builder.dimensions.update(dimensionId, (n) => n.setAlias('新产品类型')) } - builder.dimensions.update('product_type', (n) => n.setAlias('新产品类型')) } applyBuilder(builder) @@ -492,6 +505,7 @@ describe('Dimensions', () => { { "alias": "新产品类型", "field": "product_type", + "id": "id-1", }, ], "havingFilter": { diff --git a/packages/vbi/tests/examples/dimensions/remove-dimension.json b/packages/vbi/tests/examples/dimensions/remove-dimension.json index 4043bb50af..5570d55c28 100644 --- a/packages/vbi/tests/examples/dimensions/remove-dimension.json +++ b/packages/vbi/tests/examples/dimensions/remove-dimension.json @@ -30,5 +30,5 @@ "measures": [], "limit": 20 }, - "code": "const applyBuilder = (builder: VBIBuilder) => {\n builder.dimensions.update('product_type', node => {\n node.setAlias('待删除的产品类型')\n })\n builder.dimensions.remove('product_type')\n}" + "code": "const applyBuilder = (builder: VBIBuilder) => {\n const dimensionId = builder.dimensions.toJSON().find(item => item.field === 'product_type')?.id\n if (dimensionId) {\n builder.dimensions.update(dimensionId, node => {\n node.setAlias('待删除的产品类型')\n })\n builder.dimensions.remove(dimensionId)\n }\n}" } diff --git a/packages/vbi/tests/examples/dimensions/update-dimension.json b/packages/vbi/tests/examples/dimensions/update-dimension.json index 9d65dafc96..a7dfc742ed 100644 --- a/packages/vbi/tests/examples/dimensions/update-dimension.json +++ b/packages/vbi/tests/examples/dimensions/update-dimension.json @@ -22,5 +22,5 @@ "measures": [], "limit": 20 }, - "code": "const applyBuilder = (builder: VBIBuilder) => {\n const dimension = builder.dimensions.find('product_type')\n if (dimension) {\n dimension.setAlias('待调整的产品类型')\n }\n builder.dimensions.update('product_type', n => n.setAlias('新产品类型'))\n}" + "code": "const applyBuilder = (builder: VBIBuilder) => {\n const dimensionId = builder.dimensions.toJSON().find(item => item.field === 'product_type')?.id\n if (dimensionId) {\n const dimension = builder.dimensions.find((node) => node.getId() === dimensionId)\n if (dimension) {\n dimension.setAlias('待调整的产品类型')\n }\n builder.dimensions.update(dimensionId, n => n.setAlias('新产品类型'))\n }\n}" } diff --git a/packages/vbi/tests/examples/havingFilter/havingFilter.test.ts b/packages/vbi/tests/examples/havingFilter/havingFilter.test.ts index e6d395db55..856c9ae0ff 100644 --- a/packages/vbi/tests/examples/havingFilter/havingFilter.test.ts +++ b/packages/vbi/tests/examples/havingFilter/havingFilter.test.ts @@ -60,13 +60,14 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-2", }, ], "havingFilter": { "conditions": [ { "field": "销售额", - "id": "id-1", + "id": "id-3", "op": "gt", "value": 1000000, }, @@ -84,6 +85,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -225,19 +227,20 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-3", }, ], "havingFilter": { "conditions": [ { "field": "销售额", - "id": "id-1", + "id": "id-4", "op": "gt", "value": 1000000, }, { "field": "利润", - "id": "id-2", + "id": "id-5", "op": "gt", "value": 200000, }, @@ -255,6 +258,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -263,6 +267,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", @@ -427,6 +432,7 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-3", }, ], "havingFilter": { @@ -444,6 +450,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -452,6 +459,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", @@ -620,6 +628,7 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-4", }, ], "havingFilter": { @@ -628,7 +637,7 @@ describe('HavingFilter', () => { "conditions": [ { "field": "销售额", - "id": "id-2", + "id": "id-6", "op": "gte", "value": 100000, }, @@ -636,22 +645,22 @@ describe('HavingFilter', () => { "conditions": [ { "field": "利润", - "id": "id-4", + "id": "id-8", "op": "gt", "value": 20000, }, { "field": "数量", - "id": "id-5", + "id": "id-9", "op": "gte", "value": 50, }, ], - "id": "id-3", + "id": "id-7", "op": "or", }, ], - "id": "id-1", + "id": "id-5", "op": "and", }, ], @@ -668,6 +677,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -676,6 +686,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, { "aggregate": { @@ -684,6 +695,7 @@ describe('HavingFilter', () => { "alias": "数量", "encoding": "yAxis", "field": "amount", + "id": "id-3", }, ], "theme": "light", @@ -898,6 +910,7 @@ describe('HavingFilter', () => { { "alias": "省份", "field": "province", + "id": "id-5", }, ], "havingFilter": { @@ -908,40 +921,40 @@ describe('HavingFilter', () => { "conditions": [ { "field": "销售额", - "id": "id-3", + "id": "id-8", "op": "gt", "value": 500000, }, { "field": "利润", - "id": "id-4", + "id": "id-9", "op": "gt", "value": 50000, }, ], - "id": "id-2", + "id": "id-7", "op": "and", }, { "conditions": [ { "field": "数量", - "id": "id-6", + "id": "id-11", "op": "gt", "value": 100, }, { "field": "平均折扣", - "id": "id-7", + "id": "id-12", "op": "lt", "value": 0.3, }, ], - "id": "id-5", + "id": "id-10", "op": "and", }, ], - "id": "id-1", + "id": "id-6", "op": "or", }, ], @@ -958,6 +971,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -966,6 +980,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, { "aggregate": { @@ -974,6 +989,7 @@ describe('HavingFilter', () => { "alias": "数量", "encoding": "yAxis", "field": "amount", + "id": "id-3", }, { "aggregate": { @@ -982,6 +998,7 @@ describe('HavingFilter', () => { "alias": "平均折扣", "encoding": "yAxis", "field": "discount", + "id": "id-4", }, ], "theme": "light", @@ -1233,19 +1250,20 @@ describe('HavingFilter', () => { { "alias": "品类", "field": "product_type", + "id": "id-3", }, ], "havingFilter": { "conditions": [ { "field": "销售额", - "id": "id-1", + "id": "id-4", "op": "gte", "value": 500000, }, { "field": "利润", - "id": "id-2", + "id": "id-5", "op": "gte", "value": 50000, }, @@ -1263,6 +1281,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -1271,6 +1290,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", @@ -1441,6 +1461,7 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-4", }, ], "havingFilter": { @@ -1455,13 +1476,13 @@ describe('HavingFilter', () => { }, { "field": "利润", - "id": "id-1", + "id": "id-5", "op": "gt", "value": 100000, }, { "field": "数量", - "id": "id-2", + "id": "id-6", "op": "gte", "value": 200, }, @@ -1483,6 +1504,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -1491,6 +1513,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, { "aggregate": { @@ -1499,6 +1522,7 @@ describe('HavingFilter', () => { "alias": "数量", "encoding": "yAxis", "field": "amount", + "id": "id-3", }, ], "theme": "light", @@ -1704,6 +1728,7 @@ describe('HavingFilter', () => { { "alias": "品类", "field": "product_type", + "id": "id-3", }, ], "havingFilter": { @@ -1734,6 +1759,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -1742,6 +1768,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", @@ -1901,13 +1928,14 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-4", }, ], "havingFilter": { "conditions": [ { "field": "销售额", - "id": "id-1", + "id": "id-5", "op": "gt", "value": 500000, }, @@ -1915,18 +1943,18 @@ describe('HavingFilter', () => { "conditions": [ { "field": "利润", - "id": "id-3", + "id": "id-7", "op": "gt", "value": 100000, }, { "field": "数量", - "id": "id-4", + "id": "id-8", "op": "gte", "value": 30, }, ], - "id": "id-2", + "id": "id-6", "op": "or", }, ], @@ -1943,6 +1971,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -1951,6 +1980,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, { "aggregate": { @@ -1959,6 +1989,7 @@ describe('HavingFilter', () => { "alias": "数量", "encoding": "yAxis", "field": "amount", + "id": "id-3", }, ], "theme": "light", @@ -2150,10 +2181,12 @@ describe('HavingFilter', () => { { "alias": "品类", "field": "product_type", + "id": "id-3", }, { "alias": "区域", "field": "area", + "id": "id-4", }, ], "havingFilter": { @@ -2162,18 +2195,18 @@ describe('HavingFilter', () => { "conditions": [ { "field": "平均折扣", - "id": "id-2", + "id": "id-6", "op": "lt", "value": 0.2, }, { "field": "销售额", - "id": "id-3", + "id": "id-7", "op": "gt", "value": 100000, }, ], - "id": "id-1", + "id": "id-5", "op": "and", }, ], @@ -2190,6 +2223,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -2198,6 +2232,7 @@ describe('HavingFilter', () => { "alias": "平均折扣", "encoding": "yAxis", "field": "discount", + "id": "id-2", }, ], "theme": "light", @@ -2413,6 +2448,7 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-4", }, ], "havingFilter": { @@ -2421,7 +2457,7 @@ describe('HavingFilter', () => { "conditions": [ { "field": "销售额", - "id": "id-2", + "id": "id-6", "op": "gt", "value": 1000000, }, @@ -2429,22 +2465,22 @@ describe('HavingFilter', () => { "conditions": [ { "field": "利润", - "id": "id-4", + "id": "id-8", "op": "gt", "value": 200000, }, { "field": "数量", - "id": "id-5", + "id": "id-9", "op": "gte", "value": 50, }, ], - "id": "id-3", + "id": "id-7", "op": "or", }, ], - "id": "id-1", + "id": "id-5", "op": "and", }, ], @@ -2461,6 +2497,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -2469,6 +2506,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, { "aggregate": { @@ -2477,6 +2515,7 @@ describe('HavingFilter', () => { "alias": "数量", "encoding": "yAxis", "field": "amount", + "id": "id-3", }, ], "theme": "light", @@ -2663,6 +2702,7 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-3", }, ], "havingFilter": { @@ -2671,18 +2711,18 @@ describe('HavingFilter', () => { "conditions": [ { "field": "销售额", - "id": "id-2", + "id": "id-5", "op": "gt", "value": 1000000, }, { "field": "利润", - "id": "id-3", + "id": "id-6", "op": "gt", "value": 200000, }, ], - "id": "id-1", + "id": "id-4", "op": "or", }, ], @@ -2699,6 +2739,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -2707,6 +2748,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", @@ -2880,6 +2922,7 @@ describe('HavingFilter', () => { { "alias": "子品类", "field": "product_sub_type", + "id": "id-4", }, ], "havingFilter": { @@ -2888,24 +2931,24 @@ describe('HavingFilter', () => { "conditions": [ { "field": "利润", - "id": "id-2", + "id": "id-6", "op": "gt", "value": 0, }, { "field": "数量", - "id": "id-3", + "id": "id-7", "op": "gt", "value": 20, }, { "field": "销售额", - "id": "id-4", + "id": "id-8", "op": "gt", "value": 10000, }, ], - "id": "id-1", + "id": "id-5", "op": "and", }, ], @@ -2922,6 +2965,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "xAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -2930,6 +2974,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, { "aggregate": { @@ -2938,6 +2983,7 @@ describe('HavingFilter', () => { "alias": "数量", "encoding": "size", "field": "amount", + "id": "id-3", }, ], "theme": "light", @@ -3167,6 +3213,7 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-3", }, ], "havingFilter": { @@ -3203,6 +3250,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -3211,6 +3259,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", @@ -3376,6 +3425,7 @@ describe('HavingFilter', () => { { "alias": "省份", "field": "province", + "id": "id-3", }, ], "havingFilter": { @@ -3384,18 +3434,18 @@ describe('HavingFilter', () => { "conditions": [ { "field": "销售额", - "id": "id-3", + "id": "id-6", "op": "gt", "value": 50000, }, { "field": "利润", - "id": "id-4", + "id": "id-7", "op": "gt", "value": 10000, }, ], - "id": "id-2", + "id": "id-5", "op": "or", }, ], @@ -3412,6 +3462,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -3420,6 +3471,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", @@ -3428,7 +3480,7 @@ describe('HavingFilter', () => { "conditions": [ { "field": "product_type", - "id": "id-1", + "id": "id-4", "op": "=", "value": "办公用品", }, @@ -3636,6 +3688,7 @@ describe('HavingFilter', () => { { "alias": "区域", "field": "area", + "id": "id-3", }, ], "havingFilter": { @@ -3660,6 +3713,7 @@ describe('HavingFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -3668,6 +3722,7 @@ describe('HavingFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", diff --git a/packages/vbi/tests/examples/locale/locale.test.ts b/packages/vbi/tests/examples/locale/locale.test.ts index 05f18e46fb..899b924d2b 100644 --- a/packages/vbi/tests/examples/locale/locale.test.ts +++ b/packages/vbi/tests/examples/locale/locale.test.ts @@ -61,6 +61,7 @@ describe('Locale', () => { { "alias": "Province", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -78,6 +79,7 @@ describe('Locale', () => { "alias": "Sales", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -222,6 +224,7 @@ describe('Locale', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -239,6 +242,7 @@ describe('Locale', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", diff --git a/packages/vbi/tests/examples/measures/add-measure-encoding.json b/packages/vbi/tests/examples/measures/add-measure-encoding.json index 77c2fc9741..3399b0c16b 100644 --- a/packages/vbi/tests/examples/measures/add-measure-encoding.json +++ b/packages/vbi/tests/examples/measures/add-measure-encoding.json @@ -21,5 +21,5 @@ "measures": [], "limit": 20 }, - "code": "const applyBuilder = (builder: VBIBuilder) => {\n builder.measures.add('sales', n => n.setAlias('销售额'))\n builder.measures.update('sales', n => n.setEncoding('yAxis').setAggregate({ func: 'sum' }))\n}" + "code": "const applyBuilder = (builder: VBIBuilder) => {\n builder.measures.add('sales', n => n.setAlias('销售额'))\n const measureId = builder.measures.find(node => node.getField() === 'sales')?.getId()\n if (measureId) {\n builder.measures.update(measureId, n => n.setEncoding('yAxis').setAggregate({ func: 'sum' }))\n }\n}" } diff --git a/packages/vbi/tests/examples/measures/add-measure.json b/packages/vbi/tests/examples/measures/add-measure.json index fcfc3c8835..764b49b4f9 100644 --- a/packages/vbi/tests/examples/measures/add-measure.json +++ b/packages/vbi/tests/examples/measures/add-measure.json @@ -17,5 +17,5 @@ "measures": [], "limit": 20 }, - "code": "const applyBuilder = (builder: VBIBuilder) => {\n builder.measures.add('sales', node => {\n node.setAlias('原销售额')\n })\n builder.measures.update('sales', node => {\n node.setAlias('销售额').setAggregate({ func: 'sum' })\n })\n}" + "code": "const applyBuilder = (builder: VBIBuilder) => {\n builder.measures.add('sales', node => {\n node.setAlias('原销售额')\n })\n const measureId = builder.measures.find(node => node.getField() === 'sales')?.getId()\n if (measureId) {\n builder.measures.update(measureId, node => {\n node.setAlias('销售额').setAggregate({ func: 'sum' })\n })\n }\n}" } diff --git a/packages/vbi/tests/examples/measures/measures.test.ts b/packages/vbi/tests/examples/measures/measures.test.ts index 1daebfcfc6..1baa70435f 100644 --- a/packages/vbi/tests/examples/measures/measures.test.ts +++ b/packages/vbi/tests/examples/measures/measures.test.ts @@ -31,7 +31,10 @@ describe('Measures', () => { // Apply custom builder code const applyBuilder = (builder: VBIBuilder) => { builder.measures.add('sales', (n) => n.setAlias('销售额')) - builder.measures.update('sales', (n) => n.setEncoding('yAxis').setAggregate({ func: 'sum' })) + const measureId = builder.measures.find((node) => node.getField() === 'sales')?.getId() + if (measureId) { + builder.measures.update(measureId, (n) => n.setEncoding('yAxis').setAggregate({ func: 'sum' })) + } } applyBuilder(builder) @@ -57,6 +60,7 @@ describe('Measures', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -130,9 +134,12 @@ describe('Measures', () => { builder.measures.add('sales', (node) => { node.setAlias('原销售额') }) - builder.measures.update('sales', (node) => { - node.setAlias('销售额').setAggregate({ func: 'sum' }) - }) + const measureId = builder.measures.find((node) => node.getField() === 'sales')?.getId() + if (measureId) { + builder.measures.update(measureId, (node) => { + node.setAlias('销售额').setAggregate({ func: 'sum' }) + }) + } } applyBuilder(builder) @@ -158,6 +165,7 @@ describe('Measures', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -245,8 +253,11 @@ describe('Measures', () => { // Apply custom builder code const applyBuilder = (builder: VBIBuilder) => { - builder.measures.update('sales', (n) => n.setAlias('待移除的销售额')) - builder.measures.remove('sales') + const measureId = builder.measures.toJSON().find((item) => item.field === 'sales')?.id + if (measureId) { + builder.measures.update(measureId, (n) => n.setAlias('待移除的销售额')) + builder.measures.remove(measureId) + } } applyBuilder(builder) @@ -272,6 +283,7 @@ describe('Measures', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", @@ -351,11 +363,14 @@ describe('Measures', () => { // Apply custom builder code const applyBuilder = (builder: VBIBuilder) => { - const measure = builder.measures.find('sales') - if (measure) { - measure.setAlias('待调整销售额').setEncoding('yAxis') + const measureId = builder.measures.toJSON().find((item) => item.field === 'sales')?.id + if (measureId) { + const measure = builder.measures.find((node) => node.getId() === measureId) + if (measure) { + measure.setAlias('待调整销售额').setEncoding('yAxis') + } + builder.measures.update(measureId, (n) => n.setAlias('新销售额').setAggregate({ func: 'avg' })) } - builder.measures.update('sales', (n) => n.setAlias('新销售额').setAggregate({ func: 'avg' })) } applyBuilder(builder) @@ -381,6 +396,7 @@ describe('Measures', () => { "alias": "新销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", diff --git a/packages/vbi/tests/examples/measures/remove-measure.json b/packages/vbi/tests/examples/measures/remove-measure.json index db9eb63636..b0fb15afe3 100644 --- a/packages/vbi/tests/examples/measures/remove-measure.json +++ b/packages/vbi/tests/examples/measures/remove-measure.json @@ -38,5 +38,5 @@ ], "limit": 20 }, - "code": "const applyBuilder = (builder: VBIBuilder) => {\n builder.measures.update('sales', n => n.setAlias('待移除的销售额'))\n builder.measures.remove('sales')\n}" + "code": "const applyBuilder = (builder: VBIBuilder) => {\n const measureId = builder.measures.toJSON().find(item => item.field === 'sales')?.id\n if (measureId) {\n builder.measures.update(measureId, n => n.setAlias('待移除的销售额'))\n builder.measures.remove(measureId)\n }\n}" } diff --git a/packages/vbi/tests/examples/measures/update-measure.json b/packages/vbi/tests/examples/measures/update-measure.json index 2927975245..60a5b2b78d 100644 --- a/packages/vbi/tests/examples/measures/update-measure.json +++ b/packages/vbi/tests/examples/measures/update-measure.json @@ -26,5 +26,5 @@ ], "limit": 20 }, - "code": "const applyBuilder = (builder: VBIBuilder) => {\n const measure = builder.measures.find('sales')\n if (measure) {\n measure.setAlias('待调整销售额').setEncoding('yAxis')\n }\n builder.measures.update('sales', n => n.setAlias('新销售额').setAggregate({ func: 'avg' }))\n}" + "code": "const applyBuilder = (builder: VBIBuilder) => {\n const measureId = builder.measures.toJSON().find(item => item.field === 'sales')?.id\n if (measureId) {\n const measure = builder.measures.find((node) => node.getId() === measureId)\n if (measure) {\n measure.setAlias('待调整销售额').setEncoding('yAxis')\n }\n builder.measures.update(measureId, n => n.setAlias('新销售额').setAggregate({ func: 'avg' }))\n }\n}" } diff --git a/packages/vbi/tests/examples/theme/theme.test.ts b/packages/vbi/tests/examples/theme/theme.test.ts index 5b4e360697..460c1d02d7 100644 --- a/packages/vbi/tests/examples/theme/theme.test.ts +++ b/packages/vbi/tests/examples/theme/theme.test.ts @@ -61,6 +61,7 @@ describe('Theme', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -78,6 +79,7 @@ describe('Theme', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "dark", @@ -222,6 +224,7 @@ describe('Theme', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -239,6 +242,7 @@ describe('Theme', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", diff --git a/packages/vbi/tests/examples/undoManager/undoManager.test.ts b/packages/vbi/tests/examples/undoManager/undoManager.test.ts index 552ff20449..fcc57e62f6 100644 --- a/packages/vbi/tests/examples/undoManager/undoManager.test.ts +++ b/packages/vbi/tests/examples/undoManager/undoManager.test.ts @@ -75,6 +75,7 @@ describe('UndoManager', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, { "aggregate": { @@ -83,6 +84,7 @@ describe('UndoManager', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-2", }, ], "theme": "light", diff --git a/packages/vbi/tests/examples/whereFilter/whereFilter.test.ts b/packages/vbi/tests/examples/whereFilter/whereFilter.test.ts index 6ef49fd770..95aec8c31f 100644 --- a/packages/vbi/tests/examples/whereFilter/whereFilter.test.ts +++ b/packages/vbi/tests/examples/whereFilter/whereFilter.test.ts @@ -60,6 +60,7 @@ describe('WhereFilter', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -77,6 +78,7 @@ describe('WhereFilter', () => { "alias": "销售额", "encoding": "xAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -85,7 +87,7 @@ describe('WhereFilter', () => { "conditions": [ { "field": "product_type", - "id": "id-1", + "id": "id-3", "op": "eq", "value": "办公用品", }, @@ -277,6 +279,7 @@ describe('WhereFilter', () => { { "alias": "区域", "field": "area", + "id": "id-2", }, ], "havingFilter": { @@ -294,6 +297,7 @@ describe('WhereFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-1", }, ], "theme": "light", @@ -302,13 +306,13 @@ describe('WhereFilter', () => { "conditions": [ { "field": "product_type", - "id": "id-1", + "id": "id-3", "op": "eq", "value": "技术", }, { "field": "discount", - "id": "id-2", + "id": "id-4", "op": ">", "value": 0.5, }, @@ -426,6 +430,7 @@ describe('WhereFilter', () => { { "alias": "区域", "field": "area", + "id": "id-2", }, ], "havingFilter": { @@ -443,6 +448,7 @@ describe('WhereFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -453,18 +459,18 @@ describe('WhereFilter', () => { "conditions": [ { "field": "product_type", - "id": "id-2", + "id": "id-4", "op": "eq", "value": "办公用品", }, { "field": "product_type", - "id": "id-3", + "id": "id-5", "op": "eq", "value": "技术", }, ], - "id": "id-1", + "id": "id-3", "op": "or", }, ], @@ -609,6 +615,7 @@ describe('WhereFilter', () => { { "alias": "品类", "field": "product_type", + "id": "id-2", }, ], "havingFilter": { @@ -626,6 +633,7 @@ describe('WhereFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-1", }, ], "theme": "light", @@ -634,7 +642,7 @@ describe('WhereFilter', () => { "conditions": [ { "field": "sales", - "id": "id-1", + "id": "id-3", "op": "between", "value": { "max": 10000, @@ -784,6 +792,7 @@ describe('WhereFilter', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -801,6 +810,7 @@ describe('WhereFilter', () => { "alias": "利润", "encoding": "yAxis", "field": "profit", + "id": "id-1", }, ], "theme": "light", @@ -809,7 +819,7 @@ describe('WhereFilter', () => { "conditions": [ { "field": "profit", - "id": "id-1", + "id": "id-3", "op": ">", "value": 0, }, @@ -817,18 +827,18 @@ describe('WhereFilter', () => { "conditions": [ { "field": "area", - "id": "id-3", + "id": "id-5", "op": "eq", "value": "华东", }, { "field": "area", - "id": "id-4", + "id": "id-6", "op": "eq", "value": "华北", }, ], - "id": "id-2", + "id": "id-4", "op": "or", }, ], @@ -1015,6 +1025,7 @@ describe('WhereFilter', () => { { "alias": "城市", "field": "city", + "id": "id-2", }, ], "havingFilter": { @@ -1032,6 +1043,7 @@ describe('WhereFilter', () => { "alias": "销售额", "encoding": "xAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -1247,6 +1259,7 @@ describe('WhereFilter', () => { { "alias": "城市", "field": "city", + "id": "id-2", }, ], "havingFilter": { @@ -1264,6 +1277,7 @@ describe('WhereFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -1272,7 +1286,7 @@ describe('WhereFilter', () => { "conditions": [ { "field": "area", - "id": "id-1", + "id": "id-3", "op": "eq", "value": "华东", }, @@ -1280,18 +1294,18 @@ describe('WhereFilter', () => { "conditions": [ { "field": "product_type", - "id": "id-3", + "id": "id-5", "op": "eq", "value": "办公用品", }, { "field": "product_type", - "id": "id-4", + "id": "id-6", "op": "eq", "value": "家具", }, ], - "id": "id-2", + "id": "id-4", "op": "or", }, ], @@ -1470,6 +1484,7 @@ describe('WhereFilter', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -1487,6 +1502,7 @@ describe('WhereFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -1495,7 +1511,7 @@ describe('WhereFilter', () => { "conditions": [ { "field": "sales", - "id": "id-1", + "id": "id-3", "op": ">", "value": 500, }, @@ -1505,25 +1521,25 @@ describe('WhereFilter', () => { "conditions": [ { "field": "customer_type", - "id": "id-4", + "id": "id-6", "op": "eq", "value": "消费者", }, { "field": "delivery_method", - "id": "id-5", + "id": "id-7", "op": "eq", "value": "当日", }, ], - "id": "id-3", + "id": "id-5", "op": "and", }, { "conditions": [ { "field": "customer_type", - "id": "id-7", + "id": "id-9", "op": "in", "value": [ "公司", @@ -1532,16 +1548,16 @@ describe('WhereFilter', () => { }, { "field": "delivery_method", - "id": "id-8", + "id": "id-10", "op": "eq", "value": "一级", }, ], - "id": "id-6", + "id": "id-8", "op": "and", }, ], - "id": "id-2", + "id": "id-4", "op": "or", }, ], @@ -1795,6 +1811,7 @@ describe('WhereFilter', () => { { "alias": "区域", "field": "area", + "id": "id-2", }, ], "havingFilter": { @@ -1812,6 +1829,7 @@ describe('WhereFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -1978,6 +1996,7 @@ describe('WhereFilter', () => { { "alias": "配送方式", "field": "delivery_method", + "id": "id-2", }, ], "havingFilter": { @@ -1995,6 +2014,7 @@ describe('WhereFilter', () => { "alias": "订单量", "encoding": "xAxis", "field": "amount", + "id": "id-1", }, ], "theme": "light", @@ -2003,7 +2023,7 @@ describe('WhereFilter', () => { "conditions": [ { "field": "area", - "id": "id-1", + "id": "id-3", "op": "in", "value": [ "华东", @@ -2150,6 +2170,7 @@ describe('WhereFilter', () => { { "alias": "省份", "field": "province", + "id": "id-2", }, ], "havingFilter": { @@ -2167,6 +2188,7 @@ describe('WhereFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", @@ -2386,6 +2408,7 @@ describe('WhereFilter', () => { { "alias": "区域", "field": "area", + "id": "id-2", }, ], "havingFilter": { @@ -2403,6 +2426,7 @@ describe('WhereFilter', () => { "alias": "销售额", "encoding": "yAxis", "field": "sales", + "id": "id-1", }, ], "theme": "light", diff --git a/packages/vbi/tests/types/runtimeSchemas.test.ts b/packages/vbi/tests/types/runtimeSchemas.test.ts index f5d46447c3..9107443af1 100644 --- a/packages/vbi/tests/types/runtimeSchemas.test.ts +++ b/packages/vbi/tests/types/runtimeSchemas.test.ts @@ -63,9 +63,10 @@ describe('DSL schemas', () => { test('parse dimension and measure tree schemas', () => { const dimensionGroup = zVBIDimensionGroupSchema.parse({ alias: '地区层级', - children: [{ field: 'province', alias: '省份' }], + children: [{ id: 'd-1', field: 'province', alias: '省份' }], }) const measure = zVBIMeasure.parse({ + id: 'm-1', field: 'sales', alias: '销售额', encoding: 'yAxis', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e71430ac0..14362d5e13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,7 +113,7 @@ importers: version: 9.18.0 '@nestjs/cli': specifier: 11.0.0 - version: 11.0.0(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7) + version: 11.0.0(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7) '@nestjs/schematics': specifier: 11.0.0 version: 11.0.0(chokidar@4.0.3)(typescript@5.9.3) @@ -152,7 +152,7 @@ importers: version: 16.0.0 jest: specifier: 30.0.0 - version: 30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)) + version: 30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)) prettier: specifier: 3.4.2 version: 3.4.2 @@ -167,13 +167,13 @@ importers: version: 7.0.0 ts-jest: specifier: 29.2.5 - version: 29.2.5(@babel/core@7.28.3)(@jest/types@29.6.3)(jest@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.2.5(@babel/core@7.29.0)(@jest/types@29.6.3)(jest@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: 9.5.2 - version: 9.5.2(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.18))) + version: 9.5.2(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.19))) ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3) + version: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3) tsconfig-paths: specifier: 4.2.0 version: 4.2.0 @@ -278,8 +278,8 @@ importers: apps/website: dependencies: '@rspress/core': - specifier: 2.0.2 - version: 2.0.2(@module-federation/runtime-tools@0.22.0)(@types/react@19.2.7)(core-js@3.47.0) + specifier: 2.0.5 + version: 2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2) '@visactor/vbi': specifier: workspace:* version: link:../../packages/vbi @@ -317,12 +317,15 @@ importers: '@eslint/js': specifier: 9.39.0 version: 9.39.0 + '@rspress/plugin-llms': + specifier: 2.0.5 + version: 2.0.5(@rspress/core@2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2)) '@rspress/plugin-playground': - specifier: 2.0.2 - version: 2.0.2(@rspress/core@2.0.2(@module-federation/runtime-tools@0.22.0)(@types/react@19.2.7)(core-js@3.47.0))(monaco-editor@0.52.2)(react-dom@19.2.4(react@19.2.4))(react-router-dom@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + specifier: 2.0.5 + version: 2.0.5(@rspress/core@2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2))(monaco-editor@0.52.2)(react-dom@19.2.4(react@19.2.4))(react-router-dom@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) '@rspress/plugin-preview': - specifier: 2.0.2 - version: 2.0.2(@module-federation/runtime-tools@0.22.0)(@rspress/core@2.0.2(@module-federation/runtime-tools@0.22.0)(@types/react@19.2.7)(core-js@3.47.0))(core-js@3.47.0)(react-router-dom@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + specifier: 2.0.5 + version: 2.0.5(@rspress/core@2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2))(core-js@3.47.0)(react-router-dom@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) '@types/node': specifier: 24.10.1 version: 24.10.1 @@ -336,7 +339,7 @@ importers: specifier: 16.4.0 version: 16.4.0 typescript: - specifier: ^5.9.3 + specifier: 5.9.3 version: 5.9.3 typescript-eslint: specifier: 8.48.0 @@ -464,7 +467,7 @@ importers: version: 9.39.0 '@rsdoctor/rspack-plugin': specifier: 1.2.3 - version: 1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) + version: 1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) '@rslib/core': specifier: 0.16.1 version: 0.16.1(typescript@5.9.3) @@ -555,7 +558,7 @@ importers: version: 9.39.1 '@rsbuild/plugin-react': specifier: 1.4.2 - version: 1.4.2(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0)) + version: 1.4.2(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0)) '@rslib/core': specifier: 0.18.6 version: 0.18.6(typescript@5.9.3) @@ -631,7 +634,7 @@ importers: version: 9.39.1 '@rsbuild/plugin-react': specifier: ^1.4.3 - version: 1.4.3(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0)) + version: 1.4.3(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0)) '@rslib/core': specifier: ^0.19.3 version: 0.19.3(typescript@5.9.3) @@ -666,7 +669,7 @@ importers: specifier: ^19.2.3 version: 19.2.3 typescript: - specifier: ^5.9.3 + specifier: 5.9.3 version: 5.9.3 typescript-eslint: specifier: ^8.48.0 @@ -704,7 +707,7 @@ importers: version: 9.39.1 '@rsbuild/plugin-react': specifier: 1.4.2 - version: 1.4.2(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0)) + version: 1.4.2(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0)) '@rslib/core': specifier: 0.18.6 version: 0.18.6(typescript@5.9.3) @@ -780,7 +783,7 @@ importers: version: 9.39.1 '@rsbuild/plugin-react': specifier: 1.4.2 - version: 1.4.2(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0)) + version: 1.4.2(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0)) '@rslib/core': specifier: 0.18.6 version: 0.18.6(typescript@5.9.3) @@ -853,7 +856,7 @@ importers: version: 2.6.7 ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@24.10.1)(typescript@5.9.3) + version: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@24.10.1)(typescript@5.9.3) typescript: specifier: 5.9.3 version: 5.9.3 @@ -883,7 +886,7 @@ importers: version: 26.0.0 ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@24.10.1)(typescript@6.0.0-dev.20260129) + version: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@24.10.1)(typescript@6.0.0-dev.20260129) zod: specifier: 4.0.17 version: 4.0.17 @@ -3145,8 +3148,8 @@ packages: engines: {node: '>=18.12.0'} hasBin: true - '@rsbuild/core@2.0.0-beta.1': - resolution: {integrity: sha512-m7L3oi4evTDODcY+Qk3cmY/p7GCaauSRe00D0AkXVohNvxFBt7F49uPwBSThS24I9d31zFuAED2jFqBeBlDqWw==} + '@rsbuild/core@2.0.0-beta.6': + resolution: {integrity: sha512-DUBhUzvzj6xlGUAHTTipFskSuZmVEuTX7lGU+ToPuo8n3bsQrWn/UBOEQAd45g66k7QfXadoZ/v7eodQErpvGQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3271,8 +3274,8 @@ packages: cpu: [arm64] os: [darwin] - '@rspack/binding-darwin-arm64@2.0.0-alpha.1': - resolution: {integrity: sha512-+6E6pYgpKvs41cyOlqRjpCT3djjL9hnntF61JumM/TNo1aTYXMNNG4b8ZsLMpBq5ZwCy9Dg8oEDe8AZ84rfM7A==} + '@rspack/binding-darwin-arm64@2.0.0-beta.3': + resolution: {integrity: sha512-QebSomLWlCbFsC0sfDuGqLJtkgyrnr38vrCepWukaAXIY4ANy5QB49LDKdLpVv6bKlC95MpnW37NvSNWY5GMYA==} cpu: [arm64] os: [darwin] @@ -3291,8 +3294,8 @@ packages: cpu: [x64] os: [darwin] - '@rspack/binding-darwin-x64@2.0.0-alpha.1': - resolution: {integrity: sha512-Ccf9NNupVe67vlaS9zKQJ+BvsAn385uBC1vXnYaUxxHoY/tEwNJf6t+XyDARt7mCtT7+Bu4L/iJ/JEF/MsO5zg==} + '@rspack/binding-darwin-x64@2.0.0-beta.3': + resolution: {integrity: sha512-EysmBq+sz+Ph0bu0gXpU1uuZG9gXgjqY+w3MJel+ieTFyQO3L/R56V32McgssMbheJbYcviDDn7Tz4D+lTvdJA==} cpu: [x64] os: [darwin] @@ -3311,8 +3314,8 @@ packages: cpu: [arm64] os: [linux] - '@rspack/binding-linux-arm64-gnu@2.0.0-alpha.1': - resolution: {integrity: sha512-B7omNsPSsinOq2VRD4d4VFrLgHceMQobqlLg0txFUZ7PDjE307gpTcGViWQlUhNCbkZXMPzDeXBFa5ZlEmxgnA==} + '@rspack/binding-linux-arm64-gnu@2.0.0-beta.3': + resolution: {integrity: sha512-iFPj4TQZKewnqWPfTbyk3F8QCBI/Edv7TVSRIPBHRnCM0lvYZl/8IZlUzXSamLvrtDpouF0nUzht/fktoWOhAg==} cpu: [arm64] os: [linux] @@ -3331,8 +3334,8 @@ packages: cpu: [arm64] os: [linux] - '@rspack/binding-linux-arm64-musl@2.0.0-alpha.1': - resolution: {integrity: sha512-NCG401ofZcDKlTWD8VHv76Y+02Stmd9Nu5MRbVUBOCTVgXMj8Mgrm5XsGBWUjzd5J/Mvo2hstCKIZxNzmPd8uQ==} + '@rspack/binding-linux-arm64-musl@2.0.0-beta.3': + resolution: {integrity: sha512-355mygfCNb0eF/y4HgtJcd0i9csNTG4Z15PCCplIkSAKJpFpkORM2xJb50BqsbhVafYl6AHoBlGWAo9iIzUb/w==} cpu: [arm64] os: [linux] @@ -3351,8 +3354,8 @@ packages: cpu: [x64] os: [linux] - '@rspack/binding-linux-x64-gnu@2.0.0-alpha.1': - resolution: {integrity: sha512-Xgp8wJ5gjpPG8I3VMEsVAesfckWryQVUhJkHcxPfNi72QTv8UkMER7Jl+JrlQk7K7nMO5ltokx/VGl1c3tMx+w==} + '@rspack/binding-linux-x64-gnu@2.0.0-beta.3': + resolution: {integrity: sha512-U8a+bcP/tkMyiwiO9XfeRYYO20YPGiZNxWWt7FEsdmRuRAl6M+EmWaJllJFQtKH+GG8IN93pNoVPMvARjLoJOQ==} cpu: [x64] os: [linux] @@ -3371,8 +3374,8 @@ packages: cpu: [x64] os: [linux] - '@rspack/binding-linux-x64-musl@2.0.0-alpha.1': - resolution: {integrity: sha512-lrYKcOgsPA1UMswxzFAV37ofkznbtTLCcEas6lxtlT3Dr28P6VRzC8TgVbIiprkm10I0BlThQWDJ3aGzzLj9Kg==} + '@rspack/binding-linux-x64-musl@2.0.0-beta.3': + resolution: {integrity: sha512-g81rqkaqDFRTID2VrHBYeM+xZe8yWov7IcryTrl9RGXXr61s+6Tu/mWyM378PuHOCyMNu7G3blVaSjLvKauG6Q==} cpu: [x64] os: [linux] @@ -3388,8 +3391,8 @@ packages: resolution: {integrity: sha512-rGNHrk2QuLFfwOTib91skuLh2aMYeTP4lgM4zanDhtt95DLDlwioETFY7FzY1WmS+Z3qnEyrgQIRp8osy0NKTw==} cpu: [wasm32] - '@rspack/binding-wasm32-wasi@2.0.0-alpha.1': - resolution: {integrity: sha512-rppGiT7CtXlM8st+IgzBDqb7V//1xx5Oe0SY1sxxw0cfOGMpIQCwhJqx/uI6ioqJLZLGX/obt359+hPXyqGl4w==} + '@rspack/binding-wasm32-wasi@2.0.0-beta.3': + resolution: {integrity: sha512-tzGd8H2oj5F3oR/Hxp+J68zVU/nG+9ndH2KK3/RieVjNAiVNHCR0/ZU9D47s6fnmvWOqAQ1qO8gnVoVLopC4YA==} cpu: [wasm32] '@rspack/binding-win32-arm64-msvc@1.6.0-beta.1': @@ -3407,8 +3410,8 @@ packages: cpu: [arm64] os: [win32] - '@rspack/binding-win32-arm64-msvc@2.0.0-alpha.1': - resolution: {integrity: sha512-yD2g1JmnCxrix/344r7lBn+RH+Nv8uWP0UDP8kwv4kQGCWr4U7IP8PKFpoyulVOgOUjvJpgImeyrDJ7R8he+5w==} + '@rspack/binding-win32-arm64-msvc@2.0.0-beta.3': + resolution: {integrity: sha512-TZZRSWa34sm5WyoQHwnyBjLJ4w3fcWRYA9ybYjSVWjUU6tVGdMiHiZp+WexUpIETvChLXU1JENNmBg/U7wvZEA==} cpu: [arm64] os: [win32] @@ -3427,8 +3430,8 @@ packages: cpu: [ia32] os: [win32] - '@rspack/binding-win32-ia32-msvc@2.0.0-alpha.1': - resolution: {integrity: sha512-5qpQL5Qz3uYb56pwffEGzznXSX9TNkLpigQbIObfnUwX7WkdjgTT7oTHpjn2sRSLLNiJ/jCp2r4ZHvjmnNRsRA==} + '@rspack/binding-win32-ia32-msvc@2.0.0-beta.3': + resolution: {integrity: sha512-VFnfdbJhyl6gNW1VzTyd1ZrHCboHPR7vrOalEsulQRqVNbtDkjm1sqLHtDcLmhTEv0a9r4lli8uubWDwmel8KQ==} cpu: [ia32] os: [win32] @@ -3447,8 +3450,8 @@ packages: cpu: [x64] os: [win32] - '@rspack/binding-win32-x64-msvc@2.0.0-alpha.1': - resolution: {integrity: sha512-dZ76NN9tXLaF2gnB/pU+PcK4Adf9tj8dY06KcWk5F81ur2V4UbrMfkWJkQprur8cgL/F49YtFMRWa4yp/qNbpQ==} + '@rspack/binding-win32-x64-msvc@2.0.0-beta.3': + resolution: {integrity: sha512-rwZ6Y3b3oqPj+ZDPPRxr3136HUPKDSlPQa4v7bBOPLDlrFDFOynMIEqDUUi5+8lPaUQ8WWR0aJK4cgcTTT0Siw==} cpu: [x64] os: [win32] @@ -3461,8 +3464,8 @@ packages: '@rspack/binding@1.7.5': resolution: {integrity: sha512-tlZfDHfGu765FBL3hIyjrr8slJZztv7rCM+KIczZS7UlJQDl1+WsDKUe/+E1Fw9SlmorLWK40+y3rLTHmMrN2A==} - '@rspack/binding@2.0.0-alpha.1': - resolution: {integrity: sha512-Glz0SNFYPtNVM+ExJ4ocSzW+oQhb1iHTmxqVEAILbL17Hq3N/nwZpo1cWEs6hJjn8cosJIb1VKbbgb/1goEtCQ==} + '@rspack/binding@2.0.0-beta.3': + resolution: {integrity: sha512-GSj+d8AlLs1oElhYq32vIN/eAsxWG9jy0EiNgSxWTt5Gdamv87kcvsV4jwfWIjlltdnBIJgey2RnU+hDZlTAvw==} '@rspack/core@1.6.0-beta.1': resolution: {integrity: sha512-2ff8XWonPPHyQ6mEWogMspg+Sul3lXZUfNQVrbYSjfNpi8CeDV0/ZtRbHHbAXiy6pz5fvBFL6X+i/ATckjTYBw==} @@ -3491,11 +3494,11 @@ packages: '@swc/helpers': optional: true - '@rspack/core@2.0.0-alpha.1': - resolution: {integrity: sha512-2KK3hbxrRqzxtzg+ka7LsiEKIWIGIQz317k9HHC2U4IC5yLJ31K8y/vQfA1aIT2QcFls9gW7GyRjp8A4X5cvLA==} + '@rspack/core@2.0.0-beta.3': + resolution: {integrity: sha512-VuLteRIesuyFFTXZaciUY0lwDZiwMc7JcpE8guvjArztDhtpVvlaOcLlVBp/Yza8c/Tk8Dxwe1ARzFL7xG1/0w==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - '@module-federation/runtime-tools': '>=0.22.0' + '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0 '@swc/helpers': '>=0.5.1' peerDependenciesMeta: '@module-federation/runtime-tools': @@ -3519,29 +3522,35 @@ packages: webpack-hot-middleware: optional: true - '@rspress/core@2.0.2': - resolution: {integrity: sha512-tU8rUVaPyC8o8k4ezgigRVQuZhBAC41KWdwZZ0BldN6o+QXSEIb722RnxCTpa9FGK2riqcwJgM+OqqcqXsFpmw==} + '@rspress/core@2.0.5': + resolution: {integrity: sha512-2ezGmANmIrWmhsUrvlRb9Df4xsun1BDgEertDc890aQqtKcNrbu+TBRsOoO+E/N6ioavun7JGGe1wWjvxubCHw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - '@rspress/plugin-playground@2.0.2': - resolution: {integrity: sha512-AQY/jSl9Q/dPOxrXObauQ3dAdYkXIckF2dO/VkwVVv8AUiVHY/QfmKxvbIPw2CMebCQLDY1nklxdeNySOno/vg==} + '@rspress/plugin-llms@2.0.5': + resolution: {integrity: sha512-qOqeInHXhHoBnMSwQdXJuMDOdHO48pfHW/euzA1AZrlvA2jzbII33we+2AbQfkWm98NAKUO4koU1GKUV2hhJ4g==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rspress/core': ^2.0.5 + + '@rspress/plugin-playground@2.0.5': + resolution: {integrity: sha512-t7Y85GhTOy30umcA5M6Yh0Yz9KvJyJPdmUXkQP4a0QXRpzc+Q8IpDG1E0Rxlzx4EV4fsRaLckzRC/wH1rANiRA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - '@rspress/core': ^2.0.2 + '@rspress/core': ^2.0.5 react: '>=18.0.0' react-router-dom: ^6.8.1 - '@rspress/plugin-preview@2.0.2': - resolution: {integrity: sha512-1JL6XN4A1UGt+W69i/m0W3HWyItWA3d/gIRjovAYVcjY7I/rPaMv1d0JUs+poMOH+uf1rr34SllvTYCesFq7wg==} + '@rspress/plugin-preview@2.0.5': + resolution: {integrity: sha512-27tbp5U90a5w4zkOG6WWunLlPfZPr8p6eXeRS31/HyWjg4YxLPpkYLKa/9Wi59agC0hSm2psq+EskAf1t18JIg==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - '@rspress/core': ^2.0.2 + '@rspress/core': ^2.0.5 react: '>=18.0.0' react-router-dom: ^6.8.1 - '@rspress/shared@2.0.2': - resolution: {integrity: sha512-9+QC8UL1gV2KpRZx4n55vAl6bE38y7eDnGJhdFSHdJkpFbUCiJDk9ZcR6jD/Rrtq7vlT0gfumUk640pxpi3IDQ==} + '@rspress/shared@2.0.5': + resolution: {integrity: sha512-Wdhh+VjU8zJWoVLhv9KJTRAZQ4X2V/Z81Lo2D0hQsa0Kj5F3EaxlMt5/dhX7DoflqNuZPZk/e7CSUB+gO/Umlg==} '@rstest/adapter-rslib@0.1.1': resolution: {integrity: sha512-R63E/DrtG4ol9wffAxgeq9ze6/Dm9wms0S6grm8maXAQ7qLooza/CTj4AH6aXeEjbpb64hfRGZy8VmZh9+fCfg==} @@ -3581,26 +3590,37 @@ packages: '@scarf/scarf@1.4.0': resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} - '@shikijs/core@3.21.0': - resolution: {integrity: sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==} + '@shikijs/core@4.0.2': + resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.0.2': + resolution: {integrity: sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==} + engines: {node: '>=20'} - '@shikijs/engine-javascript@3.21.0': - resolution: {integrity: sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==} + '@shikijs/engine-oniguruma@4.0.2': + resolution: {integrity: sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==} + engines: {node: '>=20'} - '@shikijs/engine-oniguruma@3.21.0': - resolution: {integrity: sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==} + '@shikijs/langs@4.0.2': + resolution: {integrity: sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==} + engines: {node: '>=20'} - '@shikijs/langs@3.21.0': - resolution: {integrity: sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==} + '@shikijs/primitive@4.0.2': + resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==} + engines: {node: '>=20'} - '@shikijs/rehype@3.21.0': - resolution: {integrity: sha512-fTQvwsZL67QdosMFdTgQ5SNjW3nxaPplRy//312hqOctRbIwviTV0nAbhv3NfnztHXvFli2zLYNKsTz/f9tbpQ==} + '@shikijs/rehype@4.0.2': + resolution: {integrity: sha512-cmPlKLD8JeojasNFoY64162ScpEdEdQUMuVodPCrv1nx1z3bjmGwoKWDruQWa/ejSznImlaeB0Ty6Q3zPaVQAA==} + engines: {node: '>=20'} - '@shikijs/themes@3.21.0': - resolution: {integrity: sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==} + '@shikijs/themes@4.0.2': + resolution: {integrity: sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==} + engines: {node: '>=20'} - '@shikijs/types@3.21.0': - resolution: {integrity: sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==} + '@shikijs/types@4.0.2': + resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==} + engines: {node: '>=20'} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -3701,6 +3721,9 @@ packages: '@swc/helpers@0.5.18': resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==} + '@swc/helpers@0.5.19': + resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} + '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} @@ -4048,8 +4071,8 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@unhead/react@2.1.2': - resolution: {integrity: sha512-VNKa0JJZq5Jp28VuiOMfjAA7CTLHI0SdW/Hs1ZPq2PsNV/cgxGv8quFBGXWx4gfoHB52pejO929RKjIpYX5+iQ==} + '@unhead/react@2.1.12': + resolution: {integrity: sha512-1xXFrxyw29f+kScXfEb0GxjlgtnHxoYau0qpW9k8sgWhQUNnE5gNaH3u+rNhd5IqhyvbdDRJpQ25zoz0HIyGaw==} peerDependencies: react: '>=18.3.1' @@ -4663,6 +4686,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -6751,6 +6778,35 @@ packages: micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + micromark-extension-cjk-friendly-gfm-strikethrough@2.0.1: + resolution: {integrity: sha512-wVC0zwjJNqQeX+bb07YTPu/CvSAyCTafyYb7sMhX1r62/Lw5M/df3JyYaANyp8g15c1ypJRFSsookTqA1IDsUg==} + engines: {node: '>=18'} + peerDependencies: + micromark: ^4.0.0 + micromark-util-types: ^2.0.0 + peerDependenciesMeta: + micromark-util-types: + optional: true + + micromark-extension-cjk-friendly-util@3.0.1: + resolution: {integrity: sha512-GcbXqTTHOsiZHyF753oIddP/J2eH8j9zpyQPhkof6B2JNxfEJabnQqxbCgzJNuNes0Y2jTNJ3LiYPSXr6eJA8w==} + engines: {node: '>=18'} + peerDependencies: + micromark-util-types: '*' + peerDependenciesMeta: + micromark-util-types: + optional: true + + micromark-extension-cjk-friendly@2.0.1: + resolution: {integrity: sha512-OkzoYVTL1ChbvQ8Cc1ayTIz7paFQz8iS9oIYmewncweUSwmWR+hkJF9spJ1lxB90XldJl26A1F4IkPOKS3bDXw==} + engines: {node: '>=18'} + peerDependencies: + micromark: ^4.0.0 + micromark-util-types: ^2.0.0 + peerDependenciesMeta: + micromark-util-types: + optional: true + micromark-extension-gfm-autolink-literal@2.1.0: resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} @@ -7536,6 +7592,11 @@ packages: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} + react-render-to-markdown@19.0.1: + resolution: {integrity: sha512-BPv48o+ubcu2JyUDIktvJXFqLIZqR7hA4mvGu1eFIofz9fogT2me9UvXwRvqvGs9jEtNaJkxZIUKUX0oiK4hDA==} + peerDependencies: + react: '>=19' + react-router-dom@7.12.0: resolution: {integrity: sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==} engines: {node: '>=20.0.0'} @@ -7543,8 +7604,8 @@ packages: react: '>=18' react-dom: '>=18' - react-router-dom@7.13.0: - resolution: {integrity: sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==} + react-router-dom@7.13.1: + resolution: {integrity: sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -7560,8 +7621,8 @@ packages: react-dom: optional: true - react-router@7.13.0: - resolution: {integrity: sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==} + react-router@7.13.1: + resolution: {integrity: sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -7649,6 +7710,26 @@ packages: rehype-recma@1.0.0: resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + remark-cjk-friendly-gfm-strikethrough@2.0.1: + resolution: {integrity: sha512-pWKj25O2eLXIL1aBupayl1fKhco+Brw8qWUWJPVB9EBzbQNd7nGLj0nLmJpggWsGLR5j5y40PIdjxby9IEYTuA==} + engines: {node: '>=18'} + peerDependencies: + '@types/mdast': ^4.0.0 + unified: ^11.0.0 + peerDependenciesMeta: + '@types/mdast': + optional: true + + remark-cjk-friendly@2.0.1: + resolution: {integrity: sha512-6WwkoQyZf/4j5k53zdFYrR8Ca+UVn992jXdLUSBDZR4eBpFhKyVxmA4gUHra/5fesjGIxrDhHesNr/sVoiiysA==} + engines: {node: '>=18'} + peerDependencies: + '@types/mdast': ^4.0.0 + unified: ^11.0.0 + peerDependenciesMeta: + '@types/mdast': + optional: true + remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -8012,8 +8093,9 @@ packages: engines: {node: '>=18'} hasBin: true - shiki@3.21.0: - resolution: {integrity: sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==} + shiki@4.0.2: + resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} + engines: {node: '>=20'} shx@0.4.0: resolution: {integrity: sha512-Z0KixSIlGPpijKgcH6oCMCbltPImvaKy0sGH8AkLRXw1KyzpKtaCTizP2xen+hNDqVF4xxgvA0KXSb9o4Q6hnA==} @@ -8681,8 +8763,8 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - unhead@2.1.2: - resolution: {integrity: sha512-vSihrxyb+zsEUfEbraZBCjdE0p/WSoc2NGDrpwwSNAwuPxhYK1nH3eegf02IENLpn1sUhL8IoO84JWmRQ6tILA==} + unhead@2.1.12: + resolution: {integrity: sha512-iTHdWD9ztTunOErtfUFk6Wr11BxvzumcYJ0CzaSCBUOEtg+DUZ9+gnE99i8QkLFT2q1rZD48BYYGXpOZVDLYkA==} unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -8711,6 +8793,9 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -9487,8 +9572,8 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.3 - '@babel/types': 7.28.2 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -9574,24 +9659,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.3)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.3)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.3)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.3)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': @@ -9599,24 +9684,19 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.3)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.3)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': @@ -9624,49 +9704,44 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.3)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.3)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.3)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.3)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.3)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.3)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.3)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.3)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': @@ -10484,7 +10559,7 @@ snapshots: jest-util: 30.0.0 slash: 3.0.0 - '@jest/core@30.0.0(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3))': + '@jest/core@30.0.0(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3))': dependencies: '@jest/console': 30.0.0 '@jest/pattern': 30.0.0 @@ -10499,7 +10574,7 @@ snapshots: exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.0.0 - jest-config: 30.0.0(@types/node@24.10.1)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)) + jest-config: 30.0.0(@types/node@24.10.1)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)) jest-haste-map: 30.0.0 jest-message-util: 30.0.0 jest-regex-util: 30.0.0 @@ -10647,7 +10722,7 @@ snapshots: '@jest/transform@30.0.0': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@jest/types': 30.0.0 '@jridgewell/trace-mapping': 0.3.30 babel-plugin-istanbul: 7.0.1 @@ -10895,7 +10970,7 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@nestjs/cli@11.0.0(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)': + '@nestjs/cli@11.0.0(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)': dependencies: '@angular-devkit/core': 19.1.2(chokidar@4.0.3) '@angular-devkit/schematics': 19.1.2(chokidar@4.0.3) @@ -10906,7 +10981,7 @@ snapshots: chokidar: 4.0.3 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.18))) + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.19))) glob: 11.0.1 node-emoji: 1.11.0 ora: 5.4.1 @@ -10914,10 +10989,10 @@ snapshots: tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 typescript: 5.7.3 - webpack: 5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.18)) + webpack: 5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.19)) webpack-node-externals: 3.0.0 optionalDependencies: - '@swc/core': 1.15.11(@swc/helpers@0.5.18) + '@swc/core': 1.15.11(@swc/helpers@0.5.19) transitivePeerDependencies: - '@types/node' - esbuild @@ -11764,23 +11839,22 @@ snapshots: core-js: 3.47.0 jiti: 2.6.1 - '@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0)': + '@rsbuild/core@2.0.0-beta.6(core-js@3.47.0)': dependencies: - '@rspack/core': 2.0.0-alpha.1(@module-federation/runtime-tools@0.22.0)(@swc/helpers@0.5.18) - '@swc/helpers': 0.5.18 - jiti: 2.6.1 + '@rspack/core': 2.0.0-beta.3(@swc/helpers@0.5.19) + '@swc/helpers': 0.5.19 optionalDependencies: core-js: 3.47.0 transitivePeerDependencies: - '@module-federation/runtime-tools' - '@rsbuild/plugin-babel@1.1.0(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0))': + '@rsbuild/plugin-babel@1.1.0(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@rsbuild/core': 2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0) + '@rsbuild/core': 2.0.0-beta.6(core-js@3.47.0) '@types/babel__core': 7.20.5 deepmerge: 4.3.1 reduce-configs: 1.1.1 @@ -11806,25 +11880,25 @@ snapshots: transitivePeerDependencies: - webpack-hot-middleware - '@rsbuild/plugin-react@1.4.2(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0))': + '@rsbuild/plugin-react@1.4.2(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0))': dependencies: - '@rsbuild/core': 2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0) + '@rsbuild/core': 2.0.0-beta.6(core-js@3.47.0) '@rspack/plugin-react-refresh': 1.6.0(react-refresh@0.18.0) react-refresh: 0.18.0 transitivePeerDependencies: - webpack-hot-middleware - '@rsbuild/plugin-react@1.4.3(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0))': + '@rsbuild/plugin-react@1.4.3(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0))': dependencies: - '@rsbuild/core': 2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0) + '@rsbuild/core': 2.0.0-beta.6(core-js@3.47.0) '@rspack/plugin-react-refresh': 1.6.0(react-refresh@0.18.0) react-refresh: 0.18.0 transitivePeerDependencies: - webpack-hot-middleware - '@rsbuild/plugin-react@1.4.5(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0))': + '@rsbuild/plugin-react@1.4.5(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0))': dependencies: - '@rsbuild/core': 2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0) + '@rsbuild/core': 2.0.0-beta.6(core-js@3.47.0) '@rspack/plugin-react-refresh': 1.6.0(react-refresh@0.18.0) react-refresh: 0.18.0 transitivePeerDependencies: @@ -11832,13 +11906,13 @@ snapshots: '@rsdoctor/client@1.2.3': {} - '@rsdoctor/core@1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1)': + '@rsdoctor/core@1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1)': dependencies: '@rsbuild/plugin-check-syntax': 1.3.0(@rsbuild/core@1.7.3) - '@rsdoctor/graph': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/sdk': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/utils': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) + '@rsdoctor/graph': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/sdk': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/utils': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) axios: 1.12.1 browserslist-load-config: 1.0.1 enhanced-resolve: 5.12.0 @@ -11857,10 +11931,10 @@ snapshots: - utf-8-validate - webpack - '@rsdoctor/graph@1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1)': + '@rsdoctor/graph@1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1)': dependencies: - '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/utils': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) + '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/utils': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) lodash.unionby: 4.8.0 source-map: 0.7.6 transitivePeerDependencies: @@ -11868,16 +11942,16 @@ snapshots: - supports-color - webpack - '@rsdoctor/rspack-plugin@1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1)': + '@rsdoctor/rspack-plugin@1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1)': dependencies: - '@rsdoctor/core': 1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/graph': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/sdk': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/utils': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) + '@rsdoctor/core': 1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/graph': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/sdk': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/utils': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) lodash: 4.17.21 optionalDependencies: - '@rspack/core': 1.7.5(@swc/helpers@0.5.18) + '@rspack/core': 1.7.5(@swc/helpers@0.5.19) transitivePeerDependencies: - '@rsbuild/core' - bufferutil @@ -11886,12 +11960,12 @@ snapshots: - utf-8-validate - webpack - '@rsdoctor/sdk@1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1)': + '@rsdoctor/sdk@1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1)': dependencies: '@rsdoctor/client': 1.2.3 - '@rsdoctor/graph': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) - '@rsdoctor/utils': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) + '@rsdoctor/graph': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) + '@rsdoctor/utils': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) '@types/fs-extra': 11.0.4 body-parser: 1.20.3 cors: 2.8.5 @@ -11910,20 +11984,20 @@ snapshots: - utf-8-validate - webpack - '@rsdoctor/types@1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1)': + '@rsdoctor/types@1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1)': dependencies: '@types/connect': 3.4.38 '@types/estree': 1.0.5 '@types/tapable': 2.2.7 source-map: 0.7.6 optionalDependencies: - '@rspack/core': 1.7.5(@swc/helpers@0.5.18) + '@rspack/core': 1.7.5(@swc/helpers@0.5.19) webpack: 5.104.1 - '@rsdoctor/utils@1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1)': + '@rsdoctor/utils@1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1)': dependencies: '@babel/code-frame': 7.26.2 - '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.18))(webpack@5.104.1) + '@rsdoctor/types': 1.2.3(@rspack/core@1.7.5(@swc/helpers@0.5.19))(webpack@5.104.1) '@types/estree': 1.0.5 acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) @@ -11980,7 +12054,7 @@ snapshots: '@rspack/binding-darwin-arm64@1.7.5': optional: true - '@rspack/binding-darwin-arm64@2.0.0-alpha.1': + '@rspack/binding-darwin-arm64@2.0.0-beta.3': optional: true '@rspack/binding-darwin-x64@1.6.0-beta.1': @@ -11992,7 +12066,7 @@ snapshots: '@rspack/binding-darwin-x64@1.7.5': optional: true - '@rspack/binding-darwin-x64@2.0.0-alpha.1': + '@rspack/binding-darwin-x64@2.0.0-beta.3': optional: true '@rspack/binding-linux-arm64-gnu@1.6.0-beta.1': @@ -12004,7 +12078,7 @@ snapshots: '@rspack/binding-linux-arm64-gnu@1.7.5': optional: true - '@rspack/binding-linux-arm64-gnu@2.0.0-alpha.1': + '@rspack/binding-linux-arm64-gnu@2.0.0-beta.3': optional: true '@rspack/binding-linux-arm64-musl@1.6.0-beta.1': @@ -12016,7 +12090,7 @@ snapshots: '@rspack/binding-linux-arm64-musl@1.7.5': optional: true - '@rspack/binding-linux-arm64-musl@2.0.0-alpha.1': + '@rspack/binding-linux-arm64-musl@2.0.0-beta.3': optional: true '@rspack/binding-linux-x64-gnu@1.6.0-beta.1': @@ -12028,7 +12102,7 @@ snapshots: '@rspack/binding-linux-x64-gnu@1.7.5': optional: true - '@rspack/binding-linux-x64-gnu@2.0.0-alpha.1': + '@rspack/binding-linux-x64-gnu@2.0.0-beta.3': optional: true '@rspack/binding-linux-x64-musl@1.6.0-beta.1': @@ -12040,7 +12114,7 @@ snapshots: '@rspack/binding-linux-x64-musl@1.7.5': optional: true - '@rspack/binding-linux-x64-musl@2.0.0-alpha.1': + '@rspack/binding-linux-x64-musl@2.0.0-beta.3': optional: true '@rspack/binding-wasm32-wasi@1.6.0-beta.1': @@ -12058,7 +12132,7 @@ snapshots: '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rspack/binding-wasm32-wasi@2.0.0-alpha.1': + '@rspack/binding-wasm32-wasi@2.0.0-beta.3': dependencies: '@napi-rs/wasm-runtime': 1.0.7 optional: true @@ -12072,7 +12146,7 @@ snapshots: '@rspack/binding-win32-arm64-msvc@1.7.5': optional: true - '@rspack/binding-win32-arm64-msvc@2.0.0-alpha.1': + '@rspack/binding-win32-arm64-msvc@2.0.0-beta.3': optional: true '@rspack/binding-win32-ia32-msvc@1.6.0-beta.1': @@ -12084,7 +12158,7 @@ snapshots: '@rspack/binding-win32-ia32-msvc@1.7.5': optional: true - '@rspack/binding-win32-ia32-msvc@2.0.0-alpha.1': + '@rspack/binding-win32-ia32-msvc@2.0.0-beta.3': optional: true '@rspack/binding-win32-x64-msvc@1.6.0-beta.1': @@ -12096,7 +12170,7 @@ snapshots: '@rspack/binding-win32-x64-msvc@1.7.5': optional: true - '@rspack/binding-win32-x64-msvc@2.0.0-alpha.1': + '@rspack/binding-win32-x64-msvc@2.0.0-beta.3': optional: true '@rspack/binding@1.6.0-beta.1': @@ -12138,18 +12212,18 @@ snapshots: '@rspack/binding-win32-ia32-msvc': 1.7.5 '@rspack/binding-win32-x64-msvc': 1.7.5 - '@rspack/binding@2.0.0-alpha.1': + '@rspack/binding@2.0.0-beta.3': optionalDependencies: - '@rspack/binding-darwin-arm64': 2.0.0-alpha.1 - '@rspack/binding-darwin-x64': 2.0.0-alpha.1 - '@rspack/binding-linux-arm64-gnu': 2.0.0-alpha.1 - '@rspack/binding-linux-arm64-musl': 2.0.0-alpha.1 - '@rspack/binding-linux-x64-gnu': 2.0.0-alpha.1 - '@rspack/binding-linux-x64-musl': 2.0.0-alpha.1 - '@rspack/binding-wasm32-wasi': 2.0.0-alpha.1 - '@rspack/binding-win32-arm64-msvc': 2.0.0-alpha.1 - '@rspack/binding-win32-ia32-msvc': 2.0.0-alpha.1 - '@rspack/binding-win32-x64-msvc': 2.0.0-alpha.1 + '@rspack/binding-darwin-arm64': 2.0.0-beta.3 + '@rspack/binding-darwin-x64': 2.0.0-beta.3 + '@rspack/binding-linux-arm64-gnu': 2.0.0-beta.3 + '@rspack/binding-linux-arm64-musl': 2.0.0-beta.3 + '@rspack/binding-linux-x64-gnu': 2.0.0-beta.3 + '@rspack/binding-linux-x64-musl': 2.0.0-beta.3 + '@rspack/binding-wasm32-wasi': 2.0.0-beta.3 + '@rspack/binding-win32-arm64-msvc': 2.0.0-beta.3 + '@rspack/binding-win32-ia32-msvc': 2.0.0-beta.3 + '@rspack/binding-win32-x64-msvc': 2.0.0-beta.3 '@rspack/core@1.6.0-beta.1(@swc/helpers@0.5.18)': dependencies: @@ -12175,13 +12249,20 @@ snapshots: optionalDependencies: '@swc/helpers': 0.5.18 - '@rspack/core@2.0.0-alpha.1(@module-federation/runtime-tools@0.22.0)(@swc/helpers@0.5.18)': + '@rspack/core@1.7.5(@swc/helpers@0.5.19)': dependencies: - '@rspack/binding': 2.0.0-alpha.1 + '@module-federation/runtime-tools': 0.22.0 + '@rspack/binding': 1.7.5 '@rspack/lite-tapable': 1.1.0 optionalDependencies: - '@module-federation/runtime-tools': 0.22.0 - '@swc/helpers': 0.5.18 + '@swc/helpers': 0.5.19 + optional: true + + '@rspack/core@2.0.0-beta.3(@swc/helpers@0.5.19)': + dependencies: + '@rspack/binding': 2.0.0-beta.3 + optionalDependencies: + '@swc/helpers': 0.5.19 '@rspack/lite-tapable@1.0.1': {} @@ -12193,18 +12274,18 @@ snapshots: html-entities: 2.6.0 react-refresh: 0.18.0 - '@rspress/core@2.0.2(@module-federation/runtime-tools@0.22.0)(@types/react@19.2.7)(core-js@3.47.0)': + '@rspress/core@2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2)': dependencies: '@mdx-js/mdx': 3.1.1 '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.4) - '@rsbuild/core': 2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0) - '@rsbuild/plugin-react': 1.4.5(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0)) - '@rspress/shared': 2.0.2(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0) - '@shikijs/rehype': 3.21.0 + '@rsbuild/core': 2.0.0-beta.6(core-js@3.47.0) + '@rsbuild/plugin-react': 1.4.5(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0)) + '@rspress/shared': 2.0.5(core-js@3.47.0) + '@shikijs/rehype': 4.0.2 '@types/unist': 3.0.3 - '@unhead/react': 2.1.2(react@19.2.4) + '@unhead/react': 2.1.12(react@19.2.4) body-scroll-lock: 4.0.0-beta.0 - cac: 6.7.14 + cac: 7.0.0 chokidar: 3.6.0 clsx: 2.1.1 copy-to-clipboard: 3.3.3 @@ -12222,15 +12303,18 @@ snapshots: react-dom: 19.2.4(react@19.2.4) react-lazy-with-preload: 2.2.1 react-reconciler: 0.33.0(react@19.2.4) - react-router-dom: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-render-to-markdown: 19.0.1(react@19.2.4) + react-router-dom: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) rehype-external-links: 3.0.0 rehype-raw: 7.0.0 + remark-cjk-friendly: 2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5) + remark-cjk-friendly-gfm-strikethrough: 2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5) remark-gfm: 4.0.1 remark-mdx: 3.1.1 remark-parse: 11.0.0 remark-stringify: 11.0.0 scroll-into-view-if-needed: 3.1.0 - shiki: 3.21.0 + shiki: 4.0.2 tinyglobby: 0.2.15 tinypool: 1.1.1 unified: 11.0.5 @@ -12239,19 +12323,33 @@ snapshots: unist-util-visit-children: 3.0.0 transitivePeerDependencies: - '@module-federation/runtime-tools' + - '@types/mdast' - '@types/react' - core-js + - micromark + - micromark-util-types - supports-color - webpack-hot-middleware - '@rspress/plugin-playground@2.0.2(@rspress/core@2.0.2(@module-federation/runtime-tools@0.22.0)(@types/react@19.2.7)(core-js@3.47.0))(monaco-editor@0.52.2)(react-dom@19.2.4(react@19.2.4))(react-router-dom@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': + '@rspress/plugin-llms@2.0.5(@rspress/core@2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2))': + dependencies: + '@rspress/core': 2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2) + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + transitivePeerDependencies: + - supports-color + + '@rspress/plugin-playground@2.0.5(@rspress/core@2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2))(monaco-editor@0.52.2)(react-dom@19.2.4(react@19.2.4))(react-router-dom@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': dependencies: '@mdx-js/mdx': 3.1.1 '@monaco-editor/react': 4.7.0(monaco-editor@0.52.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@oxidation-compiler/napi': 0.2.0 - '@rspress/core': 2.0.2(@module-federation/runtime-tools@0.22.0)(@types/react@19.2.7)(core-js@3.47.0) + '@rspress/core': 2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2) react: 19.2.4 - react-router-dom: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router-dom: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) remark-gfm: 4.0.1 rspack-plugin-virtual-module: 1.0.1 unist-util-visit: 5.0.0 @@ -12260,25 +12358,25 @@ snapshots: - react-dom - supports-color - '@rspress/plugin-preview@2.0.2(@module-federation/runtime-tools@0.22.0)(@rspress/core@2.0.2(@module-federation/runtime-tools@0.22.0)(@types/react@19.2.7)(core-js@3.47.0))(core-js@3.47.0)(react-router-dom@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': + '@rspress/plugin-preview@2.0.5(@rspress/core@2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2))(core-js@3.47.0)(react-router-dom@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': dependencies: - '@rsbuild/core': 2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0) - '@rsbuild/plugin-babel': 1.1.0(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0)) - '@rsbuild/plugin-react': 1.4.5(@rsbuild/core@2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0)) - '@rspress/core': 2.0.2(@module-federation/runtime-tools@0.22.0)(@types/react@19.2.7)(core-js@3.47.0) + '@rsbuild/core': 2.0.0-beta.6(core-js@3.47.0) + '@rsbuild/plugin-babel': 1.1.0(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0)) + '@rsbuild/plugin-react': 1.4.5(@rsbuild/core@2.0.0-beta.6(core-js@3.47.0)) + '@rspress/core': 2.0.5(@types/mdast@4.0.4)(@types/react@19.2.7)(core-js@3.47.0)(micromark-util-types@2.0.2)(micromark@4.0.2) qrcode.react: 4.2.0(react@19.2.4) react: 19.2.4 - react-router-dom: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router-dom: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) transitivePeerDependencies: - '@module-federation/runtime-tools' - core-js - supports-color - webpack-hot-middleware - '@rspress/shared@2.0.2(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0)': + '@rspress/shared@2.0.5(core-js@3.47.0)': dependencies: - '@rsbuild/core': 2.0.0-beta.1(@module-federation/runtime-tools@0.22.0)(core-js@3.47.0) - '@shikijs/rehype': 3.21.0 + '@rsbuild/core': 2.0.0-beta.6(core-js@3.47.0) + '@shikijs/rehype': 4.0.2 gray-matter: 4.0.3 lodash-es: 4.17.23 unified: 11.0.5 @@ -12345,42 +12443,49 @@ snapshots: '@scarf/scarf@1.4.0': {} - '@shikijs/core@3.21.0': + '@shikijs/core@4.0.2': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/primitive': 4.0.2 + '@shikijs/types': 4.0.2 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@3.21.0': + '@shikijs/engine-javascript@4.0.2': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 4.0.2 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.4 - '@shikijs/engine-oniguruma@3.21.0': + '@shikijs/engine-oniguruma@4.0.2': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 4.0.2 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.21.0': + '@shikijs/langs@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + + '@shikijs/primitive@4.0.2': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 - '@shikijs/rehype@3.21.0': + '@shikijs/rehype@4.0.2': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 4.0.2 '@types/hast': 3.0.4 hast-util-to-string: 3.0.1 - shiki: 3.21.0 + shiki: 4.0.2 unified: 11.0.5 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 - '@shikijs/themes@3.21.0': + '@shikijs/themes@4.0.2': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 4.0.2 - '@shikijs/types@3.21.0': + '@shikijs/types@4.0.2': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -12433,7 +12538,7 @@ snapshots: '@swc/core-win32-x64-msvc@1.15.11': optional: true - '@swc/core@1.15.11(@swc/helpers@0.5.18)': + '@swc/core@1.15.11(@swc/helpers@0.5.19)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.25 @@ -12448,7 +12553,7 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.15.11 '@swc/core-win32-ia32-msvc': 1.15.11 '@swc/core-win32-x64-msvc': 1.15.11 - '@swc/helpers': 0.5.18 + '@swc/helpers': 0.5.19 optional: true '@swc/counter@0.1.3': @@ -12462,6 +12567,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@swc/helpers@0.5.19': + dependencies: + tslib: 2.8.1 + '@swc/types@0.1.25': dependencies: '@swc/counter': 0.1.3 @@ -13015,10 +13124,10 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@unhead/react@2.1.2(react@19.2.4)': + '@unhead/react@2.1.12(react@19.2.4)': dependencies: react: 19.2.4 - unhead: 2.1.2 + unhead: 2.1.12 '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -13570,13 +13679,13 @@ snapshots: b4a@1.7.3: {} - babel-jest@30.0.0(@babel/core@7.28.3): + babel-jest@30.0.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@jest/transform': 30.0.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 7.0.1 - babel-preset-jest: 30.0.0(@babel/core@7.28.3) + babel-preset-jest: 30.0.0(@babel/core@7.29.0) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -13595,34 +13704,34 @@ snapshots: babel-plugin-jest-hoist@30.0.0: dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 '@types/babel__core': 7.20.5 - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.3): + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.3 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.3) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.3) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.3) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.3) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.3) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.3) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.3) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.3) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.3) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.3) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.3) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.3) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.3) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.3) - - babel-preset-jest@30.0.0(@babel/core@7.28.3): + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@30.0.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 babel-plugin-jest-hoist: 30.0.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) bail@2.0.2: {} @@ -13801,6 +13910,8 @@ snapshots: cac@6.7.14: {} + cac@7.0.0: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -14941,7 +15052,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.18))): + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.19))): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -14956,7 +15067,7 @@ snapshots: semver: 7.7.3 tapable: 2.3.0 typescript: 5.7.3 - webpack: 5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.18)) + webpack: 5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.19)) form-data@3.0.4: dependencies: @@ -15248,7 +15359,7 @@ snapshots: mdast-util-to-hast: 13.2.0 parse5: 7.3.0 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -15621,15 +15732,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)): + jest-cli@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)): dependencies: - '@jest/core': 30.0.0(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)) + '@jest/core': 30.0.0(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)) '@jest/test-result': 30.0.0 '@jest/types': 30.0.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)) + jest-config: 30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)) jest-util: 30.0.0 jest-validate: 30.0.0 yargs: 17.7.2 @@ -15640,14 +15751,14 @@ snapshots: - supports-color - ts-node - jest-config@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)): + jest-config@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)): dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@jest/get-type': 30.0.0 '@jest/pattern': 30.0.0 '@jest/test-sequencer': 30.0.0 '@jest/types': 30.0.0 - babel-jest: 30.0.0(@babel/core@7.28.3) + babel-jest: 30.0.0(@babel/core@7.29.0) chalk: 4.1.2 ci-info: 4.3.1 deepmerge: 4.3.1 @@ -15668,19 +15779,19 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 22.10.7 - ts-node: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3) + ts-node: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@30.0.0(@types/node@24.10.1)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)): + jest-config@30.0.0(@types/node@24.10.1)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)): dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@jest/get-type': 30.0.0 '@jest/pattern': 30.0.0 '@jest/test-sequencer': 30.0.0 '@jest/types': 30.0.0 - babel-jest: 30.0.0(@babel/core@7.28.3) + babel-jest: 30.0.0(@babel/core@7.29.0) chalk: 4.1.2 ci-info: 4.3.1 deepmerge: 4.3.1 @@ -15701,7 +15812,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 24.10.1 - ts-node: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3) + ts-node: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -15894,17 +16005,17 @@ snapshots: jest-snapshot@30.0.0: dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@babel/generator': 7.28.3 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.3) - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.3) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) '@babel/types': 7.28.2 '@jest/expect-utils': 30.0.0 '@jest/get-type': 30.0.0 '@jest/snapshot-utils': 30.0.0 '@jest/transform': 30.0.0 '@jest/types': 30.0.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) chalk: 4.1.2 expect: 30.0.0 graceful-fs: 4.2.11 @@ -15979,12 +16090,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)): + jest@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)): dependencies: - '@jest/core': 30.0.0(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)) + '@jest/core': 30.0.0(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)) '@jest/types': 30.0.0 import-local: 3.2.0 - jest-cli: 30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)) + jest-cli: 30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -16441,7 +16552,7 @@ snapshots: micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 mdast-util-to-markdown@2.1.2: @@ -16499,6 +16610,38 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-cjk-friendly-gfm-strikethrough@2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2): + dependencies: + devlop: 1.1.0 + get-east-asian-width: 1.4.0 + micromark: 4.0.2 + micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + optionalDependencies: + micromark-util-types: 2.0.2 + + micromark-extension-cjk-friendly-util@3.0.1(micromark-util-types@2.0.2): + dependencies: + get-east-asian-width: 1.4.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + optionalDependencies: + micromark-util-types: 2.0.2 + + micromark-extension-cjk-friendly@2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2): + dependencies: + devlop: 1.1.0 + micromark: 4.0.2 + micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) + micromark-util-chunked: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + optionalDependencies: + micromark-util-types: 2.0.2 + micromark-extension-gfm-autolink-literal@2.1.0: dependencies: micromark-util-character: 2.1.1 @@ -17390,17 +17533,22 @@ snapshots: react-refresh@0.18.0: {} + react-render-to-markdown@19.0.1(react@19.2.4): + dependencies: + react: 19.2.4 + react-reconciler: 0.33.0(react@19.2.4) + react-router-dom@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) react-router: 7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react-router-dom@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-router-dom@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react-router@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: @@ -17410,7 +17558,7 @@ snapshots: optionalDependencies: react-dom: 19.2.3(react@19.2.3) - react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: cookie: 1.1.1 react: 19.2.4 @@ -17535,6 +17683,26 @@ snapshots: transitivePeerDependencies: - supports-color + remark-cjk-friendly-gfm-strikethrough@2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5): + dependencies: + micromark-extension-cjk-friendly-gfm-strikethrough: 2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2) + unified: 11.0.5 + optionalDependencies: + '@types/mdast': 4.0.4 + transitivePeerDependencies: + - micromark + - micromark-util-types + + remark-cjk-friendly@2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5): + dependencies: + micromark-extension-cjk-friendly: 2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2) + unified: 11.0.5 + optionalDependencies: + '@types/mdast': 4.0.4 + transitivePeerDependencies: + - micromark + - micromark-util-types + remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 @@ -17912,14 +18080,14 @@ snapshots: interpret: 1.4.0 rechoir: 0.6.2 - shiki@3.21.0: + shiki@4.0.2: dependencies: - '@shikijs/core': 3.21.0 - '@shikijs/engine-javascript': 3.21.0 - '@shikijs/engine-oniguruma': 3.21.0 - '@shikijs/langs': 3.21.0 - '@shikijs/themes': 3.21.0 - '@shikijs/types': 3.21.0 + '@shikijs/core': 4.0.2 + '@shikijs/engine-javascript': 4.0.2 + '@shikijs/engine-oniguruma': 4.0.2 + '@shikijs/langs': 4.0.2 + '@shikijs/themes': 4.0.2 + '@shikijs/types': 4.0.2 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -18294,27 +18462,27 @@ snapshots: term-size@2.2.1: {} - terser-webpack-plugin@5.3.16(@swc/core@1.15.11(@swc/helpers@0.5.18))(webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.18))): + terser-webpack-plugin@5.3.16(@swc/core@1.15.11(@swc/helpers@0.5.19))(webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.19))): dependencies: '@jridgewell/trace-mapping': 0.3.30 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.43.1 - webpack: 5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.18)) + webpack: 5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.19)) optionalDependencies: - '@swc/core': 1.15.11(@swc/helpers@0.5.18) + '@swc/core': 1.15.11(@swc/helpers@0.5.19) - terser-webpack-plugin@5.3.16(@swc/core@1.15.11(@swc/helpers@0.5.18))(webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.18))): + terser-webpack-plugin@5.3.16(@swc/core@1.15.11(@swc/helpers@0.5.19))(webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.19))): dependencies: '@jridgewell/trace-mapping': 0.3.30 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.43.1 - webpack: 5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.18)) + webpack: 5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.19)) optionalDependencies: - '@swc/core': 1.15.11(@swc/helpers@0.5.18) + '@swc/core': 1.15.11(@swc/helpers@0.5.19) terser-webpack-plugin@5.3.16(webpack@5.104.1): dependencies: @@ -18449,12 +18617,12 @@ snapshots: dependencies: typescript: 5.9.3 - ts-jest@29.2.5(@babel/core@7.28.3)(@jest/types@29.6.3)(jest@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.2.5(@babel/core@7.29.0)(@jest/types@29.6.3)(jest@30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3)) + jest: 30.0.0(@types/node@22.10.7)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -18463,10 +18631,10 @@ snapshots: typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.29.0 '@jest/types': 29.6.3 - ts-loader@9.5.2(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.18))): + ts-loader@9.5.2(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.19))): dependencies: chalk: 4.1.2 enhanced-resolve: 5.18.3 @@ -18474,14 +18642,14 @@ snapshots: semver: 7.7.3 source-map: 0.7.6 typescript: 5.9.3 - webpack: 5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.18)) + webpack: 5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.19)) ts-morph@26.0.0: dependencies: '@ts-morph/common': 0.27.0 code-block-writer: 13.0.3 - ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@22.10.7)(typescript@5.9.3): + ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@22.10.7)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 @@ -18499,9 +18667,9 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.15.11(@swc/helpers@0.5.18) + '@swc/core': 1.15.11(@swc/helpers@0.5.19) - ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@24.10.1)(typescript@5.9.3): + ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@24.10.1)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 @@ -18519,9 +18687,9 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.15.11(@swc/helpers@0.5.18) + '@swc/core': 1.15.11(@swc/helpers@0.5.19) - ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.18))(@types/node@24.10.1)(typescript@6.0.0-dev.20260129): + ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.19))(@types/node@24.10.1)(typescript@6.0.0-dev.20260129): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 @@ -18539,7 +18707,7 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.15.11(@swc/helpers@0.5.18) + '@swc/core': 1.15.11(@swc/helpers@0.5.19) tsconfig-paths-webpack-plugin@4.2.0: dependencies: @@ -18667,7 +18835,7 @@ snapshots: undici-types@7.16.0: {} - unhead@2.1.2: + unhead@2.1.12: dependencies: hookable: 6.0.1 @@ -18718,6 +18886,12 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + universalify@0.1.2: {} universalify@2.0.1: {} @@ -18839,7 +19013,7 @@ snapshots: picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.46.1 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.1 fsevents: 2.3.3 @@ -18981,7 +19155,7 @@ snapshots: - uglify-js optional: true - webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.18)): + webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.19)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -19005,7 +19179,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(@swc/core@1.15.11(@swc/helpers@0.5.18))(webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.18))) + terser-webpack-plugin: 5.3.16(@swc/core@1.15.11(@swc/helpers@0.5.19))(webpack@5.104.1(@swc/core@1.15.11(@swc/helpers@0.5.19))) watchpack: 2.5.1 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -19013,7 +19187,7 @@ snapshots: - esbuild - uglify-js - webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.18)): + webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.19)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -19035,7 +19209,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(@swc/core@1.15.11(@swc/helpers@0.5.18))(webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.18))) + terser-webpack-plugin: 5.3.16(@swc/core@1.15.11(@swc/helpers@0.5.19))(webpack@5.97.1(@swc/core@1.15.11(@swc/helpers@0.5.19))) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: diff --git a/practices/demo/src/App/App.tsx b/practices/demo/src/App/App.tsx index 5ad4b62318..b5cdf1ecb8 100644 --- a/practices/demo/src/App/App.tsx +++ b/practices/demo/src/App/App.tsx @@ -1,4 +1,4 @@ -import { Flex, Spin, Card } from 'antd'; +import { Flex, Spin, Card, Empty, Typography, Space } from 'antd'; import { VSeedRender } from 'src/components/Render'; import { MeasuresList } from 'src/components/Fields/MeasuresList'; import { DimensionsList } from 'src/components/Fields/DimensionsList'; @@ -117,6 +117,8 @@ export const APP = (props: APPProps) => { const ChartWrapper = () => { const vseed = useVBIStore((state) => state.vseed); const loading = useVBIStore((state) => state.loading); + const builder = useVBIStore((state) => state.builder); + const isEmptyDsl = builder.isEmpty(); return ( { }, }} > - {vseed && } + {isEmptyDsl ? ( + + + 暂时为空 + + 请先拖拽至少一个 Dimensions 或 Measures 字段到右侧分析区 + + + } + /> + + ) : ( + vseed && + )} ); }; diff --git a/practices/demo/src/components/Shelfs/DimensionShelf.tsx b/practices/demo/src/components/Shelfs/DimensionShelf.tsx index 735cc4ffd2..7c4a8808ed 100644 --- a/practices/demo/src/components/Shelfs/DimensionShelf.tsx +++ b/practices/demo/src/components/Shelfs/DimensionShelf.tsx @@ -1,12 +1,12 @@ -import { CloseOutlined, DownOutlined } from '@ant-design/icons'; -import { Flex } from 'antd'; +import { DownOutlined, HolderOutlined } from '@ant-design/icons'; +import { Dropdown, Flex, Input, Modal, message, type MenuProps } from 'antd'; import { useVBIStore } from 'src/model'; import { useVBIDimensions } from 'src/hooks'; import { useState } from 'react'; export const DimensionShelf = ({ style }: { style?: React.CSSProperties }) => { const builder = useVBIStore((state) => state.builder); - const { dimensions, addDimension, removeDimension } = + const { dimensions, addDimension, removeDimension, updateDimension } = useVBIDimensions(builder); const [isDragOver, setIsDragOver] = useState(false); @@ -53,15 +53,23 @@ export const DimensionShelf = ({ style }: { style?: React.CSSProperties }) => { const draggedDimension = dimensions[dragIndex]; if (draggedDimension) { + type YArrayLike = { + get: (index: number) => unknown; + delete: (index: number, length: number) => void; + insert: (index: number, content: unknown[]) => void; + }; + const yDimensions = builder.dsl.get('dimensions') as + | YArrayLike + | undefined; + if (!yDimensions) return; + builder.doc.transact(() => { - builder.dimensions.remove(draggedDimension.field); - const newDimensions = [...dimensions]; - newDimensions.splice(dragIndex, 1); - newDimensions.splice(dropIndex, 0, draggedDimension); + const draggedYMap = yDimensions.get(dragIndex); + if (!draggedYMap) return; - newDimensions.forEach((d) => { - builder.dimensions.add(d.field, () => {}); - }); + yDimensions.delete(dragIndex, 1); + const insertIndex = dragIndex < dropIndex ? dropIndex - 1 : dropIndex; + yDimensions.insert(insertIndex, [draggedYMap]); }); } }; @@ -93,6 +101,71 @@ export const DimensionShelf = ({ style }: { style?: React.CSSProperties }) => { } }; + const renameDimension = (id: string, alias: string) => { + updateDimension(id, (node) => { + node.setAlias(alias); + }); + }; + + const openRenameModal = (id: string, currentAlias: string) => { + let nextAlias = currentAlias; + Modal.confirm({ + title: '重命名维度', + okText: '保存', + cancelText: '取消', + content: ( + { + nextAlias = e.target.value; + }} + /> + ), + onOk: () => { + const trimmed = nextAlias.trim(); + if (!trimmed) { + message.warning('名称不能为空'); + return Promise.reject(); + } + renameDimension(id, trimmed); + return Promise.resolve(); + }, + }); + }; + + const buildDimensionMenuItems = (): MenuProps['items'] => { + return [ + { + key: 'rename', + label: '重命名', + }, + { + type: 'divider', + }, + { + key: 'delete', + label: 删除, + }, + ]; + }; + + const onDimensionMenuClick = ( + dimension: (typeof dimensions)[number], + key: string, + ) => { + if (key === 'rename') { + openRenameModal(dimension.id, dimension.alias || dimension.field); + return; + } + + if (key === 'delete') { + removeDimension(dimension.id); + } + }; + return ( { )} {dimensions.map((dimension, index) => (
handleDragStart(e, index)} + key={`dimension-shelf-${dimension.id}`} onDragOver={handleDragOver} onDrop={(e) => handleDrop(e, index)} style={{ @@ -147,26 +218,51 @@ export const DimensionShelf = ({ style }: { style?: React.CSSProperties }) => { height: 24, }} > - - handleDragStart(e, index)} + onClick={(e) => e.stopPropagation()} + style={{ fontSize: 10, cursor: 'grab', color: '#8c8c8c' }} + /> + { + domEvent.stopPropagation(); + onDimensionMenuClick(dimension, key); + }, }} > - {dimension.field} - - { - e.stopPropagation(); - removeDimension(dimension.field); - }} - style={{ fontSize: 10, cursor: 'pointer', color: '#8c8c8c' }} - onMouseEnter={(e) => (e.currentTarget.style.color = '#ff4d4f')} - onMouseLeave={(e) => (e.currentTarget.style.color = '#8c8c8c')} - /> + { + e.stopPropagation(); + }} + style={{ + display: 'inline-flex', + alignItems: 'center', + gap: 4, + cursor: 'pointer', + flex: 1, + minWidth: 0, + justifyContent: 'center', + }} + > + + {dimension.alias || dimension.field} + + + +
))}
diff --git a/practices/demo/src/components/Shelfs/FilterShelf.tsx b/practices/demo/src/components/Shelfs/FilterShelf.tsx index d68f66e244..8fa260a57e 100644 --- a/practices/demo/src/components/Shelfs/FilterShelf.tsx +++ b/practices/demo/src/components/Shelfs/FilterShelf.tsx @@ -407,7 +407,9 @@ export const HavingShelf = ({ style }: { style?: React.CSSProperties }) => { } // 查找现有过滤器以获取原始 field - const existingFilter = builder.havingFilter.find(updatedItem.id); + const existingFilter = builder.havingFilter.find( + (entry) => entry.getId() === updatedItem.id, + ); const existingField = existingFilter && 'getField' in existingFilter diff --git a/practices/demo/src/components/Shelfs/MeasureShelf.tsx b/practices/demo/src/components/Shelfs/MeasureShelf.tsx index 3b4d94c366..1d5c3bc284 100644 --- a/practices/demo/src/components/Shelfs/MeasureShelf.tsx +++ b/practices/demo/src/components/Shelfs/MeasureShelf.tsx @@ -1,12 +1,21 @@ -import { CloseOutlined, DownOutlined } from '@ant-design/icons'; -import { Flex } from 'antd'; +import { DownOutlined, HolderOutlined } from '@ant-design/icons'; +import { Dropdown, Flex, Input, Modal, message, type MenuProps } from 'antd'; import { useVBIStore } from 'src/model'; import { useVBIMeasures } from 'src/hooks'; import { useState } from 'react'; +const AGGREGATE_ITEMS = [ + { key: 'sum', label: '求和 (sum)' }, + { key: 'count', label: '计数 (count)' }, + { key: 'avg', label: '平均值 (avg)' }, + { key: 'min', label: '最小值 (min)' }, + { key: 'max', label: '最大值 (max)' }, +] as const; + export const MeasureShelf = ({ style }: { style?: React.CSSProperties }) => { const builder = useVBIStore((state) => state.builder); - const { measures, addMeasure, removeMeasure } = useVBIMeasures(builder); + const { measures, addMeasure, removeMeasure, updateMeasure } = + useVBIMeasures(builder); const [isDragOver, setIsDragOver] = useState(false); const handleDragStart = (e: React.DragEvent, index: number) => { @@ -54,16 +63,21 @@ export const MeasureShelf = ({ style }: { style?: React.CSSProperties }) => { // 通过删除并重新添加来实现排序 const draggedMeasure = measures[dragIndex]; if (draggedMeasure) { + type YArrayLike = { + get: (index: number) => unknown; + delete: (index: number, length: number) => void; + insert: (index: number, content: unknown[]) => void; + }; + const yMeasures = builder.dsl.get('measures') as YArrayLike | undefined; + if (!yMeasures) return; + builder.doc.transact(() => { - builder.measures.remove(draggedMeasure.field); - // 将元素插入到新位置 - const newMeasures = [...measures]; - newMeasures.splice(dragIndex, 1); - newMeasures.splice(dropIndex, 0, draggedMeasure); - - newMeasures.forEach((m) => { - builder.measures.add(m.field, () => {}); - }); + const draggedYMap = yMeasures.get(dragIndex); + if (!draggedYMap) return; + + yMeasures.delete(dragIndex, 1); + const insertIndex = dragIndex < dropIndex ? dropIndex - 1 : dropIndex; + yMeasures.insert(insertIndex, [draggedYMap]); }); } }; @@ -96,6 +110,114 @@ export const MeasureShelf = ({ style }: { style?: React.CSSProperties }) => { } }; + const renameMeasure = (id: string, alias: string) => { + updateMeasure(id, (node) => { + node.setAlias(alias); + }); + }; + + const changeAggregate = ( + id: string, + aggregate: 'sum' | 'count' | 'avg' | 'min' | 'max', + ) => { + updateMeasure(id, (node) => { + node.setAggregate({ func: aggregate }); + }); + }; + + const openRenameModal = (id: string, currentAlias: string) => { + let nextAlias = currentAlias; + Modal.confirm({ + title: '重命名指标', + okText: '保存', + cancelText: '取消', + content: ( + { + nextAlias = e.target.value; + }} + /> + ), + onOk: () => { + const trimmed = nextAlias.trim(); + if (!trimmed) { + message.warning('名称不能为空'); + return Promise.reject(); + } + renameMeasure(id, trimmed); + return Promise.resolve(); + }, + }); + }; + + const buildMeasureMenuItems = ( + measure: (typeof measures)[number], + ): MenuProps['items'] => { + const currentAggregate = measure.aggregate?.func ?? 'sum'; + return [ + { + key: 'aggregate', + label: '修改聚合方式', + children: AGGREGATE_ITEMS.map((item) => ({ + key: `aggregate:${item.key}`, + label: `${currentAggregate === item.key ? '✓ ' : ''}${item.label}`, + })), + }, + { + key: 'rename', + label: '重命名', + }, + { + type: 'divider', + }, + { + key: 'delete', + label: 删除, + }, + ]; + }; + + const onMeasureMenuClick = ( + measure: (typeof measures)[number], + key: string, + ) => { + if (key === 'rename') { + openRenameModal(measure.id, measure.alias || measure.field); + return; + } + + if (key === 'delete') { + removeMeasure(measure.id); + return; + } + + if (key.startsWith('aggregate:')) { + const aggregate = key.replace('aggregate:', ''); + if ( + aggregate === 'sum' || + aggregate === 'count' || + aggregate === 'avg' || + aggregate === 'min' || + aggregate === 'max' + ) { + changeAggregate(measure.id, aggregate); + } + } + }; + + const getMeasureDisplayLabel = (measure: (typeof measures)[number]) => { + const baseLabel = measure.alias || measure.field; + const aggregate = measure.aggregate?.func; + if (!aggregate) { + return baseLabel; + } + return `${aggregate}(${baseLabel})`; + }; + return ( { )} {measures.map((measure, index) => (
handleDragStart(e, index)} + key={`measure-shelf-${measure.id}`} onDragOver={handleDragOver} onDrop={(e) => handleDrop(e, index)} style={{ @@ -150,26 +270,51 @@ export const MeasureShelf = ({ style }: { style?: React.CSSProperties }) => { height: 24, }} > - - handleDragStart(e, index)} + onClick={(e) => e.stopPropagation()} + style={{ fontSize: 10, cursor: 'grab', color: '#8c8c8c' }} + /> + { + domEvent.stopPropagation(); + onMeasureMenuClick(measure, key); + }, }} > - {measure.field} - - { - e.stopPropagation(); - removeMeasure(measure.field); - }} - style={{ fontSize: 9, cursor: 'pointer', color: '#8c8c8c' }} - onMouseEnter={(e) => (e.currentTarget.style.color = '#ff4d4f')} - onMouseLeave={(e) => (e.currentTarget.style.color = '#8c8c8c')} - /> + { + e.stopPropagation(); + }} + style={{ + display: 'inline-flex', + alignItems: 'center', + gap: 4, + cursor: 'pointer', + flex: 1, + minWidth: 0, + justifyContent: 'center', + }} + > + + {getMeasureDisplayLabel(measure)} + + + +
))}
diff --git a/practices/demo/src/hooks/useVBIDimensions.ts b/practices/demo/src/hooks/useVBIDimensions.ts index 06be9b59bd..4d647ba100 100644 --- a/practices/demo/src/hooks/useVBIDimensions.ts +++ b/practices/demo/src/hooks/useVBIDimensions.ts @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import { VBIBuilder } from '@visactor/vbi'; export interface VBIDimension { + id: string; field: string; alias: string; } @@ -18,16 +19,17 @@ export const useVBIDimensions = (builder: VBIBuilder | undefined) => { return; } - // 初始化 - setDimensions(builder.dimensions.toJSON() as VBIDimension[]); - - // 监听变化 - const updateHandler = () => { + // 统一由 toJSON 驱动 UI 状态 + const syncFromBuilder = () => { setDimensions(builder.dimensions.toJSON() as VBIDimension[]); }; - const unobserve = builder.dimensions.observe(updateHandler); - return unobserve; + syncFromBuilder(); + builder.doc.on('update', syncFromBuilder); + + return () => { + builder.doc.off('update', syncFromBuilder); + }; }, [builder]); // 添加维度 @@ -44,10 +46,10 @@ export const useVBIDimensions = (builder: VBIBuilder | undefined) => { // 删除维度 const removeDimension = useCallback( - (field: string) => { + (id: string) => { if (builder) { builder.doc.transact(() => { - builder.dimensions.remove(field); + builder.dimensions.remove(id); }); } }, @@ -56,10 +58,10 @@ export const useVBIDimensions = (builder: VBIBuilder | undefined) => { // 更新维度 const updateDimension = useCallback( - (field: string, callback: (node: any) => void) => { + (id: string, callback: (node: any) => void) => { if (builder) { builder.doc.transact(() => { - builder.dimensions.update(field, callback); + builder.dimensions.update(id, callback); }); } }, @@ -68,9 +70,9 @@ export const useVBIDimensions = (builder: VBIBuilder | undefined) => { // 查找维度 const findDimension = useCallback( - (field: string) => { + (id: string) => { if (builder) { - return builder.dimensions.find(field); + return builder.dimensions.find((node) => node.getId() === id); } return undefined; }, diff --git a/practices/demo/src/hooks/useVBIHavingFilter.ts b/practices/demo/src/hooks/useVBIHavingFilter.ts index 12b0a7155b..77b48c1b5a 100644 --- a/practices/demo/src/hooks/useVBIHavingFilter.ts +++ b/practices/demo/src/hooks/useVBIHavingFilter.ts @@ -96,7 +96,7 @@ export const useVBIHavingFilter = (builder: VBIBuilder | undefined) => { const findFilter = useCallback( (id: string) => { if (builder) { - return builder.havingFilter.find(id); + return builder.havingFilter.find((entry) => entry.getId() === id); } return undefined; }, diff --git a/practices/demo/src/hooks/useVBIMeasures.ts b/practices/demo/src/hooks/useVBIMeasures.ts index d0bb3a02a2..c1b3591cfb 100644 --- a/practices/demo/src/hooks/useVBIMeasures.ts +++ b/practices/demo/src/hooks/useVBIMeasures.ts @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import { VBIBuilder } from '@visactor/vbi'; export interface VBIMeasure { + id: string; field: string; alias: string; encoding?: string; @@ -22,16 +23,17 @@ export const useVBIMeasures = (builder: VBIBuilder | undefined) => { return; } - // 初始化 - setMeasures(builder.measures.toJSON() as VBIMeasure[]); - - // 监听变化 - const updateHandler = () => { + // 统一由 toJSON 驱动 UI 状态 + const syncFromBuilder = () => { setMeasures(builder.measures.toJSON() as VBIMeasure[]); }; - const unobserve = builder.measures.observe(updateHandler); - return unobserve; + syncFromBuilder(); + builder.doc.on('update', syncFromBuilder); + + return () => { + builder.doc.off('update', syncFromBuilder); + }; }, [builder]); // 添加度量 @@ -48,10 +50,10 @@ export const useVBIMeasures = (builder: VBIBuilder | undefined) => { // 删除度量 const removeMeasure = useCallback( - (field: string) => { + (id: string) => { if (builder) { builder.doc.transact(() => { - builder.measures.remove(field); + builder.measures.remove(id); }); } }, @@ -60,10 +62,10 @@ export const useVBIMeasures = (builder: VBIBuilder | undefined) => { // 更新度量 const updateMeasure = useCallback( - (field: string, callback: (node: any) => void) => { + (id: string, callback: (node: any) => void) => { if (builder) { builder.doc.transact(() => { - builder.measures.update(field, callback); + builder.measures.update(id, callback); }); } }, @@ -72,9 +74,9 @@ export const useVBIMeasures = (builder: VBIBuilder | undefined) => { // 查找度量 const findMeasure = useCallback( - (field: string) => { + (id: string) => { if (builder) { - return builder.measures.find(field); + return builder.measures.find((node) => node.getId() === id); } return undefined; }, diff --git a/practices/demo/src/hooks/useVBIWhereFilter.ts b/practices/demo/src/hooks/useVBIWhereFilter.ts index e4ba77f76c..8ab4eeb7f0 100644 --- a/practices/demo/src/hooks/useVBIWhereFilter.ts +++ b/practices/demo/src/hooks/useVBIWhereFilter.ts @@ -93,7 +93,7 @@ export const useVBIWhereFilter = (builder: VBIBuilder | undefined) => { const findFilter = useCallback( (id: string) => { if (builder) { - return builder.whereFilter.find(id); + return builder.whereFilter.find((entry) => entry.getId() === id); } return undefined; }, @@ -165,7 +165,9 @@ export const useVBIWhereFilter = (builder: VBIBuilder | undefined) => { const findGroup = useCallback( (id: string) => { if (builder) { - const result = builder.whereFilter.find(id); + const result = builder.whereFilter.find( + (entry) => entry.getId() === id, + ); if (result && 'getOperator' in result) { return result; } diff --git a/practices/demo/src/model/VBIStore.ts b/practices/demo/src/model/VBIStore.ts index a5eb9b4f6f..4534550e78 100644 --- a/practices/demo/src/model/VBIStore.ts +++ b/practices/demo/src/model/VBIStore.ts @@ -51,6 +51,12 @@ export const useVBIStore = create((set, get) => ({ const { builder, setLoading, setVSeed, setDsl } = get(); const updateAll = async () => { + if (builder.isEmpty()) { + setLoading(false); + setVSeed(null); + return; + } + setLoading(true); try { const newVSeed = await builder.buildVSeed(); diff --git a/practices/minimalist/package.json b/practices/minimalist/package.json index cd4da56909..409c0045f4 100644 --- a/practices/minimalist/package.json +++ b/practices/minimalist/package.json @@ -46,7 +46,7 @@ "happy-dom": "^20.3.3", "prettier": "^3.7.3", "react": "^19.2.3", - "typescript": "^5.9.3", + "typescript": "5.9.3", "typescript-eslint": "^8.48.0" }, "peerDependencies": {