Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

max call size stack error when trying to update entity after getByKey #3930

Closed
lincheney opened this issue Jun 13, 2023 · 7 comments
Closed
Labels
bug Something isn't working

Comments

@lincheney
Copy link

Describe the bug
A clear and concise description of what the bug is.

I'm trying to update entity according to the documentation here: https://sap.github.io/cloud-sdk/docs/js/features/odata/v2-client#updaterequestbuilder

The code I have looks like:

const entity = await api.requestBuilder().getByKey('12345').execute(destination);
entity.key = 'value';
await api.requestBuilder().update(entity).execute(destination);

But when the code runs, the following error is thrown (along with warnings):

[2023-06-13T09:01:57.440Z] WARN     (entity-serializer): Could not serialize value for unknown field: undefined. Skipping field.
RangeError: Maximum call stack size exceeded
    at /path/node_modules/@sap-cloud-sdk/util/src/equal.ts:16:17
    at Array.every (<anonymous>)
    at equalObjects (/path/node_modules/@sap-cloud-sdk/util/src/equal.ts:16:11)
    at equal (/path/node_modules/@sap-cloud-sdk/util/src/equal.ts:39:12)
    at /path/node_modules/@sap-cloud-sdk/util/src/equal.ts:16:24
    at Array.every (<anonymous>)
    at equalObjects (/path/node_modules/@sap-cloud-sdk/util/src/equal.ts:16:11)
    at equal (/path/node_modules/@sap-cloud-sdk/util/src/equal.ts:39:12)
    at /path/node_modules/@sap-cloud-sdk/util/src/equal.ts:16:24
    at Array.every (<anonymous>)

I did a bit of debugging and found it is trying to check the entity._entityApi if it has changed and therefore should be included in the request payload, but it is getting stuck recursing through it.

To Reproduce
Steps to reproduce the behavior:

See above.

  1. Set up ...
  2. Execute ...
  3. Confirm ...
  4. See error

Expected behavior
A clear and concise description of what you expected to happen.

The update call should work.

Screenshots
If applicable, add screenshots to help explain your problem.

Used Versions:

  • node version via node -v - v18.16.0
  • npm version via npm -v - 9.5.1
  • SAP Cloud SDK version you used as dependency - 3.2.0

Code Examples
If applicable, add code snippets as examples to help explain your problem. Please remove sensitive information.

See above

Log file
If applicable, add your log file or related error message. Again, please remove your sensitive information.

Impact / Priority

Affected development phase: e.g. Getting Started, Development, Release, Production

Impact: e.g. No Impact, Inconvenience, Impaired, Blocked

Timeline: e.g. Go-Live is in 12 weeks.

Additional context
Add any other context about the problem here.

@lincheney lincheney added the bug Something isn't working label Jun 13, 2023
@deekshas8
Copy link
Contributor

Hi @lincheney,

Could you please share the edmx specification file and the payload you got from executing the getByKey()? If you downloaded it from the SAP Business Accelerator Hub, you can also share the service name.

@lincheney
Copy link
Author

Hello

I am getting this error when I am getting+updating maintenance plans and maintenance items.
https://api.sap.com/api/API_MAINTENANCEPLAN/resource/Maintenance_Plan
https://api.sap.com/api/API_MAINTENANCEPLAN/resource/Maintenance_Item
I expect it not to be isolated to just these APIs.

I have included payloads at the bottom.

I want to note that this error does not occur if before performing the update I do:

(entity as any).remoteState._entityApi = null;

or

const entityApi = entity._entityApi;
delete (entity as any)._entityApi;
// make _entityApi non enumerable
Object.defineProperty(entity, '_entityApi', {value: entityApi});

but obviously these are some pretty awful hacks.

Notably, I also get this error if I attempt to console.log(JSON.stringify(entity)):

TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'MaintenancePlanApi'
    |     property 'navigationPropertyFields' -> object with constructor 'Object'
    |     property 'TO_ITEM' -> object with constructor 'Link'
    --- property '_entityApi' closes the circle
    at JSON.stringify (<anonymous>)

I believe this circular JSON error is indicative of why the update() ends up in endless recursion.
In particular, this JSON error also does not occur if I apply a hack from above.

Here is what the payload looks like from the getByKey() API call:

{"d":{"__metadata":{"id":"https://url.com:443/sap/opu/odata/sap/API_MAINTENANCEPLAN/MaintenancePlan('123456')","uri":"https://url.com:443/sap/opu/odata/sap/API_MAINTENANCEPLAN/MaintenancePlan('123456')","type":"cds_api_maintenanceplan.MaintenancePlanType","etag":"......"},"MaintenancePlan":"123456","MaintenancePlanDesc":"","CreationDate":"/Date(1600000000)/","CreatedByUser":"......","LastChangeDate":"/Date(1600000000)/","LastChangedByUser":".....","MaintenanceStrategy":"DAYNON","SchedulingDuration":"0","SchedulingDurationUnit":"DAY","NumberOfMaintenanceItems":"1","CycleModificationRatio":"1.00","MaintPlanSchedgIndicator":"","CallHorizonPercent":"0","CallHorizonInDays":"0","MaintenanceCallHorizonCalcType":"","AuthorizationGroup":"","MaintenancePlanInternalID":"","MaintenanceCall":1,"MaintenancePlanCategory":".....","MaintPlanFreeDefinedAttrib":"","BasicStartDate":null,"SchedulingStartDate":null,"SchedulingStartTime":"PT00H00M00S","MaintPlanStartCntrReadingValue":"","MaintPlnStrtBufDurationInDays":"0","MaintPlanStartBufferUnit":"DAY","FactoryCalendar":"","LateCompletionShiftInPercent":"0","LateCompletionTolerancePercent":"0","EarlyCompletionShiftInPercent":"0","EarlyCompletionTolerancePct":"0","PrdcssrCallObjCompltnIsRqd":"","MaintPlanLogicalOperatorCode":"","SchedulingEndDate":null,"MaintPlanEndCntrReadingValue":"","LastChangeDateTime":"/Date(1600000000+0000)/","MultipleCounterPlanShiftFactor":"","MaintenanceLeadFloatInDays":"0","MaintenancePlanCallObject":"","MaintenancePlanSystemStatus":".....","to_Item":{"__deferred":{"uri":"https://url.com:443/sap/opu/odata/sap/API_MAINTENANCEPLAN/MaintenancePlan('123456')/to_Item"}},"to_LongText":{"__deferred":{"uri":"https://url.com:443/sap/opu/odata/sap/API_MAINTENANCEPLAN/MaintenancePlan('123456')/to_LongText"}},"to_MaintenanceCycle":{"__deferred":{"uri":"https://url.com:443/sap/opu/odata/sap/API_MAINTENANCEPLAN/MaintenancePlan('123456')/to_MaintenanceCycle"}},"to_MaintPlanClfnClass":{"__deferred":{"uri":"https://url.com:443/sap/opu/odata/sap/API_MAINTENANCEPLAN/MaintenancePlan('123456')/to_MaintPlanClfnClass"}},"to_Schedules":{"__deferred":{"uri":"https://url.com:443/sap/opu/odata/sap/API_MAINTENANCEPLAN/MaintenancePlan('123456')/to_Schedules"}},"to_StrategyCycle":{"__deferred":{"uri":"https://url.com:443/sap/opu/odata/sap/API_MAINTENANCEPLAN/MaintenancePlan('123456')/to_StrategyCycle"}}}}

And here is same again but the console.log of that entity:

MaintenancePlan {
  _entityApi: MaintenancePlanApi {
    deSerializers: {
      'Edm.Binary': [Object],
      'Edm.Boolean': [Object],
      'Edm.Byte': [Object],
      'Edm.Double': [Object],
      'Edm.Float': [Object],
      'Edm.Int16': [Object],
      'Edm.Int32': [Object],
      'Edm.Int64': [Object],
      'Edm.SByte': [Object],
      'Edm.Single': [Object],
      'Edm.String': [Object],
      'Edm.Any': [Object],
      'Edm.Decimal': [Object],
      'Edm.Guid': [Object],
      'Edm.DateTime': [Object],
      'Edm.DateTimeOffset': [Object],
      'Edm.Time': [Object]
    },
    navigationPropertyFields: {
      TO_ITEM: [Link],
      TO_LONG_TEXT: [Link],
      TO_MAINTENANCE_CYCLE: [Link],
      TO_MAINT_PLAN_CLFN_CLASS: [Link],
      TO_SCHEDULES: [Link],
      TO_STRATEGY_CYCLE: [Link]
    },
    entityConstructor: [class MaintenancePlan extends Entity] {
      _entityName: 'MaintenancePlan',
      _defaultBasePath: '/sap/opu/odata/sap/API_MAINTENANCEPLAN',
      _keys: [Array]
    },
    _fieldBuilder: FieldBuilder { fieldOf: [Function], deSerializers: [Object] },
    _schema: {
      MAINTENANCE_PLAN: [OrderableEdmTypeField],
      MAINTENANCE_PLAN_DESC: [OrderableEdmTypeField],
      CREATION_DATE: [OrderableEdmTypeField],
      CREATED_BY_USER: [OrderableEdmTypeField],
      LAST_CHANGE_DATE: [OrderableEdmTypeField],
      LAST_CHANGED_BY_USER: [OrderableEdmTypeField],
      MAINTENANCE_STRATEGY: [OrderableEdmTypeField],
      SCHEDULING_DURATION: [OrderableEdmTypeField],
      SCHEDULING_DURATION_UNIT: [OrderableEdmTypeField],
      NUMBER_OF_MAINTENANCE_ITEMS: [OrderableEdmTypeField],
      CYCLE_MODIFICATION_RATIO: [OrderableEdmTypeField],
      MAINT_PLAN_SCHEDG_INDICATOR: [OrderableEdmTypeField],
      CALL_HORIZON_PERCENT: [OrderableEdmTypeField],
      CALL_HORIZON_IN_DAYS: [OrderableEdmTypeField],
      MAINTENANCE_CALL_HORIZON_CALC_TYPE: [OrderableEdmTypeField],
      AUTHORIZATION_GROUP: [OrderableEdmTypeField],
      MAINTENANCE_PLAN_INTERNAL_ID: [OrderableEdmTypeField],
      MAINTENANCE_CALL: [OrderableEdmTypeField],
      MAINTENANCE_PLAN_CATEGORY: [OrderableEdmTypeField],
      MAINT_PLAN_FREE_DEFINED_ATTRIB: [OrderableEdmTypeField],
      BASIC_START_DATE: [OrderableEdmTypeField],
      SCHEDULING_START_DATE: [OrderableEdmTypeField],
      SCHEDULING_START_TIME: [OrderableEdmTypeField],
      MAINT_PLAN_START_CNTR_READING_VALUE: [OrderableEdmTypeField],
      MAINT_PLN_STRT_BUF_DURATION_IN_DAYS: [OrderableEdmTypeField],
      MAINT_PLAN_START_BUFFER_UNIT: [OrderableEdmTypeField],
      FACTORY_CALENDAR: [OrderableEdmTypeField],
      LATE_COMPLETION_SHIFT_IN_PERCENT: [OrderableEdmTypeField],
      LATE_COMPLETION_TOLERANCE_PERCENT: [OrderableEdmTypeField],
      EARLY_COMPLETION_SHIFT_IN_PERCENT: [OrderableEdmTypeField],
      EARLY_COMPLETION_TOLERANCE_PCT: [OrderableEdmTypeField],
      PRDCSSR_CALL_OBJ_COMPLTN_IS_RQD: [OrderableEdmTypeField],
      MAINT_PLAN_LOGICAL_OPERATOR_CODE: [OrderableEdmTypeField],
      SCHEDULING_END_DATE: [OrderableEdmTypeField],
      MAINT_PLAN_END_CNTR_READING_VALUE: [OrderableEdmTypeField],
      LAST_CHANGE_DATE_TIME: [OrderableEdmTypeField],
      MULTIPLE_COUNTER_PLAN_SHIFT_FACTOR: [OrderableEdmTypeField],
      MAINTENANCE_LEAD_FLOAT_IN_DAYS: [OrderableEdmTypeField],
      MAINTENANCE_PLAN_CALL_OBJECT: [OrderableEdmTypeField],
      MAINTENANCE_PLAN_SYSTEM_STATUS: [OrderableEdmTypeField],
      TO_ITEM: [Link],
      TO_LONG_TEXT: [Link],
      TO_MAINTENANCE_CYCLE: [Link],
      TO_MAINT_PLAN_CLFN_CLASS: [Link],
      TO_SCHEDULES: [Link],
      TO_STRATEGY_CYCLE: [Link],
      ALL_FIELDS: [AllFields]
    }
  },
  maintenancePlan: '123456',
  maintenancePlanDesc: '',
  creationDate: Moment<2020-09-13T12:26:40+00:00>,
  createdByUser: '.....',
  lastChangeDate: Moment<2020-09-13T12:26:40+00:00>,
  lastChangedByUser: '.....',
  maintenanceStrategy: 'DAYNON',
  schedulingDuration: '0',
  schedulingDurationUnit: 'DAY',
  numberOfMaintenanceItems: '1',
  cycleModificationRatio: BigNumber { s: 1, e: 0, c: [ 1 ] },
  maintPlanSchedgIndicator: '',
  callHorizonPercent: '0',
  callHorizonInDays: '0',
  maintenanceCallHorizonCalcType: '',
  authorizationGroup: '',
  maintenancePlanInternalId: '.....',
  maintenanceCall: 1,
  maintenancePlanCategory: '.....',
  maintPlanFreeDefinedAttrib: '',
  basicStartDate: null,
  schedulingStartDate: null,
  schedulingStartTime: { hours: 0, minutes: 0, seconds: 0 },
  maintPlanStartCntrReadingValue: '',
  maintPlnStrtBufDurationInDays: '0',
  maintPlanStartBufferUnit: 'DAY',
  factoryCalendar: '',
  lateCompletionShiftInPercent: '0',
  lateCompletionTolerancePercent: '0',
  earlyCompletionShiftInPercent: '0',
  earlyCompletionTolerancePct: '0',
  prdcssrCallObjCompltnIsRqd: '',
  maintPlanLogicalOperatorCode: '',
  schedulingEndDate: null,
  maintPlanEndCntrReadingValue: '',
  lastChangeDateTime: Moment<2020-09-13T12:26:40Z>,
  multipleCounterPlanShiftFactor: '',
  maintenanceLeadFloatInDays: '0',
  maintenancePlanCallObject: '',
  maintenancePlanSystemStatus: '.....',
  toItem: [],
  toLongText: [],
  toMaintenanceCycle: [],
  toMaintPlanClfnClass: [],
  toSchedules: [],
  toStrategyCycle: []
}

@deekshas8
Copy link
Contributor

Hi @lincheney ,

So I executed the same request against the SAP Business Accelerator Hub Sandbox and it worked for me. Below is the code I tried:

const { maintenancePlanApi, maintenanceItemApi } = apiMaintenanceplan();
 const entity = await maintenancePlanApi
  .requestBuilder()
  .getByKey('1')
  .addCustomHeaders({ APIKey: '<YOUR-API-KEY>' })
  .execute({
    url: 'https://sandbox.api.sap.com/s4hanacloud'
  });

You mentioned you execute the following code before an update request:

const entityApi = entity._entityApi;
delete (entity as any)._entityApi;
// make _entityApi non enumerable
Object.defineProperty(entity, '_entityApi', {value: entityApi});

However, we do set the _entityApi property as nonEnumerable. See here in #L83.

Can you confirm you have the latest dependencies for all sdk packages, including odata-common ?

@lincheney
Copy link
Author

lincheney commented Jun 20, 2023

Hi @deekshas8

Thanks for your reply.

tldr I did some more debugging and found the issue is caused by microsoft/TypeScript#45584
and if I set "useDefineForClassFields": false in my tsconfig.json then it works.

However, we do set the _entityApi property as nonEnumerable. See here in #L83.

Thanks for this tip. So I checked the version of odata-common I have and it is latest, and I also checked the JS inside my node_modules/ directory and it is indeed setting the property to non enumerable and moreover I added console.log(this.propertyIsEnumerable('_entityApi')) to the EntityBase constructor and it correctly printed false!
However if I do the same against the entity returned from getByKey(), the console.log(entity.propertyIsEnumerable('_entityApi')) prints true. This was very confusing to me.

Anyway, after much debugging, I discovered that the issue is due to the way that tsc is compiling my project.
In tsconfig.json I have "target": "es2022" and according to this when the target is es2022 then useDefineForClassFields defaults to true, the effect of which is described here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier

Essentially what is happening is that when useDefineForClassFields is true, tsc compiles classes so that their properties are always declared in the JS class. For subclasses, this appears to have the effect of overriding what is happening in the base class (in this case specifically the EntityBase class).
So when I compile the MaintenanceItem class in my project, then it generates JS code like:

class MaintenanceItem extends odata_v2_1.Entity {
    _entityApi;
    maintenanceItem;
    maintenanceItemDescription;
    // etc etc etc
    constructor(_entityApi) {
        super(_entityApi);
        this._entityApi = _entityApi;
    }
}

The problem is the second line; this causes this._entityApi to become a normal enumerable property set to undefined immediately after the super() call returns.

If I explicitly set "useDefineForClassFields": false in my tsconfig.json, then it instead generates

class MaintenanceItem extends odata_v2_1.Entity {
    constructor(_entityApi) {
        super(_entityApi);
        this._entityApi = _entityApi;
    }
}

which does not have the problem.

As an aside, this actually causes an additional problem that I had not mentioned earlier as I had not realised it was related.
Previously I had attempted to construct the payload for the update() using an entity builder like so:

const entity = api.entityBuilder().maintenanceItem('12345').functionalLocationLabelName('blahblah').build();

with the expectation that this would construct a JSON payload with only the MaintenanceItem and FunctionalLocationLabelName fields.
However it constructed a JSON payload with all the other fields included as well and set to null which caused errors.
Turns out this is also caused by useDefineForClassFields=true;
since the classes are being compiled so that all fields are set to undefined,
then those fields end up being serialised as null in the JSON payload.
What is meant to happen, I believe, is that those fields are not set at all (not even to undefined) and then they simply don't appear in the JSON payload.

For now, if I set "useDefineForClassFields": false, then this solves the issue for me.

However, I would be interested to know if sap-cloud-sdk will support "useDefineForClassFields": true given it has become the default value for es2022

@deekshas8
Copy link
Contributor

Hi @lincheney ,

Sorry for the late response. Thank you for investigating this and your findings. I have created a backlog item for this. We will update you when this is supported.

@ThomasL81
Copy link

Hello @deekshas8
I would like to inform you that we are experiencing exactly the same issue using contact metadata from the standard C4C OData service. The call stack exceeded issue was raised during the comparison of changed properties, similar as in the OP's example.

Running the following versions:
Node: v18.16.0
npm: 9.5.1
├── @sap-cloud-sdk/connectivity@3.6.0
├── @sap-cloud-sdk/generator@3.6.0
├── @sap-cloud-sdk/http-client@3.6.0
├── @sap-cloud-sdk/odata-v2@3.6.0
├── @sap-cloud-sdk/resilience@3.6.0

[cds] - ❗️Uncaught RangeError: Maximum call stack size exceeded at .\node_modules\@sap-cloud-sdk\util\src\equal.ts:16:17 at Array.every (<anonymous>) at equalObjects (.\node_modules\@sap-cloud-sdk\util\src\equal.ts:16:11) at equal (.\node_modules\@sap-cloud-sdk\util\src\equal.ts:39:12) at .\node_modules\@sap-cloud-sdk\util\src\equal.ts:16:24 at Array.every (<anonymous>) at equalObjects (.\node_modules\@sap-cloud-sdk\util\src\equal.ts:16:11) at equal (.\node_modules\@sap-cloud-sdk\util\src\equal.ts:39:12) at .\node_modules\@sap-cloud-sdk\util\src\equal.ts:16:24 at Array.every (<anonymous>) { id: '1311187', level: 'ERROR', timestamp: 1697125094296 }

Kindly keep me updated aswell.

Br,
Thomas

@deekshas8
Copy link
Contributor

Hi @lincheney , @ThomasL81, the fix is part of the newly released v3.7.0. Please give it a try.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants