serializable-bptree 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,237 @@
1
+ # serializable-bptree
2
+
3
+ [![](https://data.jsdelivr.com/v1/package/npm/serializable-bptree/badge)](https://www.jsdelivr.com/package/npm/serialiable-bptree)
4
+ ![Node.js workflow](https://github.com/izure1/serializable-bptree/actions/workflows/node.js.yml/badge.svg)
5
+
6
+ This is a B+tree that's totally okay with duplicate values. If you need to keep track of the B+ tree's state, don't just leave it in memory - make sure you write it down.
7
+
8
+ ```typescript
9
+ import { readFileSync, writeFileSync, existsSync } from 'fs'
10
+ import { BPTree, SerializeStrategy, NumericComparator } from 'serializable-bptree'
11
+
12
+ class FileStoreStrategy extends SerializeStrategy<K, V> {
13
+ id(): number {
14
+ const random = Math.ceil(Math.random()*1000000)
15
+ return random
16
+ }
17
+
18
+ read(id: number): BPTreeNode<K, V> {
19
+ const raw = readFileSync(id.toString(), 'utf8')
20
+ return JSON.parse(raw)
21
+ }
22
+
23
+ write(id: number, node: BPTreeNode<K, V>): void {
24
+ const stringify = JSON.stringify(node)
25
+ writeFileSync(id.toString(), stringify, 'utf8')
26
+ }
27
+
28
+ readHead(): SerializeStrategyHead|null {
29
+ if (!existsSync('head')) {
30
+ return null
31
+ }
32
+ const raw = readFileSync('head', 'utf8')
33
+ return JSON.parse(raw)
34
+ }
35
+
36
+ writeHead(head: SerializeStrategyHead): void {
37
+ const stringify = JSON.stringify(head)
38
+ writeFileSync('head', stringify, 'utf8')
39
+ }
40
+ }
41
+
42
+ const order = 5
43
+ const tree = new BPTree(
44
+ new SerializeStrategy(order),
45
+ new NumericComparator()
46
+ )
47
+
48
+ tree.insert('a', 1)
49
+ tree.insert('b', 2)
50
+ tree.insert('c', 3)
51
+
52
+ tree.delete('b', 2)
53
+
54
+ tree.where({ equal: 1 }) // [{ key: 'a', value: 1 }]
55
+ tree.where({ gt: 1 }) // [{ key: 'c', value: 3 }]
56
+ tree.where({ lt: 2 }) // [{ key: 'a', value: 1 }]
57
+ tree.where({ gt: 0, lt: 4 }) // [{ key: 'a', value: 1 }, { key: 'c', value: 3 }]
58
+ ```
59
+
60
+ ## Why use a `serializable-bptree`?
61
+
62
+ Firstly, in most cases, there is no need to use a B+tree in JavaScript. This is because there is a great alternative, the Map object. Nonetheless, if you need to retrieve values in a sorted order, a B+tree can be a good solution. These cases are often related to databases, and you may want to store this state not just in memory, but on a remote server or in a file. In this case, `serializable-bptree` can help you.
63
+
64
+ ## How to use
65
+
66
+ ### Node.js (cjs)
67
+
68
+ ```bash
69
+ npm i serializable-bptree
70
+ ```
71
+
72
+ ```typescript
73
+ import { BPTree } from 'serializable-bptree'
74
+ ```
75
+
76
+ ### Browser (esm)
77
+
78
+ ```html
79
+ <script type="module">
80
+ import { BPTree } from 'https://cdn.jsdelivr.net/npm/serializable-bptree@1.x.x/dist/esm/index.min.js'
81
+ </script>
82
+ ```
83
+
84
+ ## conceptualization
85
+
86
+ ### Value comparator
87
+
88
+ B+tree needs to keep values in sorted order. Therefore, a process to compare the sizes of values is needed, and that role is played by the `ValueComparator`.
89
+
90
+ Commonly used numerical and string comparisons are natively supported by the `serializable-bptree` library. Use it as follows:
91
+
92
+ ```typescript
93
+ import { NumericComparator, StringComparator } from 'serializable-bptree'
94
+ ```
95
+
96
+ However, you may want to sort complex objects other than numbers and strings. For example, if you want to sort by the `age` property order of an object, you need to create a new class that inherits from the `ValueComparator` class. Use it as follows:
97
+
98
+ ```typescript
99
+ import { ValueComparator } from 'serializable-bptree'
100
+
101
+ interface MyObject {
102
+ age: number
103
+ name: string
104
+ }
105
+
106
+ class AgeComparator {
107
+ asc(a: MyObject, b: MyObject): number {
108
+ return a.age - b.age
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### Serialize strategy
114
+
115
+ A B+tree instance is made up of numerous nodes. You would want to store this value when such nodes are created or updated. Let's assume you want to save it to a file.
116
+
117
+ You need to construct a logic for input/output from the file by inheriting the SerializeStrategy class. Look at the class structure below:
118
+
119
+ ```typescript
120
+ import { SerializeStrategy } from 'serializable-bptree'
121
+
122
+ class MyFileIOStrategy extends SerializeStrategy {
123
+ id(): number
124
+ read(id: number): BPTreeNode<K, V>
125
+ write(id: number, node: BPTreeNode<K, V>): void
126
+ readHead(): SerializeStrategyHead|null
127
+ writeHead(head: SerializeStrategyHead): void
128
+ }
129
+ ```
130
+
131
+ What does this method mean? And why do we need to construct such a method?
132
+
133
+ #### id(): `number`
134
+
135
+ When a node is created in the B+tree, the node needs a unique value to represent itself. This is the `node.id` attribute, and you can specify this attribute yourself. For example, it could be implemented like this.
136
+
137
+ ```typescript
138
+ id(): number {
139
+ const current = before + 1
140
+ before = current
141
+ return current
142
+ }
143
+ ```
144
+
145
+ Or, you could use file input/output to save and load the value of the `before` variable.
146
+
147
+ #### read(id: `number`): `BPTreeNode<K, V>`
148
+
149
+ This is a method to load the saved value as a tree instance. If you have previously saved the node as a file, you should use this method to convert it back to JavaScript JSON format and return it.
150
+
151
+ Please refer to the example below:
152
+
153
+ ```typescript
154
+ read(id: number): BPTreeNode<K, V> {
155
+ const filePath = `./my-store/${id.toString()}`
156
+ const raw = fs.readFileSync(filePath, 'utf8')
157
+ return JSON.parse(raw)
158
+ }
159
+ ```
160
+
161
+ This method is called only once when loading a node from a tree instance.
162
+
163
+ #### write(id: `number`, node: `BPTreeNode<K, V>`): `void`
164
+
165
+ This method is called when there are changes in the internal nodes due to the insert or delete operations of the tree instance. In other words, it's a necessary method for synchronizing the in-memory nodes into a file.
166
+
167
+ Since this method is called frequently, be mindful of performance. There are ways to optimize it using a write-back caching technique.
168
+
169
+ Please refer to the example below:
170
+
171
+ ```typescript
172
+ let queue = 0
173
+ function writeBack(id: number, node: BPTreeNode<K, V>, timer: number) {
174
+ clearTimeout(queue)
175
+ queue = setTimeout(() => {
176
+ const filePath = `./my-store/${id.toString()}`
177
+ const stringify = JSON.stringify(node)
178
+ writeFileSync(filePath, stringify, 'utf8')
179
+ }, timer)
180
+ }
181
+
182
+ ...
183
+ write(id: number, node: BPTreeNode<K, V>): void {
184
+ const writeBackInterval = 100
185
+ writeBack(id, node, writeBackInterval)
186
+ }
187
+ ```
188
+
189
+ This kind of delay writing should ideally occur within a few milliseconds. If this is not feasible, consider other approaches.
190
+
191
+ #### readHead(): `SerializeStrategyHead`|`null`
192
+
193
+ This method is called only once when the tree is created. It's a method to restore the saved tree information. If it's the first tree creation and there are no saved tree information, return `null`.
194
+
195
+ This method should return the value stored in the `writeHead` method.
196
+
197
+ #### writeHead(head: `SerializeStrategyHead`): `void`
198
+
199
+ This method is called whenever the head information of the tree changes, typically when the root node changes.
200
+
201
+ As a parameter, it receives the header information of the tree. This value should be serialized and stored. Later, the `readHead` method should convert this serialized value into a json format and return it.
202
+
203
+
204
+ ### The Default `ValueComparator` and `SerializeStrategy`
205
+
206
+ To utilize `serializable-bptree`, you need to implement certain functions. However, a few basic helper classes are provided by default.
207
+
208
+ #### ValueComparator
209
+
210
+ * `NumericComparator`
211
+ * `StringComparator`
212
+
213
+ If the values being inserted into the tree are numeric, please use the `NumericComparator` class.
214
+
215
+ ```typescript
216
+ import { NumericComparator } from 'serializable-bptree'
217
+ ```
218
+
219
+ If the values being inserted into the tree can be strings, you can use the `StringComparator` class in this case.
220
+
221
+ ```typescript
222
+ import { StringComparator } from 'serializable-bptree'
223
+ ```
224
+
225
+ #### SerializeStrategy
226
+
227
+ * `InMemoryStoreStrategy`
228
+
229
+ As of now, the only class supported by default is the `InMemoryStoreStrategy`. This class is suitable for use when you prefer to operate the tree solely in-memory, similar to a typical B+ tree.
230
+
231
+ ```typescript
232
+ import { InMemoryStoreStrategy } from 'serializable-bptree'
233
+ ```
234
+
235
+ ## LICENSE
236
+
237
+ MIT LICENSE