Giter Site home page Giter Site logo

odata2ts / odata2ts Goto Github PK

View Code? Open in Web Editor NEW
70.0 4.0 8.0 4.28 MB

Flexible generator to produce various TypeScript artefacts (from simple model interfaces to complete odata clients) from OData metadata files

Home Page: https://odata2ts.github.io/

License: MIT License

JavaScript 0.02% TypeScript 99.98%
generator odata query-builder type-safe typescript

odata2ts's Introduction

GitHub Workflow Status Coveralls

odata2ts

If you use TypeScript and need to interact with an OData service, then odata2ts might be for you. It centers around the generation of TypeScript artefacts out of readily available metadata descriptions of given OData services.

With the help of odata2ts you can:

  • generate tailor-made TypeScript model interfaces for entities, complex types and what not
  • generate powerful q-objects to leverage the type-safe and fluent query builder
  • generate a full-fledged, domain-savvy OData client supporting type-safe queries, CRUD operations and more

Feature Highlights:

  • support for OData V2 and V4
  • generation of compiled JS / DTS or (prettified) TypeScript files
  • allows for handling multiple odata services
  • TypeScript based configuration file
  • powerful, type-safe and fluent query builder
  • use existing or own converters to interact with data types of your choice
  • allows for name mappings of attributes

The generated code artefacts can be used in Browser or Node.js environments.

Documentation

Getting Started

Main documentation for the odata2ts eco system: https://odata2ts.github.io

Examples

See example packages for examples of how to integrate odata2ts.

Support, Feedback, Contributing

This project is open to feature requests, suggestions, bug reports, usage questions etc. via GitHub issues.

Contributions and feedback are encouraged and always welcome.

See the contribution guidelines for further information.

Spirit

This project has been created and is maintained in the following spirit:

  • adhere to the OData specification as much as possible
    • support any OData service implementation which conforms to the spec
    • allow to work around faulty implementations if possible
  • stability matters
    • exercise Test Driven Development
    • bomb the place with unit tests (code coverage > 95%)
    • ensure that assumptions & understanding are correct by creating integration tests

License

MIT - see License.

odata2ts's People

Contributors

dependabot[bot] avatar involture avatar manuelseeger avatar texttechne avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

odata2ts's Issues

Issue building minimal example

I am trying to build a minimal exemple but I get an error.

I am pretty sure it is a configuration problem on my side and it has nothing to do with your library but I have don't know where to seek help except there.
Sorry in advance for opening an issue

package.json

{
  "devDependencies": {
    "@odata2ts/odata2ts": "^0.18.2",
    "typescript": "^4.9.4"
  },
  "dependencies": {
    "@odata2ts/axios-odata-client": "^0.5.1",
    "@types/node": "^18.11.18"
  },
  "scripts": {
    "gen-odata": "npx odata2ts -d -s .\\odataMeta\\odata_v2_meta.xml -o .\\gen\\odataV2"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es2016",
    "lib": ["esnext"],
    "module": "commonjs",
    "baseUrl": "./src", 
    "outDir": "./build",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true, 
    "strict": true, 
    "skipLibCheck": false
  }
}

src/main.ts

import { ODataService } from "../gen/odataV2/ODataService"
import { AxiosODataClient } from "@odata2ts/axios-odata-client"

const axiosClient = new AxiosODataClient()
const dataService = new ODataService(axiosClient, _SomeUrl_)

The objects are built correctly when I invoke npm run gen-odata
Invoking tsc does not raise any error and generate main.js in the build folder

but then invoking node.exe .\build\main.js raises the following error

Uncaught TypeError TypeError: odata_service_1.EntityServiceResolver is not a constructor
    at createEntityserviceResolver (c:\Users\mjanin\Documents\ProgramingProjects\node-api-tester\gen\odataV2\service\EntityService.js:52:12)
    at ODataService (c:\Users\mjanin\Documents\ProgramingProjects\node-api-tester\gen\odataV2\ODataService.js:255:71)
    at <anonymous> (c:\Users\mjanin\Documents\ProgramingProjects\node-api-tester\build\main.js:6:21)
    at Module._compile (internal/modules/cjs/loader:1159:14)
    at Module._extensions..js (internal/modules/cjs/loader:1213:10)
    at Module.load (internal/modules/cjs/loader:1037:32)
    at Module._load (internal/modules/cjs/loader:878:12)
    at executeUserEntryPoint (internal/modules/run_main:82:12)
    at <anonymous> (internal/main/run_main_module:23:47)

Any help is appreciated,
Thank for your work, this package looks dope

Generate Individual OData-Service Client

We know all there is to know about the given OData-Service.
We should exploit that knowledge to generate an indivual odata client along these lines:

// EntitySets
TrippinService.People.query((builder, qPerson) => builder.filter(qPerson.age.gt(18)))
TrippinService.People.update(id: string, data): void

// Functions & Actions
TrippinService.getNearestAirport(....): Array<AirportModel>

Handling of Large Numbers (IEEE754 Compatible)

The following two data types of OData are 64-bit binary format IEEE 754 values, which might actually not fit into the JS number type:

  • Edm.Int64
  • Edm.Decimal

While V2 represents these numbers as string (among many other number types), V4 represents all number types as number. V4 has a special format setting to handle both mentioned number types as strings:

  • the format value is always application/json;IEEE754Compatible=true
  • querying / retrieving IEEE 754 compatible numbers
  • sending IEEE 754 compatible numbers (POST, PUT, PATCH)
    • see Spec: Header Content Type
    • set Content-Type header (of the request) to the format
    • the Content-Type header of the response will have the format, if not empty

Tasks

  • generator: add global configuration option so that all requests use the IEEE 754 format by default
  • generator: map Edm.Int64 and Edm.Decimal to string when generating models
  • extend http-client-api and http-client-base which set the correct headers
    • approach: setter on http client which is called by each service
    • one call to the setter would be enough, but then it works for usage with individual services not instantiated by main service
  • generator: when generating services, make use of new http client setter
  • V4 only: add format option to query builder so that individual resquests may or may not use the IEEE 754 format
  • Converter Int64 to BigInt
    • common converter package

Issue when building with vite

Current behavior

For a vue 3 app built with vite, the application fails to start

image

Which brings me to the QBooleanParam definition from @odata2ts/odata-query-objects

image

However, it works when serving the application.

Disclaimer: This may not be related to this project but instead with vite build configuration. But I haven't found a way to make it work for a production build

Possible area of investigation?

  • An issue with how the npm package is built?
  • A circular dependency?

OData v2 FunctionImport HTTP Method incorrect

Example:

        <FunctionImport Name="Logon" ReturnType="cds_zscm_bin2bin.DummyFunctionImportResult"
                        m:HttpMethod="POST"/>

Output:

  public logon(): ODataResponse<ODataModelResponseV2<DummyFunctionImportResultModel>> {
    const url = compileFunctionPathV2(this.getPath(), "Logon");
    return this.client.get(url);
  }

Expected:

  public logon(): ODataResponse<ODataModelResponseV2<DummyFunctionImportResultModel>> {
    const url = compileFunctionPathV2(this.getPath(), "Logon");
    return this.client.post(url);
  }

Documentation Page

Requirements

  • hosted via Github-Pages
  • build and deployed by Github Actions
  • easy-to-use, react based static site generator
  • suited for technical documentation
  • markdown support

Main Tasks

  • base setup: docusaurus
  • deployment via github actions
  • main doc migration
  • README updates of main repos

Content

  • Getting Started
  • Generator
  • Query Builder
  • Services
  • Converter
  • OData Client incl. Axios-OData-Client
  • Q-Objects

Bug: JS Generation Broken

Generating stuff with js-dts works, but throws errors when used.
More investigation needed, but tsconfig compiler settings seem to be ignored.

UriBuilder: add in statement

Actually the in operator doesn't seem to be supported by Northwind or CAP.
However, it is useful & concise. Hence we implement it by mapping it to a concatenation of or-expressions.

Improved Release Process

We currently use an old Lerna version.
Also, the release process is quite inflexible.
Last but not least Lerna is probably not able to handle our dependency requirements.

Challenge: odata2ts Deps

As generator odata2ts will produce code artefacts that rely on our libs

  • odata-query-objects
  • odata-api-client
  • odata-query-builder
  • odata-service

However, these are not direct dependencies of the odata2ts module, but it's nonetheless crucial to keep the modules aligned with each other. The current solution is to use peer deps. However, Lerna is not auto updating peer deps and any manual config should be avoided.

And don't forget: odata2ts is a dev dep, while odata-service or the like would be the runtime dep.

Task

Refactor the release process.

OData V2 compileFunctionPathV2 exclude null from query parameter

Nullable Parameters in FunctionImport of OData v2 shouldn't be included in the query string, if the value is null.

see https://www.odata.org/documentation/odata-version-2-0/uri-conventions/

  1. Service Operation Parameters
    Service Operations represent functions exposed by an OData service. These functions may accept zero or more primitive type parameters. If a Service Operation requires an input parameter those parameters are passed via query string name/value pairs appended to the URI which identify the Service Operation as described in the Addressing Service Operations section. For nullable type parameters, a null value may be specified by not including the parameter in the query string of the URI.

Example FunctionImport:

        <FunctionImport Name="Dropoff_HU" ReturnType="cds_zscm_bin2bin.HUType" EntitySet="HU"
                        m:HttpMethod="POST" sap:action-for="cds_zscm_bin2bin.HUType">
          <Parameter Name="Warehouse" Type="Edm.String" Mode="In" MaxLength="4"/>
          <Parameter Name="HUIdent" Type="Edm.String" Mode="In" MaxLength="20"/>
          <Parameter Name="Destination" Type="Edm.String" Mode="In" MaxLength="20" Nullable="true"/>
          <Parameter Name="Day_Location" Type="Edm.String" Mode="In" MaxLength="15" Nullable="true"/>
        </FunctionImport>

Providing null for Destination or Day_Location should not include the property in the url.

[Enhancement][AxiosOdataClient]Better error reporting

I have been using the provided axiosOdataClient as a client for odata2ts.
I think it would be a good idea to keep the original axios error. It is necessary for accessing the request data for example.

In general, making use of the new standard cause field (as of ES 2022) of the Error interface seems to be a good idea.
However the target of AxiosODataClient seems to be ES 2016 (sensible choice). A polyfill lib could be added such as this one, or a cause field could be added to the AxiosOdataClientError class.

It would make the type RequestError redondant

Also my understanding is that a proper name for the error is better for error reporting.

My suggestion for modifying the convertError code
DISCLAMER: it did not check/test this code, its merely an idea

export class AxiosODataClientError extends Error {
  name: "AxiosODataClientError"
  isAxiosOdataClientError: true
  constructor(msg: string, options?: {cause?: any}): {
    super(msg, options)
  }
}

private convertError(error: Error): Error {
  if ((error as AxiosError).isAxiosError) {
    const axiosError: AxiosError = error as AxiosError;

    const message = this.getErrorMessage(axiosError);

    if (message) {
      return new AxiosODataClientError("OData server error: " + message, { cause: error })
    } else {
      return new AxiosODataClientError("Axios error", { cause: error })
    }
  }
  return new AxiosODataClientError("Internal Error", { cause: error });
}

I am not sure I understand error management good practices properly, but I am sure it would be nice to access the resquest and response fields of the AxiosError

@texttechne What do you think ?

Source maps in distributed packages

Problem
All the packages of @odata2ts group seem to be published with source maps files referencing typescript sources. Once distributed, these source maps points to non-existant files. It can raise some warnings in some build systems.

Solution
One of the three following options would work

  • Do not distribute source maps with the packages
  • Inline the typescript sources in the sourcemaps files using the inlineSources tsc option
  • Distribute the src directory containing the typescript files with the packages

Model with entity sets before entity types not parsing

Some models contains two schema in their DataServices tag : one containing the entity container and the entity sets and the other one containing the entity types.
One such model is Northwind V2.

Moreover, some models (for example some from the sap world) have the entity set schema before the entity types one.

To illustrate this, one can find below the metadata file of Northwind v2 with the two schema swapped.
northwindv2metadataReordered.zip

The resulting file structure is the following

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" class="TridactylThemeDark">
  <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="1.0">
    <Schema Namespace="ODataWeb.Northwind.Model" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
      <EntityContainer Name="NorthwindEntities" p7:LazyLoadingEnabled="true" m:IsDefaultEntityContainer="true" xmlns:p7="http://schemas.microsoft.com/ado/2009/02/edm/annotation">
        <EntitySet Name="Categories" EntityType="NorthwindModel.Category"/>
           ...
      </EntityContainer>
    </Schema>
    <Schema Namespace="NorthwindModel" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
      <EntityType Name="Category">
        <Key>
          <PropertyRef Name="CategoryID"/>
        </Key>
        <Property Name="CategoryID" Type="Edm.Int32" Nullable="false" p8:StoreGeneratedPattern="Identity" xmlns:p8="http://schemas.microsoft.com/ado/2009/02/edm/annotation"/>
        <Property Name="CategoryName" Type="Edm.String" Nullable="false" MaxLength="15" Unicode="true" FixedLength="false"/>
        <Property Name="Description" Type="Edm.String" Nullable="true" MaxLength="Max" Unicode="true" FixedLength="false"/>
        <Property Name="Picture" Type="Edm.Binary" Nullable="true" MaxLength="Max" FixedLength="false"/>
        <NavigationProperty Name="Products" Relationship="NorthwindModel.FK_Products_Categories" FromRole="Categories" ToRole="Products"/>
      </EntityType>
      ...
    </Schema>
  </edmx:DataServices><iframe xmlns="http://www.w3.org/1999/xhtml" class="cleanslate hidden" src="$metadata_fichiers/commandline.htm" id="cmdline_iframe" loading="lazy" style="height: 0px !important;"></iframe>
</edmx:Edmx>

When trying to generate artifactsodata2ts fails with the following error :

Starting generation process
Reading file: $metadata.xml
Error while running the program Error: Association end couldn't be determined for NavigationProperty [Products]
    at C:\Users\Marti\Documents\Odata2ts\node_modules\@odata2ts\odata2ts\src\data-model\DataModelDigestionV2.ts:44:17
    at Array.map (<anonymous>)
    at DigesterV3.getNavigationProps (C:\Users\Marti\Documents\Odata2ts\node_modules\@odata2ts\odata2ts\src\data-model\DataModelDigestionV2.ts:39:36)
    at DigesterV3.getBaseModel (C:\Users\Marti\Documents\Odata2ts\node_modules\@odata2ts\odata2ts\src\data-model\DataModelDigestion.ts:98:55)        
    at DigesterV3.addEntityType (C:\Users\Marti\Documents\Odata2ts\node_modules\@odata2ts\odata2ts\src\data-model\DataModelDigestion.ts:134:30)      
    at DigesterV3.digestSchema (C:\Users\Marti\Documents\Odata2ts\node_modules\@odata2ts\odata2ts\src\data-model\DataModelDigestion.ts:82:10)        
    at DigesterV3.<anonymous> (C:\Users\Marti\Documents\Odata2ts\node_modules\@odata2ts\odata2ts\src\data-model\DataModelDigestion.ts:51:10)
    at Generator.next (<anonymous>)
    at C:\Users\Marti\Documents\Odata2ts\node_modules\tslib\tslib.js:167:75
    at new Promise (<anonymous>)

Way to replace the Id function without changing generated code?

I have an issue with the Ids generated by odata2ts for the SAP CPI management OData API:

https://api.sap.com/api/IntegrationContent/path/get_IntegrationDesigntimeArtifacts_Id___Id___Version___Version___

The problem:
Resources require composite keys:
/IntegrationDesigntimeArtifacts(Id='{Id}',Version='{Version}'

// design time error since Version is not a key in metadata
const keycomplex = {
  Id: iFlowId,
  Version: "active",
};

// runtime/API error since string will get URL encoded and put in single quotes
const keysimple = `Id='${iFlowId}',Version='active'`;

const patchResult = await cpi
  .navToIntegrationDesigntimeArtifacts()
  .patch(keysimple, iFlowContent);

I realize the problem here is that the API is inconsistent: Some resources require keys which are not specified as keys in the metadata. I looked into replacing the Id function of the service to account for that, but have not found a way which doesn't require changing the generated code. Is there something I can do here?

I also realize this isn't exactly an issue with odata2ts - is there a development community or some place where I could raise questions like this?

Support for OData V2

Hello,
Found this repo after asking on twitter. I work in the SAP ecosystem and recently the official UI framework SAPUI5 is moving to typescript. Since SAPUI5 relies heavily on OData both V2 and V4, I thought this could be a great tool for helping in that journey. However when I tested it, I saw that OData V2 can't be typed currently.

Before I get working on this, I wanted to ask if you are interested in getting this support. I suppose an additional parameter in the CLI --v2 or something to indicate it's a v2 model.

Add support to create & parse identifers

UI5 provides the method createKey to generate the identifer for an collection item. It would be nice have this method also in odata2ts, because this simplifies routing in your app with composite keys.

Example Entity HU with Keys Warehouse and HUIdent:

  1. createKey
model.createKey({
  Warehouse: "3001",
  HUIdent: "340077640010000622"
})

// Result:
// HU(Warehouse='3001',HUIdent='340077640010000622')
  1. parseKey
model.parseKey("HU(Warehouse='3001',HUIdent='340077640010000622')")

// Result:
{
  Warehouse: "3001",
  HUIdent: "340077640010000622"
}

The Big Renaming

Module Renaming

The generator itself should be named odata2ts instead of odata2model.
Stick to the name of the game :-)

Also, we're really building queries with "uri-builder".

  • rename odata2model => odata2ts
    • move source
    • rename main command
  • deprecate odata2model (updated docs)
  • rename odata-uri-builder => odata-query-builder
  • deprecate odata-uri-builder (updated docs)

[Feature Request] Expand ODataClient to support Binary Data

Currently, odata2ts generates a string type property in place of Binary Data (CDS type LargeBinary). This should be resolved to BLOB instead.
Also, e.g. PUT requests on a "content" property of an Entity (containing the Binary Data) should also be possible as it's supported by OData itself.

Possibility to ignore non-existing nav properties

Hi,

we have a metadata file containing a lot of entities (XML file > 10mb).
We have the possibility to generate a metadata only containing the Entities we are interested in.
With this metadata, the tool will crash, as there are nav properties to entities that don't exist in the XML.

Would it be possible to add an option to ignore those nav properties and just remove the form of the API?

Weird output path

When I have a config like

const config: ConfigFileOptions = {
  mode: Modes.models,
  emitMode: EmitModes.dts,
  services: {
    trippin: {
      source: 'app/src/localService/metadataBP.xml',
      output: 'app/src/localService/types/metadataBP',
    },
  },
};

the final path of the dTs file is:

app/src/localService/metadataBP/app/src/localService/metadataBP/BusinessPartnersServiceModel.d.ts

Is there a way to get rid of the second app/... ?

Support Binary Type

Improve QBinaryPath: binary'xxxx' or X'xxx'.
We take the former version, because it's more explicit)

Also: API calls require some special configuration regarding HTTP headers.

[Feature Request]: Provide Setting to Use Types With Extra Result Wrapping

As mentioned in #125, OData V2 services provided by SAP systems wrap expanded navigation properties in an extra object containing a results array.

While the workaround works for the generated client, the generated typings do not represent the SAP style.

Please add a setting for the generated typings instead of the service as suggested as initial workaround in #125.

Perfectionism regarding Handling of Number Types

Basically, only one QNumberPath is used for handling all different number types.

Currently known Flaws

  • only Double and Decimal should allow for round, ceiling and floor functions

Challenges

Changing from one QNumber type to several opens a can of worms:

  • understanding of casting rules becomes relevant
    • adding Edm.Byte and Edm.Int16 => Should be Edm.Int16?
    • what if we add value directly => Edm.Byte add 16.1
    • differences: v2 vs v4?
  • testability is limited
    • RAP & CAP don't support arithmetic operators
  • V4 only: casting would be relevant

Add service base path to query objects

The service name serves as base path for any odata requests. Include it in query objects & exploit it in the query builder.

Also add a configuration to the query builder to ignore the base path.

Explicit property renaming does not seem to work

I have the following configfileoptions:

{
  allowRenaming: true,
  prettier: true, 
  mode: Modes.service,
  emitMode: EmitModes.ts,
  services: {
    cpi: {
      source: "assets/CPI$metadata.xml",
      output: "src/generated/cpi",
      propertiesByName: [
        {
          name: "FromId_",
          mappedName: "fromId_"
        }
      ]
    }
  },
}

But it appears the property by name setting is not applied. Source Metadata:

            <EntityType Name="IdMapToId">
                <Key>
                    <PropertyRef Name="ToId" />
                </Key>
                <Property Name="ToId" Type="Edm.String" Nullable="false" />
                <Property Name="FromId_" Type="Edm.String" Nullable="true" />
                <Property Name="Mapper" Type="Edm.String" Nullable="true" />
                <Property Name="ExpirationTime" Type="Edm.DateTimeOffset" Nullable="true" />
                <Property Name="Qualifier" Type="Edm.String" Nullable="true" />
                <Property Name="Context" Type="Edm.String" Nullable="true" />
                <NavigationProperty Name="FromId" Relationship="com.sap.hci.api.ToIdsForFromId"
                    FromRole="r_ToIds" ToRole="r_FromId" />
                <NavigationProperty Name="FromId2s" Relationship="com.sap.hci.api.ToIdsForFromId2"
                    FromRole="r_ToId" ToRole="r_FromId2s" />
            </EntityType>

In the generated TS:

export interface IdMapToId {
  toId: string;
  fromId: string | null;
  mapper: string | null;
  expirationTime: string | null;
  qualifier: string | null;
  context: string | null;
  fromId: IdMapFromId | null | DeferredContent;
  fromId2s: Array<IdMapFromId2> | DeferredContent;
}

(The renaming strategy produces duplicate identifiers by stripping the trailing underscore from Property "FromId_", which is why I wanted to explicitly set the name of this property)

Am I missing something?

Full metadata: https://api.sap.com/api/IntegrationContent/overview

Changing Default Regarding Renaming of Props and Models

Currently, odata2model renames properties and models according to the default naming strategies (pascal case for models / classes and camel case for props / params). However, this feature should be opt-in, so that the generated client API mirrors the server API as far as possible by default.

V2: Expanded Collections with Extra Result Wrapping

In some V2 services, at least in the SAP universe, expanded collection properties responses are represented by a JSON array encapsulated in an addtional results object (@Involture brought this up in #119).

Borrowing from the Trippin service, for a Person with selected and expanded Trips it would look like:

{
  "d": {
    "trips": {
      "results": [
        ...
      ]
    } 
  }
}

Instead of being the results array itself, trips is an object with the results property which holds the results array.

The spec actually has an example for that: JSON-Format, chap. 9. Hence, it seems to be plainly wrong, as @Involture pointed out. It's nevertheless an use case if some SAP services behave this way.

Solution Approach

First and foremost we need a workaround which removes the extra wrapping, so that the generated types are correct.

If useful, provide an additional service setting which respects this extra wrapping.
Setting name: v2ExtraResultsWrapping
Setting type: boolean (default: false)

Handle Managed Props

The entity types that are declared in the metadata define the properties of each attribute, e.g. its type or if its required or optional. However, regarding the typical CRUD operations we're missing one central piece of information: Is an attribute managed / generated on the server side?

When a prop is managed, the client may not edit this property and so it should not occur in the editable version of the generated model interface of the respective entity. The following operations are affected: Create, Update, Patch.

Typical use cases for managed props:

  • Id field
  • createdAt, createdBy, modifiedAt, modifiedBy

Tasks

  • provide property configuration which allows to set managed flag
  • generation process for EditableModel respects managed flag
  • automatically set managed flags
    • logic: single key = managed, composite key = unmanaged
    • provide configuration to disable automatism

Notes

Services may add annotations to promote this information. But since this information is service specific then, it is out-of-scope for the current issue.

Support Value Converters

Value converters should implement something like the following interface:

interface Converter<Original, Converted> {
  convertFromOData(value: Original): Converted
  convertToOData(value: Converted): Original
}

Value converters enable us to convert values used in URLs as well as JSON values used in request / response bodies to different types. They would be especially useful for handling V2 values like /Date(TIMESTAMP)/ by converting those values into something more useful like ISO 8601 strings or Date objects.

Users should have the possibilities to write their own converters and plug them into the generation process

  • QPath and QParam must support converters
  • generator config allows to specify converters by type or per field
  • model generator respects converters
  • q-object generator injects converters
  • service generator respects converters

OData Actions invalid typing

The generated property names for action paramters seems to be wrong in my case, because the original casing was transformed to lowercase.

Metadata definition:

<Action Name="Reverse" IsBound="true">
  <Parameter Name="_it" Type="com.sap.gateway.srvd_a2x.zscm_reversal.v0001.ConsumptionType"
             Nullable="false"/>
  <Parameter Name="Quantity" Type="Edm.Decimal" Nullable="false" Precision="31" Scale="14"/>
  <Parameter Name="Unit" Type="Edm.String" Nullable="false" MaxLength="3"/>
</Action>

Generated action method

public reverse(params: { quantity: number; unit: string }): ODataResponse<ODataModelResponseV4<void>> {
  const url = compileActionPath(this.getPath(), "com.sap.gateway.srvd_a2x.zscm_reversal.v0001.Reverse");
  return this.client.post(url, params);
}

Expected action method:

public reverse(params: { Quantity: number; Unit: string }): ODataResponse<ODataModelResponseV4<void>> {
  const url = compileActionPath(this.getPath(), "com.sap.gateway.srvd_a2x.zscm_reversal.v0001.Reverse");
  return this.client.post(url, params);
}

The Wholy Grail: Query Dependent Response Data Types

One central feature about OData is it's capability to allow the client to determine the data shape of the response. The two main operations here are $selecting properties of an entity and $expanding associated entities - the properties of which then can selected and / or expanded again and ad inifinitum.

Currently, odata2ts types query responses in a generic way:

  • Entities are always typed with all required and optional properties
    • wrong: properties might not be present, because not selected
  • Associated entities are typed to be undefined or present (again in their entirety)
    • wrong: if not expanded, property is missing in response
    • to check: response type if expanded and no value?

Although this information is present by virtue of the written query, the query builder only produces an URL string and has no connection to the response data shape it requested.

Tasks

  • provide way to supply typing manually
    • like `navToBook().query<Pick<Book, "author" | "title">>(...)
    • escape hatch or only a sub selection / type narrowing allowed ?
    • only intended as workaround
  • do typing magic: make query builder methods pass the state of data shape back to the user
  • do typing magic again: let odata service methods retrieve the state of the QB and expose it as return type

[Enhancement suggestion] Better managed property detection by checking annotations

Managed properties are automatically detected and excluded from EditableXY types, but this detection is so far very basic, only covering single-key fields, as described at https://odata2ts.github.io/docs/generator/managed-props

At least in SAP environments with CDS, the detection could be improved by looking at the annotations. E.g. the properties "createdAt" "modifiedBy" etc. for which the manual exclusion is demonstrated in the documentation could all be automatically detected as being managed and unchangeable by checking whether the "Core.Computed" annotation is "true". See https://cap.cloud.sap/docs/guides/providing-services#readonly

Further annotations might also help, like "Core.Readonly" and "Core.Immutable" (only for updates - this would require a differentioation of the editable types between creation and updates), but at least they are not present in the most basic example services.

[@odata2ts/odata2model] Cannot Generate Types for Functions with Collection Parameters

Bug report

Custom functions associated types are missing from the generated "Qxxx" file and the "xxxModel" file, even though they are references by the generated service (class).
It is then used in the resource path of URL components for odata v4.

Here is an extract of the xml file:

<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
  <edmx:DataServices>
    <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="XEntities">
      [...]
    </Schema>
    <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Default">
        <Function Name="FacetFilter" IsBound="true">
          <Parameter Name="bindingParameter" Type="Collection(XEntities.AEntity)" />
          <Parameter Name="themeIds" Type="Collection(Edm.Guid)" Nullable="false">
            <Annotation Term="Org.OData.Core.V1.OptionalParameter" />
          </Parameter>
          <Parameter Name="categoryIds" Type="Collection(Edm.Guid)" Nullable="false">
            <Annotation Term="Org.OData.Core.V1.OptionalParameter" />
          </Parameter>
          <ReturnType Type="Collection(XEntities.AEntity)" />
        </Function>
        <Function Name="YYYFilter" IsBound="true">
          <Parameter Name="bindingParameter" Type="Collection(XEntities.AEntity)" />
          <Parameter Name="themeIds" Type="Collection(Edm.Guid)" Nullable="false">
            <Annotation Term="Org.OData.Core.V1.OptionalParameter" />
          </Parameter>
          <Parameter Name="categoryIds" Type="Collection(Edm.Guid)" Nullable="false">
            <Annotation Term="Org.OData.Core.V1.OptionalParameter" />
          </Parameter>
          <ReturnType Type="Collection(XEntities.YYYFilterEntity)" />
        </Function>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

In the called URI it should look like: .../aentities/FacetFilter(themeIds=[],%20categoryIds=[])?$skip=0&$top=30

Fix: Support Number Value Suffixes in URLs (V2 Only)

While V4 is consistent in its usage of data types, V2 is a mess:

  • we need to differentiate betwenn values used in the URL and values used in JSON request or response bodies
  • some numbers require a special value suffix (well, sometimes ๐Ÿ˜„) when used in URLs

The last point is the reason for this ticket and must be fixed.
For V2 we are missing type suffixes for the the following EDM types:

  • Decimal => 12.03M
  • Double => 2.02d
  • Single => 2.02f
  • Int64 => 64L

The spec clearly states, that the listed number types MUST be suffixed when used in URLs, which would affect

  • filter queries
  • function params (represented as query params)

All web services that I've tested support number values with and without the suffix in the filter query.

However, function params require the type suffix - at least RAP requires this.

So the task should be to support these number suffixes in all URL params, within filter queries and function params.
For now, it is sufficient that the type suffixes are automatically added in the case of function params.

Reference

OData V2 Spec: Chapter 6

Can't run odata2ts CLI within package type module (require() of ES modules is not supported.)

When trying to run the generator CLI in a package of "type": "module" I am getting the following error:

> node node_modules\@odata2ts\odata2ts\lib\run-cli.js
Bad arguments! TypeScriptLoader failed to compile TypeScript:
Must use import to load ES Module: .\odata2ts.config.ts
require() of ES modules is not supported.
require() of .\odata2ts.config.ts from .\node_modules\cosmiconfig-typescript-loader\dist\cjs\index.js is an ES module file as it is a .ts file whose nearest parent package.json contains "type": "module" which defines all .ts files in that package scope as ES modules.
Instead change the requiring code to use import(), or remove "type": "module" from .\package.json.

It appears that this is a limitation of cosmiconfic-typescript-loader. See cosmiconfig/cosmiconfig#283

Not sure if odata2ts can work around this. In any case leaving the issue here so it can be solved either by an updated cosmiconfig or a workaround.

Improved Number Handling

Current Flaws:

  • V2: QParam must use strings for most number types (everything except int16 & int32)
  • V2: QPath should allow for strings for most number types (everything except int16 & int32)
  • V2: divby doesnt exist in V2

Contributing : Questions about the developpement environment

I am trying to understand the build and test process of the repo.
After a fresh clone I run

> yarn install
> npm run buil
> npm run int-test

at the root and all the tests fail.
Below is a sample of the errors

--snip--
FAIL int-test/OperationFunctionality.test.ts (8.935 s)
  โ— CAP V4 Integration Testing: Operation Capabilities โ€บ string returning function with param
    connect ECONNREFUSED 127.0.0.1:4004
  โ— CAP V4 Integration Testing: Operation Capabilities โ€บ string collection returning function
    connect ECONNREFUSED 127.0.0.1:4004
  โ— CAP V4 Integration Testing: Operation Capabilities โ€บ entity type returning function
    connect ECONNREFUSED 127.0.0.1:4004
  โ— CAP V4 Integration Testing: Operation Capabilities โ€บ entity collection returning function
    connect ECONNREFUSED 127.0.0.1:4004
FAIL int-test/QueryFunctionality.test.ts (8.973 s)
  โ— CAP V4 Integration Testing: Query Capabilities โ€บ list books with count
    connect ECONNREFUSED 127.0.0.1:4004
  โ— CAP V4 Integration Testing: Query Capabilities โ€บ get book zero
    connect ECONNREFUSED 127.0.0.1:4004
--snip--

If I run examples/bookshop > npm run start they all pass.

Maybe thats why the script is lifted in the global package.json under the name start-cap ?

Is the complete build/test process supposed to be :

> yarn install
> npm run build
> npm run test
> npm run start-cap
> npm run int-test

?

Common Data Model for Test Server Implementations

All public available OData services are more or less buggy and don't encompass all the OData functionality. Hence, it would be nice to have custom test server implementations. The following technologies come to mind:

  • C# / AspNet (we can borrow from magic-odata here)
  • CAP (typical bookshop example already exists in example folder)
  • Olingo

Shortly discussed with @ShaneGH the need to provide a common data model which is suitable to test as many OData features as possible. And this is what this ticket is about: Defining this common data model.

@ShaneGH you already provided the first step in your test implementation with the legendary OneOfEverything model, so that all data types are testable (Edm.Binary and Edm.Stream are special cases for me outside of that model). More thoughts and ideas?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.