@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Clément Ringot and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @open-rap/runtime
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The generic runtime of the applications produced by
|
|
4
|
+
[Open RAP Runtime](https://clementringot.github.io/RAP-Transpiler/): Fastify server, OData V4
|
|
5
|
+
(`$batch`, draft, ETags), RAP managed runtime, execution of the transpiled behavior pool, OIDC
|
|
6
|
+
authentication, launchpad and MCP server.
|
|
7
|
+
|
|
8
|
+
An application generated by `rapt build` bundles it in `vendor/`; `rapt dev` uses it directly.
|
|
9
|
+
Installed with the command line:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install -g open-rap-runtime
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
MIT license.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background processing (bgPF, decision D41): cl_bgmc_process_factory saves a process in the LUW
|
|
3
|
+
* of the request (table rapt_background_process, state NEW); once that LUW is committed, the
|
|
4
|
+
* operation runs in a LUW of its own, as the same user, after the request. A controlled operation
|
|
5
|
+
* (if_bgmc_op_single) writes the database after cl_abap_tx=>save( ) and its EML changes are saved
|
|
6
|
+
* at its end; an uncontrolled one (if_bgmc_op_single_tx_uncontr) writes when it wants and saves
|
|
7
|
+
* its EML changes with COMMIT ENTITIES. The process then ends SUCCESSFUL or ERRONEOUS.
|
|
8
|
+
*
|
|
9
|
+
* The operation object stays in memory: a process that has not run when the application stops
|
|
10
|
+
* is not run again (see KNOWN_GAPS.md).
|
|
11
|
+
*/
|
|
12
|
+
import type { RapRuntime } from "../rap/runtime.js";
|
|
13
|
+
import { type AbapData } from "./convert.js";
|
|
14
|
+
export declare function installBackground(kernel: AbapData, runtime: RapRuntime): void;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background processing (bgPF, decision D41): cl_bgmc_process_factory saves a process in the LUW
|
|
3
|
+
* of the request (table rapt_background_process, state NEW); once that LUW is committed, the
|
|
4
|
+
* operation runs in a LUW of its own, as the same user, after the request. A controlled operation
|
|
5
|
+
* (if_bgmc_op_single) writes the database after cl_abap_tx=>save( ) and its EML changes are saved
|
|
6
|
+
* at its end; an uncontrolled one (if_bgmc_op_single_tx_uncontr) writes when it wants and saves
|
|
7
|
+
* its EML changes with COMMIT ENTITIES. The process then ends SUCCESSFUL or ERRONEOUS.
|
|
8
|
+
*
|
|
9
|
+
* The operation object stays in memory: a process that has not run when the application stops
|
|
10
|
+
* is not run again (see KNOWN_GAPS.md).
|
|
11
|
+
*/
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { sql } from "kysely";
|
|
14
|
+
import { same } from "@open-rap/model";
|
|
15
|
+
import { RapRuntimeError } from "../rap/types.js";
|
|
16
|
+
import { setSubrc } from "./bridge.js";
|
|
17
|
+
import { abapGlobal, abapString } from "./convert.js";
|
|
18
|
+
class RolledBack extends Error {
|
|
19
|
+
}
|
|
20
|
+
const flag = (value) => value !== undefined && abapString(value) !== "";
|
|
21
|
+
export function installBackground(kernel, runtime) {
|
|
22
|
+
const abap = abapGlobal();
|
|
23
|
+
const errorText = (error) => {
|
|
24
|
+
const name = error?.constructor?.INTERNAL_NAME;
|
|
25
|
+
if (name !== undefined) {
|
|
26
|
+
return `${name}: ${runtime.bridge.exceptionText(error)}`;
|
|
27
|
+
}
|
|
28
|
+
return error instanceof Error ? error.message : String(error);
|
|
29
|
+
};
|
|
30
|
+
const finish = async (luw, id, state, error) => {
|
|
31
|
+
await runtime.transaction(luw.db, luw.user, luw.language, async (tx) => {
|
|
32
|
+
await sql `UPDATE rapt_background_process SET state = ${state}, error_text = ${error}, finished_at = now() WHERE process_id = ${id}`.execute(tx.db);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
kernel.bgmc_save = async (input) => {
|
|
36
|
+
const context = runtime.context();
|
|
37
|
+
const id = randomUUID().replace(/-/g, "").toUpperCase();
|
|
38
|
+
const name = abapString(input?.["name"]);
|
|
39
|
+
const controlled = flag(input?.["controlled"]);
|
|
40
|
+
// the reference of the operation, kept until the process runs
|
|
41
|
+
const operation = new abap.types.ABAPObject().set(input?.["operation"]);
|
|
42
|
+
if (operation.get() === undefined) {
|
|
43
|
+
throw new RapRuntimeError("save_for_execution: the process has no operation (set_operation or set_operation_tx_uncontrolled)");
|
|
44
|
+
}
|
|
45
|
+
await sql `INSERT INTO rapt_background_process (process_id, process_name, state, process_user, created_at)
|
|
46
|
+
VALUES (${id}, ${name}, 'NEW', ${context.user.abapName}, now())`.execute(context.tx.db);
|
|
47
|
+
context.afterCommit.push(async (luw) => {
|
|
48
|
+
const background = { controlled, committed: false, rolledBack: false };
|
|
49
|
+
let state = "SUCCESSFUL";
|
|
50
|
+
let error = "";
|
|
51
|
+
try {
|
|
52
|
+
await runtime.transaction(luw.db, luw.user, luw.language, async (tx) => {
|
|
53
|
+
await abap.Classes["ZRAP_CL_BGMC_PROCESS"].zrap_run({ operation, controlled: new abap.types.Character(1).set(controlled ? "X" : "") });
|
|
54
|
+
if (background.rolledBack) {
|
|
55
|
+
throw new RolledBack();
|
|
56
|
+
}
|
|
57
|
+
if (!background.committed) {
|
|
58
|
+
// the changes of the operation's EML are saved at its end, as by the RAP framework
|
|
59
|
+
const responses = await tx.save();
|
|
60
|
+
if (responses.hasFailed) {
|
|
61
|
+
throw new Error(`the save sequence failed: ${responses.reported.map((r) => r.message.text).join("; ") || "failed instances"}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}, { background });
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
if (!(e instanceof RolledBack)) {
|
|
68
|
+
state = "ERRONEOUS";
|
|
69
|
+
error = errorText(e);
|
|
70
|
+
process.stderr.write(`open-rap-runtime: background process ${name || id} failed: ${error}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
await finish(luw, id, state, error);
|
|
74
|
+
});
|
|
75
|
+
return new abap.types.String().set(id);
|
|
76
|
+
};
|
|
77
|
+
kernel.bgmc_state = async (input) => {
|
|
78
|
+
const { rows } = await sql `SELECT state FROM rapt_background_process WHERE process_id = ${abapString(input?.["id"])}`.execute(runtime.context().tx.db);
|
|
79
|
+
return new abap.types.String().set(rows[0]?.state ?? "");
|
|
80
|
+
};
|
|
81
|
+
// cl_abap_tx=>modify( ) / save( ): the transactional phase of a controlled operation
|
|
82
|
+
kernel.tx_phase = async (input) => {
|
|
83
|
+
const context = runtime.context();
|
|
84
|
+
if (context.background?.controlled === true) {
|
|
85
|
+
context.tx.inSaver = abapString(input?.["phase"]) === "save";
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
// COMMIT ENTITIES and ROLLBACK ENTITIES: only in a background operation
|
|
89
|
+
const forbidden = () => {
|
|
90
|
+
throw new RapRuntimeError("COMMIT ENTITIES and ROLLBACK ENTITIES are not allowed in a behavior implementation");
|
|
91
|
+
};
|
|
92
|
+
kernel.commit = async (input) => {
|
|
93
|
+
const context = runtime.context();
|
|
94
|
+
const background = context.background;
|
|
95
|
+
if (background === undefined) {
|
|
96
|
+
forbidden();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (background.committed) {
|
|
100
|
+
throw new RapRuntimeError("COMMIT ENTITIES: a background operation commits once");
|
|
101
|
+
}
|
|
102
|
+
const responses = await context.tx.save();
|
|
103
|
+
background.responses = responses;
|
|
104
|
+
if (!flag(input?.["simulation"]) && !responses.hasFailed) {
|
|
105
|
+
background.committed = true;
|
|
106
|
+
}
|
|
107
|
+
setSubrc(responses.hasFailed ? 4 : 0);
|
|
108
|
+
};
|
|
109
|
+
kernel.commit_response = async (input) => {
|
|
110
|
+
const background = runtime.context().background;
|
|
111
|
+
if (background === undefined) {
|
|
112
|
+
forbidden();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const bdef = abapString(input?.["bdef"]);
|
|
116
|
+
const bo = runtime.bo.entities.find((e) => same(e.bdef.name, bdef));
|
|
117
|
+
if (bo !== undefined && background.responses !== undefined) {
|
|
118
|
+
await runtime.bridge.writeResponses(bo, background.responses, input?.["failed"], input?.["reported"]);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
kernel.rollback = async () => {
|
|
122
|
+
const background = runtime.context().background;
|
|
123
|
+
if (background === undefined) {
|
|
124
|
+
forbidden();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
background.rolledBack = true;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=background.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background.js","sourceRoot":"","sources":["../../src/abap/background.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bridge between the managed runtime (canonical rows, JavaScript responses) and the
|
|
3
|
+
* transpiled behavior pool (ABAP data objects of the derived types, handler methods).
|
|
4
|
+
*/
|
|
5
|
+
import { type HandlerRef, type FieldModel } from "@open-rap/model";
|
|
6
|
+
import type { User } from "../auth/user.js";
|
|
7
|
+
import type { BoEntity, BoModel } from "../rap/bo.js";
|
|
8
|
+
import { Responses, type Message, type Row } from "../rap/types.js";
|
|
9
|
+
import { type AbapData } from "./convert.js";
|
|
10
|
+
/** Imports the transpiled modules (`abap/init.mjs` sets the global `abap` object). */
|
|
11
|
+
export declare function loadAbap(abapDir: string, entry?: string): Promise<void>;
|
|
12
|
+
/** Sets the system fields the ABAP code reads (sy-uname, sy-datum, sy-uzeit, sy-langu). */
|
|
13
|
+
export declare function setSystemFields(user: User, language: string, now?: Date): void;
|
|
14
|
+
export declare function setSubrc(value: number): void;
|
|
15
|
+
export type MessageTexts = Record<string, Record<string, string>>;
|
|
16
|
+
/** T100 text with its placeholders (`&1`…`&4`, `&`) replaced. */
|
|
17
|
+
export declare function formatT100(texts: MessageTexts, id: string, number: string, vars: string[]): string;
|
|
18
|
+
export declare class AbapBridge {
|
|
19
|
+
private readonly bo;
|
|
20
|
+
private readonly texts;
|
|
21
|
+
constructor(bo: BoModel, texts: MessageTexts);
|
|
22
|
+
iface(bo: BoEntity): AbapData;
|
|
23
|
+
/** A new data object of a derived type (`tt_e1_keys`…). */
|
|
24
|
+
data(bo: BoEntity, typeName: string): AbapData;
|
|
25
|
+
/** Sets the fields of `row` (all, or the given ones) in an ABAP structure that has them. */
|
|
26
|
+
fillRow(target: AbapData, bo: BoEntity, row: Row, fields?: Iterable<string>): void;
|
|
27
|
+
fillKey(target: AbapData, bo: BoEntity, key: Row): void;
|
|
28
|
+
readRow(source: AbapData, bo: BoEntity, fields?: Iterable<string>): Row;
|
|
29
|
+
readKey(source: AbapData, bo: BoEntity): Row;
|
|
30
|
+
/** Fields flagged in the `%control` component of a row; undefined when the row has none. */
|
|
31
|
+
readControl(source: AbapData, bo: BoEntity): Set<string> | undefined;
|
|
32
|
+
fillControl(target: AbapData, bo: BoEntity, fields: Iterable<string>): void;
|
|
33
|
+
readText(source: AbapData, name: string): string;
|
|
34
|
+
/** The fields of a parameter structure (`%param`) of an abstract entity. */
|
|
35
|
+
readParameter(source: AbapData, fields: FieldModel[]): Row;
|
|
36
|
+
fillParameter(target: AbapData, fields: FieldModel[], values: Row): void;
|
|
37
|
+
/** A table of keys (`tt_eN_keys` or another table with the key fields) from canonical keys. */
|
|
38
|
+
keysTable(bo: BoEntity, keys: Row[], typeName?: string): AbapData;
|
|
39
|
+
/** `ts_failed_early`, `ts_reported_late`… of the BDEF of `bo`. */
|
|
40
|
+
response(bo: BoEntity, kind: "failed" | "reported" | "mapped", late?: boolean): AbapData;
|
|
41
|
+
private entitiesOf;
|
|
42
|
+
/** Reads the ABAP responses of a handler or of an EML statement. */
|
|
43
|
+
readResponses(bo: BoEntity, failed?: AbapData, reported?: AbapData, mapped?: AbapData): Promise<Responses>;
|
|
44
|
+
private failedOperation;
|
|
45
|
+
/** Text and severity of a message object (`%msg`, an ABAP reference). */
|
|
46
|
+
message(ref: AbapData | undefined): Promise<Message | undefined>;
|
|
47
|
+
/** A message object for the ABAP code (`%msg` of the responses the kernel writes). */
|
|
48
|
+
messageObject(message: Message): Promise<AbapData>;
|
|
49
|
+
/** Writes responses into the ABAP response structures of an EML statement (cleared first). */
|
|
50
|
+
writeResponses(bo: BoEntity, responses: Responses, failed?: AbapData, reported?: AbapData, mapped?: AbapData): Promise<void>;
|
|
51
|
+
/** Calls a handler method of a local class of the behavior pool. */
|
|
52
|
+
call(ref: HandlerRef, params: Record<string, AbapData>): Promise<void>;
|
|
53
|
+
t100(id: string, number: string, vars: string[]): string;
|
|
54
|
+
/** The T100 message key of a message object (`if_t100_message~t100key`), if it has one. */
|
|
55
|
+
t100Key(object: AbapData): {
|
|
56
|
+
msgid: string;
|
|
57
|
+
msgno: string;
|
|
58
|
+
attributes: string[];
|
|
59
|
+
} | undefined;
|
|
60
|
+
/** Text of an exception: its T100 message with the attributes named by attr1…attr4. */
|
|
61
|
+
exceptionText(object: AbapData): string;
|
|
62
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bridge between the managed runtime (canonical rows, JavaScript responses) and the
|
|
3
|
+
* transpiled behavior pool (ABAP data objects of the derived types, handler methods).
|
|
4
|
+
*/
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { abapName } from "@open-rap/model";
|
|
8
|
+
import { FAIL_CAUSES, Responses } from "../rap/types.js";
|
|
9
|
+
import { abapGlobal, abapString, appendRow, component, getAbapValue, isFlagOn, rowsOf, setAbapValue, setFlag } from "./convert.js";
|
|
10
|
+
/** Imports the transpiled modules (`abap/init.mjs` sets the global `abap` object). */
|
|
11
|
+
export async function loadAbap(abapDir, entry = "init.mjs") {
|
|
12
|
+
await import(pathToFileURL(path.resolve(abapDir, entry)).href);
|
|
13
|
+
abapGlobal();
|
|
14
|
+
}
|
|
15
|
+
const LANGUAGES = { en: "E", de: "D", fr: "F", es: "S", it: "I", pt: "P", nl: "N", ja: "J", zh: "1", ru: "R", pl: "L", cs: "C", da: "K", fi: "U", sv: "V", tr: "T", ko: "3" };
|
|
16
|
+
/** Sets the system fields the ABAP code reads (sy-uname, sy-datum, sy-uzeit, sy-langu). */
|
|
17
|
+
export function setSystemFields(user, language, now = new Date()) {
|
|
18
|
+
const sy = abapGlobal().builtin.sy.get();
|
|
19
|
+
sy.uname.set(user.abapName);
|
|
20
|
+
const iso = now.toISOString();
|
|
21
|
+
sy.datum.set(iso.slice(0, 10).replace(/-/g, ""));
|
|
22
|
+
sy.uzeit.set(iso.slice(11, 19).replace(/:/g, ""));
|
|
23
|
+
sy.langu.set(LANGUAGES[language.slice(0, 2).toLowerCase()] ?? "E");
|
|
24
|
+
}
|
|
25
|
+
export function setSubrc(value) {
|
|
26
|
+
abapGlobal().builtin.sy.get().subrc.set(value);
|
|
27
|
+
}
|
|
28
|
+
/** T100 text with its placeholders (`&1`…`&4`, `&`) replaced. */
|
|
29
|
+
export function formatT100(texts, id, number, vars) {
|
|
30
|
+
const text = texts[id.toUpperCase()]?.[number.padStart(3, "0")];
|
|
31
|
+
if (text === undefined) {
|
|
32
|
+
return `${id} ${number}${vars.some((v) => v !== "") ? ` ${vars.join(" ")}`.trimEnd() : ""}`;
|
|
33
|
+
}
|
|
34
|
+
let next = 0;
|
|
35
|
+
return text.replace(/&(\d)|&/g, (_match, n) => {
|
|
36
|
+
const value = n === undefined ? vars[next++] : vars[Number(n) - 1];
|
|
37
|
+
return value ?? "";
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export class AbapBridge {
|
|
41
|
+
bo;
|
|
42
|
+
texts;
|
|
43
|
+
constructor(bo, texts) {
|
|
44
|
+
this.bo = bo;
|
|
45
|
+
this.texts = texts;
|
|
46
|
+
}
|
|
47
|
+
iface(bo) {
|
|
48
|
+
const iface = abapGlobal().Classes[bo.iface];
|
|
49
|
+
if (iface === undefined) {
|
|
50
|
+
throw new Error(`derived types ${bo.iface} not loaded`);
|
|
51
|
+
}
|
|
52
|
+
return iface;
|
|
53
|
+
}
|
|
54
|
+
/** A new data object of a derived type (`tt_e1_keys`…). */
|
|
55
|
+
data(bo, typeName) {
|
|
56
|
+
const type = this.iface(bo)[typeName];
|
|
57
|
+
if (type === undefined) {
|
|
58
|
+
throw new Error(`derived type ${bo.iface}=>${typeName} not found`);
|
|
59
|
+
}
|
|
60
|
+
return type.clone();
|
|
61
|
+
}
|
|
62
|
+
// -------------------------------------------------------------------------------------------
|
|
63
|
+
// rows
|
|
64
|
+
/** Sets the fields of `row` (all, or the given ones) in an ABAP structure that has them. */
|
|
65
|
+
fillRow(target, bo, row, fields) {
|
|
66
|
+
const names = fields === undefined ? bo.fields.map((f) => f.name) : [...fields];
|
|
67
|
+
for (const name of names) {
|
|
68
|
+
const field = this.bo.field(bo, name);
|
|
69
|
+
const data = field === undefined ? undefined : component(target, field.name);
|
|
70
|
+
if (field !== undefined && data !== undefined && name in row) {
|
|
71
|
+
setAbapValue(data, field.type, row[field.name]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
fillKey(target, bo, key) {
|
|
76
|
+
this.fillRow(target, bo, key, bo.keys.map((k) => k.name));
|
|
77
|
+
}
|
|
78
|
+
readRow(source, bo, fields) {
|
|
79
|
+
const row = {};
|
|
80
|
+
const list = fields === undefined ? bo.fields : [...fields].map((n) => this.bo.field(bo, n)).filter((f) => f !== undefined);
|
|
81
|
+
for (const field of list) {
|
|
82
|
+
const data = component(source, field.name);
|
|
83
|
+
if (data !== undefined) {
|
|
84
|
+
row[field.name] = getAbapValue(data, field.type);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return row;
|
|
88
|
+
}
|
|
89
|
+
readKey(source, bo) {
|
|
90
|
+
return this.readRow(source, bo, bo.keys.map((k) => k.name));
|
|
91
|
+
}
|
|
92
|
+
/** Fields flagged in the `%control` component of a row; undefined when the row has none. */
|
|
93
|
+
readControl(source, bo) {
|
|
94
|
+
const control = component(source, "%control");
|
|
95
|
+
if (control === undefined) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const fields = new Set();
|
|
99
|
+
for (const field of bo.fields) {
|
|
100
|
+
if (isFlagOn(component(control, field.name))) {
|
|
101
|
+
fields.add(field.name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return fields;
|
|
105
|
+
}
|
|
106
|
+
fillControl(target, bo, fields) {
|
|
107
|
+
const control = component(target, "%control");
|
|
108
|
+
if (control === undefined) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const name of fields) {
|
|
112
|
+
setFlag(component(control, name), true);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
readText(source, name) {
|
|
116
|
+
return abapString(component(source, name));
|
|
117
|
+
}
|
|
118
|
+
/** The fields of a parameter structure (`%param`) of an abstract entity. */
|
|
119
|
+
readParameter(source, fields) {
|
|
120
|
+
const param = component(source, "%param");
|
|
121
|
+
const row = {};
|
|
122
|
+
if (param === undefined) {
|
|
123
|
+
return row;
|
|
124
|
+
}
|
|
125
|
+
for (const field of fields) {
|
|
126
|
+
const data = component(param, field.name);
|
|
127
|
+
if (data !== undefined) {
|
|
128
|
+
row[field.name] = getAbapValue(data, field.type);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return row;
|
|
132
|
+
}
|
|
133
|
+
fillParameter(target, fields, values) {
|
|
134
|
+
const param = component(target, "%param");
|
|
135
|
+
if (param === undefined) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
for (const field of fields) {
|
|
139
|
+
const data = component(param, field.name);
|
|
140
|
+
if (data !== undefined && field.name in values) {
|
|
141
|
+
setAbapValue(data, field.type, values[field.name]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** A table of keys (`tt_eN_keys` or another table with the key fields) from canonical keys. */
|
|
146
|
+
keysTable(bo, keys, typeName = `tt_e${bo.index}_keys`) {
|
|
147
|
+
const table = this.data(bo, typeName);
|
|
148
|
+
for (const key of keys) {
|
|
149
|
+
appendRow(table, (row) => this.fillKey(row, bo, key));
|
|
150
|
+
}
|
|
151
|
+
return table;
|
|
152
|
+
}
|
|
153
|
+
// -------------------------------------------------------------------------------------------
|
|
154
|
+
// responses
|
|
155
|
+
/** `ts_failed_early`, `ts_reported_late`… of the BDEF of `bo`. */
|
|
156
|
+
response(bo, kind, late = false) {
|
|
157
|
+
return this.data(bo, `ts_${kind}_${late ? "late" : "early"}`);
|
|
158
|
+
}
|
|
159
|
+
entitiesOf(bo) {
|
|
160
|
+
return this.bo.entities.filter((e) => e.bdef === bo.bdef);
|
|
161
|
+
}
|
|
162
|
+
/** Reads the ABAP responses of a handler or of an EML statement. */
|
|
163
|
+
async readResponses(bo, failed, reported, mapped) {
|
|
164
|
+
const responses = new Responses();
|
|
165
|
+
for (const entity of this.entitiesOf(bo)) {
|
|
166
|
+
const alias = abapName(entity.behavior.alias);
|
|
167
|
+
for (const row of rowsOf(failed === undefined ? undefined : component(failed, alias))) {
|
|
168
|
+
const fail = component(row, "%fail");
|
|
169
|
+
const cause = FAIL_CAUSES[parseInt(String(fail === undefined ? "0" : component(fail, "cause")?.get() ?? "0"), 16)] ?? "unspecific";
|
|
170
|
+
const entry = { entity: entity.entity.name, key: this.readKey(row, entity), cause };
|
|
171
|
+
const cid = this.readText(row, "%cid");
|
|
172
|
+
if (cid !== "") {
|
|
173
|
+
entry.cid = cid;
|
|
174
|
+
}
|
|
175
|
+
const operation = this.failedOperation(row, entity);
|
|
176
|
+
if (operation !== undefined) {
|
|
177
|
+
entry.operation = operation;
|
|
178
|
+
}
|
|
179
|
+
responses.failed.push(entry);
|
|
180
|
+
}
|
|
181
|
+
for (const row of rowsOf(reported === undefined ? undefined : component(reported, alias))) {
|
|
182
|
+
const message = await this.message(component(row, "%msg"));
|
|
183
|
+
if (message === undefined) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const elements = [];
|
|
187
|
+
const element = component(row, "%element");
|
|
188
|
+
if (element !== undefined) {
|
|
189
|
+
for (const name of Object.keys(element.get())) {
|
|
190
|
+
if (isFlagOn(component(element, name))) {
|
|
191
|
+
elements.push(entity.fields.find((f) => abapName(f.name) === name)?.name ?? entity.associations.find((a) => abapName(a.name) === name)?.name ?? name);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const key = this.readKey(row, entity);
|
|
196
|
+
const hasKey = Object.values(key).some((v) => v !== null && v !== "" && v !== false && !/^0+$/.test(String(v)));
|
|
197
|
+
const entry = { entity: entity.entity.name, message, elements, global: isFlagOn(component(row, "%global")) };
|
|
198
|
+
if (hasKey) {
|
|
199
|
+
entry.key = key;
|
|
200
|
+
}
|
|
201
|
+
const cid = this.readText(row, "%cid");
|
|
202
|
+
if (cid !== "") {
|
|
203
|
+
entry.cid = cid;
|
|
204
|
+
}
|
|
205
|
+
const stateArea = this.readText(row, "%state_area");
|
|
206
|
+
if (stateArea !== "") {
|
|
207
|
+
entry.stateArea = stateArea;
|
|
208
|
+
}
|
|
209
|
+
responses.reported.push(entry);
|
|
210
|
+
}
|
|
211
|
+
for (const row of rowsOf(mapped === undefined ? undefined : component(mapped, alias))) {
|
|
212
|
+
responses.mapped.push({ entity: entity.entity.name, cid: this.readText(row, "%cid"), key: this.readKey(row, entity) });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const ref of rowsOf(reported === undefined ? undefined : component(reported, "%other"))) {
|
|
216
|
+
const message = await this.message(ref);
|
|
217
|
+
if (message !== undefined) {
|
|
218
|
+
responses.reported.push({ message, elements: [], global: true });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return responses;
|
|
222
|
+
}
|
|
223
|
+
failedOperation(row, bo) {
|
|
224
|
+
for (const kind of ["create", "update", "delete"]) {
|
|
225
|
+
if (isFlagOn(component(row, `%${kind}`))) {
|
|
226
|
+
return { kind };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const action = component(row, "%action");
|
|
230
|
+
for (const a of bo.behavior.actions) {
|
|
231
|
+
if (action !== undefined && isFlagOn(component(action, a.name))) {
|
|
232
|
+
return { kind: "action", name: a.name };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const assoc = component(row, "%assoc");
|
|
236
|
+
for (const a of bo.associations) {
|
|
237
|
+
if (assoc !== undefined && isFlagOn(component(assoc, a.name))) {
|
|
238
|
+
return { kind: "association", name: a.name };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
/** Text and severity of a message object (`%msg`, an ABAP reference). */
|
|
244
|
+
async message(ref) {
|
|
245
|
+
const object = ref?.get?.();
|
|
246
|
+
if (object === undefined || object === null) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
const severity = (abapString(object["if_abap_behv_message$m_severity"]) || "E");
|
|
250
|
+
let text = "";
|
|
251
|
+
for (const method of ["if_message$get_text", "if_abap_behv_message$get_text", "get_text"]) {
|
|
252
|
+
if (typeof object[method] === "function") {
|
|
253
|
+
text = abapString(await object[method]({}));
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const message = { severity, text };
|
|
258
|
+
const key = this.t100Key(object);
|
|
259
|
+
if (key !== undefined) {
|
|
260
|
+
message.msgid = key.msgid;
|
|
261
|
+
message.msgno = key.msgno;
|
|
262
|
+
}
|
|
263
|
+
return message;
|
|
264
|
+
}
|
|
265
|
+
/** A message object for the ABAP code (`%msg` of the responses the kernel writes). */
|
|
266
|
+
async messageObject(message) {
|
|
267
|
+
const abap = abapGlobal();
|
|
268
|
+
const MessageClass = abap.Classes["ZRAP_CL_MESSAGE"];
|
|
269
|
+
const object = await new MessageClass().constructor_({
|
|
270
|
+
severity: new abap.types.Character(1).set(message.severity),
|
|
271
|
+
text: new abap.types.String().set(message.text),
|
|
272
|
+
});
|
|
273
|
+
const ref = new abap.types.ABAPObject();
|
|
274
|
+
ref.set(object);
|
|
275
|
+
return ref;
|
|
276
|
+
}
|
|
277
|
+
/** Writes responses into the ABAP response structures of an EML statement (cleared first). */
|
|
278
|
+
async writeResponses(bo, responses, failed, reported, mapped) {
|
|
279
|
+
failed?.clear();
|
|
280
|
+
reported?.clear();
|
|
281
|
+
mapped?.clear();
|
|
282
|
+
const entities = this.entitiesOf(bo);
|
|
283
|
+
const entityOf = (name) => entities.find((e) => e.entity.name === name);
|
|
284
|
+
for (const entry of responses.failed) {
|
|
285
|
+
const entity = entityOf(entry.entity);
|
|
286
|
+
const table = entity === undefined || failed === undefined ? undefined : component(failed, abapName(entity.behavior.alias));
|
|
287
|
+
if (entity === undefined || table === undefined) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
appendRow(table, (row) => {
|
|
291
|
+
if (entry.key !== undefined) {
|
|
292
|
+
this.fillKey(row, entity, entry.key);
|
|
293
|
+
}
|
|
294
|
+
component(row, "%cid")?.set(entry.cid ?? "");
|
|
295
|
+
const fail = component(row, "%fail");
|
|
296
|
+
component(fail, "cause")?.set(`0${FAIL_CAUSES.indexOf(entry.cause)}`);
|
|
297
|
+
const operation = entry.operation;
|
|
298
|
+
if (operation?.kind === "create" || operation?.kind === "update" || operation?.kind === "delete") {
|
|
299
|
+
setFlag(component(row, `%${operation.kind}`), true);
|
|
300
|
+
}
|
|
301
|
+
else if (operation?.kind === "action") {
|
|
302
|
+
const action = component(row, "%action");
|
|
303
|
+
setFlag(action === undefined ? undefined : component(action, operation.name), true);
|
|
304
|
+
}
|
|
305
|
+
else if (operation?.kind === "association") {
|
|
306
|
+
const assoc = component(row, "%assoc");
|
|
307
|
+
setFlag(assoc === undefined ? undefined : component(assoc, operation.name), true);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
for (const entry of responses.reported) {
|
|
312
|
+
const entity = entityOf(entry.entity);
|
|
313
|
+
const msg = await this.messageObject(entry.message);
|
|
314
|
+
if (entity === undefined) {
|
|
315
|
+
const other = reported === undefined ? undefined : component(reported, "%other");
|
|
316
|
+
if (other !== undefined) {
|
|
317
|
+
other.append(msg);
|
|
318
|
+
}
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const table = reported === undefined ? undefined : component(reported, abapName(entity.behavior.alias));
|
|
322
|
+
if (table === undefined) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
appendRow(table, (row) => {
|
|
326
|
+
if (entry.key !== undefined) {
|
|
327
|
+
this.fillKey(row, entity, entry.key);
|
|
328
|
+
}
|
|
329
|
+
component(row, "%cid")?.set(entry.cid ?? "");
|
|
330
|
+
component(row, "%msg")?.set(msg);
|
|
331
|
+
const element = component(row, "%element");
|
|
332
|
+
for (const name of entry.elements) {
|
|
333
|
+
setFlag(element === undefined ? undefined : component(element, name), true);
|
|
334
|
+
}
|
|
335
|
+
setFlag(component(row, "%global"), entry.global);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
for (const entry of responses.mapped) {
|
|
339
|
+
const entity = entityOf(entry.entity);
|
|
340
|
+
const table = entity === undefined || mapped === undefined ? undefined : component(mapped, abapName(entity.behavior.alias));
|
|
341
|
+
if (entity === undefined || table === undefined) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
appendRow(table, (row) => {
|
|
345
|
+
this.fillKey(row, entity, entry.key);
|
|
346
|
+
component(row, "%cid")?.set(entry.cid);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// -------------------------------------------------------------------------------------------
|
|
351
|
+
// handlers
|
|
352
|
+
/** Calls a handler method of a local class of the behavior pool. */
|
|
353
|
+
async call(ref, params) {
|
|
354
|
+
const Class = abapGlobal().Classes[ref.classKey];
|
|
355
|
+
if (Class === undefined) {
|
|
356
|
+
throw new Error(`handler class ${ref.classKey} is not loaded`);
|
|
357
|
+
}
|
|
358
|
+
const instance = await new Class().constructor_();
|
|
359
|
+
const friends = instance.FRIENDS_ACCESS_INSTANCE;
|
|
360
|
+
const method = friends?.[ref.method] ?? (typeof instance[ref.method] === "function" ? instance[ref.method].bind(instance) : undefined);
|
|
361
|
+
if (method === undefined) {
|
|
362
|
+
throw new Error(`handler method ${ref.className}->${ref.method} not found`);
|
|
363
|
+
}
|
|
364
|
+
await method(params);
|
|
365
|
+
}
|
|
366
|
+
t100(id, number, vars) {
|
|
367
|
+
return formatT100(this.texts, id, number, vars);
|
|
368
|
+
}
|
|
369
|
+
/** The T100 message key of a message object (`if_t100_message~t100key`), if it has one. */
|
|
370
|
+
t100Key(object) {
|
|
371
|
+
const key = object?.["if_t100_message$t100key"];
|
|
372
|
+
if (key === undefined) {
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
const msgid = abapString(component(key, "msgid"));
|
|
376
|
+
if (msgid === "") {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
return { msgid, msgno: abapString(component(key, "msgno")), attributes: [1, 2, 3, 4].map((n) => abapString(component(key, `attr${n}`))) };
|
|
380
|
+
}
|
|
381
|
+
/** Text of an exception: its T100 message with the attributes named by attr1…attr4. */
|
|
382
|
+
exceptionText(object) {
|
|
383
|
+
// the exceptions of the framework that carry a text of their own (HTTP, OData client proxy)
|
|
384
|
+
const own = object?.zrap_text === undefined ? "" : abapString(object.zrap_text);
|
|
385
|
+
if (own !== "") {
|
|
386
|
+
return own;
|
|
387
|
+
}
|
|
388
|
+
const key = this.t100Key(object);
|
|
389
|
+
if (key === undefined) {
|
|
390
|
+
return "An exception was raised";
|
|
391
|
+
}
|
|
392
|
+
const vars = key.attributes.map((name) => {
|
|
393
|
+
if (name === "") {
|
|
394
|
+
return "";
|
|
395
|
+
}
|
|
396
|
+
const attribute = object[name.toLowerCase().replace(/~/g, "$")];
|
|
397
|
+
return attribute === undefined ? name : abapString(attribute);
|
|
398
|
+
});
|
|
399
|
+
return this.t100(key.msgid, key.msgno, vars);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
//# sourceMappingURL=bridge.js.map
|