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..5ff55c3 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 [Dynamic Types](/developers/references/sdk/getting-started/dynamic-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/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 fbcc0a6..ebc04c4 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,30 @@ 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 + * import type { EntityRecord } from '@base44/sdk'; + * + * // 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 +180,7 @@ export interface EntityHandler { sort?: SortField, limit?: number, skip?: number, - fields?: K[] + fields?: K[], ): Promise[]>; /** @@ -229,7 +246,7 @@ export interface EntityHandler { sort?: SortField, limit?: number, skip?: number, - fields?: K[] + fields?: K[], ): Promise[]>; /** @@ -436,7 +453,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 +464,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 [Dynamic Types](/developers/references/sdk/getting-started/dynamic-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..73d9e9d 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('calculateTotal', { items: ['item1', 'item2'] }); + * ``` */ 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 [Dynamic Types](/developers/references/sdk/getting-started/dynamic-types) guide to get started. */ export interface FunctionsModule { /** @@ -39,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); * ```