schemantic 0.1.0 → 0.2.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/CHANGELOG.md +48 -3
- package/README.md +17 -14
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +19 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/core/schemantic.d.ts.map +1 -1
- package/dist/core/schemantic.js +61 -13
- package/dist/core/schemantic.js.map +1 -1
- package/dist/generators/api-client-generator.d.ts.map +1 -1
- package/dist/generators/api-client-generator.js +23 -3
- package/dist/generators/api-client-generator.js.map +1 -1
- package/dist/generators/hook-generator.d.ts.map +1 -1
- package/dist/generators/hook-generator.js +37 -2
- package/dist/generators/hook-generator.js.map +1 -1
- package/dist/plugins/branded-types.js +1 -1
- package/dist/plugins/builtin.d.ts.map +1 -1
- package/dist/plugins/builtin.js +13 -6
- package/dist/plugins/builtin.js.map +1 -1
- package/dist/plugins/performance-monitoring.js +1 -1
- package/dist/plugins/request-deduplication.js +1 -1
- package/dist/plugins/zod-validation.d.ts +2 -1
- package/dist/plugins/zod-validation.d.ts.map +1 -1
- package/dist/plugins/zod-validation.js +46 -29
- package/dist/plugins/zod-validation.js.map +1 -1
- package/docs/CONFIGURATION.md +4 -4
- package/docs/FASTAPI_TUTORIAL.md +1497 -1497
- package/docs/PLUGINS.md +4 -0
- package/docs/PLUGIN_DEVELOPMENT.md +720 -720
- package/package.json +9 -5
|
@@ -1,720 +1,720 @@
|
|
|
1
|
-
# Plugin Development Guide
|
|
2
|
-
|
|
3
|
-
This guide explains how to create custom plugins for Schemantic to extend its functionality.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Schemantic's plugin system allows you to:
|
|
8
|
-
|
|
9
|
-
- Transform OpenAPI schemas before processing
|
|
10
|
-
- Customize type generation logic
|
|
11
|
-
- Add additional generators (e.g., validation schemas, documentation)
|
|
12
|
-
- Modify generated code after creation
|
|
13
|
-
- Add new CLI commands and options
|
|
14
|
-
|
|
15
|
-
## Plugin Interface
|
|
16
|
-
|
|
17
|
-
Every plugin must implement the `SchemanticPlugin` interface:
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
interface SchemanticPlugin {
|
|
21
|
-
name: string;
|
|
22
|
-
version: string;
|
|
23
|
-
description: string;
|
|
24
|
-
|
|
25
|
-
// Optional lifecycle hooks
|
|
26
|
-
beforeSchemaLoad?: (context: GenerationContext) => Promise<void>;
|
|
27
|
-
afterSchemaLoad?: (
|
|
28
|
-
schema: OpenAPIV3.Document,
|
|
29
|
-
context: GenerationContext
|
|
30
|
-
) => Promise<void>;
|
|
31
|
-
beforeTypeGeneration?: (
|
|
32
|
-
typeName: string,
|
|
33
|
-
schema: any,
|
|
34
|
-
context: GenerationContext
|
|
35
|
-
) => Promise<void>;
|
|
36
|
-
afterTypeGeneration?: (
|
|
37
|
-
typeName: string,
|
|
38
|
-
generatedType: GeneratedType,
|
|
39
|
-
context: GenerationContext
|
|
40
|
-
) => Promise<void>;
|
|
41
|
-
beforeClientGeneration?: (context: GenerationContext) => Promise<void>;
|
|
42
|
-
afterClientGeneration?: (
|
|
43
|
-
generatedClient: GeneratedClient,
|
|
44
|
-
context: GenerationContext
|
|
45
|
-
) => Promise<void>;
|
|
46
|
-
beforeOutput?: (
|
|
47
|
-
files: GeneratedFile[],
|
|
48
|
-
context: GenerationContext
|
|
49
|
-
) => Promise<void>;
|
|
50
|
-
afterOutput?: (
|
|
51
|
-
files: GeneratedFile[],
|
|
52
|
-
context: GenerationContext
|
|
53
|
-
) => Promise<void>;
|
|
54
|
-
|
|
55
|
-
// Optional transformers
|
|
56
|
-
transformSchema?: (
|
|
57
|
-
schema: OpenAPIV3.Document,
|
|
58
|
-
context: GenerationContext
|
|
59
|
-
) => OpenAPIV3.Document;
|
|
60
|
-
transformType?: (
|
|
61
|
-
generatedType: GeneratedType,
|
|
62
|
-
context: GenerationContext
|
|
63
|
-
) => GeneratedType;
|
|
64
|
-
transformClient?: (
|
|
65
|
-
generatedClient: GeneratedClient,
|
|
66
|
-
context: GenerationContext
|
|
67
|
-
) => GeneratedClient;
|
|
68
|
-
|
|
69
|
-
// Optional custom generators
|
|
70
|
-
customTypeGenerators?: { [key: string]: TypeGenerator };
|
|
71
|
-
customOutputGenerators?: OutputGenerator[];
|
|
72
|
-
|
|
73
|
-
// Optional CLI extensions
|
|
74
|
-
cliCommands?: CliCommand[];
|
|
75
|
-
cliOptions?: CliOption[];
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Core Types
|
|
80
|
-
|
|
81
|
-
### GenerationContext
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
interface GenerationContext {
|
|
85
|
-
config: SchemanticConfig;
|
|
86
|
-
schema: OpenAPIV3.Document;
|
|
87
|
-
outputDir: string;
|
|
88
|
-
logger: Logger;
|
|
89
|
-
utilities: PluginUtilities;
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### GeneratedType
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
interface GeneratedType {
|
|
97
|
-
name: string;
|
|
98
|
-
content: string;
|
|
99
|
-
dependencies: string[];
|
|
100
|
-
exports: string[];
|
|
101
|
-
isInterface: boolean;
|
|
102
|
-
isEnum: boolean;
|
|
103
|
-
isUnion: boolean;
|
|
104
|
-
sourceSchema: any;
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### GeneratedClient
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
interface GeneratedClient {
|
|
112
|
-
className: string;
|
|
113
|
-
content: string;
|
|
114
|
-
methods: GeneratedMethod[];
|
|
115
|
-
dependencies: string[];
|
|
116
|
-
exports: string[];
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### GeneratedFile
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
interface GeneratedFile {
|
|
124
|
-
path: string;
|
|
125
|
-
content: string;
|
|
126
|
-
type: "types" | "client" | "hooks" | "other";
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Creating a Basic Plugin
|
|
131
|
-
|
|
132
|
-
### 1. Simple Transform Plugin
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
import { SchemanticPlugin, GenerationContext, GeneratedType } from "schemantic";
|
|
136
|
-
|
|
137
|
-
const addCommentsPlugin: SchemanticPlugin = {
|
|
138
|
-
name: "add-comments",
|
|
139
|
-
version: "1.0.0",
|
|
140
|
-
description: "Adds helpful comments to generated types",
|
|
141
|
-
|
|
142
|
-
afterTypeGeneration: async (
|
|
143
|
-
typeName: string,
|
|
144
|
-
generatedType: GeneratedType,
|
|
145
|
-
context: GenerationContext
|
|
146
|
-
) => {
|
|
147
|
-
// Add a comment at the top of each generated type
|
|
148
|
-
const comment = `/**\n * Generated type: ${typeName}\n * Source: ${context.schema.info.title}\n */\n`;
|
|
149
|
-
generatedType.content = comment + generatedType.content;
|
|
150
|
-
},
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
export default addCommentsPlugin;
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### 2. Schema Transform Plugin
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
import { SchemanticPlugin, GenerationContext } from "schemantic";
|
|
160
|
-
import { OpenAPIV3 } from "openapi-types";
|
|
161
|
-
|
|
162
|
-
const schemaTransformPlugin: SchemanticPlugin = {
|
|
163
|
-
name: "schema-transformer",
|
|
164
|
-
version: "1.0.0",
|
|
165
|
-
description: "Transforms schema before processing",
|
|
166
|
-
|
|
167
|
-
transformSchema: (
|
|
168
|
-
schema: OpenAPIV3.Document,
|
|
169
|
-
context: GenerationContext
|
|
170
|
-
): OpenAPIV3.Document => {
|
|
171
|
-
// Example: Add custom properties to all schemas
|
|
172
|
-
if (schema.components?.schemas) {
|
|
173
|
-
Object.values(schema.components.schemas).forEach((schemaObj: any) => {
|
|
174
|
-
if (schemaObj.type === "object" && schemaObj.properties) {
|
|
175
|
-
// Add a metadata field to all objects
|
|
176
|
-
schemaObj.properties._metadata = {
|
|
177
|
-
type: "object",
|
|
178
|
-
description: "Metadata added by plugin",
|
|
179
|
-
properties: {
|
|
180
|
-
generatedAt: { type: "string", format: "date-time" },
|
|
181
|
-
version: { type: "string" },
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return schema;
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
export default schemaTransformPlugin;
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### 3. Custom Type Generator Plugin
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
import { SchemanticPlugin, GenerationContext, TypeGenerator } from "schemantic";
|
|
199
|
-
|
|
200
|
-
const customGeneratorPlugin: SchemanticPlugin = {
|
|
201
|
-
name: "validation-generator",
|
|
202
|
-
version: "1.0.0",
|
|
203
|
-
description: "Generates validation schemas alongside types",
|
|
204
|
-
|
|
205
|
-
customTypeGenerators: {
|
|
206
|
-
"validation-schema": (schema: any, context: GenerationContext) => {
|
|
207
|
-
const typeName = schema.title || "UnknownType";
|
|
208
|
-
const validationName = `${typeName}ValidationSchema`;
|
|
209
|
-
|
|
210
|
-
// Generate Zod validation schema
|
|
211
|
-
let validationContent = `export const ${validationName} = z.object({\n`;
|
|
212
|
-
|
|
213
|
-
if (schema.properties) {
|
|
214
|
-
Object.entries(schema.properties).forEach(
|
|
215
|
-
([propName, propSchema]: [string, any]) => {
|
|
216
|
-
const zodType = mapOpenAPIToZod(propSchema);
|
|
217
|
-
const isRequired = schema.required?.includes(propName);
|
|
218
|
-
validationContent += ` ${propName}: ${zodType}${
|
|
219
|
-
isRequired ? "" : ".optional()"
|
|
220
|
-
},\n`;
|
|
221
|
-
}
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
validationContent += "});\n\n";
|
|
226
|
-
validationContent += `export type ${typeName}ValidationInput = z.infer<typeof ${validationName}>;\n`;
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
name: validationName,
|
|
230
|
-
content: validationContent,
|
|
231
|
-
dependencies: ["zod"],
|
|
232
|
-
exports: [validationName, `${typeName}ValidationInput`],
|
|
233
|
-
isInterface: false,
|
|
234
|
-
isEnum: false,
|
|
235
|
-
isUnion: false,
|
|
236
|
-
sourceSchema: schema,
|
|
237
|
-
};
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
function mapOpenAPIToZod(schema: any): string {
|
|
243
|
-
switch (schema.type) {
|
|
244
|
-
case "string":
|
|
245
|
-
if (schema.format === "email") return "z.string().email()";
|
|
246
|
-
if (schema.format === "uuid") return "z.string().uuid()";
|
|
247
|
-
return "z.string()";
|
|
248
|
-
case "number":
|
|
249
|
-
case "integer":
|
|
250
|
-
return "z.number()";
|
|
251
|
-
case "boolean":
|
|
252
|
-
return "z.boolean()";
|
|
253
|
-
case "array":
|
|
254
|
-
return `z.array(${mapOpenAPIToZod(schema.items)})`;
|
|
255
|
-
default:
|
|
256
|
-
return "z.unknown()";
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export default customGeneratorPlugin;
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
## Advanced Plugin Features
|
|
264
|
-
|
|
265
|
-
### 1. CLI Command Extension
|
|
266
|
-
|
|
267
|
-
```typescript
|
|
268
|
-
import { SchemanticPlugin, CliCommand } from "schemantic";
|
|
269
|
-
|
|
270
|
-
const cliExtensionPlugin: SchemanticPlugin = {
|
|
271
|
-
name: "cli-extension",
|
|
272
|
-
version: "1.0.0",
|
|
273
|
-
description: "Adds custom CLI commands",
|
|
274
|
-
|
|
275
|
-
cliCommands: [
|
|
276
|
-
{
|
|
277
|
-
name: "analyze",
|
|
278
|
-
description: "Analyze OpenAPI schema complexity",
|
|
279
|
-
options: [
|
|
280
|
-
{ name: "url", alias: "u", description: "Schema URL", type: "string" },
|
|
281
|
-
{
|
|
282
|
-
name: "output",
|
|
283
|
-
alias: "o",
|
|
284
|
-
description: "Output file",
|
|
285
|
-
type: "string",
|
|
286
|
-
},
|
|
287
|
-
],
|
|
288
|
-
action: async (options: any) => {
|
|
289
|
-
console.log("Analyzing schema complexity...");
|
|
290
|
-
// Implementation here
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
};
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### 2. File Output Generator
|
|
298
|
-
|
|
299
|
-
```typescript
|
|
300
|
-
import {
|
|
301
|
-
SchemanticPlugin,
|
|
302
|
-
OutputGenerator,
|
|
303
|
-
GenerationContext,
|
|
304
|
-
} from "schemantic";
|
|
305
|
-
|
|
306
|
-
const docGeneratorPlugin: SchemanticPlugin = {
|
|
307
|
-
name: "doc-generator",
|
|
308
|
-
version: "1.0.0",
|
|
309
|
-
description: "Generates API documentation",
|
|
310
|
-
|
|
311
|
-
customOutputGenerators: [
|
|
312
|
-
{
|
|
313
|
-
name: "api-docs",
|
|
314
|
-
generate: async (context: GenerationContext) => {
|
|
315
|
-
const { schema, outputDir } = context;
|
|
316
|
-
|
|
317
|
-
let markdownContent = `# ${schema.info.title} API Documentation\n\n`;
|
|
318
|
-
markdownContent += `Version: ${schema.info.version}\n\n`;
|
|
319
|
-
markdownContent += `${schema.info.description || ""}\n\n`;
|
|
320
|
-
|
|
321
|
-
// Generate endpoint documentation
|
|
322
|
-
if (schema.paths) {
|
|
323
|
-
markdownContent += "## Endpoints\n\n";
|
|
324
|
-
Object.entries(schema.paths).forEach(
|
|
325
|
-
([path, pathItem]: [string, any]) => {
|
|
326
|
-
Object.entries(pathItem).forEach(
|
|
327
|
-
([method, operation]: [string, any]) => {
|
|
328
|
-
if (operation && typeof operation === "object") {
|
|
329
|
-
markdownContent += `### ${method.toUpperCase()} ${path}\n\n`;
|
|
330
|
-
markdownContent += `${
|
|
331
|
-
operation.summary || operation.description || ""
|
|
332
|
-
}\n\n`;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return [
|
|
341
|
-
{
|
|
342
|
-
path: `${outputDir}/API_DOCUMENTATION.md`,
|
|
343
|
-
content: markdownContent,
|
|
344
|
-
type: "other" as const,
|
|
345
|
-
},
|
|
346
|
-
];
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
],
|
|
350
|
-
};
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
### 3. Configuration-Based Plugin
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
import { SchemanticPlugin, GenerationContext } from "schemantic";
|
|
357
|
-
|
|
358
|
-
interface PluginConfig {
|
|
359
|
-
addTimestamps: boolean;
|
|
360
|
-
customPrefix: string;
|
|
361
|
-
excludePatterns: string[];
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
class ConfigurablePlugin implements SchemanticPlugin {
|
|
365
|
-
name = "configurable-plugin";
|
|
366
|
-
version = "1.0.0";
|
|
367
|
-
description = "A configurable plugin example";
|
|
368
|
-
|
|
369
|
-
private config: PluginConfig;
|
|
370
|
-
|
|
371
|
-
constructor(config: Partial<PluginConfig> = {}) {
|
|
372
|
-
this.config = {
|
|
373
|
-
addTimestamps: true,
|
|
374
|
-
customPrefix: "",
|
|
375
|
-
excludePatterns: [],
|
|
376
|
-
...config,
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
afterTypeGeneration = async (
|
|
381
|
-
typeName: string,
|
|
382
|
-
generatedType: any,
|
|
383
|
-
context: GenerationContext
|
|
384
|
-
) => {
|
|
385
|
-
if (this.shouldExclude(typeName)) {
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (this.config.addTimestamps) {
|
|
390
|
-
const timestamp = `// Generated on ${new Date().toISOString()}\n`;
|
|
391
|
-
generatedType.content = timestamp + generatedType.content;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (this.config.customPrefix) {
|
|
395
|
-
generatedType.name = `${this.config.customPrefix}${generatedType.name}`;
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
private shouldExclude(typeName: string): boolean {
|
|
400
|
-
return this.config.excludePatterns.some((pattern) =>
|
|
401
|
-
new RegExp(pattern).test(typeName)
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Usage
|
|
407
|
-
const plugin = new ConfigurablePlugin({
|
|
408
|
-
addTimestamps: true,
|
|
409
|
-
customPrefix: "My",
|
|
410
|
-
excludePatterns: ["^Internal.*", ".*Error$"],
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
export default plugin;
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
## Plugin Utilities
|
|
417
|
-
|
|
418
|
-
Schemantic provides utilities to help with plugin development:
|
|
419
|
-
|
|
420
|
-
```typescript
|
|
421
|
-
interface PluginUtilities {
|
|
422
|
-
// String utilities
|
|
423
|
-
toCamelCase(str: string): string;
|
|
424
|
-
toPascalCase(str: string): string;
|
|
425
|
-
toSnakeCase(str: string): string;
|
|
426
|
-
|
|
427
|
-
// Type utilities
|
|
428
|
-
isOptionalProperty(property: any, required: string[]): boolean;
|
|
429
|
-
getTypeFromSchema(schema: any): string;
|
|
430
|
-
resolveReference(ref: string, document: OpenAPIV3.Document): any;
|
|
431
|
-
|
|
432
|
-
// File utilities
|
|
433
|
-
ensureDirectoryExists(path: string): Promise<void>;
|
|
434
|
-
writeFile(path: string, content: string): Promise<void>;
|
|
435
|
-
|
|
436
|
-
// Validation utilities
|
|
437
|
-
validateSchema(schema: any): { isValid: boolean; errors: string[] };
|
|
438
|
-
validateGeneratedType(type: GeneratedType): {
|
|
439
|
-
isValid: boolean;
|
|
440
|
-
errors: string[];
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
## Plugin Loading
|
|
446
|
-
|
|
447
|
-
### 1. File-based Plugin
|
|
448
|
-
|
|
449
|
-
```typescript
|
|
450
|
-
// my-plugin.js
|
|
451
|
-
module.exports = {
|
|
452
|
-
name: "my-plugin",
|
|
453
|
-
version: "1.0.0",
|
|
454
|
-
description: "My custom plugin",
|
|
455
|
-
|
|
456
|
-
afterTypeGeneration: async (typeName, generatedType, context) => {
|
|
457
|
-
// Plugin logic here
|
|
458
|
-
},
|
|
459
|
-
};
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
Load with CLI:
|
|
463
|
-
|
|
464
|
-
```bash
|
|
465
|
-
npx schemantic plugin load ./my-plugin.js
|
|
466
|
-
npx schemantic generate --plugins my-plugin
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
### 2. NPM Package Plugin
|
|
470
|
-
|
|
471
|
-
```typescript
|
|
472
|
-
// @my-org/Schemantic-plugin/index.js
|
|
473
|
-
const plugin = {
|
|
474
|
-
name: "npm-plugin",
|
|
475
|
-
version: "1.0.0",
|
|
476
|
-
description: "NPM distributed plugin",
|
|
477
|
-
|
|
478
|
-
transformSchema: (schema, context) => {
|
|
479
|
-
// Transform logic
|
|
480
|
-
return schema;
|
|
481
|
-
},
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
module.exports = plugin;
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
Load with CLI:
|
|
488
|
-
|
|
489
|
-
```bash
|
|
490
|
-
npm install @my-org/Schemantic-plugin
|
|
491
|
-
npx schemantic plugin load @my-org/Schemantic-plugin
|
|
492
|
-
npx schemantic generate --plugins npm-plugin
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
### 3. Programmatic Plugin
|
|
496
|
-
|
|
497
|
-
```typescript
|
|
498
|
-
import { Schemantic, SchemanticConfig } from "schemantic";
|
|
499
|
-
import myPlugin from "./my-plugin";
|
|
500
|
-
|
|
501
|
-
const config: SchemanticConfig = {
|
|
502
|
-
schemaUrl: "http://localhost:8000/openapi.json",
|
|
503
|
-
outputDir: "./generated",
|
|
504
|
-
plugins: [{ name: "my-plugin", enabled: true }],
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
const Schemantic = new Schemantic(config);
|
|
508
|
-
const pluginManager = Schemantic.getPluginManager();
|
|
509
|
-
|
|
510
|
-
// Register custom plugin
|
|
511
|
-
pluginManager.registerPlugin(myPlugin);
|
|
512
|
-
pluginManager.enablePlugin("my-plugin");
|
|
513
|
-
|
|
514
|
-
// Generate with plugin
|
|
515
|
-
const result = await Schemantic.generate();
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
## Testing Plugins
|
|
519
|
-
|
|
520
|
-
### Unit Testing
|
|
521
|
-
|
|
522
|
-
```typescript
|
|
523
|
-
import { describe, it, expect } from "jest";
|
|
524
|
-
import myPlugin from "./my-plugin";
|
|
525
|
-
import { GenerationContext, GeneratedType } from "schemantic";
|
|
526
|
-
|
|
527
|
-
describe("MyPlugin", () => {
|
|
528
|
-
it("should add comments to generated types", async () => {
|
|
529
|
-
const mockContext: GenerationContext = {
|
|
530
|
-
config: {},
|
|
531
|
-
schema: { info: { title: "Test API" } },
|
|
532
|
-
outputDir: "./test",
|
|
533
|
-
logger: console,
|
|
534
|
-
utilities: {},
|
|
535
|
-
} as any;
|
|
536
|
-
|
|
537
|
-
const generatedType: GeneratedType = {
|
|
538
|
-
name: "TestType",
|
|
539
|
-
content: "export interface TestType { id: string; }",
|
|
540
|
-
dependencies: [],
|
|
541
|
-
exports: ["TestType"],
|
|
542
|
-
isInterface: true,
|
|
543
|
-
isEnum: false,
|
|
544
|
-
isUnion: false,
|
|
545
|
-
sourceSchema: {},
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
await myPlugin.afterTypeGeneration?.(
|
|
549
|
-
"TestType",
|
|
550
|
-
generatedType,
|
|
551
|
-
mockContext
|
|
552
|
-
);
|
|
553
|
-
|
|
554
|
-
expect(generatedType.content).toContain("/**");
|
|
555
|
-
expect(generatedType.content).toContain("Generated type: TestType");
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
### Integration Testing
|
|
561
|
-
|
|
562
|
-
```typescript
|
|
563
|
-
import { Schemantic } from "schemantic";
|
|
564
|
-
import myPlugin from "./my-plugin";
|
|
565
|
-
|
|
566
|
-
describe("Plugin Integration", () => {
|
|
567
|
-
it("should work with full generation process", async () => {
|
|
568
|
-
const Schemantic = new Schemantic({
|
|
569
|
-
schemaFile: "./test-schema.json",
|
|
570
|
-
outputDir: "./test-output",
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
const pluginManager = Schemantic.getPluginManager();
|
|
574
|
-
pluginManager.registerPlugin(myPlugin);
|
|
575
|
-
pluginManager.enablePlugin("my-plugin");
|
|
576
|
-
|
|
577
|
-
const result = await Schemantic.generate();
|
|
578
|
-
expect(result.success).toBe(true);
|
|
579
|
-
|
|
580
|
-
// Verify plugin effects in generated files
|
|
581
|
-
const typesContent = result.generatedFiles.find(
|
|
582
|
-
(f) => f.type === "types"
|
|
583
|
-
)?.content;
|
|
584
|
-
expect(typesContent).toContain("Generated type:");
|
|
585
|
-
});
|
|
586
|
-
});
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
## Best Practices
|
|
590
|
-
|
|
591
|
-
### 1. Error Handling
|
|
592
|
-
|
|
593
|
-
```typescript
|
|
594
|
-
const robustPlugin: SchemanticPlugin = {
|
|
595
|
-
name: "robust-plugin",
|
|
596
|
-
version: "1.0.0",
|
|
597
|
-
description: "Example of robust error handling",
|
|
598
|
-
|
|
599
|
-
afterTypeGeneration: async (typeName, generatedType, context) => {
|
|
600
|
-
try {
|
|
601
|
-
// Plugin logic that might fail
|
|
602
|
-
generatedType.content = processContent(generatedType.content);
|
|
603
|
-
} catch (error) {
|
|
604
|
-
context.logger.warn(
|
|
605
|
-
`Plugin failed for type ${typeName}: ${error.message}`
|
|
606
|
-
);
|
|
607
|
-
// Don't throw - allow generation to continue
|
|
608
|
-
}
|
|
609
|
-
},
|
|
610
|
-
};
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
### 2. Performance Considerations
|
|
614
|
-
|
|
615
|
-
```typescript
|
|
616
|
-
const performantPlugin: SchemanticPlugin = {
|
|
617
|
-
name: 'performant-plugin',
|
|
618
|
-
version: '1.0.0',
|
|
619
|
-
description: 'Example of performance-conscious plugin',
|
|
620
|
-
|
|
621
|
-
// Cache expensive computations
|
|
622
|
-
private cache = new Map<string, any>();
|
|
623
|
-
|
|
624
|
-
transformSchema: (schema, context) => {
|
|
625
|
-
const cacheKey = JSON.stringify(schema);
|
|
626
|
-
|
|
627
|
-
if (this.cache.has(cacheKey)) {
|
|
628
|
-
return this.cache.get(cacheKey);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const transformed = expensiveTransform(schema);
|
|
632
|
-
this.cache.set(cacheKey, transformed);
|
|
633
|
-
|
|
634
|
-
return transformed;
|
|
635
|
-
}
|
|
636
|
-
};
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
### 3. Configuration Validation
|
|
640
|
-
|
|
641
|
-
```typescript
|
|
642
|
-
import * as Joi from "joi";
|
|
643
|
-
|
|
644
|
-
const configSchema = Joi.object({
|
|
645
|
-
enabled: Joi.boolean().default(true),
|
|
646
|
-
options: Joi.object({
|
|
647
|
-
prefix: Joi.string().default(""),
|
|
648
|
-
includeComments: Joi.boolean().default(true),
|
|
649
|
-
}),
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
const validatedPlugin: SchemanticPlugin = {
|
|
653
|
-
name: "validated-plugin",
|
|
654
|
-
version: "1.0.0",
|
|
655
|
-
description: "Plugin with configuration validation",
|
|
656
|
-
|
|
657
|
-
beforeSchemaLoad: async (context) => {
|
|
658
|
-
const pluginConfig = context.config.plugins?.find(
|
|
659
|
-
(p) => p.name === "validated-plugin"
|
|
660
|
-
);
|
|
661
|
-
|
|
662
|
-
if (pluginConfig) {
|
|
663
|
-
const { error } = configSchema.validate(pluginConfig);
|
|
664
|
-
if (error) {
|
|
665
|
-
throw new Error(`Invalid plugin configuration: ${error.message}`);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
},
|
|
669
|
-
};
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
## Publishing Plugins
|
|
673
|
-
|
|
674
|
-
### 1. NPM Package Structure
|
|
675
|
-
|
|
676
|
-
```
|
|
677
|
-
my-Schemantic-plugin/
|
|
678
|
-
├── package.json
|
|
679
|
-
├── index.js
|
|
680
|
-
├── README.md
|
|
681
|
-
├── LICENSE
|
|
682
|
-
└── test/
|
|
683
|
-
└── plugin.test.js
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
### 2. Package.json
|
|
687
|
-
|
|
688
|
-
```json
|
|
689
|
-
{
|
|
690
|
-
"name": "@my-org/Schemantic-plugin-name",
|
|
691
|
-
"version": "1.0.0",
|
|
692
|
-
"description": "Description of your plugin",
|
|
693
|
-
"main": "index.js",
|
|
694
|
-
"keywords": ["Schemantic", "plugin", "openapi", "typescript"],
|
|
695
|
-
"peerDependencies": {
|
|
696
|
-
"schemantic": "^1.0.0"
|
|
697
|
-
},
|
|
698
|
-
"files": ["index.js", "README.md", "LICENSE"]
|
|
699
|
-
}
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
### 3. Plugin Registry
|
|
703
|
-
|
|
704
|
-
Consider adding your plugin to the community registry by submitting a PR to the Schemantic plugins repository.
|
|
705
|
-
|
|
706
|
-
## Community Plugins
|
|
707
|
-
|
|
708
|
-
Popular community plugins include:
|
|
709
|
-
|
|
710
|
-
- `@Schemantic/plugin-zod` - Generates Zod validation schemas
|
|
711
|
-
- `@Schemantic/plugin-react-query` - Generates React Query hooks
|
|
712
|
-
- `@Schemantic/plugin-swagger-ui` - Generates Swagger UI documentation
|
|
713
|
-
- `@Schemantic/plugin-mock-data` - Generates mock data factories
|
|
714
|
-
|
|
715
|
-
## Support
|
|
716
|
-
|
|
717
|
-
- 📖 [Plugin API Reference](./API.md#plugin-api)
|
|
718
|
-
- 💬 [Plugin Development Discussions](https://github.com/Cstannahill/schemantic/discussions)
|
|
719
|
-
- 🐛 [Report Plugin Issues](https://github.com/Cstannahill/schemantic/issues)
|
|
720
|
-
- 📧 [Plugin Development Support](mailto:plugins@Schemantic.dev)
|
|
1
|
+
# Plugin Development Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to create custom plugins for Schemantic to extend its functionality.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Schemantic's plugin system allows you to:
|
|
8
|
+
|
|
9
|
+
- Transform OpenAPI schemas before processing
|
|
10
|
+
- Customize type generation logic
|
|
11
|
+
- Add additional generators (e.g., validation schemas, documentation)
|
|
12
|
+
- Modify generated code after creation
|
|
13
|
+
- Add new CLI commands and options
|
|
14
|
+
|
|
15
|
+
## Plugin Interface
|
|
16
|
+
|
|
17
|
+
Every plugin must implement the `SchemanticPlugin` interface:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
interface SchemanticPlugin {
|
|
21
|
+
name: string;
|
|
22
|
+
version: string;
|
|
23
|
+
description: string;
|
|
24
|
+
|
|
25
|
+
// Optional lifecycle hooks
|
|
26
|
+
beforeSchemaLoad?: (context: GenerationContext) => Promise<void>;
|
|
27
|
+
afterSchemaLoad?: (
|
|
28
|
+
schema: OpenAPIV3.Document,
|
|
29
|
+
context: GenerationContext
|
|
30
|
+
) => Promise<void>;
|
|
31
|
+
beforeTypeGeneration?: (
|
|
32
|
+
typeName: string,
|
|
33
|
+
schema: any,
|
|
34
|
+
context: GenerationContext
|
|
35
|
+
) => Promise<void>;
|
|
36
|
+
afterTypeGeneration?: (
|
|
37
|
+
typeName: string,
|
|
38
|
+
generatedType: GeneratedType,
|
|
39
|
+
context: GenerationContext
|
|
40
|
+
) => Promise<void>;
|
|
41
|
+
beforeClientGeneration?: (context: GenerationContext) => Promise<void>;
|
|
42
|
+
afterClientGeneration?: (
|
|
43
|
+
generatedClient: GeneratedClient,
|
|
44
|
+
context: GenerationContext
|
|
45
|
+
) => Promise<void>;
|
|
46
|
+
beforeOutput?: (
|
|
47
|
+
files: GeneratedFile[],
|
|
48
|
+
context: GenerationContext
|
|
49
|
+
) => Promise<void>;
|
|
50
|
+
afterOutput?: (
|
|
51
|
+
files: GeneratedFile[],
|
|
52
|
+
context: GenerationContext
|
|
53
|
+
) => Promise<void>;
|
|
54
|
+
|
|
55
|
+
// Optional transformers
|
|
56
|
+
transformSchema?: (
|
|
57
|
+
schema: OpenAPIV3.Document,
|
|
58
|
+
context: GenerationContext
|
|
59
|
+
) => OpenAPIV3.Document;
|
|
60
|
+
transformType?: (
|
|
61
|
+
generatedType: GeneratedType,
|
|
62
|
+
context: GenerationContext
|
|
63
|
+
) => GeneratedType;
|
|
64
|
+
transformClient?: (
|
|
65
|
+
generatedClient: GeneratedClient,
|
|
66
|
+
context: GenerationContext
|
|
67
|
+
) => GeneratedClient;
|
|
68
|
+
|
|
69
|
+
// Optional custom generators
|
|
70
|
+
customTypeGenerators?: { [key: string]: TypeGenerator };
|
|
71
|
+
customOutputGenerators?: OutputGenerator[];
|
|
72
|
+
|
|
73
|
+
// Optional CLI extensions
|
|
74
|
+
cliCommands?: CliCommand[];
|
|
75
|
+
cliOptions?: CliOption[];
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Core Types
|
|
80
|
+
|
|
81
|
+
### GenerationContext
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
interface GenerationContext {
|
|
85
|
+
config: SchemanticConfig;
|
|
86
|
+
schema: OpenAPIV3.Document;
|
|
87
|
+
outputDir: string;
|
|
88
|
+
logger: Logger;
|
|
89
|
+
utilities: PluginUtilities;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### GeneratedType
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
interface GeneratedType {
|
|
97
|
+
name: string;
|
|
98
|
+
content: string;
|
|
99
|
+
dependencies: string[];
|
|
100
|
+
exports: string[];
|
|
101
|
+
isInterface: boolean;
|
|
102
|
+
isEnum: boolean;
|
|
103
|
+
isUnion: boolean;
|
|
104
|
+
sourceSchema: any;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### GeneratedClient
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
interface GeneratedClient {
|
|
112
|
+
className: string;
|
|
113
|
+
content: string;
|
|
114
|
+
methods: GeneratedMethod[];
|
|
115
|
+
dependencies: string[];
|
|
116
|
+
exports: string[];
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### GeneratedFile
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
interface GeneratedFile {
|
|
124
|
+
path: string;
|
|
125
|
+
content: string;
|
|
126
|
+
type: "types" | "client" | "hooks" | "other";
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Creating a Basic Plugin
|
|
131
|
+
|
|
132
|
+
### 1. Simple Transform Plugin
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { SchemanticPlugin, GenerationContext, GeneratedType } from "schemantic";
|
|
136
|
+
|
|
137
|
+
const addCommentsPlugin: SchemanticPlugin = {
|
|
138
|
+
name: "add-comments",
|
|
139
|
+
version: "1.0.0",
|
|
140
|
+
description: "Adds helpful comments to generated types",
|
|
141
|
+
|
|
142
|
+
afterTypeGeneration: async (
|
|
143
|
+
typeName: string,
|
|
144
|
+
generatedType: GeneratedType,
|
|
145
|
+
context: GenerationContext
|
|
146
|
+
) => {
|
|
147
|
+
// Add a comment at the top of each generated type
|
|
148
|
+
const comment = `/**\n * Generated type: ${typeName}\n * Source: ${context.schema.info.title}\n */\n`;
|
|
149
|
+
generatedType.content = comment + generatedType.content;
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export default addCommentsPlugin;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 2. Schema Transform Plugin
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { SchemanticPlugin, GenerationContext } from "schemantic";
|
|
160
|
+
import { OpenAPIV3 } from "openapi-types";
|
|
161
|
+
|
|
162
|
+
const schemaTransformPlugin: SchemanticPlugin = {
|
|
163
|
+
name: "schema-transformer",
|
|
164
|
+
version: "1.0.0",
|
|
165
|
+
description: "Transforms schema before processing",
|
|
166
|
+
|
|
167
|
+
transformSchema: (
|
|
168
|
+
schema: OpenAPIV3.Document,
|
|
169
|
+
context: GenerationContext
|
|
170
|
+
): OpenAPIV3.Document => {
|
|
171
|
+
// Example: Add custom properties to all schemas
|
|
172
|
+
if (schema.components?.schemas) {
|
|
173
|
+
Object.values(schema.components.schemas).forEach((schemaObj: any) => {
|
|
174
|
+
if (schemaObj.type === "object" && schemaObj.properties) {
|
|
175
|
+
// Add a metadata field to all objects
|
|
176
|
+
schemaObj.properties._metadata = {
|
|
177
|
+
type: "object",
|
|
178
|
+
description: "Metadata added by plugin",
|
|
179
|
+
properties: {
|
|
180
|
+
generatedAt: { type: "string", format: "date-time" },
|
|
181
|
+
version: { type: "string" },
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return schema;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export default schemaTransformPlugin;
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 3. Custom Type Generator Plugin
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { SchemanticPlugin, GenerationContext, TypeGenerator } from "schemantic";
|
|
199
|
+
|
|
200
|
+
const customGeneratorPlugin: SchemanticPlugin = {
|
|
201
|
+
name: "validation-generator",
|
|
202
|
+
version: "1.0.0",
|
|
203
|
+
description: "Generates validation schemas alongside types",
|
|
204
|
+
|
|
205
|
+
customTypeGenerators: {
|
|
206
|
+
"validation-schema": (schema: any, context: GenerationContext) => {
|
|
207
|
+
const typeName = schema.title || "UnknownType";
|
|
208
|
+
const validationName = `${typeName}ValidationSchema`;
|
|
209
|
+
|
|
210
|
+
// Generate Zod validation schema
|
|
211
|
+
let validationContent = `export const ${validationName} = z.object({\n`;
|
|
212
|
+
|
|
213
|
+
if (schema.properties) {
|
|
214
|
+
Object.entries(schema.properties).forEach(
|
|
215
|
+
([propName, propSchema]: [string, any]) => {
|
|
216
|
+
const zodType = mapOpenAPIToZod(propSchema);
|
|
217
|
+
const isRequired = schema.required?.includes(propName);
|
|
218
|
+
validationContent += ` ${propName}: ${zodType}${
|
|
219
|
+
isRequired ? "" : ".optional()"
|
|
220
|
+
},\n`;
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
validationContent += "});\n\n";
|
|
226
|
+
validationContent += `export type ${typeName}ValidationInput = z.infer<typeof ${validationName}>;\n`;
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
name: validationName,
|
|
230
|
+
content: validationContent,
|
|
231
|
+
dependencies: ["zod"],
|
|
232
|
+
exports: [validationName, `${typeName}ValidationInput`],
|
|
233
|
+
isInterface: false,
|
|
234
|
+
isEnum: false,
|
|
235
|
+
isUnion: false,
|
|
236
|
+
sourceSchema: schema,
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
function mapOpenAPIToZod(schema: any): string {
|
|
243
|
+
switch (schema.type) {
|
|
244
|
+
case "string":
|
|
245
|
+
if (schema.format === "email") return "z.string().email()";
|
|
246
|
+
if (schema.format === "uuid") return "z.string().uuid()";
|
|
247
|
+
return "z.string()";
|
|
248
|
+
case "number":
|
|
249
|
+
case "integer":
|
|
250
|
+
return "z.number()";
|
|
251
|
+
case "boolean":
|
|
252
|
+
return "z.boolean()";
|
|
253
|
+
case "array":
|
|
254
|
+
return `z.array(${mapOpenAPIToZod(schema.items)})`;
|
|
255
|
+
default:
|
|
256
|
+
return "z.unknown()";
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export default customGeneratorPlugin;
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Advanced Plugin Features
|
|
264
|
+
|
|
265
|
+
### 1. CLI Command Extension
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { SchemanticPlugin, CliCommand } from "schemantic";
|
|
269
|
+
|
|
270
|
+
const cliExtensionPlugin: SchemanticPlugin = {
|
|
271
|
+
name: "cli-extension",
|
|
272
|
+
version: "1.0.0",
|
|
273
|
+
description: "Adds custom CLI commands",
|
|
274
|
+
|
|
275
|
+
cliCommands: [
|
|
276
|
+
{
|
|
277
|
+
name: "analyze",
|
|
278
|
+
description: "Analyze OpenAPI schema complexity",
|
|
279
|
+
options: [
|
|
280
|
+
{ name: "url", alias: "u", description: "Schema URL", type: "string" },
|
|
281
|
+
{
|
|
282
|
+
name: "output",
|
|
283
|
+
alias: "o",
|
|
284
|
+
description: "Output file",
|
|
285
|
+
type: "string",
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
action: async (options: any) => {
|
|
289
|
+
console.log("Analyzing schema complexity...");
|
|
290
|
+
// Implementation here
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
};
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 2. File Output Generator
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import {
|
|
301
|
+
SchemanticPlugin,
|
|
302
|
+
OutputGenerator,
|
|
303
|
+
GenerationContext,
|
|
304
|
+
} from "schemantic";
|
|
305
|
+
|
|
306
|
+
const docGeneratorPlugin: SchemanticPlugin = {
|
|
307
|
+
name: "doc-generator",
|
|
308
|
+
version: "1.0.0",
|
|
309
|
+
description: "Generates API documentation",
|
|
310
|
+
|
|
311
|
+
customOutputGenerators: [
|
|
312
|
+
{
|
|
313
|
+
name: "api-docs",
|
|
314
|
+
generate: async (context: GenerationContext) => {
|
|
315
|
+
const { schema, outputDir } = context;
|
|
316
|
+
|
|
317
|
+
let markdownContent = `# ${schema.info.title} API Documentation\n\n`;
|
|
318
|
+
markdownContent += `Version: ${schema.info.version}\n\n`;
|
|
319
|
+
markdownContent += `${schema.info.description || ""}\n\n`;
|
|
320
|
+
|
|
321
|
+
// Generate endpoint documentation
|
|
322
|
+
if (schema.paths) {
|
|
323
|
+
markdownContent += "## Endpoints\n\n";
|
|
324
|
+
Object.entries(schema.paths).forEach(
|
|
325
|
+
([path, pathItem]: [string, any]) => {
|
|
326
|
+
Object.entries(pathItem).forEach(
|
|
327
|
+
([method, operation]: [string, any]) => {
|
|
328
|
+
if (operation && typeof operation === "object") {
|
|
329
|
+
markdownContent += `### ${method.toUpperCase()} ${path}\n\n`;
|
|
330
|
+
markdownContent += `${
|
|
331
|
+
operation.summary || operation.description || ""
|
|
332
|
+
}\n\n`;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return [
|
|
341
|
+
{
|
|
342
|
+
path: `${outputDir}/API_DOCUMENTATION.md`,
|
|
343
|
+
content: markdownContent,
|
|
344
|
+
type: "other" as const,
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
};
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 3. Configuration-Based Plugin
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { SchemanticPlugin, GenerationContext } from "schemantic";
|
|
357
|
+
|
|
358
|
+
interface PluginConfig {
|
|
359
|
+
addTimestamps: boolean;
|
|
360
|
+
customPrefix: string;
|
|
361
|
+
excludePatterns: string[];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
class ConfigurablePlugin implements SchemanticPlugin {
|
|
365
|
+
name = "configurable-plugin";
|
|
366
|
+
version = "1.0.0";
|
|
367
|
+
description = "A configurable plugin example";
|
|
368
|
+
|
|
369
|
+
private config: PluginConfig;
|
|
370
|
+
|
|
371
|
+
constructor(config: Partial<PluginConfig> = {}) {
|
|
372
|
+
this.config = {
|
|
373
|
+
addTimestamps: true,
|
|
374
|
+
customPrefix: "",
|
|
375
|
+
excludePatterns: [],
|
|
376
|
+
...config,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
afterTypeGeneration = async (
|
|
381
|
+
typeName: string,
|
|
382
|
+
generatedType: any,
|
|
383
|
+
context: GenerationContext
|
|
384
|
+
) => {
|
|
385
|
+
if (this.shouldExclude(typeName)) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (this.config.addTimestamps) {
|
|
390
|
+
const timestamp = `// Generated on ${new Date().toISOString()}\n`;
|
|
391
|
+
generatedType.content = timestamp + generatedType.content;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (this.config.customPrefix) {
|
|
395
|
+
generatedType.name = `${this.config.customPrefix}${generatedType.name}`;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
private shouldExclude(typeName: string): boolean {
|
|
400
|
+
return this.config.excludePatterns.some((pattern) =>
|
|
401
|
+
new RegExp(pattern).test(typeName)
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Usage
|
|
407
|
+
const plugin = new ConfigurablePlugin({
|
|
408
|
+
addTimestamps: true,
|
|
409
|
+
customPrefix: "My",
|
|
410
|
+
excludePatterns: ["^Internal.*", ".*Error$"],
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
export default plugin;
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Plugin Utilities
|
|
417
|
+
|
|
418
|
+
Schemantic provides utilities to help with plugin development:
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
interface PluginUtilities {
|
|
422
|
+
// String utilities
|
|
423
|
+
toCamelCase(str: string): string;
|
|
424
|
+
toPascalCase(str: string): string;
|
|
425
|
+
toSnakeCase(str: string): string;
|
|
426
|
+
|
|
427
|
+
// Type utilities
|
|
428
|
+
isOptionalProperty(property: any, required: string[]): boolean;
|
|
429
|
+
getTypeFromSchema(schema: any): string;
|
|
430
|
+
resolveReference(ref: string, document: OpenAPIV3.Document): any;
|
|
431
|
+
|
|
432
|
+
// File utilities
|
|
433
|
+
ensureDirectoryExists(path: string): Promise<void>;
|
|
434
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
435
|
+
|
|
436
|
+
// Validation utilities
|
|
437
|
+
validateSchema(schema: any): { isValid: boolean; errors: string[] };
|
|
438
|
+
validateGeneratedType(type: GeneratedType): {
|
|
439
|
+
isValid: boolean;
|
|
440
|
+
errors: string[];
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Plugin Loading
|
|
446
|
+
|
|
447
|
+
### 1. File-based Plugin
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
// my-plugin.js
|
|
451
|
+
module.exports = {
|
|
452
|
+
name: "my-plugin",
|
|
453
|
+
version: "1.0.0",
|
|
454
|
+
description: "My custom plugin",
|
|
455
|
+
|
|
456
|
+
afterTypeGeneration: async (typeName, generatedType, context) => {
|
|
457
|
+
// Plugin logic here
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Load with CLI:
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
npx schemantic plugin load ./my-plugin.js
|
|
466
|
+
npx schemantic generate --plugins my-plugin
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### 2. NPM Package Plugin
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
// @my-org/Schemantic-plugin/index.js
|
|
473
|
+
const plugin = {
|
|
474
|
+
name: "npm-plugin",
|
|
475
|
+
version: "1.0.0",
|
|
476
|
+
description: "NPM distributed plugin",
|
|
477
|
+
|
|
478
|
+
transformSchema: (schema, context) => {
|
|
479
|
+
// Transform logic
|
|
480
|
+
return schema;
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
module.exports = plugin;
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Load with CLI:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
npm install @my-org/Schemantic-plugin
|
|
491
|
+
npx schemantic plugin load @my-org/Schemantic-plugin
|
|
492
|
+
npx schemantic generate --plugins npm-plugin
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### 3. Programmatic Plugin
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import { Schemantic, SchemanticConfig } from "schemantic";
|
|
499
|
+
import myPlugin from "./my-plugin";
|
|
500
|
+
|
|
501
|
+
const config: SchemanticConfig = {
|
|
502
|
+
schemaUrl: "http://localhost:8000/openapi.json",
|
|
503
|
+
outputDir: "./generated",
|
|
504
|
+
plugins: [{ name: "my-plugin", enabled: true }],
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const Schemantic = new Schemantic(config);
|
|
508
|
+
const pluginManager = Schemantic.getPluginManager();
|
|
509
|
+
|
|
510
|
+
// Register custom plugin
|
|
511
|
+
pluginManager.registerPlugin(myPlugin);
|
|
512
|
+
pluginManager.enablePlugin("my-plugin");
|
|
513
|
+
|
|
514
|
+
// Generate with plugin
|
|
515
|
+
const result = await Schemantic.generate();
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## Testing Plugins
|
|
519
|
+
|
|
520
|
+
### Unit Testing
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
import { describe, it, expect } from "jest";
|
|
524
|
+
import myPlugin from "./my-plugin";
|
|
525
|
+
import { GenerationContext, GeneratedType } from "schemantic";
|
|
526
|
+
|
|
527
|
+
describe("MyPlugin", () => {
|
|
528
|
+
it("should add comments to generated types", async () => {
|
|
529
|
+
const mockContext: GenerationContext = {
|
|
530
|
+
config: {},
|
|
531
|
+
schema: { info: { title: "Test API" } },
|
|
532
|
+
outputDir: "./test",
|
|
533
|
+
logger: console,
|
|
534
|
+
utilities: {},
|
|
535
|
+
} as any;
|
|
536
|
+
|
|
537
|
+
const generatedType: GeneratedType = {
|
|
538
|
+
name: "TestType",
|
|
539
|
+
content: "export interface TestType { id: string; }",
|
|
540
|
+
dependencies: [],
|
|
541
|
+
exports: ["TestType"],
|
|
542
|
+
isInterface: true,
|
|
543
|
+
isEnum: false,
|
|
544
|
+
isUnion: false,
|
|
545
|
+
sourceSchema: {},
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
await myPlugin.afterTypeGeneration?.(
|
|
549
|
+
"TestType",
|
|
550
|
+
generatedType,
|
|
551
|
+
mockContext
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
expect(generatedType.content).toContain("/**");
|
|
555
|
+
expect(generatedType.content).toContain("Generated type: TestType");
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Integration Testing
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
import { Schemantic } from "schemantic";
|
|
564
|
+
import myPlugin from "./my-plugin";
|
|
565
|
+
|
|
566
|
+
describe("Plugin Integration", () => {
|
|
567
|
+
it("should work with full generation process", async () => {
|
|
568
|
+
const Schemantic = new Schemantic({
|
|
569
|
+
schemaFile: "./test-schema.json",
|
|
570
|
+
outputDir: "./test-output",
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
const pluginManager = Schemantic.getPluginManager();
|
|
574
|
+
pluginManager.registerPlugin(myPlugin);
|
|
575
|
+
pluginManager.enablePlugin("my-plugin");
|
|
576
|
+
|
|
577
|
+
const result = await Schemantic.generate();
|
|
578
|
+
expect(result.success).toBe(true);
|
|
579
|
+
|
|
580
|
+
// Verify plugin effects in generated files
|
|
581
|
+
const typesContent = result.generatedFiles.find(
|
|
582
|
+
(f) => f.type === "types"
|
|
583
|
+
)?.content;
|
|
584
|
+
expect(typesContent).toContain("Generated type:");
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
## Best Practices
|
|
590
|
+
|
|
591
|
+
### 1. Error Handling
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
const robustPlugin: SchemanticPlugin = {
|
|
595
|
+
name: "robust-plugin",
|
|
596
|
+
version: "1.0.0",
|
|
597
|
+
description: "Example of robust error handling",
|
|
598
|
+
|
|
599
|
+
afterTypeGeneration: async (typeName, generatedType, context) => {
|
|
600
|
+
try {
|
|
601
|
+
// Plugin logic that might fail
|
|
602
|
+
generatedType.content = processContent(generatedType.content);
|
|
603
|
+
} catch (error) {
|
|
604
|
+
context.logger.warn(
|
|
605
|
+
`Plugin failed for type ${typeName}: ${error.message}`
|
|
606
|
+
);
|
|
607
|
+
// Don't throw - allow generation to continue
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
};
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### 2. Performance Considerations
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
const performantPlugin: SchemanticPlugin = {
|
|
617
|
+
name: 'performant-plugin',
|
|
618
|
+
version: '1.0.0',
|
|
619
|
+
description: 'Example of performance-conscious plugin',
|
|
620
|
+
|
|
621
|
+
// Cache expensive computations
|
|
622
|
+
private cache = new Map<string, any>();
|
|
623
|
+
|
|
624
|
+
transformSchema: (schema, context) => {
|
|
625
|
+
const cacheKey = JSON.stringify(schema);
|
|
626
|
+
|
|
627
|
+
if (this.cache.has(cacheKey)) {
|
|
628
|
+
return this.cache.get(cacheKey);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const transformed = expensiveTransform(schema);
|
|
632
|
+
this.cache.set(cacheKey, transformed);
|
|
633
|
+
|
|
634
|
+
return transformed;
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### 3. Configuration Validation
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
import * as Joi from "joi";
|
|
643
|
+
|
|
644
|
+
const configSchema = Joi.object({
|
|
645
|
+
enabled: Joi.boolean().default(true),
|
|
646
|
+
options: Joi.object({
|
|
647
|
+
prefix: Joi.string().default(""),
|
|
648
|
+
includeComments: Joi.boolean().default(true),
|
|
649
|
+
}),
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
const validatedPlugin: SchemanticPlugin = {
|
|
653
|
+
name: "validated-plugin",
|
|
654
|
+
version: "1.0.0",
|
|
655
|
+
description: "Plugin with configuration validation",
|
|
656
|
+
|
|
657
|
+
beforeSchemaLoad: async (context) => {
|
|
658
|
+
const pluginConfig = context.config.plugins?.find(
|
|
659
|
+
(p) => p.name === "validated-plugin"
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
if (pluginConfig) {
|
|
663
|
+
const { error } = configSchema.validate(pluginConfig);
|
|
664
|
+
if (error) {
|
|
665
|
+
throw new Error(`Invalid plugin configuration: ${error.message}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
## Publishing Plugins
|
|
673
|
+
|
|
674
|
+
### 1. NPM Package Structure
|
|
675
|
+
|
|
676
|
+
```
|
|
677
|
+
my-Schemantic-plugin/
|
|
678
|
+
├── package.json
|
|
679
|
+
├── index.js
|
|
680
|
+
├── README.md
|
|
681
|
+
├── LICENSE
|
|
682
|
+
└── test/
|
|
683
|
+
└── plugin.test.js
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### 2. Package.json
|
|
687
|
+
|
|
688
|
+
```json
|
|
689
|
+
{
|
|
690
|
+
"name": "@my-org/Schemantic-plugin-name",
|
|
691
|
+
"version": "1.0.0",
|
|
692
|
+
"description": "Description of your plugin",
|
|
693
|
+
"main": "index.js",
|
|
694
|
+
"keywords": ["Schemantic", "plugin", "openapi", "typescript"],
|
|
695
|
+
"peerDependencies": {
|
|
696
|
+
"schemantic": "^1.0.0"
|
|
697
|
+
},
|
|
698
|
+
"files": ["index.js", "README.md", "LICENSE"]
|
|
699
|
+
}
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### 3. Plugin Registry
|
|
703
|
+
|
|
704
|
+
Consider adding your plugin to the community registry by submitting a PR to the Schemantic plugins repository.
|
|
705
|
+
|
|
706
|
+
## Community Plugins
|
|
707
|
+
|
|
708
|
+
Popular community plugins include:
|
|
709
|
+
|
|
710
|
+
- `@Schemantic/plugin-zod` - Generates Zod validation schemas
|
|
711
|
+
- `@Schemantic/plugin-react-query` - Generates React Query hooks
|
|
712
|
+
- `@Schemantic/plugin-swagger-ui` - Generates Swagger UI documentation
|
|
713
|
+
- `@Schemantic/plugin-mock-data` - Generates mock data factories
|
|
714
|
+
|
|
715
|
+
## Support
|
|
716
|
+
|
|
717
|
+
- 📖 [Plugin API Reference](./API.md#plugin-api)
|
|
718
|
+
- 💬 [Plugin Development Discussions](https://github.com/Cstannahill/schemantic/discussions)
|
|
719
|
+
- 🐛 [Report Plugin Issues](https://github.com/Cstannahill/schemantic/issues)
|
|
720
|
+
- 📧 [Plugin Development Support](mailto:plugins@Schemantic.dev)
|