nesoi 3.3.29 → 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.
- package/lib/elements/edge/controller/adapters/controller_adapter.js +1 -3
- package/lib/elements/entities/message/template/message_template_field.builder.js +2 -1
- package/lib/elements/entities/message/template/message_template_parser.d.ts +5 -1
- package/lib/elements/entities/message/template/message_template_parser.js +16 -10
- package/lib/engine/daemon.d.ts +1 -11
- package/lib/engine/daemon.js +3 -26
- package/lib/engine/data/datetime.d.ts +35 -1
- package/lib/engine/data/datetime.js +103 -16
- package/lib/engine/data/error.d.ts +5 -0
- package/lib/engine/data/error.js +4 -0
- package/lib/engine/dependency.js +6 -2
- package/lib/engine/transaction/nodes/bucket.trx_node.d.ts +2 -0
- package/lib/engine/transaction/nodes/bucket.trx_node.js +12 -0
- package/lib/engine/transaction/nodes/bucket_query.trx_node.js +1 -1
- package/lib/engine/transaction/nodes/external.trx_node.js +15 -18
- package/lib/engine/transaction/nodes/job.trx_node.d.ts +4 -3
- package/lib/engine/transaction/nodes/job.trx_node.js +6 -1
- package/lib/engine/transaction/nodes/resource.trx_node.js +2 -1
- package/lib/engine/transaction/trx.d.ts +4 -3
- package/lib/engine/transaction/trx.js +15 -11
- package/lib/engine/transaction/trx_engine.config.d.ts +7 -3
- package/lib/engine/transaction/trx_engine.d.ts +7 -3
- package/lib/engine/transaction/trx_engine.js +101 -45
- package/lib/engine/transaction/trx_node.d.ts +4 -1
- package/lib/engine/transaction/trx_node.js +12 -9
- package/package.json +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -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
|
-
|
|
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);
|
|
@@ -173,7 +173,8 @@ class MessageTemplateFieldBuilder {
|
|
|
173
173
|
}
|
|
174
174
|
// Build
|
|
175
175
|
static build(builder, name, tree, module, basePathRaw, basePathParsed) {
|
|
176
|
-
const pathRaw = basePathRaw +
|
|
176
|
+
const pathRaw = basePathRaw +
|
|
177
|
+
(builder._rawName ?? (builder.type === 'id' ? `${name}_id` : name));
|
|
177
178
|
const pathParsed = basePathParsed + name;
|
|
178
179
|
const childrenBasePathRaw = pathRaw + '.';
|
|
179
180
|
const childrenBasePathParsed = pathParsed + '.';
|
|
@@ -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):
|
|
11
|
+
export declare function isEmpty(value: any): 0 | 2 | 1;
|
|
@@ -15,7 +15,7 @@ async function MessageTemplateFieldParser(trx, fields, raw) {
|
|
|
15
15
|
const key_raw = field.pathRaw.split('.')[0];
|
|
16
16
|
const key_parsed = field.pathParsed.split('.')[0];
|
|
17
17
|
const value = raw[key_raw];
|
|
18
|
-
parsed[key_parsed] = await parseFieldValue(trx, field,
|
|
18
|
+
parsed[key_parsed] = await parseFieldValue(trx, field, field.pathRaw.split('.'), raw, value, inject);
|
|
19
19
|
}
|
|
20
20
|
Object.assign(parsed, inject);
|
|
21
21
|
return parsed;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
41
|
+
// If the value is null/undefined, transform into undefined
|
|
42
|
+
else if (empty === 1) {
|
|
41
43
|
output = undefined;
|
|
42
44
|
}
|
|
43
45
|
}
|
|
@@ -135,7 +137,7 @@ async function parseParentField(trx, field, path, raw, value, inject) {
|
|
|
135
137
|
const parsedParent = [];
|
|
136
138
|
for (const key in children) {
|
|
137
139
|
const child = children[key];
|
|
138
|
-
parsedParent.push(await parseFieldValue(trx, child.field,
|
|
140
|
+
parsedParent.push(await parseFieldValue(trx, child.field, child.field.pathRaw.split('.'), raw, child.value, inject));
|
|
139
141
|
}
|
|
140
142
|
return parsedParent;
|
|
141
143
|
}
|
|
@@ -150,7 +152,7 @@ async function parseParentField(trx, field, path, raw, value, inject) {
|
|
|
150
152
|
const parsedParent = {};
|
|
151
153
|
for (const key in children) {
|
|
152
154
|
const child = children[key];
|
|
153
|
-
parsedParent[key] = await parseFieldValue(trx, child.field,
|
|
155
|
+
parsedParent[key] = await parseFieldValue(trx, child.field, child.field.pathRaw.split('.'), raw, child.value, inject);
|
|
154
156
|
}
|
|
155
157
|
return parsedParent;
|
|
156
158
|
}
|
|
@@ -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
|
|
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
|
|
199
|
+
return 0;
|
|
194
200
|
}
|
|
195
201
|
/**
|
|
196
202
|
* Rules
|
package/lib/engine/daemon.d.ts
CHANGED
|
@@ -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.
|
package/lib/engine/daemon.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
46
|
+
this.epoch = epoch ?? new Date().getTime();
|
|
47
47
|
this.tz = tz;
|
|
48
48
|
}
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
return new
|
|
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;
|
package/lib/engine/data/error.js
CHANGED
|
@@ -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($) {
|
package/lib/engine/dependency.js
CHANGED
|
@@ -126,10 +126,10 @@ class Tag {
|
|
|
126
126
|
return module.machines[self.name];
|
|
127
127
|
if (self.type === 'controller')
|
|
128
128
|
return module.controllers[self.name];
|
|
129
|
-
if (self.type === 'topic')
|
|
130
|
-
return module.topics[self.name];
|
|
131
129
|
if (self.type === 'queue')
|
|
132
130
|
return module.queues[self.name];
|
|
131
|
+
if (self.type === 'topic')
|
|
132
|
+
return module.topics[self.name];
|
|
133
133
|
throw new Error(`Schema with tag ${self.full} not found on module ${module.name}`);
|
|
134
134
|
}
|
|
135
135
|
static resolveExternal(self, externals) {
|
|
@@ -185,6 +185,10 @@ class Tag {
|
|
|
185
185
|
return module.machines[self.name];
|
|
186
186
|
if (self.type === 'controller')
|
|
187
187
|
return module.controllers[self.name];
|
|
188
|
+
if (self.type === 'queue')
|
|
189
|
+
return module.queues[self.name];
|
|
190
|
+
if (self.type === 'topic')
|
|
191
|
+
return module.topics[self.name];
|
|
188
192
|
throw new Error(`Element with tag ${self.full} not found on module ${module.name}`);
|
|
189
193
|
}
|
|
190
194
|
static matches(self, other) {
|
|
@@ -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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
},
|
|
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 (!
|
|
59
|
-
|
|
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
|
|
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('
|
|
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
|
-
},
|
|
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 {
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 {
|