mdbxmou 0.2.6 → 0.3.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/CMakeLists.txt CHANGED
@@ -24,6 +24,7 @@ add_library(${PROJECT_NAME} SHARED
24
24
  "src/envmou.cpp"
25
25
  "src/txnmou.cpp"
26
26
  "src/dbimou.cpp"
27
+ "src/cursormou.cpp"
27
28
  "src/dbi.cpp")
28
29
 
29
30
  # Gives our library file a .node extension without any "lib" prefix
package/README.md CHANGED
@@ -9,7 +9,7 @@ High-performance Node.js binding for libmdbx — a fast, lightweight, embedded k
9
9
  - **Transactions** — ACID transactions with read/write modes
10
10
  - **Multiple key/value types** — String, binary, ordinal (integer) keys
11
11
  - **Batch operations** — Efficient multi-key read/write
12
- - **Memory-mapped** — Zero-copy data access
12
+ - **Memory-mapped** — High-performance memory-mapped I/O
13
13
 
14
14
  ## Installation
15
15
 
@@ -266,6 +266,199 @@ dbi.drop(txn, true);
266
266
  dbi.drop(txn);
267
267
  ```
268
268
 
269
+ ### Cursor (MDBX_Cursor)
270
+
271
+ Cursors provide low-level control for database traversal with positioning and iteration capabilities.
272
+
273
+ #### Creating a Cursor
274
+ ```javascript
275
+ const txn = env.startRead();
276
+ const dbi = txn.openMap();
277
+ const cursor = txn.openCursor(dbi);
278
+ ```
279
+
280
+ #### Navigation Methods
281
+
282
+ **first() → {key, value} | undefined**
283
+ ```javascript
284
+ const item = cursor.first();
285
+ if (item) {
286
+ console.log(item.key, item.value);
287
+ }
288
+ ```
289
+
290
+ **last() → {key, value} | undefined**
291
+ ```javascript
292
+ const item = cursor.last();
293
+ ```
294
+
295
+ **next() → {key, value} | undefined**
296
+ ```javascript
297
+ const item = cursor.next();
298
+ ```
299
+
300
+ **prev() → {key, value} | undefined**
301
+ ```javascript
302
+ const item = cursor.prev();
303
+ ```
304
+
305
+ **current() → {key, value} | undefined**
306
+ ```javascript
307
+ const item = cursor.current();
308
+ ```
309
+
310
+ #### Search Methods
311
+
312
+ **seek(key) → {key, value} | undefined**
313
+
314
+ Exact key match. Returns `undefined` if key not found.
315
+ ```javascript
316
+ const item = cursor.seek('user:123');
317
+ if (item) {
318
+ console.log('Found:', item.value);
319
+ }
320
+ ```
321
+
322
+ **seekGE(key) → {key, value} | undefined**
323
+
324
+ Find first key greater or equal to given key (lower_bound).
325
+ ```javascript
326
+ const item = cursor.seekGE('user:100');
327
+ // Returns first key >= 'user:100'
328
+ ```
329
+
330
+ #### Modification Methods
331
+
332
+ **put(key, value, [flags])**
333
+
334
+ Insert or update a record at cursor position.
335
+ ```javascript
336
+ cursor.put('newKey', 'newValue');
337
+
338
+ // With flags (MDBX_NOOVERWRITE, etc.)
339
+ cursor.put('key', 'value', MDBX_Param.queryMode.insertUnique);
340
+ ```
341
+
342
+ **del([flags]) → boolean**
343
+
344
+ Delete record at current cursor position. Returns `true` if deleted, `false` if not found.
345
+ ```javascript
346
+ cursor.seek('keyToDelete');
347
+ const deleted = cursor.del();
348
+ ```
349
+
350
+ #### Control Methods
351
+
352
+ **close()**
353
+ ```javascript
354
+ cursor.close();
355
+ ```
356
+
357
+ #### Cursor Examples
358
+
359
+ **Iterate all records:**
360
+ ```javascript
361
+ const cursor = txn.openCursor(dbi);
362
+
363
+ for (let item = cursor.first(); item; item = cursor.next()) {
364
+ console.log(item.key, item.value.toString());
365
+ }
366
+
367
+ cursor.close();
368
+ txn.abort();
369
+ ```
370
+
371
+ **Range iteration:**
372
+ ```javascript
373
+ const cursor = txn.openCursor(dbi);
374
+
375
+ // Find all keys starting with 'user:'
376
+ for (let item = cursor.seekGE('user:'); item; item = cursor.next()) {
377
+ if (!item.key.startsWith('user:')) break;
378
+ console.log(item.key, item.value.toString());
379
+ }
380
+
381
+ cursor.close();
382
+ ```
383
+
384
+ **Pagination:**
385
+ ```javascript
386
+ function getPage(cursor, offset, limit) {
387
+ const results = [];
388
+
389
+ let item = cursor.first();
390
+
391
+ // Skip offset
392
+ for (let i = 0; i < offset && item; i++) {
393
+ item = cursor.next();
394
+ }
395
+
396
+ // Collect limit items
397
+ for (let i = 0; i < limit && item; i++) {
398
+ results.push({ key: item.key, value: item.value.toString() });
399
+ item = cursor.next();
400
+ }
401
+
402
+ return results;
403
+ }
404
+
405
+ const txn = env.startRead();
406
+ const dbi = txn.openMap();
407
+ const cursor = txn.openCursor(dbi);
408
+
409
+ const page1 = getPage(cursor, 0, 10); // First 10 items
410
+ const page2 = getPage(cursor, 10, 10); // Next 10 items
411
+
412
+ cursor.close();
413
+ txn.abort();
414
+ ```
415
+
416
+ **Reverse iteration:**
417
+ ```javascript
418
+ const cursor = txn.openCursor(dbi);
419
+
420
+ for (let item = cursor.last(); item; item = cursor.prev()) {
421
+ console.log(item.key, item.value.toString());
422
+ }
423
+
424
+ cursor.close();
425
+ ```
426
+
427
+ **Bulk insert with cursor:**
428
+ ```javascript
429
+ const txn = env.startWrite();
430
+ const dbi = txn.createMap();
431
+ const cursor = txn.openCursor(dbi);
432
+
433
+ for (let i = 0; i < 1000; i++) {
434
+ cursor.put(`key${i}`, `value${i}`);
435
+ }
436
+
437
+ cursor.close();
438
+ txn.commit();
439
+ ```
440
+
441
+ **Delete with cursor:**
442
+ ```javascript
443
+ const txn = env.startWrite();
444
+ const dbi = txn.openMap();
445
+ const cursor = txn.openCursor(dbi);
446
+
447
+ // Delete specific key
448
+ if (cursor.seek('keyToDelete')) {
449
+ cursor.del();
450
+ }
451
+
452
+ // Delete range
453
+ for (let item = cursor.seekGE('prefix:'); item; item = cursor.next()) {
454
+ if (!item.key.startsWith('prefix:')) break;
455
+ cursor.del();
456
+ }
457
+
458
+ cursor.close();
459
+ txn.commit();
460
+ ```
461
+
269
462
  ## Key and Value Types
270
463
 
271
464
  ### Key Modes (MDBX_Param.keyMode)
@@ -623,7 +816,7 @@ Note: For ordinal (integer) keys, use keyFlag.number or keyFlag.bigint to specif
623
816
  1. **Use ordinal keys** for integer data - much faster than string keys
624
817
  2. **Batch operations** - Use query API for bulk operations
625
818
  3. **Reuse transactions** - Keep read transactions open for multiple operations
626
- 4. **Memory mapping** - MDBX uses memory-mapped files for zero-copy access
819
+ 4. **Memory mapping** - MDBX uses memory-mapped files for fast I/O
627
820
  5. **Transaction scope** - Always pass transaction object to DBI methods
628
821
 
629
822
  ## License
package/package.json CHANGED
@@ -46,7 +46,8 @@
46
46
  ],
47
47
  "dependencies": {
48
48
  "cmake-js": "^7.3.1",
49
- "node-addon-api": "^8.5.0"
49
+ "node-addon-api": "^8.5.0",
50
+ "uuid": "^13.0.0"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@types/node": "^22.10.2",
@@ -65,7 +66,7 @@
65
66
  },
66
67
  "gypfile": true,
67
68
  "name": "mdbxmou",
68
- "version": "0.2.6",
69
+ "version": "0.3.0",
69
70
  "description": "Node bindings for mdbx",
70
71
  "repository": {
71
72
  "type": "git",
@@ -0,0 +1,316 @@
1
+ #include "cursormou.hpp"
2
+ #include "dbimou.hpp"
3
+ #include "txnmou.hpp"
4
+
5
+ namespace mdbxmou {
6
+
7
+ Napi::FunctionReference cursormou::ctor{};
8
+
9
+ void cursormou::init(const char *class_name, Napi::Env env)
10
+ {
11
+ auto func = DefineClass(env, class_name, {
12
+ InstanceMethod("first", &cursormou::first),
13
+ InstanceMethod("last", &cursormou::last),
14
+ InstanceMethod("next", &cursormou::next),
15
+ InstanceMethod("prev", &cursormou::prev),
16
+ InstanceMethod("seek", &cursormou::seek),
17
+ InstanceMethod("seekGE", &cursormou::seek_ge),
18
+ InstanceMethod("current", &cursormou::current),
19
+ InstanceMethod("put", &cursormou::put),
20
+ InstanceMethod("del", &cursormou::del),
21
+ InstanceMethod("forEach", &cursormou::for_each),
22
+ InstanceMethod("close", &cursormou::close),
23
+ });
24
+
25
+ ctor = Napi::Persistent(func);
26
+ ctor.SuppressDestruct();
27
+ }
28
+
29
+ void cursormou::do_close() noexcept
30
+ {
31
+ if (cursor_) {
32
+ ::mdbx_cursor_close(std::exchange(cursor_, nullptr));
33
+ if (txn_) {
34
+ --(*txn_);
35
+ }
36
+ }
37
+ }
38
+
39
+ cursormou::~cursormou()
40
+ {
41
+ do_close();
42
+ }
43
+
44
+ void cursormou::attach(txnmou& txn, dbimou& dbi, MDBX_cursor* cursor)
45
+ {
46
+ txn_ = &txn;
47
+ dbi_ = &dbi;
48
+ cursor_ = cursor;
49
+ ++(*txn_);
50
+ }
51
+
52
+ // Внутренний хелпер для навигации
53
+ Napi::Value cursormou::move(const Napi::Env& env, MDBX_cursor_op op)
54
+ {
55
+ if (!cursor_) {
56
+ throw Napi::Error::New(env, "cursor closed");
57
+ }
58
+
59
+ keymou key{};
60
+ valuemou val{};
61
+ auto rc = mdbx_cursor_get(cursor_, key, val, op);
62
+ if (rc == MDBX_NOTFOUND) {
63
+ return env.Undefined();
64
+ }
65
+
66
+ if (rc != MDBX_SUCCESS) {
67
+ throw Napi::Error::New(env, mdbx_strerror(rc));
68
+ }
69
+
70
+ // Возвращаем {key, value}
71
+ auto result = Napi::Object::New(env);
72
+
73
+ auto key_mode = dbi_->get_key_mode();
74
+ auto key_flag = dbi_->get_key_flag();
75
+ auto value_flag = dbi_->get_value_flag();
76
+
77
+ // Ключ
78
+ if (key_mode.val & key_mode::ordinal) {
79
+ if (key_flag.val & base_flag::bigint) {
80
+ result.Set("key", key.to_bigint(env));
81
+ } else {
82
+ result.Set("key", key.to_number(env));
83
+ }
84
+ } else {
85
+ result.Set("key", key.to_string(env));
86
+ }
87
+
88
+ // Значение
89
+ if (value_flag.val & base_flag::string) {
90
+ result.Set("value", val.to_string(env));
91
+ } else {
92
+ result.Set("value", val.to_buffer(env));
93
+ }
94
+
95
+ return result;
96
+ }
97
+
98
+ Napi::Value cursormou::first(const Napi::CallbackInfo& info) {
99
+ return move(info.Env(), MDBX_FIRST);
100
+ }
101
+
102
+ Napi::Value cursormou::last(const Napi::CallbackInfo& info) {
103
+ return move(info.Env(), MDBX_LAST);
104
+ }
105
+
106
+ Napi::Value cursormou::next(const Napi::CallbackInfo& info) {
107
+ return move(info.Env(), MDBX_NEXT);
108
+ }
109
+
110
+ Napi::Value cursormou::prev(const Napi::CallbackInfo& info) {
111
+ return move(info.Env(), MDBX_PREV);
112
+ }
113
+
114
+ Napi::Value cursormou::current(const Napi::CallbackInfo& info) {
115
+ return move(info.Env(), MDBX_GET_CURRENT);
116
+ }
117
+
118
+ // Хелпер для поиска
119
+ Napi::Value cursormou::seek_impl(const Napi::CallbackInfo& info, MDBX_cursor_op op)
120
+ {
121
+ Napi::Env env = info.Env();
122
+
123
+ if (!cursor_) {
124
+ throw Napi::Error::New(env, "cursor closed");
125
+ }
126
+
127
+ if (info.Length() < 1) {
128
+ throw Napi::Error::New(env, "key required");
129
+ }
130
+
131
+ auto key_mode = dbi_->get_key_mode();
132
+
133
+ keymou key = (key_mode.val & key_mode::ordinal) ?
134
+ keymou::from(info[0], env, key_num_) :
135
+ keymou::from(info[0], env, key_buf_);
136
+
137
+ valuemou val{};
138
+ auto rc = mdbx_cursor_get(cursor_, key, val, op);
139
+ if (MDBX_NOTFOUND == rc) {
140
+ return env.Undefined();
141
+ }
142
+
143
+ if (MDBX_SUCCESS != rc) {
144
+ throw Napi::Error::New(env, mdbx_strerror(rc));
145
+ }
146
+
147
+ // Возвращаем {key, value}
148
+ auto result = Napi::Object::New(env);
149
+
150
+ auto key_flag = dbi_->get_key_flag();
151
+ auto value_flag = dbi_->get_value_flag();
152
+
153
+ // Ключ
154
+ if (key_mode.val & key_mode::ordinal) {
155
+ if (key_flag.val & base_flag::bigint) {
156
+ result.Set("key", key.to_bigint(env));
157
+ } else {
158
+ result.Set("key", key.to_number(env));
159
+ }
160
+ } else {
161
+ result.Set("key", key.to_string(env));
162
+ }
163
+
164
+ // Значение
165
+ if (value_flag.val & base_flag::string) {
166
+ result.Set("value", val.to_string(env));
167
+ } else {
168
+ result.Set("value", val.to_buffer(env));
169
+ }
170
+
171
+ return result;
172
+ }
173
+
174
+ Napi::Value cursormou::seek(const Napi::CallbackInfo& info) {
175
+ return seek_impl(info, MDBX_SET_KEY);
176
+ }
177
+
178
+ Napi::Value cursormou::seek_ge(const Napi::CallbackInfo& info) {
179
+ return seek_impl(info, MDBX_SET_RANGE);
180
+ }
181
+
182
+ Napi::Value cursormou::put(const Napi::CallbackInfo& info) {
183
+ Napi::Env env = info.Env();
184
+
185
+ if (!cursor_) {
186
+ throw Napi::Error::New(env, "cursor closed");
187
+ }
188
+
189
+ if (info.Length() < 2) {
190
+ throw Napi::Error::New(env, "key and value required");
191
+ }
192
+
193
+ auto key_mode = dbi_->get_key_mode();
194
+
195
+ keymou key = (key_mode.val & key_mode::ordinal) ?
196
+ keymou::from(info[0], env, key_num_) :
197
+ keymou::from(info[0], env, key_buf_);
198
+
199
+ valuemou val = valuemou::from(info[1], env, val_buf_);
200
+
201
+ // Опциональный флаг put_mode (по умолчанию MDBX_UPSERT)
202
+ MDBX_put_flags_t flags = MDBX_UPSERT;
203
+ if (info.Length() > 2 && info[2].IsNumber()) {
204
+ flags = static_cast<MDBX_put_flags_t>(info[2].As<Napi::Number>().Int32Value());
205
+ }
206
+
207
+ auto rc = mdbx_cursor_put(cursor_, key, val, flags);
208
+ if (MDBX_SUCCESS != rc) {
209
+ throw Napi::Error::New(env, mdbx_strerror(rc));
210
+ }
211
+
212
+ return env.Undefined();
213
+ }
214
+
215
+ Napi::Value cursormou::del(const Napi::CallbackInfo& info) {
216
+ Napi::Env env = info.Env();
217
+
218
+ if (!cursor_) {
219
+ throw Napi::Error::New(env, "cursor closed");
220
+ }
221
+
222
+ // Опциональный флаг (MDBX_NODUPDATA для удаления только текущего значения в multi-value)
223
+ MDBX_put_flags_t flags = MDBX_CURRENT;
224
+ if (info.Length() > 0 && info[0].IsNumber()) {
225
+ flags = static_cast<MDBX_put_flags_t>(info[0].As<Napi::Number>().Int32Value());
226
+ }
227
+
228
+ auto rc = mdbx_cursor_del(cursor_, flags);
229
+ if (MDBX_NOTFOUND == rc) {
230
+ return Napi::Boolean::New(env, false);
231
+ }
232
+
233
+ if (MDBX_SUCCESS != rc) {
234
+ throw Napi::Error::New(env, mdbx_strerror(rc));
235
+ }
236
+
237
+ return Napi::Boolean::New(env, true);
238
+ }
239
+
240
+ // forEach(callback, backward = false)
241
+ // callback({key, value}) => true продолжить, false/undefined остановить
242
+ Napi::Value cursormou::for_each(const Napi::CallbackInfo& info) {
243
+ Napi::Env env = info.Env();
244
+
245
+ if (!cursor_) {
246
+ throw Napi::Error::New(env, "cursor closed");
247
+ }
248
+
249
+ if (info.Length() < 1 || !info[0].IsFunction()) {
250
+ throw Napi::TypeError::New(env, "forEach: callback function required");
251
+ }
252
+
253
+ auto callback = info[0].As<Napi::Function>();
254
+ bool backward = info.Length() > 1 && info[1].ToBoolean().Value();
255
+
256
+ auto key_mode = dbi_->get_key_mode();
257
+ auto key_flag = dbi_->get_key_flag();
258
+ auto value_flag = dbi_->get_value_flag();
259
+
260
+ MDBX_cursor_op start_op = backward ? MDBX_LAST : MDBX_FIRST;
261
+ MDBX_cursor_op move_op = backward ? MDBX_PREV : MDBX_NEXT;
262
+
263
+ keymou key{};
264
+ valuemou val{};
265
+ // Первое позиционирование
266
+ auto rc = mdbx_cursor_get(cursor_, key, val, start_op);
267
+ while (MDBX_SUCCESS == rc)
268
+ {
269
+ auto result = Napi::Object::New(env);
270
+
271
+ // Ключ
272
+ if (key_mode.val & key_mode::ordinal) {
273
+ if (key_flag.val & base_flag::bigint) {
274
+ result.Set("key", key.to_bigint(env));
275
+ } else {
276
+ result.Set("key", key.to_number(env));
277
+ }
278
+ } else {
279
+ result.Set("key", key.to_string(env));
280
+ }
281
+
282
+ // Значение
283
+ if (value_flag.val & base_flag::string) {
284
+ result.Set("value", val.to_string(env));
285
+ } else {
286
+ result.Set("value", val.to_buffer(env));
287
+ }
288
+
289
+ // Вызов callback
290
+ auto ret = callback.Call({result});
291
+
292
+ // Если вернул false или undefined - остановить
293
+ if (ret.IsBoolean() && !ret.As<Napi::Boolean>().Value()) {
294
+ break;
295
+ }
296
+ if (ret.IsUndefined() || ret.IsNull()) {
297
+ // undefined/null = продолжаем (как forEach в JS)
298
+ }
299
+
300
+ // Следующий элемент
301
+ rc = mdbx_cursor_get(cursor_, key, val, move_op);
302
+ }
303
+
304
+ if (rc != MDBX_SUCCESS && rc != MDBX_NOTFOUND) {
305
+ throw Napi::Error::New(env, mdbx_strerror(rc));
306
+ }
307
+
308
+ return env.Undefined();
309
+ }
310
+
311
+ Napi::Value cursormou::close(const Napi::CallbackInfo& info) {
312
+ do_close();
313
+ return info.Env().Undefined();
314
+ }
315
+
316
+ } // namespace mdbxmou
@@ -0,0 +1,69 @@
1
+ #pragma once
2
+
3
+ #include "typemou.hpp"
4
+ #include "valuemou.hpp"
5
+
6
+ namespace mdbxmou {
7
+
8
+ class dbimou;
9
+ class txnmou;
10
+
11
+ class cursormou final
12
+ : public Napi::ObjectWrap<cursormou>
13
+ {
14
+ private:
15
+ txnmou* txn_{nullptr};
16
+ dbimou* dbi_{nullptr};
17
+ MDBX_cursor* cursor_{nullptr};
18
+
19
+ // Буферы для ключей/значений
20
+ buffer_type key_buf_{};
21
+ buffer_type val_buf_{};
22
+ std::uint64_t key_num_{};
23
+
24
+ // Внутренний хелпер для навигации
25
+ Napi::Value move(const Napi::Env& env, MDBX_cursor_op op);
26
+
27
+ // Хелпер для поиска
28
+ Napi::Value seek_impl(const Napi::CallbackInfo& info, MDBX_cursor_op op);
29
+
30
+ // Закрытие курсора
31
+ void do_close() noexcept;
32
+
33
+ public:
34
+ static Napi::FunctionReference ctor;
35
+ static void init(const char *class_name, Napi::Env env);
36
+
37
+ cursormou(const Napi::CallbackInfo& info)
38
+ : Napi::ObjectWrap<cursormou>(info)
39
+ { }
40
+
41
+ ~cursormou();
42
+
43
+ // Навигация
44
+ Napi::Value first(const Napi::CallbackInfo&);
45
+ Napi::Value last(const Napi::CallbackInfo&);
46
+ Napi::Value next(const Napi::CallbackInfo&);
47
+ Napi::Value prev(const Napi::CallbackInfo&);
48
+
49
+ // Поиск
50
+ Napi::Value seek(const Napi::CallbackInfo&); // exact match
51
+ Napi::Value seek_ge(const Napi::CallbackInfo&); // >= key (lower_bound)
52
+
53
+ // Текущая позиция
54
+ Napi::Value current(const Napi::CallbackInfo&);
55
+
56
+ // Модификация
57
+ Napi::Value put(const Napi::CallbackInfo&);
58
+ Napi::Value del(const Napi::CallbackInfo&);
59
+
60
+ // Итерация
61
+ Napi::Value for_each(const Napi::CallbackInfo&);
62
+
63
+ // Закрытие
64
+ Napi::Value close(const Napi::CallbackInfo&);
65
+
66
+ void attach(txnmou& txn, dbimou& dbi, MDBX_cursor* cursor);
67
+ };
68
+
69
+ } // namespace mdbxmou
package/src/dbimou.cpp CHANGED
@@ -51,6 +51,7 @@ Napi::Value dbimou::put(const Napi::CallbackInfo& info)
51
51
  auto key = (key_mode_.val & key_mode::ordinal) ?
52
52
  keymou::from(info[1], env, t) :
53
53
  keymou::from(info[1], env, key_buf_);
54
+
54
55
  auto val = valuemou::from(info[2], env, val_buf_);
55
56
  dbi::put(*txn, key, val, *this);
56
57
  } catch (const std::exception& e) {
@@ -139,7 +140,7 @@ Napi::Value dbimou::has(const Napi::CallbackInfo& info) {
139
140
  auto key = (key_mode_.val & key_mode::ordinal) ?
140
141
  keymou::from(info[1], env, t) :
141
142
  keymou::from(info[1], env, key_buf_);
142
-
143
+
143
144
  bool result = dbi::has(*txn, key);
144
145
  return Napi::Value::From(env, result);
145
146
  } catch (const std::exception& e) {
@@ -177,7 +178,6 @@ Napi::Value dbimou::for_each(const Napi::CallbackInfo& info) {
177
178
  }
178
179
 
179
180
  uint32_t index{};
180
-
181
181
  if (key_mode_.val & key_mode::ordinal) {
182
182
  cursor.scan([&](const mdbx::pair& f) {
183
183
  keymou key{f.key};
package/src/dbimou.hpp CHANGED
@@ -33,9 +33,6 @@ public:
33
33
  , dbi{}
34
34
  { }
35
35
 
36
- //~dbimou() { fprintf(stderr, "dbimou::~dbimou %p key_cap=%zu val_cap=%zu\n",this, key_buf_.capacity(), val_buf_.capacity()); }
37
- //void Finalize(Napi::Env env) { fprintf(stderr, "~dbimou %p key_cap=%zu val_cap=%zu\n",this, key_buf_.capacity(), val_buf_.capacity()); }
38
-
39
36
  Napi::Value get_id(const Napi::CallbackInfo& info) {
40
37
  return Napi::BigInt::New(info.Env(), static_cast<int64_t>(id_));
41
38
  }
package/src/env_arg0.hpp CHANGED
@@ -1,9 +1,6 @@
1
1
  #pragma once
2
2
 
3
3
  #include "valuemou.hpp"
4
- #ifdef _WIN32
5
- typedef unsigned short mode_t;
6
- #endif
7
4
 
8
5
  namespace mdbxmou {
9
6
 
@@ -12,7 +9,7 @@ struct env_arg0 {
12
9
  MDBX_dbi max_dbi{32};
13
10
  mdbx::env::geometry geom{};
14
11
  env_flag flag{};
15
- mode_t file_mode{0664};
12
+ mdbx_mode_t file_mode{0664};
16
13
  std::uint32_t max_readers{128};
17
14
  base_flag key_flag{};
18
15
  base_flag value_flag{};
package/src/envmou.cpp CHANGED
@@ -103,7 +103,7 @@ env_arg0 envmou::parse(const Napi::Value& arg0)
103
103
 
104
104
  if (obj.Has("mode")) {
105
105
  auto value = obj.Get("mode").As<Napi::Number>();
106
- rc.file_mode = static_cast<mode_t>(value.Int32Value());
106
+ rc.file_mode = static_cast<mdbx_mode_t>(value.Int32Value());
107
107
  }
108
108
 
109
109
  if (obj.Has("keyFlag")) {
@@ -234,7 +234,7 @@ Napi::Value envmou::close(const Napi::CallbackInfo& info)
234
234
  return env.Undefined();
235
235
  }
236
236
 
237
- if (trx_count_.load() > 0) {
237
+ if (trx_count_ > 0) {
238
238
  throw std::runtime_error("active transactions");
239
239
  }
240
240
 
@@ -392,10 +392,10 @@ Napi::Value envmou::query(const Napi::CallbackInfo& info)
392
392
  auto* worker = new async_query(env, *this, mode,
393
393
  std::move(query), arg0.IsObject());
394
394
  auto promise = worker->GetPromise();
395
- worker->Queue();
396
395
 
397
- // Увеличиваем счетчик транзакций после успешного создания
396
+ // Увеличиваем счетчик ДО Queue() worker гарантированно вызовет --env_
398
397
  ++(*this);
398
+ worker->Queue();
399
399
 
400
400
  return promise;
401
401
  } catch (const std::exception& e) {
@@ -433,10 +433,10 @@ Napi::Value envmou::keys(const Napi::CallbackInfo& info)
433
433
  auto* worker = new async_keys(env, *this, mode,
434
434
  std::move(query), arg0.IsObject());
435
435
  auto promise = worker->GetPromise();
436
- worker->Queue();
437
436
 
438
- // Увеличиваем счетчик транзакций после успешного создания
437
+ // Увеличиваем счетчик ДО Queue() worker гарантированно вызовет --env_
439
438
  ++(*this);
439
+ worker->Queue();
440
440
 
441
441
  return promise;
442
442
  } catch (const std::exception& e) {
package/src/envmou.hpp CHANGED
@@ -27,7 +27,8 @@ class envmou final
27
27
  };
28
28
 
29
29
  std::unique_ptr<MDBX_env, free_env> env_{};
30
- std::atomic<std::size_t> trx_count_{};
30
+ // счетчик транзакицй, не требующий атомарности
31
+ std::size_t trx_count_{};
31
32
  env_arg0 arg0_{};
32
33
  std::mutex lock_{};
33
34
 
@@ -106,7 +107,7 @@ public:
106
107
  }
107
108
 
108
109
  void do_close() {
109
- if (trx_count_.load() > 0) {
110
+ if (trx_count_ > 0) {
110
111
  throw std::runtime_error("transaction in progress");
111
112
  }
112
113
  env_.reset();
package/src/modulemou.cpp CHANGED
@@ -1,4 +1,5 @@
1
1
  #include "envmou.hpp"
2
+ #include "cursormou.hpp"
2
3
 
3
4
  using namespace Napi;
4
5
 
@@ -106,6 +107,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports)
106
107
  mdbxmou::envmou::init("MDBX_Env", env, exports);
107
108
  mdbxmou::txnmou::init("MDBX_Txn", env);
108
109
  mdbxmou::dbimou::init("MDBX_Dbi", env);
110
+ mdbxmou::cursormou::init("MDBX_Cursor", env);
109
111
 
110
112
  return exports;
111
113
  }
package/src/txnmou.cpp CHANGED
@@ -1,5 +1,6 @@
1
1
  #include "txnmou.hpp"
2
2
  #include "envmou.hpp"
3
+ #include "cursormou.hpp"
3
4
 
4
5
  namespace mdbxmou {
5
6
 
@@ -11,6 +12,7 @@ void txnmou::init(const char *class_name, Napi::Env env) {
11
12
  InstanceMethod("abort", &txnmou::abort),
12
13
  InstanceMethod("openMap", &txnmou::open_map),
13
14
  InstanceMethod("createMap", &txnmou::create_map),
15
+ InstanceMethod("openCursor", &txnmou::open_cursor),
14
16
  InstanceMethod("isActive", &txnmou::is_active),
15
17
  });
16
18
 
@@ -21,18 +23,19 @@ void txnmou::init(const char *class_name, Napi::Env env) {
21
23
  Napi::Value txnmou::commit(const Napi::CallbackInfo& info) {
22
24
  Napi::Env env = info.Env();
23
25
 
24
- try {
25
- check();
26
-
27
- auto rc = mdbx_txn_commit(*this);
28
- if (rc != MDBX_SUCCESS) {
29
- throw Napi::Error::New(env, std::string("txn: ") + mdbx_strerror(rc));
30
- }
31
-
32
- dec_counter();
33
- txn_.release();
34
- } catch (const std::exception& e) {
35
- throw Napi::Error::New(env, e.what());
26
+ if (!txn_) {
27
+ throw Napi::Error::New(env, "txn already completed");
28
+ }
29
+
30
+ if (cursor_count_ > 0) {
31
+ throw Napi::Error::New(env,
32
+ "txn commit: " + std::to_string(cursor_count_) + " cursor(s) still open");
33
+ }
34
+
35
+ dec_counter();
36
+ auto rc = mdbx_txn_commit(txn_.release());
37
+ if (rc != MDBX_SUCCESS) {
38
+ throw Napi::Error::New(env, std::string("txn commit: ") + mdbx_strerror(rc));
36
39
  }
37
40
 
38
41
  return env.Undefined();
@@ -41,18 +44,19 @@ Napi::Value txnmou::commit(const Napi::CallbackInfo& info) {
41
44
  Napi::Value txnmou::abort(const Napi::CallbackInfo& info) {
42
45
  Napi::Env env = info.Env();
43
46
 
44
- try {
45
- check();
46
-
47
- auto rc = mdbx_txn_abort(*this);
48
- if (rc != MDBX_SUCCESS) {
49
- throw Napi::Error::New(env, std::string("txn: ") + mdbx_strerror(rc));
50
- }
51
-
52
- dec_counter();
53
- txn_.release();
54
- } catch (const std::exception& e) {
55
- throw Napi::Error::New(env, e.what());
47
+ if (!txn_) {
48
+ throw Napi::Error::New(env, "txn already completed");
49
+ }
50
+
51
+ if (cursor_count_ > 0) {
52
+ throw Napi::Error::New(env,
53
+ "txn abort: " + std::to_string(cursor_count_) + " cursor(s) still open");
54
+ }
55
+
56
+ dec_counter();
57
+ auto rc = mdbx_txn_abort(txn_.release());
58
+ if (rc != MDBX_SUCCESS) {
59
+ throw Napi::Error::New(env, std::string("txn abort: ") + mdbx_strerror(rc));
56
60
  }
57
61
 
58
62
  return env.Undefined();
@@ -70,73 +74,97 @@ Napi::Value txnmou::get_dbi(const Napi::CallbackInfo& info, db_mode db_mode)
70
74
  {
71
75
  Napi::Env env = info.Env();
72
76
 
73
- try
74
- {
75
- if ((mode_.val & txn_mode::ro) && (db_mode.val & db_mode::create)) {
76
- throw std::runtime_error("dbi: cannot open DB in read-only transaction");
77
- }
77
+ if ((mode_.val & txn_mode::ro) && (db_mode.val & db_mode::create)) {
78
+ throw Napi::Error::New(env, "dbi: cannot open DB in read-only transaction");
79
+ }
78
80
 
79
- key_mode key_mode{};
80
- value_mode value_mode{};
81
- std::string db_name{};
82
- auto conf = get_env_userctx(*env_);
83
- auto key_flag = conf->key_flag;
84
- auto value_flag = conf->value_flag;
85
- auto arg_count = info.Length();
86
- if (arg_count == 3) {
87
- auto arg0 = info[0]; // db_name
88
- auto arg1 = info[1]; // key_mode
89
- auto arg2 = info[2]; // value_mode
81
+ key_mode key_mode{};
82
+ value_mode value_mode{};
83
+ std::string db_name{};
84
+ auto conf = get_env_userctx(*env_);
85
+ auto key_flag = conf->key_flag;
86
+ auto value_flag = conf->value_flag;
87
+ auto arg_count = info.Length();
88
+ if (arg_count == 3) {
89
+ auto arg0 = info[0]; // db_name
90
+ auto arg1 = info[1]; // key_mode
91
+ auto arg2 = info[2]; // value_mode
92
+ db_name = arg0.As<Napi::String>().Utf8Value();
93
+ key_mode = parse_key_mode(env, arg1, key_flag);
94
+ value_mode = value_mode::parse(arg2);
95
+ } else if (arg_count == 2) {
96
+ // db_name + key_mode || key_mode + value_mode
97
+ auto arg0 = info[0];
98
+ auto arg1 = info[1];
99
+ if (arg0.IsString()) {
90
100
  db_name = arg0.As<Napi::String>().Utf8Value();
91
101
  key_mode = parse_key_mode(env, arg1, key_flag);
92
- value_mode = value_mode::parse(arg2);
93
- } else if (arg_count == 2) {
94
- // db_name + key_mode || key_mode + value_mode
95
- auto arg0 = info[0];
96
- auto arg1 = info[1];
97
- if (arg0.IsString()) {
98
- db_name = arg0.As<Napi::String>().Utf8Value();
99
- key_mode = parse_key_mode(env, arg1, key_flag);
100
- } else if (arg0.IsNumber() || arg0.IsBigInt()) {
101
- key_mode = parse_key_mode(env, arg0, key_flag);
102
- value_mode = value_mode::parse(arg1);
103
- } else {
104
- throw Napi::Error::New(env, "Invalid argument type for db_name or value_mode");
105
- }
106
- } else if (arg_count == 1) {
107
- // db_name || key_mode
108
- auto arg0 = info[0];
109
- if (arg0.IsString()) {
110
- db_name = arg0.As<Napi::String>().Utf8Value();
111
- } else if (arg0.IsNumber() || arg0.IsBigInt()) {
112
- key_mode = parse_key_mode(env, arg0, key_flag);
113
- } else {
114
- throw Napi::Error::New(env, "Invalid argument type: expected string (db_name) or number (key_mode)");
115
- }
102
+ } else if (arg0.IsNumber() || arg0.IsBigInt()) {
103
+ key_mode = parse_key_mode(env, arg0, key_flag);
104
+ value_mode = value_mode::parse(arg1);
105
+ } else {
106
+ throw Napi::Error::New(env, "Invalid argument type for db_name or value_mode");
107
+ }
108
+ } else if (arg_count == 1) {
109
+ // db_name || key_mode
110
+ auto arg0 = info[0];
111
+ if (arg0.IsString()) {
112
+ db_name = arg0.As<Napi::String>().Utf8Value();
113
+ } else if (arg0.IsNumber() || arg0.IsBigInt()) {
114
+ key_mode = parse_key_mode(env, arg0, key_flag);
115
+ } else {
116
+ throw Napi::Error::New(env, "Invalid argument type: expected string (db_name) or number (key_mode)");
116
117
  }
117
- // arg_count == 0: используем значения по умолчанию (строковый ключ, default db)
118
+ }
119
+ // arg_count == 0: используем значения по умолчанию (строковый ключ, default db)
118
120
 
119
- check();
121
+ if (!txn_) {
122
+ throw Napi::Error::New(env, "txn already completed");
123
+ }
120
124
 
121
- MDBX_dbi dbi{};
122
- auto flags = static_cast<MDBX_db_flags_t>(db_mode.val|key_mode.val|value_mode.val);
123
- auto rc = mdbx_dbi_open(*this, db_name.empty() ? nullptr : db_name.c_str(), flags, &dbi);
124
- if (rc != MDBX_SUCCESS) {
125
- throw std::runtime_error(std::string("mdbx_dbi_open ") + mdbx_strerror(rc));
126
- }
127
- // создаем новый объект dbi
128
- auto obj = dbimou::ctor.New({});
129
- auto ptr = dbimou::Unwrap(obj);
130
- ptr->attach(dbi, db_mode, key_mode,
131
- value_mode, key_flag, value_flag);
132
- return obj;
125
+ MDBX_dbi dbi{};
126
+ auto flags = static_cast<MDBX_db_flags_t>(db_mode.val|key_mode.val|value_mode.val);
127
+ auto rc = mdbx_dbi_open(*this, db_name.empty() ? nullptr : db_name.c_str(), flags, &dbi);
128
+ if (rc != MDBX_SUCCESS) {
129
+ throw Napi::Error::New(env, std::string("mdbx_dbi_open: ") + mdbx_strerror(rc));
133
130
  }
134
- catch(const std::exception& e)
135
- {
136
- throw Napi::Error::New(env, std::string("txn: get_dbi ") + e.what());
131
+ // создаем новый объект dbi
132
+ auto obj = dbimou::ctor.New({});
133
+ auto ptr = dbimou::Unwrap(obj);
134
+ ptr->attach(dbi, db_mode, key_mode,
135
+ value_mode, key_flag, value_flag);
136
+ return obj;
137
+ }
138
+
139
+ Napi::Value txnmou::open_cursor(const Napi::CallbackInfo& info) {
140
+ Napi::Env env = info.Env();
141
+
142
+ if (!txn_) {
143
+ throw Napi::Error::New(env, "txn not active");
137
144
  }
138
145
 
139
- return env.Undefined();
146
+ if (info.Length() < 1 || !info[0].IsObject()) {
147
+ throw Napi::Error::New(env, "dbi required");
148
+ }
149
+
150
+ auto arg0 = info[0].As<Napi::Object>();
151
+ if (!arg0.InstanceOf(dbimou::ctor.Value())) {
152
+ throw Napi::TypeError::New(env, "openCursor: first argument must be MDBX_Dbi instance");
153
+ }
154
+
155
+ auto* dbi = dbimou::Unwrap(arg0);
156
+
157
+ MDBX_cursor* cursor{};
158
+ int rc = mdbx_cursor_open(txn_.get(), dbi->get_id(), &cursor);
159
+
160
+ if (rc != MDBX_SUCCESS) {
161
+ throw Napi::Error::New(env, std::string("mdbx_cursor_open: ") + mdbx_strerror(rc));
162
+ }
163
+
164
+ auto obj = cursormou::ctor.New({});
165
+ auto ptr = cursormou::Unwrap(obj);
166
+ ptr->attach(*this, *dbi, cursor);
167
+ return obj;
140
168
  }
141
169
 
142
170
  Napi::Value txnmou::is_active(const Napi::CallbackInfo& info) {
package/src/txnmou.hpp CHANGED
@@ -10,17 +10,19 @@ class txnmou final
10
10
  : public Napi::ObjectWrap<txnmou>
11
11
  {
12
12
  private:
13
- envmou* env_{nullptr};
13
+ envmou* env_{nullptr};
14
+
15
+ // свободу txn
16
+ struct free_txn
17
+ {
18
+ void operator()(MDBX_txn *txn) const noexcept {
19
+ mdbx_txn_abort(txn);
20
+ }
21
+ };
14
22
 
15
- std::unique_ptr<MDBX_txn,
16
- txnmou_managed::free_txn> txn_{};
23
+ std::unique_ptr<MDBX_txn, free_txn> txn_{};
17
24
  txn_mode mode_{};
18
-
19
- void check() const {
20
- if (!txn_) {
21
- throw std::runtime_error("txn: inactive");
22
- }
23
- }
25
+ std::size_t cursor_count_{};
24
26
 
25
27
  // Уменьшает счетчик транзакций
26
28
  void dec_counter() noexcept;
@@ -40,8 +42,6 @@ public:
40
42
  }
41
43
  }
42
44
 
43
- //void Finalize(Napi::Env env) { fprintf(stderr, "txnmou::Finalize %p\n", this); }
44
-
45
45
  static void init(const char *class_name, Napi::Env env);
46
46
 
47
47
  Napi::Value commit(const Napi::CallbackInfo&);
@@ -52,11 +52,18 @@ public:
52
52
  Napi::Value create_map(const Napi::CallbackInfo& info) {
53
53
  return get_dbi(info, {db_mode::create});
54
54
  }
55
+
56
+ Napi::Value open_cursor(const Napi::CallbackInfo&);
55
57
 
56
- operator MDBX_txn*() const noexcept {
58
+ operator MDBX_txn*() noexcept {
57
59
  return txn_.get();
58
60
  }
59
61
 
62
+ // Cursor counting
63
+ txnmou& operator++() noexcept { ++cursor_count_; return *this; }
64
+ txnmou& operator--() noexcept { --cursor_count_; return *this; }
65
+ std::size_t cursor_count() const noexcept { return cursor_count_; }
66
+
60
67
  Napi::Value is_active(const Napi::CallbackInfo&);
61
68
 
62
69
  void attach(envmou& env, MDBX_txn* txn, txn_mode mode);
package/src/typemou.hpp CHANGED
@@ -4,6 +4,7 @@
4
4
  #include <mdbx.h++>
5
5
  #include <cstdint>
6
6
  #include <vector>
7
+ #include <utility>
7
8
 
8
9
  namespace mdbxmou {
9
10
 
@@ -12,57 +13,39 @@ using buffer_type = std::vector<char>;
12
13
  struct txnmou_managed final
13
14
  : mdbx::txn
14
15
  {
15
- // свободу txn
16
- struct free_txn
17
- {
18
- void operator()(MDBX_txn *txn) const noexcept {
19
- mdbx_txn_abort(txn);
20
- }
21
- };
22
-
23
- std::unique_ptr<MDBX_txn, free_txn> guard_;
24
-
25
- txnmou_managed(MDBX_txn* txn) noexcept
16
+ txnmou_managed(MDBX_txn *txn) noexcept
26
17
  : mdbx::txn(txn)
27
- , guard_(txn)
28
18
  { }
29
-
30
- // Move конструктор
31
- txnmou_managed(txnmou_managed&& other) noexcept
32
- : mdbx::txn(other.txn::handle_)
33
- , guard_(std::move(other.guard_))
34
- {
35
- other.txn::handle_ = nullptr;
36
- }
37
-
38
- // Запрет копирования
19
+
20
+ txnmou_managed(txnmou_managed &&other) noexcept
21
+ : mdbx::txn(std::exchange(other.txn::handle_, nullptr))
22
+ { }
23
+
39
24
  txnmou_managed(const txnmou_managed&) = delete;
40
25
  txnmou_managed& operator=(const txnmou_managed&) = delete;
41
26
 
42
- ~txnmou_managed() noexcept {
43
- // guard_ автоматически откатит транзакцию
44
- txn::handle_ = nullptr;
27
+ ~txnmou_managed() noexcept
28
+ {
29
+ if (txn::handle_) {
30
+ ::mdbx_txn_abort(txn::handle_);
31
+ }
45
32
  }
46
33
 
47
- // Commit транзакции
48
- void commit() {
49
- if (guard_) {
50
- auto rc = ::mdbx_txn_commit(guard_.release());
51
- txn::handle_ = nullptr;
52
- if (rc != MDBX_SUCCESS) {
53
- throw std::runtime_error(::mdbx_strerror(rc));
54
- }
34
+ void commit()
35
+ {
36
+ auto rc = ::mdbx_txn_commit(
37
+ std::exchange(txn::handle_, nullptr));
38
+ if (rc != MDBX_SUCCESS) {
39
+ throw std::runtime_error(::mdbx_strerror(rc));
55
40
  }
56
41
  }
57
42
 
58
- // Abort транзакции
59
- void abort() {
60
- if (guard_) {
61
- auto rc = ::mdbx_txn_abort(guard_.release());
62
- txn::handle_ = nullptr;
63
- if (rc != MDBX_SUCCESS) {
64
- throw std::runtime_error(::mdbx_strerror(rc));
65
- }
43
+ void abort()
44
+ {
45
+ auto rc = ::mdbx_txn_abort(
46
+ std::exchange(txn::handle_, nullptr));
47
+ if (rc != MDBX_SUCCESS) {
48
+ throw std::runtime_error(::mdbx_strerror(rc));
66
49
  }
67
50
  }
68
51
  };
@@ -70,36 +53,22 @@ struct txnmou_managed final
70
53
  struct cursormou_managed final
71
54
  : mdbx::cursor
72
55
  {
73
- // свободу курсору
74
- struct free_cursor
75
- {
76
- void operator()(MDBX_cursor* c) const noexcept {
77
- if (c) ::mdbx_cursor_close(c);
78
- }
79
- };
80
-
81
- std::unique_ptr<MDBX_cursor, free_cursor> guard_;
82
-
83
56
  explicit cursormou_managed(MDBX_cursor* cursor) noexcept
84
57
  : mdbx::cursor(cursor)
85
- , guard_(cursor)
58
+ { }
59
+
60
+ cursormou_managed(cursormou_managed &&other) noexcept
61
+ : mdbx::cursor(std::exchange(other.cursor::handle_, nullptr))
86
62
  { }
87
63
 
88
- // Move конструктор
89
- cursormou_managed(cursormou_managed&& other) noexcept
90
- : mdbx::cursor(other.cursor::handle_)
91
- , guard_(std::move(other.guard_))
92
- {
93
- other.cursor::handle_ = nullptr;
94
- }
95
-
96
- // Запрет копирования
97
64
  cursormou_managed(const cursormou_managed&) = delete;
98
65
  cursormou_managed& operator=(const cursormou_managed&) = delete;
99
66
 
100
- ~cursormou_managed() noexcept {
101
- // guard_ автоматически закроет курсор
102
- cursor::handle_ = nullptr;
67
+ ~cursormou_managed() noexcept
68
+ {
69
+ if (cursor::handle_) {
70
+ ::mdbx_cursor_close(cursor::handle_);
71
+ }
103
72
  }
104
73
  };
105
74
 
package/src/valuemou.hpp CHANGED
@@ -4,11 +4,18 @@
4
4
 
5
5
  namespace mdbxmou {
6
6
 
7
- class valuemou
8
- : public mdbx::slice
7
+ // Обёртка над mdbx::slice — НЕ ВЛАДЕЕТ памятью.
8
+ // Хранит указатель на внешний буфер (buffer_type или stack).
9
+ // Copy и move эквивалентны — просто копируют указатель и размер.
10
+ // ВАЖНО: не использовать после уничтожения буфера!
11
+ struct valuemou
12
+ : mdbx::slice
9
13
  {
10
- public:
11
14
  valuemou() = default;
15
+ valuemou(const valuemou&) = default;
16
+ valuemou& operator=(const valuemou&) = default;
17
+ valuemou(valuemou&&) = default;
18
+ valuemou& operator=(valuemou&&) = default;
12
19
 
13
20
  valuemou(const mdbx::slice& arg0) noexcept
14
21
  : mdbx::slice{arg0}
@@ -39,10 +46,10 @@ public:
39
46
  throw Napi::Error::New(env, "napi_get_value_string_utf8 length");
40
47
  }
41
48
 
42
- mem.reserve(length + 1);
49
+ mem.resize(length + 1);
43
50
 
44
51
  status = napi_get_value_string_utf8(
45
- env, arg0, mem.data(), mem.capacity(), nullptr);
52
+ env, arg0, mem.data(), mem.size(), nullptr);
46
53
  if (status != napi_ok) {
47
54
  throw Napi::Error::New(env, "napi_get_value_string_utf8 copyout");
48
55
  }
@@ -76,11 +83,14 @@ public:
76
83
  }
77
84
  };
78
85
 
79
- class keymou final
80
- : public valuemou
86
+ struct keymou final
87
+ : valuemou
81
88
  {
82
- public:
83
89
  keymou() = default;
90
+ keymou(const keymou&) = default;
91
+ keymou& operator=(const keymou&) = default;
92
+ keymou(keymou&&) = default;
93
+ keymou& operator=(keymou&&) = default;
84
94
 
85
95
  keymou(const valuemou& arg0) noexcept
86
96
  : valuemou{arg0}