@squadbase/vite-server 0.1.4-dev.1 → 0.1.5-dev.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.
- package/dist/cli/index.js +488 -102
- package/dist/connectors/amplitude.js +1 -1
- package/dist/connectors/asana.js +1 -1
- package/dist/connectors/attio.js +1 -1
- package/dist/connectors/backlog-api-key.js +1 -1
- package/dist/connectors/customerio.js +1 -1
- package/dist/connectors/gamma.js +1 -1
- package/dist/connectors/gmail.js +1 -1
- package/dist/connectors/google-docs.js +3 -1
- package/dist/connectors/hubspot.js +1 -1
- package/dist/connectors/intercom.js +1 -1
- package/dist/connectors/jira-api-key.js +1 -1
- package/dist/connectors/kintone-api-token.js +1 -1
- package/dist/connectors/mailchimp.js +1 -1
- package/dist/connectors/mixpanel.js +1 -1
- package/dist/connectors/notion-oauth.js +1 -1
- package/dist/connectors/notion.js +1 -1
- package/dist/connectors/salesforce.d.ts +5 -0
- package/dist/connectors/salesforce.js +862 -0
- package/dist/connectors/sentry.js +1 -1
- package/dist/connectors/stripe-api-key.js +1 -1
- package/dist/connectors/zendesk.js +1 -1
- package/dist/index.js +488 -102
- package/dist/main.js +488 -102
- package/dist/vite-plugin.js +488 -102
- package/package.json +5 -1
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
// ../connectors/src/parameter-definition.ts
|
|
2
|
+
var ParameterDefinition = class {
|
|
3
|
+
slug;
|
|
4
|
+
name;
|
|
5
|
+
description;
|
|
6
|
+
envVarBaseKey;
|
|
7
|
+
type;
|
|
8
|
+
secret;
|
|
9
|
+
required;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.slug = config.slug;
|
|
12
|
+
this.name = config.name;
|
|
13
|
+
this.description = config.description;
|
|
14
|
+
this.envVarBaseKey = config.envVarBaseKey;
|
|
15
|
+
this.type = config.type;
|
|
16
|
+
this.secret = config.secret;
|
|
17
|
+
this.required = config.required;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the parameter value from a ConnectorConnectionObject.
|
|
21
|
+
*/
|
|
22
|
+
getValue(connection2) {
|
|
23
|
+
const param = connection2.parameters.find(
|
|
24
|
+
(p) => p.parameterSlug === this.slug
|
|
25
|
+
);
|
|
26
|
+
if (!param || param.value == null) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return param.value;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Try to get the parameter value. Returns undefined if not found (for optional params).
|
|
35
|
+
*/
|
|
36
|
+
tryGetValue(connection2) {
|
|
37
|
+
const param = connection2.parameters.find(
|
|
38
|
+
(p) => p.parameterSlug === this.slug
|
|
39
|
+
);
|
|
40
|
+
if (!param || param.value == null) return void 0;
|
|
41
|
+
return param.value;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ../connectors/src/connectors/salesforce/parameters.ts
|
|
46
|
+
var parameters = {
|
|
47
|
+
username: new ParameterDefinition({
|
|
48
|
+
slug: "username",
|
|
49
|
+
name: "Username",
|
|
50
|
+
description: "Your Salesforce account username (the email you use to sign in).",
|
|
51
|
+
envVarBaseKey: "SALESFORCE_USERNAME",
|
|
52
|
+
type: "text",
|
|
53
|
+
secret: false,
|
|
54
|
+
required: true
|
|
55
|
+
}),
|
|
56
|
+
password: new ParameterDefinition({
|
|
57
|
+
slug: "password",
|
|
58
|
+
name: "Password",
|
|
59
|
+
description: "Your Salesforce account password concatenated with your security token (password + securityToken). The security token is emailed to you when you reset it from Settings \u2192 My Personal Information \u2192 Reset My Security Token.",
|
|
60
|
+
envVarBaseKey: "SALESFORCE_PASSWORD",
|
|
61
|
+
type: "text",
|
|
62
|
+
secret: true,
|
|
63
|
+
required: true
|
|
64
|
+
}),
|
|
65
|
+
clientId: new ParameterDefinition({
|
|
66
|
+
slug: "client-id",
|
|
67
|
+
name: "Consumer Key",
|
|
68
|
+
description: "The Consumer Key (client_id) of your Salesforce Connected App. Enable OAuth Settings and 'Allow OAuth Username-Password Flows' in your org's identity settings.",
|
|
69
|
+
envVarBaseKey: "SALESFORCE_CLIENT_ID",
|
|
70
|
+
type: "text",
|
|
71
|
+
secret: false,
|
|
72
|
+
required: true
|
|
73
|
+
}),
|
|
74
|
+
clientSecret: new ParameterDefinition({
|
|
75
|
+
slug: "client-secret",
|
|
76
|
+
name: "Consumer Secret",
|
|
77
|
+
description: "The Consumer Secret (client_secret) of your Salesforce Connected App.",
|
|
78
|
+
envVarBaseKey: "SALESFORCE_CLIENT_SECRET",
|
|
79
|
+
type: "text",
|
|
80
|
+
secret: true,
|
|
81
|
+
required: true
|
|
82
|
+
}),
|
|
83
|
+
isSandbox: new ParameterDefinition({
|
|
84
|
+
slug: "is-sandbox",
|
|
85
|
+
name: "Use Sandbox",
|
|
86
|
+
description: 'Set to "true" to authenticate against a Salesforce sandbox (test.salesforce.com) instead of production (login.salesforce.com). Defaults to "false".',
|
|
87
|
+
envVarBaseKey: "SALESFORCE_IS_SANDBOX",
|
|
88
|
+
type: "text",
|
|
89
|
+
secret: false,
|
|
90
|
+
required: false
|
|
91
|
+
})
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// ../connectors/src/connectors/salesforce/sdk/index.ts
|
|
95
|
+
var DEFAULT_API_VERSION = "60.0";
|
|
96
|
+
async function fetchAccessToken(loginHost, clientId, clientSecret, username, password) {
|
|
97
|
+
const body = new URLSearchParams({
|
|
98
|
+
grant_type: "password",
|
|
99
|
+
client_id: clientId,
|
|
100
|
+
client_secret: clientSecret,
|
|
101
|
+
username,
|
|
102
|
+
password
|
|
103
|
+
});
|
|
104
|
+
const res = await fetch(`${loginHost}/services/oauth2/token`, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
107
|
+
body: body.toString()
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
const errText = await res.text().catch(() => "(unreadable body)");
|
|
111
|
+
throw new Error(
|
|
112
|
+
`salesforce: failed to obtain access token: ${res.status} ${res.statusText} \u2014 ${errText}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const json = await res.json();
|
|
116
|
+
if (!json.access_token || !json.instance_url) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"salesforce: access_token or instance_url not found in token response"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return { accessToken: json.access_token, instanceUrl: json.instance_url };
|
|
122
|
+
}
|
|
123
|
+
function createClient(params) {
|
|
124
|
+
const username = params[parameters.username.slug];
|
|
125
|
+
const password = params[parameters.password.slug];
|
|
126
|
+
const clientId = params[parameters.clientId.slug];
|
|
127
|
+
const clientSecret = params[parameters.clientSecret.slug];
|
|
128
|
+
const isSandbox = (params[parameters.isSandbox.slug] ?? "").toLowerCase() === "true";
|
|
129
|
+
for (const [slug, value] of [
|
|
130
|
+
[parameters.username.slug, username],
|
|
131
|
+
[parameters.password.slug, password],
|
|
132
|
+
[parameters.clientId.slug, clientId],
|
|
133
|
+
[parameters.clientSecret.slug, clientSecret]
|
|
134
|
+
]) {
|
|
135
|
+
if (!value) {
|
|
136
|
+
throw new Error(`salesforce: missing required parameter: ${slug}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const loginHost = isSandbox ? "https://test.salesforce.com" : "https://login.salesforce.com";
|
|
140
|
+
async function getToken() {
|
|
141
|
+
return fetchAccessToken(
|
|
142
|
+
loginHost,
|
|
143
|
+
clientId,
|
|
144
|
+
clientSecret,
|
|
145
|
+
username,
|
|
146
|
+
password
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
async function authFetch(path2, init) {
|
|
150
|
+
const { accessToken, instanceUrl } = await getToken();
|
|
151
|
+
const url = path2.startsWith("http") ? path2 : `${instanceUrl}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
152
|
+
const headers = new Headers(init?.headers);
|
|
153
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
154
|
+
if (!headers.has("Content-Type") && init?.body) {
|
|
155
|
+
headers.set("Content-Type", "application/json");
|
|
156
|
+
}
|
|
157
|
+
return fetch(url, { ...init, headers });
|
|
158
|
+
}
|
|
159
|
+
async function assertOk(res, label) {
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
const body = await res.text().catch(() => "(unreadable body)");
|
|
162
|
+
throw new Error(
|
|
163
|
+
`salesforce ${label}: ${res.status} ${res.statusText} \u2014 ${body}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const apiBase = `/services/data/v${DEFAULT_API_VERSION}`;
|
|
168
|
+
return {
|
|
169
|
+
request(path2, init) {
|
|
170
|
+
return authFetch(path2, init);
|
|
171
|
+
},
|
|
172
|
+
async query(soql) {
|
|
173
|
+
const res = await authFetch(
|
|
174
|
+
`${apiBase}/query?q=${encodeURIComponent(soql)}`,
|
|
175
|
+
{ method: "GET" }
|
|
176
|
+
);
|
|
177
|
+
await assertOk(res, "query");
|
|
178
|
+
return await res.json();
|
|
179
|
+
},
|
|
180
|
+
async queryMore(nextRecordsUrl) {
|
|
181
|
+
const res = await authFetch(nextRecordsUrl, { method: "GET" });
|
|
182
|
+
await assertOk(res, "queryMore");
|
|
183
|
+
return await res.json();
|
|
184
|
+
},
|
|
185
|
+
async describeSObject(objectType) {
|
|
186
|
+
const res = await authFetch(
|
|
187
|
+
`${apiBase}/sobjects/${encodeURIComponent(objectType)}/describe`,
|
|
188
|
+
{ method: "GET" }
|
|
189
|
+
);
|
|
190
|
+
await assertOk(res, "describeSObject");
|
|
191
|
+
return await res.json();
|
|
192
|
+
},
|
|
193
|
+
async getRecord(objectType, id, options) {
|
|
194
|
+
const qs = options?.fields?.length ? `?fields=${encodeURIComponent(options.fields.join(","))}` : "";
|
|
195
|
+
const res = await authFetch(
|
|
196
|
+
`${apiBase}/sobjects/${encodeURIComponent(objectType)}/${encodeURIComponent(id)}${qs}`,
|
|
197
|
+
{ method: "GET" }
|
|
198
|
+
);
|
|
199
|
+
await assertOk(res, "getRecord");
|
|
200
|
+
return await res.json();
|
|
201
|
+
},
|
|
202
|
+
async createRecord(objectType, fields) {
|
|
203
|
+
const res = await authFetch(
|
|
204
|
+
`${apiBase}/sobjects/${encodeURIComponent(objectType)}`,
|
|
205
|
+
{
|
|
206
|
+
method: "POST",
|
|
207
|
+
headers: { "Content-Type": "application/json" },
|
|
208
|
+
body: JSON.stringify(fields)
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
await assertOk(res, "createRecord");
|
|
212
|
+
return await res.json();
|
|
213
|
+
},
|
|
214
|
+
async updateRecord(objectType, id, fields) {
|
|
215
|
+
const res = await authFetch(
|
|
216
|
+
`${apiBase}/sobjects/${encodeURIComponent(objectType)}/${encodeURIComponent(id)}`,
|
|
217
|
+
{
|
|
218
|
+
method: "PATCH",
|
|
219
|
+
headers: { "Content-Type": "application/json" },
|
|
220
|
+
body: JSON.stringify(fields)
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
await assertOk(res, "updateRecord");
|
|
224
|
+
},
|
|
225
|
+
async deleteRecord(objectType, id) {
|
|
226
|
+
const res = await authFetch(
|
|
227
|
+
`${apiBase}/sobjects/${encodeURIComponent(objectType)}/${encodeURIComponent(id)}`,
|
|
228
|
+
{ method: "DELETE" }
|
|
229
|
+
);
|
|
230
|
+
await assertOk(res, "deleteRecord");
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ../connectors/src/connector-onboarding.ts
|
|
236
|
+
var ConnectorOnboarding = class {
|
|
237
|
+
/** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
|
|
238
|
+
connectionSetupInstructions;
|
|
239
|
+
/** Phase 2: Data overview instructions */
|
|
240
|
+
dataOverviewInstructions;
|
|
241
|
+
constructor(config) {
|
|
242
|
+
this.connectionSetupInstructions = config.connectionSetupInstructions;
|
|
243
|
+
this.dataOverviewInstructions = config.dataOverviewInstructions;
|
|
244
|
+
}
|
|
245
|
+
getConnectionSetupPrompt(language) {
|
|
246
|
+
return this.connectionSetupInstructions?.[language] ?? null;
|
|
247
|
+
}
|
|
248
|
+
getDataOverviewInstructions(language) {
|
|
249
|
+
return this.dataOverviewInstructions[language];
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// ../connectors/src/connector-tool.ts
|
|
254
|
+
var ConnectorTool = class {
|
|
255
|
+
name;
|
|
256
|
+
description;
|
|
257
|
+
inputSchema;
|
|
258
|
+
outputSchema;
|
|
259
|
+
_execute;
|
|
260
|
+
constructor(config) {
|
|
261
|
+
this.name = config.name;
|
|
262
|
+
this.description = config.description;
|
|
263
|
+
this.inputSchema = config.inputSchema;
|
|
264
|
+
this.outputSchema = config.outputSchema;
|
|
265
|
+
this._execute = config.execute;
|
|
266
|
+
}
|
|
267
|
+
createTool(connections, config) {
|
|
268
|
+
return {
|
|
269
|
+
description: this.description,
|
|
270
|
+
inputSchema: this.inputSchema,
|
|
271
|
+
outputSchema: this.outputSchema,
|
|
272
|
+
execute: (input) => this._execute(input, connections, config)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// ../connectors/src/connector-plugin.ts
|
|
278
|
+
var ConnectorPlugin = class _ConnectorPlugin {
|
|
279
|
+
slug;
|
|
280
|
+
authType;
|
|
281
|
+
name;
|
|
282
|
+
description;
|
|
283
|
+
iconUrl;
|
|
284
|
+
parameters;
|
|
285
|
+
releaseFlag;
|
|
286
|
+
proxyPolicy;
|
|
287
|
+
experimentalAttributes;
|
|
288
|
+
onboarding;
|
|
289
|
+
systemPrompt;
|
|
290
|
+
tools;
|
|
291
|
+
query;
|
|
292
|
+
checkConnection;
|
|
293
|
+
constructor(config) {
|
|
294
|
+
this.slug = config.slug;
|
|
295
|
+
this.authType = config.authType;
|
|
296
|
+
this.name = config.name;
|
|
297
|
+
this.description = config.description;
|
|
298
|
+
this.iconUrl = config.iconUrl;
|
|
299
|
+
this.parameters = config.parameters;
|
|
300
|
+
this.releaseFlag = config.releaseFlag;
|
|
301
|
+
this.proxyPolicy = config.proxyPolicy;
|
|
302
|
+
this.experimentalAttributes = config.experimentalAttributes;
|
|
303
|
+
this.onboarding = config.onboarding;
|
|
304
|
+
this.systemPrompt = config.systemPrompt;
|
|
305
|
+
this.tools = config.tools;
|
|
306
|
+
this.query = config.query;
|
|
307
|
+
this.checkConnection = config.checkConnection;
|
|
308
|
+
}
|
|
309
|
+
get connectorKey() {
|
|
310
|
+
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Create tools for connections that belong to this connector.
|
|
314
|
+
* Filters connections by connectorKey internally.
|
|
315
|
+
* Returns tools keyed as `${connectorKey}_${toolName}`.
|
|
316
|
+
*/
|
|
317
|
+
createTools(connections, config, opts) {
|
|
318
|
+
const myConnections = connections.filter(
|
|
319
|
+
(c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
|
|
320
|
+
);
|
|
321
|
+
const result = {};
|
|
322
|
+
for (const t of Object.values(this.tools)) {
|
|
323
|
+
const tool = t.createTool(myConnections, config);
|
|
324
|
+
const originalToModelOutput = tool.toModelOutput;
|
|
325
|
+
result[`${this.connectorKey}_${t.name}`] = {
|
|
326
|
+
...tool,
|
|
327
|
+
toModelOutput: async (options) => {
|
|
328
|
+
if (!originalToModelOutput) {
|
|
329
|
+
return opts.truncateOutput(options.output);
|
|
330
|
+
}
|
|
331
|
+
const modelOutput = await originalToModelOutput(options);
|
|
332
|
+
if (modelOutput.type === "text" || modelOutput.type === "json") {
|
|
333
|
+
return opts.truncateOutput(modelOutput.value);
|
|
334
|
+
}
|
|
335
|
+
return modelOutput;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
static deriveKey(slug, authType) {
|
|
342
|
+
if (authType) return `${slug}-${authType}`;
|
|
343
|
+
const LEGACY_NULL_AUTH_TYPE_MAP = {
|
|
344
|
+
// user-password
|
|
345
|
+
"postgresql": "user-password",
|
|
346
|
+
"mysql": "user-password",
|
|
347
|
+
"clickhouse": "user-password",
|
|
348
|
+
"kintone": "user-password",
|
|
349
|
+
"squadbase-db": "user-password",
|
|
350
|
+
// service-account
|
|
351
|
+
"snowflake": "service-account",
|
|
352
|
+
"bigquery": "service-account",
|
|
353
|
+
"google-analytics": "service-account",
|
|
354
|
+
"google-calendar": "service-account",
|
|
355
|
+
"aws-athena": "service-account",
|
|
356
|
+
"redshift": "service-account",
|
|
357
|
+
// api-key
|
|
358
|
+
"databricks": "api-key",
|
|
359
|
+
"dbt": "api-key",
|
|
360
|
+
"airtable": "api-key",
|
|
361
|
+
"openai": "api-key",
|
|
362
|
+
"gemini": "api-key",
|
|
363
|
+
"anthropic": "api-key",
|
|
364
|
+
"wix-store": "api-key"
|
|
365
|
+
};
|
|
366
|
+
const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
|
|
367
|
+
if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
|
|
368
|
+
return slug;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// ../connectors/src/auth-types.ts
|
|
373
|
+
var AUTH_TYPES = {
|
|
374
|
+
OAUTH: "oauth",
|
|
375
|
+
API_KEY: "api-key",
|
|
376
|
+
JWT: "jwt",
|
|
377
|
+
SERVICE_ACCOUNT: "service-account",
|
|
378
|
+
PAT: "pat",
|
|
379
|
+
USER_PASSWORD: "user-password"
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// ../connectors/src/connectors/salesforce/setup.ts
|
|
383
|
+
var salesforceOnboarding = new ConnectorOnboarding({
|
|
384
|
+
connectionSetupInstructions: {
|
|
385
|
+
en: `#### Create a Connected App in Salesforce
|
|
386
|
+
1. In Salesforce Setup, go to App Manager \u2192 New Connected App
|
|
387
|
+
2. Under API (Enable OAuth Settings), check "Enable OAuth Settings"
|
|
388
|
+
3. Add OAuth scopes: "Manage user data via APIs (api)" and "Perform requests at any time (refresh_token, offline_access)"
|
|
389
|
+
4. Save and note the Consumer Key (client_id) and Consumer Secret (client_secret)
|
|
390
|
+
|
|
391
|
+
#### Allow Username-Password Flow
|
|
392
|
+
1. Go to Setup \u2192 Identity \u2192 OAuth and OpenID Connect Settings
|
|
393
|
+
2. Enable "Allow OAuth Username-Password Flows"
|
|
394
|
+
|
|
395
|
+
#### Reset your Security Token
|
|
396
|
+
1. Go to Settings \u2192 My Personal Information \u2192 Reset My Security Token
|
|
397
|
+
2. Salesforce emails you a new security token \u2014 append it to your password when entering the Password parameter (password + securityToken)
|
|
398
|
+
|
|
399
|
+
#### Sandbox vs Production
|
|
400
|
+
- Leave Use Sandbox as "false" (or empty) to connect to production (login.salesforce.com)
|
|
401
|
+
- Set Use Sandbox to "true" to connect to a sandbox (test.salesforce.com)`,
|
|
402
|
+
ja: `#### Salesforce \u3067 Connected App \u3092\u4F5C\u6210
|
|
403
|
+
1. Setup \u2192 App Manager \u2192 New Connected App
|
|
404
|
+
2. API (Enable OAuth Settings) \u30BB\u30AF\u30B7\u30E7\u30F3\u3067 "Enable OAuth Settings" \u3092\u6709\u52B9\u5316
|
|
405
|
+
3. OAuth \u30B9\u30B3\u30FC\u30D7\u306B "Manage user data via APIs (api)" \u3068 "Perform requests at any time (refresh_token, offline_access)" \u3092\u8FFD\u52A0
|
|
406
|
+
4. \u4FDD\u5B58\u5F8C\u3001Consumer Key (client_id) \u3068 Consumer Secret (client_secret) \u3092\u63A7\u3048\u308B
|
|
407
|
+
|
|
408
|
+
#### Username-Password Flow \u3092\u8A31\u53EF
|
|
409
|
+
1. Setup \u2192 Identity \u2192 OAuth and OpenID Connect Settings
|
|
410
|
+
2. "Allow OAuth Username-Password Flows" \u3092\u6709\u52B9\u5316
|
|
411
|
+
|
|
412
|
+
#### Security Token \u306E\u767A\u884C
|
|
413
|
+
1. \u500B\u4EBA\u8A2D\u5B9A \u2192 My Personal Information \u2192 Reset My Security Token
|
|
414
|
+
2. Salesforce \u304B\u3089\u9001\u3089\u308C\u308B\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u30C8\u30FC\u30AF\u30F3\u3092\u30D1\u30B9\u30EF\u30FC\u30C9\u306B\u9023\u7D50\u3057\u3066\u5165\u529B\uFF08password + securityToken\uFF09
|
|
415
|
+
|
|
416
|
+
#### Sandbox / Production
|
|
417
|
+
- \u672C\u756A (login.salesforce.com) \u306E\u5834\u5408: Use Sandbox \u3092 "false" \u307E\u305F\u306F\u672A\u5165\u529B
|
|
418
|
+
- Sandbox (test.salesforce.com) \u306E\u5834\u5408: Use Sandbox \u3092 "true" \u306B\u8A2D\u5B9A`
|
|
419
|
+
},
|
|
420
|
+
dataOverviewInstructions: {
|
|
421
|
+
en: `1. Call salesforce_request with GET /services/data/v60.0/sobjects/ to list available sObjects (standard + custom)
|
|
422
|
+
2. Call salesforce_request with GET /services/data/v60.0/sobjects/Account/describe to inspect Account fields; repeat for Contact, Opportunity, Lead as needed
|
|
423
|
+
3. Run a sample SOQL query: GET /services/data/v60.0/query?q=SELECT+Id,Name,Industry+FROM+Account+LIMIT+5 to verify access and explore data`,
|
|
424
|
+
ja: `1. salesforce_request \u3067 GET /services/data/v60.0/sobjects/ \u3092\u547C\u3073\u51FA\u3057\u3001\u5229\u7528\u53EF\u80FD\u306A sObject\uFF08\u6A19\u6E96 + \u30AB\u30B9\u30BF\u30E0\uFF09\u3092\u4E00\u89A7\u53D6\u5F97
|
|
425
|
+
2. salesforce_request \u3067 GET /services/data/v60.0/sobjects/Account/describe \u3092\u547C\u3073\u51FA\u3057 Account \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u78BA\u8A8D\u3002Contact / Opportunity / Lead \u306A\u3069\u5FC5\u8981\u306A sObject \u306B\u5BFE\u3057\u3066\u540C\u69D8\u306B\u5B9F\u884C
|
|
426
|
+
3. \u30B5\u30F3\u30D7\u30EB SOQL \u3092\u5B9F\u884C: GET /services/data/v60.0/query?q=SELECT+Id,Name,Industry+FROM+Account+LIMIT+5 \u3067\u30A2\u30AF\u30BB\u30B9\u53EF\u5426\u3068\u30C7\u30FC\u30BF\u69CB\u9020\u3092\u78BA\u8A8D`
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// ../connectors/src/connectors/salesforce/tools/request.ts
|
|
431
|
+
import { z } from "zod";
|
|
432
|
+
var REQUEST_TIMEOUT_MS = 6e4;
|
|
433
|
+
var inputSchema = z.object({
|
|
434
|
+
toolUseIntent: z.string().optional().describe(
|
|
435
|
+
"Brief description of what you intend to accomplish with this tool call"
|
|
436
|
+
),
|
|
437
|
+
connectionId: z.string().describe("ID of the Salesforce connection to use"),
|
|
438
|
+
method: z.enum(["GET", "POST", "PATCH", "DELETE"]).describe(
|
|
439
|
+
"HTTP method. GET for reading resources and SOQL queries, POST for creating, PATCH for updating, DELETE for removing."
|
|
440
|
+
),
|
|
441
|
+
path: z.string().describe(
|
|
442
|
+
"API path appended to the instance URL (e.g., '/services/data/v60.0/sobjects/Account', '/services/data/v60.0/query?q=SELECT+Id,Name+FROM+Account+LIMIT+5'). Always start the path with '/services/data/'."
|
|
443
|
+
),
|
|
444
|
+
body: z.record(z.string(), z.unknown()).optional().describe("Request body (JSON) for POST/PATCH requests")
|
|
445
|
+
});
|
|
446
|
+
var outputSchema = z.discriminatedUnion("success", [
|
|
447
|
+
z.object({
|
|
448
|
+
success: z.literal(true),
|
|
449
|
+
status: z.number(),
|
|
450
|
+
data: z.record(z.string(), z.unknown())
|
|
451
|
+
}),
|
|
452
|
+
z.object({
|
|
453
|
+
success: z.literal(false),
|
|
454
|
+
error: z.string()
|
|
455
|
+
})
|
|
456
|
+
]);
|
|
457
|
+
var requestTool = new ConnectorTool({
|
|
458
|
+
name: "request",
|
|
459
|
+
description: `Send authenticated requests to the Salesforce REST API.
|
|
460
|
+
Authentication is handled automatically using the OAuth 2.0 username-password flow (Connected App Consumer Key + Secret with the Salesforce account credentials). An access token and instance URL are obtained on each request, so the tool user only provides the API path.
|
|
461
|
+
Use this tool for all Salesforce interactions: describing sObjects, running SOQL queries (GET /services/data/vXX.X/query?q=...), reading/creating/updating standard (Account, Contact, Opportunity, Lead, Case) and custom objects.
|
|
462
|
+
Prefer SOQL via the /query endpoint for filtered, joined, or aggregated reads rather than paginating /sobjects/{Type} endpoints.`,
|
|
463
|
+
inputSchema,
|
|
464
|
+
outputSchema,
|
|
465
|
+
async execute({ connectionId, method, path: path2, body }, connections) {
|
|
466
|
+
const connection2 = connections.find((c) => c.id === connectionId);
|
|
467
|
+
if (!connection2) {
|
|
468
|
+
return {
|
|
469
|
+
success: false,
|
|
470
|
+
error: `Connection ${connectionId} not found`
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
console.log(
|
|
474
|
+
`[connector-request] salesforce/${connection2.name}: ${method} ${path2}`
|
|
475
|
+
);
|
|
476
|
+
try {
|
|
477
|
+
const username = parameters.username.getValue(connection2);
|
|
478
|
+
const password = parameters.password.getValue(connection2);
|
|
479
|
+
const clientId = parameters.clientId.getValue(connection2);
|
|
480
|
+
const clientSecret = parameters.clientSecret.getValue(connection2);
|
|
481
|
+
const isSandbox = parameters.isSandbox.tryGetValue(connection2)?.toLowerCase() === "true";
|
|
482
|
+
const loginHost = isSandbox ? "https://test.salesforce.com" : "https://login.salesforce.com";
|
|
483
|
+
const tokenBody = new URLSearchParams({
|
|
484
|
+
grant_type: "password",
|
|
485
|
+
client_id: clientId,
|
|
486
|
+
client_secret: clientSecret,
|
|
487
|
+
username,
|
|
488
|
+
password
|
|
489
|
+
});
|
|
490
|
+
const tokenRes = await fetch(`${loginHost}/services/oauth2/token`, {
|
|
491
|
+
method: "POST",
|
|
492
|
+
headers: {
|
|
493
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
494
|
+
},
|
|
495
|
+
body: tokenBody.toString()
|
|
496
|
+
});
|
|
497
|
+
if (!tokenRes.ok) {
|
|
498
|
+
const errText = await tokenRes.text().catch(() => "(unreadable body)");
|
|
499
|
+
return {
|
|
500
|
+
success: false,
|
|
501
|
+
error: `Failed to obtain access token: ${tokenRes.status} ${tokenRes.statusText} \u2014 ${errText}`
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
const tokenJson = await tokenRes.json();
|
|
505
|
+
if (!tokenJson.access_token || !tokenJson.instance_url) {
|
|
506
|
+
return {
|
|
507
|
+
success: false,
|
|
508
|
+
error: "access_token or instance_url not found in token response"
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
const url = `${tokenJson.instance_url}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
512
|
+
const controller = new AbortController();
|
|
513
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
514
|
+
try {
|
|
515
|
+
const response = await fetch(url, {
|
|
516
|
+
method,
|
|
517
|
+
headers: {
|
|
518
|
+
Authorization: `Bearer ${tokenJson.access_token}`,
|
|
519
|
+
"Content-Type": "application/json"
|
|
520
|
+
},
|
|
521
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
522
|
+
signal: controller.signal
|
|
523
|
+
});
|
|
524
|
+
const text = await response.text();
|
|
525
|
+
let data;
|
|
526
|
+
try {
|
|
527
|
+
data = text ? JSON.parse(text) : {};
|
|
528
|
+
} catch {
|
|
529
|
+
data = { raw: text };
|
|
530
|
+
}
|
|
531
|
+
if (!response.ok) {
|
|
532
|
+
let errorMessage = `HTTP ${response.status} ${response.statusText}`;
|
|
533
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
534
|
+
const first = data[0];
|
|
535
|
+
if (first.message) {
|
|
536
|
+
errorMessage = first.errorCode ? `${first.errorCode}: ${first.message}` : first.message;
|
|
537
|
+
}
|
|
538
|
+
} else if (typeof data.message === "string") {
|
|
539
|
+
errorMessage = data.message;
|
|
540
|
+
} else if (typeof data.error === "string") {
|
|
541
|
+
errorMessage = data.error;
|
|
542
|
+
}
|
|
543
|
+
return { success: false, error: errorMessage };
|
|
544
|
+
}
|
|
545
|
+
return { success: true, status: response.status, data };
|
|
546
|
+
} finally {
|
|
547
|
+
clearTimeout(timeout);
|
|
548
|
+
}
|
|
549
|
+
} catch (err) {
|
|
550
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
551
|
+
return { success: false, error: msg };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// ../connectors/src/connectors/salesforce/index.ts
|
|
557
|
+
var tools = { request: requestTool };
|
|
558
|
+
var salesforceConnector = new ConnectorPlugin({
|
|
559
|
+
slug: "salesforce",
|
|
560
|
+
authType: AUTH_TYPES.USER_PASSWORD,
|
|
561
|
+
name: "Salesforce",
|
|
562
|
+
description: "Connect to Salesforce CRM for accounts, contacts, opportunities, leads, cases, and custom objects via SOQL and the REST API.",
|
|
563
|
+
iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/6vZlbrUKhxXIiuvWJlb8YB/bbc5e08b88de46c8ed338a74c7d0abb3/salesforce-icon.png",
|
|
564
|
+
parameters,
|
|
565
|
+
releaseFlag: { dev1: true, dev2: false, prod: false },
|
|
566
|
+
onboarding: salesforceOnboarding,
|
|
567
|
+
systemPrompt: {
|
|
568
|
+
en: `### Tools
|
|
569
|
+
|
|
570
|
+
- \`salesforce_request\`: The only way to call the Salesforce REST API. Use it to run SOQL queries, describe sObjects, and read/create/update/delete standard (Account, Contact, Opportunity, Lead, Case) and custom objects. Authentication (OAuth 2.0 username-password flow against the Connected App) is configured automatically \u2014 an access token and the org's instance URL are resolved on each request. Prefer SOQL via \`GET /services/data/v60.0/query?q=...\` over paginating \`/sobjects/{Type}\` endpoints for filtered or joined reads.
|
|
571
|
+
|
|
572
|
+
### Business Logic
|
|
573
|
+
|
|
574
|
+
The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
|
|
575
|
+
|
|
576
|
+
SDK methods (client created via \`connection(connectionId)\`):
|
|
577
|
+
- \`client.request(path, init?)\` \u2014 low-level authenticated fetch against the Salesforce instance URL
|
|
578
|
+
- \`client.query(soql)\` \u2014 run a SOQL query and return \`{ totalSize, done, records, nextRecordsUrl? }\`
|
|
579
|
+
- \`client.queryMore(nextRecordsUrl)\` \u2014 fetch the next page of SOQL results
|
|
580
|
+
- \`client.describeSObject(objectType)\` \u2014 describe an sObject's metadata
|
|
581
|
+
- \`client.getRecord(objectType, id, options?)\` \u2014 fetch a single record (optionally restrict fields)
|
|
582
|
+
- \`client.createRecord(objectType, fields)\` \u2014 create a new record
|
|
583
|
+
- \`client.updateRecord(objectType, id, fields)\` \u2014 patch an existing record
|
|
584
|
+
- \`client.deleteRecord(objectType, id)\` \u2014 delete a record
|
|
585
|
+
|
|
586
|
+
\`\`\`ts
|
|
587
|
+
import type { Context } from "hono";
|
|
588
|
+
import { connection } from "@squadbase/vite-server/connectors/salesforce";
|
|
589
|
+
|
|
590
|
+
const salesforce = connection("<connectionId>");
|
|
591
|
+
|
|
592
|
+
export default async function handler(c: Context) {
|
|
593
|
+
const { industry, limit = 50 } = await c.req.json<{
|
|
594
|
+
industry?: string;
|
|
595
|
+
limit?: number;
|
|
596
|
+
}>();
|
|
597
|
+
|
|
598
|
+
const where = industry
|
|
599
|
+
? \`WHERE Industry = '\${industry.replace(/'/g, "\\\\'")}'\`
|
|
600
|
+
: "";
|
|
601
|
+
const soql = \`SELECT Id, Name, Industry, AnnualRevenue FROM Account \${where} ORDER BY AnnualRevenue DESC NULLS LAST LIMIT \${limit}\`;
|
|
602
|
+
|
|
603
|
+
const { records } = await salesforce.query<{
|
|
604
|
+
Id: string;
|
|
605
|
+
Name: string;
|
|
606
|
+
Industry: string | null;
|
|
607
|
+
AnnualRevenue: number | null;
|
|
608
|
+
}>(soql);
|
|
609
|
+
|
|
610
|
+
return c.json({ accounts: records });
|
|
611
|
+
}
|
|
612
|
+
\`\`\`
|
|
613
|
+
|
|
614
|
+
### Salesforce REST API Reference
|
|
615
|
+
|
|
616
|
+
- Login host: \`https://login.salesforce.com\` (production) or \`https://test.salesforce.com\` (sandbox)
|
|
617
|
+
- Token endpoint: \`POST /services/oauth2/token\` (grant_type=password + client_id/secret + username/password)
|
|
618
|
+
- Base path after login: \`{instance_url}/services/data/v60.0\`
|
|
619
|
+
- Authentication: Bearer token (handled automatically per request)
|
|
620
|
+
- Pagination (SOQL): follow \`nextRecordsUrl\` from the response (absolute path starting with \`/services/data/v60.0/query/...\`)
|
|
621
|
+
|
|
622
|
+
#### Common Endpoints
|
|
623
|
+
- GET \`/services/data/\` \u2014 List available API versions
|
|
624
|
+
- GET \`/services/data/v60.0/sobjects/\` \u2014 List all sObjects (standard + custom)
|
|
625
|
+
- GET \`/services/data/v60.0/sobjects/{Type}/describe\` \u2014 Describe an sObject (fields, relationships, picklists)
|
|
626
|
+
- GET \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 Get a record (supports \`?fields=...\`)
|
|
627
|
+
- POST \`/services/data/v60.0/sobjects/{Type}\` \u2014 Create a record
|
|
628
|
+
- PATCH \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 Update a record
|
|
629
|
+
- DELETE \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 Delete a record
|
|
630
|
+
- GET \`/services/data/v60.0/query?q={soql}\` \u2014 Run SOQL
|
|
631
|
+
- GET \`/services/data/v60.0/search?q={sosl}\` \u2014 Run SOSL
|
|
632
|
+
|
|
633
|
+
#### SOQL Reference
|
|
634
|
+
- SELECT column list is required (no \`SELECT *\`)
|
|
635
|
+
- Filter: \`WHERE\`, \`AND\` / \`OR\`, parent/child relationship fields (e.g., \`Account.Name\`)
|
|
636
|
+
- Sort: \`ORDER BY field [ASC|DESC] [NULLS FIRST|NULLS LAST]\`
|
|
637
|
+
- Paginate: \`LIMIT n\`, \`OFFSET m\`; for large result sets, follow \`nextRecordsUrl\` instead of OFFSET
|
|
638
|
+
- Aggregate: \`GROUP BY\`, \`HAVING\`, \`COUNT()\`, \`SUM()\`, \`AVG()\`, \`MIN()\`, \`MAX()\`
|
|
639
|
+
- Parent-to-child subquery: \`SELECT Id, Name, (SELECT Id, Email FROM Contacts) FROM Account\``,
|
|
640
|
+
ja: `### \u30C4\u30FC\u30EB
|
|
641
|
+
|
|
642
|
+
- \`salesforce_request\`: Salesforce REST API \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002SOQL \u30AF\u30A8\u30EA\u306E\u5B9F\u884C\u3001sObject \u306E describe\u3001\u6A19\u6E96\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\uFF08Account, Contact, Opportunity, Lead, Case\uFF09\u3084\u30AB\u30B9\u30BF\u30E0\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306E\u8AAD\u307F\u66F8\u304D\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u8A8D\u8A3C\uFF08Connected App + OAuth 2.0 Username-Password Flow\uFF09\u306F\u81EA\u52D5\u3067\u884C\u308F\u308C\u3001\u30EA\u30AF\u30A8\u30B9\u30C8\u3054\u3068\u306B\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u3068\u7D44\u7E54\u306E instance URL \u304C\u89E3\u6C7A\u3055\u308C\u307E\u3059\u3002\u30D5\u30A3\u30EB\u30BF\u3084\u7D50\u5408\u306E\u3042\u308B\u8AAD\u307F\u53D6\u308A\u3067\u306F \`/sobjects/{Type}\` \u3092\u30DA\u30FC\u30B8\u30F3\u30B0\u3059\u308B\u306E\u3067\u306F\u306A\u304F\u3001\`GET /services/data/v60.0/query?q=...\` \u306E SOQL \u3092\u512A\u5148\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
643
|
+
|
|
644
|
+
### Business Logic
|
|
645
|
+
|
|
646
|
+
\u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BF SDK \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
|
|
647
|
+
|
|
648
|
+
SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
|
|
649
|
+
- \`client.request(path, init?)\` \u2014 Salesforce \u306E instance URL \u306B\u5BFE\u3059\u308B\u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304D fetch
|
|
650
|
+
- \`client.query(soql)\` \u2014 SOQL \u30AF\u30A8\u30EA\u3092\u5B9F\u884C\u3057 \`{ totalSize, done, records, nextRecordsUrl? }\` \u3092\u8FD4\u5374
|
|
651
|
+
- \`client.queryMore(nextRecordsUrl)\` \u2014 SOQL \u7D50\u679C\u306E\u6B21\u30DA\u30FC\u30B8\u3092\u53D6\u5F97
|
|
652
|
+
- \`client.describeSObject(objectType)\` \u2014 sObject \u306E\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u53D6\u5F97
|
|
653
|
+
- \`client.getRecord(objectType, id, options?)\` \u2014 1 \u4EF6\u306E\u30EC\u30B3\u30FC\u30C9\u3092\u53D6\u5F97\uFF08\u4EFB\u610F\u3067\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u6307\u5B9A\uFF09
|
|
654
|
+
- \`client.createRecord(objectType, fields)\` \u2014 \u30EC\u30B3\u30FC\u30C9\u3092\u65B0\u898F\u4F5C\u6210
|
|
655
|
+
- \`client.updateRecord(objectType, id, fields)\` \u2014 \u65E2\u5B58\u30EC\u30B3\u30FC\u30C9\u3092\u66F4\u65B0\uFF08PATCH\uFF09
|
|
656
|
+
- \`client.deleteRecord(objectType, id)\` \u2014 \u30EC\u30B3\u30FC\u30C9\u3092\u524A\u9664
|
|
657
|
+
|
|
658
|
+
\`\`\`ts
|
|
659
|
+
import type { Context } from "hono";
|
|
660
|
+
import { connection } from "@squadbase/vite-server/connectors/salesforce";
|
|
661
|
+
|
|
662
|
+
const salesforce = connection("<connectionId>");
|
|
663
|
+
|
|
664
|
+
export default async function handler(c: Context) {
|
|
665
|
+
const { industry, limit = 50 } = await c.req.json<{
|
|
666
|
+
industry?: string;
|
|
667
|
+
limit?: number;
|
|
668
|
+
}>();
|
|
669
|
+
|
|
670
|
+
const where = industry
|
|
671
|
+
? \`WHERE Industry = '\${industry.replace(/'/g, "\\\\'")}'\`
|
|
672
|
+
: "";
|
|
673
|
+
const soql = \`SELECT Id, Name, Industry, AnnualRevenue FROM Account \${where} ORDER BY AnnualRevenue DESC NULLS LAST LIMIT \${limit}\`;
|
|
674
|
+
|
|
675
|
+
const { records } = await salesforce.query<{
|
|
676
|
+
Id: string;
|
|
677
|
+
Name: string;
|
|
678
|
+
Industry: string | null;
|
|
679
|
+
AnnualRevenue: number | null;
|
|
680
|
+
}>(soql);
|
|
681
|
+
|
|
682
|
+
return c.json({ accounts: records });
|
|
683
|
+
}
|
|
684
|
+
\`\`\`
|
|
685
|
+
|
|
686
|
+
### Salesforce REST API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
|
|
687
|
+
|
|
688
|
+
- \u30ED\u30B0\u30A4\u30F3\u30DB\u30B9\u30C8: \`https://login.salesforce.com\`\uFF08\u672C\u756A\uFF09\u307E\u305F\u306F \`https://test.salesforce.com\`\uFF08Sandbox\uFF09
|
|
689
|
+
- \u30C8\u30FC\u30AF\u30F3\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8: \`POST /services/oauth2/token\`\uFF08grant_type=password + client_id/secret + username/password\uFF09
|
|
690
|
+
- \u30ED\u30B0\u30A4\u30F3\u5F8C\u306E\u30D9\u30FC\u30B9\u30D1\u30B9: \`{instance_url}/services/data/v60.0\`
|
|
691
|
+
- \u8A8D\u8A3C: Bearer \u30C8\u30FC\u30AF\u30F3\uFF08\u30EA\u30AF\u30A8\u30B9\u30C8\u3054\u3068\u306B\u81EA\u52D5\u8A2D\u5B9A\uFF09
|
|
692
|
+
- \u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\uFF08SOQL\uFF09: \u30EC\u30B9\u30DD\u30F3\u30B9\u306E \`nextRecordsUrl\`\uFF08\`/services/data/v60.0/query/...\` \u304B\u3089\u59CB\u307E\u308B\u7D76\u5BFE\u30D1\u30B9\uFF09\u3092\u8FBF\u308B
|
|
693
|
+
|
|
694
|
+
#### \u4E3B\u8981\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
|
|
695
|
+
- GET \`/services/data/\` \u2014 \u5229\u7528\u53EF\u80FD\u306A API \u30D0\u30FC\u30B8\u30E7\u30F3\u4E00\u89A7
|
|
696
|
+
- GET \`/services/data/v60.0/sobjects/\` \u2014 sObject \u4E00\u89A7\uFF08\u6A19\u6E96 + \u30AB\u30B9\u30BF\u30E0\uFF09
|
|
697
|
+
- GET \`/services/data/v60.0/sobjects/{Type}/describe\` \u2014 sObject \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u30FB\u30EA\u30EC\u30FC\u30B7\u30E7\u30F3\u30FB\u30D4\u30C3\u30AF\u30EA\u30B9\u30C8\u3092\u53D6\u5F97
|
|
698
|
+
- GET \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 \u30EC\u30B3\u30FC\u30C9\u53D6\u5F97\uFF08\`?fields=...\` \u3067\u7D5E\u308A\u8FBC\u307F\u53EF\uFF09
|
|
699
|
+
- POST \`/services/data/v60.0/sobjects/{Type}\` \u2014 \u30EC\u30B3\u30FC\u30C9\u4F5C\u6210
|
|
700
|
+
- PATCH \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 \u30EC\u30B3\u30FC\u30C9\u66F4\u65B0
|
|
701
|
+
- DELETE \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 \u30EC\u30B3\u30FC\u30C9\u524A\u9664
|
|
702
|
+
- GET \`/services/data/v60.0/query?q={soql}\` \u2014 SOQL \u3092\u5B9F\u884C
|
|
703
|
+
- GET \`/services/data/v60.0/search?q={sosl}\` \u2014 SOSL \u3092\u5B9F\u884C
|
|
704
|
+
|
|
705
|
+
#### SOQL \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
|
|
706
|
+
- SELECT \u5217\u306E\u660E\u793A\u304C\u5FC5\u9808\uFF08\`SELECT *\` \u306F\u4E0D\u53EF\uFF09
|
|
707
|
+
- \u30D5\u30A3\u30EB\u30BF: \`WHERE\`\u3001\`AND\` / \`OR\`\u3001\u89AA\u5B50\u30EA\u30EC\u30FC\u30B7\u30E7\u30F3\u53C2\u7167\uFF08\u4F8B: \`Account.Name\`\uFF09
|
|
708
|
+
- \u4E26\u3073\u66FF\u3048: \`ORDER BY field [ASC|DESC] [NULLS FIRST|NULLS LAST]\`
|
|
709
|
+
- \u30DA\u30FC\u30B8\u30F3\u30B0: \`LIMIT n\`, \`OFFSET m\`\u3002\u5927\u91CF\u30C7\u30FC\u30BF\u3067\u306F OFFSET \u3067\u306F\u306A\u304F \`nextRecordsUrl\` \u3092\u5229\u7528\u3059\u308B
|
|
710
|
+
- \u96C6\u8A08: \`GROUP BY\`, \`HAVING\`, \`COUNT()\`, \`SUM()\`, \`AVG()\`, \`MIN()\`, \`MAX()\`
|
|
711
|
+
- \u89AA\u2192\u5B50\u30B5\u30D6\u30AF\u30A8\u30EA: \`SELECT Id, Name, (SELECT Id, Email FROM Contacts) FROM Account\``
|
|
712
|
+
},
|
|
713
|
+
tools
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// src/connectors/create-connector-sdk.ts
|
|
717
|
+
import { readFileSync } from "fs";
|
|
718
|
+
import path from "path";
|
|
719
|
+
|
|
720
|
+
// src/connector-client/env.ts
|
|
721
|
+
function resolveEnvVar(entry, key, connectionId) {
|
|
722
|
+
const envVarName = entry.envVars[key];
|
|
723
|
+
if (!envVarName) {
|
|
724
|
+
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
725
|
+
}
|
|
726
|
+
const value = process.env[envVarName];
|
|
727
|
+
if (!value) {
|
|
728
|
+
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
729
|
+
}
|
|
730
|
+
return value;
|
|
731
|
+
}
|
|
732
|
+
function resolveEnvVarOptional(entry, key) {
|
|
733
|
+
const envVarName = entry.envVars[key];
|
|
734
|
+
if (!envVarName) return void 0;
|
|
735
|
+
return process.env[envVarName] || void 0;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// src/connector-client/proxy-fetch.ts
|
|
739
|
+
import { getContext } from "hono/context-storage";
|
|
740
|
+
import { getCookie } from "hono/cookie";
|
|
741
|
+
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
742
|
+
function normalizeHeaders(input) {
|
|
743
|
+
const out = {};
|
|
744
|
+
if (!input) return out;
|
|
745
|
+
new Headers(input).forEach((value, key) => {
|
|
746
|
+
out[key] = value;
|
|
747
|
+
});
|
|
748
|
+
return out;
|
|
749
|
+
}
|
|
750
|
+
function createSandboxProxyFetch(connectionId) {
|
|
751
|
+
return async (input, init) => {
|
|
752
|
+
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
753
|
+
const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
|
|
754
|
+
if (!token || !sandboxId) {
|
|
755
|
+
throw new Error(
|
|
756
|
+
"Connection proxy is not configured. Please check your deployment settings."
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
760
|
+
const originalMethod = init?.method ?? "GET";
|
|
761
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
762
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
763
|
+
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
764
|
+
return fetch(proxyUrl, {
|
|
765
|
+
method: "POST",
|
|
766
|
+
headers: {
|
|
767
|
+
"Content-Type": "application/json",
|
|
768
|
+
Authorization: `Bearer ${token}`
|
|
769
|
+
},
|
|
770
|
+
body: JSON.stringify({
|
|
771
|
+
url: originalUrl,
|
|
772
|
+
method: originalMethod,
|
|
773
|
+
headers: normalizeHeaders(init?.headers),
|
|
774
|
+
body: originalBody
|
|
775
|
+
})
|
|
776
|
+
});
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function createDeployedAppProxyFetch(connectionId) {
|
|
780
|
+
const projectId = process.env["SQUADBASE_PROJECT_ID"];
|
|
781
|
+
if (!projectId) {
|
|
782
|
+
throw new Error(
|
|
783
|
+
"Connection proxy is not configured. Please check your deployment settings."
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
787
|
+
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
788
|
+
return async (input, init) => {
|
|
789
|
+
const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
790
|
+
const originalMethod = init?.method ?? "GET";
|
|
791
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
792
|
+
const c = getContext();
|
|
793
|
+
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
794
|
+
if (!appSession) {
|
|
795
|
+
throw new Error(
|
|
796
|
+
"No authentication method available for connection proxy."
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return fetch(proxyUrl, {
|
|
800
|
+
method: "POST",
|
|
801
|
+
headers: {
|
|
802
|
+
"Content-Type": "application/json",
|
|
803
|
+
Authorization: `Bearer ${appSession}`
|
|
804
|
+
},
|
|
805
|
+
body: JSON.stringify({
|
|
806
|
+
url: originalUrl,
|
|
807
|
+
method: originalMethod,
|
|
808
|
+
headers: normalizeHeaders(init?.headers),
|
|
809
|
+
body: originalBody
|
|
810
|
+
})
|
|
811
|
+
});
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function createProxyFetch(connectionId) {
|
|
815
|
+
if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
|
|
816
|
+
return createSandboxProxyFetch(connectionId);
|
|
817
|
+
}
|
|
818
|
+
return createDeployedAppProxyFetch(connectionId);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/connectors/create-connector-sdk.ts
|
|
822
|
+
function loadConnectionsSync() {
|
|
823
|
+
const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
824
|
+
try {
|
|
825
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
826
|
+
return JSON.parse(raw);
|
|
827
|
+
} catch {
|
|
828
|
+
return {};
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function createConnectorSdk(plugin, createClient2) {
|
|
832
|
+
return (connectionId) => {
|
|
833
|
+
const connections = loadConnectionsSync();
|
|
834
|
+
const entry = connections[connectionId];
|
|
835
|
+
if (!entry) {
|
|
836
|
+
throw new Error(
|
|
837
|
+
`Connection "${connectionId}" not found in .squadbase/connections.json`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
if (entry.connector.slug !== plugin.slug) {
|
|
841
|
+
throw new Error(
|
|
842
|
+
`Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
const params = {};
|
|
846
|
+
for (const param of Object.values(plugin.parameters)) {
|
|
847
|
+
if (param.required) {
|
|
848
|
+
params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
|
|
849
|
+
} else {
|
|
850
|
+
const val = resolveEnvVarOptional(entry, param.slug);
|
|
851
|
+
if (val !== void 0) params[param.slug] = val;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return createClient2(params, createProxyFetch(connectionId));
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/connectors/entries/salesforce.ts
|
|
859
|
+
var connection = createConnectorSdk(salesforceConnector, createClient);
|
|
860
|
+
export {
|
|
861
|
+
connection
|
|
862
|
+
};
|