delegate-sf-mcp 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.eslintrc.json +20 -0
  2. package/LICENSE +24 -0
  3. package/README.md +76 -0
  4. package/auth.js +148 -0
  5. package/bin/config-helper.js +51 -0
  6. package/bin/mcp-salesforce.js +12 -0
  7. package/bin/setup.js +266 -0
  8. package/bin/status.js +134 -0
  9. package/docs/README.md +52 -0
  10. package/docs/step1.png +0 -0
  11. package/docs/step2.png +0 -0
  12. package/docs/step3.png +0 -0
  13. package/docs/step4.png +0 -0
  14. package/examples/README.md +35 -0
  15. package/package.json +16 -0
  16. package/scripts/README.md +30 -0
  17. package/src/auth/file-storage.js +447 -0
  18. package/src/auth/oauth.js +417 -0
  19. package/src/auth/token-manager.js +207 -0
  20. package/src/backup/manager.js +949 -0
  21. package/src/index.js +168 -0
  22. package/src/salesforce/client.js +388 -0
  23. package/src/sf-client.js +79 -0
  24. package/src/tools/auth.js +190 -0
  25. package/src/tools/backup.js +486 -0
  26. package/src/tools/create.js +109 -0
  27. package/src/tools/delegate-hygiene.js +268 -0
  28. package/src/tools/delegate-validate.js +212 -0
  29. package/src/tools/delegate-verify.js +143 -0
  30. package/src/tools/delete.js +72 -0
  31. package/src/tools/describe.js +132 -0
  32. package/src/tools/installation-info.js +656 -0
  33. package/src/tools/learn-context.js +1077 -0
  34. package/src/tools/learn.js +351 -0
  35. package/src/tools/query.js +82 -0
  36. package/src/tools/repair-credentials.js +77 -0
  37. package/src/tools/setup.js +120 -0
  38. package/src/tools/time_machine.js +347 -0
  39. package/src/tools/update.js +138 -0
  40. package/src/tools.js +214 -0
  41. package/src/utils/cache.js +120 -0
  42. package/src/utils/debug.js +52 -0
  43. package/src/utils/logger.js +19 -0
  44. package/tokens.json +8 -0
package/src/index.js ADDED
@@ -0,0 +1,168 @@
1
+ /**
2
+ * index.js — Delegate SF MCP Server
3
+ *
4
+ * Start: node src/index.js
5
+ * Requires tokens.json (run node auth.js first)
6
+ */
7
+
8
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
11
+
12
+ import {
13
+ handleQuery,
14
+ handleGetRecord,
15
+ handleSearch,
16
+ handleDescribe,
17
+ handleCreate,
18
+ handleUpdate,
19
+ handleVerifyRecordExists,
20
+ handleGetHygieneScore,
21
+ } from './tools.js';
22
+
23
+ // ── Tool definitions ──────────────────────────────────────────────────────────
24
+
25
+ const TOOLS = [
26
+ {
27
+ name: 'sf_query',
28
+ description: 'Run a SOQL query against Salesforce. Use for reading records with filters. Example: SELECT Id, Name, StageName FROM Opportunity WHERE StageName = \'Prospecting\'',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: { soql: { type: 'string', description: 'The SOQL query to execute' } },
32
+ required: ['soql'],
33
+ },
34
+ },
35
+ {
36
+ name: 'sf_get_record',
37
+ description: 'Get a single Salesforce record by ID with specific fields.',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ object_type: { type: 'string', description: 'Salesforce object API name (e.g. Opportunity, Contact, Account)' },
42
+ record_id: { type: 'string', description: '18-character Salesforce record ID' },
43
+ fields: { type: 'array', items: { type: 'string' }, description: 'Fields to return. Defaults to Id, Name.' },
44
+ },
45
+ required: ['object_type', 'record_id'],
46
+ },
47
+ },
48
+ {
49
+ name: 'sf_search',
50
+ description: 'Search across Salesforce objects using SOSL. Use when you have a name or keyword but not a specific ID.',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ search_term: { type: 'string', description: 'Text to search for' },
55
+ object_types: { type: 'array', items: { type: 'string' }, description: 'Objects to search. Defaults to Contact, Account, Lead, Opportunity.' },
56
+ fields: { type: 'array', items: { type: 'string' }, description: 'Fields to return per object. Defaults to Id, Name.' },
57
+ },
58
+ required: ['search_term'],
59
+ },
60
+ },
61
+ {
62
+ name: 'sf_describe',
63
+ description: 'Get field metadata for a Salesforce object — field names, types, required fields, picklist values. Use before creating or updating records to know what fields exist.',
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: { object_type: { type: 'string', description: 'Salesforce object API name' } },
67
+ required: ['object_type'],
68
+ },
69
+ },
70
+ {
71
+ name: 'sf_create',
72
+ description: 'Create a new record in Salesforce. Always run sf_verify_exists first for Contact/Lead/Account to prevent duplicates.',
73
+ inputSchema: {
74
+ type: 'object',
75
+ properties: {
76
+ object_type: { type: 'string', description: 'Salesforce object API name' },
77
+ fields: { type: 'object', description: 'Key-value pairs of field API names and values' },
78
+ },
79
+ required: ['object_type', 'fields'],
80
+ },
81
+ },
82
+ {
83
+ name: 'sf_update',
84
+ description: 'Update an existing Salesforce record. Requires the record ID.',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ object_type: { type: 'string', description: 'Salesforce object API name' },
89
+ record_id: { type: 'string', description: '18-character Salesforce record ID' },
90
+ fields: { type: 'object', description: 'Key-value pairs of fields to update' },
91
+ },
92
+ required: ['object_type', 'record_id', 'fields'],
93
+ },
94
+ },
95
+ {
96
+ name: 'sf_verify_exists',
97
+ description: 'Check if a Contact or Lead already exists before creating one. Prevents duplicates. Matches by email first, then phone, then name+company. Always use before sf_create for people records.',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ object_type: { type: 'string', description: 'Contact, Lead, or Account' },
102
+ name: { type: 'string', description: 'Full name to check' },
103
+ email: { type: 'string', description: 'Email address (most reliable match)' },
104
+ phone: { type: 'string', description: 'Phone number' },
105
+ company: { type: 'string', description: 'Company name (used with name match)' },
106
+ },
107
+ required: ['object_type'],
108
+ },
109
+ },
110
+ {
111
+ name: 'sf_hygiene_score',
112
+ description: 'Get a hygiene score (0-100) for a Salesforce Account. Checks field completeness, activity recency, stage accuracy, contact coverage, and next step presence. Returns score, grade, strengths, and issues.',
113
+ inputSchema: {
114
+ type: 'object',
115
+ properties: { account_id: { type: 'string', description: '18-character Salesforce Account ID' } },
116
+ required: ['account_id'],
117
+ },
118
+ },
119
+ ];
120
+
121
+ // ── Handler map ───────────────────────────────────────────────────────────────
122
+
123
+ const HANDLERS = {
124
+ sf_query: handleQuery,
125
+ sf_get_record: handleGetRecord,
126
+ sf_search: handleSearch,
127
+ sf_describe: handleDescribe,
128
+ sf_create: handleCreate,
129
+ sf_update: handleUpdate,
130
+ sf_verify_exists: handleVerifyRecordExists,
131
+ sf_hygiene_score: handleGetHygieneScore,
132
+ };
133
+
134
+ // ── Server ────────────────────────────────────────────────────────────────────
135
+
136
+ const server = new Server(
137
+ { name: 'delegate-sf-mcp', version: '0.2.0' },
138
+ { capabilities: { tools: {} } }
139
+ );
140
+
141
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
142
+
143
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
144
+ const { name, arguments: args } = req.params;
145
+
146
+ const handler = HANDLERS[name];
147
+ if (!handler) {
148
+ return {
149
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
150
+ isError: true,
151
+ };
152
+ }
153
+
154
+ try {
155
+ const text = await handler(args ?? {});
156
+ return { content: [{ type: 'text', text }] };
157
+ } catch (err) {
158
+ return {
159
+ content: [{ type: 'text', text: `❌ ${err.message}` }],
160
+ isError: true,
161
+ };
162
+ }
163
+ });
164
+
165
+ // ── Start ─────────────────────────────────────────────────────────────────────
166
+
167
+ const transport = new StdioServerTransport();
168
+ await server.connect(transport);
@@ -0,0 +1,388 @@
1
+ import jsforce from 'jsforce';
2
+ import { TokenManager } from '../auth/token-manager.js';
3
+ import { FileStorageManager } from '../auth/file-storage.js';
4
+
5
+ export class SalesforceClient {
6
+ constructor(clientId, clientSecret, instanceUrl) {
7
+ this.clientId = clientId;
8
+ this.clientSecret = clientSecret;
9
+ this.instanceUrl = instanceUrl;
10
+ this.tokenManager = new TokenManager(clientId, clientSecret, instanceUrl);
11
+ this.connection = null;
12
+ this.initialized = false;
13
+ }
14
+
15
+ /**
16
+ * Initialize the Salesforce client
17
+ */
18
+ async initialize() {
19
+ if (this.initialized) {
20
+ return true;
21
+ }
22
+
23
+ try {
24
+ // Initialize token manager
25
+ const hasTokens = await this.tokenManager.initialize();
26
+ if (!hasTokens) {
27
+ throw new Error('Authentication required - no valid tokens found. Use the salesforce_auth tool to authenticate with Salesforce.');
28
+ }
29
+
30
+ // Create jsforce connection
31
+ await this.createConnection();
32
+
33
+ this.initialized = true;
34
+ return true;
35
+ } catch (error) {
36
+ throw error;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Create and configure jsforce connection
42
+ */
43
+ async createConnection() {
44
+ try {
45
+ const accessToken = await this.tokenManager.getValidAccessToken();
46
+ const tokenInfo = this.tokenManager.getTokenInfo();
47
+
48
+ // Get API version from config file
49
+ const fileStorage = new FileStorageManager();
50
+ const apiConfig = await fileStorage.getApiConfig();
51
+
52
+ this.connection = new jsforce.Connection({
53
+ instanceUrl: tokenInfo.instance_url,
54
+ accessToken: accessToken,
55
+ version: apiConfig.apiVersion
56
+ });
57
+
58
+ // Test connection
59
+ await this.connection.identity();
60
+ } catch (error) {
61
+ throw new Error(`Failed to create Salesforce connection: ${error.message}`);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Ensure connection is valid, refresh if needed
67
+ */
68
+ async ensureValidConnection() {
69
+ if (!this.initialized) {
70
+ await this.initialize();
71
+ }
72
+
73
+ try {
74
+ // Check if token needs refresh
75
+ if (await this.tokenManager.needsRefresh()) {
76
+ await this.tokenManager.refreshTokens();
77
+ await this.createConnection();
78
+ }
79
+ } catch (error) {
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Execute SOQL query
86
+ */
87
+ async query(soql, options = {}) {
88
+ await this.ensureValidConnection();
89
+
90
+ try {
91
+ const result = await this.connection.query(soql, options);
92
+
93
+ return {
94
+ totalSize: result.totalSize,
95
+ done: result.done,
96
+ records: result.records,
97
+ nextRecordsUrl: result.nextRecordsUrl
98
+ };
99
+ } catch (error) {
100
+ // Handle authentication errors first
101
+ if (this.isAuthenticationError(error)) {
102
+ throw new Error('Authentication required - your Salesforce session has expired. Use the salesforce_auth tool to re-authenticate.');
103
+ }
104
+
105
+ // Handle specific Salesforce errors
106
+ if (error.name === 'INVALID_QUERY' || error.errorCode === 'INVALID_QUERY') {
107
+ throw new Error(`Invalid SOQL query: ${error.message}`);
108
+ }
109
+
110
+ if (error.name === 'INVALID_FIELD' || error.errorCode === 'INVALID_FIELD') {
111
+ throw new Error(`Invalid field in query: ${error.message}`);
112
+ }
113
+
114
+ if (error.name === 'INVALID_TYPE' || error.errorCode === 'INVALID_TYPE') {
115
+ throw new Error(`Invalid object type: ${error.message}`);
116
+ }
117
+
118
+ // Handle authentication errors
119
+ if (error.message.includes('Session expired') || error.message.includes('INVALID_SESSION_ID')) {
120
+ await this.tokenManager.refreshTokens();
121
+ await this.createConnection();
122
+ // Retry once after token refresh
123
+ try {
124
+ const retryResult = await this.connection.query(soql, options);
125
+ return {
126
+ totalSize: retryResult.totalSize,
127
+ done: retryResult.done,
128
+ records: retryResult.records,
129
+ nextRecordsUrl: retryResult.nextRecordsUrl
130
+ };
131
+ } catch (retryError) {
132
+ throw new Error(`Query failed after token refresh: ${retryError.message}`);
133
+ }
134
+ }
135
+
136
+ // Generic error handling with better message
137
+ const errorMessage = error.message || 'Unknown Salesforce error';
138
+ throw new Error(`Salesforce query error: ${this.formatSalesforceError(error)}`);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Create a new record
144
+ */
145
+ async create(sobject, data) {
146
+ await this.ensureValidConnection();
147
+
148
+ try {
149
+ const result = await this.connection.sobject(sobject).create(data);
150
+
151
+ if (result.success) {
152
+ return {
153
+ id: result.id,
154
+ success: true,
155
+ sobject: sobject,
156
+ data: data
157
+ };
158
+ } else {
159
+ throw new Error(`Create failed: ${JSON.stringify(result.errors)}`);
160
+ }
161
+ } catch (error) {
162
+ if (this.isAuthenticationError(error)) {
163
+ throw new Error('Authentication required - your Salesforce session has expired. Use the salesforce_auth tool to re-authenticate.');
164
+ }
165
+ throw new Error(`Create ${sobject} failed: ${this.formatSalesforceError(error)}`);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Update an existing record
171
+ */
172
+ async update(sobject, id, data) {
173
+ await this.ensureValidConnection();
174
+
175
+ try {
176
+ const result = await this.connection.sobject(sobject).update({
177
+ Id: id,
178
+ ...data
179
+ });
180
+
181
+ if (result.success) {
182
+ return {
183
+ id: result.id,
184
+ success: true,
185
+ sobject: sobject,
186
+ data: data
187
+ };
188
+ } else {
189
+ throw new Error(`Update failed: ${JSON.stringify(result.errors)}`);
190
+ }
191
+ } catch (error) {
192
+ if (this.isAuthenticationError(error)) {
193
+ throw new Error('Authentication required - your Salesforce session has expired. Use the salesforce_auth tool to re-authenticate.');
194
+ }
195
+ throw new Error(`Update ${sobject} failed: ${this.formatSalesforceError(error)}`);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Delete a record
201
+ */
202
+ async delete(sobject, id) {
203
+ await this.ensureValidConnection();
204
+
205
+ try {
206
+ const result = await this.connection.sobject(sobject).destroy(id);
207
+
208
+ if (result.success) {
209
+ return {
210
+ id: result.id,
211
+ success: true,
212
+ sobject: sobject
213
+ };
214
+ } else {
215
+ throw new Error(`Delete failed: ${JSON.stringify(result.errors)}`);
216
+ }
217
+ } catch (error) {
218
+ if (this.isAuthenticationError(error)) {
219
+ throw new Error('Authentication required - your Salesforce session has expired. Use the salesforce_auth tool to re-authenticate.');
220
+ }
221
+ throw new Error(`Delete ${sobject} failed: ${this.formatSalesforceError(error)}`);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Describe an SObject (get schema information)
227
+ */
228
+ async describe(sobject) {
229
+ await this.ensureValidConnection();
230
+
231
+ try {
232
+ const result = await this.connection.sobject(sobject).describe();
233
+
234
+ return {
235
+ name: result.name,
236
+ label: result.label,
237
+ labelPlural: result.labelPlural,
238
+ keyPrefix: result.keyPrefix,
239
+ createable: result.createable,
240
+ updateable: result.updateable,
241
+ deletable: result.deletable,
242
+ queryable: result.queryable,
243
+ fields: result.fields.map(field => ({
244
+ name: field.name,
245
+ label: field.label,
246
+ type: field.type,
247
+ length: field.length,
248
+ required: !field.nillable && !field.defaultedOnCreate,
249
+ createable: field.createable,
250
+ updateable: field.updateable,
251
+ picklistValues: field.picklistValues || [],
252
+ referenceTo: field.referenceTo || [],
253
+ relationshipName: field.relationshipName
254
+ })),
255
+ recordTypeInfos: result.recordTypeInfos || []
256
+ };
257
+ } catch (error) {
258
+ if (this.isAuthenticationError(error)) {
259
+ throw new Error('Authentication required - your Salesforce session has expired. Use the salesforce_auth tool to re-authenticate.');
260
+ }
261
+ throw new Error(`Describe ${sobject} failed: ${this.formatSalesforceError(error)}`);
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Get all available SObjects
267
+ */
268
+ async describeGlobal() {
269
+ await this.ensureValidConnection();
270
+
271
+ try {
272
+ const result = await this.connection.describeGlobal();
273
+
274
+ const sobjects = result.sobjects
275
+ .filter(sobject => sobject.queryable) // Only queryable objects
276
+ .map(sobject => ({
277
+ name: sobject.name,
278
+ label: sobject.label,
279
+ labelPlural: sobject.labelPlural,
280
+ keyPrefix: sobject.keyPrefix,
281
+ custom: sobject.custom,
282
+ createable: sobject.createable,
283
+ updateable: sobject.updateable,
284
+ deletable: sobject.deletable,
285
+ queryable: sobject.queryable
286
+ }))
287
+ .sort((a, b) => a.label.localeCompare(b.label));
288
+
289
+ return sobjects;
290
+ } catch (error) {
291
+ if (this.isAuthenticationError(error)) {
292
+ throw new Error('Authentication required - your Salesforce session has expired. Use the salesforce_auth tool to re-authenticate.');
293
+ }
294
+ throw new Error(`Global describe failed: ${this.formatSalesforceError(error)}`);
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Get user information
300
+ */
301
+ async getUserInfo() {
302
+ await this.ensureValidConnection();
303
+
304
+ try {
305
+ const identity = await this.connection.identity();
306
+ return {
307
+ id: identity.user_id,
308
+ username: identity.username,
309
+ display_name: identity.display_name,
310
+ email: identity.email,
311
+ organization_id: identity.organization_id,
312
+ urls: identity.urls
313
+ };
314
+ } catch (error) {
315
+ throw new Error(`Failed to get user info: ${this.formatSalesforceError(error)}`);
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Format Salesforce error messages for better readability
321
+ */
322
+ formatSalesforceError(error) {
323
+ if (error.name === 'INVALID_FIELD') {
324
+ return `Invalid field: ${error.message}`;
325
+ }
326
+ if (error.name === 'REQUIRED_FIELD_MISSING') {
327
+ return `Required field missing: ${error.message}`;
328
+ }
329
+ if (error.name === 'FIELD_CUSTOM_VALIDATION_EXCEPTION') {
330
+ return `Validation rule failed: ${error.message}`;
331
+ }
332
+ if (error.name === 'INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY') {
333
+ return `Insufficient permissions: ${error.message}`;
334
+ }
335
+ if (error.name === 'INVALID_SESSION_ID') {
336
+ return 'Session expired - please re-authenticate';
337
+ }
338
+
339
+ return error.message || 'Unknown Salesforce error';
340
+ }
341
+
342
+ /**
343
+ * Check if an error is authentication-related
344
+ */
345
+ isAuthenticationError(error) {
346
+ const authErrorIndicators = [
347
+ 'INVALID_SESSION_ID',
348
+ 'Session expired',
349
+ 'invalid_grant',
350
+ 'Authentication failure',
351
+ 'Unauthorized',
352
+ 'Invalid token',
353
+ 'Token expired',
354
+ 'Not authenticated',
355
+ 'Authentication required',
356
+ 'No access token available',
357
+ 'refresh token is invalid',
358
+ 'Session has expired'
359
+ ];
360
+
361
+ const errorMessage = error.message || '';
362
+ const errorString = error.toString() || '';
363
+
364
+ return authErrorIndicators.some(indicator =>
365
+ errorMessage.includes(indicator) || errorString.includes(indicator)
366
+ );
367
+ }
368
+
369
+ /**
370
+ * Test connection health
371
+ */
372
+ async testConnection() {
373
+ try {
374
+ await this.ensureValidConnection();
375
+ const identity = await this.connection.identity();
376
+ return {
377
+ connected: true,
378
+ user: identity.username,
379
+ organization: identity.organization_id
380
+ };
381
+ } catch (error) {
382
+ return {
383
+ connected: false,
384
+ error: error.message
385
+ };
386
+ }
387
+ }
388
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * sf-client.js — Salesforce connection
3
+ * Reads tokens.json, handles refresh automatically via jsforce v1
4
+ */
5
+
6
+ import jsforce from 'jsforce';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const TOKEN_FILE = path.join(__dirname, '..', 'tokens.json');
13
+
14
+ let _conn = null;
15
+
16
+ export function getConnection() {
17
+ if (_conn) return _conn;
18
+
19
+ if (!fs.existsSync(TOKEN_FILE)) {
20
+ throw new Error('No tokens.json found. Run: node auth.js');
21
+ }
22
+
23
+ const tokens = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf8'));
24
+
25
+ const oauth2 = new jsforce.OAuth2({
26
+ loginUrl: tokens.loginUrl,
27
+ clientId: tokens.clientId,
28
+ clientSecret: tokens.clientSecret,
29
+ redirectUri: 'http://localhost:8482/callback',
30
+ });
31
+
32
+ _conn = new jsforce.Connection({
33
+ oauth2,
34
+ instanceUrl: tokens.instanceUrl,
35
+ accessToken: tokens.accessToken,
36
+ refreshToken: tokens.refreshToken,
37
+ version: '59.0',
38
+ });
39
+
40
+ // Persist refreshed tokens automatically
41
+ _conn.on('refresh', (newAccessToken) => {
42
+ const updated = { ...tokens, accessToken: newAccessToken };
43
+ fs.writeFileSync(TOKEN_FILE, JSON.stringify(updated, null, 2));
44
+ });
45
+
46
+ return _conn;
47
+ }
48
+
49
+ /** Run a SOQL query, return array of records */
50
+ export async function query(soql) {
51
+ const conn = getConnection();
52
+ const result = await conn.query(soql);
53
+ return result.records;
54
+ }
55
+
56
+ /** Run SOSL search, return flat array of records across all types */
57
+ export async function search(sosl) {
58
+ const conn = getConnection();
59
+ const result = await conn.search(sosl);
60
+ return result.searchRecords ?? [];
61
+ }
62
+
63
+ /** Describe a Salesforce object — returns field metadata */
64
+ export async function describe(objectName) {
65
+ const conn = getConnection();
66
+ return await conn.describe(objectName);
67
+ }
68
+
69
+ /** Create a record */
70
+ export async function createRecord(objectName, fields) {
71
+ const conn = getConnection();
72
+ return await conn.sobject(objectName).create(fields);
73
+ }
74
+
75
+ /** Update a record */
76
+ export async function updateRecord(objectName, id, fields) {
77
+ const conn = getConnection();
78
+ return await conn.sobject(objectName).update({ Id: id, ...fields });
79
+ }