├── .dataverse-gen.json ├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── UPGRADING.md ├── _templates ├── action.ejs ├── complextype.ejs ├── entity.ejs ├── enum.ejs ├── function.ejs └── metadata.ejs ├── config └── test.yaml ├── dist-tests ├── jasmine-4.1.1 │ ├── boot0.js │ ├── boot1.js │ ├── jasmine-html.js │ ├── jasmine.css │ ├── jasmine.js │ └── jasmine_favicon.png └── test-runner.html ├── docs ├── home.md ├── integration-testing.md ├── quick-start.md ├── using-without-service-client.md └── why-metadata.md ├── integration-test-solution ├── README.md ├── cdsifyintegrationtests.zip └── cdsifyintegrationtests_managed.zip ├── jest.config.js ├── package-lock.json ├── package.json ├── postinstall.js ├── src ├── browser-tests │ ├── README.md │ ├── SetupGlobalContext.ts │ ├── config-browser.ts │ └── index.ts ├── dataverse-gen │ ├── actions │ │ ├── AddToQueue.ts │ │ ├── WinOpportunity.ts │ │ ├── cdsify_BoundCollectionEcho.ts │ │ ├── cdsify_BoundEcho.ts │ │ └── cdsify_UnboundEcho.ts │ ├── complextypes │ │ ├── AddToQueueResponse.ts │ │ ├── AssociatedMenuConfiguration.ts │ │ ├── AttributeQueryExpression.ts │ │ ├── AttributeRequiredLevelManagedProperty.ts │ │ ├── AttributeTypeDisplayName.ts │ │ ├── BooleanManagedProperty.ts │ │ ├── CalculateTotalTimeIncidentResponse.ts │ │ ├── CascadeConfiguration.ts │ │ ├── ComplexAttributeMetadata.ts │ │ ├── ComplexEntityKeyMetadata.ts │ │ ├── ComplexEntityMetadata.ts │ │ ├── ComplexManyToManyRelationshipMetadata.ts │ │ ├── ComplexOneToManyRelationshipMetadata.ts │ │ ├── DeletedMetadataCollection.ts │ │ ├── EntityKeyQueryExpression.ts │ │ ├── EntityQueryExpression.ts │ │ ├── EntitySetting.ts │ │ ├── GuidCollection.ts │ │ ├── Label.ts │ │ ├── LabelQueryExpression.ts │ │ ├── LocalizedLabel.ts │ │ ├── MetadataConditionExpression.ts │ │ ├── MetadataFilterExpression.ts │ │ ├── MetadataPropertiesExpression.ts │ │ ├── ObjectValue.ts │ │ ├── RelationshipAttribute.ts │ │ ├── RelationshipQueryExpression.ts │ │ ├── RetrieveMetadataChangesResponse.ts │ │ ├── SecurityPrivilegeMetadata.ts │ │ ├── cdsify_BoundCollectionEchoResponse.ts │ │ ├── cdsify_BoundEchoFunctionResponse.ts │ │ ├── cdsify_BoundEchoResponse.ts │ │ ├── cdsify_UnboundEchoFunction2Response.ts │ │ └── cdsify_UnboundEchoResponse.ts │ ├── entities │ │ ├── Account.ts │ │ ├── Contact.ts │ │ ├── Incident.ts │ │ ├── Letter.ts │ │ ├── Opportunity.ts │ │ ├── OpportunityClose.ts │ │ ├── Queue.ts │ │ ├── QueueItem.ts │ │ └── cdsify_IntegrationTest.ts │ ├── enums │ │ ├── AssociatedMenuBehavior.ts │ │ ├── AssociatedMenuGroup.ts │ │ ├── AttributeRequiredLevel.ts │ │ ├── AttributeTypeCode.ts │ │ ├── CascadeType.ts │ │ ├── DeletedMetadataFilters.ts │ │ ├── EntityKeyIndexStatus.ts │ │ ├── LogicalOperator.ts │ │ ├── MetadataConditionOperator.ts │ │ ├── OwnershipTypes.ts │ │ ├── PrivilegeType.ts │ │ ├── RelationshipType.ts │ │ ├── SecurityTypes.ts │ │ ├── account_account_accountcategorycode.ts │ │ ├── account_account_accountclassificationcode.ts │ │ ├── account_account_accountratingcode.ts │ │ ├── account_account_address1_addresstypecode.ts │ │ ├── account_account_address1_freighttermscode.ts │ │ ├── account_account_address1_shippingmethodcode.ts │ │ ├── account_account_address2_addresstypecode.ts │ │ ├── account_account_address2_freighttermscode.ts │ │ ├── account_account_address2_shippingmethodcode.ts │ │ ├── account_account_businesstypecode.ts │ │ ├── account_account_customersizecode.ts │ │ ├── account_account_customertypecode.ts │ │ ├── account_account_industrycode.ts │ │ ├── account_account_ownershipcode.ts │ │ ├── account_account_paymenttermscode.ts │ │ ├── account_account_preferredappointmentdaycode.ts │ │ ├── account_account_preferredappointmenttimecode.ts │ │ ├── account_account_preferredcontactmethodcode.ts │ │ ├── account_account_shippingmethodcode.ts │ │ ├── account_account_statecode.ts │ │ ├── account_account_statuscode.ts │ │ ├── account_account_territorycode.ts │ │ ├── activitypointer_deliveryprioritycode.ts │ │ ├── budgetstatus.ts │ │ ├── cdsify_integrationtest_cdsify_integrationtest_statecode.ts │ │ ├── cdsify_integrationtest_cdsify_integrationtest_statuscode.ts │ │ ├── contact_contact_accountrolecode.ts │ │ ├── contact_contact_address1_addresstypecode.ts │ │ ├── contact_contact_address1_freighttermscode.ts │ │ ├── contact_contact_address1_shippingmethodcode.ts │ │ ├── contact_contact_address2_addresstypecode.ts │ │ ├── contact_contact_address2_freighttermscode.ts │ │ ├── contact_contact_address2_shippingmethodcode.ts │ │ ├── contact_contact_address3_addresstypecode.ts │ │ ├── contact_contact_address3_freighttermscode.ts │ │ ├── contact_contact_address3_shippingmethodcode.ts │ │ ├── contact_contact_customersizecode.ts │ │ ├── contact_contact_customertypecode.ts │ │ ├── contact_contact_educationcode.ts │ │ ├── contact_contact_familystatuscode.ts │ │ ├── contact_contact_gendercode.ts │ │ ├── contact_contact_haschildrencode.ts │ │ ├── contact_contact_leadsourcecode.ts │ │ ├── contact_contact_msdyn_orgchangestatus.ts │ │ ├── contact_contact_paymenttermscode.ts │ │ ├── contact_contact_preferredappointmentdaycode.ts │ │ ├── contact_contact_preferredappointmenttimecode.ts │ │ ├── contact_contact_preferredcontactmethodcode.ts │ │ ├── contact_contact_shippingmethodcode.ts │ │ ├── contact_contact_statecode.ts │ │ ├── contact_contact_statuscode.ts │ │ ├── contact_contact_territorycode.ts │ │ ├── incident_caseorigincode.ts │ │ ├── incident_incident_casetypecode.ts │ │ ├── incident_incident_contractservicelevelcode.ts │ │ ├── incident_incident_customersatisfactioncode.ts │ │ ├── incident_incident_firstresponseslastatus.ts │ │ ├── incident_incident_incidentstagecode.ts │ │ ├── incident_incident_prioritycode.ts │ │ ├── incident_incident_resolvebyslastatus.ts │ │ ├── incident_incident_severitycode.ts │ │ ├── incident_incident_statecode.ts │ │ ├── incident_incident_statuscode.ts │ │ ├── initialcommunication.ts │ │ ├── letter_letter_prioritycode.ts │ │ ├── letter_letter_statecode.ts │ │ ├── letter_letter_statuscode.ts │ │ ├── msdyn_opportunitygradeoptset.ts │ │ ├── msdyn_opportunityscoretrendoptset.ts │ │ ├── msdyn_queueassignmentstrategy.ts │ │ ├── msdyn_queuetype.ts │ │ ├── need.ts │ │ ├── opportunity_msdyn_opportunity_msdyn_forecastcategory.ts │ │ ├── opportunity_opportunity_opportunityratingcode.ts │ │ ├── opportunity_opportunity_prioritycode.ts │ │ ├── opportunity_opportunity_salesstagecode.ts │ │ ├── opportunity_opportunity_statecode.ts │ │ ├── opportunity_opportunity_statuscode.ts │ │ ├── opportunity_opportunity_timeline.ts │ │ ├── opportunity_salesstage.ts │ │ ├── opportunityclose_OpportunityClose_opportunity_statecode.ts │ │ ├── opportunityclose__opportunityclose_instancetypecode.ts │ │ ├── opportunityclose__opportunityclose_prioritycode.ts │ │ ├── opportunityclose_opportunityclose_opportunity_statuscode.ts │ │ ├── opportunityclose_opportunityclose_statecode.ts │ │ ├── opportunityclose_opportunityclose_statuscode.ts │ │ ├── purchaseprocess.ts │ │ ├── purchasetimeframe.ts │ │ ├── qooi_pricingerrorcode.ts │ │ ├── qooi_skippricecalculation.ts │ │ ├── queue_queue_emailrouteraccessapproval.ts │ │ ├── queue_queue_incomingemaildeliverymethod.ts │ │ ├── queue_queue_incomingemailfilteringmethod.ts │ │ ├── queue_queue_outgoingemaildeliverymethod.ts │ │ ├── queue_queue_queuetypecode.ts │ │ ├── queue_queue_queueviewtype.ts │ │ ├── queue_queue_statecode.ts │ │ ├── queue_queue_statuscode.ts │ │ ├── queueitem_queueitem_objecttypecode.ts │ │ ├── queueitem_queueitem_statecode.ts │ │ ├── queueitem_queueitem_statuscode.ts │ │ ├── servicestage.ts │ │ ├── socialactivity_postmessagetype.ts │ │ └── socialprofile_community.ts │ ├── functions │ │ ├── CalculateRollupField.ts │ │ ├── CalculateTotalTimeIncident.ts │ │ ├── RetrieveMetadataChanges.ts │ │ ├── cdsify_BoundEchoFunction.ts │ │ └── cdsify_UnboundEchoFunction2.ts │ ├── index.ts │ └── metadata.ts ├── dataverse-ify │ ├── DataverseClient │ │ ├── DataverseClient.ts │ │ ├── XrmContextDataverseClient.ts │ │ └── index.ts │ ├── __tests__ │ │ ├── integration-tests │ │ │ ├── CRUD.test.ts │ │ │ ├── activity.test.ts │ │ │ ├── addtoqueue.test.ts │ │ │ ├── associate.test.ts │ │ │ ├── calculaterollupfield.test.ts │ │ │ ├── custom-api-bound-action.test.ts │ │ │ ├── custom-api-bound-function.test.ts │ │ │ ├── custom-api-executemultiple.test.ts │ │ │ ├── custom-api-unbound-action.test.ts │ │ │ ├── custom-api-unbound-function.test.ts │ │ │ ├── customer.test.ts │ │ │ ├── execute-crud.test.ts │ │ │ ├── execute-multiple-crud-changeset.test.ts │ │ │ ├── execute-multiple-crud-error.test.ts │ │ │ ├── execute-multiple-crud.test.ts │ │ │ ├── fetchxml.test.ts │ │ │ ├── filecolumn.test.ts │ │ │ ├── get-metadata.test.ts │ │ │ ├── nullvalues.test.ts │ │ │ ├── odata-retrievemultiple.test.ts │ │ │ ├── pascalcase-navigation-property.test.ts │ │ │ └── winopportunity.test.ts │ │ └── unit-tests │ │ │ ├── action-winopportunity.test.ts │ │ │ ├── field-customer.test.ts │ │ │ ├── field-lookup.test.ts │ │ │ ├── field-multiselect.test.ts │ │ │ ├── field-primitive.test.ts │ │ │ ├── formattedvalues.test.ts │ │ │ ├── lookup-jit-metadata.test.ts │ │ │ ├── primaryid-mapping.test.ts │ │ │ └── update.test.ts │ ├── index.ts │ ├── odataify │ │ ├── odataify.ts │ │ └── odataifyFields.ts │ └── sdkify │ │ ├── dateReviver.ts │ │ └── sdkify.ts ├── index.ts ├── metadata │ ├── EntityWebApiMetadata.ts │ ├── MetadataCache.ts │ ├── WebApiExecuteRequestMetadata.ts │ ├── fixWebresourceXrm.ts │ ├── getEntityMetadataFromRecord.ts │ └── index.ts ├── types │ ├── ActivityParty.ts │ ├── AttributeTypes.ts │ ├── Dictionary.ts │ ├── Entity.ts │ ├── EntityCollection.ts │ ├── EntityReference.ts │ ├── Guid.ts │ ├── IEntity.ts │ ├── IEntityCollection.ts │ ├── IEntityReference.ts │ ├── OperationType.ts │ ├── ParameterType.ts │ ├── RequestWithTarget.ts │ ├── RetrieveMultipleResult.ts │ ├── StructuralProperty.ts │ ├── WebApiError.ts │ ├── WebApiExecuteRequest.ts │ ├── WebApiRequest.ts │ ├── index.ts │ └── requests │ │ ├── AssociateRequest.ts │ │ ├── Create.ts │ │ ├── Delete.ts │ │ ├── DisassociateRequest.ts │ │ ├── ExecuteMultiple.ts │ │ ├── Update.ts │ │ └── index.ts └── webapi │ ├── GlobalContextStatic.ts │ ├── HttpReader.ts │ ├── WebApiBase.ts │ ├── WebApiRequest.ts │ ├── XrmApi.ts │ ├── XrmUtility.ts │ ├── __tests__ │ ├── test-values.ts │ ├── unit-odataquery.test.ts │ ├── unit.batch-request.test.ts │ ├── unit.batch-response-parts.test.ts │ ├── unit.changeset-batch-request.test.ts │ ├── unit.changeset-batch-responses.test.ts │ └── unit.parseresponse.test.ts │ ├── browser │ ├── BrowserWebApi.ts │ ├── BrowserWebApiRequest.ts │ └── index.ts │ ├── index.ts │ ├── node │ ├── MsalAuth │ │ ├── MsalCachePlugin.ts │ │ ├── MsalConfig.ts │ │ ├── MsalNodeAuth.ts │ │ └── index.ts │ ├── NodeWebApi.ts │ ├── NodeWebApiRequest.ts │ ├── SetupGlobalContext.ts │ ├── __tests__ │ │ ├── CRUD │ │ │ ├── Create.test.ts │ │ │ ├── DeepInsert.test.ts │ │ │ ├── Delete.test.ts │ │ │ ├── FetchXml.test.ts │ │ │ ├── OdataQuery.test.ts │ │ │ └── response-errors.test.ts │ │ └── Execute │ │ │ ├── associate.test.ts │ │ │ ├── custom-api-bound-action.test.ts │ │ │ ├── custom-api-bound-function.test.ts │ │ │ ├── custom-api-collection-bound-action.test.ts │ │ │ ├── custom-api-unbound-action.test.ts │ │ │ ├── execute-function.test.ts │ │ │ ├── execute-multiple-changeset-error.test.ts │ │ │ ├── execute-multiple-changeset.test.ts │ │ │ ├── execute-multiple.test.ts │ │ │ └── execute.test.ts │ ├── config │ │ ├── NodeXrmConfig.ts │ │ ├── NodeXrmConfigProxy.ts │ │ ├── NodeXrmConfigServer.ts │ │ └── index.ts │ └── index.ts │ ├── utils │ ├── GetHeaderValue.ts │ ├── NullOrUndefined.ts │ └── StringFormat.ts │ └── whoAmI.ts ├── test-setup.js ├── tsconfig.json └── webpack.config.js /.dataverse-gen.json: -------------------------------------------------------------------------------- 1 | { 2 | "referencedTypes": { 3 | "Guid": { 4 | "name": "Guid", 5 | "import": "../../types/Guid" 6 | }, 7 | "Entity": { 8 | "name": "IEntity", 9 | "import": "../../types/IEntity" 10 | }, 11 | "EntityReference": { 12 | "name": "EntityReference", 13 | "import": "../../types/EntityReference" 14 | }, 15 | "ActivityParty": { 16 | "name": "ActivityParty", 17 | "import": "../../types/ActivityParty" 18 | }, 19 | "StructuralProperty": { 20 | "name": "StructuralProperty", 21 | "import": "../../types/StructuralProperty" 22 | }, 23 | "WebApiExecuteRequest": { 24 | "name": "WebApiExecuteRequest", 25 | "import": "../../types/WebApiExecuteRequest" 26 | }, 27 | "OperationType": { 28 | "name": "OperationType", 29 | "import": "../../types/OperationType" 30 | }, 31 | "enums": { 32 | "import": "../enums/" 33 | }, 34 | "complexTypes": { 35 | "import": "../complextypes/" 36 | }, 37 | "entityTypes": { 38 | "import": "../entities/" 39 | }, 40 | "File": { 41 | "import": "../entities/" 42 | } 43 | }, 44 | "entities": [ 45 | "account", 46 | "contact", 47 | "letter", 48 | "opportunity", 49 | "cdsify_integrationtest" 50 | ], 51 | "actions": [ 52 | "WinOpportunity", 53 | "AddToQueue", 54 | "cdsify_UnboundEcho", 55 | "cdsify_BoundEcho", 56 | "cdsify_BoundCollectionEcho" 57 | ], 58 | "functions": [ 59 | "CalculateTotalTimeIncident", 60 | "CalculateRollupField", 61 | "RetrieveMetadataChanges", 62 | "cdsify_UnboundEchoFunction2", 63 | "cdsify_BoundEchoFunction" 64 | ], 65 | "output": { 66 | "outputRoot": "./src/dataverse-gen" 67 | }, 68 | "generateFormContext": false, 69 | "generateEntityTypes": true 70 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:prettier/recommended", 10 | "prettier", 11 | "plugin:sonarjs/recommended" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 12, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "@typescript-eslint", 23 | "prettier", 24 | "sonarjs", 25 | "@microsoft/power-apps" 26 | ], 27 | "ignorePatterns": [ 28 | "**/generated/*.ts" 29 | ], 30 | "rules": { 31 | "eqeqeq": [ 32 | 2, 33 | "smart" 34 | ], 35 | "prettier/prettier": "error", 36 | "arrow-body-style": "off", 37 | "prefer-arrow-callback": "off", 38 | "linebreak-style": [ 39 | "error", 40 | "windows" 41 | ], 42 | "quotes": [ 43 | "error", 44 | "double" 45 | ], 46 | "semi": [ 47 | "error", 48 | "always" 49 | ], 50 | "@microsoft/power-apps/avoid-2011-api": "error", 51 | "@microsoft/power-apps/avoid-browser-specific-api": "error", 52 | "@microsoft/power-apps/avoid-crm2011-service-odata": "warn", 53 | "@microsoft/power-apps/avoid-crm2011-service-soap": "warn", 54 | "@microsoft/power-apps/avoid-dom-form-event": "warn", 55 | "@microsoft/power-apps/avoid-dom-form": "warn", 56 | "@microsoft/power-apps/avoid-isactivitytype": "warn", 57 | "@microsoft/power-apps/avoid-modals": "warn", 58 | "@microsoft/power-apps/avoid-unpub-api": "warn", 59 | "@microsoft/power-apps/avoid-window-top": "warn", 60 | "@microsoft/power-apps/do-not-make-parent-assumption": "warn", 61 | "@microsoft/power-apps/use-async": "error", 62 | "@microsoft/power-apps/use-cached-webresource": "warn", 63 | "@microsoft/power-apps/use-client-context": "warn", 64 | "@microsoft/power-apps/use-global-context": "error", 65 | "@microsoft/power-apps/use-grid-api": "warn", 66 | "@microsoft/power-apps/use-navigation-api": "warn", 67 | "@microsoft/power-apps/use-offline": "warn", 68 | "@microsoft/power-apps/use-org-setting": "error", 69 | "@microsoft/power-apps/use-relative-uri": "warn", 70 | "@microsoft/power-apps/use-utility-dialogs": "warn" 71 | } 72 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | !.vscode/settings.json 3 | !.vscode/tasks.json 4 | !.vscode/launch.json 5 | !.vscode/extensions.json 6 | *.code-workspace 7 | 8 | # Local History for Visual Studio Code 9 | .history/ 10 | 11 | node_modules 12 | lib 13 | dist 14 | coverage 15 | dist-tests/browser-tests.js 16 | .env 17 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 120, 6 | "tabWidth": 2, 7 | "endOfLine":"auto" 8 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "name": "vscode-jest-tests", 10 | "request": "launch", 11 | "args": ["${fileBasename}", "--runInBand", "--code-coverage=false" 12 | //"--testNamePattern", "activity" 13 | //"--testNamePattern", "customer" 14 | ], 15 | "cwd": "${workspaceFolder}", 16 | "console": "integratedTerminal", 17 | "smartStep": true, 18 | "internalConsoleOptions": "neverOpen", 19 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 20 | "skipFiles": ["node_modules/**/*.js", "/**"], 21 | "runtimeArgs": [ 22 | "--harmony", 23 | "--no-deprecation" 24 | ], 25 | "sourceMaps": true 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Scott Durow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to dataverse-ify 2 | 3 | ```text 4 | __ __ _ ____ 5 | ____/ /___ _/ /_____ __ _____ _____________ (_) __/_ __ 6 | / __ / __ `/ __/ __ `/ | / / _ \/ ___/ ___/ _ \______/ / /_/ / / / 7 | / /_/ / /_/ / /_/ /_/ /| |/ / __/ / (__ ) __/_____/ / __/ /_/ / 8 | \__,_/\__,_/\__/\__,_/ |___/\___/_/ /____/\___/ /_/_/ \__, / 9 | /____/ 10 | ``` 11 | 12 | The TypeScript library that allows you to use the Microsoft Dataverse `Xrm.WebApi` using `IOrganizationService` SDK like types. 13 | Works with [dataverse-gen](https://www.npmjs.com/package/dataverse-gen) and [dataverse-auth](https://www.npmjs.com/package/dataverse-gen) to create early bound classes. 14 | 15 | > **NOTE**: This is version 2. For upgrading from version 1, see the article on [UPGRADING](/UPGRADING.md). 16 | 17 | - [Introduction to dataverse-ify](docs/home.md) 18 | - [Quick Start](docs/quick-start.md) 19 | - [Integration tests with dataverse-ify](docs/integration-testing.md) 20 | - [Why Metadata?](docs/why-metadata.md) 21 | - [Using without DataverseClient](docs/using-without-service-client.md) 22 | 23 | ## Contributing 24 | 25 | To build dataverse-ify locally you can fork the repo and then use: 26 | 27 | ```text 28 | npm install 29 | npm run build 30 | ``` 31 | 32 | There are both unit tests and integration tests. 33 | To run the unit tests: 34 | 35 | ```text 36 | npm test 37 | ``` 38 | 39 | to run the integration tests, you will need to install the test solution (that contains some custom attributes) that is found at `integration-test-solution\cdsifyintegrationtests.zip` 40 | 41 | Update the config file `config\test.yaml` with the org url of your environment, then run: 42 | 43 | ```text 44 | npm run integration-test 45 | ``` 46 | 47 | You can then test the library in your projects by using: 48 | 49 | ```text 50 | npm link 51 | ``` 52 | 53 | In your project that uses dataverse-ify use: 54 | 55 | ```text 56 | npm link dataverse-ify 57 | ``` 58 | 59 | Finally, unlink on the dataverse-ify project using: 60 | 61 | ```text 62 | npm unlink dateverse-ify 63 | ``` 64 | 65 | Commit your changes to your fork and then submit a pull request for review. 66 | 67 | 68 | 69 | ### Upgrading from version 1 to 2? 70 | 71 | You will need to make [some minor changes](UPGRADING.md). 72 | -------------------------------------------------------------------------------- /_templates/action.ejs: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { <%-referencedTypes["WebApiExecuteRequest"].name%> } from "<%-referencedTypes["WebApiExecuteRequest"].import%>"; 3 | import { <%-referencedTypes["StructuralProperty"].name%> } from "<%-referencedTypes["StructuralProperty"].import%>"; 4 | import { <%-referencedTypes["OperationType"].name%> } from "<%-referencedTypes["OperationType"].import%>"; 5 | 6 | // Action <%-Name%> 7 | export const <%-Name%>Metadata = { 8 | <%_ if (IsBound){ _%> 9 | boundParameter: "entity", 10 | <%_}_%> 11 | parameterTypes: { 12 | <%_ locals.Parameters.forEach(function(param){ _%> 13 | "<%- param.Name %>": { 14 | typeName: "<%-param.Type %>", 15 | structuralProperty: StructuralProperty.<%- param.structuralTypeName %> 16 | }, 17 | <%});%> 18 | }, 19 | operationType: OperationType.Action, 20 | operationName: "<%-Name%>" 21 | }; 22 | 23 | export interface <%- Name %>Request extends <%-referencedTypes["WebApiExecuteRequest"].name%> { 24 | <%_locals.Parameters.forEach(function(param){ _%> 25 | <%-param.Name%><%-(param.IsRequired || param.Name==locals.BindingParameter ? ": " : "?: ") _%><%-param.TypescriptTypes.map(function(outputType){ 26 | return (outputType.importLocation ? `import("${outputType.importLocation}").` : "") + outputType.name}).join(" | ")%>; 27 | <%_ }) _%> 28 | } -------------------------------------------------------------------------------- /_templates/complextype.ejs: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface <%- locals.Name %> { 3 | <%_locals.Properties.forEach(function(property){ _%> 4 | <%- property.Name %>?: <%-property.TypescriptType.importLocation && `import("${property.TypescriptType.importLocation}").`%><%-property.TypescriptType.name%>; 5 | <%_})_%> 6 | } -------------------------------------------------------------------------------- /_templates/entity.ejs: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { <%-referencedTypes["Entity"].name%> } from "<%-referencedTypes["Entity"].import%>"; 3 | // Entity <%- locals.SchemaName %> 4 | export const <%- Name %>Metadata = { 5 | typeName: "mscrm.<%- Name %>", 6 | logicalName: "<%- Name %>", 7 | collectionName: "<%- EntitySetName %>", 8 | primaryIdAttribute: "<%- KeyName %>", 9 | attributeTypes: { 10 | // Numeric Types 11 | <%_ Properties.filter(p=>p.TypescriptType.name=="number").forEach(function(attribute){ _%> 12 | <%-attribute.Name%>: "<%-attribute.Type.replace("Type","")-%>", 13 | <%_})_%> 14 | // Optionsets 15 | <%_ Properties.filter(p=>p.IsEnum).forEach(function(attribute){ _%> 16 | <%-attribute.Name%>: "<%-attribute.IsMultiSelect ? "MultiSelect" : "Optionset"%>", 17 | <%_})_%> 18 | // Date Formats 19 | <%_ Properties.filter(p=>p.TypescriptType.name == "Date").forEach(function(attribute){ _%> 20 | <%-attribute.Name%>: "<%-attribute.Format-%>", 21 | <%_})_%> 22 | }, 23 | navigation: { 24 | <%_ NavigationProperties.filter(n=>!n.IsCollection).forEach(function(navProp){ _%> 25 | <%-navProp.Name%>: [<%-navProp.Type.split(",").map(function(a){return '"' + a + '"'}).join(",")%>], 26 | <%_})_%> 27 | }, 28 | }; 29 | 30 | // Attribute constants 31 | export enum <%- locals.SchemaName %>Attributes { 32 | <%locals.Properties && locals.Properties.forEach(function(property){ _%> 33 | <%- property.SchemaName %> = "<%- property.Name %>", 34 | <%})_%> 35 | } 36 | 37 | // Early Bound Interface 38 | export interface <%- locals.SchemaName ? locals.SchemaName : Name %> extends <%-referencedTypes["Entity"].name%> { 39 | <%locals.Properties && locals.Properties.forEach(function(property){ _%> 40 | // <%- property.DisplayName ? property.DisplayName : "" %><%- property.IsRequired == true ? " [Required]" : "" %><%- property.Type ? (" " + property.Type) : "" %><%- property.Description ? (" " + property.Description) :"" %><%- property.Format ? (" " + property.Format) : "" %> 41 | <%- property.Name %>?: <%-property.TypescriptType.importLocation && `import("${property.TypescriptType.importLocation}").`%><%- property.TypescriptType.name %><%- property.IsRequired == true ? "" : " | null" %>; 42 | <%})_%> 43 | } 44 | -------------------------------------------------------------------------------- /_templates/enum.ejs: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum <%- locals.Name %> 3 | export const enum <%- Name %> { 4 | <% locals.Members && locals.Members.forEach(function(member){ _%> 5 | <%_if (locals.StringMembers){_%> 6 | <%- member.Name %> = "<%- member.Name %>", 7 | <%_} else {_%> 8 | <%- member.Name %> = <%- member.Value %>, 9 | <%_}_%> 10 | <%_}); _%> 11 | } 12 | -------------------------------------------------------------------------------- /_templates/function.ejs: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { <%-referencedTypes["WebApiExecuteRequest"].name%> } from "<%-referencedTypes["WebApiExecuteRequest"].import%>"; 3 | import { <%-referencedTypes["StructuralProperty"].name%> } from "<%-referencedTypes["StructuralProperty"].import%>"; 4 | import { <%-referencedTypes["OperationType"].name%> } from "<%-referencedTypes["OperationType"].import%>"; 5 | 6 | // Action <%-Name%> 7 | export const <%-Name%>Metadata = { 8 | <%_ if (IsBound){ _%> 9 | boundParameter: "entity", 10 | <%_}_%> 11 | parameterTypes: { 12 | <%_ locals.Parameters.forEach(function(param){ _%> 13 | "<%- param.Name %>": { 14 | typeName: "<%-param.Type %>", 15 | structuralProperty: StructuralProperty.<%- param.structuralTypeName %> 16 | }, 17 | <%});%> 18 | }, 19 | operationType: OperationType.Function, 20 | operationName: "<%-Name%>" 21 | }; 22 | 23 | export interface <%- Name %>Request extends <%-referencedTypes["WebApiExecuteRequest"].name%> { 24 | <%_locals.Parameters.forEach(function(param){ _%> 25 | <%-param.Name%><%-(param.IsRequired || param.Name==locals.BindingParameter ? ": " : "?: ") _%><%-param.TypescriptTypes.map(function(outputType){ 26 | return (outputType.importLocation ? `import("${outputType.importLocation}").` : "") + outputType.name}).join(" | ")%>; 27 | <%_ }) _%> 28 | } -------------------------------------------------------------------------------- /_templates/metadata.ejs: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | <%_ EntityTypes.forEach(function(e){_%> 3 | import { <%-e.Name%>Metadata } from "./entities/<%-e.SchemaName%>"; 4 | <%_})_%> 5 | <%_ Actions.forEach(function(e){_%> 6 | import { <%-e.Name%>Metadata } from "./actions/<%-e.Name%>"; 7 | <%_})_%> 8 | <%_ Functions.forEach(function(e){_%> 9 | import { <%-e.Name%>Metadata } from "./functions/<%-e.Name%>"; 10 | <%_})_%> 11 | 12 | export const Entities = { 13 | <%_ EntityTypes.forEach(function(e){_%> 14 | <%-e.SchemaName%>: "<%-e.Name%>", 15 | <%_})_%> 16 | }; 17 | 18 | // Setup Metadata 19 | // Usage: setMetadataCache(metadataCache); 20 | export const metadataCache = { 21 | entities: { 22 | <%_ EntityTypes.forEach(function(e){_%> 23 | <%-e.Name%>: <%-e.Name%>Metadata, 24 | <%_})_%> 25 | }, 26 | actions: { 27 | <%_ Actions.forEach(function(e){_%> 28 | <%-e.Name%>: <%-e.Name%>Metadata, 29 | <%_})_%> 30 | <%_ Functions.forEach(function(e){_%> 31 | <%-e.Name%>: <%-e.Name%>Metadata, 32 | <%_})_%> 33 | } 34 | }; -------------------------------------------------------------------------------- /config/test.yaml: -------------------------------------------------------------------------------- 1 | # Configuration file for running integration tests 2 | # host is defined in the .env file or by environment variables when running inside the ci pipeline 3 | nodewebapi: 4 | server: 5 | version: 9.2 -------------------------------------------------------------------------------- /dist-tests/jasmine-4.1.1/boot0.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2022 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | /** 24 | This file starts the process of "booting" Jasmine. It initializes Jasmine, 25 | makes its globals available, and creates the env. This file should be loaded 26 | after `jasmine.js` and `jasmine_html.js`, but before `boot1.js` or any project 27 | source files or spec files are loaded. 28 | */ 29 | (function() { 30 | var jasmineRequire = window.jasmineRequire || require('./jasmine.js'); 31 | 32 | /** 33 | * ## Require & Instantiate 34 | * 35 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. 36 | */ 37 | var jasmine = jasmineRequire.core(jasmineRequire), 38 | global = jasmine.getGlobal(); 39 | global.jasmine = jasmine; 40 | 41 | /** 42 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. 43 | */ 44 | jasmineRequire.html(jasmine); 45 | 46 | /** 47 | * Create the Jasmine environment. This is used to run all specs in a project. 48 | */ 49 | var env = jasmine.getEnv(); 50 | 51 | /** 52 | * ## The Global Interface 53 | * 54 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. 55 | */ 56 | var jasmineInterface = jasmineRequire.interface(jasmine, env); 57 | 58 | /** 59 | * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. 60 | */ 61 | for (var property in jasmineInterface) { 62 | global[property] = jasmineInterface[property]; 63 | } 64 | })(); 65 | -------------------------------------------------------------------------------- /dist-tests/jasmine-4.1.1/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdurow/dataverse-ify/0c8321d10b69556b2941aa2f330611ff67b020b6/dist-tests/jasmine-4.1.1/jasmine_favicon.png -------------------------------------------------------------------------------- /dist-tests/test-runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner v4.1.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/using-without-service-client.md: -------------------------------------------------------------------------------- 1 | # Using dataverse‐ify without the CdsServiceClient 2 | ## "I don't want to use your stinky DataverseClient class..." 3 | 4 | That's fine, you can still get the benefit from `dataverse-ify` by using `odataify` and `sdkify` to move objects back and forth between the `WebApi` format and the SDK style format: 5 | 6 | ``` 7 | const sdkStyleEntity = { 8 | logicalName: opportunityMetadata.logicalName, 9 | customerid: new EntityReference("account", "123"), 10 | } as Opportunity; 11 | 12 | const webapiStyleEntity = odataify("Create",sdkStyleEntity); 13 | await Xrm.WebApi.create("opportunity", webapiStyleEntity); 14 | ``` 15 | 16 | This is basically what the `DataverseClient` implementations does internally. See [quick-start](quick-start.md). 17 | 18 | Then when you retrieve a record you can do the reverse using: 19 | ``` 20 | const response = await Xrm.WebApi.retrieveRecord("opportunity", id, query); 21 | const sdkified = (await sdkify(response, "opportunity")) as Opportunity; 22 | ``` 23 | 24 | For some visibility of the transformations - take a look at some of the [unit tests](https://github.com/scottdurow/dataverse-ify/tree/master/src/dataverse-ify/__tests__/unit-tests). 25 | 26 | The same goes for functions and actions: 27 | ``` 28 | const winRequest = { 29 | logicalName: WinOpportunityMetadata.operationName, 30 | Status: 3, 31 | OpportunityClose: { 32 | logicalName: opportunitycloseMetadata.logicalName, 33 | description: "Sample Opportunity Close", 34 | subject: "Sample", 35 | 36 | opportunityid: new EntityReference(opportunityMetadata.logicalName, "5deb00bd-5685-ea11-a812-000d3a7f4cf5"), 37 | }, 38 | } as WinOpportunityRequest; 39 | 40 | const webapiRequest = await odataify("Action",winRequest); 41 | const response = await Xrm.WebApi.execute(webapiRequest); 42 | 43 | ``` -------------------------------------------------------------------------------- /docs/why-metadata.md: -------------------------------------------------------------------------------- 1 | # Why generate metadata? 2 | 3 | Unlike the old SOAP endpoint, much of the metadata about requests and responses cannot be inferred from the types. For instance, Integers, Floats, Money, Decimal, Optionsets all are transmitted as simple Numbers. Additionally, much of the navigation properties rely on prior knowledge of the structure of the metadata. 4 | 5 | For this reason, `dataverse-ify` expects some metadata to be available when using `odataify` and `sdkify`. This metadata is output when generating the types with `dataverse-gen` (see [quick-start](quick-start.md) ). 6 | 7 | You can then use the metadata in its entirety using: 8 | ``` 9 | setMetadataCache(metadataCache); 10 | ``` 11 | 12 | Or if you want to use just a subset, you can use: 13 | ``` 14 | setMetadataCache({ 15 | entities: { 16 | opportunity: opportunityMetadata, 17 | opportunityclose: opportunitycloseMetadata, 18 | }, 19 | actions: { WinOpportunity: WinOpportunityMetadata }, 20 | }); 21 | ``` 22 | 23 | The metadata objects are constants that are defined in the entity/action/function typescript files output from `dataverse-gen`. 24 | 25 | If you wanted to you could populate the metadata yourself - or even dynamically load it from the server at runtime. 26 | The metadata takes the form: 27 | ``` 28 | export const queueitemMetadata = { 29 | typeName: "mscrm.queueitem", 30 | logicalName: "queueitem", 31 | collectionName: "queueitems", 32 | primaryIdAttribute: "queueitemid", 33 | attributeTypes: { 34 | // Numeric Types - so we can convert to the right kind of number 35 | exchangerate: "Decimal", 36 | status: "Integer", 37 | ... 38 | // Optionsets 39 | statecode: "Optionset", 40 | ... 41 | // Date Formats 42 | workeridmodifiedon: "DateOnly:UserLocal", 43 | ... 44 | }, 45 | // Navigation to indicate which entity logical names are valid for the relationship 46 | navigation: { 47 | transactioncurrencyid: ["mscrm.transactioncurrency"], 48 | queueid: ["mscrm.queue"], 49 | workerid: ["systemuser","team"], 50 | ... 51 | }, 52 | }; 53 | ``` -------------------------------------------------------------------------------- /integration-test-solution/README.md: -------------------------------------------------------------------------------- 1 | # Integration Tests 2 | This folder contains the solution (managed and unmanaged) you will need to install 3 | in order to integration test dataverse-ify against a Microsoft Dataverse D365 Environment. Taken the decision to use D365 for integration tests because it creates lots of interesting metadata! 4 | 5 | Integration tests differ from the unit tests in that they actually test the functionality 6 | against Dataverse it's self rather than mocking the server calls. 7 | 8 | To get integration tests running you will need to: 9 | 1. Create a auth token for your integration test environment:\ 10 | ``` 11 | Use dataverse-auth org.crm.dynamics.com 12 | ``` 13 | 2. Set the target environment in the integration test configuration at `\config\test.yaml`\ 14 | ``` 15 | server: 16 | host: https://org.crm.dynamics.com 17 | ``` 18 | 19 | 3. Ensure integration tests are set to run in the same config 20 | ``` 21 | runIntegrationTests: true 22 | ``` 23 | 24 | 4. From command line run jest tests 25 | ``` 26 | jest 27 | ``` 28 | 29 | 5. Optionally you can run specific tests, e.g. 30 | ``` 31 | jest activity.test.ts 32 | ``` -------------------------------------------------------------------------------- /integration-test-solution/cdsifyintegrationtests.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdurow/dataverse-ify/0c8321d10b69556b2941aa2f330611ff67b020b6/integration-test-solution/cdsifyintegrationtests.zip -------------------------------------------------------------------------------- /integration-test-solution/cdsifyintegrationtests_managed.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdurow/dataverse-ify/0c8321d10b69556b2941aa2f330611ff67b020b6/integration-test-solution/cdsifyintegrationtests_managed.zip -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | roots: ["/src/"], 5 | testPathIgnorePatterns: ["test-values.ts"], 6 | globals: { 7 | "ts-jest": { 8 | tsconfig: "tsconfig.json", 9 | }, 10 | }, 11 | globalSetup: "./test-setup.js", 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dataverse-ify", 3 | "version": "2.0.8", 4 | "description": "Easily call the Microsoft Dataverse WebApi using SDK style types. Works with 'dataverse-gen' to create early bound classes.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "test": "jest", 9 | "ci-test": "jest --runInBand --ci", 10 | "unit-test": "jest unit-tests/", 11 | "integration-test": "jest integration-tests/", 12 | "browser-tests-build": "webpack --config webpack.config.js --watch", 13 | "start-browser": "webpack --config webpack.config.js --watch", 14 | "prepublish-test": "npm run ci-test", 15 | "preversion": "npm run prepublish-test", 16 | "version": "npm run build", 17 | "postversion": "git push && git push --tags", 18 | "prepublishOnly": "npm version patch", 19 | "lint": "eslint src --ext .ts", 20 | "lint:fix": "npm run lint -- --fix", 21 | "coverage": "jest --coverage" 22 | }, 23 | "author": "Scott Durow", 24 | "license": "MIT", 25 | "dependencies": { 26 | "@azure/msal-node": "^2.16.2", 27 | "config": "^3.3.7", 28 | "cryptr": "^6.0.3", 29 | "fs": "^0.0.1-security", 30 | "https-proxy-agent": "^5.0.1", 31 | "node-fetch": "^2.6.7", 32 | "os": "^0.1.2" 33 | }, 34 | "devDependencies": { 35 | "@microsoft/eslint-plugin-power-apps": "^0.2.2", 36 | "@types/config": "^0.0.41", 37 | "@types/cryptr": "^4.0.1", 38 | "@types/jest": "^27.5.1", 39 | "@types/node-fetch": "^2.6.1", 40 | "@types/request": "^2.48.8", 41 | "@types/xrm": "^9.0.58", 42 | "@typescript-eslint/eslint-plugin": "^5.16.0", 43 | "@typescript-eslint/parser": "^5.16.0", 44 | "dotenv": "^16.0.1", 45 | "eslint": "^8.12.0", 46 | "eslint-config-prettier": "^8.5.0", 47 | "eslint-plugin-prettier": "^4.0.0", 48 | "eslint-plugin-sonarjs": "^0.13.0", 49 | "expect": "^28.1.0", 50 | "jest": "^28.1.0", 51 | "jest-environment-jsdom": "^28.1.0", 52 | "jest-mock": "^28.1.0", 53 | "prettier": "^2.6.1", 54 | "process": "^0.11.10", 55 | "syswide-cas": "^5.3.0", 56 | "ts-jest": "^28.0.3", 57 | "ts-loader": "^9.3.0", 58 | "typescript": "^4.6.4", 59 | "webpack": "^5.72.1", 60 | "webpack-cli": "^4.9.2", 61 | "webpack-merge": "^5.8.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /postinstall.js: -------------------------------------------------------------------------------- 1 | console.log(`\n🙌 Thank you for using dataverse-ify version 1.🙌 2 | Please consider upgrading to version 2. 3 | There are some minor breaking changes. Read more at https://github.com/scottdurow/dataverse-ify`); -------------------------------------------------------------------------------- /src/browser-tests/README.md: -------------------------------------------------------------------------------- 1 | # Dataverse-ify browser tests 2 | Dataverse-ify integration tests can be run inside the browser without the node Xrm global context setup - but instead using the native Xrm global context. 3 | This ensures that the behavior is consistent between both the node and native implementations. 4 | 5 | ## How it works 6 | 7 | The browser tests use the jasmine test runner since the api almost exactly matches the jest api. 8 | 9 | There are some pollyfills added in `src\browser-tests\index.ts` to provide the missing jest features. 10 | 11 | ## Running the tests 12 | 13 | To run the browser tests: 14 | 15 | 1. Run `npm run browser-tests-build`. This will use web-pack to create the `dist-tests\browser-test.js` next to the `browser-tests.html` page. 16 | 17 | 2. Configure a Fiddler autoresponder: 18 | `REGEX:(?insx).+\/cdsify_\/jasmine\/(?'fname'[^?]*.*))` 19 | 20 | `\dist-tests\${fname}` 21 | 22 | 3. In the browser, open the dataverse-ify integration test solution model-driven app. 23 | -------------------------------------------------------------------------------- /src/browser-tests/SetupGlobalContext.ts: -------------------------------------------------------------------------------- 1 | export async function SetupGlobalContext() { 2 | //noop 3 | } 4 | -------------------------------------------------------------------------------- /src/browser-tests/config-browser.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 2 | export function get(): any { 3 | return {}; 4 | } 5 | -------------------------------------------------------------------------------- /src/dataverse-gen/actions/AddToQueue.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { WebApiExecuteRequest } from "../../types/WebApiExecuteRequest"; 3 | import { StructuralProperty } from "../../types/StructuralProperty"; 4 | import { OperationType } from "../../types/OperationType"; 5 | 6 | // Action AddToQueue 7 | export const AddToQueueMetadata = { 8 | boundParameter: "entity", 9 | parameterTypes: { 10 | "entity": { 11 | typeName: "mscrm.queue", 12 | structuralProperty: StructuralProperty.EntityType 13 | }, 14 | "Target": { 15 | typeName: "mscrm.crmbaseentity", 16 | structuralProperty: StructuralProperty.EntityType 17 | }, 18 | "SourceQueue": { 19 | typeName: "mscrm.queue", 20 | structuralProperty: StructuralProperty.EntityType 21 | }, 22 | "QueueItemProperties": { 23 | typeName: "mscrm.queueitem", 24 | structuralProperty: StructuralProperty.EntityType 25 | }, 26 | 27 | }, 28 | operationType: OperationType.Action, 29 | operationName: "AddToQueue" 30 | }; 31 | 32 | export interface AddToQueueRequest extends WebApiExecuteRequest { 33 | entity?: import("../../types/EntityReference").EntityReference | import("../entities/Queue").Queue; 34 | Target?: any; 35 | SourceQueue?: import("../../types/EntityReference").EntityReference | import("../entities/Queue").Queue; 36 | QueueItemProperties?: import("../../types/EntityReference").EntityReference | import("../entities/QueueItem").QueueItem; 37 | } -------------------------------------------------------------------------------- /src/dataverse-gen/actions/WinOpportunity.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { WebApiExecuteRequest } from "../../types/WebApiExecuteRequest"; 3 | import { StructuralProperty } from "../../types/StructuralProperty"; 4 | import { OperationType } from "../../types/OperationType"; 5 | 6 | // Action WinOpportunity 7 | export const WinOpportunityMetadata = { 8 | parameterTypes: { 9 | "OpportunityClose": { 10 | typeName: "mscrm.opportunityclose", 11 | structuralProperty: StructuralProperty.EntityType 12 | }, 13 | "Caller": { 14 | typeName: "Edm.String", 15 | structuralProperty: StructuralProperty.PrimitiveType 16 | }, 17 | "Status": { 18 | typeName: "Edm.Int32", 19 | structuralProperty: StructuralProperty.PrimitiveType 20 | }, 21 | 22 | }, 23 | operationType: OperationType.Action, 24 | operationName: "WinOpportunity" 25 | }; 26 | 27 | export interface WinOpportunityRequest extends WebApiExecuteRequest { 28 | OpportunityClose?: import("../../types/EntityReference").EntityReference | import("../entities/OpportunityClose").OpportunityClose; 29 | Caller?: string; 30 | Status?: number; 31 | } -------------------------------------------------------------------------------- /src/dataverse-gen/actions/cdsify_BoundCollectionEcho.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { WebApiExecuteRequest } from "../../types/WebApiExecuteRequest"; 3 | import { StructuralProperty } from "../../types/StructuralProperty"; 4 | import { OperationType } from "../../types/OperationType"; 5 | 6 | // Action cdsify_BoundCollectionEcho 7 | export const cdsify_BoundCollectionEchoMetadata = { 8 | boundParameter: "entityset", 9 | parameterTypes: { 10 | "entityset": { 11 | typeName: "mscrm.cdsify_integrationtest", 12 | structuralProperty: StructuralProperty.Collection 13 | }, 14 | "cdsify_BoundCollectionEchoInString": { 15 | typeName: "Edm.String", 16 | structuralProperty: StructuralProperty.PrimitiveType 17 | }, 18 | 19 | }, 20 | operationType: OperationType.Action, 21 | operationName: "cdsify_BoundCollectionEcho" 22 | }; 23 | 24 | export interface cdsify_BoundCollectionEchoRequest extends WebApiExecuteRequest { 25 | entityset?: import("../../types/EntityReference").EntityReference[] | import("../entities/cdsify_IntegrationTest").cdsify_IntegrationTest[]; 26 | cdsify_BoundCollectionEchoInString?: string; 27 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/AddToQueueResponse.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface AddToQueueResponse { 3 | QueueItemId?: import("../../types/Guid").Guid; 4 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/AssociatedMenuConfiguration.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface AssociatedMenuConfiguration { 3 | AvailableOffline?: boolean; 4 | Behavior?: import("../enums/AssociatedMenuBehavior").AssociatedMenuBehavior; 5 | Group?: import("../enums/AssociatedMenuGroup").AssociatedMenuGroup; 6 | Icon?: string; 7 | IsCustomizable?: boolean; 8 | Label?: import("../complextypes/Label").Label; 9 | MenuId?: string; 10 | Order?: number; 11 | QueryApi?: string; 12 | ViewId?: import("../../types/Guid").Guid; 13 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/AttributeQueryExpression.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface AttributeQueryExpression { 3 | Criteria?: import("../complextypes/MetadataFilterExpression").MetadataFilterExpression; 4 | Properties?: import("../complextypes/MetadataPropertiesExpression").MetadataPropertiesExpression; 5 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/AttributeRequiredLevelManagedProperty.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface AttributeRequiredLevelManagedProperty { 3 | CanBeChanged?: boolean; 4 | ManagedPropertyLogicalName?: string; 5 | Value?: import("../enums/AttributeRequiredLevel").AttributeRequiredLevel; 6 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/AttributeTypeDisplayName.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface AttributeTypeDisplayName { 3 | Value?: string; 4 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/BooleanManagedProperty.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface BooleanManagedProperty { 3 | CanBeChanged?: boolean; 4 | ManagedPropertyLogicalName?: string; 5 | Value?: boolean; 6 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/CalculateTotalTimeIncidentResponse.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface CalculateTotalTimeIncidentResponse { 3 | TotalTime?: number; 4 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/CascadeConfiguration.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface CascadeConfiguration { 3 | Archive?: import("../enums/CascadeType").CascadeType; 4 | Assign?: import("../enums/CascadeType").CascadeType; 5 | Delete?: import("../enums/CascadeType").CascadeType; 6 | Merge?: import("../enums/CascadeType").CascadeType; 7 | Reparent?: import("../enums/CascadeType").CascadeType; 8 | RollupView?: import("../enums/CascadeType").CascadeType; 9 | Share?: import("../enums/CascadeType").CascadeType; 10 | Unshare?: import("../enums/CascadeType").CascadeType; 11 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/ComplexAttributeMetadata.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface ComplexAttributeMetadata { 3 | AttributeOf?: string; 4 | AttributeType?: import("../enums/AttributeTypeCode").AttributeTypeCode; 5 | AttributeTypeName?: import("../complextypes/AttributeTypeDisplayName").AttributeTypeDisplayName; 6 | AutoNumberFormat?: string; 7 | CanBeSecuredForCreate?: boolean; 8 | CanBeSecuredForRead?: boolean; 9 | CanBeSecuredForUpdate?: boolean; 10 | CanModifyAdditionalSettings?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 11 | ColumnNumber?: number; 12 | CreatedOn?: Date; 13 | DeprecatedVersion?: string; 14 | Description?: import("../complextypes/Label").Label; 15 | DisplayName?: import("../complextypes/Label").Label; 16 | EntityLogicalName?: string; 17 | ExternalName?: string; 18 | HasChanged?: boolean; 19 | InheritsFrom?: string; 20 | IntroducedVersion?: string; 21 | IsAuditEnabled?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 22 | IsCustomAttribute?: boolean; 23 | IsCustomizable?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 24 | IsDataSourceSecret?: boolean; 25 | IsFilterable?: boolean; 26 | IsGlobalFilterEnabled?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 27 | IsLogical?: boolean; 28 | IsManaged?: boolean; 29 | IsPrimaryId?: boolean; 30 | IsPrimaryName?: boolean; 31 | IsRenameable?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 32 | IsRequiredForForm?: boolean; 33 | IsRetrievable?: boolean; 34 | IsSearchable?: boolean; 35 | IsSecured?: boolean; 36 | IsSortableEnabled?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 37 | IsValidForAdvancedFind?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 38 | IsValidForCreate?: boolean; 39 | IsValidForForm?: boolean; 40 | IsValidForGrid?: boolean; 41 | IsValidForRead?: boolean; 42 | IsValidForUpdate?: boolean; 43 | IsValidODataAttribute?: boolean; 44 | LinkedAttributeId?: import("../../types/Guid").Guid; 45 | LogicalName?: string; 46 | MetadataId?: import("../../types/Guid").Guid; 47 | ModifiedOn?: Date; 48 | RequiredLevel?: import("../complextypes/AttributeRequiredLevelManagedProperty").AttributeRequiredLevelManagedProperty; 49 | SchemaName?: string; 50 | Settings?: import("../complextypes/EntitySetting").EntitySetting[]; 51 | SourceType?: number; 52 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/ComplexEntityKeyMetadata.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface ComplexEntityKeyMetadata { 3 | DisplayName?: import("../complextypes/Label").Label; 4 | EntityKeyIndexStatus?: import("../enums/EntityKeyIndexStatus").EntityKeyIndexStatus; 5 | EntityLogicalName?: string; 6 | HasChanged?: boolean; 7 | IntroducedVersion?: string; 8 | IsCustomizable?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 9 | IsExportKey?: boolean; 10 | IsManaged?: boolean; 11 | IsSecondaryKey?: boolean; 12 | IsSynchronous?: boolean; 13 | KeyAttributes?: string[]; 14 | LogicalName?: string; 15 | MetadataId?: import("../../types/Guid").Guid; 16 | SchemaName?: string; 17 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/ComplexManyToManyRelationshipMetadata.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface ComplexManyToManyRelationshipMetadata { 3 | Entity1AssociatedMenuConfiguration?: import("../complextypes/AssociatedMenuConfiguration").AssociatedMenuConfiguration; 4 | Entity1IntersectAttribute?: string; 5 | Entity1LogicalName?: string; 6 | Entity1NavigationPropertyName?: string; 7 | Entity2AssociatedMenuConfiguration?: import("../complextypes/AssociatedMenuConfiguration").AssociatedMenuConfiguration; 8 | Entity2IntersectAttribute?: string; 9 | Entity2LogicalName?: string; 10 | Entity2NavigationPropertyName?: string; 11 | HasChanged?: boolean; 12 | IntersectEntityName?: string; 13 | IntroducedVersion?: string; 14 | IsCustomRelationship?: boolean; 15 | IsCustomizable?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 16 | IsManaged?: boolean; 17 | IsValidForAdvancedFind?: boolean; 18 | MetadataId?: import("../../types/Guid").Guid; 19 | RelationshipType?: import("../enums/RelationshipType").RelationshipType; 20 | SchemaName?: string; 21 | SecurityTypes?: import("../enums/SecurityTypes").SecurityTypes; 22 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/ComplexOneToManyRelationshipMetadata.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface ComplexOneToManyRelationshipMetadata { 3 | AssociatedMenuConfiguration?: import("../complextypes/AssociatedMenuConfiguration").AssociatedMenuConfiguration; 4 | CascadeConfiguration?: import("../complextypes/CascadeConfiguration").CascadeConfiguration; 5 | DenormalizedAttributeName?: string; 6 | EntityKey?: string; 7 | HasChanged?: boolean; 8 | IntroducedVersion?: string; 9 | IsCustomRelationship?: boolean; 10 | IsCustomizable?: import("../complextypes/BooleanManagedProperty").BooleanManagedProperty; 11 | IsDenormalizedLookup?: boolean; 12 | IsHierarchical?: boolean; 13 | IsManaged?: boolean; 14 | IsRelationshipAttributeDenormalized?: boolean; 15 | IsValidForAdvancedFind?: boolean; 16 | MetadataId?: import("../../types/Guid").Guid; 17 | ReferencedAttribute?: string; 18 | ReferencedEntity?: string; 19 | ReferencedEntityNavigationPropertyName?: string; 20 | ReferencingAttribute?: string; 21 | ReferencingEntity?: string; 22 | ReferencingEntityNavigationPropertyName?: string; 23 | RelationshipAttributes?: import("../complextypes/RelationshipAttribute").RelationshipAttribute[]; 24 | RelationshipBehavior?: number; 25 | RelationshipType?: import("../enums/RelationshipType").RelationshipType; 26 | SchemaName?: string; 27 | SecurityTypes?: import("../enums/SecurityTypes").SecurityTypes; 28 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/DeletedMetadataCollection.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface DeletedMetadataCollection { 3 | Count?: number; 4 | IsReadOnly?: boolean; 5 | Keys?: import("../enums/DeletedMetadataFilters").DeletedMetadataFilters[]; 6 | Values?: import("../complextypes/GuidCollection").GuidCollection[]; 7 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/EntityKeyQueryExpression.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface EntityKeyQueryExpression { 3 | Criteria?: import("../complextypes/MetadataFilterExpression").MetadataFilterExpression; 4 | Properties?: import("../complextypes/MetadataPropertiesExpression").MetadataPropertiesExpression; 5 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/EntityQueryExpression.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface EntityQueryExpression { 3 | AttributeQuery?: import("../complextypes/AttributeQueryExpression").AttributeQueryExpression; 4 | Criteria?: import("../complextypes/MetadataFilterExpression").MetadataFilterExpression; 5 | KeyQuery?: import("../complextypes/EntityKeyQueryExpression").EntityKeyQueryExpression; 6 | LabelQuery?: import("../complextypes/LabelQueryExpression").LabelQueryExpression; 7 | Properties?: import("../complextypes/MetadataPropertiesExpression").MetadataPropertiesExpression; 8 | RelationshipQuery?: import("../complextypes/RelationshipQueryExpression").RelationshipQueryExpression; 9 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/EntitySetting.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface EntitySetting { 3 | ChildSettings?: import("../complextypes/EntitySetting").EntitySetting[]; 4 | Name?: string; 5 | Value?: any; 6 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/GuidCollection.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface GuidCollection { 3 | Items?: import("../../types/Guid").Guid[]; 4 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/Label.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface Label { 3 | LocalizedLabels?: import("../complextypes/LocalizedLabel").LocalizedLabel[]; 4 | UserLocalizedLabel?: import("../complextypes/LocalizedLabel").LocalizedLabel; 5 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/LabelQueryExpression.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface LabelQueryExpression { 3 | FilterLanguages?: number[]; 4 | MissingLabelBehavior?: number; 5 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/LocalizedLabel.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface LocalizedLabel { 3 | HasChanged?: boolean; 4 | IsManaged?: boolean; 5 | Label?: string; 6 | LanguageCode?: number; 7 | MetadataId?: import("../../types/Guid").Guid; 8 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/MetadataConditionExpression.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface MetadataConditionExpression { 3 | ConditionOperator?: import("../enums/MetadataConditionOperator").MetadataConditionOperator; 4 | PropertyName?: string; 5 | Value?: import("../complextypes/ObjectValue").ObjectValue; 6 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/MetadataFilterExpression.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface MetadataFilterExpression { 3 | Conditions?: import("../complextypes/MetadataConditionExpression").MetadataConditionExpression[]; 4 | FilterOperator?: import("../enums/LogicalOperator").LogicalOperator; 5 | Filters?: import("../complextypes/MetadataFilterExpression").MetadataFilterExpression[]; 6 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/MetadataPropertiesExpression.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface MetadataPropertiesExpression { 3 | AllProperties?: boolean; 4 | PropertyNames?: string[]; 5 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/ObjectValue.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface ObjectValue { 3 | Type?: string; 4 | Value?: string; 5 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/RelationshipAttribute.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface RelationshipAttribute { 3 | ReferencedAttributeName?: string; 4 | ReferencingAttributeName?: string; 5 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/RelationshipQueryExpression.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface RelationshipQueryExpression { 3 | Criteria?: import("../complextypes/MetadataFilterExpression").MetadataFilterExpression; 4 | Properties?: import("../complextypes/MetadataPropertiesExpression").MetadataPropertiesExpression; 5 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/RetrieveMetadataChangesResponse.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface RetrieveMetadataChangesResponse { 3 | DeletedMetadata?: import("../complextypes/DeletedMetadataCollection").DeletedMetadataCollection; 4 | EntityMetadata?: import("../complextypes/ComplexEntityMetadata").ComplexEntityMetadata[]; 5 | ServerVersionStamp?: string; 6 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/SecurityPrivilegeMetadata.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface SecurityPrivilegeMetadata { 3 | CanBeBasic?: boolean; 4 | CanBeDeep?: boolean; 5 | CanBeEntityReference?: boolean; 6 | CanBeGlobal?: boolean; 7 | CanBeLocal?: boolean; 8 | CanBeParentEntityReference?: boolean; 9 | Name?: string; 10 | PrivilegeId?: import("../../types/Guid").Guid; 11 | PrivilegeType?: import("../enums/PrivilegeType").PrivilegeType; 12 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/cdsify_BoundCollectionEchoResponse.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface cdsify_BoundCollectionEchoResponse { 3 | cdsify_BoundCollectionEchoOutString?: string; 4 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/cdsify_BoundEchoFunctionResponse.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface cdsify_BoundEchoFunctionResponse { 3 | cdsify_BoundEchoFunctionOutInteger?: number; 4 | cdsify_BoundEchoFunctionOutString?: string; 5 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/cdsify_BoundEchoResponse.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface cdsify_BoundEchoResponse { 3 | cdsify_BoundOutBoolean?: boolean; 4 | cdsify_BoundOutDateTime?: Date; 5 | cdsify_BoundOutDecimal?: number; 6 | cdsify_BoundOutFloat?: number; 7 | cdsify_BoundOutGuid?: import("../../types/Guid").Guid; 8 | cdsify_BoundOutInteger?: number; 9 | cdsify_BoundOutMoney?: number; 10 | cdsify_BoundOutPicklist?: number; 11 | cdsify_BoundOutString?: string; 12 | cdsify_BoundOutStringArray?: string[]; 13 | cdsify_BoundOutEntity?: import("../entities/cdsify_IntegrationTest").cdsify_IntegrationTest; 14 | cdsify_BoundOutEntityCollection?: any[]; 15 | cdsify_BoundOutEntityReference?: import("../entities/cdsify_IntegrationTest").cdsify_IntegrationTest; 16 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/cdsify_UnboundEchoFunction2Response.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface cdsify_UnboundEchoFunction2Response { 3 | cdsify_UnboundFunctionOutBoolean?: boolean; 4 | cdsify_UnboundFunctionOutDateTime?: Date; 5 | cdsify_UnboundFunctionOutDecimal?: number; 6 | cdsify_UnboundFunctionOutFloat?: number; 7 | cdsify_UnboundFunctionOutInteger?: number; 8 | cdsify_UnboundFunctionOutString?: string; 9 | cdsify_UnboundFunctionOutStringArray?: string[]; 10 | } -------------------------------------------------------------------------------- /src/dataverse-gen/complextypes/cdsify_UnboundEchoResponse.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export interface cdsify_UnboundEchoResponse { 3 | cdsify_UnboundOutBoolean?: boolean; 4 | cdsify_UnboundOutDateTime?: Date; 5 | cdsify_UnboundOutDecimal?: number; 6 | cdsify_UnboundOutFloat?: number; 7 | cdsify_UnboundOutGuid?: import("../../types/Guid").Guid; 8 | cdsify_UnboundOutInteger?: number; 9 | cdsify_UnboundOutMoney?: number; 10 | cdsify_UnboundOutPicklist?: number; 11 | cdsify_UnboundOutString?: string; 12 | cdsify_UnboundOutStringArray?: string[]; 13 | cdsify_UnboundOutEntity?: import("../entities/cdsify_IntegrationTest").cdsify_IntegrationTest; 14 | cdsify_UnboundOutEntityCollection?: any[]; 15 | cdsify_UnboundOutEntityReference?: import("../entities/cdsify_IntegrationTest").cdsify_IntegrationTest; 16 | } -------------------------------------------------------------------------------- /src/dataverse-gen/enums/AssociatedMenuBehavior.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum AssociatedMenuBehavior 3 | export const enum AssociatedMenuBehavior { 4 | UseCollectionName = "UseCollectionName", 5 | UseLabel = "UseLabel", 6 | DoNotDisplay = "DoNotDisplay", 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/AssociatedMenuGroup.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum AssociatedMenuGroup 3 | export const enum AssociatedMenuGroup { 4 | Details = "Details", 5 | Sales = "Sales", 6 | Service = "Service", 7 | Marketing = "Marketing", 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/AttributeRequiredLevel.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum AttributeRequiredLevel 3 | export const enum AttributeRequiredLevel { 4 | None = "None", 5 | SystemRequired = "SystemRequired", 6 | ApplicationRequired = "ApplicationRequired", 7 | Recommended = "Recommended", 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/AttributeTypeCode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum AttributeTypeCode 3 | export const enum AttributeTypeCode { 4 | Boolean = "Boolean", 5 | Customer = "Customer", 6 | DateTime = "DateTime", 7 | Decimal = "Decimal", 8 | Double = "Double", 9 | Integer = "Integer", 10 | Lookup = "Lookup", 11 | Memo = "Memo", 12 | Money = "Money", 13 | Owner = "Owner", 14 | PartyList = "PartyList", 15 | Picklist = "Picklist", 16 | State = "State", 17 | Status = "Status", 18 | String = "String", 19 | Uniqueidentifier = "Uniqueidentifier", 20 | CalendarRules = "CalendarRules", 21 | Virtual = "Virtual", 22 | BigInt = "BigInt", 23 | ManagedProperty = "ManagedProperty", 24 | EntityName = "EntityName", 25 | } 26 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/CascadeType.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum CascadeType 3 | export const enum CascadeType { 4 | NoCascade = "NoCascade", 5 | Cascade = "Cascade", 6 | Active = "Active", 7 | UserOwned = "UserOwned", 8 | RemoveLink = "RemoveLink", 9 | Restrict = "Restrict", 10 | } 11 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/DeletedMetadataFilters.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum DeletedMetadataFilters 3 | export const enum DeletedMetadataFilters { 4 | Default = "Default", 5 | Attribute = "Attribute", 6 | Relationship = "Relationship", 7 | Label = "Label", 8 | OptionSet = "OptionSet", 9 | All = "All", 10 | } 11 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/EntityKeyIndexStatus.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum EntityKeyIndexStatus 3 | export const enum EntityKeyIndexStatus { 4 | Pending = "Pending", 5 | InProgress = "InProgress", 6 | Active = "Active", 7 | Failed = "Failed", 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/LogicalOperator.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum LogicalOperator 3 | export const enum LogicalOperator { 4 | And = "And", 5 | Or = "Or", 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/MetadataConditionOperator.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum MetadataConditionOperator 3 | export const enum MetadataConditionOperator { 4 | Equals = "Equals", 5 | NotEquals = "NotEquals", 6 | In = "In", 7 | NotIn = "NotIn", 8 | GreaterThan = "GreaterThan", 9 | LessThan = "LessThan", 10 | } 11 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/OwnershipTypes.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum OwnershipTypes 3 | export const enum OwnershipTypes { 4 | None = "None", 5 | UserOwned = "UserOwned", 6 | TeamOwned = "TeamOwned", 7 | BusinessOwned = "BusinessOwned", 8 | OrganizationOwned = "OrganizationOwned", 9 | BusinessParented = "BusinessParented", 10 | Filtered = "Filtered", 11 | } 12 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/PrivilegeType.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum PrivilegeType 3 | export const enum PrivilegeType { 4 | None = "None", 5 | Create = "Create", 6 | Read = "Read", 7 | Write = "Write", 8 | Delete = "Delete", 9 | Assign = "Assign", 10 | Share = "Share", 11 | Append = "Append", 12 | AppendTo = "AppendTo", 13 | } 14 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/RelationshipType.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum RelationshipType 3 | export const enum RelationshipType { 4 | OneToManyRelationship = "OneToManyRelationship", 5 | ManyToManyRelationship = "ManyToManyRelationship", 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/SecurityTypes.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum SecurityTypes 3 | export const enum SecurityTypes { 4 | None = "None", 5 | Append = "Append", 6 | ParentChild = "ParentChild", 7 | Pointer = "Pointer", 8 | Inheritance = "Inheritance", 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_accountcategorycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_accountcategorycode 3 | export const enum account_account_accountcategorycode { 4 | PreferredCustomer = 1, 5 | Standard = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_accountclassificationcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_accountclassificationcode 3 | export const enum account_account_accountclassificationcode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_accountratingcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_accountratingcode 3 | export const enum account_account_accountratingcode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_address1_addresstypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_address1_addresstypecode 3 | export const enum account_account_address1_addresstypecode { 4 | BillTo = 1, 5 | ShipTo = 2, 6 | Primary = 3, 7 | Other = 4, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_address1_freighttermscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_address1_freighttermscode 3 | export const enum account_account_address1_freighttermscode { 4 | FOB = 1, 5 | NoCharge = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_address1_shippingmethodcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_address1_shippingmethodcode 3 | export const enum account_account_address1_shippingmethodcode { 4 | Airborne = 1, 5 | DHL = 2, 6 | FedEx = 3, 7 | UPS = 4, 8 | PostalMail = 5, 9 | FullLoad = 6, 10 | WillCall = 7, 11 | } 12 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_address2_addresstypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_address2_addresstypecode 3 | export const enum account_account_address2_addresstypecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_address2_freighttermscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_address2_freighttermscode 3 | export const enum account_account_address2_freighttermscode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_address2_shippingmethodcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_address2_shippingmethodcode 3 | export const enum account_account_address2_shippingmethodcode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_businesstypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_businesstypecode 3 | export const enum account_account_businesstypecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_customersizecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_customersizecode 3 | export const enum account_account_customersizecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_customertypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_customertypecode 3 | export const enum account_account_customertypecode { 4 | Competitor = 1, 5 | Consultant = 2, 6 | Customer = 3, 7 | Investor = 4, 8 | Partner = 5, 9 | Influencer = 6, 10 | Press = 7, 11 | Prospect = 8, 12 | Reseller = 9, 13 | Supplier = 10, 14 | Vendor = 11, 15 | Other = 12, 16 | } 17 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_industrycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_industrycode 3 | export const enum account_account_industrycode { 4 | Accounting = 1, 5 | AgricultureandNonpetrolNaturalResourceExtraction = 2, 6 | BroadcastingPrintingandPublishing = 3, 7 | Brokers = 4, 8 | BuildingSupplyRetail = 5, 9 | BusinessServices = 6, 10 | Consulting = 7, 11 | ConsumerServices = 8, 12 | DesignDirectionandCreativeManagement = 9, 13 | DistributorsDispatchersandProcessors = 10, 14 | DoctorsOfficesandClinics = 11, 15 | DurableManufacturing = 12, 16 | EatingandDrinkingPlaces = 13, 17 | EntertainmentRetail = 14, 18 | EquipmentRentalandLeasing = 15, 19 | Financial = 16, 20 | FoodandTobaccoProcessing = 17, 21 | InboundCapitalIntensiveProcessing = 18, 22 | InboundRepairandServices = 19, 23 | Insurance = 20, 24 | LegalServices = 21, 25 | NonDurableMerchandiseRetail = 22, 26 | OutboundConsumerService = 23, 27 | PetrochemicalExtractionandDistribution = 24, 28 | ServiceRetail = 25, 29 | SIGAffiliations = 26, 30 | SocialServices = 27, 31 | SpecialOutboundTradeContractors = 28, 32 | SpecialtyRealty = 29, 33 | Transportation = 30, 34 | UtilityCreationandDistribution = 31, 35 | VehicleRetail = 32, 36 | Wholesale = 33, 37 | } 38 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_ownershipcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_ownershipcode 3 | export const enum account_account_ownershipcode { 4 | Public = 1, 5 | Private = 2, 6 | Subsidiary = 3, 7 | Other = 4, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_paymenttermscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_paymenttermscode 3 | export const enum account_account_paymenttermscode { 4 | Net30 = 1, 5 | _210Net30 = 2, 6 | Net45 = 3, 7 | Net60 = 4, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_preferredappointmentdaycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_preferredappointmentdaycode 3 | export const enum account_account_preferredappointmentdaycode { 4 | Sunday = 0, 5 | Monday = 1, 6 | Tuesday = 2, 7 | Wednesday = 3, 8 | Thursday = 4, 9 | Friday = 5, 10 | Saturday = 6, 11 | } 12 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_preferredappointmenttimecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_preferredappointmenttimecode 3 | export const enum account_account_preferredappointmenttimecode { 4 | Morning = 1, 5 | Afternoon = 2, 6 | Evening = 3, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_preferredcontactmethodcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_preferredcontactmethodcode 3 | export const enum account_account_preferredcontactmethodcode { 4 | Any = 1, 5 | Email = 2, 6 | Phone = 3, 7 | Fax = 4, 8 | Mail = 5, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_shippingmethodcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_shippingmethodcode 3 | export const enum account_account_shippingmethodcode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_statecode 3 | export const enum account_account_statecode { 4 | Active = 0, 5 | Inactive = 1, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_statuscode 3 | export const enum account_account_statuscode { 4 | Active = 1, 5 | Inactive = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/account_account_territorycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum account_account_territorycode 3 | export const enum account_account_territorycode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/activitypointer_deliveryprioritycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum activitypointer_deliveryprioritycode 3 | export const enum activitypointer_deliveryprioritycode { 4 | Low = 0, 5 | Normal = 1, 6 | High = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/budgetstatus.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum budgetstatus 3 | export const enum budgetstatus { 4 | NoCommittedBudget = 0, 5 | MayBuy = 1, 6 | CanBuy = 2, 7 | WillBuy = 3, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/cdsify_integrationtest_cdsify_integrationtest_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum cdsify_integrationtest_cdsify_integrationtest_statecode 3 | export const enum cdsify_integrationtest_cdsify_integrationtest_statecode { 4 | Active = 0, 5 | Inactive = 1, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/cdsify_integrationtest_cdsify_integrationtest_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum cdsify_integrationtest_cdsify_integrationtest_statuscode 3 | export const enum cdsify_integrationtest_cdsify_integrationtest_statuscode { 4 | Active = 1, 5 | Inactive = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_accountrolecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_accountrolecode 3 | export const enum contact_contact_accountrolecode { 4 | DecisionMaker = 1, 5 | Employee = 2, 6 | Influencer = 3, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_address1_addresstypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_address1_addresstypecode 3 | export const enum contact_contact_address1_addresstypecode { 4 | BillTo = 1, 5 | ShipTo = 2, 6 | Primary = 3, 7 | Other = 4, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_address1_freighttermscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_address1_freighttermscode 3 | export const enum contact_contact_address1_freighttermscode { 4 | FOB = 1, 5 | NoCharge = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_address1_shippingmethodcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_address1_shippingmethodcode 3 | export const enum contact_contact_address1_shippingmethodcode { 4 | Airborne = 1, 5 | DHL = 2, 6 | FedEx = 3, 7 | UPS = 4, 8 | PostalMail = 5, 9 | FullLoad = 6, 10 | WillCall = 7, 11 | } 12 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_address2_addresstypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_address2_addresstypecode 3 | export const enum contact_contact_address2_addresstypecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_address2_freighttermscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_address2_freighttermscode 3 | export const enum contact_contact_address2_freighttermscode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_address2_shippingmethodcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_address2_shippingmethodcode 3 | export const enum contact_contact_address2_shippingmethodcode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_address3_addresstypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_address3_addresstypecode 3 | export const enum contact_contact_address3_addresstypecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_address3_freighttermscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_address3_freighttermscode 3 | export const enum contact_contact_address3_freighttermscode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_address3_shippingmethodcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_address3_shippingmethodcode 3 | export const enum contact_contact_address3_shippingmethodcode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_customersizecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_customersizecode 3 | export const enum contact_contact_customersizecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_customertypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_customertypecode 3 | export const enum contact_contact_customertypecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_educationcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_educationcode 3 | export const enum contact_contact_educationcode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_familystatuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_familystatuscode 3 | export const enum contact_contact_familystatuscode { 4 | Single = 1, 5 | Married = 2, 6 | Divorced = 3, 7 | Widowed = 4, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_gendercode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_gendercode 3 | export const enum contact_contact_gendercode { 4 | Male = 1, 5 | Female = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_haschildrencode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_haschildrencode 3 | export const enum contact_contact_haschildrencode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_leadsourcecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_leadsourcecode 3 | export const enum contact_contact_leadsourcecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_msdyn_orgchangestatus.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_msdyn_orgchangestatus 3 | export const enum contact_contact_msdyn_orgchangestatus { 4 | NoFeedback = 0, 5 | NotatCompany = 1, 6 | Ignore = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_paymenttermscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_paymenttermscode 3 | export const enum contact_contact_paymenttermscode { 4 | Net30 = 1, 5 | _210Net30 = 2, 6 | Net45 = 3, 7 | Net60 = 4, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_preferredappointmentdaycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_preferredappointmentdaycode 3 | export const enum contact_contact_preferredappointmentdaycode { 4 | Sunday = 0, 5 | Monday = 1, 6 | Tuesday = 2, 7 | Wednesday = 3, 8 | Thursday = 4, 9 | Friday = 5, 10 | Saturday = 6, 11 | } 12 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_preferredappointmenttimecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_preferredappointmenttimecode 3 | export const enum contact_contact_preferredappointmenttimecode { 4 | Morning = 1, 5 | Afternoon = 2, 6 | Evening = 3, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_preferredcontactmethodcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_preferredcontactmethodcode 3 | export const enum contact_contact_preferredcontactmethodcode { 4 | Any = 1, 5 | Email = 2, 6 | Phone = 3, 7 | Fax = 4, 8 | Mail = 5, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_shippingmethodcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_shippingmethodcode 3 | export const enum contact_contact_shippingmethodcode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_statecode 3 | export const enum contact_contact_statecode { 4 | Active = 0, 5 | Inactive = 1, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_statuscode 3 | export const enum contact_contact_statuscode { 4 | Active = 1, 5 | Inactive = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/contact_contact_territorycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum contact_contact_territorycode 3 | export const enum contact_contact_territorycode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_caseorigincode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_caseorigincode 3 | export const enum incident_caseorigincode { 4 | Phone = 1, 5 | Email = 2, 6 | Web = 3, 7 | Facebook = 2483, 8 | Twitter = 3986, 9 | IoT = 700610000, 10 | } 11 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_casetypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_casetypecode 3 | export const enum incident_incident_casetypecode { 4 | Question = 1, 5 | Problem = 2, 6 | Request = 3, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_contractservicelevelcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_contractservicelevelcode 3 | export const enum incident_incident_contractservicelevelcode { 4 | Gold = 1, 5 | Silver = 2, 6 | Bronze = 3, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_customersatisfactioncode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_customersatisfactioncode 3 | export const enum incident_incident_customersatisfactioncode { 4 | VeryDissatisfied = 1, 5 | Dissatisfied = 2, 6 | Neutral = 3, 7 | Satisfied = 4, 8 | VerySatisfied = 5, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_firstresponseslastatus.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_firstresponseslastatus 3 | export const enum incident_incident_firstresponseslastatus { 4 | InProgress = 1, 5 | NearingNoncompliance = 2, 6 | Succeeded = 3, 7 | Noncompliant = 4, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_incidentstagecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_incidentstagecode 3 | export const enum incident_incident_incidentstagecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_prioritycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_prioritycode 3 | export const enum incident_incident_prioritycode { 4 | High = 1, 5 | Normal = 2, 6 | Low = 3, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_resolvebyslastatus.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_resolvebyslastatus 3 | export const enum incident_incident_resolvebyslastatus { 4 | InProgress = 1, 5 | NearingNoncompliance = 2, 6 | Succeeded = 3, 7 | Noncompliant = 4, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_severitycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_severitycode 3 | export const enum incident_incident_severitycode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_statecode 3 | export const enum incident_incident_statecode { 4 | Active = 0, 5 | Resolved = 1, 6 | Cancelled = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/incident_incident_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum incident_incident_statuscode 3 | export const enum incident_incident_statuscode { 4 | InProgress = 1, 5 | OnHold = 2, 6 | WaitingforDetails = 3, 7 | Researching = 4, 8 | ProblemSolved = 5, 9 | Cancelled = 6, 10 | InformationProvided = 1000, 11 | Merged = 2000, 12 | } 13 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/initialcommunication.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum initialcommunication 3 | export const enum initialcommunication { 4 | Contacted = 0, 5 | NotContacted = 1, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/letter_letter_prioritycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum letter_letter_prioritycode 3 | export const enum letter_letter_prioritycode { 4 | Low = 0, 5 | Normal = 1, 6 | High = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/letter_letter_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum letter_letter_statecode 3 | export const enum letter_letter_statecode { 4 | Open = 0, 5 | Completed = 1, 6 | Canceled = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/letter_letter_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum letter_letter_statuscode 3 | export const enum letter_letter_statuscode { 4 | Open = 1, 5 | Draft = 2, 6 | Received = 3, 7 | Sent = 4, 8 | Canceled = 5, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/msdyn_opportunitygradeoptset.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum msdyn_opportunitygradeoptset 3 | export const enum msdyn_opportunitygradeoptset { 4 | GradeA = 0, 5 | GradeB = 1, 6 | GradeC = 2, 7 | GradeD = 3, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/msdyn_opportunityscoretrendoptset.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum msdyn_opportunityscoretrendoptset 3 | export const enum msdyn_opportunityscoretrendoptset { 4 | Improving = 0, 5 | Steady = 1, 6 | Declining = 2, 7 | Notenoughinfo = 3, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/msdyn_queueassignmentstrategy.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum msdyn_queueassignmentstrategy 3 | export const enum msdyn_queueassignmentstrategy { 4 | OmnichannelAssignment = 192350000, 5 | RoundRobin = 192350001, 6 | CustomAssignmentConfiguration = 192350002, 7 | LongestIdle = 192350003, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/msdyn_queuetype.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum msdyn_queuetype 3 | export const enum msdyn_queuetype { 4 | Messaging = 192350000, 5 | Entity = 192350001, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/need.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum need 3 | export const enum need { 4 | Musthave = 0, 5 | Shouldhave = 1, 6 | Goodtohave = 2, 7 | Noneed = 3, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunity_msdyn_opportunity_msdyn_forecastcategory.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunity_msdyn_opportunity_msdyn_forecastcategory 3 | export const enum opportunity_msdyn_opportunity_msdyn_forecastcategory { 4 | Pipeline = 100000001, 5 | Bestcase = 100000002, 6 | Committed = 100000003, 7 | Omitted = 100000004, 8 | Won = 100000005, 9 | Lost = 100000006, 10 | } 11 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunity_opportunity_opportunityratingcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunity_opportunity_opportunityratingcode 3 | export const enum opportunity_opportunity_opportunityratingcode { 4 | Hot = 1, 5 | Warm = 2, 6 | Cold = 3, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunity_opportunity_prioritycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunity_opportunity_prioritycode 3 | export const enum opportunity_opportunity_prioritycode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunity_opportunity_salesstagecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunity_opportunity_salesstagecode 3 | export const enum opportunity_opportunity_salesstagecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunity_opportunity_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunity_opportunity_statecode 3 | export const enum opportunity_opportunity_statecode { 4 | Open = 0, 5 | Won = 1, 6 | Lost = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunity_opportunity_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunity_opportunity_statuscode 3 | export const enum opportunity_opportunity_statuscode { 4 | InProgress = 1, 5 | OnHold = 2, 6 | Won = 3, 7 | Canceled = 4, 8 | OutSold = 5, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunity_opportunity_timeline.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunity_opportunity_timeline 3 | export const enum opportunity_opportunity_timeline { 4 | Immediate = 0, 5 | ThisQuarter = 1, 6 | NextQuarter = 2, 7 | ThisYear = 3, 8 | Notknown = 4, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunity_salesstage.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunity_salesstage 3 | export const enum opportunity_salesstage { 4 | Qualify = 0, 5 | Develop = 1, 6 | Propose = 2, 7 | Close = 3, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunityclose_OpportunityClose_opportunity_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunityclose_OpportunityClose_opportunity_statecode 3 | export const enum opportunityclose_OpportunityClose_opportunity_statecode { 4 | Open = 0, 5 | Won = 1, 6 | Lost = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunityclose__opportunityclose_instancetypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunityclose__opportunityclose_instancetypecode 3 | export const enum opportunityclose__opportunityclose_instancetypecode { 4 | NotRecurring = 0, 5 | RecurringMaster = 1, 6 | RecurringInstance = 2, 7 | RecurringException = 3, 8 | RecurringFutureException = 4, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunityclose__opportunityclose_prioritycode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunityclose__opportunityclose_prioritycode 3 | export const enum opportunityclose__opportunityclose_prioritycode { 4 | Low = 0, 5 | Normal = 1, 6 | High = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunityclose_opportunityclose_opportunity_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunityclose_OpportunityClose_opportunity_statuscode 3 | export const enum opportunityclose_OpportunityClose_opportunity_statuscode { 4 | InProgress = 1, 5 | OnHold = 2, 6 | Won = 3, 7 | Canceled = 4, 8 | OutSold = 5, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunityclose_opportunityclose_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunityclose_opportunityclose_statecode 3 | export const enum opportunityclose_opportunityclose_statecode { 4 | Open = 0, 5 | Completed = 1, 6 | Canceled = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/opportunityclose_opportunityclose_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum opportunityclose_opportunityclose_statuscode 3 | export const enum opportunityclose_opportunityclose_statuscode { 4 | Open = 1, 5 | Completed = 2, 6 | Canceled = 3, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/purchaseprocess.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum purchaseprocess 3 | export const enum purchaseprocess { 4 | Individual = 0, 5 | Committee = 1, 6 | Unknown = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/purchasetimeframe.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum purchasetimeframe 3 | export const enum purchasetimeframe { 4 | Immediate = 0, 5 | ThisQuarter = 1, 6 | NextQuarter = 2, 7 | ThisYear = 3, 8 | Unknown = 4, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/qooi_pricingerrorcode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum qooi_pricingerrorcode 3 | export const enum qooi_pricingerrorcode { 4 | None = 0, 5 | DetailError = 1, 6 | MissingPriceLevel = 2, 7 | InactivePriceLevel = 3, 8 | MissingQuantity = 4, 9 | MissingUnitPrice = 5, 10 | MissingProduct = 6, 11 | InvalidProduct = 7, 12 | MissingPricingCode = 8, 13 | InvalidPricingCode = 9, 14 | MissingUOM = 10, 15 | ProductNotInPriceLevel = 11, 16 | MissingPriceLevelAmount = 12, 17 | MissingPriceLevelPercentage = 13, 18 | MissingPrice = 14, 19 | MissingCurrentCost = 15, 20 | MissingStandardCost = 16, 21 | InvalidPriceLevelAmount = 17, 22 | InvalidPriceLevelPercentage = 18, 23 | InvalidPrice = 19, 24 | InvalidCurrentCost = 20, 25 | InvalidStandardCost = 21, 26 | InvalidRoundingPolicy = 22, 27 | InvalidRoundingOption = 23, 28 | InvalidRoundingAmount = 24, 29 | PriceCalculationError = 25, 30 | InvalidDiscountType = 26, 31 | DiscountTypeInvalidState = 27, 32 | InvalidDiscount = 28, 33 | InvalidQuantity = 29, 34 | InvalidPricingPrecision = 30, 35 | MissingProductDefaultUOM = 31, 36 | MissingProductUOMSchedule = 32, 37 | InactiveDiscountType = 33, 38 | InvalidPriceLevelCurrency = 34, 39 | PriceAttributeOutOfRange = 35, 40 | BaseCurrencyAttributeOverflow = 36, 41 | BaseCurrencyAttributeUnderflow = 37, 42 | Transactioncurrencyisnotsetfortheproductpricelistitem = 38, 43 | } 44 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/qooi_skippricecalculation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum qooi_skippricecalculation 3 | export const enum qooi_skippricecalculation { 4 | DoPriceCalcAlways = 0, 5 | SkipPriceCalcOnRetrieve = 1, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queue_queue_emailrouteraccessapproval.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queue_queue_emailrouteraccessapproval 3 | export const enum queue_queue_emailrouteraccessapproval { 4 | Empty = 0, 5 | Approved = 1, 6 | PendingApproval = 2, 7 | Rejected = 3, 8 | } 9 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queue_queue_incomingemaildeliverymethod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queue_queue_incomingemaildeliverymethod 3 | export const enum queue_queue_incomingemaildeliverymethod { 4 | None = 0, 5 | ServerSideSynchronizationorEmailRouter = 2, 6 | ForwardMailbox = 3, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queue_queue_incomingemailfilteringmethod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queue_queue_incomingemailfilteringmethod 3 | export const enum queue_queue_incomingemailfilteringmethod { 4 | Allemailmessages = 0, 5 | EmailmessagesinresponsetoDynamics365email = 1, 6 | EmailmessagesfromDynamics365LeadsContactsandAccounts = 2, 7 | EmailmessagesfromDynamics365recordsthatareemailenabled = 3, 8 | Noemailmessages = 4, 9 | } 10 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queue_queue_outgoingemaildeliverymethod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queue_queue_outgoingemaildeliverymethod 3 | export const enum queue_queue_outgoingemaildeliverymethod { 4 | None = 0, 5 | ServerSideSynchronizationorEmailRouter = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queue_queue_queuetypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queue_queue_queuetypecode 3 | export const enum queue_queue_queuetypecode { 4 | DefaultValue = 1, 5 | } 6 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queue_queue_queueviewtype.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queue_queue_queueviewtype 3 | export const enum queue_queue_queueviewtype { 4 | Public = 0, 5 | Private = 1, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queue_queue_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queue_queue_statecode 3 | export const enum queue_queue_statecode { 4 | Active = 0, 5 | Inactive = 1, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queue_queue_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queue_queue_statuscode 3 | export const enum queue_queue_statuscode { 4 | Active = 1, 5 | Inactive = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queueitem_queueitem_objecttypecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queueitem_queueitem_objecttypecode 3 | export const enum queueitem_queueitem_objecttypecode { 4 | Case = 112, 5 | Activity = 4200, 6 | Appointment = 4201, 7 | Email = 4202, 8 | Fax = 4204, 9 | Letter = 4207, 10 | PhoneCall = 4210, 11 | Task = 4212, 12 | ServiceActivity = 4214, 13 | SocialActivity = 4216, 14 | RecurringAppointment = 4251, 15 | CampaignResponse = 4401, 16 | CampaignActivity = 4402, 17 | QuickCampaign = 4406, 18 | KnowledgeArticle = 9953, 19 | Teamschat = 10085, 20 | KnowledgeArticleTemplate = 10099, 21 | IoTAlert = 10201, 22 | CustomerVoicealert = 10362, 23 | CustomerVoicesurveyinvite = 10372, 24 | CustomerVoicesurveyresponse = 10374, 25 | OverflowActionConfig = 10437, 26 | OngoingconversationDeprecated = 10461, 27 | Conversation = 10473, 28 | Session = 10490, 29 | } 30 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queueitem_queueitem_statecode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queueitem_queueitem_statecode 3 | export const enum queueitem_queueitem_statecode { 4 | Active = 0, 5 | Inactive = 1, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/queueitem_queueitem_statuscode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum queueitem_queueitem_statuscode 3 | export const enum queueitem_queueitem_statuscode { 4 | Active = 1, 5 | Inactive = 2, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/servicestage.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum servicestage 3 | export const enum servicestage { 4 | Identify = 0, 5 | Research = 1, 6 | Resolve = 2, 7 | } 8 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/socialactivity_postmessagetype.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum socialactivity_postmessagetype 3 | export const enum socialactivity_postmessagetype { 4 | PublicMessage = 0, 5 | PrivateMessage = 1, 6 | } 7 | -------------------------------------------------------------------------------- /src/dataverse-gen/enums/socialprofile_community.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | // Enum socialprofile_community 3 | export const enum socialprofile_community { 4 | Other = 0, 5 | Facebook = 1, 6 | Twitter = 2, 7 | Line = 3, 8 | Wechat = 4, 9 | Cortana = 5, 10 | DirectLine = 6, 11 | MicrosoftTeams = 7, 12 | DirectLineSpeech = 8, 13 | Email = 9, 14 | GroupMe = 10, 15 | Kik = 11, 16 | Telegram = 12, 17 | Skype = 13, 18 | Slack = 14, 19 | WhatsApp = 15, 20 | AppleMessagesForBusiness = 16, 21 | GooglesBusinessMessages = 17, 22 | } 23 | -------------------------------------------------------------------------------- /src/dataverse-gen/functions/CalculateRollupField.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { WebApiExecuteRequest } from "../../types/WebApiExecuteRequest"; 3 | import { StructuralProperty } from "../../types/StructuralProperty"; 4 | import { OperationType } from "../../types/OperationType"; 5 | 6 | // Action CalculateRollupField 7 | export const CalculateRollupFieldMetadata = { 8 | parameterTypes: { 9 | "Target": { 10 | typeName: "mscrm.crmbaseentity", 11 | structuralProperty: StructuralProperty.EntityType 12 | }, 13 | "FieldName": { 14 | typeName: "Edm.String", 15 | structuralProperty: StructuralProperty.PrimitiveType 16 | }, 17 | 18 | }, 19 | operationType: OperationType.Function, 20 | operationName: "CalculateRollupField" 21 | }; 22 | 23 | export interface CalculateRollupFieldRequest extends WebApiExecuteRequest { 24 | Target?: any; 25 | FieldName?: string; 26 | } -------------------------------------------------------------------------------- /src/dataverse-gen/functions/CalculateTotalTimeIncident.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { WebApiExecuteRequest } from "../../types/WebApiExecuteRequest"; 3 | import { StructuralProperty } from "../../types/StructuralProperty"; 4 | import { OperationType } from "../../types/OperationType"; 5 | 6 | // Action CalculateTotalTimeIncident 7 | export const CalculateTotalTimeIncidentMetadata = { 8 | boundParameter: "entity", 9 | parameterTypes: { 10 | "entity": { 11 | typeName: "mscrm.incident", 12 | structuralProperty: StructuralProperty.EntityType 13 | }, 14 | 15 | }, 16 | operationType: OperationType.Function, 17 | operationName: "CalculateTotalTimeIncident" 18 | }; 19 | 20 | export interface CalculateTotalTimeIncidentRequest extends WebApiExecuteRequest { 21 | entity?: import("../../types/EntityReference").EntityReference | import("../entities/Incident").Incident; 22 | } -------------------------------------------------------------------------------- /src/dataverse-gen/functions/RetrieveMetadataChanges.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { WebApiExecuteRequest } from "../../types/WebApiExecuteRequest"; 3 | import { StructuralProperty } from "../../types/StructuralProperty"; 4 | import { OperationType } from "../../types/OperationType"; 5 | 6 | // Action RetrieveMetadataChanges 7 | export const RetrieveMetadataChangesMetadata = { 8 | parameterTypes: { 9 | "Query": { 10 | typeName: "mscrm.EntityQueryExpression", 11 | structuralProperty: StructuralProperty.EntityType 12 | }, 13 | "DeletedMetadataFilters": { 14 | typeName: "mscrm.DeletedMetadataFilters", 15 | structuralProperty: StructuralProperty.EnumerationType 16 | }, 17 | "ClientVersionStamp": { 18 | typeName: "Edm.String", 19 | structuralProperty: StructuralProperty.PrimitiveType 20 | }, 21 | "AppModuleId": { 22 | typeName: "Edm.Guid", 23 | structuralProperty: StructuralProperty.PrimitiveType 24 | }, 25 | "RetrieveAllSettings": { 26 | typeName: "Edm.Boolean", 27 | structuralProperty: StructuralProperty.PrimitiveType 28 | }, 29 | 30 | }, 31 | operationType: OperationType.Function, 32 | operationName: "RetrieveMetadataChanges" 33 | }; 34 | 35 | export interface RetrieveMetadataChangesRequest extends WebApiExecuteRequest { 36 | Query?: import("../complextypes/EntityQueryExpression").EntityQueryExpression; 37 | DeletedMetadataFilters?: import("../enums/DeletedMetadataFilters").DeletedMetadataFilters; 38 | ClientVersionStamp?: string; 39 | AppModuleId?: import("../../types/Guid").Guid; 40 | RetrieveAllSettings?: boolean; 41 | } -------------------------------------------------------------------------------- /src/dataverse-gen/functions/cdsify_BoundEchoFunction.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { WebApiExecuteRequest } from "../../types/WebApiExecuteRequest"; 3 | import { StructuralProperty } from "../../types/StructuralProperty"; 4 | import { OperationType } from "../../types/OperationType"; 5 | 6 | // Action cdsify_BoundEchoFunction 7 | export const cdsify_BoundEchoFunctionMetadata = { 8 | boundParameter: "entity", 9 | parameterTypes: { 10 | "entity": { 11 | typeName: "mscrm.cdsify_integrationtest", 12 | structuralProperty: StructuralProperty.EntityType 13 | }, 14 | "cdsify_BoundEchoFunctionInInteger": { 15 | typeName: "Edm.Int32", 16 | structuralProperty: StructuralProperty.PrimitiveType 17 | }, 18 | "cdsify_BoundEchoFunctionInString": { 19 | typeName: "Edm.String", 20 | structuralProperty: StructuralProperty.PrimitiveType 21 | }, 22 | 23 | }, 24 | operationType: OperationType.Function, 25 | operationName: "cdsify_BoundEchoFunction" 26 | }; 27 | 28 | export interface cdsify_BoundEchoFunctionRequest extends WebApiExecuteRequest { 29 | entity?: import("../../types/EntityReference").EntityReference | import("../entities/cdsify_IntegrationTest").cdsify_IntegrationTest; 30 | cdsify_BoundEchoFunctionInInteger?: number; 31 | cdsify_BoundEchoFunctionInString?: string; 32 | } -------------------------------------------------------------------------------- /src/dataverse-gen/functions/cdsify_UnboundEchoFunction2.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { WebApiExecuteRequest } from "../../types/WebApiExecuteRequest"; 3 | import { StructuralProperty } from "../../types/StructuralProperty"; 4 | import { OperationType } from "../../types/OperationType"; 5 | 6 | // Action cdsify_UnboundEchoFunction2 7 | export const cdsify_UnboundEchoFunction2Metadata = { 8 | parameterTypes: { 9 | "cdsify_UnboundEchoFunctionInDecimal": { 10 | typeName: "Edm.Decimal", 11 | structuralProperty: StructuralProperty.PrimitiveType 12 | }, 13 | "cdsify_UnboundEchoFunctionInFloat": { 14 | typeName: "Edm.Double", 15 | structuralProperty: StructuralProperty.PrimitiveType 16 | }, 17 | "cdsify_UnboundEchoFunctionInBoolean": { 18 | typeName: "Edm.Boolean", 19 | structuralProperty: StructuralProperty.PrimitiveType 20 | }, 21 | "cdsify_UnboundEchoFunctionInInteger": { 22 | typeName: "Edm.Int32", 23 | structuralProperty: StructuralProperty.PrimitiveType 24 | }, 25 | "cdsify_UnboundEchoFunctionInString": { 26 | typeName: "Edm.String", 27 | structuralProperty: StructuralProperty.PrimitiveType 28 | }, 29 | "cdsify_UnboundEchoFunctionInStringArray": { 30 | typeName: "Collection(Edm.String)", 31 | structuralProperty: StructuralProperty.Collection 32 | }, 33 | "cdsify_UnboundEchoFunctionInDateTime": { 34 | typeName: "Edm.DateTimeOffset", 35 | structuralProperty: StructuralProperty.PrimitiveType 36 | }, 37 | 38 | }, 39 | operationType: OperationType.Function, 40 | operationName: "cdsify_UnboundEchoFunction2" 41 | }; 42 | 43 | export interface cdsify_UnboundEchoFunction2Request extends WebApiExecuteRequest { 44 | cdsify_UnboundEchoFunctionInDecimal?: number; 45 | cdsify_UnboundEchoFunctionInFloat?: number; 46 | cdsify_UnboundEchoFunctionInBoolean?: boolean; 47 | cdsify_UnboundEchoFunctionInInteger?: number; 48 | cdsify_UnboundEchoFunctionInString?: string; 49 | cdsify_UnboundEchoFunctionInStringArray?: string[]; 50 | cdsify_UnboundEchoFunctionInDateTime?: Date; 51 | } -------------------------------------------------------------------------------- /src/dataverse-gen/metadata.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | import { accountMetadata } from "./entities/Account"; 3 | import { cdsify_integrationtestMetadata } from "./entities/cdsify_IntegrationTest"; 4 | import { contactMetadata } from "./entities/Contact"; 5 | import { incidentMetadata } from "./entities/Incident"; 6 | import { letterMetadata } from "./entities/Letter"; 7 | import { opportunityMetadata } from "./entities/Opportunity"; 8 | import { opportunitycloseMetadata } from "./entities/OpportunityClose"; 9 | import { queueMetadata } from "./entities/Queue"; 10 | import { queueitemMetadata } from "./entities/QueueItem"; 11 | import { AddToQueueMetadata } from "./actions/AddToQueue"; 12 | import { WinOpportunityMetadata } from "./actions/WinOpportunity"; 13 | import { cdsify_BoundCollectionEchoMetadata } from "./actions/cdsify_BoundCollectionEcho"; 14 | import { cdsify_BoundEchoMetadata } from "./actions/cdsify_BoundEcho"; 15 | import { cdsify_UnboundEchoMetadata } from "./actions/cdsify_UnboundEcho"; 16 | import { CalculateRollupFieldMetadata } from "./functions/CalculateRollupField"; 17 | import { CalculateTotalTimeIncidentMetadata } from "./functions/CalculateTotalTimeIncident"; 18 | import { RetrieveMetadataChangesMetadata } from "./functions/RetrieveMetadataChanges"; 19 | import { cdsify_BoundEchoFunctionMetadata } from "./functions/cdsify_BoundEchoFunction"; 20 | import { cdsify_UnboundEchoFunction2Metadata } from "./functions/cdsify_UnboundEchoFunction2"; 21 | 22 | export const Entities = { 23 | Account: "account", 24 | cdsify_IntegrationTest: "cdsify_integrationtest", 25 | Contact: "contact", 26 | Incident: "incident", 27 | Letter: "letter", 28 | Opportunity: "opportunity", 29 | OpportunityClose: "opportunityclose", 30 | Queue: "queue", 31 | QueueItem: "queueitem", 32 | }; 33 | 34 | // Setup Metadata 35 | // Usage: setMetadataCache(metadataCache); 36 | export const metadataCache = { 37 | entities: { 38 | account: accountMetadata, 39 | cdsify_integrationtest: cdsify_integrationtestMetadata, 40 | contact: contactMetadata, 41 | incident: incidentMetadata, 42 | letter: letterMetadata, 43 | opportunity: opportunityMetadata, 44 | opportunityclose: opportunitycloseMetadata, 45 | queue: queueMetadata, 46 | queueitem: queueitemMetadata, 47 | }, 48 | actions: { 49 | AddToQueue: AddToQueueMetadata, 50 | WinOpportunity: WinOpportunityMetadata, 51 | cdsify_BoundCollectionEcho: cdsify_BoundCollectionEchoMetadata, 52 | cdsify_BoundEcho: cdsify_BoundEchoMetadata, 53 | cdsify_UnboundEcho: cdsify_UnboundEchoMetadata, 54 | CalculateRollupField: CalculateRollupFieldMetadata, 55 | CalculateTotalTimeIncident: CalculateTotalTimeIncidentMetadata, 56 | RetrieveMetadataChanges: RetrieveMetadataChangesMetadata, 57 | cdsify_BoundEchoFunction: cdsify_BoundEchoFunctionMetadata, 58 | cdsify_UnboundEchoFunction2: cdsify_UnboundEchoFunction2Metadata, 59 | } 60 | }; -------------------------------------------------------------------------------- /src/dataverse-ify/DataverseClient/DataverseClient.ts: -------------------------------------------------------------------------------- 1 | import { IEntity } from "../../types/IEntity"; 2 | import { EntityCollection } from "../../types/EntityCollection"; 3 | import { Guid } from "../../types/Guid"; 4 | import { EntityReference } from "../../types/EntityReference"; 5 | import { WebApiExecuteRequest } from "../../types/WebApiExecuteRequest"; 6 | 7 | export interface RetrieveMultipleOptions { 8 | returnRawEntities?: boolean; 9 | } 10 | 11 | export interface FetchRetrieveMultipleOptions extends RetrieveMultipleOptions { 12 | // Provide logicalName to bypass extracting it from the fetchxml 13 | logicalName?: string; 14 | } 15 | 16 | export interface ODataRetrieveMultipleOptions extends RetrieveMultipleOptions { 17 | logicalName: string; 18 | maxPageSize?: number; 19 | } 20 | 21 | export interface DataverseClient { 22 | create(entity: IEntity): Promise; 23 | update(entity: IEntity): Promise; 24 | delete(entity: IEntity): Promise; 25 | delete(entityName: string, id: Guid): Promise; 26 | retrieve(entityName: string, id: Guid, columnSet: string[] | boolean): Promise; 27 | retrieveMultiple( 28 | query: string, 29 | options?: FetchRetrieveMultipleOptions | ODataRetrieveMultipleOptions, 30 | ): Promise>; 31 | associate( 32 | entityName: string, 33 | entityId: string, 34 | relationship: string, 35 | relatedEntities: EntityReference[], 36 | ): Promise; 37 | disassociate( 38 | entityName: string, 39 | entityId: string, 40 | relationship: string, 41 | relatedEntities: EntityReference[], 42 | ): Promise; 43 | execute(request: WebApiExecuteRequest): Promise; 44 | executeMultiple(requests: WebApiExecuteRequest[]): Promise; 45 | } 46 | -------------------------------------------------------------------------------- /src/dataverse-ify/DataverseClient/index.ts: -------------------------------------------------------------------------------- 1 | export { DataverseClient } from "./DataverseClient"; 2 | export { XrmContextDataverseClient } from "./XrmContextDataverseClient"; 3 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/integration-tests/addtoqueue.test.ts: -------------------------------------------------------------------------------- 1 | import { SetupGlobalContext } from "../../../webapi/node/SetupGlobalContext"; 2 | import { setMetadataCache } from "../../../metadata/MetadataCache"; 3 | import { Entity } from "../../../types/Entity"; 4 | import { XrmContextDataverseClient } from "../.."; 5 | import { letterMetadata, Letter } from "../../../dataverse-gen/entities/Letter"; 6 | import { queueMetadata, Queue } from "../../../dataverse-gen/entities/Queue"; 7 | import { queueitemMetadata } from "../../../dataverse-gen/entities/QueueItem"; 8 | import { AddToQueueMetadata, AddToQueueRequest } from "../../../dataverse-gen/actions/AddToQueue"; 9 | import { AddToQueueResponse } from "../../../dataverse-gen/complextypes/AddToQueueResponse"; 10 | describe("AddToQueue", () => { 11 | beforeAll(async () => { 12 | // Is this running inside NodeJS? 13 | if (typeof Xrm === "undefined") { 14 | // Set up the Node Xrm global context 15 | await SetupGlobalContext(); 16 | } 17 | }, 300000); 18 | test("AddToQueue", async () => { 19 | setMetadataCache({ 20 | entities: { 21 | letter: letterMetadata, 22 | queue: queueMetadata, 23 | queueitem: queueitemMetadata, 24 | }, 25 | actions: { AddToQueue: AddToQueueMetadata }, 26 | }); 27 | 28 | const queue = { 29 | logicalName: queueMetadata.logicalName, 30 | name: "Sample Queue", 31 | } as Queue; 32 | 33 | const letter = { 34 | logicalName: letterMetadata.logicalName, 35 | subject: "Sample Letter", 36 | } as Letter; 37 | 38 | const serviceClient = new XrmContextDataverseClient(Xrm.WebApi); 39 | try { 40 | // Create Queue 41 | queue.id = await serviceClient.create(queue); 42 | 43 | // Create letter 44 | letter.id = await serviceClient.create(letter); 45 | 46 | // Add letter to queue 47 | const request = { 48 | logicalName: AddToQueueMetadata.operationName, 49 | entity: Entity.toEntityReference(queue), 50 | Target: Entity.toEntityReference(letter), 51 | } as AddToQueueRequest; 52 | 53 | const response = (await serviceClient.execute(request)) as AddToQueueResponse; 54 | expect(response.QueueItemId).toBeDefined(); 55 | } catch (ex) { 56 | expect(ex).toBeUndefined(); 57 | } finally { 58 | if (letter.id) { 59 | // Tidy up 60 | await serviceClient.delete(letter); 61 | } 62 | if (queue.id) { 63 | // Tidy up 64 | await serviceClient.delete(queue); 65 | } 66 | } 67 | }, 300000); 68 | }); 69 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/integration-tests/calculaterollupfield.test.ts: -------------------------------------------------------------------------------- 1 | import { SetupGlobalContext } from "../../../webapi/node/SetupGlobalContext"; 2 | import { setMetadataCache } from "../../../metadata/MetadataCache"; 3 | import { XrmContextDataverseClient } from "../.."; 4 | import { Entity } from "../../../types/Entity"; 5 | import { accountMetadata, Account } from "../../../dataverse-gen/entities/Account"; 6 | import { 7 | CalculateRollupFieldMetadata, 8 | CalculateRollupFieldRequest, 9 | } from "../../../dataverse-gen/functions/CalculateRollupField"; 10 | describe("CalculateRollUpField", () => { 11 | beforeAll(async () => { 12 | // Is this running inside NodeJS? 13 | if (typeof Xrm === "undefined") { 14 | // Set up the Node Xrm global context 15 | await SetupGlobalContext(); 16 | } 17 | }, 30000); 18 | test("CalculateRollupField", async () => { 19 | setMetadataCache({ 20 | entities: { account: accountMetadata }, 21 | actions: { CalculateRollupField: CalculateRollupFieldMetadata }, 22 | }); 23 | 24 | const account1 = { 25 | logicalName: accountMetadata.logicalName, 26 | name: "Account 1", 27 | } as Account; 28 | 29 | const serviceClient = new XrmContextDataverseClient(Xrm.WebApi); 30 | try { 31 | // Create Account 32 | account1.id = await serviceClient.create(account1); 33 | 34 | // Calculate Rollup field open deals 35 | const request = { 36 | logicalName: CalculateRollupFieldMetadata.operationName, 37 | FieldName: "opendeals", 38 | Target: Entity.toEntityReference(account1), 39 | } as CalculateRollupFieldRequest; 40 | 41 | const response = await serviceClient.execute(request); 42 | expect(response).toBeDefined(); 43 | } catch (ex) { 44 | expect(ex).toBeUndefined(); 45 | } finally { 46 | if (account1.id) { 47 | // Tidy up 48 | await serviceClient.delete(account1); 49 | } 50 | } 51 | }, 30000); 52 | }); 53 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/integration-tests/custom-api-bound-action.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import { SetupGlobalContext } from "../../../webapi/node/SetupGlobalContext"; 4 | import { setMetadataCache } from "../../../metadata/MetadataCache"; 5 | import { metadataCache } from "../../../dataverse-gen/metadata"; 6 | import { XrmContextDataverseClient } from "../../DataverseClient"; 7 | import { 8 | TEST_DATE, 9 | TEST_DECIMAL, 10 | TEST_FLOAT, 11 | TEST_GUID, 12 | TEST_INTEGER, 13 | TEST_STRING, 14 | } from "../../../webapi/__tests__/test-values"; 15 | import { 16 | cdsify_IntegrationTest, 17 | cdsify_integrationtestMetadata, 18 | } from "../../../dataverse-gen/entities/cdsify_IntegrationTest"; 19 | import { toEntityReference } from "../../../types"; 20 | import { cdsify_BoundEchoMetadata, cdsify_BoundEchoRequest } from "../../../dataverse-gen/actions/cdsify_BoundEcho"; 21 | import { cdsify_BoundEchoResponse } from "../../../dataverse-gen/complextypes/cdsify_BoundEchoResponse"; 22 | 23 | describe("XrmContextDataverseClient", () => { 24 | beforeAll(async () => { 25 | // Is this running inside NodeJS? 26 | if (typeof Xrm === "undefined") { 27 | // Set up the Node Xrm global context 28 | await SetupGlobalContext(); 29 | } 30 | }, 10000); 31 | 32 | it("custom bound api function mapping", async () => { 33 | setMetadataCache(metadataCache); 34 | const serviceClient = new XrmContextDataverseClient(Xrm.WebApi); 35 | 36 | // Create test record 37 | const testRecord1 = { 38 | logicalName: cdsify_integrationtestMetadata.logicalName, 39 | cdsify_name: "test", 40 | } as cdsify_IntegrationTest; 41 | 42 | testRecord1.cdsify_integrationtestid = await serviceClient.create(testRecord1); 43 | 44 | try { 45 | const request: cdsify_BoundEchoRequest = { 46 | logicalName: cdsify_BoundEchoMetadata.operationName, 47 | entity: toEntityReference(testRecord1), 48 | cdsify_BoundInBoolean: true, 49 | cdsify_BoundInDateTime: TEST_DATE[0], 50 | cdsify_BoundInDecimal: TEST_DECIMAL[0], 51 | cdsify_BoundInEntity: testRecord1, 52 | cdsify_BoundInEntityCollection: [testRecord1], 53 | cdsify_BoundInEntityReference: toEntityReference(testRecord1), 54 | cdsify_BoundInFloat: TEST_FLOAT[0], 55 | cdsify_BoundInGuid: TEST_GUID[0], 56 | cdsify_BoundInInteger: TEST_INTEGER[0], 57 | cdsify_BoundInMoney: TEST_DECIMAL[0], 58 | cdsify_BoundInPicklist: TEST_INTEGER[0], 59 | cdsify_BoundInString: TEST_STRING[0], 60 | cdsify_BoundInStringArray: [TEST_STRING[0], TEST_STRING[1]], 61 | }; 62 | 63 | const response = (await serviceClient.execute(request as any)) as cdsify_BoundEchoResponse; 64 | expect(response).toBeDefined(); 65 | expect(response.cdsify_BoundOutString).toBe(TEST_STRING[0]); 66 | } finally { 67 | await serviceClient.delete(testRecord1); 68 | } 69 | }, 100000); 70 | 71 | it.todo("allow passing the full entity rather than an entity reference to the entity bound parameter"); 72 | }); 73 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/integration-tests/custom-api-bound-function.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import { SetupGlobalContext } from "../../../webapi/node/SetupGlobalContext"; 4 | import { setMetadataCache } from "../../../metadata/MetadataCache"; 5 | import { metadataCache } from "../../../dataverse-gen/metadata"; 6 | import { XrmContextDataverseClient } from "../../DataverseClient"; 7 | import { TEST_INTEGER, TEST_STRING } from "../../../webapi/__tests__/test-values"; 8 | import { 9 | cdsify_BoundEchoFunctionMetadata, 10 | cdsify_BoundEchoFunctionRequest, 11 | } from "../../../dataverse-gen/functions/cdsify_BoundEchoFunction"; 12 | import { cdsify_BoundEchoFunctionResponse } from "../../../dataverse-gen/complextypes/cdsify_BoundEchoFunctionResponse"; 13 | import { 14 | cdsify_IntegrationTest, 15 | cdsify_integrationtestMetadata, 16 | } from "../../../dataverse-gen/entities/cdsify_IntegrationTest"; 17 | import { toEntityReference } from "../../../types"; 18 | 19 | describe("XrmContextDataverseClient", () => { 20 | beforeAll(async () => { 21 | // Is this running inside NodeJS? 22 | if (typeof Xrm === "undefined") { 23 | // Set up the Node Xrm global context 24 | await SetupGlobalContext(); 25 | } 26 | }, 10000); 27 | 28 | it("custom bound api function mapping", async () => { 29 | setMetadataCache(metadataCache); 30 | const serviceClient = new XrmContextDataverseClient(Xrm.WebApi); 31 | 32 | // Create test record 33 | const testRecord1 = { 34 | logicalName: cdsify_integrationtestMetadata.logicalName, 35 | cdsify_name: "test", 36 | } as cdsify_IntegrationTest; 37 | 38 | testRecord1.cdsify_integrationtestid = await serviceClient.create(testRecord1); 39 | 40 | try { 41 | const request: cdsify_BoundEchoFunctionRequest = { 42 | logicalName: cdsify_BoundEchoFunctionMetadata.operationName, 43 | entity: toEntityReference(testRecord1), 44 | cdsify_BoundEchoFunctionInString: TEST_STRING[0], 45 | cdsify_BoundEchoFunctionInInteger: TEST_INTEGER[0], 46 | }; 47 | 48 | const response = (await serviceClient.execute(request as any)) as cdsify_BoundEchoFunctionResponse; 49 | expect(response).toBeDefined(); 50 | expect(response.cdsify_BoundEchoFunctionOutString).toBe(request.cdsify_BoundEchoFunctionInString); 51 | } finally { 52 | await serviceClient.delete(testRecord1); 53 | } 54 | }, 100000); 55 | 56 | it.todo("allow passing the full entity rather than an entity reference to the entity bound parameter"); 57 | }); 58 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/integration-tests/fetchxml.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | 4 | import { SetupGlobalContext } from "../../../webapi/node/SetupGlobalContext"; 5 | import { setMetadataCache } from "../../../metadata/MetadataCache"; 6 | import { XrmContextDataverseClient } from "../../DataverseClient"; 7 | import { 8 | cdsify_IntegrationTest, 9 | cdsify_integrationtestMetadata, 10 | } from "../../../dataverse-gen/entities/cdsify_IntegrationTest"; 11 | import { CreateRequest, DeleteRequest, EntityReference, CreateResponse } from "../../../types"; 12 | describe("retrieveMultiple", () => { 13 | beforeAll(async () => { 14 | // Is this running inside NodeJS? 15 | if (typeof Xrm === "undefined") { 16 | // Set up the Node Xrm global context 17 | await SetupGlobalContext(); 18 | } 19 | }, 10000); 20 | 21 | it("allows retrieveMultiple via fetchxml with paging cookie", async () => { 22 | setMetadataCache({ 23 | entities: { 24 | cdsify_integrationtest: cdsify_integrationtestMetadata, 25 | }, 26 | }); 27 | 28 | const client = new XrmContextDataverseClient(Xrm.WebApi); 29 | 30 | // Create 12 records 31 | const name = `fetch test ${new Date()}`; 32 | const testRecord = { 33 | logicalName: cdsify_integrationtestMetadata.logicalName, 34 | cdsify_name: name, 35 | } as cdsify_IntegrationTest; 36 | 37 | const createChangeSet = [...Array(12).keys()].map(() => { 38 | return { 39 | logicalName: "Create", 40 | target: testRecord, 41 | } as CreateRequest; 42 | }); 43 | 44 | const createResponses = await client.executeMultiple([createChangeSet]); 45 | 46 | try { 47 | const results = await client.retrieveMultiple(` 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | `); 56 | expect(results).toBeDefined(); 57 | expect(results.pagingCooking).toBeDefined(); 58 | // You don't get these from the implementation inside Xrm.Webapi - sad face 59 | //expect(results.moreRecords).toBe(true); 60 | //expect(results.totalRecordCount).toBe(12); 61 | } finally { 62 | // Tidy up 63 | const deleteChangeSet = [...Array(12).keys()].map((i) => { 64 | return { 65 | logicalName: "Delete", 66 | target: new EntityReference( 67 | cdsify_integrationtestMetadata.logicalName, 68 | createResponses && createResponses[i].id, 69 | ), 70 | } as DeleteRequest; 71 | }); 72 | 73 | await client.executeMultiple([deleteChangeSet]); 74 | } 75 | }, 100000); 76 | }); 77 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/integration-tests/get-metadata.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import { SetupGlobalContext } from "../../../webapi/node/SetupGlobalContext"; 4 | import { setMetadataCache } from "../../../metadata/MetadataCache"; 5 | import { metadataCache } from "../../../dataverse-gen/metadata"; 6 | import { MetadataConditionOperator } from "../../../dataverse-gen/enums/MetadataConditionOperator"; 7 | import { LogicalOperator } from "../../../dataverse-gen/enums/LogicalOperator"; 8 | import { RetrieveMetadataChangesRequest } from "../../../dataverse-gen/functions/RetrieveMetadataChanges"; 9 | import { RetrieveMetadataChangesResponse } from "../../../dataverse-gen/complextypes/RetrieveMetadataChangesResponse"; 10 | import { XrmContextDataverseClient } from "../../DataverseClient"; 11 | describe("RetrieveMetadataChangesRequest", () => { 12 | beforeAll(async () => { 13 | // Is this running inside NodeJS? 14 | if (typeof Xrm === "undefined") { 15 | // Set up the Node Xrm global context 16 | await SetupGlobalContext(); 17 | } 18 | }, 10000); 19 | 20 | it("does not timeout", async () => { 21 | // Create an account with sdk types 22 | setMetadataCache(metadataCache); 23 | 24 | const metadataQuery = { 25 | logicalName: "RetrieveMetadataChanges", 26 | Query: { 27 | Criteria: { 28 | Conditions: [ 29 | { 30 | PropertyName: "LogicalName", 31 | ConditionOperator: MetadataConditionOperator.Equals, 32 | Value: { 33 | Value: "account", 34 | Type: "System.String", 35 | }, 36 | }, 37 | ], 38 | FilterOperator: LogicalOperator.And, 39 | }, 40 | Properties: { 41 | PropertyNames: ["Attributes", "SchemaName", "EntitySetName"], 42 | }, 43 | AttributeQuery: { 44 | Properties: { 45 | PropertyNames: [ 46 | "SchemaName", 47 | "LogicalName", 48 | "OptionSet", 49 | "RequiredLevel", 50 | "AttributeType", 51 | "AttributeTypeName", 52 | "SourceType", 53 | "IsLogical", 54 | "AttributeOf", 55 | "Targets", 56 | "Description", 57 | "DateTimeBehavior", 58 | "Format", 59 | "DisplayName", 60 | ], 61 | }, 62 | }, 63 | }, 64 | } as RetrieveMetadataChangesRequest; 65 | 66 | const serviceClient = new XrmContextDataverseClient(Xrm.WebApi); 67 | const metadataResponse = (await serviceClient.execute(metadataQuery as any)) as RetrieveMetadataChangesResponse; 68 | expect(metadataResponse).toBeDefined(); 69 | }, 100000); 70 | }); 71 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/integration-tests/odata-retrievemultiple.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | 4 | import { SetupGlobalContext } from "../../../webapi/node/SetupGlobalContext"; 5 | import { setMetadataCache } from "../../../metadata/MetadataCache"; 6 | import { XrmContextDataverseClient } from "../../DataverseClient"; 7 | import { 8 | cdsify_IntegrationTest, 9 | cdsify_integrationtestMetadata, 10 | } from "../../../dataverse-gen/entities/cdsify_IntegrationTest"; 11 | import { CreateRequest, DeleteRequest, EntityReference, CreateResponse } from "../../../types"; 12 | describe("retrieveMultiple", () => { 13 | beforeAll(async () => { 14 | // Is this running inside NodeJS? 15 | if (typeof Xrm === "undefined") { 16 | // Set up the Node Xrm global context 17 | await SetupGlobalContext(); 18 | } 19 | }, 10000); 20 | 21 | it("allows retrieveMultiple via odata with nextLink", async () => { 22 | setMetadataCache({ 23 | entities: { 24 | cdsify_integrationtest: cdsify_integrationtestMetadata, 25 | }, 26 | }); 27 | 28 | const client = new XrmContextDataverseClient(Xrm.WebApi); 29 | 30 | // Create 12 records 31 | const name = `fetch test ${new Date()}`; 32 | const testRecord = { 33 | logicalName: cdsify_integrationtestMetadata.logicalName, 34 | cdsify_name: name, 35 | } as cdsify_IntegrationTest; 36 | 37 | const createChangeSet = [...Array(12).keys()].map(() => { 38 | return { 39 | logicalName: "Create", 40 | target: testRecord, 41 | } as CreateRequest; 42 | }); 43 | 44 | const createResponses = await client.executeMultiple([createChangeSet]); 45 | 46 | try { 47 | const results = await client.retrieveMultiple( 48 | `?$select=cdsify_name&$filter=(cdsify_name eq '${name}')&$orderby=createdon asc`, 49 | { 50 | logicalName: cdsify_integrationtestMetadata.logicalName, 51 | maxPageSize: 5, 52 | }, 53 | ); 54 | expect(results).toBeDefined(); 55 | expect(results.nextLink).toBeDefined(); 56 | } finally { 57 | // Tidy up 58 | const deleteChangeSet = [...Array(12).keys()].map((i) => { 59 | return { 60 | logicalName: "Delete", 61 | target: new EntityReference( 62 | cdsify_integrationtestMetadata.logicalName, 63 | createResponses && createResponses[i].id, 64 | ), 65 | } as DeleteRequest; 66 | }); 67 | 68 | await client.executeMultiple([deleteChangeSet]); 69 | } 70 | }, 100000); 71 | }); 72 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/integration-tests/pascalcase-navigation-property.test.ts: -------------------------------------------------------------------------------- 1 | import { SetupGlobalContext } from "../../../webapi/node/SetupGlobalContext"; 2 | import { setMetadataCache } from "../../../metadata/MetadataCache"; 3 | import { accountMetadata, Account, AccountAttributes } from "../../../dataverse-gen/entities/Account"; 4 | import { XrmContextDataverseClient } from "../.."; 5 | import { Entity } from "../../../types"; 6 | describe("pascalcase-navigation-property", () => { 7 | beforeAll(async () => { 8 | // Is this running inside NodeJS? 9 | if (typeof Xrm === "undefined") { 10 | // Set up the Node Xrm global context 11 | await SetupGlobalContext(); 12 | } 13 | }, 30000); 14 | test("pascalcase-navigation-property", async () => { 15 | setMetadataCache({ 16 | entities: { 17 | account: accountMetadata, 18 | }, 19 | }); 20 | const serviceClient = new XrmContextDataverseClient(Xrm.WebApi); 21 | 22 | const accountRelated = { 23 | logicalName: accountMetadata.logicalName, 24 | name: "Account " + new Date().toISOString(), 25 | } as Account; 26 | 27 | // Create account 28 | accountRelated.id = await serviceClient.create(accountRelated); 29 | 30 | const account1 = { 31 | logicalName: accountMetadata.logicalName, 32 | name: "Account " + new Date().toISOString(), 33 | cdsify_account1: Entity.toEntityReference(accountRelated), 34 | } as Account; 35 | 36 | // Create account 37 | account1.id = await serviceClient.create(account1); 38 | 39 | // Retrieve the account 40 | const accountRetrieved = (await serviceClient.retrieve(account1.logicalName, account1.id, [ 41 | AccountAttributes.cdsify_Account1, 42 | ])) as Account; 43 | 44 | expect(accountRetrieved.cdsify_account1).toBeDefined(); 45 | 46 | // Tidy up 47 | await serviceClient.delete(accountRelated); 48 | await serviceClient.delete(account1); 49 | }, 30000); 50 | }); 51 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/unit-tests/action-winopportunity.test.ts: -------------------------------------------------------------------------------- 1 | import { WinOpportunityRequest, WinOpportunityMetadata } from "../../../dataverse-gen/actions/WinOpportunity"; 2 | import { opportunitycloseMetadata } from "../../../dataverse-gen/entities/OpportunityClose"; 3 | import { odataify } from "../../odataify/odataify"; 4 | import { EntityReference } from "../../../types"; 5 | import { setMetadataCache } from "../../../metadata"; 6 | import { opportunityMetadata } from "../../../dataverse-gen/entities/Opportunity"; 7 | 8 | test("odataify WinOpportunity", async () => { 9 | setMetadataCache({ 10 | entities: { 11 | opportunity: opportunityMetadata, 12 | opportunityclose: opportunitycloseMetadata, 13 | }, 14 | actions: { WinOpportunity: WinOpportunityMetadata }, 15 | }); 16 | const winRequest = { 17 | logicalName: WinOpportunityMetadata.operationName, 18 | Status: 3, 19 | OpportunityClose: { 20 | logicalName: opportunitycloseMetadata.logicalName, 21 | description: "Sample Opportunity Close", 22 | subject: "Sample", 23 | 24 | opportunityid: new EntityReference(opportunityMetadata.logicalName, "5deb00bd-5685-ea11-a812-000d3a7f4cf5"), 25 | }, 26 | } as WinOpportunityRequest; 27 | 28 | const odata = await odataify("Action", winRequest); 29 | const expected = { 30 | Status: 3, 31 | OpportunityClose: { 32 | description: "Sample Opportunity Close", 33 | subject: "Sample", 34 | "@odata.type": "Microsoft.Dynamics.CRM.opportunityclose", 35 | "opportunityid@odata.bind": "opportunities(5deb00bd-5685-ea11-a812-000d3a7f4cf5)", 36 | }, 37 | } as unknown; 38 | 39 | expect(JSON.stringify(odata)).toBe(JSON.stringify(expected)); 40 | expect(odata.getMetadata).toBeDefined(); 41 | const metadata = odata.getMetadata(); 42 | const expectedMetadata = { 43 | parameterTypes: { 44 | OpportunityClose: { typeName: "mscrm.opportunityclose", structuralProperty: 5 }, 45 | Caller: { typeName: "Edm.String", structuralProperty: 1 }, 46 | Status: { typeName: "Edm.Int32", structuralProperty: 1 }, 47 | }, 48 | operationType: 0, 49 | operationName: "WinOpportunity", 50 | }; 51 | expect(JSON.stringify(metadata)).toBe(JSON.stringify(expectedMetadata)); 52 | }); 53 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/unit-tests/field-customer.test.ts: -------------------------------------------------------------------------------- 1 | import { setMetadataCache } from "../../../metadata"; 2 | import { odataify } from "../../odataify/odataify"; 3 | import { EntityReference, sdkify } from "../../.."; 4 | import { opportunityMetadata, Opportunity } from "../../../dataverse-gen/entities/Opportunity"; 5 | import { accountMetadata } from "../../../dataverse-gen/entities/Account"; 6 | test("odataify customer lookups", async () => { 7 | setMetadataCache({ 8 | entities: { opportunity: opportunityMetadata, account: accountMetadata }, 9 | }); 10 | const sdkEntity = { 11 | logicalName: opportunityMetadata.logicalName, 12 | customerid: new EntityReference("account", "123"), 13 | } as Opportunity; 14 | 15 | const odataEntity = await odataify("Create", sdkEntity); 16 | expect(odataEntity).toBeDefined(); 17 | const expectedOdata = { 18 | "@odata.type": "Microsoft.Dynamics.CRM.opportunity", 19 | "customerid_account@odata.bind": "accounts(123)", 20 | }; 21 | expect(JSON.stringify(odataEntity)).toBe(JSON.stringify(expectedOdata)); 22 | }); 23 | 24 | test("sdkify lookups", async () => { 25 | setMetadataCache({ entities: { opportunity: opportunityMetadata } }); 26 | const recordOdata = { 27 | "@odata.context": 28 | // eslint-disable-next-line @microsoft/power-apps/use-relative-uri 29 | "https://develop1v9demo.crm11.dynamics.com/api/data/v9.1/$metadata#opportunities(_customerid_value)/$entity", 30 | // eslint-disable-next-line quotes 31 | "@odata.etag": 'W/"24587791"', 32 | "_customerid_value@OData.Community.Display.V1.FormattedValue": "Test Unit", 33 | "_customerid_value@Microsoft.Dynamics.CRM.associatednavigationproperty": "customerid_contact", 34 | "_customerid_value@Microsoft.Dynamics.CRM.lookuplogicalname": "contact", 35 | _customerid_value: "3191b1c8-4c85-ea11-a812-000d3a7f4cf5", 36 | opportunityid: "0faa41b7-4c85-ea11-a811-00224801badc", 37 | }; 38 | 39 | const sdkRecord = await sdkify(recordOdata, "opportunity"); 40 | const expected = { 41 | opportunityid: "0faa41b7-4c85-ea11-a811-00224801badc", 42 | logicalName: "opportunity", 43 | formattedValues: { 44 | _customerid_value: "Test Unit", 45 | }, 46 | customerid: { 47 | entityType: "contact", 48 | id: "3191b1c8-4c85-ea11-a812-000d3a7f4cf5", 49 | name: "Test Unit", 50 | }, 51 | }; 52 | 53 | expect(JSON.stringify(sdkRecord)).toBe(JSON.stringify(expected)); 54 | }); 55 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/unit-tests/field-lookup.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sonarjs/no-duplicate-string */ 2 | import { accountMetadata, Account } from "../../../dataverse-gen/entities/Account"; 3 | import { setMetadataCache } from "../../../metadata"; 4 | import { odataify } from "../../odataify/odataify"; 5 | import { EntityReference, sdkify } from "../../.."; 6 | 7 | test("odataify lookups", async () => { 8 | setMetadataCache({ entities: { account: accountMetadata } }); 9 | const accountSdk = { 10 | logicalName: accountMetadata.logicalName, 11 | parentaccountid: new EntityReference("account", "123"), 12 | } as Account; 13 | 14 | const accountOdata = await odataify("Create", accountSdk); 15 | expect(accountOdata).toBeDefined(); 16 | const expectedOdata = { 17 | "@odata.type": "Microsoft.Dynamics.CRM.account", 18 | "parentaccountid@odata.bind": "accounts(123)", 19 | }; 20 | expect(JSON.stringify(accountOdata)).toBe(JSON.stringify(expectedOdata)); 21 | }); 22 | test("odataify lookups", async () => { 23 | setMetadataCache({ entities: { account: accountMetadata } }); 24 | const odataRecord = { 25 | // eslint-disable-next-line @microsoft/power-apps/use-relative-uri 26 | "@odata.context": "https://develop1v9demo.crm11.dynamics.com/api/data/v9.1/$metadata#accounts/$entity", 27 | // eslint-disable-next-line quotes 28 | "@odata.etag": 'W/"24588382"', 29 | name: "Account 1", 30 | "_preferredsystemuserid_value@OData.Community.Display.V1.FormattedValue": "Scott Durow", 31 | "_preferredsystemuserid_value@Microsoft.Dynamics.CRM.lookuplogicalname": "systemuser", 32 | _preferredsystemuserid_value: "d3b787e3-8c39-48e6-97fc-26dfc14f104d", 33 | }; 34 | const sdkRecord = await sdkify(odataRecord, "account"); 35 | 36 | const expectedSdk = { 37 | name: "Account 1", 38 | logicalName: "account", 39 | formattedValues: { 40 | _preferredsystemuserid_value: "Scott Durow", // This shouldn't really be output because it's in the EntityReference 41 | }, 42 | preferredsystemuserid: { 43 | entityType: "systemuser", 44 | id: "d3b787e3-8c39-48e6-97fc-26dfc14f104d", 45 | name: "Scott Durow", 46 | }, 47 | }; 48 | expect(JSON.stringify(sdkRecord)).toBe(JSON.stringify(expectedSdk)); 49 | }); 50 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/unit-tests/field-multiselect.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import { sdkify, odataify } from "../.."; 3 | import { accountMetadata, Account } from "../../../dataverse-gen/entities/Account"; 4 | import { setMetadataCache } from "../../.."; 5 | import { socialprofile_community } from "../../../dataverse-gen/enums/socialprofile_community"; 6 | 7 | test("sdkify MultiSelects", async () => { 8 | setMetadataCache({ entities: { account: accountMetadata } }); 9 | const accountSdk = { 10 | logicalName: accountMetadata.logicalName, 11 | name: "Account 1", 12 | cdsify_multiselect: [socialprofile_community.Facebook, socialprofile_community.Other], 13 | } as Account; 14 | 15 | const odata = await odataify("Create", accountSdk); 16 | expect(odata).toBeDefined(); 17 | expect(odata.cdsify_multiselect).toBe("1,0"); 18 | expect(odata.name).toBe("Account 1"); 19 | expect(odata["@odata.type"]).toBe("Microsoft.Dynamics.CRM.account"); 20 | }); 21 | 22 | test("sdkify MultiSelects", async () => { 23 | setMetadataCache({ entities: { account: accountMetadata } }); 24 | const odataRecord = { 25 | // eslint-disable-next-line @microsoft/power-apps/use-relative-uri 26 | "@odata.context": "https://org.crm11.dynamics.com/api/data/v9.1/$metadata#accounts/$entity", 27 | // eslint-disable-next-line quotes 28 | "@odata.etag": 'W/"24588414"', 29 | "cdsify_multiselect@OData.Community.Display.V1.FormattedValue": "Facebook", 30 | cdsify_multiselect: "1,2", 31 | name: "Account 1", 32 | }; 33 | 34 | const sdkRecord = (await sdkify(odataRecord, "account")) as Account; 35 | 36 | expect(sdkRecord.cdsify_multiselect).toBeDefined(); 37 | expect(sdkRecord.cdsify_multiselect?.length).toBe(2); 38 | expect(sdkRecord.cdsify_multiselect && sdkRecord.cdsify_multiselect[0]).toBe(socialprofile_community.Twitter); 39 | expect(sdkRecord.cdsify_multiselect && sdkRecord.cdsify_multiselect[1]).toBe(socialprofile_community.Facebook); 40 | }); 41 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/unit-tests/lookup-jit-metadata.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { accountMetadata, Account } from "../../../dataverse-gen/entities/Account"; 3 | import { EntityReference, odataify, setMetadataCache } from "../../.."; 4 | import { XrmApi } from "../../../webapi/XrmApi"; 5 | import { XrmUtility } from "../../../webapi"; 6 | import { sdkify } from "../../sdkify/sdkify"; 7 | 8 | test("odataify lookups - pascal case navigation property", async () => { 9 | setMetadataCache({ entities: { account: accountMetadata } }); 10 | 11 | const account = { 12 | logicalName: accountMetadata.logicalName, 13 | cdsify_account1: new EntityReference(accountMetadata.logicalName, "123"), 14 | } as Account; 15 | 16 | (global as any).Xrm = new XrmApi(); 17 | Xrm.Utility = new XrmUtility(); 18 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 19 | Xrm.Utility.getEntityMetadata = jest.fn().mockImplementation((entityName: string, _attributes?: string[]) => { 20 | return entityName === "resource" 21 | ? ({ 22 | EntitySetName: "Resources", 23 | } as Xrm.Metadata.EntityMetadata) 24 | : null; 25 | }); 26 | const odataEntity = await odataify("Create", account); 27 | expect(odataEntity).toBeDefined(); 28 | 29 | if (odataEntity) { 30 | expect(odataEntity["cdsify_Account1@odata.bind"]).toEqual("accounts(123)"); 31 | } 32 | }); 33 | 34 | test("sdkify lookups - pascal case navigation property", async () => { 35 | setMetadataCache({ entities: { account: accountMetadata } }); 36 | 37 | const account = { 38 | accountid: "123", 39 | _cdsify_account1_value: "123", 40 | "_cdsify_account1_value@Microsoft.Dynamics.CRM.associatednavigationproperty": "cdsify_Account1", 41 | "_cdsify_account1_value@Microsoft.Dynamics.CRM.lookuplogicalname": "account", 42 | }; 43 | const sdkAccount = (await sdkify(account, accountMetadata.logicalName)) as Account; 44 | 45 | expect(sdkAccount.cdsify_account1).toBeDefined(); 46 | expect(sdkAccount.cdsify_account1?.id).toEqual("123"); 47 | expect(sdkAccount.cdsify_account1?.entityType).toEqual("account"); 48 | }); 49 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/unit-tests/primaryid-mapping.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sonarjs/no-duplicate-string */ 2 | import { Account, accountMetadata } from "../../../dataverse-gen/entities/Account"; 3 | import { setMetadataCache, odataify } from "../../.."; 4 | 5 | describe("odataify", () => { 6 | it("leaves explicit primary attribute id and removes id", async () => { 7 | setMetadataCache({ entities: { account: accountMetadata } }); 8 | 9 | // When odataify maps an output entity, the primary attribute id can be either id or explicity the name of the attribute 10 | // When id, it will be mapped over to the primary id attribute 11 | 12 | const explicitIdAttribute = { 13 | logicalName: accountMetadata.logicalName, 14 | accountid: "123", 15 | id: "456", 16 | } as Account; 17 | 18 | const explicitIdAttributeOutput = await odataify("Action", explicitIdAttribute); 19 | expect(explicitIdAttributeOutput.accountid).toBe("123"); 20 | expect(explicitIdAttributeOutput.id).toBeUndefined(); 21 | }); 22 | 23 | it("adds primary attribute id and removes id", async () => { 24 | setMetadataCache({ entities: { account: accountMetadata } }); 25 | 26 | // When odataify maps an output entity, the primary attribute id can be either id or explicity the name of the attribute 27 | // When id, it will be mapped over to the primary id attribute 28 | 29 | const explicitIdAttribute = { 30 | logicalName: accountMetadata.logicalName, 31 | id: "456", 32 | } as Account; 33 | 34 | const explicitIdAttributeOutput = await odataify("Action", explicitIdAttribute); 35 | expect(explicitIdAttributeOutput.accountid).toBe("456"); 36 | expect(explicitIdAttributeOutput.id).toBeUndefined(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/dataverse-ify/__tests__/unit-tests/update.test.ts: -------------------------------------------------------------------------------- 1 | import { Contact, contactMetadata } from "../../../dataverse-gen/entities/Contact"; 2 | import { setMetadataCache } from "../../../metadata"; 3 | import { odataify } from "../.."; 4 | 5 | test("update removes primary ID attribute", async () => { 6 | setMetadataCache({ entities: { contact: contactMetadata } }); 7 | const sdkRecord = { 8 | logicalName: contactMetadata.logicalName, 9 | contactid: "1234", 10 | lastname: "Test", 11 | lastonholdtime: new Date(), 12 | } as Contact; 13 | 14 | const odataRecord = await odataify("Update", sdkRecord); 15 | expect(odataRecord.contactid).toBeUndefined(); 16 | }); 17 | -------------------------------------------------------------------------------- /src/dataverse-ify/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./sdkify/sdkify"; 2 | export * from "./odataify/odataify"; 3 | export * from "./DataverseClient"; 4 | -------------------------------------------------------------------------------- /src/dataverse-ify/sdkify/dateReviver.ts: -------------------------------------------------------------------------------- 1 | export function dateReviver(_key: string, value: string): string | Date { 2 | if (typeof value === "string") { 3 | const a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 4 | if (a) { 5 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); 6 | } 7 | } 8 | return value; 9 | } 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Default exports for dataverse-ify 2 | // Excludes the xrm.webapi implementation - this must be imported explicitly 3 | export * from "./types"; 4 | export * from "./metadata"; 5 | export * from "./dataverse-ify"; 6 | -------------------------------------------------------------------------------- /src/metadata/EntityWebApiMetadata.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary } from "../types/Dictionary"; 2 | 3 | export interface EntityWebApiMetadata { 4 | typeName?: string; 5 | logicalName: string; 6 | collectionName: string; 7 | primaryIdAttribute: string; 8 | attributeTypes: Dictionary; 9 | navigation?: Dictionary; 10 | } 11 | -------------------------------------------------------------------------------- /src/metadata/WebApiExecuteRequestMetadata.ts: -------------------------------------------------------------------------------- 1 | import { OperationType } from "../types/OperationType"; 2 | import { Dictionary } from "../types/Dictionary"; 3 | import { ParameterType } from "../types/ParameterType"; 4 | 5 | export interface WebApiExecuteRequestMetadata { 6 | boundParameter?: string; 7 | parameterTypes: Dictionary; 8 | operationType: OperationType; 9 | operationName: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/metadata/fixWebresourceXrm.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataCache } from "./MetadataCache"; 2 | import { Dictionary } from "../types/Dictionary"; 3 | export function fixWebresourceXrm(): void { 4 | // This is a hack to fix a bug in the UCI where the entitySetNames are not set 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | const windowStatic: any = window; 7 | if (windowStatic.ENTITY_SET_NAMES) return; 8 | 9 | const metadata = getMetadataCache(); 10 | const entNames: Dictionary = {}; 11 | for (const entity in metadata.entitySetNames) { 12 | entNames[entity] = metadata.entitySetNames[entity]; 13 | } 14 | 15 | windowStatic.ENTITY_SET_NAMES = JSON.stringify(entNames); 16 | const primaryKeys: Dictionary = {}; 17 | for (const entity in metadata.entities) { 18 | primaryKeys[entity] = metadata.entities[entity].primaryIdAttribute; 19 | } 20 | windowStatic.ENTITY_PRIMARY_KEYS = JSON.stringify(primaryKeys); 21 | } 22 | -------------------------------------------------------------------------------- /src/metadata/getEntityMetadataFromRecord.ts: -------------------------------------------------------------------------------- 1 | import { IEntity } from "../types/IEntity"; 2 | import { getMetadataFromEntitySet, getMetadataByLogicalName } from "./MetadataCache"; 3 | import { EntityWebApiMetadata } from "./EntityWebApiMetadata"; 4 | import { WebApiExecuteRequestMetadata } from "./WebApiExecuteRequestMetadata"; 5 | import { isNullOrUndefined } from "../webapi/utils/NullOrUndefined"; 6 | import { sdkifyOptions } from "../dataverse-ify/sdkify/sdkify"; 7 | 8 | const actionPrefix = "Microsoft.Dynamics.CRM."; 9 | // eslint-disable-next-line sonarjs/cognitive-complexity 10 | export function getEntityMetadataFromRecord( 11 | entityRecord: IEntity, 12 | logicalName?: string, 13 | options?: sdkifyOptions, 14 | ): EntityWebApiMetadata | undefined { 15 | let entityMetadata: EntityWebApiMetadata | WebApiExecuteRequestMetadata | null; 16 | entityRecord.logicalName = entityRecord.logicalName || (logicalName as string); // allow passing the logical name rather than using the @odata.context 17 | if (isNullOrUndefined(entityRecord.logicalName)) { 18 | // Get the @data.context to get the logical name 19 | // E.g. https://org.crm11.dynamics.com/api/data/v9.0/$metadata#accounts(name,parentaccountid)/$entity 20 | const defaultMetadata = options && options.allowPassthroughMapping ? ({} as EntityWebApiMetadata) : undefined; 21 | const entitySetName = getOdataContextName(entityRecord); 22 | if (entitySetName) { 23 | if (entitySetName.startsWith(actionPrefix)) { 24 | const actionName = entitySetName.substring(actionPrefix.length); 25 | entityMetadata = getMetadataByLogicalName(actionName, defaultMetadata); 26 | if (entityMetadata != null) { 27 | entityRecord.logicalName = entityMetadata.logicalName; 28 | } 29 | } else { 30 | // Find the logical name from the entity set name 31 | entityMetadata = getMetadataFromEntitySet(entitySetName, defaultMetadata); 32 | entityRecord.logicalName = (entityMetadata as EntityWebApiMetadata).logicalName; 33 | } 34 | } else if (!options?.allowPassthroughMapping) { 35 | throw new Error("Cannot find the odata.context to get the logical name"); 36 | } else return undefined; 37 | } else { 38 | // Get metadata 39 | entityMetadata = getMetadataByLogicalName(entityRecord.logicalName); 40 | } 41 | return entityMetadata; 42 | } 43 | 44 | function getOdataContextName(entityRecord: IEntity) { 45 | const oDataContext = entityRecord["@odata.context"] as string; 46 | const contextRegex = /\$metadata#([\w.]*)(\([\w()]*\))?(\/\$entity)?/g; 47 | const match = contextRegex.exec(oDataContext); 48 | return match != null && match.length > 1 ? match[1] : undefined; 49 | } 50 | -------------------------------------------------------------------------------- /src/metadata/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./MetadataCache"; 2 | -------------------------------------------------------------------------------- /src/types/ActivityParty.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import { EntityReference } from "./EntityReference"; 3 | import { IEntity } from "./IEntity"; 4 | import { Guid } from "./Guid"; 5 | export const activitypartyMetadata = { 6 | typeName: "mscrm.activityparty", 7 | logicalName: "activityparty", 8 | collectionName: "activityparties", 9 | primaryIdAttribute: "activitypartyid", 10 | attributeTypes: { 11 | // Numeric Types 12 | addressusedemailcolumnnumber: "Integer", 13 | effort: "Double", 14 | versionnumber: "BigInt", 15 | // Option sets 16 | instancetypecode: "Optionset", 17 | participationtypemask: "Optionset", 18 | // Date Formats 19 | scheduledend: "DateOnly:UserLocal", 20 | scheduledstart: "DateOnly:UserLocal", 21 | }, 22 | navigation: { 23 | activityid: ["activitypointer"], 24 | partyid: ["account", "contact", "knowledgearticle", "queue", "systemuser"], 25 | }, 26 | }; 27 | export const enum activityparty_participationtypemask { 28 | Sender = 1, 29 | To_Recipient = 2, 30 | CC_Recipient = 3, 31 | BCC_Recipient = 4, 32 | Required_attendee = 5, 33 | Optional_attendee = 6, 34 | Organizer = 7, 35 | Regarding = 8, 36 | Owner = 9, 37 | Resource = 10, 38 | Customer = 11, 39 | } 40 | export interface ActivityParty extends IEntity { 41 | /**Activity LookupType 42 | Unique identifier of the activity associated with the activity party. (A "party" is any person who is associated with an activity.) 43 | */ 44 | activityid?: EntityReference; 45 | /**Activity Party Unique identifier Type 46 | Unique identifier of the activity party. 47 | */ 48 | activitypartyid?: Guid; 49 | /**Participation Type activityparty_activityparty_participationtypemask 50 | Role of the person in the activity, such as sender, to, cc, bcc, required, optional, organizer, regarding, or owner. 51 | */ 52 | participationtypemask?: activityparty_participationtypemask; 53 | /**Party LookupType 54 | Unique identifier of the party associated with the activity. 55 | */ 56 | partyid?: EntityReference; 57 | /**Resource Specification LookupType 58 | Unique identifier of the resource specification for the activity party. 59 | */ 60 | resourcespecid?: EntityReference; 61 | } 62 | -------------------------------------------------------------------------------- /src/types/AttributeTypes.ts: -------------------------------------------------------------------------------- 1 | export enum AttributeTypes { 2 | EntityCollection = 0, 3 | EntityReference = 1, 4 | Money = 2, 5 | OptionSetValue = 3, 6 | MultiSelectOptionSetValue = 4, 7 | Date = 5, 8 | DateTime = 6, 9 | Unknown = 1000, 10 | } 11 | -------------------------------------------------------------------------------- /src/types/Dictionary.ts: -------------------------------------------------------------------------------- 1 | export interface Dictionary { 2 | [key: string]: T; 3 | } 4 | -------------------------------------------------------------------------------- /src/types/Entity.ts: -------------------------------------------------------------------------------- 1 | import { IEntity } from "./IEntity"; 2 | import { EntityReference } from "./EntityReference"; 3 | export class Entity { 4 | static containsFields(instance: unknown, keys: string[]): boolean { 5 | let allOk = true; 6 | for (const key of keys) { 7 | // eslint-disable-next-line no-prototype-builtins 8 | allOk = allOk && (instance as object).hasOwnProperty(key); 9 | if (!allOk) break; 10 | } 11 | return allOk; 12 | } 13 | 14 | static toEntityReference(instance: IEntity): EntityReference { 15 | return new EntityReference(instance.logicalName, instance.id); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/types/EntityCollection.ts: -------------------------------------------------------------------------------- 1 | import { IEntity } from "./IEntity"; 2 | import { IEntityCollection } from "./IEntityCollection"; 3 | export class EntityCollection implements IEntityCollection { 4 | entities: Array; 5 | pagingCooking?: string; 6 | nextLink?: string; 7 | moreRecords?: boolean; 8 | totalRecordCount?: number; 9 | totalRecordCountExceeded?: boolean; 10 | constructor(entities?: Array) { 11 | this.entities = entities ?? []; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/types/EntityReference.ts: -------------------------------------------------------------------------------- 1 | import { IEntity } from "./IEntity"; 2 | import { Guid } from "./Guid"; 3 | import { getMetadata } from "../metadata/MetadataCache"; 4 | 5 | export class EntityReference { 6 | constructor(logicalName?: string, id?: Guid | null, name?: string) { 7 | this.entityType = logicalName; 8 | this.id = id as string | undefined; 9 | this.name = name; 10 | } 11 | entityType?: string; 12 | id?: string; 13 | name?: string; 14 | } 15 | 16 | export function toEntityReference(record: IEntity): EntityReference { 17 | let id = record.id; 18 | // Set the id field if not already 19 | if (id === undefined || id === null) { 20 | const metadata = getMetadata(record); 21 | id = record[metadata.primaryIdAttribute] as string; 22 | } 23 | 24 | return { 25 | id: id, 26 | entityType: record.logicalName, 27 | } as EntityReference; 28 | } 29 | 30 | export function fromEntityReference(record: IEntity, entityReference: EntityReference): void { 31 | if (record.logicalName !== entityReference.entityType) 32 | throw new Error(`Cannot map ${entityReference.entityType} into ${record.logicalName}`); 33 | record.id = entityReference.id; 34 | } 35 | -------------------------------------------------------------------------------- /src/types/Guid.ts: -------------------------------------------------------------------------------- 1 | import { getHeaderValue } from "../webapi/utils/GetHeaderValue"; 2 | 3 | export type Guid = string; 4 | export function trimGuid(guid: string): string { 5 | return guid.replace("{", "").replace("}", ""); 6 | } 7 | export function guidEqual(guid1: string | undefined, guid2: string | undefined): boolean { 8 | if (guid1 && guid2) { 9 | return trimGuid(guid1).toLowerCase() === trimGuid(guid2).toLowerCase(); 10 | } else if (!guid1 && !guid2) { 11 | return true; 12 | } 13 | 14 | return false; 15 | } 16 | export function getGuidFromODataUrl(url?: string) { 17 | if (url) { 18 | const guidMatch = /\(([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\)/g.exec(url); 19 | if (guidMatch && guidMatch.length > 0) return guidMatch[1]; 20 | } 21 | throw new Error("Could not find the guid in response"); 22 | } 23 | 24 | export function getGuidFromHeaders(headers: unknown) { 25 | const account1Url = getHeaderValue(headers, "odata-entityid") || getHeaderValue(headers, "Location"); 26 | return getGuidFromODataUrl(account1Url); 27 | } 28 | -------------------------------------------------------------------------------- /src/types/IEntity.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary } from "./Dictionary"; 2 | import { Guid } from "./Guid"; 3 | export interface IEntity { 4 | id?: Guid | null; 5 | logicalName: string; 6 | [index: string]: unknown; 7 | formattedValues?: Dictionary; 8 | } 9 | -------------------------------------------------------------------------------- /src/types/IEntityCollection.ts: -------------------------------------------------------------------------------- 1 | import { IEntity } from "./IEntity"; 2 | export interface IEntityCollection { 3 | entities: Array; 4 | pagingCooking?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/types/IEntityReference.ts: -------------------------------------------------------------------------------- 1 | import { trimGuid } from "./Guid"; 2 | import { IEntity } from "./IEntity"; 3 | import { getEntitySetName } from "../metadata"; 4 | 5 | export interface IEntityReference { 6 | entityType: string; 7 | id: string; 8 | } 9 | export function odatifyEntityReference(entitySetName: string, id: string): string { 10 | // We set null ids to string null so we can pick up and run a disassociate 11 | return `${entitySetName}(${id === null ? "null" : trimGuid(id)})`; 12 | } 13 | export async function getNavigationPathForEntityReference( 14 | entity: IEntity, 15 | attributeLogicalName: string, 16 | ): Promise { 17 | const entityReference = entity[attributeLogicalName] as IEntityReference; 18 | const collectionName = await getEntitySetName(entityReference.entityType); 19 | if (collectionName) { 20 | return odatifyEntityReference(collectionName, entityReference.id); 21 | } 22 | throw new Error(`Cannot find navigation metadata for ${attributeLogicalName}`); 23 | } 24 | -------------------------------------------------------------------------------- /src/types/OperationType.ts: -------------------------------------------------------------------------------- 1 | export enum OperationType { 2 | Action = 0, 3 | Function = 1, 4 | CRUD = 2, 5 | } 6 | -------------------------------------------------------------------------------- /src/types/ParameterType.ts: -------------------------------------------------------------------------------- 1 | import { StructuralProperty } from "./StructuralProperty"; 2 | export interface ParameterType { 3 | typeName: string; 4 | structuralProperty: StructuralProperty; 5 | } 6 | -------------------------------------------------------------------------------- /src/types/RequestWithTarget.ts: -------------------------------------------------------------------------------- 1 | import { IEntityReference } from "./IEntityReference"; 2 | export interface RequestWithTarget { 3 | target: IEntityReference; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/RetrieveMultipleResult.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @microsoft/power-apps/avoid-unpub-api 2 | export interface RetrieveMultipleResultEx extends Xrm.RetrieveMultipleResult { 3 | fetchXmlPagingCookie?: string; 4 | moreRecords?: boolean; 5 | totalRecordCount?: number; 6 | totalRecordCountExceeded?: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/StructuralProperty.ts: -------------------------------------------------------------------------------- 1 | export enum StructuralProperty { 2 | Unknown = 0, 3 | PrimitiveType = 1, 4 | ComplexType = 2, 5 | EnumerationType = 3, 6 | Collection = 4, 7 | EntityType = 5, 8 | } 9 | -------------------------------------------------------------------------------- /src/types/WebApiError.ts: -------------------------------------------------------------------------------- 1 | export interface WebApiError extends Error { 2 | code: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/types/WebApiExecuteRequest.ts: -------------------------------------------------------------------------------- 1 | import { WebApiExecuteRequestMetadata } from "../metadata/WebApiExecuteRequestMetadata"; 2 | 3 | export interface WebApiExecuteRequest { 4 | logicalName?: string; 5 | [index: string]: unknown; 6 | } 7 | 8 | export interface WebApiExecuteRequestWithMetadata { 9 | logicalName?: string; 10 | [index: string]: unknown; 11 | getMetadata(): WebApiExecuteRequestMetadata; 12 | } 13 | export class WebApiExecuteRequestBase implements WebApiExecuteRequestWithMetadata { 14 | [index: string]: unknown; 15 | constructor(metadata: WebApiExecuteRequestMetadata, parameters?: Record) { 16 | this.getMetadata = () => { 17 | return metadata; 18 | }; 19 | if (parameters) { 20 | for (const key in parameters) { 21 | this[key] = parameters[key]; 22 | } 23 | } 24 | } 25 | getMetadata(): WebApiExecuteRequestMetadata { 26 | throw "Not implemented"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/types/WebApiRequest.ts: -------------------------------------------------------------------------------- 1 | export interface WebApiRequestDefinition { 2 | action: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; 3 | path: string; 4 | options?: string; 5 | data?: unknown; 6 | additionalHeaders?: { [key: string]: string }; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ActivityParty"; 2 | export * from "./AttributeTypes"; 3 | export * from "./Dictionary"; 4 | export * from "./Entity"; 5 | export * from "./EntityCollection"; 6 | export * from "./EntityReference"; 7 | export * from "./Guid"; 8 | export * from "./IEntity"; 9 | export * from "./IEntityCollection"; 10 | export * from "./OperationType"; 11 | export * from "./ParameterType"; 12 | export * from "./RequestWithTarget"; 13 | export * from "./StructuralProperty"; 14 | export * from "./WebApiExecuteRequest"; 15 | export * from "./WebApiRequest"; 16 | export * from "./requests"; 17 | -------------------------------------------------------------------------------- /src/types/requests/AssociateRequest.ts: -------------------------------------------------------------------------------- 1 | import { IEntityReference } from "../IEntityReference"; 2 | 3 | export interface AssociateRequest { 4 | target: IEntityReference; 5 | relationship: string; 6 | relatedEntities: IEntityReference[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/requests/Create.ts: -------------------------------------------------------------------------------- 1 | import { Guid } from "../Guid"; 2 | import { IEntity } from "../IEntity"; 3 | import { WebApiExecuteRequest } from "../WebApiExecuteRequest"; 4 | 5 | export interface CreateRequest extends WebApiExecuteRequest { 6 | target: IEntity; 7 | } 8 | 9 | export interface CreateResponse { 10 | id: Guid; 11 | } 12 | -------------------------------------------------------------------------------- /src/types/requests/Delete.ts: -------------------------------------------------------------------------------- 1 | import { EntityReference } from "../EntityReference"; 2 | import { WebApiExecuteRequest } from "../WebApiExecuteRequest"; 3 | 4 | export interface DeleteRequest extends WebApiExecuteRequest { 5 | target: EntityReference; 6 | } 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 9 | export interface DeleteResponse {} 10 | -------------------------------------------------------------------------------- /src/types/requests/DisassociateRequest.ts: -------------------------------------------------------------------------------- 1 | import { IEntityReference } from "../IEntityReference"; 2 | export interface DisassociateRequest { 3 | target: IEntityReference; 4 | relationship: string; 5 | relatedEntityId: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/types/requests/ExecuteMultiple.ts: -------------------------------------------------------------------------------- 1 | import { WebApiExecuteRequest } from "../WebApiExecuteRequest"; 2 | 3 | export interface ExecuteMultipleRequest extends WebApiExecuteRequest { 4 | settings?: { ContinueOnError?: boolean; ReturnResponses?: boolean }; 5 | requests: unknown[]; 6 | } 7 | 8 | export interface ExecuteMultipleResponse { 9 | isFaulted: boolean; 10 | responses: unknown[]; 11 | } 12 | -------------------------------------------------------------------------------- /src/types/requests/Update.ts: -------------------------------------------------------------------------------- 1 | import { IEntity } from "../IEntity"; 2 | import { WebApiExecuteRequest } from "../WebApiExecuteRequest"; 3 | 4 | export interface UpdateRequest extends WebApiExecuteRequest { 5 | target: IEntity; 6 | } 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 9 | export interface UpdateResponse {} 10 | -------------------------------------------------------------------------------- /src/types/requests/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AssociateRequest"; 2 | export * from "./DisassociateRequest"; 3 | export * from "./ExecuteMultiple"; 4 | export * from "./Create"; 5 | export * from "./Update"; 6 | export * from "./Delete"; 7 | -------------------------------------------------------------------------------- /src/webapi/GlobalContextStatic.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sonarjs/no-duplicate-string */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { XrmApi } from "./XrmApi"; 4 | export class GlobalContextStatic { 5 | private _userId: string; 6 | constructor(userId: string) { 7 | this._userId = userId; 8 | } 9 | client?: Xrm.ClientContext; 10 | organizationSettings?: Xrm.OrganizationSettings; 11 | userSettings?: Xrm.UserSettings; 12 | getAdvancedConfigSetting(_setting: "MaxChildIncidentNumber" | "MaxIncidentMergeNumber"): number { 13 | throw new Error("Method not implemented."); 14 | } 15 | getClientUrl(): string { 16 | // Return the full url of the endpoint in the form "https://org.crm11.dynamics.com" 17 | return XrmApi.webapiInstance.getClientUrl().toString(); 18 | } 19 | getCurrentAppName(): Xrm.Async.PromiseLike { 20 | throw new Error("Method not implemented."); 21 | } 22 | getCurrentAppProperties(): Xrm.Async.PromiseLike { 23 | throw new Error("Method not implemented."); 24 | } 25 | getCurrentAppUrl(): string { 26 | throw new Error("Method not implemented."); 27 | } 28 | getCurrentTheme(): Xrm.Theme { 29 | throw new Error("Method not implemented."); 30 | } 31 | getIsAutoSaveEnabled(): boolean { 32 | throw new Error("Method not implemented."); 33 | } 34 | getOrgLcid(): number { 35 | throw new Error("Method not implemented."); 36 | } 37 | getOrgUniqueName(): string { 38 | throw new Error("Method not implemented."); 39 | } 40 | getQueryStringParameters(): { 41 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 42 | [index: string]: any; 43 | } { 44 | throw new Error("Method not implemented."); 45 | } 46 | getTimeZoneOffsetMinutes(): number { 47 | throw new Error("Method not implemented."); 48 | } 49 | getUserId(): string { 50 | return this._userId; 51 | } 52 | getUserLcid(): number { 53 | throw new Error("Method not implemented."); 54 | } 55 | getUserName(): string { 56 | throw new Error("Method not implemented."); 57 | } 58 | getUserRoles(): string[] { 59 | throw new Error("Method not implemented."); 60 | } 61 | getVersion(): string { 62 | return XrmApi.webapiInstance.requestImplementation.apiVersion; 63 | } 64 | isOnPremise(): boolean { 65 | throw new Error("Method not implemented."); 66 | } 67 | prependOrgName(_path: string): string { 68 | throw new Error("Method not implemented."); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/webapi/XrmApi.ts: -------------------------------------------------------------------------------- 1 | import { WebApiBase } from "./WebApiBase"; 2 | import { XrmUtility } from "./XrmUtility"; 3 | // eslint-disable-next-line @microsoft/power-apps/avoid-unpub-api 4 | export class XrmApi implements Xrm.XrmStatic { 5 | App!: Xrm.App; 6 | Page!: Xrm.Page; 7 | Navigation!: Xrm.Navigation; 8 | Utility!: Xrm.Utility; 9 | Mobile!: Xrm.Mobile; 10 | Panel!: Xrm.Panel; 11 | WebApi!: Xrm.WebApi; 12 | Device!: Xrm.Device; 13 | Encoding!: Xrm.Encoding; 14 | static xrmInstance: XrmApi; 15 | static webapiInstance: WebApiBase; 16 | static xrmGlobalContext: Xrm.GlobalContext; 17 | static createInstance(webApiImplementation: WebApiBase) { 18 | if (!XrmApi.xrmInstance) { 19 | XrmApi.xrmInstance = new XrmApi(); 20 | XrmApi.xrmInstance.WebApi = webApiImplementation as unknown as Xrm.WebApi; 21 | XrmApi.xrmInstance.Utility = new XrmUtility(); 22 | } 23 | return XrmApi.xrmInstance; 24 | } 25 | 26 | static getGlobalContext() { 27 | return XrmApi.xrmGlobalContext; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/webapi/__tests__/test-values.ts: -------------------------------------------------------------------------------- 1 | export const TEST_STRING = ["'abc!\"£$%^&*()?<> test string 1", "\"abc!'£$%^&*()?<> test string 2"]; 2 | export const TEST_INTEGER = [123, 456]; 3 | export const TEST_DECIMAL = [123.456, 789.111111]; 4 | export const TEST_FLOAT = [1.2e-8]; 5 | export const TEST_DATE = [new Date(60000), new Date(700000)]; 6 | export const TEST_GUID = ["9ae14746-030d-ed11-b83e-0022483d2320", "6ae14746-030d-ed11-b83e-0022483d2320"]; 7 | -------------------------------------------------------------------------------- /src/webapi/__tests__/unit-odataquery.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | import { TEST_STRING } from "./test-values"; 3 | 4 | describe("XrmWebApiNode", () => { 5 | it("creates accounts and retrieveMultiple by id", async () => { 6 | // Arrange 7 | const webApiService = { 8 | createRecord: jest 9 | .fn() 10 | .mockImplementationOnce(() => { 11 | return { id: "1" }; 12 | }) 13 | .mockImplementationOnce(() => { 14 | return { id: "2" }; 15 | }), 16 | retrieveMultipleRecords: jest.fn().mockImplementationOnce(() => { 17 | return { 18 | entities: [ 19 | { 20 | name: TEST_STRING[0], 21 | accountid: "1", 22 | }, 23 | { 24 | name: TEST_STRING[1], 25 | accountid: "2", 26 | }, 27 | ], 28 | }; 29 | }), 30 | } as unknown as Xrm.WebApi; 31 | 32 | // Act 33 | const accounts = await someLogic(TEST_STRING[0], TEST_STRING[1], webApiService); 34 | 35 | // Assert 36 | expect(webApiService.createRecord).toHaveBeenNthCalledWith( 37 | 1, 38 | "account", 39 | expect.objectContaining({ name: TEST_STRING[0] }), 40 | ); 41 | 42 | expect(webApiService.createRecord).toHaveBeenNthCalledWith( 43 | 2, 44 | "account", 45 | expect.objectContaining({ name: TEST_STRING[1] }), 46 | ); 47 | 48 | expect(webApiService.retrieveMultipleRecords).toHaveBeenCalled(); 49 | 50 | expect(accounts).toHaveLength(2); 51 | expect(accounts[0].name).toMatch(TEST_STRING[0]); 52 | expect(accounts[1].name).toMatch(TEST_STRING[1]); 53 | }, 10000); 54 | }); 55 | 56 | async function someLogic(name1: string, name2: string, webApiService: Xrm.WebApi) { 57 | const createResponse1 = await webApiService.createRecord("account", { 58 | name: name1, 59 | }); 60 | 61 | const createResponse2 = await webApiService.createRecord("account", { 62 | name: name2, 63 | }); 64 | 65 | const accountIds = `'${createResponse1.id}','${createResponse2.id}'`; 66 | const retrieveMultipleResponse = await webApiService.retrieveMultipleRecords( 67 | "account", 68 | `?$select=name&$orderby=createdon asc&$filter=(Microsoft.Dynamics.CRM.In(PropertyName=%27accountid%27,PropertyValues=[${encodeURIComponent( 69 | accountIds, 70 | )}]))`, 71 | ); 72 | return retrieveMultipleResponse.entities; 73 | } 74 | -------------------------------------------------------------------------------- /src/webapi/__tests__/unit.batch-request.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sonarjs/no-duplicate-string */ 2 | import { WebApiRequestDefinition } from "../../types/WebApiRequest"; 3 | import { sendBatchWebApiRequest, WebApiRequest, WebApiResponse } from "../WebApiRequest"; 4 | 5 | describe("sendBatchWebApiRequest", () => { 6 | it("builds a batch request", async () => { 7 | const mockSend = jest.fn().mockReturnValue({ ok: true } as WebApiResponse); 8 | const requestImp = { 9 | apiVersion: "9.2", 10 | send: mockSend, 11 | server: "org.crm.dynamics.com", 12 | } as WebApiRequest; 13 | 14 | const request1 = { 15 | action: "POST", 16 | path: "/api/data/v9.2/accounts", 17 | data: { name: "Account 1" }, 18 | } as WebApiRequestDefinition; 19 | 20 | const request2 = { 21 | action: "POST", 22 | path: "/api/data/v9.2/accounts", 23 | data: { name: "Account 2" }, 24 | } as WebApiRequestDefinition; 25 | 26 | await sendBatchWebApiRequest([request1, request2], requestImp, "1659569326460"); 27 | expect(requestImp.send).toBeCalledTimes(1); 28 | expect(requestImp.send).toHaveBeenCalledWith( 29 | "POST", 30 | "org.crm.dynamics.com/api/data/v9.2/$batch", 31 | expect.anything(), 32 | expect.anything(), 33 | expect.anything(), 34 | ); 35 | 36 | expect(mockSend.mock.lastCall[2]).toMatchInlineSnapshot(` 37 | Object { 38 | "Accept": "application/json", 39 | "Connection": "keep-alive", 40 | "Content-Type": "multipart/mixed;boundary=batch_1659569326460", 41 | "MSCRM.SuppressDuplicateDetection": "true", 42 | "OData-MaxVersion": "4.0", 43 | "OData-Version": "4.0", 44 | } 45 | `); 46 | 47 | expect(mockSend.mock.lastCall[3]).toMatchInlineSnapshot(` 48 | "--batch_1659569326460 49 | Content-Type: application/http 50 | Content-Transfer-Encoding: binary 51 | 52 | POST /api/data/v9.2//api/data/v9.2/accounts HTTP/1.1 53 | Accept: application/json 54 | Content-Type: application/json;type=entry 55 | 56 | {\\"name\\":\\"Account 1\\"} 57 | --batch_1659569326460 58 | Content-Type: application/http 59 | Content-Transfer-Encoding: binary 60 | 61 | POST /api/data/v9.2//api/data/v9.2/accounts HTTP/1.1 62 | Accept: application/json 63 | Content-Type: application/json;type=entry 64 | 65 | {\\"name\\":\\"Account 2\\"} 66 | --batch_1659569326460-- 67 | " 68 | `); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/webapi/__tests__/unit.parseresponse.test.ts: -------------------------------------------------------------------------------- 1 | import { HttpReader } from "../HttpReader"; 2 | import { getWebApiResponseFromBatchPart } from "../WebApiRequest"; 3 | 4 | describe("WebApiResponse", () => { 5 | const batchResponse = ` 6 | --batchresponse_1a603e08-fa34-4f5d-a336-9a70cfb71db0 7 | Content-Type: application/http 8 | Content-Transfer-Encoding: binary 9 | 10 | HTTP/1.1 204 No Content 11 | OData-Version: 4.0 12 | Location: https://org1bfe9950.api.crm3.dynamics.com/api/data/v9.0/accounts(858411ad-380a-ed11-b83e-0022483d2320) 13 | OData-EntityId: https://org1bfe9950.api.crm3.dynamics.com/api/data/v9.0/accounts(858411ad-380a-ed11-b83e-0022483d2320) 14 | Preference-Applied: odata.include-annotations="*" 15 | 16 | --batchresponse_1a603e08-fa34-4f5d-a336-9a70cfb71db0 17 | `; 18 | it("parses from batch response", async () => { 19 | const response = getWebApiResponseFromBatchPart( 20 | new HttpReader(batchResponse), 21 | "batchresponse_1a603e08-fa34-4f5d-a336-9a70cfb71db0", 22 | ); 23 | 24 | expect(response.headers["Location"]).toBe( 25 | // eslint-disable-next-line @microsoft/power-apps/use-relative-uri 26 | "https://org1bfe9950.api.crm3.dynamics.com/api/data/v9.0/accounts(858411ad-380a-ed11-b83e-0022483d2320)", 27 | ); 28 | }, 1000); 29 | }); 30 | -------------------------------------------------------------------------------- /src/webapi/browser/BrowserWebApi.ts: -------------------------------------------------------------------------------- 1 | import { WebApiBase } from "../WebApiBase"; 2 | import { BrowserWebApiRequest } from "./BrowserWebApiRequest"; 3 | 4 | export class BrowserWebApi extends WebApiBase { 5 | constructor(server: string, apiVersion?: string) { 6 | const nodeWebApiImplementation = new BrowserWebApiRequest({ server, apiVersion }); 7 | super(nodeWebApiImplementation); 8 | } 9 | 10 | public setAccessToken(accessToken: string) { 11 | (this.getRequestImplementation() as BrowserWebApiRequest).setAccessToken(accessToken); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/webapi/browser/BrowserWebApiRequest.ts: -------------------------------------------------------------------------------- 1 | import { WebApiRequest, WebApiResponse } from "../WebApiRequest"; 2 | 3 | export class BrowserWebApiRequest implements WebApiRequest { 4 | public server: string; 5 | public apiVersion: string; 6 | private accessToken?: string; 7 | 8 | // Supports calling the webapi from inside the browser running under the following contexts: 9 | // 1. Inside a model driven app where Xrm.Utility.getGlobalContext is available and the bearer token is automatically provided 10 | // 2. Inside a SPA (Single Page Application) where setAccessToken is called after authenticating using MSAL 11 | constructor(parameters?: { context?: Xrm.GlobalContext; server?: string; apiVersion?: string }) { 12 | if (parameters?.context) { 13 | this.server = parameters.context.getClientUrl(); 14 | this.apiVersion = parameters.context.getVersion(); 15 | } else if (parameters?.server) { 16 | this.server = parameters?.server; 17 | this.apiVersion = parameters?.apiVersion || "9.0"; 18 | } else if (Xrm && Xrm.Utility && Xrm.Utility.getGlobalContext) { 19 | const context = Xrm.Utility.getGlobalContext(); 20 | this.server = context.getClientUrl(); 21 | this.apiVersion = context.getVersion(); 22 | Xrm.Utility.getGlobalContext(); 23 | } else throw "Supply either context or server to constructor"; 24 | } 25 | public setAccessToken(accessToken: string) { 26 | this.accessToken = accessToken; 27 | } 28 | async send( 29 | method: "POST" | "PATCH" | "PUT" | "GET" | "DELETE", 30 | uri: string, 31 | headers: Record, 32 | payload?: unknown, 33 | skipStringify?: boolean, 34 | ): Promise { 35 | // Construct a fully qualified URI if a relative URI is passed in. 36 | if (uri.charAt(0) === "/") { 37 | if (uri.startsWith("/api/")) { 38 | uri = `${this.server}${uri}`; 39 | } else { 40 | uri = `${this.server}/api/data/v${this.apiVersion}${uri}`; 41 | } 42 | } 43 | const accessToken = this.accessToken; 44 | 45 | const fetchHeaders = new Headers(headers); 46 | 47 | if (accessToken) { 48 | fetchHeaders.append("Authorization", "Bearer " + accessToken); 49 | } 50 | 51 | const response = await fetch(uri, { 52 | method: method, 53 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 54 | body: skipStringify !== true ? JSON.stringify(payload) : (payload as any), 55 | headers: fetchHeaders, 56 | }); 57 | 58 | const responseHeaders = {} as Record; 59 | response.headers.forEach((value, key) => (responseHeaders[key] = value)); 60 | const webApiResponse = { 61 | headers: responseHeaders, 62 | status: response.status, 63 | statusText: response.statusText, 64 | ok: response.ok, 65 | } as WebApiResponse; 66 | 67 | webApiResponse.body = await response.text(); 68 | return webApiResponse; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/webapi/browser/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./BrowserWebApiRequest"; 2 | export * from "./BrowserWebApi"; 3 | -------------------------------------------------------------------------------- /src/webapi/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./XrmApi"; 2 | export * from "./XrmUtility"; 3 | export * from "./WebApiBase"; 4 | -------------------------------------------------------------------------------- /src/webapi/node/MsalAuth/MsalCachePlugin.ts: -------------------------------------------------------------------------------- 1 | import { ICachePlugin, TokenCacheContext } from "@azure/msal-node"; 2 | import { constants } from "fs"; 3 | import fs from "fs/promises"; 4 | import os from "os"; 5 | import path from "path"; 6 | import Cryptr from "cryptr"; 7 | 8 | // This is a encrypted auth token cache 9 | // IDeally we should use msal-node-extensions to provide a secure storage of tokens 10 | // See https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-node-migration#enable-token-caching 11 | // However - this library does not come with pre-compiled native libraries 12 | // See https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/3332 13 | function getAuthCachePath(): string { 14 | const homeDirPath = os.homedir(); 15 | return path.join(homeDirPath, "dataverse-auth-cache"); 16 | } 17 | 18 | function getCrypto(): Cryptr { 19 | return new Cryptr(os.userInfo().username); 20 | } 21 | 22 | // Call back APIs which automatically write and read into a .json file - example implementation 23 | const beforeCacheAccess = async (cacheContext: TokenCacheContext): Promise => { 24 | const cachePath = getAuthCachePath(); 25 | let exists = true; 26 | try { 27 | await fs.access(cachePath, constants.R_OK | constants.W_OK); 28 | } catch { 29 | exists = false; 30 | } 31 | if (exists) { 32 | try { 33 | const cache = await fs.readFile(cachePath, "utf-8"); 34 | cacheContext.tokenCache.deserialize(getCrypto().decrypt(cache)); 35 | } catch (e) { 36 | console.warn(e); 37 | } 38 | } 39 | }; 40 | 41 | const afterCacheAccess = async (cacheContext: TokenCacheContext): Promise => { 42 | if (cacheContext.cacheHasChanged) { 43 | const data = getCrypto().encrypt(cacheContext.tokenCache.serialize()); 44 | await fs.writeFile(getAuthCachePath(), data); 45 | } 46 | }; 47 | 48 | // Cache Plugin 49 | export const MsalCachePlugin: ICachePlugin = { 50 | beforeCacheAccess, 51 | afterCacheAccess, 52 | }; 53 | -------------------------------------------------------------------------------- /src/webapi/node/MsalAuth/MsalConfig.ts: -------------------------------------------------------------------------------- 1 | // Keep this separate as an import to prevent cross-polluting electron with msal-node-extensions since the versions of node API will mismatch. 2 | export const msalConfig = { 3 | clientId: "51f81489-12ee-4a9e-aaae-a2591f45987d", 4 | redirectUrl: "app://58145b91-0c36-4500-8554-080854f2ac97", 5 | }; 6 | -------------------------------------------------------------------------------- /src/webapi/node/MsalAuth/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./MsalNodeAuth"; 2 | -------------------------------------------------------------------------------- /src/webapi/node/NodeWebApi.ts: -------------------------------------------------------------------------------- 1 | import { WebApiBase } from "../WebApiBase"; 2 | import { acquireToken, acquireTokenByClientSecret } from "./MsalAuth/MsalNodeAuth"; 3 | import { NodeWebApiRequest } from "./NodeWebApiRequest"; 4 | 5 | export class NodeWebApi extends WebApiBase { 6 | constructor(host: string, version?: string) { 7 | const nodeWebApiImplementation = new NodeWebApiRequest(host, version); 8 | super(nodeWebApiImplementation); 9 | } 10 | 11 | async authorize() { 12 | const requestImpl = this.getRequestImplementation() as NodeWebApiRequest; 13 | const accessToken = await acquireToken(requestImpl.server.replace("https://", "")); 14 | requestImpl.setAccessToken(accessToken); 15 | } 16 | 17 | async authorizeWithSecret(tenantId: string, clientId: string, clientSecret: string) { 18 | const requestImpl = this.getRequestImplementation() as NodeWebApiRequest; 19 | const accessToken = await acquireTokenByClientSecret( 20 | requestImpl.server.replace("https://", ""), 21 | tenantId, 22 | clientId, 23 | clientSecret, 24 | ); 25 | 26 | requestImpl.setAccessToken(accessToken.accessToken); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/webapi/node/NodeWebApiRequest.ts: -------------------------------------------------------------------------------- 1 | import { WebApiRequest, WebApiResponse } from "../WebApiRequest"; 2 | import fetch, { Headers } from "node-fetch"; 3 | import { HttpsProxyAgent } from "https-proxy-agent"; 4 | 5 | export class NodeWebApiRequest implements WebApiRequest { 6 | public server: string; 7 | public apiVersion: string; 8 | private accessToken?: string; 9 | private agent?: HttpsProxyAgent; 10 | constructor(server: string, apiVersion?: string, accessToken?: string) { 11 | this.server = server; 12 | this.apiVersion = apiVersion || "9.0"; 13 | this.accessToken = accessToken; 14 | 15 | // For fiddler support, define the https agent 16 | if (process.env.https_proxy) { 17 | this.agent = new HttpsProxyAgent(process.env.https_proxy); 18 | } 19 | } 20 | public setAccessToken(accessToken: string) { 21 | this.accessToken = accessToken; 22 | } 23 | 24 | async send( 25 | method: "POST" | "PATCH" | "PUT" | "GET" | "DELETE", 26 | uri: string, 27 | headers: Record, 28 | payload?: unknown, 29 | skipStringify?: boolean, 30 | ): Promise { 31 | // Construct a fully qualified URI if a relative URI is passed in. 32 | if (uri.charAt(0) === "/") { 33 | if (uri.startsWith("/api/")) { 34 | uri = `${this.server}${uri}`; 35 | } else { 36 | uri = `${this.server}/api/data/v${this.apiVersion}${uri}`; 37 | } 38 | } 39 | const accessToken = this.accessToken; 40 | 41 | const fetchHeaders = new Headers(headers); 42 | 43 | if (accessToken) { 44 | fetchHeaders.append("Authorization", "Bearer " + accessToken); 45 | } 46 | 47 | if (method === "GET" || method === "DELETE") { 48 | payload = undefined; 49 | skipStringify = true; 50 | } 51 | const response = await fetch(uri, { 52 | method: method, 53 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 54 | body: skipStringify !== true ? JSON.stringify(payload) : (payload as any), 55 | headers: fetchHeaders, 56 | agent: this.agent, 57 | }); 58 | 59 | const responseHeaders = {} as Record; 60 | response.headers.forEach((value, key) => (responseHeaders[key] = value)); 61 | const webApiResponse = { 62 | headers: responseHeaders, 63 | status: response.status, 64 | statusText: response.statusText, 65 | ok: response.ok, 66 | } as WebApiResponse; 67 | 68 | webApiResponse.body = await response.text(); 69 | 70 | return webApiResponse; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/CRUD/Create.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 3 | import { whoAmI } from "../../../whoAmI"; 4 | 5 | describe("XrmWebApiNode CRUD", () => { 6 | beforeAll(async () => { 7 | // Is this running inside NodeJS? 8 | if (typeof Xrm === "undefined") { 9 | // Set up the Node Xrm global context 10 | await SetupGlobalContext(); 11 | } 12 | }, 10000); 13 | 14 | test("Create, Update then Delete", async () => { 15 | const userId = await whoAmI(); 16 | const account1 = { 17 | name: "Sample Account", 18 | accountcategorycode: 1, //Preferred_Customer 19 | creditlimit: 1000, 20 | creditonhold: true, 21 | numberofemployees: 10, 22 | lastonholdtime: new Date(), 23 | "preferredsystemuserid@odata.bind": `systemusers(${userId})`, 24 | } as any; 25 | 26 | try { 27 | // Create Account 28 | const newAccount = (await Xrm.WebApi.createRecord("account", account1)) as any; 29 | account1.accountid = newAccount.id; 30 | 31 | if (!account1.accountid) { 32 | throw new Error("Account not created"); 33 | } 34 | 35 | // Check the account has been created 36 | const account2 = (await Xrm.WebApi.retrieveRecord("account", account1.accountid, "?$select=name")) as any; 37 | 38 | expect(account2.name).toBe("Sample Account"); 39 | } finally { 40 | // Delete account 41 | if (account1.accountid) { 42 | await Xrm.WebApi.deleteRecord("account", account1.accountid); 43 | } 44 | } 45 | }, 10000); 46 | }); 47 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/CRUD/DeepInsert.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 3 | 4 | describe("XrmWebApiNode CRUD", () => { 5 | beforeAll(async () => { 6 | // Is this running inside NodeJS? 7 | if (typeof Xrm === "undefined") { 8 | // Set up the Node Xrm global context 9 | await SetupGlobalContext(); 10 | } 11 | }, 30000); 12 | test("Deep insert", async () => { 13 | const account = { 14 | name: "Sample Account", 15 | contact_customer_accounts: [ 16 | { 17 | firstname: "Sample", 18 | lastname: "contact 1", 19 | }, 20 | { 21 | firstname: "Sample", 22 | lastname: "Contact 2", 23 | }, 24 | ], 25 | } as any; 26 | 27 | try { 28 | // Create Account & Contacts 29 | account.accountid = (await (Xrm.WebApi.createRecord("account", account) as any)).id; 30 | 31 | if (!account.accountid) throw new Error("Account not created"); 32 | 33 | const accountCreated = await Xrm.WebApi.retrieveRecord( 34 | "account", 35 | account.accountid, 36 | "?$select=name&$expand=contact_customer_accounts($select=firstname,lastname)", 37 | ); 38 | expect(accountCreated.contact_customer_accounts.length).toBe(2); 39 | } finally { 40 | // Delete account 41 | if (account.accountid) { 42 | await Xrm.WebApi.deleteRecord("account", account.accountid); 43 | } 44 | } 45 | }, 30000); 46 | }); 47 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/CRUD/Delete.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 3 | 4 | describe("XrmWebApiNode CRUD", () => { 5 | beforeAll(async () => { 6 | // Is this running inside NodeJS? 7 | if (typeof Xrm === "undefined") { 8 | // Set up the Node Xrm global context 9 | await SetupGlobalContext(); 10 | } 11 | }, 10000); 12 | test("Delete", async () => { 13 | const record = { 14 | name: "Sample Account", 15 | } as any; 16 | 17 | // Create Account 18 | record.accountid = (await (Xrm.WebApi.createRecord("account", record) as any)).id; 19 | 20 | // Delete account 21 | if (record.accountid) { 22 | await Xrm.WebApi.deleteRecord("account", record.accountid); 23 | } 24 | 25 | // Check the account has been deleted 26 | const fetch = ` 27 | 28 | 29 | 30 | 31 | 32 | `; 33 | 34 | const accounts = await Xrm.WebApi.retrieveMultipleRecords("account", "?fetchXml=" + fetch); 35 | expect(accounts.entities.length).toBe(0); 36 | }, 30000); 37 | }); 38 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/CRUD/FetchXml.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 3 | 4 | describe("XrmWebApiNode", () => { 5 | beforeAll(async () => { 6 | // Is this running inside NodeJS? 7 | if (typeof Xrm === "undefined") { 8 | // Set up the Node Xrm global context 9 | await SetupGlobalContext(); 10 | } 11 | }, 30000); 12 | test("FetchXml", async () => { 13 | // Check the account has been created 14 | const fetch = ` 15 | 16 | 17 | 18 | `; 19 | 20 | const users = await Xrm.WebApi.retrieveMultipleRecords("systemuser", "?fetchXml=" + fetch); 21 | expect(users.entities.length).toBeGreaterThan(0); 22 | }, 30000); 23 | }); 24 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/CRUD/OdataQuery.test.ts: -------------------------------------------------------------------------------- 1 | import { TEST_STRING } from "../../../__tests__/test-values"; 2 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 3 | 4 | describe("XrmWebApiNode", () => { 5 | beforeAll(async () => { 6 | // Is this running inside NodeJS? 7 | if (typeof Xrm === "undefined") { 8 | // Set up the Node Xrm global context 9 | await SetupGlobalContext(); 10 | } 11 | }); 12 | 13 | it("creates accounts and retrieveMultiple by id", async () => { 14 | const webApiService = Xrm.WebApi; 15 | 16 | let createResponse1: Xrm.CreateResponse | undefined; 17 | let createResponse2: Xrm.CreateResponse | undefined; 18 | 19 | try { 20 | createResponse1 = await webApiService.createRecord("account", { 21 | name: TEST_STRING[0], 22 | }); 23 | expect(createResponse1.id).toBeDefined(); 24 | 25 | createResponse2 = await webApiService.createRecord("account", { 26 | name: TEST_STRING[1], 27 | }); 28 | expect(createResponse2.id).toBeDefined(); 29 | 30 | const accountIds = `'${createResponse1.id}','${createResponse2.id}'`; 31 | const retrieveMultipleResponse = await webApiService.retrieveMultipleRecords( 32 | "account", 33 | `?$select=name&$orderby=createdon asc&$filter=(Microsoft.Dynamics.CRM.In(PropertyName=%27accountid%27,PropertyValues=[${encodeURIComponent( 34 | accountIds, 35 | )}]))`, 36 | ); 37 | 38 | expect(retrieveMultipleResponse.entities).toHaveLength(2); 39 | expect(retrieveMultipleResponse.entities[0].name).toMatch(TEST_STRING[0]); 40 | expect(retrieveMultipleResponse.entities[1].name).toMatch(TEST_STRING[1]); 41 | } finally { 42 | // And tidy up 43 | if (createResponse1) { 44 | await webApiService.deleteRecord("account", createResponse1.id); 45 | } 46 | 47 | if (createResponse2) { 48 | await webApiService.deleteRecord("account", createResponse2.id); 49 | } 50 | } 51 | }, 10000); 52 | }); 53 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/CRUD/response-errors.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 3 | 4 | describe("XrmWebApiNode", () => { 5 | beforeAll(async () => { 6 | // Is this running inside NodeJS? 7 | if (typeof Xrm === "undefined") { 8 | // Set up the Node Xrm global context 9 | await SetupGlobalContext(); 10 | } 11 | }, 10000); 12 | 13 | test("throws error from response", async () => { 14 | const account1 = { 15 | foo_bar: "baz", 16 | } as any; 17 | 18 | try { 19 | // Create Account 20 | const newAccount = (await Xrm.WebApi.createRecord("account", account1)) as any; 21 | account1.accountid = newAccount.id; 22 | } catch (e) { 23 | expect((e as Error).message).toMatch(/Invalid property 'foo_bar' was found in entity/); 24 | } 25 | }, 10000); 26 | 27 | test("error when deleting record that does not exist", async () => { 28 | try { 29 | // Delete non existent Account 30 | await Xrm.WebApi.deleteRecord("account", "f0161204-0f0a-ed11-82e6-0022483d2320"); 31 | } catch (e) { 32 | expect((e as Error).message).toMatch( 33 | /(Id = f0161204-0f0a-ed11-82e6-0022483d2320 Does Not Exist)|(The requested record was not found or you do not have sufficient permissions to view it)/, 34 | ); 35 | } 36 | }, 10000); 37 | }); 38 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/Execute/custom-api-bound-action.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sonarjs/no-duplicate-string */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 4 | import { WebApiExecuteRequestBase } from "../../../../types/WebApiExecuteRequest"; 5 | import { TEST_DATE, TEST_STRING } from "../../../__tests__/test-values"; 6 | import { cdsify_BoundEchoMetadata, cdsify_BoundEchoRequest } from "../../../../dataverse-gen/actions/cdsify_BoundEcho"; 7 | import { cdsify_integrationtestMetadata } from "../../../../dataverse-gen/entities/cdsify_IntegrationTest"; 8 | 9 | describe("XrmWebApiNode", () => { 10 | beforeAll(async () => { 11 | // Is this running inside NodeJS? 12 | if (typeof Xrm === "undefined") { 13 | // Set up the Node Xrm global context 14 | await SetupGlobalContext(); 15 | } 16 | }, 10000); 17 | 18 | it("execute bound action", async () => { 19 | // Create test record 20 | const integrationTestId = ( 21 | await Xrm.WebApi.createRecord(cdsify_integrationtestMetadata.logicalName, { 22 | cdsify_name: "Test record", 23 | }) 24 | ).id; 25 | 26 | try { 27 | const request = new WebApiExecuteRequestBase(cdsify_BoundEchoMetadata, { 28 | entity: { entityType: cdsify_integrationtestMetadata.logicalName, id: integrationTestId }, 29 | cdsify_BoundInString: TEST_STRING[0], 30 | cdsify_BoundInBoolean: true, 31 | cdsify_BoundInDateTime: TEST_DATE[0], 32 | cdsify_BoundInDecimal: 1, 33 | cdsify_BoundInFloat: 1, 34 | cdsify_BoundInInteger: 1, 35 | cdsify_BoundInMoney: 1, 36 | cdsify_BoundInPicklist: 1, 37 | cdsify_BoundInStringArray: ["a", "b"], 38 | cdsify_BoundInGuid: { guid: "9ae14746-030d-ed11-b83e-0022483d2320" } as any, 39 | cdsify_BoundInEntity: { 40 | "@odata.type": "Microsoft.Dynamics.CRM.cdsify_integrationtest", 41 | cdsify_name: "test 1", 42 | }, 43 | cdsify_BoundInEntityCollection: [ 44 | { 45 | "@odata.type": "Microsoft.Dynamics.CRM.cdsify_integrationtest", 46 | cdsify_name: "test 1", 47 | }, 48 | { 49 | "@odata.type": "Microsoft.Dynamics.CRM.cdsify_integrationtest", 50 | cdsify_name: "test 2", 51 | }, 52 | ], 53 | cdsify_BoundInEntityReference: { 54 | "@odata.type": "Microsoft.Dynamics.CRM.cdsify_integrationtest", 55 | cdsify_integrationtestid: integrationTestId, 56 | }, 57 | } as cdsify_BoundEchoRequest); 58 | 59 | const response = await Xrm.WebApi.online.execute(request); 60 | expect(response).toBeDefined(); 61 | const responseObject = await response.json(); 62 | expect(responseObject.cdsify_BoundOutString).toBe(TEST_STRING[0]); 63 | } finally { 64 | await Xrm.WebApi.deleteRecord(cdsify_integrationtestMetadata.logicalName, integrationTestId); 65 | } 66 | }, 100000); 67 | }); 68 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/Execute/custom-api-bound-function.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 3 | import { WebApiExecuteRequestBase } from "../../../../types/WebApiExecuteRequest"; 4 | import { 5 | cdsify_BoundEchoFunctionMetadata, 6 | cdsify_BoundEchoFunctionRequest, 7 | } from "../../../../dataverse-gen/functions/cdsify_BoundEchoFunction"; 8 | import { 9 | cdsify_IntegrationTest, 10 | cdsify_integrationtestMetadata, 11 | } from "../../../../dataverse-gen/entities/cdsify_IntegrationTest"; 12 | import { TEST_INTEGER, TEST_STRING } from "../../../__tests__/test-values"; 13 | 14 | describe("XrmWebApiNode", () => { 15 | beforeAll(async () => { 16 | // Is this running inside NodeJS? 17 | if (typeof Xrm === "undefined") { 18 | // Set up the Node Xrm global context 19 | await SetupGlobalContext(); 20 | } 21 | }, 10000); 22 | 23 | it("custom api bound function", async () => { 24 | const integrationTestId = ( 25 | await Xrm.WebApi.createRecord(cdsify_integrationtestMetadata.logicalName, { 26 | cdsify_name: "foo", 27 | } as cdsify_IntegrationTest) 28 | ).id; 29 | 30 | try { 31 | const request = new WebApiExecuteRequestBase(cdsify_BoundEchoFunctionMetadata, { 32 | cdsify_BoundEchoFunctionInInteger: TEST_INTEGER[0], 33 | cdsify_BoundEchoFunctionInString: TEST_STRING[0], 34 | entity: { entityType: cdsify_integrationtestMetadata.logicalName, id: integrationTestId }, 35 | } as cdsify_BoundEchoFunctionRequest); 36 | 37 | const response = await Xrm.WebApi.online.execute(request); 38 | expect(response).toBeDefined(); 39 | const responseObject = await response.json(); 40 | expect(responseObject.cdsify_BoundEchoFunctionOutString).toBe(TEST_STRING[0]); 41 | expect(responseObject.cdsify_BoundEchoFunctionOutInteger).toBe(TEST_INTEGER[0]); 42 | } finally { 43 | await Xrm.WebApi.deleteRecord(cdsify_integrationtestMetadata.logicalName, integrationTestId); 44 | } 45 | }, 100000); 46 | }); 47 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/Execute/custom-api-collection-bound-action.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 3 | import { WebApiExecuteRequestBase } from "../../../../types/WebApiExecuteRequest"; 4 | import { 5 | cdsify_BoundCollectionEchoMetadata, 6 | cdsify_BoundCollectionEchoRequest, 7 | } from "../../../../dataverse-gen/actions/cdsify_BoundCollectionEcho"; 8 | import { TEST_STRING } from "../../../__tests__/test-values"; 9 | 10 | describe("XrmWebApiNode", () => { 11 | beforeAll(async () => { 12 | // Is this running inside NodeJS? 13 | if (typeof Xrm === "undefined") { 14 | // Set up the Node Xrm global context 15 | await SetupGlobalContext(); 16 | } 17 | }, 10000); 18 | 19 | it("execute collection bound action", async () => { 20 | const request = new WebApiExecuteRequestBase(cdsify_BoundCollectionEchoMetadata, { 21 | cdsify_BoundCollectionEchoInString: TEST_STRING[0], 22 | } as cdsify_BoundCollectionEchoRequest); 23 | 24 | const response = await Xrm.WebApi.online.execute(request); 25 | expect(response).toBeDefined(); 26 | const responseObject = await response.json(); 27 | expect(responseObject.cdsify_BoundCollectionEchoOutString).toBe(TEST_STRING[0]); 28 | }, 100000); 29 | }); 30 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/Execute/execute-function.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 3 | import { WebApiExecuteRequestBase } from "../../../../types/WebApiExecuteRequest"; 4 | 5 | describe("XrmWebApiNode", () => { 6 | beforeAll(async () => { 7 | // Is this running inside NodeJS? 8 | if (typeof Xrm === "undefined") { 9 | // Set up the Node Xrm global context 10 | await SetupGlobalContext(); 11 | } 12 | }, 10000); 13 | 14 | it("execute function", async () => { 15 | const whoAmIRequest = new WebApiExecuteRequestBase({ 16 | boundParameter: undefined, 17 | parameterTypes: {}, 18 | operationType: 1, // Associate and Disassociate fall under the CRUD umbrella 19 | operationName: "WhoAmI", 20 | }); 21 | 22 | const response = await Xrm.WebApi.online.execute(whoAmIRequest); 23 | expect(response).toBeDefined(); 24 | }, 100000); 25 | }); 26 | -------------------------------------------------------------------------------- /src/webapi/node/__tests__/Execute/execute.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | 4 | import { SetupGlobalContext } from "../../SetupGlobalContext"; 5 | import { Account, accountMetadata } from "../../../../dataverse-gen/entities/Account"; 6 | import { getGuidFromHeaders } from "../../../../types/Guid"; 7 | import { WebApiExecuteRequestBase } from "../../../../types"; 8 | 9 | describe("XrmWebApiNode-execute", () => { 10 | beforeAll(async () => { 11 | // Is this running inside NodeJS? 12 | if (typeof Xrm === "undefined") { 13 | // Set up the Node Xrm global context 14 | await SetupGlobalContext(); 15 | } 16 | }, 10000); 17 | 18 | test("create using execute", async () => { 19 | const account = { 20 | //logicalName: accountMetadata.logicalName, 21 | name: "Account 1", 22 | } as Account; 23 | let id = ""; 24 | 25 | const account1Create = new WebApiExecuteRequestBase( 26 | { 27 | parameterTypes: {}, 28 | boundParameter: undefined, 29 | operationType: 2, 30 | operationName: "Create", 31 | }, 32 | { 33 | etn: accountMetadata.logicalName, 34 | payload: account, 35 | }, 36 | ); 37 | 38 | try { 39 | const response1 = await Xrm.WebApi.online.execute(account1Create); 40 | expect(response1).toBeDefined(); 41 | 42 | // When using the real WebApi, execute create does not return the 43 | // in our node implementation we do! 44 | try { 45 | id = getGuidFromHeaders(response1.headers); 46 | } catch (e) { 47 | console.log(e); 48 | } 49 | } finally { 50 | const account1Delete = new WebApiExecuteRequestBase( 51 | { 52 | parameterTypes: {}, 53 | boundParameter: undefined, 54 | operationType: 2, 55 | operationName: "Delete", 56 | }, 57 | { entityReference: { entityType: accountMetadata.logicalName, id: id } }, 58 | ); 59 | 60 | await Xrm.WebApi.online.execute(account1Delete); 61 | } 62 | }, 10000); 63 | 64 | test("update using execute", async () => { 65 | const account = { 66 | name: "Account 1", 67 | } as Account; 68 | 69 | const id = (await Xrm.WebApi.createRecord(accountMetadata.logicalName, account)).id; 70 | 71 | account.name = "Account 1 updated"; 72 | const updateRequest = new WebApiExecuteRequestBase( 73 | { 74 | parameterTypes: {}, 75 | boundParameter: undefined, 76 | operationType: 2, 77 | operationName: "Update", 78 | }, 79 | { 80 | etn: accountMetadata.logicalName, 81 | id: id, 82 | payload: account, 83 | }, 84 | ); 85 | 86 | try { 87 | const response1 = await Xrm.WebApi.online.execute(updateRequest); 88 | expect(response1).toBeDefined(); 89 | } finally { 90 | await Xrm.WebApi.online.deleteRecord(accountMetadata.logicalName, id); 91 | } 92 | }, 10000); 93 | }); 94 | -------------------------------------------------------------------------------- /src/webapi/node/config/NodeXrmConfig.ts: -------------------------------------------------------------------------------- 1 | import { NodeXrmConfigServer } from "./NodeXrmConfigServer"; 2 | import { NodeXrmConfigProxy } from "./NodeXrmConfigProxy"; 3 | export interface NodeXrmConfig { 4 | server: NodeXrmConfigServer; 5 | proxy?: NodeXrmConfigProxy; 6 | } 7 | -------------------------------------------------------------------------------- /src/webapi/node/config/NodeXrmConfigProxy.ts: -------------------------------------------------------------------------------- 1 | export interface NodeXrmConfigProxy { 2 | useproxy?: boolean; 3 | httpProxy?: string; 4 | httpsProxy?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/webapi/node/config/NodeXrmConfigServer.ts: -------------------------------------------------------------------------------- 1 | export interface NodeXrmConfigServer { 2 | host: string; 3 | version: string; 4 | appid?: string; 5 | tenant?: string; 6 | secret?: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/webapi/node/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeXrmConfig"; 2 | export * from "./NodeXrmConfigProxy"; 3 | export * from "./NodeXrmConfigServer"; 4 | -------------------------------------------------------------------------------- /src/webapi/node/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SetupGlobalContext"; 2 | export * from "./NodeWebApiRequest"; 3 | export * from "./NodeWebApi"; 4 | export * from "./MsalAuth"; 5 | export * from "./config"; 6 | -------------------------------------------------------------------------------- /src/webapi/utils/GetHeaderValue.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 2 | export function getHeaderValue(headers: any, header: string): string | undefined { 3 | if (headers.get) { 4 | return headers.get(header); 5 | } 6 | const headerLower = header.toLowerCase(); 7 | const headersRecord = headers._headers || headers; 8 | if (headersRecord) { 9 | for (const key in headersRecord) { 10 | if (key.toLowerCase() === headerLower) { 11 | return headersRecord[key]; 12 | } 13 | } 14 | } 15 | return undefined; 16 | } 17 | -------------------------------------------------------------------------------- /src/webapi/utils/NullOrUndefined.ts: -------------------------------------------------------------------------------- 1 | export function isNullOrUndefined(value: unknown): boolean { 2 | return typeof value === "undefined" || value === null; 3 | } 4 | 5 | export function requireValue(argumentName: string, value: unknown): void { 6 | if (isNullOrUndefined(value)) { 7 | throw new Error(`${argumentName} is a required parameter`); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/webapi/utils/StringFormat.ts: -------------------------------------------------------------------------------- 1 | export function StringFormat(text: string, ...args: string[]): string { 2 | return text.replace(/{(\d+)}/g, (match, num) => { 3 | return typeof args[num] !== "undefined" ? args[num] : match; 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /src/webapi/whoAmI.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { OperationType } from "../types/OperationType"; 3 | import { trimGuid } from "../types/Guid"; 4 | 5 | export async function whoAmI(): Promise { 6 | const request = new (class { 7 | getMetadata(): any { 8 | return { 9 | parameterTypes: {}, 10 | operationType: OperationType.Function, 11 | operationName: "WhoAmI", 12 | }; 13 | } 14 | })(); 15 | const whoAmIResponse = await Xrm.WebApi.online.execute(request); 16 | const response = await whoAmIResponse.json(); 17 | return trimGuid(response.UserId as string); 18 | } 19 | -------------------------------------------------------------------------------- /test-setup.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (globalConfig, projectConfig) { 2 | require("dotenv").config(); 3 | // If running in CI mode, never use the proxy - otherwise, control using the DATAVERSEIFY_USEPROXY environment variable set in the .env file 4 | /* 5 | DATAVERSEIFY_USEPROXY=1 6 | DATAVERSEIFY_PROXY=https://127.0.0.1:8888 7 | */ 8 | if (!globalConfig.ci && process.env["DATAVERSEIFY_USEPROXY"] === "1") { 9 | const httpProxy = process.env["DATAVERSEIFY_PROXY"]; 10 | console.debug("Adding proxy:" + httpProxy); 11 | if (!httpProxy) { 12 | throw "To use a proxy, please set the environment variable DATAVERSEIFY_PROXY (E.g. https://127.0.0.1:8888)"; 13 | } 14 | process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; 15 | process.env.https_proxy = httpProxy.replace("https:", "http:"); 16 | process.env.http_proxy = httpProxy.replace("http:", "https:"); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "Node16", 5 | "moduleResolution": "node16", 6 | "lib": [ 7 | "ES2020", 8 | "dom" 9 | ], 10 | "rootDir": "src", 11 | "outDir": "lib", 12 | "strict": true, 13 | "alwaysStrict": true, 14 | "strictFunctionTypes": true, 15 | "strictNullChecks": true, 16 | "strictPropertyInitialization": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noImplicitAny": true, 19 | "noImplicitReturns": true, 20 | "noImplicitThis": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "declaration": true, 25 | "sourceMap": true, 26 | "pretty": true, 27 | "esModuleInterop": true 28 | }, 29 | "exclude": [ 30 | "src/browser-tests", 31 | "lib" 32 | ], 33 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require("path"); 3 | const webpack = require("webpack"); 4 | module.exports = { 5 | entry: "./src/browser-tests/index.ts", 6 | mode: "development", 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.tsx?$/, 11 | use: "ts-loader", 12 | exclude: /node_modules/, 13 | }, 14 | ], 15 | }, 16 | plugins: [ 17 | new webpack.ProvidePlugin({ 18 | process: "process/browser", 19 | }), 20 | ], 21 | resolve: { 22 | extensions: [".tsx", ".ts", ".js"], 23 | alias: { 24 | // Since this is for running in the browser, we don't include the node context Xrm.WebApi 25 | config: path.resolve(__dirname, "src/browser-tests/config-browser"), 26 | "../../../webapi/node/SetupGlobalContext": path.resolve(__dirname, "src/browser-tests/SetupGlobalContext"), 27 | "../../SetupGlobalContext": path.resolve(__dirname, "src/browser-tests/SetupGlobalContext"), 28 | }, 29 | fallback: { 30 | crypto: false, 31 | path: false, 32 | https: false, 33 | fs: false, 34 | util: false, 35 | buffer: false, 36 | querystring: false, 37 | assert: false, 38 | http: false, 39 | tls: false, 40 | url: false, 41 | net: false, 42 | module: false, 43 | stream: false, 44 | zlib: false, 45 | constants: false, 46 | tunnel: false, 47 | }, 48 | }, 49 | 50 | devtool: "eval-source-map", 51 | output: { 52 | path: path.resolve(__dirname, "dist-tests"), 53 | filename: "browser-tests.js", 54 | // Set this to your namespace e.g. cds.ClientHooks 55 | library: ["tests"], 56 | libraryTarget: "var", 57 | }, 58 | }; 59 | --------------------------------------------------------------------------------