@flink-app/ts-source-to-json-schema

中文文档
0.1.9发布于 2026/08/19MITTypeScriptESM + CJS零依赖近 30 天下载 4206

A zero-dependency TypeScript-to-JSON Schema converter that uses its own mini-parser

原始使用文档
文档目录26 节
  1. Why
  2. What it handles
  3. Installation
  4. CLI Usage
  5. CLI Options
  6. Multi-File Support ( --followImports )
  7. Batch Mode ( --batch )
  8. Diagnostics Mode ( --doctor )
  9. Programmatic Usage
  10. String-Based API
  11. File-Based API with Import Resolution
  12. Batch Schema Generation
  13. Options
  14. includeJSDoc (optional)
  15. includeSchema (optional)
  16. schemaVersion (optional)
  17. additionalProperties (optional)
  18. @additionalProperties JSDoc Tag
  19. JSDoc Tags on Array Properties
  20. @oneOf / @anyOf JSDoc Tags
  21. followImports (optional)
  22. baseDir (optional)
  23. onDuplicateDeclarations (optional)
  24. onUnresolvedReferences (optional)
  25. defineId (optional)
  26. What it doesn't handle

ts-to-jsonschema

A zero-dependency TypeScript-to-JSON Schema converter that uses its own mini-parser instead of the TypeScript compiler.

It parses TypeScript type declarations (interfaces, type aliases, enums) directly from source strings and emits JSON Schema (2020-12 draft). No ts.createProgram, no node_modules bloat, no compiler startup cost.

Why

Existing tools like ts-json-schema-generator and typescript-json-schema rely on the full TypeScript compiler API to resolve types. That works, but it's slow, heavy, and ties you to Node.js. Furthermore, these libraries might need significant rewrites when TypeScript 7 (tsgo) is released, making their long-term future uncertain.

This library takes a different approach: instead of running the type checker, it tokenizes and parses a practical subset of TypeScript's type syntax directly. The result is a tool that's fast enough to run in a hot path, portable enough to run anywhere (Cloudflare Workers, browser, CLI), and simple enough to extend.

The trade-off is explicit: it handles the type constructs you'd actually use in API contracts, tool schemas, and config definitions — not the full type-level programming language.

What it handles

  • Declarations: interface, type, enum (string + numeric), export
  • Primitives: string, number, boolean, null, undefined, any, unknown, never, void, bigint
  • Built-in types: Date → { type: "string", format: "date-time" }
  • Literal types: "active", 42, true
  • Unions: A | B | C → anyOf (or enum when all members are literals)
  • Intersections: A & B → allOf
  • Arrays: T[], Array<T>, nested T[][]
  • Tuples: [string, number] → prefixItems
  • Nullable: string | null → type: ["string", "null"]
  • Nested objects: inline { foo: string } and cross-references via $ref
  • Self-referential types: Task containing subtasks: Task[]
  • Interface extends: interface Dog extends Animal → allOf
  • Index signatures: [key: string]: T → additionalProperties
  • Generic type instantiation: interface Foo extends Generic<TypeArg> → inlines with type parameter substitution
    • Supports interfaces and type aliases with type arguments
    • Handles nested generics: Box<Box<string>>
    • Multiple type parameters with any names: Pair<T, U>, ApiResponse<TData>, Pair<First, Second>
    • Constraints and defaults are accepted (<T extends string = "a">), though not enforced
    • Type parameters are substituted throughout properties, arrays, unions, etc.
    • Falls back to $ref when generic definition is not found
  • Function/method members: onChange: (value: string) => void and findById(id: string): User are omitted from the schema (functions have no JSON representation) instead of failing the conversion
  • Utility types: Partial<T>, Required<T>, Pick<T, K>, Omit<T, K>, Record<K, V>, Readonly<T>, Set<T>, Map<K, V>, Promise<T> (unwrapped)
  • Local imports: Automatic resolution of relative imports (./ and ../) across files
  • JSDoc: /** description */ → description, plus tags: @minimum, @maximum, @minLength, @maxLength, @pattern, @format, @minItems, @maxItems, @uniqueItems, @default, @deprecated, @title, @example, @additionalProperties, @oneOf, @anyOf
    • On array properties (string[], Array<T>, …) value constraints like @format uuid apply to the items, so /** @format uuid */ to: string[] validates every element
    • @oneOf / @anyOf express cross-field rules: @oneOf required(message, to) | required(message, conversationId); a bare @oneOf on a union type alias emits oneOf instead of anyOf
  • Readonly: readonly → readOnly in schema

Installation

npm install ts-source-to-json-schema

CLI Usage

The package includes a command-line tool for converting TypeScript files to JSON Schema:

# Convert a TypeScript file
npx ts-source-to-json-schema src/types.ts

# Convert with automatic import resolution (default)
npx ts-source-to-json-schema src/api.ts

# Convert a specific type as the root schema
npx ts-source-to-json-schema src/api.ts --rootType ApiResponse

# Use strict mode (no additional properties allowed)
npx ts-source-to-json-schema src/config.ts --strictObjects

# Disable JSDoc processing
npx ts-source-to-json-schema src/types.ts --includeJSDoc false

# Single file mode, no imports
npx ts-source-to-json-schema src/types.ts --followImports none

# Combine multiple options
npx ts-source-to-json-schema src/user.ts -r User --strictObjects --followImports local

CLI Options

-h, --help                     Show help message
-v, --version                  Show version number
    --doctor                   Output diagnostic information for debugging
    --batch                    Batch mode: generate schemas for all types across files

-r, --rootType <name>          Emit this type as root (others in $defs)
-s, --includeSchema <bool>     Include $schema property (default: true)
    --schemaVersion <url>      Custom $schema URL
    --strictObjects            Set additionalProperties: false globally
    --additionalProperties     Set additionalProperties default (true/false)
    --includeJSDoc <bool>      Include JSDoc comments (default: true)

--followImports <mode>         Follow imports: none, local, all (default: local)
--baseDir <path>               Base directory for resolving imports

The CLI reads TypeScript files and outputs JSON Schema to stdout, making it easy to pipe to files or other tools:

# Save to file
npx ts-source-to-json-schema src/types.ts > schema.json

# Pretty-print with jq
npx ts-source-to-json-schema src/types.ts | jq '.'

# Use in scripts
npx ts-source-to-json-schema src/api.ts --rootType Request > openapi/request-schema.json

Multi-File Support (--followImports)

By default, the CLI automatically follows local relative imports (./ and ../) to resolve type definitions across multiple files:

// pet.ts
export interface Pet {
  _id: string;
  name: string;
  species: string;
}

// api.ts
import { Pet } from './pet';
export interface PostPetReq extends Omit<Pet, "_id"> {}
# Follows imports and resolves Pet type (default behavior)
npx ts-source-to-json-schema api.ts --rootType PostPetReq

# Output includes both Pet (in $defs) and PostPetReq (as root)

Import Resolution Modes:

  • local (default in CLI): Follows relative imports (./ and ../), skips node_modules
  • none: Single-file mode, does not follow any imports
  • all: Reserved for future node_modules support (currently behaves like local)

Key Features:

  • Circular dependency detection (no infinite loops)
  • Duplicate name detection (errors if same type name in multiple files)
  • Automatic extension resolution (.ts, .tsx, .d.ts)
  • Index file support (./types → ./types/index.ts)

Examples:

# Multi-file project with local imports
npx ts-source-to-json-schema src/api.ts --followImports local

# Single file only, ignore imports
npx ts-source-to-json-schema src/standalone.ts --followImports none

# Custom base directory for import resolution
npx ts-source-to-json-schema src/api.ts --baseDir ./src

Batch Mode (--batch)

Use --batch to generate schemas for all types across multiple files at once. Accepts glob patterns or multiple file paths:

# Batch: all schemas from matching files
npx ts-source-to-json-schema --batch 'src/schemas/**/*.ts' --followImports local

# Batch: specific files
npx ts-source-to-json-schema --batch src/PostReq.ts src/PostRes.ts --followImports local

# Batch with strict objects
npx ts-source-to-json-schema --batch 'src/types/*.ts' --strictObjects

Output is a JSON object where keys are type names and values are standalone schemas:

{
  "PostReq": { "$schema": "...", "type": "object", "properties": { ... } },
  "PostRes": { "$schema": "...", "type": "object", "properties": { ... } }
}

Shared imports are resolved once and deduplicated across all entry files.

Diagnostics Mode (--doctor)

When you encounter issues with schema conversion, use the --doctor flag to output comprehensive diagnostic information that can be shared with developers:

npx ts-source-to-json-schema src/problematic-types.ts --doctor

The doctor output includes:

  • Timestamp: When the conversion was attempted
  • Environment: Node.js version, platform, architecture, and current working directory
  • Input file details: Path, existence, size, modification time, source length, and full source code
  • Options used: All configuration options passed to the converter
  • Conversion result: Either the successfully generated schema or detailed error information with stack traces

This makes it easy to:

  1. Copy-paste the full diagnostic output when reporting issues
  2. Debug why a particular TypeScript file isn't converting as expected
  3. Share reproducible examples with maintainers

Example output:

{
  "timestamp": "2026-02-25T10:50:55.680Z",
  "environment": {
    "nodeVersion": "v20.17.0",
    "platform": "darwin",
    "arch": "arm64",
    "cwd": "/path/to/project"
  },
  "input": {
    "filePath": "types.ts",
    "absolutePath": "/absolute/path/to/types.ts",
    "fileExists": true,
    "fileSize": 486,
    "sourceLength": 486,
    "source": "interface User { ... }"
  },
  "options": { "rootType": "User" },
  "conversionResult": {
    "success": true,
    "schema": { ... }
  }
}

Programmatic Usage

String-Based API

import { toJsonSchema } from "ts-source-to-json-schema";

const schema = toJsonSchema(`
  /** Input for the ad analysis tool */
  interface AnalyzeAdInput {
    /** URL of the ad to analyze */
    url: string;
    /** Platform the ad is from */
    platform: "instagram" | "facebook" | "tiktok";
    /** Whether to extract color palette */
    extractColors?: boolean;
    /** Max elements to identify
     * @minimum 1
     * @maximum 50
     * @default 10
     */
    maxElements?: number;
    tags: string[];
  }
`, { rootType: "AnalyzeAdInput" });

File-Based API with Import Resolution

import { toJsonSchemaFromFile } from "ts-source-to-json-schema";

// Convert a TypeScript file with automatic import resolution
const schema = toJsonSchemaFromFile('./src/types/api.ts', {
  followImports: 'local',  // Follow relative imports
  rootType: 'ApiRequest',
  strictObjects: true
});

// Single-file mode (no import resolution)
const singleFileSchema = toJsonSchemaFromFile('./src/types.ts', {
  followImports: 'none'
});

Batch Schema Generation

When you need schemas for multiple types from the same source file, use toJsonSchemas() (plural) for better performance. This is particularly useful for build tools, documentation generators, or API schema validators that process many types at once.

import { toJsonSchemas } from "ts-source-to-json-schema";

const source = `
  export interface User { id: string; name: string; }
  export interface Post { id: string; title: string; author: User; }
  export interface Comment { id: string; text: string; }
`;

// Generate all schemas at once (~70% faster than calling toJsonSchema() for each type)
const schemas = toJsonSchemas(source);

console.log(schemas.User);    // Standalone User schema
console.log(schemas.Post);    // Post schema with User in definitions
console.log(schemas.Comment); // Standalone Comment schema

Key benefits:

  • Performance: ~70% faster when generating 30+ schemas from the same source
  • Single parse: Source is tokenized and parsed only once
  • Standalone schemas: Each schema includes only the types it references in its definitions field
  • Self-contained: Each schema includes $schema field by default for better IDE validation
  • Draft-07 compatible: Uses definitions instead of $defs for broader compatibility

How it works:

  • Parses the source once and emits all type declarations
  • Each schema is standalone with minimal definitions (only transitively referenced types)
  • Uses #/definitions/TypeName refs instead of #/$defs/TypeName
  • Handles circular references, transitive dependencies, and utility types

Use cases:

  • Framework integrations (like Flink) that need many schemas from one source
  • Build tools generating schemas for entire API definition files
  • Documentation generators processing large type files
  • Schema validation libraries caching multiple schemas

Example with real-world API:

const schemas = toJsonSchemas(`
  interface User { id: string; name: string; }
  interface CreateUserRequest { name: string; }
  interface CreateUserResponse { user: User; }
  interface GetUserRequest { id: string; }
  interface GetUserResponse { user: User; }
`, { includeJSDoc: true, strictObjects: true });

// Extract specific schemas as needed
const requestSchemas = {
  CreateUser: schemas.CreateUserRequest,
  GetUser: schemas.GetUserRequest
};

Batch Generation with Import Resolution

If your types are spread across multiple files with imports, use toJsonSchemasFromFile() (the file-based batch API):

import { toJsonSchemasFromFile } from "ts-source-to-json-schema";

// Intermediate schema file (e.g., generated by a framework)
// schemas.ts:
//   import TreeNode from "../../src/schemas/TreeNode";
//   import Car from "../../src/schemas/Car";
//   export interface GetTree_12_ResSchema extends TreeNode {}
//   export interface GetCar_10_ResSchema extends Car {}

const schemas = toJsonSchemasFromFile('./schemas.ts', {
  followImports: 'local',  // Resolve relative imports
  strictObjects: true
});

console.log(schemas.GetTree_12_ResSchema);
// {
//   "$ref": "#/definitions/TreeNode",
//   "definitions": {
//     "TreeNode": { "type": "object", ... }  // ✅ Included from imported file!
//   }
// }

Why use toJsonSchemasFromFile()?

  • Automatically resolves imports from local files
  • Generates batch schemas for all types (like toJsonSchemas())
  • Handles circular dependencies across files
  • Perfect for framework integrations (like Flink) with generated intermediate files

Performance: Same batch performance benefits as toJsonSchemas(), plus automatic import resolution.

Multi-Entry Batch Generation

When you need to generate schemas from multiple TypeScript files (not just one entry file), use toJsonSchemasFromFiles(). It accepts a glob pattern or an array of file paths:

import { toJsonSchemasFromFiles } from "ts-source-to-json-schema";

// Glob pattern — matches all .ts files in src/schemas/
const schemas = toJsonSchemasFromFiles('src/schemas/**/*.ts', {
  followImports: 'local'
});

// Array of specific files
const schemas = toJsonSchemasFromFiles([
  'src/schemas/PostUserReq.ts',
  'src/schemas/PostUserRes.ts'
], { followImports: 'local' });

// With defineNameTransform for namespacing
const schemas = toJsonSchemasFromFiles('src/schemas/**/*.ts', {
  followImports: 'local',
  defineNameTransform: (name, decl, ctx) => {
    const file = path.basename(ctx?.relativePath ?? '', '.ts');
    return `${file}.${name}`;
  }
});

Key benefits over toJsonSchemasFromFile():

  • Accepts multiple entry files or a glob pattern (not just a single file)
  • Shared imports are resolved once and deduplicated across all entry files
  • Combined with defineNameTransform, enables stable type IDs across all schemas
  • Returns {} when no files match (safe for empty globs)

Glob support:

  • * matches any characters within a file/directory name
  • ** matches zero or more directory levels
  • ? matches a single character

Output:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "description": "Input for the ad analysis tool",
  "properties": {
    "url": { "type": "string", "description": "URL of the ad to analyze" },
    "platform": {
      "type": "string",
      "enum": ["instagram", "facebook", "tiktok"],
      "description": "Platform the ad is from"
    },
    "extractColors": { "type": "boolean", "description": "Whether to extract color palette" },
    "maxElements": {
      "type": "number",
      "description": "Max elements to identify",
      "minimum": 1,
      "maximum": 50,
      "default": 10
    },
    "tags": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["url", "platform", "tags"]
}

Options

toJsonSchema(source, {
  rootType: "MyType",            // Emit this type as the root schema (others go in $defs)
  includeSchema: true,           // Include $schema field (default: true)
  strictObjects: false,          // Set additionalProperties: false on all objects
  schemaVersion: "https://json-schema.org/draft/2020-12/schema",
  includeJSDoc: true,            // Include JSDoc descriptions and tags (default: true)
  additionalProperties: undefined, // Default value for additionalProperties (undefined, true, or false)
  onUnresolvedReferences: 'ignore', // Handle dangling $refs: 'error', 'warn', 'ignore' (default)
});

toJsonSchemaFromFile(filePath, {
  // All options from toJsonSchema, plus:
  followImports: 'local',        // Follow imports: 'none' (default for API), 'local' (default for CLI), 'all'
  baseDir: './src',              // Base directory for resolving imports (default: dirname(filePath))
  onDuplicateDeclarations: 'error', // Handle duplicate type names: 'error' (default), 'warn', 'silent'
});

toJsonSchemasFromFile(filePath, {
  // All options from toJsonSchemas, plus:
  followImports: 'local',        // Follow imports: 'none', 'local' (default), 'all'
  baseDir: './src',              // Base directory for resolving imports (default: dirname(filePath))
  onDuplicateDeclarations: 'error', // Handle duplicate type names: 'error' (default), 'warn', 'silent'
  // Note: rootType is not supported (generates schemas for all types)
});

toJsonSchemasFromFiles(entries, {
  // entries: glob pattern (string) or array of file paths
  // All options from toJsonSchemas, plus:
  followImports: 'local',        // Follow imports: 'none' (default), 'local', 'all'
  baseDir: './src',              // Base directory for resolving imports (default: cwd)
  onDuplicateDeclarations: 'error', // Handle duplicate type names: 'error' (default), 'warn', 'silent'
  // Note: rootType is not supported (generates schemas for all types)
});

includeJSDoc (optional)

  • Type: boolean
  • Default: true
  • Description: Controls whether JSDoc comments are processed and included in the schema

When true (default):

  • Interface/type descriptions are extracted from JSDoc comments
  • Property descriptions are included
  • JSDoc tags like @minimum, @maximum, @pattern are applied as constraints

When false:

  • All JSDoc comments are ignored
  • Schema only contains structural information (types, properties, required fields)
  • Useful for generating minimal schemas or when descriptions aren't needed

Example:

const schema = toJsonSchema(`
  /** User profile */
  interface User {
    /** @minLength 1 */
    name: string;
  }
`, { rootType: 'User', includeJSDoc: false });

// Result: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }
// No description or minLength constraint

includeSchema (optional)

  • Type: boolean
  • Default: true
  • Description: Controls whether the $schema field is included in generated schemas

When true (default):

  • Each generated schema includes a $schema field pointing to the JSON Schema specification
  • Makes schemas self-contained and improves IDE validation support
  • Uses the URL from schemaVersion option (default: https://json-schema.org/draft/2020-12/schema)

When false:

  • The $schema field is omitted from all generated schemas
  • Useful when embedding schemas in larger documents or when the schema version is managed elsewhere

Example:

const schema = toJsonSchema(`
  interface User {
    name: string;
  }
`, { rootType: 'User', includeSchema: true });

// Result includes: { "$schema": "https://json-schema.org/draft/2020-12/schema", type: 'object', ... }

Batch generation: The toJsonSchemas() function also respects this option and includes $schema in each generated schema by default.

schemaVersion (optional)

  • Type: string
  • Default: "https://json-schema.org/draft/2020-12/schema"
  • Description: Specifies the JSON Schema draft version URL

Use this to generate schemas compatible with different JSON Schema drafts:

// Generate draft-07 compatible schema
const schema = toJsonSchema(`interface User { name: string; }`, {
  rootType: 'User',
  schemaVersion: 'http://json-schema.org/draft-07/schema#'
});

// Result includes: { "$schema": "http://json-schema.org/draft-07/schema#", ... }

Note: This library always generates draft-2020-12 compatible schemas. The schemaVersion option only changes the $schema field value. For maximum compatibility with draft-07 validators, use toJsonSchemas() which uses definitions instead of $defs.

additionalProperties (optional)

  • Type: boolean | undefined
  • Default: undefined
  • Description: Sets the default value for additionalProperties on all object schemas

This option provides a global default for additionalProperties when not explicitly set via JSDoc or index signatures.

Precedence (highest to lowest):

  1. Index signature: [key: string]: T → additionalProperties: T
  2. JSDoc @additionalProperties tag
  3. strictObjects option
  4. additionalProperties option
  5. Not set (JSON Schema default behavior)

Example:

// Set all objects to disallow additional properties by default
const schema = toJsonSchema(`
  interface User {
    name: string;
  }
`, { rootType: 'User', additionalProperties: false });

// Result: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'], additionalProperties: false }

@additionalProperties JSDoc Tag

Control additionalProperties on specific types or properties using the @additionalProperties JSDoc tag.

Supported values: true | false (case-insensitive)

Usage at interface/type level:

/**
 * Strict configuration object
 * @additionalProperties false
 */
interface Config {
  host: string;
  port: number;
}
// Result: { type: 'object', properties: {...}, additionalProperties: false }

Usage at property level:

interface Settings {
  /**
   * Database configuration
   * @additionalProperties false
   */
  database: {
    host: string;
    port: number;
  };
}
// Result: database property has additionalProperties: false

Interaction with other options:

  • When includeJSDoc: false, the tag is ignored
  • The tag overrides the global additionalProperties and strictObjects options
  • Index signatures take precedence over the tag

JSDoc Tags on Array Properties

On an array-typed property (T[], Array<T>, readonly T[], Set<T>, T[] | null, nested T[][]), value constraints describe the elements, so they are applied to items rather than the array itself (where JSON Schema would silently ignore them):

interface SendMessageRequest {
  /**
   * @format uuid
   * @minItems 1
   * @uniqueItems
   */
  to: string[];
}
// Result:
// to: { type: 'array', items: { type: 'string', format: 'uuid' }, minItems: 1, uniqueItems: true }
Applied to itemsStays on the array
@format, @pattern, @minLength, @maxLength, @minimum, @maximum, @additionalProperties@minItems, @maxItems, @uniqueItems, @title, @default, @example, @deprecated, description

Tuples ([string, number]) are not affected — their elements are heterogeneous.

@oneOf / @anyOf JSDoc Tags

TypeScript interfaces cannot express "either these fields or those fields". The @oneOf and @anyOf tags let a declaration (or an inline object property) declare cross-field requirements as |-separated groups of property names. Each group becomes { required: [...] } under the combinator:

/**
 * Either (message + to) or (message + conversationId), never both.
 * @oneOf required(message, to) | required(message, conversationId)
 */
interface SendMessageRequest {
  message: string;
  to?: string[];
  conversationId?: string;
}
// Result:
// {
//   type: 'object',
//   properties: { ... },
//   required: ['message'],
//   oneOf: [ { required: ['message', 'to'] }, { required: ['message', 'conversationId'] } ]
// }

/** @anyOf required(email) | required(phoneNumber) | required(skypeId) */
interface ContactRequest {
  email?: string;
  phoneNumber?: string;
  skypeId?: string;
}
// Result: anyOf: [ { required: ['email'] }, { required: ['phoneNumber'] }, { required: ['skypeId'] } ]

The required(...) wrapper is optional — @anyOf email | phoneNumber | skypeId is equivalent.

Unions: a union of object types is emitted as anyOf (matching TypeScript semantics, where a value may satisfy several members). Add a bare @oneOf to the type alias when exactly one member must match:

/** @oneOf */
export type SendMessageRequest =
  | { message: string; to: string[]; referenceId?: string }
  | { message: string; conversationId: string };
// Result: { oneOf: [ { type: 'object', ... }, { type: 'object', ... } ] }

Both tags are ignored when includeJSDoc: false.

followImports (optional)

  • Type: "none" | "local" | "all"
  • Default: "none" (programmatic API), "local" (CLI)
  • Description: Controls import resolution across multiple TypeScript files

Modes:

  • none: Single-file mode, imports are ignored
  • local: Follows relative imports (./ and ../), skips node_modules
  • all: Reserved for future node_modules support (currently behaves like local)

Only available with file-based APIs (toJsonSchemaFromFile(), toJsonSchemasFromFile(), and toJsonSchemasFromFiles()) — the string-based APIs (toJsonSchema() and toJsonSchemas()) do not support import resolution.

Example:

// Given: pet.ts exports Pet interface
// Given: api.ts imports Pet and uses it in PostPetReq

const schema = toJsonSchemaFromFile('./api.ts', {
  followImports: 'local',
  rootType: 'PostPetReq'
});

// Result: Schema includes both Pet (in $defs) and PostPetReq

Features:

  • Circular dependency detection (prevents infinite loops)
  • Duplicate name handling (configurable with onDuplicateDeclarations option)
  • Automatic extension resolution (.ts, .tsx, .d.ts)
  • Index file resolution (./types → ./types/index.ts)

baseDir (optional)

  • Type: string
  • Default: path.dirname(filePath)
  • Description: Base directory for resolving relative imports

Only relevant when followImports is not "none".

onDuplicateDeclarations (optional)

  • Type: "error" | "warn" | "silent"
  • Default: "error"
  • Description: Controls how to handle duplicate type names across multiple files

Only relevant when followImports is not "none" (requires import resolution).

Modes:

  • error (default): Throws an error when the same type name is found in multiple files
  • warn: Uses the first declaration encountered and logs a warning to console.warn
  • silent: Uses the first declaration encountered without any warning

When to use:

  • error: Strict mode for catching unintentional duplicates (recommended for most cases)
  • warn: Development mode where you want to be notified but not block execution
  • silent: When you have intentional duplicates and want to use the first declaration

Example:

// File 1: schemas/CampaignImage.ts
export interface ImageDimensions {
  width: number;
  height: number;
  format?: string;
}

// File 2: schemas/BrandAsset.ts
export interface ImageDimensions {
  width: number;
  height: number;
  format: string;  // Required (different from File 1)
}

// With error mode (default) - throws error
toJsonSchemaFromFile('entry.ts', {
  followImports: 'local'
});
// Error: Duplicate declaration "ImageDimensions" found in:
//   .../CampaignImage.ts
//   .../BrandAsset.ts

// With warn mode - uses first declaration
toJsonSchemaFromFile('entry.ts', {
  followImports: 'local',
  onDuplicateDeclarations: 'warn'
});
// Warning: Duplicate declaration "ImageDimensions" found...
// Uses ImageDimensions from CampaignImage.ts (format is optional)

// With silent mode - uses first declaration without warning
toJsonSchemaFromFile('entry.ts', {
  followImports: 'local',
  onDuplicateDeclarations: 'silent'
});
// No warning, uses ImageDimensions from CampaignImage.ts

Best practice: Keep the default error mode to catch duplicate names early. If you encounter duplicates:

  1. Check if the types are structurally identical — if so, extract to a shared file
  2. If they're different but have the same name, rename one of them for clarity
  3. Use warn or silent mode only as a temporary workaround

onUnresolvedReferences (optional)

  • Type: "error" | "warn" | "ignore"
  • Default: "ignore"
  • Description: Controls how to handle $ref pointers to types that are not defined in the emitted schema

A reference to a type that is not declared in the source (a typo, or an import that was not followed because followImports is "none") produces a schema with a dangling $ref — it looks valid, but fails later when a validator like AJV tries to resolve it.

Modes:

  • error: Throws an error listing the unresolved type names (and, in batch mode, which schema contains them)
  • warn: Emits the schema as-is and logs a warning to console.warn
  • ignore (default): Emits the schema as-is, preserving previous behavior

Example:

// "Customer" is referenced but never declared
toJsonSchema(`interface Order { customer: Customer }`, {
  rootType: 'Order',
  onUnresolvedReferences: 'error'
});
// Error: Unresolved type reference: Customer. The referenced type(s) are
// not defined in the provided source. Define them, enable followImports,
// or set onUnresolvedReferences to "warn" or "ignore".

Best practice: Set 'error' in CI or build pipelines to catch typos and missing imports at generation time instead of at validation time. The default stays 'ignore' for backward compatibility — schemas that intentionally carry external references (resolved by your validator's registry) keep working.

Works with toJsonSchema, toJsonSchemas, and the file-based variants.

defineId (optional)

  • Type: (originalName: string, declaration: Declaration, context?: { absolutePath: string; relativePath: string }) => string
  • Default: undefined
  • Description: Assigns a $id to each schema and uses external $ref instead of local #/definitions/ refs

When set on batch APIs (toJsonSchemas, toJsonSchemasFromFile, toJsonSchemasFromFiles):

  1. Each schema gets a $id field (the return value of the callback)
  2. Internal $ref pointers become external refs pointing to the $id of the referenced type
  3. The definitions block is omitted entirely
  4. The $id is used as the key in the returned Record<string, JSONSchema>

This is designed for use with AJV's global registry, where schemas are registered by $id and cross-references are resolved at validation time.

Example with AJV:

import { toJsonSchemas } from "ts-source-to-json-schema";
import Ajv from "ajv";

const schemas = toJsonSchemas(`
  interface User { id: string; name: string; }
  interface Post { title: string; author: User; }
`, {
  defineId: (name) => \`schemas.\${name}\`
});

// schemas = {
//   "schemas.User": {
//     "$id": "schemas.User",
//     "$schema": "...",
//     "type": "object",
//     "properties": { "id": { "type": "string" }, "name": { "type": "string" } }
//   },
//   "schemas.Post": {
//     "$id": "schemas.Post",
//     "$schema": "...",
//     "type": "object",
//     "properties": { "title": { "type": "string" }, "author": { "$ref": "schemas.User" } }
//   }
// }

// Register all schemas with AJV
const ajv = new Ajv();
for (const schema of Object.values(schemas)) {
  ajv.addSchema(schema);
}

// Validate — AJV resolves $ref: "schemas.User" from its global registry
const validate = ajv.getSchema("schemas.Post");
validate({ title: "Hello", author: { id: "1", name: "Joel" } }); // ✅

With file context for namespacing:

const schemas = toJsonSchemasFromFiles('src/schemas/**/*.ts', {
  followImports: 'local',
  defineId: (name, decl, ctx) => {
    const dir = ctx?.relativePath.replace(/\.ts$/, '').replace(/\//g, '.') ?? '';
    return dir ? \`\${dir}.\${name}\` : name;
  }
});

Can be combined with defineNameTransform — both options are independent. defineId controls $id/external $ref, while defineNameTransform controls $defs/#/definitions/ names.

Throws if the callback returns duplicate $id values for different types.

What it doesn't handle

Anything that requires the type checker to evaluate:

  • Conditional types (T extends U ? X : Y)
  • Mapped types ({ [K in keyof T]: ... })
  • Template literal types (`${A}-${B}`)
  • typeof, keyof, infer
  • node_modules imports (planned for future)

Generic type parameter notes:

  • Type parameter names are parsed from the declaration, so any names work: Box<TValue>, Pair<First, Second>
  • Constraints (extends ...) and defaults (= ...) are parsed but not enforced or applied — a type argument must always be passed explicitly

Function and method types (onChange: (v: string) => void, save(): void) are not representable in JSON Schema. They are parsed and omitted from the output rather than treated as errors.

If you need these, use ts-json-schema-generator. If your types look like API contracts and tool definitions, this is probably all you need.

版本与发布记录

@flink-app/ts-source-to-json-schema 共发布 10 个版本,首次发布于 2026/02/25;过去 12 个月发布了 10 个版本。

版本发布日期距今
0.1.9latest2026/08/192 个月前
0.1.82026/06/035 个月前
0.1.72026/03/118 个月前
0.1.62026/03/038 个月前
0.1.52026/02/278 个月前
0.1.42026/02/278 个月前

关于 @flink-app/ts-source-to-json-schema 的常见问题

@flink-app/ts-source-to-json-schema 是什么?

A zero-dependency TypeScript-to-JSON Schema converter that uses its own mini-parser

如何安装 @flink-app/ts-source-to-json-schema?

在项目目录运行 npm install @flink-app/ts-source-to-json-schema,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 ts-source-to-json-schema,可以用 npx ts-source-to-json-schema 直接运行。

@flink-app/ts-source-to-json-schema 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@flink-app/ts-source-to-json-schema 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@flink-app/ts-source-to-json-schema 可以商用吗?

@flink-app/ts-source-to-json-schema 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@flink-app/ts-source-to-json-schema 的下载量有多少?

根据 npm 官方下载统计,@flink-app/ts-source-to-json-schema 在 2026/10/15 – 2026/11/13 这 30 天内被下载 4,206 次,日均约 140 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@flink-app/ts-source-to-json-schema 还在维护吗?

最新版本 0.1.9 发布于 2026/08/19(2 个月前);至今共发布 10 个版本,其中过去 12 个月发布了 10 个;首次发布于 2026/02/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。