From 8ca5a018917b47677ea14a8e59c16981a14d471e Mon Sep 17 00:00:00 2001 From: Sam Markowitz Date: Sun, 11 Jan 2026 12:28:54 +0200 Subject: [PATCH 1/3] updates for realtime entities --- package.json | 1 + .../copy-to-local-docs.js | 266 ++++++++++++++++++ .../push-to-docs-repo.js | 83 +++--- src/index.ts | 1 - src/modules/agents.types.ts | 20 +- src/modules/entities.ts | 3 +- src/modules/entities.types.ts | 21 +- 7 files changed, 335 insertions(+), 60 deletions(-) create mode 100644 scripts/mintlify-post-processing/copy-to-local-docs.js diff --git a/package.json b/package.json index 982e2bf..54bf347 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "prepublishOnly": "npm run build", "create-docs": "npm run create-docs:generate && npm run create-docs:process", "push-docs": "node scripts/mintlify-post-processing/push-to-docs-repo.js", + "copy-docs-local": "node scripts/mintlify-post-processing/copy-to-local-docs.js", "create-docs:generate": "typedoc", "create-docs:process": "node scripts/mintlify-post-processing/file-processing/file-processing.js" }, diff --git a/scripts/mintlify-post-processing/copy-to-local-docs.js b/scripts/mintlify-post-processing/copy-to-local-docs.js new file mode 100644 index 0000000..9fc2c10 --- /dev/null +++ b/scripts/mintlify-post-processing/copy-to-local-docs.js @@ -0,0 +1,266 @@ +#!/usr/bin/env node + +/** + * Local docs copy script - copies SDK docs to a local mintlify-docs repo. + * + * Usage: + * node copy-to-local-docs.js [--target ] + * + * Options: + * --target Path to the mintlify-docs repo. Defaults to ../mintlify-docs + * (assumes both repos are in the same parent folder) + * + * Examples: + * node copy-to-local-docs.js + * node copy-to-local-docs.js --target ~/Projects/mintlify-docs + * npm run copy-docs-local + * npm run copy-docs-local -- --target ~/Projects/mintlify-docs + */ + +import fs from "fs"; +import path from "path"; + +console.debug = () => {}; // Disable debug logging. Comment this out to enable debug logging. + +const DOCS_SOURCE_PATH = path.join(import.meta.dirname, "../../docs/content"); +const CATEGORY_MAP_PATH = path.join(import.meta.dirname, "./category-map.json"); + +// Default: assume mintlify-docs is a sibling directory to javascript-sdk +const SDK_ROOT = path.join(import.meta.dirname, "../.."); +const DEFAULT_TARGET = path.join(SDK_ROOT, "../mintlify-docs"); + +function parseArgs() { + const args = process.argv.slice(2); + let target = DEFAULT_TARGET; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if ((arg === "--target" || arg === "-t") && i + 1 < args.length) { + target = args[++i]; + // Expand ~ to home directory + if (target.startsWith("~")) { + target = path.join(process.env.HOME, target.slice(1)); + } + // Resolve to absolute path + target = path.resolve(target); + } + + if (arg === "--help" || arg === "-h") { + console.log(` +Local docs copy script - copies SDK docs to a local mintlify-docs repo. + +Usage: + node copy-to-local-docs.js [--target ] + +Options: + --target, -t Path to the mintlify-docs repo. + Defaults to ../mintlify-docs (sibling directory) + --help, -h Show this help message + +Examples: + node copy-to-local-docs.js + node copy-to-local-docs.js --target ~/Projects/mintlify-docs + npm run copy-docs-local + npm run copy-docs-local -- --target ~/Projects/mintlify-docs +`); + process.exit(0); + } + } + + return { target }; +} + +function scanSdkDocs(sdkDocsDir) { + const result = {}; + + // Get a list of all the subdirectories in the sdkDocsDir + const subdirectories = fs + .readdirSync(sdkDocsDir) + .filter((file) => fs.statSync(path.join(sdkDocsDir, file)).isDirectory()); + console.log(`Subdirectories: ${subdirectories}`); + + for (const subdirectory of subdirectories) { + const subdirectoryPath = path.join(sdkDocsDir, subdirectory); + const files = fs + .readdirSync(subdirectoryPath) + .filter((file) => file.endsWith(".mdx")); + result[subdirectory] = files.map((file) => path.basename(file, ".mdx")); + } + return result; +} + +function updateDocsJson(repoDir, sdkFiles) { + const docsJsonPath = path.join(repoDir, "docs.json"); + let categoryMap = {}; + try { + categoryMap = JSON.parse(fs.readFileSync(CATEGORY_MAP_PATH, "utf8")); + } catch (e) { + console.error(`Error: Category map file not found: ${CATEGORY_MAP_PATH}`); + process.exit(1); + } + + console.log(`Reading docs.json from ${docsJsonPath}...`); + const docsContent = fs.readFileSync(docsJsonPath, "utf8"); + const docs = JSON.parse(docsContent); + + // Build the new SDK Reference groups + const groupMap = new Map(); // group name -> pages array + + const addToGroup = (groupName, pages) => { + if (!groupName || pages.length === 0) return; + if (!groupMap.has(groupName)) { + groupMap.set(groupName, []); + } + groupMap.get(groupName).push(...pages); + }; + + if (sdkFiles.functions?.length > 0 && categoryMap.functions) { + addToGroup( + categoryMap.functions, + sdkFiles.functions.map((file) => `sdk-docs/functions/${file}`) + ); + } + + if (sdkFiles.interfaces?.length > 0 && categoryMap.interfaces) { + addToGroup( + categoryMap.interfaces, + sdkFiles.interfaces.map((file) => `sdk-docs/interfaces/${file}`) + ); + } + + if (sdkFiles.classes?.length > 0 && categoryMap.classes) { + addToGroup( + categoryMap.classes, + sdkFiles.classes.map((file) => `sdk-docs/classes/${file}`) + ); + } + + if (sdkFiles["type-aliases"]?.length > 0 && categoryMap["type-aliases"]) { + addToGroup( + categoryMap["type-aliases"], + sdkFiles["type-aliases"].map((file) => `sdk-docs/type-aliases/${file}`) + ); + } + + // Convert map to array of nested groups for SDK Reference + const sdkReferencePages = Array.from(groupMap.entries()).map( + ([groupName, pages]) => ({ + group: groupName, + pages: pages.sort(), // Sort pages alphabetically within each group + }) + ); + + console.debug( + `SDK Reference pages: ${JSON.stringify(sdkReferencePages, null, 2)}` + ); + + // Navigate to: Developers tab -> SDK group -> SDK Reference group + const developersTab = docs.navigation.tabs.find( + (tab) => tab.tab === "Developers" + ); + + if (!developersTab) { + console.error("Could not find 'Developers' tab in docs.json"); + process.exit(1); + } + + // Find the SDK group (it's a top-level group in the Developers tab) + const sdkGroup = developersTab.groups.find((g) => g.group === "SDK"); + + if (!sdkGroup) { + console.error("Could not find 'SDK' group in Developers tab"); + process.exit(1); + } + + // Find SDK Reference within SDK's pages (it's a nested group object) + const sdkRefIndex = sdkGroup.pages.findIndex( + (page) => typeof page === "object" && page.group === "SDK Reference" + ); + + if (sdkRefIndex === -1) { + console.error("Could not find 'SDK Reference' group in SDK"); + process.exit(1); + } + + // Update the SDK Reference pages with our generated groups + sdkGroup.pages[sdkRefIndex] = { + group: "SDK Reference", + icon: "brackets-curly", + pages: sdkReferencePages, + }; + + // Remove the old standalone "SDK Reference" tab if it exists + const oldSdkTabIndex = docs.navigation.tabs.findIndex( + (tab) => tab.tab === "SDK Reference" + ); + if (oldSdkTabIndex !== -1) { + console.log("Removing old standalone 'SDK Reference' tab..."); + docs.navigation.tabs.splice(oldSdkTabIndex, 1); + } + + // Write updated docs.json + console.log(`Writing updated docs.json to ${docsJsonPath}...`); + fs.writeFileSync(docsJsonPath, JSON.stringify(docs, null, 2) + "\n", "utf8"); + + console.log("Successfully updated docs.json"); +} + +function main() { + const { target } = parseArgs(); + + console.log(`Source: ${DOCS_SOURCE_PATH}`); + console.log(`Target: ${target}`); + + // Validate source exists + if ( + !fs.existsSync(DOCS_SOURCE_PATH) || + !fs.statSync(DOCS_SOURCE_PATH).isDirectory() + ) { + console.error(`Error: docs directory does not exist: ${DOCS_SOURCE_PATH}`); + console.error("Have you run 'npm run create-docs' first?"); + process.exit(1); + } + + // Validate target exists and looks like a mintlify-docs repo + if (!fs.existsSync(target) || !fs.statSync(target).isDirectory()) { + console.error(`Error: target directory does not exist: ${target}`); + process.exit(1); + } + + const docsJsonPath = path.join(target, "docs.json"); + if (!fs.existsSync(docsJsonPath)) { + console.error( + `Error: docs.json not found in ${target}. Is this a mintlify-docs repo?` + ); + process.exit(1); + } + + try { + // Remove the existing sdk-docs directory + const sdkDocsTarget = path.join(target, "sdk-docs"); + if (fs.existsSync(sdkDocsTarget)) { + console.log(`Removing existing sdk-docs directory...`); + fs.rmSync(sdkDocsTarget, { recursive: true, force: true }); + } + + // Copy the docs directory to the target + console.log(`Copying docs to ${sdkDocsTarget}...`); + fs.cpSync(DOCS_SOURCE_PATH, sdkDocsTarget, { recursive: true }); + + // Scan the sdk-docs directory + const sdkFiles = scanSdkDocs(sdkDocsTarget); + console.debug(`SDK files: ${JSON.stringify(sdkFiles, null, 2)}`); + + // Update the docs.json file + updateDocsJson(target, sdkFiles); + + console.log("\n✅ Successfully copied SDK docs to local mintlify-docs repo"); + console.log(`\nTo preview the docs, run 'mintlify dev' in ${target}`); + } catch (e) { + console.error(`Error: Failed to copy docs: ${e}`); + process.exit(1); + } +} + +main(); diff --git a/scripts/mintlify-post-processing/push-to-docs-repo.js b/scripts/mintlify-post-processing/push-to-docs-repo.js index 3eb3dc4..60f06ba 100644 --- a/scripts/mintlify-post-processing/push-to-docs-repo.js +++ b/scripts/mintlify-post-processing/push-to-docs-repo.js @@ -58,23 +58,7 @@ function updateDocsJson(repoDir, sdkFiles) { const docsContent = fs.readFileSync(docsJsonPath, "utf8"); const docs = JSON.parse(docsContent); - // Find the "SDK Reference" tab - const sdkTabIndex = docs.navigation.tabs.findIndex( - (tab) => tab.tab === "SDK Reference" - ); - let sdkTab = docs.navigation.tabs[sdkTabIndex]; - - if (sdkTabIndex === -1) { - console.log( - "Could not find 'SDK Reference' tab in docs.json. Creating it..." - ); - sdkTab = { - tab: "SDK Reference", - groups: [], - }; - } - - // Update the groups - merge categories that map to the same group name + // Build the new SDK Reference groups const groupMap = new Map(); // group name -> pages array const addToGroup = (groupName, pages) => { @@ -113,43 +97,62 @@ function updateDocsJson(repoDir, sdkFiles) { ); } - // Convert map to array of groups - const newGroups = Array.from(groupMap.entries()).map( + // Convert map to array of nested groups for SDK Reference + const sdkReferencePages = Array.from(groupMap.entries()).map( ([groupName, pages]) => ({ group: groupName, pages: pages.sort(), // Sort pages alphabetically within each group }) ); - const newGroupNames = new Set(newGroups.map((group) => group.group)); - const preservedGroups = []; - let insertionIndex; + console.debug( + `SDK Reference pages: ${JSON.stringify(sdkReferencePages, null, 2)}` + ); - for (const existingGroup of sdkTab.groups ?? []) { - if (newGroupNames.has(existingGroup.group)) { - if (insertionIndex === undefined) { - insertionIndex = preservedGroups.length; - } - continue; - } - preservedGroups.push(existingGroup); + // Navigate to: Developers tab -> SDK group -> SDK Reference group + const developersTab = docs.navigation.tabs.find( + (tab) => tab.tab === "Developers" + ); + + if (!developersTab) { + console.error("Could not find 'Developers' tab in docs.json"); + process.exit(1); } - const finalGroups = [...preservedGroups]; - const targetIndex = insertionIndex ?? finalGroups.length; - finalGroups.splice(targetIndex, 0, ...newGroups); - sdkTab.groups = finalGroups; + // Find the SDK group (it's a top-level group in the Developers tab) + const sdkGroup = developersTab.groups.find((g) => g.group === "SDK"); - if (sdkTabIndex === -1) { - docs.navigation.tabs.push(sdkTab); - } else { - docs.navigation.tabs[sdkTabIndex] = sdkTab; + if (!sdkGroup) { + console.error("Could not find 'SDK' group in Developers tab"); + process.exit(1); } - console.debug( - `New groups for docs.json: ${JSON.stringify(newGroups, null, 2)}` + // Find SDK Reference within SDK's pages (it's a nested group object) + const sdkRefIndex = sdkGroup.pages.findIndex( + (page) => typeof page === "object" && page.group === "SDK Reference" ); + if (sdkRefIndex === -1) { + console.error("Could not find 'SDK Reference' group in SDK"); + process.exit(1); + } + + // Update the SDK Reference pages with our generated groups + sdkGroup.pages[sdkRefIndex] = { + group: "SDK Reference", + icon: "brackets-curly", + pages: sdkReferencePages, + }; + + // Remove the old standalone "SDK Reference" tab if it exists + const oldSdkTabIndex = docs.navigation.tabs.findIndex( + (tab) => tab.tab === "SDK Reference" + ); + if (oldSdkTabIndex !== -1) { + console.log("Removing old standalone 'SDK Reference' tab..."); + docs.navigation.tabs.splice(oldSdkTabIndex, 1); + } + // Write updated docs.json console.log(`Writing updated docs.json to ${docsJsonPath}...`); fs.writeFileSync(docsJsonPath, JSON.stringify(docs, null, 2) + "\n", "utf8"); diff --git a/src/index.ts b/src/index.ts index 752f560..9ea7892 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,7 +39,6 @@ export type { RealtimeEventType, RealtimeEvent, RealtimeCallback, - Subscription, } from "./modules/entities.types.js"; export type { diff --git a/src/modules/agents.types.ts b/src/modules/agents.types.ts index c22313f..5bc0ea7 100644 --- a/src/modules/agents.types.ts +++ b/src/modules/agents.types.ts @@ -147,7 +147,7 @@ export interface CreateConversationParams { export interface AgentsModuleConfig { /** Axios instance for HTTP requests */ axios: AxiosInstance; - /** Function to get WebSocket instance for real-time updates (lazy initialization) */ + /** Function to get WebSocket instance for realtime updates (lazy initialization) */ getSocket: () => ReturnType; /** Application ID */ appId: string; @@ -161,7 +161,7 @@ export interface AgentsModuleConfig { * Agents module for managing AI agent conversations. * * This module provides methods to create and manage conversations with AI agents, - * send messages, and subscribe to real-time updates. Conversations can be used + * send messages, and subscribe to realtime updates. Conversations can be used * for chat interfaces, support systems, or any interactive AI app. * * The agents module enables you to: @@ -169,7 +169,7 @@ export interface AgentsModuleConfig { * - **Create conversations** with agents defined in the app. * - **Send messages** from users to agents and receive AI-generated responses. * - **Retrieve conversations** individually or as filtered lists with sorting and pagination. - * - **Subscribe to real-time updates** using WebSocket connections to receive instant notifications when new messages arrive. + * - **Subscribe to realtime updates** using WebSocket connections to receive instant notifications when new messages arrive. * - **Attach metadata** to conversations for tracking context, categories, priorities, or linking to external systems. * - **Generate WhatsApp connection URLs** for users to interact with agents through WhatsApp. * @@ -295,7 +295,7 @@ export interface AgentsModule { * Adds a message to a conversation. * * Sends a message to the agent and updates the conversation. This method - * also updates the real-time socket to notify any subscribers. + * also updates the realtime socket to notify any subscribers. * * @param conversation - The conversation to add the message to. * @param message - The message to add. @@ -317,19 +317,25 @@ export interface AgentsModule { ): Promise; /** - * Subscribes to real-time updates for a conversation. + * Subscribes to realtime updates for a conversation. * * Establishes a WebSocket connection to receive instant updates when new * messages are added to the conversation. Returns an unsubscribe function * to clean up the connection. * * @param conversationId - The conversation ID to subscribe to. - * @param onUpdate - Callback function called when the conversation is updated. + * @param onUpdate - Callback function called when the conversation is updated. The callback receives a conversation object with the following properties: + * - `id`: Unique identifier for the conversation. + * - `agent_name`: Name of the agent in this conversation. + * - `created_date`: ISO 8601 timestamp of when the conversation was created. + * - `updated_date`: ISO 8601 timestamp of when the conversation was last updated. + * - `messages`: Array of messages in the conversation. Each message includes `id`, `role` (`'user'`, `'assistant'`, or `'system'`), `content`, `created_date`, and optionally `tool_calls`, `reasoning`, `file_urls`, and `usage`. + * - `metadata`: Optional metadata associated with the conversation. * @returns Unsubscribe function to stop receiving updates. * * @example * ```typescript - * // Subscribe to real-time updates + * // Subscribe to realtime updates * const unsubscribe = base44.agents.subscribeToConversation( * 'conv-123', * (updatedConversation) => { diff --git a/src/modules/entities.ts b/src/modules/entities.ts index 6ffb179..9cfc6bd 100644 --- a/src/modules/entities.ts +++ b/src/modules/entities.ts @@ -5,7 +5,6 @@ import { RealtimeCallback, RealtimeEvent, RealtimeEventType, - Subscription, } from "./entities.types"; import { RoomsSocket } from "../utils/socket-utils.js"; @@ -165,7 +164,7 @@ function createEntityHandler( }, // Subscribe to realtime updates - subscribe(callback: RealtimeCallback): Subscription { + subscribe(callback: RealtimeCallback): () => void { const room = `entities:${appId}:${entityName}`; // Get the socket and subscribe to the room diff --git a/src/modules/entities.types.ts b/src/modules/entities.types.ts index 2251244..58c03c7 100644 --- a/src/modules/entities.types.ts +++ b/src/modules/entities.types.ts @@ -22,11 +22,6 @@ export interface RealtimeEvent { */ export type RealtimeCallback = (event: RealtimeEvent) => void; -/** - * Function returned from subscribe, call it to unsubscribe. - */ -export type Subscription = () => void; - /** * Entity handler providing CRUD operations for a specific entity type. * @@ -294,10 +289,16 @@ export interface EntityHandler { /** * Subscribes to realtime updates for all records of this entity type. * - * Receives notifications whenever any record is created, updated, or deleted. + * Establishes a WebSocket connection to receive instant updates when any + * record is created, updated, or deleted. Returns an unsubscribe function + * to clean up the connection. * - * @param callback - Function called when an entity changes. - * @returns Unsubscribe function to stop listening. + * @param callback - Callback function called when an entity changes. The callback receives an event object with the following properties: + * - `type`: The type of change that occurred - `'create'`, `'update'`, or `'delete'`. + * - `data`: The entity data after the change. + * - `id`: The unique identifier of the affected entity. + * - `timestamp`: ISO 8601 timestamp of when the event occurred. + * @returns Unsubscribe function to stop receiving updates. * * @example * ```typescript @@ -306,11 +307,11 @@ export interface EntityHandler { * console.log(`Task ${event.id} was ${event.type}d:`, event.data); * }); * - * // Later, unsubscribe + * // Later, clean up the subscription * unsubscribe(); * ``` */ - subscribe(callback: RealtimeCallback): Subscription; + subscribe(callback: RealtimeCallback): () => void; } /** From 65418b9a28c0cabd0154764e3b5d959b34040df7 Mon Sep 17 00:00:00 2001 From: Sam Markowitz Date: Sun, 11 Jan 2026 12:42:34 +0200 Subject: [PATCH 2/3] hide stuff not ready to document yet --- src/client.types.ts | 5 ++++- src/modules/agents.types.ts | 4 ++-- src/modules/analytics.types.ts | 3 +++ src/modules/custom-integrations.types.ts | 3 +++ src/modules/integrations.types.ts | 1 + 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/client.types.ts b/src/client.types.ts index a86af41..a9a854b 100644 --- a/src/client.types.ts +++ b/src/client.types.ts @@ -86,7 +86,10 @@ export interface Base44Client { agents: AgentsModule; /** {@link AppLogsModule | App logs module} for tracking app usage. */ appLogs: AppLogsModule; - /** {@link AnalyticsModule | Analytics module} for tracking app usage. */ + /** + * {@link AnalyticsModule | Analytics module} for tracking app usage. + * @internal + */ analytics: AnalyticsModule; /** Cleanup function to disconnect WebSocket connections. Call when you're done with the client. */ cleanup: () => void; diff --git a/src/modules/agents.types.ts b/src/modules/agents.types.ts index 5bc0ea7..b437eb2 100644 --- a/src/modules/agents.types.ts +++ b/src/modules/agents.types.ts @@ -78,7 +78,7 @@ export interface AgentMessageMetadata { export interface AgentConversation { /** Unique identifier for the conversation. */ id: string; - /** Application ID. */ + /** App ID. */ app_id: string; /** Name of the agent in this conversation. */ agent_name: string; @@ -149,7 +149,7 @@ export interface AgentsModuleConfig { axios: AxiosInstance; /** Function to get WebSocket instance for realtime updates (lazy initialization) */ getSocket: () => ReturnType; - /** Application ID */ + /** App ID */ appId: string; /** Server URL */ serverUrl?: string; diff --git a/src/modules/analytics.types.ts b/src/modules/analytics.types.ts index 8172cb6..e0f29e3 100644 --- a/src/modules/analytics.types.ts +++ b/src/modules/analytics.types.ts @@ -46,6 +46,9 @@ export type AnalyticsModuleOptions = { heartBeatInterval?: number; }; +/** + * @internal + */ export type AnalyticsModule = { track: (params: TrackEventParams) => void; }; diff --git a/src/modules/custom-integrations.types.ts b/src/modules/custom-integrations.types.ts index ecaf024..40d766e 100644 --- a/src/modules/custom-integrations.types.ts +++ b/src/modules/custom-integrations.types.ts @@ -1,5 +1,6 @@ /** * Parameters for calling a custom integration endpoint. + * @internal */ export interface CustomIntegrationCallParams { /** @@ -26,6 +27,7 @@ export interface CustomIntegrationCallParams { /** * Response from a custom integration call. + * @internal */ export interface CustomIntegrationCallResponse { /** @@ -92,6 +94,7 @@ export interface CustomIntegrationCallResponse { * } * ); * ``` + * @internal */ export interface CustomIntegrationsModule { /** diff --git a/src/modules/integrations.types.ts b/src/modules/integrations.types.ts index e2b36a2..08d8563 100644 --- a/src/modules/integrations.types.ts +++ b/src/modules/integrations.types.ts @@ -391,6 +391,7 @@ export type IntegrationsModule = { * } * ); * ``` + * @internal */ custom: CustomIntegrationsModule; } & { From 2ce20053182a5015d5df1a6c82d99cd20676e17a Mon Sep 17 00:00:00 2001 From: Sam Markowitz Date: Sun, 11 Jan 2026 12:49:22 +0200 Subject: [PATCH 3/3] new command for creating and copying docs locally --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 54bf347..694136f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "docs": "typedoc", "prepublishOnly": "npm run build", "create-docs": "npm run create-docs:generate && npm run create-docs:process", + "create-docs-local": "npm run create-docs && npm run copy-docs-local", "push-docs": "node scripts/mintlify-post-processing/push-to-docs-repo.js", "copy-docs-local": "node scripts/mintlify-post-processing/copy-to-local-docs.js", "create-docs:generate": "typedoc",