@open-rap/runtime 0.0.0-stage → 0.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.
- package/LICENSE +21 -0
- package/README.md +14 -2
- package/dist/abap/background.d.ts +14 -0
- package/dist/abap/background.js +130 -0
- package/dist/abap/background.js.map +1 -0
- package/dist/abap/bridge.d.ts +62 -0
- package/dist/abap/bridge.js +402 -0
- package/dist/abap/bridge.js.map +1 -0
- package/dist/abap/convert.d.ts +26 -0
- package/dist/abap/convert.js +197 -0
- package/dist/abap/convert.js.map +1 -0
- package/dist/abap/http.d.ts +34 -0
- package/dist/abap/http.js +133 -0
- package/dist/abap/http.js.map +1 -0
- package/dist/abap/mail.d.ts +10 -0
- package/dist/abap/mail.js +83 -0
- package/dist/abap/mail.js.map +1 -0
- package/dist/abap/odata-client.d.ts +33 -0
- package/dist/abap/odata-client.js +576 -0
- package/dist/abap/odata-client.js.map +1 -0
- package/dist/abap/sql.d.ts +92 -0
- package/dist/abap/sql.js +261 -0
- package/dist/abap/sql.js.map +1 -0
- package/dist/abap/utils.d.ts +41 -0
- package/dist/abap/utils.js +976 -0
- package/dist/abap/utils.js.map +1 -0
- package/dist/app.d.ts +53 -0
- package/dist/app.js +169 -0
- package/dist/app.js.map +1 -0
- package/dist/auth/auth.d.ts +68 -0
- package/dist/auth/auth.js +140 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/authorizations.d.ts +39 -0
- package/dist/auth/authorizations.js +103 -0
- package/dist/auth/authorizations.js.map +1 -0
- package/dist/auth/business-users.d.ts +15 -0
- package/dist/auth/business-users.js +36 -0
- package/dist/auth/business-users.js.map +1 -0
- package/dist/auth/dev-oauth.d.ts +38 -0
- package/dist/auth/dev-oauth.js +156 -0
- package/dist/auth/dev-oauth.js.map +1 -0
- package/dist/auth/environment.d.ts +7 -0
- package/dist/auth/environment.js +57 -0
- package/dist/auth/environment.js.map +1 -0
- package/dist/auth/session.d.ts +45 -0
- package/dist/auth/session.js +257 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/user.d.ts +21 -0
- package/dist/auth/user.js +12 -0
- package/dist/auth/user.js.map +1 -0
- package/dist/db/database.d.ts +30 -0
- package/dist/db/database.js +131 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/seed.d.ts +9 -0
- package/dist/db/seed.js +63 -0
- package/dist/db/seed.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/launchpad/dev.d.ts +14 -0
- package/dist/launchpad/dev.js +48 -0
- package/dist/launchpad/dev.js.map +1 -0
- package/dist/launchpad/proxy.d.ts +30 -0
- package/dist/launchpad/proxy.js +98 -0
- package/dist/launchpad/proxy.js.map +1 -0
- package/dist/launchpad/server.d.ts +77 -0
- package/dist/launchpad/server.js +177 -0
- package/dist/launchpad/server.js.map +1 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.js +222 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/odata/access.d.ts +31 -0
- package/dist/odata/access.js +142 -0
- package/dist/odata/access.js.map +1 -0
- package/dist/odata/batch.d.ts +36 -0
- package/dist/odata/batch.js +147 -0
- package/dist/odata/batch.js.map +1 -0
- package/dist/odata/csrf.d.ts +13 -0
- package/dist/odata/csrf.js +27 -0
- package/dist/odata/csrf.js.map +1 -0
- package/dist/odata/custom.d.ts +90 -0
- package/dist/odata/custom.js +373 -0
- package/dist/odata/custom.js.map +1 -0
- package/dist/odata/draft.d.ts +78 -0
- package/dist/odata/draft.js +590 -0
- package/dist/odata/draft.js.map +1 -0
- package/dist/odata/errors.d.ts +27 -0
- package/dist/odata/errors.js +41 -0
- package/dist/odata/errors.js.map +1 -0
- package/dist/odata/handler.d.ts +90 -0
- package/dist/odata/handler.js +312 -0
- package/dist/odata/handler.js.map +1 -0
- package/dist/odata/json.d.ts +23 -0
- package/dist/odata/json.js +85 -0
- package/dist/odata/json.js.map +1 -0
- package/dist/odata/read.d.ts +90 -0
- package/dist/odata/read.js +686 -0
- package/dist/odata/read.js.map +1 -0
- package/dist/odata/service.d.ts +45 -0
- package/dist/odata/service.js +145 -0
- package/dist/odata/service.js.map +1 -0
- package/dist/odata/uri/ast.d.ts +103 -0
- package/dist/odata/uri/ast.js +3 -0
- package/dist/odata/uri/ast.js.map +1 -0
- package/dist/odata/uri/parser.d.ts +26 -0
- package/dist/odata/uri/parser.js +697 -0
- package/dist/odata/uri/parser.js.map +1 -0
- package/dist/odata/write.d.ts +127 -0
- package/dist/odata/write.js +799 -0
- package/dist/odata/write.js.map +1 -0
- package/dist/platform.d.ts +14 -0
- package/dist/platform.js +66 -0
- package/dist/platform.js.map +1 -0
- package/dist/rap/bo.d.ts +76 -0
- package/dist/rap/bo.js +184 -0
- package/dist/rap/bo.js.map +1 -0
- package/dist/rap/kernel.d.ts +7 -0
- package/dist/rap/kernel.js +287 -0
- package/dist/rap/kernel.js.map +1 -0
- package/dist/rap/runtime.d.ts +84 -0
- package/dist/rap/runtime.js +128 -0
- package/dist/rap/runtime.js.map +1 -0
- package/dist/rap/transaction.d.ts +227 -0
- package/dist/rap/transaction.js +1385 -0
- package/dist/rap/transaction.js.map +1 -0
- package/dist/rap/types.d.ts +74 -0
- package/dist/rap/types.js +25 -0
- package/dist/rap/types.js.map +1 -0
- package/dist/server.d.ts +34 -0
- package/dist/server.js +114 -0
- package/dist/server.js.map +1 -0
- package/dist/ui/plugin.d.ts +14 -0
- package/dist/ui/plugin.js +47 -0
- package/dist/ui/plugin.js.map +1 -0
- package/dist/ui/resources.d.ts +22 -0
- package/dist/ui/resources.js +88 -0
- package/dist/ui/resources.js.map +1 -0
- package/dist/ui/variants.d.ts +25 -0
- package/dist/ui/variants.js +68 -0
- package/dist/ui/variants.js.map +1 -0
- package/dist/values.d.ts +43 -0
- package/dist/values.js +367 -0
- package/dist/values.js.map +1 -0
- package/package.json +60 -3
|
@@ -0,0 +1,976 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The kernel side of the clean-room utilities of ABAP Cloud (decision D40): JSON (/ui2/cl_json,
|
|
3
|
+
* xco_cp_json), code pages and encodings, gzip, hashes, random seeds, regular expressions, date
|
|
4
|
+
* and time stamp arithmetic, number ranges and the application log (in the database of the
|
|
5
|
+
* application, in the LUW of the request), and the description of data objects for the RTTS.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash, createHmac, getHashes, randomInt } from "node:crypto";
|
|
8
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
9
|
+
import { sql } from "kysely";
|
|
10
|
+
import { abapGlobal, abapString, appendRow, rowsOf } from "./convert.js";
|
|
11
|
+
import { raise } from "./http.js";
|
|
12
|
+
// ------------------------------------------------------------------------------ data objects
|
|
13
|
+
/** The data object behind field symbols. */
|
|
14
|
+
function deref(value) {
|
|
15
|
+
const { types } = abapGlobal();
|
|
16
|
+
let current = value;
|
|
17
|
+
while (current instanceof types.FieldSymbol) {
|
|
18
|
+
current = current.getPointer();
|
|
19
|
+
}
|
|
20
|
+
return current;
|
|
21
|
+
}
|
|
22
|
+
const BOOLEAN_TYPES = new Set(["ABAP_BOOL", "ABAP_BOOLEAN", "BOOLEAN", "BOOLE_D", "XFELD", "XSDBOOLEAN", "FLAG", "OS_BOOLEAN", "SAP_BOOL"]);
|
|
23
|
+
const TIMESTAMP_TYPES = new Set(["TIMESTAMP", "TIMESTAMPL", "TZNTSTMPS", "TZNTSTMPL", "ABP_CREATION_TSTMPL", "ABP_LASTCHANGE_TSTMPL", "ABP_LOCINST_LASTCHANGE_TSTMPL"]);
|
|
24
|
+
function qualified(value) {
|
|
25
|
+
const name = typeof value?.getQualifiedName === "function" ? value.getQualifiedName() : undefined;
|
|
26
|
+
return typeof name === "string" ? name.toUpperCase() : "";
|
|
27
|
+
}
|
|
28
|
+
function isTable(value) {
|
|
29
|
+
const { types } = abapGlobal();
|
|
30
|
+
return value instanceof types.Table || (types.HashedTable !== undefined && value instanceof types.HashedTable) || typeof value?.array === "function";
|
|
31
|
+
}
|
|
32
|
+
function packedText(value) {
|
|
33
|
+
return typeof value.toFixed === "function" ? value.toFixed(value.getDecimals()) : String(value.get());
|
|
34
|
+
}
|
|
35
|
+
function charText(value) {
|
|
36
|
+
return String(value.get()).replace(/\s+$/, "");
|
|
37
|
+
}
|
|
38
|
+
/** A JSON parser that keeps the numbers as written (no rounding through doubles). */
|
|
39
|
+
export function parseJson(text) {
|
|
40
|
+
let i = 0;
|
|
41
|
+
const fail = (what) => {
|
|
42
|
+
throw new Error(`invalid JSON at ${i}: ${what}`);
|
|
43
|
+
};
|
|
44
|
+
const space = () => {
|
|
45
|
+
while (i < text.length && /\s/.test(text[i] ?? ""))
|
|
46
|
+
i++;
|
|
47
|
+
};
|
|
48
|
+
const value = () => {
|
|
49
|
+
space();
|
|
50
|
+
const c = text[i];
|
|
51
|
+
if (c === "{") {
|
|
52
|
+
i++;
|
|
53
|
+
const m = [];
|
|
54
|
+
space();
|
|
55
|
+
if (text[i] === "}") {
|
|
56
|
+
i++;
|
|
57
|
+
return { t: "o", m };
|
|
58
|
+
}
|
|
59
|
+
for (;;) {
|
|
60
|
+
space();
|
|
61
|
+
if (text[i] !== '"')
|
|
62
|
+
fail("member name expected");
|
|
63
|
+
const key = string();
|
|
64
|
+
space();
|
|
65
|
+
if (text[i] !== ":")
|
|
66
|
+
fail("':' expected");
|
|
67
|
+
i++;
|
|
68
|
+
m.push([key, value()]);
|
|
69
|
+
space();
|
|
70
|
+
if (text[i] === ",") {
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
else if (text[i] === "}") {
|
|
74
|
+
i++;
|
|
75
|
+
return { t: "o", m };
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
fail("',' or '}' expected");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (c === "[") {
|
|
83
|
+
i++;
|
|
84
|
+
const v = [];
|
|
85
|
+
space();
|
|
86
|
+
if (text[i] === "]") {
|
|
87
|
+
i++;
|
|
88
|
+
return { t: "a", v };
|
|
89
|
+
}
|
|
90
|
+
for (;;) {
|
|
91
|
+
v.push(value());
|
|
92
|
+
space();
|
|
93
|
+
if (text[i] === ",") {
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
else if (text[i] === "]") {
|
|
97
|
+
i++;
|
|
98
|
+
return { t: "a", v };
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
fail("',' or ']' expected");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (c === '"') {
|
|
106
|
+
return { t: "s", v: string() };
|
|
107
|
+
}
|
|
108
|
+
const literal = /^(true|false|null|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(text.slice(i));
|
|
109
|
+
if (literal === null) {
|
|
110
|
+
return fail("value expected");
|
|
111
|
+
}
|
|
112
|
+
i += literal[0].length;
|
|
113
|
+
const word = literal[0];
|
|
114
|
+
return word === "true" ? { t: "b", v: true } : word === "false" ? { t: "b", v: false } : word === "null" ? { t: "z" } : { t: "n", v: word };
|
|
115
|
+
};
|
|
116
|
+
const string = () => {
|
|
117
|
+
const start = i;
|
|
118
|
+
i++;
|
|
119
|
+
let escaped = false;
|
|
120
|
+
for (; i < text.length; i++) {
|
|
121
|
+
if (escaped) {
|
|
122
|
+
escaped = false;
|
|
123
|
+
}
|
|
124
|
+
else if (text[i] === "\\") {
|
|
125
|
+
escaped = true;
|
|
126
|
+
}
|
|
127
|
+
else if (text[i] === '"') {
|
|
128
|
+
i++;
|
|
129
|
+
return JSON.parse(text.slice(start, i));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return fail("unterminated string");
|
|
133
|
+
};
|
|
134
|
+
const result = value();
|
|
135
|
+
space();
|
|
136
|
+
if (i < text.length)
|
|
137
|
+
fail("end expected");
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
export function writeJson(node, indent = false, level = 0) {
|
|
141
|
+
const pad = (n) => (indent ? `\n${" ".repeat(n)}` : "");
|
|
142
|
+
switch (node.t) {
|
|
143
|
+
case "o":
|
|
144
|
+
return node.m.length === 0 ? "{}" : `{${node.m.map(([k, v]) => `${pad(level + 1)}${JSON.stringify(k)}:${indent ? " " : ""}${writeJson(v, indent, level + 1)}`).join(",")}${pad(level)}}`;
|
|
145
|
+
case "a":
|
|
146
|
+
return node.v.length === 0 ? "[]" : `[${node.v.map((v) => `${pad(level + 1)}${writeJson(v, indent, level + 1)}`).join(",")}${pad(level)}]`;
|
|
147
|
+
case "s":
|
|
148
|
+
return JSON.stringify(node.v);
|
|
149
|
+
case "n":
|
|
150
|
+
return node.v;
|
|
151
|
+
case "b":
|
|
152
|
+
return node.v ? "true" : "false";
|
|
153
|
+
case "z":
|
|
154
|
+
return "null";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** ABAP component name → JSON member name, for a pretty mode of /ui2/cl_json (or xco). */
|
|
158
|
+
export function jsonName(abap, mode, mappings) {
|
|
159
|
+
const mapped = mappings.get(abap.toUpperCase());
|
|
160
|
+
if (mapped !== undefined && (mode === "U" || mode === "C" || mode === "")) {
|
|
161
|
+
return mapped;
|
|
162
|
+
}
|
|
163
|
+
const camel = (name) => name.toLowerCase().replace(/_+([a-z0-9])/g, (_m, c) => c.toUpperCase());
|
|
164
|
+
switch (mode) {
|
|
165
|
+
case "L":
|
|
166
|
+
case "C":
|
|
167
|
+
return abap.toLowerCase();
|
|
168
|
+
case "X":
|
|
169
|
+
case "U":
|
|
170
|
+
return camel(abap);
|
|
171
|
+
case "Y":
|
|
172
|
+
// extended: like camel case, "__" keeps one underscore, "___" gives a dot
|
|
173
|
+
return abap
|
|
174
|
+
.toLowerCase()
|
|
175
|
+
.split("___")
|
|
176
|
+
.map((part) => part.split("__").map(camel).join("_"))
|
|
177
|
+
.join(".");
|
|
178
|
+
default:
|
|
179
|
+
return abap.toUpperCase();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function parseMappings(text) {
|
|
183
|
+
const mappings = new Map();
|
|
184
|
+
for (const line of text.split("\n")) {
|
|
185
|
+
const [abap, json] = line.split("\t");
|
|
186
|
+
if (abap !== undefined && abap.trim() !== "" && json !== undefined) {
|
|
187
|
+
mappings.set(abap.trim().toUpperCase(), json);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return mappings;
|
|
191
|
+
}
|
|
192
|
+
function isoOfDigits(digits, fraction) {
|
|
193
|
+
const d = digits.padStart(14, "0");
|
|
194
|
+
return `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}T${d.slice(8, 10)}:${d.slice(10, 12)}:${d.slice(12, 14)}${fraction === "" ? "" : `.${fraction}`}Z`;
|
|
195
|
+
}
|
|
196
|
+
/** An ABAP data object as a JSON node; undefined to leave out (compress). */
|
|
197
|
+
function toJson(input, options) {
|
|
198
|
+
const { types } = abapGlobal();
|
|
199
|
+
const value = deref(input);
|
|
200
|
+
if (value === undefined || value === null) {
|
|
201
|
+
return { t: "z" };
|
|
202
|
+
}
|
|
203
|
+
if (value instanceof types.Structure) {
|
|
204
|
+
const m = [];
|
|
205
|
+
for (const [name, component] of Object.entries(value.get())) {
|
|
206
|
+
const node = toJson(component, options);
|
|
207
|
+
if (node !== undefined) {
|
|
208
|
+
m.push([jsonName(name, options.mode, options.mappings), node]);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return options.compress && m.length === 0 ? undefined : { t: "o", m };
|
|
212
|
+
}
|
|
213
|
+
if (isTable(value)) {
|
|
214
|
+
const v = rowsOf(value).map((row) => toJson(row, options) ?? { t: "o", m: [] });
|
|
215
|
+
return options.compress && v.length === 0 ? undefined : { t: "a", v };
|
|
216
|
+
}
|
|
217
|
+
if (value instanceof types.DataReference) {
|
|
218
|
+
const target = value.getPointer();
|
|
219
|
+
return target === undefined ? (options.compress ? undefined : { t: "z" }) : toJson(target, options);
|
|
220
|
+
}
|
|
221
|
+
if (value instanceof types.ABAPObject) {
|
|
222
|
+
return options.compress ? undefined : { t: "z" };
|
|
223
|
+
}
|
|
224
|
+
const omit = (initial) => options.compress && initial;
|
|
225
|
+
if (value instanceof types.Character) {
|
|
226
|
+
const text = charText(value);
|
|
227
|
+
if (options.booleans && value.getLength() === 1 && BOOLEAN_TYPES.has(qualified(value))) {
|
|
228
|
+
return omit(text === "") ? undefined : { t: "b", v: text !== "" };
|
|
229
|
+
}
|
|
230
|
+
return omit(text === "") ? undefined : { t: "s", v: text };
|
|
231
|
+
}
|
|
232
|
+
if (value instanceof types.String) {
|
|
233
|
+
const text = String(value.get());
|
|
234
|
+
return omit(text === "") ? undefined : { t: "s", v: text };
|
|
235
|
+
}
|
|
236
|
+
if (value instanceof types.Numc) {
|
|
237
|
+
const text = String(value.get());
|
|
238
|
+
if (omit(/^0*$/.test(text)))
|
|
239
|
+
return undefined;
|
|
240
|
+
return options.numcAsString ? { t: "s", v: text } : { t: "n", v: String(BigInt(text === "" ? "0" : text)) };
|
|
241
|
+
}
|
|
242
|
+
if (value instanceof types.Date) {
|
|
243
|
+
const d = String(value.get());
|
|
244
|
+
const initial = /^0*$/.test(d) || d.trim() === "";
|
|
245
|
+
return omit(initial) ? undefined : { t: "s", v: initial ? "" : `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}` };
|
|
246
|
+
}
|
|
247
|
+
if (value instanceof types.Time) {
|
|
248
|
+
const t = String(value.get()).padEnd(6, "0");
|
|
249
|
+
return omit(/^0*$/.test(t)) ? undefined : { t: "s", v: `${t.slice(0, 2)}:${t.slice(2, 4)}:${t.slice(4, 6)}` };
|
|
250
|
+
}
|
|
251
|
+
if (value instanceof types.Packed) {
|
|
252
|
+
const text = packedText(value);
|
|
253
|
+
if (omit(/^-?[0.]*$/.test(text)))
|
|
254
|
+
return undefined;
|
|
255
|
+
if (options.tsAsIso && TIMESTAMP_TYPES.has(qualified(value))) {
|
|
256
|
+
const [whole = "", fraction = ""] = text.split(".");
|
|
257
|
+
return /^0*$/.test(whole) ? { t: "s", v: "" } : { t: "s", v: isoOfDigits(whole, fraction) };
|
|
258
|
+
}
|
|
259
|
+
return { t: "n", v: text };
|
|
260
|
+
}
|
|
261
|
+
if (value instanceof types.Integer || value instanceof types.Integer8 || value instanceof types.Float) {
|
|
262
|
+
const text = String(value.get());
|
|
263
|
+
return omit(text === "0") ? undefined : { t: "n", v: text };
|
|
264
|
+
}
|
|
265
|
+
if (types.DecFloat34 !== undefined && value instanceof types.DecFloat34) {
|
|
266
|
+
const text = typeof value.toText === "function" ? value.toText() : String(value.get());
|
|
267
|
+
return omit(Number(text) === 0) ? undefined : { t: "n", v: text };
|
|
268
|
+
}
|
|
269
|
+
if (value instanceof types.Hex || value instanceof types.XString) {
|
|
270
|
+
const hex = String(value.get());
|
|
271
|
+
if (omit(hex === "" || /^0*$/.test(hex)))
|
|
272
|
+
return undefined;
|
|
273
|
+
return { t: "s", v: options.hexAsBase64 ? Buffer.from(hex, "hex").toString("base64") : hex };
|
|
274
|
+
}
|
|
275
|
+
if (value instanceof types.UTCLong) {
|
|
276
|
+
const text = String(value.get()).trim();
|
|
277
|
+
return omit(text === "") ? undefined : { t: "s", v: text === "" ? "" : `${text.replace(" ", "T")}Z` };
|
|
278
|
+
}
|
|
279
|
+
const text = String(value.get?.() ?? "");
|
|
280
|
+
return omit(text === "") ? undefined : { t: "s", v: text };
|
|
281
|
+
}
|
|
282
|
+
function normalizedName(name) {
|
|
283
|
+
return name.toLowerCase().replace(/[_\-.]/g, "");
|
|
284
|
+
}
|
|
285
|
+
/** Fills an ABAP data object from a JSON node. */
|
|
286
|
+
function fromJson(target, node, options) {
|
|
287
|
+
const { types } = abapGlobal();
|
|
288
|
+
const value = deref(target);
|
|
289
|
+
if (value === undefined || node.t === "z") {
|
|
290
|
+
value?.clear?.();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (value instanceof types.Structure) {
|
|
294
|
+
if (node.t !== "o")
|
|
295
|
+
return;
|
|
296
|
+
const byName = new Map(node.m);
|
|
297
|
+
const byNormalized = new Map(node.m.map(([k, v]) => [normalizedName(k), v]));
|
|
298
|
+
for (const [name, component] of Object.entries(value.get())) {
|
|
299
|
+
const found = byName.get(jsonName(name, options.mode, options.mappings)) ?? byName.get(name) ?? byNormalized.get(normalizedName(name));
|
|
300
|
+
if (found !== undefined) {
|
|
301
|
+
fromJson(component, found, options);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (isTable(value)) {
|
|
307
|
+
value.clear();
|
|
308
|
+
const elements = node.t === "a" ? node.v : node.t === "o" ? node.m.map(([, v]) => v) : [node];
|
|
309
|
+
for (const element of elements) {
|
|
310
|
+
appendRow(value, (row) => fromJson(row, element, options));
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (value instanceof types.DataReference || value instanceof types.ABAPObject) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const text = node.t === "s" || node.t === "n" ? node.v : node.t === "b" ? (node.v ? "X" : "") : writeJson(node);
|
|
318
|
+
if (value instanceof types.Date) {
|
|
319
|
+
value.set(text === "" ? "00000000" : text.replace(/-/g, "").slice(0, 8));
|
|
320
|
+
}
|
|
321
|
+
else if (value instanceof types.Time) {
|
|
322
|
+
value.set(text === "" ? "000000" : text.replace(/:/g, "").slice(0, 6));
|
|
323
|
+
}
|
|
324
|
+
else if (value instanceof types.Packed) {
|
|
325
|
+
if (node.t === "s" && /^\d{4}-\d{2}-\d{2}T/.test(text)) {
|
|
326
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?/.exec(text);
|
|
327
|
+
value.set(m === null ? "0" : `${m[1]}${m[2]}${m[3]}${m[4]}${m[5]}${m[6]}${m[7] === undefined ? "" : `.${m[7]}`}`);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
value.set(text === "" ? "0" : text);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else if (value instanceof types.Integer || value instanceof types.Float) {
|
|
334
|
+
value.set(text === "" ? 0 : Number(text));
|
|
335
|
+
}
|
|
336
|
+
else if (value instanceof types.Integer8) {
|
|
337
|
+
value.set(BigInt(text === "" ? "0" : text.split(".")[0] ?? "0"));
|
|
338
|
+
}
|
|
339
|
+
else if (value instanceof types.Hex || value instanceof types.XString) {
|
|
340
|
+
value.set(options.hexAsBase64 && node.t === "s" ? Buffer.from(text, "base64").toString("hex").toUpperCase() : text.toUpperCase());
|
|
341
|
+
}
|
|
342
|
+
else if (value instanceof types.UTCLong) {
|
|
343
|
+
value.set(text.replace("T", " ").replace(/Z$/, ""));
|
|
344
|
+
}
|
|
345
|
+
else if (value instanceof types.Numc) {
|
|
346
|
+
value.set(text.split(".")[0] ?? "");
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
value.set(text);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/** The member names of a JSON node, transformed (xco_cp_json transformations). */
|
|
353
|
+
function transform(node, kinds) {
|
|
354
|
+
const name = (key) => {
|
|
355
|
+
let result = key;
|
|
356
|
+
for (const kind of kinds) {
|
|
357
|
+
switch (kind) {
|
|
358
|
+
case "camel_case_to_underscore":
|
|
359
|
+
case "pascal_case_to_underscore":
|
|
360
|
+
result = result
|
|
361
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
362
|
+
.replace(/([A-Z])([A-Z][a-z])/g, "$1_$2")
|
|
363
|
+
.toUpperCase();
|
|
364
|
+
break;
|
|
365
|
+
case "underscore_to_camel_case":
|
|
366
|
+
result = result.toLowerCase().replace(/_+([a-z0-9])/g, (_m, c) => c.toUpperCase());
|
|
367
|
+
break;
|
|
368
|
+
case "underscore_to_pascal_case":
|
|
369
|
+
result = result.toLowerCase().replace(/(^|_+)([a-z0-9])/g, (_m, _u, c) => c.toUpperCase());
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return result;
|
|
374
|
+
};
|
|
375
|
+
const booleans = kinds.includes("boolean_to_abap_bool");
|
|
376
|
+
const abapBools = kinds.includes("abap_bool_to_boolean");
|
|
377
|
+
const walk = (n) => {
|
|
378
|
+
switch (n.t) {
|
|
379
|
+
case "o":
|
|
380
|
+
return { t: "o", m: n.m.map(([k, v]) => [name(k), walk(v)]) };
|
|
381
|
+
case "a":
|
|
382
|
+
return { t: "a", v: n.v.map(walk) };
|
|
383
|
+
case "b":
|
|
384
|
+
return booleans ? { t: "s", v: n.v ? "X" : "" } : n;
|
|
385
|
+
case "s":
|
|
386
|
+
return abapBools && (n.v === "X" || n.v === "") ? { t: "b", v: n.v === "X" } : n;
|
|
387
|
+
default:
|
|
388
|
+
return n;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
return walk(node);
|
|
392
|
+
}
|
|
393
|
+
export function codePage(name) {
|
|
394
|
+
const key = name.trim().toUpperCase().replace(/[-_\s]/g, "");
|
|
395
|
+
const pages = {
|
|
396
|
+
UTF8: "utf8",
|
|
397
|
+
"4110": "utf8",
|
|
398
|
+
UTF16: "utf16le",
|
|
399
|
+
UTF16LE: "utf16le",
|
|
400
|
+
"4103": "utf16le",
|
|
401
|
+
UTF16BE: "utf16be",
|
|
402
|
+
"4102": "utf16be",
|
|
403
|
+
ISO88591: "latin1",
|
|
404
|
+
LATIN1: "latin1",
|
|
405
|
+
"1100": "latin1",
|
|
406
|
+
ISO885915: "latin1",
|
|
407
|
+
"1160": "windows-1252",
|
|
408
|
+
WINDOWS1252: "windows-1252",
|
|
409
|
+
CP1252: "windows-1252",
|
|
410
|
+
USASCII: "ascii",
|
|
411
|
+
ASCII: "ascii",
|
|
412
|
+
};
|
|
413
|
+
return pages[key];
|
|
414
|
+
}
|
|
415
|
+
async function encode(text, page) {
|
|
416
|
+
switch (page) {
|
|
417
|
+
case "utf8":
|
|
418
|
+
return Buffer.from(text, "utf8");
|
|
419
|
+
case "utf16le":
|
|
420
|
+
return Buffer.from(text, "utf16le");
|
|
421
|
+
case "utf16be":
|
|
422
|
+
return Buffer.from(text, "utf16le").swap16();
|
|
423
|
+
case "ascii":
|
|
424
|
+
case "latin1":
|
|
425
|
+
case "windows-1252": {
|
|
426
|
+
const limit = page === "ascii" ? 0x7f : 0xff;
|
|
427
|
+
const bad = [...text].find((c) => (c.codePointAt(0) ?? 0) > limit);
|
|
428
|
+
if (bad !== undefined) {
|
|
429
|
+
return raise("cx_sy_conversion_codepage", `the character ${JSON.stringify(bad)} has no representation in the code page ${page}`);
|
|
430
|
+
}
|
|
431
|
+
return Buffer.from(text, "latin1");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async function decode(bytes, page) {
|
|
436
|
+
try {
|
|
437
|
+
switch (page) {
|
|
438
|
+
case "utf8":
|
|
439
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
440
|
+
case "utf16le":
|
|
441
|
+
return bytes.toString("utf16le");
|
|
442
|
+
case "utf16be":
|
|
443
|
+
return Buffer.from(bytes).swap16().toString("utf16le");
|
|
444
|
+
case "ascii":
|
|
445
|
+
case "latin1":
|
|
446
|
+
return bytes.toString("latin1");
|
|
447
|
+
case "windows-1252":
|
|
448
|
+
return new TextDecoder("windows-1252").decode(bytes);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
catch (e) {
|
|
452
|
+
return raise("cx_sy_conversion_codepage", `the bytes are not valid ${page}: ${e.message}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// ------------------------------------------------------------------------------ regular expressions
|
|
456
|
+
/** An ABAP pattern as a JavaScript one: extended mode (x) strips spaces and comments. */
|
|
457
|
+
function regExp(pattern, flags, global = true) {
|
|
458
|
+
let source = pattern;
|
|
459
|
+
if (flags.includes("x")) {
|
|
460
|
+
let out = "";
|
|
461
|
+
let inClass = false;
|
|
462
|
+
for (let i = 0; i < source.length; i++) {
|
|
463
|
+
const c = source[i] ?? "";
|
|
464
|
+
if (c === "\\") {
|
|
465
|
+
out += c + (source[i + 1] ?? "");
|
|
466
|
+
i++;
|
|
467
|
+
}
|
|
468
|
+
else if (inClass) {
|
|
469
|
+
if (c === "]")
|
|
470
|
+
inClass = false;
|
|
471
|
+
out += c;
|
|
472
|
+
}
|
|
473
|
+
else if (c === "[") {
|
|
474
|
+
inClass = true;
|
|
475
|
+
out += c;
|
|
476
|
+
}
|
|
477
|
+
else if (c === "#") {
|
|
478
|
+
while (i < source.length && source[i] !== "\n")
|
|
479
|
+
i++;
|
|
480
|
+
}
|
|
481
|
+
else if (!/\s/.test(c)) {
|
|
482
|
+
out += c;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
source = out;
|
|
486
|
+
}
|
|
487
|
+
if (!flags.includes("p")) {
|
|
488
|
+
// POSIX classes and word boundaries of the ABAP (POSIX) syntax
|
|
489
|
+
source = source
|
|
490
|
+
.replace(/\[:alpha:\]/g, "A-Za-z")
|
|
491
|
+
.replace(/\[:digit:\]/g, "0-9")
|
|
492
|
+
.replace(/\[:alnum:\]/g, "A-Za-z0-9")
|
|
493
|
+
.replace(/\[:upper:\]/g, "A-Z")
|
|
494
|
+
.replace(/\[:lower:\]/g, "a-z")
|
|
495
|
+
.replace(/\[:space:\]/g, "\\s")
|
|
496
|
+
.replace(/\[:punct:\]/g, "!-\\/:-@\\[-`{-~")
|
|
497
|
+
.replace(/\\</g, "\\b(?=\\w)")
|
|
498
|
+
.replace(/\\>/g, "\\b(?<=\\w)");
|
|
499
|
+
}
|
|
500
|
+
return new RegExp(source, `${global ? "g" : ""}d${flags.includes("i") ? "i" : ""}${flags.includes("m") ? "m" : ""}${flags.includes("s") ? "s" : ""}`);
|
|
501
|
+
}
|
|
502
|
+
async function compile(pattern, flags) {
|
|
503
|
+
try {
|
|
504
|
+
return regExp(pattern, flags);
|
|
505
|
+
}
|
|
506
|
+
catch (e) {
|
|
507
|
+
return raise("cx_sy_regex", `invalid regular expression ${pattern}: ${e.message}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// ------------------------------------------------------------------------------ dates and time stamps
|
|
511
|
+
const TICKS_PER_SECOND = 10000000n;
|
|
512
|
+
/** `YYYYMMDDhhmmss[.fffffff]` → ticks (100 ns) since 1970-01-01, through the proleptic calendar of Date. */
|
|
513
|
+
function tstmpTicks(text) {
|
|
514
|
+
const [whole = "0", fraction = ""] = text.trim().replace(/^-/, "").split(".");
|
|
515
|
+
const d = whole.padStart(14, "0");
|
|
516
|
+
const date = new Date(0);
|
|
517
|
+
date.setUTCFullYear(Number(d.slice(0, 4)), Number(d.slice(4, 6)) - 1, Number(d.slice(6, 8)));
|
|
518
|
+
date.setUTCHours(Number(d.slice(8, 10)), Number(d.slice(10, 12)), Number(d.slice(12, 14)), 0);
|
|
519
|
+
const seconds = BigInt(Math.floor(date.getTime() / 1000));
|
|
520
|
+
return seconds * TICKS_PER_SECOND + BigInt(fraction.padEnd(7, "0").slice(0, 7) || "0");
|
|
521
|
+
}
|
|
522
|
+
function ticksTstmp(ticks, decimals = 7) {
|
|
523
|
+
let seconds = ticks / TICKS_PER_SECOND;
|
|
524
|
+
let fraction = ticks % TICKS_PER_SECOND;
|
|
525
|
+
if (fraction < 0n) {
|
|
526
|
+
fraction += TICKS_PER_SECOND;
|
|
527
|
+
seconds -= 1n;
|
|
528
|
+
}
|
|
529
|
+
const date = new Date(Number(seconds) * 1000);
|
|
530
|
+
const pad = (n, l = 2) => String(n).padStart(l, "0");
|
|
531
|
+
const digits = `${pad(date.getUTCFullYear(), 4)}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;
|
|
532
|
+
return decimals === 0 ? digits : `${digits}.${fraction.toString().padStart(7, "0").slice(0, decimals)}`;
|
|
533
|
+
}
|
|
534
|
+
/** Seconds as a decimal text → ticks. */
|
|
535
|
+
function secondsTicks(text) {
|
|
536
|
+
const t = text.trim().replace(/E\+?0*$/i, "");
|
|
537
|
+
const negative = t.startsWith("-");
|
|
538
|
+
const [whole = "0", fraction = ""] = t.replace(/^[-+]/, "").split(".");
|
|
539
|
+
const ticks = BigInt(whole === "" ? "0" : whole) * TICKS_PER_SECOND + BigInt(fraction.padEnd(7, "0").slice(0, 7) || "0");
|
|
540
|
+
return negative ? -ticks : ticks;
|
|
541
|
+
}
|
|
542
|
+
function ticksSeconds(ticks) {
|
|
543
|
+
const negative = ticks < 0n;
|
|
544
|
+
const abs = negative ? -ticks : ticks;
|
|
545
|
+
return `${negative ? "-" : ""}${abs / TICKS_PER_SECOND}.${(abs % TICKS_PER_SECOND).toString().padStart(7, "0")}`;
|
|
546
|
+
}
|
|
547
|
+
/** Adds years and months (the day clamped to the end of the month), then days. */
|
|
548
|
+
function addCalendar(year, month, day, years, months, days) {
|
|
549
|
+
const total = year * 12 + (month - 1) + years * 12 + months;
|
|
550
|
+
const y = Math.floor(total / 12);
|
|
551
|
+
const m = total - y * 12;
|
|
552
|
+
const last = new Date(Date.UTC(2000, m + 1, 0));
|
|
553
|
+
last.setUTCFullYear(y, m + 1, 0);
|
|
554
|
+
const date = new Date(0);
|
|
555
|
+
date.setUTCFullYear(y, m, Math.min(day, last.getUTCDate()));
|
|
556
|
+
date.setUTCDate(date.getUTCDate() + days);
|
|
557
|
+
return date;
|
|
558
|
+
}
|
|
559
|
+
const pad2 = (n) => String(n).padStart(2, "0");
|
|
560
|
+
const dateDigits = (d) => `${String(d.getUTCFullYear()).padStart(4, "0")}${pad2(d.getUTCMonth() + 1)}${pad2(d.getUTCDate())}`;
|
|
561
|
+
function utclongText(ticks) {
|
|
562
|
+
const t = ticksTstmp(ticks);
|
|
563
|
+
return `${t.slice(0, 4)}-${t.slice(4, 6)}-${t.slice(6, 8)} ${t.slice(8, 10)}:${t.slice(10, 12)}:${t.slice(12, 14)}.${t.slice(15, 22)}`;
|
|
564
|
+
}
|
|
565
|
+
function utclongTicks(text) {
|
|
566
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:[.,](\d{1,7}))?Z?$/.exec(text.trim());
|
|
567
|
+
if (m === null) {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
return tstmpTicks(`${m[1]}${m[2]}${m[3]}${m[4]}${m[5]}${m[6]}.${m[7] ?? ""}`);
|
|
571
|
+
}
|
|
572
|
+
// ------------------------------------------------------------------------------ installation
|
|
573
|
+
const intOf = (value) => (value === undefined ? 0 : Number(deref(value).get()));
|
|
574
|
+
const hexOf = (value) => (value === undefined ? "" : String(deref(value).get()));
|
|
575
|
+
const flag = (value) => value !== undefined && abapString(value) !== "";
|
|
576
|
+
export function installUtilities(kernel, services) {
|
|
577
|
+
const abap = abapGlobal();
|
|
578
|
+
const string = (text) => new abap.types.String().set(text);
|
|
579
|
+
const xstring = (bytes) => new abap.types.XString().set(bytes.toString("hex").toUpperCase());
|
|
580
|
+
const timestampl = (text) => new abap.types.Packed({ length: 11, decimals: 7 }).set(text);
|
|
581
|
+
// ---------------------------------------------------------------- JSON
|
|
582
|
+
kernel.json_serialize = async (input) => {
|
|
583
|
+
const mode = abapString(input?.["mode"]);
|
|
584
|
+
const xco = mode === "xco";
|
|
585
|
+
const node = toJson(input?.["data"], {
|
|
586
|
+
mode: xco ? "" : mode,
|
|
587
|
+
compress: flag(input?.["compress"]),
|
|
588
|
+
numcAsString: flag(input?.["numc_as_string"]),
|
|
589
|
+
tsAsIso: flag(input?.["ts_as_iso8601"]),
|
|
590
|
+
hexAsBase64: input?.["hex_as_base64"] === undefined ? true : flag(input["hex_as_base64"]),
|
|
591
|
+
booleans: flag(input?.["booleans"]),
|
|
592
|
+
mappings: parseMappings(String(input?.["mappings"]?.get?.() ?? "")),
|
|
593
|
+
});
|
|
594
|
+
return string(node === undefined ? "{}" : writeJson(node, flag(input?.["format_output"])));
|
|
595
|
+
};
|
|
596
|
+
kernel.json_deserialize = async (input) => {
|
|
597
|
+
const text = String(input?.["json"]?.get?.() ?? "");
|
|
598
|
+
let node;
|
|
599
|
+
try {
|
|
600
|
+
node = parseJson(text);
|
|
601
|
+
}
|
|
602
|
+
catch (e) {
|
|
603
|
+
// /ui2/cl_json leaves the data unchanged when the JSON is invalid
|
|
604
|
+
if (abapString(input?.["mode"]) === "xco") {
|
|
605
|
+
await raise("cx_sy_conversion_error", e.message);
|
|
606
|
+
}
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const mode = abapString(input?.["mode"]);
|
|
610
|
+
fromJson(input?.["data"], node, {
|
|
611
|
+
mode: mode === "xco" ? "" : mode,
|
|
612
|
+
hexAsBase64: input?.["hex_as_base64"] === undefined ? true : flag(input["hex_as_base64"]),
|
|
613
|
+
mappings: parseMappings(String(input?.["mappings"]?.get?.() ?? "")),
|
|
614
|
+
});
|
|
615
|
+
};
|
|
616
|
+
kernel.json_transform = async (input) => {
|
|
617
|
+
const kinds = abapString(input?.["kinds"]).split(/\s+/).filter((k) => k !== "");
|
|
618
|
+
return string(writeJson(transform(parseJson(String(input?.["json"]?.get?.() ?? "")), kinds)));
|
|
619
|
+
};
|
|
620
|
+
kernel.json_quote = async (input) => string(JSON.stringify(String(input?.["text"]?.get?.() ?? "")));
|
|
621
|
+
// ---------------------------------------------------------------- encodings
|
|
622
|
+
kernel.codepage_check = async (input) => {
|
|
623
|
+
const name = abapString(input?.["codepage"]);
|
|
624
|
+
if (codePage(name) === undefined) {
|
|
625
|
+
await raise("cx_parameter_invalid_range", `the code page ${name} is not supported (UTF-8, UTF-16LE, UTF-16BE, ISO-8859-1, windows-1252, US-ASCII)`);
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
kernel.codepage_out = async (input) => {
|
|
629
|
+
const name = abapString(input?.["codepage"]);
|
|
630
|
+
const page = codePage(name);
|
|
631
|
+
if (page === undefined) {
|
|
632
|
+
return raise("cx_sy_conversion_codepage", `the code page ${name} is not supported`);
|
|
633
|
+
}
|
|
634
|
+
return xstring(await encode(String(input?.["source"]?.get?.() ?? ""), page));
|
|
635
|
+
};
|
|
636
|
+
kernel.codepage_in = async (input) => {
|
|
637
|
+
const name = abapString(input?.["codepage"]);
|
|
638
|
+
const page = codePage(name);
|
|
639
|
+
if (page === undefined) {
|
|
640
|
+
return raise("cx_sy_conversion_codepage", `the code page ${name} is not supported`);
|
|
641
|
+
}
|
|
642
|
+
return string(await decode(Buffer.from(hexOf(input?.["source"]), "hex"), page));
|
|
643
|
+
};
|
|
644
|
+
kernel.base64_encode_x = async (input) => string(Buffer.from(hexOf(input?.["data"]), "hex").toString("base64"));
|
|
645
|
+
kernel.base64_decode = async (input) => xstring(Buffer.from(String(input?.["text"]?.get?.() ?? "").trim(), "base64"));
|
|
646
|
+
kernel.text_escape = async (input) => {
|
|
647
|
+
const text = String(input?.["text"]?.get?.() ?? "");
|
|
648
|
+
switch (abapString(input?.["format"])) {
|
|
649
|
+
case "url":
|
|
650
|
+
return string(encodeURIComponent(text));
|
|
651
|
+
case "url_unescape":
|
|
652
|
+
try {
|
|
653
|
+
return string(decodeURIComponent(text.replace(/\+/g, " ")));
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
return string(text);
|
|
657
|
+
}
|
|
658
|
+
case "html":
|
|
659
|
+
return string(text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"));
|
|
660
|
+
case "rtrim":
|
|
661
|
+
return string(text.replace(/ +$/, ""));
|
|
662
|
+
default:
|
|
663
|
+
return string(text);
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
kernel.chars_raw = async (input) => {
|
|
667
|
+
const value = deref(input?.["value"]);
|
|
668
|
+
const text = String(value?.get?.() ?? "");
|
|
669
|
+
return string(typeof value?.getLength === "function" ? text.padEnd(value.getLength(), " ") : text);
|
|
670
|
+
};
|
|
671
|
+
kernel.char_code = async (input) => string(String.fromCharCode(intOf(input?.["code"])));
|
|
672
|
+
kernel.gzip = async (input) => {
|
|
673
|
+
const source = Buffer.from(hexOf(input?.["source"]), "hex");
|
|
674
|
+
try {
|
|
675
|
+
return xstring(abapString(input?.["operation"]) === "compress" ? gzipSync(source) : gunzipSync(source));
|
|
676
|
+
}
|
|
677
|
+
catch (e) {
|
|
678
|
+
return raise("cx_sy_compression_error", `gzip: ${e.message}`);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
kernel.digest = async (input) => {
|
|
682
|
+
const name = abapString(input?.["algorithm"]).toLowerCase().replace(/-/g, "");
|
|
683
|
+
if (!getHashes().includes(name)) {
|
|
684
|
+
return raise("cx_abap_message_digest", `the hash algorithm ${abapString(input?.["algorithm"])} is not supported (MD5, SHA1, SHA224, SHA256, SHA384, SHA512)`);
|
|
685
|
+
}
|
|
686
|
+
const data = Buffer.from(hexOf(input?.["data"]), "hex");
|
|
687
|
+
const key = hexOf(input?.["key"]);
|
|
688
|
+
const hash = input?.["key"] !== undefined && key !== "" ? createHmac(name, Buffer.from(key, "hex")) : createHash(name);
|
|
689
|
+
return xstring(hash.update(data).digest());
|
|
690
|
+
};
|
|
691
|
+
kernel.random_seed = async () => new abap.types.Integer().set(randomInt(1, 2147483646));
|
|
692
|
+
// ---------------------------------------------------------------- regular expressions
|
|
693
|
+
kernel.regex_check = async (input) => {
|
|
694
|
+
await compile(abapString(input?.["pattern"]), abapString(input?.["flags"]));
|
|
695
|
+
};
|
|
696
|
+
kernel.regex_find = async (input) => {
|
|
697
|
+
const re = await compile(String(input?.["pattern"]?.get?.() ?? ""), abapString(input?.["flags"]));
|
|
698
|
+
const text = String(input?.["text"]?.get?.() ?? "");
|
|
699
|
+
re.lastIndex = intOf(input?.["start"]);
|
|
700
|
+
const match = re.lastIndex > text.length ? null : re.exec(text);
|
|
701
|
+
if (match === null) {
|
|
702
|
+
input?.["offset"]?.set(-1);
|
|
703
|
+
input?.["length"]?.set(0);
|
|
704
|
+
input?.["submatches"]?.set("");
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
input?.["offset"]?.set(match.index);
|
|
708
|
+
input?.["length"]?.set(match[0].length);
|
|
709
|
+
const indices = match.indices ?? [];
|
|
710
|
+
input?.["submatches"]?.set(indices
|
|
711
|
+
.slice(1)
|
|
712
|
+
.map((pair) => (pair === undefined ? "-1,0" : `${pair[0]},${pair[1] - pair[0]}`))
|
|
713
|
+
.join(" "));
|
|
714
|
+
};
|
|
715
|
+
kernel.regex_replace = async (input) => {
|
|
716
|
+
const all = flag(input?.["all"]);
|
|
717
|
+
const re = await compile(String(input?.["pattern"]?.get?.() ?? ""), abapString(input?.["flags"]));
|
|
718
|
+
const pattern = all ? re : new RegExp(re.source, re.flags.replace("g", ""));
|
|
719
|
+
const text = String(input?.["text"]?.get?.() ?? "");
|
|
720
|
+
const replacement = String(input?.["newtext"]?.get?.() ?? "").replace(/\$0/g, "$$&").replace(/\\([1-9])/g, "$$$1");
|
|
721
|
+
const matches = [...text.matchAll(new RegExp(re.source, re.flags.includes("g") ? re.flags : `${re.flags}g`))];
|
|
722
|
+
const count = all ? matches.length : Math.min(matches.length, 1);
|
|
723
|
+
const result = text.replace(pattern, replacement);
|
|
724
|
+
input?.["count"]?.set(count);
|
|
725
|
+
return string(result);
|
|
726
|
+
};
|
|
727
|
+
// ---------------------------------------------------------------- dates and time stamps
|
|
728
|
+
kernel.date_add = async (input) => {
|
|
729
|
+
const d = String(deref(input?.["date"]).get());
|
|
730
|
+
if (/^0*$/.test(d) || d.trim() === "") {
|
|
731
|
+
return new abap.types.Date().set(d);
|
|
732
|
+
}
|
|
733
|
+
const result = addCalendar(Number(d.slice(0, 4)), Number(d.slice(4, 6)), Number(d.slice(6, 8)), intOf(input?.["years"]), intOf(input?.["months"]), intOf(input?.["days"]));
|
|
734
|
+
return new abap.types.Date().set(dateDigits(result));
|
|
735
|
+
};
|
|
736
|
+
kernel.moment_add = async (input) => {
|
|
737
|
+
const digits = packedText(deref(input?.["moment"])).split(".")[0]?.padStart(14, "0") ?? "";
|
|
738
|
+
const date = addCalendar(Number(digits.slice(0, 4)), Number(digits.slice(4, 6)), Number(digits.slice(6, 8)), intOf(input?.["years"]), intOf(input?.["months"]), intOf(input?.["days"]));
|
|
739
|
+
const ticks = tstmpTicks(`${dateDigits(date)}${digits.slice(8, 14)}`) + BigInt(intOf(input?.["seconds"])) * TICKS_PER_SECOND;
|
|
740
|
+
return new abap.types.Packed({ length: 8, decimals: 0 }).set(ticksTstmp(ticks, 0));
|
|
741
|
+
};
|
|
742
|
+
kernel.unix_time = async (input) => {
|
|
743
|
+
const digits = packedText(deref(input?.["moment"])).split(".")[0] ?? "0";
|
|
744
|
+
return new abap.types.Integer8().set(tstmpTicks(digits) / TICKS_PER_SECOND);
|
|
745
|
+
};
|
|
746
|
+
kernel.tstmp_add = async (input) => timestampl(ticksTstmp(tstmpTicks(abapString(input?.["tstmp"])) + secondsTicks(abapString(input?.["seconds"]))));
|
|
747
|
+
kernel.tstmp_diff = async (input) => timestampl(ticksSeconds(tstmpTicks(abapString(input?.["tstmp1"])) - tstmpTicks(abapString(input?.["tstmp2"]))));
|
|
748
|
+
kernel.tstmp_to_utclong = async (input) => {
|
|
749
|
+
const text = abapString(input?.["tstmp"]);
|
|
750
|
+
return new abap.types.UTCLong().set(/^[0.]*$/.test(text) ? "" : utclongText(tstmpTicks(text)));
|
|
751
|
+
};
|
|
752
|
+
kernel.utclong_to_tstmp = async (input) => {
|
|
753
|
+
const ticks = utclongTicks(String(deref(input?.["value"]).get() ?? ""));
|
|
754
|
+
return timestampl(ticks === undefined ? "0" : ticksTstmp(ticks));
|
|
755
|
+
};
|
|
756
|
+
kernel.utclong_read = async (input) => {
|
|
757
|
+
const text = abapString(input?.["text"]);
|
|
758
|
+
const ticks = utclongTicks(text);
|
|
759
|
+
if (ticks === undefined) {
|
|
760
|
+
return raise("cx_abap_utclong_invalid", `${text} is not a time stamp (YYYY-MM-DD hh:mm:ss[.fffffff])`);
|
|
761
|
+
}
|
|
762
|
+
return new abap.types.UTCLong().set(utclongText(ticks));
|
|
763
|
+
};
|
|
764
|
+
// ---------------------------------------------------------------- number ranges
|
|
765
|
+
const interval = (input) => ({
|
|
766
|
+
object: abapString(input?.["object"]).toUpperCase(),
|
|
767
|
+
subobject: abapString(input?.["subobject"]).toUpperCase(),
|
|
768
|
+
nr: abapString(input?.["interval"]).toUpperCase(),
|
|
769
|
+
toyear: abapString(input?.["toyear"]).replace(/^0+$/, ""),
|
|
770
|
+
});
|
|
771
|
+
kernel.number_get = async (input) => {
|
|
772
|
+
const key = interval(input);
|
|
773
|
+
const quantity = Math.max(intOf(input?.["quantity"]), 1);
|
|
774
|
+
const db = services.db();
|
|
775
|
+
// an interval that is not configured (rapt/number-ranges.json) starts at 1
|
|
776
|
+
await sql `INSERT INTO rapt_number_range (nr_object, nr_subobject, nr_interval, nr_toyear, from_number, to_number)
|
|
777
|
+
VALUES (${key.object}, ${key.subobject}, ${key.nr}, ${key.toyear}, 1, 99999999999999999999)
|
|
778
|
+
ON CONFLICT DO NOTHING`.execute(db);
|
|
779
|
+
const { rows } = await sql `SELECT current_number::text, from_number::text, to_number::text
|
|
780
|
+
FROM rapt_number_range WHERE nr_object = ${key.object} AND nr_subobject = ${key.subobject} AND nr_interval = ${key.nr} AND nr_toyear = ${key.toyear}`.execute(db);
|
|
781
|
+
const row = rows[0];
|
|
782
|
+
if (row === undefined) {
|
|
783
|
+
return raise("cx_nr_object_not_found", `number range object ${key.object}: interval ${key.nr} not found`);
|
|
784
|
+
}
|
|
785
|
+
const from = BigInt(row.from_number);
|
|
786
|
+
const to = BigInt(row.to_number);
|
|
787
|
+
const current = row.current_number === null ? from - 1n : BigInt(row.current_number);
|
|
788
|
+
if (current >= to) {
|
|
789
|
+
return raise("cx_number_ranges", `number range object ${key.object}, interval ${key.nr}: no number left (last number ${to})`);
|
|
790
|
+
}
|
|
791
|
+
const last = current + BigInt(quantity) > to ? to : current + BigInt(quantity);
|
|
792
|
+
await sql `UPDATE rapt_number_range SET current_number = ${last.toString()}::numeric
|
|
793
|
+
WHERE nr_object = ${key.object} AND nr_subobject = ${key.subobject} AND nr_interval = ${key.nr} AND nr_toyear = ${key.toyear}`.execute(db);
|
|
794
|
+
const returned = Number(last - current);
|
|
795
|
+
input?.["number"]?.set(last.toString().padStart(20, "0"));
|
|
796
|
+
input?.["returned"]?.set(returned);
|
|
797
|
+
input?.["returncode"]?.set(returned < quantity ? "3" : last === to ? "2" : " ");
|
|
798
|
+
};
|
|
799
|
+
kernel.number_check = async (input) => {
|
|
800
|
+
const key = interval(input);
|
|
801
|
+
const { rows } = await sql `SELECT from_number::text, to_number::text FROM rapt_number_range
|
|
802
|
+
WHERE nr_object = ${key.object} AND nr_subobject = ${key.subobject} AND nr_interval = ${key.nr} AND nr_toyear = ${key.toyear}`.execute(services.db());
|
|
803
|
+
const row = rows[0];
|
|
804
|
+
const number = BigInt(abapString(input?.["number"]).replace(/\D/g, "") || "0");
|
|
805
|
+
input?.["returncode"]?.set(row !== undefined && number >= BigInt(row.from_number) && number <= BigInt(row.to_number) ? " " : "1");
|
|
806
|
+
};
|
|
807
|
+
// ---------------------------------------------------------------- application log
|
|
808
|
+
kernel.bali_save = async (input) => {
|
|
809
|
+
const db = services.db();
|
|
810
|
+
const handle = abapString(input?.["handle"]);
|
|
811
|
+
const expiry = String(deref(input?.["expiry_date"])?.get?.() ?? "");
|
|
812
|
+
await sql `DELETE FROM rapt_application_log_item WHERE log_handle = ${handle}`.execute(db);
|
|
813
|
+
await sql `INSERT INTO rapt_application_log (log_handle, log_object, log_subobject, external_id, log_user, log_timestamp, expiry_date)
|
|
814
|
+
VALUES (${handle}, ${abapString(input?.["object"])}, ${abapString(input?.["subobject"])}, ${abapString(input?.["external_id"])}, ${services.user()}, now(),
|
|
815
|
+
${/^\d{8}$/.test(expiry) && !/^0+$/.test(expiry) ? `${expiry.slice(0, 4)}-${expiry.slice(4, 6)}-${expiry.slice(6, 8)}` : null}::date)
|
|
816
|
+
ON CONFLICT (log_handle) DO UPDATE SET log_object = excluded.log_object, log_subobject = excluded.log_subobject,
|
|
817
|
+
external_id = excluded.external_id, expiry_date = excluded.expiry_date`.execute(db);
|
|
818
|
+
let number = 0;
|
|
819
|
+
for (const item of rowsOf(input?.["items"])) {
|
|
820
|
+
const field = (name) => String(item.get()[name]?.get?.() ?? "").replace(/\s+$/, "");
|
|
821
|
+
number++;
|
|
822
|
+
const ts = packedText(item.get()["timestamp"]);
|
|
823
|
+
await sql `INSERT INTO rapt_application_log_item (log_handle, item_number, severity, category, message_text, msgid, msgno, msgv1, msgv2, msgv3, msgv4, item_timestamp)
|
|
824
|
+
VALUES (${handle}, ${number}, ${field("severity")}, ${field("category")}, ${field("text")}, ${field("id")}, ${field("number")},
|
|
825
|
+
${field("v1")}, ${field("v2")}, ${field("v3")}, ${field("v4")}, ${/^[0.]*$/.test(ts) ? null : ticksTstmp(tstmpTicks(ts))})`.execute(db);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
kernel.bali_query = async (input) => {
|
|
829
|
+
const handle = abapString(input?.["handle"]);
|
|
830
|
+
const conditions = [sql `true`];
|
|
831
|
+
if (handle !== "")
|
|
832
|
+
conditions.push(sql `log_handle = ${handle}`);
|
|
833
|
+
for (const [column, name] of [
|
|
834
|
+
["log_object", "object"],
|
|
835
|
+
["log_subobject", "subobject"],
|
|
836
|
+
["external_id", "external_id"],
|
|
837
|
+
["log_user", "user"],
|
|
838
|
+
]) {
|
|
839
|
+
const value = abapString(input?.[name]);
|
|
840
|
+
if (value !== "")
|
|
841
|
+
conditions.push(sql `${sql.ref(column)} = ${value}`);
|
|
842
|
+
}
|
|
843
|
+
const start = utclongTicks(String(deref(input?.["start_time"])?.get?.() ?? ""));
|
|
844
|
+
const end = utclongTicks(String(deref(input?.["end_time"])?.get?.() ?? ""));
|
|
845
|
+
const iso = (ticks) => `${utclongText(ticks).replace(" ", "T")}Z`;
|
|
846
|
+
if (start !== undefined)
|
|
847
|
+
conditions.push(sql `log_timestamp >= ${iso(start)}::timestamptz`);
|
|
848
|
+
if (end !== undefined)
|
|
849
|
+
conditions.push(sql `log_timestamp <= ${iso(end)}::timestamptz`);
|
|
850
|
+
const max = intOf(input?.["max_count"]);
|
|
851
|
+
const { rows } = await sql `
|
|
852
|
+
SELECT log_handle, log_object, log_subobject, external_id, to_char(expiry_date, 'YYYYMMDD') AS expiry_date, log_user,
|
|
853
|
+
to_char(log_timestamp AT TIME ZONE 'UTC', 'YYYYMMDDHH24MISS.US') AS ts
|
|
854
|
+
FROM rapt_application_log WHERE ${sql.join(conditions, sql ` AND `)}
|
|
855
|
+
ORDER BY log_timestamp, log_handle ${max > 0 ? sql `LIMIT ${max}` : sql ``}`.execute(services.db());
|
|
856
|
+
const logs = input?.["logs"];
|
|
857
|
+
logs?.clear();
|
|
858
|
+
for (const row of rows) {
|
|
859
|
+
appendRow(logs, (target) => {
|
|
860
|
+
const c = target.get();
|
|
861
|
+
c["handle"]?.set(row.log_handle);
|
|
862
|
+
c["object"]?.set(row.log_object);
|
|
863
|
+
c["subobject"]?.set(row.log_subobject);
|
|
864
|
+
c["external_id"]?.set(row.external_id);
|
|
865
|
+
c["expiry_date"]?.set(row.expiry_date ?? "00000000");
|
|
866
|
+
c["log_user"]?.set(row.log_user);
|
|
867
|
+
c["log_timestamp"]?.set(row.ts);
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
kernel.bali_items = async (input) => {
|
|
872
|
+
const { rows } = await sql `
|
|
873
|
+
SELECT severity, category, message_text, msgid, msgno, msgv1, msgv2, msgv3, msgv4, item_timestamp::text AS item_timestamp
|
|
874
|
+
FROM rapt_application_log_item WHERE log_handle = ${abapString(input?.["handle"])} ORDER BY item_number`.execute(services.db());
|
|
875
|
+
const items = input?.["items"];
|
|
876
|
+
items?.clear();
|
|
877
|
+
for (const row of rows) {
|
|
878
|
+
appendRow(items, (target) => {
|
|
879
|
+
const c = target.get();
|
|
880
|
+
c["severity"]?.set(row.severity);
|
|
881
|
+
c["category"]?.set(row.category);
|
|
882
|
+
c["text"]?.set(row.message_text);
|
|
883
|
+
c["id"]?.set(row.msgid);
|
|
884
|
+
c["number"]?.set(row.msgno);
|
|
885
|
+
c["v1"]?.set(row.msgv1);
|
|
886
|
+
c["v2"]?.set(row.msgv2);
|
|
887
|
+
c["v3"]?.set(row.msgv3);
|
|
888
|
+
c["v4"]?.set(row.msgv4);
|
|
889
|
+
if (row.item_timestamp !== null)
|
|
890
|
+
c["timestamp"]?.set(row.item_timestamp);
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
kernel.bali_delete = async (input) => {
|
|
895
|
+
const handle = abapString(input?.["handle"]);
|
|
896
|
+
await sql `DELETE FROM rapt_application_log_item WHERE log_handle = ${handle}`.execute(services.db());
|
|
897
|
+
await sql `DELETE FROM rapt_application_log WHERE log_handle = ${handle}`.execute(services.db());
|
|
898
|
+
};
|
|
899
|
+
// ---------------------------------------------------------------- RTTS
|
|
900
|
+
kernel.rtti_describe = async (input) => {
|
|
901
|
+
const { types } = abap;
|
|
902
|
+
const value = deref(input?.["data"]);
|
|
903
|
+
const set = (kind, typeKind, length = 0, decimals = 0, components = "") => {
|
|
904
|
+
input?.["kind"]?.set(kind);
|
|
905
|
+
input?.["type_kind"]?.set(typeKind);
|
|
906
|
+
input?.["length"]?.set(length);
|
|
907
|
+
input?.["decimals"]?.set(decimals);
|
|
908
|
+
input?.["components"]?.set(components);
|
|
909
|
+
const name = qualified(value);
|
|
910
|
+
input?.["name"]?.set(name === "" ? "" : `\\TYPE=${name}`);
|
|
911
|
+
};
|
|
912
|
+
if (value instanceof types.Structure) {
|
|
913
|
+
const components = Object.entries(value.get());
|
|
914
|
+
const deep = components.some(([, c]) => isTable(deref(c)) || deref(c) instanceof types.String || deref(c) instanceof types.XString || deref(c) instanceof types.Structure);
|
|
915
|
+
set("S", deep ? "v" : "u", 0, 0, components.map(([n]) => n.toUpperCase()).join(" "));
|
|
916
|
+
}
|
|
917
|
+
else if (isTable(value)) {
|
|
918
|
+
set("T", "h", 8);
|
|
919
|
+
}
|
|
920
|
+
else if (value instanceof types.DataReference) {
|
|
921
|
+
set("R", "l", 8);
|
|
922
|
+
}
|
|
923
|
+
else if (value instanceof types.ABAPObject) {
|
|
924
|
+
set("R", "r", 8);
|
|
925
|
+
}
|
|
926
|
+
else if (value instanceof types.Character) {
|
|
927
|
+
set("E", "C", value.getLength() * 2);
|
|
928
|
+
}
|
|
929
|
+
else if (value instanceof types.String) {
|
|
930
|
+
set("E", "g", 8);
|
|
931
|
+
}
|
|
932
|
+
else if (value instanceof types.Numc) {
|
|
933
|
+
set("E", "N", value.getLength() * 2);
|
|
934
|
+
}
|
|
935
|
+
else if (value instanceof types.Date) {
|
|
936
|
+
set("E", "D", 16);
|
|
937
|
+
}
|
|
938
|
+
else if (value instanceof types.Time) {
|
|
939
|
+
set("E", "T", 12);
|
|
940
|
+
}
|
|
941
|
+
else if (value instanceof types.Packed) {
|
|
942
|
+
set("E", "P", value.getLength(), value.getDecimals());
|
|
943
|
+
}
|
|
944
|
+
else if (value instanceof types.Integer8) {
|
|
945
|
+
set("E", "8", 8);
|
|
946
|
+
}
|
|
947
|
+
else if (value instanceof types.Integer) {
|
|
948
|
+
const name = qualified(value);
|
|
949
|
+
set("E", name === "INT1" ? "b" : name === "INT2" ? "s" : "I", name === "INT1" ? 1 : name === "INT2" ? 2 : 4);
|
|
950
|
+
}
|
|
951
|
+
else if (value instanceof types.Float) {
|
|
952
|
+
set("E", "F", 8);
|
|
953
|
+
}
|
|
954
|
+
else if (types.DecFloat34 !== undefined && value instanceof types.DecFloat34) {
|
|
955
|
+
set("E", "e", 16);
|
|
956
|
+
}
|
|
957
|
+
else if (value instanceof types.XString) {
|
|
958
|
+
set("E", "y", 8);
|
|
959
|
+
}
|
|
960
|
+
else if (value instanceof types.Hex) {
|
|
961
|
+
set("E", "X", value.getLength());
|
|
962
|
+
}
|
|
963
|
+
else if (value instanceof types.UTCLong) {
|
|
964
|
+
set("E", "p", 8);
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
set("E", "~");
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
kernel.class_name = async (input) => {
|
|
971
|
+
const object = deref(input?.["object"])?.get?.();
|
|
972
|
+
const name = object?.constructor?.INTERNAL_NAME ?? object?.constructor?.name ?? "";
|
|
973
|
+
return string(String(name).toUpperCase());
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
//# sourceMappingURL=utils.js.map
|