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
2 changes: 2 additions & 0 deletions .changeset/thick-singers-pick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### `createDomain()`

Creates a new domain.

Returns an [`OrganizationDomainResource`](/docs/reference/types/organization-domain-resource) object.

> [!WARNING]
> You must have [**Verified domains**](/docs/guides/organizations/add-members/verified-domains) enabled in your app's settings in the Clerk Dashboard.

```typescript
function createDomain(domainName: string, params?: Pick<CreateOrganizationDomainParams, "enrollmentMode">): Promise<OrganizationDomainResource>
```

#### Parameters


| Parameter | Type | Description |
| ------ | ------ | ------ |
| `domainName` | `string` | The name of the domain to create. |
| `params?` | `Pick`\<`CreateOrganizationDomainParams`, `"enrollmentMode"`\> | Optional parameters, including the `enrollmentMode` to assign to the new domain. |
| `params?.enrollmentMode?` | <code>"manual_invitation" \| "automatic_invitation" \| "automatic_suggestion" \| "enterprise_sso"</code> | The enrollment mode that determines how matching users are added to the Organization. Defaults to `manual_invitation`. |
121 changes: 121 additions & 0 deletions .typedoc/__tests__/custom-theme.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { ReflectionKind, type Type } from 'typedoc';
import { describe, expect, it } from 'vitest';

import { getParameterObjectShapeDeclaration, getPickPropertyNames } from '../custom-theme.mjs';

const literal = (value: unknown) => ({ type: 'literal', value }) as unknown as Type;
const union = (...types: unknown[]) => ({ type: 'union', types }) as unknown as Type;

describe('getPickPropertyNames', () => {
it('returns the single key for a string literal', () => {
expect(getPickPropertyNames(literal('enrollmentMode'))).toEqual(['enrollmentMode']);
});

it('flattens a union of string literals', () => {
expect(getPickPropertyNames(union(literal('a'), literal('b')))).toEqual(['a', 'b']);
});

it('recurses into nested unions', () => {
expect(getPickPropertyNames(union(literal('a'), union(literal('b'), literal('c'))))).toEqual(['a', 'b', 'c']);
});

it('bails on a non-string literal key', () => {
expect(getPickPropertyNames(literal(0))).toBeUndefined();
});

it('bails when any union arm is not a string literal', () => {
expect(getPickPropertyNames(union(literal('a'), { type: 'reference', name: 'Foo' }))).toBeUndefined();
});
});

describe('getParameterObjectShapeDeclaration', () => {
const sourceInterface = (...names: string[]) => ({
reflection: {
kind: ReflectionKind.Interface,
name: 'Src',
children: names.map(name => ({ name })),
},
});

const pick = (source: unknown, keys: unknown) =>
({
type: 'reference',
name: 'Pick',
package: 'typescript',
typeArguments: [{ type: 'reference', ...(source as object) }, keys],
}) as unknown as Type;

it('does not resolve Pick sources whose declaration kind is unsupported', () => {
const sourceType = {
type: 'reference',
name: 'ExampleClass',
reflection: {
kind: ReflectionKind.Class,
children: [
{
name: 'someMethodName',
kind: ReflectionKind.Method,
signatures: [{}],
},
],
},
} as unknown as Type;
const pickType = {
type: 'reference',
name: 'Pick',
package: 'typescript',
typeArguments: [sourceType, { type: 'literal', value: 'someMethodName' }],
} as unknown as Type;

expect(getParameterObjectShapeDeclaration(pickType)).toBeUndefined();
});

it('selects only the picked properties from a multi-key Pick', () => {
const decl = getParameterObjectShapeDeclaration(
pick(sourceInterface('name', 'enrollmentMode', 'other'), union(literal('name'), literal('enrollmentMode'))),
);
expect(decl?.children?.map(child => child.name)).toEqual(['name', 'enrollmentMode']);
});

it('does not mutate the source declaration', () => {
const source = sourceInterface('name', 'enrollmentMode');
getParameterObjectShapeDeclaration(pick(source, literal('enrollmentMode')));
expect(source.reflection.children.map(child => child.name)).toEqual(['name', 'enrollmentMode']);
});

it('fails closed when a picked key is not a property of the source', () => {
expect(getParameterObjectShapeDeclaration(pick(sourceInterface('name'), literal('missing')))).toBeUndefined();
});

it('fails closed for non-literal keys', () => {
expect(
getParameterObjectShapeDeclaration(
pick(sourceInterface('name', 'enrollmentMode'), { type: 'reference', name: 'keyof Src' }),
),
).toBeUndefined();
});

it('fails closed for generic source instantiations', () => {
const source = {
...sourceInterface('value'),
name: 'Box',
typeArguments: [{ type: 'intrinsic', name: 'string' }],
};

expect(getParameterObjectShapeDeclaration(pick(source, literal('value')))).toBeUndefined();
});

it('does not treat Omit as a flattenable builtin', () => {
const omit = {
type: 'reference',
name: 'Omit',
package: 'typescript',
reflection: undefined,
typeArguments: [
{ type: 'reference', ...sourceInterface('name', 'enrollmentMode') },
{ type: 'literal', value: 'name' },
],
} as unknown as Type;
expect(getParameterObjectShapeDeclaration(omit)).toBeUndefined();
});
});
6 changes: 6 additions & 0 deletions .typedoc/__tests__/extract-methods.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { describe, expect, it } from 'vitest';
* - `methods/sign-out.mdx` – simple zero-arg callable
* - `methods/handle-redirect-callback.mdx` – multi-param `parametersTable` with nested rows
* - `methods/handle-email-link-verification.mdx` – required parent (`params`) flattened to `.`
* - `methods/create-domain.mdx` – `Pick<T, K>` parameter flattened to only the selected property
* - `methods/join-waitlist.mdx` – single nominal-param section (`JoinWaitlistParams`)
* - `methods/create.mdx` (api-key) – another single-nominal-param case + warning callout
* - `methods/check-authorization.mdx` – generic instantiation (`CheckAuthorization`)
Expand Down Expand Up @@ -45,6 +46,11 @@ describe('extract-methods snapshots', () => {
await expect(content).toMatchFileSnapshot('./__snapshots__/clerk-methods-handle-email-link-verification.mdx');
});

it('Pick parameter includes only selected properties without linking the full type: organization.createDomain()', async () => {
const content = await readGenerated('shared/organization-resource/methods/create-domain.mdx');
await expect(content).toMatchFileSnapshot('./__snapshots__/organization-resource-methods-create-domain.mdx');
});

it('single nominal-param section: clerk.joinWaitlist()', async () => {
const content = await readGenerated('shared/clerk/methods/join-waitlist.mdx');
await expect(content).toMatchFileSnapshot('./__snapshots__/clerk-methods-join-waitlist.mdx');
Expand Down
106 changes: 103 additions & 3 deletions .typedoc/custom-theme.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,51 @@ function hasDefaultValuesForParameters(parameters) {
}

/**
* Object shape for a parameter: inline `{ … }`, optional-wrapped, or reference to a type alias / interface.
* Collects string literal members from a type used as `Pick`'s key argument.
*
* @param {import('typedoc').Type | undefined} t
* @returns {string[] | undefined}
*/
function getPickPropertyNames(t) {
const unwrapped = unwrapOptional(t);
if (!unwrapped || typeof unwrapped !== 'object') {
return undefined;
}
if (unwrapped.type === 'literal') {
const literal = /** @type {import('typedoc').LiteralType} */ (unwrapped);
if (typeof literal.value === 'string') {
return [literal.value];
}
return undefined;
}
if (!isUnionTypeDoc(unwrapped)) {
return undefined;
}
const names = [];
const union = /** @type {import('typedoc').UnionType} */ (unwrapped);
for (const type of union.types) {
const nestedNames = getPickPropertyNames(type);
if (!nestedNames) {
return undefined;
}
names.push(...nestedNames);
}
return names;
}

/**
* @param {import('typedoc').Type | undefined} t
* @returns {boolean}
*/
function isPickReferenceType(t) {
if (!isReferenceTypeDoc(t)) {
return false;
}
return t.name === 'Pick' && t.package === 'typescript' && t.typeArguments?.length === 2;
}

/**
* Object shape for a parameter: inline `{ … }`, optional-wrapped, reference to a type alias / interface, or `Pick<T, K>` with literal keys.
*
* @param {import('typedoc').Type | undefined} t
* @returns {import('typedoc').DeclarationReflection | undefined}
Expand All @@ -470,6 +514,40 @@ function getParameterObjectShapeDeclaration(t) {
}
if (o.type === 'reference') {
const ref = /** @type {import('typedoc').ReferenceType} */ (t);
if (isPickReferenceType(ref)) {
const [sourceType, keysType] = /** @type {[import('typedoc').Type, import('typedoc').Type]} */ (
ref.typeArguments
);
if (sourceType.type === 'reference' && sourceType.typeArguments?.length) {
return undefined;
}
const propertyNames = getPickPropertyNames(keysType);
if (!propertyNames?.length) {
return undefined;
}
const sourceDecl = getParameterObjectShapeDeclaration(sourceType);
const sourceRef = sourceType.type === 'reference' ? sourceType.reflection : undefined;
const sourceWithChildren =
sourceDecl ??
(sourceRef &&
(sourceRef.kind === ReflectionKind.TypeAlias || sourceRef.kind === ReflectionKind.Interface) &&
'children' in sourceRef
? /** @type {import('typedoc').DeclarationReflection} */ (sourceRef)
: undefined);
if (!sourceWithChildren?.children?.length) {
return undefined;
}
const selected = new Set(propertyNames);
const children = sourceWithChildren.children.filter(child => selected.has(child.name));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For a generic source like Pick<Box<string>, 'value'>, this reuses Box<T>'s child reflections, so the flattened row documents the property's type as T rather than string — TypeDoc keeps the instantiation in the use-site typeArguments and doesn't rewrite the referenced declaration's children.

Nothing in the entry points hits this today (CreateOrganizationDomainParams isn't generic), and it's a pre-existing trait of the reference-flatten path, not something this PR introduces. The one wrinkle: this branch fails open (a wrong type) rather than closed.

Worth bailing to the opaque output when the source is a generic instantiation — returning undefined here when sourceType.typeArguments?.length — so it degrades instead of documenting T?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch -Pick<Box<string>, 'value'> could otherwise flatten the unspecialized declaration and surface T instead of string. I added a fail-closed guard for generic source instantiations and regression coverage in e07caea.

if (children.length !== selected.size) {
return undefined;
}
return /** @type {import('typedoc').DeclarationReflection} */ ({
...sourceWithChildren,
kind: ReflectionKind.TypeLiteral,
children,
});
}
const sym = ref.reflection;
if (!sym) {
return undefined;
Expand Down Expand Up @@ -504,6 +582,27 @@ function shouldFlattenInlineObjectParameter(decl) {
return Boolean(only?.comment?.hasVisibleComponent());
}

/**
* Whether a parameter is a built-in `Pick<T, K>` that will be flattened into nested rows. Its source type should not
* link to the full unpicked declaration, which documents properties the parameter does not accept.
*
* @param {import('typedoc').Type | undefined} t
*/
function isFlattenedPickParameter(t) {
const unwrapped = unwrapOptional(t);
if (!isPickReferenceType(unwrapped)) {
return false;
}
return shouldFlattenInlineObjectParameter(getParameterObjectShapeDeclaration(t));
}

/**
* @param {string} value
*/
function stripMarkdownLinks(value) {
return value.replace(/\[([^\[\]]*)\]\((.*?)\)/gm, '$1');
}

/**
* Same as typedoc-plugin-markdown `member.parametersTable`, with `shouldFlattenInlineObjectParameter` and `getParameterObjectShapeDeclaration`.
*
Expand Down Expand Up @@ -581,12 +680,13 @@ function clerkParametersTable(model) {
const optional = isOptional ? '?' : '';
row.push(`${rest}${backTicks(`${parameter.name}${optional}`)}`);
if (parameter.type) {
const displayType =
const renderedType =
parameter.type instanceof ReflectionType
? this.partials.reflectionType(parameter.type, {
forceCollapse: true,
})
: this.partials.someType(parameter.type);
const displayType = isFlattenedPickParameter(parameter.type) ? stripMarkdownLinks(renderedType) : renderedType;
row.push(removeLineBreaks(displayType));
}
if (showDefaults) {
Expand Down Expand Up @@ -1954,4 +2054,4 @@ function isCallablePropertyValueType(t, helpers, seenReflectionIds) {
return false;
}

export { isCallableInterfaceProperty };
export { getParameterObjectShapeDeclaration, getPickPropertyNames, isCallableInterfaceProperty };
Loading