diff --git a/.typedoc/custom-theme.mjs b/.typedoc/custom-theme.mjs index 1ce9c403d22..6d2458e6bbb 100644 --- a/.typedoc/custom-theme.mjs +++ b/.typedoc/custom-theme.mjs @@ -296,6 +296,7 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext { }, /** * This hides the "Type parameters" section, the declaration title, and the "Type declaration" heading from the output + * Unless the @includeType tag is present, in which case it shows the type in a parameter table format * @param {import('typedoc').DeclarationReflection} model * @param {{ headingLevel: number, nested?: boolean }} options */ diff --git a/.typedoc/extract-returns-and-params.mjs b/.typedoc/extract-returns-and-params.mjs new file mode 100644 index 00000000000..3dbda8cfdfc --- /dev/null +++ b/.typedoc/extract-returns-and-params.mjs @@ -0,0 +1,164 @@ +// @ts-check +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Extracts the "## Returns" section from a markdown file and writes it to a separate file. + * @param {string} filePath - The path to the markdown file + * @returns {boolean} True if a file was created + */ +function extractReturnsSection(filePath) { + const content = fs.readFileSync(filePath, 'utf-8'); + + // Find the "## Returns" section + const returnsStart = content.indexOf('## Returns'); + + if (returnsStart === -1) { + return false; // No Returns section found + } + + // Find the next heading after "## Returns" (or end of file) + const afterReturns = content.slice(returnsStart + 10); // Skip past "## Returns" + const nextHeadingMatch = afterReturns.match(/\n## /); + const returnsEnd = + nextHeadingMatch && typeof nextHeadingMatch.index === 'number' + ? returnsStart + 10 + nextHeadingMatch.index + : content.length; + + // Extract the Returns section and trim trailing whitespace + const returnsContent = content.slice(returnsStart, returnsEnd).trimEnd(); + + // Generate the new filename: use-auth.mdx -> use-auth-return.mdx + const fileName = path.basename(filePath, '.mdx'); + const dirName = path.dirname(filePath); + const newFilePath = path.join(dirName, `${fileName}-return.mdx`); + + // Write the extracted Returns section to the new file + fs.writeFileSync(newFilePath, returnsContent, 'utf-8'); + + console.log(`[extract-returns] Created ${path.relative(process.cwd(), newFilePath)}`); + return true; +} + +/** + * Extracts the "## Parameters" section from a markdown file and writes it to a separate file. + * @param {string} filePath - The path to the markdown file + * @param {string} dirName - The directory containing the files + * @returns {boolean} True if a file was created + */ +function extractParametersSection(filePath, dirName) { + const content = fs.readFileSync(filePath, 'utf-8'); + const fileName = path.basename(filePath, '.mdx'); + + // Always use -params suffix + const suffix = '-params'; + const targetFileName = `${fileName}${suffix}.mdx`; + const propsFileName = `${fileName}-props.mdx`; + + // Delete any existing -props file (TypeDoc-generated) + const propsFilePath = path.join(dirName, propsFileName); + if (fs.existsSync(propsFilePath)) { + fs.unlinkSync(propsFilePath); + console.log(`[extract-returns] Deleted ${path.relative(process.cwd(), propsFilePath)}`); + } + + // Find the "## Parameters" section + const paramsStart = content.indexOf('## Parameters'); + + if (paramsStart === -1) { + return false; // No Parameters section found + } + + // Find the next heading after "## Parameters" (or end of file) + const afterParams = content.slice(paramsStart + 13); // Skip past "## Parameters" + const nextHeadingMatch = afterParams.match(/\n## /); + const paramsEnd = + nextHeadingMatch && typeof nextHeadingMatch.index === 'number' + ? paramsStart + 13 + nextHeadingMatch.index + : content.length; + + // Extract the Parameters section and trim trailing whitespace + const paramsContent = content.slice(paramsStart, paramsEnd).trimEnd(); + + // Write to new file + const newFilePath = path.join(dirName, targetFileName); + fs.writeFileSync(newFilePath, paramsContent, 'utf-8'); + + console.log(`[extract-returns] Created ${path.relative(process.cwd(), newFilePath)}`); + return true; +} + +/** + * Recursively reads all .mdx files in a directory, excluding generated files + * @param {string} dir - The directory to read + * @returns {string[]} Array of file paths + */ +function getAllMdxFiles(dir) { + /** @type {string[]} */ + const files = []; + + if (!fs.existsSync(dir)) { + return files; + } + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + files.push(...getAllMdxFiles(fullPath)); + } else if (entry.isFile() && entry.name.endsWith('.mdx')) { + // Exclude generated files + const isGenerated = + entry.name.endsWith('-return.mdx') || entry.name.endsWith('-params.mdx') || entry.name.endsWith('-props.mdx'); + if (!isGenerated) { + files.push(fullPath); + } + } + } + + return files; +} + +/** + * Main function to process all clerk-react files + */ +function main() { + const packages = ['clerk-react']; + const dirs = packages.map(folder => path.join(__dirname, 'temp-docs', folder)); + + for (const dir of dirs) { + if (!fs.existsSync(dir)) { + console.log(`[extract-returns] ${dir} directory not found, skipping extraction`); + continue; + } + + const mdxFiles = getAllMdxFiles(dir); + console.log(`[extract-returns] Processing ${mdxFiles.length} files in ${dir}/`); + + let returnsCount = 0; + let paramsCount = 0; + + for (const filePath of mdxFiles) { + // Extract Returns sections + if (extractReturnsSection(filePath)) { + returnsCount++; + } + + // Extract Parameters sections + if (extractParametersSection(filePath, dir)) { + paramsCount++; + } + } + + console.log(`[extract-returns] Extracted ${returnsCount} Returns sections`); + console.log(`[extract-returns] Extracted ${paramsCount} Parameters sections`); + } +} + +main(); diff --git a/package.json b/package.json index 1320877a2a8..a4f9e4239e8 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "test:typedoc": "pnpm typedoc:generate && cd ./.typedoc && vitest run", "turbo:clean": "turbo daemon clean", "typedoc:generate": "pnpm build:declarations && pnpm typedoc:generate:skip-build", - "typedoc:generate:skip-build": "typedoc --tsconfig tsconfig.typedoc.json && rm -rf .typedoc/docs && mv .typedoc/temp-docs .typedoc/docs", + "typedoc:generate:skip-build": "typedoc --tsconfig tsconfig.typedoc.json && node .typedoc/extract-returns-and-params.mjs && rm -rf .typedoc/docs && mv .typedoc/temp-docs .typedoc/docs", "version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false", "version-packages:canary": "./scripts/canary.mjs", "version-packages:snapshot": "./scripts/snapshot.mjs", diff --git a/packages/react/typedoc.json b/packages/react/typedoc.json index 2d417a9b83f..4eab6484506 100644 --- a/packages/react/typedoc.json +++ b/packages/react/typedoc.json @@ -1,4 +1,4 @@ { "$schema": "https://typedoc.org/schema.json", - "entryPoints": ["./src/index.ts", "./src/experimental.ts"] + "entryPoints": ["./src/index.ts", "./src/experimental.ts", "./src/hooks/*.{ts,tsx}"] } diff --git a/packages/shared/src/react/hooks/useReverification.ts b/packages/shared/src/react/hooks/useReverification.ts index dadbe438104..31f3edbdf88 100644 --- a/packages/shared/src/react/hooks/useReverification.ts +++ b/packages/shared/src/react/hooks/useReverification.ts @@ -11,6 +11,9 @@ import { useSafeLayoutEffect } from './useSafeLayoutEffect'; const CLERK_API_REVERIFICATION_ERROR_CODE = 'session_reverification_required'; +/** + * + */ async function resolveResult(result: Promise | T): Promise> { try { const r = await result; @@ -42,6 +45,7 @@ type NeedsReverificationParameters = { /** * The optional options object. + * * @interface */ type UseReverificationOptions = { @@ -51,7 +55,6 @@ type UseReverificationOptions = { * @param cancel - A function that will cancel the reverification process. * @param complete - A function that will retry the original request after reverification. * @param level - The level returned with the reverification hint. - * */ onNeedsReverification?: (properties: NeedsReverificationParameters) => void; }; @@ -79,7 +82,13 @@ type CreateReverificationHandlerParams = UseReverificationOptions & { telemetry: Clerk['telemetry']; }; +/** + * + */ function createReverificationHandler(params: CreateReverificationHandlerParams) { + /** + * + */ function assertReverification Promise | undefined>( fetcher: Fetcher, ): (...args: Parameters) => Promise>>> { @@ -191,7 +200,6 @@ function createReverificationHandler(params: CreateReverificationHandlerParams) * return * } * ``` - * */ export const useReverification: UseReverification = (fetcher, options) => { const { __internal_openReverification, telemetry } = useClerk(); diff --git a/packages/shared/src/react/hooks/useSession.ts b/packages/shared/src/react/hooks/useSession.ts index 7427e53baf3..df656dc6042 100644 --- a/packages/shared/src/react/hooks/useSession.ts +++ b/packages/shared/src/react/hooks/useSession.ts @@ -15,7 +15,6 @@ const hookName = `useSession`; * @function * * @param [options] - An object containing options for the `useSession()` hook. - * * @example * ### Access the `Session` object * diff --git a/packages/shared/src/react/hooks/useSubscription.tsx b/packages/shared/src/react/hooks/useSubscription.tsx index 53c48df8c08..a2197ebf0f5 100644 --- a/packages/shared/src/react/hooks/useSubscription.tsx +++ b/packages/shared/src/react/hooks/useSubscription.tsx @@ -1,4 +1,4 @@ -import type { EnvironmentResource, ForPayerType } from '@clerk/types'; +import type { BillingSubscriptionResource, EnvironmentResource, ForPayerType } from '@clerk/types'; import { useCallback } from 'react'; import { eventMethodCalled } from '../../telemetry/events'; @@ -12,16 +12,65 @@ import { const hookName = 'useSubscription'; -type UseSubscriptionParams = { +/** + * @interface + */ +export type UseSubscriptionParams = { + /** + * Specifies whether to fetch subscription for an organization or user. + * + * @default 'user' + */ for?: ForPayerType; /** - * If `true`, the previous data will be kept in the cache until new data is fetched. + * If `true`, the previous data will be kept in the cache until new data is fetched. This helps prevent layout shifts. * * @default false */ keepPreviousData?: boolean; }; +/** + * @interface + */ +export type UseSubscriptionReturn = + | { + /** + * A boolean that indicates whether the initial data is still being fetched. + */ + data: null; + /** + * A boolean that indicates whether the initial data is still being fetched. + */ + isLoaded: false; + /** + * A boolean that indicates whether any request is still in flight, including background updates. + */ + isFetching: false; + /** + * Any error that occurred during the data fetch, or `null` if no error occurred. + */ + error: null; + /** + * Function to manually trigger a refresh of the subscription data. + */ + revalidate: () => Promise; + } + | { + data: BillingSubscriptionResource; + isLoaded: true; + isFetching: true; + error: Error; + revalidate: () => Promise; + } + | { + data: BillingSubscriptionResource | null; + isLoaded: boolean; + isFetching: boolean; + error: Error | null; + revalidate: () => Promise; + }; + /** * @internal * diff --git a/packages/types/src/hooks.ts b/packages/types/src/hooks.ts index 6c814689fe3..d460c75d027 100644 --- a/packages/types/src/hooks.ts +++ b/packages/types/src/hooks.ts @@ -142,7 +142,7 @@ export type UseSignInReturn = }; /** - * @inline + * @inline */ export type UseSignUpReturn = | { diff --git a/packages/types/src/session.ts b/packages/types/src/session.ts index a189d85c73c..d873a48f739 100644 --- a/packages/types/src/session.ts +++ b/packages/types/src/session.ts @@ -43,10 +43,14 @@ type DisallowSystemPermissions

= P extends `${OrganizationSyst ? 'System permissions are not included in session claims and cannot be used on the server-side' : P; -/** @inline */ +/** + * @inline + */ export type CheckAuthorizationFn = (isAuthorizedParams: Params) => boolean; -/** @inline */ +/** + * @inline + */ export type CheckAuthorizationWithCustomPermissions = CheckAuthorizationFn;