@knotx/plugins-batch 0.4.11 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.en.md +286 -0
  2. package/README.md +286 -0
  3. package/package.json +8 -8
package/README.en.md ADDED
@@ -0,0 +1,286 @@
1
+ # @knotx/plugins-batch
2
+
3
+ Batch plugin that provides operation batching functionality for KnotX.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @knotx/plugins-batch
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ The Batch plugin provides operation batching functionality for KnotX, capable of merging multiple consecutive node operations into a single batch operation, thereby improving performance and reducing history entries. This plugin implements operation merging through RxJS buffering mechanisms.
14
+
15
+ ## Implementation Principle
16
+
17
+ The core implementation principles of the Batch plugin:
18
+
19
+ 1. **Operation Interception**: Intercepts node operation pipelines to identify batchable operations
20
+ 2. **Buffering Mechanism**: Uses RxJS `buffer` and `timer` to merge operations within time windows
21
+ 3. **Operation Merging**: Combines multiple individual operations into a single batch operation
22
+ 4. **Recursive Processing**: Supports flattening of nested batch operations
23
+
24
+ ## Dependencies
25
+
26
+ ### Core Dependencies
27
+ - `@knotx/core`: Provides base plugin architecture and data management
28
+ - `@knotx/decorators`: Provides decorator support
29
+ - `rxjs`: Provides reactive programming support
30
+
31
+ ### Automatic Dependencies
32
+ - Node operation pipeline in `@knotx/core`: Automatically integrates into operation processing flow
33
+
34
+ ## API Documentation
35
+
36
+ ### Main Classes
37
+
38
+ #### Batch
39
+
40
+ The main class of the Batch plugin, extending `BasePlugin`.
41
+
42
+ ```typescript
43
+ export class Batch extends BasePlugin<'batch'> {
44
+ name = 'batch' as const
45
+ }
46
+ ```
47
+
48
+ ### Implementation Logic
49
+
50
+ This plugin works automatically without manual configuration. It will:
51
+
52
+ 1. **Monitor Node Operations**: Automatically intercepts all node operations
53
+ 2. **Determine Batch Conditions**: If operation contains `draftId`, process immediately (no batching)
54
+ 3. **Batch Processing**: Merge operations within 100ms into a single batch operation
55
+ 4. **Recursive Flattening**: Handle nested batch operations
56
+
57
+ ## Usage Examples
58
+
59
+ ### Basic Usage
60
+
61
+ ```typescript
62
+ import { Batch } from '@knotx/plugins-batch'
63
+
64
+ const engine = new Engine({
65
+ plugins: [Batch],
66
+ })
67
+
68
+ // Batching works automatically without additional configuration
69
+ ```
70
+
71
+ ### Integration with Other Plugins
72
+
73
+ ```typescript
74
+ import { Batch } from '@knotx/plugins-batch'
75
+ import { Drag } from '@knotx/plugins-drag'
76
+ import { History } from '@knotx/plugins-history'
77
+
78
+ const engine = new Engine({
79
+ plugins: [Batch, History, Drag],
80
+ })
81
+
82
+ // Batching will optimize history records and drag operations
83
+ ```
84
+
85
+ ### Observing Batch Effects
86
+
87
+ ```typescript
88
+ class BatchObserverPlugin extends BasePlugin {
89
+ @inject.nodesManager()
90
+ nodesManager!: DataManager<Node>
91
+
92
+ @OnInit
93
+ init() {
94
+ // Monitor node operations
95
+ this.nodesManager.operations$.subscribe((operation) => {
96
+ if (operation.type === 'batch') {
97
+ console.log('Batch operation:', operation.operations.length, 'operations')
98
+ }
99
+ else {
100
+ console.log('Single operation:', operation.type)
101
+ }
102
+ })
103
+ }
104
+ }
105
+ ```
106
+
107
+ ## Detailed Working Principle
108
+
109
+ ### Operation Type Determination
110
+
111
+ ```typescript
112
+ // Source code example (simplified)
113
+ transform: operations$ => pipe(
114
+ exhaustMap((firstOperation) => {
115
+ // If draft operation, process immediately
116
+ if ('draftId' in firstOperation) {
117
+ return of([firstOperation])
118
+ }
119
+
120
+ // Otherwise wait for buffer window
121
+ return operations$.pipe(
122
+ startWith(firstOperation),
123
+ buffer(timer(100)), // 100ms buffer window
124
+ take(1),
125
+ )
126
+ }),
127
+ map(operations => ({
128
+ type: 'batch' as const,
129
+ operations: operations.flatMap(op =>
130
+ op.type === 'batch' ? op.operations : op
131
+ ),
132
+ })),
133
+ )
134
+ ```
135
+
136
+ ### Batch Advantages
137
+
138
+ 1. **Performance Improvement**: Reduces frequent re-renders and state updates
139
+ 2. **History Optimization**: Merges multiple operations into a single history entry
140
+ 3. **User Experience**: Provides smoother operation feel
141
+ 4. **Resource Savings**: Reduces unnecessary computation and memory allocation
142
+
143
+ ## Real-world Application Scenarios
144
+
145
+ ### Drag Operation Optimization
146
+
147
+ ```typescript
148
+ // Dragging produces many position update operations
149
+ // Batching will merge these operations into a single batch operation
150
+
151
+ engine.dispatchNodeOperation({
152
+ type: 'update',
153
+ data: { id: 'node1', position: { x: 100, y: 100 } },
154
+ })
155
+
156
+ engine.dispatchNodeOperation({
157
+ type: 'update',
158
+ data: { id: 'node1', position: { x: 101, y: 101 } },
159
+ })
160
+
161
+ engine.dispatchNodeOperation({
162
+ type: 'update',
163
+ data: { id: 'node1', position: { x: 102, y: 102 } },
164
+ })
165
+
166
+ // Above operations will be merged into a single batch operation
167
+ ```
168
+
169
+ ### Batch Adding Nodes
170
+
171
+ ```typescript
172
+ // Batch adding multiple nodes
173
+ for (let i = 0; i < 10; i++) {
174
+ engine.dispatchNodeOperation({
175
+ type: 'add',
176
+ data: { id: `node${i}`, position: { x: i * 100, y: 100 } },
177
+ })
178
+ }
179
+
180
+ // These operations will be merged into a single batch operation
181
+ ```
182
+
183
+ ### Complex Operation Sequences
184
+
185
+ ```typescript
186
+ // Complex operation sequences
187
+ engine.dispatchNodeOperation({
188
+ type: 'add',
189
+ data: { id: 'node1', position: { x: 100, y: 100 } },
190
+ })
191
+
192
+ engine.dispatchNodeOperation({
193
+ type: 'update',
194
+ data: { id: 'node1', data: { label: 'Updated' } },
195
+ })
196
+
197
+ engine.dispatchNodeOperation({
198
+ type: 'add',
199
+ data: { id: 'node2', position: { x: 200, y: 200 } },
200
+ })
201
+
202
+ // These operations will be merged into a single batch operation
203
+ ```
204
+
205
+ ## Performance Impact
206
+
207
+ ### Optimization Effects
208
+
209
+ 1. **Reduced Re-renders**: Batched operations only trigger one re-render
210
+ 2. **Optimized History**: Reduces number of history entries
211
+ 3. **Improved Responsiveness**: Reduces operation processing latency
212
+
213
+ ### Buffer Time Adjustment
214
+
215
+ Batching uses a fixed 100ms buffer window, which balances performance and responsiveness:
216
+
217
+ - **Too Short**: May not effectively merge operations
218
+ - **Too Long**: May affect real-time user experience
219
+
220
+ ## Interaction with Other Plugins
221
+
222
+ ### History Plugin
223
+
224
+ ```typescript
225
+ // Batching will significantly reduce history entries
226
+ const engine = new Engine({
227
+ plugins: [Batch, History],
228
+ })
229
+
230
+ // Originally 100 operations might produce only 1 history entry
231
+ ```
232
+
233
+ ### Drag Plugin
234
+
235
+ ```typescript
236
+ // Drag operations produce many position updates
237
+ // Batching will merge these updates
238
+ const engine = new Engine({
239
+ plugins: [Batch, Drag],
240
+ })
241
+ ```
242
+
243
+ ## File Directory Structure
244
+
245
+ ```
246
+ packages/plugins-batch/
247
+ ├── src/
248
+ │ ├── batch.ts # Main implementation file
249
+ │ └── index.ts # Export file
250
+ ├── dist/ # Build output directory
251
+ ├── package.json # Package configuration
252
+ ├── build.config.ts # Build configuration
253
+ ├── tsconfig.json # TypeScript configuration
254
+ ├── eslint.config.mjs # ESLint configuration
255
+ └── CHANGELOG.md # Changelog
256
+ ```
257
+
258
+ ### Core Files Description
259
+
260
+ - **batch.ts**: Contains the main implementation of the Batch plugin, including operation interception, buffering mechanism, and batch processing logic
261
+ - **index.ts**: Exports the Batch class and related type definitions
262
+
263
+ ## Best Practices
264
+
265
+ ### Performance Optimization
266
+
267
+ 1. **Reasonable Usage**: Batching is most effective for frequent operation sequences
268
+ 2. **Combine with Other Plugins**: Best results when used with History and Drag plugins
269
+ 3. **Monitor Effects**: Monitor batching effects during development
270
+
271
+ ### Debugging Recommendations
272
+
273
+ 1. **Operation Logging**: Log operation counts before and after batching
274
+ 2. **Performance Testing**: Compare performance with/without batching enabled
275
+ 3. **Memory Monitoring**: Ensure batching doesn't cause memory leaks
276
+
277
+ ## Notes
278
+
279
+ 1. **Draft Operations**: Operations containing `draftId` will not be batched
280
+ 2. **Operation Order**: Batching preserves the original order of operations
281
+ 3. **Error Handling**: Errors in batch operations affect the entire batch
282
+ 4. **Time Window**: The 100ms buffer time is fixed and not configurable
283
+
284
+ ## License
285
+
286
+ MIT
package/README.md ADDED
@@ -0,0 +1,286 @@
1
+ # @knotx/plugins-batch
2
+
3
+ 批处理插件,为 KnotX 提供操作批处理功能。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @knotx/plugins-batch
9
+ ```
10
+
11
+ ## 概述
12
+
13
+ Batch 插件为 KnotX 提供了操作批处理功能,能够将多个连续的节点操作合并为一个批处理操作,从而提高性能并减少历史记录条目。该插件通过 RxJS 的缓冲机制实现操作合并。
14
+
15
+ ## 实现原理
16
+
17
+ Batch 插件的核心实现原理:
18
+
19
+ 1. **操作拦截**:拦截节点操作管道,识别可批处理的操作
20
+ 2. **缓冲机制**:使用 RxJS 的 `buffer` 和 `timer` 实现时间窗口内的操作合并
21
+ 3. **操作合并**:将多个单独操作合并为一个批处理操作
22
+ 4. **递归处理**:支持嵌套批处理操作的扁平化处理
23
+
24
+ ## 依赖关系
25
+
26
+ ### 核心依赖
27
+ - `@knotx/core`:提供基础插件架构和数据管理
28
+ - `@knotx/decorators`:提供装饰器支持
29
+ - `rxjs`:提供响应式编程支持
30
+
31
+ ### 自动依赖
32
+ - `@knotx/core` 中的节点操作管道:自动集成到操作处理流程中
33
+
34
+ ## API 文档
35
+
36
+ ### 主要类
37
+
38
+ #### Batch
39
+
40
+ 批处理插件的主要类,继承自 `BasePlugin`。
41
+
42
+ ```typescript
43
+ export class Batch extends BasePlugin<'batch'> {
44
+ name = 'batch' as const
45
+ }
46
+ ```
47
+
48
+ ### 实现逻辑
49
+
50
+ 该插件自动工作,无需手动配置。它会:
51
+
52
+ 1. **监听节点操作**:自动拦截所有节点操作
53
+ 2. **判断批处理条件**:如果操作包含 `draftId`,则立即处理(不批处理)
54
+ 3. **批量处理**:将 100ms 内的操作合并为一个批处理操作
55
+ 4. **递归扁平化**:处理嵌套的批处理操作
56
+
57
+ ## 使用示例
58
+
59
+ ### 基本用法
60
+
61
+ ```typescript
62
+ import { Batch } from '@knotx/plugins-batch'
63
+
64
+ const engine = new Engine({
65
+ plugins: [Batch],
66
+ })
67
+
68
+ // 批处理会自动工作,无需额外配置
69
+ ```
70
+
71
+ ### 与其他插件集成
72
+
73
+ ```typescript
74
+ import { Batch } from '@knotx/plugins-batch'
75
+ import { Drag } from '@knotx/plugins-drag'
76
+ import { History } from '@knotx/plugins-history'
77
+
78
+ const engine = new Engine({
79
+ plugins: [Batch, History, Drag],
80
+ })
81
+
82
+ // 批处理会优化历史记录和拖拽操作
83
+ ```
84
+
85
+ ### 观察批处理效果
86
+
87
+ ```typescript
88
+ class BatchObserverPlugin extends BasePlugin {
89
+ @inject.nodesManager()
90
+ nodesManager!: DataManager<Node>
91
+
92
+ @OnInit
93
+ init() {
94
+ // 监听节点操作
95
+ this.nodesManager.operations$.subscribe((operation) => {
96
+ if (operation.type === 'batch') {
97
+ console.log('批处理操作:', operation.operations.length, '个操作')
98
+ }
99
+ else {
100
+ console.log('单个操作:', operation.type)
101
+ }
102
+ })
103
+ }
104
+ }
105
+ ```
106
+
107
+ ## 工作原理详解
108
+
109
+ ### 操作类型判断
110
+
111
+ ```typescript
112
+ // 源码示例(简化版)
113
+ transform: operations$ => pipe(
114
+ exhaustMap((firstOperation) => {
115
+ // 如果是草稿操作,立即处理
116
+ if ('draftId' in firstOperation) {
117
+ return of([firstOperation])
118
+ }
119
+
120
+ // 否则等待缓冲窗口
121
+ return operations$.pipe(
122
+ startWith(firstOperation),
123
+ buffer(timer(100)), // 100ms 缓冲窗口
124
+ take(1),
125
+ )
126
+ }),
127
+ map(operations => ({
128
+ type: 'batch' as const,
129
+ operations: operations.flatMap(op =>
130
+ op.type === 'batch' ? op.operations : op
131
+ ),
132
+ })),
133
+ )
134
+ ```
135
+
136
+ ### 批处理优势
137
+
138
+ 1. **性能提升**:减少频繁的重渲染和状态更新
139
+ 2. **历史记录优化**:将多个操作合并为一个历史记录条目
140
+ 3. **用户体验**:提供更流畅的操作感受
141
+ 4. **资源节约**:减少不必要的计算和内存分配
142
+
143
+ ## 实际应用场景
144
+
145
+ ### 拖拽操作优化
146
+
147
+ ```typescript
148
+ // 拖拽过程中会产生大量位置更新操作
149
+ // 批处理会将这些操作合并为一个批处理操作
150
+
151
+ engine.dispatchNodeOperation({
152
+ type: 'update',
153
+ data: { id: 'node1', position: { x: 100, y: 100 } },
154
+ })
155
+
156
+ engine.dispatchNodeOperation({
157
+ type: 'update',
158
+ data: { id: 'node1', position: { x: 101, y: 101 } },
159
+ })
160
+
161
+ engine.dispatchNodeOperation({
162
+ type: 'update',
163
+ data: { id: 'node1', position: { x: 102, y: 102 } },
164
+ })
165
+
166
+ // 以上操作会被合并为一个批处理操作
167
+ ```
168
+
169
+ ### 批量添加节点
170
+
171
+ ```typescript
172
+ // 批量添加多个节点
173
+ for (let i = 0; i < 10; i++) {
174
+ engine.dispatchNodeOperation({
175
+ type: 'add',
176
+ data: { id: `node${i}`, position: { x: i * 100, y: 100 } },
177
+ })
178
+ }
179
+
180
+ // 这些操作会被合并为一个批处理操作
181
+ ```
182
+
183
+ ### 复杂操作序列
184
+
185
+ ```typescript
186
+ // 复杂的操作序列
187
+ engine.dispatchNodeOperation({
188
+ type: 'add',
189
+ data: { id: 'node1', position: { x: 100, y: 100 } },
190
+ })
191
+
192
+ engine.dispatchNodeOperation({
193
+ type: 'update',
194
+ data: { id: 'node1', data: { label: 'Updated' } },
195
+ })
196
+
197
+ engine.dispatchNodeOperation({
198
+ type: 'add',
199
+ data: { id: 'node2', position: { x: 200, y: 200 } },
200
+ })
201
+
202
+ // 这些操作会被合并为一个批处理操作
203
+ ```
204
+
205
+ ## 性能影响
206
+
207
+ ### 优化效果
208
+
209
+ 1. **减少重渲染**:批处理后的操作只触发一次重渲染
210
+ 2. **优化历史记录**:减少历史记录条目数量
211
+ 3. **提升响应性**:减少操作处理的延迟
212
+
213
+ ### 缓冲时间调整
214
+
215
+ 批处理使用固定的 100ms 缓冲窗口,这个时间在性能和响应性之间取得平衡:
216
+
217
+ - **太短**:可能无法有效合并操作
218
+ - **太长**:可能影响用户体验的实时性
219
+
220
+ ## 与其他插件的交互
221
+
222
+ ### History 插件
223
+
224
+ ```typescript
225
+ // 批处理会大大减少历史记录条目
226
+ const engine = new Engine({
227
+ plugins: [Batch, History],
228
+ })
229
+
230
+ // 原本 100 个操作可能只产生 1 个历史记录条目
231
+ ```
232
+
233
+ ### Drag 插件
234
+
235
+ ```typescript
236
+ // 拖拽操作会产生大量位置更新
237
+ // 批处理会将这些更新合并
238
+ const engine = new Engine({
239
+ plugins: [Batch, Drag],
240
+ })
241
+ ```
242
+
243
+ ## 文件目录结构
244
+
245
+ ```
246
+ packages/plugins-batch/
247
+ ├── src/
248
+ │ ├── batch.ts # 主要实现文件
249
+ │ └── index.ts # 导出文件
250
+ ├── dist/ # 构建输出目录
251
+ ├── package.json # 包配置文件
252
+ ├── build.config.ts # 构建配置
253
+ ├── tsconfig.json # TypeScript 配置
254
+ ├── eslint.config.mjs # ESLint 配置
255
+ └── CHANGELOG.md # 更新日志
256
+ ```
257
+
258
+ ### 核心文件说明
259
+
260
+ - **batch.ts**:包含 Batch 插件的主要实现,包括操作拦截、缓冲机制和批处理逻辑
261
+ - **index.ts**:导出 Batch 类和相关类型定义
262
+
263
+ ## 最佳实践
264
+
265
+ ### 性能优化
266
+
267
+ 1. **合理使用**:批处理对于频繁的操作序列最有效
268
+ 2. **配合其他插件**:与 History 和 Drag 插件配合使用效果最佳
269
+ 3. **监控效果**:在开发阶段监控批处理的效果
270
+
271
+ ### 调试建议
272
+
273
+ 1. **操作日志**:记录批处理前后的操作数量
274
+ 2. **性能测试**:对比启用/禁用批处理的性能差异
275
+ 3. **内存监控**:确保批处理不会导致内存泄漏
276
+
277
+ ## 注意事项
278
+
279
+ 1. **草稿操作**:包含 `draftId` 的操作不会被批处理
280
+ 2. **操作顺序**:批处理保持操作的原始顺序
281
+ 3. **错误处理**:批处理操作中的错误会影响整个批次
282
+ 4. **时间窗口**:100ms 的缓冲时间是固定的,不可配置
283
+
284
+ ## 许可证
285
+
286
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knotx/plugins-batch",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "Batch Plugin for Knotx",
5
5
  "author": "boenfu",
6
6
  "license": "MIT",
@@ -28,18 +28,18 @@
28
28
  "dist"
29
29
  ],
30
30
  "peerDependencies": {
31
- "@knotx/jsx": "0.4.11"
31
+ "@knotx/jsx": "0.4.13"
32
32
  },
33
33
  "dependencies": {
34
34
  "rxjs": "^7.8.1",
35
- "@knotx/core": "0.4.11",
36
- "@knotx/decorators": "0.4.11"
35
+ "@knotx/core": "0.4.13",
36
+ "@knotx/decorators": "0.4.13"
37
37
  },
38
38
  "devDependencies": {
39
- "@knotx/build-config": "0.4.11",
40
- "@knotx/eslint-config": "0.4.11",
41
- "@knotx/jsx": "0.4.11",
42
- "@knotx/typescript-config": "0.4.11"
39
+ "@knotx/build-config": "0.4.13",
40
+ "@knotx/eslint-config": "0.4.13",
41
+ "@knotx/jsx": "0.4.13",
42
+ "@knotx/typescript-config": "0.4.13"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "unbuild",