Skip to content

Commit 918e2e0

Browse files
authored
fix(backend): JSDoc for verifyWebook & verifyToken (#6060)
1 parent 7d680bc commit 918e2e0

File tree

9 files changed

+201
-10
lines changed

9 files changed

+201
-10
lines changed

.changeset/six-lions-wear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/backend': patch
3+
---
4+
5+
Improve JSDoc comments for verifyWebhook and verifyToken

.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
157157
"backend/organization-invitation.mdx",
158158
"backend/organization-membership-public-user-data.mdx",
159159
"backend/organization-membership.mdx",
160+
"backend/organization-sync-options.mdx",
160161
"backend/organization-sync-target.mdx",
161162
"backend/organization.mdx",
162163
"backend/paginated-resource-response.mdx",

.typedoc/custom-plugin.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const LINK_REPLACEMENTS = [
5252
['phone-number', '/docs/references/backend/types/backend-phone-number'],
5353
['saml-account', '/docs/references/backend/types/backend-saml-account'],
5454
['web3-wallet', '/docs/references/backend/types/backend-web3-wallet'],
55+
['verify-token-options', '#verify-token-options'],
5556
];
5657

5758
/**

.typedoc/custom-theme.mjs

Lines changed: 168 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @ts-check
2-
import { ReflectionType, UnionType } from 'typedoc';
2+
import { ReflectionKind, ReflectionType, UnionType } from 'typedoc';
33
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';
44

55
/**
@@ -47,22 +47,111 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
4747
this.partials = {
4848
...superPartials,
4949
/**
50-
* This hides the "Type parameters" section and the signature title from the output
50+
* This hides the "Type parameters" section and the signature title from the output (by default). Shows the signature title if the `@displayFunctionSignature` tag is present.
5151
* @param {import('typedoc').SignatureReflection} model
5252
* @param {{ headingLevel: number, nested?: boolean, accessor?: string, multipleSignatures?: boolean; hideTitle?: boolean }} options
5353
*/
5454
signature: (model, options) => {
55+
const displayFunctionSignatureTag = model.comment?.getTag('@displayFunctionSignature');
56+
const paramExtensionTag = model.comment?.getTag('@paramExtension');
57+
const delimiter = '\n\n';
58+
5559
const customizedOptions = {
5660
...options,
57-
hideTitle: true,
61+
// Hide the title by default, only show it if the `@displayFunctionSignature` tag is present
62+
hideTitle: !displayFunctionSignatureTag,
5863
};
5964
const customizedModel = model;
6065
customizedModel.typeParameters = undefined;
6166

62-
const output = superPartials.signature(customizedModel, customizedOptions);
67+
let output = superPartials.signature(customizedModel, customizedOptions);
68+
69+
// If there are extra tags, split the output by the delimiter and do the work
70+
if (displayFunctionSignatureTag || paramExtensionTag) {
71+
let splitOutput = output.split(delimiter);
72+
73+
if (displayFunctionSignatureTag) {
74+
// Change position of the 0 index and 2 index of the output
75+
// This way the function signature is below the description
76+
splitOutput = swap(splitOutput, 0, 2);
77+
}
78+
79+
if (paramExtensionTag) {
80+
const stuff = this.helpers.getCommentParts(paramExtensionTag.content);
81+
82+
// Find the index of the item that contains '## Returns'
83+
const index = splitOutput.findIndex(item => item.includes('## Returns'));
84+
85+
// If the index is found, insert the stuff before it
86+
if (index !== -1) {
87+
splitOutput.splice(index, 0, stuff);
88+
}
89+
}
90+
91+
// Join the output again
92+
output = splitOutput.join(delimiter);
93+
}
6394

6495
return output;
6596
},
97+
/**
98+
* If `signature` has @displayFunctionSignature tag, the function will run `signatureTitle`. We want to use a completely custom code block here.
99+
* @param {import('typedoc').SignatureReflection} model
100+
* @param {{ accessor?: string; includeType?: boolean }} [options]
101+
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/c83cff97b72ab25b224463ceec118c34e940cb8a/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureTitle.ts
102+
*/
103+
signatureTitle: (model, options) => {
104+
/**
105+
* @type {string[]}
106+
*/
107+
const md = [];
108+
109+
const keyword = this.helpers.getKeyword(model.parent.kind);
110+
111+
if (this.helpers.isGroupKind(model.parent) && keyword) {
112+
md.push(keyword + ' ');
113+
}
114+
115+
if (options?.accessor) {
116+
md.push(options?.accessor + ' ');
117+
}
118+
119+
if (model.parent) {
120+
const flagsString = this.helpers.getReflectionFlags(model.parent?.flags);
121+
if (flagsString.length) {
122+
md.push(this.helpers.getReflectionFlags(model.parent.flags) + ' ');
123+
}
124+
}
125+
126+
if (!['__call', '__type'].includes(model.name)) {
127+
/**
128+
* @type {string[]}
129+
*/
130+
const name = [];
131+
if (model.kind === ReflectionKind.ConstructorSignature) {
132+
name.push('new');
133+
}
134+
name.push(escapeChars(model.name));
135+
md.push(name.join(' '));
136+
}
137+
138+
if (model.typeParameters) {
139+
md.push(
140+
`${this.helpers.getAngleBracket('<')}${model.typeParameters
141+
.map(typeParameter => typeParameter.name)
142+
.join(', ')}${this.helpers.getAngleBracket('>')}`,
143+
);
144+
}
145+
146+
md.push(this.partials.signatureParameters(model.parameters || []));
147+
148+
if (model.type) {
149+
md.push(`: ${this.partials.someType(model.type)}`);
150+
}
151+
152+
const result = md.join('');
153+
return codeBlock(result);
154+
},
66155
/**
67156
* This condenses the output if only a "simple" return type + `@returns` is given.
68157
* @param {import('typedoc').SignatureReflection} model
@@ -326,3 +415,78 @@ ${tabs}
326415
};
327416
}
328417
}
418+
419+
/**
420+
* @param {string} str - The string to unescape
421+
*/
422+
function unEscapeChars(str) {
423+
return str
424+
.replace(/(`[^`]*?)\\*([^`]*?`)/g, (_match, p1, p2) => `${p1}${p2.replace(/\*/g, '\\*')}`)
425+
.replace(/\\\\/g, '\\')
426+
.replace(/(?<!\\)\*/g, '')
427+
.replace(/\\</g, '<')
428+
.replace(/\\>/g, '>')
429+
.replace(/&lt;/g, '<')
430+
.replace(/&gt;/g, '>')
431+
.replace(/\\_/g, '_')
432+
.replace(/\\{/g, '{')
433+
.replace(/\\}/g, '}')
434+
.replace(/``.*?``|(?<!\\)`/g, match => (match.startsWith('``') ? match : ''))
435+
.replace(/`` /g, '')
436+
.replace(/ ``/g, '')
437+
.replace(/\\`/g, '`')
438+
.replace(/\\\*/g, '*')
439+
.replace(/\\\|/g, '|')
440+
.replace(/\\\]/g, ']')
441+
.replace(/\\\[/g, '[')
442+
.replace(/\[([^[\]]*)\]\((.*?)\)/gm, '$1');
443+
}
444+
445+
/**
446+
* @param {string} content
447+
*/
448+
function codeBlock(content) {
449+
/**
450+
* @param {string} content
451+
*/
452+
const trimLastLine = content => {
453+
const lines = content.split('\n');
454+
return lines.map((line, index) => (index === lines.length - 1 ? line.trim() : line)).join('\n');
455+
};
456+
const trimmedContent =
457+
content.endsWith('}') || content.endsWith('};') || content.endsWith('>') || content.endsWith('>;')
458+
? trimLastLine(content)
459+
: content;
460+
return '```ts\n' + unEscapeChars(trimmedContent) + '\n```';
461+
}
462+
463+
/**
464+
* @param {string} str
465+
*/
466+
function escapeChars(str) {
467+
return str
468+
.replace(/>/g, '\\>')
469+
.replace(/</g, '\\<')
470+
.replace(/{/g, '\\{')
471+
.replace(/}/g, '\\}')
472+
.replace(/_/g, '\\_')
473+
.replace(/`/g, '\\`')
474+
.replace(/\|/g, '\\|')
475+
.replace(/\[/g, '\\[')
476+
.replace(/\]/g, '\\]')
477+
.replace(/\*/g, '\\*');
478+
}
479+
480+
/**
481+
*
482+
* @param {string[]} arr
483+
* @param {number} i
484+
* @param {number} j
485+
* @returns
486+
*/
487+
function swap(arr, i, j) {
488+
let t = arr[i];
489+
arr[i] = arr[j];
490+
arr[j] = t;
491+
return arr;
492+
}

eslint.config.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,10 @@ export default tseslint.config([
432432
...pluginJsDoc.configs['flat/recommended-typescript'].rules,
433433
'jsdoc/check-examples': 'off',
434434
'jsdoc/informative-docs': 'warn',
435-
'jsdoc/check-tag-names': ['warn', { definedTags: ['inline', 'unionReturnHeadings'], typed: false }],
435+
'jsdoc/check-tag-names': [
436+
'warn',
437+
{ definedTags: ['inline', 'unionReturnHeadings', 'displayFunctionSignature', 'paramExtension'], typed: false },
438+
],
436439
'jsdoc/require-hyphen-before-param-description': 'warn',
437440
'jsdoc/require-description': 'warn',
438441
'jsdoc/require-description-complete-sentence': 'warn',

packages/backend/src/tokens/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ export type AuthenticateRequestOptions = {
6060
acceptsToken?: TokenType | TokenType[] | 'any';
6161
} & VerifyTokenOptions;
6262

63+
/**
64+
* @inline
65+
*/
6366
export type OrganizationSyncOptions = {
6467
/**
6568
* Specifies URL patterns that are organization-specific, containing an organization ID or slug as a path parameter. If a request matches this path, the organization identifier will be used to set that org as active.
@@ -68,8 +71,7 @@ export type OrganizationSyncOptions = {
6871
*
6972
* Patterns must have a path parameter named either `:id` (to match a Clerk organization ID) or `:slug` (to match a Clerk organization slug).
7073
*
71-
* > [!WARNING]
72-
* > If the organization can't be activated—either because it doesn't exist or the user lacks access—the previously active organization will remain unchanged. Components must detect this case and provide an appropriate error and/or resolution pathway, such as calling `notFound()` or displaying an [`<OrganizationSwitcher />`](https://clerk.com/docs/components/organization/organization-switcher).
74+
* If the organization can't be activated—either because it doesn't exist or the user lacks access—the previously active organization will remain unchanged. Components must detect this case and provide an appropriate error and/or resolution pathway, such as calling `notFound()` or displaying an [`<OrganizationSwitcher />`](https://clerk.com/docs/components/organization/organization-switcher).
7375
*
7476
* @example
7577
* ["/orgs/:slug", "/orgs/:slug/(.*)"]

packages/backend/src/tokens/verify.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ export type VerifyTokenOptions = Omit<VerifyJwtOptions, 'key'> &
3939
* @param token - The token to verify.
4040
* @param options - Options for verifying the token.
4141
*
42+
* @displayFunctionSignature
43+
*
44+
* @paramExtension
45+
*
46+
* ### `VerifyTokenOptions`
47+
*
48+
* It is recommended to set these options as [environment variables](/docs/deployments/clerk-environment-variables#api-and-sdk-configuration) where possible, and then pass them to the function. For example, you can set the `secretKey` option using the `CLERK_SECRET_KEY` environment variable, and then pass it to the function like this: `createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY })`.
49+
*
50+
* > [!WARNING]
51+
* You must provide either `jwtKey` or `secretKey`.
52+
*
53+
* <Typedoc src="backend/verify-token-options" />
54+
*
4255
* @example
4356
*
4457
* The following example demonstrates how to use the [JavaScript Backend SDK](https://clerk.com/docs/references/backend/overview) to verify the token signature.

packages/backend/src/webhooks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export * from './api/resources/Webhooks';
2828
* @param request - The request object.
2929
* @param options - Optional configuration object.
3030
*
31+
* @displayFunctionSignature
32+
*
3133
* @example
3234
* See the [guide on syncing data](https://clerk.com/docs/webhooks/sync-data) for more comprehensive and framework-specific examples that you can copy and paste into your app.
3335
*

typedoc.config.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import fs from 'node:fs';
33
import { OptionDefaults } from 'typedoc';
44

55
const IGNORE_LIST = ['.DS_Store', 'dev-cli', 'expo-passkeys', 'testing', 'themes', 'upgrade'];
6-
const CUSTOM_TAGS = ['@unionReturnHeadings'];
6+
const CUSTOM_BLOCK_TAGS = ['@unionReturnHeadings', '@displayFunctionSignature', '@paramExtension'];
77

88
/**
99
* Return an array of relative paths to all folders in the "packages" folder to be used for the "entryPoints" option.
@@ -90,7 +90,7 @@ const config = {
9090
theme: 'clerkTheme',
9191
router: 'clerk-router',
9292
readme: 'none',
93-
notRenderedTags: [...OptionDefaults.notRenderedTags, ...CUSTOM_TAGS],
93+
notRenderedTags: [...OptionDefaults.notRenderedTags, ...CUSTOM_BLOCK_TAGS],
9494
packageOptions: {
9595
includeVersion: false,
9696
excludePrivate: true,
@@ -100,7 +100,7 @@ const config = {
100100
excludeInternal: true,
101101
excludeNotDocumented: true,
102102
gitRevision: 'main',
103-
blockTags: [...OptionDefaults.blockTags, ...CUSTOM_TAGS],
103+
blockTags: [...OptionDefaults.blockTags, ...CUSTOM_BLOCK_TAGS],
104104
modifierTags: [...OptionDefaults.modifierTags],
105105
exclude: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
106106
readme: 'none',

0 commit comments

Comments
 (0)