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,9 +22,7 @@ class ControllerAdapter {
22
22
  const trx = this.daemon.trx(this.schema.module)
23
23
  .origin('controller:' + this.schema.name + ':' + endpoint.name)
24
24
  .auth(auth);
25
- if (endpoint.idempotent)
26
- trx.idempotent();
27
- return await trx.run(fn);
25
+ return await trx.run(fn, undefined, endpoint.idempotent);
28
26
  }
29
27
  catch (e) {
30
28
  log_1.Log.error('controller', this.schema.name, 'Unknown error', e);
@@ -3,5 +3,9 @@ import { AnyTrxNode } from "../../../../engine/transaction/trx_node";
3
3
  export declare function MessageTemplateFieldParser(trx: AnyTrxNode, fields: $MessageTemplateFields, raw: Record<string, any>): Promise<Record<string, any>>;
4
4
  /**
5
5
  * Empty values: `{}`, `[]`, `''`, `null`, `undefined`
6
+ * @returns
7
+ * 0: not empty
8
+ * 1: null, undefined
9
+ * 2: {}, [] or ''
6
10
  */
7
- export declare function isEmpty(value: any): boolean;
11
+ export declare function isEmpty(value: any): 0 | 2 | 1;
@@ -30,14 +30,16 @@ async function MessageTemplateFieldParser(trx, fields, raw) {
30
30
  async function parseFieldValue(trx, field, path, raw, value, inject) {
31
31
  sanitize(field, path, value);
32
32
  let output;
33
- if (isEmpty(value)) {
33
+ const empty = isEmpty(value);
34
+ if (empty) {
34
35
  if (field.required) {
35
36
  throw error_1.NesoiError.Message.FieldIsRequired({ alias: field.alias, path: path.join('.'), value });
36
37
  }
37
38
  else if (field.defaultValue !== undefined) {
38
39
  output = field.defaultValue;
39
40
  }
40
- else {
41
+ // If the value is null/undefined, transform into undefined
42
+ else if (empty === 1) {
41
43
  output = undefined;
42
44
  }
43
45
  }
@@ -176,21 +178,25 @@ function sanitize(field, path, value) {
176
178
  }
177
179
  /**
178
180
  * Empty values: `{}`, `[]`, `''`, `null`, `undefined`
181
+ * @returns
182
+ * 0: not empty
183
+ * 1: null, undefined
184
+ * 2: {}, [] or ''
179
185
  */
180
186
  function isEmpty(value) {
181
187
  if (value === null || value === undefined) {
182
- return true;
188
+ return 1;
183
189
  }
184
190
  if (Array.isArray(value)) {
185
- return value.length === 0;
191
+ return value.length === 0 ? 2 : 0;
186
192
  }
187
193
  if (typeof value === 'object') {
188
- return Object.keys(value).length === 0;
194
+ return Object.keys(value).length === 0 ? 2 : 0;
189
195
  }
190
196
  if (typeof value === 'string') {
191
- return value.length === 0;
197
+ return value.length === 0 ? 2 : 0;
192
198
  }
193
- return false;
199
+ return 0;
194
200
  }
195
201
  /**
196
202
  * Rules
@@ -127,21 +127,11 @@ export declare class DaemonTrx<S extends $Space, M extends $Module, AuthUsers ex
127
127
  *
128
128
  */
129
129
  private _origin?;
130
- /**
131
- * An idempotent transaction doesn't generate a commit/rollback.
132
- */
133
- private _idempotent;
134
130
  /**
135
131
  * @param trxEngine The transaction engine where to run the transaction.
136
132
  */
137
133
  constructor(trxEngine: AnyTrxEngine);
138
134
  origin(origin: string): this;
139
- /**
140
- * Flags this transaction as idempotent.
141
- * This means its not stored, neither commited/rolled back.
142
- * This should generally be used for readonly transactions.
143
- */
144
- idempotent(value?: boolean): this;
145
135
  /**
146
136
  * Inherit authentication from another transaction node.
147
137
  */
@@ -158,7 +148,7 @@ export declare class DaemonTrx<S extends $Space, M extends $Module, AuthUsers ex
158
148
  * @param fn A function to execute inside the transaction
159
149
  * @returns A `TrxStatus` containing metadata about the transaction and the function response
160
150
  */
161
- run<Output>(fn: (trx: TrxNode<S, M, AuthUsers>) => Promise<Output>, id?: string): Promise<TrxStatus<Output>>;
151
+ run<Output>(fn: (trx: TrxNode<S, M, AuthUsers>) => Promise<Output>, id?: string, idempotent?: boolean): Promise<TrxStatus<Output>>;
162
152
  /**
163
153
  * Run a method inside the transaction, and hold it until
164
154
  * the external caller decides to commit.
@@ -180,10 +180,6 @@ class DaemonTrx {
180
180
  *
181
181
  */
182
182
  _origin;
183
- /**
184
- * An idempotent transaction doesn't generate a commit/rollback.
185
- */
186
- _idempotent = false;
187
183
  /**
188
184
  * @param trxEngine The transaction engine where to run the transaction.
189
185
  */
@@ -194,15 +190,6 @@ class DaemonTrx {
194
190
  this._origin = origin;
195
191
  return this;
196
192
  }
197
- /**
198
- * Flags this transaction as idempotent.
199
- * This means its not stored, neither commited/rolled back.
200
- * This should generally be used for readonly transactions.
201
- */
202
- idempotent(value = true) {
203
- this._idempotent = value;
204
- return this;
205
- }
206
193
  /**
207
194
  * Inherit authentication from another transaction node.
208
195
  */
@@ -225,14 +212,14 @@ class DaemonTrx {
225
212
  * @param fn A function to execute inside the transaction
226
213
  * @returns A `TrxStatus` containing metadata about the transaction and the function response
227
214
  */
228
- run(fn, id) {
215
+ run(fn, id, idempotent) {
229
216
  const inheritedAuth = this._inherit?.auth;
230
217
  const tokens = {
231
218
  ...inheritedAuth?.tokens,
232
219
  ...this.tokens
233
220
  };
234
221
  const users = inheritedAuth?.users;
235
- return this.trxEngine.trx(fn, id, tokens, users, this._origin, this._idempotent);
222
+ return this.trxEngine.trx(fn, id, tokens, users, this._origin, idempotent);
236
223
  }
237
224
  /**
238
225
  * Run a method inside the transaction, and hold it until
@@ -248,17 +235,7 @@ class DaemonTrx {
248
235
  ...this.tokens
249
236
  };
250
237
  const users = inheritedAuth?.users;
251
- // Idempotent transactions are not commited/rolled back, so they don't need to be held.
252
- if (this._idempotent) {
253
- const status = await this.trxEngine.trx(fn, id, tokens, users, this._origin, this._idempotent);
254
- return {
255
- id: status.id,
256
- status,
257
- commit: () => Promise.resolve(),
258
- rollback: () => Promise.resolve(),
259
- };
260
- }
261
- return this.trxEngine.trx_hold(fn, id, tokens, users);
238
+ return this.trxEngine.trx_hold(fn, id, tokens, users, this._origin);
262
239
  }
263
240
  }
264
241
  exports.DaemonTrx = DaemonTrx;
@@ -1,4 +1,14 @@
1
1
  import { NesoiDuration } from './duration';
2
+ export type NesoiDateTimeValues = {
3
+ year: number;
4
+ month: number;
5
+ day: number;
6
+ hour: number;
7
+ minute: number;
8
+ second: number;
9
+ ms: number;
10
+ tz: NesoiDatetime['tz'];
11
+ };
2
12
  /**
3
13
  * @category Engine
4
14
  * @subcategory Data
@@ -39,17 +49,41 @@ export declare class NesoiDatetime {
39
49
  */
40
50
  tz: keyof typeof NesoiDatetime.tz;
41
51
  constructor(epoch?: number, tz?: keyof typeof NesoiDatetime.tz);
42
- toISO(): string;
52
+ atTimezone(tz: NesoiDatetime['tz']): NesoiDatetime;
43
53
  /**
44
54
  * Parse a timestamp from ISO 8601 format.
45
55
  *
46
56
  * Example: `2025-04-16T23:04:42.000-03:00`
47
57
  */
48
58
  static fromISO(iso: string): NesoiDatetime;
59
+ /**
60
+ * Make a new `NesoiDateTime`
61
+ * @param year Numeric year
62
+ * @param month 1~12
63
+ * @param day 1~31
64
+ * @param hour 0~24
65
+ * @param minute 0~60
66
+ * @param second 0~60
67
+ * @param ms 0~999
68
+ * @param tz
69
+ * @returns
70
+ */
71
+ static make(year?: number, month?: number, day?: number, hour?: number, minute?: number, second?: number, ms?: number, tz?: NesoiDatetime['tz']): NesoiDatetime;
72
+ static fromValues(values: Partial<NesoiDateTimeValues>): NesoiDatetime;
73
+ toISO(): string;
74
+ toValues(): NesoiDateTimeValues;
75
+ toJSDate(): Date;
49
76
  static now(): NesoiDatetime;
50
77
  static isoNow(): string;
51
78
  static shortIsoNow(): string;
52
79
  plus(period: `${number} ${keyof typeof NesoiDuration.UNITS}`): NesoiDatetime;
53
80
  minus(period: `${number} ${keyof typeof NesoiDuration.UNITS}`): NesoiDatetime;
54
81
  shift(period: `${'+' | '-'} ${number} ${keyof typeof NesoiDuration.UNITS}`): NesoiDatetime;
82
+ /**
83
+ * Returns a new `NesoiDatetime` which refers to the
84
+ * start of a given period **on the object timezone**.
85
+ * @param period
86
+ * @returns
87
+ */
88
+ startOf(period: 'day' | 'month' | 'year'): NesoiDatetime;
55
89
  }
@@ -43,25 +43,14 @@ class NesoiDatetime {
43
43
  */
44
44
  tz;
45
45
  constructor(epoch, tz = 'Z') {
46
- this.epoch = epoch || new Date().getTime();
46
+ this.epoch = epoch ?? new Date().getTime();
47
47
  this.tz = tz;
48
48
  }
49
- // Dump
50
- toISO() {
51
- return new Date(this.epoch).toLocaleString('sv-SE', {
52
- timeZone: NesoiDatetime.tz[this.tz],
53
- year: 'numeric',
54
- month: 'numeric',
55
- day: 'numeric',
56
- hour: 'numeric',
57
- minute: 'numeric',
58
- second: 'numeric',
59
- fractionalSecondDigits: 3
60
- })
61
- .replace(' ', 'T')
62
- .replace(',', '.')
63
- + this.tz;
49
+ // Manipulate timezone
50
+ atTimezone(tz) {
51
+ return new NesoiDatetime(this.epoch, tz);
64
52
  }
53
+ // Parse
65
54
  /**
66
55
  * Parse a timestamp from ISO 8601 format.
67
56
  *
@@ -87,6 +76,79 @@ class NesoiDatetime {
87
76
  const jsDate = Date.parse(iso);
88
77
  return new NesoiDatetime(jsDate, tz);
89
78
  }
79
+ /**
80
+ * Make a new `NesoiDateTime`
81
+ * @param year Numeric year
82
+ * @param month 1~12
83
+ * @param day 1~31
84
+ * @param hour 0~24
85
+ * @param minute 0~60
86
+ * @param second 0~60
87
+ * @param ms 0~999
88
+ * @param tz
89
+ * @returns
90
+ */
91
+ static make(year = 0, month = 1, day = 1, hour = 0, minute = 0, second = 0, ms = 0, tz = 'Z') {
92
+ const _month = (month < 10 ? '0' : '') + month;
93
+ const _day = (day < 10 ? '0' : '') + day;
94
+ const _hour = (hour < 10 ? '0' : '') + hour;
95
+ const _minute = (minute < 10 ? '0' : '') + minute;
96
+ const _second = (second < 10 ? '0' : '') + second;
97
+ const _ms = (ms < 100 ? '0' : '') + (ms < 10 ? '0' : '') + ms;
98
+ return this.fromISO(`${year}-${_month}-${_day}T${_hour}:${_minute}:${_second}.${_ms}${tz}`);
99
+ }
100
+ static fromValues(values) {
101
+ return this.make(values.year, values.month, values.day, values.hour, values.minute, values.second, values.ms, values.tz);
102
+ }
103
+ // Dump
104
+ toISO() {
105
+ const date = new Date(0);
106
+ date.setUTCMilliseconds(this.epoch);
107
+ return date.toLocaleString('sv-SE', {
108
+ timeZone: NesoiDatetime.tz[this.tz],
109
+ year: 'numeric',
110
+ month: 'numeric',
111
+ day: 'numeric',
112
+ hour: 'numeric',
113
+ minute: 'numeric',
114
+ second: 'numeric',
115
+ fractionalSecondDigits: 3
116
+ })
117
+ .replace(' ', 'T')
118
+ .replace(',', '.')
119
+ + this.tz;
120
+ }
121
+ toValues() {
122
+ const date = new Date(0);
123
+ date.setUTCMilliseconds(this.epoch);
124
+ const str = date.toLocaleString('sv-SE', {
125
+ timeZone: NesoiDatetime.tz[this.tz],
126
+ year: 'numeric',
127
+ month: 'numeric',
128
+ day: 'numeric',
129
+ hour: 'numeric',
130
+ minute: 'numeric',
131
+ second: 'numeric',
132
+ fractionalSecondDigits: 3,
133
+ });
134
+ const [_, year, month, day, hour, minute, second, ms] = str.match(/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}),(\d{3})/);
135
+ return {
136
+ year: parseInt(year),
137
+ month: parseInt(month),
138
+ day: parseInt(day),
139
+ hour: parseInt(hour),
140
+ minute: parseInt(minute),
141
+ second: parseInt(second),
142
+ ms: parseInt(ms),
143
+ tz: this.tz
144
+ };
145
+ }
146
+ toJSDate() {
147
+ const date = new Date(0);
148
+ date.setUTCMilliseconds(this.epoch);
149
+ return date;
150
+ }
151
+ // Now
90
152
  static now() {
91
153
  return new NesoiDatetime();
92
154
  }
@@ -96,6 +158,7 @@ class NesoiDatetime {
96
158
  static shortIsoNow() {
97
159
  return new NesoiDatetime().toISO().slice(5, 19);
98
160
  }
161
+ // Shift
99
162
  plus(period) {
100
163
  return this.shift(`+ ${period}`);
101
164
  }
@@ -157,5 +220,29 @@ class NesoiDatetime {
157
220
  }
158
221
  return new NesoiDatetime(epoch, this.tz);
159
222
  }
223
+ // Start Of
224
+ /**
225
+ * Returns a new `NesoiDatetime` which refers to the
226
+ * start of a given period **on the object timezone**.
227
+ * @param period
228
+ * @returns
229
+ */
230
+ startOf(period) {
231
+ const values = this.toValues();
232
+ values.ms = 0;
233
+ values.second = 0;
234
+ values.minute = 0;
235
+ values.hour = 0;
236
+ switch (period) {
237
+ case 'month':
238
+ values.day = 1;
239
+ break;
240
+ case 'year':
241
+ values.day = 1;
242
+ values.month = 1;
243
+ break;
244
+ }
245
+ return NesoiDatetime.fromValues(values);
246
+ }
160
247
  }
161
248
  exports.NesoiDatetime = NesoiDatetime;
@@ -120,6 +120,11 @@ export declare namespace NesoiError {
120
120
  path: string;
121
121
  bucket: string;
122
122
  }): BaseError;
123
+ function IdempotentTransaction($: {
124
+ bucket: string;
125
+ trx: string;
126
+ action: string;
127
+ }): BaseError;
123
128
  namespace Graph {
124
129
  function LinkNotFound($: {
125
130
  bucket: string;
@@ -229,6 +229,10 @@ var NesoiError;
229
229
  return new BaseError('Bucket.FieldNotFound', `Field '${$.path}' not found on bucket '${$.bucket}'`, Status.NOT_FOUND, $);
230
230
  }
231
231
  Bucket.FieldNotFound = FieldNotFound;
232
+ function IdempotentTransaction($) {
233
+ return new BaseError('Bucket.IdempotentTransaction', `Action '${$.action}' on bucket '${$.bucket}' not allowed for idempotent transaction ${$.trx}`, Status.NOT_FOUND, $);
234
+ }
235
+ Bucket.IdempotentTransaction = IdempotentTransaction;
232
236
  let Graph;
233
237
  (function (Graph) {
234
238
  function LinkNotFound($) {
@@ -231,6 +231,8 @@ export declare class BucketUnsafeTrxNode<M extends $Module, $ extends $Bucket> {
231
231
  export declare class BucketDriveTrxNode<M extends $Module, $ extends $Bucket> {
232
232
  private bucketTrx;
233
233
  private drive;
234
+ private tag;
235
+ private trx;
234
236
  constructor(bucketTrx: BucketTrxNode<M, $>, drive: DriveAdapter);
235
237
  /**
236
238
  * Read the contents of a File of this bucket's drive
@@ -41,6 +41,10 @@ class BucketTrxNode {
41
41
  */
42
42
  async wrap(action, input, fn, fmtTrxOut, idempotent = false) {
43
43
  const wrapped = async (parentTrx, bucket) => {
44
+ const trx_idempotent = parentTrx.trx.idempotent;
45
+ if (trx_idempotent && !idempotent) {
46
+ throw error_1.NesoiError.Bucket.IdempotentTransaction({ bucket: this.tag.full, trx: this.trx.globalId, action });
47
+ }
44
48
  const trx = trx_node_1.TrxNode.makeChildNode(parentTrx, bucket.schema.module, 'bucket', bucket.schema.name);
45
49
  trx_node_1.TrxNode.open(trx, action, input);
46
50
  let out;
@@ -55,6 +59,10 @@ class BucketTrxNode {
55
59
  };
56
60
  if (this.external) {
57
61
  const ext = new external_trx_node_1.ExternalTrxNode(this.trx, this.tag, idempotent);
62
+ // The if below is not strictly necessary but avoids a warning.
63
+ if (idempotent) {
64
+ return ext.run(trx => dependency_1.Tag.element(this.tag, trx), wrapped);
65
+ }
58
66
  return ext.run_and_hold(trx => dependency_1.Tag.element(this.tag, trx), wrapped);
59
67
  }
60
68
  else {
@@ -456,9 +464,13 @@ exports.BucketUnsafeTrxNode = BucketUnsafeTrxNode;
456
464
  class BucketDriveTrxNode {
457
465
  bucketTrx;
458
466
  drive;
467
+ tag;
468
+ trx;
459
469
  constructor(bucketTrx, drive) {
460
470
  this.bucketTrx = bucketTrx;
461
471
  this.drive = drive;
472
+ this.tag = bucketTrx.tag;
473
+ this.trx = bucketTrx.trx;
462
474
  }
463
475
  /**
464
476
  * Read the contents of a File of this bucket's drive
@@ -71,7 +71,7 @@ class BucketQueryTrxNode {
71
71
  };
72
72
  if (this.external) {
73
73
  const ext = new external_trx_node_1.ExternalTrxNode(this.trx, this.tag, true);
74
- return ext.run_and_hold(trx => dependency_1.Tag.element(this.tag, trx), wrapped);
74
+ return ext.run(trx => dependency_1.Tag.element(this.tag, trx), wrapped);
75
75
  }
76
76
  else {
77
77
  return wrapped(this.trx, this.bucket);
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ExternalTrxNode = void 0;
4
4
  const trx_node_1 = require("../trx_node");
5
5
  const error_1 = require("../../data/error");
6
+ const log_1 = require("../../util/log");
6
7
  /**
7
8
  * @category Engine
8
9
  * @subcategory Transaction
@@ -23,25 +24,26 @@ class ExternalTrxNode {
23
24
  this.daemon = _module.daemon;
24
25
  }
25
26
  async run_and_hold(element, fn) {
26
- const root = this.trx.trx;
27
+ if (this.idempotent) {
28
+ log_1.Log.debug('trx', 'external', `Attempt to hold idempotent external node of transaction ${this.trx.globalId} on ${this.tag.full} ignored. Running without hold.`);
29
+ return this.run(element, fn);
30
+ }
31
+ const parent = this.trx.trx;
27
32
  const module = trx_node_1.TrxNode.getModule(this.trx);
28
33
  const trx = trx_node_1.TrxNode.makeChildNode(this.trx, module.name, 'externals', this.tag.full);
29
34
  trx_node_1.TrxNode.open(trx, '~', {
30
35
  tag: this.tag
31
36
  });
37
+ const origin = module.name + '::trx:' + parent.id;
38
+ const tag = this.tag.module + '::trx:' + parent.id;
32
39
  let out;
33
40
  try {
34
41
  const dtrx = await this.daemon.trx(this.tag.module)
35
- .origin('ext:' + root.id)
36
- // This can be overriden by the TrxEngine if the root
37
- // it not idempotent (which makes it not idempotent)
38
- .idempotent(this.idempotent);
39
- let idempotent = false;
42
+ .origin(origin);
40
43
  const hold = await dtrx
41
44
  .auth_inherit(trx)
42
45
  .run_and_hold(async (extTrx) => {
43
46
  try {
44
- idempotent = extTrx.trx.idempotent;
45
47
  return await fn(extTrx, element(extTrx));
46
48
  }
47
49
  catch (e) {
@@ -50,15 +52,13 @@ class ExternalTrxNode {
50
52
  finally {
51
53
  trx_node_1.TrxNode.merge(trx, extTrx);
52
54
  }
53
- }, root.id);
55
+ }, parent.id);
54
56
  if (hold.status.state === 'error') {
55
57
  throw hold.status.error;
56
58
  }
57
59
  out = hold.status.output;
58
- if (!idempotent) {
59
- if (!(root.id in root.holds)) {
60
- root.holds[root.id] = hold;
61
- }
60
+ if (!(tag in parent.holds)) {
61
+ parent.holds[tag] = hold;
62
62
  }
63
63
  }
64
64
  catch (e) {
@@ -68,7 +68,7 @@ class ExternalTrxNode {
68
68
  return out;
69
69
  }
70
70
  async run(element, fn) {
71
- const root = this.trx.trx;
71
+ const parent = this.trx.trx;
72
72
  const module = trx_node_1.TrxNode.getModule(this.trx);
73
73
  const trx = trx_node_1.TrxNode.makeChildNode(this.trx, module.name, 'externals', this.tag.full);
74
74
  trx_node_1.TrxNode.open(trx, '~', {
@@ -77,10 +77,7 @@ class ExternalTrxNode {
77
77
  let out;
78
78
  try {
79
79
  const dtrx = await this.daemon.trx(this.tag.module)
80
- .origin('ext:' + root.id)
81
- // This can be overriden by the TrxEngine if the root
82
- // it not idempotent (which makes it not idempotent)
83
- .idempotent(this.idempotent);
80
+ .origin(module.name + '::trx:' + parent.id);
84
81
  const res = await dtrx
85
82
  .auth_inherit(trx)
86
83
  .run(async (extTrx) => {
@@ -93,7 +90,7 @@ class ExternalTrxNode {
93
90
  finally {
94
91
  trx_node_1.TrxNode.merge(trx, extTrx);
95
92
  }
96
- }, root.id);
93
+ }, parent.id, this.idempotent);
97
94
  if (res.state === 'error') {
98
95
  throw res.error;
99
96
  }
@@ -1,7 +1,6 @@
1
1
  import { $Module } from "../../../schema";
2
- import { AnyTrxNode, TrxNode } from '../trx_node';
2
+ import { TrxNode } from '../trx_node';
3
3
  import { $Job } from "../../../elements/blocks/job/job.schema";
4
- import { Job } from "../../../elements/blocks/job/job";
5
4
  import { Message } from "../../../elements/entities/message/message";
6
5
  import { JobInput } from "../../../elements/blocks/job/job.types";
7
6
  import { Tag } from "../../dependency";
@@ -15,8 +14,10 @@ export declare class JobTrxNode<M extends $Module, $ extends $Job> {
15
14
  private ctx?;
16
15
  private external;
17
16
  private job?;
17
+ private _idempotent;
18
18
  constructor(trx: TrxNode<any, M, any>, tag: Tag, ctx?: Record<string, any> | undefined);
19
- wrap(action: string, input: Record<string, any>, fn: (trx: AnyTrxNode, element: Job<any, M, $>) => Promise<any>, fmtTrxOut?: (out: any) => any): Promise<any>;
19
+ get idempotent(): this;
20
+ private wrap;
20
21
  run(message: JobInput<$>): Promise<$['#output']>;
21
22
  forward(message: Message<$['#input']>): Promise<$['#output']>;
22
23
  }
@@ -15,6 +15,7 @@ class JobTrxNode {
15
15
  ctx;
16
16
  external;
17
17
  job;
18
+ _idempotent = false;
18
19
  constructor(trx, tag, ctx) {
19
20
  this.trx = trx;
20
21
  this.tag = tag;
@@ -28,6 +29,10 @@ class JobTrxNode {
28
29
  }
29
30
  }
30
31
  }
32
+ get idempotent() {
33
+ this._idempotent = true;
34
+ return this;
35
+ }
31
36
  /*
32
37
  Wrap
33
38
  */
@@ -47,7 +52,7 @@ class JobTrxNode {
47
52
  };
48
53
  if (this.external) {
49
54
  const root = this.trx.trx;
50
- const ext = new external_trx_node_1.ExternalTrxNode(this.trx, this.tag, root.idempotent);
55
+ const ext = new external_trx_node_1.ExternalTrxNode(this.trx, this.tag, this._idempotent);
51
56
  return ext.run_and_hold(trx => dependency_1.Tag.element(this.tag, trx), wrapped);
52
57
  }
53
58
  else {
@@ -44,7 +44,8 @@ class ResourceTrxNode {
44
44
  return out;
45
45
  };
46
46
  if (this.external) {
47
- const ext = new external_trx_node_1.ExternalTrxNode(this.trx, this.tag);
47
+ const idempotent = action === 'view' || action === 'query';
48
+ const ext = new external_trx_node_1.ExternalTrxNode(this.trx, this.tag, idempotent);
48
49
  return ext.run_and_hold(trx => dependency_1.Tag.element(this.tag, trx), wrapped);
49
50
  }
50
51
  else {
@@ -20,8 +20,9 @@ export declare class TrxStatus<Output> {
20
20
  state?: TrxState | undefined;
21
21
  output?: Output | undefined;
22
22
  error?: NesoiError.BaseError | undefined;
23
+ idempotent: boolean;
23
24
  nodes: TrxNodeStatus[];
24
- constructor(id: string, origin: TrxOrigin, start: NesoiDatetime, end?: NesoiDatetime | undefined, state?: TrxState | undefined, output?: Output | undefined, error?: NesoiError.BaseError | undefined, nodes?: TrxNodeStatus[]);
25
+ constructor(id: string, origin: TrxOrigin, start: NesoiDatetime, end?: NesoiDatetime | undefined, state?: TrxState | undefined, output?: Output | undefined, error?: NesoiError.BaseError | undefined, idempotent?: boolean, nodes?: TrxNodeStatus[]);
25
26
  summary(): string;
26
27
  }
27
28
  /**
@@ -67,8 +68,8 @@ export declare class Trx<S extends $Space, M extends $Module, AuthUsers extends
67
68
  * to the transaction. Elements should not modify the transaction.
68
69
  */
69
70
  static set(node: AnyTrxNode, key: string, value: any): AnyTrx;
70
- static onCommit(trx: AnyTrx): Promise<void>;
71
- static onRollback(trx: AnyTrx): Promise<void>;
71
+ static commitHolds(trx: AnyTrx): Promise<void>;
72
+ static rollbackHolds(trx: AnyTrx): Promise<void>;
72
73
  }
73
74
  export type AnyTrx = Trx<any, any, any>;
74
75
  export {};