@based/db 0.0.31 → 0.0.33

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.
Files changed (83) hide show
  1. package/README.md +565 -3
  2. package/dist/lib/darwin_aarch64/include/selva/db.h +1 -1
  3. package/dist/lib/darwin_aarch64/include/selva/fields.h +0 -2
  4. package/dist/lib/darwin_aarch64/libdeflate.dylib +0 -0
  5. package/dist/lib/darwin_aarch64/libjemalloc_selva.2.dylib +0 -0
  6. package/dist/lib/darwin_aarch64/libnode-v20.node +0 -0
  7. package/dist/lib/darwin_aarch64/libnode-v21.node +0 -0
  8. package/dist/lib/darwin_aarch64/libnode-v22.node +0 -0
  9. package/dist/lib/darwin_aarch64/libnode-v23.node +0 -0
  10. package/dist/lib/darwin_aarch64/libselva.dylib +0 -0
  11. package/dist/lib/linux_aarch64/include/selva/db.h +1 -1
  12. package/dist/lib/linux_aarch64/include/selva/fields.h +0 -2
  13. package/dist/lib/linux_aarch64/libnode-v20.node +0 -0
  14. package/dist/lib/linux_aarch64/libnode-v21.node +0 -0
  15. package/dist/lib/linux_aarch64/libnode-v22.node +0 -0
  16. package/dist/lib/linux_aarch64/libnode-v23.node +0 -0
  17. package/dist/lib/linux_aarch64/libselva.so +0 -0
  18. package/dist/lib/linux_x86_64/include/selva/db.h +1 -1
  19. package/dist/lib/linux_x86_64/include/selva/fields.h +0 -2
  20. package/dist/lib/linux_x86_64/libnode-v20.node +0 -0
  21. package/dist/lib/linux_x86_64/libnode-v21.node +0 -0
  22. package/dist/lib/linux_x86_64/libnode-v22.node +0 -0
  23. package/dist/lib/linux_x86_64/libnode-v23.node +0 -0
  24. package/dist/lib/linux_x86_64/libselva.so +0 -0
  25. package/dist/src/client/flushModify.js +5 -4
  26. package/dist/src/client/index.d.ts +3 -2
  27. package/dist/src/client/modify/binary.js +1 -1
  28. package/dist/src/client/modify/cardinality.js +1 -1
  29. package/dist/src/client/modify/references/appendEdgeRefs.js +3 -0
  30. package/dist/src/client/modify/references/edge.js +6 -0
  31. package/dist/src/client/modify/references/getEdgeSize.js +1 -1
  32. package/dist/src/client/modify/string.js +10 -4
  33. package/dist/src/client/modify/text.js +1 -9
  34. package/dist/src/client/modify/types.d.ts +1 -0
  35. package/dist/src/client/modify/types.js +1 -0
  36. package/dist/src/client/modify/upsert.js +33 -21
  37. package/dist/src/client/query/BasedDbQuery.js +1 -1
  38. package/dist/src/client/query/BasedIterable.d.ts +2 -2
  39. package/dist/src/client/query/BasedIterable.js +7 -1
  40. package/dist/src/client/query/debug.js +3 -2
  41. package/dist/src/client/query/display.js +1 -1
  42. package/dist/src/client/query/filter/createFixedFilterBuffer.js +1 -1
  43. package/dist/src/client/query/filter/createVariableFilterBuffer.js +1 -1
  44. package/dist/src/client/query/filter/parseFilterValue.js +1 -2
  45. package/dist/src/client/query/queryDef.js +2 -2
  46. package/dist/src/client/query/read/read.js +4 -14
  47. package/dist/src/client/query/registerQuery.js +1 -1
  48. package/dist/src/client/query/search/index.js +1 -1
  49. package/dist/src/client/query/toBuffer.js +11 -15
  50. package/dist/src/client/query/types.d.ts +7 -0
  51. package/dist/src/client/query/types.js +8 -0
  52. package/dist/src/client/query/validation.d.ts +1 -1
  53. package/dist/src/client/query/validation.js +4 -5
  54. package/dist/src/client/string.js +1 -3
  55. package/dist/src/index.d.ts +1 -0
  56. package/dist/src/index.js +5 -1
  57. package/dist/src/native.d.ts +1 -2
  58. package/dist/src/native.js +2 -5
  59. package/dist/src/server/csmt/draw-dot.d.ts +3 -1
  60. package/dist/src/server/csmt/draw-dot.js +7 -2
  61. package/dist/src/server/csmt/memebership-proof.d.ts +1 -1
  62. package/dist/src/server/csmt/tree-utils.d.ts +4 -4
  63. package/dist/src/server/csmt/tree.d.ts +1 -1
  64. package/dist/src/server/csmt/tree.js +1 -1
  65. package/dist/src/server/csmt/types.d.ts +10 -10
  66. package/dist/src/server/dbHash.js +1 -1
  67. package/dist/src/server/index.d.ts +6 -2
  68. package/dist/src/server/index.js +28 -13
  69. package/dist/src/server/migrate/worker.js +11 -0
  70. package/dist/src/server/save.js +3 -2
  71. package/dist/src/server/start.js +9 -5
  72. package/dist/src/utils.d.ts +0 -10
  73. package/dist/src/utils.js +0 -152
  74. package/package.json +3 -3
  75. package/dist/lib/darwin_aarch64/include/selva/xxhash64.h +0 -23
  76. package/dist/lib/linux_aarch64/include/selva/xxhash64.h +0 -23
  77. package/dist/lib/linux_x86_64/include/selva/xxhash64.h +0 -23
  78. package/dist/src/client/query/subscription/markers.d.ts +0 -10
  79. package/dist/src/client/query/subscription/markers.js +0 -213
  80. package/dist/src/client/query/subscription/run.d.ts +0 -5
  81. package/dist/src/client/query/subscription/run.js +0 -76
  82. package/dist/src/client/tmpBuffer.d.ts +0 -3
  83. package/dist/src/client/tmpBuffer.js +0 -20
package/README.md CHANGED
@@ -23,11 +23,11 @@ BasedDb is a powerful database solution that supports various data types, refere
23
23
 
24
24
  ```bash
25
25
  npm i
26
- npm run get-napi // only need this the first time
26
+ npm run get-napi # only need this the first time
27
27
  npm run build
28
28
  ```
29
29
 
30
- ## Running tests
30
+ ## Testing
31
31
 
32
32
  Run all tests + ldb + build c, zig and js
33
33
 
@@ -47,7 +47,7 @@ Run specific test file & run specific test
47
47
  npm run test -- range.js:range
48
48
  ```
49
49
 
50
- Different flavours of test
50
+ Different flavours of test:
51
51
 
52
52
  Only builds zig
53
53
 
@@ -60,3 +60,565 @@ Builds nothing only runs tests
60
60
  ```bash
61
61
  npm run test-fast
62
62
  ```
63
+
64
+ ## API Documentation
65
+
66
+ This documentation is generated based on the features demonstrated in the \`./test\` directory.
67
+
68
+ ### Setup
69
+
70
+ ```typescript
71
+ import { BasedDb } from './src/index.js' // Adjust import path as needed
72
+
73
+ // Instantiate the database
74
+ const db = new BasedDb({
75
+ path: './db-directory', // Path to store database files
76
+ saveIntervalInSeconds: 1, // Optional: Auto-save interval (e.g., every second)
77
+ })
78
+
79
+ // Start the database (connects/loads existing or creates new)
80
+ // clean: true wipes data on start if path exists
81
+ await db.start({ clean: true })
82
+
83
+ // Define the database schema (required before most operations)
84
+ await db.setSchema({
85
+ // Optional: Define locales for 'text' fields
86
+ locales: {
87
+ en: { required: true }, // 'en' is required
88
+ nl: { fallback: ['en'] }, // 'nl' falls back to 'en' if missing
89
+ fi: { fallback: ['en'] },
90
+ },
91
+ // Optional: Define properties directly on the root node
92
+ props: {
93
+ siteName: 'string',
94
+ featuredItems: { items: { ref: 'product' } },
95
+ },
96
+ // Define data types
97
+ types: {
98
+ user: {
99
+ props: {
100
+ name: 'string', // Simple string
101
+ email: { type: 'alias', required: true }, // Unique alias (indexed string)
102
+ age: 'uint32', // Unsigned 32-bit integer
103
+ score: { type: 'number', min: 0, max: 100, step: 0.5 }, // Float with validation
104
+ isActive: 'boolean', // Boolean true/false
105
+ createdAt: { type: 'timestamp', on: 'create' }, // Auto-set on creation
106
+ updatedAt: { type: 'timestamp', on: 'update' }, // Auto-set on update/create
107
+ bio: 'text', // Multi-language text
108
+ friends: {
109
+ // List of references
110
+ items: {
111
+ ref: 'user', // References 'user' type
112
+ prop: 'friends', // Bidirectional link property
113
+ $friendshipLevel: 'uint8', // Optional: Edge property
114
+ },
115
+ },
116
+ bestFriend: {
117
+ // Single reference
118
+ ref: 'user',
119
+ prop: 'bestFriendOf', // Bidirectional link
120
+ },
121
+ profilePicture: 'binary', // Raw binary data (Uint8Array)
122
+ settings: 'json', // JSON object/array
123
+ visits: 'cardinality', // HyperLogLog counter for unique values
124
+ embedding: { type: 'vector', size: 5 }, // Fixed-size float vector (size from test)
125
+ status: ['pending', 'active', 'inactive'], // Enum type
126
+ countryCode: { type: 'string', maxBytes: 2 }, // String with max byte length
127
+ nestedData: {
128
+ // Nested object
129
+ type: 'object',
130
+ props: {
131
+ value: 'string',
132
+ nestedRef: { ref: 'product', prop: 'nestedUsers' },
133
+ },
134
+ },
135
+ customValidated: {
136
+ // Custom validation function
137
+ type: 'number',
138
+ validation: (v) => v % 2 === 0, // Must be an even number
139
+ },
140
+ },
141
+ },
142
+ product: {
143
+ props: {
144
+ title: 'text',
145
+ price: 'number',
146
+ nestedUsers: { items: { ref: 'user', prop: 'nestedData.nestedRef' } },
147
+ },
148
+ },
149
+ // Other types from tests (e.g., payment, round, vote, contestant, dialog, article, etc.)
150
+ // ...
151
+ },
152
+ })
153
+ ```
154
+
155
+ ### Create
156
+
157
+ ```typescript
158
+ // Create a new node
159
+ const userId = await db.create('user', {
160
+ name: 'Alice',
161
+ email: 'alice@example.com',
162
+ })
163
+ // userId is the numeric ID of the created node
164
+
165
+ // Create with references and edge properties
166
+ const friendId = await db.create('user', {
167
+ name: 'Bob',
168
+ email: 'bob@example.com',
169
+ })
170
+ const userWithFriendId = await db.create('user', {
171
+ name: 'Charlie',
172
+ email: 'charlie@example.com',
173
+ friends: [{ id: friendId, $friendshipLevel: 5 }], // Reference Bob with edge data
174
+ bestFriend: friendId, // Single reference
175
+ })
176
+
177
+ // Create text with specific locale
178
+ const dialogFi = await db.create('dialog', { fun: 'hauskaa' }, { locale: 'fi' })
179
+ ```
180
+
181
+ ### Read (Query)
182
+
183
+ The \`query\` method starts building a request to retrieve data.
184
+
185
+ ```typescript
186
+ // Query by type and optionally ID(s) or alias
187
+ db.query('user') // Query all users (limited by default)
188
+ db.query('user', userId) // Query a single user by ID
189
+ db.query('user', [userId, friendId]) // Query multiple users by IDs
190
+ db.query('user', { alias: 'alice@example.com' }) // Query by unique alias
191
+ db.query() // Query root properties
192
+ ```
193
+
194
+ #### \`.include(...fields)\`
195
+
196
+ Specifies which fields to return. Supports nested fields and edge properties.
197
+
198
+ ```typescript
199
+ // Include specific fields
200
+ await db.query('user', userId).include('name', 'age').get()
201
+ // -> { id: userId, name: 'Alice', age: 30 }
202
+
203
+ // Include all direct fields (non-nested, non-reference)
204
+ await db.query('user', userId).include('*').get()
205
+ // -> { id: userId, name: 'Alice', email: '...', age: 30, ... } (no friends, bestFriend data)
206
+
207
+ // Include nested fields and reference fields
208
+ await db
209
+ .query('user', userWithFriendId)
210
+ .include('name', 'friends.name', 'bestFriend.email')
211
+ .get()
212
+ // -> { id: ..., name: 'Charlie', friends: [{ id: ..., name: 'Bob' }], bestFriend: { id: ..., email: '...' } }
213
+
214
+ // Include specific language from a 'text' field
215
+ await db.query('user', userId).include('bio.en').get()
216
+ // -> { id: userId, bio: { en: 'Engineer' } }
217
+
218
+ // Include edge properties from a reference list
219
+ await db
220
+ .query('user', userWithFriendId)
221
+ .include('friends.$friendshipLevel')
222
+ .get()
223
+ // -> { id: ..., friends: [{ id: ..., $friendshipLevel: 5 }] }
224
+ ```
225
+
226
+ #### \`.filter(field, operator, value, options?)\`
227
+
228
+ Filters the results based on field values.
229
+
230
+ Operators:
231
+
232
+ - \`=\`: Equal to (works for most types, including exact string match).
233
+ - \`!=\`: Not equal to.
234
+ - \`>\`: Greater than (numbers, timestamps).
235
+ - \`<\`: Less than (numbers, timestamps).
236
+ - \`>=\`: Greater than or equal to.
237
+ - \`<=\`: Less than or equal to.
238
+ - \`has\`: Contains substring (case-insensitive by default for \`string\`, \`text\`).
239
+ - \`like\`: Fuzzy search / similarity (for \`string\`, \`text\`, \`vector\`).
240
+
241
+ **Filter Examples:**
242
+
243
+ - **Equality (=)**
244
+ Finds nodes where the field exactly matches the value.
245
+
246
+ ```typescript
247
+ // Find users named exactly 'Alice'
248
+ await db.query('user').filter('name', '=', 'Alice').get()
249
+
250
+ // Find users with age 30
251
+ await db.query('user').filter('age', '=', 30).get()
252
+
253
+ // Find users with a specific country code
254
+ await db.query('user').filter('countryCode', '=', 'NL').get()
255
+
256
+ // Find users with a specific vector (exact match)
257
+ const queryVector = new Float32Array([
258
+ /* ... */
259
+ ])
260
+ await db.query('user').filter('embedding', '=', queryVector).get()
261
+ ```
262
+
263
+ - **Inequality (!=)**
264
+ Finds nodes where the field does _not_ match the value.
265
+
266
+ ```typescript
267
+ // Find users not named 'Alice'
268
+ await db.query('user').filter('name', '!=', 'Alice').get()
269
+
270
+ // Find users whose status is not 'pending'
271
+ await db.query('user').filter('status', '!=', 'pending').get()
272
+ ```
273
+
274
+ - **Comparison (>, \`<, >=, <=)**
275
+ Finds nodes based on numerical or timestamp comparisons.
276
+
277
+ ```typescript
278
+ // Find users older than 50
279
+ await db.query('user').filter('age', '>', 50).get()
280
+
281
+ // Find users with a score less than or equal to 75.5
282
+ await db.query('user').filter('score', '<=', 75.5).get()
283
+
284
+ // Find users created within the last 24 hours
285
+ await db
286
+ .query('user')
287
+ .filter('createdAt', '>=', Date.now() - 86400000)
288
+ .get()
289
+ ```
290
+
291
+ - **String/Text Contains (has)**
292
+ Finds nodes where a \`string\` or \`text\` field includes a substring. Case-insensitive by default.
293
+
294
+ ```typescript
295
+ // Find users whose name contains 'ali' (matches 'Alice', 'Ali', 'Salim', etc.)
296
+ await db.query('user').filter('name', 'has', 'ali').get()
297
+
298
+ // Find users whose name contains 'ALI' (case-sensitive)
299
+ await db
300
+ .query('user')
301
+ .filter('name', 'has', 'ALI', { lowerCase: false })
302
+ .get()
303
+
304
+ // Find users whose bio (any language) contains 'engineer'
305
+ await db.query('user').filter('bio', 'has', 'engineer').get()
306
+
307
+ // Find users whose English bio contains 'dev'
308
+ await db.query('user').filter('bio.en', 'has', 'dev').get()
309
+ ```
310
+
311
+ - **Fuzzy Match / Similarity (like)**
312
+ Finds nodes based on approximate matching for \`string\`, \`text\`, or \`vector\` types.
313
+
314
+ ```typescript
315
+ // Find users whose bio might contain a typo like 'engneer'
316
+ await db.query('user').filter('bio', 'like', 'engneer').get()
317
+
318
+ // Find users whose embedding vector is similar to queryVector (cosine similarity >= 0.8)
319
+ const queryVector = new Float32Array([
320
+ /* ... */
321
+ ])
322
+ await db
323
+ .query('user')
324
+ .filter('embedding', 'like', queryVector, { score: 0.8 })
325
+ .get()
326
+
327
+ // Find users whose embedding vector is similar (Euclidean distance <= 1.0)
328
+ await db
329
+ .query('user')
330
+ .filter('embedding', 'like', queryVector, {
331
+ fn: 'euclideanDistance',
332
+ score: 1.0,
333
+ })
334
+ .get()
335
+ ```
336
+
337
+ - **Boolean Filtering**
338
+ Finds nodes based on a boolean field's value.
339
+
340
+ ```typescript
341
+ // Find active users (explicitly true)
342
+ await db.query('user').filter('isActive', '=', true).get()
343
+
344
+ // Find active users (shortcut for true)
345
+ await db.query('user').filter('isActive').get()
346
+
347
+ // Find inactive users
348
+ await db.query('user').filter('isActive', false).get()
349
+ ```
350
+
351
+ - **Enum Filtering**
352
+ Finds nodes where an \`enum\` field matches a specific value.
353
+
354
+ ```typescript
355
+ // Find users with status 'active'
356
+ await db.query('user').filter('status', '=', 'active').get()
357
+
358
+ // Find users whose status is not 'pending'
359
+ await db.query('user').filter('status', '!=', 'pending').get()
360
+ ```
361
+
362
+ - **Filtering on Nested Fields**
363
+ Uses dot notation to access fields within nested objects.
364
+
365
+ ```typescript
366
+ // Find users where nestedData.value is 'nested info'
367
+ await db.query('user').filter('nestedData.value', '=', 'nested info').get()
368
+ ```
369
+
370
+ - **Filtering on Reference Fields**
371
+ Uses dot notation to filter based on fields of referenced nodes.
372
+
373
+ ```typescript
374
+ // Find users whose best friend is named 'Bob'
375
+ await db.query('user').filter('bestFriend.name', '=', 'Bob').get()
376
+
377
+ // Find users who have at least one friend older than 30
378
+ await db.query('user').filter('friends.age', '>', 30).get()
379
+
380
+ // Find users whose best friend's status is 'active'
381
+ await db.query('user').filter('bestFriend.status', '=', 'active').get()
382
+ ```
383
+
384
+ #### \`.sort(field, direction?)\`
385
+
386
+ Sorts the results by a specific field. \`direction\` can be \`'asc'\` (default) or \`'desc'\`.
387
+
388
+ ```typescript
389
+ // Sort by age descending
390
+ await db.query('user').sort('age', 'desc').get()
391
+
392
+ // Sort by name ascending
393
+ await db.query('user').sort('name').get() // 'asc' is default
394
+
395
+ // Sort by text field (uses locale if provided)
396
+ await db.query('user').locale('nl').sort('bio').get()
397
+
398
+ // Sort by cardinality (HLL count)
399
+ await db.query('user').sort('visits', 'desc').get()
400
+
401
+ // Sort by alias
402
+ await db.query('article').sort('email', 'desc').get()
403
+
404
+ // Sort by timestamp
405
+ await db.query('event').sort('startTime').get()
406
+ ```
407
+
408
+ #### \`.range(offset, limit)\`
409
+
410
+ Paginates the results.
411
+
412
+ ```typescript
413
+ // Get users 11-20 sorted by name
414
+ await db.query('user').range(10, 10).sort('name').get()
415
+ ```
416
+
417
+ #### \`.search(term, fieldOrWeights, options?)\`
418
+
419
+ Performs full-text or vector search, returning results sorted by relevance/similarity.
420
+
421
+ ```typescript
422
+ // Full-text search for 'engineer' in 'name' and 'bio' fields
423
+ await db.query('user').search('engineer', 'name', 'bio').get()
424
+
425
+ // Full-text search with field weights
426
+ await db.query('user').search('engineer', { bio: 0.8, name: 0.2 }).get()
427
+
428
+ // Vector search (returns sorted by similarity)
429
+ const queryVector = new Float32Array([
430
+ /* ... */
431
+ ])
432
+ await db.query('user').search(queryVector, 'embedding', { score: 0.7 }).get()
433
+ ```
434
+
435
+ #### \`.locale(langCode)\`
436
+
437
+ Specifies the language context for \`text\` fields in \`include\`, \`filter\`, and \`sort\`.
438
+
439
+ ```typescript
440
+ // Get 'bio' in 'nl', falling back to 'en' if 'nl' is missing
441
+ await db.query('user', userId).locale('nl').include('bio').get()
442
+ // -> { id: userId, bio: 'Ingenieur' } (assuming 'nl' exists, else 'Engineer')
443
+
444
+ // Filter based on 'nl' text, return 'nl' text
445
+ await db
446
+ .query('user')
447
+ .locale('nl')
448
+ .filter('bio', 'has', 'ingenieur')
449
+ .include('bio')
450
+ .get()
451
+ ```
452
+
453
+ #### \`.get()\`
454
+
455
+ Executes the query and returns a \`BasedQueryResponse\` promise.
456
+
457
+ ```typescript
458
+ const response = await db.query('user').include('name').get()
459
+
460
+ // Get results as plain objects
461
+ const userObjects = response.toObject() // Returns array or single object/null
462
+
463
+ // Get single node result directly (for single ID or alias queries)
464
+ const singleUserObject = await db
465
+ .query('user', userId)
466
+ .get()
467
+ .then((res) => res.node()) // Returns single object or null
468
+ ```
469
+
470
+ #### Combining Methods
471
+
472
+ Query methods can be chained together.
473
+
474
+ ```typescript
475
+ const specificUsers = await db
476
+ .query('user')
477
+ .filter('age', '>', 25)
478
+ .filter('status', '=', 'active')
479
+ .sort('createdAt', 'desc')
480
+ .range(0, 5)
481
+ .include('name', 'email')
482
+ .locale('en') // Optional: set locale context
483
+ .get()
484
+ .then((res) => res.toObject()) // Get plain objects
485
+ ```
486
+
487
+ ### Update
488
+
489
+ ```typescript
490
+ // Update specific fields
491
+ await db.update('user', userId, { age: 31, isActive: false })
492
+
493
+ // Update using payload.id
494
+ await db.update('user', { id: userId, age: 32 })
495
+
496
+ // Atomic increment/decrement (number/timestamp)
497
+ await db.update('user', userId, {
498
+ age: { increment: 1 },
499
+ score: { decrement: 0.5 },
500
+ })
501
+
502
+ // Update references (single)
503
+ await db.update('user', userWithFriendId, { bestFriend: userId })
504
+
505
+ // Update references (list) - add, delete, update edge props
506
+ await db.update('user', userWithFriendId, {
507
+ friends: {
508
+ add: [userId],
509
+ delete: [friendId],
510
+ update: [{ id: userId, $friendshipLevel: 10 }],
511
+ },
512
+ })
513
+
514
+ // Replace references (list)
515
+ await db.update('user', userWithFriendId, { friends: [userId] }) // Replaces entire list
516
+
517
+ // Clear references
518
+ await db.update('user', userWithFriendId, { friends: null, bestFriend: null })
519
+
520
+ // Update cardinality field
521
+ await db.update('user', userId, { visits: 'newSession' }) // Adds 'newSession' if unique
522
+
523
+ // Update root properties
524
+ await db.update({ siteName: 'My Awesome Site V2' })
525
+
526
+ // Update text with specific locale
527
+ await db.update(
528
+ 'dialog',
529
+ dialogFi,
530
+ { fun: 'vielä hauskempaa' },
531
+ { locale: 'fi' },
532
+ )
533
+
534
+ // Update timestamp with string parsing
535
+ await db.update('user', userId, { updatedAt: 'now + 1h' }) // Relative time
536
+ ```
537
+
538
+ ### Upsert
539
+
540
+ Update if alias exists, otherwise create.
541
+
542
+ ```typescript
543
+ await db.upsert('user', {
544
+ alias: 'alice@example.com', // The alias to match
545
+ name: 'Alice Smith', // Field to update or set on create
546
+ age: 33, // Another field
547
+ })
548
+ ```
549
+
550
+ ### Delete
551
+
552
+ ```typescript
553
+ // Delete a node by ID
554
+ await db.delete('user', userId)
555
+ ```
556
+
557
+ ### Persistence & Control
558
+
559
+ ```typescript
560
+ // Manually trigger a save to disk
561
+ await db.save()
562
+
563
+ // Wait for pending modifications to be processed
564
+ await db.drain()
565
+ // or use the promise returned by isModified()
566
+ await db.isModified()
567
+
568
+ // Gracefully stop the database (saves pending changes)
569
+ await db.stop()
570
+
571
+ // Destroy the instance (call stop() first for graceful shutdown)
572
+ await db.destroy()
573
+
574
+ // Completely clear all data and schema (USE WITH CAUTION!)
575
+ await db.wipe()
576
+ ```
577
+
578
+ ### Validation
579
+
580
+ BasedDb automatically validates data against the schema on \`create\`, \`update\`, and \`upsert\`. It checks types, required fields, enums, number constraints (min, \`max\`, \`step\`), string constraints (maxBytes), vector size, references, aliases, locales, and custom validation functions. Invalid operations will throw an error.
581
+
582
+ ```typescript
583
+ // Example: This would throw if 'invalid-status' is not in the enum
584
+ // await db.create('user', { status: 'invalid-status', email: 'fail@example.com' })
585
+
586
+ // Example: This would throw if score is > 100
587
+ // await db.update('user', userId, { score: 101 })
588
+
589
+ // Example: This would throw if the custom validation fails (e.g., not an even number)
590
+ // await db.create('user', { customValidated: 3, email: 'customfail@example.com' })
591
+ ```
592
+
593
+ ### Subscriptions (Client/Server Context)
594
+
595
+ Note: Subscriptions are primarily managed by a higher-level client/server setup.
596
+
597
+ ```typescript
598
+ // Client-side subscription setup (simplified)
599
+ const closeSub = db
600
+ .query('user')
601
+ .filter('isActive')
602
+ .include('name')
603
+ .subscribe((data) => {
604
+ console.log('Subscription update:', data.toObject())
605
+ })
606
+
607
+ // To stop listening:
608
+ closeSub()
609
+ ```
610
+
611
+ ### Schema Updates (Client/Server Context)
612
+
613
+ Note: Schema updates in a client/server environment involve coordination.
614
+
615
+ ```typescript
616
+ // Client initiates a schema update
617
+ await clientDb.setSchema({
618
+ types: {
619
+ /* ... new or modified schema ... */
620
+ },
621
+ })
622
+
623
+ // Server processes and notifies clients.
624
+ ```
@@ -205,7 +205,7 @@ SELVA_EXPORT
205
205
  void selva_expire_node_cancel(struct SelvaDb *db, node_type_t type, node_id_t node_id);
206
206
 
207
207
  SELVA_EXPORT
208
- void selva_db_expire_tick(struct SelvaDb *db, int64_t now);
208
+ void selva_db_expire_tick(struct SelvaDb *db, selva_dirty_node_cb_t dirty_cb, void *dirty_ctx, int64_t now);
209
209
 
210
210
  /**
211
211
  * Delete a node.
@@ -105,7 +105,6 @@ void selva_fields_ensure_ref_meta(
105
105
  */
106
106
  SELVA_EXPORT
107
107
  int selva_fields_set(
108
- struct SelvaDb *db,
109
108
  struct SelvaNode *node,
110
109
  const struct SelvaFieldSchema *fs,
111
110
  const void *value, size_t len);
@@ -117,7 +116,6 @@ int selva_fields_set(
117
116
  */
118
117
  SELVA_EXPORT
119
118
  int fields_set2(
120
- struct SelvaDb *db,
121
119
  struct SelvaNode *node,
122
120
  const struct SelvaFieldSchema *fs,
123
121
  struct SelvaFields *fields,
@@ -205,7 +205,7 @@ SELVA_EXPORT
205
205
  void selva_expire_node_cancel(struct SelvaDb *db, node_type_t type, node_id_t node_id);
206
206
 
207
207
  SELVA_EXPORT
208
- void selva_db_expire_tick(struct SelvaDb *db, int64_t now);
208
+ void selva_db_expire_tick(struct SelvaDb *db, selva_dirty_node_cb_t dirty_cb, void *dirty_ctx, int64_t now);
209
209
 
210
210
  /**
211
211
  * Delete a node.
@@ -105,7 +105,6 @@ void selva_fields_ensure_ref_meta(
105
105
  */
106
106
  SELVA_EXPORT
107
107
  int selva_fields_set(
108
- struct SelvaDb *db,
109
108
  struct SelvaNode *node,
110
109
  const struct SelvaFieldSchema *fs,
111
110
  const void *value, size_t len);
@@ -117,7 +116,6 @@ int selva_fields_set(
117
116
  */
118
117
  SELVA_EXPORT
119
118
  int fields_set2(
120
- struct SelvaDb *db,
121
119
  struct SelvaNode *node,
122
120
  const struct SelvaFieldSchema *fs,
123
121
  struct SelvaFields *fields,
Binary file
@@ -205,7 +205,7 @@ SELVA_EXPORT
205
205
  void selva_expire_node_cancel(struct SelvaDb *db, node_type_t type, node_id_t node_id);
206
206
 
207
207
  SELVA_EXPORT
208
- void selva_db_expire_tick(struct SelvaDb *db, int64_t now);
208
+ void selva_db_expire_tick(struct SelvaDb *db, selva_dirty_node_cb_t dirty_cb, void *dirty_ctx, int64_t now);
209
209
 
210
210
  /**
211
211
  * Delete a node.
@@ -105,7 +105,6 @@ void selva_fields_ensure_ref_meta(
105
105
  */
106
106
  SELVA_EXPORT
107
107
  int selva_fields_set(
108
- struct SelvaDb *db,
109
108
  struct SelvaNode *node,
110
109
  const struct SelvaFieldSchema *fs,
111
110
  const void *value, size_t len);
@@ -117,7 +116,6 @@ int selva_fields_set(
117
116
  */
118
117
  SELVA_EXPORT
119
118
  int fields_set2(
120
- struct SelvaDb *db,
121
119
  struct SelvaNode *node,
122
120
  const struct SelvaFieldSchema *fs,
123
121
  struct SelvaFields *fields,