From 41b6a4be956c7cf7daa5060a66c054f8a436752d Mon Sep 17 00:00:00 2001 From: Petr Plenkov Date: Mon, 16 Mar 2026 01:17:25 +0100 Subject: [PATCH 1/5] feat(ddic): add abapGit handlers for TABL and TTYP object types - 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. --- packages/adk/src/base/adt.ts | 6 + packages/adk/src/base/kinds.ts | 17 +- packages/adk/src/index.ts | 16 + .../adk/src/objects/ddic/doma/doma.model.ts | 55 +++ packages/adk/src/objects/ddic/doma/index.ts | 6 + .../adk/src/objects/ddic/dtel/dtel.model.ts | 58 +++ packages/adk/src/objects/ddic/dtel/index.ts | 6 + packages/adk/src/objects/ddic/index.ts | 8 + packages/adk/src/objects/ddic/tabl/index.ts | 6 + .../adk/src/objects/ddic/tabl/tabl.model.ts | 84 +++++ packages/adk/src/objects/ddic/ttyp/index.ts | 6 + .../adk/src/objects/ddic/ttyp/ttyp.model.ts | 58 +++ packages/adt-client/src/index.ts | 5 + .../src/adt/ddic/dataelements.ts | 26 ++ .../adt-contracts/src/adt/ddic/domains.ts | 23 ++ packages/adt-contracts/src/adt/ddic/index.ts | 50 +++ .../adt-contracts/src/adt/ddic/structures.ts | 87 +++++ packages/adt-contracts/src/adt/ddic/tables.ts | 88 +++++ .../adt-contracts/src/adt/ddic/tabletypes.ts | 26 ++ packages/adt-contracts/src/adt/index.ts | 4 + .../adt-contracts/src/generated/schemas.ts | 4 + .../src/lib/handlers/adk.ts | 5 + .../src/lib/handlers/objects/doma.ts | 44 +++ .../src/lib/handlers/objects/dtel.ts | 49 +++ .../src/lib/handlers/objects/index.ts | 4 + .../src/lib/handlers/objects/tabl.ts | 34 ++ .../src/lib/handlers/objects/ttyp.ts | 41 +++ .../src/schemas/generated/index.ts | 14 + .../src/schemas/generated/schemas/index.ts | 2 + .../src/schemas/generated/schemas/tabl.ts | 292 +++++++++++++++ .../src/schemas/generated/schemas/ttyp.ts | 162 +++++++++ .../src/schemas/generated/types/index.ts | 2 + .../src/schemas/generated/types/tabl.ts | 109 ++++++ .../src/schemas/generated/types/ttyp.ts | 59 +++ .../fixtures/tabl/zage_structure.tabl.xml | 44 +++ .../tabl/zage_transparent_table.tabl.xml | 46 +++ .../fixtures/ttyp/zage_string_table.ttyp.xml | 18 + .../ttyp/zage_struct_table_type.ttyp.xml | 18 + .../tests/schemas/tabl.test.ts | 85 +++++ .../tests/schemas/ttyp.test.ts | 61 ++++ packages/adt-plugin-abapgit/ts-xsd.config.ts | 12 +- packages/adt-plugin-abapgit/xsd/tabl.xsd | 36 ++ packages/adt-plugin-abapgit/xsd/ttyp.xsd | 34 ++ .../adt-plugin-abapgit/xsd/types/dd02v.xsd | 22 ++ .../adt-plugin-abapgit/xsd/types/dd03p.xsd | 38 ++ .../adt-plugin-abapgit/xsd/types/dd40v.xsd | 23 ++ .../generated/schemas/sap/dataelements.ts | 246 +++++++++++++ .../schemas/generated/schemas/sap/domain.ts | 220 +++++++++++ .../schemas/generated/schemas/sap/index.ts | 4 + .../generated/schemas/sap/tablesettings.ts | 323 +++++++++++++++++ .../generated/schemas/sap/tabletype.ts | 341 ++++++++++++++++++ .../src/schemas/generated/typed.ts | 16 + .../generated/types/sap/dataelements.types.ts | 48 +++ .../generated/types/sap/domain.types.ts | 132 +++++++ .../types/sap/tablesettings.types.ts | 79 ++++ .../generated/types/sap/tabletype.types.ts | 151 ++++++++ packages/adt-schemas/ts-xsd.config.ts | 5 + 57 files changed, 3455 insertions(+), 3 deletions(-) create mode 100644 packages/adk/src/objects/ddic/doma/doma.model.ts create mode 100644 packages/adk/src/objects/ddic/doma/index.ts create mode 100644 packages/adk/src/objects/ddic/dtel/dtel.model.ts create mode 100644 packages/adk/src/objects/ddic/dtel/index.ts create mode 100644 packages/adk/src/objects/ddic/index.ts create mode 100644 packages/adk/src/objects/ddic/tabl/index.ts create mode 100644 packages/adk/src/objects/ddic/tabl/tabl.model.ts create mode 100644 packages/adk/src/objects/ddic/ttyp/index.ts create mode 100644 packages/adk/src/objects/ddic/ttyp/ttyp.model.ts create mode 100644 packages/adt-contracts/src/adt/ddic/dataelements.ts create mode 100644 packages/adt-contracts/src/adt/ddic/domains.ts create mode 100644 packages/adt-contracts/src/adt/ddic/index.ts create mode 100644 packages/adt-contracts/src/adt/ddic/structures.ts create mode 100644 packages/adt-contracts/src/adt/ddic/tables.ts create mode 100644 packages/adt-contracts/src/adt/ddic/tabletypes.ts create mode 100644 packages/adt-plugin-abapgit/src/lib/handlers/objects/doma.ts create mode 100644 packages/adt-plugin-abapgit/src/lib/handlers/objects/dtel.ts create mode 100644 packages/adt-plugin-abapgit/src/lib/handlers/objects/tabl.ts create mode 100644 packages/adt-plugin-abapgit/src/lib/handlers/objects/ttyp.ts create mode 100644 packages/adt-plugin-abapgit/src/schemas/generated/schemas/tabl.ts create mode 100644 packages/adt-plugin-abapgit/src/schemas/generated/schemas/ttyp.ts create mode 100644 packages/adt-plugin-abapgit/src/schemas/generated/types/tabl.ts create mode 100644 packages/adt-plugin-abapgit/src/schemas/generated/types/ttyp.ts create mode 100644 packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_structure.tabl.xml create mode 100644 packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_transparent_table.tabl.xml create mode 100644 packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_string_table.ttyp.xml create mode 100644 packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_struct_table_type.ttyp.xml create mode 100644 packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts create mode 100644 packages/adt-plugin-abapgit/tests/schemas/ttyp.test.ts create mode 100644 packages/adt-plugin-abapgit/xsd/tabl.xsd create mode 100644 packages/adt-plugin-abapgit/xsd/ttyp.xsd create mode 100644 packages/adt-plugin-abapgit/xsd/types/dd02v.xsd create mode 100644 packages/adt-plugin-abapgit/xsd/types/dd03p.xsd create mode 100644 packages/adt-plugin-abapgit/xsd/types/dd40v.xsd create mode 100644 packages/adt-schemas/src/schemas/generated/schemas/sap/dataelements.ts create mode 100644 packages/adt-schemas/src/schemas/generated/schemas/sap/domain.ts create mode 100644 packages/adt-schemas/src/schemas/generated/schemas/sap/tablesettings.ts create mode 100644 packages/adt-schemas/src/schemas/generated/schemas/sap/tabletype.ts create mode 100644 packages/adt-schemas/src/schemas/generated/types/sap/dataelements.types.ts create mode 100644 packages/adt-schemas/src/schemas/generated/types/sap/domain.types.ts create mode 100644 packages/adt-schemas/src/schemas/generated/types/sap/tablesettings.types.ts create mode 100644 packages/adt-schemas/src/schemas/generated/types/sap/tabletype.types.ts diff --git a/packages/adk/src/base/adt.ts b/packages/adk/src/base/adt.ts index c65e618d..975e71aa 100644 --- a/packages/adk/src/base/adt.ts +++ b/packages/adk/src/base/adt.ts @@ -37,6 +37,9 @@ export type { TransportGetResponse, ProgramResponse as ProgramResponseUnion, FunctionGroupResponse as FunctionGroupResponseUnion, + DomainResponse, + DataElementResponse, + TableTypeResponse, } from '@abapify/adt-client'; // CRUD contract types for typed ADK base model @@ -128,6 +131,8 @@ export interface AdkContract { readonly programs: AdtContracts['programs']; /** Functions contracts (function groups) */ readonly functions: AdtContracts['functions']; + /** DDIC contracts (domains, data elements, structures, tables, table types) */ + readonly ddic: AdtContracts['ddic']; } /** @@ -145,5 +150,6 @@ export function createAdkContract(client: AdtClient): AdkContract { repository: client.adt.repository, programs: client.adt.programs, functions: client.adt.functions, + ddic: client.adt.ddic, }; } diff --git a/packages/adk/src/base/kinds.ts b/packages/adk/src/base/kinds.ts index fe5e54d5..b54761d2 100644 --- a/packages/adk/src/base/kinds.ts +++ b/packages/adk/src/base/kinds.ts @@ -67,6 +67,10 @@ import type { AdkTransportTask, } from '../objects/cts/transport/transport'; import type { AdkObject } from './model'; +import type { AdkDomain } from '../objects/ddic/doma/doma.model'; +import type { AdkDataElement } from '../objects/ddic/dtel/dtel.model'; +import type { AdkTable, AdkStructure } from '../objects/ddic/tabl/tabl.model'; +import type { AdkTableType } from '../objects/ddic/ttyp/ttyp.model'; /** * Maps ADK kind to concrete object type @@ -91,5 +95,14 @@ export type AdkObjectForKind = K extends typeof Class ? AdkTransportRequest : K extends typeof TransportTask ? AdkTransportTask - : // Add more mappings as types are implemented - AdkObject; // fallback + : K extends typeof Domain + ? AdkDomain + : K extends typeof DataElement + ? AdkDataElement + : K extends typeof Table + ? AdkTable + : K extends typeof Structure + ? AdkStructure + : K extends typeof TableType + ? AdkTableType + : AdkObject; // fallback diff --git a/packages/adk/src/index.ts b/packages/adk/src/index.ts index a93f8236..5d63927c 100644 --- a/packages/adk/src/index.ts +++ b/packages/adk/src/index.ts @@ -45,6 +45,9 @@ export type { ProgramResponse, FunctionGroupResponse, TransportGetResponse, + DomainResponse, + DataElementResponse, + TableTypeResponse, } from './base/adt'; export { createAdkContract } from './base/adt'; @@ -103,6 +106,19 @@ export type { } from './objects/repository/fugr'; export { AdkFunctionGroup } from './objects/repository/fugr'; +// DDIC types and classes +export { + AdkDomain, + AdkDataElement, + AdkTable, + AdkStructure, + AdkTableType, + type DomainXml, + type DataElementXml, + type TableXml, + type TableTypeXml, +} from './objects/ddic'; + // CTS types (legacy complex transport) export type { TransportData, diff --git a/packages/adk/src/objects/ddic/doma/doma.model.ts b/packages/adk/src/objects/ddic/doma/doma.model.ts new file mode 100644 index 00000000..c9d1507e --- /dev/null +++ b/packages/adk/src/objects/ddic/doma/doma.model.ts @@ -0,0 +1,55 @@ +/** + * DOMA - Domain + * + * ADK object for ABAP Domains (DOMA). + * DDIC objects are metadata-only (no source code). + */ + +import { AdkMainObject } from '../../../base/model'; +import { Domain as DomainKind } from '../../../base/kinds'; +import { getGlobalContext } from '../../../base/global-context'; +import type { AdkContext } from '../../../base/context'; + +import type { DomainResponse } from '../../../base/adt'; + +/** + * Domain data type - unwrap from schema root element + */ +export type DomainXml = DomainResponse['domain']; + +/** + * ADK Domain object + * + * Inherits from AdkMainObject which provides: + * - name, type, description, version, language, changedBy/At, createdBy/At, links + * - package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion + * + * Domain-specific properties via `data`: + * - data.typeInformation (dataType, length, decimals, outputLength) + * - data.outputInformation (conversionExit, signPresentation, lowerCase) + * - data.fixedValues + */ +export class AdkDomain extends AdkMainObject { + static readonly kind = DomainKind; + readonly kind = AdkDomain.kind; + + get objectUri(): string { + return `/sap/bc/adt/ddic/domains/${encodeURIComponent(this.name.toLowerCase())}`; + } + + protected override get wrapperKey() { + return 'domain'; + } + protected override get crudContract(): any { + return this.ctx.client.adt.ddic.domains; + } + + static async get(name: string, ctx?: AdkContext): Promise { + const context = ctx ?? getGlobalContext(); + return new AdkDomain(context, name).load(); + } +} + +// Self-register with ADK registry +import { registerObjectType } from '../../../base/registry'; +registerObjectType('DOMA', DomainKind, AdkDomain); diff --git a/packages/adk/src/objects/ddic/doma/index.ts b/packages/adk/src/objects/ddic/doma/index.ts new file mode 100644 index 00000000..3b7a8702 --- /dev/null +++ b/packages/adk/src/objects/ddic/doma/index.ts @@ -0,0 +1,6 @@ +/** + * DOMA - Domain + */ + +export { AdkDomain } from './doma.model'; +export type { DomainXml } from './doma.model'; diff --git a/packages/adk/src/objects/ddic/dtel/dtel.model.ts b/packages/adk/src/objects/ddic/dtel/dtel.model.ts new file mode 100644 index 00000000..3d249f48 --- /dev/null +++ b/packages/adk/src/objects/ddic/dtel/dtel.model.ts @@ -0,0 +1,58 @@ +/** + * DTEL - Data Element + * + * ADK object for ABAP Data Elements (DTEL). + * DDIC objects are metadata-only (no source code). + */ + +import { AdkMainObject } from '../../../base/model'; +import { DataElement as DataElementKind } from '../../../base/kinds'; +import { getGlobalContext } from '../../../base/global-context'; +import type { AdkContext } from '../../../base/context'; + +import type { DataElementResponse } from '../../../base/adt'; + +/** + * Data Element data type - unwrap from schema root element + */ +export type DataElementXml = DataElementResponse['dataElement']; + +/** + * ADK Data Element object + * + * Inherits from AdkMainObject which provides: + * - name, type, description, version, language, changedBy/At, createdBy/At, links + * - package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion + * + * Data element-specific properties via `data`: + * - data.typeKind, data.typeName, data.dataType + * - data.shortDescription, data.mediumDescription, data.longDescription, data.headingDescription + * - data.searchHelp + */ +export class AdkDataElement extends AdkMainObject< + typeof DataElementKind, + DataElementXml +> { + static readonly kind = DataElementKind; + readonly kind = AdkDataElement.kind; + + get objectUri(): string { + return `/sap/bc/adt/ddic/dataelements/${encodeURIComponent(this.name.toLowerCase())}`; + } + + protected override get wrapperKey() { + return 'dataElement'; + } + protected override get crudContract(): any { + return this.ctx.client.adt.ddic.dataelements; + } + + static async get(name: string, ctx?: AdkContext): Promise { + const context = ctx ?? getGlobalContext(); + return new AdkDataElement(context, name).load(); + } +} + +// Self-register with ADK registry +import { registerObjectType } from '../../../base/registry'; +registerObjectType('DTEL', DataElementKind, AdkDataElement); diff --git a/packages/adk/src/objects/ddic/dtel/index.ts b/packages/adk/src/objects/ddic/dtel/index.ts new file mode 100644 index 00000000..c6ea337d --- /dev/null +++ b/packages/adk/src/objects/ddic/dtel/index.ts @@ -0,0 +1,6 @@ +/** + * DTEL - Data Element + */ + +export { AdkDataElement } from './dtel.model'; +export type { DataElementXml } from './dtel.model'; diff --git a/packages/adk/src/objects/ddic/index.ts b/packages/adk/src/objects/ddic/index.ts new file mode 100644 index 00000000..06446f06 --- /dev/null +++ b/packages/adk/src/objects/ddic/index.ts @@ -0,0 +1,8 @@ +/** + * DDIC - Data Dictionary Objects + */ + +export { AdkDomain, type DomainXml } from './doma'; +export { AdkDataElement, type DataElementXml } from './dtel'; +export { AdkTable, AdkStructure, type TableXml } from './tabl'; +export { AdkTableType, type TableTypeXml } from './ttyp'; diff --git a/packages/adk/src/objects/ddic/tabl/index.ts b/packages/adk/src/objects/ddic/tabl/index.ts new file mode 100644 index 00000000..a216af00 --- /dev/null +++ b/packages/adk/src/objects/ddic/tabl/index.ts @@ -0,0 +1,6 @@ +/** + * TABL - Database Table / Structure + */ + +export { AdkTable, AdkStructure } from './tabl.model'; +export type { TableXml } from './tabl.model'; diff --git a/packages/adk/src/objects/ddic/tabl/tabl.model.ts b/packages/adk/src/objects/ddic/tabl/tabl.model.ts new file mode 100644 index 00000000..b4ae15d3 --- /dev/null +++ b/packages/adk/src/objects/ddic/tabl/tabl.model.ts @@ -0,0 +1,84 @@ +/** + * TABL - Database Table / Structure + * + * ADK object for ABAP Database Tables (TABL/DT) and Structures (TABL/DS). + * DDIC objects are metadata-only (no source code). + * + * Note: Tables and structures share the same ADT main type (TABL) + * but use different endpoints and subtypes. + * - Tables: /sap/bc/adt/ddic/tables (TABL/DT) + * - Structures: /sap/bc/adt/ddic/structures (TABL/DS) + * + * Since no typed XSD schema is available yet for tables/structures, + * these use a generic data type. + */ + +import { AdkMainObject } from '../../../base/model'; +import { + Table as TableKind, + Structure as StructureKind, +} from '../../../base/kinds'; +import { getGlobalContext } from '../../../base/global-context'; +import type { AdkContext } from '../../../base/context'; + +/** + * Generic table/structure data (untyped until schema is available) + */ +export type TableXml = Record; + +/** + * ADK Table object (database table - TABL/DT) + */ +export class AdkTable extends AdkMainObject { + static readonly kind = TableKind; + readonly kind = AdkTable.kind; + + get objectUri(): string { + return `/sap/bc/adt/ddic/tables/${encodeURIComponent(this.name.toLowerCase())}`; + } + + protected override get wrapperKey(): undefined { + return undefined; + } + protected override get crudContract(): any { + return this.ctx.client.adt.ddic.tables; + } + + static async get(name: string, ctx?: AdkContext): Promise { + const context = ctx ?? getGlobalContext(); + return new AdkTable(context, name).load(); + } +} + +/** + * ADK Structure object (TABL/DS) + */ +export class AdkStructure extends AdkMainObject< + typeof StructureKind, + TableXml +> { + static readonly kind = StructureKind; + readonly kind = AdkStructure.kind; + + get objectUri(): string { + return `/sap/bc/adt/ddic/structures/${encodeURIComponent(this.name.toLowerCase())}`; + } + + protected override get wrapperKey(): undefined { + return undefined; + } + protected override get crudContract(): any { + return this.ctx.client.adt.ddic.structures; + } + + static async get(name: string, ctx?: AdkContext): Promise { + const context = ctx ?? getGlobalContext(); + return new AdkStructure(context, name).load(); + } +} + +// 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 diff --git a/packages/adk/src/objects/ddic/ttyp/index.ts b/packages/adk/src/objects/ddic/ttyp/index.ts new file mode 100644 index 00000000..65acd0c5 --- /dev/null +++ b/packages/adk/src/objects/ddic/ttyp/index.ts @@ -0,0 +1,6 @@ +/** + * TTYP - Table Type + */ + +export { AdkTableType } from './ttyp.model'; +export type { TableTypeXml } from './ttyp.model'; diff --git a/packages/adk/src/objects/ddic/ttyp/ttyp.model.ts b/packages/adk/src/objects/ddic/ttyp/ttyp.model.ts new file mode 100644 index 00000000..d94ba1f8 --- /dev/null +++ b/packages/adk/src/objects/ddic/ttyp/ttyp.model.ts @@ -0,0 +1,58 @@ +/** + * TTYP - Table Type + * + * ADK object for ABAP Table Types (TTYP). + * DDIC objects are metadata-only (no source code). + */ + +import { AdkMainObject } from '../../../base/model'; +import { TableType as TableTypeKind } from '../../../base/kinds'; +import { getGlobalContext } from '../../../base/global-context'; +import type { AdkContext } from '../../../base/context'; + +import type { TableTypeResponse } from '../../../base/adt'; + +/** + * Table Type data type - unwrap from schema root element + */ +export type TableTypeXml = TableTypeResponse['tableType']; + +/** + * ADK Table Type object + * + * Inherits from AdkMainObject which provides: + * - name, type, description, version, language, changedBy/At, createdBy/At, links + * - package, packageRef, responsible, masterLanguage, masterSystem, abapLanguageVersion + * + * Table type-specific properties via `data`: + * - data.rowType (typeName, typeCategory, tableTypeSchemaReference) + * - data.primaryKey + * - data.secondaryKeys + */ +export class AdkTableType extends AdkMainObject< + typeof TableTypeKind, + TableTypeXml +> { + static readonly kind = TableTypeKind; + readonly kind = AdkTableType.kind; + + get objectUri(): string { + return `/sap/bc/adt/ddic/tabletypes/${encodeURIComponent(this.name.toLowerCase())}`; + } + + protected override get wrapperKey() { + return 'tableType'; + } + protected override get crudContract(): any { + return this.ctx.client.adt.ddic.tabletypes; + } + + static async get(name: string, ctx?: AdkContext): Promise { + const context = ctx ?? getGlobalContext(); + return new AdkTableType(context, name).load(); + } +} + +// Self-register with ADK registry +import { registerObjectType } from '../../../base/registry'; +registerObjectType('TTYP', TableTypeKind, AdkTableType); diff --git a/packages/adt-client/src/index.ts b/packages/adt-client/src/index.ts index 9bb17fd3..ac8df684 100644 --- a/packages/adt-client/src/index.ts +++ b/packages/adt-client/src/index.ts @@ -69,6 +69,11 @@ export type { ClassResponse, InterfaceResponse } from '@abapify/adt-contracts'; export type { Package as PackageResponse } from '@abapify/adt-contracts'; export type { ProgramResponse } from '@abapify/adt-contracts'; export type { FunctionGroupResponse } from '@abapify/adt-contracts'; +export type { + DomainResponse, + DataElementResponse, + TableTypeResponse, +} from '@abapify/adt-contracts'; // Transport response type - exported directly from contracts // Note: Transport business logic has moved to @abapify/adk (AdkTransportRequest) diff --git a/packages/adt-contracts/src/adt/ddic/dataelements.ts b/packages/adt-contracts/src/adt/ddic/dataelements.ts new file mode 100644 index 00000000..9cf38f23 --- /dev/null +++ b/packages/adt-contracts/src/adt/ddic/dataelements.ts @@ -0,0 +1,26 @@ +/** + * DDIC Data Element Contract + * + * ADT endpoint: /sap/bc/adt/ddic/dataelements + * Content-Type: application/vnd.sap.adt.dataelements.v2+xml + * Object type: DTEL/DE (dtelde) + */ + +import { crud } from '../../helpers/crud'; +import { + dataelements as dataelementsSchema, + type InferTypedSchema, +} from '../../schemas'; + +/** + * Data Element response type - exported for consumers (ADK, etc.) + */ +export type DataElementResponse = InferTypedSchema; + +export type DataelementsContract = typeof dataelementsContract; + +export const dataelementsContract = crud({ + basePath: '/sap/bc/adt/ddic/dataelements', + schema: dataelementsSchema, + contentType: 'application/vnd.sap.adt.dataelements.v2+xml', +}); diff --git a/packages/adt-contracts/src/adt/ddic/domains.ts b/packages/adt-contracts/src/adt/ddic/domains.ts new file mode 100644 index 00000000..260b7aad --- /dev/null +++ b/packages/adt-contracts/src/adt/ddic/domains.ts @@ -0,0 +1,23 @@ +/** + * DDIC Domain Contract + * + * ADT endpoint: /sap/bc/adt/ddic/domains + * Content-Type: application/vnd.sap.adt.domains.v2+xml + * Object type: DOMA/DD (domadd) + */ + +import { crud } from '../../helpers/crud'; +import { domain as domainSchema, type InferTypedSchema } from '../../schemas'; + +/** + * Domain response type - exported for consumers (ADK, etc.) + */ +export type DomainResponse = InferTypedSchema; + +export type DomainsContract = typeof domainsContract; + +export const domainsContract = crud({ + basePath: '/sap/bc/adt/ddic/domains', + schema: domainSchema, + contentType: 'application/vnd.sap.adt.domains.v2+xml', +}); diff --git a/packages/adt-contracts/src/adt/ddic/index.ts b/packages/adt-contracts/src/adt/ddic/index.ts new file mode 100644 index 00000000..73db0543 --- /dev/null +++ b/packages/adt-contracts/src/adt/ddic/index.ts @@ -0,0 +1,50 @@ +/** + * DDIC (Data Dictionary) Contracts + * + * Covers all DDIC object types: + * - Domains (DOMA) + * - Data Elements (DTEL) + * - Structures (TABL/DS) + * - Database Tables (TABL/DT) + * - Table Types (TTYP) + */ + +export { + domainsContract, + type DomainsContract, + type DomainResponse, +} from './domains'; +export { + dataelementsContract, + type DataelementsContract, + type DataElementResponse, +} from './dataelements'; +export { structuresContract } from './structures'; +export { tablesContract } from './tables'; +export { + tabletypesContract, + type TabletypesContract, + type TableTypeResponse, +} from './tabletypes'; + +import { domainsContract } from './domains'; +import { dataelementsContract } from './dataelements'; +import { structuresContract } from './structures'; +import { tablesContract } from './tables'; +import { tabletypesContract } from './tabletypes'; + +export interface DdicContract { + domains: typeof domainsContract; + dataelements: typeof dataelementsContract; + structures: typeof structuresContract; + tables: typeof tablesContract; + tabletypes: typeof tabletypesContract; +} + +export const ddicContract: DdicContract = { + domains: domainsContract, + dataelements: dataelementsContract, + structures: structuresContract, + tables: tablesContract, + tabletypes: tabletypesContract, +}; diff --git a/packages/adt-contracts/src/adt/ddic/structures.ts b/packages/adt-contracts/src/adt/ddic/structures.ts new file mode 100644 index 00000000..be79ed9e --- /dev/null +++ b/packages/adt-contracts/src/adt/ddic/structures.ts @@ -0,0 +1,87 @@ +/** + * DDIC Structure Contract + * + * ADT endpoint: /sap/bc/adt/ddic/structures + * Content-Type: application/vnd.sap.adt.structures.v2+xml + * Object type: TABL/DS (tablds) + * + * Note: No XSD schema available yet for structures. + * Using undefined responses until a proper schema is available. + */ + +import { http } from '@abapify/speci/rest'; + +const basePath = '/sap/bc/adt/ddic/structures'; +const contentType = 'application/vnd.sap.adt.structures.v2+xml'; +const nameTransform = (n: string) => n.toLowerCase(); + +export const structuresContract = { + get: (name: string, options?: { version?: string }) => + http.get(`${basePath}/${nameTransform(name)}`, { + responses: { 200: undefined }, + headers: { Accept: contentType }, + query: options?.version ? { version: options.version } : undefined, + }), + + post: (options?: { corrNr?: string }) => + http.post(basePath, { + body: undefined, + responses: { 200: undefined }, + headers: { + Accept: contentType, + 'Content-Type': 'application/*', + }, + query: options?.corrNr ? { corrNr: options.corrNr } : undefined, + }), + + put: (name: string, options?: { corrNr?: string; lockHandle?: string }) => + http.put(`${basePath}/${nameTransform(name)}`, { + body: undefined, + responses: { 200: undefined }, + headers: { + Accept: contentType, + 'Content-Type': contentType, + }, + query: { + ...(options?.corrNr ? { corrNr: options.corrNr } : {}), + ...(options?.lockHandle ? { lockHandle: options.lockHandle } : {}), + }, + }), + + delete: (name: string, options?: { corrNr?: string; lockHandle?: string }) => + http.delete(`${basePath}/${nameTransform(name)}`, { + responses: { 204: undefined }, + query: { + ...(options?.corrNr ? { corrNr: options.corrNr } : {}), + ...(options?.lockHandle ? { lockHandle: options.lockHandle } : {}), + }, + }), + + lock: (name: string, options?: { corrNr?: string; accessMode?: string }) => + http.post(`${basePath}/${nameTransform(name)}`, { + responses: { 200: undefined }, + headers: { + 'X-sap-adt-sessiontype': 'stateful', + }, + query: { + _action: 'LOCK', + accessMode: options?.accessMode ?? 'MODIFY', + ...(options?.corrNr ? { corrNr: options.corrNr } : {}), + }, + }), + + unlock: (name: string, options: { lockHandle: string }) => + http.post(`${basePath}/${nameTransform(name)}`, { + responses: { 200: undefined }, + query: { + _action: 'UNLOCK', + lockHandle: options.lockHandle, + }, + }), + + objectstructure: (name: string, options?: { version?: string }) => + http.get(`${basePath}/${nameTransform(name)}/objectstructure`, { + responses: { 200: undefined }, + query: options?.version ? { version: options.version } : undefined, + }), +}; diff --git a/packages/adt-contracts/src/adt/ddic/tables.ts b/packages/adt-contracts/src/adt/ddic/tables.ts new file mode 100644 index 00000000..f0ed8f31 --- /dev/null +++ b/packages/adt-contracts/src/adt/ddic/tables.ts @@ -0,0 +1,88 @@ +/** + * DDIC Database Table Contract + * + * ADT endpoint: /sap/bc/adt/ddic/tables + * Content-Type: application/vnd.sap.adt.tables.v2+xml + * Object type: TABL/DT (tabldt) + * + * Note: No XSD schema available yet for tables. + * The tablesettings schema covers technical settings only. + * Using undefined responses until a proper schema is available. + */ + +import { http } from '@abapify/speci/rest'; + +const basePath = '/sap/bc/adt/ddic/tables'; +const contentType = 'application/vnd.sap.adt.tables.v2+xml'; +const nameTransform = (n: string) => n.toLowerCase(); + +export const tablesContract = { + get: (name: string, options?: { version?: string }) => + http.get(`${basePath}/${nameTransform(name)}`, { + responses: { 200: undefined }, + headers: { Accept: contentType }, + query: options?.version ? { version: options.version } : undefined, + }), + + post: (options?: { corrNr?: string }) => + http.post(basePath, { + body: undefined, + responses: { 200: undefined }, + headers: { + Accept: contentType, + 'Content-Type': 'application/*', + }, + query: options?.corrNr ? { corrNr: options.corrNr } : undefined, + }), + + put: (name: string, options?: { corrNr?: string; lockHandle?: string }) => + http.put(`${basePath}/${nameTransform(name)}`, { + body: undefined, + responses: { 200: undefined }, + headers: { + Accept: contentType, + 'Content-Type': contentType, + }, + query: { + ...(options?.corrNr ? { corrNr: options.corrNr } : {}), + ...(options?.lockHandle ? { lockHandle: options.lockHandle } : {}), + }, + }), + + delete: (name: string, options?: { corrNr?: string; lockHandle?: string }) => + http.delete(`${basePath}/${nameTransform(name)}`, { + responses: { 204: undefined }, + query: { + ...(options?.corrNr ? { corrNr: options.corrNr } : {}), + ...(options?.lockHandle ? { lockHandle: options.lockHandle } : {}), + }, + }), + + lock: (name: string, options?: { corrNr?: string; accessMode?: string }) => + http.post(`${basePath}/${nameTransform(name)}`, { + responses: { 200: undefined }, + headers: { + 'X-sap-adt-sessiontype': 'stateful', + }, + query: { + _action: 'LOCK', + accessMode: options?.accessMode ?? 'MODIFY', + ...(options?.corrNr ? { corrNr: options.corrNr } : {}), + }, + }), + + unlock: (name: string, options: { lockHandle: string }) => + http.post(`${basePath}/${nameTransform(name)}`, { + responses: { 200: undefined }, + query: { + _action: 'UNLOCK', + lockHandle: options.lockHandle, + }, + }), + + objectstructure: (name: string, options?: { version?: string }) => + http.get(`${basePath}/${nameTransform(name)}/objectstructure`, { + responses: { 200: undefined }, + query: options?.version ? { version: options.version } : undefined, + }), +}; diff --git a/packages/adt-contracts/src/adt/ddic/tabletypes.ts b/packages/adt-contracts/src/adt/ddic/tabletypes.ts new file mode 100644 index 00000000..d68d95d7 --- /dev/null +++ b/packages/adt-contracts/src/adt/ddic/tabletypes.ts @@ -0,0 +1,26 @@ +/** + * DDIC Table Type Contract + * + * ADT endpoint: /sap/bc/adt/ddic/tabletypes + * Content-Type: application/vnd.sap.adt.tabletype.v1+xml + * Object type: TTYP/DA (ttypda) + */ + +import { crud } from '../../helpers/crud'; +import { + tabletype as tabletypeSchema, + type InferTypedSchema, +} from '../../schemas'; + +/** + * Table Type response type - exported for consumers (ADK, etc.) + */ +export type TableTypeResponse = InferTypedSchema; + +export type TabletypesContract = typeof tabletypesContract; + +export const tabletypesContract = crud({ + basePath: '/sap/bc/adt/ddic/tabletypes', + schema: tabletypeSchema, + contentType: 'application/vnd.sap.adt.tabletype.v1+xml', +}); diff --git a/packages/adt-contracts/src/adt/index.ts b/packages/adt-contracts/src/adt/index.ts index 275a1ff4..8fc29919 100644 --- a/packages/adt-contracts/src/adt/index.ts +++ b/packages/adt-contracts/src/adt/index.ts @@ -12,6 +12,7 @@ export * from './core'; export * from './repository'; export * from './programs'; export * from './functions'; +export * from './ddic'; /** * Complete ADT Contract @@ -29,6 +30,7 @@ import { type ProgramsModuleContract, } from './programs'; import { functionsContract, type FunctionsContract } from './functions'; +import { ddicContract, type DdicContract } from './ddic'; /** * Explicit type to avoid TS7056 "inferred type exceeds maximum length" @@ -44,6 +46,7 @@ export interface AdtContract { repository: RepositoryContract; programs: ProgramsModuleContract; functions: FunctionsContract; + ddic: DdicContract; } export const adtContract: AdtContract = { @@ -57,6 +60,7 @@ export const adtContract: AdtContract = { repository: repositoryContract, programs: programsModuleContract, functions: functionsContract, + ddic: ddicContract, }; // Import RestClient from base for client type definition diff --git a/packages/adt-contracts/src/generated/schemas.ts b/packages/adt-contracts/src/generated/schemas.ts index afd8f50e..d0735296 100644 --- a/packages/adt-contracts/src/generated/schemas.ts +++ b/packages/adt-contracts/src/generated/schemas.ts @@ -55,6 +55,10 @@ export const aunitRun = toSpeciSchema(adtSchemas.aunitRun); export const aunitResult = toSpeciSchema(adtSchemas.aunitResult); export const programs = toSpeciSchema(adtSchemas.programs); export const groups = toSpeciSchema(adtSchemas.groups); +export const domain = toSpeciSchema(adtSchemas.domain); +export const dataelements = toSpeciSchema(adtSchemas.dataelements); +export const tabletype = toSpeciSchema(adtSchemas.tabletype); +export const tablesettings = toSpeciSchema(adtSchemas.tablesettings); // ============================================================================ // JSON Schemas (re-exported directly - they use zod, not ts-xsd) diff --git a/packages/adt-plugin-abapgit/src/lib/handlers/adk.ts b/packages/adt-plugin-abapgit/src/lib/handlers/adk.ts index ee2d13d4..877607f2 100644 --- a/packages/adt-plugin-abapgit/src/lib/handlers/adk.ts +++ b/packages/adt-plugin-abapgit/src/lib/handlers/adk.ts @@ -11,6 +11,11 @@ export { AdkPackage, AdkProgram, AdkFunctionGroup, + AdkDomain, + AdkDataElement, + AdkTable, + AdkStructure, + AdkTableType, } from '@abapify/adk'; // Types diff --git a/packages/adt-plugin-abapgit/src/lib/handlers/objects/doma.ts b/packages/adt-plugin-abapgit/src/lib/handlers/objects/doma.ts new file mode 100644 index 00000000..11aaa0a7 --- /dev/null +++ b/packages/adt-plugin-abapgit/src/lib/handlers/objects/doma.ts @@ -0,0 +1,44 @@ +/** + * Domain (DOMA) object handler for abapGit format + */ + +import { AdkDomain } from '../adk'; +import { doma } from '../../../schemas/generated'; +import { createHandler } from '../base'; +import { isoToSapLang, sapLangToIso } from '../lang'; + +export const domainHandler = createHandler(AdkDomain, { + schema: doma, + version: 'v1.0.0', + serializer: 'LCL_OBJECT_DOMA', + serializer_version: 'v1.0.0', + + toAbapGit: (obj) => { + const data = obj.dataSync; + const typeInfo = data?.content?.typeInformation; + const outInfo = data?.content?.outputInformation; + return { + DD01V: { + DOMNAME: obj.name ?? '', + DDLANGUAGE: isoToSapLang(data?.language), + DATATYPE: typeInfo?.datatype ?? '', + LENG: String(typeInfo?.length ?? ''), + OUTPUTLEN: String(outInfo?.length ?? ''), + DECIMALS: String(typeInfo?.decimals ?? ''), + LOWERCASE: outInfo?.lowercase ? 'X' : '', + SIGNFLAG: outInfo?.signExists ? 'X' : '', + CONVEXIT: outInfo?.conversionExit ?? '', + DDTEXT: obj.description ?? '', + }, + }; + }, + + fromAbapGit: ({ DD01V }) => + ({ + name: (DD01V?.DOMNAME ?? '').toUpperCase(), + type: 'DOMA/DD', + description: DD01V?.DDTEXT, + language: sapLangToIso(DD01V?.DDLANGUAGE), + masterLanguage: sapLangToIso(DD01V?.DDLANGUAGE), + }) as { name: string } & Record, +}); diff --git a/packages/adt-plugin-abapgit/src/lib/handlers/objects/dtel.ts b/packages/adt-plugin-abapgit/src/lib/handlers/objects/dtel.ts new file mode 100644 index 00000000..e47d2267 --- /dev/null +++ b/packages/adt-plugin-abapgit/src/lib/handlers/objects/dtel.ts @@ -0,0 +1,49 @@ +/** + * Data Element (DTEL) object handler for abapGit format + */ + +import { AdkDataElement } from '../adk'; +import { dtel } from '../../../schemas/generated'; +import { createHandler } from '../base'; +import { isoToSapLang, sapLangToIso } from '../lang'; + +export const dataElementHandler = createHandler(AdkDataElement, { + schema: dtel, + version: 'v1.0.0', + serializer: 'LCL_OBJECT_DTEL', + serializer_version: 'v1.0.0', + + toAbapGit: (obj) => { + const data = obj.dataSync; + return { + DD04V: { + ROLLNAME: obj.name ?? '', + DDLANGUAGE: isoToSapLang(obj.language || undefined), + DDTEXT: obj.description ?? '', + DOMNAME: data?.typeName ?? '', + DATATYPE: data?.dataType ?? '', + LENG: String(data?.dataTypeLength ?? ''), + DECIMALS: String(data?.dataTypeDecimals ?? ''), + REPTEXT: data?.headingFieldLabel ?? '', + SCRTEXT_S: data?.shortFieldLabel ?? '', + SCRTEXT_M: data?.mediumFieldLabel ?? '', + SCRTEXT_L: data?.longFieldLabel ?? '', + HEADLEN: String(data?.headingFieldLength ?? ''), + SCRLEN1: String(data?.shortFieldLength ?? ''), + SCRLEN2: String(data?.mediumFieldLength ?? ''), + SCRLEN3: String(data?.longFieldLength ?? ''), + REFKIND: data?.typeKind === 'domain' ? 'D' : '', + }, + }; + }, + + fromAbapGit: ({ DD04V }) => + ({ + name: (DD04V?.ROLLNAME ?? '').toUpperCase(), + type: 'DTEL/DE', + description: DD04V?.DDTEXT, + language: sapLangToIso(DD04V?.DDLANGUAGE), + masterLanguage: sapLangToIso(DD04V?.DDLANGUAGE), + abapLanguageVersion: DD04V?.ABAP_LANGUAGE_VERSION, + }) as { name: string } & Record, +}); diff --git a/packages/adt-plugin-abapgit/src/lib/handlers/objects/index.ts b/packages/adt-plugin-abapgit/src/lib/handlers/objects/index.ts index 77757277..60969b1f 100644 --- a/packages/adt-plugin-abapgit/src/lib/handlers/objects/index.ts +++ b/packages/adt-plugin-abapgit/src/lib/handlers/objects/index.ts @@ -9,3 +9,7 @@ export { interfaceHandler } from './intf'; export { packageHandler } from './devc'; export { programHandler } from './prog'; export { functionGroupHandler } from './fugr'; +export { domainHandler } from './doma'; +export { dataElementHandler } from './dtel'; +export { tableHandler } from './tabl'; +export { tableTypeHandler } from './ttyp'; diff --git a/packages/adt-plugin-abapgit/src/lib/handlers/objects/tabl.ts b/packages/adt-plugin-abapgit/src/lib/handlers/objects/tabl.ts new file mode 100644 index 00000000..0f264619 --- /dev/null +++ b/packages/adt-plugin-abapgit/src/lib/handlers/objects/tabl.ts @@ -0,0 +1,34 @@ +/** + * Table/Structure (TABL) object handler for abapGit format + */ + +import { AdkTable } from '../adk'; +import { tabl } from '../../../schemas/generated'; +import { createHandler } from '../base'; +import { isoToSapLang, sapLangToIso } from '../lang'; + +export const tableHandler = createHandler(AdkTable, { + schema: tabl, + version: 'v1.0.0', + serializer: 'LCL_OBJECT_TABL', + serializer_version: 'v1.0.0', + + toAbapGit: (obj) => ({ + DD02V: { + TABNAME: obj.name ?? '', + DDLANGUAGE: isoToSapLang(obj.language || undefined), + TABCLASS: (obj.dataSync as any)?.tabClass ?? '', + DDTEXT: obj.description ?? '', + EXCLASS: (obj.dataSync as any)?.exclass ?? '', + }, + }), + + 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, +}); diff --git a/packages/adt-plugin-abapgit/src/lib/handlers/objects/ttyp.ts b/packages/adt-plugin-abapgit/src/lib/handlers/objects/ttyp.ts new file mode 100644 index 00000000..ee7bf514 --- /dev/null +++ b/packages/adt-plugin-abapgit/src/lib/handlers/objects/ttyp.ts @@ -0,0 +1,41 @@ +/** + * Table Type (TTYP) object handler for abapGit format + */ + +import { AdkTableType } from '../adk'; +import { ttyp } from '../../../schemas/generated'; +import { createHandler } from '../base'; +import { isoToSapLang, sapLangToIso } from '../lang'; + +export const tableTypeHandler = createHandler(AdkTableType, { + schema: ttyp, + version: 'v1.0.0', + serializer: 'LCL_OBJECT_TTYP', + serializer_version: 'v1.0.0', + + toAbapGit: (obj) => { + const data = obj.dataSync; + return { + DD40V: { + TYPENAME: obj.name ?? '', + DDLANGUAGE: isoToSapLang(data?.language || undefined), + ROWTYPE: data?.rowType?.typeName ?? '', + ROWKIND: data?.rowType?.typeKind ?? '', + DATATYPE: data?.rowType?.builtInType?.dataType ?? '', + ACCESSMODE: data?.accessType ?? '', + KEYDEF: data?.primaryKey?.definition ?? '', + KEYKIND: data?.primaryKey?.kind ?? '', + DDTEXT: obj.description ?? '', + }, + }; + }, + + fromAbapGit: ({ DD40V }) => + ({ + name: (DD40V?.TYPENAME ?? '').toUpperCase(), + type: 'TTYP/TT', + description: DD40V?.DDTEXT, + language: sapLangToIso(DD40V?.DDLANGUAGE), + masterLanguage: sapLangToIso(DD40V?.DDLANGUAGE), + }) as { name: string } & Record, +}); diff --git a/packages/adt-plugin-abapgit/src/schemas/generated/index.ts b/packages/adt-plugin-abapgit/src/schemas/generated/index.ts index e5cee40f..bc52f1c4 100644 --- a/packages/adt-plugin-abapgit/src/schemas/generated/index.ts +++ b/packages/adt-plugin-abapgit/src/schemas/generated/index.ts @@ -21,6 +21,8 @@ import _dtel from './schemas/dtel'; import _intf from './schemas/intf'; import _prog from './schemas/prog'; import _fugr from './schemas/fugr'; +import _tabl from './schemas/tabl'; +import _ttyp from './schemas/ttyp'; // Full AbapGit types - using flattened root types // Note: Generated types may be unions, we import the raw schema type @@ -31,6 +33,8 @@ import type { DtelSchema as _DtelSchema } from './types/dtel'; import type { IntfSchema as _IntfSchema } from './types/intf'; import type { ProgSchema as _ProgSchema } from './types/prog'; import type { FugrSchema as _FugrSchema } from './types/fugr'; +import type { TablSchema as _TablSchema } from './types/tabl'; +import type { TtypSchema as _TtypSchema } from './types/ttyp'; // Extract the abapGit variant from union types (generated types may be unions) type ClasAbapGitType = Extract<_ClasSchema, { abapGit: unknown }>; @@ -40,6 +44,8 @@ type DtelAbapGitType = Extract<_DtelSchema, { abapGit: unknown }>; type IntfAbapGitType = Extract<_IntfSchema, { abapGit: unknown }>; type ProgAbapGitType = Extract<_ProgSchema, { abapGit: unknown }>; type FugrAbapGitType = Extract<_FugrSchema, { abapGit: unknown }>; +type TablAbapGitType = Extract<_TablSchema, { abapGit: unknown }>; +type TtypAbapGitType = Extract<_TtypSchema, { abapGit: unknown }>; // AbapGit schema instances - using flattened types with values extracted from abapGit.abap.values export const clas = abapGitSchema< @@ -70,6 +76,14 @@ export const fugr = abapGitSchema< FugrAbapGitType, FugrAbapGitType['abapGit']['abap']['values'] >(_fugr); +export const tabl = abapGitSchema< + TablAbapGitType, + TablAbapGitType['abapGit']['abap']['values'] +>(_tabl); +export const ttyp = abapGitSchema< + TtypAbapGitType, + TtypAbapGitType['abapGit']['abap']['values'] +>(_ttyp); // Re-export types and utilities export { diff --git a/packages/adt-plugin-abapgit/src/schemas/generated/schemas/index.ts b/packages/adt-plugin-abapgit/src/schemas/generated/schemas/index.ts index 5d484860..8eb4c998 100644 --- a/packages/adt-plugin-abapgit/src/schemas/generated/schemas/index.ts +++ b/packages/adt-plugin-abapgit/src/schemas/generated/schemas/index.ts @@ -11,3 +11,5 @@ export { default as dtel } from './dtel'; export { default as fugr } from './fugr'; export { default as intf } from './intf'; export { default as prog } from './prog'; +export { default as tabl } from './tabl'; +export { default as ttyp } from './ttyp'; diff --git a/packages/adt-plugin-abapgit/src/schemas/generated/schemas/tabl.ts b/packages/adt-plugin-abapgit/src/schemas/generated/schemas/tabl.ts new file mode 100644 index 00000000..cd187c61 --- /dev/null +++ b/packages/adt-plugin-abapgit/src/schemas/generated/schemas/tabl.ts @@ -0,0 +1,292 @@ +/** + * Auto-generated schema from XSD + * + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: abapgit/tabl.xsd + */ + +export default { + $xmlns: { + xs: 'http://www.w3.org/2001/XMLSchema', + asx: 'http://www.sap.com/abapxml', + }, + targetNamespace: 'http://www.sap.com/abapxml', + elementFormDefault: 'unqualified', + element: [ + { + name: 'abapGit', + complexType: { + sequence: { + element: [ + { + ref: 'asx:abap', + }, + ], + }, + attribute: [ + { + name: 'version', + type: 'xs:string', + use: 'required', + }, + { + name: 'serializer', + type: 'xs:string', + use: 'required', + }, + { + name: 'serializer_version', + type: 'xs:string', + use: 'required', + }, + ], + }, + }, + { + name: 'Schema', + abstract: true, + }, + { + name: 'abap', + type: 'asx:AbapType', + }, + ], + complexType: [ + { + name: 'AbapValuesType', + all: { + element: [ + { + name: 'DD02V', + type: 'asx:Dd02vType', + minOccurs: '0', + }, + { + name: 'DD03P_TABLE', + type: 'asx:Dd03pTableType', + minOccurs: '0', + }, + ], + }, + }, + { + name: 'Dd02vType', + all: { + element: [ + { + name: 'TABNAME', + type: 'xs:string', + }, + { + name: 'DDLANGUAGE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'TABCLASS', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'SQLTAB', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DATCLASS', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'BUFFERED', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'MASTERLANG', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'MATEFLAG', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'CONTFLAG', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'SHLPEXI', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'EXCLASS', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DDTEXT', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'AUTHCLASS', + type: 'xs:string', + minOccurs: '0', + }, + ], + }, + }, + { + name: 'Dd03pType', + all: { + element: [ + { + name: 'TABNAME', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'FIELDNAME', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DDLANGUAGE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'POSITION', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'KEYFLAG', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'ROLLNAME', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'ADMINFIELD', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'INTTYPE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'INTLEN', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DATATYPE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'LENG', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DECIMALS', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'NOTNULL', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DOMNAME', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'SHLPORIGIN', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'COMPTYPE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'MASK', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'REFTABLE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'REFFIELD', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'CONRFLAG', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'PRECFIELD', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DDTEXT', + type: 'xs:string', + minOccurs: '0', + }, + ], + }, + }, + { + name: 'Dd03pTableType', + sequence: { + element: [ + { + name: 'DD03P', + type: 'Dd03pType', + minOccurs: '0', + maxOccurs: 'unbounded', + }, + ], + }, + }, + { + name: 'AbapType', + sequence: { + element: [ + { + name: 'values', + type: 'asx:AbapValuesType', + }, + ], + }, + attribute: [ + { + name: 'version', + type: 'xs:string', + default: '1.0', + }, + ], + }, + ], +} as const; diff --git a/packages/adt-plugin-abapgit/src/schemas/generated/schemas/ttyp.ts b/packages/adt-plugin-abapgit/src/schemas/generated/schemas/ttyp.ts new file mode 100644 index 00000000..60a0ce3d --- /dev/null +++ b/packages/adt-plugin-abapgit/src/schemas/generated/schemas/ttyp.ts @@ -0,0 +1,162 @@ +/** + * Auto-generated schema from XSD + * + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: abapgit/ttyp.xsd + */ + +export default { + $xmlns: { + xs: 'http://www.w3.org/2001/XMLSchema', + asx: 'http://www.sap.com/abapxml', + }, + targetNamespace: 'http://www.sap.com/abapxml', + elementFormDefault: 'unqualified', + element: [ + { + name: 'abapGit', + complexType: { + sequence: { + element: [ + { + ref: 'asx:abap', + }, + ], + }, + attribute: [ + { + name: 'version', + type: 'xs:string', + use: 'required', + }, + { + name: 'serializer', + type: 'xs:string', + use: 'required', + }, + { + name: 'serializer_version', + type: 'xs:string', + use: 'required', + }, + ], + }, + }, + { + name: 'Schema', + abstract: true, + }, + { + name: 'abap', + type: 'asx:AbapType', + }, + ], + complexType: [ + { + name: 'AbapValuesType', + all: { + element: [ + { + name: 'DD40V', + type: 'asx:Dd40vType', + minOccurs: '0', + }, + ], + }, + }, + { + name: 'Dd40vType', + all: { + element: [ + { + name: 'TYPENAME', + type: 'xs:string', + }, + { + name: 'DDLANGUAGE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'ROWTYPE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'ROWKIND', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DATATYPE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'ACCESSMODE', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'KEYDEF', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'KEYKIND', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'GENERIC', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'LENG', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DECIMALS', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DDTEXT', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'TYPELEN', + type: 'xs:string', + minOccurs: '0', + }, + { + name: 'DEFFDNAME', + type: 'xs:string', + minOccurs: '0', + }, + ], + }, + }, + { + name: 'AbapType', + sequence: { + element: [ + { + name: 'values', + type: 'asx:AbapValuesType', + }, + ], + }, + attribute: [ + { + name: 'version', + type: 'xs:string', + default: '1.0', + }, + ], + }, + ], +} as const; diff --git a/packages/adt-plugin-abapgit/src/schemas/generated/types/index.ts b/packages/adt-plugin-abapgit/src/schemas/generated/types/index.ts index c56412f3..b4c005e6 100644 --- a/packages/adt-plugin-abapgit/src/schemas/generated/types/index.ts +++ b/packages/adt-plugin-abapgit/src/schemas/generated/types/index.ts @@ -12,3 +12,5 @@ export type { DtelSchema as DtelAbapGitType } from './dtel'; export type { IntfSchema as IntfAbapGitType } from './intf'; export type { ProgSchema as ProgAbapGitType } from './prog'; export type { FugrSchema as FugrAbapGitType } from './fugr'; +export type { TablSchema as TablAbapGitType } from './tabl'; +export type { TtypSchema as TtypAbapGitType } from './ttyp'; diff --git a/packages/adt-plugin-abapgit/src/schemas/generated/types/tabl.ts b/packages/adt-plugin-abapgit/src/schemas/generated/types/tabl.ts new file mode 100644 index 00000000..58c48cf4 --- /dev/null +++ b/packages/adt-plugin-abapgit/src/schemas/generated/types/tabl.ts @@ -0,0 +1,109 @@ +/** + * Auto-generated TypeScript interfaces from XSD + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: abapgit/tabl.xsd + * Mode: Flattened + */ + +export type TablSchema = + | { + abapGit: { + abap: { + values: { + DD02V?: { + TABNAME: string; + DDLANGUAGE?: string; + TABCLASS?: string; + SQLTAB?: string; + DATCLASS?: string; + BUFFERED?: string; + MASTERLANG?: string; + MATEFLAG?: string; + CONTFLAG?: string; + SHLPEXI?: string; + EXCLASS?: string; + DDTEXT?: string; + AUTHCLASS?: string; + }; + DD03P_TABLE?: { + DD03P?: { + TABNAME?: string; + FIELDNAME?: string; + DDLANGUAGE?: string; + POSITION?: string; + KEYFLAG?: string; + ROLLNAME?: string; + ADMINFIELD?: string; + INTTYPE?: string; + INTLEN?: string; + DATATYPE?: string; + LENG?: string; + DECIMALS?: string; + NOTNULL?: string; + DOMNAME?: string; + SHLPORIGIN?: string; + COMPTYPE?: string; + MASK?: string; + REFTABLE?: string; + REFFIELD?: string; + CONRFLAG?: string; + PRECFIELD?: string; + DDTEXT?: string; + }[]; + }; + }; + version?: string; + }; + version: string; + serializer: string; + serializer_version: string; + }; + } + | { + abap: { + values: { + DD02V?: { + TABNAME: string; + DDLANGUAGE?: string; + TABCLASS?: string; + SQLTAB?: string; + DATCLASS?: string; + BUFFERED?: string; + MASTERLANG?: string; + MATEFLAG?: string; + CONTFLAG?: string; + SHLPEXI?: string; + EXCLASS?: string; + DDTEXT?: string; + AUTHCLASS?: string; + }; + DD03P_TABLE?: { + DD03P?: { + TABNAME?: string; + FIELDNAME?: string; + DDLANGUAGE?: string; + POSITION?: string; + KEYFLAG?: string; + ROLLNAME?: string; + ADMINFIELD?: string; + INTTYPE?: string; + INTLEN?: string; + DATATYPE?: string; + LENG?: string; + DECIMALS?: string; + NOTNULL?: string; + DOMNAME?: string; + SHLPORIGIN?: string; + COMPTYPE?: string; + MASK?: string; + REFTABLE?: string; + REFFIELD?: string; + CONRFLAG?: string; + PRECFIELD?: string; + DDTEXT?: string; + }[]; + }; + }; + version?: string; + }; + }; diff --git a/packages/adt-plugin-abapgit/src/schemas/generated/types/ttyp.ts b/packages/adt-plugin-abapgit/src/schemas/generated/types/ttyp.ts new file mode 100644 index 00000000..f73d6c33 --- /dev/null +++ b/packages/adt-plugin-abapgit/src/schemas/generated/types/ttyp.ts @@ -0,0 +1,59 @@ +/** + * Auto-generated TypeScript interfaces from XSD + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: abapgit/ttyp.xsd + * Mode: Flattened + */ + +export type TtypSchema = + | { + abapGit: { + abap: { + values: { + DD40V?: { + TYPENAME: string; + DDLANGUAGE?: string; + ROWTYPE?: string; + ROWKIND?: string; + DATATYPE?: string; + ACCESSMODE?: string; + KEYDEF?: string; + KEYKIND?: string; + GENERIC?: string; + LENG?: string; + DECIMALS?: string; + DDTEXT?: string; + TYPELEN?: string; + DEFFDNAME?: string; + }; + }; + version?: string; + }; + version: string; + serializer: string; + serializer_version: string; + }; + } + | { + abap: { + values: { + DD40V?: { + TYPENAME: string; + DDLANGUAGE?: string; + ROWTYPE?: string; + ROWKIND?: string; + DATATYPE?: string; + ACCESSMODE?: string; + KEYDEF?: string; + KEYKIND?: string; + GENERIC?: string; + LENG?: string; + DECIMALS?: string; + DDTEXT?: string; + TYPELEN?: string; + DEFFDNAME?: string; + }; + }; + version?: string; + }; + }; diff --git a/packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_structure.tabl.xml b/packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_structure.tabl.xml new file mode 100644 index 00000000..33b36ce7 --- /dev/null +++ b/packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_structure.tabl.xml @@ -0,0 +1,44 @@ + + + + + + ZAGE_STRUCTURE + E + INTTAB + AGE Test Structure + 1 + + + + PARTNER_ID + 0001 + CHAR10 + 0 + CHAR + 000010 + CHAR + + + DESCRIPTION + 0002 + CHAR40 + 0 + CHAR + 000040 + CHAR + + + AMOUNT + 0003 + 0 + P + 000008 + CURR + 000015 + 000002 + + + + + diff --git a/packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_transparent_table.tabl.xml b/packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_transparent_table.tabl.xml new file mode 100644 index 00000000..779eb6a7 --- /dev/null +++ b/packages/adt-plugin-abapgit/tests/fixtures/tabl/zage_transparent_table.tabl.xml @@ -0,0 +1,46 @@ + + + + + + ZAGE_TRANSPARENT_TABLE + E + TRANSP + AGE Test Transparent Table + A + 1 + + + + MANDT + 0001 + X + MANDT + 0 + X + E + + + KEY_FIELD + 0002 + X + 0 + C + 000020 + CHAR + 000010 + X + + + VALUE_FIELD + 0003 + CHAR40 + 0 + CHAR + 000040 + CHAR + + + + + diff --git a/packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_string_table.ttyp.xml b/packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_string_table.ttyp.xml new file mode 100644 index 00000000..d2ba668b --- /dev/null +++ b/packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_string_table.ttyp.xml @@ -0,0 +1,18 @@ + + + + + + ZAGE_STRING_TABLE + E + STRING + S + STRG + T + D + N + AGE Test String Table Type + + + + diff --git a/packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_struct_table_type.ttyp.xml b/packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_struct_table_type.ttyp.xml new file mode 100644 index 00000000..e574ba44 --- /dev/null +++ b/packages/adt-plugin-abapgit/tests/fixtures/ttyp/zage_struct_table_type.ttyp.xml @@ -0,0 +1,18 @@ + + + + + + ZAGE_STRUCT_TABLE_TYPE + E + ZAGE_STRUCTURE + S + STRU + T + D + N + AGE Test Structure Table Type + + + + diff --git a/packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts b/packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts new file mode 100644 index 00000000..c9e0cabc --- /dev/null +++ b/packages/adt-plugin-abapgit/tests/schemas/tabl.test.ts @@ -0,0 +1,85 @@ +/** + * Test for TABL (Table/Structure) schema + * + * Fixture-driven: parses XML, validates content, round-trips + */ + +import assert from 'node:assert'; +import { + runSchemaTests, + createTypedSchema, + type SchemaScenario, +} from './base/scenario.ts'; +import { tabl as tablSchema } from '../../src/schemas/generated/schemas/index.ts'; +import type { TablSchema } from '../../src/schemas/generated/types/tabl.ts'; + +const schema = createTypedSchema(tablSchema); + +const scenario: SchemaScenario = { + name: 'TABL', + xsdName: 'tabl', + schema, + fixtures: [ + { + path: 'tabl/zage_structure.tabl.xml', + validate: (data) => { + const root = (data as any).abapGit; + + // Envelope + assert.strictEqual(root.version, 'v1.0.0'); + assert.strictEqual(root.serializer, 'LCL_OBJECT_TABL'); + assert.strictEqual(root.serializer_version, 'v1.0.0'); + + // DD02V content (table header) + const dd02v = root.abap.values.DD02V!; + assert.strictEqual(dd02v.TABNAME, 'ZAGE_STRUCTURE'); + assert.strictEqual(dd02v.DDLANGUAGE, 'E'); + assert.strictEqual(dd02v.TABCLASS, 'INTTAB'); + assert.strictEqual(dd02v.DDTEXT, 'AGE Test Structure'); + + // DD03P_TABLE content (fields) + const dd03pTable = root.abap.values.DD03P_TABLE; + assert.ok(dd03pTable, 'DD03P_TABLE should exist'); + assert.strictEqual(dd03pTable!.DD03P?.length, 3); + + // First field + const field1 = dd03pTable!.DD03P![0]; + assert.strictEqual(field1.FIELDNAME, 'PARTNER_ID'); + assert.strictEqual(field1.POSITION, '0001'); + assert.strictEqual(field1.DATATYPE, 'CHAR'); + assert.strictEqual(field1.LENG, '000010'); + + // Third field (CURR type) + const field3 = dd03pTable!.DD03P![2]; + assert.strictEqual(field3.FIELDNAME, 'AMOUNT'); + assert.strictEqual(field3.DATATYPE, 'CURR'); + assert.strictEqual(field3.DECIMALS, '000002'); + }, + }, + { + path: 'tabl/zage_transparent_table.tabl.xml', + validate: (data) => { + const root = (data as any).abapGit; + + // DD02V + const dd02v = root.abap.values.DD02V!; + assert.strictEqual(dd02v.TABNAME, 'ZAGE_TRANSPARENT_TABLE'); + assert.strictEqual(dd02v.TABCLASS, 'TRANSP'); + assert.strictEqual(dd02v.CONTFLAG, 'A'); + + // Fields + const dd03pTable = root.abap.values.DD03P_TABLE; + assert.ok(dd03pTable); + assert.strictEqual(dd03pTable!.DD03P?.length, 3); + + // Key field + const mandt = dd03pTable!.DD03P![0]; + assert.strictEqual(mandt.FIELDNAME, 'MANDT'); + assert.strictEqual(mandt.KEYFLAG, 'X'); + assert.strictEqual(mandt.NOTNULL, 'X'); + }, + }, + ], +}; + +runSchemaTests(scenario); diff --git a/packages/adt-plugin-abapgit/tests/schemas/ttyp.test.ts b/packages/adt-plugin-abapgit/tests/schemas/ttyp.test.ts new file mode 100644 index 00000000..d66659f2 --- /dev/null +++ b/packages/adt-plugin-abapgit/tests/schemas/ttyp.test.ts @@ -0,0 +1,61 @@ +/** + * Test for TTYP (Table Type) schema + * + * Fixture-driven: parses XML, validates content, round-trips + */ + +import assert from 'node:assert'; +import { + runSchemaTests, + createTypedSchema, + type SchemaScenario, +} from './base/scenario.ts'; +import { ttyp as ttypSchema } from '../../src/schemas/generated/schemas/index.ts'; +import type { TtypSchema } from '../../src/schemas/generated/types/ttyp.ts'; + +const schema = createTypedSchema(ttypSchema); + +const scenario: SchemaScenario = { + name: 'TTYP', + xsdName: 'ttyp', + schema, + fixtures: [ + { + path: 'ttyp/zage_string_table.ttyp.xml', + validate: (data) => { + const root = (data as any).abapGit; + + // Envelope + assert.strictEqual(root.version, 'v1.0.0'); + assert.strictEqual(root.serializer, 'LCL_OBJECT_TTYP'); + assert.strictEqual(root.serializer_version, 'v1.0.0'); + + // DD40V content + const dd40v = root.abap.values.DD40V!; + assert.strictEqual(dd40v.TYPENAME, 'ZAGE_STRING_TABLE'); + assert.strictEqual(dd40v.DDLANGUAGE, 'E'); + assert.strictEqual(dd40v.ROWTYPE, 'STRING'); + assert.strictEqual(dd40v.ROWKIND, 'S'); + assert.strictEqual(dd40v.ACCESSMODE, 'T'); + assert.strictEqual(dd40v.KEYDEF, 'D'); + assert.strictEqual(dd40v.KEYKIND, 'N'); + assert.strictEqual(dd40v.DDTEXT, 'AGE Test String Table Type'); + }, + }, + { + path: 'ttyp/zage_struct_table_type.ttyp.xml', + validate: (data) => { + const root = (data as any).abapGit; + + // DD40V content + const dd40v = root.abap.values.DD40V!; + assert.strictEqual(dd40v.TYPENAME, 'ZAGE_STRUCT_TABLE_TYPE'); + assert.strictEqual(dd40v.ROWTYPE, 'ZAGE_STRUCTURE'); + assert.strictEqual(dd40v.DATATYPE, 'STRU'); + assert.strictEqual(dd40v.DDTEXT, 'AGE Test Structure Table Type'); + }, + }, + ], +}; + +runSchemaTests(scenario); diff --git a/packages/adt-plugin-abapgit/ts-xsd.config.ts b/packages/adt-plugin-abapgit/ts-xsd.config.ts index 0011b45b..9479ff67 100644 --- a/packages/adt-plugin-abapgit/ts-xsd.config.ts +++ b/packages/adt-plugin-abapgit/ts-xsd.config.ts @@ -28,7 +28,17 @@ export default defineConfig({ outputDir: 'src/schemas/generated/schemas', // Only object schemas - base schemas (asx, abapgit) are included via xs:import/xs:include // and their types get merged into each object schema during resolution - schemas: ['clas', 'devc', 'doma', 'dtel', 'intf', 'prog', 'fugr'], + schemas: [ + 'clas', + 'devc', + 'doma', + 'dtel', + 'intf', + 'prog', + 'fugr', + 'tabl', + 'ttyp', + ], }, }, generators: [ diff --git a/packages/adt-plugin-abapgit/xsd/tabl.xsd b/packages/adt-plugin-abapgit/xsd/tabl.xsd new file mode 100644 index 00000000..bc95f385 --- /dev/null +++ b/packages/adt-plugin-abapgit/xsd/tabl.xsd @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/adt-plugin-abapgit/xsd/ttyp.xsd b/packages/adt-plugin-abapgit/xsd/ttyp.xsd new file mode 100644 index 00000000..025dbcd3 --- /dev/null +++ b/packages/adt-plugin-abapgit/xsd/ttyp.xsd @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/adt-plugin-abapgit/xsd/types/dd02v.xsd b/packages/adt-plugin-abapgit/xsd/types/dd02v.xsd new file mode 100644 index 00000000..fd33ba42 --- /dev/null +++ b/packages/adt-plugin-abapgit/xsd/types/dd02v.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/adt-plugin-abapgit/xsd/types/dd03p.xsd b/packages/adt-plugin-abapgit/xsd/types/dd03p.xsd new file mode 100644 index 00000000..74dd5b3d --- /dev/null +++ b/packages/adt-plugin-abapgit/xsd/types/dd03p.xsd @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/adt-plugin-abapgit/xsd/types/dd40v.xsd b/packages/adt-plugin-abapgit/xsd/types/dd40v.xsd new file mode 100644 index 00000000..7a8ded3e --- /dev/null +++ b/packages/adt-plugin-abapgit/xsd/types/dd40v.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/adt-schemas/src/schemas/generated/schemas/sap/dataelements.ts b/packages/adt-schemas/src/schemas/generated/schemas/sap/dataelements.ts new file mode 100644 index 00000000..f3cdc881 --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/schemas/sap/dataelements.ts @@ -0,0 +1,246 @@ +/** + * Auto-generated schema from XSD + * + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/sap/dataelements.xsd + */ + +export default { + $xmlns: { + dtel: 'http://www.sap.com/adt/dictionary/dataelements', + ecore: 'http://www.eclipse.org/emf/2002/Ecore', + xsd: 'http://www.w3.org/2001/XMLSchema', + package: 'http://www.sap.com/adt/dictionary/dataelements', + }, + targetNamespace: 'http://www.sap.com/adt/dictionary/dataelements', + attributeFormDefault: 'unqualified', + elementFormDefault: 'qualified', + element: [ + { + name: 'dataElement', + type: 'dtel:DataElement', + }, + ], + complexType: [ + { + name: 'DataElement', + sequence: { + element: [ + { + name: 'typeKind', + type: 'dtel:TypeKind', + minOccurs: '1', + maxOccurs: '1', + }, + { + name: 'typeName', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'dataType', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'dataTypeLength', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'dataTypeLengthEnabled', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'dataTypeDecimals', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'dataTypeDecimalsEnabled', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'shortFieldLabel', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'shortFieldLength', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'shortFieldMaxLength', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'mediumFieldLabel', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'mediumFieldLength', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'mediumFieldMaxLength', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'longFieldLabel', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'longFieldLength', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'longFieldMaxLength', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'headingFieldLabel', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'headingFieldLength', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'headingFieldMaxLength', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'searchHelp', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'searchHelpParameter', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'setGetParameter', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'defaultComponentName', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'deactivateInputHistory', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'changeDocument', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'leftToRightDirection', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'deactivateBIDIFiltering', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'documentationStatus', + type: 'dtel:DocumentationStatus', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + }, + ], + simpleType: [ + { + name: 'TypeKind', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: 'domain', + }, + { + value: 'predefinedAbapType', + }, + { + value: 'refToPredefinedAbapType', + }, + { + value: 'refToDictionaryType', + }, + { + value: 'refToClifType', + }, + ], + }, + }, + { + name: 'DocumentationStatus', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: 'required', + }, + { + value: 'notUsedInScreens', + }, + { + value: 'explainedByShortText', + }, + { + value: 'postponed', + }, + ], + }, + }, + ], +} as const; diff --git a/packages/adt-schemas/src/schemas/generated/schemas/sap/domain.ts b/packages/adt-schemas/src/schemas/generated/schemas/sap/domain.ts new file mode 100644 index 00000000..da011bd7 --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/schemas/sap/domain.ts @@ -0,0 +1,220 @@ +/** + * Auto-generated schema from XSD + * + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/sap/domain.xsd + */ + +import adtcore from './adtcore'; + +export default { + $xmlns: { + adtcore: 'http://www.sap.com/adt/core', + ecore: 'http://www.eclipse.org/emf/2002/Ecore', + xsd: 'http://www.w3.org/2001/XMLSchema', + doma: 'http://www.sap.com/dictionary/domain', + }, + $imports: [adtcore], + targetNamespace: 'http://www.sap.com/dictionary/domain', + attributeFormDefault: 'qualified', + elementFormDefault: 'qualified', + element: [ + { + name: 'domain', + type: 'doma:Domain', + }, + ], + complexType: [ + { + name: 'Domain', + complexContent: { + extension: { + base: 'adtcore:AdtMainObject', + sequence: { + element: [ + { + name: 'content', + type: 'doma:Content', + }, + ], + }, + }, + }, + }, + { + name: 'Content', + sequence: { + element: [ + { + name: 'typeInformation', + type: 'doma:TypeInformation', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'outputInformation', + type: 'doma:OutputInformation', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'valueInformation', + type: 'doma:ValueInformation', + }, + { + name: 'appendInformation', + type: 'doma:AppendInformation', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + }, + { + name: 'TypeInformation', + sequence: { + element: [ + { + name: 'datatype', + type: 'xsd:string', + }, + { + name: 'length', + type: 'xsd:int', + }, + { + name: 'decimals', + type: 'xsd:int', + }, + ], + }, + }, + { + name: 'OutputInformation', + sequence: { + element: [ + { + name: 'length', + type: 'xsd:int', + }, + { + name: 'style', + type: 'xsd:string', + }, + { + name: 'conversionExit', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'signExists', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'lowercase', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'ampmFormat', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + }, + { + name: 'ValueInformation', + sequence: { + element: [ + { + name: 'valueTableRef', + type: 'adtcore:AdtObjectReference', + }, + { + name: 'appendExists', + type: 'xsd:boolean', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'fixValues', + type: 'doma:FixValues', + }, + ], + }, + }, + { + name: 'FixValues', + sequence: { + element: [ + { + name: 'fixValue', + type: 'doma:FixValue', + minOccurs: '0', + maxOccurs: 'unbounded', + }, + ], + }, + }, + { + name: 'FixValue', + sequence: { + element: [ + { + name: 'position', + type: 'xsd:int', + }, + { + name: 'low', + type: 'xsd:string', + }, + { + name: 'high', + type: 'xsd:string', + }, + { + name: 'text', + type: 'xsd:string', + }, + { + name: 'contributingAppendRef', + type: 'adtcore:AdtObjectReference', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'switchRef', + type: 'adtcore:AdtSwitchReference', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + }, + { + name: 'AppendInformation', + sequence: { + element: [ + { + name: 'appendedDomainRef', + type: 'adtcore:AdtObjectReference', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'switchRef', + type: 'adtcore:AdtSwitchReference', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + }, + ], +} as const; diff --git a/packages/adt-schemas/src/schemas/generated/schemas/sap/index.ts b/packages/adt-schemas/src/schemas/generated/schemas/sap/index.ts index 9af4c68d..a899e4be 100644 --- a/packages/adt-schemas/src/schemas/generated/schemas/sap/index.ts +++ b/packages/adt-schemas/src/schemas/generated/schemas/sap/index.ts @@ -32,6 +32,10 @@ export { default as traces } from './traces'; export { default as quickfixes } from './quickfixes'; export { default as log } from './log'; export { default as templatelink } from './templatelink'; +export { default as domain } from './domain'; +export { default as dataelements } from './dataelements'; +export { default as tabletype } from './tabletype'; +export { default as tablesettings } from './tablesettings'; export { default as xml } from './xml'; export { default as abapoo } from './abapoo'; export { default as abapsource } from './abapsource'; diff --git a/packages/adt-schemas/src/schemas/generated/schemas/sap/tablesettings.ts b/packages/adt-schemas/src/schemas/generated/schemas/sap/tablesettings.ts new file mode 100644 index 00000000..a97fa144 --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/schemas/sap/tablesettings.ts @@ -0,0 +1,323 @@ +/** + * Auto-generated schema from XSD + * + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/sap/tablesettings.xsd + */ + +import adtcore from './adtcore'; + +export default { + $xmlns: { + adtcore: 'http://www.sap.com/adt/core', + ecore: 'http://www.eclipse.org/emf/2002/Ecore', + xsd: 'http://www.w3.org/2001/XMLSchema', + ts: 'http://www.sap.com/dictionary/table/settings', + }, + $imports: [adtcore], + targetNamespace: 'http://www.sap.com/dictionary/table/settings', + attributeFormDefault: 'qualified', + elementFormDefault: 'qualified', + element: [ + { + name: 'tableSettings', + type: 'ts:TableSettings', + }, + { + name: 'loggingAssessment', + type: 'ts:LoggingAssessment', + }, + ], + complexType: [ + { + name: 'TableSettings', + complexContent: { + extension: { + base: 'adtcore:AdtObject', + sequence: { + element: [ + { + name: 'dataClassCategory', + type: 'xsd:string', + }, + { + name: 'sizeCategory', + type: 'xsd:string', + }, + { + name: 'buffering', + type: 'ts:Buffering', + }, + { + name: 'storageType', + type: 'ts:StorageType', + }, + { + name: 'sharingType', + type: 'ts:SharingType', + }, + { + name: 'loadUnit', + type: 'ts:LoadUnit', + }, + { + name: 'translation', + type: 'ts:TranslationSetting', + }, + { + name: 'loggingEnabled', + type: 'xsd:boolean', + }, + { + name: 'supportsLoggingAssessment', + type: 'xsd:boolean', + }, + { + name: 'isWritableByAMDP', + type: 'xsd:boolean', + }, + ], + }, + }, + }, + }, + { + name: 'Buffering', + sequence: { + element: [ + { + name: 'allowed', + type: 'ts:BufferingAllowed', + }, + { + name: 'type', + type: 'ts:BufferingType', + }, + { + name: 'areaKeyFields', + type: 'xsd:string', + }, + ], + }, + }, + { + name: 'LoggingAssessment', + sequence: { + element: [ + { + name: 'allowed', + type: 'xsd:boolean', + }, + { + name: 'rating', + type: 'ts:Rating', + }, + { + name: 'reason', + type: 'xsd:string', + }, + ], + }, + attribute: [ + { + name: 'isVisible', + type: 'xsd:boolean', + }, + { + name: 'isEditable', + type: 'xsd:boolean', + }, + { + name: 'name', + type: 'xsd:string', + }, + ], + }, + { + name: 'TranslationSetting', + attribute: [ + { + name: 'value', + type: 'ts:Translation', + }, + { + name: 'granularity', + type: 'ts:TranslationGranularity', + }, + { + name: 'isVisible', + type: 'xsd:boolean', + }, + { + name: 'isEditable', + type: 'xsd:boolean', + }, + ], + }, + ], + simpleType: [ + { + name: 'BufferingAllowed', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: 'N', + }, + { + value: 'X', + }, + { + value: 'A', + }, + ], + }, + }, + { + name: 'BufferingType', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: 'P', + }, + { + value: 'G', + }, + { + value: 'X', + }, + { + value: '', + }, + ], + }, + }, + { + name: 'StorageType', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: 'R', + }, + { + value: 'C', + }, + { + value: '', + }, + ], + }, + }, + { + name: 'SharingType', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: 'L', + }, + { + value: 'R', + }, + { + value: 'W', + }, + { + value: 'T', + }, + { + value: 'S', + }, + { + value: '', + }, + ], + }, + }, + { + name: 'Translation', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: 'X', + }, + { + value: 'L', + }, + { + value: 'T', + }, + { + value: 'N', + }, + { + value: '', + }, + ], + }, + }, + { + name: 'TranslationGranularity', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: '1', + }, + { + value: '2', + }, + { + value: '', + }, + ], + }, + }, + { + name: 'Rating', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: 'REQUIRED', + }, + { + value: 'NOT_REQUIRED', + }, + { + value: 'MAY_BE_REQUIRED', + }, + { + value: 'UNDEFINED', + }, + { + value: '', + }, + ], + }, + }, + { + name: 'LoadUnit', + restriction: { + base: 'xsd:string', + enumeration: [ + { + value: '', + }, + { + value: 'P', + }, + { + value: 'A', + }, + { + value: 'Q', + }, + ], + }, + }, + ], +} as const; diff --git a/packages/adt-schemas/src/schemas/generated/schemas/sap/tabletype.ts b/packages/adt-schemas/src/schemas/generated/schemas/sap/tabletype.ts new file mode 100644 index 00000000..2a5e7f73 --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/schemas/sap/tabletype.ts @@ -0,0 +1,341 @@ +/** + * Auto-generated schema from XSD + * + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/sap/tabletype.xsd + */ + +import adtcore from './adtcore'; + +export default { + $xmlns: { + adtcore: 'http://www.sap.com/adt/core', + ecore: 'http://www.eclipse.org/emf/2002/Ecore', + xsd: 'http://www.w3.org/2001/XMLSchema', + ttyp: 'http://www.sap.com/dictionary/tabletype', + }, + $imports: [adtcore], + targetNamespace: 'http://www.sap.com/dictionary/tabletype', + attributeFormDefault: 'qualified', + elementFormDefault: 'qualified', + element: [ + { + name: 'tableType', + type: 'ttyp:TableType', + }, + ], + complexType: [ + { + name: 'TableType', + complexContent: { + extension: { + base: 'adtcore:AdtMainObject', + sequence: { + element: [ + { + name: 'rowType', + type: 'ttyp:RowType', + }, + { + name: 'initialRowCount', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'accessType', + type: 'xsd:string', + }, + { + name: 'primaryKey', + type: 'ttyp:PrimaryKey', + }, + { + name: 'secondaryKeys', + type: 'ttyp:SecondaryKeys', + }, + { + name: 'valueHelps', + type: 'ttyp:ValueHelps', + }, + ], + }, + }, + }, + }, + { + name: 'RowType', + sequence: { + element: [ + { + name: 'typeKind', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'typeName', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'builtInType', + type: 'ttyp:BuiltInType', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'rangeType', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + }, + { + name: 'BuiltInType', + sequence: { + element: [ + { + name: 'dataType', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'length', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'decimals', + type: 'xsd:int', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + }, + { + name: 'PrimaryKey', + sequence: { + element: [ + { + name: 'definition', + type: 'xsd:string', + }, + { + name: 'kind', + type: 'xsd:string', + }, + { + name: 'components', + type: 'ttyp:KeyComponents', + minOccurs: '0', + maxOccurs: '1', + }, + { + name: 'alias', + type: 'xsd:string', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + attribute: [ + { + name: 'isVisible', + type: 'xsd:boolean', + use: 'optional', + }, + { + name: 'isEditable', + type: 'xsd:boolean', + use: 'optional', + }, + ], + }, + { + name: 'SecondaryKeys', + sequence: { + element: [ + { + name: 'allowed', + type: 'xsd:string', + }, + { + name: 'secondaryKey', + type: 'ttyp:SecondaryKey', + minOccurs: '0', + maxOccurs: 'unbounded', + }, + ], + }, + attribute: [ + { + name: 'isVisible', + type: 'xsd:boolean', + use: 'optional', + }, + { + name: 'isEditable', + type: 'xsd:boolean', + use: 'optional', + }, + ], + }, + { + name: 'SecondaryKey', + sequence: { + element: [ + { + name: 'components', + type: 'ttyp:KeyComponents', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + attribute: [ + { + name: 'identifier', + type: 'xsd:string', + use: 'optional', + }, + { + name: 'description', + type: 'xsd:string', + use: 'optional', + }, + { + name: 'language', + type: 'xsd:string', + use: 'optional', + }, + { + name: 'access', + type: 'xsd:string', + use: 'optional', + }, + { + name: 'definition', + type: 'xsd:string', + use: 'optional', + }, + ], + }, + { + name: 'KeyComponents', + sequence: { + element: [ + { + name: 'component', + type: 'ttyp:KeyComponent', + minOccurs: '0', + maxOccurs: 'unbounded', + }, + ], + }, + attribute: [ + { + name: 'isVisible', + type: 'xsd:boolean', + }, + ], + }, + { + name: 'KeyComponent', + attribute: [ + { + name: 'name', + type: 'xsd:string', + }, + ], + }, + { + name: 'ValueHelps', + sequence: { + element: [ + { + name: 'typeKindValues', + type: 'ttyp:ValueHelpList', + minOccurs: '1', + maxOccurs: '1', + }, + { + name: 'keyDefinitionValues', + type: 'ttyp:ValueHelpList', + minOccurs: '1', + maxOccurs: '1', + }, + { + name: 'keyKindValues', + type: 'ttyp:ValueHelpList', + minOccurs: '1', + maxOccurs: '1', + }, + { + name: 'accessTypeValues', + type: 'ttyp:ValueHelpList', + minOccurs: '1', + maxOccurs: '1', + }, + { + name: 'secKeyAccessValues', + type: 'ttyp:ValueHelpList', + minOccurs: '1', + maxOccurs: '1', + }, + { + name: 'secKeyDefinitionValues', + type: 'ttyp:ValueHelpList', + minOccurs: '1', + maxOccurs: '1', + }, + { + name: 'secKeyAllowedValues', + type: 'ttyp:ValueHelpList', + minOccurs: '1', + maxOccurs: '1', + }, + ], + }, + }, + { + name: 'ValueHelpList', + sequence: { + element: [ + { + name: 'valueHelp', + type: 'ttyp:ValueHelp', + minOccurs: '1', + maxOccurs: 'unbounded', + }, + ], + }, + }, + { + name: 'ValueHelp', + attribute: [ + { + name: 'key', + type: 'xsd:string', + use: 'required', + }, + { + name: 'value', + type: 'xsd:string', + use: 'required', + }, + { + name: 'description', + type: 'xsd:string', + use: 'optional', + }, + ], + }, + ], +} as const; diff --git a/packages/adt-schemas/src/schemas/generated/typed.ts b/packages/adt-schemas/src/schemas/generated/typed.ts index 78335824..70b4ef16 100644 --- a/packages/adt-schemas/src/schemas/generated/typed.ts +++ b/packages/adt-schemas/src/schemas/generated/typed.ts @@ -43,6 +43,10 @@ import type { TracesSchema } from './types/sap/traces.types'; import type { QuickfixesSchema } from './types/sap/quickfixes.types'; import type { LogSchema } from './types/sap/log.types'; import type { TemplatelinkSchema } from './types/sap/templatelink.types'; +import type { DomainSchema } from './types/sap/domain.types'; +import type { DataelementsSchema } from './types/sap/dataelements.types'; +import type { TabletypeSchema } from './types/sap/tabletype.types'; +import type { TablesettingsSchema } from './types/sap/tablesettings.types'; import type { AtomExtendedSchema } from './types/custom/atomExtended.types'; import type { DiscoverySchema } from './types/custom/discovery.types'; import type { HttpSchema } from './types/custom/http.types'; @@ -139,6 +143,18 @@ export const log: TypedSchema = typedSchema(_log); import _templatelink from './schemas/sap/templatelink'; export const templatelink: TypedSchema = typedSchema(_templatelink); +import _domain from './schemas/sap/domain'; +export const domain: TypedSchema = + typedSchema(_domain); +import _dataelements from './schemas/sap/dataelements'; +export const dataelements: TypedSchema = + typedSchema(_dataelements); +import _tabletype from './schemas/sap/tabletype'; +export const tabletype: TypedSchema = + typedSchema(_tabletype); +import _tablesettings from './schemas/sap/tablesettings'; +export const tablesettings: TypedSchema = + typedSchema(_tablesettings); // Custom schemas import _atomExtended from './schemas/custom/atomExtended'; diff --git a/packages/adt-schemas/src/schemas/generated/types/sap/dataelements.types.ts b/packages/adt-schemas/src/schemas/generated/types/sap/dataelements.types.ts new file mode 100644 index 00000000..1612be7e --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/types/sap/dataelements.types.ts @@ -0,0 +1,48 @@ +/** + * Auto-generated TypeScript interfaces from XSD + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/sap/dataelements.xsd + * Mode: Flattened + */ + +export type DataelementsSchema = { + dataElement: { + typeKind: + | 'domain' + | 'predefinedAbapType' + | 'refToPredefinedAbapType' + | 'refToDictionaryType' + | 'refToClifType'; + typeName?: string; + dataType?: string; + dataTypeLength?: number; + dataTypeLengthEnabled?: boolean; + dataTypeDecimals?: number; + dataTypeDecimalsEnabled?: boolean; + shortFieldLabel?: string; + shortFieldLength?: number; + shortFieldMaxLength?: number; + mediumFieldLabel?: string; + mediumFieldLength?: number; + mediumFieldMaxLength?: number; + longFieldLabel?: string; + longFieldLength?: number; + longFieldMaxLength?: number; + headingFieldLabel?: string; + headingFieldLength?: number; + headingFieldMaxLength?: number; + searchHelp?: string; + searchHelpParameter?: string; + setGetParameter?: string; + defaultComponentName?: string; + deactivateInputHistory?: boolean; + changeDocument?: boolean; + leftToRightDirection?: boolean; + deactivateBIDIFiltering?: boolean; + documentationStatus?: + | 'required' + | 'notUsedInScreens' + | 'explainedByShortText' + | 'postponed'; + }; +}; diff --git a/packages/adt-schemas/src/schemas/generated/types/sap/domain.types.ts b/packages/adt-schemas/src/schemas/generated/types/sap/domain.types.ts new file mode 100644 index 00000000..b0e6b822 --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/types/sap/domain.types.ts @@ -0,0 +1,132 @@ +/** + * Auto-generated TypeScript interfaces from XSD + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/sap/domain.xsd + * Mode: Flattened + */ + +export type DomainSchema = { + domain: { + containerRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + adtTemplate?: { + adtProperty?: { + $value?: string; + key?: string; + }[]; + name?: string; + }; + packageRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + content: { + typeInformation?: { + datatype: string; + length: number; + decimals: number; + }; + outputInformation?: { + length: number; + style: string; + conversionExit?: string; + signExists?: boolean; + lowercase?: boolean; + ampmFormat?: boolean; + }; + valueInformation: { + valueTableRef: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + appendExists?: boolean; + fixValues: { + fixValue?: { + position: number; + low: string; + high: string; + text: string; + contributingAppendRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + switchRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + state?: '' | 'undefined' | 'on' | 'off' | 'stand-by'; + }; + }[]; + }; + }; + appendInformation?: { + appendedDomainRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + switchRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + state?: '' | 'undefined' | 'on' | 'off' | 'stand-by'; + }; + }; + }; + name: string; + type: string; + changedBy?: string; + changedAt?: string; + createdAt?: string; + createdBy?: string; + version?: + | '' + | 'active' + | 'inactive' + | 'workingArea' + | 'new' + | 'partlyActive' + | 'activeWithInactiveVersion'; + description?: string; + descriptionTextLimit?: number; + language?: string; + masterSystem?: string; + masterLanguage?: string; + responsible?: string; + abapLanguageVersion?: string; + }; +}; diff --git a/packages/adt-schemas/src/schemas/generated/types/sap/tablesettings.types.ts b/packages/adt-schemas/src/schemas/generated/types/sap/tablesettings.types.ts new file mode 100644 index 00000000..05c065e8 --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/types/sap/tablesettings.types.ts @@ -0,0 +1,79 @@ +/** + * Auto-generated TypeScript interfaces from XSD + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/sap/tablesettings.xsd + * Mode: Flattened + */ + +export type TablesettingsSchema = + | { + tableSettings: { + containerRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + adtTemplate?: { + adtProperty?: { + $value?: string; + key?: string; + }[]; + name?: string; + }; + dataClassCategory: string; + sizeCategory: string; + buffering: { + allowed: 'N' | 'X' | 'A'; + type: unknown; + areaKeyFields: string; + }; + storageType: '' | 'R' | 'C'; + sharingType: '' | 'R' | 'L' | 'W' | 'T' | 'S'; + loadUnit: '' | 'A' | 'P' | 'Q'; + translation: { + value?: '' | 'N' | 'X' | 'L' | 'T'; + granularity?: '' | '1' | '2'; + isVisible?: boolean; + isEditable?: boolean; + }; + loggingEnabled: boolean; + supportsLoggingAssessment: boolean; + isWritableByAMDP: boolean; + name: string; + type: string; + changedBy?: string; + changedAt?: string; + createdAt?: string; + createdBy?: string; + version?: + | '' + | 'active' + | 'inactive' + | 'workingArea' + | 'new' + | 'partlyActive' + | 'activeWithInactiveVersion'; + description?: string; + descriptionTextLimit?: number; + language?: string; + }; + } + | { + loggingAssessment: { + allowed: boolean; + rating: + | '' + | 'REQUIRED' + | 'NOT_REQUIRED' + | 'MAY_BE_REQUIRED' + | 'UNDEFINED'; + reason: string; + isVisible?: boolean; + isEditable?: boolean; + name?: string; + }; + }; diff --git a/packages/adt-schemas/src/schemas/generated/types/sap/tabletype.types.ts b/packages/adt-schemas/src/schemas/generated/types/sap/tabletype.types.ts new file mode 100644 index 00000000..72a2e21e --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/types/sap/tabletype.types.ts @@ -0,0 +1,151 @@ +/** + * Auto-generated TypeScript interfaces from XSD + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/sap/tabletype.xsd + * Mode: Flattened + */ + +export type TabletypeSchema = { + tableType: { + containerRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + adtTemplate?: { + adtProperty?: { + $value?: string; + key?: string; + }[]; + name?: string; + }; + packageRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + rowType: { + typeKind?: string; + typeName?: string; + builtInType?: { + dataType?: string; + length?: number; + decimals?: number; + }; + rangeType?: string; + }; + initialRowCount?: number; + accessType: string; + primaryKey: { + definition: string; + kind: string; + components?: { + component?: { + name?: string; + }[]; + isVisible?: boolean; + }; + alias?: string; + isVisible?: boolean; + isEditable?: boolean; + }; + secondaryKeys: { + allowed: string; + secondaryKey?: { + components?: { + component?: { + name?: string; + }[]; + isVisible?: boolean; + }; + identifier?: string; + description?: string; + language?: string; + access?: string; + definition?: string; + }[]; + isVisible?: boolean; + isEditable?: boolean; + }; + valueHelps: { + typeKindValues: { + valueHelp: { + key: string; + value: string; + description?: string; + }[]; + }; + keyDefinitionValues: { + valueHelp: { + key: string; + value: string; + description?: string; + }[]; + }; + keyKindValues: { + valueHelp: { + key: string; + value: string; + description?: string; + }[]; + }; + accessTypeValues: { + valueHelp: { + key: string; + value: string; + description?: string; + }[]; + }; + secKeyAccessValues: { + valueHelp: { + key: string; + value: string; + description?: string; + }[]; + }; + secKeyDefinitionValues: { + valueHelp: { + key: string; + value: string; + description?: string; + }[]; + }; + secKeyAllowedValues: { + valueHelp: { + key: string; + value: string; + description?: string; + }[]; + }; + }; + name: string; + type: string; + changedBy?: string; + changedAt?: string; + createdAt?: string; + createdBy?: string; + version?: + | '' + | 'active' + | 'inactive' + | 'workingArea' + | 'new' + | 'partlyActive' + | 'activeWithInactiveVersion'; + description?: string; + descriptionTextLimit?: number; + language?: string; + masterSystem?: string; + masterLanguage?: string; + responsible?: string; + abapLanguageVersion?: string; + }; +}; diff --git a/packages/adt-schemas/ts-xsd.config.ts b/packages/adt-schemas/ts-xsd.config.ts index 8f7bd74f..2a70a337 100644 --- a/packages/adt-schemas/ts-xsd.config.ts +++ b/packages/adt-schemas/ts-xsd.config.ts @@ -60,6 +60,11 @@ const targetSchemas = [ 'sap/quickfixes', 'sap/log', 'sap/templatelink', + // Data Dictionary + 'sap/domain', + 'sap/dataelements', + 'sap/tabletype', + 'sap/tablesettings', // Custom schemas 'custom/atomExtended', 'custom/discovery', From ac0d5802d160b1e87734838a5eb407dd67058779 Mon Sep 17 00:00:00 2001 From: Petr Plenkov Date: Mon, 16 Mar 2026 11:12:47 +0100 Subject: [PATCH 2/5] fix(adk): robust upsert fallback for DDIC objects (405/422 handling) - 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 --- packages/adk/src/base/adt.ts | 2 + packages/adk/src/base/model.ts | 69 +++++++++++-- packages/adk/src/base/object-set.ts | 6 +- .../adk/src/objects/ddic/dtel/dtel.model.ts | 7 +- .../adk/src/objects/ddic/tabl/tabl.model.ts | 21 ++-- packages/adt-client/src/index.ts | 2 + .../src/adt/ddic/dataelements.ts | 12 ++- packages/adt-contracts/src/adt/ddic/index.ts | 12 ++- .../adt-contracts/src/adt/ddic/structures.ts | 87 ++++------------- packages/adt-contracts/src/adt/ddic/tables.ts | 88 ++++------------- .../adt-contracts/src/generated/schemas.ts | 2 + packages/adt-contracts/src/helpers/crud.ts | 4 +- .../adt-contracts/tests/contracts/oo.test.ts | 4 +- .../tests/contracts/packages.test.ts | 2 +- .../adt-schemas/.xsd/custom/blueSource.xsd | 37 +++++++ .../.xsd/custom/dataelementWrapper.xsd | 41 ++++++++ .../generated/schemas/custom/blueSource.ts | 39 ++++++++ .../schemas/custom/dataelementWrapper.ts | 48 ++++++++++ .../schemas/generated/schemas/custom/index.ts | 2 + .../src/schemas/generated/typed.ts | 8 ++ .../types/custom/blueSource.types.ts | 81 ++++++++++++++++ .../types/custom/dataelementWrapper.types.ts | 96 +++++++++++++++++++ packages/adt-schemas/ts-xsd.config.ts | 2 + 23 files changed, 511 insertions(+), 161 deletions(-) create mode 100644 packages/adt-schemas/.xsd/custom/blueSource.xsd create mode 100644 packages/adt-schemas/.xsd/custom/dataelementWrapper.xsd create mode 100644 packages/adt-schemas/src/schemas/generated/schemas/custom/blueSource.ts create mode 100644 packages/adt-schemas/src/schemas/generated/schemas/custom/dataelementWrapper.ts create mode 100644 packages/adt-schemas/src/schemas/generated/types/custom/blueSource.types.ts create mode 100644 packages/adt-schemas/src/schemas/generated/types/custom/dataelementWrapper.types.ts diff --git a/packages/adk/src/base/adt.ts b/packages/adk/src/base/adt.ts index 975e71aa..3b572f47 100644 --- a/packages/adk/src/base/adt.ts +++ b/packages/adk/src/base/adt.ts @@ -39,6 +39,8 @@ export type { FunctionGroupResponse as FunctionGroupResponseUnion, DomainResponse, DataElementResponse, + TableResponse, + StructureResponse, TableTypeResponse, } from '@abapify/adt-client'; diff --git a/packages/adk/src/base/model.ts b/packages/adk/src/base/model.ts index 45fedb1f..7193f4aa 100644 --- a/packages/adk/src/base/model.ts +++ b/packages/adk/src/base/model.ts @@ -475,9 +475,21 @@ export abstract class AdkObject { try { await this.lock(transport); } catch (e) { - // For upsert, only fallback to create if it's a 404 Not Found error - if (mode === 'upsert' && this.isNotFoundError(e)) { - return this.save({ ...options, mode: 'create' }); + // For upsert, fallback to create if object doesn't exist (404) + // or endpoint doesn't support the operation (405 - common for DDIC types) + if (mode === 'upsert' && this.shouldFallbackToCreate(e)) { + try { + return await this.save({ ...options, mode: 'create' }); + } catch (createErr) { + // If POST fails with 422 "already exists", the object exists but + // the endpoint doesn't support lock/PUT (e.g., some DDIC types). + // Treat as unchanged — we can't update it via this mechanism. + if (this.isAlreadyExistsError(createErr)) { + this._unchanged = true; + return this; + } + throw createErr; + } } throw e; } @@ -510,9 +522,25 @@ export abstract class AdkObject { return this; } catch (e: unknown) { - // For upsert with PUT failure (404), try POST - if (mode === 'upsert' && this.isNotFoundError(e)) { - return this.save({ ...options, mode: 'create' }); + // For upsert with PUT failure (404/405), try POST (create) + if (mode === 'upsert' && this.shouldFallbackToCreate(e)) { + try { + return await this.save({ ...options, mode: 'create' }); + } catch (createErr) { + // If POST fails with 422 "already exists", the object exists but + // the endpoint doesn't support PUT for updates (e.g., TABL). + // Treat as unchanged — we can't update it via this mechanism. + if (this.isAlreadyExistsError(createErr)) { + this._unchanged = true; + return this; + } + throw createErr; + } + } + // If PUT returns 422 "already exists" directly in upsert mode + if (mode === 'upsert' && this.isAlreadyExistsError(e)) { + this._unchanged = true; + return this; } throw e; } finally { @@ -586,6 +614,35 @@ export abstract class AdkObject { return false; } + /** + * Check if error suggests the object doesn't exist or the operation + * is not supported for the current state. + * + * Some SAP DDIC endpoints (TABL, TTYP) return 405 Method Not Allowed + * instead of 404 when attempting to lock or PUT a non-existent object. + * In upsert mode, both should trigger a fallback to POST (create). + */ + protected shouldFallbackToCreate(e: unknown): boolean { + return ( + this.isNotFoundError(e) || + (e instanceof Error && + (e.message.includes('405') || e.message.includes('Method Not Allowed'))) + ); + } + + /** + * Check if error is a 422 "already exists" from a POST create attempt + */ + protected isAlreadyExistsError(e: unknown): boolean { + if (e instanceof Error) { + return ( + (e.message.includes('422') || e.message.includes('Unprocessable')) && + e.message.includes('already exists') + ); + } + return false; + } + /** * Check if object has pending sources to save * Subclasses with source code (classes, interfaces) override this diff --git a/packages/adk/src/base/object-set.ts b/packages/adk/src/base/object-set.ts index 8acf7373..4d8ad8bc 100644 --- a/packages/adk/src/base/object-set.ts +++ b/packages/adk/src/base/object-set.ts @@ -345,7 +345,11 @@ export class AdkObjectSet { */ async unlockAll(): Promise { for (const obj of this.objects) { - await obj.unlock(); + try { + await obj.unlock(); + } catch { + // Ignore unlock failures - object may not have been locked + } } } diff --git a/packages/adk/src/objects/ddic/dtel/dtel.model.ts b/packages/adk/src/objects/ddic/dtel/dtel.model.ts index 3d249f48..b108d756 100644 --- a/packages/adk/src/objects/ddic/dtel/dtel.model.ts +++ b/packages/adk/src/objects/ddic/dtel/dtel.model.ts @@ -13,9 +13,10 @@ import type { AdkContext } from '../../../base/context'; import type { DataElementResponse } from '../../../base/adt'; /** - * Data Element data type - unwrap from schema root element + * Data Element data type - unwrap from wrapper root element + * SAP wraps DTEL content in a blue:wbobj element extending AdtMainObject */ -export type DataElementXml = DataElementResponse['dataElement']; +export type DataElementXml = DataElementResponse['wbobj']; /** * ADK Data Element object @@ -41,7 +42,7 @@ export class AdkDataElement extends AdkMainObject< } protected override get wrapperKey() { - return 'dataElement'; + return 'wbobj'; } protected override get crudContract(): any { return this.ctx.client.adt.ddic.dataelements; diff --git a/packages/adk/src/objects/ddic/tabl/tabl.model.ts b/packages/adk/src/objects/ddic/tabl/tabl.model.ts index b4ae15d3..b9599fbd 100644 --- a/packages/adk/src/objects/ddic/tabl/tabl.model.ts +++ b/packages/adk/src/objects/ddic/tabl/tabl.model.ts @@ -2,15 +2,16 @@ * TABL - Database Table / Structure * * ADK object for ABAP Database Tables (TABL/DT) and Structures (TABL/DS). - * DDIC objects are metadata-only (no source code). + * Tables and structures are source-based DDIC objects whose definition + * lives in ABAP source code (retrieved via sourceUri). * * Note: Tables and structures share the same ADT main type (TABL) * but use different endpoints and subtypes. * - Tables: /sap/bc/adt/ddic/tables (TABL/DT) * - Structures: /sap/bc/adt/ddic/structures (TABL/DS) * - * Since no typed XSD schema is available yet for tables/structures, - * these use a generic data type. + * SAP wraps both in a blue:blueSource root element + * (namespace http://www.sap.com/wbobj/blue) extending AbapSourceMainObject. */ import { AdkMainObject } from '../../../base/model'; @@ -21,10 +22,12 @@ import { import { getGlobalContext } from '../../../base/global-context'; import type { AdkContext } from '../../../base/context'; +import type { TableResponse } from '../../../base/adt'; + /** - * Generic table/structure data (untyped until schema is available) + * Table/Structure data type - unwrap from blueSource wrapper root element */ -export type TableXml = Record; +export type TableXml = TableResponse['blueSource']; /** * ADK Table object (database table - TABL/DT) @@ -37,8 +40,8 @@ export class AdkTable extends AdkMainObject { return `/sap/bc/adt/ddic/tables/${encodeURIComponent(this.name.toLowerCase())}`; } - protected override get wrapperKey(): undefined { - return undefined; + protected override get wrapperKey() { + return 'blueSource'; } protected override get crudContract(): any { return this.ctx.client.adt.ddic.tables; @@ -64,8 +67,8 @@ export class AdkStructure extends AdkMainObject< return `/sap/bc/adt/ddic/structures/${encodeURIComponent(this.name.toLowerCase())}`; } - protected override get wrapperKey(): undefined { - return undefined; + protected override get wrapperKey() { + return 'blueSource'; } protected override get crudContract(): any { return this.ctx.client.adt.ddic.structures; diff --git a/packages/adt-client/src/index.ts b/packages/adt-client/src/index.ts index ac8df684..863a438a 100644 --- a/packages/adt-client/src/index.ts +++ b/packages/adt-client/src/index.ts @@ -72,6 +72,8 @@ export type { FunctionGroupResponse } from '@abapify/adt-contracts'; export type { DomainResponse, DataElementResponse, + TableResponse, + StructureResponse, TableTypeResponse, } from '@abapify/adt-contracts'; diff --git a/packages/adt-contracts/src/adt/ddic/dataelements.ts b/packages/adt-contracts/src/adt/ddic/dataelements.ts index 9cf38f23..5b2938df 100644 --- a/packages/adt-contracts/src/adt/ddic/dataelements.ts +++ b/packages/adt-contracts/src/adt/ddic/dataelements.ts @@ -8,19 +8,25 @@ import { crud } from '../../helpers/crud'; import { - dataelements as dataelementsSchema, + dataelementWrapper as dataelementWrapperSchema, type InferTypedSchema, } from '../../schemas'; /** * Data Element response type - exported for consumers (ADK, etc.) + * + * Uses the custom wrapper schema because SAP wraps the inner + * dtel:dataElement content in a blue:wbobj root element + * (namespace http://www.sap.com/wbobj/dictionary/dtel). */ -export type DataElementResponse = InferTypedSchema; +export type DataElementResponse = InferTypedSchema< + typeof dataelementWrapperSchema +>; export type DataelementsContract = typeof dataelementsContract; export const dataelementsContract = crud({ basePath: '/sap/bc/adt/ddic/dataelements', - schema: dataelementsSchema, + schema: dataelementWrapperSchema, contentType: 'application/vnd.sap.adt.dataelements.v2+xml', }); diff --git a/packages/adt-contracts/src/adt/ddic/index.ts b/packages/adt-contracts/src/adt/ddic/index.ts index 73db0543..18ac70d4 100644 --- a/packages/adt-contracts/src/adt/ddic/index.ts +++ b/packages/adt-contracts/src/adt/ddic/index.ts @@ -19,8 +19,16 @@ export { type DataelementsContract, type DataElementResponse, } from './dataelements'; -export { structuresContract } from './structures'; -export { tablesContract } from './tables'; +export { + structuresContract, + type StructuresContract, + type StructureResponse, +} from './structures'; +export { + tablesContract, + type TablesContract, + type TableResponse, +} from './tables'; export { tabletypesContract, type TabletypesContract, diff --git a/packages/adt-contracts/src/adt/ddic/structures.ts b/packages/adt-contracts/src/adt/ddic/structures.ts index be79ed9e..41e0471b 100644 --- a/packages/adt-contracts/src/adt/ddic/structures.ts +++ b/packages/adt-contracts/src/adt/ddic/structures.ts @@ -5,82 +5,37 @@ * Content-Type: application/vnd.sap.adt.structures.v2+xml * Object type: TABL/DS (tablds) * - * Note: No XSD schema available yet for structures. - * Using undefined responses until a proper schema is available. + * Uses the custom blueSource wrapper schema because SAP wraps + * structure responses in a blue:blueSource root element + * (namespace http://www.sap.com/wbobj/blue) extending AbapSourceMainObject. */ +import { crud } from '../../helpers/crud'; import { http } from '@abapify/speci/rest'; +import { + blueSource as blueSourceSchema, + type InferTypedSchema, +} from '../../schemas'; + +/** + * Structure response type - exported for consumers (ADK, etc.) + */ +export type StructureResponse = InferTypedSchema; + +export type StructuresContract = typeof structuresContract; const basePath = '/sap/bc/adt/ddic/structures'; const contentType = 'application/vnd.sap.adt.structures.v2+xml'; -const nameTransform = (n: string) => n.toLowerCase(); export const structuresContract = { - get: (name: string, options?: { version?: string }) => - http.get(`${basePath}/${nameTransform(name)}`, { - responses: { 200: undefined }, - headers: { Accept: contentType }, - query: options?.version ? { version: options.version } : undefined, - }), - - post: (options?: { corrNr?: string }) => - http.post(basePath, { - body: undefined, - responses: { 200: undefined }, - headers: { - Accept: contentType, - 'Content-Type': 'application/*', - }, - query: options?.corrNr ? { corrNr: options.corrNr } : undefined, - }), - - put: (name: string, options?: { corrNr?: string; lockHandle?: string }) => - http.put(`${basePath}/${nameTransform(name)}`, { - body: undefined, - responses: { 200: undefined }, - headers: { - Accept: contentType, - 'Content-Type': contentType, - }, - query: { - ...(options?.corrNr ? { corrNr: options.corrNr } : {}), - ...(options?.lockHandle ? { lockHandle: options.lockHandle } : {}), - }, - }), - - delete: (name: string, options?: { corrNr?: string; lockHandle?: string }) => - http.delete(`${basePath}/${nameTransform(name)}`, { - responses: { 204: undefined }, - query: { - ...(options?.corrNr ? { corrNr: options.corrNr } : {}), - ...(options?.lockHandle ? { lockHandle: options.lockHandle } : {}), - }, - }), - - lock: (name: string, options?: { corrNr?: string; accessMode?: string }) => - http.post(`${basePath}/${nameTransform(name)}`, { - responses: { 200: undefined }, - headers: { - 'X-sap-adt-sessiontype': 'stateful', - }, - query: { - _action: 'LOCK', - accessMode: options?.accessMode ?? 'MODIFY', - ...(options?.corrNr ? { corrNr: options.corrNr } : {}), - }, - }), - - unlock: (name: string, options: { lockHandle: string }) => - http.post(`${basePath}/${nameTransform(name)}`, { - responses: { 200: undefined }, - query: { - _action: 'UNLOCK', - lockHandle: options.lockHandle, - }, - }), + ...crud({ + basePath, + schema: blueSourceSchema, + contentType, + }), objectstructure: (name: string, options?: { version?: string }) => - http.get(`${basePath}/${nameTransform(name)}/objectstructure`, { + http.get(`${basePath}/${name.toLowerCase()}/objectstructure`, { responses: { 200: undefined }, query: options?.version ? { version: options.version } : undefined, }), diff --git a/packages/adt-contracts/src/adt/ddic/tables.ts b/packages/adt-contracts/src/adt/ddic/tables.ts index f0ed8f31..c133b72b 100644 --- a/packages/adt-contracts/src/adt/ddic/tables.ts +++ b/packages/adt-contracts/src/adt/ddic/tables.ts @@ -5,83 +5,37 @@ * Content-Type: application/vnd.sap.adt.tables.v2+xml * Object type: TABL/DT (tabldt) * - * Note: No XSD schema available yet for tables. - * The tablesettings schema covers technical settings only. - * Using undefined responses until a proper schema is available. + * Uses the custom blueSource wrapper schema because SAP wraps + * TABL responses in a blue:blueSource root element + * (namespace http://www.sap.com/wbobj/blue) extending AbapSourceMainObject. */ +import { crud } from '../../helpers/crud'; import { http } from '@abapify/speci/rest'; +import { + blueSource as blueSourceSchema, + type InferTypedSchema, +} from '../../schemas'; + +/** + * Table response type - exported for consumers (ADK, etc.) + */ +export type TableResponse = InferTypedSchema; + +export type TablesContract = typeof tablesContract; const basePath = '/sap/bc/adt/ddic/tables'; const contentType = 'application/vnd.sap.adt.tables.v2+xml'; -const nameTransform = (n: string) => n.toLowerCase(); export const tablesContract = { - get: (name: string, options?: { version?: string }) => - http.get(`${basePath}/${nameTransform(name)}`, { - responses: { 200: undefined }, - headers: { Accept: contentType }, - query: options?.version ? { version: options.version } : undefined, - }), - - post: (options?: { corrNr?: string }) => - http.post(basePath, { - body: undefined, - responses: { 200: undefined }, - headers: { - Accept: contentType, - 'Content-Type': 'application/*', - }, - query: options?.corrNr ? { corrNr: options.corrNr } : undefined, - }), - - put: (name: string, options?: { corrNr?: string; lockHandle?: string }) => - http.put(`${basePath}/${nameTransform(name)}`, { - body: undefined, - responses: { 200: undefined }, - headers: { - Accept: contentType, - 'Content-Type': contentType, - }, - query: { - ...(options?.corrNr ? { corrNr: options.corrNr } : {}), - ...(options?.lockHandle ? { lockHandle: options.lockHandle } : {}), - }, - }), - - delete: (name: string, options?: { corrNr?: string; lockHandle?: string }) => - http.delete(`${basePath}/${nameTransform(name)}`, { - responses: { 204: undefined }, - query: { - ...(options?.corrNr ? { corrNr: options.corrNr } : {}), - ...(options?.lockHandle ? { lockHandle: options.lockHandle } : {}), - }, - }), - - lock: (name: string, options?: { corrNr?: string; accessMode?: string }) => - http.post(`${basePath}/${nameTransform(name)}`, { - responses: { 200: undefined }, - headers: { - 'X-sap-adt-sessiontype': 'stateful', - }, - query: { - _action: 'LOCK', - accessMode: options?.accessMode ?? 'MODIFY', - ...(options?.corrNr ? { corrNr: options.corrNr } : {}), - }, - }), - - unlock: (name: string, options: { lockHandle: string }) => - http.post(`${basePath}/${nameTransform(name)}`, { - responses: { 200: undefined }, - query: { - _action: 'UNLOCK', - lockHandle: options.lockHandle, - }, - }), + ...crud({ + basePath, + schema: blueSourceSchema, + contentType, + }), objectstructure: (name: string, options?: { version?: string }) => - http.get(`${basePath}/${nameTransform(name)}/objectstructure`, { + http.get(`${basePath}/${name.toLowerCase()}/objectstructure`, { responses: { 200: undefined }, query: options?.version ? { version: options.version } : undefined, }), diff --git a/packages/adt-contracts/src/generated/schemas.ts b/packages/adt-contracts/src/generated/schemas.ts index d0735296..a3e3acaa 100644 --- a/packages/adt-contracts/src/generated/schemas.ts +++ b/packages/adt-contracts/src/generated/schemas.ts @@ -57,8 +57,10 @@ export const programs = toSpeciSchema(adtSchemas.programs); export const groups = toSpeciSchema(adtSchemas.groups); export const domain = toSpeciSchema(adtSchemas.domain); export const dataelements = toSpeciSchema(adtSchemas.dataelements); +export const dataelementWrapper = toSpeciSchema(adtSchemas.dataelementWrapper); export const tabletype = toSpeciSchema(adtSchemas.tabletype); export const tablesettings = toSpeciSchema(adtSchemas.tablesettings); +export const blueSource = toSpeciSchema(adtSchemas.blueSource); // ============================================================================ // JSON Schemas (re-exported directly - they use zod, not ts-xsd) diff --git a/packages/adt-contracts/src/helpers/crud.ts b/packages/adt-contracts/src/helpers/crud.ts index 797837bb..0f4b94f4 100644 --- a/packages/adt-contracts/src/helpers/crud.ts +++ b/packages/adt-contracts/src/helpers/crud.ts @@ -346,7 +346,7 @@ export function crud< responses: { 200: schema }, headers: { Accept: contentType, - 'Content-Type': 'application/*', + 'Content-Type': contentType, }, query: queryOptions?.corrNr ? { corrNr: queryOptions.corrNr } @@ -406,6 +406,8 @@ export function crud< responses: { 200: undefined }, headers: { 'X-sap-adt-sessiontype': 'stateful', + Accept: + 'application/*,application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result', }, query: { _action: 'LOCK', diff --git a/packages/adt-contracts/tests/contracts/oo.test.ts b/packages/adt-contracts/tests/contracts/oo.test.ts index dc9a59f4..d2fa7752 100644 --- a/packages/adt-contracts/tests/contracts/oo.test.ts +++ b/packages/adt-contracts/tests/contracts/oo.test.ts @@ -35,7 +35,7 @@ class ClassesScenario extends ContractScenario { path: '/sap/bc/adt/oo/classes', headers: { Accept: 'application/vnd.sap.adt.oo.classes.v4+xml', - 'Content-Type': 'application/*', + 'Content-Type': 'application/vnd.sap.adt.oo.classes.v4+xml', }, body: { schema: classesSchema }, response: { status: 200, schema: classesSchema }, @@ -136,7 +136,7 @@ class InterfacesScenario extends ContractScenario { path: '/sap/bc/adt/oo/interfaces', headers: { Accept: 'application/vnd.sap.adt.oo.interfaces.v5+xml', - 'Content-Type': 'application/*', + 'Content-Type': 'application/vnd.sap.adt.oo.interfaces.v5+xml', }, body: { schema: interfacesSchema }, response: { status: 200, schema: interfacesSchema }, diff --git a/packages/adt-contracts/tests/contracts/packages.test.ts b/packages/adt-contracts/tests/contracts/packages.test.ts index 2b9cfaf0..a6dfbcad 100644 --- a/packages/adt-contracts/tests/contracts/packages.test.ts +++ b/packages/adt-contracts/tests/contracts/packages.test.ts @@ -40,7 +40,7 @@ class PackagesScenario extends ContractScenario { path: '/sap/bc/adt/packages', headers: { Accept: 'application/vnd.sap.adt.packages.v2+xml', - 'Content-Type': 'application/*', + 'Content-Type': 'application/vnd.sap.adt.packages.v2+xml', }, query: { corrNr: 'DEVK900001' }, body: { schema: packagesV1 }, diff --git a/packages/adt-schemas/.xsd/custom/blueSource.xsd b/packages/adt-schemas/.xsd/custom/blueSource.xsd new file mode 100644 index 00000000..16637b11 --- /dev/null +++ b/packages/adt-schemas/.xsd/custom/blueSource.xsd @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/adt-schemas/.xsd/custom/dataelementWrapper.xsd b/packages/adt-schemas/.xsd/custom/dataelementWrapper.xsd new file mode 100644 index 00000000..4cbe80b3 --- /dev/null +++ b/packages/adt-schemas/.xsd/custom/dataelementWrapper.xsd @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/adt-schemas/src/schemas/generated/schemas/custom/blueSource.ts b/packages/adt-schemas/src/schemas/generated/schemas/custom/blueSource.ts new file mode 100644 index 00000000..f5729b16 --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/schemas/custom/blueSource.ts @@ -0,0 +1,39 @@ +/** + * Auto-generated schema from XSD + * + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/custom/blueSource.xsd + */ + +import adtcore from '../sap/adtcore'; +import abapsource from '../sap/abapsource'; + +export default { + $xmlns: { + adtcore: 'http://www.sap.com/adt/core', + abapsource: 'http://www.sap.com/adt/abapsource', + ecore: 'http://www.eclipse.org/emf/2002/Ecore', + xsd: 'http://www.w3.org/2001/XMLSchema', + blue: 'http://www.sap.com/wbobj/blue', + }, + $imports: [adtcore, abapsource], + targetNamespace: 'http://www.sap.com/wbobj/blue', + attributeFormDefault: 'qualified', + elementFormDefault: 'qualified', + element: [ + { + name: 'blueSource', + type: 'blue:BlueSource', + }, + ], + complexType: [ + { + name: 'BlueSource', + complexContent: { + extension: { + base: 'abapsource:AbapSourceMainObject', + }, + }, + }, + ], +} as const; diff --git a/packages/adt-schemas/src/schemas/generated/schemas/custom/dataelementWrapper.ts b/packages/adt-schemas/src/schemas/generated/schemas/custom/dataelementWrapper.ts new file mode 100644 index 00000000..1a1b954e --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/schemas/custom/dataelementWrapper.ts @@ -0,0 +1,48 @@ +/** + * Auto-generated schema from XSD + * + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/custom/dataelementWrapper.xsd + */ + +import adtcore from '../sap/adtcore'; +import dataelements from '../sap/dataelements'; + +export default { + $xmlns: { + adtcore: 'http://www.sap.com/adt/core', + dtel: 'http://www.sap.com/adt/dictionary/dataelements', + ecore: 'http://www.eclipse.org/emf/2002/Ecore', + xsd: 'http://www.w3.org/2001/XMLSchema', + blue: 'http://www.sap.com/wbobj/dictionary/dtel', + }, + $imports: [adtcore, dataelements], + targetNamespace: 'http://www.sap.com/wbobj/dictionary/dtel', + attributeFormDefault: 'qualified', + elementFormDefault: 'qualified', + element: [ + { + name: 'wbobj', + type: 'blue:DataElementWrapper', + }, + ], + complexType: [ + { + name: 'DataElementWrapper', + complexContent: { + extension: { + base: 'adtcore:AdtMainObject', + sequence: { + element: [ + { + ref: 'dtel:dataElement', + minOccurs: '0', + maxOccurs: '1', + }, + ], + }, + }, + }, + }, + ], +} as const; diff --git a/packages/adt-schemas/src/schemas/generated/schemas/custom/index.ts b/packages/adt-schemas/src/schemas/generated/schemas/custom/index.ts index a1069f19..9e4e30ca 100644 --- a/packages/adt-schemas/src/schemas/generated/schemas/custom/index.ts +++ b/packages/adt-schemas/src/schemas/generated/schemas/custom/index.ts @@ -3,6 +3,8 @@ * DO NOT EDIT - Generated by ts-xsd codegen */ +export { default as dataelementWrapper } from './dataelementWrapper'; +export { default as blueSource } from './blueSource'; export { default as atomExtended } from './atomExtended'; export { default as discovery } from './discovery'; export { default as http } from './http'; diff --git a/packages/adt-schemas/src/schemas/generated/typed.ts b/packages/adt-schemas/src/schemas/generated/typed.ts index 70b4ef16..560945b5 100644 --- a/packages/adt-schemas/src/schemas/generated/typed.ts +++ b/packages/adt-schemas/src/schemas/generated/typed.ts @@ -47,6 +47,8 @@ import type { DomainSchema } from './types/sap/domain.types'; import type { DataelementsSchema } from './types/sap/dataelements.types'; import type { TabletypeSchema } from './types/sap/tabletype.types'; import type { TablesettingsSchema } from './types/sap/tablesettings.types'; +import type { DataelementWrapperSchema } from './types/custom/dataelementWrapper.types'; +import type { BlueSourceSchema } from './types/custom/blueSource.types'; import type { AtomExtendedSchema } from './types/custom/atomExtended.types'; import type { DiscoverySchema } from './types/custom/discovery.types'; import type { HttpSchema } from './types/custom/http.types'; @@ -157,6 +159,12 @@ export const tablesettings: TypedSchema = typedSchema(_tablesettings); // Custom schemas +import _dataelementWrapper from './schemas/custom/dataelementWrapper'; +export const dataelementWrapper: TypedSchema = + typedSchema(_dataelementWrapper); +import _blueSource from './schemas/custom/blueSource'; +export const blueSource: TypedSchema = + typedSchema(_blueSource); import _atomExtended from './schemas/custom/atomExtended'; export const atomExtended: TypedSchema = typedSchema(_atomExtended); diff --git a/packages/adt-schemas/src/schemas/generated/types/custom/blueSource.types.ts b/packages/adt-schemas/src/schemas/generated/types/custom/blueSource.types.ts new file mode 100644 index 00000000..23939fcc --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/types/custom/blueSource.types.ts @@ -0,0 +1,81 @@ +/** + * Auto-generated TypeScript interfaces from XSD + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/custom/blueSource.xsd + * Mode: Flattened + */ + +export type BlueSourceSchema = { + blueSource: { + containerRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + adtTemplate?: { + adtProperty?: { + $value?: string; + key?: string; + }[]; + name?: string; + }; + packageRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + template?: { + property?: { + $value?: string; + key?: string; + }[]; + name?: string; + }; + syntaxConfiguration?: { + language?: { + version?: string; + description?: string; + }; + objectUsage?: { + restricted?: boolean; + }; + }; + name: string; + type: string; + changedBy?: string; + changedAt?: string; + createdAt?: string; + createdBy?: string; + version?: + | '' + | 'active' + | 'inactive' + | 'workingArea' + | 'new' + | 'partlyActive' + | 'activeWithInactiveVersion'; + description?: string; + descriptionTextLimit?: number; + language?: string; + masterSystem?: string; + masterLanguage?: string; + responsible?: string; + abapLanguageVersion?: string; + sourceUri?: string; + sourceObjectStatus?: + | 'SAPStandardProduction' + | 'customerProduction' + | 'system' + | 'test'; + fixPointArithmetic?: boolean; + activeUnicodeCheck?: boolean; + }; +}; diff --git a/packages/adt-schemas/src/schemas/generated/types/custom/dataelementWrapper.types.ts b/packages/adt-schemas/src/schemas/generated/types/custom/dataelementWrapper.types.ts new file mode 100644 index 00000000..edaae6a5 --- /dev/null +++ b/packages/adt-schemas/src/schemas/generated/types/custom/dataelementWrapper.types.ts @@ -0,0 +1,96 @@ +/** + * Auto-generated TypeScript interfaces from XSD + * DO NOT EDIT - Generated by ts-xsd codegen + * Source: xsd/custom/dataelementWrapper.xsd + * Mode: Flattened + */ + +export type DataelementWrapperSchema = { + wbobj: { + containerRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + adtTemplate?: { + adtProperty?: { + $value?: string; + key?: string; + }[]; + name?: string; + }; + packageRef?: { + extension?: unknown; + uri?: string; + parentUri?: string; + type?: string; + name?: string; + packageName?: string; + description?: string; + }; + dataElement?: { + typeKind: + | 'domain' + | 'predefinedAbapType' + | 'refToPredefinedAbapType' + | 'refToDictionaryType' + | 'refToClifType'; + typeName?: string; + dataType?: string; + dataTypeLength?: number; + dataTypeLengthEnabled?: boolean; + dataTypeDecimals?: number; + dataTypeDecimalsEnabled?: boolean; + shortFieldLabel?: string; + shortFieldLength?: number; + shortFieldMaxLength?: number; + mediumFieldLabel?: string; + mediumFieldLength?: number; + mediumFieldMaxLength?: number; + longFieldLabel?: string; + longFieldLength?: number; + longFieldMaxLength?: number; + headingFieldLabel?: string; + headingFieldLength?: number; + headingFieldMaxLength?: number; + searchHelp?: string; + searchHelpParameter?: string; + setGetParameter?: string; + defaultComponentName?: string; + deactivateInputHistory?: boolean; + changeDocument?: boolean; + leftToRightDirection?: boolean; + deactivateBIDIFiltering?: boolean; + documentationStatus?: + | 'required' + | 'notUsedInScreens' + | 'explainedByShortText' + | 'postponed'; + }; + name: string; + type: string; + changedBy?: string; + changedAt?: string; + createdAt?: string; + createdBy?: string; + version?: + | '' + | 'active' + | 'inactive' + | 'workingArea' + | 'new' + | 'partlyActive' + | 'activeWithInactiveVersion'; + description?: string; + descriptionTextLimit?: number; + language?: string; + masterSystem?: string; + masterLanguage?: string; + responsible?: string; + abapLanguageVersion?: string; + }; +}; diff --git a/packages/adt-schemas/ts-xsd.config.ts b/packages/adt-schemas/ts-xsd.config.ts index 2a70a337..9de4f9ed 100644 --- a/packages/adt-schemas/ts-xsd.config.ts +++ b/packages/adt-schemas/ts-xsd.config.ts @@ -66,6 +66,8 @@ const targetSchemas = [ 'sap/tabletype', 'sap/tablesettings', // Custom schemas + 'custom/dataelementWrapper', // DTEL wrapper (SAP wraps inner dtel:dataElement in blue:wbobj) + 'custom/blueSource', // TABL/Structure wrapper (SAP uses blue:blueSource extending AbapSourceMainObject) 'custom/atomExtended', 'custom/discovery', 'custom/http', From f16f5257036ac196352e04112075debf7be9e316 Mon Sep 17 00:00:00 2001 From: Petr Plenkov Date: Mon, 16 Mar 2026 11:27:38 +0100 Subject: [PATCH 3/5] feat(adt-contracts): split Accept vs Content-Type headers in crud() 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. --- .../src/adt/ddic/dataelements.ts | 2 ++ .../adt-contracts/src/adt/ddic/domains.ts | 2 ++ .../adt-contracts/src/adt/ddic/structures.ts | 3 +++ packages/adt-contracts/src/adt/ddic/tables.ts | 3 +++ .../adt-contracts/src/adt/ddic/tabletypes.ts | 2 ++ .../adt-contracts/src/adt/functions/groups.ts | 2 ++ packages/adt-contracts/src/adt/oo/classes.ts | 2 ++ .../adt-contracts/src/adt/oo/interfaces.ts | 2 ++ .../adt-contracts/src/adt/packages/index.ts | 2 ++ .../src/adt/programs/programs.ts | 2 ++ packages/adt-contracts/src/helpers/crud.ts | 14 +++++++----- .../adt-contracts/tests/contracts/oo.test.ts | 22 ++++++++++++++----- .../tests/contracts/packages.test.ts | 13 ++++++++--- 13 files changed, 57 insertions(+), 14 deletions(-) diff --git a/packages/adt-contracts/src/adt/ddic/dataelements.ts b/packages/adt-contracts/src/adt/ddic/dataelements.ts index 5b2938df..dca06e08 100644 --- a/packages/adt-contracts/src/adt/ddic/dataelements.ts +++ b/packages/adt-contracts/src/adt/ddic/dataelements.ts @@ -29,4 +29,6 @@ export const dataelementsContract = crud({ basePath: '/sap/bc/adt/ddic/dataelements', schema: dataelementWrapperSchema, contentType: 'application/vnd.sap.adt.dataelements.v2+xml', + accept: + 'application/vnd.sap.adt.dataelements.v2+xml, application/vnd.sap.adt.dataelements.v1+xml', }); diff --git a/packages/adt-contracts/src/adt/ddic/domains.ts b/packages/adt-contracts/src/adt/ddic/domains.ts index 260b7aad..3a22ea0b 100644 --- a/packages/adt-contracts/src/adt/ddic/domains.ts +++ b/packages/adt-contracts/src/adt/ddic/domains.ts @@ -20,4 +20,6 @@ export const domainsContract = crud({ basePath: '/sap/bc/adt/ddic/domains', schema: domainSchema, contentType: 'application/vnd.sap.adt.domains.v2+xml', + accept: + 'application/vnd.sap.adt.domains.v2+xml, application/vnd.sap.adt.domains.v1+xml', }); diff --git a/packages/adt-contracts/src/adt/ddic/structures.ts b/packages/adt-contracts/src/adt/ddic/structures.ts index 41e0471b..e2f42f7e 100644 --- a/packages/adt-contracts/src/adt/ddic/structures.ts +++ b/packages/adt-contracts/src/adt/ddic/structures.ts @@ -26,12 +26,15 @@ export type StructuresContract = typeof structuresContract; const basePath = '/sap/bc/adt/ddic/structures'; const contentType = 'application/vnd.sap.adt.structures.v2+xml'; +const accept = + 'application/vnd.sap.adt.structures.v2+xml, application/vnd.sap.adt.structures.v1+xml'; export const structuresContract = { ...crud({ basePath, schema: blueSourceSchema, contentType, + accept, }), objectstructure: (name: string, options?: { version?: string }) => diff --git a/packages/adt-contracts/src/adt/ddic/tables.ts b/packages/adt-contracts/src/adt/ddic/tables.ts index c133b72b..64dabd5f 100644 --- a/packages/adt-contracts/src/adt/ddic/tables.ts +++ b/packages/adt-contracts/src/adt/ddic/tables.ts @@ -26,12 +26,15 @@ export type TablesContract = typeof tablesContract; const basePath = '/sap/bc/adt/ddic/tables'; const contentType = 'application/vnd.sap.adt.tables.v2+xml'; +const accept = + 'application/vnd.sap.adt.blues.v1+xml, application/vnd.sap.adt.tables.v2+xml'; export const tablesContract = { ...crud({ basePath, schema: blueSourceSchema, contentType, + accept, }), objectstructure: (name: string, options?: { version?: string }) => diff --git a/packages/adt-contracts/src/adt/ddic/tabletypes.ts b/packages/adt-contracts/src/adt/ddic/tabletypes.ts index d68d95d7..317b0b8e 100644 --- a/packages/adt-contracts/src/adt/ddic/tabletypes.ts +++ b/packages/adt-contracts/src/adt/ddic/tabletypes.ts @@ -23,4 +23,6 @@ export const tabletypesContract = crud({ basePath: '/sap/bc/adt/ddic/tabletypes', schema: tabletypeSchema, contentType: 'application/vnd.sap.adt.tabletype.v1+xml', + accept: + 'application/vnd.sap.adt.tabletypes.v2+xml, application/vnd.sap.adt.tabletypes.v1+xml, application/vnd.sap.adt.blues.v1+xml', }); diff --git a/packages/adt-contracts/src/adt/functions/groups.ts b/packages/adt-contracts/src/adt/functions/groups.ts index 0d0e1be2..47ab1c77 100644 --- a/packages/adt-contracts/src/adt/functions/groups.ts +++ b/packages/adt-contracts/src/adt/functions/groups.ts @@ -30,6 +30,8 @@ export const functionGroupsContract = crud({ basePath: '/sap/bc/adt/functions/groups', schema: groups, contentType: 'application/vnd.sap.adt.functions.groups.v3+xml', + accept: + 'application/vnd.sap.adt.functions.groups.v2+xml, application/vnd.sap.adt.functions.groups.v1+xml', sources: ['main'] as const, }); diff --git a/packages/adt-contracts/src/adt/oo/classes.ts b/packages/adt-contracts/src/adt/oo/classes.ts index 1d17ab7e..a01a1fad 100644 --- a/packages/adt-contracts/src/adt/oo/classes.ts +++ b/packages/adt-contracts/src/adt/oo/classes.ts @@ -46,6 +46,8 @@ export const classesContract = crud({ basePath: '/sap/bc/adt/oo/classes', schema: classesSchema, contentType: 'application/vnd.sap.adt.oo.classes.v4+xml', + accept: + 'application/vnd.sap.adt.oo.classes.v4+xml, application/vnd.sap.adt.oo.classes.v3+xml, application/vnd.sap.adt.oo.classes.v2+xml, application/vnd.sap.adt.oo.classes.v1+xml', sources: ['main'] as const, includes: [ 'definitions', diff --git a/packages/adt-contracts/src/adt/oo/interfaces.ts b/packages/adt-contracts/src/adt/oo/interfaces.ts index 954a9c45..e10a7d32 100644 --- a/packages/adt-contracts/src/adt/oo/interfaces.ts +++ b/packages/adt-contracts/src/adt/oo/interfaces.ts @@ -36,6 +36,8 @@ export const interfacesContract = crud({ basePath: '/sap/bc/adt/oo/interfaces', schema: interfacesSchema, contentType: 'application/vnd.sap.adt.oo.interfaces.v5+xml', + accept: + 'application/vnd.sap.adt.oo.interfaces.v5+xml, application/vnd.sap.adt.oo.interfaces.v4+xml, application/vnd.sap.adt.oo.interfaces.v3+xml, application/vnd.sap.adt.oo.interfaces.v2+xml, application/vnd.sap.adt.oo.interfaces+xml', sources: ['main'] as const, }); diff --git a/packages/adt-contracts/src/adt/packages/index.ts b/packages/adt-contracts/src/adt/packages/index.ts index 53be607d..14faaa73 100644 --- a/packages/adt-contracts/src/adt/packages/index.ts +++ b/packages/adt-contracts/src/adt/packages/index.ts @@ -30,6 +30,8 @@ export const packagesContract = crud({ basePath: '/sap/bc/adt/packages', schema: packagesV1, contentType: 'application/vnd.sap.adt.packages.v2+xml', + accept: + 'application/vnd.sap.adt.packages.v2+xml, application/vnd.sap.adt.packages.v1+xml', nameTransform: (n) => encodeURIComponent(n), // preserve case (packages are uppercase) }); diff --git a/packages/adt-contracts/src/adt/programs/programs.ts b/packages/adt-contracts/src/adt/programs/programs.ts index b9831f7a..213bdefc 100644 --- a/packages/adt-contracts/src/adt/programs/programs.ts +++ b/packages/adt-contracts/src/adt/programs/programs.ts @@ -30,6 +30,8 @@ export const programsContract = crud({ basePath: '/sap/bc/adt/programs/programs', schema: programs, contentType: 'application/vnd.sap.adt.programs.programs.v2+xml', + accept: + 'application/vnd.sap.adt.programs.programs.v2+xml, application/vnd.sap.adt.programs.programs.v1+xml', sources: ['main'] as const, }); diff --git a/packages/adt-contracts/src/helpers/crud.ts b/packages/adt-contracts/src/helpers/crud.ts index 0f4b94f4..5b44c17d 100644 --- a/packages/adt-contracts/src/helpers/crud.ts +++ b/packages/adt-contracts/src/helpers/crud.ts @@ -77,8 +77,10 @@ export interface CrudOptions> { basePath: string; /** Schema for parsing/building XML */ schema: S; - /** Content-Type header value (e.g., 'application/vnd.sap.adt.oo.classes.v4+xml') */ + /** Content-Type header for POST/PUT (single specific version, e.g., 'application/vnd.sap.adt.oo.classes.v4+xml') */ contentType: string; + /** Accept header for GET (multi-version, newest first). Falls back to contentType if not provided. */ + accept?: string; /** Optional: Transform object name for URL (default: lowercase) */ nameTransform?: (name: string) => string; /** Optional: Source endpoints to generate (e.g., ['main']) */ @@ -317,6 +319,7 @@ export function crud< basePath, schema, contentType, + accept = contentType, nameTransform = (n) => n.toLowerCase(), sources, includes, @@ -330,7 +333,7 @@ export function crud< get: (name: string, queryOptions?: Pick) => http.get(`${basePath}/${nameTransform(name)}`, { responses: { 200: schema }, - headers: { Accept: contentType }, + headers: { Accept: accept }, query: queryOptions?.version ? { version: queryOptions.version } : undefined, @@ -345,7 +348,7 @@ export function crud< body: schema, responses: { 200: schema }, headers: { - Accept: contentType, + Accept: accept, 'Content-Type': contentType, }, query: queryOptions?.corrNr @@ -365,7 +368,7 @@ export function crud< body: schema, responses: { 200: schema }, headers: { - Accept: contentType, + Accept: accept, 'Content-Type': contentType, }, query: { @@ -526,6 +529,7 @@ export function repo>( schema: S, contentType: string, nameTransform?: (name: string) => string, + accept?: string, ): CrudContract { - return crud({ basePath, schema, contentType, nameTransform }); + return crud({ basePath, schema, contentType, accept, nameTransform }); } diff --git a/packages/adt-contracts/tests/contracts/oo.test.ts b/packages/adt-contracts/tests/contracts/oo.test.ts index d2fa7752..91187e63 100644 --- a/packages/adt-contracts/tests/contracts/oo.test.ts +++ b/packages/adt-contracts/tests/contracts/oo.test.ts @@ -21,7 +21,10 @@ class ClassesScenario extends ContractScenario { contract: () => ooContract.classes.get('ZCL_TEST'), method: 'GET', path: '/sap/bc/adt/oo/classes/zcl_test', - headers: { Accept: 'application/vnd.sap.adt.oo.classes.v4+xml' }, + headers: { + Accept: + 'application/vnd.sap.adt.oo.classes.v4+xml, application/vnd.sap.adt.oo.classes.v3+xml, application/vnd.sap.adt.oo.classes.v2+xml, application/vnd.sap.adt.oo.classes.v1+xml', + }, response: { status: 200, schema: classesSchema, @@ -34,7 +37,8 @@ class ClassesScenario extends ContractScenario { method: 'POST', path: '/sap/bc/adt/oo/classes', headers: { - Accept: 'application/vnd.sap.adt.oo.classes.v4+xml', + Accept: + 'application/vnd.sap.adt.oo.classes.v4+xml, application/vnd.sap.adt.oo.classes.v3+xml, application/vnd.sap.adt.oo.classes.v2+xml, application/vnd.sap.adt.oo.classes.v1+xml', 'Content-Type': 'application/vnd.sap.adt.oo.classes.v4+xml', }, body: { schema: classesSchema }, @@ -46,7 +50,8 @@ class ClassesScenario extends ContractScenario { method: 'PUT', path: '/sap/bc/adt/oo/classes/zcl_test', headers: { - Accept: 'application/vnd.sap.adt.oo.classes.v4+xml', + Accept: + 'application/vnd.sap.adt.oo.classes.v4+xml, application/vnd.sap.adt.oo.classes.v3+xml, application/vnd.sap.adt.oo.classes.v2+xml, application/vnd.sap.adt.oo.classes.v1+xml', 'Content-Type': 'application/vnd.sap.adt.oo.classes.v4+xml', }, body: { schema: classesSchema }, @@ -123,7 +128,10 @@ class InterfacesScenario extends ContractScenario { contract: () => ooContract.interfaces.get('ZIF_TEST'), method: 'GET', path: '/sap/bc/adt/oo/interfaces/zif_test', - headers: { Accept: 'application/vnd.sap.adt.oo.interfaces.v5+xml' }, + headers: { + Accept: + 'application/vnd.sap.adt.oo.interfaces.v5+xml, application/vnd.sap.adt.oo.interfaces.v4+xml, application/vnd.sap.adt.oo.interfaces.v3+xml, application/vnd.sap.adt.oo.interfaces.v2+xml, application/vnd.sap.adt.oo.interfaces+xml', + }, response: { status: 200, schema: interfacesSchema, @@ -135,7 +143,8 @@ class InterfacesScenario extends ContractScenario { method: 'POST', path: '/sap/bc/adt/oo/interfaces', headers: { - Accept: 'application/vnd.sap.adt.oo.interfaces.v5+xml', + Accept: + 'application/vnd.sap.adt.oo.interfaces.v5+xml, application/vnd.sap.adt.oo.interfaces.v4+xml, application/vnd.sap.adt.oo.interfaces.v3+xml, application/vnd.sap.adt.oo.interfaces.v2+xml, application/vnd.sap.adt.oo.interfaces+xml', 'Content-Type': 'application/vnd.sap.adt.oo.interfaces.v5+xml', }, body: { schema: interfacesSchema }, @@ -147,7 +156,8 @@ class InterfacesScenario extends ContractScenario { method: 'PUT', path: '/sap/bc/adt/oo/interfaces/zif_test', headers: { - Accept: 'application/vnd.sap.adt.oo.interfaces.v5+xml', + Accept: + 'application/vnd.sap.adt.oo.interfaces.v5+xml, application/vnd.sap.adt.oo.interfaces.v4+xml, application/vnd.sap.adt.oo.interfaces.v3+xml, application/vnd.sap.adt.oo.interfaces.v2+xml, application/vnd.sap.adt.oo.interfaces+xml', 'Content-Type': 'application/vnd.sap.adt.oo.interfaces.v5+xml', }, body: { schema: interfacesSchema }, diff --git a/packages/adt-contracts/tests/contracts/packages.test.ts b/packages/adt-contracts/tests/contracts/packages.test.ts index a6dfbcad..e1d5b32f 100644 --- a/packages/adt-contracts/tests/contracts/packages.test.ts +++ b/packages/adt-contracts/tests/contracts/packages.test.ts @@ -18,7 +18,10 @@ class PackagesScenario extends ContractScenario { contract: () => packagesContract.get('$TMP'), method: 'GET', path: '/sap/bc/adt/packages/%24TMP', // $ is URL-encoded - headers: { Accept: 'application/vnd.sap.adt.packages.v2+xml' }, + headers: { + Accept: + 'application/vnd.sap.adt.packages.v2+xml, application/vnd.sap.adt.packages.v1+xml', + }, response: { status: 200, schema: packagesV1, @@ -30,7 +33,10 @@ class PackagesScenario extends ContractScenario { contract: () => packagesContract.get('Z_MY_PACKAGE'), method: 'GET', path: '/sap/bc/adt/packages/Z_MY_PACKAGE', - headers: { Accept: 'application/vnd.sap.adt.packages.v2+xml' }, + headers: { + Accept: + 'application/vnd.sap.adt.packages.v2+xml, application/vnd.sap.adt.packages.v1+xml', + }, response: { status: 200, schema: packagesV1 }, }, { @@ -39,7 +45,8 @@ class PackagesScenario extends ContractScenario { method: 'POST', path: '/sap/bc/adt/packages', headers: { - Accept: 'application/vnd.sap.adt.packages.v2+xml', + Accept: + 'application/vnd.sap.adt.packages.v2+xml, application/vnd.sap.adt.packages.v1+xml', 'Content-Type': 'application/vnd.sap.adt.packages.v2+xml', }, query: { corrNr: 'DEVK900001' }, From 355c8388e66ba45d7de8a8a5041dfacb3da49581 Mon Sep 17 00:00:00 2001 From: Petr Plenkov Date: Mon, 16 Mar 2026 11:53:02 +0100 Subject: [PATCH 4/5] chore: update abapgit-examples submodule (DDIC examples + table rename) --- git_modules/abapgit-examples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_modules/abapgit-examples b/git_modules/abapgit-examples index d6cee132..74cd5bc8 160000 --- a/git_modules/abapgit-examples +++ b/git_modules/abapgit-examples @@ -1 +1 @@ -Subproject commit d6cee1321197ce77e195c5d047b9bb391adef4c7 +Subproject commit 74cd5bc854a8b4c83397881fd0ae66813b099b56 From 2816dd4be411112964b4f9486c81d1549c6f1e1d Mon Sep 17 00:00:00 2001 From: Petr Plenkov Date: Mon, 16 Mar 2026 12:56:42 +0100 Subject: [PATCH 5/5] fix(adk): address PR review findings - reset _unchanged, deduplicate 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 --- packages/adk/src/base/model.ts | 57 +++++++++---------- .../adt-contracts/src/adt/ddic/tabletypes.ts | 2 +- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/packages/adk/src/base/model.ts b/packages/adk/src/base/model.ts index 7193f4aa..18fcf40c 100644 --- a/packages/adk/src/base/model.ts +++ b/packages/adk/src/base/model.ts @@ -456,6 +456,9 @@ export abstract class AdkObject { async save(options: SaveOptions = {}): Promise { const { transport, mode = 'update' } = options; + // Reset per-save-attempt state + this._unchanged = false; + // Check if object has pending sources (from abapGit deserialization) const hasPendingSources = this.hasPendingSources(); @@ -478,18 +481,7 @@ export abstract class AdkObject { // 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)) { - try { - return await this.save({ ...options, mode: 'create' }); - } catch (createErr) { - // If POST fails with 422 "already exists", the object exists but - // the endpoint doesn't support lock/PUT (e.g., some DDIC types). - // Treat as unchanged — we can't update it via this mechanism. - if (this.isAlreadyExistsError(createErr)) { - this._unchanged = true; - return this; - } - throw createErr; - } + return this.fallbackToCreate(options); } throw e; } @@ -524,18 +516,7 @@ export abstract class AdkObject { } catch (e: unknown) { // For upsert with PUT failure (404/405), try POST (create) if (mode === 'upsert' && this.shouldFallbackToCreate(e)) { - try { - return await this.save({ ...options, mode: 'create' }); - } catch (createErr) { - // If POST fails with 422 "already exists", the object exists but - // the endpoint doesn't support PUT for updates (e.g., TABL). - // Treat as unchanged — we can't update it via this mechanism. - if (this.isAlreadyExistsError(createErr)) { - this._unchanged = true; - return this; - } - throw createErr; - } + return this.fallbackToCreate(options); } // If PUT returns 422 "already exists" directly in upsert mode if (mode === 'upsert' && this.isAlreadyExistsError(e)) { @@ -614,6 +595,22 @@ export abstract class AdkObject { return false; } + /** + * Upsert fallback: try POST (create), treat 422 "already exists" as unchanged. + * Extracted to avoid duplication in lock-catch and saveViaContract-catch paths. + */ + private async fallbackToCreate(options: SaveOptions): Promise { + try { + return await this.save({ ...options, mode: 'create' }); + } catch (createErr) { + if (this.isAlreadyExistsError(createErr)) { + this._unchanged = true; + return this; + } + throw createErr; + } + } + /** * Check if error suggests the object doesn't exist or the operation * is not supported for the current state. @@ -634,13 +631,11 @@ export abstract class AdkObject { * Check if error is a 422 "already exists" from a POST create attempt */ protected isAlreadyExistsError(e: unknown): boolean { - if (e instanceof Error) { - return ( - (e.message.includes('422') || e.message.includes('Unprocessable')) && - e.message.includes('already exists') - ); - } - return false; + const msg = e instanceof Error ? e.message.toLowerCase() : ''; + return ( + (msg.includes('422') || msg.includes('unprocessable')) && + msg.includes('already exists') + ); } /** diff --git a/packages/adt-contracts/src/adt/ddic/tabletypes.ts b/packages/adt-contracts/src/adt/ddic/tabletypes.ts index 317b0b8e..e7a5be9c 100644 --- a/packages/adt-contracts/src/adt/ddic/tabletypes.ts +++ b/packages/adt-contracts/src/adt/ddic/tabletypes.ts @@ -3,7 +3,7 @@ * * ADT endpoint: /sap/bc/adt/ddic/tabletypes * Content-Type: application/vnd.sap.adt.tabletype.v1+xml - * Object type: TTYP/DA (ttypda) + * Object type: TTYP/TT */ import { crud } from '../../helpers/crud';