Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .claude/skills/sdk-docs-writing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,14 @@ Every public method needs: description, `@param` tags, `@returns`, and at least
- **Sentence case** for free-text headings in JSDoc.
- **State environment constraints** when a method is browser-only: "Requires a browser environment and can't be used in the backend."
- **Document side effects** explicitly (e.g., "automatically sets the token for subsequent requests").
- **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.
- **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. There are two exceptions, where `{@link}` produces a broken or missing link, so write a markdown link instead:
- A method on another module's page. Link to the page's route, as in ``[`InvokeLLM()`](/developers/references/sdk/docs/type-aliases/integrations#invokellm)``.
- A method mentioned in the comment of a type or property that renders inside a field, such as `Purchase` or `CreateCheckoutParams`. The pipeline drops the link and its code formatting there, so link to the method's anchor on the page, as in ``[`refund()`](#refund)``.
- **Never use a colon as a sentence break.** Write "Use this when you sync data from another system. Name the field..." not "...another system: name the field...". Reserve colons for `label: description` bullet-list items, not prose sentences.
- **Name the two SDKs.** Call `@base44/sdk` the "App SDK" and `@base44/platform` the "Platform SDK" wherever a page mentions both. Docs for only one package can say "the SDK".
- **Use a serial comma.** Put a comma before the final "and" or "or" in every series of three or more, as in "snapshots, events, and errors".
- **Never start a sentence with a lowercase inline-code term.** If the natural phrasing would open with one (e.g., "`week` is only supported on..."), restructure so a capitalized word leads instead (e.g., "Every date field supports `day`, `month` and `year`. The `created_date` and `updated_date` fields also support `week`.").
- **Call the app user the client acts for "the current user".** Don't write "signed-in user" or "logged-in user". When the difference matters, say which authentication mode applies, as in "Rejects with `forbidden` when the client uses anonymous authentication."
- **Name the place and the AI correctly.** Write "app editor" (lowercase) for the place where you build, and "the AI" or "the AI chat" for the assistant doing the work ("The AI inserts this ID", "ask the AI chat to fix it"). Never use "builder" for either. It stays only for the Builder plan, the Base44 Builder GitHub App, people who build apps ("builders", "app builders"), and exact UI labels or code names.

## References
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Examples are the most impactful part of the docs. The TypeDoc plugin converts ea

## Format

Always use TypeScript fenced code blocks. The first comment line becomes the tab title:
Always use TypeScript fenced code blocks, or `tsx` for a React example tab, when the method needs one. The first comment line becomes the tab title:

```typescript
@example
Expand All @@ -17,6 +17,7 @@ const records = await base44.entities.MyEntity.list();
## Guidelines

- **Start simple.** First example should be the most basic call with minimal parameters.
- **Don't tie examples to a framework.** Show the plain SDK call first. Add a React example, as its own tab after the plain one, only when the method needs something React-specific to use correctly, such as cleanup on unmount or a ref to a DOM element.
- **Show real patterns.** Use realistic entity names (`Task`, `User`), not abstract ones.
- **Build complexity.** Progress from basic → with options → with error handling.
- **Use `base44.` prefix.** All examples should show the call path from the SDK client: `base44.entities.X`, `base44.auth.X`, `base44.integrations.Core.X`.
Expand Down
2 changes: 1 addition & 1 deletion packages/platform/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { createChat } from "./modules/chat.js";
* [`createPlatformServer()`](/developers/references/platform-sdk/docs/functions/createPlatformServer)
* from `@base44/platform/server`.
*
* @returns A new platform client.
* @returns A new platform client, with one property for each module.
*
* @example
* ```typescript
Expand Down
3 changes: 2 additions & 1 deletion packages/platform/src/react/chat.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ export interface ChatBackend {
* whenever the session expires. Sessions last one hour.
*
* @param appId - ID of the app to follow.
* @returns Promise resolving to the session.
* @returns Promise resolving to the three fields of a [Create socket session](/api-reference/create-socket-session)
* response that the browser needs. Keep the response's `session_id` on your server.
*
* @example
* ```typescript
Expand Down
2 changes: 1 addition & 1 deletion packages/platform/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { PlatformServer } from "./server.types.js";
* such as [`webhooks`](/developers/references/platform-sdk/docs/interfaces/webhooks). It takes no
* options and makes no network requests. Each module takes what it needs when you call it.
*
* @returns A new platform server client.
* @returns A new platform server client, with one property for each module.
*
* @example
* ```typescript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"interfaces/ActorNameRegistry"
],
"interfaces/PaymentsModule": [
"interfaces/ServiceRolePaymentsModule",
"type-aliases/PaymentsErrorCode",
"type-aliases/PurchaseStatus",
"type-aliases/PaymentsRecurringFrequency"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,9 +672,12 @@ function updatePanelWithHeadings(hostContent, headings) {
return ensurePanelSpacing(updatedContent);
}

function prepareAppendedSection(appendPath) {
// A renamed module's page is titled with its slug, so pass `heading` to title its section with
// the interface name instead, which page-sections.json `typesWithOwnMethods` can then rename.
function prepareAppendedSection(appendPath, heading = null) {
const rawContent = fs.readFileSync(appendPath, "utf-8");
const { title, content: withoutFrontMatter } = stripFrontMatter(rawContent);
const { title: frontMatterTitle, content: withoutFrontMatter } = stripFrontMatter(rawContent);
const title = heading ?? frontMatterTitle;
const withoutPanel = removeFirstPanelBlock(withoutFrontMatter);
const { content: normalizedContent, headings } =
normalizeHeadings(withoutPanel);
Expand Down Expand Up @@ -766,7 +769,10 @@ function applyAppendedArticles(appendedArticles, inlinedTypeNames = new Set()) {
}
}

const { section, headings } = prepareAppendedSection(appendPath);
const { section, headings } = prepareAppendedSection(
appendPath,
appendModuleRename ? appendName : null
);
combinedSections += `\n\n${section}`;
if (PANELS_ENABLED && collectedHeadings) {
collectedHeadings.push(...headings);
Expand All @@ -791,7 +797,10 @@ function applyAppendedArticles(appendedArticles, inlinedTypeNames = new Set()) {
hostContent = hostContent.trimEnd() + combinedSections + "\n";
hostContent = updatePanelWithHeadings(hostContent, collectedHeadings);

hostContent = linkMergedTypesInPage(hostContent, mergedTypeNames);
// An appended module links back to the host's methods by the host's type name, which is
// this page too.
const linkedTypeNames = hostModuleRename ? [hostName, ...mergedTypeNames] : mergedTypeNames;
hostContent = linkMergedTypesInPage(hostContent, linkedTypeNames);
fs.writeFileSync(hostPath, hostContent, "utf-8");
}
}
Expand All @@ -806,8 +815,10 @@ function applyAppendedArticles(appendedArticles, inlinedTypeNames = new Set()) {
*/
function linkMergedTypesInPage(content, typeNames) {
for (const typeName of typeNames) {
// A renamed module's links already use its slug, such as `service-role-payments`.
const rename = getModuleRename(typeName);
const linkRegex = new RegExp(
`\\]\\((?:\\.\\./(?:interfaces|type-aliases|classes|functions)/)?${typeName}(?:\\.mdx|\\.md)?(#[\\w-]*)?\\)`,
`\\]\\((?:\\.\\./(?:interfaces|type-aliases|classes|functions)/)?(?:${typeName}${rename ? `|${rename}` : ""})(?:\\.mdx|\\.md)?(#[\\w-]*)?\\)`,
"g"
);
content = content.replace(
Expand Down Expand Up @@ -1662,7 +1673,7 @@ function nestUnderGroupTypes(section, typeNames) {
* Rename and merge method sections for better TOC organization
* - EntityHandler → Entity Handler Methods
* - First ## Methods in integrations → Core Integrations Methods
* - custom-integrations → Custom Integrations Methods
* - CustomIntegrationsModule → Custom Integrations Methods
* - Create Type Definitions section with Core, custom, and Indexable
*/
function mergeSectionWithMethods(content, filePath) {
Expand All @@ -1678,14 +1689,14 @@ function mergeSectionWithMethods(content, filePath) {
let indexableContent = [];
let customIntegrationsModuleDescription = [];

// For connectors.mdx: remove the appended user-connectors intro section and
// For connectors.mdx: remove the appended UserConnectorsModule intro section and
// fold connectAppUser/disconnectAppUser into the existing ## Methods section.
if (isConnectors) {
let userConnectorsStart = -1;
let appendedMethodsStart = -1;

for (let i = 0; i < lines.length; i++) {
if (lines[i].trim() === "## user-connectors") {
if (lines[i].trim() === "## UserConnectorsModule") {
userConnectorsStart = i;
} else if (userConnectorsStart !== -1 && lines[i].trim() === "## Methods") {
appendedMethodsStart = i;
Expand All @@ -1696,7 +1707,7 @@ function mergeSectionWithMethods(content, filePath) {
if (userConnectorsStart !== -1 && appendedMethodsStart !== -1) {
// Extract the method lines (everything after the appended ## Methods heading)
const methodLines = lines.splice(appendedMethodsStart + 1);
// Remove the appended ## user-connectors section (intro + ## Methods heading)
// Remove the appended ## UserConnectorsModule section (intro + ## Methods heading)
lines.splice(userConnectorsStart, lines.length - userConnectorsStart);

// ## Type Definitions doesn't exist yet at this stage — groupTypeDefinitions
Expand Down Expand Up @@ -1944,8 +1955,8 @@ function mergeSectionWithMethods(content, filePath) {
i--;
continue;
}
// custom-integrations section
else if (line === "## custom-integrations") {
// CustomIntegrationsModule section
else if (line === "## CustomIntegrationsModule") {
// Replace with Custom Integrations Methods
lines[i] = "## Custom Integrations Methods";
modified = true;
Expand Down
11 changes: 9 additions & 2 deletions packages/sdk/scripts/mintlify-post-processing/page-sections.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"EntityHandler": "Entity Handler Methods",
"ActorRef": "Actor methods",
"Connection": "Connection methods",
"ActorSubscription": "Subscription methods"
"ActorSubscription": "Subscription methods",
"ServiceRolePaymentsModule": "Service role methods"
},
"typeDefinitionGroups": [
[
Expand Down Expand Up @@ -31,6 +32,12 @@
],
[
"AppPublicSettings"
]
],
{
"title": "Errors",
"types": [
"PaymentsErrorCode"
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -505,16 +505,18 @@ function rewriteReturnSections(content, options) {
result.push(`\`${typeNameForDisplay}\``);
}
const descriptionParts = [];
if (typeDescription) {
descriptionParts.push(typeDescription);
}
if (leadingText) {
descriptionParts.push(leadingText);
}
const returnsDescription = extractReturnsDescription(page);
if (returnsDescription && !isShown(returnsDescription, descriptionParts)) {
descriptionParts.push(returnsDescription);
}
// The method's own @returns text says what this call returns, so the return type's
// general description only fills in when there's none.
if (typeDescription && descriptionParts.length === 0) {
descriptionParts.push(typeDescription);
}
if (descriptionParts.length > 0) {
result.push("");
result.push(descriptionParts.join("\n\n"));
Expand Down Expand Up @@ -610,13 +612,8 @@ function rewriteReturnSections(content, options) {
result.push("");
result.push(`\`${typeNameForDisplay}\``);
}
const descriptionParts = [];
if (typeDescription) {
descriptionParts.push(typeDescription);
}
if (leadingText) {
descriptionParts.push(leadingText);
}
// As above, the type's description only fills in when the section has no text of its own.
const descriptionParts = [leadingText || typeDescription].filter(Boolean);
if (descriptionParts.length > 0) {
result.push("");
result.push(descriptionParts.join("\n\n"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
[
"AiGatewayConnection",
"ActorConnectOptions",
"AiGatewayConnection",
"AiGatewayConnectionOptions",
"AppPublicSettingsResponse",
"BrowserSession",
"BrowserSessionOptions",
"ConnectorApiRequest",
"ConnectorApiResponse",
"DeleteManyResult",
"DeleteResult",
"EntityAggregateResult",
Expand Down
55 changes: 45 additions & 10 deletions packages/sdk/src/modules/ai-gateway.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface AiGatewayModuleConfig {
/**
* AI Gateway module for calling Base44's managed AI models from your own code.
*
* `connection()` hands you a `baseURL`, `token`, and `headers` that
* Call `connection()` to get a `baseURL`, `token`, and `headers` that
* authenticate as your Base44 app. An OpenAI-compatible client is any library, such as the `openai`
* SDK or the Vercel AI SDK, that has the same request and response format
* as OpenAI's API and lets you point it at a custom `baseURL`
Expand All @@ -53,9 +53,9 @@ export interface AiGatewayModuleConfig {
* against the provider directly, no separate account, API key, or billing
* setup with the underlying model provider required.
*
* `connection()` takes an optional `provider`, and each provider serves a different API:
* The optional `provider` picks the API you connect to:
*
* - `openai` (default): OpenAI-compatible chat completions and image endpoints.
* - `openai` (default): OpenAI-compatible chat completions, image, and video endpoints.
* - `typesafe`: structured evaluations with the `jev` model.
*
* Call `connection()` from a backend function rather than the browser. This
Expand All @@ -73,21 +73,32 @@ export interface AiGatewayModuleConfig {
*
* The gateway also serves OpenAI's image endpoints, `/images/generations`
* and `/images/edits`, so you can generate images and edit them using
* reference images. Image requests use their own set of models: pass
* reference images. Image requests use their own set of models. Pass
* `'automatic'` to let Base44 choose one, or pin a model such as
* `'gemini_3_1_flash_image'` or `'gpt_image_2'`. For request options, the
* full model list, and limits, see [Generate images with the AI Gateway](/developers/references/sdk/getting-started/ai-gateway-images).
*
* ## Video
*
* The gateway also serves OpenAI's video endpoints, `/videos`, so you can
* generate video from a prompt, from a still image, or from existing media.
* Video generation is asynchronous. `POST /videos` returns a job, and you poll
* `GET /videos/{id}` until its status is `completed` or `failed`, which can
* take minutes. Video models are their own set, and there's no `automatic`
* option, so pin a model such as `'veo_3_1_fast'` or `'seedance_2'`. For
* request options, the full model list, and limits, see
* [Generate video with the AI Gateway](/developers/references/sdk/getting-started/ai-gateway-video).
*
* ## Structured evaluations
*
* The `typesafe` provider runs the `jev` evaluation model through
* `@ai-sdk/typesafe-ai` and `experimental_evaluate` from the `ai` package,
* version 7.0.105 or later. Pass `evaluationModel("jev")`; the gateway rejects
* other model IDs, such as `"jev-latest"`. Give it some app state and a set of
* questions, each a `choice`, `score`, or `boolean`. Choice and score answers
* come back with `probabilities` per option; boolean answers with a single
* `probability` of `true`. Use it to classify, score, or route records, then
* apply your own thresholds and actions in code.
* version 7.0.105 or later. Pass `evaluationModel("jev")`, and note that the
* gateway rejects other model IDs, such as `"jev-latest"`. Give it some app
* state and a set of questions, each a `choice`, `score`, or `boolean`. Choice
* and score answers come back with `probabilities` per option, and boolean
* answers with a single `probability` of `true`. Use it to classify, score, or
* route records, then apply your own thresholds and actions in code.
*
* ## Authentication Modes
*
Expand Down Expand Up @@ -208,6 +219,30 @@ export interface AiGatewayModule {
*
* @example
* ```typescript
* // Start a video and record the job to poll later
* import { createClientFromRequest } from "@base44/sdk";
*
* // Runs inside a backend function
* const base44 = createClientFromRequest(request);
* const { baseURL, token, headers } = base44.asServiceRole.aiGateway.connection();
*
* const response = await fetch(`${baseURL}/videos`, {
* method: "POST",
* headers: { ...headers, Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
* body: JSON.stringify({
* model: "veo_3_1_fast",
* prompt: "A lighthouse beam sweeping across a calm sea at dawn",
* seconds: 8,
* }),
* });
*
* // Video generation takes minutes, so store the id and poll it from a later request
* const { id } = await response.json();
* await base44.asServiceRole.entities.Clip.update(clipId, { video_id: id });
* ```
*
* @example
* ```typescript
* // Route a support ticket with a structured evaluation
* import { createClientFromRequest } from "@base44/sdk";
* import { experimental_evaluate as evaluate } from "ai";
Expand Down
Loading
Loading