schemantic 0.2.1 → 0.4.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 +196 -159
- package/LICENSE +21 -21
- package/README.md +88 -830
- package/dist/cli/index.d.ts +4 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +186 -62
- package/dist/cli/index.js.map +1 -1
- package/dist/core/configuration.d.ts +6 -0
- package/dist/core/configuration.d.ts.map +1 -0
- package/dist/core/configuration.js +64 -0
- package/dist/core/configuration.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/output-writer.d.ts +7 -0
- package/dist/core/output-writer.d.ts.map +1 -0
- package/dist/core/output-writer.js +255 -0
- package/dist/core/output-writer.js.map +1 -0
- package/dist/core/schemantic.d.ts +8 -6
- package/dist/core/schemantic.d.ts.map +1 -1
- package/dist/core/schemantic.js +194 -127
- package/dist/core/schemantic.js.map +1 -1
- package/dist/generators/api-client-generator.d.ts +9 -20
- package/dist/generators/api-client-generator.d.ts.map +1 -1
- package/dist/generators/api-client-generator.js +364 -424
- package/dist/generators/api-client-generator.js.map +1 -1
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +7 -25
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/enum-generator.d.ts.map +1 -1
- package/dist/generators/enum-generator.js +30 -3
- package/dist/generators/enum-generator.js.map +1 -1
- package/dist/generators/hook-generator.d.ts +3 -17
- package/dist/generators/hook-generator.d.ts.map +1 -1
- package/dist/generators/hook-generator.js +51 -445
- package/dist/generators/hook-generator.js.map +1 -1
- package/dist/generators/naming.d.ts +10 -0
- package/dist/generators/naming.d.ts.map +1 -0
- package/dist/generators/naming.js +331 -0
- package/dist/generators/naming.js.map +1 -0
- package/dist/generators/object-generator.d.ts +0 -4
- package/dist/generators/object-generator.d.ts.map +1 -1
- package/dist/generators/object-generator.js +13 -101
- package/dist/generators/object-generator.js.map +1 -1
- package/dist/generators/primitive-generator.d.ts +1 -13
- package/dist/generators/primitive-generator.d.ts.map +1 -1
- package/dist/generators/primitive-generator.js +11 -76
- package/dist/generators/primitive-generator.js.map +1 -1
- package/dist/generators/projections.d.ts +10 -0
- package/dist/generators/projections.d.ts.map +1 -0
- package/dist/generators/projections.js +65 -0
- package/dist/generators/projections.js.map +1 -0
- package/dist/generators/schema-type.d.ts +23 -0
- package/dist/generators/schema-type.d.ts.map +1 -0
- package/dist/generators/schema-type.js +233 -0
- package/dist/generators/schema-type.js.map +1 -0
- package/dist/parsers/base.d.ts +2 -0
- package/dist/parsers/base.d.ts.map +1 -1
- package/dist/parsers/base.js +12 -2
- package/dist/parsers/base.js.map +1 -1
- package/dist/parsers/factory.d.ts.map +1 -1
- package/dist/parsers/factory.js +2 -1
- package/dist/parsers/factory.js.map +1 -1
- package/dist/parsers/openapi.d.ts +4 -0
- package/dist/parsers/openapi.d.ts.map +1 -1
- package/dist/parsers/openapi.js +107 -20
- package/dist/parsers/openapi.js.map +1 -1
- package/dist/plugins/branded-types.d.ts.map +1 -1
- package/dist/plugins/branded-types.js +231 -365
- package/dist/plugins/branded-types.js.map +1 -1
- package/dist/plugins/builtin.d.ts.map +1 -1
- package/dist/plugins/builtin.js +6 -141
- package/dist/plugins/builtin.js.map +1 -1
- package/dist/plugins/index.d.ts +4 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +17 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/loader.d.ts +5 -1
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +42 -5
- package/dist/plugins/loader.js.map +1 -1
- package/dist/plugins/manager.d.ts.map +1 -1
- package/dist/plugins/manager.js +7 -7
- package/dist/plugins/manager.js.map +1 -1
- package/dist/plugins/performance-monitoring.d.ts +31 -1
- package/dist/plugins/performance-monitoring.d.ts.map +1 -1
- package/dist/plugins/performance-monitoring.js +146 -207
- package/dist/plugins/performance-monitoring.js.map +1 -1
- package/dist/plugins/request-deduplication.d.ts.map +1 -1
- package/dist/plugins/request-deduplication.js +151 -586
- package/dist/plugins/request-deduplication.js.map +1 -1
- package/dist/plugins/zod-validation.d.ts +23 -15
- package/dist/plugins/zod-validation.d.ts.map +1 -1
- package/dist/plugins/zod-validation.js +390 -268
- package/dist/plugins/zod-validation.js.map +1 -1
- package/dist/types/core.d.ts +20 -4
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js +2 -1
- package/dist/types/core.js.map +1 -1
- package/dist/types/openapi.d.ts +14 -1
- package/dist/types/openapi.d.ts.map +1 -1
- package/docs/ADVANCED_PLUGINS_IMPLEMENTATION.md +24 -355
- package/docs/ADVANCED_PLUGIN_DEVELOPMENT.md +33 -309
- package/docs/API.md +80 -600
- package/docs/AUDIT.md +49 -0
- package/docs/CONFIGURATION.md +81 -722
- package/docs/FASTAPI_TUTORIAL.md +109 -1497
- package/docs/NAMING_MIGRATION.md +59 -0
- package/docs/PLUGINS.md +49 -687
- package/docs/PLUGIN_DEVELOPMENT.md +53 -720
- package/docs/REAUDIT.md +399 -0
- package/docs/RELEASING.md +88 -0
- package/docs/REMEDIATION.md +58 -0
- package/docs/superpowers/plans/2026-10-10-audit-remediation.md +113 -0
- package/package.json +114 -95
- package/dist/__tests__/setup.d.ts +0 -6
- package/dist/__tests__/setup.d.ts.map +0 -1
- package/dist/__tests__/setup.js +0 -30
- package/dist/__tests__/setup.js.map +0 -1
- package/dist/__tests__/test-config.d.ts +0 -36
- package/dist/__tests__/test-config.d.ts.map +0 -1
- package/dist/__tests__/test-config.js +0 -563
- package/dist/__tests__/test-config.js.map +0 -1
- package/dist/lib/api/api-client.d.ts +0 -71
- package/dist/lib/api/api-client.d.ts.map +0 -1
- package/dist/lib/api/api-client.js +0 -238
- package/dist/lib/api/api-client.js.map +0 -1
- package/dist/lib/api/barrel.d.ts +0 -4
- package/dist/lib/api/barrel.d.ts.map +0 -1
- package/dist/lib/api/barrel.js +0 -26
- package/dist/lib/api/barrel.js.map +0 -1
- package/dist/lib/api/hooks.d.ts +0 -36
- package/dist/lib/api/hooks.d.ts.map +0 -1
- package/dist/lib/api/hooks.js +0 -120
- package/dist/lib/api/hooks.js.map +0 -1
- package/dist/lib/api/index.d.ts +0 -4
- package/dist/lib/api/index.d.ts.map +0 -1
- package/dist/lib/api/index.js +0 -8
- package/dist/lib/api/index.js.map +0 -1
- package/dist/lib/api/types.d.ts +0 -366
- package/dist/lib/api/types.d.ts.map +0 -1
- package/dist/lib/api/types.js +0 -315
- package/dist/lib/api/types.js.map +0 -1
|
@@ -1,720 +1,53 @@
|
|
|
1
|
-
# Plugin
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
2
|
+
|
|
3
|
+
A plugin implements `SchemanticPlugin`. Import public types from the package root. Hooks can return promises except the synchronous schema transform and custom generator functions.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import type { SchemanticPlugin } from 'schemantic';
|
|
7
|
+
|
|
8
|
+
export const plugin: SchemanticPlugin = {
|
|
9
|
+
name: 'example',
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
description: 'Adds generation diagnostics',
|
|
12
|
+
beforeGeneration(context) {
|
|
13
|
+
console.log(`Generating ${context.schema.info.title}`);
|
|
14
|
+
},
|
|
15
|
+
afterTypeGeneration(_name, generatedType) {
|
|
16
|
+
generatedType.content = '/** Generated by example */\n' + generatedType.content;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Supported hooks and signatures:
|
|
22
|
+
|
|
23
|
+
| Hook | Arguments |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `beforeGeneration` | `context` |
|
|
26
|
+
| `afterGeneration` | `context, result` |
|
|
27
|
+
| `beforeTypeGeneration` | `typeName, resolvedSchema, context` |
|
|
28
|
+
| `afterTypeGeneration` | `typeName, generatedType, context` |
|
|
29
|
+
| `beforeClientGeneration` | `context` |
|
|
30
|
+
| `afterClientGeneration` | `generatedClient, context` |
|
|
31
|
+
| `transformSchema` | `resolvedSchema, context`; returns a resolved schema synchronously |
|
|
32
|
+
| `customTypeGenerators` | Record of functions `(resolvedSchema, context) => GeneratedType` |
|
|
33
|
+
| `customClientGenerators` | Record of functions `(context) => GeneratedApiClient` |
|
|
34
|
+
|
|
35
|
+
There are no `beforeSchemaLoad`, `afterSchemaLoad`, `beforeFileWrite`, `afterFileWrite`, CLI command extension, or automatic runtime middleware hooks in this interface. Register only implemented hooks. Custom outputs must accurately list `exports` and `dependencies` and preserve required imports. Avoid modifying source data retained by your caller.
|
|
36
|
+
|
|
37
|
+
Programmatic registration:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { Schemantic } from 'schemantic';
|
|
41
|
+
import { plugin } from './plugin.js';
|
|
42
|
+
|
|
43
|
+
const generator = new Schemantic({ schemaFile: './openapi.json', outputDir: './generated' });
|
|
44
|
+
const manager = generator.getPluginManager();
|
|
45
|
+
manager.registerPlugin(plugin);
|
|
46
|
+
manager.enablePlugin(plugin.name);
|
|
47
|
+
const result = await generator.generate();
|
|
48
|
+
if (!result.success) throw new Error(result.errors.map(error => error.message).join('\n'));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
CLI loading requires a compiled JavaScript module (`.cjs`, `.mjs`, or `.js`). Export the plugin directly, as default, or as `plugin`; use the filename/package specifier in config or `generate --plugins`. The loader resolves packages from the consumer, not the generator's internal dependencies. Duplicate exported names fail. `plugin load` is inspection only.
|
|
52
|
+
|
|
53
|
+
Test plugins using actual consuming code and runtime assertions. String output alone does not establish that a module compiles, a validator rejects invalid data, or a helper preserves request identity. Cover disabled options and combinations with other plugins. Hook failures must appear as generation failures; do not treat a logged warning as successful required plugin execution.
|