From c38ea66c036830c90c4bc01a6dfc1dda86b127b5 Mon Sep 17 00:00:00 2001 From: Sam Markowitz Date: Mon, 16 Feb 2026 07:05:56 +0200 Subject: [PATCH 1/4] Update docs and type generation for better clarity - Add Generated Types sections to SDK interfaces - Improve JSDoc comments for type registries - Clean up signatures and remove redundant Type Parameters sections - Simplify field-selection generics in entity methods Co-authored-by: Cursor --- .../appended-articles.json | 14 +- .../file-processing/file-processing.js | 306 +++++++++++++++++- .../typedoc-mintlify-returns.js | 64 ++-- .../types-to-delete-after-processing.json | 5 + .../types-to-expose.json | 9 + src/index.ts | 5 + src/modules/agents.types.ts | 21 +- src/modules/entities.types.ts | 48 ++- src/modules/functions.types.ts | 16 +- 9 files changed, 437 insertions(+), 51 deletions(-) create mode 100644 scripts/mintlify-post-processing/types-to-delete-after-processing.json diff --git a/scripts/mintlify-post-processing/appended-articles.json b/scripts/mintlify-post-processing/appended-articles.json index 969e10d..4c2d346 100644 --- a/scripts/mintlify-post-processing/appended-articles.json +++ b/scripts/mintlify-post-processing/appended-articles.json @@ -1,5 +1,17 @@ { - "interfaces/EntitiesModule": "interfaces/EntityHandler", + "type-aliases/EntitiesModule": [ + "interfaces/EntityHandler", + "type-aliases/EntityRecord", + "interfaces/EntityTypeRegistry" + ], + "interfaces/FunctionsModule": [ + "type-aliases/FunctionName", + "interfaces/FunctionNameRegistry" + ], + "interfaces/AgentsModule": [ + "type-aliases/AgentName", + "interfaces/AgentNameRegistry" + ], "type-aliases/integrations": [ "interfaces/CoreIntegrations", "interfaces/CustomIntegrationsModule" diff --git a/scripts/mintlify-post-processing/file-processing/file-processing.js b/scripts/mintlify-post-processing/file-processing/file-processing.js index fd799b9..4599f7f 100755 --- a/scripts/mintlify-post-processing/file-processing/file-processing.js +++ b/scripts/mintlify-post-processing/file-processing/file-processing.js @@ -25,6 +25,7 @@ const TEMPLATE_PATH = path.join(__dirname, "docs-json-template.json"); const STYLING_CSS_PATH = path.join(__dirname, "styling.css"); const CATEGORY_MAP_PATH = path.join(__dirname, "../category-map.json"); const TYPES_TO_EXPOSE_PATH = path.join(__dirname, "..", "types-to-expose.json"); +const TYPES_TO_DELETE_PATH = path.join(__dirname, "..", "types-to-delete-after-processing.json"); const APPENDED_ARTICLES_PATH = path.join( __dirname, "../appended-articles.json" @@ -135,9 +136,11 @@ function processLinksInFile(filePath) { modified = true; } - // Remove undesirable lines like "> **IntegrationsModule** = `object` & `object`" - // This typically appears in type alias files using intersection types - const typeDefinitionRegex = /^> \*\*\w+\*\* = `object` & `object`\s*$/m; + // Remove undesirable type-alias definition lines like: + // > **IntegrationsModule** = `object` & `object` + // > **EntitiesModule** = `TypedEntitiesModule` & `DynamicEntitiesModule` + // These appear in type alias files using intersection types and are not useful in docs. + const typeDefinitionRegex = /^> \*\*\w+\*\* = `\w+` & `\w+`\s*$/m; if (typeDefinitionRegex.test(content)) { content = content.replace(typeDefinitionRegex, ""); modified = true; @@ -761,6 +764,255 @@ function applyAppendedArticles(appendedArticles) { } } +/** + * Clean up method signatures and type parameter sections: + * 1. Replace truncated generics (e.g., Pick<..., ...> → Pick) + * 2. Simplify resolved keyof constraints (string | number | symbol → keyof T) + * 3. Break long signature lines into multi-line blockquote format + * 4. Remove method-level Type Parameters sections (redundant with signature + param docs) + * 4b. Remove page-level ## Type Parameters sections (not useful in docs) + * 5. Clean up broken function-return-type sections (e.g., () => void returns) + * 7. Simplify field-selection generics: remove \ from signatures, Pick → T, K[] → (keyof T)[] + */ +function cleanupSignatures(content) { + let modified = false; + + // Fix 7: Simplify field-selection generic K out of signatures. + // K is a TypeScript implementation detail for field selection (Pick). + // In docs it's confusing — replace with clearer types. + + // 7a: Annotate \<`K`\> with its constraint → \<`K extends keyof T`\> + if (content.includes("\\<`K`\\>")) { + content = content.replace(/\\<`K`\\>/g, "\\<`K extends keyof T`\\>"); + modified = true; + } + + // 7b: Expand truncated `Pick`\<..., ...\> to `Pick`\<`T`, `K`\> + if (content.includes("`Pick`\\<..., ...\\>")) { + content = content.replace(/`Pick`\\<\.\.\., \.\.\.\\>/g, "`Pick`\\<`T`, `K`\\>"); + modified = true; + } + + // 7c: Replace type="K[]" with type="(keyof T)[]" in ParamField elements + if (content.includes('type="K[]"')) { + content = content.replace(/type="K\[\]"/g, 'type="(keyof T)[]"'); + modified = true; + } + + // Fix 5: Clean up broken function-return-type patterns. + // When a method returns a function (e.g., () => void), TypeDoc generates a stray + // function signature and an empty Accordion in the Returns section. Remove them. + // Pattern: "> (): `void`" followed by empty Accordion with "Returns" ResponseField. + content = content.replace( + /\n> \(\): `void`\n\n\n\n\n\n<\/ResponseField>\n<\/Accordion>\n/g, + () => { + modified = true; + return "\n"; + } + ); + + // Fix 6: Clean up truncated EntityRecord mapped type signature. + // TypeDoc renders `EntityTypeRegistry[K]` as `(...)[(...)]`. + content = content.replace( + /\(\.\.\.\)\[\(\.\.\.\)\]\s*&\s*ServerEntityFields/g, + () => { + modified = true; + return "EntityTypeRegistry[K] & ServerEntityFields"; + } + ); + + const lines = content.split("\n"); + + // Collect page-level type parameter names from ## Type Parameters section. + // Before heading demotion, these are ### headings (e.g., ### T). + const pageTypeParams = []; + for (let i = 0; i < lines.length; i++) { + if (lines[i].trim() === "## Type Parameters") { + for (let j = i + 1; j < lines.length; j++) { + if (lines[j].startsWith("## ") && lines[j].trim() !== "## Type Parameters") + break; + const paramMatch = lines[j].match(/^#{3,5}\s+(\w+)\s*$/); + if (paramMatch) { + pageTypeParams.push(paramMatch[1]); + } + } + break; + } + } + + const result = []; + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + + // Fix 1: Replace `string` | `number` | `symbol` with keyof `T` + // TypeDoc resolves `keyof T` to `string | number | symbol` when T is unconstrained. + if (line.includes("`string` | `number` | `symbol`")) { + const defaultMatch = line.match(/= keyof `(\w+)`/); + const typeName = + defaultMatch ? defaultMatch[1] : pageTypeParams[0] || "T"; + line = line.replace( + /`string` \| `number` \| `symbol`( = keyof `\w+`)?/, + "keyof `" + typeName + "`" + ); + modified = true; + } + + // Fix 4b: Remove page-level ## Type Parameters sections. + // These are not useful in docs — generic type params are an implementation detail. + // Skip from "## Type Parameters" until the next "## " heading. + if (line.trim() === "## Type Parameters") { + let j = i + 1; + while (j < lines.length) { + const upcoming = lines[j].trim(); + if (upcoming.startsWith("## ") && upcoming !== "## Type Parameters") break; + j++; + } + // Skip trailing blank lines + while (j > i + 1 && lines[j - 1].trim() === "") { + j--; + } + i = j - 1; + modified = true; + continue; + } + + // Fix 4: Remove method-level #### Type Parameters sections. + // These are redundant — the info is already in the signature and parameter docs. + // Skip from "#### Type Parameters" until the next "#### " heading. + if (line.trim() === "#### Type Parameters") { + // Skip ahead past this section until the next #### heading or ### heading + let j = i + 1; + while (j < lines.length) { + const upcoming = lines[j].trim(); + if (upcoming.startsWith("#### ") && upcoming !== "#### Type Parameters") break; + if (upcoming.startsWith("### ")) break; + if (upcoming.startsWith("## ")) break; + j++; + } + // Also skip any trailing blank lines + while (j > i + 1 && lines[j - 1].trim() === "") { + j--; + } + i = j - 1; // -1 because the loop will increment + modified = true; + continue; + } + + // Fix 2 & 3: Signatures starting with > **methodName** + if (line.startsWith("> **") && line.includes("(")) { + // Extract method-level type params from signature (e.g., \<`K`\>) + const methodTypeParams = []; + const typeParamMatch = line.match(/\\<`(\w+)`\\>/); + if (typeParamMatch) { + methodTypeParams.push(typeParamMatch[1]); + } + + // Replace truncated generics: \<..., ...\> → \<`T`, `K`\> + if (line.includes("\\<..., ...\\>")) { + const allTypeParams = [...pageTypeParams, ...methodTypeParams]; + if (allTypeParams.length >= 2) { + line = line.replace( + /\\<\.\.\., \.\.\.\\>/g, + "\\<`" + allTypeParams[0] + "`, `" + allTypeParams[1] + "`\\>" + ); + modified = true; + } + } + + // Break long signatures into multi-line blockquote format. + // Each line ends with two trailing spaces to force a hard line break + // in Mintlify's Markdown renderer (otherwise blockquote lines get joined). + if (line.length > 85) { + const openParen = line.indexOf("("); + const returnMarker = line.lastIndexOf("): "); + + if (openParen > -1 && returnMarker > openParen) { + const prefix = line.slice(0, openParen); + const params = line.slice(openParen + 1, returnMarker); + const returnType = line.slice(returnMarker + 1); + + const paramList = params.split(", "); + if (paramList.length >= 3) { + result.push(prefix + "( "); + for (let j = 0; j < paramList.length; j++) { + const comma = j < paramList.length - 1 ? "," : ""; + result.push("> " + paramList[j] + comma + " "); + } + result.push("> )" + returnType); + modified = true; + continue; + } + } + } + } + + result.push(line); + } + + // Fix 6: Enrich bare type names in Returns sections with generics from signatures. + // E.g., `ImportResult` → `ImportResult` when the signature shows ImportResult\. + for (let i = 0; i < result.length; i++) { + const line = result[i]; + // Match a standalone backtick-wrapped type name (only content on the line) + const bareTypeMatch = line.match(/^`([A-Z]\w+)`$/); + if (!bareTypeMatch) continue; + const typeName = bareTypeMatch[1]; + + // Verify this follows a Returns heading (scan back past blank lines) + let isInReturns = false; + for (let j = i - 1; j >= Math.max(0, i - 3); j--) { + if (result[j].trim() === "") continue; + if (/^#{2,5} Returns/.test(result[j])) { + isInReturns = true; + } + break; + } + if (!isInReturns) continue; + + // Scan backwards for the nearest signature line + for (let j = i - 1; j >= Math.max(0, i - 30); j--) { + const sigLine = result[j]; + if (!sigLine.startsWith("> **") && !sigLine.startsWith("> )")) continue; + // Look for TypeName\<`GenericParam`\> in the signature + const genericPattern = new RegExp( + "`" + typeName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + + "`\\\\<`(\\w+)`\\\\>" + ); + const genMatch = sigLine.match(genericPattern); + if (genMatch) { + result[i] = "`" + typeName + "<" + genMatch[1] + ">`"; + modified = true; + } + break; + } + } + + return { content: result.join("\n"), modified }; +} + +function applySignatureCleanup(dir) { + if (!fs.existsSync(dir)) return; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + applySignatureCleanup(entryPath); + } else if ( + entry.isFile() && + (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) + ) { + const content = fs.readFileSync(entryPath, "utf-8"); + const { content: updated, modified } = cleanupSignatures(content); + if (modified) { + fs.writeFileSync(entryPath, updated, "utf-8"); + console.log( + `Cleaned up signatures: ${path.relative(DOCS_DIR, entryPath)}` + ); + } + } + } +} + function demoteNonCallableHeadings(content) { const lines = content.split("\n"); let inFence = false; @@ -967,6 +1219,48 @@ function applyNonExposedTypeLinkRemoval(dir, exposedTypeNames) { /** * Main function */ +/** + * Delete types that should not appear in navigation but were needed for inline rendering. + * These types are listed in types-to-delete-after-processing.json + */ +function deleteTypesAfterProcessing(docsDir) { + let typesToDelete = new Set(); + try { + const content = fs.readFileSync(TYPES_TO_DELETE_PATH, "utf-8"); + const parsed = JSON.parse(content); + if (Array.isArray(parsed)) { + typesToDelete = new Set(parsed); + } + } catch (e) { + // No types to delete, that's fine + return; + } + + if (typesToDelete.size === 0) { + return; + } + + const contentDir = path.join(docsDir, "content"); + const sections = ["functions", "interfaces", "classes", "type-aliases"]; + + for (const section of sections) { + const sectionDir = path.join(contentDir, section); + if (!fs.existsSync(sectionDir)) continue; + + const files = fs.readdirSync(sectionDir); + for (const file of files) { + if (!file.endsWith(".mdx") && !file.endsWith(".md")) continue; + + const fileName = path.basename(file, path.extname(file)); + if (typesToDelete.has(fileName)) { + const filePath = path.join(sectionDir, file); + fs.unlinkSync(filePath); + console.log(`Removed (after processing): content/${section}/${file}`); + } + } + } +} + function main() { console.log("Processing TypeDoc MDX files for Mintlify...\n"); @@ -990,6 +1284,9 @@ function main() { const appendedArticles = loadAppendedArticlesConfig(); applyAppendedArticles(appendedArticles); + // Clean up signatures: fix truncated generics, simplify keyof constraints, break long lines + applySignatureCleanup(DOCS_DIR); + applyHeadingDemotion(DOCS_DIR); // Link type names in Type Declarations sections to their corresponding headings @@ -998,6 +1295,9 @@ function main() { // Remove links to types that aren't exposed (would 404) applyNonExposedTypeLinkRemoval(DOCS_DIR, exposedTypeNames); + // Delete types that should not appear in navigation but were needed for inline rendering + deleteTypesAfterProcessing(DOCS_DIR); + // Clean up the linked types file try { if (fs.existsSync(LINKED_TYPES_FILE)) { diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js index 38e1374..e82f079 100644 --- a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js @@ -297,6 +297,23 @@ export function convertClassMethodReturns( }); } +/** + * If the full return type from the signature contains the type name with generic + * parameters (e.g., "Promise>"), enrich the display name to include + * those generics (e.g., "ImportResult" → "ImportResult"). + */ +function enrichTypeNameWithGenerics(typeName, returnTypeFromSignature) { + if (!typeName || !returnTypeFromSignature) return typeName; + const escaped = typeName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const genericMatch = returnTypeFromSignature.match( + new RegExp(escaped + "<([^>]+)>") + ); + if (genericMatch) { + return `${typeName}<${genericMatch[1]}>`; + } + return typeName; +} + function rewriteReturnSections(content, options) { const { heading, @@ -405,7 +422,10 @@ function rewriteReturnSections(content, options) { if (fields.length === 0 && !indexSignature) { result.push(...sectionLines); } else { - const typeNameForDisplay = extractedTypeName || returnTypeName; + const typeNameForDisplay = enrichTypeNameWithGenerics( + extractedTypeName || returnTypeName, + returnTypeFromSignature + ); if (typeNameForDisplay) { result.push(""); result.push(`\`${typeNameForDisplay}\``); @@ -500,7 +520,10 @@ function rewriteReturnSections(content, options) { if (fields.length === 0 && !indexSignature) { result.push(...sectionLines); } else { - const typeNameForDisplay = extractedTypeName || returnTypeName; + const typeNameForDisplay = enrichTypeNameWithGenerics( + extractedTypeName || returnTypeName, + returnTypeFromSignature + ); if (typeNameForDisplay) { result.push(""); result.push(`\`${typeNameForDisplay}\``); @@ -1035,13 +1058,16 @@ function formatReturnFieldsOutput( const isSingleSimpleField = fields.length === 1 && + fields[0].name === "result" && (!fields[0].nested || fields[0].nested.length === 0) && !indexSignature; if (isSingleSimpleField) { - // For a single, non-object field, we only need to return its description text. - // The type is already rendered separately (`typeNameForDisplay`), so avoid wrapping - // it in a ResponseField to keep the output concise. + // For a single, non-object field with the default "result" name, we only need to + // return its description text. The type is already rendered separately + // (`typeNameForDisplay`), so avoid wrapping it in a ResponseField to keep the + // output concise. Fields with actual property names (e.g., extracted from a linked + // type like DeleteResult) should still get proper ResponseField rendering. return fields[0].description || ""; } @@ -1067,30 +1093,16 @@ function formatReturnFieldsOutput( return ""; } - const hasMultipleFields = fields.length > 1; - const hasNestedFields = fields.some( - (field) => Array.isArray(field.nested) && field.nested.length > 0 - ); - - if (hasMultipleFields || hasNestedFields || indexSignature) { - // Extract the simple type name to display above the Accordion - let typeDisplay = ""; - if (returnType) { - const simpleTypeName = getSimpleTypeName(returnType); - if (simpleTypeName && !PRIMITIVE_TYPES.includes(simpleTypeName)) { - typeDisplay = `\`${simpleTypeName}\`\n\n`; - } - } - // If we still don't have a type display and have multiple fields, - // try to infer from the context (e.g., if all fields are from the same type) - if (!typeDisplay && hasMultipleFields && fields.length > 0) { - // Check if we can get a type hint from the first field's description or context - // This is a fallback for cases where returnType wasn't passed correctly + // Extract the simple type name to display above the Accordion + let typeDisplay = ""; + if (returnType) { + const simpleTypeName = getSimpleTypeName(returnType); + if (simpleTypeName && !PRIMITIVE_TYPES.includes(simpleTypeName)) { + typeDisplay = `\`${simpleTypeName}\`\n\n`; } - return `${typeDisplay}\n\n${fieldsBlock}${indexSignatureBlock}\n`; } - return fieldsBlock + indexSignatureBlock; + return `${typeDisplay}\n\n${fieldsBlock}${indexSignatureBlock}\n`; } function renderNestedResponseFields( diff --git a/scripts/mintlify-post-processing/types-to-delete-after-processing.json b/scripts/mintlify-post-processing/types-to-delete-after-processing.json new file mode 100644 index 0000000..1b79c6c --- /dev/null +++ b/scripts/mintlify-post-processing/types-to-delete-after-processing.json @@ -0,0 +1,5 @@ +[ + "DeleteManyResult", + "DeleteResult", + "ImportResult" +] diff --git a/scripts/mintlify-post-processing/types-to-expose.json b/scripts/mintlify-post-processing/types-to-expose.json index 3bbdb35..2cd803c 100644 --- a/scripts/mintlify-post-processing/types-to-expose.json +++ b/scripts/mintlify-post-processing/types-to-expose.json @@ -1,13 +1,22 @@ [ + "AgentName", + "AgentNameRegistry", "AgentsModule", "AnalyticsModule", "AppLogsModule", "AuthModule", "ConnectorsModule", "CustomIntegrationsModule", + "DeleteManyResult", + "DeleteResult", "EntitiesModule", "EntityHandler", + "EntityRecord", + "EntityTypeRegistry", + "FunctionName", + "FunctionNameRegistry", "FunctionsModule", + "ImportResult", "IntegrationsModule", "CoreIntegrations", "SsoModule" diff --git a/src/index.ts b/src/index.ts index 05632ca..4ae03a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,10 +34,13 @@ export * from "./types.js"; // Module types export type { + DeleteManyResult, + DeleteResult, EntitiesModule, EntityHandler, EntityRecord, EntityTypeRegistry, + ImportResult, RealtimeEventType, RealtimeEvent, RealtimeCallback, @@ -75,11 +78,13 @@ export type { export type { FunctionsModule, + FunctionName, FunctionNameRegistry, } from "./modules/functions.types.js"; export type { AgentsModule, + AgentName, AgentNameRegistry, AgentConversation, AgentMessage, diff --git a/src/modules/agents.types.ts b/src/modules/agents.types.ts index 5c14b55..22462b8 100644 --- a/src/modules/agents.types.ts +++ b/src/modules/agents.types.ts @@ -3,13 +3,19 @@ import { RoomsSocket } from "../utils/socket-utils.js"; import { ModelFilterParams } from "../types.js"; /** - * Registry of agent names. - * Augment this interface to enable autocomplete for agent names. + * Registry of agent names. The [`types generate`](/developers/references/cli/commands/types-generate) command fills this registry, then [`AgentName`](#agentname) resolves to a union of the keys. */ export interface AgentNameRegistry {} /** - * Agent name type - uses registry keys if augmented, otherwise string. + * Union of all agent names from the [`AgentNameRegistry`](#agentnameregistry). Defaults to `string` when no types have been generated. + * + * @example + * ```typescript + * // Using generated agent name types + * // With generated types, you get autocomplete on agent names + * const conversation = await base44.agents.createConversation({ agent_name: 'SupportBot' }); + * ``` */ export type AgentName = keyof AgentNameRegistry extends never ? string @@ -197,6 +203,9 @@ export interface AgentsModuleConfig { * - **Anonymous or User authentication** (`base44.agents`): Access is scoped to the current user's permissions. Users must be authenticated to create and access conversations. * - **Service role authentication** (`base44.asServiceRole.agents`): Operations have elevated admin-level permissions. Can access all conversations that the app's admin role has access to. * + * ## Generated Types + * + * If you're working in a TypeScript project, you can generate types from your agents to get autocomplete on agent names when creating conversations or subscribing to updates. See the [Generated Types](/developers/references/sdk/getting-started/generated-types) guide to get started. */ export interface AgentsModule { /** @@ -222,7 +231,7 @@ export interface AgentsModule { * Gets a specific conversation by ID. * * Retrieves a single conversation using its unique identifier. To retrieve - * all conversations, use {@linkcode getConversations | getConversations()} To filter, sort, or paginate conversations, use {@linkcode listConversations | listConversations()}. + * all conversations, use {@linkcode getConversations | getConversations()}. To filter, sort, or paginate conversations, use {@linkcode listConversations | listConversations()}. * * This function returns the complete stored conversation including full tool call results, even for large responses. * @@ -339,8 +348,8 @@ export interface AgentsModule { * to clean up the connection. * * -When receiving messages through this function, tool call data is truncated for efficiency. The `arguments_string` is limited to 500 characters and `results` to 50 characters. The complete tool call data is always saved in storage and can be retrieved by calling {@linkcode getConversation | getConversation()} after the message completes. - + * When receiving messages through this function, tool call data is truncated for efficiency. The `arguments_string` is limited to 500 characters and `results` to 50 characters. The complete tool call data is always saved in storage and can be retrieved by calling {@linkcode getConversation | getConversation()} after the message completes. + * * * @param conversationId - The conversation ID to subscribe to. * @param onUpdate - Callback function called when the conversation is updated. The callback receives a conversation object with the following properties: diff --git a/src/modules/entities.types.ts b/src/modules/entities.types.ts index fbcc0a6..141f143 100644 --- a/src/modules/entities.types.ts +++ b/src/modules/entities.types.ts @@ -30,7 +30,7 @@ export type RealtimeCallback = (event: RealtimeEvent) => void; * Result returned when deleting a single entity. */ export interface DeleteResult { - /** Whether the deletion was successful */ + /** Whether the deletion was successful. */ success: boolean; } @@ -38,9 +38,9 @@ export interface DeleteResult { * Result returned when deleting multiple entities. */ export interface DeleteManyResult { - /** Whether the deletion was successful */ + /** Whether the deletion was successful. */ success: boolean; - /** Number of entities that were deleted */ + /** Number of entities that were deleted. */ deleted: number; } @@ -50,11 +50,11 @@ export interface DeleteManyResult { * @typeParam T - The entity type for imported records. Defaults to `any`. */ export interface ImportResult { - /** Status of the import operation */ + /** Status of the import operation. */ status: "success" | "error"; - /** Details message, e.g., "Successfully imported 3 entities with RLS enforcement" */ + /** Details message, e.g., "Successfully imported 3 entities with RLS enforcement". */ details: string | null; - /** Array of created entity objects when successful, or null on error */ + /** Array of created entity objects when successful, or null on error. */ output: T[] | null; } @@ -99,13 +99,33 @@ interface ServerEntityFields { } /** - * Registry mapping entity names to their TypeScript types. - * Augment this interface with your entity schema (user-defined fields only). + * Registry mapping entity names to their TypeScript types. The [`types generate`](/developers/references/cli/commands/types-generate) command fills this registry, then [`EntityRecord`](#entityrecord) adds server fields. */ export interface EntityTypeRegistry {} /** - * Full record type for each entity: schema fields + server-injected fields (id, created_date, etc.). + * Combines the [`EntityTypeRegistry`](#entitytyperegistry) schemas with server fields like `id`, `created_date`, and `updated_date` to give the complete record type for each entity. Use this when you need to type variables holding entity data. + * + * @example + * ```typescript + * // Type-safe entity records + * import type { EntityRecord } from '@base44/sdk'; + * // Import your generated entity schema + * import type { Task } from '@/base44/.types/types'; + * + * // Combine your schema with server fields (id, created_date, etc.) + * type TaskRecord = EntityRecord['Task']; + * + * const task: TaskRecord = await base44.entities.Task.create({ + * title: 'My task', + * status: 'pending' + * }); + * + * // Task now includes both your fields and server fields: + * console.log(task.id); // Server field + * console.log(task.created_date); // Server field + * console.log(task.title); // Your field + * ``` */ export type EntityRecord = { [K in keyof EntityTypeRegistry]: EntityTypeRegistry[K] & ServerEntityFields; @@ -163,7 +183,7 @@ export interface EntityHandler { sort?: SortField, limit?: number, skip?: number, - fields?: K[] + fields?: K[], ): Promise[]>; /** @@ -229,7 +249,7 @@ export interface EntityHandler { sort?: SortField, limit?: number, skip?: number, - fields?: K[] + fields?: K[], ): Promise[]>; /** @@ -436,7 +456,7 @@ type DynamicEntitiesModule = { * Entities are accessed dynamically using the pattern: * `base44.entities.EntityName.method()` * - * This module is available to use with a client in all three authentication modes: + * This module is available to use with a client in all authentication modes: * * - **Anonymous or User authentication** (`base44.entities`): Access is scoped to the current user's permissions. Anonymous users can only access public entities, while authenticated users can access entities they have permission to view or modify. * - **Service role authentication** (`base44.asServiceRole.entities`): Operations have elevated admin-level permissions. Can access all entities that the app's admin role has access to. @@ -447,6 +467,10 @@ type DynamicEntitiesModule = { * * Regular users can only read and update their own user record. With service role authentication, you can read, update, and delete any user. You can't create users using the entities module. Instead, use the functions of the {@link AuthModule | auth module} to invite or register new users. * + * ## Generated Types + * + * If you're working in a TypeScript project, you can generate types from your entity schemas to get autocomplete and type checking on all entity methods. See the [Generated Types](/developers/references/sdk/getting-started/generated-types) guide to get started. + * * @example * ```typescript * // Get all records from the MyEntity entity diff --git a/src/modules/functions.types.ts b/src/modules/functions.types.ts index 95dd123..9e82ddd 100644 --- a/src/modules/functions.types.ts +++ b/src/modules/functions.types.ts @@ -1,11 +1,17 @@ /** - * Registry of function names. - * Augment this interface to enable autocomplete for function names. + * Registry of function names. The [`types generate`](/developers/references/cli/commands/types-generate) command fills this registry, then [`FunctionName`](#functionname) resolves to a union of the keys. */ export interface FunctionNameRegistry {} /** - * Function name type - uses registry keys if augmented, otherwise string. + * Union of all function names from the [`FunctionNameRegistry`](#functionnameregistry). Defaults to `string` when no types have been generated. + * + * @example + * ```typescript + * // Using generated function name types + * // With generated types, you get autocomplete on function names + * await base44.functions.invoke('sendEmail', { to: 'user@example.com' }); + * ``` */ export type FunctionName = keyof FunctionNameRegistry extends never ? string @@ -20,6 +26,10 @@ export type FunctionName = keyof FunctionNameRegistry extends never * * - **Anonymous or User authentication** (`base44.functions`): Functions are invoked with the current user's permissions. Anonymous users invoke functions without authentication, while authenticated users invoke functions with their authentication context. * - **Service role authentication** (`base44.asServiceRole.functions`): Functions are invoked with elevated admin-level permissions. The function code receives a request with admin authentication context. + * + * ## Generated Types + * + * If you're working in a TypeScript project, you can generate types from your backend functions to get autocomplete on function names when calling `invoke()`. See the [Generated Types](/developers/references/sdk/getting-started/generated-types) guide to get started. */ export interface FunctionsModule { /** From 3bb960e68926caa53100f467305320687e411ed4 Mon Sep 17 00:00:00 2001 From: Sam Markowitz Date: Mon, 16 Feb 2026 09:17:02 +0200 Subject: [PATCH 2/4] Update FunctionName example to use calculateTotal Changed from sendEmail to calculateTotal to match the invoke() function examples for consistency. Co-authored-by: Cursor --- src/modules/functions.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/functions.types.ts b/src/modules/functions.types.ts index 9e82ddd..29a7ca9 100644 --- a/src/modules/functions.types.ts +++ b/src/modules/functions.types.ts @@ -10,7 +10,7 @@ export interface FunctionNameRegistry {} * ```typescript * // Using generated function name types * // With generated types, you get autocomplete on function names - * await base44.functions.invoke('sendEmail', { to: 'user@example.com' }); + * await base44.functions.invoke('calculateTotal', { items: ['item1', 'item2'] }); * ``` */ export type FunctionName = keyof FunctionNameRegistry extends never From c1c7c164eaca292db5512d85a5cc609da52add3d Mon Sep 17 00:00:00 2001 From: Sam Markowitz Date: Mon, 16 Feb 2026 09:33:03 +0200 Subject: [PATCH 3/4] Regenerate docs with updated FunctionName example Co-authored-by: Cursor --- src/modules/functions.types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/functions.types.ts b/src/modules/functions.types.ts index 29a7ca9..ad2e5eb 100644 --- a/src/modules/functions.types.ts +++ b/src/modules/functions.types.ts @@ -49,7 +49,6 @@ export interface FunctionsModule { * // Basic function call * const result = await base44.functions.invoke('calculateTotal', { * items: ['item1', 'item2'], - * discount: 0.1 * }); * console.log(result.data.total); * ``` From 23ebf8cd2d87d5e741fa42623013c8511d736c60 Mon Sep 17 00:00:00 2001 From: Sam Markowitz Date: Mon, 16 Feb 2026 13:38:03 +0200 Subject: [PATCH 4/4] Fix JSDoc links and add connectors to type generation docs - Fix all module JSDoc links to reference /dynamic-types instead of /generated-types - Remove bad import example from EntityRecord JSDoc (don't import from @base44/.types/types) - Add connectors to type generation documentation following the pattern for entities/functions/agents - Update ConnectorIntegrationTypeRegistry and ConnectorIntegrationType JSDoc to match other registries - Add Dynamic Types section to ConnectorsModule JSDoc Addresses PR feedback from kfirstri on PR #127. Co-authored-by: Cursor --- src/modules/agents.types.ts | 2 +- src/modules/connectors.types.ts | 17 +++++++++++++---- src/modules/entities.types.ts | 5 +---- src/modules/functions.types.ts | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/modules/agents.types.ts b/src/modules/agents.types.ts index 22462b8..5ff55c3 100644 --- a/src/modules/agents.types.ts +++ b/src/modules/agents.types.ts @@ -205,7 +205,7 @@ export interface AgentsModuleConfig { * * ## Generated Types * - * If you're working in a TypeScript project, you can generate types from your agents to get autocomplete on agent names when creating conversations or subscribing to updates. See the [Generated Types](/developers/references/sdk/getting-started/generated-types) guide to get started. + * If you're working in a TypeScript project, you can generate types from your agents to get autocomplete on agent names when creating conversations or subscribing to updates. See the [Dynamic Types](/developers/references/sdk/getting-started/dynamic-types) guide to get started. */ export interface AgentsModule { /** diff --git a/src/modules/connectors.types.ts b/src/modules/connectors.types.ts index 585604e..f25953d 100644 --- a/src/modules/connectors.types.ts +++ b/src/modules/connectors.types.ts @@ -1,12 +1,17 @@ /** - * Registry of connector integration types. - * Augment this interface to enable autocomplete for connector integration types. + * Registry of connector integration type names. The [`types generate`](/developers/references/cli/commands/types-generate) command fills this registry, then [`ConnectorIntegrationType`](#connectorintegrationtype) resolves to a union of the keys. */ export interface ConnectorIntegrationTypeRegistry {} /** - * The type of external integration/connector, such as `'googlecalendar'`, `'slack'`, or `'github'`. - * Uses registry keys if augmented, otherwise falls back to string. + * Union of all connector integration type names from the [`ConnectorIntegrationTypeRegistry`](#connectorintegrationtyperegistry). Defaults to `string` when no types have been generated. + * + * @example + * ```typescript + * // Using generated connector type names + * // With generated types, you get autocomplete on integration types + * const token = await base44.asServiceRole.connectors.getAccessToken('googlecalendar'); + * ``` */ export type ConnectorIntegrationType = keyof ConnectorIntegrationTypeRegistry extends never ? string @@ -30,6 +35,10 @@ export interface ConnectorAccessTokenResponse { * covered by Base44's pre-built integrations. * * This module is only available to use with a client in service role authentication mode, which means it can only be used in backend environments. + * + * ## Dynamic Types + * + * If you're working in a TypeScript project, you can generate types from your app's connector configurations to get autocomplete on integration type names when calling `getAccessToken()`. See the [Dynamic Types](/developers/references/sdk/getting-started/dynamic-types) guide to get started. */ export interface ConnectorsModule { /** diff --git a/src/modules/entities.types.ts b/src/modules/entities.types.ts index 141f143..ebc04c4 100644 --- a/src/modules/entities.types.ts +++ b/src/modules/entities.types.ts @@ -108,10 +108,7 @@ export interface EntityTypeRegistry {} * * @example * ```typescript - * // Type-safe entity records * import type { EntityRecord } from '@base44/sdk'; - * // Import your generated entity schema - * import type { Task } from '@/base44/.types/types'; * * // Combine your schema with server fields (id, created_date, etc.) * type TaskRecord = EntityRecord['Task']; @@ -469,7 +466,7 @@ type DynamicEntitiesModule = { * * ## Generated Types * - * If you're working in a TypeScript project, you can generate types from your entity schemas to get autocomplete and type checking on all entity methods. See the [Generated Types](/developers/references/sdk/getting-started/generated-types) guide to get started. + * If you're working in a TypeScript project, you can generate types from your entity schemas to get autocomplete and type checking on all entity methods. See the [Dynamic Types](/developers/references/sdk/getting-started/dynamic-types) guide to get started. * * @example * ```typescript diff --git a/src/modules/functions.types.ts b/src/modules/functions.types.ts index ad2e5eb..73d9e9d 100644 --- a/src/modules/functions.types.ts +++ b/src/modules/functions.types.ts @@ -29,7 +29,7 @@ export type FunctionName = keyof FunctionNameRegistry extends never * * ## Generated Types * - * If you're working in a TypeScript project, you can generate types from your backend functions to get autocomplete on function names when calling `invoke()`. See the [Generated Types](/developers/references/sdk/getting-started/generated-types) guide to get started. + * If you're working in a TypeScript project, you can generate types from your backend functions to get autocomplete on function names when calling `invoke()`. See the [Dynamic Types](/developers/references/sdk/getting-started/dynamic-types) guide to get started. */ export interface FunctionsModule { /**