@lexho111/plainblog 0.8.6 → 0.9.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.
@@ -26,9 +26,11 @@ export var TraversalOrder;
26
26
  })(TraversalOrder || (TraversalOrder = {}));
27
27
  export class BinarySearchTree {
28
28
  root;
29
+ dateRoot;
29
30
  searchIndex;
30
31
  constructor() {
31
32
  this.root = null;
33
+ this.dateRoot = null;
32
34
  this.searchIndex = new InvertedIndexSearch();
33
35
  }
34
36
  // O(n) - Linear search (Base class has no HashMap)
@@ -37,34 +39,63 @@ export class BinarySearchTree {
37
39
  }
38
40
  // Insert a new article
39
41
  insert(article) {
42
+ const time_start = performance.now();
40
43
  this.searchIndex.add(article);
41
- // Note: The tree is now sorted by date. ID-based methods will not work correctly.
42
- // if (!article.id) article.id = this.getNextID();
43
- // Add to the Set
44
- const newNode = new BlogNode(article);
44
+ // 1. Insert into ID Tree
45
+ const idNode = new BlogNode(article);
45
46
  if (!this.root) {
46
- this.root = newNode;
47
- return;
47
+ this.root = idNode;
48
48
  }
49
- let current = this.root;
50
- const newTime = newNode.article.createdAt;
51
- while (true) {
52
- // Using getTime() for numeric comparison. Duplicates go to the right.
53
- if (newTime < current.article.createdAt) {
54
- if (!current.left) {
55
- current.left = newNode;
49
+ else {
50
+ let current = this.root;
51
+ while (true) {
52
+ if (article.id < current.article.id) {
53
+ if (!current.left) {
54
+ current.left = idNode;
55
+ break;
56
+ }
57
+ current = current.left;
58
+ }
59
+ else if (article.id > current.article.id) {
60
+ if (!current.right) {
61
+ current.right = idNode;
62
+ break;
63
+ }
64
+ current = current.right;
65
+ }
66
+ else {
67
+ current.article = article; // Update existing
56
68
  break;
57
69
  }
58
- current = current.left;
59
70
  }
60
- else {
61
- if (!current.right) {
62
- current.right = newNode;
63
- break;
71
+ }
72
+ // 2. Insert into Date Tree
73
+ const dateNode = new BlogNode(article);
74
+ if (!this.dateRoot) {
75
+ this.dateRoot = dateNode;
76
+ }
77
+ else {
78
+ let current = this.dateRoot;
79
+ const newTime = new Date(article.createdAt).getTime();
80
+ while (true) {
81
+ // Duplicates go to the right
82
+ if (newTime < new Date(current.article.createdAt).getTime()) {
83
+ if (!current.left) {
84
+ current.left = dateNode;
85
+ break;
86
+ }
87
+ current = current.left;
88
+ }
89
+ else {
90
+ if (!current.right) {
91
+ current.right = dateNode;
92
+ break;
93
+ }
94
+ current = current.right;
64
95
  }
65
- current = current.right;
66
96
  }
67
97
  }
98
+ debug_perf("BST.insert", performance.now() - time_start);
68
99
  }
69
100
  update(id, title, content) {
70
101
  const article = this.find(id);
@@ -90,29 +121,27 @@ export class BinarySearchTree {
90
121
  }
91
122
  // Search for a specific article by ID
92
123
  find(id) {
93
- // Tree is sorted by Date, so we must traverse all nodes to find by ID (O(n))
94
- return this._findNodeDFS(this.root, id);
95
- }
96
- _findNodeDFS(node, id) {
97
- const stack = [];
98
- if (node)
99
- stack.push(node);
100
- while (stack.length > 0) {
101
- const current = stack.pop();
102
- if (current.article.id === id)
124
+ const time_start = performance.now();
125
+ // Use ID Tree for O(log n) lookup
126
+ let current = this.root;
127
+ while (current) {
128
+ if (id === current.article.id) {
129
+ debug_perf("BST.find", performance.now() - time_start);
103
130
  return current.article;
104
- if (current.right)
105
- stack.push(current.right);
106
- if (current.left)
107
- stack.push(current.left);
131
+ }
132
+ if (id < current.article.id)
133
+ current = current.left;
134
+ else
135
+ current = current.right;
108
136
  }
137
+ debug_perf("BST.find", performance.now() - time_start);
109
138
  return null;
110
139
  }
111
140
  // Get all articles in order (Sorted by ID)
112
141
  getAllArticles(order = TraversalOrder.NewestFirst) {
113
142
  const articles = [];
114
- if (this.root) {
115
- this._inOrder(this.root, articles, order);
143
+ if (this.dateRoot) {
144
+ this._inOrder(this.dateRoot, articles, order);
116
145
  }
117
146
  return articles;
118
147
  }
@@ -144,7 +173,7 @@ export class BinarySearchTree {
144
173
  }
145
174
  *traverse(order = TraversalOrder.NewestFirst) {
146
175
  const stack = [];
147
- let current = this.root;
176
+ let current = this.dateRoot;
148
177
  let visitedCount = 0;
149
178
  if (order === TraversalOrder.NewestFirst) {
150
179
  while (current || stack.length > 0) {
@@ -173,6 +202,7 @@ export class BinarySearchTree {
173
202
  debug("Traversed %d nodes of %d", visitedCount, this.size());
174
203
  }
175
204
  getRange(startdate, enddate, limit = null) {
205
+ const time_start = performance.now();
176
206
  debug("Using BST.getRange for start: '%s', end: '%s', limit: %s", startdate, enddate, limit);
177
207
  if (typeof startdate === "number") {
178
208
  debug("startdate is number");
@@ -193,7 +223,8 @@ export class BinarySearchTree {
193
223
  const end = toTimestamp(enddate, Infinity);
194
224
  debug("startdate: %d", start);
195
225
  debug("enddate: %d", end);
196
- this._rangeSearch(this.root, start, end, limit, results);
226
+ this._rangeSearch(this.dateRoot, start, end, limit, results);
227
+ debug_perf("BST.getRange", performance.now() - time_start);
197
228
  return results;
198
229
  }
199
230
  _rangeSearch(node, start, end, limit, results) {
@@ -206,7 +237,7 @@ export class BinarySearchTree {
206
237
  // Go right as much as possible, but prune if > end
207
238
  while (current) {
208
239
  visitedCount++;
209
- if (current.article.createdAt > end) {
240
+ if (new Date(current.article.createdAt).getTime() > end) {
210
241
  current = current.left;
211
242
  }
212
243
  else {
@@ -218,7 +249,7 @@ export class BinarySearchTree {
218
249
  current = stack.pop();
219
250
  if (limit !== null && results.length >= limit)
220
251
  break;
221
- if (current.article.createdAt >= start) {
252
+ if (new Date(current.article.createdAt).getTime() >= start) {
222
253
  results.push(current.article);
223
254
  current = current.left;
224
255
  }
@@ -246,103 +277,87 @@ export class BinarySearchTree {
246
277
  return count;
247
278
  }
248
279
  remove(data) {
249
- const id = data instanceof Article ? data.id : data;
250
- const date = data instanceof Article ? data.createdAt : null;
251
- const { root, removed } = this._removeIterative(this.root, id, date);
252
- this.root = root;
253
- if (removed) {
254
- this.searchIndex.remove(removed.id);
280
+ const time_start = performance.now();
281
+ let articleToRemove = null;
282
+ if (data instanceof Article) {
283
+ articleToRemove = data;
284
+ }
285
+ else {
286
+ articleToRemove = this.find(data);
255
287
  }
256
- return removed;
288
+ if (articleToRemove) {
289
+ // Remove from ID tree
290
+ this.root = this._removeNodeID(this.root, articleToRemove.id);
291
+ // Remove from Date tree
292
+ this.dateRoot = this._removeNodeDate(this.dateRoot, articleToRemove);
293
+ this.searchIndex.remove(articleToRemove.id);
294
+ }
295
+ debug_perf("BST.remove", performance.now() - time_start);
296
+ return articleToRemove;
257
297
  }
258
- _removeIterative(root, id, date) {
259
- let parent = null;
260
- let current = root;
261
- let isLeftChild = false;
262
- // 1. Find the node
263
- if (date !== null) {
264
- // Optimized search using BST property (Date)
265
- while (current) {
266
- if (current.article.id == id)
267
- break;
268
- parent = current;
269
- if (date < current.article.createdAt) {
270
- current = current.left;
271
- isLeftChild = true;
272
- }
273
- else {
274
- current = current.right;
275
- isLeftChild = false;
276
- }
277
- }
298
+ _removeNodeID(root, id) {
299
+ if (!root)
300
+ return null;
301
+ if (id < root.article.id) {
302
+ root.left = this._removeNodeID(root.left, id);
303
+ return root;
278
304
  }
279
- else {
280
- const stack = root ? [{ node: root, parent: null, isLeft: false }] : [];
281
- current = null;
282
- while (stack.length > 0) {
283
- const item = stack.pop();
284
- if (item.node.article.id === id) {
285
- current = item.node;
286
- parent = item.parent;
287
- isLeftChild = item.isLeft;
288
- break;
289
- }
290
- if (item.node.right)
291
- stack.push({
292
- node: item.node.right,
293
- parent: item.node,
294
- isLeft: false,
295
- });
296
- if (item.node.left)
297
- stack.push({ node: item.node.left, parent: item.node, isLeft: true });
298
- }
305
+ else if (id > root.article.id) {
306
+ root.right = this._removeNodeID(root.right, id);
307
+ return root;
299
308
  }
300
- if (!current)
301
- return { root, removed: null }; // Not found
302
- const removed = current.article;
303
- // 2. Delete node
304
- if (!current.left && !current.right) {
305
- if (!parent)
306
- return { root: null, removed }; // Root was deleted
307
- if (isLeftChild)
308
- parent.left = null;
309
- else
310
- parent.right = null;
309
+ else {
310
+ // Found
311
+ if (!root.left)
312
+ return root.right;
313
+ if (!root.right)
314
+ return root.left;
315
+ let minRight = root.right;
316
+ while (minRight.left)
317
+ minRight = minRight.left;
318
+ root.article = minRight.article;
319
+ root.right = this._removeNodeID(root.right, minRight.article.id);
320
+ return root;
311
321
  }
312
- else if (!current.right) {
313
- if (!parent)
314
- return { root: current.left, removed };
315
- if (isLeftChild)
316
- parent.left = current.left;
317
- else
318
- parent.right = current.left;
322
+ }
323
+ _removeNodeDate(root, article) {
324
+ if (!root)
325
+ return null;
326
+ const time = new Date(article.createdAt).getTime();
327
+ const rootTime = new Date(root.article.createdAt).getTime();
328
+ if (time < rootTime) {
329
+ root.left = this._removeNodeDate(root.left, article);
330
+ return root;
319
331
  }
320
- else if (!current.left) {
321
- if (!parent)
322
- return { root: current.right, removed };
323
- if (isLeftChild)
324
- parent.left = current.right;
325
- else
326
- parent.right = current.right;
332
+ else if (time > rootTime) {
333
+ root.right = this._removeNodeDate(root.right, article);
334
+ return root;
327
335
  }
328
336
  else {
329
- // Two children: Find successor (min in right subtree)
330
- let successorParent = current;
331
- let successor = current.right;
332
- while (successor.left) {
333
- successorParent = successor;
334
- successor = successor.left;
337
+ // Dates equal. Check ID.
338
+ if (article.id === root.article.id) {
339
+ // Found exact match
340
+ if (!root.left)
341
+ return root.right;
342
+ if (!root.right)
343
+ return root.left;
344
+ let minRight = root.right;
345
+ while (minRight.left)
346
+ minRight = minRight.left;
347
+ root.article = minRight.article;
348
+ root.right = this._removeNodeDate(root.right, minRight.article);
349
+ return root;
350
+ }
351
+ else {
352
+ // Dates equal but ID different. Duplicates are on right.
353
+ root.right = this._removeNodeDate(root.right, article);
354
+ return root;
335
355
  }
336
- current.article = successor.article;
337
- if (successorParent === current)
338
- successorParent.right = successor.right;
339
- else
340
- successorParent.left = successor.right;
341
356
  }
342
- return { root, removed };
343
357
  }
344
358
  clear() {
345
359
  this.root = null;
360
+ this.dateRoot = null;
346
361
  this.searchIndex = new InvertedIndexSearch();
347
362
  }
348
363
  size() {
package/index.js CHANGED
@@ -2,7 +2,6 @@ import { Blog, BlogBuilder } from "./Blog.js";
2
2
  import Article from "./Article.js";
3
3
  import FileAdapter from "./model/FileAdapter.js";
4
4
  import PostgresAdapter from "./model/PostgresAdapter.js";
5
- import SequelizeAdapter from "./model/SequelizeAdapter.js";
6
5
  import SqliteAdapter from "./model/SqliteAdapter.js";
7
6
  import { startCluster } from "./cluster-server.js";
8
7
  import { articleFactory, articleGenerator } from "./utilities.js";
@@ -12,7 +11,6 @@ export {
12
11
  BlogBuilder,
13
12
  Article,
14
13
  PostgresAdapter,
15
- SequelizeAdapter,
16
14
  FileAdapter,
17
15
  SqliteAdapter,
18
16
  startCluster,