@knotx/data 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.
- package/README.en.md +490 -0
- package/README.md +490 -0
- package/package.json +4 -4
package/README.en.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# @knotx/data
|
|
2
|
+
|
|
3
|
+
A reactive data management library based on RxJS, providing powerful data operations and bidirectional relationship management capabilities.
|
|
4
|
+
|
|
5
|
+
## 📦 Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @knotx/data
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
yarn add @knotx/data
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @knotx/data
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 🔧 Overview
|
|
20
|
+
|
|
21
|
+
`@knotx/data` is the core data management package of the Knotx ecosystem, providing two main data management tools:
|
|
22
|
+
|
|
23
|
+
- **DataManager**: Manages CRUD operations for data with advanced features like draft mode, batch operations, and version control
|
|
24
|
+
- **DualRelation**: Manages bidirectional relationships between parent and child nodes with level management and circular reference detection
|
|
25
|
+
|
|
26
|
+
## 🚀 Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { DataManager, DualRelation } from '@knotx/data'
|
|
30
|
+
|
|
31
|
+
// Create data manager
|
|
32
|
+
const dataManager = new DataManager('my-data')
|
|
33
|
+
|
|
34
|
+
// Create dual relation manager
|
|
35
|
+
const relation = new DualRelation<string, string>()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 📚 API Documentation
|
|
39
|
+
|
|
40
|
+
### DataManager
|
|
41
|
+
|
|
42
|
+
#### Type Definitions
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
interface IData {
|
|
46
|
+
id: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// DataManager class type definition
|
|
50
|
+
interface DataManager<TData extends IData = IData, TTag extends string = any> {
|
|
51
|
+
// Data manager methods and properties
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Constructor
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const dataManager = new DataManager<MyData>('my-tag')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Parameters:**
|
|
62
|
+
- `tag`: Identifier for the data manager
|
|
63
|
+
|
|
64
|
+
#### Core Methods
|
|
65
|
+
|
|
66
|
+
##### `init(initialDataList?: TData[]): void`
|
|
67
|
+
|
|
68
|
+
Initialize the data manager with an initial data list.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface User {
|
|
72
|
+
id: string
|
|
73
|
+
name: string
|
|
74
|
+
age: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const userManager = new DataManager<User>('users')
|
|
78
|
+
userManager.init([
|
|
79
|
+
{ id: '1', name: 'Alice', age: 25 },
|
|
80
|
+
{ id: '2', name: 'Bob', age: 30 }
|
|
81
|
+
])
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
##### `getDataList$(): Observable<TData[]>`
|
|
85
|
+
|
|
86
|
+
Get reactive stream of data list.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
userManager.getDataList$().subscribe((users) => {
|
|
90
|
+
console.log('User list updated:', users)
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
##### `getDataList(): TData[]`
|
|
95
|
+
|
|
96
|
+
Get current data list.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const users = userManager.getDataList()
|
|
100
|
+
console.log('Current users:', users)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
##### `getData$(id: string, equal?: (a?: TData, b?: TData) => boolean): Observable<TData | undefined>`
|
|
104
|
+
|
|
105
|
+
Get reactive stream of specific data by ID.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
userManager.getData$('1').subscribe((user) => {
|
|
109
|
+
console.log('User 1 data:', user)
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
##### `getData(id: string): TData | undefined`
|
|
114
|
+
|
|
115
|
+
Get data by ID.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const user = userManager.getData('1')
|
|
119
|
+
console.log('User 1:', user)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
##### `dispatch(operation: DataOperation<TData>): void`
|
|
123
|
+
|
|
124
|
+
Dispatch data operations.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Add user
|
|
128
|
+
userManager.dispatch({
|
|
129
|
+
type: 'add',
|
|
130
|
+
data: { id: '3', name: 'Charlie', age: 28 }
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Update user
|
|
134
|
+
userManager.dispatch({
|
|
135
|
+
type: 'update',
|
|
136
|
+
id: '1',
|
|
137
|
+
data: { age: 26 }
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Remove user
|
|
141
|
+
userManager.dispatch({
|
|
142
|
+
type: 'remove',
|
|
143
|
+
id: '2'
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Batch operations
|
|
147
|
+
userManager.dispatch({
|
|
148
|
+
type: 'batch',
|
|
149
|
+
operations: [
|
|
150
|
+
{ type: 'add', data: { id: '4', name: 'David', age: 35 } },
|
|
151
|
+
{ type: 'update', id: '3', data: { age: 29 } }
|
|
152
|
+
]
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Draft Mode
|
|
157
|
+
|
|
158
|
+
Draft mode allows you to perform data operations without affecting the main data.
|
|
159
|
+
|
|
160
|
+
##### `dispatch` Draft Operations
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// Start draft
|
|
164
|
+
userManager.dispatch({
|
|
165
|
+
type: 'startDraft',
|
|
166
|
+
draftId: 'my-draft'
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// Operate in draft
|
|
170
|
+
userManager.dispatch({
|
|
171
|
+
type: 'draftOperation',
|
|
172
|
+
draftId: 'my-draft',
|
|
173
|
+
operation: {
|
|
174
|
+
type: 'add',
|
|
175
|
+
data: { id: '5', name: 'Eve', age: 24 }
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// Commit draft
|
|
180
|
+
userManager.dispatch({
|
|
181
|
+
type: 'commitDraft',
|
|
182
|
+
draftId: 'my-draft'
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Or discard draft
|
|
186
|
+
userManager.dispatch({
|
|
187
|
+
type: 'discardDraft',
|
|
188
|
+
draftId: 'my-draft'
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
##### `getDraftDataList(draftId: string): TData[] | undefined`
|
|
193
|
+
|
|
194
|
+
Get data list in draft.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const draftUsers = userManager.getDraftDataList('my-draft')
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
##### `getDraftData(draftId: string, dataId: string): TData | undefined`
|
|
201
|
+
|
|
202
|
+
Get specific data in draft.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const draftUser = userManager.getDraftData('my-draft', '5')
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
##### `dryRun(operation: DataOperation<TData>): Map<string, TData>`
|
|
209
|
+
|
|
210
|
+
Simulate operation execution, returns result without actually modifying data.
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
const result = userManager.dryRun({
|
|
214
|
+
type: 'commitDraft',
|
|
215
|
+
draftId: 'my-draft'
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### Version Control
|
|
220
|
+
|
|
221
|
+
DataManager provides version control functionality to track data changes.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// Listen to version changes
|
|
225
|
+
userManager.patchVersion$.subscribe((version) => {
|
|
226
|
+
console.log('Patch version:', version)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
userManager.mapVersion$.subscribe((version) => {
|
|
230
|
+
console.log('Map version:', version)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// Get current version
|
|
234
|
+
console.log('Current version:', userManager.version)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Operation Pipes
|
|
238
|
+
|
|
239
|
+
Operation pipes allow you to insert custom logic at different stages of data operations.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
interface DataOperationPipe<T extends IData = IData> {
|
|
243
|
+
transform?: (operations$: Subject<DataOperation<T>>) => OperatorFunction<DataOperation<T>, DataOperation<T>>
|
|
244
|
+
preOperation?: (operations$: Subject<DataOperation<T>>) => OperatorFunction<DataOperation<T>, DataOperation<T>>
|
|
245
|
+
postOperation?: (operations$: Subject<DataOperation<T>>) => OperatorFunction<
|
|
246
|
+
{ dataMap: Map<string, T>, operations: DataOperation<T>[] },
|
|
247
|
+
{ dataMap: Map<string, T>, operations: DataOperation<T>[] }
|
|
248
|
+
>
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Add operation pipe
|
|
252
|
+
const removePipe = userManager.addDataOperationPipe({
|
|
253
|
+
preOperation: () => tap((operation) => {
|
|
254
|
+
console.log('Before operation:', operation)
|
|
255
|
+
}),
|
|
256
|
+
postOperation: () => tap(({ dataMap, operations }) => {
|
|
257
|
+
console.log('After operation:', { dataMap, operations })
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Remove pipe
|
|
262
|
+
removePipe()
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### DualRelation
|
|
266
|
+
|
|
267
|
+
#### Type Definitions
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// DualRelation class type definition
|
|
271
|
+
interface DualRelation<P, C> {
|
|
272
|
+
// Dual relation methods and properties
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Generic Parameters:**
|
|
277
|
+
- `P`: Parent node type
|
|
278
|
+
- `C`: Child node type
|
|
279
|
+
|
|
280
|
+
#### Constructor
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
interface DualRelationOptions {
|
|
284
|
+
allowEmptyParent?: boolean // Whether to allow parent nodes without children
|
|
285
|
+
historyDepth?: number // Maximum number of change history records
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const relation = new DualRelation<string, string>({
|
|
289
|
+
allowEmptyParent: true,
|
|
290
|
+
historyDepth: 50
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### Core Methods
|
|
295
|
+
|
|
296
|
+
##### `add(parent: P, child: C): void`
|
|
297
|
+
|
|
298
|
+
Add parent-child relationship.
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// Create file system relationship
|
|
302
|
+
const fileSystem = new DualRelation<string, string>()
|
|
303
|
+
|
|
304
|
+
fileSystem.add('root', 'folder1')
|
|
305
|
+
fileSystem.add('root', 'folder2')
|
|
306
|
+
fileSystem.add('folder1', 'file1.txt')
|
|
307
|
+
fileSystem.add('folder1', 'file2.txt')
|
|
308
|
+
fileSystem.add('folder2', 'file3.txt')
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
##### `removeChild(child: C): boolean`
|
|
312
|
+
|
|
313
|
+
Remove child node and its parent relationship.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const removed = fileSystem.removeChild('file1.txt')
|
|
317
|
+
console.log('Successfully removed:', removed)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
##### `removeParent(parent: P): boolean`
|
|
321
|
+
|
|
322
|
+
Remove parent node and all its child relationships.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const removed = fileSystem.removeParent('folder1')
|
|
326
|
+
console.log('Successfully removed:', removed)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
##### `getChildren(parent: P): Set<C>`
|
|
330
|
+
|
|
331
|
+
Get all children of parent node.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const children = fileSystem.getChildren('root')
|
|
335
|
+
console.log('Root children:', Array.from(children))
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
##### `getParent(child: C): P | undefined`
|
|
339
|
+
|
|
340
|
+
Get parent of child node.
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
const parent = fileSystem.getParent('file1.txt')
|
|
344
|
+
console.log('Parent of file1.txt:', parent)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
##### `getRootParent(node: C): P | C | null`
|
|
348
|
+
|
|
349
|
+
Get root parent of node.
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const rootParent = fileSystem.getRootParent('file1.txt')
|
|
353
|
+
console.log('Root parent of file1.txt:', rootParent)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
##### `hasRelation(parent: P, child: C): boolean`
|
|
357
|
+
|
|
358
|
+
Check if parent-child relationship exists.
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
const exists = fileSystem.hasRelation('folder1', 'file1.txt')
|
|
362
|
+
console.log('Relationship exists:', exists)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
##### `hasCircularReference(): boolean`
|
|
366
|
+
|
|
367
|
+
Check if circular reference exists.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
const hasCircular = fileSystem.hasCircularReference()
|
|
371
|
+
console.log('Has circular reference:', hasCircular)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
##### `addSafe(parent: P, child: C): boolean`
|
|
375
|
+
|
|
376
|
+
Safely add relationship with circular reference check.
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
const added = fileSystem.addSafe('file1.txt', 'root') // Will fail due to circular reference
|
|
380
|
+
console.log('Successfully added:', added)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### Level Management
|
|
384
|
+
|
|
385
|
+
##### `getNodeLevel(node: P | C): number`
|
|
386
|
+
|
|
387
|
+
Get node level.
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
const level = fileSystem.getNodeLevel('file1.txt')
|
|
391
|
+
console.log('Level of file1.txt:', level)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
##### `getDescendants(parent: P, includeSelf?: boolean): Set<C>`
|
|
395
|
+
|
|
396
|
+
Get all descendants of parent.
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
const descendants = fileSystem.getDescendants('root', true)
|
|
400
|
+
console.log('All descendants of root:', Array.from(descendants))
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
#### Batch Operations
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
// Begin batch operation
|
|
407
|
+
fileSystem.beginBatch()
|
|
408
|
+
|
|
409
|
+
// Execute multiple operations
|
|
410
|
+
fileSystem.add('root', 'folder3')
|
|
411
|
+
fileSystem.add('folder3', 'file4.txt')
|
|
412
|
+
fileSystem.add('folder3', 'file5.txt')
|
|
413
|
+
|
|
414
|
+
// Commit batch operation
|
|
415
|
+
const hasChanges = fileSystem.commitBatch()
|
|
416
|
+
console.log('Has changes:', hasChanges)
|
|
417
|
+
|
|
418
|
+
// Or use convenience method
|
|
419
|
+
const hasChanges2 = fileSystem.batch(() => {
|
|
420
|
+
fileSystem.add('root', 'folder4')
|
|
421
|
+
fileSystem.add('folder4', 'file6.txt')
|
|
422
|
+
})
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### Change History
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// Listen to version changes
|
|
429
|
+
fileSystem.version.subscribe((version) => {
|
|
430
|
+
console.log('Version:', version)
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
// Get change history
|
|
434
|
+
const history = fileSystem.changeHistory
|
|
435
|
+
console.log('Change history:', history)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
#### Utility Methods
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
// Get all parents
|
|
442
|
+
const allParents = fileSystem.getAllParents()
|
|
443
|
+
|
|
444
|
+
// Get all children
|
|
445
|
+
const allChildren = fileSystem.getAllChildren()
|
|
446
|
+
|
|
447
|
+
// Get relationship count
|
|
448
|
+
const count = fileSystem.size()
|
|
449
|
+
|
|
450
|
+
// Clear all relationships
|
|
451
|
+
fileSystem.clear()
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## 📁 Directory Structure
|
|
455
|
+
|
|
456
|
+
```
|
|
457
|
+
packages/data/
|
|
458
|
+
├── src/
|
|
459
|
+
│ ├── index.ts # Main export file
|
|
460
|
+
│ ├── data-manager.ts # DataManager class and related interfaces
|
|
461
|
+
│ └── dual-relation.ts # DualRelation class and related interfaces
|
|
462
|
+
├── dist/ # Compiled output directory
|
|
463
|
+
├── package.json # Package configuration
|
|
464
|
+
├── README.md # Chinese documentation
|
|
465
|
+
├── README.en.md # English documentation
|
|
466
|
+
├── CHANGELOG.md # Change log
|
|
467
|
+
├── build.config.ts # Build configuration
|
|
468
|
+
├── eslint.config.mjs # ESLint configuration
|
|
469
|
+
└── tsconfig.json # TypeScript configuration
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Source Code Structure
|
|
473
|
+
|
|
474
|
+
- **`index.ts`**: Main export file, exports DataManager and DualRelation
|
|
475
|
+
- **`data-manager.ts`**: Complete implementation of DataManager class providing data management functionality
|
|
476
|
+
- **`dual-relation.ts`**: Complete implementation of DualRelation class providing bidirectional relationship management
|
|
477
|
+
|
|
478
|
+
## 🔗 Related Links
|
|
479
|
+
|
|
480
|
+
- [GitHub Repository](https://github.com/boenfu/knotx)
|
|
481
|
+
- [Issue Tracker](https://github.com/boenfu/knotx/issues)
|
|
482
|
+
- [Contributing Guide](https://github.com/boenfu/knotx/blob/main/CONTRIBUTING.md)
|
|
483
|
+
|
|
484
|
+
## 📄 License
|
|
485
|
+
|
|
486
|
+
MIT License - See [LICENSE](../../LICENSE) file for details.
|
|
487
|
+
|
|
488
|
+
## 🤝 Contributing
|
|
489
|
+
|
|
490
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for detailed information.
|
package/README.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# @knotx/data
|
|
2
|
+
|
|
3
|
+
一个基于 RxJS 的响应式数据管理库,提供强大的数据操作和双向关系管理功能。
|
|
4
|
+
|
|
5
|
+
## 📦 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @knotx/data
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
yarn add @knotx/data
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @knotx/data
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 🔧 基本概述
|
|
20
|
+
|
|
21
|
+
`@knotx/data` 是 Knotx 生态系统的核心数据管理包,提供两个主要的数据管理工具:
|
|
22
|
+
|
|
23
|
+
- **DataManager**: 用于管理数据的增删改查操作,支持草稿模式、批量操作、版本控制等高级功能
|
|
24
|
+
- **DualRelation**: 用于管理父子节点之间的双向关系,支持层级管理、循环引用检测等功能
|
|
25
|
+
|
|
26
|
+
## 🚀 快速开始
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { DataManager, DualRelation } from '@knotx/data'
|
|
30
|
+
|
|
31
|
+
// 创建数据管理器
|
|
32
|
+
const dataManager = new DataManager('my-data')
|
|
33
|
+
|
|
34
|
+
// 创建双向关系管理器
|
|
35
|
+
const relation = new DualRelation<string, string>()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 📚 API 文档
|
|
39
|
+
|
|
40
|
+
### DataManager
|
|
41
|
+
|
|
42
|
+
#### 类型定义
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
interface IData {
|
|
46
|
+
id: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// DataManager 类的类型定义
|
|
50
|
+
interface DataManager<TData extends IData = IData, TTag extends string = any> {
|
|
51
|
+
// 数据管理器的方法和属性
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### 构造函数
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const dataManager = new DataManager<MyData>('my-tag')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**参数:**
|
|
62
|
+
- `tag`: 数据管理器的标识符
|
|
63
|
+
|
|
64
|
+
#### 核心方法
|
|
65
|
+
|
|
66
|
+
##### `init(initialDataList?: TData[]): void`
|
|
67
|
+
|
|
68
|
+
初始化数据管理器,设置初始数据列表。
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface User {
|
|
72
|
+
id: string
|
|
73
|
+
name: string
|
|
74
|
+
age: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const userManager = new DataManager<User>('users')
|
|
78
|
+
userManager.init([
|
|
79
|
+
{ id: '1', name: 'Alice', age: 25 },
|
|
80
|
+
{ id: '2', name: 'Bob', age: 30 }
|
|
81
|
+
])
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
##### `getDataList$(): Observable<TData[]>`
|
|
85
|
+
|
|
86
|
+
获取数据列表的响应式流。
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
userManager.getDataList$().subscribe((users) => {
|
|
90
|
+
console.log('用户列表更新:', users)
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
##### `getDataList(): TData[]`
|
|
95
|
+
|
|
96
|
+
获取当前数据列表。
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const users = userManager.getDataList()
|
|
100
|
+
console.log('当前用户:', users)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
##### `getData$(id: string, equal?: (a?: TData, b?: TData) => boolean): Observable<TData | undefined>`
|
|
104
|
+
|
|
105
|
+
获取指定ID数据的响应式流。
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
userManager.getData$('1').subscribe((user) => {
|
|
109
|
+
console.log('用户1数据:', user)
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
##### `getData(id: string): TData | undefined`
|
|
114
|
+
|
|
115
|
+
获取指定ID的数据。
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const user = userManager.getData('1')
|
|
119
|
+
console.log('用户1:', user)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
##### `dispatch(operation: DataOperation<TData>): void`
|
|
123
|
+
|
|
124
|
+
分发数据操作。
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// 添加用户
|
|
128
|
+
userManager.dispatch({
|
|
129
|
+
type: 'add',
|
|
130
|
+
data: { id: '3', name: 'Charlie', age: 28 }
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// 更新用户
|
|
134
|
+
userManager.dispatch({
|
|
135
|
+
type: 'update',
|
|
136
|
+
id: '1',
|
|
137
|
+
data: { age: 26 }
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// 删除用户
|
|
141
|
+
userManager.dispatch({
|
|
142
|
+
type: 'remove',
|
|
143
|
+
id: '2'
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// 批量操作
|
|
147
|
+
userManager.dispatch({
|
|
148
|
+
type: 'batch',
|
|
149
|
+
operations: [
|
|
150
|
+
{ type: 'add', data: { id: '4', name: 'David', age: 35 } },
|
|
151
|
+
{ type: 'update', id: '3', data: { age: 29 } }
|
|
152
|
+
]
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### 草稿模式
|
|
157
|
+
|
|
158
|
+
草稿模式允许你在不影响主数据的情况下进行数据操作。
|
|
159
|
+
|
|
160
|
+
##### `dispatch` 草稿操作
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// 开始草稿
|
|
164
|
+
userManager.dispatch({
|
|
165
|
+
type: 'startDraft',
|
|
166
|
+
draftId: 'my-draft'
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// 在草稿中操作
|
|
170
|
+
userManager.dispatch({
|
|
171
|
+
type: 'draftOperation',
|
|
172
|
+
draftId: 'my-draft',
|
|
173
|
+
operation: {
|
|
174
|
+
type: 'add',
|
|
175
|
+
data: { id: '5', name: 'Eve', age: 24 }
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// 提交草稿
|
|
180
|
+
userManager.dispatch({
|
|
181
|
+
type: 'commitDraft',
|
|
182
|
+
draftId: 'my-draft'
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// 或者丢弃草稿
|
|
186
|
+
userManager.dispatch({
|
|
187
|
+
type: 'discardDraft',
|
|
188
|
+
draftId: 'my-draft'
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
##### `getDraftDataList(draftId: string): TData[] | undefined`
|
|
193
|
+
|
|
194
|
+
获取草稿中的数据列表。
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const draftUsers = userManager.getDraftDataList('my-draft')
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
##### `getDraftData(draftId: string, dataId: string): TData | undefined`
|
|
201
|
+
|
|
202
|
+
获取草稿中的特定数据。
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const draftUser = userManager.getDraftData('my-draft', '5')
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
##### `dryRun(operation: DataOperation<TData>): Map<string, TData>`
|
|
209
|
+
|
|
210
|
+
模拟执行操作,返回结果但不实际修改数据。
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
const result = userManager.dryRun({
|
|
214
|
+
type: 'commitDraft',
|
|
215
|
+
draftId: 'my-draft'
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### 版本控制
|
|
220
|
+
|
|
221
|
+
DataManager 提供版本控制功能,跟踪数据变更。
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// 监听版本变更
|
|
225
|
+
userManager.patchVersion$.subscribe((version) => {
|
|
226
|
+
console.log('补丁版本:', version)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
userManager.mapVersion$.subscribe((version) => {
|
|
230
|
+
console.log('映射版本:', version)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// 获取当前版本
|
|
234
|
+
console.log('当前版本:', userManager.version)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### 操作管道
|
|
238
|
+
|
|
239
|
+
操作管道允许你在数据操作的不同阶段插入自定义逻辑。
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
interface DataOperationPipe<T extends IData = IData> {
|
|
243
|
+
transform?: (operations$: Subject<DataOperation<T>>) => OperatorFunction<DataOperation<T>, DataOperation<T>>
|
|
244
|
+
preOperation?: (operations$: Subject<DataOperation<T>>) => OperatorFunction<DataOperation<T>, DataOperation<T>>
|
|
245
|
+
postOperation?: (operations$: Subject<DataOperation<T>>) => OperatorFunction<
|
|
246
|
+
{ dataMap: Map<string, T>, operations: DataOperation<T>[] },
|
|
247
|
+
{ dataMap: Map<string, T>, operations: DataOperation<T>[] }
|
|
248
|
+
>
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 添加操作管道
|
|
252
|
+
const removePipe = userManager.addDataOperationPipe({
|
|
253
|
+
preOperation: () => tap((operation) => {
|
|
254
|
+
console.log('执行操作前:', operation)
|
|
255
|
+
}),
|
|
256
|
+
postOperation: () => tap(({ dataMap, operations }) => {
|
|
257
|
+
console.log('执行操作后:', { dataMap, operations })
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// 移除管道
|
|
262
|
+
removePipe()
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### DualRelation
|
|
266
|
+
|
|
267
|
+
#### 类型定义
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// DualRelation 类的类型定义
|
|
271
|
+
interface DualRelation<P, C> {
|
|
272
|
+
// 双向关系的方法和属性
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**泛型参数:**
|
|
277
|
+
- `P`: 父节点类型
|
|
278
|
+
- `C`: 子节点类型
|
|
279
|
+
|
|
280
|
+
#### 构造函数
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
interface DualRelationOptions {
|
|
284
|
+
allowEmptyParent?: boolean // 是否允许父节点没有子节点
|
|
285
|
+
historyDepth?: number // 历史变更记录的最大数量
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const relation = new DualRelation<string, string>({
|
|
289
|
+
allowEmptyParent: true,
|
|
290
|
+
historyDepth: 50
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### 核心方法
|
|
295
|
+
|
|
296
|
+
##### `add(parent: P, child: C): void`
|
|
297
|
+
|
|
298
|
+
添加父子关系。
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// 创建文件系统关系
|
|
302
|
+
const fileSystem = new DualRelation<string, string>()
|
|
303
|
+
|
|
304
|
+
fileSystem.add('root', 'folder1')
|
|
305
|
+
fileSystem.add('root', 'folder2')
|
|
306
|
+
fileSystem.add('folder1', 'file1.txt')
|
|
307
|
+
fileSystem.add('folder1', 'file2.txt')
|
|
308
|
+
fileSystem.add('folder2', 'file3.txt')
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
##### `removeChild(child: C): boolean`
|
|
312
|
+
|
|
313
|
+
移除子节点及其父关系。
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const removed = fileSystem.removeChild('file1.txt')
|
|
317
|
+
console.log('是否成功移除:', removed)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
##### `removeParent(parent: P): boolean`
|
|
321
|
+
|
|
322
|
+
移除父节点及其所有子关系。
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const removed = fileSystem.removeParent('folder1')
|
|
326
|
+
console.log('是否成功移除:', removed)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
##### `getChildren(parent: P): Set<C>`
|
|
330
|
+
|
|
331
|
+
获取父节点的所有子节点。
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const children = fileSystem.getChildren('root')
|
|
335
|
+
console.log('root的子节点:', Array.from(children))
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
##### `getParent(child: C): P | undefined`
|
|
339
|
+
|
|
340
|
+
获取子节点的父节点。
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
const parent = fileSystem.getParent('file1.txt')
|
|
344
|
+
console.log('file1.txt的父节点:', parent)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
##### `getRootParent(node: C): P | C | null`
|
|
348
|
+
|
|
349
|
+
获取节点的根父节点。
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const rootParent = fileSystem.getRootParent('file1.txt')
|
|
353
|
+
console.log('file1.txt的根父节点:', rootParent)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
##### `hasRelation(parent: P, child: C): boolean`
|
|
357
|
+
|
|
358
|
+
检查是否存在父子关系。
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
const exists = fileSystem.hasRelation('folder1', 'file1.txt')
|
|
362
|
+
console.log('关系是否存在:', exists)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
##### `hasCircularReference(): boolean`
|
|
366
|
+
|
|
367
|
+
检查是否存在循环引用。
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
const hasCircular = fileSystem.hasCircularReference()
|
|
371
|
+
console.log('是否存在循环引用:', hasCircular)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
##### `addSafe(parent: P, child: C): boolean`
|
|
375
|
+
|
|
376
|
+
安全地添加关系,会检查循环引用。
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
const added = fileSystem.addSafe('file1.txt', 'root') // 会失败,因为会产生循环引用
|
|
380
|
+
console.log('是否成功添加:', added)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### 层级管理
|
|
384
|
+
|
|
385
|
+
##### `getNodeLevel(node: P | C): number`
|
|
386
|
+
|
|
387
|
+
获取节点的层级。
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
const level = fileSystem.getNodeLevel('file1.txt')
|
|
391
|
+
console.log('file1.txt的层级:', level)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
##### `getDescendants(parent: P, includeSelf?: boolean): Set<C>`
|
|
395
|
+
|
|
396
|
+
获取父节点的所有后代。
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
const descendants = fileSystem.getDescendants('root', true)
|
|
400
|
+
console.log('root的所有后代:', Array.from(descendants))
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
#### 批量操作
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
// 开始批量操作
|
|
407
|
+
fileSystem.beginBatch()
|
|
408
|
+
|
|
409
|
+
// 执行多个操作
|
|
410
|
+
fileSystem.add('root', 'folder3')
|
|
411
|
+
fileSystem.add('folder3', 'file4.txt')
|
|
412
|
+
fileSystem.add('folder3', 'file5.txt')
|
|
413
|
+
|
|
414
|
+
// 提交批量操作
|
|
415
|
+
const hasChanges = fileSystem.commitBatch()
|
|
416
|
+
console.log('是否有变更:', hasChanges)
|
|
417
|
+
|
|
418
|
+
// 或者使用便捷方法
|
|
419
|
+
const hasChanges2 = fileSystem.batch(() => {
|
|
420
|
+
fileSystem.add('root', 'folder4')
|
|
421
|
+
fileSystem.add('folder4', 'file6.txt')
|
|
422
|
+
})
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### 变更历史
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// 监听版本变更
|
|
429
|
+
fileSystem.version.subscribe((version) => {
|
|
430
|
+
console.log('版本:', version)
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
// 获取变更历史
|
|
434
|
+
const history = fileSystem.changeHistory
|
|
435
|
+
console.log('变更历史:', history)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
#### 工具方法
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
// 获取所有父节点
|
|
442
|
+
const allParents = fileSystem.getAllParents()
|
|
443
|
+
|
|
444
|
+
// 获取所有子节点
|
|
445
|
+
const allChildren = fileSystem.getAllChildren()
|
|
446
|
+
|
|
447
|
+
// 获取关系数量
|
|
448
|
+
const count = fileSystem.size()
|
|
449
|
+
|
|
450
|
+
// 清空所有关系
|
|
451
|
+
fileSystem.clear()
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## 📁 目录结构
|
|
455
|
+
|
|
456
|
+
```
|
|
457
|
+
packages/data/
|
|
458
|
+
├── src/
|
|
459
|
+
│ ├── index.ts # 主要导出文件
|
|
460
|
+
│ ├── data-manager.ts # DataManager 类和相关接口
|
|
461
|
+
│ └── dual-relation.ts # DualRelation 类和相关接口
|
|
462
|
+
├── dist/ # 编译输出目录
|
|
463
|
+
├── package.json # 包配置文件
|
|
464
|
+
├── README.md # 中文文档
|
|
465
|
+
├── README.en.md # 英文文档
|
|
466
|
+
├── CHANGELOG.md # 变更日志
|
|
467
|
+
├── build.config.ts # 构建配置
|
|
468
|
+
├── eslint.config.mjs # ESLint 配置
|
|
469
|
+
└── tsconfig.json # TypeScript 配置
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### 源代码结构
|
|
473
|
+
|
|
474
|
+
- **`index.ts`**: 主要的导出文件,导出 DataManager 和 DualRelation
|
|
475
|
+
- **`data-manager.ts`**: 包含 DataManager 类的完整实现,提供数据管理功能
|
|
476
|
+
- **`dual-relation.ts`**: 包含 DualRelation 类的完整实现,提供双向关系管理功能
|
|
477
|
+
|
|
478
|
+
## 🔗 相关链接
|
|
479
|
+
|
|
480
|
+
- [GitHub 仓库](https://github.com/boenfu/knotx)
|
|
481
|
+
- [问题反馈](https://github.com/boenfu/knotx/issues)
|
|
482
|
+
- [贡献指南](https://github.com/boenfu/knotx/blob/main/CONTRIBUTING.md)
|
|
483
|
+
|
|
484
|
+
## 📄 许可证
|
|
485
|
+
|
|
486
|
+
MIT License - 详见 [LICENSE](../../LICENSE) 文件。
|
|
487
|
+
|
|
488
|
+
## 🤝 贡献
|
|
489
|
+
|
|
490
|
+
欢迎贡献代码!请查看 [CONTRIBUTING.md](../../CONTRIBUTING.md) 了解详细信息。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knotx/data",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
4
4
|
"description": "Data for Knotx",
|
|
5
5
|
"author": "boenfu",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/lodash-es": "^4.17.12",
|
|
36
|
-
"@knotx/build-config": "0.4.
|
|
37
|
-
"@knotx/eslint-config": "0.4.
|
|
38
|
-
"@knotx/typescript-config": "0.4.
|
|
36
|
+
"@knotx/build-config": "0.4.13",
|
|
37
|
+
"@knotx/eslint-config": "0.4.13",
|
|
38
|
+
"@knotx/typescript-config": "0.4.13"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "unbuild",
|