diff --git a/.changeset/plugin-exact-optional-property-types.md b/.changeset/plugin-exact-optional-property-types.md new file mode 100644 index 00000000..b954c0be --- /dev/null +++ b/.changeset/plugin-exact-optional-property-types.md @@ -0,0 +1,7 @@ +--- +"@streamdown/code": patch +"@streamdown/math": patch +"@streamdown/mermaid": patch +--- + +Support `exactOptionalPropertyTypes`: add `| undefined` to optional props in the plugin option types so possibly-`undefined` values can be forwarded to `createCodePlugin`, `createMathPlugin` and `createMermaidPlugin` without type errors diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 892f27f7..aabb83ae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: run: pnpm build:packages - name: Check exactOptionalPropertyTypes compatibility - run: pnpm --filter streamdown exec tsc -p tsconfig.exact-optional.json + run: pnpm --filter streamdown --filter @streamdown/code --filter @streamdown/math --filter @streamdown/mermaid exec tsc -p tsconfig.exact-optional.json - name: Build Website run: pnpm build --filter website diff --git a/packages/streamdown-code/index.ts b/packages/streamdown-code/index.ts index 146046b1..a9b015fd 100644 --- a/packages/streamdown-code/index.ts +++ b/packages/streamdown-code/index.ts @@ -33,7 +33,7 @@ export interface HighlightOptions { * Whether the code block is still streaming. Its result is superseded by * the next update, so it is not cached. */ - isIncomplete?: boolean; + isIncomplete?: boolean | undefined; language: BundledLanguage; themes: [ThemeInput, ThemeInput]; } @@ -75,7 +75,7 @@ export interface CodePluginOptions { * Default themes for syntax highlighting [light, dark] * @default ["github-light", "github-dark"] */ - themes?: [ThemeInput, ThemeInput]; + themes?: [ThemeInput, ThemeInput] | undefined; } const languageAliases = Object.fromEntries( diff --git a/packages/streamdown-code/package.json b/packages/streamdown-code/package.json index 4fa4eebc..e63c76fd 100644 --- a/packages/streamdown-code/package.json +++ b/packages/streamdown-code/package.json @@ -28,6 +28,7 @@ "scripts": { "bench": "vitest bench --run > results.txt", "build": "tsup", + "check-types:exact-optional": "pnpm build && tsc -p tsconfig.exact-optional.json", "test": "vitest run", "test:coverage": "vitest --coverage run" }, diff --git a/packages/streamdown-code/tsconfig.exact-optional.json b/packages/streamdown-code/tsconfig.exact-optional.json new file mode 100644 index 00000000..fc13af8e --- /dev/null +++ b/packages/streamdown-code/tsconfig.exact-optional.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "exactOptionalPropertyTypes": true + }, + "include": ["type-tests/exact-optional-property-types.ts"], + "exclude": ["node_modules", "build", "dist"] +} diff --git a/packages/streamdown-code/tsconfig.json b/packages/streamdown-code/tsconfig.json index 17da37c9..fa581703 100644 --- a/packages/streamdown-code/tsconfig.json +++ b/packages/streamdown-code/tsconfig.json @@ -16,5 +16,5 @@ "noEmit": true }, "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["node_modules", "build", "dist"] + "exclude": ["node_modules", "build", "dist", "type-tests"] } diff --git a/packages/streamdown-code/type-tests/exact-optional-property-types.ts b/packages/streamdown-code/type-tests/exact-optional-property-types.ts new file mode 100644 index 00000000..ca742d36 --- /dev/null +++ b/packages/streamdown-code/type-tests/exact-optional-property-types.ts @@ -0,0 +1,27 @@ +/** + * Type-level regression test for `exactOptionalPropertyTypes` support. + * + * This file is type-checked with `tsconfig.exact-optional.json` + * (`pnpm check-types:exact-optional`) against the built declarations, with + * `exactOptionalPropertyTypes` enabled. It is never executed. + */ +import { createCodePlugin, type ThemeInput } from "../dist/index"; + +interface CodeOptions { + isIncomplete?: boolean; + themes?: [ThemeInput, ThemeInput]; +} + +// Forwarding possibly-`undefined` optional props must type-check. +export const createPlugin = (options: CodeOptions) => + createCodePlugin({ themes: options.themes }); + +export const plugin = createCodePlugin({ themes: undefined }); + +export const highlight = (options: CodeOptions) => + plugin.highlight({ + code: "", + isIncomplete: options.isIncomplete, + language: "ts", + themes: plugin.getThemes(), + }); diff --git a/packages/streamdown-math/index.ts b/packages/streamdown-math/index.ts index fc7d4f13..87d33efa 100644 --- a/packages/streamdown-math/index.ts +++ b/packages/streamdown-math/index.ts @@ -11,7 +11,7 @@ export interface MathPlugin { /** * Get CSS styles path for math rendering */ - getStyles?: () => string; + getStyles?: (() => string) | undefined; name: "katex"; /** * Rehype plugin for rendering math @@ -32,12 +32,12 @@ export interface MathPluginOptions { * KaTeX error color * @default "var(--color-muted-foreground)" */ - errorColor?: string; + errorColor?: string | undefined; /** * Enable single dollar sign for inline math ($...$) * @default false */ - singleDollarTextMath?: boolean; + singleDollarTextMath?: boolean | undefined; } /** diff --git a/packages/streamdown-math/package.json b/packages/streamdown-math/package.json index b4024a75..98e9de4a 100644 --- a/packages/streamdown-math/package.json +++ b/packages/streamdown-math/package.json @@ -24,6 +24,7 @@ ], "scripts": { "build": "tsup", + "check-types:exact-optional": "pnpm build && tsc -p tsconfig.exact-optional.json", "test": "vitest run", "test:coverage": "vitest --coverage run" }, diff --git a/packages/streamdown-math/tsconfig.exact-optional.json b/packages/streamdown-math/tsconfig.exact-optional.json new file mode 100644 index 00000000..fc13af8e --- /dev/null +++ b/packages/streamdown-math/tsconfig.exact-optional.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "exactOptionalPropertyTypes": true + }, + "include": ["type-tests/exact-optional-property-types.ts"], + "exclude": ["node_modules", "build", "dist"] +} diff --git a/packages/streamdown-math/tsconfig.json b/packages/streamdown-math/tsconfig.json index 17da37c9..fa581703 100644 --- a/packages/streamdown-math/tsconfig.json +++ b/packages/streamdown-math/tsconfig.json @@ -16,5 +16,5 @@ "noEmit": true }, "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["node_modules", "build", "dist"] + "exclude": ["node_modules", "build", "dist", "type-tests"] } diff --git a/packages/streamdown-math/type-tests/exact-optional-property-types.ts b/packages/streamdown-math/type-tests/exact-optional-property-types.ts new file mode 100644 index 00000000..11778220 --- /dev/null +++ b/packages/streamdown-math/type-tests/exact-optional-property-types.ts @@ -0,0 +1,31 @@ +/** + * Type-level regression test for `exactOptionalPropertyTypes` support. + * + * This file is type-checked with `tsconfig.exact-optional.json` + * (`pnpm check-types:exact-optional`) against the built declarations, with + * `exactOptionalPropertyTypes` enabled. It is never executed. + */ +import { createMathPlugin, type MathPlugin } from "../dist/index"; + +interface MathOptions { + errorColor?: string; + getStyles?: () => string; + singleDollarTextMath?: boolean; +} + +// Forwarding possibly-`undefined` optional props must type-check. +export const createPlugin = (options: MathOptions) => + createMathPlugin({ + errorColor: options.errorColor, + singleDollarTextMath: options.singleDollarTextMath, + }); + +export const plugin = createMathPlugin({ + errorColor: undefined, + singleDollarTextMath: undefined, +}); + +export const withStyles = (options: MathOptions): MathPlugin => ({ + ...plugin, + getStyles: options.getStyles, +}); diff --git a/packages/streamdown-mermaid/index.ts b/packages/streamdown-mermaid/index.ts index 704808de..64b70f22 100644 --- a/packages/streamdown-mermaid/index.ts +++ b/packages/streamdown-mermaid/index.ts @@ -34,7 +34,7 @@ export interface MermaidPluginOptions { /** * Default Mermaid configuration */ - config?: MermaidConfig; + config?: MermaidConfig | undefined; } const defaultConfig: MermaidConfig = { diff --git a/packages/streamdown-mermaid/package.json b/packages/streamdown-mermaid/package.json index cf3d0c8a..f94e1bb5 100644 --- a/packages/streamdown-mermaid/package.json +++ b/packages/streamdown-mermaid/package.json @@ -24,6 +24,7 @@ ], "scripts": { "build": "tsup", + "check-types:exact-optional": "pnpm build && tsc -p tsconfig.exact-optional.json", "test": "vitest run", "test:coverage": "vitest --coverage run" }, diff --git a/packages/streamdown-mermaid/tsconfig.exact-optional.json b/packages/streamdown-mermaid/tsconfig.exact-optional.json new file mode 100644 index 00000000..fc13af8e --- /dev/null +++ b/packages/streamdown-mermaid/tsconfig.exact-optional.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "exactOptionalPropertyTypes": true + }, + "include": ["type-tests/exact-optional-property-types.ts"], + "exclude": ["node_modules", "build", "dist"] +} diff --git a/packages/streamdown-mermaid/tsconfig.json b/packages/streamdown-mermaid/tsconfig.json index 17da37c9..fa581703 100644 --- a/packages/streamdown-mermaid/tsconfig.json +++ b/packages/streamdown-mermaid/tsconfig.json @@ -16,5 +16,5 @@ "noEmit": true }, "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["node_modules", "build", "dist"] + "exclude": ["node_modules", "build", "dist", "type-tests"] } diff --git a/packages/streamdown-mermaid/type-tests/exact-optional-property-types.ts b/packages/streamdown-mermaid/type-tests/exact-optional-property-types.ts new file mode 100644 index 00000000..35fa4055 --- /dev/null +++ b/packages/streamdown-mermaid/type-tests/exact-optional-property-types.ts @@ -0,0 +1,19 @@ +/** + * Type-level regression test for `exactOptionalPropertyTypes` support. + * + * This file is type-checked with `tsconfig.exact-optional.json` + * (`pnpm check-types:exact-optional`) against the built declarations, with + * `exactOptionalPropertyTypes` enabled. It is never executed. + */ +import type { MermaidConfig } from "mermaid"; +import { createMermaidPlugin } from "../dist/index"; + +interface MermaidOptions { + config?: MermaidConfig; +} + +// Forwarding possibly-`undefined` optional props must type-check. +export const createPlugin = (options: MermaidOptions) => + createMermaidPlugin({ config: options.config }); + +export const plugin = createMermaidPlugin({ config: undefined });