Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/adk/src/base/adt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export type {
TransportGetResponse,
ProgramResponse as ProgramResponseUnion,
FunctionGroupResponse as FunctionGroupResponseUnion,
DomainResponse,
DataElementResponse,
TableResponse,
StructureResponse,
TableTypeResponse,
} from '@abapify/adt-client';

// CRUD contract types for typed ADK base model
Expand Down Expand Up @@ -128,6 +133,8 @@ export interface AdkContract {
readonly programs: AdtContracts['programs'];
/** Functions contracts (function groups) */
readonly functions: AdtContracts['functions'];
/** DDIC contracts (domains, data elements, structures, tables, table types) */
readonly ddic: AdtContracts['ddic'];
}

/**
Expand All @@ -145,5 +152,6 @@ export function createAdkContract(client: AdtClient): AdkContract {
repository: client.adt.repository,
programs: client.adt.programs,
functions: client.adt.functions,
ddic: client.adt.ddic,
};
}
17 changes: 15 additions & 2 deletions packages/adk/src/base/kinds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ import type {
AdkTransportTask,
} from '../objects/cts/transport/transport';
import type { AdkObject } from './model';
import type { AdkDomain } from '../objects/ddic/doma/doma.model';
import type { AdkDataElement } from '../objects/ddic/dtel/dtel.model';
import type { AdkTable, AdkStructure } from '../objects/ddic/tabl/tabl.model';
import type { AdkTableType } from '../objects/ddic/ttyp/ttyp.model';

/**
* Maps ADK kind to concrete object type
Expand All @@ -91,5 +95,14 @@ export type AdkObjectForKind<K extends AdkKind> = K extends typeof Class
? AdkTransportRequest
: K extends typeof TransportTask
? AdkTransportTask
: // Add more mappings as types are implemented
AdkObject; // fallback
: K extends typeof Domain
? AdkDomain
: K extends typeof DataElement
? AdkDataElement
: K extends typeof Table
? AdkTable
: K extends typeof Structure
? AdkStructure
: K extends typeof TableType
? AdkTableType
: AdkObject; // fallback
64 changes: 58 additions & 6 deletions packages/adk/src/base/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,9 @@ export abstract class AdkObject<K extends AdkKind = AdkKind, D = any> {
async save(options: SaveOptions = {}): Promise<this> {
const { transport, mode = 'update' } = options;

// Reset per-save-attempt state
this._unchanged = false;

// Check if object has pending sources (from abapGit deserialization)
const hasPendingSources = this.hasPendingSources();

Expand All @@ -475,9 +478,10 @@ export abstract class AdkObject<K extends AdkKind = AdkKind, D = any> {
try {
await this.lock(transport);
} catch (e) {
// For upsert, only fallback to create if it's a 404 Not Found error
if (mode === 'upsert' && this.isNotFoundError(e)) {
return this.save({ ...options, mode: 'create' });
// For upsert, fallback to create if object doesn't exist (404)
// or endpoint doesn't support the operation (405 - common for DDIC types)
if (mode === 'upsert' && this.shouldFallbackToCreate(e)) {
return this.fallbackToCreate(options);
}
Comment on lines +481 to 485
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't treat lock() 405s and create already exists as a successful upsert.

A 405 from lock() only proves the lock endpoint is unusable, not that the object is missing. If an existing DDIC object hits that path, we jump to POST and then collapse 422 already exists into _unchanged = true, so the caller gets a false success even though this instance's payload was never written. Please only fall back to create on a definitive not-found, and retry update or load+compare before treating already exists as unchanged.

🔧 Minimal safe direction
     } catch (e) {
-      if (mode === 'upsert' && this.shouldFallbackToCreate(e)) {
+      if (mode === 'upsert' && this.isNotFoundError(e)) {
         return this.fallbackToCreate(options);
       }
       throw e;
     }
...
     } catch (e: unknown) {
       if (mode === 'upsert' && this.shouldFallbackToCreate(e)) {
         return this.fallbackToCreate(options);
       }
-      // If PUT returns 422 "already exists" directly in upsert mode
-      if (mode === 'upsert' && this.isAlreadyExistsError(e)) {
-        this._unchanged = true;
-        return this;
-      }
       throw e;
     }
...
     } catch (createErr) {
       if (this.isAlreadyExistsError(createErr)) {
-        this._unchanged = true;
-        return this;
+        throw createErr;
       }
       throw createErr;
     }

Also applies to: 517-524, 602-608

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adk/src/base/model.ts` around lines 481 - 485, The current upsert
flow incorrectly treats a 405 from lock() and a subsequent POST returning
"already exists" (422) as a successful upsert; update the logic in
shouldFallbackToCreate and the upsert path so that fallbackToCreate is only
triggered for definitive not-found responses (404) and NOT for lock 405, and
change the POST error handling so that a 422 "already exists" does not set
_unchanged = true but instead retries an update or performs a load+compare
(e.g., call update() or loadAndCompare()) before declaring success; adjust
related handlers used by fallbackToCreate, shouldFallbackToCreate, lock(), and
the code that collapses 422 responses so existing DDIC objects aren’t
misreported as written.

throw e;
}
Expand Down Expand Up @@ -510,9 +514,14 @@ export abstract class AdkObject<K extends AdkKind = AdkKind, D = any> {

return this;
} catch (e: unknown) {
// For upsert with PUT failure (404), try POST
if (mode === 'upsert' && this.isNotFoundError(e)) {
return this.save({ ...options, mode: 'create' });
// For upsert with PUT failure (404/405), try POST (create)
if (mode === 'upsert' && this.shouldFallbackToCreate(e)) {
return this.fallbackToCreate(options);
}
// If PUT returns 422 "already exists" directly in upsert mode
if (mode === 'upsert' && this.isAlreadyExistsError(e)) {
this._unchanged = true;
return this;
}
throw e;
} finally {
Expand Down Expand Up @@ -586,6 +595,49 @@ export abstract class AdkObject<K extends AdkKind = AdkKind, D = any> {
return false;
}

/**
* Upsert fallback: try POST (create), treat 422 "already exists" as unchanged.
* Extracted to avoid duplication in lock-catch and saveViaContract-catch paths.
*/
private async fallbackToCreate(options: SaveOptions): Promise<this> {
try {
return await this.save({ ...options, mode: 'create' });
} catch (createErr) {
if (this.isAlreadyExistsError(createErr)) {
this._unchanged = true;
return this;
}
throw createErr;
}
}

/**
* Check if error suggests the object doesn't exist or the operation
* is not supported for the current state.
*
* Some SAP DDIC endpoints (TABL, TTYP) return 405 Method Not Allowed
* instead of 404 when attempting to lock or PUT a non-existent object.
* In upsert mode, both should trigger a fallback to POST (create).
*/
protected shouldFallbackToCreate(e: unknown): boolean {
return (
this.isNotFoundError(e) ||
(e instanceof Error &&
(e.message.includes('405') || e.message.includes('Method Not Allowed')))
);
}

/**
* Check if error is a 422 "already exists" from a POST create attempt
*/
protected isAlreadyExistsError(e: unknown): boolean {
const msg = e instanceof Error ? e.message.toLowerCase() : '';
return (
(msg.includes('422') || msg.includes('unprocessable')) &&
msg.includes('already exists')
);
}

/**
* Check if object has pending sources to save
* Subclasses with source code (classes, interfaces) override this
Expand Down
6 changes: 5 additions & 1 deletion packages/adk/src/base/object-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,11 @@ export class AdkObjectSet {
*/
async unlockAll(): Promise<void> {
for (const obj of this.objects) {
await obj.unlock();
try {
await obj.unlock();
} catch {
// Ignore unlock failures - object may not have been locked
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions packages/adk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export type {
ProgramResponse,
FunctionGroupResponse,
TransportGetResponse,
DomainResponse,
DataElementResponse,
TableTypeResponse,
} from './base/adt';
export { createAdkContract } from './base/adt';

Expand Down Expand Up @@ -103,6 +106,19 @@ export type {
} from './objects/repository/fugr';
export { AdkFunctionGroup } from './objects/repository/fugr';

// DDIC types and classes
export {
AdkDomain,
AdkDataElement,
AdkTable,
AdkStructure,
AdkTableType,
type DomainXml,
type DataElementXml,
type TableXml,
type TableTypeXml,
} from './objects/ddic';

// CTS types (legacy complex transport)
export type {
TransportData,
Expand Down
55 changes: 55 additions & 0 deletions packages/adk/src/objects/ddic/doma/doma.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* DOMA - Domain
*
* ADK object for ABAP Domains (DOMA).
* DDIC objects are metadata-only (no source code).
*/

import { AdkMainObject } from '../../../base/model';
import { Domain as DomainKind } from '../../../base/kinds';
import { getGlobalContext } from '../../../base/global-context';
import type { AdkContext } from '../../../base/context';

import type { DomainResponse } from '../../../base/adt';

/**
* Domain data type - unwrap from schema root element
*/
export type DomainXml = DomainResponse['domain'];

/**
* ADK Domain object
*
* Inherits from AdkMainObject which provides:
* - name, type, description, version, language, changedBy/At, createdBy/At, links
* - package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
*
* Domain-specific properties via `data`:
* - data.typeInformation (dataType, length, decimals, outputLength)
* - data.outputInformation (conversionExit, signPresentation, lowerCase)
* - data.fixedValues
*/
export class AdkDomain extends AdkMainObject<typeof DomainKind, DomainXml> {
static readonly kind = DomainKind;
readonly kind = AdkDomain.kind;

get objectUri(): string {
return `/sap/bc/adt/ddic/domains/${encodeURIComponent(this.name.toLowerCase())}`;
}

protected override get wrapperKey() {
return 'domain';
}
protected override get crudContract(): any {
return this.ctx.client.adt.ddic.domains;
}

static async get(name: string, ctx?: AdkContext): Promise<AdkDomain> {
const context = ctx ?? getGlobalContext();
return new AdkDomain(context, name).load();
}
}

// Self-register with ADK registry
import { registerObjectType } from '../../../base/registry';
registerObjectType('DOMA', DomainKind, AdkDomain);
6 changes: 6 additions & 0 deletions packages/adk/src/objects/ddic/doma/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* DOMA - Domain
*/

export { AdkDomain } from './doma.model';
export type { DomainXml } from './doma.model';
59 changes: 59 additions & 0 deletions packages/adk/src/objects/ddic/dtel/dtel.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* DTEL - Data Element
*
* ADK object for ABAP Data Elements (DTEL).
* DDIC objects are metadata-only (no source code).
*/

import { AdkMainObject } from '../../../base/model';
import { DataElement as DataElementKind } from '../../../base/kinds';
import { getGlobalContext } from '../../../base/global-context';
import type { AdkContext } from '../../../base/context';

import type { DataElementResponse } from '../../../base/adt';

/**
* Data Element data type - unwrap from wrapper root element
* SAP wraps DTEL content in a blue:wbobj element extending AdtMainObject
*/
export type DataElementXml = DataElementResponse['wbobj'];

/**
* ADK Data Element object
*
* Inherits from AdkMainObject which provides:
* - name, type, description, version, language, changedBy/At, createdBy/At, links
* - package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion
*
* Data element-specific properties via `data`:
* - data.typeKind, data.typeName, data.dataType
* - data.shortDescription, data.mediumDescription, data.longDescription, data.headingDescription
* - data.searchHelp
*/
export class AdkDataElement extends AdkMainObject<
typeof DataElementKind,
DataElementXml
> {
static readonly kind = DataElementKind;
readonly kind = AdkDataElement.kind;

get objectUri(): string {
return `/sap/bc/adt/ddic/dataelements/${encodeURIComponent(this.name.toLowerCase())}`;
}

protected override get wrapperKey() {
return 'wbobj';
}
protected override get crudContract(): any {
return this.ctx.client.adt.ddic.dataelements;
Comment on lines +47 to +48
Copy link

@coderabbitai coderabbitai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify explicit any usage in ADK DDIC models
rg -nP --type=ts 'crudContract\(\):\s*any' packages/adk/src/objects/ddic

Repository: abapify/adt-cli

Length of output: 553


🏁 Script executed:

#!/bin/bash
# Inspect dtel.model.ts file to see imports and class structure
head -60 packages/adk/src/objects/ddic/dtel/dtel.model.ts | cat -n

Repository: abapify/adt-cli

Length of output: 2333


🏁 Script executed:

#!/bin/bash
# Find the base class that defines crudContract
fd -e ts -p "packages/adk/src" | xargs rg -l "class.*Model.*\{" | head -5

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Search for the base class definition of DDIC models (likely in packages/adk/src/objects)
rg -A 5 "class DtelModel" packages/adk/src/objects/ddic/dtel/dtel.model.ts | head -20

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check what type ctx.client.adt.ddic.dataelements actually is
# by looking at client definition and types
find packages/adt-client/src -name "*contract*" | xargs rg -l "dataelements" | head -3

Repository: abapify/adt-cli

Length of output: 203


🏁 Script executed:

#!/bin/bash
# Check base class AdkMainObject definition
rg -A 20 "class AdkMainObject" packages/adk/src/base/model.ts | head -40

Repository: abapify/adt-cli

Length of output: 761


🏁 Script executed:

#!/bin/bash
# Look for crudContract definition in base class
rg -B 2 -A 5 "crudContract" packages/adk/src/base/model.ts | head -30

Repository: abapify/adt-cli

Length of output: 901


🏁 Script executed:

#!/bin/bash
# Check AdkContext type definition to understand ctx.client structure
rg -A 30 "export.*type AdkContext" packages/adk/src/base/context.ts | head -50

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if other non-DDIC models use proper types for crudContract
rg "crudContract.*:" packages/adk/src/objects --type=ts | grep -v "any" | head -10

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Find context.ts and check AdkContext definition more carefully
find packages/adk/src -name "context.ts" -exec cat {} \;

Repository: abapify/adt-cli

Length of output: 1069


🏁 Script executed:

#!/bin/bash
# Check the get signature in crudContract to understand expected type
rg -B 5 -A 10 "get.*crudContract" packages/adk/src/base/model.ts | head -50

Repository: abapify/adt-cli

Length of output: 569


🏁 Script executed:

#!/bin/bash
# Look at the contract type - check what dataelements contract looks like
head -50 packages/adt-contracts/src/adt/ddic/dataelements.ts

Repository: abapify/adt-cli

Length of output: 785


🏁 Script executed:

#!/bin/bash
# Check if any ADK models override crudContract with a specific type (not any)
rg "override get crudContract" packages/adk/src/objects -A 1 | head -40

Repository: abapify/adt-cli

Length of output: 2059


🏁 Script executed:

#!/bin/bash
# Check what types are exported from dataelements contract
cat -n packages/adt-contracts/src/adt/ddic/dataelements.ts

Repository: abapify/adt-cli

Length of output: 967


🏁 Script executed:

#!/bin/bash
# Check if contracts are imported in ADK or if there's a typing gap
rg "import.*dataelements" packages/adk/src/objects/ddic/dtel/ | head -10

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check the base class to see if the any has justification
cat -n packages/adk/src/base/model.ts | sed -n '200,250p'

Repository: abapify/adt-cli

Length of output: 1742


🏁 Script executed:

#!/bin/bash
# Check if AdtClient defines the contract types properly
rg -A 20 "export.*AdtClient\|interface AdtClient" packages/adk/src/base/adt.ts | head -50

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if ADK models can import contract types from adt-contracts
rg "from.*adt-contracts" packages/adk/src/objects --type=ts | head -5

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check how the contracts are organized - see if DataelementsContract is the right type
rg "export.*Contract" packages/adt-contracts/src/adt/ddic/ --type=ts

Repository: abapify/adt-cli

Length of output: 1170


🏁 Script executed:

#!/bin/bash
# Check the actual adt.ts or client builder to understand if contracts are properly typed
fd -e ts "adt\\.ts|client\\.ts" packages/adk/src/base/ | xargs cat | head -100

Repository: abapify/adt-cli

Length of output: 3069


🏁 Script executed:

#!/bin/bash
# Check if there's already a pattern for importing contract types in the codebase
rg "DataelementsContract\|programsContract\|classesContract" packages/adk/src --type=ts | head -5

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if `@abapify/adt-client` exports the contract types
rg "export.*Contract" packages/adt-contracts/src/index.ts | head -20

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check the main adt-contracts/index.ts to see what's exported
cat packages/adt-contracts/src/index.ts | head -100

Repository: abapify/adt-cli

Length of output: 932


🏁 Script executed:

#!/bin/bash
# Verify if ADK can import from adt-contracts or adt-client
grep -r "from.*@abapify" packages/adk/src --include="*.ts" | head -5

Repository: abapify/adt-cli

Length of output: 401


🏁 Script executed:

#!/bin/bash
# Check if the suggested type signature actually works by testing the path
cat -n packages/adk/src/base/context.ts

Repository: abapify/adt-cli

Length of output: 1335


🏁 Script executed:

#!/bin/bash
# Check if AdtClient type definition properly types the contract nested access
rg -A 30 "interface AdtClient\|type AdtClient" packages/adt-client/src --type=ts | head -60

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check how AdtClient.adt.ddic.dataelements is typed - is it strongly typed?
rg "ddic.*dataelements\|dataelements.*:" packages/adt-client/src --type=ts -B 2 -A 2 | head -30

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if the base class comment explains why any is used
rg -B 10 "protected get crudContract.*any" packages/adk/src/base/model.ts | head -20

Repository: abapify/adt-cli

Length of output: 370


🏁 Script executed:

#!/bin/bash
# Check the actual AdtClient type structure to understand if the nested property type is accessible
find packages/adt-client/src -name "*.ts" | xargs rg "ddic.*dataelements\|export.*adt:" | head -20

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if adt-contracts properly exports DataelementsContract for import
cat packages/adt-contracts/src/adt/ddic/index.ts

Repository: abapify/adt-cli

Length of output: 1361


🏁 Script executed:

#!/bin/bash
# Verify if any ADK model imports contract types from adt-contracts
rg "import.*Contract.*from" packages/adk/src --type=ts

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Verify that AdtClient typing doesn't provide inferred contract types for nested access
# Check if adt-client exports AdtClient with proper nested typing
cat packages/adt-client/src/index.ts

Repository: abapify/adt-cli

Length of output: 2615


🏁 Script executed:

#!/bin/bash
# Check what package.json says about the adt-client version/exports
cat packages/adt-client/package.json | jq '.exports, .main'

Repository: abapify/adt-cli

Length of output: 131


Add TypeScript strict-mode justification or type crudContract properly.

All ADK object models override crudContract with any but lack the // eslint-disable-next-line @typescript-eslint/no-explicit-any`` comment present in the base class. This violates strict-mode requirements.

Either add the disable comment with justification:

// eslint-disable-next-line `@typescript-eslint/no-explicit-any`
protected override get crudContract(): any {
  return this.ctx.client.adt.ddic.dataelements;
}

Or import and use the contract type directly:

import type { DataelementsContract } from '@abapify/adt-contracts';

protected override get crudContract(): DataelementsContract {
  return this.ctx.client.adt.ddic.dataelements;
}

This pattern affects 10+ models across the codebase (dtel, doma, tabl, ttyp, clas, intf, prog, devc, fugr, and others).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adk/src/objects/ddic/dtel/dtel.model.ts` around lines 46 - 47, The
getter protected override get crudContract in dtel.model.ts uses an untyped any
which violates strict-mode; fix by either adding the eslint disable comment
above the getter (// eslint-disable-next-line
`@typescript-eslint/no-explicit-any`) with a short justification, or better import
the proper contract type (e.g., import type { DataelementsContract } from
'@abapify/adt-contracts') and change the signature to protected override get
crudContract(): DataelementsContract { return
this.ctx.client.adt.ddic.dataelements; } so the getter is correctly typed;
update the dtel.model.ts getter accordingly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dismissed — pre-existing base class limitation. ALL ADK models (CLAS, INTF, PROG, FUGR, DEVC + DDIC) use crudContract(): any. The ADK intentionally does not import contract types from adt-contracts (dependency direction constraint). Not a regression in this PR.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThePlenkov, understood — the crudContract(): any pattern is a deliberate architectural constraint across all ADK object models due to the unidirectional dependency (ADK → adt-client only, never → adt-contracts). The existing knowledge base entry already captures this broadly for packages/adk/src/objects/**/*.model.ts, so I won't flag this pattern in future reviews.

( ˘ω˘ ) 🐇


🧠 Learnings used
Learnt from: ThePlenkov
Repo: abapify/adt-cli PR: 84
File: packages/adk/src/objects/ddic/tabl/tabl.model.ts:46-47
Timestamp: 2026-03-16T12:24:05.651Z
Learning: In ADK object models (files matching packages/adk/src/objects/**/*.model.ts), the protected override get crudContract(): any pattern is intentional because ADK has a one-way dependency (ADK → adt-client, not ADK → adt-contracts). Therefore, returning any for crudContract() should not be flagged as a violation. This should apply broadly to all ADK object models, not just the single file.

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-plugin-abapgit/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:40.877Z
Learning: Applies to packages/adt-plugin-abapgit/src/lib/handlers/**/*.ts : Use ADK types from local re-export module `../adk` instead of directly from `abapify/adk` in handler files

Learnt from: ThePlenkov
Repo: abapify/adt-cli PR: 84
File: packages/adt-contracts/src/adt/ddic/structures.ts:0-0
Timestamp: 2026-03-16T12:24:08.647Z
Learning: In `packages/adt-contracts/src/adt/ddic/structures.ts`, the correct Content-Type for POST requests to the SAP ADT structures endpoint (`/sap/bc/adt/ddic/structures`) is `application/vnd.sap.adt.tablestructures.v2+xml`, which differs from the path-level MIME type `application/vnd.sap.adt.structures.v2+xml` stored in the `contentType` variable.

Learnt from: ThePlenkov
Repo: abapify/adt-cli PR: 84
File: packages/adt-contracts/src/adt/ddic/structures.ts:0-0
Timestamp: 2026-03-16T12:24:08.648Z
Learning: In `packages/adt-contracts/src/adt/ddic/structures.ts`, using a wildcard Accept header (e.g., `application/*`) in DDIC contract POST requests is intentional — it serves as a fallback for SAP ADT endpoints that may return responses in different versions.

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-plugin-abapgit/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:40.876Z
Learning: Applies to packages/adt-plugin-abapgit/xsd/**/*.xsd : Reuse SAP structures via `xs:complexType`, NOT via global elements

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-plugin-abapgit/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:40.877Z
Learning: Applies to packages/adt-plugin-abapgit/src/lib/handlers/**/*.ts : Avoid manual XML string building; use schema `.build()` method instead

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-plugin-abapgit/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:40.877Z
Learning: Applies to packages/adt-plugin-abapgit/src/lib/handlers/**/*.ts : Avoid `as any` type assertions; fix types at source in XSD schemas instead

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-client/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:07.245Z
Learning: Applies to packages/adt-client/src/**/*.ts : NEVER use `as any` type assertions without explicit justification. If type inference fails, fix the schema or contract instead of bypassing with casts.

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-client/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:07.245Z
Learning: Applies to packages/adt-client/src/adt/**/*-contract.ts : Keep contracts thin with no business logic. Move business logic to services layer.

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-client/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:07.245Z
Learning: Applies to packages/adt-client/src/adt/**/*-contract.ts : NEVER add a `metadata` field to contracts. The adapter automatically detects schemas from `responses[200]`. The `metadata: { responseSchema: ... }` field is legacy and should be removed.

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-15T23:46:25.807Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript strict mode — no `any` without a comment explaining why

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-contracts/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:19.204Z
Learning: Applies to packages/adt-contracts/src/adt/**/*.ts : Always import schemas from '../../schemas' in contract definitions, not directly from 'abapify/adt-schemas'

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-plugin-abapgit/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:40.877Z
Learning: Applies to packages/adt-plugin-abapgit/src/lib/handlers/objects/**/*.ts : Handler files must NOT contain file system operations, ADT client calls, XML string building, or Promise handling for sources

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-client/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:07.245Z
Learning: Applies to packages/adt-client/src/adt/**/*-contract.ts : The adapter automatically handles response parsing based on content-type (JSON, XML with schema, or text). Do not parse responses manually in contracts.

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-cli/AGENTS.md:0-0
Timestamp: 2026-03-02T17:23:46.791Z
Learning: Applies to packages/adt-cli/src/lib/commands/**/*.ts : For new commands needing ADT API access, use V2 client (`abapify/adt-client`) when an endpoint has a contract in v2. Use V1 only when endpoint not yet migrated or v1-specific features are needed.

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-client/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:07.245Z
Learning: Applies to packages/adt-client/src/contract.ts : The `fetch()` method is a utility function on the client for debugging and ad-hoc requests, NOT a contract endpoint. Only expose methods as contracts if they map to specific SAP ADT endpoints with known schemas.

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-client/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:07.245Z
Learning: File structure for adt-client: organize contracts by SAP endpoint path under src/adt/, create corresponding schema files with *-schema.ts naming, register contracts in src/contract.ts, and create type inference tests in tests/.

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-15T23:46:25.807Z
Learning: Applies to packages/adk/src/** : Object types in ADK must be registered with `handlers.set()` using an `AdkObjectHandler` bridge pattern with a parser function and URL factory

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-contracts/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:19.204Z
Learning: Add fixtures to adt-fixtures package when new contract scenarios require real SAP XML sample data

Learnt from: CR
Repo: abapify/adt-cli PR: 0
File: packages/adt-plugin-abapgit/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:40.876Z
Learning: Applies to packages/adt-plugin-abapgit/xsd/*.xsd : Create concrete document schema in `xsd/{type}.xsd` with ONE root element (`abapGit`)

}

static async get(name: string, ctx?: AdkContext): Promise<AdkDataElement> {
const context = ctx ?? getGlobalContext();
return new AdkDataElement(context, name).load();
}
}

// Self-register with ADK registry
import { registerObjectType } from '../../../base/registry';
registerObjectType('DTEL', DataElementKind, AdkDataElement);
6 changes: 6 additions & 0 deletions packages/adk/src/objects/ddic/dtel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* DTEL - Data Element
*/

export { AdkDataElement } from './dtel.model';
export type { DataElementXml } from './dtel.model';
8 changes: 8 additions & 0 deletions packages/adk/src/objects/ddic/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* DDIC - Data Dictionary Objects
*/

export { AdkDomain, type DomainXml } from './doma';
export { AdkDataElement, type DataElementXml } from './dtel';
export { AdkTable, AdkStructure, type TableXml } from './tabl';
export { AdkTableType, type TableTypeXml } from './ttyp';
6 changes: 6 additions & 0 deletions packages/adk/src/objects/ddic/tabl/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* TABL - Database Table / Structure
*/

export { AdkTable, AdkStructure } from './tabl.model';
export type { TableXml } from './tabl.model';
Loading
Loading