@type-crafter/mcp 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,7 +6,35 @@ import path from 'path';
6
6
  import { parse as parseYaml } from 'yaml';
7
7
  import { exec } from 'child_process';
8
8
  import { promisify } from 'util';
9
+ import { z } from 'zod';
9
10
  const execAsync = promisify(exec);
11
+ // Zod schemas for all tools
12
+ const generateTypesSchema = z.object({
13
+ language: z
14
+ .enum(['typescript', 'typescript-with-decoders'])
15
+ .describe('Target language for type generation'),
16
+ specFilePath: z.string().describe('Path to the YAML specification file'),
17
+ outputDirectory: z.string().describe('Directory where generated types will be written'),
18
+ typesWriterMode: z
19
+ .enum(['SingleFile', 'Files'])
20
+ .optional()
21
+ .describe('Writer mode for types: SingleFile or Files'),
22
+ groupedTypesWriterMode: z
23
+ .enum(['FolderWithFiles', 'SingleFile'])
24
+ .optional()
25
+ .describe('Writer mode for grouped types: FolderWithFiles or SingleFile'),
26
+ });
27
+ const validateSpecSchema = z.object({
28
+ specFilePath: z.string().describe('Path to the YAML specification file to validate'),
29
+ });
30
+ const listLanguagesSchema = z.object({});
31
+ const getSpecInfoSchema = z.object({
32
+ specFilePath: z.string().describe('Path to the YAML specification file'),
33
+ });
34
+ const getSpecRulesSchema = z.object({});
35
+ const checkSpecSchema = z.object({
36
+ specFilePath: z.string().describe('Path to the YAML specification file to check for common mistakes'),
37
+ });
10
38
  // Type guards
11
39
  function isRecord(value) {
12
40
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -67,74 +95,10 @@ const server = new McpServer({
67
95
  tools: {},
68
96
  },
69
97
  });
70
- // Schema definitions for all tools
71
- const generateTypesSchema = {
72
- type: 'object',
73
- properties: {
74
- language: {
75
- type: 'string',
76
- description: 'Target language for type generation',
77
- enum: ['typescript', 'typescript-with-decoders'],
78
- },
79
- specFilePath: {
80
- type: 'string',
81
- description: 'Path to the YAML specification file',
82
- },
83
- outputDirectory: {
84
- type: 'string',
85
- description: 'Directory where generated types will be written',
86
- },
87
- typesWriterMode: {
88
- type: 'string',
89
- description: 'Writer mode for types: SingleFile or Files',
90
- enum: ['SingleFile', 'Files'],
91
- },
92
- groupedTypesWriterMode: {
93
- type: 'string',
94
- description: 'Writer mode for grouped types: FolderWithFiles or SingleFile',
95
- enum: ['FolderWithFiles', 'SingleFile'],
96
- },
97
- },
98
- required: ['language', 'specFilePath', 'outputDirectory'],
99
- additionalProperties: false,
100
- };
101
- const validateSpecSchema = {
102
- type: 'object',
103
- properties: {
104
- specFilePath: {
105
- type: 'string',
106
- description: 'Path to the YAML specification file to validate',
107
- },
108
- },
109
- required: ['specFilePath'],
110
- additionalProperties: false,
111
- };
112
- const listLanguagesSchema = {
113
- type: 'object',
114
- properties: {},
115
- additionalProperties: false,
116
- };
117
- const getSpecInfoSchema = {
118
- type: 'object',
119
- properties: {
120
- specFilePath: {
121
- type: 'string',
122
- description: 'Path to the YAML specification file',
123
- },
124
- },
125
- required: ['specFilePath'],
126
- additionalProperties: false,
127
- };
128
- const getSpecRulesSchema = {
129
- type: 'object',
130
- properties: {},
131
- additionalProperties: false,
132
- };
133
98
  // Register generate-types tool
134
99
  server.registerTool('generate-types', {
135
100
  description: 'Generate type definitions from a YAML specification file. Supports TypeScript and TypeScript with decoders. ' +
136
101
  'The YAML spec should follow the Type Crafter format with info, types, and/or groupedTypes sections.',
137
- // @ts-expect-error - MCP SDK schema type mismatch
138
102
  inputSchema: generateTypesSchema,
139
103
  }, async (args) => {
140
104
  if (!isGenerateTypesArgs(args)) {
@@ -221,7 +185,6 @@ server.registerTool('generate-types', {
221
185
  server.registerTool('validate-spec', {
222
186
  description: 'Validate a YAML specification file without generating types. ' +
223
187
  'Checks if the spec file is valid and can be processed by Type Crafter.',
224
- // @ts-expect-error - MCP SDK schema type mismatch
225
188
  inputSchema: validateSpecSchema,
226
189
  }, async (args) => {
227
190
  if (!isSpecFilePathArgs(args)) {
@@ -283,7 +246,6 @@ server.registerTool('validate-spec', {
283
246
  // Register list-languages tool
284
247
  server.registerTool('list-languages', {
285
248
  description: 'List all supported target languages for type generation',
286
- // @ts-expect-error - MCP SDK schema type mismatch
287
249
  inputSchema: listLanguagesSchema,
288
250
  }, async () => {
289
251
  return {
@@ -299,7 +261,6 @@ server.registerTool('list-languages', {
299
261
  server.registerTool('get-spec-info', {
300
262
  description: 'Get information about a YAML specification file including version, title, ' +
301
263
  'and counts of types and grouped types defined in the spec.',
302
- // @ts-expect-error - MCP SDK schema type mismatch
303
264
  inputSchema: getSpecInfoSchema,
304
265
  }, async (args) => {
305
266
  if (!isSpecFilePathArgs(args)) {
@@ -391,7 +352,6 @@ server.registerTool('get-spec-rules', {
391
352
  description: 'Get comprehensive rules and guidelines for writing Type Crafter YAML specification files. ' +
392
353
  'This provides LLMs with detailed information about the YAML spec format, type mappings, nullable types, ' +
393
354
  'references, composition, best practices, and common patterns. Use this before creating or modifying spec files.',
394
- // @ts-expect-error - MCP SDK schema type mismatch
395
355
  inputSchema: getSpecRulesSchema,
396
356
  }, async () => {
397
357
  try {
@@ -430,6 +390,187 @@ server.registerTool('get-spec-rules', {
430
390
  };
431
391
  }
432
392
  });
393
+ // Register check-spec tool
394
+ server.registerTool('check-spec', {
395
+ description: 'Check a YAML specification file for common mistakes and provide helpful feedback. ' +
396
+ 'This tool detects issues like using nullable/optional properties, incorrect array definitions, ' +
397
+ 'wrong file paths, and other common errors. Use this before generating types to catch mistakes early.',
398
+ inputSchema: checkSpecSchema,
399
+ }, async (args) => {
400
+ if (!isSpecFilePathArgs(args)) {
401
+ return {
402
+ content: [
403
+ {
404
+ type: 'text',
405
+ text: 'Error: Invalid arguments provided',
406
+ },
407
+ ],
408
+ isError: true,
409
+ };
410
+ }
411
+ const { specFilePath } = args;
412
+ // Resolve path
413
+ const resolvedSpecPath = path.resolve(specFilePath);
414
+ // Check if spec file exists
415
+ try {
416
+ await fs.access(resolvedSpecPath);
417
+ }
418
+ catch {
419
+ return {
420
+ content: [
421
+ {
422
+ type: 'text',
423
+ text: `Error: Specification file not found at ${resolvedSpecPath}`,
424
+ },
425
+ ],
426
+ isError: true,
427
+ };
428
+ }
429
+ // Read the spec file
430
+ let specContent;
431
+ try {
432
+ specContent = await fs.readFile(resolvedSpecPath, 'utf-8');
433
+ }
434
+ catch (error) {
435
+ if (!isExecError(error)) {
436
+ return {
437
+ content: [
438
+ {
439
+ type: 'text',
440
+ text: 'Error: Unknown error occurred while reading spec file',
441
+ },
442
+ ],
443
+ isError: true,
444
+ };
445
+ }
446
+ return {
447
+ content: [
448
+ {
449
+ type: 'text',
450
+ text: `Error reading spec file: ${error.message}`,
451
+ },
452
+ ],
453
+ isError: true,
454
+ };
455
+ }
456
+ const issues = [];
457
+ const warnings = [];
458
+ // Check for common mistakes
459
+ const lines = specContent.split('\n');
460
+ lines.forEach((line, index) => {
461
+ const lineNum = index + 1;
462
+ // Check for 'nullable: true'
463
+ if (line.match(/nullable\s*:\s*true/i)) {
464
+ issues.push(`Line ${lineNum}: Found 'nullable: true' - This property does NOT exist in Type Crafter. ` +
465
+ `Use the 'required' array to control nullability instead.`);
466
+ }
467
+ // Check for 'optional: true'
468
+ if (line.match(/optional\s*:\s*true/i)) {
469
+ issues.push(`Line ${lineNum}: Found 'optional: true' - This property does NOT exist in Type Crafter. ` +
470
+ `Use the 'required' array to control optionality instead.`);
471
+ }
472
+ // Check for property names with '?'
473
+ if (line.match(/^\s+[\w]+\?\s*:/)) {
474
+ issues.push(`Line ${lineNum}: Found property name with '?' suffix - This syntax is NOT supported. ` +
475
+ `Use the 'required' array instead.`);
476
+ }
477
+ // Check for type: [string, null] pattern
478
+ if (line.match(/type\s*:\s*\[.*,\s*null\]/)) {
479
+ issues.push(`Line ${lineNum}: Found 'type: [type, null]' pattern - This is NOT supported. ` +
480
+ `Use the 'required' array to control nullability instead.`);
481
+ }
482
+ // Check for top-level array types (heuristic)
483
+ if (line.match(/^\w+:\s*$/) && lines[index + 1]?.match(/^\s+type\s*:\s*array/)) {
484
+ warnings.push(`Line ${lineNum}: Possible top-level array type - Arrays cannot be top-level types. ` +
485
+ `They must be properties within objects. Verify this is inside an object's properties.`);
486
+ }
487
+ // Check for '../' in $ref paths
488
+ if (line.match(/\$ref\s*:\s*['"].*\.\.\//)) {
489
+ issues.push(`Line ${lineNum}: Found relative path with '../' in $ref - Paths should be from project root, ` +
490
+ `not relative to the current file. Use './path/from/root/file.yaml#/Type' format.`);
491
+ }
492
+ // Check for missing './' prefix in external $ref
493
+ if (line.match(/\$ref\s*:\s*['"][^#'][^/]/)) {
494
+ const match = line.match(/\$ref\s*:\s*['"]([^'"]+)['"]/);
495
+ if (match && match[1] && !match[1].startsWith('#') && !match[1].startsWith('./')) {
496
+ warnings.push(`Line ${lineNum}: External $ref path should start with './' - ` +
497
+ `Use './path/from/root/file.yaml#/Type' format.`);
498
+ }
499
+ }
500
+ });
501
+ // Try to parse YAML and check for structural issues
502
+ try {
503
+ const specData = parseYaml(specContent);
504
+ if (!isRecord(specData)) {
505
+ issues.push('Spec file root is not an object. Expected YAML object at root level.');
506
+ }
507
+ else {
508
+ // Check for missing info section
509
+ if (!isRecord(specData.info)) {
510
+ issues.push("Missing 'info' section - Every spec file must have an 'info' section with 'version' and 'title'.");
511
+ }
512
+ else if (!isSpecInfo(specData.info)) {
513
+ if (typeof specData.info.version !== 'string') {
514
+ issues.push("Missing 'info.version' - Must be a string in semver format (e.g., '1.0.0').");
515
+ }
516
+ if (typeof specData.info.title !== 'string') {
517
+ issues.push("Missing 'info.title' - Must be a string describing the spec.");
518
+ }
519
+ }
520
+ // Check if at least one of types or groupedTypes exists
521
+ const hasTypes = isRecord(specData.types);
522
+ const hasGroupedTypes = isRecord(specData.groupedTypes);
523
+ if (!hasTypes && !hasGroupedTypes) {
524
+ issues.push("Missing 'types' or 'groupedTypes' section - At least one must be defined.");
525
+ }
526
+ }
527
+ }
528
+ catch (error) {
529
+ if (isExecError(error)) {
530
+ issues.push(`YAML parsing error: ${error.message}`);
531
+ }
532
+ else {
533
+ issues.push('YAML parsing error: Unable to parse spec file.');
534
+ }
535
+ }
536
+ // Generate response
537
+ if (issues.length === 0 && warnings.length === 0) {
538
+ return {
539
+ content: [
540
+ {
541
+ type: 'text',
542
+ text: '✅ No common mistakes detected!\n\nThe spec file looks good. You can proceed with validation using validate-spec or generation using generate-types.',
543
+ },
544
+ ],
545
+ };
546
+ }
547
+ let response = '';
548
+ if (issues.length > 0) {
549
+ response += '❌ Issues Found:\n\n';
550
+ issues.forEach((issue, idx) => {
551
+ response += `${idx + 1}. ${issue}\n\n`;
552
+ });
553
+ }
554
+ if (warnings.length > 0) {
555
+ if (issues.length > 0)
556
+ response += '\n';
557
+ response += '⚠️ Warnings:\n\n';
558
+ warnings.forEach((warning, idx) => {
559
+ response += `${idx + 1}. ${warning}\n\n`;
560
+ });
561
+ }
562
+ response +=
563
+ '\n📖 For detailed rules and examples, use the get-spec-rules tool to see complete documentation.';
564
+ return {
565
+ content: [
566
+ {
567
+ type: 'text',
568
+ text: response,
569
+ },
570
+ ],
571
+ isError: issues.length > 0,
572
+ };
573
+ });
433
574
  // Start the server
434
575
  async function main() {
435
576
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@type-crafter/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "MCP server for Type Crafter - generate types from YAML specifications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/GUIDE.md CHANGED
@@ -42,7 +42,7 @@ types: export type User = {
42
42
 
43
43
  **When to use:** ALWAYS use this FIRST before creating or modifying YAML specs
44
44
 
45
- **What it does:** Returns comprehensive rules for writing Type Crafter YAML specs
45
+ **What it does:** Returns comprehensive rules for writing Type Crafter YAML specs, including common mistakes section, multi-file spec patterns, and detailed examples
46
46
 
47
47
  **Workflow:**
48
48
 
@@ -50,27 +50,75 @@ types: export type User = {
50
50
  User asks: "Create types for my API"
51
51
 
52
52
  1. Call get-spec-rules to learn the format
53
- 2. Read the returned rules (nullable types, arrays, paths, etc.)
53
+ 2. Read the returned rules (especially common mistakes, nullable types, arrays, paths)
54
54
  3. Now you know how to write valid YAML specs
55
55
  ```
56
56
 
57
- ### 2. `validate-spec`
57
+ **Important:** The rules include a "Common Mistakes" section at the top that shows what NOT to do (like using `nullable: true`, `optional: true`, or `?` suffixes).
58
58
 
59
- **When to use:** After creating or modifying a YAML spec
59
+ ### 2. `check-spec` 🔍
60
60
 
61
- **What it does:** Validates the spec structure without generating types
61
+ **When to use:** IMMEDIATELY after creating or modifying a YAML spec, BEFORE validation or generation
62
+
63
+ **What it does:** Scans the spec file for common mistakes and provides specific feedback with line numbers
64
+
65
+ **Detects:**
66
+
67
+ - Invalid properties like `nullable: true` or `optional: true`
68
+ - Property names with `?` suffix
69
+ - Incorrect type arrays like `type: [string, null]`
70
+ - Top-level array types (which are invalid)
71
+ - Wrong file paths (using `../` or missing `./` prefix)
72
+ - Missing `info` section
73
+ - Structural issues
62
74
 
63
75
  **Workflow:**
64
76
 
65
77
  ```
66
- You created: my-types.yaml
78
+ You created/modified: my-types.yaml
79
+
80
+ 1. Call check-spec with path to my-types.yaml
81
+ 2. Review issues and warnings with specific line numbers
82
+ 3. Fix all issues before proceeding
83
+ 4. Run check-spec again to verify fixes
84
+ ```
85
+
86
+ **Example Output:**
87
+
88
+ ```
89
+ ❌ Issues Found:
90
+
91
+ 1. Line 15: Found 'nullable: true' - This property does NOT exist in Type Crafter.
92
+ Use the 'required' array to control nullability instead.
93
+
94
+ 2. Line 42: Found relative path with '../' in $ref - Paths should be from project root.
95
+ Use './path/from/root/file.yaml#/Type' format.
96
+
97
+ ⚠️ Warnings:
98
+
99
+ 1. Line 28: Possible top-level array type - Arrays cannot be top-level types.
100
+ They must be properties within objects.
101
+ ```
102
+
103
+ ### 3. `validate-spec` ✅
104
+
105
+ **When to use:** After check-spec passes, to do full structural validation
106
+
107
+ **What it does:** Validates the spec structure and reports counts of types
108
+
109
+ **Workflow:**
110
+
111
+ ```
112
+ check-spec passed
67
113
 
68
114
  1. Call validate-spec with path to my-types.yaml
69
- 2. Check if valid or see error messages
70
- 3. Fix errors if needed and validate again
115
+ 2. Get confirmation with type counts
116
+ 3. Proceed to generation
71
117
  ```
72
118
 
73
- ### 3. `generate-types` 🔨
119
+ **Note:** Use `check-spec` first to catch common mistakes, then `validate-spec` for final structural validation.
120
+
121
+ ### 4. `generate-types` 🔨
74
122
 
75
123
  **When to use:** After spec is validated and user wants TypeScript output
76
124
 
@@ -94,7 +142,7 @@ Spec is valid
94
142
  3. User can now import and use them in TypeScript
95
143
  ```
96
144
 
97
- ### 4. `get-spec-info` 📊
145
+ ### 5. `get-spec-info` 📊
98
146
 
99
147
  **When to use:** To understand what's in an existing spec file
100
148
 
@@ -110,7 +158,7 @@ User asks: "What types are in my spec?"
110
158
  3. Understand the structure before modifying
111
159
  ```
112
160
 
113
- ### 5. `list-languages` 📝
161
+ ### 6. `list-languages` 📝
114
162
 
115
163
  **When to use:** User asks what output formats are supported
116
164
 
@@ -121,6 +169,35 @@ User asks: "What types are in my spec?"
121
169
  - `typescript` - TypeScript type definitions
122
170
  - `typescript-with-decoders` - TypeScript + runtime decoders
123
171
 
172
+ ## Quick Decision Tree
173
+
174
+ **What tool should I use?**
175
+
176
+ ```
177
+ User asks to create/modify spec file?
178
+ → get-spec-rules FIRST (learn the format)
179
+ → Create/modify the file
180
+ → check-spec (catch common mistakes)
181
+ → validate-spec (structural validation)
182
+ → generate-types (create TypeScript)
183
+
184
+ User asks "is my spec valid?"
185
+ → check-spec (find specific issues)
186
+ → validate-spec (confirm structure)
187
+
188
+ User asks "what types are in this spec?"
189
+ → get-spec-info
190
+
191
+ User asks "what languages are supported?"
192
+ → list-languages
193
+
194
+ User wants to generate types?
195
+ → Ensure spec is valid first (check-spec + validate-spec)
196
+ → generate-types
197
+ ```
198
+
199
+ **IMPORTANT: Always use check-spec IMMEDIATELY after creating or modifying a spec file. It will catch common mistakes like `nullable: true`, `optional: true`, wrong file paths, and more.**
200
+
124
201
  ## Complete Workflows
125
202
 
126
203
  ### Workflow 1: Creating Types from Scratch
@@ -130,7 +207,8 @@ User: "I need types for my e-commerce cart API"
130
207
 
131
208
  Step 1: Learn the rules
132
209
  → Call: get-spec-rules
133
- → Read and understand: nullable types, arrays, structure
210
+ → Read and understand: common mistakes, nullable types, arrays, structure
211
+ → Pay special attention to the "Common Mistakes" section
134
212
 
135
213
  Step 2: Gather requirements
136
214
  → Ask user: What fields does the cart have?
@@ -140,16 +218,22 @@ Step 3: Create YAML spec
140
218
  → Create spec file following the rules
141
219
  → Remember: Properties not in 'required' → Type | null
142
220
  → Remember: Arrays must be properties, not top-level
221
+ → Remember: NO nullable/optional properties, use 'required' array
222
+
223
+ Step 4: Check for common mistakes
224
+ → Call: check-spec with the spec path
225
+ → Review any issues or warnings with line numbers
226
+ → Fix all issues before proceeding
143
227
 
144
- Step 4: Validate
228
+ Step 5: Validate structure
145
229
  → Call: validate-spec with the spec path
146
- Fix any errors
230
+ Confirm type counts are correct
147
231
 
148
- Step 5: Generate
232
+ Step 6: Generate
149
233
  → Call: generate-types with language and paths
150
234
  → Types are now available in output directory
151
235
 
152
- Step 6: Confirm
236
+ Step 7: Confirm
153
237
  → Tell user where types were generated
154
238
  → Optionally show a snippet of generated types
155
239
  ```
@@ -161,6 +245,7 @@ User: "Add a 'description' field to the Product type"
161
245
 
162
246
  Step 1: Understand current spec
163
247
  → Call: get-spec-info to see what types exist
248
+ → Identify where Product type is defined
164
249
  → Optionally: get-spec-rules to refresh on rules
165
250
 
166
251
  Step 2: Read the spec file
@@ -169,11 +254,16 @@ Step 2: Read the spec file
169
254
  Step 3: Modify the spec
170
255
  → Add the new field
171
256
  → Decide if it's required or optional (Type | null)
257
+ → Use 'required' array ONLY, no nullable/optional properties
172
258
 
173
- Step 4: Validate
259
+ Step 4: Check for mistakes
260
+ → Call: check-spec with the modified spec path
261
+ → Fix any issues found
262
+
263
+ Step 5: Validate structure
174
264
  → Call: validate-spec
175
265
 
176
- Step 5: Regenerate
266
+ Step 6: Regenerate
177
267
  → Call: generate-types to update TypeScript files
178
268
  ```
179
269
 
@@ -182,14 +272,18 @@ Step 5: Regenerate
182
272
  ```
183
273
  User: "Can you check if my spec is valid?"
184
274
 
185
- Step 1: Validate
275
+ Step 1: Check for common mistakes
276
+ → Call: check-spec with their spec path
277
+ → Report any issues with line numbers
278
+
279
+ Step 2: Validate structure (if no issues)
186
280
  → Call: validate-spec with their spec path
187
281
 
188
- Step 2: Report results
282
+ Step 3: Report results
189
283
  → If valid: Confirm and show summary
190
- → If invalid: Explain errors and suggest fixes
284
+ → If issues: Explain errors and suggest fixes
191
285
 
192
- Step 3: Optionally generate
286
+ Step 4: Optionally generate
193
287
  → Ask if they want to generate types now
194
288
  → Call: generate-types if yes
195
289
  ```
package/src/SPEC_RULES.md CHANGED
@@ -6,6 +6,8 @@ Complete guide for writing Type Crafter YAML specifications and understanding Ty
6
6
 
7
7
  ## Table of Contents
8
8
 
9
+ - [🚨 Common Mistakes (Read This First)](#-common-mistakes-read-this-first)
10
+ - [Multi-File Specifications](#multi-file-specifications)
9
11
  - [Root Structure](#root-structure)
10
12
  - [Data Types & Type Mapping](#data-types--type-mapping)
11
13
  - [Constraints & Limitations](#constraints--limitations)
@@ -20,6 +22,359 @@ Complete guide for writing Type Crafter YAML specifications and understanding Ty
20
22
 
21
23
  ---
22
24
 
25
+ ## 🚨 Common Mistakes (Read This First)
26
+
27
+ ### ❌ NEVER Do These
28
+
29
+ ```yaml
30
+ # ❌ WRONG: Adding 'nullable: true' property
31
+ User:
32
+ type: object
33
+ properties:
34
+ name:
35
+ type: string
36
+ nullable: true # ❌ INVALID! This property does NOT exist
37
+
38
+ # ❌ WRONG: Using 'optional: true' property
39
+ User:
40
+ type: object
41
+ properties:
42
+ name:
43
+ type: string
44
+ optional: true # ❌ INVALID! This property does NOT exist
45
+
46
+ # ❌ WRONG: Using '?' suffix for optional
47
+ User:
48
+ type: object
49
+ properties:
50
+ name?: # ❌ INVALID! Don't use ? suffix
51
+ type: string
52
+
53
+ # ❌ WRONG: Creating top-level array types
54
+ Tags:
55
+ type: array # ❌ INVALID! Arrays cannot be top-level types
56
+ items:
57
+ type: string
58
+
59
+ # ❌ WRONG: Using relative paths from current file
60
+ # File: src/api/users.yaml
61
+ User:
62
+ type: object
63
+ properties:
64
+ profile:
65
+ $ref: './profile.yaml#/Profile' # ❌ WRONG! Not relative to current file
66
+ ```
67
+
68
+ ### ✅ CORRECT Ways
69
+
70
+ ```yaml
71
+ # ✅ CORRECT: Use 'required' array to control nullable
72
+ User:
73
+ type: object
74
+ required:
75
+ - id # id is required (NOT nullable)
76
+ # name is NOT in required, so it's nullable
77
+ properties:
78
+ id:
79
+ type: string # Generates: string
80
+ name:
81
+ type: string # Generates: string | null (because not in required)
82
+
83
+ # ✅ CORRECT: Arrays must be properties within objects
84
+ Post:
85
+ type: object
86
+ properties:
87
+ tags:
88
+ type: array
89
+ items:
90
+ type: string
91
+
92
+ # ✅ CORRECT: Use paths from project root
93
+ # File: src/api/users.yaml (running type-crafter from project root)
94
+ User:
95
+ type: object
96
+ properties:
97
+ profile:
98
+ $ref: './src/api/profile.yaml#/Profile' # ✅ From project root
99
+ ```
100
+
101
+ ### 🔑 Key Rules to Remember
102
+
103
+ 1. **Nullable is controlled by `required` array ONLY**
104
+ - In `required` → generates `Type`
105
+ - NOT in `required` → generates `Type | null`
106
+ - No properties like `nullable`, `optional`, `?` exist
107
+
108
+ 2. **Arrays cannot be top-level types**
109
+ - Arrays MUST be properties within objects
110
+ - Use `type: array` with `items` specification
111
+
112
+ 3. **File paths are from project root**
113
+ - NOT relative to current YAML file
114
+ - Always use `./path/from/root/file.yaml#/Type`
115
+
116
+ 4. **Every spec file needs `info` section**
117
+ - Even sub-files need `info: { version, title }`
118
+ - Top-level file and all referenced files must have `info`
119
+
120
+ ---
121
+
122
+ ## Multi-File Specifications
123
+
124
+ ### File Organization Patterns
125
+
126
+ #### Pattern 1: Single Top-Level File
127
+
128
+ **Best for:** Small to medium projects
129
+
130
+ ```
131
+ project/
132
+ ├── types.yaml # Single file with all types
133
+ └── src/
134
+ └── index.ts
135
+ ```
136
+
137
+ **types.yaml:**
138
+ ```yaml
139
+ info:
140
+ version: '1.0.0'
141
+ title: 'Project Types'
142
+
143
+ types:
144
+ User:
145
+ type: object
146
+ properties:
147
+ id: { type: string }
148
+
149
+ Post:
150
+ type: object
151
+ properties:
152
+ author: { $ref: '#/types/User' }
153
+ ```
154
+
155
+ #### Pattern 2: Multiple Files with Main Entry Point
156
+
157
+ **Best for:** Large projects with logical domain separation
158
+
159
+ ```
160
+ project/
161
+ ├── types/
162
+ │ ├── index.yaml # Main entry point (top-level file)
163
+ │ ├── user.yaml # User-related types (sub-file)
164
+ │ ├── post.yaml # Post-related types (sub-file)
165
+ │ └── comment.yaml # Comment-related types (sub-file)
166
+ └── src/
167
+ └── index.ts
168
+ ```
169
+
170
+ **types/index.yaml (Top-level file):**
171
+ ```yaml
172
+ info:
173
+ version: '1.0.0'
174
+ title: 'Main API Types'
175
+
176
+ # Import types from other files
177
+ types:
178
+ # Reference to external file (from project root)
179
+ User:
180
+ $ref: './types/user.yaml#/User'
181
+
182
+ Post:
183
+ $ref: './types/post.yaml#/Post'
184
+
185
+ Comment:
186
+ $ref: './types/comment.yaml#/Comment'
187
+ ```
188
+
189
+ **types/user.yaml (Sub-file):**
190
+ ```yaml
191
+ info:
192
+ version: '1.0.0'
193
+ title: 'User Types'
194
+
195
+ types:
196
+ User:
197
+ type: object
198
+ required:
199
+ - id
200
+ - email
201
+ properties:
202
+ id:
203
+ type: string
204
+ email:
205
+ type: string
206
+ name:
207
+ type: string # nullable (not in required)
208
+ ```
209
+
210
+ **types/post.yaml (Sub-file):**
211
+ ```yaml
212
+ info:
213
+ version: '1.0.0'
214
+ title: 'Post Types'
215
+
216
+ types:
217
+ Post:
218
+ type: object
219
+ required:
220
+ - id
221
+ - title
222
+ - author
223
+ properties:
224
+ id:
225
+ type: string
226
+ title:
227
+ type: string
228
+ author:
229
+ # Reference to user.yaml from project root
230
+ $ref: './types/user.yaml#/User'
231
+ ```
232
+
233
+ #### Pattern 3: Grouped Types with External References
234
+
235
+ **Best for:** Complex domains with many related types
236
+
237
+ ```
238
+ project/
239
+ ├── specs/
240
+ │ ├── api.yaml # Main entry point
241
+ │ ├── auth/
242
+ │ │ └── types.yaml # Auth domain types
243
+ │ └── shop/
244
+ │ ├── cart.yaml # Shopping cart types
245
+ │ └── product.yaml # Product types
246
+ └── src/
247
+ ```
248
+
249
+ **specs/api.yaml (Top-level file):**
250
+ ```yaml
251
+ info:
252
+ version: '1.0.0'
253
+ title: 'API Specification'
254
+
255
+ groupedTypes:
256
+ # Import entire groups from external files
257
+ Auth:
258
+ $ref: './specs/auth/types.yaml#/AuthTypes'
259
+
260
+ Shop:
261
+ Cart:
262
+ $ref: './specs/shop/cart.yaml#/ShopTypes/Cart'
263
+ Product:
264
+ $ref: './specs/shop/product.yaml#/ShopTypes/Product'
265
+ ```
266
+
267
+ **specs/auth/types.yaml:**
268
+ ```yaml
269
+ info:
270
+ version: '1.0.0'
271
+ title: 'Authentication Types'
272
+
273
+ groupedTypes:
274
+ AuthTypes:
275
+ LoginRequest:
276
+ type: object
277
+ required:
278
+ - email
279
+ - password
280
+ properties:
281
+ email: { type: string }
282
+ password: { type: string }
283
+
284
+ LoginResponse:
285
+ type: object
286
+ required:
287
+ - token
288
+ - user
289
+ properties:
290
+ token: { type: string }
291
+ user:
292
+ $ref: './specs/user.yaml#/User' # Cross-file reference
293
+ ```
294
+
295
+ ### Multi-File Reference Rules
296
+
297
+ #### 1. Every File Needs `info` Section
298
+
299
+ ```yaml
300
+ # ✅ CORRECT: All files need info section
301
+ info:
302
+ version: '1.0.0'
303
+ title: 'File Title'
304
+
305
+ types:
306
+ # ... your types
307
+ ```
308
+
309
+ #### 2. Reference Format
310
+
311
+ ```yaml
312
+ # Local reference (same file)
313
+ $ref: '#/types/TypeName'
314
+ $ref: '#/groupedTypes/GroupName/TypeName'
315
+
316
+ # External reference (from project root)
317
+ $ref: './path/from/root/file.yaml#/TypeName'
318
+ $ref: './path/from/root/file.yaml#/GroupName/TypeName'
319
+ ```
320
+
321
+ #### 3. Path Resolution
322
+
323
+ **CRITICAL:** All paths are from **project root** (where you run `type-crafter` command)
324
+
325
+ ```bash
326
+ # If you run this command from /project
327
+ cd /project
328
+ type-crafter generate typescript ./specs/api.yaml ./output
329
+
330
+ # Then all $ref paths in ANY file are relative to /project
331
+ ```
332
+
333
+ **Example:**
334
+ ```
335
+ /project/
336
+ ├── specs/
337
+ │ ├── api.yaml # File A
338
+ │ └── user/
339
+ │ └── types.yaml # File B
340
+ ```
341
+
342
+ **In specs/api.yaml:**
343
+ ```yaml
344
+ # ✅ CORRECT: Path from project root
345
+ User:
346
+ $ref: './specs/user/types.yaml#/User'
347
+
348
+ # ❌ WRONG: Don't use relative path from current file
349
+ User:
350
+ $ref: './user/types.yaml#/User'
351
+ ```
352
+
353
+ **In specs/user/types.yaml:**
354
+ ```yaml
355
+ # ✅ CORRECT: Path from project root
356
+ Profile:
357
+ $ref: './specs/api.yaml#/ApiTypes/Profile'
358
+
359
+ # ❌ WRONG: Don't use relative path
360
+ Profile:
361
+ $ref: '../api.yaml#/ApiTypes/Profile'
362
+ ```
363
+
364
+ ### Generating Types from Multi-File Specs
365
+
366
+ ```bash
367
+ # Generate from top-level file (references will be resolved automatically)
368
+ type-crafter generate typescript ./types/index.yaml ./output
369
+
370
+ # Or from any entry point
371
+ type-crafter generate typescript ./specs/api.yaml ./src/types
372
+ ```
373
+
374
+ All referenced files will be automatically processed and types generated.
375
+
376
+ ---
377
+
23
378
  ## Root Structure
24
379
 
25
380
  ### Required Fields
@@ -124,40 +479,242 @@ Only these can be top-level types:
124
479
 
125
480
  **Properties NOT in `required` array become nullable (`Type | null`)**
126
481
 
482
+ This is controlled EXCLUSIVELY by the `required` array. There are NO other properties or syntax for controlling nullability.
483
+
484
+ ### ❌ WRONG: What NOT to Do
485
+
486
+ ```yaml
487
+ # ❌ WRONG: Do NOT use 'nullable' property
488
+ User:
489
+ type: object
490
+ properties:
491
+ name:
492
+ type: string
493
+ nullable: true # ❌ INVALID! This property does NOT exist in Type Crafter
494
+
495
+ # ❌ WRONG: Do NOT use 'optional' property
496
+ User:
497
+ type: object
498
+ properties:
499
+ name:
500
+ type: string
501
+ optional: true # ❌ INVALID! This property does NOT exist
502
+
503
+ # ❌ WRONG: Do NOT use '?' suffix
504
+ User:
505
+ type: object
506
+ properties:
507
+ name?: # ❌ INVALID! Don't use ? in property names
508
+ type: string
509
+
510
+ # ❌ WRONG: Do NOT use null in type
511
+ User:
512
+ type: object
513
+ properties:
514
+ name:
515
+ type: [string, null] # ❌ INVALID! Don't use array of types here
516
+ ```
517
+
518
+ ### ✅ CORRECT: Use `required` Array Only
519
+
127
520
  ```yaml
521
+ # ✅ CORRECT: Control nullability with 'required' array
128
522
  User:
129
523
  type: object
130
524
  required:
131
- - id # Will be: id: string
132
- - email # Will be: email: string
525
+ - id # id is required → generates: string
526
+ - email # email is required → generates: string
527
+ # name is NOT in required → generates: string | null
528
+ # age is NOT in required → generates: number | null
133
529
  properties:
134
530
  id:
135
531
  type: string
136
532
  email:
137
533
  type: string
138
534
  name:
139
- type: string # NOT in required → becomes: string | null
535
+ type: string
140
536
  age:
141
- type: number # NOT in required → becomes: number | null
537
+ type: number
142
538
  ```
143
539
 
144
540
  **TypeScript Output:**
145
541
 
146
542
  ```typescript
147
543
  export type User = {
148
- id: string; // Required - no null
149
- email: string; // Required - no null
150
- name: string | null; // Optional - nullable
151
- age: number | null; // Optional - nullable
544
+ id: string; // Required (in required array)
545
+ email: string; // Required (in required array)
546
+ name: string | null; // Nullable (NOT in required array)
547
+ age: number | null; // Nullable (NOT in required array)
152
548
  };
153
549
  ```
154
550
 
155
- ### Rules
551
+ ### Detailed Rules
552
+
553
+ 1. ✅ **Property in `required` array** → Generates `Type` (NOT nullable)
554
+
555
+ ```yaml
556
+ User:
557
+ type: object
558
+ required:
559
+ - name
560
+ properties:
561
+ name: { type: string }
562
+ # Generates: name: string
563
+ ```
564
+
565
+ 2. ✅ **Property NOT in `required` array** → Generates `Type | null` (nullable)
566
+
567
+ ```yaml
568
+ User:
569
+ type: object
570
+ required:
571
+ - id
572
+ properties:
573
+ id: { type: string }
574
+ name: { type: string } # NOT in required
575
+ # Generates: name: string | null
576
+ ```
577
+
578
+ 3. ✅ **No `required` array at all** → All properties are `Type | null`
579
+
580
+ ```yaml
581
+ User:
582
+ type: object
583
+ # No required array
584
+ properties:
585
+ id: { type: string }
586
+ name: { type: string }
587
+ # Generates: id: string | null, name: string | null
588
+ ```
589
+
590
+ 4. ✅ **Empty `required: []`** → All properties are `Type | null`
591
+ ```yaml
592
+ User:
593
+ type: object
594
+ required: [] # Empty array
595
+ properties:
596
+ id: { type: string }
597
+ # Generates: id: string | null
598
+ ```
599
+
600
+ ### Complete Examples
601
+
602
+ #### Example 1: All Properties Required
156
603
 
157
- 1. ✅ **In `required` array** → `Type`
158
- 2. ✅ **NOT in `required` array** → `Type | null`
159
- 3. ✅ **No `required` array** → All properties are `Type | null`
160
- 4. ❌ **Empty `required: []`** → All properties are `Type | null`
604
+ ```yaml
605
+ User:
606
+ type: object
607
+ required:
608
+ - id
609
+ - email
610
+ - name
611
+ - age
612
+ properties:
613
+ id: { type: string }
614
+ email: { type: string }
615
+ name: { type: string }
616
+ age: { type: number }
617
+ ```
618
+
619
+ ```typescript
620
+ // Generated TypeScript
621
+ export type User = {
622
+ id: string;
623
+ email: string;
624
+ name: string;
625
+ age: number;
626
+ };
627
+ ```
628
+
629
+ #### Example 2: Some Properties Required
630
+
631
+ ```yaml
632
+ User:
633
+ type: object
634
+ required:
635
+ - id
636
+ - email
637
+ properties:
638
+ id: { type: string }
639
+ email: { type: string }
640
+ name: { type: string }
641
+ age: { type: number }
642
+ ```
643
+
644
+ ```typescript
645
+ // Generated TypeScript
646
+ export type User = {
647
+ id: string;
648
+ email: string;
649
+ name: string | null;
650
+ age: number | null;
651
+ };
652
+ ```
653
+
654
+ #### Example 3: No Properties Required
655
+
656
+ ```yaml
657
+ User:
658
+ type: object
659
+ properties:
660
+ id: { type: string }
661
+ email: { type: string }
662
+ name: { type: string }
663
+ age: { type: number }
664
+ ```
665
+
666
+ ```typescript
667
+ // Generated TypeScript - All nullable
668
+ export type User = {
669
+ id: string | null;
670
+ email: string | null;
671
+ name: string | null;
672
+ age: number | null;
673
+ };
674
+ ```
675
+
676
+ ### Nested Objects and Arrays
677
+
678
+ ```yaml
679
+ User:
680
+ type: object
681
+ required:
682
+ - profile
683
+ - posts
684
+ properties:
685
+ profile:
686
+ type: object
687
+ required:
688
+ - name
689
+ properties:
690
+ name: { type: string }
691
+ bio: { type: string } # NOT in profile.required
692
+ posts:
693
+ type: array
694
+ items:
695
+ type: object
696
+ required:
697
+ - title
698
+ properties:
699
+ title: { type: string }
700
+ content: { type: string } # NOT in items.required
701
+ ```
702
+
703
+ ```typescript
704
+ // Generated TypeScript
705
+ export type User = {
706
+ profile: {
707
+ // profile is required (in User.required)
708
+ name: string; // required in profile
709
+ bio: string | null; // NOT required in profile
710
+ };
711
+ posts: Array<{
712
+ // posts array is required
713
+ title: string; // required in post item
714
+ content: string | null; // NOT required in post item
715
+ }>;
716
+ };
717
+ ```
161
718
 
162
719
  ---
163
720