Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/dry-poets-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
'@clerk/types': patch
---

Improve JSDoc documentation
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,5 @@ scripts/.env
!scripts/.env.example

# typedoc
.typedoc
.typedoc/docs
.typedoc/docs.json
29 changes: 29 additions & 0 deletions .typedoc/custom-plugin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @ts-check
import { MarkdownRendererEvent } from 'typedoc-plugin-markdown';

/**
* @param {string} str
*/
function toKebabCase(str) {
return str.replace(/((?<=[a-z\d])[A-Z]|(?<=[A-Z\d])[A-Z](?=[a-z]))/g, '-$1').toLowerCase();
}

/**
* @param {import('typedoc-plugin-markdown').MarkdownApplication} app
*/
export function load(app) {
app.renderer.on(MarkdownRendererEvent.BEGIN, output => {
// Do not output README.mdx files
output.urls = output.urls
?.filter(e => !e.url.endsWith('README.mdx'))
.map(e => {
// Convert URLs (by default camelCase) to kebab-case
const kebabUrl = toKebabCase(e.url);

e.url = kebabUrl;
e.model.url = kebabUrl;

return e;
});
});
}
261 changes: 261 additions & 0 deletions .typedoc/custom-theme.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// @ts-check
import { ReflectionKind } from 'typedoc';
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';

/**
* @param {import('typedoc-plugin-markdown').MarkdownApplication} app
*/
export function load(app) {
app.renderer.defineTheme('clerkTheme', ClerkMarkdownTheme);
}

class ClerkMarkdownTheme extends MarkdownTheme {
/**
* @param {import('typedoc-plugin-markdown').MarkdownPageEvent} page
*/
getRenderContext(page) {
return new ClerkMarkdownThemeContext(this, page, this.application.options);
}
}

/**
* Our custom Clerk theme
* @extends MarkdownThemeContext
*/
class ClerkMarkdownThemeContext extends MarkdownThemeContext {
/**
* @param {MarkdownTheme} theme
* @param {import('typedoc-plugin-markdown').MarkdownPageEvent} page
* @param {MarkdownTheme["application"]["options"]} options
*/
constructor(theme, page, options) {
super(theme, page, options);

const superPartials = this.partials;

this.partials = {
...superPartials,
/**
* Copied from default theme / source code. This hides the return type from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureReturns.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number }} options
*/
signatureReturns: (model, options) => {
const md = [];

/**
* @type any
*/
const modelType = model.type;
/**
* @type {import('typedoc').DeclarationReflection}
*/
const typeDeclaration = modelType?.declaration;

md.push(heading(options.headingLevel, this.i18n.theme_returns()));

if (model.comment?.blockTags.length) {
const tags = model.comment.blockTags
.filter(tag => tag.tag === '@returns')
.map(tag => this.helpers.getCommentParts(tag.content));
md.push(tags.join('\n\n'));
}

if (typeDeclaration?.signatures) {
typeDeclaration.signatures.forEach(signature => {
md.push(
this.partials.signature(signature, {
headingLevel: options.headingLevel + 1,
nested: true,
}),
);
});
}

if (typeDeclaration?.children) {
md.push(
this.partials.typeDeclaration(typeDeclaration, {
headingLevel: options.headingLevel,
}),
);
}

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section and the signature title from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signature.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number, nested?: boolean, accessor?: string, multipleSignatures?: boolean }} options
*/
signature: (model, options) => {
const md = [];

if (!options.nested && model.sources && !this.options.getValue('disableSources')) {
md.push(this.partials.sources(model));
}

let modelComments = options.multipleSignatures ? model.comment : model.comment || model.parent?.comment;

if (modelComments && model.parent?.comment?.summary && !options.multipleSignatures) {
modelComments = Object.assign(modelComments, {
summary: model.parent.comment.summary,
});
}

if (modelComments && model.parent?.comment?.blockTags) {
modelComments.blockTags = [...(model.parent?.comment?.blockTags || []), ...(model.comment?.blockTags || [])];
}

if (modelComments) {
md.push(
this.partials.comment(modelComments, {
headingLevel: options.headingLevel,
showTags: false,
showSummary: true,
}),
);
}

if (!options.multipleSignatures && model.parent?.documents) {
md.push(
this.partials.documents(model?.parent, {
headingLevel: options.headingLevel,
}),
);
}

if (model.parameters?.length) {
md.push(heading(options.headingLevel, this.internationalization.kindPluralString(ReflectionKind.Parameter)));
if (this.helpers.useTableFormat('parameters')) {
md.push(this.partials.parametersTable(model.parameters));
} else {
md.push(
this.partials.parametersList(model.parameters, {
headingLevel: options.headingLevel,
}),
);
}
}

if (model.type) {
md.push(
this.partials.signatureReturns(model, {
headingLevel: options.headingLevel,
}),
);
}

if (modelComments) {
md.push(
this.partials.comment(modelComments, {
headingLevel: options.headingLevel,
showTags: true,
showSummary: false,
}),
);
}

md.push(this.partials.inheritance(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.memberWithGroups.ts#L58
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number }} options
*/
memberWithGroups: (model, options) => {
const md = [];

if (
![ReflectionKind.Module, ReflectionKind.Namespace].includes(model.kind) &&
model.sources &&
!this.options.getValue('disableSources')
) {
md.push(this.partials.sources(model));
}

if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: options.headingLevel,
}),
);
}

if (model.typeHierarchy?.next) {
md.push(
this.partials.hierarchy(model.typeHierarchy, {
headingLevel: options.headingLevel,
}),
);
}

if (model.implementedTypes?.length) {
md.push(heading(options.headingLevel, this.i18n.theme_implements()));
md.push(
unorderedList(model.implementedTypes.map(implementedType => this.partials.someType(implementedType))),
);
}

if (model.kind === ReflectionKind.Class && model.categories?.length) {
model.groups
?.filter(group => group.title === this.i18n.kind_plural_constructor())
.forEach(group => {
md.push(heading(options.headingLevel, this.i18n.kind_plural_constructor()));
group.children.forEach(child => {
md.push(
this.partials.constructor(/** @type {import('typedoc').DeclarationReflection} */ (child), {
headingLevel: options.headingLevel + 1,
}),
);
});
});
}

if ('signatures' in model && model.signatures?.length) {
model.signatures.forEach(signature => {
md.push(
this.partials.signature(signature, {
headingLevel: options.headingLevel,
}),
);
});
}

if (model.indexSignatures?.length) {
md.push(heading(options.headingLevel, this.i18n.theme_indexable()));
model.indexSignatures.forEach(indexSignature => {
md.push(this.partials.indexSignature(indexSignature));
});
}

md.push(this.partials.body(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
};
}
}

/**
* Returns a heading in markdown format
* @param {number} level The level of the heading
* @param {string} text The text of the heading
*/
function heading(level, text) {
level = level > 6 ? 6 : level;
return `${[...Array(level)].map(() => '#').join('')} ${text}`;
}

/**
* Create an unordered list from an array of items
* @param {string[]} items
* @returns
*/
function unorderedList(items) {
return items.map(item => `- ${item}`).join('\n');
}
9 changes: 9 additions & 0 deletions .typedoc/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"allowJs": true,
"noEmit": true,
"moduleResolution": "node16",
"module": "Node16"
}
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@
"tsup": "catalog:repo",
"turbo": "^2.0.14",
"turbo-ignore": "^2.0.6",
"typedoc": "0.27.6",
"typedoc-plugin-markdown": "4.4.1",
"typedoc": "0.27.9",
"typedoc-plugin-markdown": "4.4.2",
"typedoc-plugin-replace-text": "4.1.0",
"typescript": "catalog:repo",
"typescript-eslint": "8.21.0",
"uuid": "8.3.2",
Expand Down
5 changes: 5 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ import { createGetToken, createSignOut } from './utils';
/**
* The `useAuth()` hook provides access to the current user's authentication state and methods to manage the active session.
*
* @param [initialAuthState] - An object containing the initial authentication state. If not provided, the hook will attempt to derive the state from the context.
*
* @example
*
* > [!NOTE]
* > For frameworks like Next.js that support multiple ways of rendering its content, it might be preferable to use the [`auth()`](https://clerk.com/docs/references/nextjs/auth) helper instead of `useAuth()`. This depends on if you want to use React Server Components, SSR, or client-side rendering. Learn more in the [rendering modes](https://clerk.com/docs/references/nextjs/rendering-modes) guide. If you only want to access data on the client-side, `useAuth()` is sufficient.
*
* The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource.
*
* ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }}
Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/hooks/useSignIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The `useSignIn()` hook can also be used to build fully custom sign-in flows, if Clerk's prebuilt components don't meet your specific needs or if you require more control over the authentication flow. Different sign-in flows include email and password, email and phone codes, email links, and multifactor (MFA). To learn more about using the `useSignIn()` hook to create custom flows, see the [custom flow guides](https://clerk.com/docs/custom-flows/overview).
*
* ```
* ```
* ```empty```
*/
export const useSignIn = (): UseSignInReturn => {
useAssertWrappedByClerkProvider('useSignIn');
Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/hooks/useSignUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The `useSignUp()` hook can also be used to build fully custom sign-up flows, if Clerk's prebuilt components don't meet your specific needs or if you require more control over the authentication flow. Different sign-up flows include email and password, email and phone codes, email links, and multifactor (MFA). To learn more about using the `useSignUp()` hook to create custom flows, see the [custom flow guides](https://clerk.com/docs/custom-flows/overview).
*
* ```tsx
* ```
* ```empty```
*/
export const useSignUp = (): UseSignUpReturn => {
useAssertWrappedByClerkProvider('useSignUp');
Expand Down
15 changes: 8 additions & 7 deletions packages/react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,27 @@ export type IsomorphicClerkOptions = Without<ClerkOptions, 'isSatellite'> & {
*/
clerkJSVersion?: string;
/**
* The Clerk publishable key for your instance
* @note This can be found in your Clerk Dashboard on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) page
* The Clerk Publishable Key for your instance. This can be found on the [API keys](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard.
*/
publishableKey: string;
/**
* This nonce value will be passed through to the `@clerk/clerk-js` script tag.
* @note You can use this to implement [strict-dynamic CSP](https://clerk.com/docs/security/clerk-csp#implementing-a-strict-dynamic-csp)
* This nonce value will be passed through to the `@clerk/clerk-js` script tag. Use it to implement a [strict-dynamic CSP](https://clerk.com/docs/security/clerk-csp#implementing-a-strict-dynamic-csp). Requires the `dynamic` prop to also be set.
*/
nonce?: string;
} & MultiDomainAndOrProxy;

/**
* @interface
*/
export type ClerkProviderProps = IsomorphicClerkOptions & {
children: React.ReactNode;
/**
* Provide an initial state of the Clerk client during server-side rendering (SSR)
* Provide an initial state of the Clerk client during server-side rendering. You don't need to set this value yourself unless you're [developing an SDK](https://clerk.com/docs/references/sdk/overview).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

*/
initialState?: InitialState;
/**
* Indicates to silently fail the initialization process when the publishable keys is not provided, instead of throwing an error.
* Defaults to `false`.
* Indicates to silently fail the initialization process when the publishable keys is not provided, instead of throwing an error. Defaults to `false`.
* @internal
*/
__internal_bypassMissingPublishableKey?: boolean;
};
Expand Down
Loading
Loading