@abapify/adk 0.3.6 → 0.4.1
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/dist/factory-DtyrTGlN.mjs +394 -0
- package/dist/factory-DtyrTGlN.mjs.map +1 -0
- package/dist/index.d.mts +2899 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +4300 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +4 -4
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4300 @@
|
|
|
1
|
+
import { A as Package, C as Domain, D as Include, E as FunctionModule, F as TransportRequest, I as TransportTask, M as Structure, N as Table, O as Interface, P as TableType, S as DataElement, T as FunctionGroup, _ as parseAdtType, a as parseXmlIdentity, b as resolveType, c as getKindForType, d as getObjectUri, f as getRegisteredKinds, g as normalizeObjectName, h as isTypeRegistered, j as Program, k as MessageClass, l as getMainType, m as getTypeForKind, n as createAdk, o as ADT_TYPE_MAPPINGS, p as getRegisteredTypes, r as createAdkFactory, s as getEndpointForType, t as AdkGenericObject, u as getObjectRootUri, v as registerObjectType, w as EnhancementSpot, x as Class, y as resolveKind } from "./factory-DtyrTGlN.mjs";
|
|
2
|
+
import { createLockService, createLockService as createLockService$1 } from "@abapify/adt-locks";
|
|
3
|
+
|
|
4
|
+
//#region src/base/fetch-utils.ts
|
|
5
|
+
/**
|
|
6
|
+
* Utility for handling v2 client fetch responses.
|
|
7
|
+
*
|
|
8
|
+
* The v2 ADT client's `fetch()` returns parsed data directly (a string for
|
|
9
|
+
* text/plain content), not a standard `Response` object. This helper
|
|
10
|
+
* normalises both cases so ADK objects work with both client versions.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Convert a fetch result to a text string.
|
|
14
|
+
*
|
|
15
|
+
* - If the result is already a string (v2 client), return it as-is.
|
|
16
|
+
* - If the result is a Response-like object (v1 / standard fetch), call `.text()`.
|
|
17
|
+
*/
|
|
18
|
+
async function toText(result) {
|
|
19
|
+
if (typeof result === "string") return result;
|
|
20
|
+
if (result && typeof result === "object" && "text" in result && typeof result.text === "function") return result.text();
|
|
21
|
+
if (result === null || result === void 0) return "";
|
|
22
|
+
if (typeof result === "object") try {
|
|
23
|
+
return JSON.stringify(result);
|
|
24
|
+
} catch {
|
|
25
|
+
return String(result);
|
|
26
|
+
}
|
|
27
|
+
return String(result);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/base/model.ts
|
|
32
|
+
/**
|
|
33
|
+
* Base class for all ADK objects
|
|
34
|
+
*
|
|
35
|
+
* All ADK objects (AdkPackage, AdkTransportRequest, etc.) extend this.
|
|
36
|
+
* Supports deferred loading - create with just a name, then call load().
|
|
37
|
+
*
|
|
38
|
+
* Note: Uses loose type constraint for D to allow schema-inferred types
|
|
39
|
+
* from contracts which may have different shapes. The getters safely
|
|
40
|
+
* handle missing properties with defaults.
|
|
41
|
+
*
|
|
42
|
+
* @typeParam K - The object kind (from AdkKind)
|
|
43
|
+
* @typeParam D - The internal data type (typically schema-inferred from contract)
|
|
44
|
+
*/
|
|
45
|
+
var AdkObject = class {
|
|
46
|
+
/**
|
|
47
|
+
* ADT collection URI - base path for creating new objects.
|
|
48
|
+
* Example: `/sap/bc/adt/oo/classes`
|
|
49
|
+
* Default: derived from objectUri by removing the object name
|
|
50
|
+
*/
|
|
51
|
+
get collectionUri() {
|
|
52
|
+
const uri = this.objectUri;
|
|
53
|
+
const lastSlash = uri.lastIndexOf("/");
|
|
54
|
+
return lastSlash > 0 ? uri.substring(0, lastSlash) : uri;
|
|
55
|
+
}
|
|
56
|
+
ctx;
|
|
57
|
+
/** Public access to the ADT client for plugins and external code */
|
|
58
|
+
get client() {
|
|
59
|
+
return this.ctx.client;
|
|
60
|
+
}
|
|
61
|
+
_data;
|
|
62
|
+
_name;
|
|
63
|
+
cache = /* @__PURE__ */ new Map();
|
|
64
|
+
dirty = /* @__PURE__ */ new Set();
|
|
65
|
+
_lockHandle;
|
|
66
|
+
/** Set by save() when all pending changes are identical to SAP state */
|
|
67
|
+
_unchanged = false;
|
|
68
|
+
/**
|
|
69
|
+
* Create ADK object
|
|
70
|
+
* @param ctx - ADK context
|
|
71
|
+
* @param dataOrName - Full data object OR just the object name for deferred loading
|
|
72
|
+
*/
|
|
73
|
+
constructor(ctx, dataOrName) {
|
|
74
|
+
this.ctx = ctx;
|
|
75
|
+
if (typeof dataOrName === "string") this._name = dataOrName;
|
|
76
|
+
else {
|
|
77
|
+
this._data = dataOrName;
|
|
78
|
+
this._name = dataOrName.name;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Adopt a richer context (merge services into existing context).
|
|
83
|
+
*
|
|
84
|
+
* Used by AdkObjectSet to propagate lockService / lockStore
|
|
85
|
+
* to objects created by plugins that only have { client }.
|
|
86
|
+
* Keeps the original client reference intact.
|
|
87
|
+
*/
|
|
88
|
+
adoptContext(ctx) {
|
|
89
|
+
this.ctx = {
|
|
90
|
+
...this.ctx,
|
|
91
|
+
...ctx
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Load object data from SAP
|
|
96
|
+
*
|
|
97
|
+
* Default implementation uses crudContract.get() and unwraps with wrapperKey.
|
|
98
|
+
* Objects without crudContract must override this.
|
|
99
|
+
*
|
|
100
|
+
* @returns this (for chaining)
|
|
101
|
+
*/
|
|
102
|
+
async load() {
|
|
103
|
+
const contract = this.crudContract;
|
|
104
|
+
const wrapperKey = this.wrapperKey;
|
|
105
|
+
if (!contract || !wrapperKey) throw new Error(`Load not implemented for ${this.kind}. Override load() or provide crudContract/wrapperKey.`);
|
|
106
|
+
const response = await contract.get(this.name);
|
|
107
|
+
if (!response || !(wrapperKey in response)) throw new Error(`${this.kind} '${this.name}' not found or returned empty response`);
|
|
108
|
+
this.setData(response[wrapperKey]);
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
/** Whether data has been loaded */
|
|
112
|
+
get isLoaded() {
|
|
113
|
+
return !!this._data;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get data - auto-loads if not already loaded
|
|
117
|
+
* @returns Promise that resolves to the data
|
|
118
|
+
*/
|
|
119
|
+
async data() {
|
|
120
|
+
if (!this._data) await this.load();
|
|
121
|
+
return this._data;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get data synchronously - throws if not loaded
|
|
125
|
+
* Use when you know data is already loaded
|
|
126
|
+
*/
|
|
127
|
+
get dataSync() {
|
|
128
|
+
if (!this._data) throw new Error(`${this.kind} '${this._name}' not loaded. Call load() or use data() instead.`);
|
|
129
|
+
return this._data;
|
|
130
|
+
}
|
|
131
|
+
/** Set data (for subclasses to use after loading) */
|
|
132
|
+
setData(data) {
|
|
133
|
+
this._data = data;
|
|
134
|
+
this._name = data.name;
|
|
135
|
+
}
|
|
136
|
+
/** Helper to access data with AdkObjectData properties */
|
|
137
|
+
get coreData() {
|
|
138
|
+
return this.dataSync;
|
|
139
|
+
}
|
|
140
|
+
/** ADT object name (adtcore:name) - available even before load() */
|
|
141
|
+
get name() {
|
|
142
|
+
return this._name;
|
|
143
|
+
}
|
|
144
|
+
/** ADT object type (adtcore:type, e.g. 'RQRQ', 'DEVC/K') - requires load() */
|
|
145
|
+
get type() {
|
|
146
|
+
return this.coreData.type;
|
|
147
|
+
}
|
|
148
|
+
/** Description text (adtcore:description) */
|
|
149
|
+
get description() {
|
|
150
|
+
return this.coreData.description ?? "";
|
|
151
|
+
}
|
|
152
|
+
/** Version state (adtcore:version) - active, inactive, etc. */
|
|
153
|
+
get version() {
|
|
154
|
+
return this.coreData.version ?? "";
|
|
155
|
+
}
|
|
156
|
+
/** Current language (adtcore:language) */
|
|
157
|
+
get language() {
|
|
158
|
+
return this.coreData.language ?? "";
|
|
159
|
+
}
|
|
160
|
+
/** User who last modified (adtcore:changedBy) */
|
|
161
|
+
get changedBy() {
|
|
162
|
+
return this.coreData.changedBy ?? "";
|
|
163
|
+
}
|
|
164
|
+
/** Last modification timestamp (adtcore:changedAt) */
|
|
165
|
+
get changedAt() {
|
|
166
|
+
const val = this.coreData.changedAt;
|
|
167
|
+
return val instanceof Date ? val : val ? new Date(val) : /* @__PURE__ */ new Date(0);
|
|
168
|
+
}
|
|
169
|
+
/** User who created (adtcore:createdBy) */
|
|
170
|
+
get createdBy() {
|
|
171
|
+
return this.coreData.createdBy ?? "";
|
|
172
|
+
}
|
|
173
|
+
/** Creation timestamp (adtcore:createdAt) */
|
|
174
|
+
get createdAt() {
|
|
175
|
+
const val = this.coreData.createdAt;
|
|
176
|
+
return val instanceof Date ? val : val ? new Date(val) : /* @__PURE__ */ new Date(0);
|
|
177
|
+
}
|
|
178
|
+
/** All atom:link elements */
|
|
179
|
+
get links() {
|
|
180
|
+
return this.coreData.links ?? [];
|
|
181
|
+
}
|
|
182
|
+
/** Find link by rel attribute */
|
|
183
|
+
getLink(rel) {
|
|
184
|
+
return this.links.find((l) => l.rel === rel);
|
|
185
|
+
}
|
|
186
|
+
/** Get link href by rel, or undefined */
|
|
187
|
+
getLinkHref(rel) {
|
|
188
|
+
return this.getLink(rel)?.href;
|
|
189
|
+
}
|
|
190
|
+
/** Container reference (adtcore:containerRef) */
|
|
191
|
+
get containerRef() {
|
|
192
|
+
return this.coreData.containerRef;
|
|
193
|
+
}
|
|
194
|
+
/** Check if object is currently locked */
|
|
195
|
+
get isLocked() {
|
|
196
|
+
return !!this._lockHandle;
|
|
197
|
+
}
|
|
198
|
+
/** Get current lock handle */
|
|
199
|
+
get lockHandle() {
|
|
200
|
+
return this._lockHandle;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Lock the object for modification
|
|
204
|
+
*
|
|
205
|
+
* Delegates to the lock service (single lock mechanism).
|
|
206
|
+
*
|
|
207
|
+
* The lock response contains:
|
|
208
|
+
* - LOCK_HANDLE: Required for subsequent PUT/unlock operations
|
|
209
|
+
* - CORRNR: Transport request assigned to this object (use for PUT if no explicit transport)
|
|
210
|
+
* - CORRUSER: User who owns the transport
|
|
211
|
+
*
|
|
212
|
+
* @param transport - Optional transport request to use for locking.
|
|
213
|
+
* Required when object is already in a transport.
|
|
214
|
+
*/
|
|
215
|
+
async lock(transport) {
|
|
216
|
+
if (this._lockHandle) return this._lockHandle;
|
|
217
|
+
const lockService = this.ctx.lockService;
|
|
218
|
+
if (!lockService) throw new Error(`Lock not available: no lockService in context. Did you call initializeAdk()?`);
|
|
219
|
+
this._lockHandle = await lockService.lock(this.objectUri, {
|
|
220
|
+
transport,
|
|
221
|
+
objectName: this.name,
|
|
222
|
+
objectType: this.kind
|
|
223
|
+
});
|
|
224
|
+
return this._lockHandle;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Unlock the object
|
|
228
|
+
*
|
|
229
|
+
* Delegates to the lock service (single lock mechanism).
|
|
230
|
+
*/
|
|
231
|
+
async unlock() {
|
|
232
|
+
if (!this._lockHandle) return;
|
|
233
|
+
const lockService = this.ctx.lockService;
|
|
234
|
+
if (!lockService) throw new Error(`Unlock not available: no lockService in context. Did you call initializeAdk()?`);
|
|
235
|
+
await lockService.unlock(this.objectUri, { lockHandle: this._lockHandle.handle });
|
|
236
|
+
this._lockHandle = void 0;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Set lock handle (for subclasses that need custom lock logic)
|
|
240
|
+
*/
|
|
241
|
+
setLockHandle(handle) {
|
|
242
|
+
this._lockHandle = handle;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Save the object to SAP
|
|
246
|
+
*
|
|
247
|
+
* Generic implementation that handles:
|
|
248
|
+
* - Lock/unlock lifecycle
|
|
249
|
+
* - Mode switching (create/update/upsert)
|
|
250
|
+
* - Error handling
|
|
251
|
+
*
|
|
252
|
+
* Subclasses implement `saveViaContract()` to provide the typed contract call.
|
|
253
|
+
*
|
|
254
|
+
* Modes:
|
|
255
|
+
* - 'update' (default): PUT to existing object
|
|
256
|
+
* - 'create': POST skeleton, then lock → PUT/source → unlock (Eclipse ADT pattern)
|
|
257
|
+
* - 'upsert': Check existence, create if needed, then update
|
|
258
|
+
*
|
|
259
|
+
* @param options - Save options
|
|
260
|
+
* @returns this (for chaining)
|
|
261
|
+
*/
|
|
262
|
+
async save(options = {}) {
|
|
263
|
+
const { transport, mode = "update" } = options;
|
|
264
|
+
this._unchanged = false;
|
|
265
|
+
if (mode === "create") {
|
|
266
|
+
if (!await this.checkObjectExists()) await this.saveViaContract("create", { transport });
|
|
267
|
+
return this.save({
|
|
268
|
+
...options,
|
|
269
|
+
mode: "update"
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const hasPendingSources = this.hasPendingSources();
|
|
273
|
+
if (hasPendingSources) {
|
|
274
|
+
await this.checkPendingSourcesUnchanged();
|
|
275
|
+
if (this._unchanged) return this;
|
|
276
|
+
}
|
|
277
|
+
if (!hasPendingSources) {
|
|
278
|
+
await this.checkMetadataUnchanged();
|
|
279
|
+
if (this._unchanged) return this;
|
|
280
|
+
}
|
|
281
|
+
let confirmedExists = false;
|
|
282
|
+
if (mode === "upsert") {
|
|
283
|
+
confirmedExists = await this.checkObjectExists();
|
|
284
|
+
if (!confirmedExists) return this.fallbackToCreate(options);
|
|
285
|
+
}
|
|
286
|
+
const wasLocked = this.isLocked;
|
|
287
|
+
if (!wasLocked) try {
|
|
288
|
+
await this.lock(transport);
|
|
289
|
+
} catch (e) {
|
|
290
|
+
if (mode === "upsert" && !confirmedExists && this.shouldFallbackToCreate(e)) return this.fallbackToCreate(options);
|
|
291
|
+
throw e;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const effectiveTransport = transport ?? this._lockHandle?.correlationNumber;
|
|
295
|
+
if (!wasLocked) await this.refreshETagsAfterLock();
|
|
296
|
+
if (hasPendingSources) await this.savePendingSources({
|
|
297
|
+
lockHandle: this._lockHandle?.handle,
|
|
298
|
+
transport: effectiveTransport
|
|
299
|
+
});
|
|
300
|
+
else {
|
|
301
|
+
const effectiveMode = mode === "upsert" ? "update" : mode;
|
|
302
|
+
await this.saveViaContract(effectiveMode, {
|
|
303
|
+
transport: effectiveTransport,
|
|
304
|
+
lockHandle: this._lockHandle?.handle
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
this.dirty.clear();
|
|
308
|
+
return this;
|
|
309
|
+
} catch (e) {
|
|
310
|
+
if (mode === "upsert" && this.shouldFallbackToCreate(e)) {
|
|
311
|
+
if (!wasLocked) try {
|
|
312
|
+
await this.unlock();
|
|
313
|
+
} catch {}
|
|
314
|
+
return this.fallbackToCreate(options);
|
|
315
|
+
}
|
|
316
|
+
if (mode === "upsert" && this.isAlreadyExistsError(e)) {
|
|
317
|
+
this._unchanged = true;
|
|
318
|
+
return this;
|
|
319
|
+
}
|
|
320
|
+
throw e;
|
|
321
|
+
} finally {
|
|
322
|
+
if (!wasLocked) await this.unlock();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Wrapper key for the data in contract requests/responses
|
|
327
|
+
*
|
|
328
|
+
* Contracts wrap data like { abapClass: ... } or { abapInterface: ... }.
|
|
329
|
+
* Subclasses that support save override this.
|
|
330
|
+
*/
|
|
331
|
+
get wrapperKey() {}
|
|
332
|
+
/**
|
|
333
|
+
* CRUD contract for this object type
|
|
334
|
+
*
|
|
335
|
+
* Subclasses that support save override this to return their contract.
|
|
336
|
+
*/
|
|
337
|
+
get crudContract() {}
|
|
338
|
+
/**
|
|
339
|
+
* Execute the typed contract call for save
|
|
340
|
+
*
|
|
341
|
+
* Generic implementation using wrapperKey and crudContract.
|
|
342
|
+
* Objects that don't support save leave wrapperKey/crudContract undefined.
|
|
343
|
+
*
|
|
344
|
+
* For 'create' mode: POSTs skeleton data (minimal fields).
|
|
345
|
+
* For 'update' mode: PUTs full data (all fields except abapLanguageVersion).
|
|
346
|
+
*
|
|
347
|
+
* @param mode - 'create' (POST skeleton) or 'update' (PUT full)
|
|
348
|
+
* @param options - Contract options (transport, lockHandle)
|
|
349
|
+
*/
|
|
350
|
+
async saveViaContract(mode, options) {
|
|
351
|
+
const wrapperKey = this.wrapperKey;
|
|
352
|
+
const contract = this.crudContract;
|
|
353
|
+
if (!wrapperKey || !contract) throw new Error(`Save not supported for ${this.kind}.`);
|
|
354
|
+
if (mode === "create") {
|
|
355
|
+
const skeleton = await this.getSkeletonData();
|
|
356
|
+
const data = { [wrapperKey]: skeleton };
|
|
357
|
+
await contract.post({ corrNr: options.transport }, data);
|
|
358
|
+
} else {
|
|
359
|
+
const { abapLanguageVersion: _, ...rest } = await this.data();
|
|
360
|
+
const saveData = rest;
|
|
361
|
+
const data = { [wrapperKey]: saveData };
|
|
362
|
+
if (contract.get) try {
|
|
363
|
+
await contract.get(this.name);
|
|
364
|
+
} catch {
|
|
365
|
+
this.ctx.client.clearETag();
|
|
366
|
+
}
|
|
367
|
+
await contract.put(this.name, {
|
|
368
|
+
corrNr: options.transport,
|
|
369
|
+
lockHandle: options.lockHandle
|
|
370
|
+
}, data);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get skeleton data for object creation (POST).
|
|
375
|
+
*
|
|
376
|
+
* Returns only the minimal fields needed to create the object on SAP.
|
|
377
|
+
* SAP creates the object structure (includes, etc.) from these fields.
|
|
378
|
+
* Full metadata and source code are sent via PUT in the update step.
|
|
379
|
+
*
|
|
380
|
+
* Override in subclasses that need different skeleton fields.
|
|
381
|
+
*/
|
|
382
|
+
async getSkeletonData() {
|
|
383
|
+
const d = await this.data();
|
|
384
|
+
return {
|
|
385
|
+
name: d.name,
|
|
386
|
+
type: d.type,
|
|
387
|
+
description: d.description ?? "",
|
|
388
|
+
language: d.language ?? "EN",
|
|
389
|
+
masterLanguage: d.masterLanguage ?? "EN",
|
|
390
|
+
packageRef: d.packageRef
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Check if the object exists on the SAP system.
|
|
395
|
+
*
|
|
396
|
+
* Used by upsert mode to decide between update and create paths upfront.
|
|
397
|
+
* On BTP, locking a non-existent DDIC object creates a draft that persists
|
|
398
|
+
* even after unlock, blocking subsequent POST (create) attempts. Checking
|
|
399
|
+
* existence first with GET avoids this problem entirely.
|
|
400
|
+
*/
|
|
401
|
+
async checkObjectExists() {
|
|
402
|
+
const contract = this.crudContract;
|
|
403
|
+
if (!contract?.get) return true;
|
|
404
|
+
try {
|
|
405
|
+
await contract.get(this.name);
|
|
406
|
+
return true;
|
|
407
|
+
} catch (e) {
|
|
408
|
+
if (this.shouldFallbackToCreate(e)) return false;
|
|
409
|
+
throw e;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Check if error is a 404 Not Found
|
|
414
|
+
*/
|
|
415
|
+
isNotFoundError(e) {
|
|
416
|
+
if (e instanceof Error) return e.message.includes("404") || e.message.includes("Not Found");
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Upsert fallback: try POST (create), treat 422 "already exists" as unchanged.
|
|
421
|
+
* Extracted to avoid duplication in lock-catch and saveViaContract-catch paths.
|
|
422
|
+
*/
|
|
423
|
+
async fallbackToCreate(options) {
|
|
424
|
+
try {
|
|
425
|
+
return await this.save({
|
|
426
|
+
...options,
|
|
427
|
+
mode: "create"
|
|
428
|
+
});
|
|
429
|
+
} catch (createErr) {
|
|
430
|
+
if (this.isAlreadyExistsError(createErr)) {
|
|
431
|
+
this._unchanged = true;
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
throw createErr;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Check if error suggests the object doesn't exist or the operation
|
|
439
|
+
* is not supported for the current state.
|
|
440
|
+
*
|
|
441
|
+
* Some SAP DDIC endpoints (TABL, TTYP) return 405 Method Not Allowed
|
|
442
|
+
* instead of 404 when attempting to lock or PUT a non-existent object.
|
|
443
|
+
* In upsert mode, both should trigger a fallback to POST (create).
|
|
444
|
+
*/
|
|
445
|
+
shouldFallbackToCreate(e) {
|
|
446
|
+
if (this.isNotFoundError(e)) return true;
|
|
447
|
+
if (e instanceof Error) {
|
|
448
|
+
const msg = e.message;
|
|
449
|
+
if (msg.includes("405") || msg.includes("Method Not Allowed")) return true;
|
|
450
|
+
if (msg.includes("403") && msg.includes("S_ABPLNGVS")) return true;
|
|
451
|
+
}
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Check if error is a 422 "already exists" from a POST create attempt
|
|
456
|
+
*/
|
|
457
|
+
isAlreadyExistsError(e) {
|
|
458
|
+
const msg = e instanceof Error ? e.message.toLowerCase() : "";
|
|
459
|
+
return (msg.includes("422") || msg.includes("unprocessable")) && msg.includes("already exists");
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Check if object has pending sources to save
|
|
463
|
+
* Subclasses with source code (classes, interfaces) override this
|
|
464
|
+
*/
|
|
465
|
+
hasPendingSources() {
|
|
466
|
+
const self = this;
|
|
467
|
+
return !!(self._pendingSources || self._pendingSource);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Normalize source code for comparison.
|
|
471
|
+
* Trims trailing whitespace/newlines so minor formatting differences
|
|
472
|
+
* don't trigger unnecessary saves.
|
|
473
|
+
*/
|
|
474
|
+
normalizeSource(source) {
|
|
475
|
+
return source.split("\n").map((line) => line.trimEnd()).join("\n").trimEnd();
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Check if pending sources are unchanged vs SAP (pre-lock comparison).
|
|
479
|
+
* Sets _unchanged = true if nothing needs saving.
|
|
480
|
+
*
|
|
481
|
+
* Default handles single-source objects (_pendingSource) by fetching
|
|
482
|
+
* GET {objectUri}/source/main and comparing.
|
|
483
|
+
* AdkClass overrides this for multi-include logic.
|
|
484
|
+
*/
|
|
485
|
+
async checkPendingSourcesUnchanged() {
|
|
486
|
+
const self = this;
|
|
487
|
+
if (!self._pendingSource) return;
|
|
488
|
+
try {
|
|
489
|
+
const currentSource = await toText(await this.ctx.client.fetch(`${this.objectUri}/source/main`, {
|
|
490
|
+
method: "GET",
|
|
491
|
+
headers: { Accept: "text/plain" }
|
|
492
|
+
}));
|
|
493
|
+
if (this.normalizeSource(currentSource) === this.normalizeSource(self._pendingSource)) {
|
|
494
|
+
this._unchanged = true;
|
|
495
|
+
delete self._pendingSource;
|
|
496
|
+
}
|
|
497
|
+
} catch {}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Check if metadata-only objects are unchanged on SAP.
|
|
501
|
+
*
|
|
502
|
+
* For objects without source code (DDIC types like DOMA, DTEL, TABL, TTYP),
|
|
503
|
+
* fetches the current SAP state and compares only the fields present in
|
|
504
|
+
* the local data. SAP returns extra volatile fields (timestamps, links,
|
|
505
|
+
* version, etc.) that are ignored — only semantic fields matter.
|
|
506
|
+
*/
|
|
507
|
+
async checkMetadataUnchanged() {
|
|
508
|
+
const contract = this.crudContract;
|
|
509
|
+
const wrapperKey = this.wrapperKey;
|
|
510
|
+
if (!contract?.get || !wrapperKey) return;
|
|
511
|
+
try {
|
|
512
|
+
const sapData = (await contract.get(this.name))?.[wrapperKey];
|
|
513
|
+
if (!sapData) return;
|
|
514
|
+
const localData = await this.data();
|
|
515
|
+
const isEmpty = (v) => v == null || v === "" || Array.isArray(v) && v.length === 0 || typeof v === "object" && v !== null && Object.keys(v).length === 0;
|
|
516
|
+
const isSubsetMatch = (local, sap) => {
|
|
517
|
+
if (local === sap) return true;
|
|
518
|
+
if (isEmpty(local) && isEmpty(sap)) return true;
|
|
519
|
+
if (isEmpty(local) && sap != null) return true;
|
|
520
|
+
if (local == null || sap == null) return false;
|
|
521
|
+
if (typeof local !== typeof sap) return false;
|
|
522
|
+
if (Array.isArray(local)) {
|
|
523
|
+
if (!Array.isArray(sap)) return false;
|
|
524
|
+
if (local.length !== sap.length) return false;
|
|
525
|
+
return local.every((item, i) => isSubsetMatch(item, sap[i]));
|
|
526
|
+
}
|
|
527
|
+
if (typeof local === "object") {
|
|
528
|
+
const localObj = local;
|
|
529
|
+
const sapObj = sap;
|
|
530
|
+
return Object.keys(localObj).every((key) => {
|
|
531
|
+
if (key === "abapLanguageVersion") return true;
|
|
532
|
+
return isSubsetMatch(localObj[key], sapObj[key]);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return local === sap;
|
|
536
|
+
};
|
|
537
|
+
if (isSubsetMatch(localData, sapData)) this._unchanged = true;
|
|
538
|
+
} catch {}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Refresh cached ETags after acquiring a lock.
|
|
542
|
+
*
|
|
543
|
+
* SAP changes the object's internal version when a lock is acquired,
|
|
544
|
+
* which invalidates any cached ETags. Without refreshing, the next
|
|
545
|
+
* PUT sends a stale If-Match header and SAP returns HTTP 412.
|
|
546
|
+
*
|
|
547
|
+
* For objects with pending sources: re-GET {objectUri}/source/main
|
|
548
|
+
* For metadata-only objects: re-GET via crudContract.get()
|
|
549
|
+
*
|
|
550
|
+
* Subclasses with multiple source endpoints (e.g., AdkClass with includes)
|
|
551
|
+
* should override this to refresh all relevant endpoints.
|
|
552
|
+
*/
|
|
553
|
+
async refreshETagsAfterLock() {
|
|
554
|
+
if (this.hasPendingSources()) {
|
|
555
|
+
const sourceUrl = `${this.objectUri}/source/main`;
|
|
556
|
+
try {
|
|
557
|
+
await this.ctx.client.fetch(sourceUrl, {
|
|
558
|
+
method: "GET",
|
|
559
|
+
headers: { Accept: "text/plain" }
|
|
560
|
+
});
|
|
561
|
+
} catch {
|
|
562
|
+
this.ctx.client.clearETag(sourceUrl);
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
const contract = this.crudContract;
|
|
566
|
+
if (contract?.get) try {
|
|
567
|
+
await contract.get(this.name);
|
|
568
|
+
} catch {
|
|
569
|
+
this.ctx.client.clearETag();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Save pending sources to SAP.
|
|
575
|
+
*
|
|
576
|
+
* Default handles single-source objects (_pendingSource) by PUTting
|
|
577
|
+
* to {objectUri}/source/main.
|
|
578
|
+
* AdkClass overrides this for multi-include logic.
|
|
579
|
+
*/
|
|
580
|
+
async savePendingSources(options) {
|
|
581
|
+
const self = this;
|
|
582
|
+
if (!self._pendingSource) return;
|
|
583
|
+
const sourceUrl = `${this.objectUri}/source/main`;
|
|
584
|
+
try {
|
|
585
|
+
await this.ctx.client.fetch(sourceUrl, {
|
|
586
|
+
method: "GET",
|
|
587
|
+
headers: { Accept: "text/plain" }
|
|
588
|
+
});
|
|
589
|
+
} catch {
|
|
590
|
+
this.ctx.client.clearETag(sourceUrl);
|
|
591
|
+
}
|
|
592
|
+
const params = new URLSearchParams();
|
|
593
|
+
if (options?.lockHandle) params.set("lockHandle", options.lockHandle);
|
|
594
|
+
if (options?.transport) params.set("corrNr", options.transport);
|
|
595
|
+
const qs = params.toString();
|
|
596
|
+
await this.ctx.client.fetch(`${sourceUrl}${qs ? "?" + qs : ""}`, {
|
|
597
|
+
method: "PUT",
|
|
598
|
+
headers: { "Content-Type": "text/plain" },
|
|
599
|
+
body: self._pendingSource
|
|
600
|
+
});
|
|
601
|
+
delete self._pendingSource;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Set source content for a source-based object
|
|
605
|
+
*
|
|
606
|
+
* Generic implementation for objects with source code (classes, includes, etc.)
|
|
607
|
+
* Subclasses should override if they need custom source handling.
|
|
608
|
+
*
|
|
609
|
+
* @param sourcePath - Relative path to source (e.g., '/source/main')
|
|
610
|
+
* @param content - Source code content
|
|
611
|
+
* @param options - Save options
|
|
612
|
+
*/
|
|
613
|
+
async setSource(sourcePath, content, options = {}) {
|
|
614
|
+
const { transport } = options;
|
|
615
|
+
const wasLocked = this.isLocked;
|
|
616
|
+
if (!wasLocked) await this.lock();
|
|
617
|
+
try {
|
|
618
|
+
const params = new URLSearchParams();
|
|
619
|
+
if (transport) params.set("corrNr", transport);
|
|
620
|
+
if (this._lockHandle) params.set("lockHandle", this._lockHandle.handle);
|
|
621
|
+
const fullPath = `${this.objectUri}${sourcePath}`;
|
|
622
|
+
const url = params.toString() ? `${fullPath}?${params.toString()}` : fullPath;
|
|
623
|
+
await this.ctx.client.fetch(url, {
|
|
624
|
+
method: "PUT",
|
|
625
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
626
|
+
body: content
|
|
627
|
+
});
|
|
628
|
+
} finally {
|
|
629
|
+
if (!wasLocked) await this.unlock();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Activate this object
|
|
634
|
+
*
|
|
635
|
+
* Activates a single object. For bulk activation of multiple objects,
|
|
636
|
+
* use `AdkObjectSet.activateAll()`.
|
|
637
|
+
*
|
|
638
|
+
* @returns this (for chaining)
|
|
639
|
+
*/
|
|
640
|
+
async activate() {
|
|
641
|
+
const activationXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
642
|
+
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
643
|
+
<adtcore:objectReference adtcore:uri="${this.objectUri}" adtcore:type="${this.type}" adtcore:name="${this.name}"/>
|
|
644
|
+
</adtcore:objectReferences>`;
|
|
645
|
+
await this.ctx.client.fetch("/sap/bc/adt/activation?method=activate&preauditRequested=true", {
|
|
646
|
+
method: "POST",
|
|
647
|
+
headers: {
|
|
648
|
+
"Content-Type": "application/xml",
|
|
649
|
+
Accept: "application/xml"
|
|
650
|
+
},
|
|
651
|
+
body: activationXml
|
|
652
|
+
});
|
|
653
|
+
return this;
|
|
654
|
+
}
|
|
655
|
+
/** Lazy load a segment (async) - fetches only once, then cached */
|
|
656
|
+
async lazy(key, loader) {
|
|
657
|
+
if (!this.cache.has(key)) this.cache.set(key, await loader());
|
|
658
|
+
return this.cache.get(key);
|
|
659
|
+
}
|
|
660
|
+
/** Lazy compute a segment (sync) - computes only once, then cached */
|
|
661
|
+
cached(key, compute) {
|
|
662
|
+
if (!this.cache.has(key)) this.cache.set(key, compute());
|
|
663
|
+
return this.cache.get(key);
|
|
664
|
+
}
|
|
665
|
+
/** Set a cached value directly (for local/generated data) */
|
|
666
|
+
set(key, value) {
|
|
667
|
+
this.cache.set(key, value);
|
|
668
|
+
}
|
|
669
|
+
/** Mark a segment as dirty (modified locally) */
|
|
670
|
+
markDirty(key) {
|
|
671
|
+
this.dirty.add(key);
|
|
672
|
+
}
|
|
673
|
+
/** Check if any segment (or specific segment) is dirty */
|
|
674
|
+
isDirty(segment) {
|
|
675
|
+
return segment ? this.dirty.has(segment) : this.dirty.size > 0;
|
|
676
|
+
}
|
|
677
|
+
/** Clear cache for a segment (force reload on next access) */
|
|
678
|
+
invalidate(key) {
|
|
679
|
+
this.cache.delete(key);
|
|
680
|
+
this.dirty.delete(key);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Fetch a text resource from an ADT endpoint.
|
|
684
|
+
* Delegates to the underlying ADT client's fetch.
|
|
685
|
+
*
|
|
686
|
+
* @param url - ADT endpoint path (e.g., '/sap/bc/adt/ddic/dataelements/spras')
|
|
687
|
+
* @param headers - Optional HTTP headers (defaults to no Accept header, letting server choose)
|
|
688
|
+
* @returns Response content as text, or undefined if 404
|
|
689
|
+
*/
|
|
690
|
+
async fetchText(url, headers) {
|
|
691
|
+
try {
|
|
692
|
+
return toText(await this.ctx.client.fetch(url, {
|
|
693
|
+
method: "GET",
|
|
694
|
+
headers: headers ?? {}
|
|
695
|
+
}));
|
|
696
|
+
} catch {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
/**
|
|
702
|
+
* Base class for main development objects (AdtMainObject)
|
|
703
|
+
*
|
|
704
|
+
* Extends AdkObject with package reference, responsible user, and master language.
|
|
705
|
+
* Used for repository objects like classes, interfaces, packages, etc.
|
|
706
|
+
*
|
|
707
|
+
* Note: Uses type assertions internally because schema-inferred types from
|
|
708
|
+
* contracts may have different shapes than AdkMainObjectData. The getters
|
|
709
|
+
* safely handle missing properties with defaults.
|
|
710
|
+
*
|
|
711
|
+
* @typeParam K - The object kind (from AdkKind)
|
|
712
|
+
* @typeParam D - The internal data type (typically schema-inferred from contract)
|
|
713
|
+
*/
|
|
714
|
+
var AdkMainObject = class extends AdkObject {
|
|
715
|
+
get mainData() {
|
|
716
|
+
return this.dataSync;
|
|
717
|
+
}
|
|
718
|
+
/** Package reference (adtcore:packageRef) */
|
|
719
|
+
get packageRef() {
|
|
720
|
+
return this.mainData.packageRef;
|
|
721
|
+
}
|
|
722
|
+
/** Package name (convenience getter) */
|
|
723
|
+
get package() {
|
|
724
|
+
return this.mainData.packageRef?.name ?? "";
|
|
725
|
+
}
|
|
726
|
+
/** Master system (adtcore:masterSystem) */
|
|
727
|
+
get masterSystem() {
|
|
728
|
+
return this.mainData.masterSystem ?? "";
|
|
729
|
+
}
|
|
730
|
+
/** Master language (adtcore:masterLanguage) */
|
|
731
|
+
get masterLanguage() {
|
|
732
|
+
return this.mainData.masterLanguage ?? "";
|
|
733
|
+
}
|
|
734
|
+
/** Responsible user (adtcore:responsible) */
|
|
735
|
+
get responsible() {
|
|
736
|
+
return this.mainData.responsible ?? "";
|
|
737
|
+
}
|
|
738
|
+
/** ABAP language version (adtcore:abapLanguageVersion) */
|
|
739
|
+
get abapLanguageVersion() {
|
|
740
|
+
return this.mainData.abapLanguageVersion ?? "";
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Save main source code to SAP.
|
|
744
|
+
* Requires the object to be locked before calling.
|
|
745
|
+
* Subclasses may override to add additional behavior (e.g. cache invalidation).
|
|
746
|
+
*/
|
|
747
|
+
async saveMainSource(source, options) {
|
|
748
|
+
const params = new URLSearchParams();
|
|
749
|
+
if (options?.lockHandle) params.set("lockHandle", options.lockHandle);
|
|
750
|
+
if (options?.transport) params.set("corrNr", options.transport);
|
|
751
|
+
const qs = params.toString();
|
|
752
|
+
await this.ctx.client.fetch(`${this.objectUri}/source/main${qs ? "?" + qs : ""}`, {
|
|
753
|
+
method: "PUT",
|
|
754
|
+
headers: { "Content-Type": "text/plain" },
|
|
755
|
+
body: source
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/base/object-set.ts
|
|
762
|
+
/**
|
|
763
|
+
* ADK Object Set - Collection of objects with bulk operations
|
|
764
|
+
*
|
|
765
|
+
* A semantic layer for managing collections of ADK objects and
|
|
766
|
+
* performing bulk operations like save and activate.
|
|
767
|
+
*/
|
|
768
|
+
var AdkObjectSet = class AdkObjectSet {
|
|
769
|
+
ctx;
|
|
770
|
+
objects = [];
|
|
771
|
+
constructor(ctx) {
|
|
772
|
+
this.ctx = ctx;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Add a single object to the set
|
|
776
|
+
*
|
|
777
|
+
* Propagates the set's context (lockService, lockStore) to the object
|
|
778
|
+
* so that objects created by plugins with a bare { client } context
|
|
779
|
+
* inherit the services configured on the set.
|
|
780
|
+
*/
|
|
781
|
+
add(object) {
|
|
782
|
+
if (!this.objects.includes(object)) {
|
|
783
|
+
object.adoptContext(this.ctx);
|
|
784
|
+
this.objects.push(object);
|
|
785
|
+
}
|
|
786
|
+
return this;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Add multiple objects to the set
|
|
790
|
+
*/
|
|
791
|
+
addAll(objects) {
|
|
792
|
+
for (const obj of objects) this.add(obj);
|
|
793
|
+
return this;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Remove an object from the set
|
|
797
|
+
*/
|
|
798
|
+
remove(object) {
|
|
799
|
+
const index = this.objects.indexOf(object);
|
|
800
|
+
if (index !== -1) this.objects.splice(index, 1);
|
|
801
|
+
return this;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Clear all objects from the set
|
|
805
|
+
*/
|
|
806
|
+
clear() {
|
|
807
|
+
this.objects.length = 0;
|
|
808
|
+
return this;
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Get all objects in the set
|
|
812
|
+
*/
|
|
813
|
+
getAll() {
|
|
814
|
+
return [...this.objects];
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Get number of objects in the set
|
|
818
|
+
*/
|
|
819
|
+
get size() {
|
|
820
|
+
return this.objects.length;
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Check if set is empty
|
|
824
|
+
*/
|
|
825
|
+
get isEmpty() {
|
|
826
|
+
return this.objects.length === 0;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Filter objects by type
|
|
830
|
+
*/
|
|
831
|
+
filterByType(type) {
|
|
832
|
+
return this.objects.filter((obj) => obj.type === type);
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Filter objects by kind
|
|
836
|
+
*/
|
|
837
|
+
filterByKind(kind) {
|
|
838
|
+
return this.objects.filter((obj) => obj.kind === kind);
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Iterate over objects
|
|
842
|
+
*/
|
|
843
|
+
[Symbol.iterator]() {
|
|
844
|
+
return this.objects[Symbol.iterator]();
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Save all objects in the set
|
|
848
|
+
*
|
|
849
|
+
* Saves each object individually, handling lock/unlock per object.
|
|
850
|
+
* By default continues on error to save as many objects as possible.
|
|
851
|
+
*
|
|
852
|
+
* @param options - Save options (transport, inactive, continueOnError)
|
|
853
|
+
* @returns Result with success/failure counts and details
|
|
854
|
+
*/
|
|
855
|
+
async saveAll(options = {}) {
|
|
856
|
+
const { continueOnError = true, onProgress, ...saveOptions } = options;
|
|
857
|
+
const result = {
|
|
858
|
+
success: 0,
|
|
859
|
+
failed: 0,
|
|
860
|
+
unchanged: 0,
|
|
861
|
+
results: []
|
|
862
|
+
};
|
|
863
|
+
const total = this.objects.length;
|
|
864
|
+
let _saved = 0;
|
|
865
|
+
let processed = 0;
|
|
866
|
+
for (const obj of this.objects) {
|
|
867
|
+
try {
|
|
868
|
+
await obj.save(saveOptions);
|
|
869
|
+
if (obj._unchanged) {
|
|
870
|
+
result.unchanged++;
|
|
871
|
+
result.results.push({
|
|
872
|
+
object: obj,
|
|
873
|
+
success: true,
|
|
874
|
+
unchanged: true
|
|
875
|
+
});
|
|
876
|
+
} else {
|
|
877
|
+
_saved++;
|
|
878
|
+
result.success++;
|
|
879
|
+
result.results.push({
|
|
880
|
+
object: obj,
|
|
881
|
+
success: true
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
} catch (error) {
|
|
885
|
+
result.failed++;
|
|
886
|
+
result.results.push({
|
|
887
|
+
object: obj,
|
|
888
|
+
success: false,
|
|
889
|
+
error: error instanceof Error ? error.message : String(error)
|
|
890
|
+
});
|
|
891
|
+
if (!continueOnError) {
|
|
892
|
+
processed++;
|
|
893
|
+
onProgress?.(processed, total, obj);
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
processed++;
|
|
898
|
+
onProgress?.(processed, total, obj);
|
|
899
|
+
}
|
|
900
|
+
return result;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Activate all objects in the set (bulk activation)
|
|
904
|
+
*
|
|
905
|
+
* Uses SAP's bulk activation endpoint for efficiency.
|
|
906
|
+
* All objects are activated in a single request.
|
|
907
|
+
*
|
|
908
|
+
* @param options - Activation options
|
|
909
|
+
* @returns Activation result with success/failure counts
|
|
910
|
+
*/
|
|
911
|
+
async activateAll(options = {}) {
|
|
912
|
+
const { onProgress } = options;
|
|
913
|
+
if (this.objects.length === 0) return {
|
|
914
|
+
success: 0,
|
|
915
|
+
failed: 0,
|
|
916
|
+
messages: []
|
|
917
|
+
};
|
|
918
|
+
onProgress?.(`Activating ${this.objects.length} objects...`);
|
|
919
|
+
const activationXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
920
|
+
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
921
|
+
${this.objects.map((obj) => `<adtcore:objectReference adtcore:uri="${obj.objectUri}" adtcore:type="${obj.type}" adtcore:name="${obj.name}"/>`).join("\n ")}
|
|
922
|
+
</adtcore:objectReferences>`;
|
|
923
|
+
try {
|
|
924
|
+
const response = await this.ctx.client.fetch("/sap/bc/adt/activation?method=activate&preauditRequested=true", {
|
|
925
|
+
method: "POST",
|
|
926
|
+
headers: {
|
|
927
|
+
"Content-Type": "application/xml",
|
|
928
|
+
Accept: "application/xml"
|
|
929
|
+
},
|
|
930
|
+
body: activationXml
|
|
931
|
+
});
|
|
932
|
+
return {
|
|
933
|
+
success: this.objects.length,
|
|
934
|
+
failed: 0,
|
|
935
|
+
messages: [],
|
|
936
|
+
response: String(response)
|
|
937
|
+
};
|
|
938
|
+
} catch (error) {
|
|
939
|
+
return {
|
|
940
|
+
success: 0,
|
|
941
|
+
failed: this.objects.length,
|
|
942
|
+
messages: [error instanceof Error ? error.message : String(error)]
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Save all objects as inactive, then bulk activate, then unlock
|
|
948
|
+
*
|
|
949
|
+
* This is the recommended pattern for deploying multiple objects:
|
|
950
|
+
* 1. Save all objects as inactive (handles dependencies)
|
|
951
|
+
* 2. Bulk activate all saved objects
|
|
952
|
+
* 3. Unlock all objects
|
|
953
|
+
*
|
|
954
|
+
* @param options - Save options (transport required)
|
|
955
|
+
* @returns Combined result of save and activation
|
|
956
|
+
*/
|
|
957
|
+
async deploy(options) {
|
|
958
|
+
const { activate = true, ...saveOptions } = options;
|
|
959
|
+
try {
|
|
960
|
+
const saveResult = await this.saveAll({
|
|
961
|
+
...saveOptions,
|
|
962
|
+
inactive: true
|
|
963
|
+
});
|
|
964
|
+
let activationResult;
|
|
965
|
+
if (activate && saveResult.success > 0) {
|
|
966
|
+
const savedSet = new AdkObjectSet(this.ctx);
|
|
967
|
+
for (const r of saveResult.results) if (r.success) savedSet.add(r.object);
|
|
968
|
+
activationResult = await savedSet.activateAll();
|
|
969
|
+
for (const r of saveResult.results) {
|
|
970
|
+
if (!r.success) continue;
|
|
971
|
+
const obj = r.object;
|
|
972
|
+
const deferred = obj._deferredSources;
|
|
973
|
+
if (deferred && deferred.length > 0) try {
|
|
974
|
+
const lock = await r.object.lock();
|
|
975
|
+
for (const [key, source] of deferred) await obj.saveIncludeSource(key, source, {
|
|
976
|
+
lockHandle: lock.handle,
|
|
977
|
+
transport: saveOptions.transport
|
|
978
|
+
});
|
|
979
|
+
delete obj._deferredSources;
|
|
980
|
+
const deferredSet = new AdkObjectSet(this.ctx);
|
|
981
|
+
deferredSet.add(r.object);
|
|
982
|
+
await deferredSet.activateAll();
|
|
983
|
+
} catch {} finally {
|
|
984
|
+
try {
|
|
985
|
+
await r.object.unlock();
|
|
986
|
+
} catch {}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return {
|
|
991
|
+
save: saveResult,
|
|
992
|
+
activation: activationResult
|
|
993
|
+
};
|
|
994
|
+
} finally {
|
|
995
|
+
await this.unlockAll();
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Lock all objects in the set
|
|
1000
|
+
*
|
|
1001
|
+
* @returns Array of lock handles
|
|
1002
|
+
*/
|
|
1003
|
+
async lockAll() {
|
|
1004
|
+
const handles = [];
|
|
1005
|
+
for (const obj of this.objects) {
|
|
1006
|
+
const handle = await obj.lock();
|
|
1007
|
+
handles.push(handle);
|
|
1008
|
+
}
|
|
1009
|
+
return handles;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Unlock all objects in the set
|
|
1013
|
+
*/
|
|
1014
|
+
async unlockAll() {
|
|
1015
|
+
for (const obj of this.objects) try {
|
|
1016
|
+
await obj.unlock();
|
|
1017
|
+
} catch {}
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Load all objects (fetch data from SAP)
|
|
1021
|
+
*
|
|
1022
|
+
* @param options - Load options
|
|
1023
|
+
* @returns This set (for chaining)
|
|
1024
|
+
*/
|
|
1025
|
+
async loadAll(options = {}) {
|
|
1026
|
+
const { parallel = false } = options;
|
|
1027
|
+
if (parallel) await Promise.all(this.objects.map((obj) => obj.load()));
|
|
1028
|
+
else for (const obj of this.objects) await obj.load();
|
|
1029
|
+
return this;
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Create an object set from an array of objects
|
|
1033
|
+
*/
|
|
1034
|
+
static from(objects, ctx) {
|
|
1035
|
+
const set = new AdkObjectSet(ctx);
|
|
1036
|
+
set.addAll(objects);
|
|
1037
|
+
return set;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Create an object set by collecting from an async generator
|
|
1041
|
+
*
|
|
1042
|
+
* Collects all objects yielded by the generator into the set.
|
|
1043
|
+
* Useful for collecting objects from format plugin export generators.
|
|
1044
|
+
*
|
|
1045
|
+
* @param generator - Async generator yielding AdkObject instances
|
|
1046
|
+
* @param ctx - ADK context for the set
|
|
1047
|
+
* @param options - Collection options
|
|
1048
|
+
* @returns Promise resolving to populated AdkObjectSet
|
|
1049
|
+
*/
|
|
1050
|
+
static async fromGenerator(generator, ctx, options = {}) {
|
|
1051
|
+
const { filter, onObject } = options;
|
|
1052
|
+
const set = new AdkObjectSet(ctx);
|
|
1053
|
+
for await (const obj of generator) {
|
|
1054
|
+
if (filter && !filter(obj)) continue;
|
|
1055
|
+
set.add(obj);
|
|
1056
|
+
onObject?.(obj);
|
|
1057
|
+
}
|
|
1058
|
+
return set;
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
//#endregion
|
|
1063
|
+
//#region src/base/adt.ts
|
|
1064
|
+
/**
|
|
1065
|
+
* Create ADK contract from ADT client
|
|
1066
|
+
*
|
|
1067
|
+
* @param client - ADT client instance from createAdtClient()
|
|
1068
|
+
* @returns ADK contract proxy
|
|
1069
|
+
*/
|
|
1070
|
+
function createAdkContract(client) {
|
|
1071
|
+
return {
|
|
1072
|
+
oo: client.adt.oo,
|
|
1073
|
+
packages: client.adt.packages,
|
|
1074
|
+
cts: client.adt.cts,
|
|
1075
|
+
core: client.adt.core,
|
|
1076
|
+
repository: client.adt.repository,
|
|
1077
|
+
programs: client.adt.programs,
|
|
1078
|
+
functions: client.adt.functions,
|
|
1079
|
+
ddic: client.adt.ddic,
|
|
1080
|
+
bo: client.adt.bo
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
//#endregion
|
|
1085
|
+
//#region src/base/global-context.ts
|
|
1086
|
+
let globalContext = null;
|
|
1087
|
+
/**
|
|
1088
|
+
* Initialize the global ADK context from an ADT client.
|
|
1089
|
+
*
|
|
1090
|
+
* Call this once during application bootstrap (e.g., in CLI initialization).
|
|
1091
|
+
* After initialization, ADK objects can be used without passing context.
|
|
1092
|
+
*
|
|
1093
|
+
* @param client - ADT client v2 instance
|
|
1094
|
+
* @throws Error if client is not provided
|
|
1095
|
+
*
|
|
1096
|
+
* @example
|
|
1097
|
+
* ```ts
|
|
1098
|
+
* import { initializeAdk } from '@abapify/adk';
|
|
1099
|
+
* import { createAdtClient } from '@abapify/adt-client';
|
|
1100
|
+
*
|
|
1101
|
+
* const client = createAdtClient({ ... });
|
|
1102
|
+
* initializeAdk(client);
|
|
1103
|
+
*
|
|
1104
|
+
* // Now ADK objects work without context
|
|
1105
|
+
* const transport = await AdkTransportRequest.get('S0DK900001');
|
|
1106
|
+
* ```
|
|
1107
|
+
*/
|
|
1108
|
+
function initializeAdk(client, options) {
|
|
1109
|
+
if (!client) throw new Error("ADK initialization failed: client is required");
|
|
1110
|
+
const lockStore = options?.lockStore;
|
|
1111
|
+
globalContext = {
|
|
1112
|
+
client,
|
|
1113
|
+
lockStore,
|
|
1114
|
+
lockService: options?.lockService ?? createLockService$1(client, { store: lockStore })
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Get the global ADK context.
|
|
1119
|
+
*
|
|
1120
|
+
* Used internally by ADK objects when no explicit context is provided.
|
|
1121
|
+
*
|
|
1122
|
+
* @returns The global ADK context
|
|
1123
|
+
* @throws Error if ADK has not been initialized
|
|
1124
|
+
*
|
|
1125
|
+
* @example
|
|
1126
|
+
* ```ts
|
|
1127
|
+
* // Internal usage in ADK objects:
|
|
1128
|
+
* static async get(number: string, ctx?: AdkContext): Promise<AdkTransportRequest> {
|
|
1129
|
+
* const context = ctx ?? getGlobalContext();
|
|
1130
|
+
* // ...
|
|
1131
|
+
* }
|
|
1132
|
+
* ```
|
|
1133
|
+
*/
|
|
1134
|
+
function getGlobalContext() {
|
|
1135
|
+
if (!globalContext) throw new Error("ADK not initialized. Call initializeAdk(client) before using ADK objects.\nExample:\n import { initializeAdk } from '@abapify/adk';\n initializeAdk(client);");
|
|
1136
|
+
return globalContext;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Check if ADK has been initialized.
|
|
1140
|
+
*
|
|
1141
|
+
* Useful for conditional logic or error handling.
|
|
1142
|
+
*
|
|
1143
|
+
* @returns true if initializeAdk() has been called
|
|
1144
|
+
*/
|
|
1145
|
+
function isAdkInitialized() {
|
|
1146
|
+
return globalContext !== null;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Reset the global ADK context.
|
|
1150
|
+
*
|
|
1151
|
+
* Primarily for testing purposes. Clears the global context.
|
|
1152
|
+
*/
|
|
1153
|
+
function resetAdk() {
|
|
1154
|
+
globalContext = null;
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Get the global context or return undefined if not initialized.
|
|
1158
|
+
*
|
|
1159
|
+
* Unlike getGlobalContext(), this doesn't throw - useful when you want
|
|
1160
|
+
* to check if context exists before using it.
|
|
1161
|
+
*
|
|
1162
|
+
* @returns The global context or undefined
|
|
1163
|
+
*/
|
|
1164
|
+
function tryGetGlobalContext() {
|
|
1165
|
+
return globalContext ?? void 0;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
//#endregion
|
|
1169
|
+
//#region src/objects/repository/devc/devc.model.ts
|
|
1170
|
+
/**
|
|
1171
|
+
* DEVC - ABAP Package
|
|
1172
|
+
*
|
|
1173
|
+
* ADK object for ABAP packages (DEVC).
|
|
1174
|
+
*/
|
|
1175
|
+
/**
|
|
1176
|
+
* ADK Package object
|
|
1177
|
+
*
|
|
1178
|
+
* Inherits from AdkMainObject which provides:
|
|
1179
|
+
* - AdkObject: name, type, description, version, language, changedBy/At, createdBy/At, links
|
|
1180
|
+
* - AdkMainObject: packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
|
|
1181
|
+
*
|
|
1182
|
+
* Note: For packages, the parent package is stored in `superPackage` not `packageRef`.
|
|
1183
|
+
* The `package` getter is overridden to return the super package name.
|
|
1184
|
+
*/
|
|
1185
|
+
var AdkPackage = class AdkPackage extends AdkMainObject {
|
|
1186
|
+
static kind = Package;
|
|
1187
|
+
kind = AdkPackage.kind;
|
|
1188
|
+
get objectUri() {
|
|
1189
|
+
return `/sap/bc/adt/packages/${encodeURIComponent(this.name)}`;
|
|
1190
|
+
}
|
|
1191
|
+
get package() {
|
|
1192
|
+
return this.dataSync.superPackage?.name ?? "";
|
|
1193
|
+
}
|
|
1194
|
+
get attributes() {
|
|
1195
|
+
const attrs = this.dataSync.attributes;
|
|
1196
|
+
return {
|
|
1197
|
+
packageType: attrs?.packageType ?? "development",
|
|
1198
|
+
isEncapsulated: attrs?.isEncapsulated ?? false,
|
|
1199
|
+
isAddingObjectsAllowed: attrs?.isAddingObjectsAllowed ?? true,
|
|
1200
|
+
recordChanges: attrs?.recordChanges ?? false,
|
|
1201
|
+
languageVersion: attrs?.languageVersion ?? ""
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
get superPackage() {
|
|
1205
|
+
const sp = this.dataSync.superPackage;
|
|
1206
|
+
if (!sp?.name) return void 0;
|
|
1207
|
+
return {
|
|
1208
|
+
uri: sp.uri ?? "",
|
|
1209
|
+
type: sp.type ?? "DEVC/K",
|
|
1210
|
+
name: sp.name,
|
|
1211
|
+
description: sp.description
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
get applicationComponent() {
|
|
1215
|
+
const ac = this.dataSync.applicationComponent;
|
|
1216
|
+
if (!ac?.name) return void 0;
|
|
1217
|
+
return {
|
|
1218
|
+
name: ac.name,
|
|
1219
|
+
description: ac.description ?? ""
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
get transport() {
|
|
1223
|
+
const t = this.dataSync.transport;
|
|
1224
|
+
if (!t) return void 0;
|
|
1225
|
+
return {
|
|
1226
|
+
softwareComponent: t.softwareComponent?.name ? {
|
|
1227
|
+
name: t.softwareComponent.name,
|
|
1228
|
+
description: t.softwareComponent.description ?? ""
|
|
1229
|
+
} : void 0,
|
|
1230
|
+
transportLayer: t.transportLayer?.name ? {
|
|
1231
|
+
name: t.transportLayer.name,
|
|
1232
|
+
description: t.transportLayer.description ?? ""
|
|
1233
|
+
} : void 0
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
async getSubpackages() {
|
|
1237
|
+
return this.lazy("subpackages", async () => {
|
|
1238
|
+
const response = await this.ctx.client.adt.repository.informationsystem.search.quickSearch({
|
|
1239
|
+
query: "*",
|
|
1240
|
+
packageName: this.name,
|
|
1241
|
+
objectType: "DEVC",
|
|
1242
|
+
maxResults: 1e3
|
|
1243
|
+
});
|
|
1244
|
+
const refs = "objectReferences" in response ? response.objectReferences?.objectReference ?? [] : [];
|
|
1245
|
+
const subpkgRefs = (Array.isArray(refs) ? refs : [refs]).filter((ref) => !!ref.name && ref.type === "DEVC/K" && ref.name !== this.name);
|
|
1246
|
+
return (await Promise.all(subpkgRefs.map(async (ref) => {
|
|
1247
|
+
const pkg = new AdkPackage(this.ctx, ref.name);
|
|
1248
|
+
await pkg.load();
|
|
1249
|
+
return pkg;
|
|
1250
|
+
}))).filter((pkg) => pkg.superPackage?.name?.toUpperCase() === this.name.toUpperCase());
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
async getObjects() {
|
|
1254
|
+
return this.lazy("objects", async () => {
|
|
1255
|
+
const response = await this.ctx.client.adt.repository.informationsystem.search.quickSearch({
|
|
1256
|
+
query: "*",
|
|
1257
|
+
packageName: this.name,
|
|
1258
|
+
maxResults: 1e3
|
|
1259
|
+
});
|
|
1260
|
+
const refs = "objectReferences" in response ? response.objectReferences?.objectReference ?? [] : [];
|
|
1261
|
+
return (Array.isArray(refs) ? refs : [refs]).filter((ref) => !!ref.name && ref.type !== "DEVC/K" && ref.packageName?.toUpperCase() === this.name.toUpperCase()).map((ref) => ({
|
|
1262
|
+
kind: ref.type ?? "",
|
|
1263
|
+
type: ref.type ?? "",
|
|
1264
|
+
name: ref.name,
|
|
1265
|
+
description: ref.description ?? "",
|
|
1266
|
+
package: ref.packageName ?? ""
|
|
1267
|
+
}));
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
async getAllObjects() {
|
|
1271
|
+
return this.lazy("allObjects", async () => {
|
|
1272
|
+
const direct = await this.getObjects();
|
|
1273
|
+
const subpackages = await this.getSubpackages();
|
|
1274
|
+
const nested = await Promise.all(subpackages.map((pkg) => pkg.getAllObjects()));
|
|
1275
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1276
|
+
return [...direct, ...nested.flat()].filter((obj) => {
|
|
1277
|
+
const key = `${obj.type}:${obj.name}`;
|
|
1278
|
+
if (seen.has(key)) return false;
|
|
1279
|
+
seen.add(key);
|
|
1280
|
+
return true;
|
|
1281
|
+
});
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
async load() {
|
|
1285
|
+
const response = await this.ctx.client.adt.packages.get(this.name);
|
|
1286
|
+
if (!response || !("package" in response) || !response.package) throw new Error(`Package '${this.name}' not found or returned empty response`);
|
|
1287
|
+
this.setData(response.package);
|
|
1288
|
+
return this;
|
|
1289
|
+
}
|
|
1290
|
+
get wrapperKey() {
|
|
1291
|
+
return "package";
|
|
1292
|
+
}
|
|
1293
|
+
get crudContract() {
|
|
1294
|
+
return this.ctx.client.adt.packages;
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Get skeleton data for package creation (POST).
|
|
1298
|
+
*
|
|
1299
|
+
* SAP requires ALL elements in the Package xs:sequence, even if empty.
|
|
1300
|
+
* The sequence order is: attributes, superPackage, extensionAlias, switch,
|
|
1301
|
+
* applicationComponent, transport, translation, useAccesses,
|
|
1302
|
+
* packageInterfaces, subPackages.
|
|
1303
|
+
* Missing elements cause "System expected the element ..." 400 errors.
|
|
1304
|
+
*/
|
|
1305
|
+
async getSkeletonData() {
|
|
1306
|
+
const d = await this.data();
|
|
1307
|
+
return {
|
|
1308
|
+
name: d.name,
|
|
1309
|
+
type: d.type ?? "DEVC/K",
|
|
1310
|
+
description: d.description ?? "",
|
|
1311
|
+
language: d.language ?? "EN",
|
|
1312
|
+
masterLanguage: d.masterLanguage ?? "EN",
|
|
1313
|
+
responsible: d.responsible ?? "",
|
|
1314
|
+
attributes: d.attributes ?? { packageType: "development" },
|
|
1315
|
+
superPackage: d.superPackage ?? {},
|
|
1316
|
+
extensionAlias: d.extensionAlias ?? {},
|
|
1317
|
+
switch: d.switch ?? {},
|
|
1318
|
+
applicationComponent: d.applicationComponent ?? {},
|
|
1319
|
+
transport: d.transport ?? {},
|
|
1320
|
+
translation: d.translation ?? {},
|
|
1321
|
+
useAccesses: d.useAccesses ?? {},
|
|
1322
|
+
packageInterfaces: d.packageInterfaces ?? {},
|
|
1323
|
+
subPackages: d.subPackages ?? {}
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Get a package by name
|
|
1328
|
+
*
|
|
1329
|
+
* @param name - Package name (e.g., '$TMP', 'ZPACKAGE')
|
|
1330
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
1331
|
+
*/
|
|
1332
|
+
static async get(name, ctx) {
|
|
1333
|
+
const pkg = new AdkPackage(ctx ?? getGlobalContext(), name);
|
|
1334
|
+
await pkg.load();
|
|
1335
|
+
return pkg;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Check if a package exists on SAP
|
|
1339
|
+
*
|
|
1340
|
+
* @param name - Package name
|
|
1341
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
1342
|
+
* @returns true if package exists, false otherwise
|
|
1343
|
+
*/
|
|
1344
|
+
static async exists(name, ctx) {
|
|
1345
|
+
try {
|
|
1346
|
+
await new AdkPackage(ctx ?? getGlobalContext(), name).load();
|
|
1347
|
+
return true;
|
|
1348
|
+
} catch {
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Create a new package on SAP
|
|
1354
|
+
*
|
|
1355
|
+
* Builds an AdkPackage with the given data and saves it in 'create' mode.
|
|
1356
|
+
*
|
|
1357
|
+
* @param name - Package name (e.g., 'ZABAPGIT_EXAMPLES_CLAS')
|
|
1358
|
+
* @param data - Package data (superPackage, attributes, transport, etc.)
|
|
1359
|
+
* @param options - Save options (transport request)
|
|
1360
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
1361
|
+
*/
|
|
1362
|
+
static async create(name, data, options, ctx) {
|
|
1363
|
+
const pkg = new AdkPackage(ctx ?? getGlobalContext(), name);
|
|
1364
|
+
pkg.setData({
|
|
1365
|
+
name,
|
|
1366
|
+
type: "DEVC/K",
|
|
1367
|
+
description: data.description ?? name,
|
|
1368
|
+
responsible: data.responsible ?? "",
|
|
1369
|
+
attributes: {
|
|
1370
|
+
packageType: "development",
|
|
1371
|
+
...data.attributes
|
|
1372
|
+
},
|
|
1373
|
+
superPackage: data.superPackage ?? {},
|
|
1374
|
+
extensionAlias: data.extensionAlias ?? {},
|
|
1375
|
+
switch: data.switch ?? {},
|
|
1376
|
+
applicationComponent: data.applicationComponent ?? {},
|
|
1377
|
+
transport: data.transport ?? {},
|
|
1378
|
+
translation: data.translation ?? {},
|
|
1379
|
+
useAccesses: data.useAccesses ?? {},
|
|
1380
|
+
packageInterfaces: data.packageInterfaces ?? {},
|
|
1381
|
+
subPackages: data.subPackages ?? {},
|
|
1382
|
+
...data
|
|
1383
|
+
});
|
|
1384
|
+
await pkg.save({
|
|
1385
|
+
transport: options?.transport,
|
|
1386
|
+
mode: "create"
|
|
1387
|
+
});
|
|
1388
|
+
return pkg;
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Delete an ABAP package from SAP
|
|
1392
|
+
*
|
|
1393
|
+
* @param name - Package name
|
|
1394
|
+
* @param options - Delete options (transport, lockHandle)
|
|
1395
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
1396
|
+
*/
|
|
1397
|
+
static async delete(name, options, ctx) {
|
|
1398
|
+
await new AdkPackage(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
1399
|
+
...options?.transport && { corrNr: options.transport },
|
|
1400
|
+
...options?.lockHandle && { lockHandle: options.lockHandle }
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
registerObjectType("DEVC", Package, AdkPackage, {
|
|
1405
|
+
endpoint: "packages",
|
|
1406
|
+
nameTransform: "preserve"
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
//#endregion
|
|
1410
|
+
//#region src/decorators/lockable.ts
|
|
1411
|
+
/**
|
|
1412
|
+
* @requiresLock - Method decorator
|
|
1413
|
+
*
|
|
1414
|
+
* Automatically acquires lock before method execution
|
|
1415
|
+
* and releases it after (unless keep: true).
|
|
1416
|
+
*
|
|
1417
|
+
* @param options - Lock options
|
|
1418
|
+
* @param options.keep - If true, keep lock after method completes
|
|
1419
|
+
*
|
|
1420
|
+
* @example
|
|
1421
|
+
* ```typescript
|
|
1422
|
+
* @requiresLock()
|
|
1423
|
+
* async update(data: UpdateData): Promise<void> {
|
|
1424
|
+
* // Lock is acquired before this runs
|
|
1425
|
+
* // Lock is released after this completes
|
|
1426
|
+
* }
|
|
1427
|
+
*
|
|
1428
|
+
* @requiresLock({ keep: true })
|
|
1429
|
+
* async startTransaction(): Promise<void> {
|
|
1430
|
+
* // Lock is acquired and KEPT for subsequent operations
|
|
1431
|
+
* }
|
|
1432
|
+
* ```
|
|
1433
|
+
*/
|
|
1434
|
+
function requiresLock(options) {
|
|
1435
|
+
return function(_target, _propertyKey, descriptor) {
|
|
1436
|
+
const originalMethod = descriptor.value;
|
|
1437
|
+
descriptor.value = async function(...args) {
|
|
1438
|
+
const wasLocked = this.isLocked;
|
|
1439
|
+
try {
|
|
1440
|
+
if (!wasLocked) await this.lock();
|
|
1441
|
+
return await originalMethod.apply(this, args);
|
|
1442
|
+
} finally {
|
|
1443
|
+
if (!wasLocked && this.isLocked && !options?.keep) await this.unlock();
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
return descriptor;
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
//#endregion
|
|
1451
|
+
//#region src/decorators/datetime.ts
|
|
1452
|
+
/**
|
|
1453
|
+
* @datetime - SAP Timestamp Property Decorator
|
|
1454
|
+
*
|
|
1455
|
+
* Parses SAP timestamp formats into JavaScript Date objects.
|
|
1456
|
+
*
|
|
1457
|
+
* SAP Timestamp Formats:
|
|
1458
|
+
* - YYYYMMDDHHMMSS (14 chars) - e.g., "20231215143052"
|
|
1459
|
+
* - YYYYMMDD (8 chars) - date only
|
|
1460
|
+
*
|
|
1461
|
+
* Usage:
|
|
1462
|
+
* class MyObject {
|
|
1463
|
+
* @datetime() lastChangedAt?: Date;
|
|
1464
|
+
* @datetime('lastchanged_timestamp') modifiedAt?: Date;
|
|
1465
|
+
* }
|
|
1466
|
+
*/
|
|
1467
|
+
/**
|
|
1468
|
+
* Parse SAP timestamp string to Date
|
|
1469
|
+
* Supports: YYYYMMDDHHMMSS (14 chars) or YYYYMMDD (8 chars)
|
|
1470
|
+
*/
|
|
1471
|
+
function parseSapTimestamp(ts) {
|
|
1472
|
+
if (!ts || typeof ts !== "string") return void 0;
|
|
1473
|
+
const s = ts.trim();
|
|
1474
|
+
if (s.length < 8) return void 0;
|
|
1475
|
+
const year = s.slice(0, 4);
|
|
1476
|
+
const month = s.slice(4, 6);
|
|
1477
|
+
const day = s.slice(6, 8);
|
|
1478
|
+
const hour = s.length >= 10 ? s.slice(8, 10) : "00";
|
|
1479
|
+
const min = s.length >= 12 ? s.slice(10, 12) : "00";
|
|
1480
|
+
const sec = s.length >= 14 ? s.slice(12, 14) : "00";
|
|
1481
|
+
return /* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${min}:${sec}Z`);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
//#endregion
|
|
1485
|
+
//#region src/decorators/lazy.ts
|
|
1486
|
+
/**
|
|
1487
|
+
* @lazy - Lazy Initialization Decorator
|
|
1488
|
+
*
|
|
1489
|
+
* Computes a value once on first access, then caches it.
|
|
1490
|
+
*
|
|
1491
|
+
* Usage:
|
|
1492
|
+
* class MyObject {
|
|
1493
|
+
* @lazy(self => `${self.a}/${self.b}`)
|
|
1494
|
+
* key!: string;
|
|
1495
|
+
* }
|
|
1496
|
+
*
|
|
1497
|
+
* Note: Due to class field shadowing, use `declare` keyword:
|
|
1498
|
+
* @lazy(self => compute())
|
|
1499
|
+
* declare myProp: Type;
|
|
1500
|
+
*/
|
|
1501
|
+
const LAZY_CACHE = Symbol("lazy:cache");
|
|
1502
|
+
/**
|
|
1503
|
+
* Invalidate a specific lazy property (force recomputation on next access)
|
|
1504
|
+
*/
|
|
1505
|
+
function invalidateLazy(obj, propertyKey) {
|
|
1506
|
+
obj[LAZY_CACHE]?.delete(propertyKey);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
//#endregion
|
|
1510
|
+
//#region src/objects/repository/clas/clas.model.ts
|
|
1511
|
+
/**
|
|
1512
|
+
* CLAS - ABAP Class
|
|
1513
|
+
*
|
|
1514
|
+
* ADK object for ABAP classes (CLAS).
|
|
1515
|
+
*/
|
|
1516
|
+
/**
|
|
1517
|
+
* ADK Class object
|
|
1518
|
+
*
|
|
1519
|
+
* Inherits from AdkMainObject which provides:
|
|
1520
|
+
* - AdkObject: name, type, description, version, language, changedBy/At, createdBy/At, links
|
|
1521
|
+
* - AdkMainObject: package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
|
|
1522
|
+
*
|
|
1523
|
+
* Access class-specific properties via `data`:
|
|
1524
|
+
* - data.category, data.final, data.abstract, data.visibility
|
|
1525
|
+
* - data.sharedMemoryEnabled, data.modeled, data.fixPointArithmetic
|
|
1526
|
+
* - data.superClassRef, data.messageClassRef, data.include
|
|
1527
|
+
*/
|
|
1528
|
+
var AdkClass = class AdkClass extends AdkMainObject {
|
|
1529
|
+
static kind = Class;
|
|
1530
|
+
kind = AdkClass.kind;
|
|
1531
|
+
get objectUri() {
|
|
1532
|
+
return `/sap/bc/adt/oo/classes/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
1533
|
+
}
|
|
1534
|
+
async getMainSource() {
|
|
1535
|
+
return this.lazy("source:main", async () => {
|
|
1536
|
+
return this.crudContract.source.main.get(this.name);
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
async getIncludeSource(includeType) {
|
|
1540
|
+
return this.lazy(`source:${includeType}`, async () => {
|
|
1541
|
+
if (includeType === "main") return this.crudContract.source.main.get(this.name);
|
|
1542
|
+
return this.crudContract.includes.get(this.name, includeType);
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
async getDefinitions() {
|
|
1546
|
+
return this.getIncludeSource("definitions");
|
|
1547
|
+
}
|
|
1548
|
+
async getImplementations() {
|
|
1549
|
+
return this.getIncludeSource("implementations");
|
|
1550
|
+
}
|
|
1551
|
+
async getTestClasses() {
|
|
1552
|
+
return this.getIncludeSource("testclasses");
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Save main source code
|
|
1556
|
+
* Requires object to be locked first
|
|
1557
|
+
*/
|
|
1558
|
+
async saveMainSource(source, options) {
|
|
1559
|
+
await this.crudContract.source.main.put(this.name, {
|
|
1560
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {},
|
|
1561
|
+
...options?.transport ? { corrNr: options.transport } : {}
|
|
1562
|
+
}, source);
|
|
1563
|
+
invalidateLazy(this, "source:main");
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Save include source code
|
|
1567
|
+
* Requires object to be locked first
|
|
1568
|
+
*/
|
|
1569
|
+
async saveIncludeSource(includeType, source, options) {
|
|
1570
|
+
const putOptions = {
|
|
1571
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {},
|
|
1572
|
+
...options?.transport ? { corrNr: options.transport } : {}
|
|
1573
|
+
};
|
|
1574
|
+
if (includeType === "main") await this.crudContract.source.main.put(this.name, putOptions, source);
|
|
1575
|
+
else await this.crudContract.includes.put(this.name, includeType, putOptions, source);
|
|
1576
|
+
invalidateLazy(this, `source:${includeType}`);
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Save all pending sources (set via _pendingSources)
|
|
1580
|
+
* Used by export workflow after deserialization from abapGit
|
|
1581
|
+
* Overrides base class method
|
|
1582
|
+
*
|
|
1583
|
+
* Note: unchanged detection is handled by checkPendingSourcesUnchanged()
|
|
1584
|
+
* which runs BEFORE lock acquisition. By the time we reach here,
|
|
1585
|
+
* at least one source differs — save them all.
|
|
1586
|
+
*/
|
|
1587
|
+
async savePendingSources(options) {
|
|
1588
|
+
const pendingSources = this._pendingSources;
|
|
1589
|
+
if (!pendingSources) return;
|
|
1590
|
+
const errors = [];
|
|
1591
|
+
const deferred = [];
|
|
1592
|
+
for (const [key, source] of Object.entries(pendingSources)) try {
|
|
1593
|
+
if (key === "main") await this.saveMainSource(source, options);
|
|
1594
|
+
else await this.saveIncludeSource(key, source, options);
|
|
1595
|
+
} catch (e) {
|
|
1596
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
1597
|
+
if (errorMsg.includes("409") || errorMsg.includes("Conflict")) continue;
|
|
1598
|
+
if (errorMsg.includes("does not have any inactive version") || errorMsg.includes("ResourceNotFound")) {
|
|
1599
|
+
deferred.push([key, source]);
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
errors.push(e instanceof Error ? e : new Error(String(e)));
|
|
1603
|
+
}
|
|
1604
|
+
if (deferred.length > 0) this._deferredSources = deferred;
|
|
1605
|
+
delete this._pendingSources;
|
|
1606
|
+
delete this._pendingSource;
|
|
1607
|
+
if (errors.length > 0) throw errors[0];
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Pre-lock comparison: check if all pending sources match SAP
|
|
1611
|
+
* If identical, sets _unchanged = true so save() skips locking and PUT
|
|
1612
|
+
*/
|
|
1613
|
+
async checkPendingSourcesUnchanged() {
|
|
1614
|
+
const pendingSources = this._pendingSources;
|
|
1615
|
+
if (!pendingSources) return;
|
|
1616
|
+
for (const [key, pendingSource] of Object.entries(pendingSources)) try {
|
|
1617
|
+
const currentSource = await this.getIncludeSource(key);
|
|
1618
|
+
if (this.normalizeSource(currentSource) !== this.normalizeSource(pendingSource)) return;
|
|
1619
|
+
} catch {
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
this._unchanged = true;
|
|
1623
|
+
delete this._pendingSources;
|
|
1624
|
+
delete this._pendingSource;
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Check if object has pending sources to save
|
|
1628
|
+
* Overrides base class method
|
|
1629
|
+
*/
|
|
1630
|
+
hasPendingSources() {
|
|
1631
|
+
return !!this._pendingSources;
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Refresh cached ETags for all pending source endpoints after locking.
|
|
1635
|
+
* Classes have multiple source URLs (main + includes) that all need fresh ETags.
|
|
1636
|
+
*/
|
|
1637
|
+
async refreshETagsAfterLock() {
|
|
1638
|
+
const pendingSources = this._pendingSources;
|
|
1639
|
+
if (!pendingSources) return super.refreshETagsAfterLock();
|
|
1640
|
+
const basePath = `/sap/bc/adt/oo/classes/${this.name.toLowerCase()}`;
|
|
1641
|
+
for (const key of Object.keys(pendingSources)) {
|
|
1642
|
+
const endpoint = key === "main" ? `${basePath}/source/main` : `${basePath}/includes/${key}`;
|
|
1643
|
+
try {
|
|
1644
|
+
if (key === "main") await this.crudContract.source.main.get(this.name);
|
|
1645
|
+
else await this.crudContract.includes.get(this.name, key);
|
|
1646
|
+
} catch {
|
|
1647
|
+
this.ctx.client.clearETag(endpoint);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
get wrapperKey() {
|
|
1652
|
+
return "abapClass";
|
|
1653
|
+
}
|
|
1654
|
+
get crudContract() {
|
|
1655
|
+
return this.ctx.client.adt.oo.classes;
|
|
1656
|
+
}
|
|
1657
|
+
static async get(name, ctx) {
|
|
1658
|
+
return new AdkClass(ctx ?? getGlobalContext(), name).load();
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Check if a class exists on SAP
|
|
1662
|
+
*/
|
|
1663
|
+
static async exists(name, ctx) {
|
|
1664
|
+
try {
|
|
1665
|
+
await AdkClass.get(name, ctx);
|
|
1666
|
+
return true;
|
|
1667
|
+
} catch {
|
|
1668
|
+
return false;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Create a new ABAP class on SAP
|
|
1673
|
+
*
|
|
1674
|
+
* @param name - Class name (e.g., 'ZCL_MY_CLASS')
|
|
1675
|
+
* @param description - Short description
|
|
1676
|
+
* @param packageName - Package to assign the class to
|
|
1677
|
+
* @param options - Save options (transport)
|
|
1678
|
+
* @param ctx - Optional ADK context
|
|
1679
|
+
*/
|
|
1680
|
+
static async create(name, description, packageName, options, ctx) {
|
|
1681
|
+
const cls = new AdkClass(ctx ?? getGlobalContext(), name.toUpperCase());
|
|
1682
|
+
cls.setData({
|
|
1683
|
+
name: name.toUpperCase(),
|
|
1684
|
+
type: "CLAS/OC",
|
|
1685
|
+
description,
|
|
1686
|
+
language: "EN",
|
|
1687
|
+
masterLanguage: "EN",
|
|
1688
|
+
packageRef: {
|
|
1689
|
+
name: packageName.toUpperCase(),
|
|
1690
|
+
uri: `/sap/bc/adt/packages/${encodeURIComponent(packageName.toUpperCase())}`,
|
|
1691
|
+
type: "DEVC/K"
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
await cls.save({
|
|
1695
|
+
transport: options?.transport,
|
|
1696
|
+
mode: "create"
|
|
1697
|
+
});
|
|
1698
|
+
return cls;
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Delete an ABAP class from SAP
|
|
1702
|
+
*
|
|
1703
|
+
* @param name - Class name
|
|
1704
|
+
* @param options - Delete options (transport)
|
|
1705
|
+
* @param ctx - Optional ADK context
|
|
1706
|
+
*/
|
|
1707
|
+
static async delete(name, options, ctx) {
|
|
1708
|
+
await new AdkClass(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
1709
|
+
corrNr: options?.transport,
|
|
1710
|
+
lockHandle: options?.lockHandle
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
registerObjectType("CLAS", Class, AdkClass, { endpoint: "oo/classes" });
|
|
1715
|
+
|
|
1716
|
+
//#endregion
|
|
1717
|
+
//#region src/objects/repository/intf/intf.model.ts
|
|
1718
|
+
/**
|
|
1719
|
+
* INTF - ABAP Interface
|
|
1720
|
+
*
|
|
1721
|
+
* ADK object for ABAP interfaces (INTF).
|
|
1722
|
+
*/
|
|
1723
|
+
/**
|
|
1724
|
+
* ADK Interface object
|
|
1725
|
+
*
|
|
1726
|
+
* Inherits from AdkMainObject which provides:
|
|
1727
|
+
* - AdkObject: name, type, description, version, language, changedBy/At, createdBy/At, links
|
|
1728
|
+
* - AdkMainObject: package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
|
|
1729
|
+
*
|
|
1730
|
+
* Access interface-specific properties via `data`:
|
|
1731
|
+
* - data.modeled, data.sourceUri, data.fixPointArithmetic, data.activeUnicodeCheck
|
|
1732
|
+
*/
|
|
1733
|
+
var AdkInterface = class AdkInterface extends AdkMainObject {
|
|
1734
|
+
static kind = Interface;
|
|
1735
|
+
kind = AdkInterface.kind;
|
|
1736
|
+
get objectUri() {
|
|
1737
|
+
return `/sap/bc/adt/oo/interfaces/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
1738
|
+
}
|
|
1739
|
+
async getSource() {
|
|
1740
|
+
return this.lazy("source", async () => {
|
|
1741
|
+
return this.ctx.client.adt.oo.interfaces.source.main.get(this.name);
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
get wrapperKey() {
|
|
1745
|
+
return "abapInterface";
|
|
1746
|
+
}
|
|
1747
|
+
get crudContract() {
|
|
1748
|
+
return this.ctx.client.adt.oo.interfaces;
|
|
1749
|
+
}
|
|
1750
|
+
static async get(name, ctx) {
|
|
1751
|
+
return new AdkInterface(ctx ?? getGlobalContext(), name).load();
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Check if an interface exists on SAP
|
|
1755
|
+
*/
|
|
1756
|
+
static async exists(name, ctx) {
|
|
1757
|
+
try {
|
|
1758
|
+
await AdkInterface.get(name, ctx);
|
|
1759
|
+
return true;
|
|
1760
|
+
} catch {
|
|
1761
|
+
return false;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Create a new ABAP interface on SAP
|
|
1766
|
+
*
|
|
1767
|
+
* @param name - Interface name (e.g., 'ZIF_MY_INTERFACE')
|
|
1768
|
+
* @param description - Short description
|
|
1769
|
+
* @param packageName - Package to assign the interface to
|
|
1770
|
+
* @param options - Save options (transport)
|
|
1771
|
+
* @param ctx - Optional ADK context
|
|
1772
|
+
*/
|
|
1773
|
+
static async create(name, description, packageName, options, ctx) {
|
|
1774
|
+
const intf = new AdkInterface(ctx ?? getGlobalContext(), name.toUpperCase());
|
|
1775
|
+
intf.setData({
|
|
1776
|
+
name: name.toUpperCase(),
|
|
1777
|
+
type: "INTF/OI",
|
|
1778
|
+
description,
|
|
1779
|
+
language: "EN",
|
|
1780
|
+
masterLanguage: "EN",
|
|
1781
|
+
packageRef: {
|
|
1782
|
+
name: packageName.toUpperCase(),
|
|
1783
|
+
uri: `/sap/bc/adt/packages/${encodeURIComponent(packageName.toUpperCase())}`,
|
|
1784
|
+
type: "DEVC/K"
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1787
|
+
await intf.save({
|
|
1788
|
+
transport: options?.transport,
|
|
1789
|
+
mode: "create"
|
|
1790
|
+
});
|
|
1791
|
+
return intf;
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Delete an ABAP interface from SAP
|
|
1795
|
+
*
|
|
1796
|
+
* @param name - Interface name
|
|
1797
|
+
* @param options - Delete options (transport)
|
|
1798
|
+
* @param ctx - Optional ADK context
|
|
1799
|
+
*/
|
|
1800
|
+
static async delete(name, options, ctx) {
|
|
1801
|
+
await new AdkInterface(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
1802
|
+
corrNr: options?.transport,
|
|
1803
|
+
lockHandle: options?.lockHandle
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
};
|
|
1807
|
+
registerObjectType("INTF", Interface, AdkInterface, { endpoint: "oo/interfaces" });
|
|
1808
|
+
|
|
1809
|
+
//#endregion
|
|
1810
|
+
//#region src/objects/repository/prog/prog.model.ts
|
|
1811
|
+
/**
|
|
1812
|
+
* PROG - ABAP Program
|
|
1813
|
+
*
|
|
1814
|
+
* ADK object for ABAP programs (PROG).
|
|
1815
|
+
*/
|
|
1816
|
+
/**
|
|
1817
|
+
* ADK Program object
|
|
1818
|
+
*
|
|
1819
|
+
* Inherits from AdkMainObject which provides:
|
|
1820
|
+
* - AdkObject: name, type, description, version, language, changedBy/At, createdBy/At, links
|
|
1821
|
+
* - AdkMainObject: package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
|
|
1822
|
+
*
|
|
1823
|
+
* Access program-specific properties via `data`:
|
|
1824
|
+
* - data.programType, data.sourceUri, data.fixPointArithmetic, data.activeUnicodeCheck
|
|
1825
|
+
*/
|
|
1826
|
+
var AdkProgram = class AdkProgram extends AdkMainObject {
|
|
1827
|
+
static kind = Program;
|
|
1828
|
+
kind = AdkProgram.kind;
|
|
1829
|
+
get objectUri() {
|
|
1830
|
+
return `/sap/bc/adt/programs/programs/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
1831
|
+
}
|
|
1832
|
+
async getSource() {
|
|
1833
|
+
return this.lazy("source", async () => {
|
|
1834
|
+
return this.ctx.client.adt.programs.programs.source.main.get(this.name);
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
get wrapperKey() {
|
|
1838
|
+
return "abapProgram";
|
|
1839
|
+
}
|
|
1840
|
+
get crudContract() {
|
|
1841
|
+
return this.ctx.client.adt.programs.programs;
|
|
1842
|
+
}
|
|
1843
|
+
static async get(name, ctx) {
|
|
1844
|
+
return new AdkProgram(ctx ?? getGlobalContext(), name).load();
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Check if a program exists on SAP
|
|
1848
|
+
*/
|
|
1849
|
+
static async exists(name, ctx) {
|
|
1850
|
+
try {
|
|
1851
|
+
await AdkProgram.get(name, ctx);
|
|
1852
|
+
return true;
|
|
1853
|
+
} catch {
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Create a new ABAP program on SAP
|
|
1859
|
+
*
|
|
1860
|
+
* @param name - Program name (e.g., 'ZMYPROGRAM')
|
|
1861
|
+
* @param description - Short description
|
|
1862
|
+
* @param packageName - Package to assign the program to
|
|
1863
|
+
* @param options - Save options (transport)
|
|
1864
|
+
* @param ctx - Optional ADK context
|
|
1865
|
+
*/
|
|
1866
|
+
static async create(name, description, packageName, options, ctx) {
|
|
1867
|
+
const prog = new AdkProgram(ctx ?? getGlobalContext(), name.toUpperCase());
|
|
1868
|
+
prog.setData({
|
|
1869
|
+
name: name.toUpperCase(),
|
|
1870
|
+
type: "PROG/P",
|
|
1871
|
+
description,
|
|
1872
|
+
language: "EN",
|
|
1873
|
+
masterLanguage: "EN",
|
|
1874
|
+
packageRef: {
|
|
1875
|
+
name: packageName.toUpperCase(),
|
|
1876
|
+
uri: `/sap/bc/adt/packages/${encodeURIComponent(packageName.toUpperCase())}`,
|
|
1877
|
+
type: "DEVC/K"
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
await prog.save({
|
|
1881
|
+
transport: options?.transport,
|
|
1882
|
+
mode: "create"
|
|
1883
|
+
});
|
|
1884
|
+
return prog;
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Delete an ABAP program from SAP
|
|
1888
|
+
*
|
|
1889
|
+
* @param name - Program name
|
|
1890
|
+
* @param options - Delete options (transport)
|
|
1891
|
+
* @param ctx - Optional ADK context
|
|
1892
|
+
*/
|
|
1893
|
+
static async delete(name, options, ctx) {
|
|
1894
|
+
await new AdkProgram(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
1895
|
+
corrNr: options?.transport,
|
|
1896
|
+
lockHandle: options?.lockHandle
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
registerObjectType("PROG", Program, AdkProgram, { endpoint: "programs/programs" });
|
|
1901
|
+
|
|
1902
|
+
//#endregion
|
|
1903
|
+
//#region src/objects/repository/incl/incl.model.ts
|
|
1904
|
+
/**
|
|
1905
|
+
* INCL - ABAP Program Include
|
|
1906
|
+
*
|
|
1907
|
+
* ADK object for ABAP program includes (PROG/I). Mirrors the PROG model
|
|
1908
|
+
* but targets the `/sap/bc/adt/programs/includes` endpoint.
|
|
1909
|
+
*/
|
|
1910
|
+
/**
|
|
1911
|
+
* ADK Include object
|
|
1912
|
+
*
|
|
1913
|
+
* Inherits from AdkMainObject which provides:
|
|
1914
|
+
* - AdkObject: name, type, description, version, language, changedBy/At,
|
|
1915
|
+
* createdBy/At, links
|
|
1916
|
+
* - AdkMainObject: package, packageRef, responsible, masterLanguage,
|
|
1917
|
+
* masterSystem, abapLanguageVersion
|
|
1918
|
+
*
|
|
1919
|
+
* Access include-specific properties via `data`:
|
|
1920
|
+
* - data.sourceUri, data.fixPointArithmetic, data.activeUnicodeCheck,
|
|
1921
|
+
* data.contextRef (pointer to the main program)
|
|
1922
|
+
*/
|
|
1923
|
+
var AdkInclude = class AdkInclude extends AdkMainObject {
|
|
1924
|
+
static kind = Include;
|
|
1925
|
+
kind = AdkInclude.kind;
|
|
1926
|
+
get objectUri() {
|
|
1927
|
+
return `/sap/bc/adt/programs/includes/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
1928
|
+
}
|
|
1929
|
+
async getSource() {
|
|
1930
|
+
return this.lazy("source", async () => {
|
|
1931
|
+
return this.ctx.client.adt.programs.includes.source.main.get(this.name);
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
get wrapperKey() {
|
|
1935
|
+
return "abapInclude";
|
|
1936
|
+
}
|
|
1937
|
+
get crudContract() {
|
|
1938
|
+
return this.ctx.client.adt.programs.includes;
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Skeleton POST data for includes.
|
|
1942
|
+
*
|
|
1943
|
+
* Extends the base skeleton (name/type/description/packageRef) with
|
|
1944
|
+
* `contextRef`, which sapcli's `sap.adt.Include(..., master=...)` serializes
|
|
1945
|
+
* at creation time. Without this the include would be created detached
|
|
1946
|
+
* from its main program.
|
|
1947
|
+
*/
|
|
1948
|
+
async getSkeletonData() {
|
|
1949
|
+
const base = await super.getSkeletonData();
|
|
1950
|
+
const d = await this.data();
|
|
1951
|
+
if (d.contextRef) base.contextRef = d.contextRef;
|
|
1952
|
+
return base;
|
|
1953
|
+
}
|
|
1954
|
+
static async get(name, ctx) {
|
|
1955
|
+
return new AdkInclude(ctx ?? getGlobalContext(), name).load();
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Check if an include exists on SAP
|
|
1959
|
+
*/
|
|
1960
|
+
static async exists(name, ctx) {
|
|
1961
|
+
try {
|
|
1962
|
+
await AdkInclude.get(name, ctx);
|
|
1963
|
+
return true;
|
|
1964
|
+
} catch {
|
|
1965
|
+
return false;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Create a new ABAP include on SAP
|
|
1970
|
+
*
|
|
1971
|
+
* @param name - Include name (e.g., 'ZMY_INCLUDE')
|
|
1972
|
+
* @param description - Short description
|
|
1973
|
+
* @param packageName - Package to assign the include to
|
|
1974
|
+
* @param options - Save options (transport, master program)
|
|
1975
|
+
* @param ctx - Optional ADK context
|
|
1976
|
+
*/
|
|
1977
|
+
static async create(name, description, packageName, options, ctx) {
|
|
1978
|
+
const incl = new AdkInclude(ctx ?? getGlobalContext(), name.toUpperCase());
|
|
1979
|
+
const data = {
|
|
1980
|
+
name: name.toUpperCase(),
|
|
1981
|
+
type: "PROG/I",
|
|
1982
|
+
description,
|
|
1983
|
+
language: "EN",
|
|
1984
|
+
masterLanguage: "EN",
|
|
1985
|
+
packageRef: {
|
|
1986
|
+
name: packageName.toUpperCase(),
|
|
1987
|
+
uri: `/sap/bc/adt/packages/${encodeURIComponent(packageName.toUpperCase())}`,
|
|
1988
|
+
type: "DEVC/K"
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
if (options?.master) {
|
|
1992
|
+
const master = options.master.toUpperCase();
|
|
1993
|
+
data.contextRef = {
|
|
1994
|
+
name: master,
|
|
1995
|
+
uri: `/sap/bc/adt/programs/programs/${encodeURIComponent(master.toLowerCase())}`,
|
|
1996
|
+
type: "PROG/P"
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
incl.setData(data);
|
|
2000
|
+
await incl.save({
|
|
2001
|
+
transport: options?.transport,
|
|
2002
|
+
mode: "create"
|
|
2003
|
+
});
|
|
2004
|
+
return incl;
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Delete an ABAP include from SAP
|
|
2008
|
+
*
|
|
2009
|
+
* @param name - Include name
|
|
2010
|
+
* @param options - Delete options (transport, lockHandle)
|
|
2011
|
+
* @param ctx - Optional ADK context
|
|
2012
|
+
*/
|
|
2013
|
+
static async delete(name, options, ctx) {
|
|
2014
|
+
await new AdkInclude(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
2015
|
+
corrNr: options?.transport,
|
|
2016
|
+
lockHandle: options?.lockHandle
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
registerObjectType("INCL", Include, AdkInclude, { endpoint: "programs/includes" });
|
|
2021
|
+
|
|
2022
|
+
//#endregion
|
|
2023
|
+
//#region src/objects/repository/fugr/fugr.model.ts
|
|
2024
|
+
/**
|
|
2025
|
+
* FUGR - ABAP Function Group
|
|
2026
|
+
*
|
|
2027
|
+
* ADK object for ABAP function groups (FUGR).
|
|
2028
|
+
*/
|
|
2029
|
+
/**
|
|
2030
|
+
* ADK Function Group object
|
|
2031
|
+
*
|
|
2032
|
+
* Inherits from AdkMainObject which provides:
|
|
2033
|
+
* - AdkObject: name, type, description, version, language, changedBy/At, createdBy/At, links
|
|
2034
|
+
* - AdkMainObject: package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
|
|
2035
|
+
*
|
|
2036
|
+
* Access function group-specific properties via `data`:
|
|
2037
|
+
* - data.sourceUri, data.fixPointArithmetic, data.activeUnicodeCheck
|
|
2038
|
+
*/
|
|
2039
|
+
var AdkFunctionGroup = class AdkFunctionGroup extends AdkMainObject {
|
|
2040
|
+
static kind = FunctionGroup;
|
|
2041
|
+
kind = AdkFunctionGroup.kind;
|
|
2042
|
+
get objectUri() {
|
|
2043
|
+
return `/sap/bc/adt/functions/groups/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
2044
|
+
}
|
|
2045
|
+
async getSource() {
|
|
2046
|
+
return this.lazy("source", async () => {
|
|
2047
|
+
return this.ctx.client.adt.functions.groups.source.main.get(this.name);
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
get wrapperKey() {
|
|
2051
|
+
return "abapFunctionGroup";
|
|
2052
|
+
}
|
|
2053
|
+
get crudContract() {
|
|
2054
|
+
return this.ctx.client.adt.functions.groups;
|
|
2055
|
+
}
|
|
2056
|
+
static async get(name, ctx) {
|
|
2057
|
+
return new AdkFunctionGroup(ctx ?? getGlobalContext(), name).load();
|
|
2058
|
+
}
|
|
2059
|
+
/**
|
|
2060
|
+
* Check if a function group exists on SAP
|
|
2061
|
+
*/
|
|
2062
|
+
static async exists(name, ctx) {
|
|
2063
|
+
try {
|
|
2064
|
+
await AdkFunctionGroup.get(name, ctx);
|
|
2065
|
+
return true;
|
|
2066
|
+
} catch {
|
|
2067
|
+
return false;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Create a new ABAP function group on SAP
|
|
2072
|
+
*/
|
|
2073
|
+
static async create(name, description, packageName, options, ctx) {
|
|
2074
|
+
const fugr = new AdkFunctionGroup(ctx ?? getGlobalContext(), name.toUpperCase());
|
|
2075
|
+
fugr.setData({
|
|
2076
|
+
name: name.toUpperCase(),
|
|
2077
|
+
type: "FUGR/F",
|
|
2078
|
+
description,
|
|
2079
|
+
language: "EN",
|
|
2080
|
+
masterLanguage: "EN",
|
|
2081
|
+
packageRef: {
|
|
2082
|
+
name: packageName.toUpperCase(),
|
|
2083
|
+
uri: `/sap/bc/adt/packages/${encodeURIComponent(packageName.toUpperCase())}`,
|
|
2084
|
+
type: "DEVC/K"
|
|
2085
|
+
}
|
|
2086
|
+
});
|
|
2087
|
+
await fugr.save({
|
|
2088
|
+
transport: options?.transport,
|
|
2089
|
+
mode: "create"
|
|
2090
|
+
});
|
|
2091
|
+
return fugr;
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Delete an ABAP function group from SAP
|
|
2095
|
+
*/
|
|
2096
|
+
static async delete(name, options, ctx) {
|
|
2097
|
+
await new AdkFunctionGroup(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
2098
|
+
corrNr: options?.transport,
|
|
2099
|
+
lockHandle: options?.lockHandle
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
registerObjectType("FUGR", FunctionGroup, AdkFunctionGroup, {
|
|
2104
|
+
endpoint: "functions/groups",
|
|
2105
|
+
normalizeName: (name) => name.toUpperCase().startsWith("SAPL") ? name.slice(4) : name
|
|
2106
|
+
});
|
|
2107
|
+
|
|
2108
|
+
//#endregion
|
|
2109
|
+
//#region src/objects/repository/fugr/func/func.model.ts
|
|
2110
|
+
/**
|
|
2111
|
+
* FUGR/FF - ABAP Function Module
|
|
2112
|
+
*
|
|
2113
|
+
* ADK object for ABAP function modules (FUGR/FF).
|
|
2114
|
+
* Function modules are child objects of function groups - they are accessed
|
|
2115
|
+
* via the parent group's URL path and require the group name for all operations.
|
|
2116
|
+
*/
|
|
2117
|
+
/**
|
|
2118
|
+
* ADK Function Module object
|
|
2119
|
+
*
|
|
2120
|
+
* Function modules are child objects of function groups. Unlike main objects
|
|
2121
|
+
* (classes, interfaces, etc.), they don't have their own package reference.
|
|
2122
|
+
* All API operations require the parent group name in the URL path.
|
|
2123
|
+
*
|
|
2124
|
+
* Key differences from AdkMainObject:
|
|
2125
|
+
* - Requires `groupName` for URI construction and all CRUD operations
|
|
2126
|
+
* - Overrides lock/unlock/load/save to pass groupName to contract calls
|
|
2127
|
+
* - No packageRef (lives within parent function group's package)
|
|
2128
|
+
*/
|
|
2129
|
+
var AdkFunctionModule = class AdkFunctionModule extends AdkObject {
|
|
2130
|
+
static kind = FunctionModule;
|
|
2131
|
+
kind = AdkFunctionModule.kind;
|
|
2132
|
+
/** Parent function group name */
|
|
2133
|
+
groupName;
|
|
2134
|
+
/**
|
|
2135
|
+
* Constructor supports two calling patterns:
|
|
2136
|
+
*
|
|
2137
|
+
* 1. Direct (3-arg): `new AdkFunctionModule(ctx, groupName, nameOrData)`
|
|
2138
|
+
* Used by static factory method and when group name is known upfront.
|
|
2139
|
+
*
|
|
2140
|
+
* 2. Factory (2-arg): `new AdkFunctionModule(ctx, data)`
|
|
2141
|
+
* Used by ADK factory's `getWithData()`. The groupName is extracted from
|
|
2142
|
+
* `data._groupName` or `data.containerRef.name`.
|
|
2143
|
+
*/
|
|
2144
|
+
constructor(ctx, groupNameOrData, dataOrName) {
|
|
2145
|
+
if (dataOrName !== void 0) {
|
|
2146
|
+
super(ctx, dataOrName);
|
|
2147
|
+
this.groupName = groupNameOrData;
|
|
2148
|
+
} else if (typeof groupNameOrData === "string") {
|
|
2149
|
+
super(ctx, groupNameOrData);
|
|
2150
|
+
this.groupName = "";
|
|
2151
|
+
} else {
|
|
2152
|
+
const data = groupNameOrData;
|
|
2153
|
+
super(ctx, groupNameOrData);
|
|
2154
|
+
this.groupName = data._groupName ?? data.containerRef?.name ?? "";
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
get objectUri() {
|
|
2158
|
+
return `/sap/bc/adt/functions/groups/${encodeURIComponent(this.groupName.toLowerCase())}/fmodules/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
2159
|
+
}
|
|
2160
|
+
get collectionUri() {
|
|
2161
|
+
return `/sap/bc/adt/functions/groups/${encodeURIComponent(this.groupName.toLowerCase())}/fmodules`;
|
|
2162
|
+
}
|
|
2163
|
+
get wrapperKey() {
|
|
2164
|
+
return "abapFunctionModule";
|
|
2165
|
+
}
|
|
2166
|
+
get crudContract() {
|
|
2167
|
+
return this.ctx.client.adt.functions.groups.fmodules;
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Load function module data from SAP
|
|
2171
|
+
*
|
|
2172
|
+
* Overrides base to pass groupName as first arg to contract.get()
|
|
2173
|
+
*/
|
|
2174
|
+
async load() {
|
|
2175
|
+
const contract = this.crudContract;
|
|
2176
|
+
const wrapperKey = this.wrapperKey;
|
|
2177
|
+
if (!contract || !wrapperKey) throw new Error(`Load not implemented for ${this.kind}. Override load() or provide crudContract/wrapperKey.`);
|
|
2178
|
+
const response = await contract.get(this.groupName, this.name);
|
|
2179
|
+
if (!response || !(wrapperKey in response)) throw new Error(`${this.kind} '${this.name}' in group '${this.groupName}' not found or returned empty response`);
|
|
2180
|
+
this.setData(response[wrapperKey]);
|
|
2181
|
+
return this;
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Get skeleton data for FM creation (POST).
|
|
2185
|
+
*
|
|
2186
|
+
* FMs only need name, type, description, and processing attributes.
|
|
2187
|
+
* No packageRef — FMs inherit the package from their parent FUGR.
|
|
2188
|
+
*/
|
|
2189
|
+
async getSkeletonData() {
|
|
2190
|
+
const d = await this.data();
|
|
2191
|
+
return {
|
|
2192
|
+
name: d.name,
|
|
2193
|
+
type: d.type,
|
|
2194
|
+
description: d.description ?? "",
|
|
2195
|
+
processingType: d.processingType,
|
|
2196
|
+
basXMLEnabled: d.basXMLEnabled
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
/**
|
|
2200
|
+
* Execute the typed contract call for save
|
|
2201
|
+
*
|
|
2202
|
+
* Overrides base to pass groupName to contract.put()/post()
|
|
2203
|
+
*/
|
|
2204
|
+
async saveViaContract(mode, options) {
|
|
2205
|
+
const wrapperKey = this.wrapperKey;
|
|
2206
|
+
const contract = this.crudContract;
|
|
2207
|
+
if (!wrapperKey || !contract) throw new Error(`Save not supported for ${this.kind}.`);
|
|
2208
|
+
if (mode === "create") {
|
|
2209
|
+
const skeleton = await this.getSkeletonData();
|
|
2210
|
+
const data = { [wrapperKey]: skeleton };
|
|
2211
|
+
await contract.post(this.groupName, { corrNr: options.transport }, data);
|
|
2212
|
+
} else {
|
|
2213
|
+
const { abapLanguageVersion: _, ...rest } = await this.data();
|
|
2214
|
+
const saveData = rest;
|
|
2215
|
+
const data = { [wrapperKey]: saveData };
|
|
2216
|
+
await contract.put(this.groupName, this.name, {
|
|
2217
|
+
corrNr: options.transport,
|
|
2218
|
+
lockHandle: options.lockHandle
|
|
2219
|
+
}, data);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Check if the function module exists on the SAP system.
|
|
2224
|
+
*
|
|
2225
|
+
* Overrides base to pass groupName to contract.get()
|
|
2226
|
+
*/
|
|
2227
|
+
async checkObjectExists() {
|
|
2228
|
+
const contract = this.crudContract;
|
|
2229
|
+
if (!contract?.get) return true;
|
|
2230
|
+
try {
|
|
2231
|
+
await contract.get(this.groupName, this.name);
|
|
2232
|
+
return true;
|
|
2233
|
+
} catch (e) {
|
|
2234
|
+
if (this.shouldFallbackToCreate(e)) return false;
|
|
2235
|
+
throw e;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Refresh cached ETags after acquiring a lock.
|
|
2240
|
+
*
|
|
2241
|
+
* FM's savePendingSources() does a metadata PUT first (to apply processingType),
|
|
2242
|
+
* then a source PUT. Both need fresh ETags after locking.
|
|
2243
|
+
* The contract uses groupName + name (potentially UPPERCASE), so we must
|
|
2244
|
+
* refresh via the contract to match the same cache key.
|
|
2245
|
+
*/
|
|
2246
|
+
async refreshETagsAfterLock() {
|
|
2247
|
+
try {
|
|
2248
|
+
await this.crudContract.get(this.groupName, this.name);
|
|
2249
|
+
} catch {
|
|
2250
|
+
this.ctx.client.clearETag();
|
|
2251
|
+
}
|
|
2252
|
+
try {
|
|
2253
|
+
await this.crudContract.source.main.get(this.groupName, this.name);
|
|
2254
|
+
} catch {
|
|
2255
|
+
this.ctx.client.clearETag(`${this.objectUri}/source/main`);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Strip the parameter comment block from FM source code.
|
|
2260
|
+
*
|
|
2261
|
+
* abapGit serializes FM source with a "Local Interface" comment block
|
|
2262
|
+
* that describes IMPORTING/EXPORTING/CHANGING/TABLES/EXCEPTIONS.
|
|
2263
|
+
* SAP ADT rejects this block when saving source — it manages the
|
|
2264
|
+
* interface metadata separately. Strip it before sending.
|
|
2265
|
+
*
|
|
2266
|
+
* Pattern:
|
|
2267
|
+
* ```
|
|
2268
|
+
* *"----------------------------------------------------------------------
|
|
2269
|
+
* *"*"Local Interface:
|
|
2270
|
+
* *" IMPORTING ...
|
|
2271
|
+
* *"----------------------------------------------------------------------
|
|
2272
|
+
* ```
|
|
2273
|
+
*/
|
|
2274
|
+
stripParameterCommentBlock(source) {
|
|
2275
|
+
const lines = source.split("\n");
|
|
2276
|
+
const result = [];
|
|
2277
|
+
let inBlock = false;
|
|
2278
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2279
|
+
const trimmed = lines[i].trimEnd();
|
|
2280
|
+
if (!inBlock && trimmed.startsWith("*\"") && trimmed.includes("---")) {
|
|
2281
|
+
if (i + 1 < lines.length && lines[i + 1].trimEnd().startsWith("*\"")) {
|
|
2282
|
+
inBlock = true;
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
if (inBlock) {
|
|
2287
|
+
if (trimmed.startsWith("*\"") && trimmed.includes("---")) {
|
|
2288
|
+
inBlock = false;
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2291
|
+
if (trimmed.startsWith("*\"")) continue;
|
|
2292
|
+
inBlock = false;
|
|
2293
|
+
}
|
|
2294
|
+
result.push(lines[i]);
|
|
2295
|
+
}
|
|
2296
|
+
return result.join("\n");
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Save pending source for function module.
|
|
2300
|
+
*
|
|
2301
|
+
* Overrides base to:
|
|
2302
|
+
* 1. PUT metadata first (SAP ignores processingType and other attributes during POST)
|
|
2303
|
+
* 2. Refresh source ETag (metadata PUT changes the object version)
|
|
2304
|
+
* 3. Strip the parameter comment block that abapGit includes
|
|
2305
|
+
* 4. Use the FM-specific source endpoint via groupName
|
|
2306
|
+
*/
|
|
2307
|
+
async savePendingSources(options) {
|
|
2308
|
+
const self = this;
|
|
2309
|
+
if (!self._pendingSource) return;
|
|
2310
|
+
await this.saveViaContract("update", {
|
|
2311
|
+
lockHandle: options?.lockHandle,
|
|
2312
|
+
transport: options?.transport
|
|
2313
|
+
});
|
|
2314
|
+
await this.crudContract.source.main.get(this.groupName, this.name);
|
|
2315
|
+
const cleanSource = this.stripParameterCommentBlock(self._pendingSource);
|
|
2316
|
+
await this.crudContract.source.main.put(this.groupName, this.name, {
|
|
2317
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {},
|
|
2318
|
+
...options?.transport ? { corrNr: options.transport } : {}
|
|
2319
|
+
}, cleanSource);
|
|
2320
|
+
delete self._pendingSource;
|
|
2321
|
+
}
|
|
2322
|
+
/**
|
|
2323
|
+
* Extract the body of a FUNCTION...ENDFUNCTION block.
|
|
2324
|
+
*
|
|
2325
|
+
* SAP reconstructs the FUNCTION header when returning source code:
|
|
2326
|
+
* Local: `FUNCTION zage_fm.` (lowercase, period inline)
|
|
2327
|
+
* SAP: `FUNCTION ZAGE_FM\n " template comment"\n.` (uppercase, period on own line)
|
|
2328
|
+
*
|
|
2329
|
+
* The body between the header and ENDFUNCTION is identical, so we compare
|
|
2330
|
+
* only that part. The header ends at the first period that terminates the
|
|
2331
|
+
* FUNCTION statement (either inline `FUNCTION name.` or on a separate line `.`).
|
|
2332
|
+
*/
|
|
2333
|
+
extractFunctionBody(source) {
|
|
2334
|
+
const lines = source.split("\n");
|
|
2335
|
+
let bodyStart = 0;
|
|
2336
|
+
let bodyEnd = lines.length;
|
|
2337
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2338
|
+
const trimmed = lines[i].trimEnd();
|
|
2339
|
+
if (i === 0 && /^FUNCTION\s+\S+\.\s*$/i.test(trimmed)) {
|
|
2340
|
+
bodyStart = i + 1;
|
|
2341
|
+
break;
|
|
2342
|
+
}
|
|
2343
|
+
if (i > 0 && trimmed === ".") {
|
|
2344
|
+
bodyStart = i + 1;
|
|
2345
|
+
break;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
for (let i = lines.length - 1; i >= bodyStart; i--) if (/^\s*ENDFUNCTION\.\s*$/i.test(lines[i].trimEnd())) {
|
|
2349
|
+
bodyEnd = i;
|
|
2350
|
+
break;
|
|
2351
|
+
}
|
|
2352
|
+
return lines.slice(bodyStart, bodyEnd).join("\n");
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Pre-lock comparison for FM source.
|
|
2356
|
+
*
|
|
2357
|
+
* Overrides base to:
|
|
2358
|
+
* 1. Strip parameter comment block from local source
|
|
2359
|
+
* 2. Extract only the function body (between FUNCTION header and ENDFUNCTION)
|
|
2360
|
+
* from both local and SAP source, because SAP reconstructs the FUNCTION
|
|
2361
|
+
* header with uppercase name and template comments.
|
|
2362
|
+
*/
|
|
2363
|
+
async checkPendingSourcesUnchanged() {
|
|
2364
|
+
const self = this;
|
|
2365
|
+
if (!self._pendingSource) return;
|
|
2366
|
+
try {
|
|
2367
|
+
const currentSource = await this.crudContract.source.main.get(this.groupName, this.name);
|
|
2368
|
+
const cleanPending = this.stripParameterCommentBlock(self._pendingSource);
|
|
2369
|
+
const localBody = this.extractFunctionBody(cleanPending);
|
|
2370
|
+
const sapBody = this.extractFunctionBody(currentSource);
|
|
2371
|
+
if (this.normalizeSource(localBody) === this.normalizeSource(sapBody)) {
|
|
2372
|
+
this._unchanged = true;
|
|
2373
|
+
delete self._pendingSource;
|
|
2374
|
+
}
|
|
2375
|
+
} catch {}
|
|
2376
|
+
}
|
|
2377
|
+
async getSource() {
|
|
2378
|
+
return this.lazy("source", async () => {
|
|
2379
|
+
return this.ctx.client.adt.functions.groups.fmodules.source.main.get(this.groupName, this.name);
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Get a function module by name
|
|
2384
|
+
*
|
|
2385
|
+
* @param groupName - Parent function group name
|
|
2386
|
+
* @param name - Function module name
|
|
2387
|
+
* @param ctx - Optional ADK context (uses global if not provided)
|
|
2388
|
+
*/
|
|
2389
|
+
static async get(groupName, name, ctx) {
|
|
2390
|
+
return new AdkFunctionModule(ctx ?? getGlobalContext(), groupName, name).load();
|
|
2391
|
+
}
|
|
2392
|
+
/**
|
|
2393
|
+
* Check if a function module exists on SAP
|
|
2394
|
+
*/
|
|
2395
|
+
static async exists(groupName, name, ctx) {
|
|
2396
|
+
try {
|
|
2397
|
+
await AdkFunctionModule.get(groupName, name, ctx);
|
|
2398
|
+
return true;
|
|
2399
|
+
} catch {
|
|
2400
|
+
return false;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Create a new ABAP function module on SAP
|
|
2405
|
+
*/
|
|
2406
|
+
static async create(groupName, name, description, options, ctx) {
|
|
2407
|
+
const fm = new AdkFunctionModule(ctx ?? getGlobalContext(), groupName.toUpperCase(), name.toUpperCase());
|
|
2408
|
+
fm.setData({
|
|
2409
|
+
name: name.toUpperCase(),
|
|
2410
|
+
type: "FUGR/FF",
|
|
2411
|
+
description,
|
|
2412
|
+
processingType: options?.processingType ?? "normal"
|
|
2413
|
+
});
|
|
2414
|
+
await fm.save({
|
|
2415
|
+
transport: options?.transport,
|
|
2416
|
+
mode: "create"
|
|
2417
|
+
});
|
|
2418
|
+
return fm;
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Delete a function module from SAP
|
|
2422
|
+
*/
|
|
2423
|
+
static async delete(groupName, name, options, ctx) {
|
|
2424
|
+
await new AdkFunctionModule(ctx ?? getGlobalContext(), groupName.toUpperCase(), name.toUpperCase()).crudContract.delete(groupName.toUpperCase(), name.toUpperCase(), {
|
|
2425
|
+
corrNr: options?.transport,
|
|
2426
|
+
lockHandle: options?.lockHandle
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
registerObjectType("FUGR/FF", FunctionModule, AdkFunctionModule, { endpoint: "functions/groups" });
|
|
2431
|
+
|
|
2432
|
+
//#endregion
|
|
2433
|
+
//#region src/objects/ddic/doma/doma.model.ts
|
|
2434
|
+
/**
|
|
2435
|
+
* DOMA - Domain
|
|
2436
|
+
*
|
|
2437
|
+
* ADK object for ABAP Domains (DOMA).
|
|
2438
|
+
* DDIC objects are metadata-only (no source code).
|
|
2439
|
+
*/
|
|
2440
|
+
/**
|
|
2441
|
+
* ADK Domain object
|
|
2442
|
+
*
|
|
2443
|
+
* Inherits from AdkMainObject which provides:
|
|
2444
|
+
* - name, type, description, version, language, changedBy/At, createdBy/At, links
|
|
2445
|
+
* - package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
|
|
2446
|
+
*
|
|
2447
|
+
* Domain-specific properties via `data`:
|
|
2448
|
+
* - data.typeInformation (dataType, length, decimals, outputLength)
|
|
2449
|
+
* - data.outputInformation (conversionExit, signPresentation, lowerCase)
|
|
2450
|
+
* - data.fixedValues
|
|
2451
|
+
*/
|
|
2452
|
+
var AdkDomain = class AdkDomain extends AdkMainObject {
|
|
2453
|
+
static kind = Domain;
|
|
2454
|
+
kind = AdkDomain.kind;
|
|
2455
|
+
get objectUri() {
|
|
2456
|
+
return `/sap/bc/adt/ddic/domains/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
2457
|
+
}
|
|
2458
|
+
get wrapperKey() {
|
|
2459
|
+
return "domain";
|
|
2460
|
+
}
|
|
2461
|
+
get crudContract() {
|
|
2462
|
+
return this.ctx.client.adt.ddic.domains;
|
|
2463
|
+
}
|
|
2464
|
+
static async get(name, ctx) {
|
|
2465
|
+
return new AdkDomain(ctx ?? getGlobalContext(), name).load();
|
|
2466
|
+
}
|
|
2467
|
+
static async exists(name, ctx) {
|
|
2468
|
+
try {
|
|
2469
|
+
await AdkDomain.get(name, ctx);
|
|
2470
|
+
return true;
|
|
2471
|
+
} catch {
|
|
2472
|
+
return false;
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* Create a new ABAP domain on SAP.
|
|
2477
|
+
*
|
|
2478
|
+
* Note: Domains are metadata-only objects (no source code). After creation,
|
|
2479
|
+
* use the `adt get domain <name>` command or abapGit-based deploy to fill in
|
|
2480
|
+
* the domain properties (data type, field length, etc.).
|
|
2481
|
+
*/
|
|
2482
|
+
static async create(name, description, packageName, options, ctx) {
|
|
2483
|
+
const domain = new AdkDomain(ctx ?? getGlobalContext(), name.toUpperCase());
|
|
2484
|
+
domain.setData({
|
|
2485
|
+
name: name.toUpperCase(),
|
|
2486
|
+
type: "DOMA",
|
|
2487
|
+
description,
|
|
2488
|
+
language: "EN",
|
|
2489
|
+
masterLanguage: "EN",
|
|
2490
|
+
packageRef: {
|
|
2491
|
+
name: packageName.toUpperCase(),
|
|
2492
|
+
uri: `/sap/bc/adt/packages/${encodeURIComponent(packageName.toUpperCase())}`,
|
|
2493
|
+
type: "DEVC/K"
|
|
2494
|
+
}
|
|
2495
|
+
});
|
|
2496
|
+
await domain.save({
|
|
2497
|
+
transport: options?.transport,
|
|
2498
|
+
mode: "create"
|
|
2499
|
+
});
|
|
2500
|
+
return domain;
|
|
2501
|
+
}
|
|
2502
|
+
static async delete(name, options, ctx) {
|
|
2503
|
+
await new AdkDomain(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
2504
|
+
...options?.transport && { corrNr: options.transport },
|
|
2505
|
+
...options?.lockHandle && { lockHandle: options.lockHandle }
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
};
|
|
2509
|
+
registerObjectType("DOMA", Domain, AdkDomain, { endpoint: "ddic/domains" });
|
|
2510
|
+
|
|
2511
|
+
//#endregion
|
|
2512
|
+
//#region src/objects/ddic/dtel/dtel.model.ts
|
|
2513
|
+
/**
|
|
2514
|
+
* DTEL - Data Element
|
|
2515
|
+
*
|
|
2516
|
+
* ADK object for ABAP Data Elements (DTEL).
|
|
2517
|
+
* DDIC objects are metadata-only (no source code).
|
|
2518
|
+
*/
|
|
2519
|
+
/**
|
|
2520
|
+
* ADK Data Element object
|
|
2521
|
+
*
|
|
2522
|
+
* Inherits from AdkMainObject which provides:
|
|
2523
|
+
* - name, type, description, version, language, changedBy/At, createdBy/At, links
|
|
2524
|
+
* - package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
|
|
2525
|
+
*
|
|
2526
|
+
* Data element-specific properties via `data`:
|
|
2527
|
+
* - data.typeKind, data.typeName, data.dataType
|
|
2528
|
+
* - data.shortDescription, data.mediumDescription, data.longDescription, data.headingDescription
|
|
2529
|
+
* - data.searchHelp
|
|
2530
|
+
*/
|
|
2531
|
+
var AdkDataElement = class AdkDataElement extends AdkMainObject {
|
|
2532
|
+
static kind = DataElement;
|
|
2533
|
+
kind = AdkDataElement.kind;
|
|
2534
|
+
get objectUri() {
|
|
2535
|
+
return `/sap/bc/adt/ddic/dataelements/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
2536
|
+
}
|
|
2537
|
+
get wrapperKey() {
|
|
2538
|
+
return "wbobj";
|
|
2539
|
+
}
|
|
2540
|
+
get crudContract() {
|
|
2541
|
+
return this.ctx.client.adt.ddic.dataelements;
|
|
2542
|
+
}
|
|
2543
|
+
static async get(name, ctx) {
|
|
2544
|
+
return new AdkDataElement(ctx ?? getGlobalContext(), name).load();
|
|
2545
|
+
}
|
|
2546
|
+
static async exists(name, ctx) {
|
|
2547
|
+
try {
|
|
2548
|
+
await AdkDataElement.get(name, ctx);
|
|
2549
|
+
return true;
|
|
2550
|
+
} catch {
|
|
2551
|
+
return false;
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
/**
|
|
2555
|
+
* Create a new ABAP data element on SAP.
|
|
2556
|
+
*/
|
|
2557
|
+
static async create(name, description, packageName, options, ctx) {
|
|
2558
|
+
const dtel = new AdkDataElement(ctx ?? getGlobalContext(), name.toUpperCase());
|
|
2559
|
+
dtel.setData({
|
|
2560
|
+
name: name.toUpperCase(),
|
|
2561
|
+
type: "DTEL",
|
|
2562
|
+
description,
|
|
2563
|
+
language: "EN",
|
|
2564
|
+
masterLanguage: "EN",
|
|
2565
|
+
packageRef: {
|
|
2566
|
+
name: packageName.toUpperCase(),
|
|
2567
|
+
uri: `/sap/bc/adt/packages/${encodeURIComponent(packageName.toUpperCase())}`,
|
|
2568
|
+
type: "DEVC/K"
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
await dtel.save({
|
|
2572
|
+
transport: options?.transport,
|
|
2573
|
+
mode: "create"
|
|
2574
|
+
});
|
|
2575
|
+
return dtel;
|
|
2576
|
+
}
|
|
2577
|
+
static async delete(name, options, ctx) {
|
|
2578
|
+
await new AdkDataElement(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
2579
|
+
...options?.transport && { corrNr: options.transport },
|
|
2580
|
+
...options?.lockHandle && { lockHandle: options.lockHandle }
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
2584
|
+
registerObjectType("DTEL", DataElement, AdkDataElement, { endpoint: "ddic/dataelements" });
|
|
2585
|
+
|
|
2586
|
+
//#endregion
|
|
2587
|
+
//#region src/objects/ddic/tabl/tabl.model.ts
|
|
2588
|
+
/**
|
|
2589
|
+
* TABL - Database Table / Structure
|
|
2590
|
+
*
|
|
2591
|
+
* ADK object for ABAP Database Tables (TABL/DT) and Structures (TABL/DS).
|
|
2592
|
+
* Tables and structures are source-based DDIC objects whose definition
|
|
2593
|
+
* lives in ABAP source code (retrieved via sourceUri).
|
|
2594
|
+
*
|
|
2595
|
+
* Note: Tables and structures share the same ADT main type (TABL)
|
|
2596
|
+
* but use different endpoints and subtypes.
|
|
2597
|
+
* - Tables: /sap/bc/adt/ddic/tables (TABL/DT)
|
|
2598
|
+
* - Structures: /sap/bc/adt/ddic/structures (TABL/DS)
|
|
2599
|
+
*
|
|
2600
|
+
* SAP wraps both in a blue:blueSource root element
|
|
2601
|
+
* (namespace http://www.sap.com/wbobj/blue) extending AbapSourceMainObject.
|
|
2602
|
+
*/
|
|
2603
|
+
/**
|
|
2604
|
+
* ADK Table object (database table - TABL/DT)
|
|
2605
|
+
*/
|
|
2606
|
+
var AdkTable = class AdkTable extends AdkMainObject {
|
|
2607
|
+
static kind = Table;
|
|
2608
|
+
kind = AdkTable.kind;
|
|
2609
|
+
get objectUri() {
|
|
2610
|
+
return `/sap/bc/adt/ddic/tables/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* Get CDS-style table source code from SAP
|
|
2614
|
+
* Returns the ABAP source definition (annotations + field definitions)
|
|
2615
|
+
*/
|
|
2616
|
+
async getSource() {
|
|
2617
|
+
return this.lazy("source", async () => {
|
|
2618
|
+
return this.ctx.client.adt.ddic.tables.source.main.get(this.name);
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Get table technical settings (DD09L data)
|
|
2623
|
+
* Returns XML from /sap/bc/adt/ddic/db/settings/{name}
|
|
2624
|
+
*/
|
|
2625
|
+
async getSettings() {
|
|
2626
|
+
return this.lazy("settings", async () => {
|
|
2627
|
+
return this.ctx.client.adt.ddic.tablesettings.get(this.name);
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
get wrapperKey() {
|
|
2631
|
+
return "blueSource";
|
|
2632
|
+
}
|
|
2633
|
+
get crudContract() {
|
|
2634
|
+
return this.ctx.client.adt.ddic.tables;
|
|
2635
|
+
}
|
|
2636
|
+
static async get(name, ctx) {
|
|
2637
|
+
return new AdkTable(ctx ?? getGlobalContext(), name).load();
|
|
2638
|
+
}
|
|
2639
|
+
static async exists(name, ctx) {
|
|
2640
|
+
try {
|
|
2641
|
+
await AdkTable.get(name, ctx);
|
|
2642
|
+
return true;
|
|
2643
|
+
} catch {
|
|
2644
|
+
return false;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* Create a new ABAP database table on SAP.
|
|
2649
|
+
*/
|
|
2650
|
+
static async create(name, description, packageName, options, ctx) {
|
|
2651
|
+
const table = new AdkTable(ctx ?? getGlobalContext(), name.toUpperCase());
|
|
2652
|
+
table.setData({
|
|
2653
|
+
name: name.toUpperCase(),
|
|
2654
|
+
type: "TABL/DT",
|
|
2655
|
+
description,
|
|
2656
|
+
language: "EN",
|
|
2657
|
+
masterLanguage: "EN",
|
|
2658
|
+
packageRef: {
|
|
2659
|
+
name: packageName.toUpperCase(),
|
|
2660
|
+
uri: `/sap/bc/adt/packages/${encodeURIComponent(packageName.toUpperCase())}`,
|
|
2661
|
+
type: "DEVC/K"
|
|
2662
|
+
}
|
|
2663
|
+
});
|
|
2664
|
+
await table.save({
|
|
2665
|
+
transport: options?.transport,
|
|
2666
|
+
mode: "create"
|
|
2667
|
+
});
|
|
2668
|
+
return table;
|
|
2669
|
+
}
|
|
2670
|
+
static async delete(name, options, ctx) {
|
|
2671
|
+
await new AdkTable(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
2672
|
+
...options?.transport && { corrNr: options.transport },
|
|
2673
|
+
...options?.lockHandle && { lockHandle: options.lockHandle }
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
};
|
|
2677
|
+
/**
|
|
2678
|
+
* ADK Structure object (TABL/DS)
|
|
2679
|
+
*/
|
|
2680
|
+
var AdkStructure = class AdkStructure extends AdkMainObject {
|
|
2681
|
+
static kind = Structure;
|
|
2682
|
+
kind = AdkStructure.kind;
|
|
2683
|
+
get objectUri() {
|
|
2684
|
+
return `/sap/bc/adt/ddic/structures/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
2685
|
+
}
|
|
2686
|
+
/**
|
|
2687
|
+
* Get CDS-style structure source code from SAP
|
|
2688
|
+
* Returns the ABAP source definition (annotations + field definitions)
|
|
2689
|
+
*/
|
|
2690
|
+
async getSource() {
|
|
2691
|
+
return this.lazy("source", async () => {
|
|
2692
|
+
return this.ctx.client.adt.ddic.structures.source.main.get(this.name);
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
get wrapperKey() {
|
|
2696
|
+
return "blueSource";
|
|
2697
|
+
}
|
|
2698
|
+
get crudContract() {
|
|
2699
|
+
return this.ctx.client.adt.ddic.structures;
|
|
2700
|
+
}
|
|
2701
|
+
static async get(name, ctx) {
|
|
2702
|
+
return new AdkStructure(ctx ?? getGlobalContext(), name).load();
|
|
2703
|
+
}
|
|
2704
|
+
static async exists(name, ctx) {
|
|
2705
|
+
try {
|
|
2706
|
+
await AdkStructure.get(name, ctx);
|
|
2707
|
+
return true;
|
|
2708
|
+
} catch {
|
|
2709
|
+
return false;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
/**
|
|
2713
|
+
* Create a new ABAP structure on SAP.
|
|
2714
|
+
*/
|
|
2715
|
+
static async create(name, description, packageName, options, ctx) {
|
|
2716
|
+
const struct = new AdkStructure(ctx ?? getGlobalContext(), name.toUpperCase());
|
|
2717
|
+
struct.setData({
|
|
2718
|
+
name: name.toUpperCase(),
|
|
2719
|
+
type: "TABL/DS",
|
|
2720
|
+
description,
|
|
2721
|
+
language: "EN",
|
|
2722
|
+
masterLanguage: "EN",
|
|
2723
|
+
packageRef: {
|
|
2724
|
+
name: packageName.toUpperCase(),
|
|
2725
|
+
uri: `/sap/bc/adt/packages/${encodeURIComponent(packageName.toUpperCase())}`,
|
|
2726
|
+
type: "DEVC/K"
|
|
2727
|
+
}
|
|
2728
|
+
});
|
|
2729
|
+
await struct.save({
|
|
2730
|
+
transport: options?.transport,
|
|
2731
|
+
mode: "create"
|
|
2732
|
+
});
|
|
2733
|
+
return struct;
|
|
2734
|
+
}
|
|
2735
|
+
static async delete(name, options, ctx) {
|
|
2736
|
+
await new AdkStructure(ctx ?? getGlobalContext(), name.toUpperCase()).crudContract.delete(name.toUpperCase(), {
|
|
2737
|
+
...options?.transport && { corrNr: options.transport },
|
|
2738
|
+
...options?.lockHandle && { lockHandle: options.lockHandle }
|
|
2739
|
+
});
|
|
2740
|
+
}
|
|
2741
|
+
};
|
|
2742
|
+
registerObjectType("TABL", Table, AdkTable, { endpoint: "ddic/tables" });
|
|
2743
|
+
registerObjectType("TABL/DS", Structure, AdkStructure, { endpoint: "ddic/structures" });
|
|
2744
|
+
|
|
2745
|
+
//#endregion
|
|
2746
|
+
//#region src/objects/ddic/ttyp/ttyp.model.ts
|
|
2747
|
+
/**
|
|
2748
|
+
* TTYP - Table Type
|
|
2749
|
+
*
|
|
2750
|
+
* ADK object for ABAP Table Types (TTYP).
|
|
2751
|
+
* DDIC objects are metadata-only (no source code).
|
|
2752
|
+
*/
|
|
2753
|
+
/**
|
|
2754
|
+
* ADK Table Type object
|
|
2755
|
+
*
|
|
2756
|
+
* Inherits from AdkMainObject which provides:
|
|
2757
|
+
* - name, type, description, version, language, changedBy/At, createdBy/At, links
|
|
2758
|
+
* - package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
|
|
2759
|
+
*
|
|
2760
|
+
* Table type-specific properties via `data`:
|
|
2761
|
+
* - data.rowType (typeName, typeCategory, tableTypeSchemaReference)
|
|
2762
|
+
* - data.primaryKey
|
|
2763
|
+
* - data.secondaryKeys
|
|
2764
|
+
*/
|
|
2765
|
+
var AdkTableType = class AdkTableType extends AdkMainObject {
|
|
2766
|
+
static kind = TableType;
|
|
2767
|
+
kind = AdkTableType.kind;
|
|
2768
|
+
get objectUri() {
|
|
2769
|
+
return `/sap/bc/adt/ddic/tabletypes/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
2770
|
+
}
|
|
2771
|
+
get wrapperKey() {
|
|
2772
|
+
return "tableType";
|
|
2773
|
+
}
|
|
2774
|
+
get crudContract() {
|
|
2775
|
+
return this.ctx.client.adt.ddic.tabletypes;
|
|
2776
|
+
}
|
|
2777
|
+
static async get(name, ctx) {
|
|
2778
|
+
return new AdkTableType(ctx ?? getGlobalContext(), name).load();
|
|
2779
|
+
}
|
|
2780
|
+
};
|
|
2781
|
+
registerObjectType("TTYP", TableType, AdkTableType, { endpoint: "ddic/tabletypes" });
|
|
2782
|
+
|
|
2783
|
+
//#endregion
|
|
2784
|
+
//#region src/objects/cds/ddl.model.ts
|
|
2785
|
+
/**
|
|
2786
|
+
* DDL - CDS Data Definition Language Source (DDLS)
|
|
2787
|
+
*
|
|
2788
|
+
* ADK object for ABAP CDS Data Definition sources.
|
|
2789
|
+
* These are source-based objects stored at:
|
|
2790
|
+
* GET/PUT /sap/bc/adt/ddic/ddl/sources/<name>/source/main
|
|
2791
|
+
*
|
|
2792
|
+
* The metadata document is at:
|
|
2793
|
+
* GET /sap/bc/adt/ddic/ddl/sources/<name>
|
|
2794
|
+
*
|
|
2795
|
+
* Lock/activate/create follow the same patterns as other source objects.
|
|
2796
|
+
*/
|
|
2797
|
+
var AdkDdlSource = class AdkDdlSource {
|
|
2798
|
+
name;
|
|
2799
|
+
ctx;
|
|
2800
|
+
constructor(ctx, name) {
|
|
2801
|
+
this.ctx = ctx;
|
|
2802
|
+
this.name = name.toUpperCase();
|
|
2803
|
+
}
|
|
2804
|
+
get objectUri() {
|
|
2805
|
+
return `/sap/bc/adt/ddic/ddl/sources/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
2806
|
+
}
|
|
2807
|
+
/** Placeholder description — full metadata requires additional SAP fetch */
|
|
2808
|
+
get description() {
|
|
2809
|
+
return this.name;
|
|
2810
|
+
}
|
|
2811
|
+
get contract() {
|
|
2812
|
+
return this.ctx.client.adt.ddic.ddl.sources;
|
|
2813
|
+
}
|
|
2814
|
+
async getSource() {
|
|
2815
|
+
return toText(await this.contract.source.main.get(this.name));
|
|
2816
|
+
}
|
|
2817
|
+
async saveMainSource(source, options) {
|
|
2818
|
+
await this.contract.source.main.put(this.name, {
|
|
2819
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {},
|
|
2820
|
+
...options?.transport ? { corrNr: options.transport } : {}
|
|
2821
|
+
}, source);
|
|
2822
|
+
}
|
|
2823
|
+
async lock(transport) {
|
|
2824
|
+
const lockService = this.ctx.lockService;
|
|
2825
|
+
if (!lockService) throw new Error("Lock not available: no lockService in context. Did you call initializeAdk()?");
|
|
2826
|
+
return lockService.lock(this.objectUri, {
|
|
2827
|
+
transport,
|
|
2828
|
+
objectName: this.name,
|
|
2829
|
+
objectType: "DDLS"
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
2832
|
+
async unlock(lockHandle) {
|
|
2833
|
+
const lockService = this.ctx.lockService;
|
|
2834
|
+
if (!lockService) throw new Error("Unlock not available: no lockService in context. Did you call initializeAdk()?");
|
|
2835
|
+
await lockService.unlock(this.objectUri, { lockHandle });
|
|
2836
|
+
}
|
|
2837
|
+
async activate() {
|
|
2838
|
+
await this.ctx.client.adt.activation.activate.post({}, { objectReferences: { objectReference: [{
|
|
2839
|
+
uri: this.objectUri,
|
|
2840
|
+
name: this.name
|
|
2841
|
+
}] } });
|
|
2842
|
+
return this;
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Get a DDL source (does not fetch metadata, just returns handle)
|
|
2846
|
+
*/
|
|
2847
|
+
static async get(name, ctx) {
|
|
2848
|
+
const obj = new AdkDdlSource(ctx ?? getGlobalContext(), name);
|
|
2849
|
+
await obj.getSource();
|
|
2850
|
+
return obj;
|
|
2851
|
+
}
|
|
2852
|
+
static async exists(name, ctx) {
|
|
2853
|
+
try {
|
|
2854
|
+
await AdkDdlSource.get(name, ctx);
|
|
2855
|
+
return true;
|
|
2856
|
+
} catch {
|
|
2857
|
+
return false;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Create a new CDS DDL source on SAP
|
|
2862
|
+
*
|
|
2863
|
+
* POST /sap/bc/adt/ddic/ddl/sources?corrNr=...
|
|
2864
|
+
* Body matches the `ddl:source` schema (extends abapsource:AbapSourceMainObject).
|
|
2865
|
+
*/
|
|
2866
|
+
static async create(name, description, packageName, options, ctx) {
|
|
2867
|
+
const context = ctx ?? getGlobalContext();
|
|
2868
|
+
const nameU = name.toUpperCase();
|
|
2869
|
+
const pkgU = packageName.toUpperCase();
|
|
2870
|
+
await context.client.adt.ddic.ddl.sources.post(options?.transport ? { corrNr: options.transport } : {}, { source: {
|
|
2871
|
+
name: nameU,
|
|
2872
|
+
description,
|
|
2873
|
+
language: "EN",
|
|
2874
|
+
masterLanguage: "EN",
|
|
2875
|
+
responsible: "$TMP",
|
|
2876
|
+
packageRef: {
|
|
2877
|
+
name: pkgU,
|
|
2878
|
+
type: "DEVC/K",
|
|
2879
|
+
uri: `/sap/bc/adt/packages/${pkgU.toLowerCase()}`
|
|
2880
|
+
}
|
|
2881
|
+
} });
|
|
2882
|
+
return new AdkDdlSource(context, nameU);
|
|
2883
|
+
}
|
|
2884
|
+
static async delete(name, options, ctx) {
|
|
2885
|
+
await (ctx ?? getGlobalContext()).client.adt.ddic.ddl.sources.delete(name.toUpperCase(), {
|
|
2886
|
+
...options?.transport ? { corrNr: options.transport } : {},
|
|
2887
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {}
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
};
|
|
2891
|
+
|
|
2892
|
+
//#endregion
|
|
2893
|
+
//#region src/objects/cds/dcl.model.ts
|
|
2894
|
+
/**
|
|
2895
|
+
* DCL - CDS Access Control Definition Language Source (DCLS)
|
|
2896
|
+
*
|
|
2897
|
+
* ADK object for ABAP CDS Access Control (DCL) sources.
|
|
2898
|
+
* These are source-based objects stored at:
|
|
2899
|
+
* GET/PUT /sap/bc/adt/acm/dcl/sources/<name>/source/main
|
|
2900
|
+
*
|
|
2901
|
+
* The metadata document is at:
|
|
2902
|
+
* GET /sap/bc/adt/acm/dcl/sources/<name>
|
|
2903
|
+
*
|
|
2904
|
+
* Lock/activate/create follow the same patterns as DDL sources.
|
|
2905
|
+
*/
|
|
2906
|
+
var AdkDclSource = class AdkDclSource {
|
|
2907
|
+
name;
|
|
2908
|
+
ctx;
|
|
2909
|
+
constructor(ctx, name) {
|
|
2910
|
+
this.ctx = ctx;
|
|
2911
|
+
this.name = name.toUpperCase();
|
|
2912
|
+
}
|
|
2913
|
+
get objectUri() {
|
|
2914
|
+
return `/sap/bc/adt/acm/dcl/sources/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
2915
|
+
}
|
|
2916
|
+
/** Placeholder description — full metadata requires additional SAP fetch */
|
|
2917
|
+
get description() {
|
|
2918
|
+
return this.name;
|
|
2919
|
+
}
|
|
2920
|
+
get contract() {
|
|
2921
|
+
return this.ctx.client.adt.ddic.dcl.sources;
|
|
2922
|
+
}
|
|
2923
|
+
async getSource() {
|
|
2924
|
+
return toText(await this.contract.source.main.get(this.name));
|
|
2925
|
+
}
|
|
2926
|
+
async saveMainSource(source, options) {
|
|
2927
|
+
await this.contract.source.main.put(this.name, {
|
|
2928
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {},
|
|
2929
|
+
...options?.transport ? { corrNr: options.transport } : {}
|
|
2930
|
+
}, source);
|
|
2931
|
+
}
|
|
2932
|
+
async lock(transport) {
|
|
2933
|
+
const lockService = this.ctx.lockService;
|
|
2934
|
+
if (!lockService) throw new Error("Lock not available: no lockService in context. Did you call initializeAdk()?");
|
|
2935
|
+
return lockService.lock(this.objectUri, {
|
|
2936
|
+
transport,
|
|
2937
|
+
objectName: this.name,
|
|
2938
|
+
objectType: "DCLS"
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
async unlock(lockHandle) {
|
|
2942
|
+
const lockService = this.ctx.lockService;
|
|
2943
|
+
if (!lockService) throw new Error("Unlock not available: no lockService in context. Did you call initializeAdk()?");
|
|
2944
|
+
await lockService.unlock(this.objectUri, { lockHandle });
|
|
2945
|
+
}
|
|
2946
|
+
async activate() {
|
|
2947
|
+
await this.ctx.client.adt.activation.activate.post({}, { objectReferences: { objectReference: [{
|
|
2948
|
+
uri: this.objectUri,
|
|
2949
|
+
name: this.name
|
|
2950
|
+
}] } });
|
|
2951
|
+
return this;
|
|
2952
|
+
}
|
|
2953
|
+
static async get(name, ctx) {
|
|
2954
|
+
const obj = new AdkDclSource(ctx ?? getGlobalContext(), name);
|
|
2955
|
+
await obj.getSource();
|
|
2956
|
+
return obj;
|
|
2957
|
+
}
|
|
2958
|
+
static async exists(name, ctx) {
|
|
2959
|
+
try {
|
|
2960
|
+
await AdkDclSource.get(name, ctx);
|
|
2961
|
+
return true;
|
|
2962
|
+
} catch {
|
|
2963
|
+
return false;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
/**
|
|
2967
|
+
* Create a new CDS DCL source on SAP
|
|
2968
|
+
*/
|
|
2969
|
+
static async create(name, description, packageName, options, ctx) {
|
|
2970
|
+
const context = ctx ?? getGlobalContext();
|
|
2971
|
+
const nameU = name.toUpperCase();
|
|
2972
|
+
const pkgU = packageName.toUpperCase();
|
|
2973
|
+
await context.client.adt.ddic.dcl.sources.post(options?.transport ? { corrNr: options.transport } : {}, { source: {
|
|
2974
|
+
name: nameU,
|
|
2975
|
+
description,
|
|
2976
|
+
language: "EN",
|
|
2977
|
+
masterLanguage: "EN",
|
|
2978
|
+
responsible: "$TMP",
|
|
2979
|
+
packageRef: {
|
|
2980
|
+
name: pkgU,
|
|
2981
|
+
type: "DEVC/K",
|
|
2982
|
+
uri: `/sap/bc/adt/packages/${pkgU.toLowerCase()}`
|
|
2983
|
+
}
|
|
2984
|
+
} });
|
|
2985
|
+
return new AdkDclSource(context, nameU);
|
|
2986
|
+
}
|
|
2987
|
+
static async delete(name, options, ctx) {
|
|
2988
|
+
await (ctx ?? getGlobalContext()).client.adt.ddic.dcl.sources.delete(name.toUpperCase(), {
|
|
2989
|
+
...options?.transport ? { corrNr: options.transport } : {},
|
|
2990
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {}
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
2994
|
+
|
|
2995
|
+
//#endregion
|
|
2996
|
+
//#region src/objects/repository/bdef/bdef.model.ts
|
|
2997
|
+
/**
|
|
2998
|
+
* BDEF - Behavior Definition (RAP)
|
|
2999
|
+
*
|
|
3000
|
+
* ADK object for ABAP RAP Behavior Definitions (BDEF).
|
|
3001
|
+
* These are source-based objects (`.abdl`) stored at:
|
|
3002
|
+
* GET /sap/bc/adt/bo/behaviordefinitions/<name>
|
|
3003
|
+
* GET/PUT /sap/bc/adt/bo/behaviordefinitions/<name>/source/main
|
|
3004
|
+
*
|
|
3005
|
+
* Lock/activate/create follow the same patterns as other source objects
|
|
3006
|
+
* (DDL, DCL). The metadata document uses the shared `blue:blueSource`
|
|
3007
|
+
* wrapper — same envelope as TABL/STRUCT.
|
|
3008
|
+
*
|
|
3009
|
+
* This class intentionally mirrors `AdkDdlSource` (lightweight ADK object,
|
|
3010
|
+
* not a full AdkMainObject subclass) because BDEF metadata is source-driven
|
|
3011
|
+
* and the typical lifecycle is: create skeleton → PUT source → activate.
|
|
3012
|
+
*/
|
|
3013
|
+
var AdkBehaviorDefinition = class AdkBehaviorDefinition {
|
|
3014
|
+
/** Static ADK kind marker — used by abapGit handler registry if needed. */
|
|
3015
|
+
static kind = "BehaviorDefinition";
|
|
3016
|
+
kind = AdkBehaviorDefinition.kind;
|
|
3017
|
+
name;
|
|
3018
|
+
ctx;
|
|
3019
|
+
constructor(ctx, name) {
|
|
3020
|
+
this.ctx = ctx;
|
|
3021
|
+
this.name = name.toUpperCase();
|
|
3022
|
+
}
|
|
3023
|
+
get objectUri() {
|
|
3024
|
+
return `/sap/bc/adt/bo/behaviordefinitions/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
3025
|
+
}
|
|
3026
|
+
/** Placeholder description — full metadata requires additional SAP fetch */
|
|
3027
|
+
get description() {
|
|
3028
|
+
return this.name;
|
|
3029
|
+
}
|
|
3030
|
+
get contract() {
|
|
3031
|
+
return this.ctx.client.adt.bo.behaviordefinitions;
|
|
3032
|
+
}
|
|
3033
|
+
async getSource() {
|
|
3034
|
+
return toText(await this.contract.source.main.get(this.name));
|
|
3035
|
+
}
|
|
3036
|
+
async saveMainSource(source, options) {
|
|
3037
|
+
await this.contract.source.main.put(this.name, {
|
|
3038
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {},
|
|
3039
|
+
...options?.transport ? { corrNr: options.transport } : {}
|
|
3040
|
+
}, source);
|
|
3041
|
+
}
|
|
3042
|
+
async lock(transport) {
|
|
3043
|
+
const lockService = this.ctx.lockService;
|
|
3044
|
+
if (!lockService) throw new Error("Lock not available: no lockService in context. Did you call initializeAdk()?");
|
|
3045
|
+
return lockService.lock(this.objectUri, {
|
|
3046
|
+
transport,
|
|
3047
|
+
objectName: this.name,
|
|
3048
|
+
objectType: "BDEF"
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
async unlock(lockHandle) {
|
|
3052
|
+
const lockService = this.ctx.lockService;
|
|
3053
|
+
if (!lockService) throw new Error("Unlock not available: no lockService in context. Did you call initializeAdk()?");
|
|
3054
|
+
await lockService.unlock(this.objectUri, { lockHandle });
|
|
3055
|
+
}
|
|
3056
|
+
async activate() {
|
|
3057
|
+
await this.ctx.client.adt.activation.activate.post({}, { objectReferences: { objectReference: [{
|
|
3058
|
+
uri: this.objectUri,
|
|
3059
|
+
name: this.name
|
|
3060
|
+
}] } });
|
|
3061
|
+
return this;
|
|
3062
|
+
}
|
|
3063
|
+
/**
|
|
3064
|
+
* Get a BDEF (validates it exists by fetching source).
|
|
3065
|
+
*/
|
|
3066
|
+
static async get(name, ctx) {
|
|
3067
|
+
const obj = new AdkBehaviorDefinition(ctx ?? getGlobalContext(), name);
|
|
3068
|
+
await obj.getSource();
|
|
3069
|
+
return obj;
|
|
3070
|
+
}
|
|
3071
|
+
static async exists(name, ctx) {
|
|
3072
|
+
try {
|
|
3073
|
+
await AdkBehaviorDefinition.get(name, ctx);
|
|
3074
|
+
return true;
|
|
3075
|
+
} catch {
|
|
3076
|
+
return false;
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
/**
|
|
3080
|
+
* Create a new BDEF on SAP.
|
|
3081
|
+
*
|
|
3082
|
+
* POST /sap/bc/adt/bo/behaviordefinitions?corrNr=...
|
|
3083
|
+
* Body matches the `blue:blueSource` envelope (extends
|
|
3084
|
+
* abapsource:AbapSourceMainObject).
|
|
3085
|
+
*/
|
|
3086
|
+
static async create(name, description, packageName, options, ctx) {
|
|
3087
|
+
const context = ctx ?? getGlobalContext();
|
|
3088
|
+
const nameU = name.toUpperCase();
|
|
3089
|
+
const pkgU = packageName.toUpperCase();
|
|
3090
|
+
await context.client.adt.bo.behaviordefinitions.post(options?.transport ? { corrNr: options.transport } : {}, { blueSource: {
|
|
3091
|
+
name: nameU,
|
|
3092
|
+
type: "BDEF/BDO",
|
|
3093
|
+
description,
|
|
3094
|
+
language: "EN",
|
|
3095
|
+
masterLanguage: "EN",
|
|
3096
|
+
responsible: pkgU,
|
|
3097
|
+
packageRef: {
|
|
3098
|
+
name: pkgU,
|
|
3099
|
+
type: "DEVC/K",
|
|
3100
|
+
uri: `/sap/bc/adt/packages/${pkgU.toLowerCase()}`
|
|
3101
|
+
}
|
|
3102
|
+
} });
|
|
3103
|
+
return new AdkBehaviorDefinition(context, nameU);
|
|
3104
|
+
}
|
|
3105
|
+
static async delete(name, options, ctx) {
|
|
3106
|
+
await (ctx ?? getGlobalContext()).client.adt.bo.behaviordefinitions.delete(name.toUpperCase(), {
|
|
3107
|
+
...options?.transport ? { corrNr: options.transport } : {},
|
|
3108
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {}
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
3111
|
+
};
|
|
3112
|
+
|
|
3113
|
+
//#endregion
|
|
3114
|
+
//#region src/objects/repository/badi/badi.model.ts
|
|
3115
|
+
/**
|
|
3116
|
+
* BAdI — Enhancement Implementation (ENHO/XHH) — RAP-era BAdI container.
|
|
3117
|
+
*
|
|
3118
|
+
* ADK object for ABAP Enhancement Implementations that host BAdI
|
|
3119
|
+
* implementations. These live at:
|
|
3120
|
+
*
|
|
3121
|
+
* GET /sap/bc/adt/enhancements/enhoxhh/<name>
|
|
3122
|
+
* PUT /sap/bc/adt/enhancements/enhoxhh/<name>
|
|
3123
|
+
* DELETE /sap/bc/adt/enhancements/enhoxhh/<name>
|
|
3124
|
+
* GET/PUT /sap/bc/adt/enhancements/enhoxhh/<name>/source/main
|
|
3125
|
+
*
|
|
3126
|
+
* Mirrors the source-driven pattern used by `AdkBehaviorDefinition`
|
|
3127
|
+
* (BDEF): create skeleton → PUT source → activate.
|
|
3128
|
+
*
|
|
3129
|
+
* sapcli reference: `sap/cli/badi.py` (`_get_enhancement_implementation`).
|
|
3130
|
+
*/
|
|
3131
|
+
var AdkBadi = class AdkBadi {
|
|
3132
|
+
/** Static ADK kind marker. */
|
|
3133
|
+
static kind = "Badi";
|
|
3134
|
+
kind = AdkBadi.kind;
|
|
3135
|
+
name;
|
|
3136
|
+
ctx;
|
|
3137
|
+
constructor(ctx, name) {
|
|
3138
|
+
this.ctx = ctx;
|
|
3139
|
+
this.name = name.toUpperCase();
|
|
3140
|
+
}
|
|
3141
|
+
/** ADT URI (lowercase path segment — SAP convention). */
|
|
3142
|
+
get objectUri() {
|
|
3143
|
+
return `/sap/bc/adt/enhancements/enhoxhh/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
3144
|
+
}
|
|
3145
|
+
/** Placeholder description — real metadata arrives via GET/objectstructure. */
|
|
3146
|
+
get description() {
|
|
3147
|
+
return this.name;
|
|
3148
|
+
}
|
|
3149
|
+
get contract() {
|
|
3150
|
+
return this.ctx.client.adt.enhancements.enhoxhh;
|
|
3151
|
+
}
|
|
3152
|
+
async getMetadata() {
|
|
3153
|
+
return await this.contract.get(this.name);
|
|
3154
|
+
}
|
|
3155
|
+
async getSource() {
|
|
3156
|
+
return toText(await this.contract.source.main.get(this.name));
|
|
3157
|
+
}
|
|
3158
|
+
async saveMainSource(source, options) {
|
|
3159
|
+
await this.contract.source.main.put(this.name, {
|
|
3160
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {},
|
|
3161
|
+
...options?.transport ? { corrNr: options.transport } : {}
|
|
3162
|
+
}, source);
|
|
3163
|
+
}
|
|
3164
|
+
async lock(transport) {
|
|
3165
|
+
const lockService = this.ctx.lockService;
|
|
3166
|
+
if (!lockService) throw new Error("Lock not available: no lockService in context. Did you call initializeAdk()?");
|
|
3167
|
+
return lockService.lock(this.objectUri, {
|
|
3168
|
+
transport,
|
|
3169
|
+
objectName: this.name,
|
|
3170
|
+
objectType: "ENHO"
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
async unlock(lockHandle) {
|
|
3174
|
+
const lockService = this.ctx.lockService;
|
|
3175
|
+
if (!lockService) throw new Error("Unlock not available: no lockService in context. Did you call initializeAdk()?");
|
|
3176
|
+
await lockService.unlock(this.objectUri, { lockHandle });
|
|
3177
|
+
}
|
|
3178
|
+
async activate() {
|
|
3179
|
+
await this.ctx.client.adt.activation.activate.post({}, { objectReferences: { objectReference: [{
|
|
3180
|
+
uri: this.objectUri,
|
|
3181
|
+
name: this.name
|
|
3182
|
+
}] } });
|
|
3183
|
+
return this;
|
|
3184
|
+
}
|
|
3185
|
+
/** Fetch a BAdI / ENHO, validating existence via a metadata GET. */
|
|
3186
|
+
static async get(name, ctx) {
|
|
3187
|
+
const obj = new AdkBadi(ctx ?? getGlobalContext(), name);
|
|
3188
|
+
await obj.getMetadata();
|
|
3189
|
+
return obj;
|
|
3190
|
+
}
|
|
3191
|
+
static async exists(name, ctx) {
|
|
3192
|
+
try {
|
|
3193
|
+
await AdkBadi.get(name, ctx);
|
|
3194
|
+
return true;
|
|
3195
|
+
} catch {
|
|
3196
|
+
return false;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
/**
|
|
3200
|
+
* Create a new Enhancement Implementation (BAdI container).
|
|
3201
|
+
*
|
|
3202
|
+
* Trial systems typically reject this with HTTP 403 — this is an
|
|
3203
|
+
* area that requires elevated authorisation on most SAP installs.
|
|
3204
|
+
*/
|
|
3205
|
+
static async create(name, description, packageName, options, ctx) {
|
|
3206
|
+
const context = ctx ?? getGlobalContext();
|
|
3207
|
+
const nameU = name.toUpperCase();
|
|
3208
|
+
const pkgU = packageName.toUpperCase();
|
|
3209
|
+
await context.client.adt.enhancements.enhoxhh.post(options?.transport ? { corrNr: options.transport } : {}, { enhancementImplementation: {
|
|
3210
|
+
name: nameU,
|
|
3211
|
+
type: "ENHO/XHH",
|
|
3212
|
+
description,
|
|
3213
|
+
language: "EN",
|
|
3214
|
+
masterLanguage: "EN",
|
|
3215
|
+
responsible: pkgU,
|
|
3216
|
+
packageRef: {
|
|
3217
|
+
name: pkgU,
|
|
3218
|
+
type: "DEVC/K",
|
|
3219
|
+
uri: `/sap/bc/adt/packages/${pkgU.toLowerCase()}`
|
|
3220
|
+
}
|
|
3221
|
+
} });
|
|
3222
|
+
return new AdkBadi(context, nameU);
|
|
3223
|
+
}
|
|
3224
|
+
static async delete(name, options, ctx) {
|
|
3225
|
+
await (ctx ?? getGlobalContext()).client.adt.enhancements.enhoxhh.delete(name.toUpperCase(), {
|
|
3226
|
+
...options?.transport ? { corrNr: options.transport } : {},
|
|
3227
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {}
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
};
|
|
3231
|
+
|
|
3232
|
+
//#endregion
|
|
3233
|
+
//#region src/objects/repository/srvd/srvd.model.ts
|
|
3234
|
+
/**
|
|
3235
|
+
* SRVD - Service Definition (RAP)
|
|
3236
|
+
*
|
|
3237
|
+
* ADK object for ABAP RAP Service Definitions (SRVD).
|
|
3238
|
+
* Source-based objects (`.asrvd`) stored at:
|
|
3239
|
+
* GET /sap/bc/adt/ddic/srvd/sources/<name>
|
|
3240
|
+
* GET/PUT /sap/bc/adt/ddic/srvd/sources/<name>/source/main
|
|
3241
|
+
*
|
|
3242
|
+
* Lock/activate/create follow the same patterns as other source objects
|
|
3243
|
+
* (DDL, DCL, BDEF). Metadata envelope is the dedicated `srvd:source`
|
|
3244
|
+
* wrapper extending abapsource:AbapSourceMainObject (mirrors DDL).
|
|
3245
|
+
*/
|
|
3246
|
+
var AdkServiceDefinition = class AdkServiceDefinition {
|
|
3247
|
+
/** Static ADK kind marker — used by abapGit handler registry if needed. */
|
|
3248
|
+
static kind = "ServiceDefinition";
|
|
3249
|
+
kind = AdkServiceDefinition.kind;
|
|
3250
|
+
name;
|
|
3251
|
+
ctx;
|
|
3252
|
+
constructor(ctx, name) {
|
|
3253
|
+
this.ctx = ctx;
|
|
3254
|
+
this.name = name.toUpperCase();
|
|
3255
|
+
}
|
|
3256
|
+
get objectUri() {
|
|
3257
|
+
return `/sap/bc/adt/ddic/srvd/sources/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
3258
|
+
}
|
|
3259
|
+
/** Placeholder description — full metadata requires additional SAP fetch */
|
|
3260
|
+
get description() {
|
|
3261
|
+
return this.name;
|
|
3262
|
+
}
|
|
3263
|
+
get contract() {
|
|
3264
|
+
return this.ctx.client.adt.ddic.srvd.sources;
|
|
3265
|
+
}
|
|
3266
|
+
async getSource() {
|
|
3267
|
+
return toText(await this.contract.source.main.get(this.name));
|
|
3268
|
+
}
|
|
3269
|
+
async saveMainSource(source, options) {
|
|
3270
|
+
await this.contract.source.main.put(this.name, {
|
|
3271
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {},
|
|
3272
|
+
...options?.transport ? { corrNr: options.transport } : {}
|
|
3273
|
+
}, source);
|
|
3274
|
+
}
|
|
3275
|
+
async lock(transport) {
|
|
3276
|
+
const lockService = this.ctx.lockService;
|
|
3277
|
+
if (!lockService) throw new Error("Lock not available: no lockService in context. Did you call initializeAdk()?");
|
|
3278
|
+
return lockService.lock(this.objectUri, {
|
|
3279
|
+
transport,
|
|
3280
|
+
objectName: this.name,
|
|
3281
|
+
objectType: "SRVD"
|
|
3282
|
+
});
|
|
3283
|
+
}
|
|
3284
|
+
async unlock(lockHandle) {
|
|
3285
|
+
const lockService = this.ctx.lockService;
|
|
3286
|
+
if (!lockService) throw new Error("Unlock not available: no lockService in context. Did you call initializeAdk()?");
|
|
3287
|
+
await lockService.unlock(this.objectUri, { lockHandle });
|
|
3288
|
+
}
|
|
3289
|
+
async activate() {
|
|
3290
|
+
await this.ctx.client.adt.activation.activate.post({}, { objectReferences: { objectReference: [{
|
|
3291
|
+
uri: this.objectUri,
|
|
3292
|
+
name: this.name
|
|
3293
|
+
}] } });
|
|
3294
|
+
return this;
|
|
3295
|
+
}
|
|
3296
|
+
/**
|
|
3297
|
+
* Get a SRVD (validates it exists by fetching source).
|
|
3298
|
+
*/
|
|
3299
|
+
static async get(name, ctx) {
|
|
3300
|
+
const obj = new AdkServiceDefinition(ctx ?? getGlobalContext(), name);
|
|
3301
|
+
await obj.getSource();
|
|
3302
|
+
return obj;
|
|
3303
|
+
}
|
|
3304
|
+
static async exists(name, ctx) {
|
|
3305
|
+
try {
|
|
3306
|
+
await AdkServiceDefinition.get(name, ctx);
|
|
3307
|
+
return true;
|
|
3308
|
+
} catch {
|
|
3309
|
+
return false;
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
/**
|
|
3313
|
+
* Create a new SRVD on SAP.
|
|
3314
|
+
*
|
|
3315
|
+
* POST /sap/bc/adt/ddic/srvd/sources?corrNr=...
|
|
3316
|
+
* Body matches the `srvd:source` envelope (extends
|
|
3317
|
+
* abapsource:AbapSourceMainObject).
|
|
3318
|
+
*/
|
|
3319
|
+
static async create(name, description, packageName, options, ctx) {
|
|
3320
|
+
const context = ctx ?? getGlobalContext();
|
|
3321
|
+
const nameU = name.toUpperCase();
|
|
3322
|
+
const pkgU = packageName.toUpperCase();
|
|
3323
|
+
await context.client.adt.ddic.srvd.sources.post(options?.transport ? { corrNr: options.transport } : {}, { source: {
|
|
3324
|
+
name: nameU,
|
|
3325
|
+
type: "SRVD/SRV",
|
|
3326
|
+
description,
|
|
3327
|
+
language: "EN",
|
|
3328
|
+
masterLanguage: "EN",
|
|
3329
|
+
responsible: pkgU,
|
|
3330
|
+
packageRef: {
|
|
3331
|
+
name: pkgU,
|
|
3332
|
+
type: "DEVC/K",
|
|
3333
|
+
uri: `/sap/bc/adt/packages/${pkgU.toLowerCase()}`
|
|
3334
|
+
}
|
|
3335
|
+
} });
|
|
3336
|
+
return new AdkServiceDefinition(context, nameU);
|
|
3337
|
+
}
|
|
3338
|
+
static async delete(name, options, ctx) {
|
|
3339
|
+
await (ctx ?? getGlobalContext()).client.adt.ddic.srvd.sources.delete(name.toUpperCase(), {
|
|
3340
|
+
...options?.transport ? { corrNr: options.transport } : {},
|
|
3341
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {}
|
|
3342
|
+
});
|
|
3343
|
+
}
|
|
3344
|
+
};
|
|
3345
|
+
|
|
3346
|
+
//#endregion
|
|
3347
|
+
//#region src/objects/repository/srvb/srvb.model.ts
|
|
3348
|
+
/**
|
|
3349
|
+
* SRVB - Service Binding (RAP)
|
|
3350
|
+
*
|
|
3351
|
+
* ADK object for ABAP RAP Service Bindings (SRVB). Unlike BDEF/SRVD,
|
|
3352
|
+
* SRVB is **metadata-only** — there is no source text. The binding
|
|
3353
|
+
* XML carries the service + protocol references.
|
|
3354
|
+
*
|
|
3355
|
+
* GET /sap/bc/adt/businessservices/bindings/<name>
|
|
3356
|
+
* POST /sap/bc/adt/businessservices/bindings
|
|
3357
|
+
* PUT /sap/bc/adt/businessservices/bindings/<name>
|
|
3358
|
+
* DELETE /sap/bc/adt/businessservices/bindings/<name>
|
|
3359
|
+
* POST /sap/bc/adt/businessservices/bindings/<name>/publishedstates (publish)
|
|
3360
|
+
* DELETE /sap/bc/adt/businessservices/bindings/<name>/publishedstates (unpublish)
|
|
3361
|
+
*
|
|
3362
|
+
* Lifecycle follows lock/activate patterns from sibling RAP objects.
|
|
3363
|
+
*/
|
|
3364
|
+
var AdkServiceBinding = class AdkServiceBinding {
|
|
3365
|
+
/** Static ADK kind marker — used by abapGit handler registry if needed. */
|
|
3366
|
+
static kind = "ServiceBinding";
|
|
3367
|
+
kind = AdkServiceBinding.kind;
|
|
3368
|
+
name;
|
|
3369
|
+
ctx;
|
|
3370
|
+
constructor(ctx, name) {
|
|
3371
|
+
this.ctx = ctx;
|
|
3372
|
+
this.name = name.toUpperCase();
|
|
3373
|
+
}
|
|
3374
|
+
get objectUri() {
|
|
3375
|
+
return `/sap/bc/adt/businessservices/bindings/${encodeURIComponent(this.name.toLowerCase())}`;
|
|
3376
|
+
}
|
|
3377
|
+
/** Placeholder description — full metadata requires additional SAP fetch */
|
|
3378
|
+
get description() {
|
|
3379
|
+
return this.name;
|
|
3380
|
+
}
|
|
3381
|
+
get contract() {
|
|
3382
|
+
return this.ctx.client.adt.businessservices.bindings;
|
|
3383
|
+
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Fetch the binding metadata document. SRVB has no source text;
|
|
3386
|
+
* `getSource()` returns an empty string for uniform CLI parity.
|
|
3387
|
+
*/
|
|
3388
|
+
async getMetadata() {
|
|
3389
|
+
return this.contract.get(this.name);
|
|
3390
|
+
}
|
|
3391
|
+
async getSource() {
|
|
3392
|
+
return "";
|
|
3393
|
+
}
|
|
3394
|
+
async lock(transport) {
|
|
3395
|
+
const lockService = this.ctx.lockService;
|
|
3396
|
+
if (!lockService) throw new Error("Lock not available: no lockService in context. Did you call initializeAdk()?");
|
|
3397
|
+
return lockService.lock(this.objectUri, {
|
|
3398
|
+
transport,
|
|
3399
|
+
objectName: this.name,
|
|
3400
|
+
objectType: "SRVB"
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3403
|
+
async unlock(lockHandle) {
|
|
3404
|
+
const lockService = this.ctx.lockService;
|
|
3405
|
+
if (!lockService) throw new Error("Unlock not available: no lockService in context. Did you call initializeAdk()?");
|
|
3406
|
+
await lockService.unlock(this.objectUri, { lockHandle });
|
|
3407
|
+
}
|
|
3408
|
+
async activate() {
|
|
3409
|
+
await this.ctx.client.adt.activation.activate.post({}, { objectReferences: { objectReference: [{
|
|
3410
|
+
uri: this.objectUri,
|
|
3411
|
+
name: this.name
|
|
3412
|
+
}] } });
|
|
3413
|
+
return this;
|
|
3414
|
+
}
|
|
3415
|
+
/**
|
|
3416
|
+
* Publish (activate) the binding in the SAP Gateway.
|
|
3417
|
+
* POST {basePath}/{name}/publishedstates
|
|
3418
|
+
*/
|
|
3419
|
+
async publish() {
|
|
3420
|
+
await this.contract.publish(this.name);
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Unpublish (deactivate) the binding.
|
|
3424
|
+
* DELETE {basePath}/{name}/publishedstates
|
|
3425
|
+
*/
|
|
3426
|
+
async unpublish() {
|
|
3427
|
+
await this.contract.unpublish(this.name);
|
|
3428
|
+
}
|
|
3429
|
+
/**
|
|
3430
|
+
* Get a SRVB (validates it exists by fetching metadata).
|
|
3431
|
+
*/
|
|
3432
|
+
static async get(name, ctx) {
|
|
3433
|
+
const obj = new AdkServiceBinding(ctx ?? getGlobalContext(), name);
|
|
3434
|
+
await obj.getMetadata();
|
|
3435
|
+
return obj;
|
|
3436
|
+
}
|
|
3437
|
+
static async exists(name, ctx) {
|
|
3438
|
+
try {
|
|
3439
|
+
await AdkServiceBinding.get(name, ctx);
|
|
3440
|
+
return true;
|
|
3441
|
+
} catch {
|
|
3442
|
+
return false;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
/**
|
|
3446
|
+
* Create a new SRVB on SAP.
|
|
3447
|
+
*
|
|
3448
|
+
* POST /sap/bc/adt/businessservices/bindings?corrNr=...
|
|
3449
|
+
* Body matches the `srvb:serviceBinding` envelope.
|
|
3450
|
+
*/
|
|
3451
|
+
static async create(name, description, packageName, options, ctx) {
|
|
3452
|
+
const context = ctx ?? getGlobalContext();
|
|
3453
|
+
const nameU = name.toUpperCase();
|
|
3454
|
+
const pkgU = packageName.toUpperCase();
|
|
3455
|
+
await context.client.adt.businessservices.bindings.post(options?.transport ? { corrNr: options.transport } : {}, { serviceBinding: {
|
|
3456
|
+
name: nameU,
|
|
3457
|
+
type: "SRVB/SVB",
|
|
3458
|
+
description,
|
|
3459
|
+
language: "EN",
|
|
3460
|
+
masterLanguage: "EN",
|
|
3461
|
+
responsible: pkgU,
|
|
3462
|
+
packageRef: {
|
|
3463
|
+
name: pkgU,
|
|
3464
|
+
type: "DEVC/K",
|
|
3465
|
+
uri: `/sap/bc/adt/packages/${pkgU.toLowerCase()}`
|
|
3466
|
+
}
|
|
3467
|
+
} });
|
|
3468
|
+
return new AdkServiceBinding(context, nameU);
|
|
3469
|
+
}
|
|
3470
|
+
static async delete(name, options, ctx) {
|
|
3471
|
+
await (ctx ?? getGlobalContext()).client.adt.businessservices.bindings.delete(name.toUpperCase(), {
|
|
3472
|
+
...options?.transport ? { corrNr: options.transport } : {},
|
|
3473
|
+
...options?.lockHandle ? { lockHandle: options.lockHandle } : {}
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
static async publish(name, ctx) {
|
|
3477
|
+
await new AdkServiceBinding(ctx ?? getGlobalContext(), name).publish();
|
|
3478
|
+
}
|
|
3479
|
+
static async unpublish(name, ctx) {
|
|
3480
|
+
await new AdkServiceBinding(ctx ?? getGlobalContext(), name).unpublish();
|
|
3481
|
+
}
|
|
3482
|
+
};
|
|
3483
|
+
|
|
3484
|
+
//#endregion
|
|
3485
|
+
//#region src/objects/cts/transport/transport-object.ts
|
|
3486
|
+
/**
|
|
3487
|
+
* Transport Object - wrapper for ABAP objects in a transport
|
|
3488
|
+
*/
|
|
3489
|
+
var AdkTransportObject = class {
|
|
3490
|
+
constructor(data) {
|
|
3491
|
+
this.data = data;
|
|
3492
|
+
}
|
|
3493
|
+
get pgmid() {
|
|
3494
|
+
return this.data.pgmid || "";
|
|
3495
|
+
}
|
|
3496
|
+
get type() {
|
|
3497
|
+
return this.data.type || "";
|
|
3498
|
+
}
|
|
3499
|
+
get name() {
|
|
3500
|
+
return this.data.name || "";
|
|
3501
|
+
}
|
|
3502
|
+
get wbtype() {
|
|
3503
|
+
return this.data.wbtype;
|
|
3504
|
+
}
|
|
3505
|
+
get objectInfo() {
|
|
3506
|
+
return this.data.obj_info;
|
|
3507
|
+
}
|
|
3508
|
+
get objectDescription() {
|
|
3509
|
+
return this.data.obj_desc;
|
|
3510
|
+
}
|
|
3511
|
+
get lockStatus() {
|
|
3512
|
+
return this.data.lock_status;
|
|
3513
|
+
}
|
|
3514
|
+
get uri() {
|
|
3515
|
+
return this.data.uri;
|
|
3516
|
+
}
|
|
3517
|
+
/** Full object key (PGMID/TYPE/NAME) */
|
|
3518
|
+
get key() {
|
|
3519
|
+
return `${this.pgmid}/${this.type}/${this.name}`;
|
|
3520
|
+
}
|
|
3521
|
+
/** Raw schema data */
|
|
3522
|
+
get raw() {
|
|
3523
|
+
return this.data;
|
|
3524
|
+
}
|
|
3525
|
+
};
|
|
3526
|
+
|
|
3527
|
+
//#endregion
|
|
3528
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/decorateMetadata.js
|
|
3529
|
+
function __decorateMetadata(k, v) {
|
|
3530
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
//#endregion
|
|
3534
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/decorate.js
|
|
3535
|
+
function __decorate(decorators, target, key, desc) {
|
|
3536
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3537
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3538
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
3539
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
//#endregion
|
|
3543
|
+
//#region src/objects/cts/transport/transport.ts
|
|
3544
|
+
const STATUS_TEXT = {
|
|
3545
|
+
D: "Modifiable",
|
|
3546
|
+
R: "Released",
|
|
3547
|
+
N: "Not released"
|
|
3548
|
+
};
|
|
3549
|
+
function asArray$1(val) {
|
|
3550
|
+
if (!val) return [];
|
|
3551
|
+
return Array.isArray(val) ? val : [val];
|
|
3552
|
+
}
|
|
3553
|
+
let cachedConfigUri;
|
|
3554
|
+
async function getConfigUri(ctx) {
|
|
3555
|
+
if (cachedConfigUri) return cachedConfigUri;
|
|
3556
|
+
const configs = (await ctx.client.adt.cts.transportrequests.searchconfiguration.configurations.get())?.configurations;
|
|
3557
|
+
if (!configs) throw new Error("No search configuration found");
|
|
3558
|
+
const configArray = Array.isArray(configs) ? configs : [configs];
|
|
3559
|
+
if (configArray.length === 0) throw new Error("No search configuration found");
|
|
3560
|
+
const uri = configArray[0]?.link?.href;
|
|
3561
|
+
if (!uri) throw new Error("No search configuration URI found");
|
|
3562
|
+
cachedConfigUri = uri;
|
|
3563
|
+
return uri;
|
|
3564
|
+
}
|
|
3565
|
+
/** Clear config cache (for testing) */
|
|
3566
|
+
function clearConfigCache() {
|
|
3567
|
+
cachedConfigUri = void 0;
|
|
3568
|
+
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Collect all requests from the transport response structure
|
|
3571
|
+
* The schema has requests in multiple places:
|
|
3572
|
+
* - workbench.modifiable.request[]
|
|
3573
|
+
* - workbench.relstarted.request[]
|
|
3574
|
+
* - workbench.released.request[]
|
|
3575
|
+
* - workbench.target[].modifiable.request[]
|
|
3576
|
+
* - customizing.* (same structure)
|
|
3577
|
+
*/
|
|
3578
|
+
function collectAllRequests(result) {
|
|
3579
|
+
const requests = [];
|
|
3580
|
+
if (!result || typeof result !== "object") return requests;
|
|
3581
|
+
const data = result;
|
|
3582
|
+
const extractFromContainer = (container) => {
|
|
3583
|
+
if (!container || typeof container !== "object") return;
|
|
3584
|
+
const reqs = container.request;
|
|
3585
|
+
if (reqs) {
|
|
3586
|
+
const reqArray = Array.isArray(reqs) ? reqs : [reqs];
|
|
3587
|
+
requests.push(...reqArray);
|
|
3588
|
+
}
|
|
3589
|
+
};
|
|
3590
|
+
const processSection = (section) => {
|
|
3591
|
+
if (!section || typeof section !== "object") return;
|
|
3592
|
+
const s = section;
|
|
3593
|
+
extractFromContainer(s.modifiable);
|
|
3594
|
+
extractFromContainer(s.relstarted);
|
|
3595
|
+
extractFromContainer(s.released);
|
|
3596
|
+
const targets = s.target;
|
|
3597
|
+
if (targets) {
|
|
3598
|
+
const targetArray = Array.isArray(targets) ? targets : [targets];
|
|
3599
|
+
for (const target of targetArray) if (target && typeof target === "object") {
|
|
3600
|
+
const t = target;
|
|
3601
|
+
extractFromContainer(t.modifiable);
|
|
3602
|
+
extractFromContainer(t.relstarted);
|
|
3603
|
+
extractFromContainer(t.released);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3607
|
+
processSection(data.workbench);
|
|
3608
|
+
processSection(data.customizing);
|
|
3609
|
+
return requests;
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Transport Request - main transport object
|
|
3613
|
+
*
|
|
3614
|
+
* Handles both requests (K) and can be extended for tasks (T).
|
|
3615
|
+
* Uses ADT contracts directly - no intermediate service layer.
|
|
3616
|
+
*/
|
|
3617
|
+
var AdkTransportRequest = class AdkTransportRequest extends AdkObject {
|
|
3618
|
+
kind = TransportRequest;
|
|
3619
|
+
_tasks;
|
|
3620
|
+
_objects;
|
|
3621
|
+
constructor(ctx, dataOrNumber) {
|
|
3622
|
+
if (typeof dataOrNumber === "string") super(ctx, dataOrNumber);
|
|
3623
|
+
else super(ctx, {
|
|
3624
|
+
name: dataOrNumber.name || dataOrNumber.request?.number || "",
|
|
3625
|
+
type: dataOrNumber.object_type === "T" ? "RQTQ" : "RQRQ",
|
|
3626
|
+
response: dataOrNumber
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
3629
|
+
get response() {
|
|
3630
|
+
return this.dataSync.response;
|
|
3631
|
+
}
|
|
3632
|
+
/** The item's data - override in subclass for different source */
|
|
3633
|
+
get itemData() {
|
|
3634
|
+
return this.response.request || {};
|
|
3635
|
+
}
|
|
3636
|
+
get objectUri() {
|
|
3637
|
+
return `/sap/bc/adt/cts/transportrequests/${this.name}`;
|
|
3638
|
+
}
|
|
3639
|
+
async load() {
|
|
3640
|
+
const response = (await this.ctx.client.adt.cts.transportrequests.get(this.name)).root;
|
|
3641
|
+
this.setData({
|
|
3642
|
+
name: this.name,
|
|
3643
|
+
type: response.object_type === "T" ? "RQTQ" : "RQRQ",
|
|
3644
|
+
response
|
|
3645
|
+
});
|
|
3646
|
+
return this;
|
|
3647
|
+
}
|
|
3648
|
+
get itemType() {
|
|
3649
|
+
return this.response.object_type === "T" ? "task" : "request";
|
|
3650
|
+
}
|
|
3651
|
+
get number() {
|
|
3652
|
+
return this.name;
|
|
3653
|
+
}
|
|
3654
|
+
get owner() {
|
|
3655
|
+
return this.itemData.owner || "";
|
|
3656
|
+
}
|
|
3657
|
+
get description() {
|
|
3658
|
+
return this.itemData.desc || "";
|
|
3659
|
+
}
|
|
3660
|
+
get status() {
|
|
3661
|
+
return this.itemData.status || "D";
|
|
3662
|
+
}
|
|
3663
|
+
get statusText() {
|
|
3664
|
+
return this.itemData.status_text || STATUS_TEXT[this.status] || this.status;
|
|
3665
|
+
}
|
|
3666
|
+
get target() {
|
|
3667
|
+
return this.itemData.target || "";
|
|
3668
|
+
}
|
|
3669
|
+
get targetDescription() {
|
|
3670
|
+
return this.itemData.target_desc || "";
|
|
3671
|
+
}
|
|
3672
|
+
get uri() {
|
|
3673
|
+
return this.itemData.uri || this.objectUri;
|
|
3674
|
+
}
|
|
3675
|
+
get parent() {
|
|
3676
|
+
return this.itemData.parent || "";
|
|
3677
|
+
}
|
|
3678
|
+
get lastChangedAt() {
|
|
3679
|
+
return parseSapTimestamp(this.itemData.lastchanged_timestamp);
|
|
3680
|
+
}
|
|
3681
|
+
/** Tasks belonging to this transport request */
|
|
3682
|
+
get tasks() {
|
|
3683
|
+
if (!this._tasks) {
|
|
3684
|
+
const taskData = this.itemData.task;
|
|
3685
|
+
this._tasks = asArray$1(taskData).map((t) => {
|
|
3686
|
+
const { request, task: _existingTasks, ...rootProps } = this.response;
|
|
3687
|
+
const { task: _nestedTasks, ...requestWithoutTasks } = request || {};
|
|
3688
|
+
const taskResponse = {
|
|
3689
|
+
...rootProps,
|
|
3690
|
+
object_type: "T",
|
|
3691
|
+
name: t.number || "",
|
|
3692
|
+
request: request ? requestWithoutTasks : void 0,
|
|
3693
|
+
task: [t]
|
|
3694
|
+
};
|
|
3695
|
+
return new AdkTransportTask(this.ctx, taskResponse, this);
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
return this._tasks;
|
|
3699
|
+
}
|
|
3700
|
+
/**
|
|
3701
|
+
* All objects in this request (aggregated from tasks)
|
|
3702
|
+
* Override in AdkTransportTask for direct objects
|
|
3703
|
+
*/
|
|
3704
|
+
get objects() {
|
|
3705
|
+
if (!this._objects) {
|
|
3706
|
+
const directObjects = asArray$1(this.itemData.abap_object).map((o) => new AdkTransportObject(o));
|
|
3707
|
+
const taskObjects = this.tasks.flatMap((t) => t.objects);
|
|
3708
|
+
this._objects = [...directObjects, ...taskObjects];
|
|
3709
|
+
}
|
|
3710
|
+
return this._objects;
|
|
3711
|
+
}
|
|
3712
|
+
async release() {
|
|
3713
|
+
try {
|
|
3714
|
+
await this.ctx.client.adt.cts.transportrequests.useraction.release(this.number, { root: { useraction: "release" } });
|
|
3715
|
+
this.itemData.status = "R";
|
|
3716
|
+
this.itemData.status_text = "Released";
|
|
3717
|
+
return { success: true };
|
|
3718
|
+
} catch (error) {
|
|
3719
|
+
return {
|
|
3720
|
+
success: false,
|
|
3721
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
async releaseAll() {
|
|
3726
|
+
for (const task of this.tasks) if (task.status !== "R") {
|
|
3727
|
+
const result = await task.release();
|
|
3728
|
+
if (!result.success) return {
|
|
3729
|
+
success: false,
|
|
3730
|
+
message: `Failed to release task ${task.number}: ${result.message}`
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
return this.release();
|
|
3734
|
+
}
|
|
3735
|
+
async update(changes) {
|
|
3736
|
+
await this.ctx.client.adt.cts.transportrequests.put(this.number, {
|
|
3737
|
+
desc: changes.description ?? this.description,
|
|
3738
|
+
target: changes.target ?? this.target
|
|
3739
|
+
});
|
|
3740
|
+
if (changes.description !== void 0) this.itemData.desc = changes.description;
|
|
3741
|
+
if (changes.target !== void 0) this.itemData.target = changes.target;
|
|
3742
|
+
}
|
|
3743
|
+
async delete() {
|
|
3744
|
+
await this.ctx.client.adt.cts.transportrequests.delete(this.number);
|
|
3745
|
+
}
|
|
3746
|
+
/**
|
|
3747
|
+
* Reassign ownership of this transport (and optionally all tasks) to a new user.
|
|
3748
|
+
*
|
|
3749
|
+
* @param newOwner - SAP username to become the new owner
|
|
3750
|
+
* @param recursive - When true, also reassign all modifiable tasks
|
|
3751
|
+
*/
|
|
3752
|
+
async reassign(newOwner, recursive = false) {
|
|
3753
|
+
await this.ctx.client.adt.cts.transportrequests.useraction.reassign(this.number, {
|
|
3754
|
+
targetUser: newOwner,
|
|
3755
|
+
recursive
|
|
3756
|
+
}, { root: {
|
|
3757
|
+
useraction: "changeowner",
|
|
3758
|
+
targetuser: newOwner
|
|
3759
|
+
} });
|
|
3760
|
+
this.itemData.owner = newOwner;
|
|
3761
|
+
if (recursive) {
|
|
3762
|
+
for (const task of this.tasks) if (task.status !== "R") await task.reassign(newOwner);
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
/**
|
|
3766
|
+
* Create a new transport request
|
|
3767
|
+
*
|
|
3768
|
+
* @param options - Transport creation options
|
|
3769
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
3770
|
+
*/
|
|
3771
|
+
static async create(options, ctx) {
|
|
3772
|
+
const context = ctx ?? getGlobalContext();
|
|
3773
|
+
let owner = options.owner;
|
|
3774
|
+
if (!owner) owner = await AdkTransportRequest.getCurrentUser(context);
|
|
3775
|
+
const response = await context.client.adt.cts.transportrequests.useraction.create({
|
|
3776
|
+
description: options.description,
|
|
3777
|
+
type: options.type,
|
|
3778
|
+
target: options.target,
|
|
3779
|
+
project: options.project,
|
|
3780
|
+
owner
|
|
3781
|
+
}, { root: {
|
|
3782
|
+
useraction: "newrequest",
|
|
3783
|
+
request: {
|
|
3784
|
+
desc: options.description,
|
|
3785
|
+
type: options.type || "K",
|
|
3786
|
+
target: options.target || "LOCAL",
|
|
3787
|
+
cts_project: options.project || "",
|
|
3788
|
+
task: [{ owner }]
|
|
3789
|
+
}
|
|
3790
|
+
} });
|
|
3791
|
+
const number = response?.root?.request?.number ?? response?.root?.name ?? void 0;
|
|
3792
|
+
if (!number) throw new Error("Failed to create transport - no number returned");
|
|
3793
|
+
return AdkTransportRequest.get(number, context);
|
|
3794
|
+
}
|
|
3795
|
+
/**
|
|
3796
|
+
* Get a transport request by number
|
|
3797
|
+
*
|
|
3798
|
+
* @param number - Transport number (e.g., 'S0DK900001')
|
|
3799
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
3800
|
+
*/
|
|
3801
|
+
static async get(number, ctx) {
|
|
3802
|
+
const context = ctx ?? getGlobalContext();
|
|
3803
|
+
const response = (await context.client.adt.cts.transportrequests.get(number)).root;
|
|
3804
|
+
if (response.object_type === "T") return new AdkTransportTask(context, response);
|
|
3805
|
+
return new AdkTransportRequest(context, response);
|
|
3806
|
+
}
|
|
3807
|
+
/**
|
|
3808
|
+
* List all transports for the current user
|
|
3809
|
+
*
|
|
3810
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
3811
|
+
*/
|
|
3812
|
+
static async list(ctx) {
|
|
3813
|
+
const context = ctx ?? getGlobalContext();
|
|
3814
|
+
const configUri = await getConfigUri(context);
|
|
3815
|
+
return collectAllRequests(await context.client.adt.cts.transportrequests.list({
|
|
3816
|
+
targets: "true",
|
|
3817
|
+
configUri
|
|
3818
|
+
})).map((r) => new AdkTransportRequest(context, {
|
|
3819
|
+
name: r.number || "",
|
|
3820
|
+
object_type: "K",
|
|
3821
|
+
request: {
|
|
3822
|
+
number: r.number,
|
|
3823
|
+
owner: r.owner,
|
|
3824
|
+
desc: r.desc,
|
|
3825
|
+
status: r.status,
|
|
3826
|
+
uri: r.uri,
|
|
3827
|
+
task: r.task,
|
|
3828
|
+
abap_object: r.abap_object
|
|
3829
|
+
}
|
|
3830
|
+
}));
|
|
3831
|
+
}
|
|
3832
|
+
/**
|
|
3833
|
+
* Get current user from the system metadata
|
|
3834
|
+
*
|
|
3835
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
3836
|
+
*/
|
|
3837
|
+
static async getCurrentUser(ctx) {
|
|
3838
|
+
const user = ((await (ctx ?? getGlobalContext()).client.adt.cts.transportrequests.searchconfiguration.metadata.get())?.configuration?.properties?.property ?? []).find((p) => p.key === "User")?.$value?.trim();
|
|
3839
|
+
if (user) return user;
|
|
3840
|
+
throw new Error("Could not detect current user from metadata response");
|
|
3841
|
+
}
|
|
3842
|
+
};
|
|
3843
|
+
__decorate([
|
|
3844
|
+
requiresLock(),
|
|
3845
|
+
__decorateMetadata("design:type", Function),
|
|
3846
|
+
__decorateMetadata("design:paramtypes", [Object]),
|
|
3847
|
+
__decorateMetadata("design:returntype", Promise)
|
|
3848
|
+
], AdkTransportRequest.prototype, "update", null);
|
|
3849
|
+
/**
|
|
3850
|
+
* Transport Task - child of a transport request
|
|
3851
|
+
*
|
|
3852
|
+
* Minimal overrides:
|
|
3853
|
+
* - itemData: from response.task instead of response.request
|
|
3854
|
+
* - objects: direct from task, not aggregated
|
|
3855
|
+
* - request: parent reference
|
|
3856
|
+
* - No tasks, no CRUD
|
|
3857
|
+
*/
|
|
3858
|
+
var AdkTransportTask = class AdkTransportTask extends AdkTransportRequest {
|
|
3859
|
+
_parentRequest;
|
|
3860
|
+
parentRef;
|
|
3861
|
+
constructor(ctx, dataOrNumber, parentRef) {
|
|
3862
|
+
super(ctx, dataOrNumber);
|
|
3863
|
+
this.parentRef = parentRef;
|
|
3864
|
+
}
|
|
3865
|
+
get itemData() {
|
|
3866
|
+
const rootTask = this.response.task;
|
|
3867
|
+
if (rootTask) {
|
|
3868
|
+
const taskArr = Array.isArray(rootTask) ? rootTask : [rootTask];
|
|
3869
|
+
const found = taskArr.find((t) => t.number === this.name) || taskArr[0];
|
|
3870
|
+
if (found) return found;
|
|
3871
|
+
}
|
|
3872
|
+
return this.response.request || {};
|
|
3873
|
+
}
|
|
3874
|
+
get tasks() {
|
|
3875
|
+
return [];
|
|
3876
|
+
}
|
|
3877
|
+
get objects() {
|
|
3878
|
+
if (!this._objects) {
|
|
3879
|
+
const taskData = this.itemData;
|
|
3880
|
+
this._objects = asArray$1(taskData?.abap_object).map((o) => new AdkTransportObject(o));
|
|
3881
|
+
}
|
|
3882
|
+
return this._objects;
|
|
3883
|
+
}
|
|
3884
|
+
/** Parent transport request */
|
|
3885
|
+
get request() {
|
|
3886
|
+
if (this.parentRef) return this.parentRef;
|
|
3887
|
+
if (!this._parentRequest) {
|
|
3888
|
+
const parentNumber = this.response.request?.number || this.parent;
|
|
3889
|
+
this._parentRequest = new AdkTransportRequest(this.ctx, {
|
|
3890
|
+
...this.response,
|
|
3891
|
+
name: parentNumber,
|
|
3892
|
+
object_type: "K"
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
return this._parentRequest;
|
|
3896
|
+
}
|
|
3897
|
+
/**
|
|
3898
|
+
* Get a transport task by number
|
|
3899
|
+
*
|
|
3900
|
+
* @param number - Task number (e.g., 'S0DK900001')
|
|
3901
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
3902
|
+
*/
|
|
3903
|
+
static async get(number, ctx) {
|
|
3904
|
+
const context = ctx ?? getGlobalContext();
|
|
3905
|
+
return new AdkTransportTask(context, (await context.client.adt.cts.transportrequests.get(number)).root);
|
|
3906
|
+
}
|
|
3907
|
+
};
|
|
3908
|
+
|
|
3909
|
+
//#endregion
|
|
3910
|
+
//#region src/objects/cts/transport-import.ts
|
|
3911
|
+
/**
|
|
3912
|
+
* Helper to normalize array/single value to array
|
|
3913
|
+
*/
|
|
3914
|
+
function asArray(val) {
|
|
3915
|
+
if (!val) return [];
|
|
3916
|
+
return Array.isArray(val) ? val : [val];
|
|
3917
|
+
}
|
|
3918
|
+
/**
|
|
3919
|
+
* Check if a value matches a selector dimension.
|
|
3920
|
+
*
|
|
3921
|
+
* - If `expected` is undefined → no filter (always matches).
|
|
3922
|
+
* - If `expected` is '*' → matches any truthy value.
|
|
3923
|
+
* - If `expected` is an array → value must be in the array.
|
|
3924
|
+
* - Otherwise exact match (case-insensitive).
|
|
3925
|
+
*/
|
|
3926
|
+
function matchesDimension(value, expected) {
|
|
3927
|
+
if (expected === void 0) return true;
|
|
3928
|
+
if (Array.isArray(expected)) return expected.some((e) => e === "*" ? !!value : e.toUpperCase() === value.toUpperCase());
|
|
3929
|
+
if (expected === "*") return !!value;
|
|
3930
|
+
return expected.toUpperCase() === value.toUpperCase();
|
|
3931
|
+
}
|
|
3932
|
+
/**
|
|
3933
|
+
* Check if an object ref matches all dimensions of a {@link TransportObjectSelector}.
|
|
3934
|
+
*
|
|
3935
|
+
* Exported so that consumers (e.g. import service) can reuse the same logic
|
|
3936
|
+
* without duplicating it.
|
|
3937
|
+
*/
|
|
3938
|
+
function matchesSelector(obj, selector) {
|
|
3939
|
+
return matchesDimension(obj.objFunc, selector.objFunc) && matchesDimension(obj.pgmid, selector.pgmid) && matchesDimension(obj.type, selector.type);
|
|
3940
|
+
}
|
|
3941
|
+
/**
|
|
3942
|
+
* Transport object reference - lightweight wrapper
|
|
3943
|
+
*
|
|
3944
|
+
* Contains metadata about an object in the transport.
|
|
3945
|
+
* Call load() to get the full ADK object.
|
|
3946
|
+
*/
|
|
3947
|
+
var AdkTransportObjectRef = class {
|
|
3948
|
+
constructor(ctx, data) {
|
|
3949
|
+
this.ctx = ctx;
|
|
3950
|
+
this.data = data;
|
|
3951
|
+
}
|
|
3952
|
+
/** Program ID (R3TR, LIMU, etc.) */
|
|
3953
|
+
get pgmid() {
|
|
3954
|
+
return this.data.pgmid ?? "";
|
|
3955
|
+
}
|
|
3956
|
+
/** Object type (CLAS, INTF, DOMA, etc.) */
|
|
3957
|
+
get type() {
|
|
3958
|
+
return this.data.type ?? "";
|
|
3959
|
+
}
|
|
3960
|
+
/** Object name */
|
|
3961
|
+
get name() {
|
|
3962
|
+
return this.data.name ?? "";
|
|
3963
|
+
}
|
|
3964
|
+
/** Workbench type (more specific type) */
|
|
3965
|
+
get wbtype() {
|
|
3966
|
+
return this.data.wbtype;
|
|
3967
|
+
}
|
|
3968
|
+
/** Object URI for direct access */
|
|
3969
|
+
get uri() {
|
|
3970
|
+
return this.data.uri;
|
|
3971
|
+
}
|
|
3972
|
+
/** Object description */
|
|
3973
|
+
get description() {
|
|
3974
|
+
return this.data.obj_desc;
|
|
3975
|
+
}
|
|
3976
|
+
/**
|
|
3977
|
+
* Object function code (SAP OBJFUNC).
|
|
3978
|
+
* 'D' = marked for deletion, 'K' = key entry, '' = regular modification.
|
|
3979
|
+
*/
|
|
3980
|
+
get objFunc() {
|
|
3981
|
+
return this.data.obj_func ?? "";
|
|
3982
|
+
}
|
|
3983
|
+
/** Whether this object is marked for deletion (obj_func === 'D') */
|
|
3984
|
+
get isDeleted() {
|
|
3985
|
+
return this.data.obj_func === "D";
|
|
3986
|
+
}
|
|
3987
|
+
/** Full object key (PGMID/TYPE/NAME) */
|
|
3988
|
+
get key() {
|
|
3989
|
+
return `${this.pgmid}/${this.type}/${this.name}`;
|
|
3990
|
+
}
|
|
3991
|
+
/**
|
|
3992
|
+
* Load the full ADK object
|
|
3993
|
+
*
|
|
3994
|
+
* Uses the ADK factory to create the appropriate object type
|
|
3995
|
+
* (AdkClass, AdkInterface, AdkPackage, etc.)
|
|
3996
|
+
*
|
|
3997
|
+
* @returns The loaded ADK object, or undefined if type not supported
|
|
3998
|
+
*/
|
|
3999
|
+
async load() {
|
|
4000
|
+
const { createAdk } = await import("./factory-DtyrTGlN.mjs").then((n) => n.i);
|
|
4001
|
+
const adk = createAdk(this.ctx.client);
|
|
4002
|
+
try {
|
|
4003
|
+
const obj = adk.get(this.name, this.type);
|
|
4004
|
+
if ("load" in obj && typeof obj.load === "function") await obj.load();
|
|
4005
|
+
return obj;
|
|
4006
|
+
} catch {
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
/** Raw data from API */
|
|
4011
|
+
get raw() {
|
|
4012
|
+
return this.data;
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
4015
|
+
/**
|
|
4016
|
+
* Transport task reference
|
|
4017
|
+
*/
|
|
4018
|
+
var AdkTransportTaskRef = class {
|
|
4019
|
+
constructor(ctx, data) {
|
|
4020
|
+
this.ctx = ctx;
|
|
4021
|
+
this.data = data;
|
|
4022
|
+
}
|
|
4023
|
+
get number() {
|
|
4024
|
+
return this.data.number ?? "";
|
|
4025
|
+
}
|
|
4026
|
+
get owner() {
|
|
4027
|
+
return this.data.owner ?? "";
|
|
4028
|
+
}
|
|
4029
|
+
get description() {
|
|
4030
|
+
return this.data.desc ?? "";
|
|
4031
|
+
}
|
|
4032
|
+
get status() {
|
|
4033
|
+
return this.data.status ?? "";
|
|
4034
|
+
}
|
|
4035
|
+
get statusText() {
|
|
4036
|
+
return this.data.status_text ?? "";
|
|
4037
|
+
}
|
|
4038
|
+
/** Objects in this task */
|
|
4039
|
+
get objects() {
|
|
4040
|
+
return asArray(this.data.abap_object).map((obj) => new AdkTransportObjectRef(this.ctx, obj));
|
|
4041
|
+
}
|
|
4042
|
+
};
|
|
4043
|
+
/**
|
|
4044
|
+
* AdkTransport - Transport request for import operations
|
|
4045
|
+
*
|
|
4046
|
+
* Simple, focused API for importing transport objects:
|
|
4047
|
+
*
|
|
4048
|
+
* @example
|
|
4049
|
+
* ```typescript
|
|
4050
|
+
* const transport = await AdkTransport.get('DEVK900001');
|
|
4051
|
+
*
|
|
4052
|
+
* for (const objRef of transport.objects) {
|
|
4053
|
+
* console.log(`${objRef.type} ${objRef.name}`);
|
|
4054
|
+
* const adkObject = await objRef.load();
|
|
4055
|
+
* if (adkObject) {
|
|
4056
|
+
* // Serialize with plugin
|
|
4057
|
+
* await plugin.serialize(adkObject);
|
|
4058
|
+
* }
|
|
4059
|
+
* }
|
|
4060
|
+
* ```
|
|
4061
|
+
*/
|
|
4062
|
+
var AdkTransport = class AdkTransport {
|
|
4063
|
+
_objects;
|
|
4064
|
+
_tasks;
|
|
4065
|
+
constructor(ctx, data) {
|
|
4066
|
+
this.ctx = ctx;
|
|
4067
|
+
this.data = data;
|
|
4068
|
+
}
|
|
4069
|
+
/** Transport number */
|
|
4070
|
+
get number() {
|
|
4071
|
+
return this.data.name ?? this.data.request?.number ?? "";
|
|
4072
|
+
}
|
|
4073
|
+
/** Transport description */
|
|
4074
|
+
get description() {
|
|
4075
|
+
return this.data.request?.desc ?? "";
|
|
4076
|
+
}
|
|
4077
|
+
/** Transport owner */
|
|
4078
|
+
get owner() {
|
|
4079
|
+
return this.data.request?.owner ?? "";
|
|
4080
|
+
}
|
|
4081
|
+
/** Transport status (D=Modifiable, R=Released) */
|
|
4082
|
+
get status() {
|
|
4083
|
+
return this.data.request?.status ?? "";
|
|
4084
|
+
}
|
|
4085
|
+
/** Transport status text */
|
|
4086
|
+
get statusText() {
|
|
4087
|
+
return this.data.request?.status_text ?? "";
|
|
4088
|
+
}
|
|
4089
|
+
/** Transport target system */
|
|
4090
|
+
get target() {
|
|
4091
|
+
return this.data.request?.target ?? "";
|
|
4092
|
+
}
|
|
4093
|
+
/** Object type (K=Request, T=Task) */
|
|
4094
|
+
get objectType() {
|
|
4095
|
+
return this.data.object_type ?? "K";
|
|
4096
|
+
}
|
|
4097
|
+
/** Tasks belonging to this transport */
|
|
4098
|
+
get tasks() {
|
|
4099
|
+
if (!this._tasks) {
|
|
4100
|
+
const requestTasks = asArray(this.data.request?.task);
|
|
4101
|
+
const rootTasks = asArray(this.data.task);
|
|
4102
|
+
this._tasks = [...requestTasks, ...rootTasks].map((t) => new AdkTransportTaskRef(this.ctx, t));
|
|
4103
|
+
}
|
|
4104
|
+
return this._tasks;
|
|
4105
|
+
}
|
|
4106
|
+
/**
|
|
4107
|
+
* All objects in this transport
|
|
4108
|
+
*
|
|
4109
|
+
* Collects objects from:
|
|
4110
|
+
* - Direct objects on request
|
|
4111
|
+
* - Objects from all tasks
|
|
4112
|
+
* - all_objects container (if present)
|
|
4113
|
+
*/
|
|
4114
|
+
get objects() {
|
|
4115
|
+
if (!this._objects) this._objects = this.collectObjects();
|
|
4116
|
+
return this._objects;
|
|
4117
|
+
}
|
|
4118
|
+
collectObjects() {
|
|
4119
|
+
const objects = [];
|
|
4120
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4121
|
+
const addObject = (obj) => {
|
|
4122
|
+
const key = `${obj.pgmid}/${obj.type}/${obj.name}`;
|
|
4123
|
+
if (!seen.has(key)) {
|
|
4124
|
+
seen.add(key);
|
|
4125
|
+
objects.push(new AdkTransportObjectRef(this.ctx, obj));
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
4128
|
+
for (const obj of asArray(this.data.request?.abap_object)) addObject(obj);
|
|
4129
|
+
for (const obj of asArray(this.data.request?.all_objects?.abap_object)) addObject(obj);
|
|
4130
|
+
for (const task of this.tasks) for (const objRef of task.objects) addObject(objRef.raw);
|
|
4131
|
+
return objects;
|
|
4132
|
+
}
|
|
4133
|
+
/**
|
|
4134
|
+
* Get objects filtered by type
|
|
4135
|
+
*
|
|
4136
|
+
* @param types - Object types to include (e.g., ['CLAS', 'INTF'])
|
|
4137
|
+
*/
|
|
4138
|
+
getObjectsByType(...types) {
|
|
4139
|
+
const typeSet = new Set(types.map((t) => t.toUpperCase()));
|
|
4140
|
+
return this.objects.filter((obj) => typeSet.has(obj.type.toUpperCase()));
|
|
4141
|
+
}
|
|
4142
|
+
/**
|
|
4143
|
+
* Get objects filtered by a {@link TransportObjectSelector}.
|
|
4144
|
+
*
|
|
4145
|
+
* All dimensions specified in the selector are ANDed together.
|
|
4146
|
+
*
|
|
4147
|
+
* @example
|
|
4148
|
+
* ```typescript
|
|
4149
|
+
* // Get only deletion objects with pgmid=R3TR
|
|
4150
|
+
* const deleted = transport.getObjectsBySelector({ objFunc: 'D', pgmid: 'R3TR' });
|
|
4151
|
+
* ```
|
|
4152
|
+
*/
|
|
4153
|
+
getObjectsBySelector(selector) {
|
|
4154
|
+
return this.objects.filter((obj) => matchesSelector(obj, selector));
|
|
4155
|
+
}
|
|
4156
|
+
/**
|
|
4157
|
+
* Convenience accessor for objects marked for deletion (obj_func === 'D').
|
|
4158
|
+
*
|
|
4159
|
+
* Equivalent to `getObjectsBySelector({ objFunc: 'D' })`.
|
|
4160
|
+
*/
|
|
4161
|
+
get deletionObjects() {
|
|
4162
|
+
return this.getObjectsBySelector({ objFunc: "D" });
|
|
4163
|
+
}
|
|
4164
|
+
/**
|
|
4165
|
+
* Get unique object types in this transport
|
|
4166
|
+
*/
|
|
4167
|
+
getObjectTypes() {
|
|
4168
|
+
const types = new Set(this.objects.map((obj) => obj.type));
|
|
4169
|
+
return Array.from(types).sort();
|
|
4170
|
+
}
|
|
4171
|
+
/**
|
|
4172
|
+
* Get object count by type
|
|
4173
|
+
*/
|
|
4174
|
+
getObjectCountByType() {
|
|
4175
|
+
const counts = {};
|
|
4176
|
+
for (const obj of this.objects) counts[obj.type] = (counts[obj.type] || 0) + 1;
|
|
4177
|
+
return counts;
|
|
4178
|
+
}
|
|
4179
|
+
/** Raw API response (unwrapped) */
|
|
4180
|
+
get raw() {
|
|
4181
|
+
return this.data;
|
|
4182
|
+
}
|
|
4183
|
+
/**
|
|
4184
|
+
* Get a transport by number
|
|
4185
|
+
*
|
|
4186
|
+
* @param number - Transport number (e.g., 'DEVK900001')
|
|
4187
|
+
* @param ctx - Optional ADK context (uses global context if not provided)
|
|
4188
|
+
*
|
|
4189
|
+
* @example
|
|
4190
|
+
* ```typescript
|
|
4191
|
+
* const transport = await AdkTransport.get('DEVK900001');
|
|
4192
|
+
* console.log(`${transport.description} - ${transport.objects.length} objects`);
|
|
4193
|
+
* ```
|
|
4194
|
+
*/
|
|
4195
|
+
static async get(number, ctx) {
|
|
4196
|
+
const context = ctx ?? getGlobalContext();
|
|
4197
|
+
return new AdkTransport(context, (await context.client.adt.cts.transportrequests.get(number)).root);
|
|
4198
|
+
}
|
|
4199
|
+
/**
|
|
4200
|
+
* Load multiple transports and merge their objects into a single view.
|
|
4201
|
+
*
|
|
4202
|
+
* De-duplication key is `pgmid/type/name`. When the same key appears in
|
|
4203
|
+
* multiple transports the **first** occurrence wins (first transport in the
|
|
4204
|
+
* `numbers` array). The source transport number is available via
|
|
4205
|
+
* `AdkTransportObjectRef.sourceTransport`.
|
|
4206
|
+
*
|
|
4207
|
+
* @param numbers - Transport numbers to load and merge
|
|
4208
|
+
* @param ctx - Optional ADK context
|
|
4209
|
+
*
|
|
4210
|
+
* @example
|
|
4211
|
+
* ```typescript
|
|
4212
|
+
* const merged = await AdkTransport.merge(['DEVK900001', 'DEVK900002']);
|
|
4213
|
+
* const deletions = merged.getObjectsBySelector({ objFunc: 'D' });
|
|
4214
|
+
* ```
|
|
4215
|
+
*/
|
|
4216
|
+
static async merge(numbers, ctx) {
|
|
4217
|
+
return new MergedTransportView(await Promise.all(numbers.map((n) => AdkTransport.get(n, ctx))));
|
|
4218
|
+
}
|
|
4219
|
+
};
|
|
4220
|
+
function getFilteredObjects(transport, selector) {
|
|
4221
|
+
return Object.keys(selector).length > 0 ? transport.getObjectsBySelector(selector) : transport.objects;
|
|
4222
|
+
}
|
|
4223
|
+
function addResolvedObject(object, sourceTransport, objects, sourceTransportMap, seenKeys) {
|
|
4224
|
+
if (seenKeys && seenKeys.has(object.key)) return;
|
|
4225
|
+
if (seenKeys) seenKeys.add(object.key);
|
|
4226
|
+
objects.push(object);
|
|
4227
|
+
sourceTransportMap.set(object.key, sourceTransport);
|
|
4228
|
+
}
|
|
4229
|
+
/**
|
|
4230
|
+
* Load and resolve objects from one or more transport numbers, with optional
|
|
4231
|
+
* selector-based filtering and deduplication across TRs.
|
|
4232
|
+
*
|
|
4233
|
+
* This is the shared logic used by both the CLI `cts tr objects` command and
|
|
4234
|
+
* the MCP `cts_transport_objects` tool — extracted here to avoid duplication.
|
|
4235
|
+
*
|
|
4236
|
+
* @param transportNumbers - Ordered list of TR numbers (first-win deduplication).
|
|
4237
|
+
* @param selector - Optional dimension filter (objFunc, pgmid, type).
|
|
4238
|
+
* @param ctx - Optional ADK context (uses global context if omitted).
|
|
4239
|
+
*/
|
|
4240
|
+
async function resolveTransportObjects(transportNumbers, selector, ctx) {
|
|
4241
|
+
const objects = [];
|
|
4242
|
+
const sourceTransportMap = /* @__PURE__ */ new Map();
|
|
4243
|
+
if (transportNumbers.length === 1) {
|
|
4244
|
+
const primaryNumber = transportNumbers[0] ?? "";
|
|
4245
|
+
const tr = await AdkTransport.get(primaryNumber, ctx);
|
|
4246
|
+
for (const object of getFilteredObjects(tr, selector)) addResolvedObject(object, primaryNumber, objects, sourceTransportMap);
|
|
4247
|
+
} else {
|
|
4248
|
+
const transports = await Promise.all(transportNumbers.map((n) => AdkTransport.get(n, ctx)));
|
|
4249
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4250
|
+
for (const tr of transports) for (const object of getFilteredObjects(tr, selector)) addResolvedObject(object, tr.number, objects, sourceTransportMap, seen);
|
|
4251
|
+
}
|
|
4252
|
+
return {
|
|
4253
|
+
objects,
|
|
4254
|
+
sourceTransportMap
|
|
4255
|
+
};
|
|
4256
|
+
}
|
|
4257
|
+
/**
|
|
4258
|
+
* Merged view of objects from multiple transports.
|
|
4259
|
+
*
|
|
4260
|
+
* De-duplicates by `pgmid/type/name`; first occurrence wins.
|
|
4261
|
+
*/
|
|
4262
|
+
var MergedTransportView = class MergedTransportView {
|
|
4263
|
+
_objects;
|
|
4264
|
+
transports;
|
|
4265
|
+
constructor(transports) {
|
|
4266
|
+
this.transports = transports;
|
|
4267
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4268
|
+
const all = [];
|
|
4269
|
+
for (const tr of transports) for (const obj of tr.objects) if (!seen.has(obj.key)) {
|
|
4270
|
+
seen.add(obj.key);
|
|
4271
|
+
all.push(obj);
|
|
4272
|
+
}
|
|
4273
|
+
this._objects = all;
|
|
4274
|
+
}
|
|
4275
|
+
/** All objects across transports (deduplicated) */
|
|
4276
|
+
get objects() {
|
|
4277
|
+
return this._objects;
|
|
4278
|
+
}
|
|
4279
|
+
/** Objects matching a selector across all transports */
|
|
4280
|
+
getObjectsBySelector(selector) {
|
|
4281
|
+
return this._objects.filter((obj) => matchesSelector(obj, selector));
|
|
4282
|
+
}
|
|
4283
|
+
/** Convenience: objects marked for deletion (obj_func === 'D') */
|
|
4284
|
+
get deletionObjects() {
|
|
4285
|
+
return this.getObjectsBySelector({ objFunc: "D" });
|
|
4286
|
+
}
|
|
4287
|
+
/**
|
|
4288
|
+
* Load multiple transports and return a merged view.
|
|
4289
|
+
*
|
|
4290
|
+
* @param numbers - Transport numbers to load and merge
|
|
4291
|
+
* @param ctx - Optional ADK context
|
|
4292
|
+
*/
|
|
4293
|
+
static async create(numbers, ctx) {
|
|
4294
|
+
return new MergedTransportView(await Promise.all(numbers.map((n) => AdkTransport.get(n, ctx))));
|
|
4295
|
+
}
|
|
4296
|
+
};
|
|
4297
|
+
|
|
4298
|
+
//#endregion
|
|
4299
|
+
export { ADT_TYPE_MAPPINGS, AdkBadi, AdkBehaviorDefinition, AdkClass, AdkDataElement, AdkDclSource, AdkDdlSource, AdkDomain, AdkFunctionGroup, AdkFunctionModule, AdkGenericObject, AdkInclude, AdkInterface, AdkMainObject, AdkObject, AdkObjectSet, AdkPackage, AdkProgram, AdkServiceBinding, AdkServiceDefinition, AdkStructure, AdkTable, AdkTableType, AdkTransport, AdkTransportRequest as AdkTransportItem, AdkTransportRequest, AdkTransportObject, AdkTransportObjectRef, AdkTransportTask, AdkTransportTaskRef, Class, DataElement, Domain, EnhancementSpot, FunctionGroup, FunctionModule, Include, Interface, MergedTransportView, MessageClass, Package, Program, Structure, Table, TableType, TransportRequest, TransportTask, clearConfigCache, createAdk, createAdkContract, createAdkFactory, createLockService, getEndpointForType, getGlobalContext, getKindForType, getMainType, getObjectRootUri, getObjectUri, getRegisteredKinds, getRegisteredTypes, getTypeForKind, initializeAdk, isAdkInitialized, isTypeRegistered, matchesSelector, normalizeObjectName, parseAdtType, parseXmlIdentity, registerObjectType, resetAdk, resolveKind, resolveTransportObjects, resolveType, tryGetGlobalContext };
|
|
4300
|
+
//# sourceMappingURL=index.mjs.map
|