Skip to content

feat(ddic): add full DDIC object type support (DOMA, DTEL, TABL, TTYP)#84

Merged
ThePlenkov merged 5 commits intomainfrom
feat/ddic-object-types
Mar 16, 2026
Merged

feat(ddic): add full DDIC object type support (DOMA, DTEL, TABL, TTYP)#84
ThePlenkov merged 5 commits intomainfrom
feat/ddic-object-types

Conversation

@ThePlenkov
Copy link
Member

@ThePlenkov ThePlenkov commented Mar 16, 2026

DDIC Object Type Support

Adds full support for ABAP Dictionary object types across the ADK/ADT stack:

What's included

ADT Schemas (adt-schemas)

  • Generated TypeScript schemas for domain, data elements, table settings, table type from SAP XSDs

ADT Contracts (adt-contracts)

  • CRUD contracts for: domains, dataelements, tables, structures, tabletypes

ADK Object Models (adk)

  • AdkDomain (DOMA/DD), AdkDataElement (DTEL/DE), AdkTable (TABL/DT), AdkStructure (TABL/DS), AdkTableType (TTYP/TT)
  • All registered in ADK kinds + registry

abapGit Handlers (adt-plugin-abapgit)

  • XSD schemas: tabl.xsd, ttyp.xsd + type XSDs (dd02v, dd03p, dd40v)
  • Handlers: doma.ts, dtel.ts, tabl.ts, ttyp.ts with toAbapGit/fromAbapGit
  • Fixtures: 6 XML fixtures (2 TABL, 2 TTYP, existing DOMA + DTEL)
  • Tests: Schema parse/validate/build/round-trip for all DDIC types

Test results

  • 56/56 tests pass in adt-plugin-abapgit
  • XSD validation with xmllint passes for all fixtures
  • Build succeeds for all affected packages

Packages modified

  • @abapify/adt-schemas
  • @abapify/adt-contracts
  • @abapify/adt-client
  • @abapify/adk
  • @abapify/adt-plugin-abapgit

Summary by CodeRabbit

  • New Features

    • Full ADT/DDIC support added for Domains, Data Elements, Tables, Structures and Table Types with type-safe models, CRUD surface and plugin exports.
    • New ABAP Git handlers for importing/exporting DDIC objects (domains, data elements, tables, table types).
  • Bug Fixes

    • Improved save/upsert fallback and unlock error handling to reduce failure modes.
    • Broader Accept header negotiation for several ADT endpoints.
  • Tests

    • Added XML fixtures and schema-driven tests for tables and table types.
  • Chores

    • Expanded schemas and codegen/config to include DDIC-related schemas and public contract surface.

- Add XSD schemas for TABL (table/structure) and TTYP (table type)
  - types/dd02v.xsd: table header type
  - types/dd03p.xsd: table field type + Dd03pTableType wrapper
  - types/dd40v.xsd: table type header type
  - tabl.xsd: concrete document schema using xs:redefine
  - ttyp.xsd: concrete document schema using xs:redefine

- Add abapGit handlers
  - tabl.ts: serialize/deserialize TABL objects (tables + structures)
  - ttyp.ts: serialize/deserialize TTYP objects (table types)
  - Register DOMA, DTEL, TABL, TTYP in objects/index.ts

- Add test fixtures
  - tabl/zage_structure.tabl.xml (structure)
  - tabl/zage_transparent_table.tabl.xml (transparent table)
  - ttyp/zage_string_table.ttyp.xml (string table type)
  - ttyp/zage_struct_table_type.ttyp.xml (structure table type)

- Add schema tests for TABL and TTYP
  - XSD validation, parse, validate content, build, round-trip

- Regenerate all abapGit schemas (9 total: +tabl, +ttyp)

All 56 tests pass. Build succeeds.
Copilot AI review requested due to automatic review settings March 16, 2026 00:18
@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

Adds metadata-only DDIC support: new ADT contracts and response types, ADK models for Domain/DataElement/Table/Structure/TableType, abapGit handlers and XSDs/fixtures/tests, accept-header and crud helper updates, and small ADK save/unlock behavior changes.

Changes

Cohort / File(s) Summary
Core ADK surface
packages/adk/src/base/adt.ts, packages/adk/src/base/kinds.ts, packages/adk/src/index.ts
Re-export DDIC response types; add ddic to AdkContract and createAdkContract; extend kind→type mapping to include DDIC kinds; export new ADK DDIC types/values.
ADK DDIC models
packages/adk/src/objects/ddic/...
doma/doma.model.ts, dtel/dtel.model.ts, tabl/tabl.model.ts, ttyp/ttyp.model.ts
Add AdkDomain, AdkDataElement, AdkTable, AdkStructure, AdkTableType classes with Xml types, objectUri/wrapperKey/crudContract overrides, static get factories, and registry registration.
ADK DDIC barrels
packages/adk/src/objects/ddic/*/index.ts, packages/adk/src/objects/ddic/index.ts
Add per-module and top-level ddic barrel exports for new models and Xml types.
ADT contracts (DDIC)
packages/adt-contracts/src/adt/ddic/*
domains.ts, dataelements.ts, structures.ts, tables.ts, tabletypes.ts, index.ts
Introduce typed DDIC contracts and response types (DomainResponse, DataElementResponse, TableResponse, StructureResponse, TableTypeResponse); provide aggregated DdicContract/ddicContract.
ADT core integration & client exports
packages/adt-contracts/src/adt/index.ts, packages/adt-client/src/index.ts
Add ddic to AdtContract/adtContract and re-export DDIC response types from adt-client public surface.
abapGit plugin handlers
packages/adt-plugin-abapgit/src/lib/handlers/adk.ts, .../objects/*.ts, .../objects/index.ts
Expose new Adk* classes and add handlers for DOMA, DTEL, TABL, TTYP implementing toAbapGit/fromAbapGit mappings (including language conversions) and registering handlers.
Schemas & fixtures (abapGit)
packages/adt-plugin-abapgit/xsd/*.xsd, xsd/types/*, tests/fixtures/*, tests/schemas/*, ts-xsd.config.ts
Add XSDs (tabl, ttyp, dd02v, dd03p, dd40v), custom wrappers (blueSource, dataelementWrapper), include new schemas in codegen, add XML fixtures and schema tests.
adt-schemas codegen targets
packages/adt-schemas/ts-xsd.config.ts
Add sap/domain, sap/dataelements, sap/tabletype, sap/tablesettings and custom wrapper schemas to codegen targets.
CRUD helper / accept header
packages/adt-contracts/src/helpers/crud.ts
Add optional accept to CrudOptions and repo(); wire accept (defaulting to contentType) and prefer it for GET requests.
Accept headers across contracts & tests
packages/adt-contracts/src/adt/..., packages/adt-contracts/tests/...
Extend Accept header lists for existing contracts (classes, interfaces, packages, programs, function groups) and update tests to reflect multi-version Accept values.
ADK behavior tweaks
packages/adk/src/base/model.ts, packages/adk/src/base/object-set.ts
Add AdkObject helpers shouldFallbackToCreate / isAlreadyExistsError and fallbackToCreate flow for upsert; reset per-save state and swallow unlock errors in unlockAll.
Plugin test fixtures & examples
packages/adt-plugin-abapgit/tests/fixtures/*, git_modules/abapgit-examples
Add tabl/ttyp fixture XMLs and update abapgit-examples submodule pointer.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer/Client
  participant ADT as ADT Client (ddicContract)
  participant ADK as ADK Models
  participant ABAPGIT as abapGit Handlers

  Dev->>ADK: AdkTable.get(name)
  ADK->>ADT: ctx.client.adt.ddic.tables.get(name)
  ADT-->>ADK: TableResponse (blueSource)
  ADK-->>Dev: AdkTable instance
  Dev->>ABAPGIT: export/import request
  ABAPGIT->>ADK: toAbapGit/fromAbapGit(Adk* instance)
  ABAPGIT-->>Dev: ABAP-XML fixture
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibble code and hop with glee,
Domains and tables join the tree,
Contracts, models, XSDs align,
Handlers hum and fixtures shine,
A rabbit claps: new DDIC — hooray!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding full DDIC object type support for multiple ABAP dictionary object types (DOMA, DTEL, TABL, TTYP) across the entire stack.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ddic-object-types
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can scan for known vulnerabilities in your dependencies using OSV Scanner.

OSV Scanner will automatically detect and report security vulnerabilities in your project's dependencies. No additional configuration is required.

@nx-cloud
Copy link
Contributor

nx-cloud bot commented Mar 16, 2026

View your CI Pipeline Execution ↗ for commit 2816dd4

Command Status Duration Result
nx affected -t lint test build e2e-ci --verbose... ✅ Succeeded 22s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-16 11:58:27 UTC

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds end-to-end DDIC support (DOMA, DTEL, TABL/DT+DS, TTYP) across schemas, contracts, ADK models, and abapGit import/export tooling.

Changes:

  • Generate and export new ADT XSD schemas/types for DDIC objects (domain, data elements, table type, table settings).
  • Introduce DDIC REST contracts/modules and surface them through adt-client + ADK.
  • Add abapGit XSDs, fixtures, schema tests, and object handlers for DDIC types.

Reviewed changes

Copilot reviewed 39 out of 57 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/adt-schemas/ts-xsd.config.ts Adds DDIC XSDs to schema generation targets.
packages/adt-schemas/src/schemas/generated/types/sap/tabletype.types.ts Generated TS types for ADT table type payloads.
packages/adt-schemas/src/schemas/generated/types/sap/tablesettings.types.ts Generated TS types for ADT table settings payloads.
packages/adt-schemas/src/schemas/generated/types/sap/domain.types.ts Generated TS types for ADT domain payloads.
packages/adt-schemas/src/schemas/generated/types/sap/dataelements.types.ts Generated TS types for ADT data element payloads.
packages/adt-schemas/src/schemas/generated/typed.ts Exposes typed schema exports for new DDIC schemas.
packages/adt-schemas/src/schemas/generated/schemas/sap/tabletype.ts Generated runtime schema for ADT table type XML.
packages/adt-schemas/src/schemas/generated/schemas/sap/tablesettings.ts Generated runtime schema for ADT table settings XML.
packages/adt-schemas/src/schemas/generated/schemas/sap/index.ts Re-exports new SAP schemas.
packages/adt-schemas/src/schemas/generated/schemas/sap/domain.ts Generated runtime schema for ADT domain XML.
packages/adt-schemas/src/schemas/generated/schemas/sap/dataelements.ts Generated runtime schema for ADT data elements XML.
packages/adt-plugin-abapgit/xsd/types/dd40v.xsd Adds abapGit type for DD40V (table type header).
packages/adt-plugin-abapgit/xsd/types/dd03p.xsd Adds abapGit type for DD03P (+ table wrapper) (fields).
packages/adt-plugin-abapgit/xsd/types/dd02v.xsd Adds abapGit type for DD02V (table/structure header).
packages/adt-plugin-abapgit/xsd/ttyp.xsd Adds abapGit object schema for TTYP.
packages/adt-plugin-abapgit/xsd/tabl.xsd Adds abapGit object schema for TABL.
packages/adt-plugin-abapgit/ts-xsd.config.ts Includes tabl/ttyp in abapGit schema generation.
packages/adt-plugin-abapgit/tests/schemas/ttyp.test.ts Schema-level parse/validate/round-trip tests for TTYP fixtures.
packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts Schema-level parse/validate/round-trip tests for TABL fixtures.
packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_struct_table_type.ttyp.xml New TTYP fixture (structure row type).
packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_string_table.ttyp.xml New TTYP fixture (string row type).
packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_transparent_table.tabl.xml New TABL fixture (transparent table).
packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_structure.tabl.xml New TABL fixture (structure).
packages/adt-plugin-abapgit/src/schemas/generated/types/ttyp.ts Generated TS types for abapGit TTYP XML.
packages/adt-plugin-abapgit/src/schemas/generated/types/tabl.ts Generated TS types for abapGit TABL XML.
packages/adt-plugin-abapgit/src/schemas/generated/types/index.ts Re-exports generated TTYP/TABL types.
packages/adt-plugin-abapgit/src/schemas/generated/schemas/ttyp.ts Generated runtime schema for abapGit TTYP XML.
packages/adt-plugin-abapgit/src/schemas/generated/schemas/tabl.ts Generated runtime schema for abapGit TABL XML.
packages/adt-plugin-abapgit/src/schemas/generated/schemas/index.ts Re-exports generated TTYP/TABL schemas.
packages/adt-plugin-abapgit/src/schemas/generated/index.ts Registers new abapGit schema instances (tabl/ttyp).
packages/adt-plugin-abapgit/src/lib/handlers/objects/ttyp.ts Adds abapGit handler for TTYP objects.
packages/adt-plugin-abapgit/src/lib/handlers/objects/tabl.ts Adds abapGit handler for TABL objects.
packages/adt-plugin-abapgit/src/lib/handlers/objects/index.ts Exports DDIC handlers from handler index.
packages/adt-plugin-abapgit/src/lib/handlers/objects/dtel.ts Adds abapGit handler for DTEL objects.
packages/adt-plugin-abapgit/src/lib/handlers/objects/doma.ts Adds abapGit handler for DOMA objects.
packages/adt-plugin-abapgit/src/lib/handlers/adk.ts Re-exports new ADK DDIC object classes for handlers.
packages/adt-contracts/src/generated/schemas.ts Exposes new DDIC schemas via contracts’ schema registry.
packages/adt-contracts/src/adt/index.ts Adds ddic module to top-level ADT contract.
packages/adt-contracts/src/adt/ddic/tabletypes.ts Adds CRUD contract for table types.
packages/adt-contracts/src/adt/ddic/tables.ts Adds table contract (untyped responses pending schema).
packages/adt-contracts/src/adt/ddic/structures.ts Adds structure contract (untyped responses pending schema).
packages/adt-contracts/src/adt/ddic/index.ts Aggregates ddic subcontracts into a single module.
packages/adt-contracts/src/adt/ddic/domains.ts Adds CRUD contract for domains.
packages/adt-contracts/src/adt/ddic/dataelements.ts Adds CRUD contract for data elements.
packages/adt-client/src/index.ts Re-exports DDIC response types for consumers.
packages/adk/src/objects/ddic/ttyp/ttyp.model.ts Adds ADK model for TTYP + self-registration.
packages/adk/src/objects/ddic/ttyp/index.ts Exports TTYP ADK model/types.
packages/adk/src/objects/ddic/tabl/tabl.model.ts Adds ADK models for TABL tables + structures.
packages/adk/src/objects/ddic/tabl/index.ts Exports TABL ADK models/types.
packages/adk/src/objects/ddic/index.ts Exposes all DDIC ADK objects from a single entrypoint.
packages/adk/src/objects/ddic/dtel/index.ts Exports DTEL ADK model/types.
packages/adk/src/objects/ddic/dtel/dtel.model.ts Adds ADK model for DTEL + self-registration.
packages/adk/src/objects/ddic/doma/index.ts Exports DOMA ADK model/types.
packages/adk/src/objects/ddic/doma/doma.model.ts Adds ADK model for DOMA + self-registration.
packages/adk/src/index.ts Re-exports DDIC ADK objects/types and response types.
packages/adk/src/base/kinds.ts Adds new DDIC kinds + kind→object type mapping.
packages/adk/src/base/adt.ts Wires ddic contracts into ADK’s contract facade + exports types.
Comments suppressed due to low confidence (1)

packages/adk/src/objects/ddic/tabl/tabl.model.ts:85

  • AdkStructure is introduced with a distinct StructureKind, but only TableKind is registered for ADT main type TABL. With the current registry implementation (maps by main type only), TABL/DS cannot resolve to AdkStructure (and byKind(Structure, ...) will throw), so structures will be instantiated as AdkTable and use the wrong endpoint (/ddic/tables). Consider making the registry subtype-aware (e.g., prefer full type mapping like TABL/DT vs TABL/DS) and registering AdkStructure accordingly, or otherwise remove/avoid the separate StructureKind until resolution is implemented.
// Self-register with ADK registry
import { registerObjectType } from '../../../base/registry';
registerObjectType('TABL', TableKind, AdkTable);
// Note: Structure uses same main type TABL but different ADK kind
// Registration uses TABL main type - structures will be resolved via subtype logic


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

*
* ADT endpoint: /sap/bc/adt/ddic/tabletypes
* Content-Type: application/vnd.sap.adt.tabletype.v1+xml
* Object type: TTYP/DA (ttypda)
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in 2816dd4 — comment now reads TTYP/TT.

Comment on lines +26 to +33
fromAbapGit: ({ DD02V }) =>
({
name: (DD02V?.TABNAME ?? '').toUpperCase(),
type: DD02V?.TABCLASS === 'INTTAB' ? 'TABL/DS' : 'TABL/DT',
description: DD02V?.DDTEXT,
language: sapLangToIso(DD02V?.DDLANGUAGE),
masterLanguage: sapLangToIso(DD02V?.DDLANGUAGE),
}) as { name: string } & Record<string, unknown>,
Copy link
Member Author

Choose a reason for hiding this comment

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

Valid observation, acknowledged for follow-up. The registry currently maps by main type only (TABL). fromAbapGit correctly returns type: TABL/DS for structures, so the data is preserved — constructor subtype-awareness is the missing piece. Tracked for a future PR.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (1)
packages/adk/src/objects/ddic/doma/doma.model.ts (1)

43-45: Avoid untyped any on crudContract getter.

The crudContract getter returns any, which bypasses type safety. Per coding guidelines, any should include a comment explaining why it's necessary, or the types should be fixed at source.

Consider adding a type annotation or a comment explaining the limitation:

♻️ Suggested fix
-  protected override get crudContract(): any {
+  // eslint-disable-next-line `@typescript-eslint/no-explicit-any` -- ADK base model expects generic CRUD contract
+  protected override get crudContract(): any {
     return this.ctx.client.adt.ddic.domains;
   }

Alternatively, if there's a proper type available from the contract definitions, consider using that instead of any.

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

In `@packages/adk/src/objects/ddic/doma/doma.model.ts` around lines 43 - 45, The
getter get crudContract currently returns the untyped any; either replace any
with the precise contract type exported by your ADT client (the type of
this.ctx.client.adt.ddic.domains) or, if no suitable type exists, keep any but
add a short explanatory comment above get crudContract documenting why any is
required and a TODO to fix once the contract typings are added; update the
signature from "protected override get crudContract(): any" to use the correct
interface name (or add the explanatory comment) so the intention and limitation
are explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/adk/src/objects/ddic/dtel/dtel.model.ts`:
- Around line 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.

In `@packages/adk/src/objects/ddic/tabl/tabl.model.ts`:
- Around line 82-84: The registry currently registers 'TABL' -> AdkTable via
registerObjectType('TABL', TableKind, AdkTable) but resolveType() only extracts
the main type and always returns AdkTable, so AdkStructure (for 'TABL/DS') is
unreachable; fix by either (A) registering the structure subtype explicitly
(registerObjectType('TABL/DS', TableKind, AdkStructure)) so the factory can
return AdkStructure, or (B) make resolveType() subtype-aware: parse the full
type token (e.g. split on '/') inside resolveType(), check for known subtypes
like 'DS' and map them to AdkStructure (while falling back to AdkTable for plain
'TABL'), and update registry lookup logic to prefer exact subtype keys before
main-type keys.
- Around line 43-44: Replace the loose any return type on the protected getter
get crudContract with the precise indexed type from the adt client rather than
any: use the type of this.ctx.client.adt.ddic['tables'] for the TablModel getter
(replace the any on get crudContract in the class that returns tables) and
likewise use this.ctx.client.adt.ddic['structures'] for the AdkStructure getter
(replace the any on its get crudContract), removing the any assertions and
returning/annotating the exact indexed contract type instead.

In `@packages/adk/src/objects/ddic/ttyp/ttyp.model.ts`:
- Around line 46-47: The getter override crudContract currently returns any
(protected override get crudContract(): any) which breaks strict linting; fix by
either (A) adding a one-line eslint disable with a brief justification above the
getter (e.g., eslint-disable-next-line `@typescript-eslint/no-explicit-any` //
wrapper for ADT client contract) or (B) properly typing it: export the concrete
TabletypesContract type from packages/adk/src/base/adt.ts, import that type into
this model, and change the getter signature to use that type and return
this.ctx.client.adt.ddic.tabletypes; apply the same pattern for the other
DDIC/repository overrides (tabl, dtel, doma, prog, intf, fugr, devc, clas).

In `@packages/adt-contracts/src/adt/ddic/structures.ts`:
- Around line 30-33: The request headers currently set 'Content-Type' to the
wildcard 'application/*' which is wrong for POSTs; update the headers object
used in the structures request so 'Content-Type' uses the concrete MIME value
(the contentType variable / declared structures MIME type) instead of
'application/*' — locate the headers definition in structures.ts (the headers: {
Accept: contentType, 'Content-Type': 'application/*' } block) and replace the
wildcard with the concrete contentType variable or the exact structures MIME
string so the POST sends the correct Content-Type.

In `@packages/adt-contracts/src/adt/ddic/tables.ts`:
- Line 17: The table-name path construction doesn't URL-encode names, so replace
uses of nameTransform (or direct n.toLowerCase()) in ADT path builders with
encodeURIComponent(name.toLowerCase()) to safely handle special characters and
namespaced names; update all path construction sites that build ADT endpoints
for tables (references around the nameTransform usage and the functions that
construct table paths) to call encodeURIComponent on the lowercased table name
so paths use encodeURIComponent(name.toLowerCase()) consistently with
packages/adk.
- Around line 27-30: The post and put handlers for the tables contract currently
set body: undefined making create/update non-functional; update the table client
in packages/adt-contracts/src/adt/ddic/tables.ts so post and put accept a table
payload parameter and pass it as the request body following the existing crud()
helper pattern (mirror how crud() constructs body/response mappings for
create/update endpoints), update the method signatures for post and put to
include options and a payload (e.g., post: (payload, options?) / put: (id,
payload, options?)) and ensure responses remain correctly typed, and add a
corresponding contract test file for tables that asserts method signatures,
paths, headers, query params, and response schemas per the contract testing
conventions used elsewhere in the repo.

In `@packages/adt-contracts/src/adt/ddic/tabletypes.ts`:
- Line 6: Update the module documentation in tabletypes.ts to correct the
object-type label from "TTYP/DA" to "TTYP/TT" so it matches the implemented
table type kind used by the new handler/model flow; locate the doc comment that
currently reads TTYP/DA and replace it with TTYP/TT to keep the module docs
consistent with the table type kind.

In `@packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts`:
- Line 26: Remove the unnecessary cast by accessing the typed property directly:
replace instances of "const root = (data as any).abapGit;" with "const root =
data.abapGit;" (data is already typed via SchemaScenario<TablSchema>), and make
the same change for the other occurrence using the same symbols (data, abapGit,
TablSchema, SchemaScenario<TablSchema>, root).

In `@packages/adt-plugin-abapgit/xsd/tabl.xsd`:
- Around line 19-31: The current xs:redefine block must be removed and replaced
by composing a new TablValuesType that extends asx:AbapValuesType: add an
xs:import for asx.xsd, define a new complexType named TablValuesType with
xs:complexContent xs:extension base="asx:AbapValuesType" and include the
TABL-specific elements DD02V and DD03P_TABLE (types asx:Dd02vType and
asx:Dd03pTableType); replace any schema declarations that expected the redefined
AbapValuesType with references to TablValuesType and remove the xs:redefine
block entirely to avoid using xs:redefine/xs:override.

In `@packages/adt-plugin-abapgit/xsd/ttyp.xsd`:
- Around line 18-29: You used xs:redefine to alter AbapValuesType which violates
the guideline; instead remove the xs:redefine block and introduce a new composed
type (e.g., AbapValuesTypeWithDD40V) that references the original
asx:AbapValuesType and adds the DD40V element; specifically create a new
xs:complexType (name AbapValuesTypeWithDD40V) that contains a sequence with an
element/ref to asx:AbapValuesType (or embeds its content via composition) plus
an optional <DD40V type="asx:Dd40vType"/>, then update any places that expected
the redefined AbapValuesType to use the new AbapValuesTypeWithDD40V instead.

---

Nitpick comments:
In `@packages/adk/src/objects/ddic/doma/doma.model.ts`:
- Around line 43-45: The getter get crudContract currently returns the untyped
any; either replace any with the precise contract type exported by your ADT
client (the type of this.ctx.client.adt.ddic.domains) or, if no suitable type
exists, keep any but add a short explanatory comment above get crudContract
documenting why any is required and a TODO to fix once the contract typings are
added; update the signature from "protected override get crudContract(): any" to
use the correct interface name (or add the explanatory comment) so the intention
and limitation are explicit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e0bbdae-0898-47aa-ae6e-35e85c14b9b7

📥 Commits

Reviewing files that changed from the base of the PR and between 5412b37 and 41b6a4b.

⛔ Files ignored due to path filters (18)
  • packages/adt-contracts/src/generated/schemas.ts is excluded by !**/generated/**
  • packages/adt-plugin-abapgit/src/schemas/generated/index.ts is excluded by !**/generated/**
  • packages/adt-plugin-abapgit/src/schemas/generated/schemas/index.ts is excluded by !**/generated/**
  • packages/adt-plugin-abapgit/src/schemas/generated/schemas/tabl.ts is excluded by !**/generated/**
  • packages/adt-plugin-abapgit/src/schemas/generated/schemas/ttyp.ts is excluded by !**/generated/**
  • packages/adt-plugin-abapgit/src/schemas/generated/types/index.ts is excluded by !**/generated/**
  • packages/adt-plugin-abapgit/src/schemas/generated/types/tabl.ts is excluded by !**/generated/**
  • packages/adt-plugin-abapgit/src/schemas/generated/types/ttyp.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/schemas/sap/dataelements.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/schemas/sap/domain.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/schemas/sap/index.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/schemas/sap/tablesettings.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/schemas/sap/tabletype.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/typed.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/types/sap/dataelements.types.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/types/sap/domain.types.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/types/sap/tablesettings.types.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/types/sap/tabletype.types.ts is excluded by !**/generated/**
📒 Files selected for processing (39)
  • packages/adk/src/base/adt.ts
  • packages/adk/src/base/kinds.ts
  • packages/adk/src/index.ts
  • packages/adk/src/objects/ddic/doma/doma.model.ts
  • packages/adk/src/objects/ddic/doma/index.ts
  • packages/adk/src/objects/ddic/dtel/dtel.model.ts
  • packages/adk/src/objects/ddic/dtel/index.ts
  • packages/adk/src/objects/ddic/index.ts
  • packages/adk/src/objects/ddic/tabl/index.ts
  • packages/adk/src/objects/ddic/tabl/tabl.model.ts
  • packages/adk/src/objects/ddic/ttyp/index.ts
  • packages/adk/src/objects/ddic/ttyp/ttyp.model.ts
  • packages/adt-client/src/index.ts
  • packages/adt-contracts/src/adt/ddic/dataelements.ts
  • packages/adt-contracts/src/adt/ddic/domains.ts
  • packages/adt-contracts/src/adt/ddic/index.ts
  • packages/adt-contracts/src/adt/ddic/structures.ts
  • packages/adt-contracts/src/adt/ddic/tables.ts
  • packages/adt-contracts/src/adt/ddic/tabletypes.ts
  • packages/adt-contracts/src/adt/index.ts
  • packages/adt-plugin-abapgit/src/lib/handlers/adk.ts
  • packages/adt-plugin-abapgit/src/lib/handlers/objects/doma.ts
  • packages/adt-plugin-abapgit/src/lib/handlers/objects/dtel.ts
  • packages/adt-plugin-abapgit/src/lib/handlers/objects/index.ts
  • packages/adt-plugin-abapgit/src/lib/handlers/objects/tabl.ts
  • packages/adt-plugin-abapgit/src/lib/handlers/objects/ttyp.ts
  • packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_structure.tabl.xml
  • packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_transparent_table.tabl.xml
  • packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_string_table.ttyp.xml
  • packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_struct_table_type.ttyp.xml
  • packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts
  • packages/adt-plugin-abapgit/tests/schemas/ttyp.test.ts
  • packages/adt-plugin-abapgit/ts-xsd.config.ts
  • packages/adt-plugin-abapgit/xsd/tabl.xsd
  • packages/adt-plugin-abapgit/xsd/ttyp.xsd
  • packages/adt-plugin-abapgit/xsd/types/dd02v.xsd
  • packages/adt-plugin-abapgit/xsd/types/dd03p.xsd
  • packages/adt-plugin-abapgit/xsd/types/dd40v.xsd
  • packages/adt-schemas/ts-xsd.config.ts

Comment on lines +46 to +47
protected override get crudContract(): any {
return this.ctx.client.adt.ddic.dataelements;
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`)

Comment on lines +82 to +84
registerObjectType('TABL', TableKind, AdkTable);
// Note: Structure uses same main type TABL but different ADK kind
// Registration uses TABL main type - structures will be resolved via subtype logic
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect TABL registrations and structure-kind routing hints
rg -nP --type=ts "registerObjectType\\('TABL'" packages/adk/src
rg -nP --type=ts '\bStructure\b|\bStructureKind\b|TABL/DS|subtype' packages/adk/src/base packages/adk/src/objects
fd registry.ts packages/adk/src -x sed -n '1,300p' {}

Repository: abapify/adt-cli

Length of output: 7198


🏁 Script executed:

# Search for subtype-based resolution logic
rg -nP --type=ts "TABL.*DS|subtype.*resolve|Kind.*subtype" packages/adk/src

# Search for AdkStructure usage and registration
rg -nP --type=ts "AdkStructure" packages/adk/src

# Search for factory or builder patterns that might do subtype routing
rg -nP --type=ts "switch|case.*DS|if.*subtype" packages/adk/src/base

Repository: abapify/adt-cli

Length of output: 1315


🏁 Script executed:

# View the context around kinds.ts:105 where AdkStructure is referenced
sed -n '95,115p' packages/adk/src/base/kinds.ts

# Search for how TABL/DS is actually instantiated or resolved at runtime
rg -nP --type=ts "new AdkStructure|AdkStructure\(" packages/adk/src

# Check if there's a factory or resolver that checks subtypes
rg -nP --type=ts "resolveType|resolveKind" packages/adk/src -A 3

Repository: abapify/adt-cli

Length of output: 2644


AdkStructure is not reachable via registry resolution for TABL/DS — subtype routing logic is missing.

The resolveType() function extracts only the main type (TABL), returning the AdkTable constructor regardless of subtype. Code comments claim "structures will be resolved via subtype logic," but this logic does not exist in the registry or factory. Either register TABL/DS explicitly alongside the main TABL registration, or implement subtype-aware resolution in resolveType().

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

In `@packages/adk/src/objects/ddic/tabl/tabl.model.ts` around lines 82 - 84, The
registry currently registers 'TABL' -> AdkTable via registerObjectType('TABL',
TableKind, AdkTable) but resolveType() only extracts the main type and always
returns AdkTable, so AdkStructure (for 'TABL/DS') is unreachable; fix by either
(A) registering the structure subtype explicitly (registerObjectType('TABL/DS',
TableKind, AdkStructure)) so the factory can return AdkStructure, or (B) make
resolveType() subtype-aware: parse the full type token (e.g. split on '/')
inside resolveType(), check for known subtypes like 'DS' and map them to
AdkStructure (while falling back to AdkTable for plain 'TABL'), and update
registry lookup logic to prefer exact subtype keys before main-type keys.

Copy link
Member Author

Choose a reason for hiding this comment

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

Acknowledged for follow-up — same as the Copilot comment about TABL/DS vs TABL/DT. Registry needs subtype-awareness. The AdkStructure class is already defined and working, just not yet registered by subtype. Will address in a follow-up 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 — thanks for the context. Since this is deferred, would you like me to open a GitHub issue to track the registry subtype-awareness work for TABL/DS / TABL/DT? That way the gap between AdkStructure being defined and it being resolvable via the registry doesn't get lost.


🧠 Learnings used
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-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: 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-client/AGENTS.md:0-0
Timestamp: 2026-03-02T17:24:07.245Z
Learning: Applies to packages/adt-client/src/adt/**/*-schema.ts : For XML Schemas, ALWAYS use the `createSchema()` helper to enable speci type inference with the `_infer` property

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/adt-schemas/** : Schema literals should be exported as TypeScript exports from `adt-schemas` package, derived from XSD via ts-xsd parsing

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/tests/contracts/**/*.ts : Use fixtures from 'adt-fixtures' for testing real SAP XML samples in contract operations

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/src/lib/handlers/objects/**/*.ts : Handler `toAbapGit()` must map to data structure with return type inferred from schema._values

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-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.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-cli/AGENTS.md:0-0
Timestamp: 2026-03-02T17:23:46.791Z
Learning: Applies to packages/adt-cli/src/lib/commands/**/*.ts : When migrating commands from v1 to v2, check if v2 contract exists, update imports to use `getAdtClientV2()`, replace client initialization, update API calls to v2 endpoints, test the command, and update AGENTS.md with migration status.

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`)

Comment on lines +46 to +47
protected override get crudContract(): any {
return this.ctx.client.adt.ddic.tabletypes;
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

🧩 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
# Find the parent class of ttyp.model.ts
head -30 packages/adk/src/objects/ddic/ttyp/ttyp.model.ts

Repository: abapify/adt-cli

Length of output: 1029


🏁 Script executed:

#!/bin/bash
# Search for crudContract definition in base classes
find packages/adk/src -name "*.ts" -type f | xargs grep -l "crudContract" | head -10

Repository: abapify/adt-cli

Length of output: 557


🏁 Script executed:

#!/bin/bash
# Look for AdkContext type definition to understand the chain
rg -nA 5 "interface AdkContext|type AdkContext" packages/adk/src

Repository: abapify/adt-cli

Length of output: 402


🏁 Script executed:

#!/bin/bash
# Check if there are any inline comments explaining the any type
rg -B 2 "crudContract\(\):\s*any" packages/adk/src/objects/ddic

Repository: abapify/adt-cli

Length of output: 1175


🏁 Script executed:

#!/bin/bash
# Find crudContract getter definition in base class
rg -A 3 "protected.*crudContract|abstract.*crudContract" packages/adk/src/base/model.ts

Repository: abapify/adt-cli

Length of output: 123


🏁 Script executed:

#!/bin/bash
# Check the full AdkContext and client structure
cat packages/adk/src/base/context.ts

Repository: abapify/adt-cli

Length of output: 1069


🏁 Script executed:

#!/bin/bash
# Check what ctx.client.adt.ddic actually returns
rg -nA 5 "ddic" packages/adk/src/base/context.ts

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Find AdtClient type definition
rg -n "export.*interface AdtClient|export.*type AdtClient" packages/adk/src/base/adt.ts

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Get the full AdtClient structure
head -100 packages/adk/src/base/adt.ts

Repository: abapify/adt-cli

Length of output: 3069


🏁 Script executed:

#!/bin/bash
# Look for where ddic.tabletypes is defined
find packages/adk/src -type f -name "*.ts" | xargs grep -l "ddic" | xargs grep -l "tabletypes"

Repository: abapify/adt-cli

Length of output: 107


🏁 Script executed:

#!/bin/bash
# Check what AdtClient exports from adt-client package
grep -n "ddic\|tabletypes" node_modules/@abapify/adt-client/dist/*/index.d.ts 2>/dev/null | head -20

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Look at imports in ttyp.model.ts to see what types are available
head -50 packages/adk/src/objects/ddic/ttyp/ttyp.model.ts

Repository: abapify/adt-cli

Length of output: 1569


🏁 Script executed:

#!/bin/bash
# Check the base model to see how crudContract is used
grep -n "crudContract" packages/adk/src/base/model.ts | head -20

Repository: abapify/adt-cli

Length of output: 864


🏁 Script executed:

#!/bin/bash
# See full context of crudContract in base/model.ts
rg -B 5 -A 10 "protected get crudContract" packages/adk/src/base/model.ts

Repository: abapify/adt-cli

Length of output: 569


🏁 Script executed:

#!/bin/bash
# Check other DDIC models to see how they override crudContract
cat packages/adk/src/objects/ddic/tabl/tabl.model.ts | grep -A 5 "crudContract"

Repository: abapify/adt-cli

Length of output: 499


🏁 Script executed:

#!/bin/bash
# Check if there are other object types with typed crudContract (non-any)
rg -A 1 "protected override get crudContract\(\):" packages/adk/src/objects --type=ts | head -40

Repository: abapify/adt-cli

Length of output: 2059


🏁 Script executed:

#!/bin/bash
# Look at adt-client to understand what type tabletypes contract is
find packages -name "*.ts" -type f | xargs grep -l "\.tabletypes" | head -5

Repository: abapify/adt-cli

Length of output: 107


🏁 Script executed:

#!/bin/bash
# Check the adt-client package index to see if ddic is exposed
cat packages/adt-client/src/index.ts | grep -i ddic

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check adt-client package structure
ls -la packages/adt-client/src

Repository: abapify/adt-cli

Length of output: 644


🏁 Script executed:

#!/bin/bash
# Look at adt-client main index
head -80 packages/adt-client/src/index.ts

Repository: abapify/adt-cli

Length of output: 2412


🏁 Script executed:

#!/bin/bash
# Find where ddic contracts are defined in adt-client
find packages/adt-client/src -name "*ddic*" -o -name "*tabl*" | head -10

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if there are type definitions in adt-client for the contracts
rg -n "class|interface" packages/adt-client/src/index.ts | head -20

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check the base class comment more carefully
sed -n '535,550p' packages/adk/src/base/model.ts

Repository: abapify/adt-cli

Length of output: 518


🏁 Script executed:

#!/bin/bash
# Look for CrudContract usage in the codebase
rg "CrudContract" packages/adk/src --type=ts -A 2 | head -30

Repository: abapify/adt-cli

Length of output: 244


🏁 Script executed:

#!/bin/bash
# Check if AdtClient type definition is accessible and what it looks like
rg -n "AdtClient" packages/adt-client/src/client.ts

Repository: abapify/adt-cli

Length of output: 498


🏁 Script executed:

#!/bin/bash
# Look at the client.ts file to understand the structure
cat packages/adt-client/src/client.ts

Repository: abapify/adt-cli

Length of output: 3644


🏁 Script executed:

#!/bin/bash
# Check if CrudContract is the right base type for all these contracts
rg -B 3 "CrudContract" packages/adt-client/src/index.ts

Repository: abapify/adt-cli

Length of output: 250


🏁 Script executed:

#!/bin/bash
# Check what type tabletypes, tables, etc. actually are
# Look for contract definitions in adt-contracts
find packages -name "*.d.ts" 2>/dev/null | xargs grep -l "tabletypes\|ddic" | head -5

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Alternative: check if there's a contracts package
ls -la packages/ | grep contract

Repository: abapify/adt-cli

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Look at the actual return value - is it ever used in a way that requires the concrete type?
rg -A 5 "this\.crudContract" packages/adk/src/base/model.ts | head -40

Repository: abapify/adt-cli

Length of output: 850


🏁 Script executed:

#!/bin/bash
# Check if the base class comment justifies the any usage for subclasses
sed -n '535,550p' packages/adk/src/base/model.ts

Repository: abapify/adt-cli

Length of output: 518


🏁 Script executed:

#!/bin/bash
# Look for adt-contracts to find the actual contract types
ls -la packages/adt-contracts/src/

Repository: abapify/adt-cli

Length of output: 522


🏁 Script executed:

#!/bin/bash
# Check what ddic contract types are available in adt-contracts
find packages/adt-contracts/src -name "*ddic*" -o -name "*tabl*" -o -name "*ttyp*" | head -10

Repository: abapify/adt-cli

Length of output: 190


🏁 Script executed:

#!/bin/bash
# Look at adt-contracts index
head -150 packages/adt-contracts/src/index.ts | tail -100

Repository: abapify/adt-cli

Length of output: 932


🏁 Script executed:

#!/bin/bash
# Check the actual tabletypes contract definition
cat packages/adt-contracts/src/adt/ddic/tabletypes.ts

Repository: abapify/adt-cli

Length of output: 751


🏁 Script executed:

#!/bin/bash
# Check if there's a CrudContract type that wraps these
rg -n "export.*CrudContract|interface.*CrudContract" packages/adt-contracts/src --type=ts

Repository: abapify/adt-cli

Length of output: 247


🏁 Script executed:

#!/bin/bash
# Look at base.ts to understand CrudContract
cat packages/adt-contracts/src/base.ts

Repository: abapify/adt-cli

Length of output: 2739


🏁 Script executed:

#!/bin/bash
# Check if TabletypesContract is exported and accessible in ADK
grep -r "TabletypesContract\|TableTypeResponse" packages/adk/src --type=ts

Repository: abapify/adt-cli

Length of output: 179


🏁 Script executed:

#!/bin/bash
# Check the pattern - is there any override with a concrete type (not any)?
rg -A 1 "protected override get crudContract" packages/adk/src/objects --type=ts -N

Repository: abapify/adt-cli

Length of output: 2059


🏁 Script executed:

#!/bin/bash
# Verify what the actual return type should be - check adt-contracts exports
rg "export.*Contract" packages/adt-contracts/src/adt/ddic/tabletypes.ts

Repository: abapify/adt-cli

Length of output: 159


🏁 Script executed:

#!/bin/bash
# Check if the suggested type annotation syntax is even valid by looking for similar patterns
rg "AdkContext\[" packages/adk/src --type=ts

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if there's a way to properly type crudContract using typeof
cat packages/adt-contracts/src/adt/ddic/tabletypes.ts | grep -A 5 "export"

Repository: abapify/adt-cli

Length of output: 437


🏁 Script executed:

#!/bin/bash
# Check how other files in ADK import contract types
rg "import.*Contract" packages/adk/src/objects --type=ts | head -10

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Look at the base class eslint comment more carefully
sed -n '540,548p' packages/adk/src/base/model.ts

Repository: abapify/adt-cli

Length of output: 251


🏁 Script executed:

#!/bin/bash
# Check what contract types are exported from adt-contracts
rg "export type.*Contract" packages/adt-contracts/src/adt/ddic --type=ts

Repository: abapify/adt-cli

Length of output: 385


🏁 Script executed:

#!/bin/bash
# Check if these contract types can be imported into ADK
rg "from '@abapify/adt-contracts" packages/adk/src --type=ts | head -10

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Verify the base/adt.ts reexports to understand what's available to import
cat packages/adk/src/base/adt.ts | grep -A 50 "Re-export types from adt-client"

Repository: abapify/adt-cli

Length of output: 1674


🏁 Script executed:

#!/bin/bash
# Check if there's any pattern of using typeof with imported contracts
rg "typeof.*Contract" packages/adk/src --type=ts

Repository: abapify/adt-cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if contract types from adt-contracts are reexported anywhere accessible to ADK
find packages/adk -name "*.ts" -type f | xargs grep "TabletypesContract\|DomainsContract" 2>/dev/null

Repository: abapify/adt-cli

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Check what adt-contracts actually exports publicly
cat packages/adt-contracts/src/adt/index.ts

Repository: abapify/adt-cli

Length of output: 2138


🏁 Script executed:

#!/bin/bash
# Verify the eslint-disable comment in base class applies to overrides
cat -n packages/adk/src/base/model.ts | sed -n '538,550p'

Repository: abapify/adt-cli

Length of output: 562


🏁 Script executed:

#!/bin/bash
# Check the TypeScript compiler output or tsconfig to see how strict mode is configured
cat packages/adk/tsconfig.json | grep -A 20 "compilerOptions"

Repository: abapify/adt-cli

Length of output: 406


🏁 Script executed:

#!/bin/bash
# Check the full context of the base class crudContract with surrounding comments
sed -n '530,550p' packages/adk/src/base/model.ts

Repository: abapify/adt-cli

Length of output: 650


🏁 Script executed:

#!/bin/bash
# Check if the eslint-disable comment in base class applies to overrides or just that one getter
grep -B 10 "eslint-disable.*any" packages/adk/src/base/model.ts | head -20

Repository: abapify/adt-cli

Length of output: 734


🏁 Script executed:

#!/bin/bash
# Check if there are any comments in the subclass overrides explaining the any
sed -n '44,48p' packages/adk/src/objects/ddic/ttyp/ttyp.model.ts

Repository: abapify/adt-cli

Length of output: 185


🏁 Script executed:

#!/bin/bash
# Verify that TabletypesContract type is not accessible in ADK (not in base/adt.ts exports)
cat packages/adk/src/base/adt.ts | grep -i "Contract"

Repository: abapify/adt-cli

Length of output: 1437


🏁 Script executed:

#!/bin/bash
# Check if TabletypesContract and other Contract types are exported from adt-contracts/index
cat packages/adt-contracts/src/index.ts

Repository: abapify/adt-cli

Length of output: 932


🏁 Script executed:

#!/bin/bash
# Final check - see if the crudContract in subclasses needs the comment too
cat -n packages/adk/src/objects/ddic/ttyp/ttyp.model.ts | sed -n '42,50p'

Repository: abapify/adt-cli

Length of output: 366


Add eslint-disable comment to crudContract override, or properly type the getter.

The override uses any without explanation, violating strict mode. All 10 overrides across DDIC and repository objects have the same issue.

The suggested type AdkContext['client']['adt']['ddic']['tabletypes'] won't compile. Either add a comment justifying the any:

  protected override get crudContract(): any {
+   // Contract type not yet exported from base/adt.ts
    return this.ctx.client.adt.ddic.tabletypes;
  }

Or export TabletypesContract and similar types from packages/adk/src/base/adt.ts and import them in the models. This same pattern applies to all 10 overrides: ttyp, tabl (2x), dtel, doma, prog, intf, fugr, devc, clas.

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

In `@packages/adk/src/objects/ddic/ttyp/ttyp.model.ts` around lines 46 - 47, The
getter override crudContract currently returns any (protected override get
crudContract(): any) which breaks strict linting; fix by either (A) adding a
one-line eslint disable with a brief justification above the getter (e.g.,
eslint-disable-next-line `@typescript-eslint/no-explicit-any` // wrapper for ADT
client contract) or (B) properly typing it: export the concrete
TabletypesContract type from packages/adk/src/base/adt.ts, import that type into
this model, and change the getter signature to use that type and return
this.ctx.client.adt.ddic.tabletypes; apply the same pattern for the other
DDIC/repository overrides (tabl, dtel, doma, prog, intf, fugr, devc, clas).

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as dtel/tabl — crudContract(): any is the established pattern. Not a regression.

{
path: 'tabl/zage_structure.tabl.xml',
validate: (data) => {
const root = (data as any).abapGit;
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for untyped any-casts in TABL tests and inspect scenario typing contract
rg -n "as any" packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts
rg -n -C3 "type SchemaScenario|interface SchemaScenario|validate" packages/adt-plugin-abapgit/tests/schemas/base/scenario.ts

Repository: abapify/adt-cli

Length of output: 1532


🏁 Script executed:

head -80 packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts

Repository: abapify/adt-cli

Length of output: 2796


🏁 Script executed:

# Find schema type definitions for TABL
rg -n "TablSchema|export.*Tabl" packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts
rg -n "as TablSchema" packages/adt-plugin-abapgit/tests/schemas/

Repository: abapify/adt-cli

Length of output: 249


🏁 Script executed:

head -50 packages/adt-plugin-abapgit/src/schemas/generated/types/tabl.ts

Repository: abapify/adt-cli

Length of output: 1535


🏁 Script executed:

# Search for abapGit property in TablSchema
rg -n "abapGit" packages/adt-plugin-abapgit/src/schemas/generated/types/tabl.ts | head -5

Repository: abapify/adt-cli

Length of output: 78


Remove unnecessary as any cast in TABL schema tests.

The data parameter is already properly typed as TablSchema from the generic SchemaScenario<TablSchema> instantiation. The cast serves no purpose and violates strict mode typing requirements.

♻️ Proposed fix
-        const root = (data as any).abapGit;
+        const root = data.abapGit;

Also applies to: 62-62

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

In `@packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts` at line 26, Remove
the unnecessary cast by accessing the typed property directly: replace instances
of "const root = (data as any).abapGit;" with "const root = data.abapGit;" (data
is already typed via SchemaScenario<TablSchema>), and make the same change for
the other occurrence using the same symbols (data, abapGit, TablSchema,
SchemaScenario<TablSchema>, root).

Comment on lines +19 to +31
<!-- Redefine AbapValuesType to include TABL-specific elements -->
<xs:redefine schemaLocation="asx.xsd">
<xs:complexType name="AbapValuesType">
<xs:complexContent>
<xs:extension base="asx:AbapValuesType">
<xs:sequence>
<xs:element name="DD02V" type="asx:Dd02vType" minOccurs="0"/>
<xs:element name="DD03P_TABLE" type="asx:Dd03pTableType" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:redefine>
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for how other document schemas extend or use AbapValuesType
rg -n "AbapValuesType" --type=xml packages/adt-plugin-abapgit/xsd/

# Check if other schemas avoid xs:redefine
rg -n "xs:redefine" --type=xml packages/adt-plugin-abapgit/xsd/

Repository: abapify/adt-cli

Length of output: 4942


Remove xs:redefine and use composition instead.

The schema violates the coding guideline: "Do NOT use xs:redefine or xs:override in XSD schemas." This pattern appears across all document schemas (doma.xsd, ttyp.xsd, intf.xsd, dtel.xsd, devc.xsd, clas.xsd).

Refactor by:

  1. Creating a new TablValuesType that extends asx:AbapValuesType with TABL-specific elements
  2. Replacing the xs:redefine block with an xs:import of asx.xsd and defining the new type directly

This avoids redefinition and aligns with XSD best practices.

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

In `@packages/adt-plugin-abapgit/xsd/tabl.xsd` around lines 19 - 31, The current
xs:redefine block must be removed and replaced by composing a new TablValuesType
that extends asx:AbapValuesType: add an xs:import for asx.xsd, define a new
complexType named TablValuesType with xs:complexContent xs:extension
base="asx:AbapValuesType" and include the TABL-specific elements DD02V and
DD03P_TABLE (types asx:Dd02vType and asx:Dd03pTableType); replace any schema
declarations that expected the redefined AbapValuesType with references to
TablValuesType and remove the xs:redefine block entirely to avoid using
xs:redefine/xs:override.

Comment on lines +18 to +29
<!-- Redefine AbapValuesType to include TTYP-specific elements -->
<xs:redefine schemaLocation="asx.xsd">
<xs:complexType name="AbapValuesType">
<xs:complexContent>
<xs:extension base="asx:AbapValuesType">
<xs:sequence>
<xs:element name="DD40V" type="asx:Dd40vType" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:redefine>
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

Guideline violation: xs:redefine is explicitly forbidden.

The coding guidelines state: "Do NOT use xs:redefine or xs:override in XSD schemas". The comment on line 8 acknowledges using xs:redefine, which directly violates this rule.

Consider an alternative approach such as:

  • Defining a new standalone type that composes the base elements plus DD40V
  • Using a separate schema structure that avoids redefining the base type
🔧 Alternative approach sketch

Instead of redefining AbapValuesType, create a composed type:

-  <!-- Redefine AbapValuesType to include TTYP-specific elements -->
-  <xs:redefine schemaLocation="asx.xsd">
-    <xs:complexType name="AbapValuesType">
-      <xs:complexContent>
-        <xs:extension base="asx:AbapValuesType">
-          <xs:sequence>
-            <xs:element name="DD40V" type="asx:Dd40vType" minOccurs="0"/>
-          </xs:sequence>
-        </xs:extension>
-      </xs:complexContent>
-    </xs:complexType>
-  </xs:redefine>
+  <!-- Include base schema -->
+  <xs:include schemaLocation="asx.xsd"/>
+
+  <!-- TTYP-specific values type extending base with DD40V -->
+  <xs:complexType name="TtypValuesType">
+    <xs:all>
+      <xs:element name="DD40V" type="asx:Dd40vType" minOccurs="0"/>
+    </xs:all>
+  </xs:complexType>

As per coding guidelines: "Do NOT use xs:redefine or xs:override in XSD schemas".

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

In `@packages/adt-plugin-abapgit/xsd/ttyp.xsd` around lines 18 - 29, You used
xs:redefine to alter AbapValuesType which violates the guideline; instead remove
the xs:redefine block and introduce a new composed type (e.g.,
AbapValuesTypeWithDD40V) that references the original asx:AbapValuesType and
adds the DD40V element; specifically create a new xs:complexType (name
AbapValuesTypeWithDD40V) that contains a sequence with an element/ref to
asx:AbapValuesType (or embeds its content via composition) plus an optional
<DD40V type="asx:Dd40vType"/>, then update any places that expected the
redefined AbapValuesType to use the new AbapValuesTypeWithDD40V instead.

- Handle HTTP 405 'Method Not Allowed' as upsert fallback trigger
  (DDIC endpoints like TABL/TTYP return 405 instead of 404)
- Handle HTTP 422 'already exists' after create fallback by marking
  object as unchanged instead of failing the deploy
- Apply isAlreadyExistsError check in both lock catch and saveViaContract
  catch paths for complete coverage
- Make unlockAll() resilient to individual unlock failures
- Fix crud() POST Content-Type: use specific content type instead of
  generic 'application/*'
- Update contract tests to match new POST Content-Type behavior
Add optional 'accept' field to CrudOptions for multi-version Accept
headers on GET/POST/PUT requests, following SAP ADT best practices:

- GET Accept: multi-version, newest first (server picks best match)
- POST/PUT Accept: same multi-version list
- POST/PUT Content-Type: single specific version (payload format)

Updated all 10 crud() call sites with proper version fallback lists
based on fr0ster/mcp-abap-adt-clients reference implementation.

The 'accept' field is optional and falls back to 'contentType' for
backward compatibility.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
packages/adt-contracts/src/adt/ddic/tables.ts (1)

33-43: ⚠️ Potential issue | 🟠 Major

URL-encode DDIC table names in all path builders.

Line 41 (and CRUD-generated paths) lowercases names but does not URL-encode them. Namespaced object names can produce invalid ADT URLs.

🔧 Proposed fix
 export const tablesContract = {
   ...crud({
     basePath,
     schema: blueSourceSchema,
     contentType,
     accept,
+    nameTransform: (n: string) => encodeURIComponent(n.toLowerCase()),
   }),

   objectstructure: (name: string, options?: { version?: string }) =>
-    http.get(`${basePath}/${name.toLowerCase()}/objectstructure`, {
+    http.get(
+      `${basePath}/${encodeURIComponent(name.toLowerCase())}/objectstructure`,
+      {
       responses: { 200: undefined },
       query: options?.version ? { version: options.version } : undefined,
-    }),
+      }
+    ),
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adt-contracts/src/adt/ddic/tables.ts` around lines 33 - 43, The path
builders currently use name.toLowerCase() (e.g., in objectstructure) and
CRUD-generated paths without URL-encoding, which can break for namespaced DDIC
table names; update all places that build paths from a table/object name (for
example the objectstructure function and the CRUD helpers that use basePath and
name) to apply encodeURIComponent to the lowercased name (e.g.,
encodeURIComponent(name.toLowerCase())) before interpolating into the URL so all
path segments are safely URL-encoded.
packages/adk/src/objects/ddic/tabl/tabl.model.ts (1)

46-47: ⚠️ Potential issue | 🟠 Major

Replace crudContract(): any with precise indexed client types.

Both getters erase contract types and bypass strict typing.

🔧 Proposed fix
-  protected override get crudContract(): any {
+  protected override get crudContract(): AdkContext['client']['adt']['ddic']['tables'] {
     return this.ctx.client.adt.ddic.tables;
   }
@@
-  protected override get crudContract(): any {
+  protected override get crudContract(): AdkContext['client']['adt']['ddic']['structures'] {
     return this.ctx.client.adt.ddic.structures;
   }
#!/bin/bash
# Verify no explicit `crudContract(): any` remains in DDIC object models
rg -nP --type=ts 'protected\s+override\s+get\s+crudContract\(\)\s*:\s*any' packages/adk/src/objects/ddic

As per coding guidelines "Use TypeScript strict mode — no any without a comment explaining why".

Also applies to: 73-74

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

In `@packages/adk/src/objects/ddic/tabl/tabl.model.ts` around lines 46 - 47, The
getter crudContract currently returns type any; change its signature to a
precise indexed client type instead of any by using the actual client member
type—for example declare the getter as returning typeof
this.ctx.client.adt.ddic.tables (or the exact interface/type exported for the
ADT tables client) and keep the implementation return
this.ctx.client.adt.ddic.tables; update the same pattern for the other DDIC
object model getters referenced (e.g., the occurrences around the later lines
73-74) so none of them use : any and they all use the concrete typeof
this.ctx.client.adt.ddic.<...> client types.
packages/adt-contracts/src/adt/ddic/tabletypes.ts (1)

6-6: ⚠️ Potential issue | 🟡 Minor

Fix object-type label in module docs (TTYP/DATTYP/TT).

Line 6 is inconsistent with the table type object-kind naming used in this PR.

📝 Proposed fix
- * Object type: TTYP/DA (ttypda)
+ * Object type: TTYP/TT (ttyptt)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adt-contracts/src/adt/ddic/tabletypes.ts` at line 6, Update the
module documentation comment that currently reads "Object type: TTYP/DA
(ttypda)" to the correct table-type label "TTYP/TT" (replace the full string so
it reads "Object type: TTYP/TT"); locate this in the header comment of the
tabletypes.ts module where the object-type label is declared and modify the
label text accordingly so the docs match the table type naming used elsewhere in
this PR.
🧹 Nitpick comments (2)
packages/adt-contracts/src/helpers/crud.ts (1)

527-534: Verify backward compatibility of the repo() helper signature change.

The new accept parameter was added as the 5th positional argument. Existing callers passing only 4 arguments remain compatible since it's optional, but this positional API could become unwieldy if more options are needed.

Consider whether future callers would benefit from an options object pattern instead. However, given the current usage is limited, this is acceptable for now.

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

In `@packages/adt-contracts/src/helpers/crud.ts` around lines 527 - 534, The
repo() helper added a new positional accept parameter which is optional but
could break ergonomics long-term; to preserve backward compatibility while
allowing future options, change repo() to accept a single options object as the
last argument (e.g., repo(basePath, schema, contentType, nameTransform?,
options?)) or add an overloaded signature that accepts either the old positional
args or a final options object, then adapt the call to crud(...) to unpack
options.accept and nameTransform appropriately; update the function declaration
for repo and its call-site to support both forms and ensure crud(...) keeps
accepting accept via the options you pass through.
packages/adk/src/base/model.ts (1)

478-493: Deduplicate the upsert→create fallback block.

The same fallback/createErr handling is repeated in two catch paths. Extracting it into one helper will prevent drift and make future fixes safer.

Also applies to: 525-539

🤖 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 478 - 493, The upsert→create
fallback and its createErr handling is duplicated; extract that logic into a
single private helper (e.g., fallbackToCreateOrHandleError or
handleUpsertCreateFallback) inside the same class and have both catch paths call
it; the helper should call this.save({ ...options, mode: 'create' }), catch
createErr, set this._unchanged = true and return this when
this.isAlreadyExistsError(createErr), or rethrow otherwise, and replace the
duplicated blocks that currently reference shouldFallbackToCreate, save,
isAlreadyExistsError, and _unchanged with a single call to that helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/adk/src/base/model.ts`:
- Around line 478-493: The issue: _unchanged is set to true in multiple save()
branches but not reset, causing stale state on subsequent save() calls; to fix,
reset this._unchanged = false at the start of the save method so each invocation
starts fresh. Update the save() function (referencing the save method in
model.ts and the _unchanged property) to clear _unchanged before any
early-return checks or branch logic (affects the upsert fallback block where
isAlreadyExistsError sets _unchanged and the similar branch around lines
525-544); ensure no other code relies on persistent _unchanged across separate
save() calls.
- Around line 633-644: In isAlreadyExistsError, make the matching
case-insensitive and robust by normalizing the error text before checking; e.g.,
obtain a safe string from the error (use e instanceof Error ? e.message :
String(e)), call .toLowerCase() on it, and then check .includes('422') ||
.includes('unprocessable') && .includes('already exists') (all lowercased) so
the function (isAlreadyExistsError) correctly detects variations in casing and
non-Error inputs.

---

Duplicate comments:
In `@packages/adk/src/objects/ddic/tabl/tabl.model.ts`:
- Around line 46-47: The getter crudContract currently returns type any; change
its signature to a precise indexed client type instead of any by using the
actual client member type—for example declare the getter as returning typeof
this.ctx.client.adt.ddic.tables (or the exact interface/type exported for the
ADT tables client) and keep the implementation return
this.ctx.client.adt.ddic.tables; update the same pattern for the other DDIC
object model getters referenced (e.g., the occurrences around the later lines
73-74) so none of them use : any and they all use the concrete typeof
this.ctx.client.adt.ddic.<...> client types.

In `@packages/adt-contracts/src/adt/ddic/tables.ts`:
- Around line 33-43: The path builders currently use name.toLowerCase() (e.g.,
in objectstructure) and CRUD-generated paths without URL-encoding, which can
break for namespaced DDIC table names; update all places that build paths from a
table/object name (for example the objectstructure function and the CRUD helpers
that use basePath and name) to apply encodeURIComponent to the lowercased name
(e.g., encodeURIComponent(name.toLowerCase())) before interpolating into the URL
so all path segments are safely URL-encoded.

In `@packages/adt-contracts/src/adt/ddic/tabletypes.ts`:
- Line 6: Update the module documentation comment that currently reads "Object
type: TTYP/DA (ttypda)" to the correct table-type label "TTYP/TT" (replace the
full string so it reads "Object type: TTYP/TT"); locate this in the header
comment of the tabletypes.ts module where the object-type label is declared and
modify the label text accordingly so the docs match the table type naming used
elsewhere in this PR.

---

Nitpick comments:
In `@packages/adk/src/base/model.ts`:
- Around line 478-493: The upsert→create fallback and its createErr handling is
duplicated; extract that logic into a single private helper (e.g.,
fallbackToCreateOrHandleError or handleUpsertCreateFallback) inside the same
class and have both catch paths call it; the helper should call this.save({
...options, mode: 'create' }), catch createErr, set this._unchanged = true and
return this when this.isAlreadyExistsError(createErr), or rethrow otherwise, and
replace the duplicated blocks that currently reference shouldFallbackToCreate,
save, isAlreadyExistsError, and _unchanged with a single call to that helper.

In `@packages/adt-contracts/src/helpers/crud.ts`:
- Around line 527-534: The repo() helper added a new positional accept parameter
which is optional but could break ergonomics long-term; to preserve backward
compatibility while allowing future options, change repo() to accept a single
options object as the last argument (e.g., repo(basePath, schema, contentType,
nameTransform?, options?)) or add an overloaded signature that accepts either
the old positional args or a final options object, then adapt the call to
crud(...) to unpack options.accept and nameTransform appropriately; update the
function declaration for repo and its call-site to support both forms and ensure
crud(...) keeps accepting accept via the options you pass through.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 55178b00-f97c-4823-a2ca-ea44bdd7f944

📥 Commits

Reviewing files that changed from the base of the PR and between 41b6a4b and f16f525.

⛔ Files ignored due to path filters (7)
  • packages/adt-contracts/src/generated/schemas.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/schemas/custom/blueSource.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/schemas/custom/dataelementWrapper.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/schemas/custom/index.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/typed.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/types/custom/blueSource.types.ts is excluded by !**/generated/**
  • packages/adt-schemas/src/schemas/generated/types/custom/dataelementWrapper.types.ts is excluded by !**/generated/**
📒 Files selected for processing (23)
  • packages/adk/src/base/adt.ts
  • packages/adk/src/base/model.ts
  • packages/adk/src/base/object-set.ts
  • packages/adk/src/objects/ddic/dtel/dtel.model.ts
  • packages/adk/src/objects/ddic/tabl/tabl.model.ts
  • packages/adt-client/src/index.ts
  • packages/adt-contracts/src/adt/ddic/dataelements.ts
  • packages/adt-contracts/src/adt/ddic/domains.ts
  • packages/adt-contracts/src/adt/ddic/index.ts
  • packages/adt-contracts/src/adt/ddic/structures.ts
  • packages/adt-contracts/src/adt/ddic/tables.ts
  • packages/adt-contracts/src/adt/ddic/tabletypes.ts
  • packages/adt-contracts/src/adt/functions/groups.ts
  • packages/adt-contracts/src/adt/oo/classes.ts
  • packages/adt-contracts/src/adt/oo/interfaces.ts
  • packages/adt-contracts/src/adt/packages/index.ts
  • packages/adt-contracts/src/adt/programs/programs.ts
  • packages/adt-contracts/src/helpers/crud.ts
  • packages/adt-contracts/tests/contracts/oo.test.ts
  • packages/adt-contracts/tests/contracts/packages.test.ts
  • packages/adt-schemas/.xsd/custom/blueSource.xsd
  • packages/adt-schemas/.xsd/custom/dataelementWrapper.xsd
  • packages/adt-schemas/ts-xsd.config.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/adt-contracts/src/adt/ddic/domains.ts
  • packages/adt-contracts/src/adt/ddic/index.ts
  • packages/adt-client/src/index.ts
  • packages/adk/src/objects/ddic/dtel/dtel.model.ts
  • packages/adt-contracts/src/adt/ddic/structures.ts

…fallback, case-insensitive error matching

- Reset _unchanged at start of save() to prevent stale state across calls
- Extract duplicated upsert-create fallback into fallbackToCreate() helper
- Make isAlreadyExistsError() case-insensitive for robustness
- Fix TTYP/DA typo to TTYP/TT in tabletypes contract comment
@sonarqubecloud
Copy link

@ThePlenkov
Copy link
Member Author

Addressing review findings (CodeRabbit, Copilot, SonarCloud)

✅ Fixed in 2816dd4

Finding Source Fix
TTYP/DA typo in tabletypes.ts comment Copilot Changed to TTYP/TT
_unchanged not reset between save() calls CodeRabbit Added this._unchanged = false at start of save()
isAlreadyExistsError() case-sensitive matching CodeRabbit Normalized to .toLowerCase() before matching
Duplicated upsert→create fallback logic CodeRabbit Extracted into fallbackToCreate() private helper

⏭️ Acknowledged — tracked for follow-up

Finding Source Reason
TABL handler does not distinguish TABL/DT vs TABL/DS during deserialization Copilot Valid — registry currently maps by main type only. fromAbapGit correctly returns the subtype, but constructor selection needs subtype-awareness. Will address in a follow-up PR.

🚫 Dismissed — not applicable

Finding Source Reason
xs:redefine in tabl.xsd / ttyp.xsd CodeRabbit Established pattern used by ALL 7 object schemas (clas, intf, devc, doma, dtel, tabl, ttyp). Removing xs:redefine requires rearchitecting the entire XSD hierarchy + ts-xsd codegen pipeline. Not in scope for this PR.
crudContract returns : any CodeRabbit Pre-existing base class limitation — ALL ADK models use the same pattern. The base class is typed as any because ADK intentionally does not import contract types from adt-contracts (dependency direction).
URL encoding in tables.ts contract paths CodeRabbit Already handled in ADK model objectUri getter via encodeURIComponent(). Contract paths use lowercase transform only.
repo() positional params ergonomics CodeRabbit Existing API predating this PR — not a regression.
as any casts in test validate callbacks CodeRabbit Established pattern across ALL 7 schema test files. Generated types are union types requiring a cast to access the root variant.

SonarCloud

No new issues found on the PR (0 issues reported).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/adk/src/base/model.ts`:
- Around line 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d30d4aa-a61a-415e-93af-519af528d2a3

📥 Commits

Reviewing files that changed from the base of the PR and between 355c838 and 2816dd4.

📒 Files selected for processing (2)
  • packages/adk/src/base/model.ts
  • packages/adt-contracts/src/adt/ddic/tabletypes.ts

Comment on lines +481 to 485
// 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);
}
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.

@ThePlenkov ThePlenkov self-assigned this Mar 16, 2026
@ThePlenkov ThePlenkov merged commit 76ae66b into main Mar 16, 2026
4 checks passed
@ThePlenkov ThePlenkov deleted the feat/ddic-object-types branch March 16, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants