serializable-bptree 1.1.0 → 3.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 CHANGED
@@ -7,9 +7,13 @@ This is a B+tree that's totally okay with duplicate values. If you need to keep
7
7
 
8
8
  ```typescript
9
9
  import { readFileSync, writeFileSync, existsSync } from 'fs'
10
- import { BPTree, SerializeStrategy, NumericComparator } from 'serializable-bptree'
10
+ import {
11
+ BPTreeSync,
12
+ SerializeStrategySync,
13
+ NumericComparator
14
+ } from 'serializable-bptree'
11
15
 
12
- class FileStoreStrategy extends SerializeStrategy<K, V> {
16
+ class FileStoreStrategySync extends SerializeStrategySync<K, V> {
13
17
  id(): number {
14
18
  const random = Math.ceil(Math.random()*1000000)
15
19
  return random
@@ -40,11 +44,12 @@ class FileStoreStrategy extends SerializeStrategy<K, V> {
40
44
  }
41
45
 
42
46
  const order = 5
43
- const tree = new BPTree(
44
- new FileStoreStrategy(order),
47
+ const tree = new BPTreeSync(
48
+ new FileStoreStrategySync(order),
45
49
  new NumericComparator()
46
50
  )
47
51
 
52
+ tree.init()
48
53
  tree.insert('a', 1)
49
54
  tree.insert('b', 2)
50
55
  tree.insert('c', 3)
@@ -59,7 +64,9 @@ tree.where({ gt: 0, lt: 4 }) // [{ key: 'a', value: 1 }, { key: 'c', value: 3 }]
59
64
 
60
65
  ## Why use a `serializable-bptree`?
61
66
 
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.
67
+ 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.
68
+
69
+ Additionally, this library supports asynchronous operations. Please refer to the section below for instructions on using it asynchronously.
63
70
 
64
71
  ## How to use
65
72
 
@@ -70,14 +77,29 @@ npm i serializable-bptree
70
77
  ```
71
78
 
72
79
  ```typescript
73
- import { BPTree } from 'serializable-bptree'
80
+ import {
81
+ BPTreeSync,
82
+ BPTreeAsync,
83
+ SerializeStrategySync,
84
+ SerializeStrategyAsync,
85
+ NumericComparator,
86
+ StringComparator
87
+ } from 'serializable-bptree'
74
88
  ```
75
89
 
76
90
  ### Browser (esm)
77
91
 
78
92
  ```html
79
93
  <script type="module">
80
- import { BPTree } from 'https://cdn.jsdelivr.net/npm/serializable-bptree@1.x.x/dist/esm/index.min.js'
94
+ import {
95
+ BPTreeSync,
96
+ BPTreeAsync,
97
+ InMemoryStoreStrategySync,
98
+ InMemoryStoreStrategyAsync,
99
+ ValueComparator,
100
+ NumericComparator,
101
+ StringComparator
102
+ } from 'https://cdn.jsdelivr.net/npm/serializable-bptree@3.x.x/dist/esm/index.min.js'
81
103
  </script>
82
104
  ```
83
105
 
@@ -85,15 +107,15 @@ import { BPTree } from 'serializable-bptree'
85
107
 
86
108
  ### Value comparator
87
109
 
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`.
110
+ 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
111
 
90
- Commonly used numerical and string comparisons are natively supported by the `serializable-bptree` library. Use it as follows:
112
+ Commonly used numerical and string comparisons are natively supported by the **serializable-bptree** library. Use it as follows:
91
113
 
92
114
  ```typescript
93
115
  import { NumericComparator, StringComparator } from 'serializable-bptree'
94
116
  ```
95
117
 
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:
118
+ 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
119
 
98
120
  ```typescript
99
121
  import { ValueComparator } from 'serializable-bptree'
@@ -103,13 +125,15 @@ interface MyObject {
103
125
  name: string
104
126
  }
105
127
 
106
- class AgeComparator {
128
+ class AgeComparator extends ValueComparator<MyObject> {
107
129
  asc(a: MyObject, b: MyObject): number {
108
130
  return a.age - b.age
109
131
  }
110
132
  }
111
133
  ```
112
134
 
135
+ The **asc** method should return values in ascending order. If the return value is negative, it means that the parameter **a** is smaller than **b**. If the return value is positive, it means that **a** is greater than **b**. If the return value is **0**, it indicates that **a** and **b** are of the same size.
136
+
113
137
  ### Serialize strategy
114
138
 
115
139
  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.
@@ -117,9 +141,9 @@ A B+tree instance is made up of numerous nodes. You would want to store this val
117
141
  You need to construct a logic for input/output from the file by inheriting the SerializeStrategy class. Look at the class structure below:
118
142
 
119
143
  ```typescript
120
- import { SerializeStrategy } from 'serializable-bptree'
144
+ import { SerializeStrategySync } from 'serializable-bptree'
121
145
 
122
- class MyFileIOStrategy extends SerializeStrategy {
146
+ class MyFileIOStrategySync extends SerializeStrategySync {
123
147
  id(): number
124
148
  read(id: number): BPTreeNode<K, V>
125
149
  write(id: number, node: BPTreeNode<K, V>): void
@@ -132,7 +156,7 @@ What does this method mean? And why do we need to construct such a method?
132
156
 
133
157
  #### id(): `number`
134
158
 
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.
159
+ 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
160
 
137
161
  ```typescript
138
162
  id(): number {
@@ -142,7 +166,9 @@ id(): number {
142
166
  }
143
167
  ```
144
168
 
145
- Or, you could use file input/output to save and load the value of the `before` variable.
169
+ Or, you could use file input/output to save and load the value of the **before** variable.
170
+
171
+ This method is called before a node is created in the tree. Therefore, it can also be used to allocate space for storing the node.
146
172
 
147
173
  #### read(id: `number`): `BPTreeNode<K, V>`
148
174
 
@@ -158,7 +184,7 @@ read(id: number): BPTreeNode<K, V> {
158
184
  }
159
185
  ```
160
186
 
161
- This method is called only once when loading a node from a tree instance.
187
+ This method is called only once when loading a node from a tree instance. The loaded node is loaded into memory, and subsequently, when the tree references the node, it operates based on the values in memory **without** re-invoking this method.
162
188
 
163
189
  #### write(id: `number`, node: `BPTreeNode<K, V>`): `void`
164
190
 
@@ -181,7 +207,7 @@ function writeBack(id: number, node: BPTreeNode<K, V>, timer: number) {
181
207
 
182
208
  ...
183
209
  write(id: number, node: BPTreeNode<K, V>): void {
184
- const writeBackInterval = 100
210
+ const writeBackInterval = 10
185
211
  writeBack(id, node, writeBackInterval)
186
212
  }
187
213
  ```
@@ -190,32 +216,32 @@ This kind of delay writing should ideally occur within a few milliseconds. If th
190
216
 
191
217
  #### readHead(): `SerializeStrategyHead`|`null`
192
218
 
193
- This method is called only once when the tree is created. It's a method to restore the saved tree information. If it is the initial creation and there is no stored root node, it should return `null`.
219
+ This method is called only once when the tree is created. It's a method to restore the saved tree information. If it is the initial creation and there is no stored root node, it should return **null**.
194
220
 
195
- This method should return the value stored in the `writeHead` method.
221
+ This method should return the value stored in the **writeHead** method.
196
222
 
197
223
  #### writeHead(head: `SerializeStrategyHead`): `void`
198
224
 
199
- This method is called whenever the head information of the tree changes, typically when the root node changes.
225
+ This method is called whenever the head information of the tree changes, typically when the root node changes. This method also works when the tree's **setHeadData** method is called. This is because the method attempts to store head data in the root node.
200
226
 
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.
227
+ 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
228
 
203
229
  ### The Default `ValueComparator` and `SerializeStrategy`
204
230
 
205
- To utilize `serializable-bptree`, you need to implement certain functions. However, a few basic helper classes are provided by default.
231
+ To utilize **serializable-bptree**, you need to implement certain functions. However, a few basic helper classes are provided by default.
206
232
 
207
233
  #### ValueComparator
208
234
 
209
235
  * `NumericComparator`
210
236
  * `StringComparator`
211
237
 
212
- If the values being inserted into the tree are numeric, please use the `NumericComparator` class.
238
+ If the values being inserted into the tree are numeric, please use the **NumericComparator** class.
213
239
 
214
240
  ```typescript
215
241
  import { NumericComparator } from 'serializable-bptree'
216
242
  ```
217
243
 
218
- If the values being inserted into the tree can be strings, you can use the `StringComparator` class in this case.
244
+ If the values being inserted into the tree can be strings, you can use the **StringComparator** class in this case.
219
245
 
220
246
  ```typescript
221
247
  import { StringComparator } from 'serializable-bptree'
@@ -223,14 +249,134 @@ import { StringComparator } from 'serializable-bptree'
223
249
 
224
250
  #### SerializeStrategy
225
251
 
226
- * `InMemoryStoreStrategy`
252
+ * `InMemoryStoreStrategySync`
253
+ * `InMemoryStoreStrategyAsync`
254
+
255
+ 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.
256
+
257
+ ```typescript
258
+ import {
259
+ InMemoryStoreStrategySync,
260
+ InMemoryStoreStrategyAsync
261
+ } from 'serializable-bptree'
262
+ ```
263
+
264
+ ## Data Query Condition Clause
265
+
266
+ This library supports various conditional clauses. Currently, it supports **gte**, **gt**, **lte**, **lt**, **equal**, **notEqual**, and **like** conditions. Each condition is as follows:
267
+
268
+ ### `gte`
269
+
270
+ Queries values that are greater than or equal to the given value.
271
+
272
+ ### `gt`
273
+
274
+ Queries values that are greater than the given value.
275
+
276
+ ### `lte`
277
+
278
+ Queries values that are less than or equal to the given value.
227
279
 
228
- 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.
280
+ ### `lt`
281
+
282
+ Queries values that are less than the given value.
283
+
284
+ ### `equal`
285
+
286
+ Queries values that match the given value.
287
+
288
+ ### `notEqual`
289
+
290
+ Queries values that do not match the given value.
291
+
292
+ ### `like`
293
+
294
+ Queries values that contain the given value in a manner similar to regular expressions. Special characters such as % and _ can be used.
295
+
296
+ **%** matches zero or more characters. For example, **%ada%** means all strings that contain "ada" anywhere in the string. **%ada** means strings that end with "ada". **ada%** means strings that start with **"ada"**.
297
+
298
+ **_** matches exactly one character.
299
+ Using **p_t**, it can match any string where the underscore is replaced by any character, such as "pit", "put", etc.
300
+
301
+ You can obtain matching data by combining these condition clauses. If there are multiple conditions, an **AND** operation is used to retrieve only the data that satisfies all conditions.
302
+
303
+ ## Using Asynchronously
304
+
305
+ Support for asynchronous trees has been available since version 3.0.0. Asynchronous is useful for operations with delays, such as file input/output and remote storage. Here is an example of how to use it:
229
306
 
230
307
  ```typescript
231
- import { InMemoryStoreStrategy } from 'serializable-bptree'
308
+ import { existsSync } from 'fs'
309
+ import { readFile, writeFile } from 'fs/promises'
310
+ import {
311
+ BPTreeAsync,
312
+ SerializeStrategyAsync,
313
+ NumericComparator,
314
+ StringComparator
315
+ } from 'serializable-bptree'
316
+
317
+ class FileStoreStrategyAsync extends SerializeStrategyAsync<K, V> {
318
+ async id(): Promise<number> {
319
+ const random = Math.ceil(Math.random()*1000000)
320
+ return random
321
+ }
322
+
323
+ async read(id: number): Promise<BPTreeNode<K, V>> {
324
+ const raw = await readFile(id.toString(), 'utf8')
325
+ return JSON.parse(raw)
326
+ }
327
+
328
+ async write(id: number, node: BPTreeNode<K, V>): Promise<void> {
329
+ const stringify = JSON.stringify(node)
330
+ await writeFile(id.toString(), stringify, 'utf8')
331
+ }
332
+
333
+ async readHead(): Promise<SerializeStrategyHead|null> {
334
+ if (!existsSync('head')) {
335
+ return null
336
+ }
337
+ const raw = await readFile('head', 'utf8')
338
+ return JSON.parse(raw)
339
+ }
340
+
341
+ async writeHead(head: SerializeStrategyHead): Promise<void> {
342
+ const stringify = JSON.stringify(head)
343
+ await writeFile('head', stringify, 'utf8')
344
+ }
345
+ }
346
+
347
+ const order = 5
348
+ const tree = new BPTreeAsync(
349
+ new FileStoreStrategyAsync(order),
350
+ new NumericComparator()
351
+ )
352
+
353
+ await tree.init()
354
+ await tree.insert('a', 1)
355
+ await tree.insert('b', 2)
356
+ await tree.insert('c', 3)
357
+
358
+ await tree.delete('b', 2)
359
+
360
+ await tree.where({ equal: 1 }) // [{ key: 'a', value: 1 }]
361
+ await tree.where({ gt: 1 }) // [{ key: 'c', value: 3 }]
362
+ await tree.where({ lt: 2 }) // [{ key: 'a', value: 1 }]
363
+ await tree.where({ gt: 0, lt: 4 }) // [{ key: 'a', value: 1 }, { key: 'c', value: 3 }]
232
364
  ```
233
365
 
366
+ The implementation method for asynchronous operations is not significantly different. The **-Async** suffix is used instead of the **-Sync** suffix in the **BPTree** and **SerializeStrategy** classes. The only difference is that the methods become asynchronous. The **ValueComparator** class and similar value comparators do not use asynchronous operations.
367
+
368
+ ## Precautions for Use
369
+
370
+ ### Synchronization Issue
371
+
372
+ The serializable-bptree minimizes file I/O by storing loaded nodes in-memory. This approach works well in situations where there is a 1:1 relationship between the remote storage and the client. However, in a 1:n scenario, where multiple clients read from and write to a single remote storage, data inconsistency between the remote storage and the clients can occur.
373
+
374
+ To solve this issue, it's necessary to update the cached nodes. The forceUpdate method was created for this purpose. It allows for the update of specific nodes, but when updating a node ID, a signal must be sent to all clients connected to the remote storage, informing them that the node has been updated. Clients must receive this signal and call the forceUpdate method to update the node. This logic goes beyond the scope of the library, so it must be implemented directly.
375
+
376
+ ### Concurrency Issue in Asynchronous Trees
377
+
378
+ This issue occurs only in asynchronous trees and can also occur in a 1:1 relationship between remote storage and client. During the process of inserting/removing data asynchronously, querying the data can result in inconsistent data. To prevent concurrency issues, do not query data while inserting/removing it.
379
+
234
380
  ## LICENSE
235
381
 
236
382
  MIT LICENSE