├── .eslintignore ├── .forceignore ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── config └── project-scratch-def.json ├── core └── main │ └── default │ ├── cachePartitions │ └── lwcmodules.cachePartition-meta.xml │ ├── classes │ ├── APICallController.cls │ ├── APICallController.cls-meta.xml │ ├── APICallControllerTest.cls │ ├── APICallControllerTest.cls-meta.xml │ ├── DynamicSOQLDMLController.cls │ ├── DynamicSOQLDMLController.cls-meta.xml │ ├── DynamicSOQLDMLControllerTest.cls │ ├── DynamicSOQLDMLControllerTest.cls-meta.xml │ ├── GetSessionIdController.cls │ ├── GetSessionIdController.cls-meta.xml │ ├── HttpResponseWrapper.cls │ ├── HttpResponseWrapper.cls-meta.xml │ ├── ObjectInfoController.cls │ ├── ObjectInfoController.cls-meta.xml │ ├── ObjectInfoControllerTest.cls │ ├── ObjectInfoControllerTest.cls-meta.xml │ ├── PlatformCacheService.cls │ ├── PlatformCacheService.cls-meta.xml │ ├── PlatformCacheServiceTest.cls │ ├── PlatformCacheServiceTest.cls-meta.xml │ ├── PlatformEventService.cls │ └── PlatformEventService.cls-meta.xml │ ├── lwc │ ├── .eslintrc.json │ ├── apiService │ │ ├── apiService.js │ │ └── apiService.js-meta.xml │ ├── calloutService │ │ ├── calloutService.js │ │ └── calloutService.js-meta.xml │ ├── describeMetadataService │ │ ├── describeMetadataService.js │ │ └── describeMetadataService.js-meta.xml │ ├── dmlService │ │ ├── dmlService.js │ │ └── dmlService.js-meta.xml │ ├── platformCacheService │ │ ├── platformCacheService.js │ │ └── platformCacheService.js-meta.xml │ ├── platformEventService │ │ ├── platformEventService.js │ │ └── platformEventService.js-meta.xml │ ├── soqlService │ │ ├── soqlService.js │ │ └── soqlService.js-meta.xml │ └── utils │ │ ├── utils.js │ │ └── utils.js-meta.xml │ ├── pages │ ├── GetSessionId.page │ └── GetSessionId.page-meta.xml │ └── remoteSiteSettings │ └── status.remoteSite-meta.xml ├── examples └── main │ └── default │ ├── cspTrustedSites │ ├── picsum.cspTrustedSite-meta.xml │ └── picsummain.cspTrustedSite-meta.xml │ └── lwc │ ├── .eslintrc.json │ ├── compositeApiExample │ ├── compositeApiExample.html │ ├── compositeApiExample.js │ └── compositeApiExample.js-meta.xml │ ├── dmlExample │ ├── dmlExample.html │ ├── dmlExample.js │ └── dmlExample.js-meta.xml │ ├── platformCacheExample │ ├── platformCacheExample.html │ ├── platformCacheExample.js │ └── platformCacheExample.js-meta.xml │ ├── platformEventExample │ ├── platformEventExample.html │ ├── platformEventExample.js │ └── platformEventExample.js-meta.xml │ ├── sfTrustInfo │ ├── sfTrustInfo.css │ ├── sfTrustInfo.html │ ├── sfTrustInfo.js │ └── sfTrustInfo.js-meta.xml │ └── soqlDatatable │ ├── soqlDatatable.html │ ├── soqlDatatable.js │ └── soqlDatatable.js-meta.xml ├── package-lock.json ├── package.json └── sfdx-project.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/lwc/**/*.css 2 | **/lwc/**/*.html 3 | **/lwc/**/*.json 4 | **/lwc/**/*.svg 5 | **/lwc/**/*.xml 6 | **/aura/**/*.auradoc 7 | **/aura/**/*.cmp 8 | **/aura/**/*.css 9 | **/aura/**/*.design 10 | **/aura/**/*.evt 11 | **/aura/**/*.json 12 | **/aura/**/*.svg 13 | **/aura/**/*.tokens 14 | **/aura/**/*.xml 15 | .sfdx -------------------------------------------------------------------------------- /.forceignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status 2 | # More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm 3 | # 4 | 5 | package.xml 6 | 7 | # LWC configuration files 8 | **/jsconfig.json 9 | **/.eslintrc.json 10 | 11 | # LWC Jest 12 | **/__tests__/** -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used for Git repositories to specify intentionally untracked files that Git should ignore. 2 | # If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore 3 | # For useful gitignore templates see: https://github.com/github/gitignore 4 | 5 | # Salesforce cache 6 | .sfdx/ 7 | .localdevserver/ 8 | 9 | # LWC VSCode autocomplete 10 | **/lwc/jsconfig.json 11 | 12 | # LWC Jest coverage reports 13 | coverage/ 14 | 15 | # SOQL Query Results 16 | **/scripts/soql/query-results 17 | 18 | # Logs 19 | logs 20 | *.log 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # Dependency directories 26 | node_modules/ 27 | 28 | # Eslint cache 29 | .eslintcache 30 | 31 | # MacOS system files 32 | .DS_Store 33 | 34 | # Windows system files 35 | Thumbs.db 36 | ehthumbs.db 37 | [Dd]esktop.ini 38 | $RECYCLE.BIN/ 39 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running prettier 2 | # More information: https://prettier.io/docs/en/ignore.html 3 | # 4 | 5 | **/staticresources/** 6 | .localdevserver 7 | .sfdx 8 | .vscode 9 | 10 | coverage/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "printWidth":120, 4 | "overrides": [ 5 | { 6 | "files": "**/lwc/**/*.html", 7 | "options": { "parser": "lwc" } 8 | }, 9 | { 10 | "files": "*.{cmp,page,component}", 11 | "options": { "parser": "html" } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "salesforce.salesforcedx-vscode", 4 | "redhat.vscode-xml", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "financialforce.lana" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.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 | "name": "Launch Apex Replay Debugger", 9 | "type": "apex-replay", 10 | "request": "launch", 11 | "logFile": "${command:AskForLogFileName}", 12 | "stopOnEntry": true, 13 | "trace": true 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/node_modules": true, 4 | "**/bower_components": true, 5 | "**/.sfdx": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Suraj Pillai 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 | # LWC Apex Services :fire: :zap: 2 | 3 | A collection of LWC modules aimed at eliminating the need for writing Apex code for building LWCs, making it easier for JS devs to build components quicker on the Salesforce platform. 4 | 5 | It contains modules for the following operations: 6 | 7 | - SOQL 8 | 9 | Import `soqlService` into your lwc and issue queries directly from your lwc! 10 | 11 | ```js 12 | import soql from "c/soqlService"; 13 | ... 14 | this.data = await soql( "Select LastName,Account.Name,Email,Account.Owner.LastName from Contact"); 15 | ``` 16 | 17 | Refer to [soqlDataTable](examples/main/default/lwc/soqlDatatable/) example for details. 18 | 19 | - DML 20 | 21 | Import all exports from `dmlService` into your lwc and use `insert`,`update`,`upsert` and `del` operations as needed. 22 | 23 | ```js 24 | import * as dml from "c/dmlService"; 25 | ... 26 | const acctId = (await dml.insert({ Name: acctName }, "Account"))[0]; //the method accepts either a single json record or json array and always returns an array of ids. 27 | ``` 28 | 29 | For `insert` and `upsert` operations, the sobject type must be specified as the second argument. 30 | 31 | - Object and Field Describes 32 | 33 | ```js 34 | import { describeSObjectInfo } from "c/describeMetadataService"; 35 | ... 36 | 37 | describeSObjectInfo(["Account", "Contact"]) //Get Describe information for multiple SObjects in a single call 38 | .then((resp) => { 39 | // the response has the shape of List 40 | console.log(">> got child object info ", resp); 41 | }) 42 | .catch((err) => { 43 | // handle error 44 | }); 45 | 46 | ``` 47 | 48 | ```js 49 | import { describeFieldInfo } from "c/describeMetadataService"; 50 | ... 51 | // Retrieve field describe info for multiple fields in a single call, including relationship fields 52 | describeFieldInfo(["Account.Name","Contact.Account.Parent.Industry"]) 53 | .then(resp=>{ 54 | // the resp has the shape of List 55 | }) 56 | 57 | ``` 58 | 59 | Refer to [soqlDatatable](examples/main/default/lwc/soqlDatatable/) for an example 60 | 61 | - Callouts via Apex (using Named Creds, if available) 62 | 63 | Call APIs that don't support CORS or require authentication, via Apex using Named Credentials. 64 | 65 | ```js 66 | 67 | import apexCallout from "c/calloutService"; 68 | ... 69 | 70 | let contact = JSON.parse((await apexCallout("callout:random_user/api")).body); //https://randomuser.me/ 71 | ``` 72 | 73 | The full signature for this function is `apexCallout(endPoint,method,headers,body)`. `headers` and `body` expect JSON inputs 74 | 75 | - Calling Salesforce APIs within your org directly from the LWC (Requires CSP Trusted Sites and CORS setup) 76 | 77 | ```js 78 | import sfapi from "c/apiService"; 79 | ... 80 | 81 | // Calling Composite API for inserting multiple related records at once 82 | let response = await sfapi( 83 | "/composite/" /*path excluding base url*/, 84 | "POST" /*method*/, 85 | {} /* additional headers */, 86 | JSON.stringify(compositeReq) /* request body */ 87 | ); 88 | ``` 89 | 90 | Refer to [compositeApiExample](examples/main/default/lwc/compositeApiExample/) for the full example. 91 | 92 | - Publish Platform Events 93 | 94 | ```js 95 | import * as platformEventService from "c/platformEventService"; 96 | ... 97 | platformEventService.publish('Test_Event__e', payload); //payload would be a json object with the shape of the Platform Event being published 98 | ``` 99 | 100 | [Example](examples/main/default/lwc/platformEventExample/). 101 | 102 | - Interact with Platform Cache 103 | 104 | ```js 105 | import * as cache from "c/platformCacheService"; 106 | ... 107 | // Add key-value pairs to cache 108 | cache.org.put(key,value); 109 | 110 | // Retrieve value from cache by key 111 | try { 112 | this.outputText = await cache.org.get(key); 113 | } catch (err) { 114 | console.error(err); 115 | } 116 | ``` 117 | 118 | Refer to [platformCacheExample](examples/main/default/lwc/platformCacheExample/) for the full example. 119 | 120 | ## Considerations 121 | 122 | - These modules are relatively insecure and are meant for usage in internal apps only. In other words, they are not recommended to be used for LWCs in communities or public sites. 123 | 124 | - This is still a work in progress :wrench:. Feedback and contributions welcome! :pray: 125 | -------------------------------------------------------------------------------- /config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "PSB company", 3 | "edition": "Developer", 4 | "features": ["PlatformCache"], 5 | "settings": { 6 | "lightningExperienceSettings": { 7 | "enableS1DesktopEnabled": true 8 | }, 9 | "mobileSettings": { 10 | "enableS1EncryptedStoragePref2": false 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /core/main/default/cachePartitions/lwcmodules.cachePartition-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | lwcmodules 5 | 6 | 3 7 | 2 8 | 0 9 | 0 10 | Session 11 | 12 | 13 | 3 14 | 1 15 | 0 16 | 0 17 | Organization 18 | 19 | 20 | -------------------------------------------------------------------------------- /core/main/default/classes/APICallController.cls: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | 3 | *** @author Suraj Pillai 4 | *** @group Controller 5 | *** @description Controller for making API calls and sending the response back 6 | *** 7 | **/ 8 | public with sharing class APICallController { 9 | /** 10 | * Given an endpoint, request params and headers, callout the api and return the response 11 | * @param endPoint The endpoint to callout to 12 | * @param method The http method to use 13 | * @param bodyStr The request body string. 14 | * @param headers Map of string key and value for request headers 15 | * @return The response for the http request 16 | * 17 | */ 18 | @AuraEnabled 19 | public static HttpResponseWrapper makeApiCall( 20 | String endPoint, 21 | String method, 22 | String bodyStr, 23 | Map headers 24 | ) { 25 | HttpRequest req = new HttpRequest(); 26 | req.setEndpoint(endPoint); 27 | req.setMethod(method); 28 | if (method != 'GET') { 29 | req.setBody(bodyStr); 30 | } 31 | if (headers != null) { 32 | for (String key : headers.keySet()) { 33 | req.setHeader(key, headers.get(key)); 34 | } 35 | } 36 | HttpResponse resp = new Http().send(req); 37 | Map respHeaders = new Map(); 38 | for (String key : resp.getHeaderKeys()) { 39 | respHeaders.put(key, String.valueOf(resp.getHeader(key))); 40 | } 41 | return new HttpResponseWrapper(resp.getBody(), resp.getStatusCode(), respHeaders); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/main/default/classes/APICallController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/APICallControllerTest.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | public with sharing class APICallControllerTest { 3 | @isTest 4 | public static void testAPICall() { 5 | Test.setMock(HttpCalloutMock.class, new APICallMock()); 6 | HttpResponseWrapper resp = APICallController.makeApiCall( 7 | 'https://api.example.com', 8 | 'POST', 9 | '{"message":"sample_request"}', 10 | new Map{ 'Accept' => 'application/json', 'Content-Type' => 'application/json' } 11 | ); 12 | system.assertEquals('{"message": "sample response"}', resp.body, 'Unexpected Response'); 13 | system.assertEquals(200, resp.statusCode, 'Incorrect value for status code'); 14 | system.assertEquals(2, resp.headers.size(), 'Mismatch in the number of response headers expected'); 15 | system.assertEquals('sample_value1', resp.headers.get('custom_header1'), 'Incorrect value for first header'); 16 | system.assertEquals('sample_value2', resp.headers.get('custom_header2'), 'Incorrect value for second header'); 17 | } 18 | 19 | class APICallMock implements HttpCalloutMock { 20 | public HttpResponse respond(HttpRequest req) { 21 | HttpResponse resp = new HttpResponse(); 22 | if ( 23 | req.getBody() == '{"message":"sample_request"}' && 24 | req.getHeader('Accept') == 'application/json' && 25 | req.getHeader('Content-Type') == 'application/json' 26 | ) { 27 | resp.setBody('{"message": "sample response"}'); 28 | resp.setHeader('custom_header1', 'sample_value1'); 29 | resp.setHeader('custom_header2', 'sample_value2'); 30 | resp.setStatusCode(200); 31 | } else { 32 | resp.setStatusCode(400); 33 | resp.setBody('{"message":"Bad Request"}'); 34 | } 35 | return resp; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/main/default/classes/APICallControllerTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/DynamicSOQLDMLController.cls: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | 3 | *** @author Suraj Pillai 4 | *** @date 05/2021 5 | *** @description Contains methods for dynamic SOQL and DML 6 | *** 7 | **/ 8 | public with sharing class DynamicSOQLDMLController { 9 | /** 10 | * Execute a Soql Query 11 | * @param query The soql query to execute 12 | * 13 | * @return SObject[] 14 | **/ 15 | @AuraEnabled 16 | public static SObject[] executeSoqlQuery(String query) { 17 | return Database.query(query); 18 | } 19 | 20 | /** 21 | * Short Description 22 | * @param recordId Get the SObject Type given a record Id 23 | * 24 | * @return String 25 | **/ 26 | @AuraEnabled(cacheable=true) 27 | public static String getSObjectTypeFromId(Id recordId) { 28 | return recordId.getSObjectType().getDescribe().getName(); 29 | } 30 | 31 | private static List deserializeContentVersion(String strData) { 32 | List deserializedRecords = (List) JSON.deserializeUntyped(strData); 33 | List recordsList = new List(); 34 | for (Object objRec : deserializedRecords) { 35 | Map record = (Map) objRec; 36 | ContentVersion cv = new ContentVersion(); 37 | String vData = String.valueOf(record.remove('VersionData')); 38 | cv = (ContentVersion) JSON.deserialize(JSON.serialize(record), ContentVersion.class); 39 | cv.put('VersionData', EncodingUtil.base64Decode(vData)); 40 | recordsList.add(cv); 41 | } 42 | return recordsList; 43 | } 44 | 45 | /** 46 | * Execute a DML statement 47 | * @param operation 'Insert','Update' or 'Upsert' 48 | * @param strData The records to update, stringified 49 | * @param sObjectType The SObject type to perform the DML on 50 | * @return Id[] 51 | **/ 52 | @AuraEnabled 53 | public static List executeDml(String operation, String strData, String sObjectType) { 54 | List records = null; 55 | 56 | if (sObjectType.equalsIgnoreCase('ContentVersion')) { 57 | records = deserializeContentVersion(strData); 58 | } else { 59 | records = (SObject[]) JSON.deserialize(strData, Type.forName('List<' + sObjectType + '>')); 60 | } 61 | 62 | if (operation == 'insert') { 63 | insert records; 64 | } else if (operation == 'update') { 65 | update records; 66 | } else if (operation == 'upsert') { 67 | upsert records; 68 | } else if (operation == 'delete') { 69 | delete records; 70 | } else { 71 | return null; 72 | } 73 | return new List(new Map(records).keySet()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /core/main/default/classes/DynamicSOQLDMLController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/DynamicSOQLDMLControllerTest.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | public with sharing class DynamicSOQLDMLControllerTest { 3 | @isTest 4 | public static void testUpdate() { 5 | Account a = new Account(Name = 'Test Account'); 6 | insert a; 7 | a.Phone = '432424'; 8 | Account[] recordsToUpdate = new List{ a }; 9 | DynamicSOQLDMLController.executeDml('update', JSON.serialize(recordsToUpdate), 'Account'); 10 | a = [SELECT Phone FROM Account WHERE Id = :a.Id]; 11 | System.assertEquals('432424', a.Phone); 12 | } 13 | 14 | @isTest 15 | public static void testInsert() { 16 | // we won't test fetching cmdt 17 | String acctString = '[{"attributes":{"type":"Account"},"Name":"Test Account"}]'; 18 | DynamicSOQLDMLController.executeDml('insert', acctString, 'Account'); 19 | System.assertEquals(1, [SELECT ID FROM Account WHERE Name = 'Test Account'].size()); 20 | } 21 | 22 | @isTest 23 | public static void testUpsertInsert() { 24 | Account a = new Account(Name = 'Test Account'); 25 | insert a; 26 | a.Phone = '432343'; 27 | Account a1 = new Account(Name = 'Test Account 2'); 28 | Account[] accountsToUpdate = new List{}; 29 | accountsToUpdate.add(a); 30 | accountsToUpdate.add(a1); 31 | 32 | String acctString = JSON.serialize(accountsToUpdate); 33 | DynamicSOQLDMLController.executeDml('upsert', acctString, 'Account'); 34 | System.assertEquals(2, [SELECT ID FROM Account].size()); 35 | a = [SELECT Phone FROM Account WHERE Id = :a.Id]; 36 | System.assertEquals('432343', a.Phone); 37 | } 38 | 39 | @isTest 40 | public static void testSoql() { 41 | Account a = new Account(Name = 'Test Account'); 42 | insert a; 43 | Account[] acctsResult = DynamicSOQLDMLController.executeSoqlQuery('Select Name from Account'); 44 | System.assertEquals(1, acctsResult.size()); 45 | System.assertEquals('Test Account', acctsResult[0].Name); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/main/default/classes/DynamicSOQLDMLControllerTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/GetSessionIdController.cls: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | 3 | *** Copyright (c) Vertex Computer Systems Inc. All rights reserved. 4 | 5 | *** @author Suraj Pillai 6 | *** @group Controller 7 | *** @date 01/2021 8 | *** @description Get API-ready session id of the current users 9 | *** 10 | **/ 11 | public with sharing class GetSessionIdController { 12 | @TestVisible 13 | private static String maxApiVersion = null; 14 | /**** 15 | ** @description Returns the current user's session id that may be used for calling Salesforce APIs 16 | ** @return the current user's api-ready session id 17 | **/ 18 | @AuraEnabled(cacheable=true) 19 | public static String getSessionId() { 20 | String content = Page.GetSessionId.getContent().toString(); 21 | return getSessionIdFromPage(content); 22 | } 23 | 24 | private static String getSessionIdFromPage(String content) { 25 | Integer s = content.indexOf('Start_Of_Session_Id') + 'Start_Of_Session_Id'.length(), 26 | e = content.indexOf('End_Of_Session_Id'); 27 | return content.substring(s, e); 28 | } 29 | 30 | class Attribute { 31 | String type; 32 | String url; 33 | } 34 | 35 | class Resp { 36 | Attribute attributes; 37 | } 38 | 39 | /**** 40 | ** @description Get the latest api version this org is on. It uses the fact that queried sobjects when serialized includes the REST API url to access the record. We extract the api version from this url 41 | ** @return Api version in the form 'v51.0' 42 | **/ 43 | private static String getCurrentMaxApiVersion() { 44 | if (String.isEmpty(maxApiVersion)) { 45 | Pattern p = Pattern.compile('v[0-9.]{4}'); // Match api version in the format v51.0 46 | Resp r = (Resp) JSON.deserialize(JSON.serialize([SELECT Id FROM User LIMIT 1][0]), Resp.class); 47 | Matcher m = p.matcher(r.attributes.url); 48 | m.find(); 49 | maxApiVersion = m.group(0); 50 | } 51 | return maxApiVersion; 52 | } 53 | 54 | /**** 55 | ** Get Base Url for REST API for the current org 56 | ** @return Base Url for REST API for the current org 57 | **/ 58 | @AuraEnabled(cacheable=true) 59 | public static String getRestAPIBaseUrl() { 60 | String currentMaxApiVersion = getCurrentMaxApiVersion(); 61 | return URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/' + currentMaxApiVersion; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/main/default/classes/GetSessionIdController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/HttpResponseWrapper.cls: -------------------------------------------------------------------------------- 1 | public with sharing class HttpResponseWrapper { 2 | @AuraEnabled 3 | public String body; 4 | @AuraEnabled 5 | public Integer statusCode; 6 | @AuraEnabled 7 | public Map headers; 8 | 9 | public HttpResponseWrapper(String body, Integer statusCode, Map headers) { 10 | this.body = body; 11 | this.statusCode = statusCode; 12 | this.headers = headers; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/main/default/classes/HttpResponseWrapper.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/ObjectInfoController.cls: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | 3 | *** @author Suraj Pillai 4 | *** @date 04/2021 5 | *** @description Contains bulkified methods for returning object and field descriptions 6 | *** 7 | **/ 8 | public inherited sharing class ObjectInfoController { 9 | private static Map objectDescribesMap = new Map(); 10 | 11 | /**** 12 | ** Returns JSON string of list of DescribSObjectResult for the given sobjects 13 | ** @param sobjectNames list of api names of sobjects 14 | ** @return JSON string of list of DescribSObjectResult for given sobjects 15 | **/ 16 | @AuraEnabled(cacheable=true) 17 | public static String getObjectInfo(List sobjectNames) { 18 | List sobjectTypes = getSObjectTypesFromNames(sobjectNames); 19 | List retVal = getSObjectDescribes(sobjectTypes); 20 | return JSON.serialize(retVal); 21 | } 22 | 23 | private static List getSObjectDescribes(List sobjectTypes) { 24 | List uncachedSObjects = removeCachedSObjectsFromList(sobjectTypes); 25 | List retVal = new List(); 26 | 27 | if (!uncachedSObjects.isEmpty()) { 28 | getAndCacheSObjectDescribes(uncachedSObjects); 29 | } 30 | for (Schema.SObjectType sobjectType : sobjectTypes) { 31 | if (sobjectType != null) { 32 | retVal.add(objectDescribesMap.get(sobjectType)); 33 | } 34 | } 35 | return retVal; 36 | } 37 | 38 | private static List removeCachedSObjectsFromList(List sobjectTypes) { 39 | List uncachedSObjects = new List(); 40 | for (Schema.SObjectType sobjectType : sobjectTypes) { 41 | if (sobjectType != null && !objectDescribesMap.containsKey(sobjectType)) { 42 | uncachedSObjects.add(sobjectType); 43 | } 44 | } 45 | return uncachedSObjects; 46 | } 47 | 48 | private static List getAndCacheSObjectDescribes(List sObjectTypes) { 49 | List sobjectDescribes = new List(); 50 | for (Schema.SObjectType sobjectType : sobjectTypes) { 51 | if (sobjectType != null) { 52 | sobjectDescribes.add(sobjectType.getDescribe()); 53 | } 54 | } 55 | cacheSObjectDescribes(sobjectDescribes); 56 | return sobjectDescribes; 57 | } 58 | 59 | private static void cacheSObjectDescribes(List sobjectDescribes) { 60 | for (DescribeSObjectResult objDescribe : sobjectDescribes) { 61 | objectDescribesMap.put(objDescribe.getSObjectType(), objDescribe); 62 | } 63 | } 64 | 65 | private static List getSObjectTypesFromNames(List sobjectNames) { 66 | List sobjectTypes = new List(); 67 | for (String sobjectName : sobjectNames) { 68 | SObjectType theType = getSObjectTypeFromName(sobjectName); 69 | if (theType != null) { 70 | sobjectTypes.add(theType); 71 | } 72 | } 73 | return sobjectTypes; 74 | } 75 | 76 | private static Schema.SObjectType getSObjectTypeFromName(String sobjectName) { 77 | Type theType = Type.forName(sobjectName); 78 | if (theType != null) { 79 | return ((SObject) theType.newInstance()).getSObjectType(); 80 | } 81 | return null; 82 | } 83 | 84 | /**** 85 | ** @description Returns field describe information for a list of qualified field names (eg: Contact.Account.Name) in the form of a JSON string 86 | ** @param fieldNames list of field names 87 | ** @return JSON string for list of field describe information for the specified fields 88 | **/ 89 | @AuraEnabled(cacheable=true) 90 | public static String getFieldInfo(List fieldNames) { 91 | List objectNames = new List(); 92 | List fieldDescribesList = new List(); 93 | 94 | for (String fieldName : fieldNames) { 95 | String originalFieldName = fieldName; 96 | String currObjName = fieldName.substringBefore('.'); 97 | Schema.SObjectType currSObjectType = getSObjectTypeFromName(currObjName); 98 | fieldName = fieldName.substringAfter('.'); 99 | 100 | while (fieldName.indexOf('.') > -1) { 101 | String currFieldName = fieldName.substringBefore('.').replace('__r', '__c'); 102 | if (!currFieldName.endsWith('__c')) { 103 | currFieldName = currFieldName + 'Id'; 104 | } 105 | currSObjectType = getSObjectDescribes(new List{ currSObjectType })[0] 106 | .fields.getMap() 107 | .get(currFieldName) 108 | .getDescribe() 109 | .getReferenceTo()[0]; 110 | fieldName = fieldName.substringAfter('.'); 111 | } 112 | 113 | DescribeFieldResult fieldDescribe = getSObjectDescribes(new List{ currSObjectType })[0] 114 | .fields.getMap() 115 | .get(fieldName) 116 | .getDescribe(); 117 | fieldDescribesList.add(fieldDescribe); 118 | } 119 | return JSON.serialize(fieldDescribesList); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /core/main/default/classes/ObjectInfoController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/ObjectInfoControllerTest.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | public inherited sharing class ObjectInfoControllerTest { 3 | public class SObjectDescribeWrapper { 4 | public String name { get; set; } 5 | } 6 | 7 | public class FieldDescribeWrapper { 8 | public String name { get; set; } 9 | public String type { get; set; } 10 | public String label { get; set; } 11 | } 12 | 13 | @isTest 14 | private static void it_should_return_object_describe() { 15 | String acctDescribe = ObjectInfoController.getObjectInfo(new List{ 'Account', 'Contact' }); 16 | List acctDesc = (List) JSON.deserialize( 17 | acctDescribe, 18 | List.class 19 | ); 20 | System.assertEquals('Account', acctDesc[0].name); 21 | System.assertEquals('Contact', acctDesc[1].name); 22 | } 23 | 24 | @isTest 25 | private static void it_should_return_field_describe() { 26 | String fieldDescribeStrings = ObjectInfoController.getFieldInfo( 27 | new List{ 'Account.Name', 'Contact.LastName', 'Contact.Account.Owner.Phone' } 28 | ); 29 | List fieldDesc = (List) JSON.deserialize( 30 | fieldDescribeStrings, 31 | List.class 32 | ); 33 | System.assertEquals('Name', fieldDesc[0].name); 34 | System.assertEquals(Schema.DisplayType.STRING.name().toLowerCase(), fieldDesc[0].type); 35 | System.assertEquals('LastName', fieldDesc[1].name); 36 | System.assertEquals('Phone', fieldDesc[2].name); 37 | System.assertEquals(Schema.DisplayType.PHONE.name().toLowerCase(), fieldDesc[2].type); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/main/default/classes/ObjectInfoControllerTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/PlatformCacheService.cls: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | 3 | *** @author Suraj Pillai 4 | *** @date 06/2021 5 | *** @description Provides methods for interacting with Platform Cache from an LWC 6 | *** 7 | **/ 8 | public with sharing class PlatformCacheService { 9 | private static final String DEFAULTPARTITION = 'lwcmodules'; 10 | 11 | /**** 12 | ** @description determines if this method was invoked in a test with @SeeAllData=true 13 | **/ 14 | @TestVisible 15 | private static Boolean isSeeAllDataTrue() { 16 | Boolean seeAllData = false; 17 | if (!Test.isRunningTest()) { 18 | return seeAllData; 19 | } 20 | 21 | try { 22 | // this will throw an exception in a data-siloed test. 23 | ConnectApi.ChatterUsers.getFollowings(null, String.valueOf(UserInfo.getUserId())); 24 | seeAllData = true; 25 | } catch (UnsupportedOperationException UE) { 26 | // this means seeAllData != true 27 | System.debug(LoggingLevel.INFO, 'Caught UnsupportedOperationException'); 28 | } 29 | return seeAllData; 30 | } 31 | 32 | /** 33 | * @description Method determines if platform cache is enabled for this org 34 | * Note: fail-safes to false. 35 | * equal to true. Use this in test contexts where cache is not available like 36 | * when you have to use seeAllData=true 37 | * @return `Boolean` 38 | */ 39 | @AuraEnabled(cacheable=true) 40 | public static Boolean isPlatformCacheEnabled() { 41 | if (isSeeAllDataTrue()) { 42 | return false; 43 | } 44 | 45 | Boolean cacheAvailable = false; 46 | try { 47 | Cache.Org.getPartition(DEFAULTPARTITION).getCapacity(); 48 | cacheAvailable = true; 49 | } catch (Exception ex) { 50 | system.debug(LoggingLevel.WARN, 'Failed to get orgCache'); 51 | } 52 | return cacheAvailable; 53 | } 54 | 55 | private static String getFullyQualifiedKey(String cacheKey) { 56 | String[] keyParts = cacheKey.split('\\.'); 57 | if (keyParts.size() == 1) 58 | cacheKey = 'local.' + DEFAULTPARTITION + '.' + cacheKey; 59 | if (keyParts.size() == 2) 60 | cacheKey = 'local.' + cacheKey; 61 | return cacheKey; 62 | } 63 | 64 | /** 65 | * Store a key value pair in default org cache 66 | * @param key The key against which the value will be stored in the org cache. The key will be needed to retrieve value from the cache later. For storing values in specific partitions and namespace, prefix it to the key. https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_cache_Org.htm 67 | * @param value The value to be stored in the cache 68 | * 69 | **/ 70 | @AuraEnabled 71 | public static void saveToOrgCache(String cacheKey, Object value) { 72 | String fullyQualifiedKey = getFullyQualifiedKey(cacheKey); 73 | Cache.Org.put(fullyQualifiedKey, value); 74 | } 75 | 76 | //Refer to https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_cache_Org.htm for details on how the key name works 77 | @AuraEnabled 78 | public static Object retrieveFromOrgCache(String cacheKey) { 79 | String fullyQualifiedKey = getFullyQualifiedKey(cacheKey); 80 | return Cache.org.get(fullyQualifiedKey); 81 | } 82 | 83 | @AuraEnabled 84 | public static void removeFromOrgCache(String cacheKey) { 85 | String fullyQualifiedKey = getFullyQualifiedKey(cacheKey); 86 | Cache.org.remove(fullyQualifiedKey); 87 | } 88 | 89 | /** 90 | * Store a key value pair in default session cache 91 | * @param key The key against which the value will be stored in the session cache. The key will be needed to retrieve value from the cache later. For storing values in specific partitions and namespace, prefix it to the key. https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_platform_cache_session_examples.htm 92 | * @param value The value to be stored in the cache 93 | * 94 | **/ 95 | @AuraEnabled 96 | public static void saveToSessionCache(String cacheKey, Object value) { 97 | String fullyQualifiedKey = getFullyQualifiedKey(cacheKey); 98 | Cache.session.put(fullyQualifiedKey, value); 99 | } 100 | 101 | @AuraEnabled 102 | public static Object retrieveFromSessionCache(String cacheKey) { 103 | String fullyQualifiedKey = getFullyQualifiedKey(cacheKey); 104 | return Cache.session.get(fullyQualifiedKey); 105 | } 106 | 107 | @AuraEnabled 108 | public static void removeFromSessionCache(String cacheKey) { 109 | String fullyQualifiedKey = getFullyQualifiedKey(cacheKey); 110 | Cache.session.remove(fullyQualifiedKey); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /core/main/default/classes/PlatformCacheService.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/PlatformCacheServiceTest.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class PlatformCacheServiceTest { 3 | private static final String TEST_KEY = 'testKey'; 4 | private static final String TEST_VAL = 'testVal'; 5 | @isTest 6 | private static void it_should_save_to_org_cache() { 7 | System.assert( 8 | PlatformCacheService.isPlatformCacheEnabled(), 9 | 'Platform Cache should be enabled and a default partition set to deploy this class' 10 | ); 11 | Test.startTest(); 12 | PlatformCacheService.saveToOrgCache(TEST_KEY, TEST_VAL); 13 | Test.stopTest(); 14 | system.assertEquals( 15 | PlatformCacheService.retrieveFromOrgCache(TEST_KEY).toString(), 16 | TEST_VAL, 17 | 'Expected saved value to be retrieved from org cache' 18 | ); 19 | } 20 | 21 | @isTest 22 | private static void it_should_remove_from_org_cache() { 23 | System.assert( 24 | PlatformCacheService.isPlatformCacheEnabled(), 25 | 'Platform Cache should be enabled and a default partition set to deploy this class' 26 | ); 27 | PlatformCacheService.saveToOrgCache(TEST_KEY, TEST_VAL); 28 | system.assertEquals( 29 | PlatformCacheService.retrieveFromOrgCache(TEST_KEY).toString(), 30 | TEST_VAL, 31 | 'Expected saved value to be retrieved from org cache' 32 | ); 33 | Test.startTest(); 34 | PlatformCacheService.removeFromOrgCache(TEST_KEY); 35 | system.assertEquals( 36 | PlatformCacheService.retrieveFromOrgCache(TEST_KEY), 37 | null, 38 | 'Expected saved value to have been removed from org cache' 39 | ); 40 | Test.stopTest(); 41 | } 42 | 43 | @isTest 44 | private static void it_should_save_to_session_cache() { 45 | System.assert( 46 | PlatformCacheService.isPlatformCacheEnabled(), 47 | 'Platform Cache should be enabled and a default partition set to deploy this class' 48 | ); 49 | Test.startTest(); 50 | PlatformCacheService.saveToSessionCache(TEST_KEY, TEST_VAL); 51 | Test.stopTest(); 52 | system.assertEquals( 53 | PlatformCacheService.retrieveFromSessionCache(TEST_KEY).toString(), 54 | TEST_VAL, 55 | 'Expected saved value to be retrieved from org cache' 56 | ); 57 | } 58 | 59 | @isTest 60 | private static void it_should_remove_from_session_cache() { 61 | System.assert( 62 | PlatformCacheService.isPlatformCacheEnabled(), 63 | 'Platform Cache should be enabled and a default partition set to deploy this class' 64 | ); 65 | PlatformCacheService.saveToSessionCache(TEST_KEY, TEST_VAL); 66 | system.assertEquals( 67 | PlatformCacheService.retrieveFromSessionCache(TEST_KEY).toString(), 68 | TEST_VAL, 69 | 'Expected saved value to be retrieved from org cache' 70 | ); 71 | Test.startTest(); 72 | PlatformCacheService.removeFromOrgCache(TEST_KEY); 73 | system.assertEquals( 74 | PlatformCacheService.retrieveFromOrgCache(TEST_KEY), 75 | null, 76 | 'Expected saved value to have been removed from org cache' 77 | ); 78 | Test.stopTest(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /core/main/default/classes/PlatformCacheServiceTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/classes/PlatformEventService.cls: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | 3 | *** @author Suraj Pillai 4 | *** @date 05/2021 5 | *** @description Contains Platform Event related methods 6 | *** 7 | **/ 8 | public with sharing class PlatformEventService { 9 | /**** 10 | ** @description Publishes a single platform event of a given type with the given data 11 | ** @param eventName The Platform Event type 12 | ** @param eventData JSON String with the platform event data 13 | **/ 14 | @AuraEnabled 15 | public static void publishEvent(String eventType, String eventData) { 16 | SObject pEvent = (SObject) JSON.deserialize(eventData, Type.forName(eventType)); 17 | Eventbus.publish(pEvent); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/main/default/classes/PlatformEventService.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/lwc/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@salesforce/eslint-config-lwc/recommended", "prettier"], 3 | "overrides": [ 4 | { 5 | "files": ["*.test.js"], 6 | "rules": { 7 | "@lwc/lwc/no-unexpected-wire-adapter-usages": "off" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /core/main/default/lwc/apiService/apiService.js: -------------------------------------------------------------------------------- 1 | import getSessionId from "@salesforce/apex/GetSessionIdController.getSessionId"; 2 | import getRestAPIBaseUrl from "@salesforce/apex/GetSessionIdController.getRestAPIBaseUrl"; 3 | 4 | export default async function sfapi( 5 | endPoint, 6 | method = "GET", 7 | headers = { "Content-Type": "application/json" }, 8 | body = null 9 | ) { 10 | if (endPoint.toLowerCase().indexOf("salesforce.com") === -1) { 11 | const baseUrl = await getRestAPIBaseUrl(); 12 | endPoint = baseUrl + endPoint; 13 | } 14 | const sessionId = await getSessionId(); 15 | headers = Object.assign(headers, { 16 | "Content-Type": "application/json", 17 | Accept: "application/json", 18 | Authorization: `Bearer ${sessionId}` 19 | }); 20 | const result = await fetch(endPoint, { 21 | mode: "cors", 22 | method, 23 | body, 24 | headers 25 | }); 26 | return result.json(); 27 | } 28 | -------------------------------------------------------------------------------- /core/main/default/lwc/apiService/apiService.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/lwc/calloutService/calloutService.js: -------------------------------------------------------------------------------- 1 | import makeApiCall from "@salesforce/apex/APICallController.makeApiCall"; 2 | 3 | export default async function callout( 4 | endPoint, 5 | method = "GET", 6 | headers = { "Content-Type": "application/json" }, 7 | body = null 8 | ) { 9 | return makeApiCall({ 10 | endPoint, 11 | method, 12 | bodyStr: body ? JSON.stringify(body) : "", 13 | headers 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /core/main/default/lwc/calloutService/calloutService.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/lwc/describeMetadataService/describeMetadataService.js: -------------------------------------------------------------------------------- 1 | import getSObjectInfo from "@salesforce/apex/ObjectInfoController.getObjectInfo"; 2 | import getFieldInfo from "@salesforce/apex/ObjectInfoController.getFieldInfo"; 3 | 4 | const describeSObjectInfo = async (sobjectNames) => { 5 | let resp = await getSObjectInfo({ sobjectNames }); 6 | return JSON.parse(resp); 7 | }; 8 | 9 | const describeFieldInfo = async (fieldNames) => { 10 | let resp = await getFieldInfo({ fieldNames }); 11 | return JSON.parse(resp); 12 | }; 13 | 14 | export { describeSObjectInfo, describeFieldInfo }; 15 | -------------------------------------------------------------------------------- /core/main/default/lwc/describeMetadataService/describeMetadataService.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/lwc/dmlService/dmlService.js: -------------------------------------------------------------------------------- 1 | import executeDml from "@salesforce/apex/DynamicSOQLDMLController.executeDml"; 2 | import getSObjectType from "@salesforce/apex/DynamicSOQLDMLController.getSObjectTypeFromId"; 3 | 4 | async function dml(dmlType, records, sObjectType) { 5 | if (records && !Array.isArray(records)) { 6 | records = [records]; 7 | } 8 | 9 | /* If sobjecType is not specified, we try to deduce it from the record id */ 10 | if (!sObjectType) sObjectType = await getSObjectType({ recordId: records[0].Id }); 11 | 12 | records = records.map((rec) => ({ 13 | ...rec, 14 | attributes: { type: sObjectType } 15 | })); 16 | 17 | let results = await executeDml({ 18 | operation: dmlType, 19 | strData: sObjectType 20 | ? JSON.stringify(records, (k, v) => { 21 | return typeof v === "number" ? "" + v : v; 22 | }) 23 | : null, 24 | sObjectType 25 | }); 26 | return results; 27 | } 28 | 29 | const insert = dml.bind(null, "insert"); 30 | const update = dml.bind(null, "update"); 31 | const upsert = dml.bind(null, "upsert"); 32 | const del = dml.bind(null, "delete"); 33 | 34 | export { insert, update, upsert, del }; 35 | -------------------------------------------------------------------------------- /core/main/default/lwc/dmlService/dmlService.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/lwc/platformCacheService/platformCacheService.js: -------------------------------------------------------------------------------- 1 | import saveToOrgCache from "@salesforce/apex/PlatformCacheService.saveToOrgCache"; 2 | import retrieveFromOrgCache from "@salesforce/apex/PlatformCacheService.retrieveFromOrgCache"; 3 | import removeFromOrgCache from "@salesforce/apex/PlatformCacheService.removeFromOrgCache"; 4 | import saveToSessionCache from "@salesforce/apex/PlatformCacheService.saveToSessionCache"; 5 | import retrieveFromSessionCache from "@salesforce/apex/PlatformCacheService.retrieveFromSessionCache"; 6 | import removeFromSessionCache from "@salesforce/apex/PlatformCacheService.removeFromSessionCache"; 7 | import isPlatformCacheEnabled from "@salesforce/apex/PlatformCacheService.isPlatformCacheEnabled"; 8 | 9 | let cacheEnableCheckComplete = false, 10 | isCacheEnabled = false; 11 | 12 | const isEnabled = async () => { 13 | if (cacheEnableCheckComplete) return isCacheEnabled; 14 | isCacheEnabled = await isPlatformCacheEnabled(); 15 | cacheEnableCheckComplete = true; 16 | return isCacheEnabled; 17 | }; 18 | 19 | const throwIfNotEnabled = async () => { 20 | await isEnabled(); 21 | if (!isCacheEnabled) throw "Please enable Platform Cache to use this module"; 22 | }; 23 | 24 | const org = { 25 | put: async (key, value) => { 26 | await throwIfNotEnabled(); 27 | return saveToOrgCache({ cacheKey: key, value }); 28 | }, 29 | 30 | get: async (key) => { 31 | await throwIfNotEnabled(); 32 | return retrieveFromOrgCache({ cacheKey: key }); 33 | }, 34 | 35 | remove: async (key) => { 36 | await throwIfNotEnabled(); 37 | return removeFromOrgCache({ cacheKey: key }); 38 | } 39 | }; 40 | 41 | const session = { 42 | put: async (key, value) => { 43 | await throwIfNotEnabled(); 44 | return saveToSessionCache({ cacheKey: key, value }); 45 | }, 46 | 47 | get: async (key) => { 48 | await throwIfNotEnabled(); 49 | return retrieveFromSessionCache({ cacheKey: key }); 50 | }, 51 | 52 | remove: async (key) => { 53 | await throwIfNotEnabled(); 54 | return removeFromSessionCache({ cacheKey: key }); 55 | } 56 | }; 57 | export { org, session, isEnabled }; 58 | -------------------------------------------------------------------------------- /core/main/default/lwc/platformCacheService/platformCacheService.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/lwc/platformEventService/platformEventService.js: -------------------------------------------------------------------------------- 1 | import publishEvent from "@salesforce/apex/PlatformEventService.publishEvent"; 2 | const publish = (eventType, eventData) => { 3 | publishEvent({ eventType, eventData }); 4 | }; 5 | 6 | export { publish }; 7 | -------------------------------------------------------------------------------- /core/main/default/lwc/platformEventService/platformEventService.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/lwc/soqlService/soqlService.js: -------------------------------------------------------------------------------- 1 | import executeSoql from "@salesforce/apex/DynamicSOQLDMLController.executeSoqlQuery"; 2 | 3 | export default async function soql(query) { 4 | let results = await executeSoql({ query }); 5 | return results; 6 | } 7 | -------------------------------------------------------------------------------- /core/main/default/lwc/soqlService/soqlService.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/lwc/utils/utils.js: -------------------------------------------------------------------------------- 1 | const flatten = (obj, delimiter, newobj, prefix) => { 2 | if (!newobj) newobj = {}; 3 | for (let prop in obj) { 4 | if (obj.hasOwnProperty(prop)) { 5 | if (!prefix) prefix = ""; 6 | if (typeof obj[prop] === "object") flatten(obj[prop], delimiter, newobj, prefix + prop + delimiter); 7 | else newobj[prefix + prop] = obj[prop]; 8 | } 9 | } 10 | return newobj; 11 | }; 12 | 13 | //source: https://github.com/tsalb/lwc-utils/blob/master/utils-core/main/default/lwc/utils/utils.js 14 | const reduceErrors = (errors) => { 15 | if (typeof errors === "string") { 16 | return [errors]; 17 | } 18 | if (!Array.isArray(errors)) { 19 | errors = [errors]; 20 | } 21 | 22 | return ( 23 | errors 24 | // Remove null/undefined items 25 | .filter((error) => !!error) 26 | // Extract an error message 27 | .map((error) => { 28 | // UI API read errors 29 | if (Array.isArray(error.body)) { 30 | return error.body.map((e) => e.message); 31 | } 32 | // FIELD VALIDATION, FIELD, and trigger.addError 33 | else if ( 34 | error.body && 35 | error.body.enhancedErrorType && 36 | error.body.enhancedErrorType.toLowerCase() === "recorderror" && 37 | error.body.output 38 | ) { 39 | let firstError = ""; 40 | if ( 41 | error.body.output.errors.length && 42 | error.body.output.errors[0].errorCode.includes("_") // one of the many salesforce errors with underscores 43 | ) { 44 | firstError = error.body.output.errors[0].message; 45 | } 46 | if (!error.body.output.errors.length && error.body.output.fieldErrors) { 47 | // It's in a really weird format... 48 | firstError = error.body.output.fieldErrors[Object.keys(error.body.output.fieldErrors)[0]][0].message; 49 | } 50 | return firstError; 51 | } 52 | // UI API DML, Apex and network errors 53 | else if (error.body && typeof error.body.message === "string") { 54 | let errorMessage = error.body.message; 55 | if (typeof error.body.stackTrace === "string") { 56 | errorMessage += `\n${error.body.stackTrace}`; 57 | } 58 | return errorMessage; 59 | } 60 | // PAGE ERRORS 61 | else if (error.body && error.body.pageErrors.length) { 62 | return error.body.pageErrors[0].message; 63 | } 64 | // JS errors 65 | else if (typeof error.message === "string") { 66 | return error.message; 67 | } 68 | // Unknown error shape so try HTTP status text 69 | return error.statusText; 70 | }) 71 | // Flatten 72 | .reduce((prev, curr) => prev.concat(curr), []) 73 | // Remove empty strings 74 | .filter((message) => !!message) 75 | ); 76 | }; 77 | 78 | const processCompositeApiResponse = (resp) => { 79 | const errors = resp.compositeResponse 80 | .filter((r) => r.httpStatusCode !== 200) 81 | .map((r) => ({ errorCode: r.errorCode, message: `${r.body[0].message}::ReferenceId: ${r.referenceId}` })); 82 | const errorToDisplay = errors 83 | .filter((err) => err.errorCode !== "PROCESSING_HALTED") 84 | .map((err) => err.message) 85 | .join("^^^^"); 86 | return { errors: errors.length, message: errorToDisplay }; 87 | }; 88 | 89 | export { flatten, reduceErrors, processCompositeApiResponse }; 90 | -------------------------------------------------------------------------------- /core/main/default/lwc/utils/utils.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/pages/GetSessionId.page: -------------------------------------------------------------------------------- 1 | 9 | Start_Of_Session_Id{!$Api.Session_ID}End_Of_Session_Id 10 | 11 | -------------------------------------------------------------------------------- /core/main/default/pages/GetSessionId.page-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/main/default/remoteSiteSettings/status.remoteSite-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | true 5 | https://api.status.salesforce.com 6 | 7 | -------------------------------------------------------------------------------- /examples/main/default/cspTrustedSites/picsum.cspTrustedSite-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | All 4 | https://*.picsum.photos 5 | true 6 | true 7 | true 8 | true 9 | true 10 | true 11 | true 12 | 13 | -------------------------------------------------------------------------------- /examples/main/default/cspTrustedSites/picsummain.cspTrustedSite-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | All 4 | https://picsum.photos 5 | true 6 | true 7 | true 8 | true 9 | true 10 | true 11 | true 12 | 13 | -------------------------------------------------------------------------------- /examples/main/default/lwc/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@salesforce/eslint-config-lwc/recommended", "prettier"], 3 | "overrides": [ 4 | { 5 | "files": ["*.test.js"], 6 | "rules": { 7 | "@lwc/lwc/no-unexpected-wire-adapter-usages": "off" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/main/default/lwc/compositeApiExample/compositeApiExample.html: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /examples/main/default/lwc/compositeApiExample/compositeApiExample.js: -------------------------------------------------------------------------------- 1 | import { LightningElement, wire } from "lwc"; 2 | import sfapi from "c/apiService"; 3 | import { ShowToastEvent } from "lightning/platformShowToastEvent"; 4 | import { reduceErrors, processCompositeApiResponse } from "c/utils"; 5 | import { getObjectInfo } from "lightning/uiObjectInfoApi"; 6 | import { describeSObjectInfo } from "c/describeMetadataService"; 7 | 8 | export default class CompositeApiExample extends LightningElement { 9 | accts = []; 10 | cntcs = []; 11 | 12 | acctFields = ["Name", "Rating", "NumberOfEmployees", "Industry"]; 13 | contactFields = ["FirstName", "LastName", "Email", "Title", "Phone"]; 14 | oppFields = ["Name", "StageName", "CloseDate", "Amount"]; 15 | 16 | @wire(getObjectInfo, { objectApiName: "Account" }) 17 | wireGetAccountInfo({ error, data }) { 18 | if (!error && data) { 19 | const childObjectApis = data.childRelationships.map((r) => r.childObjectApiName); 20 | console.log("child object Api names", childObjectApis); 21 | describeSObjectInfo(["Account", "Contact"]) 22 | .then((resp) => { 23 | console.log(">> got child object info ", resp); 24 | }) 25 | .catch((err) => { 26 | console.error(">>> child errors ", reduceErrors(err)); 27 | }); 28 | } else if (error) { 29 | this.dispatchEvent(new ShowToastEvent({ variant: "error", message: reduceErrors(error) })); 30 | } 31 | } 32 | 33 | objectsAndFields = [ 34 | { objName: "Account", fields: this.acctFields }, 35 | { objName: "Contact", fields: this.contactFields }, 36 | { objName: "Opportunity", fields: this.oppFields } 37 | ]; 38 | 39 | createFieldsJson(fields) { 40 | return fields.reduce((obj, curr) => { 41 | obj[curr.dataset.name] = curr.value; 42 | return obj; 43 | }, {}); 44 | } 45 | 46 | newHandler(event) { 47 | event.preventDefault(); 48 | const records = [...this.template.querySelectorAll("lightning-record-edit-form")]; 49 | records.forEach((rec) => { 50 | const fields = [...rec.querySelectorAll("lightning-input-field")]; 51 | const newRecord = this.createFieldsJson(fields); 52 | newRecord.referenceId = rec.dataset.name + "-" + this.guid(); 53 | this.compositeData.push(newRecord); 54 | }); 55 | } 56 | 57 | handleClick(event) { 58 | event.preventDefault(); 59 | this.setupAccounts(); 60 | this.setupContacts(); 61 | this.createAccountsAndContacts(); 62 | this.accts = []; 63 | this.cntcs = []; 64 | } 65 | 66 | setupAccounts() { 67 | this.accts.push({ body: { Name: "Amazon", Industry: "E-commerce" }, referenceId: "Amazon" }); 68 | this.accts.push({ body: { Name: "Facebook", Industry: "Social Media" }, referenceId: "Facebook" }); 69 | this.accts.push({ body: { Name: "Google", Industry: "Search" }, referenceId: "Google" }); 70 | this.accts.push({ body: { Name: "Netflix", Industry: "Entertainment" }, referenceId: "Netflix" }); 71 | } 72 | 73 | setupContacts() { 74 | /* create one contact for each Account */ 75 | this.cntcs.push({ 76 | body: { 77 | LastName: "Bezos", 78 | FirstName: "Jeff", 79 | Email: "bezos@amazon.example.com", 80 | Title: "CEO of Amazon", 81 | AccountId: "@{Amazon.id}" 82 | }, 83 | referenceId: "Jeff" 84 | }); 85 | 86 | this.cntcs.push({ 87 | body: { 88 | LastName: "Zuckerberg", 89 | FirstName: "Marc", 90 | Email: "marc@facebook.example.com", 91 | Title: "CEO of Facebook", 92 | AccountId: "@{Facebook.id}" 93 | }, 94 | referenceId: "Marc" 95 | }); 96 | 97 | this.cntcs.push({ 98 | body: { 99 | LastName: "Pichai", 100 | FirstName: "Sundar", 101 | Email: "pichai@google.example.com", 102 | Title: "CEO of Google", 103 | AccountId: "@{Google.id}" 104 | }, 105 | referenceId: "Sundar" 106 | }); 107 | 108 | this.cntcs.push({ 109 | body: { 110 | LastName: "Hastings", 111 | FirstName: "Reed", 112 | Email: "reed@netflix.example.com", 113 | Title: "CEO of Netflix", 114 | AccountId: "@{Netflix.id}" 115 | }, 116 | referenceId: "Reed" 117 | }); 118 | 119 | /* create subrequests for Account and Contact by adding `method` and `url` properties */ 120 | this.accts = this.accts.map((a) => ({ 121 | ...a, 122 | method: "POST", 123 | url: "/services/data/v51.0/sobjects/Account" 124 | })); 125 | this.cntcs = this.cntcs.map((c) => ({ 126 | ...c, 127 | method: "POST", 128 | url: "/services/data/v51.0/sobjects/Contact" 129 | })); 130 | } 131 | 132 | async createAccountsAndContacts() { 133 | try { 134 | let compositeReq = { allOrNone: true, compositeRequest: [...this.accts, ...this.cntcs] }; 135 | let response = await sfapi( 136 | "/composite/" /*path excluding base url*/, 137 | "POST" /*method*/, 138 | {} /* additional headers */, 139 | JSON.stringify(compositeReq) /* request body */ 140 | ); 141 | response = processCompositeApiResponse(response); 142 | if (response.errors === 0) 143 | this.dispatchEvent(new ShowToastEvent({ variant: "success", message: "Records created successfully" })); 144 | else 145 | this.dispatchEvent( 146 | new ShowToastEvent({ 147 | variant: "error", 148 | mode: "sticky", 149 | message: `Failed to process ${response.errors} records ${response.message}` 150 | }) 151 | ); 152 | } catch (error) { 153 | const errorStr = reduceErrors(error); 154 | this.dispatchEvent(new ShowToastEvent({ variant: "error", message: `Failed to create records ${errorStr}` })); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /examples/main/default/lwc/compositeApiExample/compositeApiExample.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | true 5 | 6 | lightning__HomePage 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/main/default/lwc/dmlExample/dmlExample.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /examples/main/default/lwc/dmlExample/dmlExample.js: -------------------------------------------------------------------------------- 1 | import { LightningElement } from "lwc"; 2 | import * as dml from "c/dmlService"; 3 | import { ShowToastEvent } from "lightning/platformShowToastEvent"; 4 | 5 | export default class DmlExample extends LightningElement { 6 | async handleSave() { 7 | try { 8 | const acctName = this.template.querySelector("lightning-input.acct").value; 9 | const cntctLastName = this.template.querySelector("lightning-input.lastname").value; 10 | const cntctFirstName = this.template.querySelector("lightning-input.firstname").value; 11 | 12 | // For 'insert' and 'upsert', specify the object name as the second param 13 | const acctId = (await dml.insert({ Name: acctName }, "Account"))[0]; 14 | const cntctId = await dml.insert( 15 | { LastName: cntctLastName, FirstName: cntctFirstName, AccountId: acctId }, 16 | "Contact" 17 | ); 18 | console.log("Account and Contact inserted", acctId, cntctId); 19 | this.dispatchEvent(new ShowToastEvent({ variant: "success", message: "Records created successfully" })); 20 | } catch (e) { 21 | this.dispatchEvent(new ShowToastEvent({ variant: "error", message: e.message || e.body.message })); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/main/default/lwc/dmlExample/dmlExample.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | true 5 | 6 | lightning__HomePage 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/main/default/lwc/platformCacheExample/platformCacheExample.html: -------------------------------------------------------------------------------- 1 | 55 | -------------------------------------------------------------------------------- /examples/main/default/lwc/platformCacheExample/platformCacheExample.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides methods for interacting with Plaform Cache 3 | * 4 | */ 5 | import { LightningElement } from "lwc"; 6 | import * as cache from "c/platformCacheService"; 7 | import { ShowToastEvent } from "lightning/platformShowToastEvent"; 8 | import { reduceErrors } from "c/utils"; 9 | 10 | const CACHE_KEY = "theKey"; //fully qualified key. If partition and namespace are not specified, the value is added to 'lwcmodules' cache partition. https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_cache_Org.htm 11 | export default class PlatformCacheExample extends LightningElement { 12 | userInput = ""; 13 | outputText = ""; 14 | 15 | async connectedCallback() { 16 | let isCacheEnabled = await cache.isEnabled(); 17 | if (!isCacheEnabled) { 18 | this.dispatchEvent( 19 | new ShowToastEvent({ variant: "info", message: "Please enable Platform Cache to use this module" }) 20 | ); 21 | } 22 | } 23 | 24 | handleInputChange(event) { 25 | this.userInput = event.target.value; 26 | } 27 | 28 | async handlePut() { 29 | try { 30 | await cache.session.put(CACHE_KEY, this.userInput); 31 | this.dispatchEvent(new ShowToastEvent({ variant: "success", message: "Value successfully saved to cache" })); 32 | } catch (err) { 33 | const errStr = reduceErrors(err); 34 | this.dispatchEvent(new ShowToastEvent({ variant: "error", message: errStr.join("\n") })); 35 | } 36 | } 37 | 38 | async handleGet() { 39 | try { 40 | this.outputText = await cache.session.get(CACHE_KEY); 41 | } catch (err) { 42 | const errStr = reduceErrors(err); 43 | this.dispatchEvent(new ShowToastEvent({ variant: "error", message: errStr.join("\n") })); 44 | } 45 | } 46 | 47 | blobToBase64(blob) { 48 | var reader = new FileReader(); 49 | return new Promise((res) => { 50 | reader.readAsDataURL(blob); 51 | reader.onloadend = function () { 52 | var base64data = reader.result; 53 | res(base64data); 54 | }; 55 | }); 56 | } 57 | 58 | async testDataSizeException() { 59 | try { 60 | let resp = await fetch("https://picsum.photos/1200"); 61 | let data = await resp.blob(); 62 | let base64data = await this.blobToBase64(data); 63 | await cache.session.put(CACHE_KEY, base64data); // this will exceed the maximum partition size and will result in an exception 64 | } catch (err) { 65 | console.error(err); 66 | const errStr = reduceErrors(err); 67 | this.dispatchEvent(new ShowToastEvent({ variant: "error", message: errStr.join("\n") })); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/main/default/lwc/platformCacheExample/platformCacheExample.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | true 5 | 6 | lightning__HomePage 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/main/default/lwc/platformEventExample/platformEventExample.html: -------------------------------------------------------------------------------- 1 | 35 | -------------------------------------------------------------------------------- /examples/main/default/lwc/platformEventExample/platformEventExample.js: -------------------------------------------------------------------------------- 1 | import { LightningElement } from "lwc"; 2 | import * as platformEventService from "c/platformEventService"; 3 | import { subscribe, unsubscribe, onError } from "lightning/empApi"; 4 | import { ShowToastEvent } from "lightning/platformShowToastEvent"; 5 | 6 | const TEST_EVENT = "Test_Event__e"; 7 | export default class PlatformEventExample extends LightningElement { 8 | channelName = `/event/${TEST_EVENT}`; 9 | isSubscribeDisabled = false; 10 | isUnsubscribeDisabled = !this.isSubscribeDisabled; 11 | 12 | subscription = {}; 13 | message = ""; 14 | receivedMessages = []; 15 | 16 | // Initializes the component 17 | connectedCallback() { 18 | // Register error listener 19 | this.registerErrorListener(); 20 | } 21 | 22 | // Handles subscribe button click 23 | handleSubscribe() { 24 | // Callback invoked whenever a new event message is received 25 | const messageCallback = (response) => { 26 | console.log("New message received: ", JSON.stringify(response)); 27 | this.receivedMessages = [...this.receivedMessages, response.data.payload.Message__c]; 28 | }; 29 | 30 | // Invoke subscribe method of empApi. Pass reference to messageCallback 31 | subscribe(this.channelName, -1, messageCallback).then((response) => { 32 | // Response contains the subscription information on subscribe call 33 | console.log("Subscription request sent to: ", JSON.stringify(response.channel)); 34 | this.subscription = response; 35 | this.toggleSubscribeButton(true); 36 | }); 37 | } 38 | 39 | // Handles unsubscribe button click 40 | handleUnsubscribe() { 41 | this.toggleSubscribeButton(false); 42 | 43 | // Invoke unsubscribe method of empApi 44 | unsubscribe(this.subscription, (response) => { 45 | console.log("unsubscribe() response: ", JSON.stringify(response)); 46 | // Response is true for successful unsubscribe 47 | }); 48 | } 49 | 50 | toggleSubscribeButton(enableSubscribe) { 51 | this.isSubscribeDisabled = enableSubscribe; 52 | this.isUnsubscribeDisabled = !enableSubscribe; 53 | } 54 | 55 | registerErrorListener() { 56 | // Invoke onError empApi method 57 | onError((error) => { 58 | console.log("Received error from server: ", JSON.stringify(error)); 59 | // Error contains the server-side error 60 | }); 61 | } 62 | 63 | handleMessageChange(event) { 64 | this.message = event.target.value; 65 | } 66 | 67 | handlePublish() { 68 | if (!this.message) { 69 | this.dispatchEvent(new ShowToastEvent({ variant: "error", message: "Please enter a message to be published" })); 70 | } 71 | const message = JSON.stringify({ Message__c: this.message }); 72 | platformEventService.publish(TEST_EVENT, message); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/main/default/lwc/platformEventExample/platformEventExample.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | true 5 | 6 | lightning__HomePage 7 | lightning__RecordPage 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/main/default/lwc/sfTrustInfo/sfTrustInfo.css: -------------------------------------------------------------------------------- 1 | .lt-bg { 2 | background: white; 3 | } 4 | -------------------------------------------------------------------------------- /examples/main/default/lwc/sfTrustInfo/sfTrustInfo.html: -------------------------------------------------------------------------------- 1 | 53 | -------------------------------------------------------------------------------- /examples/main/default/lwc/sfTrustInfo/sfTrustInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The api does not work for scratch org instances! See comment below 3 | */ 4 | import { LightningElement } from "lwc"; 5 | import apexCallout from "c/calloutService"; 6 | 7 | const INSTANCE = window.location.origin.match(/https:\/\/([^.]+).*/)[1]; 8 | //const INSTANCE = "org62"; //If you want to try this out in a scratch org, uncomment this line and comment the above line 9 | export default class SfTrustInfo extends LightningElement { 10 | maintenances = []; 11 | trustData = []; 12 | 13 | get statusLink() { 14 | return `https://status.salesforce.com/instances/${this.trustData.key}`; 15 | } 16 | 17 | async connectedCallback() { 18 | let resp = await apexCallout(`https://api.status.salesforce.com/v1/instanceAliases/${INSTANCE}/status`); 19 | this.trustData = JSON.parse(resp.body); 20 | this.maintenances = this.trustData.Maintenances; 21 | this.maintenances.forEach((m) => { 22 | m.plannedStartTime = new Date(m.plannedStartTime).toLocaleString(); 23 | m.plannedEndTime = new Date(m.plannedEndTime).toLocaleString(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/main/default/lwc/sfTrustInfo/sfTrustInfo.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | true 5 | 6 | lightning__HomePage 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/main/default/lwc/soqlDatatable/soqlDatatable.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /examples/main/default/lwc/soqlDatatable/soqlDatatable.js: -------------------------------------------------------------------------------- 1 | import { LightningElement } from "lwc"; 2 | import { describeFieldInfo } from "c/describeMetadataService"; 3 | import { flatten } from "c/utils"; 4 | import soql from "c/soqlService"; 5 | 6 | export default class SoqlDatatable extends LightningElement { 7 | fieldNames = []; //["Contact.LastName", "Contact.Account.Name", "Contact.Email", "Contact.Account.Owner.LastName"]; 8 | data = []; 9 | columns = []; 10 | 11 | get hasColumns() { 12 | return this.columns && this.columns.length > 0; 13 | } 14 | 15 | getDatatableColumnsFromDescribeFieldResult(describe) {} 16 | 17 | async connectedCallback() { 18 | try { 19 | const query = "Select LastName,Account.Name,Email,Account.Owner.LastName from Contact"; 20 | this.data = await soql(query); 21 | this.data = this.data.map((d) => flatten(d, ".")); 22 | this.fieldNames = Object.keys(this.data[0]) 23 | .filter((f) => query.indexOf(f) > -1) 24 | .map((f) => "Contact." + f); 25 | console.log("fieldnames", this.fieldNames); 26 | let result = await describeFieldInfo(this.fieldNames); 27 | this.columns = result.map((field, index) => ({ 28 | label: field.label, 29 | fieldName: this.fieldNames[index].replace(/Contact\./, "") 30 | })); 31 | console.log("data", this.data); 32 | } catch (err) { 33 | console.error("An error ", err); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/main/default/lwc/soqlDatatable/soqlDatatable.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | true 5 | 6 | lightning__HomePage 7 | 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "salesforce-app", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Salesforce App", 6 | "scripts": { 7 | "lint": "npm run lint:lwc && npm run lint:aura", 8 | "lint:aura": "eslint **/aura/**", 9 | "lint:lwc": "eslint **/lwc/**", 10 | "test": "npm run test:unit", 11 | "test:unit": "sfdx-lwc-jest", 12 | "test:unit:watch": "sfdx-lwc-jest --watch", 13 | "test:unit:debug": "sfdx-lwc-jest --debug", 14 | "test:unit:coverage": "sfdx-lwc-jest --coverage", 15 | "prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 16 | "prettier:verify": "prettier --list-different \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"" 17 | }, 18 | "devDependencies": { 19 | "@prettier/plugin-xml": "^0.12.0", 20 | "@salesforce/eslint-config-lwc": "^0.7.0", 21 | "@salesforce/eslint-plugin-aura": "^1.4.0", 22 | "@salesforce/sfdx-lwc-jest": "^0.9.2", 23 | "eslint-config-prettier": "^6.11.0", 24 | "husky": "^4.2.1", 25 | "lint-staged": "^10.0.7", 26 | "prettier": "^2.0.5", 27 | "prettier-plugin-apex": "^1.6.0" 28 | }, 29 | "husky": { 30 | "hooks": { 31 | "pre-commit": "lint-staged" 32 | } 33 | }, 34 | "lint-staged": { 35 | "**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [ 36 | "prettier --write" 37 | ], 38 | "**/{aura|lwc}/**": [ 39 | "eslint" 40 | ] 41 | }, 42 | "dependencies": { 43 | "eslint": "^6.8.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ 3 | { 4 | "path": "core", 5 | "default": true 6 | }, 7 | { 8 | "path": "examples" 9 | } 10 | ], 11 | "namespace": "", 12 | "sfdcLoginUrl": "https://login.salesforce.com", 13 | "sourceApiVersion": "52.0" 14 | } 15 | --------------------------------------------------------------------------------