@ouim/vectoriadb-server 0.1.3 → 0.1.5

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
@@ -113,13 +113,69 @@ const bobDocs = await db.query('my-collection', 'farewell', {
113
113
 
114
114
  ### Server Options
115
115
 
116
- | Option | Description | Default |
117
- | :---------------- | :------------------------------------- | :--------- |
118
- | `port` | Server listening port | `3001` |
119
- | `host` | Server host address | `0.0.0.0` |
120
- | `apiKey` | Optional key for client authentication | `null` |
121
- | `cors` | Allowed origins (array) | `[]` (All) |
122
- | `streamChunkSize` | Max results per chunk for streaming | `500` |
116
+ | Option | Description | Default |
117
+ | :------------------------ | :------------------------------------------------------------------------------------- | :--------- |
118
+ | `port` | Server listening port | `3001` |
119
+ | `host` | Server host address | `0.0.0.0` |
120
+ | `apiKey` | Optional key for client authentication | `null` |
121
+ | `cors` | Allowed origins (array) | `[]` (All) |
122
+ | `streamChunkSize` | Max results per chunk for streaming | `500` |
123
+ | `autoSaveOnMutationBurst` | Enable automatic saveToStorage after a burst of mutation calls + idle | `true` |
124
+ | `autoSaveOnInactivity` | Save to storage after `mutationInactivityMs` of no mutations (suitable for small apps) | `true` |
125
+ | `mutationBurstThreshold` | Number of mutation calls within `mutationBurstWindowMs` to consider a burst | `5` |
126
+ | `mutationBurstWindowMs` | Time window (ms) used to count burst mutations | `120000` |
127
+ | `mutationInactivityMs` | Inactivity window (ms) after last mutation that triggers save | `30000` |
128
+ | `minSaveIntervalMs` | Minimum time (ms) between automatic saves to avoid thrashing | `10000` |
129
+
130
+ Auto-save on mutation bursts
131
+
132
+ A configurable mechanism that detects "mutation bursts" and automatically calls `saveToStorage()` after activity stops. This minimizes write amplification during heavy write periods while ensuring state is persisted shortly after the burst finishes.
133
+
134
+ How it works:
135
+
136
+ - A "burst" is detected when at least `mutationBurstThreshold` mutation operations occur within the `mutationBurstWindowMs` time window.
137
+ - When a burst has been detected, the server starts (or resets) an inactivity timer of `mutationInactivityMs` after the last mutation.
138
+ - If the inactivity timer expires and the recent mutation count still meets the threshold, the server calls `saveToStorage()` once.
139
+ - `minSaveIntervalMs` prevents repeated auto-saves from occurring too frequently; overlapping saves are also avoided by an internal in-progress flag.
140
+ - After the inactivity handler runs the mutation history is cleared until new activity occurs.
141
+
142
+ Behavior timeline (concrete example):
143
+
144
+ - Settings: `mutationBurstThreshold = 100`, `mutationBurstWindowMs = 120000` (2 min), `mutationInactivityMs = 30000` (30s).
145
+ - If 120 mutations arrive between t=0 and t=90s (burst detected), and no further mutations occur after t=90s, the server waits 30s (inactivity window) and calls `saveToStorage()` at ~t=120s (provided `minSaveIntervalMs` allows it).
146
+
147
+ Configuration example:
148
+
149
+ ```javascript
150
+ const server = new VectoriaDBServer({
151
+ port: 3001,
152
+ autoSaveOnMutationBurst: true,
153
+ mutationBurstThreshold: 100, // # mutations within the window to mark a burst
154
+ mutationBurstWindowMs: 2 * 60 * 1000, // window length used to count mutations
155
+ mutationInactivityMs: 30 * 1000, // wait this long after last mutation before saving
156
+ minSaveIntervalMs: 10 * 1000, // never auto-save more often than this
157
+ })
158
+ ```
159
+
160
+ Log & testing notes:
161
+
162
+ - When the auto-save runs the server logs: `[VectoriaDBServer] auto-saved storage (<reason>)` (e.g. `mutation-burst-inactivity` or `inactivity`).
163
+ - To test: simulate mutation calls and assert `saveToStorage()` is invoked after `mutationInactivityMs`; verify `minSaveIntervalMs` prevents frequent saves.
164
+
165
+ Inactivity-only mode (simpler)
166
+
167
+ If your application is small or you prefer a simpler policy, enable `autoSaveOnInactivity: true`. With this enabled the server will call `saveToStorage()` after `mutationInactivityMs` of no mutation activity — regardless of how many mutations occurred before the idle period. The inactivity timer resets on each mutation.
168
+
169
+ Example:
170
+
171
+ ```javascript
172
+ const server = new VectoriaDBServer({
173
+ autoSaveOnInactivity: true,
174
+ mutationInactivityMs: 30 * 1000, // save 30s after last mutation
175
+ })
176
+ ```
177
+
178
+ ### Client Options
123
179
 
124
180
  ### Client Options
125
181
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouim/vectoriadb-server",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -14,6 +14,35 @@ export default class VectoriaDBServer {
14
14
  this.vectoriadbConfig = opts.vectoriadbConfig || {}
15
15
  this.streamChunkSize = opts.streamChunkSize || 500
16
16
 
17
+ // --- auto-save / burst-detection (configurable) ---
18
+ this.autoSaveOnMutationBurst = opts.autoSaveOnMutationBurst !== undefined ? !!opts.autoSaveOnMutationBurst : true
19
+ // when true the server will save after any inactivity period (see `mutationInactivityMs`) — useful for small apps
20
+ this.autoSaveOnInactivity = opts.autoSaveOnInactivity !== undefined ? !!opts.autoSaveOnInactivity : false
21
+ this.mutationBurstThreshold = Number(opts.mutationBurstThreshold) || 5
22
+ this.mutationBurstWindowMs = Number(opts.mutationBurstWindowMs) || 2 * 60 * 1000 // 2 minutes
23
+ this.mutationInactivityMs = Number(opts.mutationInactivityMs) || 30 * 1000 // 30s inactivity to trigger save
24
+ this.minSaveIntervalMs = Number(opts.minSaveIntervalMs) || 10 * 1000 // minimum time between auto-saves
25
+
26
+ // internal mutation-tracking state
27
+ this._mutationMethods = new Set([
28
+ 'add',
29
+ 'addMany',
30
+ 'update',
31
+ 'remove',
32
+ 'removeMany',
33
+ 'clear',
34
+ 'insert',
35
+ 'upsert',
36
+ 'replace',
37
+ 'put',
38
+ 'delete',
39
+ ])
40
+ this._mutationTimestamps = []
41
+ this._inactivityTimer = null
42
+ this._lastBurstAt = 0
43
+ this._savingInProgress = false
44
+ this._lastSaveAt = 0
45
+
17
46
  this._http = null
18
47
  this._io = null
19
48
  this._vectoria = null
@@ -148,6 +177,15 @@ export default class VectoriaDBServer {
148
177
  new Promise((_, reject) => setTimeout(() => reject(new Error('ServerTimeout')), 30000)),
149
178
  ])
150
179
 
180
+ // record mutation activity (used to auto-flush after a burst + inactivity)
181
+ if (this.autoSaveOnMutationBurst && this._isMutationMethod(method)) {
182
+ try {
183
+ this._recordMutation()
184
+ } catch (e) {
185
+ /* swallow tracking errors */
186
+ }
187
+ }
188
+
151
189
  const took = Date.now() - start
152
190
 
153
191
  // streaming support for very large arrays
@@ -169,8 +207,86 @@ export default class VectoriaDBServer {
169
207
  }
170
208
  }
171
209
 
210
+ // --- Auto-save on mutation bursts (configurable) ---
211
+ _isMutationMethod(method) {
212
+ return this._mutationMethods.has(method)
213
+ }
214
+
215
+ _recordMutation() {
216
+ const now = Date.now()
217
+ this._mutationTimestamps.push(now)
218
+ // purge old timestamps outside the burst window
219
+ const cutoff = now - this.mutationBurstWindowMs
220
+ while (this._mutationTimestamps.length && this._mutationTimestamps[0] < cutoff) {
221
+ this._mutationTimestamps.shift()
222
+ }
223
+ if (this._mutationTimestamps.length >= this.mutationBurstThreshold) {
224
+ this._lastBurstAt = now
225
+ }
226
+ this._scheduleInactivityTimer()
227
+ }
228
+
229
+ _scheduleInactivityTimer() {
230
+ if (this._inactivityTimer) {
231
+ clearTimeout(this._inactivityTimer)
232
+ }
233
+ this._inactivityTimer = setTimeout(() => {
234
+ // fire-and-forget async
235
+ this._onInactivityTimeout().catch(err => console.warn('Inactivity flush error:', err.message))
236
+ }, this.mutationInactivityMs)
237
+ if (this._inactivityTimer.unref) this._inactivityTimer.unref()
238
+ }
239
+
240
+ async _onInactivityTimeout() {
241
+ this._inactivityTimer = null
242
+ const now = Date.now()
243
+
244
+ // Simple "inactivity-only" mode: save after any inactivity period if enabled.
245
+ if (this.autoSaveOnInactivity) {
246
+ if (this._mutationTimestamps.length > 0) {
247
+ await this._saveToStorage('inactivity')
248
+ }
249
+ // reset history and return early
250
+ this._mutationTimestamps = []
251
+ this._lastBurstAt = 0
252
+ return
253
+ }
254
+
255
+ // count mutations within the burst window (existing burst-detection behavior)
256
+ const cutoff = now - this.mutationBurstWindowMs
257
+ const recentCount = this._mutationTimestamps.filter(ts => ts >= cutoff).length
258
+ if (recentCount >= this.mutationBurstThreshold) {
259
+ await this._saveToStorage('mutation-burst-inactivity')
260
+ }
261
+ // reset history (we only track bursts between inactivity windows)
262
+ this._mutationTimestamps = []
263
+ this._lastBurstAt = 0
264
+ }
265
+
266
+ async _saveToStorage(reason = 'manual') {
267
+ if (!this._vectoria || typeof this._vectoria.saveToStorage !== 'function') return
268
+ const now = Date.now()
269
+ if (this._savingInProgress) return
270
+ if (now - this._lastSaveAt < this.minSaveIntervalMs) return
271
+ this._savingInProgress = true
272
+ try {
273
+ await this._vectoria.saveToStorage()
274
+ this._lastSaveAt = Date.now()
275
+ console.log(`[VectoriaDBServer] auto-saved storage (${reason})`)
276
+ } catch (err) {
277
+ console.warn('[VectoriaDBServer] auto-save failed:', err.message)
278
+ } finally {
279
+ this._savingInProgress = false
280
+ }
281
+ }
282
+
172
283
  async close() {
173
284
  if (!this._started) return
285
+ // stop any pending auto-save timer
286
+ if (this._inactivityTimer) {
287
+ clearTimeout(this._inactivityTimer)
288
+ this._inactivityTimer = null
289
+ }
174
290
  // disconnect sockets
175
291
  for (const s of Array.from(this._sockets)) {
176
292
  try {