diff --git a/scripts/mintlify-post-processing/file-processing/file-processing.js b/scripts/mintlify-post-processing/file-processing/file-processing.js index 4589768..ba5ea08 100755 --- a/scripts/mintlify-post-processing/file-processing/file-processing.js +++ b/scripts/mintlify-post-processing/file-processing/file-processing.js @@ -30,6 +30,7 @@ const APPENDED_ARTICLES_PATH = path.join( __dirname, "../appended-articles.json" ); +const METHOD_ORDER_PATH = path.join(__dirname, "..", "method-order.json"); // Controlled via env var so we can re-enable Panel injection when needed. const PANELS_ENABLED = process.env.MINTLIFY_INCLUDE_PANELS === "true"; @@ -1959,6 +1960,40 @@ function cleanupSortFieldSignature(dir) { } } +/** + * Fix truncated intersection types in ParamField type attributes. + * TypeDoc renders (Partial & { id: string })[] as "Partial<...> & object[]". + */ +function cleanupTruncatedParamTypes(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()) { + cleanupTruncatedParamTypes(entryPath); + } else if ( + entry.isFile() && + (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) + ) { + let content = fs.readFileSync(entryPath, "utf-8"); + let modified = false; + + if (content.includes('type="Partial<...> & object[]"')) { + content = content.replace( + /type="Partial<\.\.\.> & object\[\]"/g, + 'type="(Partial & { id: string })[]"' + ); + modified = true; + } + + if (modified) { + fs.writeFileSync(entryPath, content, "utf-8"); + console.log(`Cleaned truncated param type: ${path.relative(DOCS_DIR, entryPath)}`); + } + } + } +} + /** * Main function */ @@ -2004,6 +2039,127 @@ function deleteTypesAfterProcessing(docsDir) { } } +/** + * Load method-order.json config. + * Keys are MDX base filenames (e.g. "entities"), values are ordered method name arrays. + */ +function loadMethodOrderConfig() { + if (!fs.existsSync(METHOD_ORDER_PATH)) return {}; + return JSON.parse(fs.readFileSync(METHOD_ORDER_PATH, "utf-8")); +} + +/** + * Reorder method blocks inside an MDX file according to a given ordered list. + * + * The file is split on `***` horizontal-rule separators into sections. Sections + * whose first `### name()` heading matches an entry in `orderedNames` are + * reordered accordingly; unlisted method sections keep their relative order + * and appear after explicitly ordered ones. + * + * Type-definition sections that are displaced between the `## ... Methods` + * heading and the first method are relocated to after the footer (where + * `## Type Definitions` lives), so they appear under the correct TOC group. + */ +function reorderMethods(content, orderedNames) { + const sections = content.split(/\n*\*{3}\n*/); + const HR = "\n\n***\n\n"; + + const methodRe = /^### (\w+)\(\)/m; + const methodsHeadingRe = /^## .+ Methods$/m; + + const tagged = sections.map((text) => ({ + text, + methodName: (text.match(methodRe) || [])[1] || null, + hasMethodsHeading: methodsHeadingRe.test(text), + })); + + const firstMethodIdx = tagged.findIndex((t) => t.methodName); + if (firstMethodIdx === -1) return { content, modified: false }; + + let lastMethodIdx = -1; + for (let i = tagged.length - 1; i >= 0; i--) { + if (tagged[i].methodName) { + lastMethodIdx = i; + break; + } + } + + const headingIdx = tagged.findIndex((t) => t.hasMethodsHeading); + const displacedStart = headingIdx !== -1 ? headingIdx + 1 : firstMethodIdx; + + const header = tagged.slice(0, displacedStart); + const displaced = tagged.slice(displacedStart, firstMethodIdx); + const middle = tagged.slice(firstMethodIdx, lastMethodIdx + 1); + const footer = tagged.slice(lastMethodIdx + 1); + + const methods = middle.filter((t) => t.methodName); + const stray = middle.filter((t) => !t.methodName); + + const orderMap = new Map(orderedNames.map((n, i) => [n, i])); + const origOrder = methods.map((m) => m.methodName).join(","); + + methods.sort((a, b) => { + const ai = orderMap.has(a.methodName) + ? orderMap.get(a.methodName) + : orderedNames.length + methods.indexOf(a); + const bi = orderMap.has(b.methodName) + ? orderMap.get(b.methodName) + : orderedNames.length + methods.indexOf(b); + return ai - bi; + }); + + const newOrder = methods.map((m) => m.methodName).join(","); + if (origOrder === newOrder && displaced.length === 0 && stray.length === 0) { + return { content, modified: false }; + } + + const ordered = [ + ...header, + ...methods, + ...footer, + ...displaced, + ...stray, + ]; + + return { content: ordered.map((t) => t.text).join(HR), modified: true }; +} + +/** + * Apply method ordering to MDX files whose base name matches a key in method-order.json. + */ +function applyMethodOrdering(dir) { + const config = loadMethodOrderConfig(); + if (Object.keys(config).length === 0) return; + + 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()) { + applyMethodOrdering(entryPath); + } else if ( + entry.isFile() && + (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) + ) { + const baseName = path.basename(entry.name, path.extname(entry.name)); + const orderedNames = config[baseName]; + if (!orderedNames) continue; + + const content = fs.readFileSync(entryPath, "utf-8"); + const { content: updated, modified } = reorderMethods( + content, + orderedNames + ); + if (modified) { + fs.writeFileSync(entryPath, updated, "utf-8"); + console.log( + `Reordered methods: ${path.relative(DOCS_DIR, entryPath)}` + ); + } + } + } +} + function main() { console.log("Processing TypeDoc MDX files for Mintlify...\n"); @@ -2033,6 +2189,9 @@ function main() { // Clean up SortField signature specifically (before general signature cleanup) cleanupSortFieldSignature(DOCS_DIR); + // Fix truncated intersection types in ParamField type attributes + cleanupTruncatedParamTypes(DOCS_DIR); + // Clean up signatures: fix truncated generics, simplify keyof constraints, break long lines applySignatureCleanup(DOCS_DIR); @@ -2050,6 +2209,9 @@ function main() { // Group type definitions under a parent heading applyTypeDefinitionGrouping(DOCS_DIR); + // Reorder methods according to method-order.json + applyMethodOrdering(DOCS_DIR); + // Link type names in Type Declarations sections to their corresponding headings applyTypeDeclarationLinking(DOCS_DIR); diff --git a/scripts/mintlify-post-processing/method-order.json b/scripts/mintlify-post-processing/method-order.json new file mode 100644 index 0000000..90ac12b --- /dev/null +++ b/scripts/mintlify-post-processing/method-order.json @@ -0,0 +1,16 @@ +{ + "entities": [ + "get", + "list", + "filter", + "create", + "bulkCreate", + "importEntities", + "update", + "updateMany", + "bulkUpdate", + "delete", + "deleteMany", + "subscribe" + ] +} diff --git a/scripts/mintlify-post-processing/types-to-delete-after-processing.json b/scripts/mintlify-post-processing/types-to-delete-after-processing.json index 392e12e..b0df28e 100644 --- a/scripts/mintlify-post-processing/types-to-delete-after-processing.json +++ b/scripts/mintlify-post-processing/types-to-delete-after-processing.json @@ -2,5 +2,6 @@ "DeleteManyResult", "DeleteResult", "ImportResult", - "SortField" + "SortField", + "UpdateManyResult" ] diff --git a/scripts/mintlify-post-processing/types-to-expose.json b/scripts/mintlify-post-processing/types-to-expose.json index 380ba19..e90bfd2 100644 --- a/scripts/mintlify-post-processing/types-to-expose.json +++ b/scripts/mintlify-post-processing/types-to-expose.json @@ -22,5 +22,6 @@ "IntegrationsModule", "CoreIntegrations", "SortField", - "SsoModule" + "SsoModule", + "UpdateManyResult" ] diff --git a/src/modules/entities.types.ts b/src/modules/entities.types.ts index c0e9a66..6da0f54 100644 --- a/src/modules/entities.types.ts +++ b/src/modules/entities.types.ts @@ -45,7 +45,7 @@ export interface DeleteManyResult { } /** - * Result returned when updating multiple entities via a query. + * Result returned when updating multiple entities using a query. */ export interface UpdateManyResult { /** Whether the operation was successful. */ @@ -307,6 +307,11 @@ export interface EntityHandler { * Updates a record by ID with the provided data. Only the fields * included in the data object will be updated. * + * To update a single record by ID, use this method. To apply the same + * update to many records matching a query, use {@linkcode updateMany | updateMany()}. + * To update multiple specific records with different data each, use + * {@linkcode bulkUpdate | bulkUpdate()}. + * * @param id - The unique identifier of the record to update. * @param data - Object containing the fields to update. * @returns Promise resolving to the updated record. @@ -392,29 +397,39 @@ export interface EntityHandler { bulkCreate(data: Partial[]): Promise; /** - * Updates multiple records matching a query using a MongoDB update operator. + * Applies the same update to all records that match a query. * - * Applies the same update operation to all records matching the query. - * The `data` parameter must contain one or more MongoDB update operators - * (e.g., `$set`, `$inc`, `$push`). Multiple operators can be combined in a - * single call, but each field may only appear in one operator. + * Use this when you need to make the same change across all records that + * match specific criteria. For example, you could set every completed order + * to "archived", or increment a counter on all active users. * - * Results are batched in groups of up to 500 — when `has_more` is `true` + * Results are batched in groups of up to 500. When `has_more` is `true` * in the response, call `updateMany` again with the same query to update - * the next batch. - * - * @param query - Query object to filter which records to update. Records matching all - * specified criteria will be updated. - * @param data - Update operation object containing one or more MongoDB update operators. + * the next batch. Make sure the query excludes already-updated records + * so you don't re-process the same entities on each iteration. For + * example, filter by `status: 'pending'` when setting status to `'processed'`. + * + * To update a single record by ID, use {@linkcode update | update()} instead. To update + * multiple specific records with different data each, use {@linkcode bulkUpdate | bulkUpdate()}. + * + * @param query - Query object to filter which records to update. Use field-value + * pairs for exact matches, or + * [MongoDB query operators](https://www.mongodb.com/docs/manual/reference/operator/query/) + * for advanced filtering. Supported query operators include `$eq`, `$ne`, `$gt`, + * `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$nor`, + * `$exists`, `$regex`, `$all`, `$elemMatch`, and `$size`. + * @param data - Update operation object containing one or more + * [MongoDB update operators](https://www.mongodb.com/docs/manual/reference/operator/update/). * Each field may only appear in one operator per call. - * Supported operators: `$set`, `$rename`, `$unset`, `$inc`, `$mul`, `$min`, `$max`, - * `$currentDate`, `$addToSet`, `$push`, `$pull`. + * Supported update operators include `$set`, `$rename`, `$unset`, `$inc`, `$mul`, `$min`, `$max`, + * `$currentDate`, `$addToSet`, `$push`, and `$pull`. * @returns Promise resolving to the update result. * * @example * ```typescript - * // Set status to 'archived' for all completed records - * const result = await base44.entities.MyEntity.updateMany( + * // Basic usage + * // Archive all completed orders + * const result = await base44.entities.Order.updateMany( * { status: 'completed' }, * { $set: { status: 'archived' } } * ); @@ -423,8 +438,19 @@ export interface EntityHandler { * * @example * ```typescript - * // Combine multiple operators in a single call - * const result = await base44.entities.MyEntity.updateMany( + * // Multiple query operators + * // Flag urgent items that haven't been handled yet + * const result = await base44.entities.Task.updateMany( + * { priority: { $in: ['high', 'critical'] }, status: { $ne: 'done' } }, + * { $set: { flagged: true } } + * ); + * ``` + * + * @example + * ```typescript + * // Multiple update operators + * // Close out sales records and bump the view count + * const result = await base44.entities.Deal.updateMany( * { category: 'sales' }, * { $set: { status: 'done' }, $inc: { view_count: 1 } } * ); @@ -432,11 +458,14 @@ export interface EntityHandler { * * @example * ```typescript - * // Handle batched updates for large datasets + * // Batched updates + * // Process all pending items in batches of 500. + * // The query filters by 'pending', so updated records (now 'processed') + * // are automatically excluded from the next batch. * let hasMore = true; * let totalUpdated = 0; * while (hasMore) { - * const result = await base44.entities.MyEntity.updateMany( + * const result = await base44.entities.Job.updateMany( * { status: 'pending' }, * { $set: { status: 'processed' } } * ); @@ -445,30 +474,48 @@ export interface EntityHandler { * } * ``` */ - updateMany(query: Partial, data: Record>): Promise; + updateMany( + query: Partial, + data: Record>, + ): Promise; /** - * Updates multiple records in a single request, each with its own update data. + * Updates the specified records in a single request, each with its own data. + * + * Use this when you already know which records to update and each one needs + * different field values. For example, you could update the status and amount + * on three separate invoices in one call. * - * Unlike `updateMany` which applies the same update to all matching records, - * `bulkUpdate` allows different updates for each record. Each item in the - * array must include an `id` field identifying which record to update. + * You can update up to 500 records per request. * - * **Note:** Maximum 500 items per request. + * To apply the same update to all records matching a query, use + * {@linkcode updateMany | updateMany()}. To update a single record by ID, use + * {@linkcode update | update()}. * - * @param data - Array of update objects (max 500). Each object must have an `id` field - * and any number of fields to update. - * @returns Promise resolving to an array of updated records. + * @param data - Array of objects to update. Each object must contain an `id` field identifying which record to update and any fields to change. + * @returns Promise resolving to an array of the updated records. * * @example * ```typescript - * // Update multiple records with different data - * const updated = await base44.entities.MyEntity.bulkUpdate([ - * { id: 'entity-1', status: 'paid', amount: 999 }, - * { id: 'entity-2', status: 'cancelled' }, - * { id: 'entity-3', name: 'Renamed Item' } + * // Basic usage + * // Update three invoices with different statuses and amounts + * const updated = await base44.entities.Invoice.bulkUpdate([ + * { id: 'inv-1', status: 'paid', amount: 999 }, + * { id: 'inv-2', status: 'cancelled' }, + * { id: 'inv-3', amount: 450 } * ]); * ``` + * + * @example + * ```typescript + * // More than 500 items + * // Reassign each task to a different owner in batches + * const allUpdates = reassignments.map(r => ({ id: r.taskId, owner: r.newOwner })); + * for (let i = 0; i < allUpdates.length; i += 500) { + * const batch = allUpdates.slice(i, i + 500); + * await base44.entities.Task.bulkUpdate(batch); + * } + * ``` */ bulkUpdate(data: (Partial & { id: string })[]): Promise;