jsql-neo 4.0.2 → 4.1.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.
@@ -104,7 +104,7 @@ pub struct Table {
104
104
  pub pk_index: FxHashMap<u64, usize>,
105
105
  }
106
106
 
107
- fn default_value(ft: &FieldType) -> FieldValue {
107
+ pub fn default_value(ft: &FieldType) -> FieldValue {
108
108
  match ft {
109
109
  FieldType::Integer => FieldValue::Int(0),
110
110
  FieldType::Float => FieldValue::Float(0.0),
@@ -230,32 +230,49 @@ impl Table {
230
230
  });
231
231
 
232
232
  for mut fields in fields_batch {
233
- let id = self.next_id;
233
+ let auto_id = self.next_id;
234
234
  self.next_id += 1;
235
+ let mut id = auto_id;
235
236
 
236
237
  if let Some(ref pk_key) = pk_auto_key {
237
238
  match fields.get(pk_key) {
238
- Some(v) if v.is_null() || v.as_i64() == Some(0) => {
239
- fields.insert(pk_key.clone(), serde_json::Value::Number(id.into()));
239
+ Some(v) if v.is_null() => {
240
+ fields.insert(pk_key.clone(), serde_json::Value::Number(auto_id.into()));
240
241
  }
241
242
  Some(v) => {
242
243
  if let Some(n) = v.as_i64() {
243
- if n > 0 { self.next_id = self.next_id.max((n + 1) as u64); }
244
+ if n > 0 {
245
+ id = n as u64;
246
+ self.next_id = self.next_id.max(id + 1);
247
+ }
244
248
  }
245
249
  }
246
250
  None => {
247
- fields.insert(pk_key.clone(), serde_json::Value::Number(id.into()));
251
+ fields.insert(pk_key.clone(), serde_json::Value::Number(auto_id.into()));
248
252
  }
249
253
  }
250
254
  }
251
255
 
252
256
  let values_start = self.values.len();
253
257
  if fields.len() < self.schema.len() {
254
- for (name, fs) in &self.schema {
258
+ for name in &self.field_order {
255
259
  if let Some(v) = fields.remove(name) {
256
260
  self.values.push(FieldValue::from_json(v));
257
- } else {
258
- self.values.push(default_value(&fs.field_type));
261
+ continue;
262
+ }
263
+ match self.schema.get(name).and_then(|fs| fs.default.clone()) {
264
+ Some(dv) => self.values.push(FieldValue::from_json(dv)),
265
+ None => {
266
+ if let Some(fs) = self.schema.get(name) {
267
+ if !fs.nullable {
268
+ self.values.push(default_value(&fs.field_type));
269
+ } else {
270
+ self.values.push(FieldValue::Null);
271
+ }
272
+ } else {
273
+ self.values.push(FieldValue::Null);
274
+ }
275
+ }
259
276
  }
260
277
  }
261
278
  } else {
@@ -299,17 +316,19 @@ impl Table {
299
316
  {
300
317
  let pk_idx = pk_auto_key.as_ref().and_then(|pk| self.field_index.get(pk.as_str()).copied());
301
318
  for mut row_vals in batch {
302
- let id = self.next_id;
319
+ let auto_id = self.next_id;
303
320
  self.next_id += 1;
321
+ let mut id = auto_id;
304
322
  if row_vals.len() < self.field_order.len() {
305
323
  row_vals.resize(self.field_order.len(), FieldValue::Null);
306
324
  }
307
325
  if let Some(pi) = pk_idx {
308
326
  if pi < row_vals.len() {
309
327
  match row_vals[pi] {
310
- FieldValue::Null | FieldValue::Int(0) => row_vals[pi] = FieldValue::Int(id as i64),
328
+ FieldValue::Null => row_vals[pi] = FieldValue::Int(auto_id as i64),
311
329
  FieldValue::Int(e) if e > 0 => {
312
- self.next_id = self.next_id.max((e + 1) as u64);
330
+ id = e as u64;
331
+ self.next_id = self.next_id.max(id + 1);
313
332
  }
314
333
  _ => {}
315
334
  }
@@ -331,26 +350,25 @@ impl Table {
331
350
  .collect();
332
351
  let explicit_pk_si = pk_auto_key.as_ref()
333
352
  .and_then(|pk| field_names.iter().position(|n| n == pk));
353
+ let pk_idx = pk_auto_key.as_ref().and_then(|pk| self.field_index.get(pk.as_str()).copied());
334
354
 
335
355
  for mut row_vals in batch {
336
- let id = self.next_id;
356
+ let auto_id = self.next_id;
337
357
  self.next_id += 1;
358
+ let mut id = auto_id;
338
359
  if let Some(si) = explicit_pk_si {
339
360
  if let Some(FieldValue::Int(e)) = row_vals.get(si) {
340
- if *e > 0 { self.next_id = self.next_id.max((*e + 1) as u64); }
361
+ if *e > 0 {
362
+ id = *e as u64;
363
+ self.next_id = self.next_id.max(id + 1);
364
+ }
341
365
  }
342
366
  }
343
367
  let values_start = self.values.len();
344
368
  for (i, remapped_si) in remap.iter().enumerate() {
345
369
  if let Some(si) = *remapped_si {
346
370
  if si < row_vals.len() {
347
- let v = std::mem::take(&mut row_vals[si]);
348
- let is_auto_pk = pk_auto_key.is_some() && self.field_order[i] == *pk_auto_key.as_ref().unwrap();
349
- if is_auto_pk && matches!(v, FieldValue::Null | FieldValue::Int(0)) {
350
- self.values.push(FieldValue::Int(id as i64));
351
- } else {
352
- self.values.push(v);
353
- }
371
+ self.values.push(std::mem::take(&mut row_vals[si]));
354
372
  continue;
355
373
  }
356
374
  }
@@ -374,6 +392,14 @@ impl Table {
374
392
  }
375
393
  }
376
394
  }
395
+ // 显式传入 null 的主键:回填自动 id,保证 pk 值与行 id 一致
396
+ if let Some(pi) = pk_idx {
397
+ if let Some(v) = self.values.get_mut(values_start + pi) {
398
+ if *v == FieldValue::Null {
399
+ *v = FieldValue::Int(id as i64);
400
+ }
401
+ }
402
+ }
377
403
  let idx = self.rows.len();
378
404
  self.pk_index.insert(id, idx);
379
405
  self.rows.push(RowStore { id, values_start, num_values: self.field_order.len(), created_at: now, updated_at: now });
@@ -433,7 +459,7 @@ impl Table {
433
459
 
434
460
  fn to_row_json_string(&self, rs: &RowStore) -> String {
435
461
  let vals = self.get_row_values(rs);
436
- let mut out = String::with_capacity(96);
462
+ let mut out = String::with_capacity(160);
437
463
  out.push_str(r#"{"id":"#);
438
464
  out.push_str(itoa::Buffer::new().format(rs.id));
439
465
  out.push_str(r#","fields":{"#);
@@ -447,7 +473,11 @@ impl Table {
447
473
  out.push_str("null");
448
474
  }
449
475
  }
450
- out.push_str("}}");
476
+ out.push_str(r#"},"created_at":"#);
477
+ write_json_string(&mut out, &millis_to_string(rs.created_at));
478
+ out.push_str(r#","updated_at":"#);
479
+ write_json_string(&mut out, &millis_to_string(rs.updated_at));
480
+ out.push_str("}");
451
481
  out
452
482
  }
453
483
 
@@ -1 +1,2 @@
1
- pub mod persistent;
1
+ pub mod persistent;
2
+ pub mod wal;
@@ -1,4 +1,5 @@
1
1
  use serde::{Deserialize, Serialize};
2
+ use std::io::{Read, Write};
2
3
 
3
4
  #[derive(Debug, Clone, Serialize, Deserialize)]
4
5
  pub enum WalOp {
@@ -0,0 +1,3 @@
1
+ fn main() {
2
+ napi_build::setup();
3
+ }
@@ -9,6 +9,26 @@ thread_local! {
9
9
  static ENGINE: RefCell<HybridEngine> = RefCell::new(HybridEngine::new());
10
10
  }
11
11
 
12
+ fn with_engine_mut<F>(f: F) -> String
13
+ where
14
+ F: FnOnce(&mut HybridEngine) -> String,
15
+ {
16
+ ENGINE.with(|cell| match cell.try_borrow_mut() {
17
+ Ok(mut eng) => f(&mut eng),
18
+ Err(_) => r#"{"ok":false,"error":"engine busy (reentrant call)"}"#.to_string(),
19
+ })
20
+ }
21
+
22
+ fn with_engine_ro<F>(f: F) -> String
23
+ where
24
+ F: FnOnce(&HybridEngine) -> String,
25
+ {
26
+ ENGINE.with(|cell| match cell.try_borrow() {
27
+ Ok(eng) => f(&eng),
28
+ Err(_) => r#"{"ok":false,"error":"engine busy (reentrant call)"}"#.to_string(),
29
+ })
30
+ }
31
+
12
32
  fn safe_json_result(result: Result<String, String>) -> String {
13
33
  match result {
14
34
  Ok(s) => s,
@@ -18,11 +38,11 @@ fn safe_json_result(result: Result<String, String>) -> String {
18
38
 
19
39
  #[napi]
20
40
  pub fn jsql_open(dir: String, mode: String) -> String {
21
- ENGINE.with(|eng| {
22
- let open_res = eng.borrow_mut().open(&dir, &mode);
41
+ with_engine_mut(|eng| {
42
+ let open_res = eng.open(&dir, &mode);
23
43
  match open_res {
24
44
  Ok(()) => {
25
- let names = eng.borrow().list_tables();
45
+ let names = eng.list_tables();
26
46
  let mut out = serde_json::Map::new();
27
47
  out.insert("ok".into(), serde_json::Value::Bool(true));
28
48
  out.insert("tables".into(), serde_json::Value::Array(
@@ -30,11 +50,7 @@ pub fn jsql_open(dir: String, mode: String) -> String {
30
50
  ));
31
51
  let mut schemas = serde_json::Map::new();
32
52
  for n in &names {
33
- let s = {
34
- let eng_ref = eng.borrow();
35
- eng_ref.table_schema(n)
36
- };
37
- if let Some(s) = s {
53
+ if let Some(s) = eng.table_schema(n) {
38
54
  let mut v = serde_json::Map::new();
39
55
  for (k, fs) in &s {
40
56
  if let Ok(fv) = serde_json::to_value(fs) {
@@ -54,8 +70,8 @@ pub fn jsql_open(dir: String, mode: String) -> String {
54
70
 
55
71
  #[napi]
56
72
  pub fn jsql_flush_dirty() -> String {
57
- ENGINE.with(|eng| {
58
- match eng.borrow().flush_dirty() {
73
+ with_engine_mut(|eng| {
74
+ match eng.flush_dirty() {
59
75
  Ok(n) => format!(r#"{{"ok":true,"flushed":{}}}"#, n),
60
76
  Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e.replace('"', r#"\""#)),
61
77
  }
@@ -64,9 +80,9 @@ pub fn jsql_flush_dirty() -> String {
64
80
 
65
81
  #[napi]
66
82
  pub fn jsql_evict() -> String {
67
- ENGINE.with(|eng| {
68
- match eng.borrow().evict_one() {
69
- Some(name) => format!(r#"{{"ok":true,"evicted":"{}","remaining":{}}}"#, name, eng.borrow().mem_count()),
83
+ with_engine_mut(|eng| {
84
+ match eng.evict_one() {
85
+ Some(name) => format!(r#"{{"ok":true,"evicted":"{}","remaining":{}}}"#, name, eng.mem_count()),
70
86
  None => r#"{"ok":true,"evicted":null,"remaining":0}"#.to_string(),
71
87
  }
72
88
  })
@@ -74,8 +90,8 @@ pub fn jsql_evict() -> String {
74
90
 
75
91
  #[napi]
76
92
  pub fn jsql_close() -> String {
77
- ENGINE.with(|eng| {
78
- eng.borrow_mut().close();
93
+ with_engine_mut(|eng| {
94
+ eng.close();
79
95
  r#"{"ok":true}"#.to_string()
80
96
  })
81
97
  }
@@ -89,7 +105,7 @@ pub fn jsql_create_table(name: String, schema_json: String) -> String {
89
105
  Err(e) => return format!(r#"{{"ok":false,"error":"invalid schema: {}"}}"#, e),
90
106
  };
91
107
  let def = TableDefinition { name: name.clone(), schema };
92
- ENGINE.with(|eng| match eng.borrow_mut().create_table(def) {
108
+ with_engine_mut(|eng| match eng.create_table(def) {
93
109
  Ok(()) => r#"{"ok":true}"#.to_string(),
94
110
  Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
95
111
  })
@@ -97,7 +113,7 @@ pub fn jsql_create_table(name: String, schema_json: String) -> String {
97
113
 
98
114
  #[napi]
99
115
  pub fn jsql_drop_table(name: String) -> String {
100
- ENGINE.with(|eng| match eng.borrow_mut().drop_table(&name) {
116
+ with_engine_mut(|eng| match eng.drop_table(&name) {
101
117
  Ok(()) => r#"{"ok":true}"#.to_string(),
102
118
  Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
103
119
  })
@@ -105,77 +121,27 @@ pub fn jsql_drop_table(name: String) -> String {
105
121
 
106
122
  #[napi]
107
123
  pub fn jsql_insert(table: String, data_json: String) -> String {
108
- ENGINE.with(|eng| {
124
+ with_engine_mut(|eng| {
109
125
  let data: Vec<HashMap<String, serde_json::Value>> = match serde_json::from_str(&data_json) {
110
126
  Ok(d) => d,
111
127
  Err(e) => return format!(r#"{{"error":"invalid data: {}"}}"#, e),
112
128
  };
113
- match eng.borrow_mut().insert(&table, data) {
129
+ match eng.insert(&table, data) {
114
130
  Ok(ids) => serde_json::to_string(&ids).unwrap_or_else(|_| "[]".to_string()),
115
131
  Err(e) => format!(r#"{{"error":"{}"}}"#, e),
116
132
  }
117
133
  })
118
134
  }
119
135
 
120
- #[inline(always)]
121
- unsafe fn parse_field_value_unsafe(data: &[u8], off: &mut usize) -> FieldValue {
122
- let tag = *data.get_unchecked(*off); *off += 1;
123
- match tag {
124
- 0 => FieldValue::Null,
125
- 1 => {
126
- let n = i64::from_le_bytes([
127
- *data.get_unchecked(*off), *data.get_unchecked(*off+1),
128
- *data.get_unchecked(*off+2), *data.get_unchecked(*off+3),
129
- *data.get_unchecked(*off+4), *data.get_unchecked(*off+5),
130
- *data.get_unchecked(*off+6), *data.get_unchecked(*off+7),
131
- ]);
132
- *off += 8;
133
- FieldValue::Int(n)
134
- }
135
- 2 => {
136
- let n = f64::from_le_bytes([
137
- *data.get_unchecked(*off), *data.get_unchecked(*off+1),
138
- *data.get_unchecked(*off+2), *data.get_unchecked(*off+3),
139
- *data.get_unchecked(*off+4), *data.get_unchecked(*off+5),
140
- *data.get_unchecked(*off+6), *data.get_unchecked(*off+7),
141
- ]);
142
- *off += 8;
143
- FieldValue::Float(n)
144
- }
145
- 3 => {
146
- let len = u32::from_le_bytes([
147
- *data.get_unchecked(*off), *data.get_unchecked(*off+1),
148
- *data.get_unchecked(*off+2), *data.get_unchecked(*off+3),
149
- ]) as usize;
150
- *off += 4;
151
- let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
152
- data.as_ptr().add(*off), len
153
- ));
154
- *off += len;
155
- FieldValue::String(s.to_string())
156
- }
157
- 4 => { let b = *data.get_unchecked(*off) != 0; *off += 1; FieldValue::Bool(b) }
158
- 5 => {
159
- let n = i32::from_le_bytes([
160
- *data.get_unchecked(*off), *data.get_unchecked(*off+1),
161
- *data.get_unchecked(*off+2), *data.get_unchecked(*off+3),
162
- ]) as i64;
163
- *off += 4;
164
- FieldValue::Int(n)
165
- }
166
- _ => FieldValue::Null,
167
- }
168
- }
169
-
170
136
  #[napi]
171
137
  pub fn jsql_insert_buf(table: String, data: JsBuffer) -> String {
172
138
  let buf_val = match data.into_value() {
173
139
  Ok(v) => v,
174
140
  Err(_) => return r#"{"error":"invalid buffer"}"#.to_string(),
175
141
  };
176
- ENGINE.with(|eng| {
142
+ with_engine_mut(|eng| {
177
143
  let buf: &[u8] = buf_val.as_ref();
178
- let res = eng.borrow().insert_buf(&table, buf)
144
+ let res = eng.insert_buf(&table, buf)
179
145
  .map(|ids| serde_json::to_string(&ids).unwrap_or_else(|_| "[]".to_string()));
180
146
  safe_json_result(res.map_err(|e| e))
181
147
  })
@@ -183,10 +149,10 @@ pub fn jsql_insert_buf(table: String, data: JsBuffer) -> String {
183
149
 
184
150
  #[napi]
185
151
  pub fn jsql_find(table: String, filter_json: String, limit: i32, offset: i32) -> String {
186
- ENGINE.with(|eng| {
152
+ with_engine_ro(|eng| {
187
153
  let filter: Option<HashMap<String, serde_json::Value>> =
188
154
  if filter_json.is_empty() { None } else { serde_json::from_str(&filter_json).ok() };
189
- match eng.borrow().find(&table, &filter, Some(limit as usize), Some(offset as usize), None, &None, &None) {
155
+ match eng.find(&table, &filter, Some(limit as usize), Some(offset as usize), None, &None, &None) {
190
156
  Ok((rows, _, _)) => serde_json::to_string(&rows).unwrap_or_else(|_| "[]".to_string()),
191
157
  Err(e) => format!(r#"{{"error":"{}"}}"#, e),
192
158
  }
@@ -195,8 +161,8 @@ pub fn jsql_find(table: String, filter_json: String, limit: i32, offset: i32) ->
195
161
 
196
162
  #[napi]
197
163
  pub fn jsql_find_by_id(table: String, id: i64) -> String {
198
- ENGINE.with(|eng| {
199
- match eng.borrow().find_by_id_json(&table, id as u64) {
164
+ with_engine_ro(|eng| {
165
+ match eng.find_by_id_json(&table, id as u64) {
200
166
  Ok(Some(s)) => s,
201
167
  Ok(None) => "null".to_string(),
202
168
  Err(e) => format!(r#"{{"error":"{}"}}"#, e),
@@ -204,11 +170,9 @@ pub fn jsql_find_by_id(table: String, id: i64) -> String {
204
170
  })
205
171
  }
206
172
 
207
-
208
-
209
173
  #[napi]
210
174
  pub fn jsql_count(table: String) -> String {
211
- ENGINE.with(|eng| match eng.borrow().count(&table) {
175
+ with_engine_ro(|eng| match eng.count(&table) {
212
176
  Ok(c) => format!("{}", c),
213
177
  Err(e) => format!(r#"{{"error":"{}"}}"#, e),
214
178
  })
@@ -216,12 +180,12 @@ pub fn jsql_count(table: String) -> String {
216
180
 
217
181
  #[napi]
218
182
  pub fn jsql_update_by_id(table: String, id: i64, data_json: String) -> String {
219
- ENGINE.with(|eng| {
183
+ with_engine_mut(|eng| {
220
184
  let data: HashMap<String, serde_json::Value> = match serde_json::from_str(&data_json) {
221
185
  Ok(d) => d,
222
186
  Err(e) => return format!(r#"{{"ok":false,"error":"invalid data: {}"}}"#, e),
223
187
  };
224
- match eng.borrow_mut().update_by_id(&table, id as u64, data) {
188
+ match eng.update_by_id(&table, id as u64, data) {
225
189
  Ok(true) => r#"{"ok":true}"#.to_string(),
226
190
  Ok(false) => r#"{"ok":false,"error":"not found"}"#.to_string(),
227
191
  Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
@@ -231,7 +195,7 @@ pub fn jsql_update_by_id(table: String, id: i64, data_json: String) -> String {
231
195
 
232
196
  #[napi]
233
197
  pub fn jsql_remove_by_id(table: String, id: i64) -> String {
234
- ENGINE.with(|eng| match eng.borrow_mut().remove_by_id(&table, id as u64) {
198
+ with_engine_mut(|eng| match eng.remove_by_id(&table, id as u64) {
235
199
  Ok(true) => r#"{"ok":true}"#.to_string(),
236
200
  Ok(false) => r#"{"ok":false,"error":"not found"}"#.to_string(),
237
201
  Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
@@ -240,12 +204,12 @@ pub fn jsql_remove_by_id(table: String, id: i64) -> String {
240
204
 
241
205
  #[napi]
242
206
  pub fn jsql_remove_by_ids(table: String, ids_json: String) -> String {
243
- ENGINE.with(|eng| {
207
+ with_engine_mut(|eng| {
244
208
  let ids: Vec<u64> = match serde_json::from_str(&ids_json) {
245
209
  Ok(v) => v,
246
210
  Err(e) => return format!(r#"{{"error":"invalid ids: {}"}}"#, e),
247
211
  };
248
- match eng.borrow_mut().remove_by_ids(&table, &ids) {
212
+ match eng.remove_by_ids(&table, &ids) {
249
213
  Ok(n) => format!(r#"{{"ok":true,"count":{}}}"#, n),
250
214
  Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
251
215
  }
@@ -254,12 +218,12 @@ pub fn jsql_remove_by_ids(table: String, ids_json: String) -> String {
254
218
 
255
219
  #[napi]
256
220
  pub fn jsql_update_by_ids(table: String, batch_json: String) -> String {
257
- ENGINE.with(|eng| {
221
+ with_engine_mut(|eng| {
258
222
  let batch: Vec<(u64, HashMap<String, serde_json::Value>)> = match serde_json::from_str(&batch_json) {
259
223
  Ok(v) => v,
260
224
  Err(e) => return format!(r#"{{"error":"invalid batch: {}"}}"#, e),
261
225
  };
262
- match eng.borrow_mut().update_by_ids(&table, batch) {
226
+ match eng.update_by_ids(&table, batch) {
263
227
  Ok(n) => format!(r#"{{"ok":true,"count":{}}}"#, n),
264
228
  Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
265
229
  }
@@ -268,14 +232,38 @@ pub fn jsql_update_by_ids(table: String, batch_json: String) -> String {
268
232
 
269
233
  #[napi]
270
234
  pub fn jsql_find_by_ids(table: String, ids_json: String) -> String {
271
- ENGINE.with(|eng| {
235
+ with_engine_ro(|eng| {
272
236
  let ids: Vec<u64> = match serde_json::from_str(&ids_json) {
273
237
  Ok(v) => v,
274
238
  Err(e) => return format!(r#"{{"error":"invalid ids: {}"}}"#, e),
275
239
  };
276
- match eng.borrow().find_by_ids_json(&table, &ids) {
240
+ match eng.find_by_ids_json(&table, &ids) {
277
241
  Ok(s) => s,
278
242
  Err(e) => format!(r#"{{"error":"{}"}}"#, e),
279
243
  }
280
244
  })
281
245
  }
246
+
247
+ #[napi]
248
+ pub fn jsql_begin_tx() -> String {
249
+ with_engine_mut(|eng| match eng.begin_tx() {
250
+ Ok(tx_id) => serde_json::json!({ "ok": true, "txId": tx_id }).to_string(),
251
+ Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
252
+ })
253
+ }
254
+
255
+ #[napi]
256
+ pub fn jsql_commit_tx(tx_id: String) -> String {
257
+ with_engine_mut(|eng| match eng.commit_tx(&tx_id) {
258
+ Ok(()) => r#"{"ok":true}"#.to_string(),
259
+ Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
260
+ })
261
+ }
262
+
263
+ #[napi]
264
+ pub fn jsql_rollback_tx(tx_id: String) -> String {
265
+ with_engine_mut(|eng| match eng.rollback_tx(&tx_id) {
266
+ Ok(()) => r#"{"ok":true}"#.to_string(),
267
+ Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
268
+ })
269
+ }