nesoi 3.3.30 → 3.4.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.
@@ -22,8 +22,9 @@ class TrxStatus {
22
22
  state;
23
23
  output;
24
24
  error;
25
+ idempotent;
25
26
  nodes;
26
- constructor(id, origin, start, end, state, output, error, nodes = []) {
27
+ constructor(id, origin, start, end, state, output, error, idempotent = false, nodes = []) {
27
28
  this.id = id;
28
29
  this.origin = origin;
29
30
  this.start = start;
@@ -31,10 +32,11 @@ class TrxStatus {
31
32
  this.state = state;
32
33
  this.output = output;
33
34
  this.error = error;
35
+ this.idempotent = idempotent;
34
36
  this.nodes = nodes;
35
37
  }
36
38
  summary() {
37
- const state = this.state ? (0, string_1.colored)(`[${this.state}]`, {
39
+ const state = this.state ? (0, string_1.colored)(`[${this.state}]${this.idempotent ? '*' : ''}`, {
38
40
  'open': 'lightblue',
39
41
  'hold': 'yellow',
40
42
  'ok': 'lightgreen',
@@ -42,10 +44,13 @@ class TrxStatus {
42
44
  }[this.state]) : 'unknown';
43
45
  let str = `${state} ${this.id} ${(0, log_1.anyScopeTag)(this.origin)} `;
44
46
  str += (0, string_1.colored)(`[${this.end ? (this.end.epoch - this.start.epoch) : -1}ms]\n`, 'brown');
45
- function print(nodes, l = 1) {
47
+ function print(nodes, idempotent, l = 1) {
46
48
  let str = '';
47
49
  nodes.forEach(node => {
48
- const state = node.state ? (0, string_1.colored)(`[${node.state}]`, {
50
+ if (node.ext?.idempotent !== undefined) {
51
+ idempotent = node.ext.idempotent;
52
+ }
53
+ const state = node.state ? (0, string_1.colored)(`[${node.state}]${idempotent ? '*' : ''}`, {
49
54
  'open': 'lightblue',
50
55
  'hold': 'yellow',
51
56
  'ok': 'lightgreen',
@@ -53,11 +58,11 @@ class TrxStatus {
53
58
  }[node.state] || 'lightred') : 'unknown';
54
59
  str += `${'-'.repeat(l)}${state} ${node.id} ${(0, log_1.anyScopeTag)(node.scope)} ${node.action} ${node.cached_buckets > 0 ? `[cached: ${node.cached_buckets}]` : ''}`;
55
60
  str += (0, string_1.colored)(` [${node.app}ms]\n`, 'brown');
56
- str += print(node.nodes, l + 1);
61
+ str += print(node.nodes, idempotent, l + 1);
57
62
  });
58
63
  return str;
59
64
  }
60
- return str + print(this.nodes);
65
+ return str + print(this.nodes, this.idempotent);
61
66
  }
62
67
  }
63
68
  exports.TrxStatus = TrxStatus;
@@ -83,7 +88,7 @@ class Trx {
83
88
  this.id = id || (Math.random() + 1).toString(36).substring(7);
84
89
  this.origin = origin;
85
90
  this.idempotent = idempotent ?? false;
86
- this.root = root || new trx_node_1.TrxNode('root', this, undefined, module, auth, false, id);
91
+ this.root = root || new trx_node_1.TrxNode('root', this, undefined, module, auth, false);
87
92
  this.nodes = nodes || {};
88
93
  }
89
94
  addNode(node) {
@@ -94,7 +99,7 @@ class Trx {
94
99
  const state = this.root.state;
95
100
  const output = this.root.output;
96
101
  const error = this.root.error;
97
- return new TrxStatus(this.id, this.origin, this.start, this.end, state, output, error, this.root.status().nodes);
102
+ return new TrxStatus(this.id, this.origin, this.start, this.end, state, output, error, this.idempotent, this.root.status().nodes);
98
103
  }
99
104
  /**
100
105
  * Cache
@@ -146,13 +151,12 @@ class Trx {
146
151
  return trx;
147
152
  }
148
153
  //
149
- static async onCommit(trx) {
154
+ static async commitHolds(trx) {
150
155
  for (const h in trx.holds) {
151
156
  await trx.holds[h].commit();
152
157
  }
153
158
  }
154
- static async onRollback(trx) {
155
- trx.end = datetime_1.NesoiDatetime.now();
159
+ static async rollbackHolds(trx) {
156
160
  for (const h in trx.holds) {
157
161
  const error = `rollback ${trx.id}`;
158
162
  await trx.holds[h].rollback(error);
@@ -1,4 +1,4 @@
1
- import { $Module, $Space } from "../../elements";
1
+ import { $Bucket, $Module, $Space } from "../../elements";
2
2
  import { BucketAdapter } from "../../elements/entities/bucket/adapters/bucket_adapter";
3
3
  import { Trx } from './trx';
4
4
  import { AnyUsers } from '../auth/authn';
@@ -7,9 +7,13 @@ import { TrxData } from './trx_engine';
7
7
  export type TrxEngineWrapFn<S extends $Space, M extends $Module> = (trx: TrxNode<S, M, any>) => Promise<any>;
8
8
  export type TrxEngineConfig<S extends $Space, M extends $Module, AuthUsers extends AnyUsers, Services extends Record<string, any>> = {
9
9
  /**
10
- * Adapter used to store transactions of this module.
10
+ * Adapter used to temporarily store transactions of this module, while they happen.
11
11
  */
12
- adapter?: (schema: M) => BucketAdapter<TrxData>;
12
+ adapter?: (schema: $Bucket) => BucketAdapter<TrxData>;
13
+ /**
14
+ * Adapter used to log transactions of this module once they're finished.
15
+ */
16
+ log_adapter?: (schema: $Bucket) => BucketAdapter<TrxData>;
13
17
  wrap?: {
14
18
  begin: <T extends Trx<S, M, AuthUsers>>(trx: T, services: Services) => Promise<void>;
15
19
  continue: <T extends Trx<S, M, AuthUsers>>(trx: T, services: Services) => Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { $Module, $Space } from "../../schema";
2
2
  import { Module } from '../module';
3
3
  import { AnyTrx, Trx, TrxStatus } from './trx';
4
- import { TrxNode, TrxNodeStatus } from './trx_node';
4
+ import { TrxNode, TrxNodeState, TrxNodeStatus } from './trx_node';
5
5
  import { AnyAuthnProviders, AnyUsers, AuthRequest } from '../auth/authn';
6
6
  import { BucketAdapterConfig } from "../../elements/entities/bucket/adapters/bucket_adapter";
7
7
  import { TrxEngineConfig } from './trx_engine.config';
@@ -12,6 +12,7 @@ import { DriveAdapter } from "../../elements/entities/drive/drive_adapter";
12
12
  export type TrxEngineOrigin = `app:${string}` | `plugin:${string}`;
13
13
  export type TrxData = {
14
14
  id: AnyTrx['id'];
15
+ state: TrxNodeState;
15
16
  origin: AnyTrx['origin'];
16
17
  module: string;
17
18
  start: AnyTrx['start'];
@@ -43,16 +44,19 @@ export declare class TrxEngine<S extends $Space, M extends $Module, AuthUsers ex
43
44
  */
44
45
  private innerTrx;
45
46
  private adapter;
47
+ private log_adapter?;
46
48
  constructor(origin: TrxEngineOrigin, module: Module<S, M>, authnProviders?: AuthUsers | undefined, config?: TrxEngineConfig<S, M, any, any> | undefined, services?: Record<string, IService>);
47
49
  getModule(): Module<S, M>;
48
- get(id?: string, origin?: string, idempotent?: boolean): Promise<Trx<S, M, any>>;
50
+ get(id?: string, origin?: string, req_idempotent?: boolean): Promise<Trx<S, M, any>>;
49
51
  trx(fn: (trx: TrxNode<S, M, any>) => Promise<TrxNodeStatus>, id?: string, tokens?: AuthRequest<keyof AuthUsers>, users?: Partial<AuthUsers>, origin?: string, idempotent?: boolean): Promise<TrxStatus<any>>;
50
- trx_hold(fn: (trx: TrxNode<S, M, any>) => Promise<any>, id?: string, authn?: AuthRequest<keyof AuthUsers>, users?: Partial<AuthUsers>, origin?: string, idempotent?: boolean): Promise<HeldTrxNode<any>>;
52
+ trx_hold(fn: (trx: TrxNode<S, M, any>) => Promise<any>, id?: string, authn?: AuthRequest<keyof AuthUsers>, users?: Partial<AuthUsers>, origin?: string): Promise<HeldTrxNode<any>>;
51
53
  getBucketMetadata(tag: Tag): BucketMetadata;
52
54
  getBucketDrive(tag: Tag): DriveAdapter | undefined;
53
55
  authenticate(node: TrxNode<S, M, any>, tokens?: AuthRequest<keyof AuthUsers>, users?: AnyUsers, force?: boolean): Promise<void>;
54
56
  private hold;
57
+ private ok;
55
58
  private commit;
59
+ private error;
56
60
  private rollback;
57
61
  }
58
62
  export type AnyTrxEngine = TrxEngine<any, any, any>;
@@ -26,6 +26,7 @@ class TrxEngine {
26
26
  */
27
27
  innerTrx;
28
28
  adapter;
29
+ log_adapter;
29
30
  constructor(origin, module, authnProviders, config, services = {}) {
30
31
  this.origin = origin;
31
32
  this.module = module;
@@ -35,29 +36,35 @@ class TrxEngine {
35
36
  this.innerTrx = new trx_1.Trx(this, this.module, `trx:${origin}`, true);
36
37
  this.$TrxBucket = new elements_1.$Bucket(this.module.name, '__trx__', `Transaction of Module '${this.module.name}'`, new bucket_model_schema_1.$BucketModel({
37
38
  id: new bucket_model_schema_1.$BucketModelField('id', 'id', 'string', 'ID', true),
39
+ state: new bucket_model_schema_1.$BucketModelField('state', 'state', 'string', 'State', true), // TrxNodeState
38
40
  origin: new bucket_model_schema_1.$BucketModelField('origin', 'origin', 'string', 'Origin', true),
39
41
  module: new bucket_model_schema_1.$BucketModelField('module', 'module', 'string', 'Module', true),
40
42
  start: new bucket_model_schema_1.$BucketModelField('start', 'start', 'datetime', 'Start', true),
41
- end: new bucket_model_schema_1.$BucketModelField('end', 'end', 'datetime', 'End', false),
43
+ end: new bucket_model_schema_1.$BucketModelField('end', 'end', 'datetime', 'End', false)
42
44
  }), new bucket_graph_schema_1.$BucketGraph(), {});
43
- this.adapter = config?.adapter?.(module.schema) || new memory_bucket_adapter_1.MemoryBucketAdapter(this.$TrxBucket, {});
45
+ this.adapter = config?.adapter?.(this.$TrxBucket) || new memory_bucket_adapter_1.MemoryBucketAdapter(this.$TrxBucket, {});
46
+ if (config?.log_adapter) {
47
+ this.log_adapter = config?.log_adapter?.(this.$TrxBucket);
48
+ }
44
49
  }
45
50
  getModule() {
46
51
  return this.module;
47
52
  }
48
- async get(id, origin, idempotent = false) {
53
+ async get(id, origin, req_idempotent = false) {
49
54
  const _origin = origin ?? this.origin;
50
55
  let trx;
51
56
  // New transaction
52
57
  if (!id) {
53
- trx = new trx_1.Trx(this, this.module, _origin, idempotent);
54
- log_1.Log.info('module', this.module.name, `Begin${idempotent ? '*' : ''} ${(0, log_1.scopeTag)('trx', trx.id + trx.root.id)} @ ${(0, log_1.anyScopeTag)(_origin)}`);
58
+ trx = new trx_1.Trx(this, this.module, _origin, req_idempotent);
59
+ log_1.Log.info('module', this.module.name, `Begin${req_idempotent ? '*' : ''} ${(0, log_1.scopeTag)('trx', trx.root.globalId)} @ ${(0, log_1.anyScopeTag)(_origin)}`);
55
60
  for (const wrap of this.config?.wrap || []) {
61
+ // The wrappers decide how to begin a db transaction, based on the trx idempotent flag.
56
62
  await wrap.begin(trx, this.services);
57
63
  }
58
- if (!idempotent) {
64
+ if (!req_idempotent) {
59
65
  await this.adapter.create(this.innerTrx.root, {
60
66
  id: trx.id,
67
+ state: 'open',
61
68
  origin: trx.origin,
62
69
  start: trx.start,
63
70
  end: trx.end,
@@ -69,49 +76,78 @@ class TrxEngine {
69
76
  // Chain/Continue transaction
70
77
  else {
71
78
  const trxData = await this.adapter.get(this.innerTrx.root, id);
79
+ // If trxData exists, the transaction to which it refers is non-idempotent,
80
+ // since idempotent transactions are not stored.
81
+ //
72
82
  // If a transaction is being continued it cannot become idempotent,
73
83
  // since the data it needs is inside the transaction.
74
- // > Started as NOT idempotent on this module, is NEVER idempotent
75
- // This only affects services which need this info for deciding whether
76
- // to commit/rollback a transaction. The trx engine still uses the requested
77
- // idempotent value to decide whether to run or not a commit/rollback,
78
- // because otherwise it would commit/rollback partially idempotent transactions
79
- // before they're actually finished.
80
- if (trxData)
81
- idempotent = false;
84
+ //
85
+ // If the transaction started as non-idempotent and is continued as idempotent,
86
+ // the code below will transform it into non-idempotent, however
87
+ // the engine.ok and engine.error still treat it as idempotent, to avoid commit/rollback
88
+ // of the original transaction.
89
+ //
82
90
  // Differently, a transaction with specific id which doesn't exist can
83
91
  // be either an ongoing idempotent transaction from this module or an
84
92
  // external transaction starting at a different module.
85
- // On either case, it's allowed to become idempotent.
86
- // > Not stored, can become idempotent.
87
- // if (!trxData) idempotent = idempotent;
93
+ // On either case, it's allowed to be idempotent or not.
94
+ const idempotent = trxData ? false : req_idempotent;
88
95
  trx = new trx_1.Trx(this, this.module, _origin, idempotent, undefined, id);
89
- // (Continue*)
90
- if (idempotent) {
91
- log_1.Log.info('module', this.module.name, `Continue* ${(0, log_1.scopeTag)('trx', id)} @ ${(0, log_1.anyScopeTag)(_origin)}`);
92
- for (const wrap of this.config?.wrap || []) {
93
- await wrap.continue(trx, this.services);
96
+ // (Begin/Continue*)
97
+ // The request is for an idempotent transaction.
98
+ if (req_idempotent) {
99
+ // A non-idempotent transaction with the same id exists on this module.
100
+ // so it's being continued as an idempotent transaction.
101
+ if (trxData) {
102
+ log_1.Log.info('module', this.module.name, `Continue* ${(0, log_1.scopeTag)('trx', trx.root.globalId)} @ ${(0, log_1.anyScopeTag)(_origin)}`);
103
+ if (trxData.state !== 'hold') {
104
+ throw new Error(`Attempt to continue transaction ${trxData.id}, currently at '${trxData.state}', failed. Should be at 'hold'. This might mean there are parallel attempts to continue a transaction, which must be handled with a queue.`);
105
+ }
106
+ for (const wrap of this.config?.wrap || []) {
107
+ // The wrappers decide how to continue a db transaction, based on the trx idempotent flag.
108
+ await wrap.continue(trx, this.services);
109
+ }
110
+ }
111
+ // No transaction with the same id exists on this module.
112
+ // so it's starting as an idempotent transaction.
113
+ else {
114
+ log_1.Log.info('module', this.module.name, `Begin* ${(0, log_1.scopeTag)('trx', trx.root.globalId)} @ ${(0, log_1.anyScopeTag)(_origin)}`);
115
+ for (const wrap of this.config?.wrap || []) {
116
+ // The wrappers decide how to begin a db transaction, based on the trx idempotent flag.
117
+ await wrap.begin(trx, this.services);
118
+ }
94
119
  }
95
120
  return trx;
96
121
  }
97
122
  // (Continue)
123
+ // A non-idempotent transaction with the same id exists on this module,
124
+ // so it's being continued.
98
125
  if (trxData) {
126
+ log_1.Log.info('module', this.module.name, `Continue ${(0, log_1.scopeTag)('trx', trx.root.globalId)} @ ${(0, log_1.anyScopeTag)(_origin)}`);
127
+ if (trxData.state !== 'hold') {
128
+ throw new Error(`Attempt to continue transaction ${trxData.id}, currently at '${trxData.state}', failed. Should be at 'hold'. This might mean there are parallel attempts to continue a transaction, which must be handled with a queue.`);
129
+ }
99
130
  // Update transaction with data read from adapter
100
131
  trx.start = trxData.start;
101
132
  trx.end = trxData.end;
102
- log_1.Log.info('module', this.module.name, `Continue ${(0, log_1.scopeTag)('trx', trx.id + trx.root.id)} @ ${(0, log_1.anyScopeTag)(_origin)}`);
103
133
  for (const wrap of this.config?.wrap || []) {
134
+ // The wrappers decide how to continue a db transaction, based on the trx idempotent flag.
104
135
  await wrap.continue(trx, this.services);
105
136
  }
106
137
  }
107
138
  // (Chain)
139
+ // No transaction with the same id exists on this module,
140
+ // so it's starting as a "chained" transaction - a new one with
141
+ // the same ID as the one from some module.
108
142
  else {
109
- log_1.Log.info('module', this.module.name, `Chain ${(0, log_1.scopeTag)('trx', id + trx.root.id)} @ ${(0, log_1.anyScopeTag)(_origin)}`);
143
+ log_1.Log.info('module', this.module.name, `Chain${trx.idempotent ? '*' : ''} ${(0, log_1.scopeTag)('trx', trx.root.globalId)} @ ${(0, log_1.anyScopeTag)(_origin)}`);
110
144
  for (const wrap of this.config?.wrap || []) {
145
+ // The wrappers decide how to begin a db transaction, based on the trx idempotent flag.
111
146
  await wrap.begin(trx, this.services);
112
147
  }
113
148
  await this.adapter.create(this.innerTrx.root, {
114
149
  id: trx.id,
150
+ state: 'open',
115
151
  origin: _origin,
116
152
  start: trx.start,
117
153
  end: trx.end,
@@ -126,29 +162,29 @@ class TrxEngine {
126
162
  try {
127
163
  await this.authenticate(trx.root, tokens, users);
128
164
  const output = await fn(trx.root);
129
- await this.commit(trx, output, idempotent);
165
+ await this.ok(trx, output, idempotent);
130
166
  }
131
167
  catch (e) {
132
- await this.rollback(trx, e, idempotent);
168
+ await this.error(trx, e, idempotent);
133
169
  }
134
170
  return trx.status();
135
171
  }
136
- async trx_hold(fn, id, authn, users, origin, idempotent = false) {
137
- const trx = await this.get(id, origin, idempotent);
172
+ async trx_hold(fn, id, authn, users, origin) {
173
+ const trx = await this.get(id, origin, false);
138
174
  let output = {};
139
175
  try {
140
176
  await this.authenticate(trx.root, authn, users);
141
177
  output = await fn(trx.root);
142
- await this.hold(trx, output, idempotent);
178
+ await this.hold(trx, output);
143
179
  }
144
180
  catch (e) {
145
- await this.rollback(trx, e, idempotent);
181
+ await this.error(trx, e, false);
146
182
  }
147
183
  return {
148
184
  id: trx.id,
149
185
  status: trx.status(),
150
- commit: () => this.commit(trx, output, idempotent),
151
- rollback: (error) => this.rollback(trx, error, idempotent)
186
+ commit: () => this.ok(trx, output, false),
187
+ rollback: (error) => this.error(trx, error, false)
152
188
  };
153
189
  }
154
190
  /* Metadata sharing between modules */
@@ -188,13 +224,12 @@ class TrxEngine {
188
224
  trx_node_1.TrxNode.addAuthn(node, _tokens, _users);
189
225
  }
190
226
  //
191
- async hold(trx, output, idempotent) {
192
- log_1.Log.debug('module', this.module.name, `Hold ${(0, log_1.scopeTag)('trx', trx.id)} @ ${(0, log_1.anyScopeTag)(this.origin)}`);
227
+ async hold(trx, output) {
228
+ log_1.Log.info('module', this.module.name, `Hold ${(0, log_1.scopeTag)('trx', trx.root.globalId)}`);
193
229
  trx_node_1.TrxNode.hold(trx.root, output);
194
- if (idempotent)
195
- return trx;
196
230
  await this.adapter.put(this.innerTrx.root, {
197
231
  id: trx.id,
232
+ state: 'hold',
198
233
  origin: this.origin,
199
234
  start: trx.start,
200
235
  end: trx.end,
@@ -202,40 +237,61 @@ class TrxEngine {
202
237
  });
203
238
  return trx;
204
239
  }
205
- async commit(trx, output, idempotent) {
240
+ async ok(trx, output, idempotent) {
241
+ if (idempotent) {
242
+ log_1.Log.info('module', this.module.name, `Ok* ${(0, log_1.scopeTag)('trx', trx.root.globalId)}`);
243
+ }
206
244
  trx_node_1.TrxNode.ok(trx.root, output);
207
245
  trx.end = datetime_1.NesoiDatetime.now();
246
+ const held_children = Object.keys(trx.holds).length;
247
+ if (held_children) {
248
+ log_1.Log.debug('module', this.module.name, `Commit Holds ${(0, log_1.scopeTag)('trx', trx.root.globalId)} (${held_children})`);
249
+ await trx_1.Trx.commitHolds(trx);
250
+ }
208
251
  if (idempotent)
209
252
  return trx;
210
- await trx_1.Trx.onCommit(trx);
211
- log_1.Log.info('module', this.module.name, `Commit ${(0, log_1.scopeTag)('trx', trx.id)} @ ${(0, log_1.anyScopeTag)(this.origin)} (held: ${Object.keys(trx.holds).length})`);
212
- await this.adapter.put(this.innerTrx.root, {
253
+ return this.commit(trx);
254
+ }
255
+ async commit(trx) {
256
+ log_1.Log.info('module', this.module.name, `Commit ${(0, log_1.scopeTag)('trx', trx.root.globalId)}`);
257
+ await this.log_adapter?.put(this.innerTrx.root, {
213
258
  id: trx.id,
259
+ state: 'ok',
214
260
  origin: this.origin,
215
261
  start: trx.start,
216
262
  end: trx.end,
217
263
  module: this.module.name,
218
264
  });
265
+ await this.adapter.delete(this.innerTrx.root, trx.id);
219
266
  for (const wrap of this.config?.wrap || []) {
220
267
  await wrap.commit(trx, this.services);
221
268
  }
222
269
  return trx;
223
270
  }
224
- async rollback(trx, error, idempotent) {
225
- log_1.Log.error('module', this.module.name, `[${error.status}] ${error.toString()}`, error.stack);
271
+ async error(trx, error, idempotent) {
272
+ log_1.Log.error('module', this.module.name, `[${error.status}]${idempotent ? '*' : ''} ${error.toString()}`, error.stack);
226
273
  trx_node_1.TrxNode.error(trx.root, error);
227
274
  trx.end = datetime_1.NesoiDatetime.now();
275
+ const held_children = Object.keys(trx.holds).length;
276
+ if (held_children) {
277
+ log_1.Log.debug('module', this.module.name, `Rollback Holds ${(0, log_1.scopeTag)('trx', trx.root.globalId)} (${held_children})`);
278
+ await trx_1.Trx.rollbackHolds(trx);
279
+ }
228
280
  if (idempotent)
229
281
  return trx;
230
- await trx_1.Trx.onRollback(trx);
231
- log_1.Log.warn('module', this.module.name, `Rollback ${(0, log_1.scopeTag)('trx', trx.id)} @ ${(0, log_1.anyScopeTag)(this.origin)} (held: ${Object.keys(trx.holds).length})`);
232
- await this.adapter.put(this.innerTrx.root, {
282
+ return this.rollback(trx);
283
+ }
284
+ async rollback(trx) {
285
+ log_1.Log.warn('module', this.module.name, `Rollback ${(0, log_1.scopeTag)('trx', trx.root.globalId)} (held: ${Object.keys(trx.holds).length})`);
286
+ await this.log_adapter?.put(this.innerTrx.root, {
233
287
  id: trx.id,
288
+ state: 'error',
234
289
  origin: this.origin,
235
290
  start: trx.start,
236
291
  end: trx.end,
237
292
  module: this.module.name,
238
293
  });
294
+ await this.adapter.delete(this.innerTrx.root, trx.id);
239
295
  for (const wrap of this.config?.wrap || []) {
240
296
  await wrap.rollback(trx, this.services);
241
297
  }
@@ -27,6 +27,9 @@ export type TrxNodeStatus = {
27
27
  cached_buckets: number;
28
28
  nodes: TrxNodeStatus[];
29
29
  app: number;
30
+ ext?: {
31
+ idempotent: boolean;
32
+ };
30
33
  };
31
34
  /**
32
35
  * @category Engine
@@ -53,7 +56,7 @@ export declare class TrxNode<Space extends $Space, M extends $Module, AuthUsers
53
56
  constructor(scope: 'root' | `${string}::${TrxNodeBlock}:${string}` | `${string}::virtual`, trx: AnyTrx, parent: AnyTrxNode | undefined, module: AnyModule, auth?: {
54
57
  tokens: AuthRequest<any>;
55
58
  users: AuthUsers;
56
- } | undefined, external?: boolean | undefined, id?: string);
59
+ } | undefined, external?: boolean | undefined);
57
60
  static open(node: AnyTrxNode, action: string, input: Record<string, any>): void;
58
61
  static hold(node: AnyTrxNode, output?: Record<string, any>): void;
59
62
  static ok(node: AnyTrxNode, output?: Record<string, any>): void;
@@ -44,19 +44,16 @@ class TrxNode {
44
44
  hold: undefined,
45
45
  end: undefined
46
46
  };
47
- constructor(scope, trx, parent, module, auth, external, id) {
47
+ constructor(scope, trx, parent, module, auth, external) {
48
48
  this.scope = scope;
49
49
  this.trx = trx;
50
50
  this.parent = parent;
51
51
  this.module = module;
52
52
  this.auth = auth;
53
53
  this.external = external;
54
- if (parent) {
55
- this.id = id || (Math.random() + 1).toString(36).substring(7);
56
- }
57
- else {
58
- this.id = '#' + (Math.random() + 1).toString(36).substring(7);
59
- }
54
+ this.id = parent
55
+ ? (Math.random() + 1).toString(36).substring(7)
56
+ : '#';
60
57
  this.globalId = `${this.trx.id}.${this.id}`;
61
58
  }
62
59
  static open(node, action, input) {
@@ -201,7 +198,7 @@ class TrxNode {
201
198
  // Status
202
199
  status() {
203
200
  return {
204
- id: this.id,
201
+ id: this.globalId,
205
202
  scope: this.scope,
206
203
  state: this.state,
207
204
  action: this.action,
@@ -210,7 +207,10 @@ class TrxNode {
210
207
  error: this.error,
211
208
  cached_buckets: Object.keys(this._cache).length,
212
209
  nodes: this.children.map(child => child.status()),
213
- app: this.time.end ? (this.time.end.epoch - this.time.start.epoch) : -1
210
+ app: this.time.end ? (this.time.end.epoch - this.time.start.epoch) : -1,
211
+ ext: this.action === '~' ? {
212
+ idempotent: this.input?.idempotent
213
+ } : undefined
214
214
  };
215
215
  }
216
216
  //
@@ -219,6 +219,9 @@ class TrxNode {
219
219
  to.children.push(child);
220
220
  to.trx.addNode(child);
221
221
  }
222
+ to.input = {
223
+ idempotent: from.trx.idempotent
224
+ };
222
225
  }
223
226
  static makeChildNode(node, module, block, name) {
224
227
  const child = new TrxNode(`${module}::${block}:${name}`, node.trx, node, node.module, node.auth);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nesoi",
3
- "version": "3.3.30",
3
+ "version": "3.4.0",
4
4
  "description": "Declarative framework for data-driven applications",
5
5
  "repository": {
6
6
  "type": "git",