Skip to content

Commit 0cd3a71

Browse files
committed
docs: add getConnection() JSDoc, update deprecated rendering pipeline
Update connectors.types.ts with getConnection() examples and description, simplify deprecation message, and add deprecated method post-processing to the doc generation pipeline (emoji in heading, Danger callout above signature). Made-with: Cursor
1 parent 13837d8 commit 0cd3a71

3 files changed

Lines changed: 171 additions & 24 deletions

File tree

.claude/skills/sdk-docs-writing/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ const records = await base44.entities.MyEntity.list();
174174
- **Avoid gerunds** in section headings within JSDoc. Prefer imperatives or noun phrases.
175175
- **State environment constraints** when a method is browser-only: "Requires a browser environment and can't be used in the backend."
176176
- **Document side effects** explicitly (e.g., "automatically sets the token for subsequent requests").
177+
- **Link method references.** When mentioning another SDK method or module by name in JSDoc prose, always use `{@link}` or `{@linkcode}` to create a cross-reference. Never leave a method name as plain text when a link target exists.
177178
178179
## Doc generation pipeline
179180

scripts/mintlify-post-processing/file-processing/file-processing.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,123 @@ function applySignatureCleanup(dir) {
10171017
}
10181018
}
10191019

1020+
/**
1021+
* Transforms deprecated method sections:
1022+
* 1. Removes ~~strikethrough~~ and prepends a warning emoji to the heading
1023+
* 2. Extracts the #### Deprecated block, removes it, and re-inserts it as a
1024+
* <Danger> callout between the heading and the signature blockquote
1025+
*/
1026+
function processDeprecatedMethods(content) {
1027+
const lines = content.split("\n");
1028+
let modified = false;
1029+
let inFence = false;
1030+
1031+
// First pass: find all deprecated sections and collect their info
1032+
const deprecatedSections = [];
1033+
for (let i = 0; i < lines.length; i++) {
1034+
const trimmed = lines[i].trim();
1035+
if (trimmed.startsWith("```")) {
1036+
inFence = !inFence;
1037+
continue;
1038+
}
1039+
if (inFence) continue;
1040+
1041+
if (trimmed === "#### Deprecated") {
1042+
const start = i;
1043+
let end = i + 1;
1044+
let message = "";
1045+
// The first non-empty line after "#### Deprecated" is the deprecation message.
1046+
// Any subsequent lines are description text that TypeDoc misplaced here.
1047+
while (end < lines.length) {
1048+
const t = lines[end].trim();
1049+
if (t.startsWith("#### ") || t.startsWith("### ") || t.startsWith("## ") || t === "***") break;
1050+
if (!message && t) {
1051+
message = t;
1052+
}
1053+
end++;
1054+
}
1055+
deprecatedSections.push({ start, end, message });
1056+
}
1057+
}
1058+
1059+
if (deprecatedSections.length === 0) {
1060+
return { content, modified: false };
1061+
}
1062+
1063+
// Remove deprecated sections bottom-up so indices stay valid
1064+
for (let s = deprecatedSections.length - 1; s >= 0; s--) {
1065+
const { start, end } = deprecatedSections[s];
1066+
lines.splice(start, end - start);
1067+
modified = true;
1068+
}
1069+
1070+
// Second pass: transform headings and insert Danger callouts
1071+
inFence = false;
1072+
for (let i = 0; i < lines.length; i++) {
1073+
const trimmed = lines[i].trim();
1074+
if (trimmed.startsWith("```")) {
1075+
inFence = !inFence;
1076+
continue;
1077+
}
1078+
if (inFence) continue;
1079+
1080+
// Match deprecated heading: ### ~~methodName()~~ (TypeDoc wraps deprecated names in ~~)
1081+
const headingMatch = trimmed.match(/^###\s+~~(.+?)~~\s*$/);
1082+
if (!headingMatch) continue;
1083+
1084+
// Remove strikethrough and prepend warning emoji
1085+
lines[i] = lines[i].replace(
1086+
/^(###\s+)~~(.+?)~~\s*$/,
1087+
"$1\u26A0\uFE0F $2"
1088+
);
1089+
modified = true;
1090+
1091+
// Find the matching deprecated message by method name
1092+
const methodName = headingMatch[1].replace(/\(\)$/, "");
1093+
const section = deprecatedSections.find((sec) =>
1094+
sec.message.toLowerCase().includes(methodName.toLowerCase()) ||
1095+
deprecatedSections.length === 1
1096+
) || deprecatedSections.shift();
1097+
1098+
if (!section || !section.message) continue;
1099+
1100+
// Insert Danger callout right after the heading (before the signature)
1101+
const dangerBlock = [
1102+
"",
1103+
"<Danger>",
1104+
`**Deprecated:** ${section.message}`,
1105+
"</Danger>",
1106+
"",
1107+
];
1108+
lines.splice(i + 1, 0, ...dangerBlock);
1109+
}
1110+
1111+
return { content: lines.join("\n"), modified };
1112+
}
1113+
1114+
function applyDeprecatedMethodProcessing(dir) {
1115+
if (!fs.existsSync(dir)) return;
1116+
const entries = fs.readdirSync(dir, { withFileTypes: true });
1117+
for (const entry of entries) {
1118+
const entryPath = path.join(dir, entry.name);
1119+
if (entry.isDirectory()) {
1120+
applyDeprecatedMethodProcessing(entryPath);
1121+
} else if (
1122+
entry.isFile() &&
1123+
(entry.name.endsWith(".mdx") || entry.name.endsWith(".md"))
1124+
) {
1125+
const content = fs.readFileSync(entryPath, "utf-8");
1126+
const { content: updated, modified } = processDeprecatedMethods(content);
1127+
if (modified) {
1128+
fs.writeFileSync(entryPath, updated, "utf-8");
1129+
console.log(
1130+
`Processed deprecated methods: ${path.relative(DOCS_DIR, entryPath)}`
1131+
);
1132+
}
1133+
}
1134+
}
1135+
}
1136+
10201137
function demoteNonCallableHeadings(content) {
10211138
const lines = content.split("\n");
10221139
let inFence = false;
@@ -1919,6 +2036,9 @@ function main() {
19192036
// Clean up signatures: fix truncated generics, simplify keyof constraints, break long lines
19202037
applySignatureCleanup(DOCS_DIR);
19212038

2039+
// Transform deprecated methods: add badge to heading, move deprecation notice to Warning callout
2040+
applyDeprecatedMethodProcessing(DOCS_DIR);
2041+
19222042
applyHeadingDemotion(DOCS_DIR);
19232043

19242044
// Group intro sections (Built-in User Entity, Generated Types, etc.) under Overview

src/modules/connectors.types.ts

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export interface ConnectorAccessTokenResponse {
2929
}
3030

3131
/**
32-
* Camel-cased connection details returned by {@linkcode ConnectorsModule.getConnection | getConnection()}.
32+
* Connection details.
3333
*/
3434
export interface ConnectorConnectionResponse {
3535
/** The OAuth access token for the external service. */
@@ -50,7 +50,7 @@ export interface ConnectorConnectionResponse {
5050
*
5151
* ## Available connectors
5252
*
53-
* All connectors work through [`getAccessToken()`](#getaccesstoken). Pass the integration type string and use the returned OAuth token to call the external service's API directly.
53+
* All connectors work through [`getConnection()`](#getconnection). Pass the integration type string and use the returned OAuth token to call the external service's API directly.
5454
*
5555
* | Service | Type identifier |
5656
* |---|---|
@@ -88,7 +88,7 @@ export interface ConnectorsModule {
8888
/**
8989
* Retrieves an OAuth access token for a specific [external integration type](#available-connectors).
9090
*
91-
* @deprecated Use {@link getConnection} and use the returned `accessToken` (and `connectionConfig` when needed) instead.
91+
* @deprecated Use {@link getConnection} instead.
9292
*
9393
* Returns the OAuth token string for an external service that an app builder
9494
* has connected to. This token represents the connected app builder's account
@@ -159,37 +159,63 @@ export interface ConnectorsModule {
159159
* Retrieves the OAuth access token and connection configuration for a specific external integration type.
160160
*
161161
* Returns both the OAuth token and any additional connection configuration
162-
* that the connector provides. This is useful when the external service requires
163-
* extra parameters beyond the access token (e.g., a shop domain, account ID, or API base URL).
162+
* that the connector provides. Some connectors require connection-specific
163+
* parameters to build API requests. For example, a service might need a
164+
* subdomain to construct the API URL (`{subdomain}.example.com`), which
165+
* is available in `connectionConfig`. Most connectors only need the
166+
* `accessToken`; `connectionConfig` will be `null` when there are no
167+
* extra parameters.
164168
*
165169
* @param integrationType - The type of integration, such as `'googlecalendar'`, `'slack'`, or `'github'`.
166170
* @returns Promise resolving to a {@link ConnectorConnectionResponse} with `accessToken` and `connectionConfig`.
167171
*
168172
* @example
169173
* ```typescript
170-
* // Basic usage
171-
* const connection = await base44.asServiceRole.connectors.getConnection('googlecalendar');
172-
* console.log(connection.accessToken);
173-
* console.log(connection.connectionConfig);
174+
* // Google Calendar connection
175+
* // Get Google Calendar OAuth token and fetch upcoming events
176+
* const { accessToken } = await base44.asServiceRole.connectors.getConnection('googlecalendar');
177+
*
178+
* const timeMin = new Date().toISOString();
179+
* const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events?maxResults=10&orderBy=startTime&singleEvents=true&timeMin=${timeMin}`;
180+
*
181+
* const calendarResponse = await fetch(url, {
182+
* headers: { Authorization: `Bearer ${accessToken}` }
183+
* });
184+
*
185+
* const events = await calendarResponse.json();
174186
* ```
175187
*
176188
* @example
177189
* ```typescript
178-
* // Shopify: connectionConfig has subdomain (e.g. "my-store" for my-store.myshopify.com)
179-
* const connection = await base44.asServiceRole.connectors.getConnection('shopify');
180-
* const { accessToken, connectionConfig } = connection;
181-
* const shop = connectionConfig?.subdomain
182-
* ? `https://${connectionConfig.subdomain}.myshopify.com`
183-
* : null;
184-
*
185-
* if (shop) {
186-
* const response = await fetch(
187-
* `${shop}/admin/api/2024-01/products.json?limit=10`,
188-
* { headers: { 'X-Shopify-Access-Token': accessToken } }
189-
* );
190-
* const { products } = await response.json();
191-
* }
190+
* // Slack connection
191+
* // Get Slack OAuth token and list channels
192+
* const { accessToken } = await base44.asServiceRole.connectors.getConnection('slack');
193+
*
194+
* const url = 'https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=100';
195+
*
196+
* const slackResponse = await fetch(url, {
197+
* headers: { Authorization: `Bearer ${accessToken}` }
198+
* });
199+
*
200+
* const data = await slackResponse.json();
201+
* ```
202+
*
203+
* @example
204+
* ```typescript
205+
* // Using connectionConfig
206+
* // Some connectors return a subdomain or other params needed to build the API URL
207+
* const { accessToken, connectionConfig } = await base44.asServiceRole.connectors.getConnection('myservice');
208+
*
209+
* const subdomain = connectionConfig?.subdomain;
210+
* const response = await fetch(
211+
* `https://${subdomain}.example.com/api/v1/resources`,
212+
* { headers: { Authorization: `Bearer ${accessToken}` } }
213+
* );
214+
*
215+
* const data = await response.json();
192216
* ```
193217
*/
194-
getConnection(integrationType: ConnectorIntegrationType): Promise<ConnectorConnectionResponse>;
218+
getConnection(
219+
integrationType: ConnectorIntegrationType,
220+
): Promise<ConnectorConnectionResponse>;
195221
}

0 commit comments

Comments
 (0)