schemantic 0.1.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 +92 -0
- package/LICENSE +21 -0
- package/README.md +827 -0
- package/dist/__tests__/setup.d.ts +6 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +30 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/test-config.d.ts +36 -0
- package/dist/__tests__/test-config.d.ts.map +1 -0
- package/dist/__tests__/test-config.js +563 -0
- package/dist/__tests__/test-config.js.map +1 -0
- package/dist/cli/index.d.ts +118 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +657 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +21 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/schemantic.d.ts +95 -0
- package/dist/core/schemantic.d.ts.map +1 -0
- package/dist/core/schemantic.js +543 -0
- package/dist/core/schemantic.js.map +1 -0
- package/dist/generators/api-client-generator.d.ts +145 -0
- package/dist/generators/api-client-generator.d.ts.map +1 -0
- package/dist/generators/api-client-generator.js +856 -0
- package/dist/generators/api-client-generator.js.map +1 -0
- package/dist/generators/base.d.ts +117 -0
- package/dist/generators/base.d.ts.map +1 -0
- package/dist/generators/base.js +232 -0
- package/dist/generators/base.js.map +1 -0
- package/dist/generators/enum-generator.d.ts +52 -0
- package/dist/generators/enum-generator.d.ts.map +1 -0
- package/dist/generators/enum-generator.js +178 -0
- package/dist/generators/enum-generator.js.map +1 -0
- package/dist/generators/factory.d.ts +59 -0
- package/dist/generators/factory.d.ts.map +1 -0
- package/dist/generators/factory.js +127 -0
- package/dist/generators/factory.js.map +1 -0
- package/dist/generators/hook-generator.d.ts +36 -0
- package/dist/generators/hook-generator.d.ts.map +1 -0
- package/dist/generators/hook-generator.js +495 -0
- package/dist/generators/hook-generator.js.map +1 -0
- package/dist/generators/index.d.ts +10 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +26 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/object-generator.d.ts +48 -0
- package/dist/generators/object-generator.d.ts.map +1 -0
- package/dist/generators/object-generator.js +280 -0
- package/dist/generators/object-generator.js.map +1 -0
- package/dist/generators/primitive-generator.d.ts +40 -0
- package/dist/generators/primitive-generator.d.ts.map +1 -0
- package/dist/generators/primitive-generator.js +150 -0
- package/dist/generators/primitive-generator.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/base.d.ts +101 -0
- package/dist/parsers/base.d.ts.map +1 -0
- package/dist/parsers/base.js +77 -0
- package/dist/parsers/base.js.map +1 -0
- package/dist/parsers/factory.d.ts +64 -0
- package/dist/parsers/factory.d.ts.map +1 -0
- package/dist/parsers/factory.js +116 -0
- package/dist/parsers/factory.js.map +1 -0
- package/dist/parsers/index.d.ts +7 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +23 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/openapi.d.ts +52 -0
- package/dist/parsers/openapi.d.ts.map +1 -0
- package/dist/parsers/openapi.js +396 -0
- package/dist/parsers/openapi.js.map +1 -0
- package/dist/plugins/branded-types.d.ts +28 -0
- package/dist/plugins/branded-types.d.ts.map +1 -0
- package/dist/plugins/branded-types.js +568 -0
- package/dist/plugins/branded-types.js.map +1 -0
- package/dist/plugins/builtin.d.ts +38 -0
- package/dist/plugins/builtin.d.ts.map +1 -0
- package/dist/plugins/builtin.js +283 -0
- package/dist/plugins/builtin.js.map +1 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +23 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/loader.d.ts +92 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +245 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/manager.d.ts +99 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +233 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/plugins/performance-monitoring.d.ts +98 -0
- package/dist/plugins/performance-monitoring.d.ts.map +1 -0
- package/dist/plugins/performance-monitoring.js +596 -0
- package/dist/plugins/performance-monitoring.js.map +1 -0
- package/dist/plugins/request-deduplication.d.ts +27 -0
- package/dist/plugins/request-deduplication.d.ts.map +1 -0
- package/dist/plugins/request-deduplication.js +650 -0
- package/dist/plugins/request-deduplication.js.map +1 -0
- package/dist/plugins/zod-validation.d.ts +63 -0
- package/dist/plugins/zod-validation.d.ts.map +1 -0
- package/dist/plugins/zod-validation.js +743 -0
- package/dist/plugins/zod-validation.js.map +1 -0
- package/dist/types/core.d.ts +203 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/core.js +24 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/openapi.d.ts +250 -0
- package/dist/types/openapi.d.ts.map +1 -0
- package/dist/types/openapi.js +8 -0
- package/dist/types/openapi.js.map +1 -0
- package/dist/types/schema.d.ts +68 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +22 -0
- package/dist/types/schema.js.map +1 -0
- package/docs/ADVANCED_PLUGINS_IMPLEMENTATION.md +355 -0
- package/docs/ADVANCED_PLUGIN_DEVELOPMENT.md +309 -0
- package/docs/API.md +600 -0
- package/docs/CONFIGURATION.md +722 -0
- package/docs/FASTAPI_TUTORIAL.md +1497 -0
- package/docs/PLUGINS.md +683 -0
- package/docs/PLUGIN_DEVELOPMENT.md +720 -0
- package/package.json +90 -0
package/docs/PLUGINS.md
ADDED
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
# Plugin Development Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to create custom plugins for Schemantic to extend its functionality.
|
|
4
|
+
|
|
5
|
+
## Plugin Overview
|
|
6
|
+
|
|
7
|
+
Plugins in Schemantic allow you to:
|
|
8
|
+
|
|
9
|
+
- Transform schemas before type generation
|
|
10
|
+
- Modify generated types and API clients
|
|
11
|
+
- Add custom type generators
|
|
12
|
+
- Add custom client generators
|
|
13
|
+
- Execute code at various stages of the generation process
|
|
14
|
+
|
|
15
|
+
## Plugin Interface
|
|
16
|
+
|
|
17
|
+
All plugins must implement the `SchemanticPlugin` interface:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
interface SchemanticPlugin {
|
|
21
|
+
name: string;
|
|
22
|
+
version: string;
|
|
23
|
+
description: string;
|
|
24
|
+
|
|
25
|
+
// Lifecycle hooks
|
|
26
|
+
beforeGeneration?(context: GenerationContext): Promise<void> | void;
|
|
27
|
+
afterGeneration?(
|
|
28
|
+
context: GenerationContext,
|
|
29
|
+
result: GenerationResult
|
|
30
|
+
): Promise<void> | void;
|
|
31
|
+
|
|
32
|
+
// Type generation hooks
|
|
33
|
+
beforeTypeGeneration?(
|
|
34
|
+
typeName: string,
|
|
35
|
+
schema: ResolvedSchema,
|
|
36
|
+
context: GenerationContext
|
|
37
|
+
): Promise<void> | void;
|
|
38
|
+
afterTypeGeneration?(
|
|
39
|
+
typeName: string,
|
|
40
|
+
generatedType: GeneratedType,
|
|
41
|
+
context: GenerationContext
|
|
42
|
+
): Promise<void> | void;
|
|
43
|
+
|
|
44
|
+
// Client generation hooks
|
|
45
|
+
beforeClientGeneration?(context: GenerationContext): Promise<void> | void;
|
|
46
|
+
afterClientGeneration?(
|
|
47
|
+
generatedClient: GeneratedApiClient,
|
|
48
|
+
context: GenerationContext
|
|
49
|
+
): Promise<void> | void;
|
|
50
|
+
|
|
51
|
+
// Schema transformation hooks
|
|
52
|
+
transformSchema?(
|
|
53
|
+
schema: ResolvedSchema,
|
|
54
|
+
context: GenerationContext
|
|
55
|
+
): ResolvedSchema;
|
|
56
|
+
|
|
57
|
+
// Custom generators
|
|
58
|
+
customTypeGenerators?: Record<
|
|
59
|
+
string,
|
|
60
|
+
(schema: ResolvedSchema, context: GenerationContext) => GeneratedType
|
|
61
|
+
>;
|
|
62
|
+
customClientGenerators?: Record<
|
|
63
|
+
string,
|
|
64
|
+
(context: GenerationContext) => GeneratedApiClient
|
|
65
|
+
>;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Basic Plugin Structure
|
|
70
|
+
|
|
71
|
+
Here's a minimal plugin:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { SchemanticPlugin, GenerationContext, GeneratedType } from "schemantic";
|
|
75
|
+
|
|
76
|
+
const myPlugin: SchemanticPlugin = {
|
|
77
|
+
name: "my-plugin",
|
|
78
|
+
version: "1.0.0",
|
|
79
|
+
description: "My custom plugin",
|
|
80
|
+
|
|
81
|
+
beforeGeneration: async (context: GenerationContext) => {
|
|
82
|
+
console.log("Starting generation...");
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
afterGeneration: async (context: GenerationContext, result: any) => {
|
|
86
|
+
console.log("Generation completed!");
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Plugin Hooks
|
|
92
|
+
|
|
93
|
+
### Lifecycle Hooks
|
|
94
|
+
|
|
95
|
+
#### `beforeGeneration`
|
|
96
|
+
|
|
97
|
+
Called before the generation process starts. Use this to:
|
|
98
|
+
|
|
99
|
+
- Validate configuration
|
|
100
|
+
- Set up plugin-specific state
|
|
101
|
+
- Modify the generation context
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
beforeGeneration: async (context: GenerationContext) => {
|
|
105
|
+
// Validate configuration
|
|
106
|
+
if (!context.config.customOption) {
|
|
107
|
+
throw new Error('Custom option is required');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Set up plugin state
|
|
111
|
+
context.pluginState = { myData: 'value' };
|
|
112
|
+
},
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### `afterGeneration`
|
|
116
|
+
|
|
117
|
+
Called after the generation process completes. Use this to:
|
|
118
|
+
|
|
119
|
+
- Clean up resources
|
|
120
|
+
- Generate additional files
|
|
121
|
+
- Post-process results
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
afterGeneration: async (context: GenerationContext, result: any) => {
|
|
125
|
+
// Generate additional files
|
|
126
|
+
if (result.success) {
|
|
127
|
+
await generateAdditionalFiles(context);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Clean up
|
|
131
|
+
delete context.pluginState;
|
|
132
|
+
},
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Type Generation Hooks
|
|
136
|
+
|
|
137
|
+
#### `beforeTypeGeneration`
|
|
138
|
+
|
|
139
|
+
Called before generating a specific type. Use this to:
|
|
140
|
+
|
|
141
|
+
- Transform the schema
|
|
142
|
+
- Add metadata
|
|
143
|
+
- Skip generation for certain types
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
beforeTypeGeneration: async (typeName: string, schema: ResolvedSchema, context: GenerationContext) => {
|
|
147
|
+
// Add custom metadata
|
|
148
|
+
schema._customMetadata = { processed: true };
|
|
149
|
+
|
|
150
|
+
// Skip certain types
|
|
151
|
+
if (typeName.includes('Internal')) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### `afterTypeGeneration`
|
|
158
|
+
|
|
159
|
+
Called after generating a specific type. Use this to:
|
|
160
|
+
|
|
161
|
+
- Modify the generated type content
|
|
162
|
+
- Add imports
|
|
163
|
+
- Post-process the type
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
afterTypeGeneration: async (typeName: string, generatedType: GeneratedType, context: GenerationContext) => {
|
|
167
|
+
// Add custom imports
|
|
168
|
+
generatedType.content = `import { CustomType } from './custom';\n\n${generatedType.content}`;
|
|
169
|
+
|
|
170
|
+
// Add custom decorators
|
|
171
|
+
if (generatedType.isInterface) {
|
|
172
|
+
generatedType.content = generatedType.content.replace(
|
|
173
|
+
/export interface (\w+)/g,
|
|
174
|
+
'@CustomDecorator\nexport interface $1'
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Client Generation Hooks
|
|
181
|
+
|
|
182
|
+
#### `beforeClientGeneration`
|
|
183
|
+
|
|
184
|
+
Called before generating API clients. Use this to:
|
|
185
|
+
|
|
186
|
+
- Set up client-specific configuration
|
|
187
|
+
- Modify the generation context
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
beforeClientGeneration: async (context: GenerationContext) => {
|
|
191
|
+
// Add custom configuration
|
|
192
|
+
context.clientConfig = {
|
|
193
|
+
baseUrl: 'https://api.example.com',
|
|
194
|
+
timeout: 30000,
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### `afterClientGeneration`
|
|
200
|
+
|
|
201
|
+
Called after generating API clients. Use this to:
|
|
202
|
+
|
|
203
|
+
- Modify the generated client
|
|
204
|
+
- Add custom methods
|
|
205
|
+
- Post-process the client
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
afterClientGeneration: async (generatedClient: GeneratedApiClient, context: GenerationContext) => {
|
|
209
|
+
// Add custom methods
|
|
210
|
+
const customMethods = `
|
|
211
|
+
async customMethod(): Promise<string> {
|
|
212
|
+
return 'custom result';
|
|
213
|
+
}
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
generatedClient.content = generatedClient.content.replace(
|
|
217
|
+
/}\s*$/,
|
|
218
|
+
`${customMethods}\n}`
|
|
219
|
+
);
|
|
220
|
+
},
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Schema Transformation
|
|
224
|
+
|
|
225
|
+
#### `transformSchema`
|
|
226
|
+
|
|
227
|
+
Transform schemas before type generation. Use this to:
|
|
228
|
+
|
|
229
|
+
- Add custom properties
|
|
230
|
+
- Modify type information
|
|
231
|
+
- Standardize schema format
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
transformSchema: (schema: ResolvedSchema, context: GenerationContext): ResolvedSchema => {
|
|
235
|
+
// Add custom properties
|
|
236
|
+
if (schema.properties) {
|
|
237
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
238
|
+
if (typeof propSchema === 'object' && propSchema !== null) {
|
|
239
|
+
// Add custom metadata
|
|
240
|
+
propSchema._customProperty = true;
|
|
241
|
+
|
|
242
|
+
// Transform specific types
|
|
243
|
+
if (propSchema.format === 'uuid') {
|
|
244
|
+
propSchema._customType = 'CustomUuid';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return schema;
|
|
251
|
+
},
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Custom Generators
|
|
255
|
+
|
|
256
|
+
### Custom Type Generators
|
|
257
|
+
|
|
258
|
+
Add custom type generators for specific schema types:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
customTypeGenerators: {
|
|
262
|
+
'custom-type': (schema: ResolvedSchema, context: GenerationContext): GeneratedType => {
|
|
263
|
+
return {
|
|
264
|
+
name: 'CustomType',
|
|
265
|
+
content: `export type CustomType = string;`,
|
|
266
|
+
dependencies: [],
|
|
267
|
+
exports: ['CustomType'],
|
|
268
|
+
isInterface: false,
|
|
269
|
+
isEnum: false,
|
|
270
|
+
isUnion: false,
|
|
271
|
+
sourceSchema: schema,
|
|
272
|
+
};
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
'date-time': (schema: ResolvedSchema, context: GenerationContext): GeneratedType => {
|
|
276
|
+
return {
|
|
277
|
+
name: 'DateTime',
|
|
278
|
+
content: `export type DateTime = string; // ISO 8601 format`,
|
|
279
|
+
dependencies: [],
|
|
280
|
+
exports: ['DateTime'],
|
|
281
|
+
isInterface: false,
|
|
282
|
+
isEnum: false,
|
|
283
|
+
isUnion: false,
|
|
284
|
+
sourceSchema: schema,
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Custom Client Generators
|
|
291
|
+
|
|
292
|
+
Add custom client generators:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
customClientGenerators: {
|
|
296
|
+
'react-query': (context: GenerationContext): GeneratedApiClient => {
|
|
297
|
+
const hooks = generateReactQueryHooks(context);
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
name: 'ReactQueryHooks',
|
|
301
|
+
content: hooks,
|
|
302
|
+
dependencies: ['react-query'],
|
|
303
|
+
exports: ['useApiHooks'],
|
|
304
|
+
endpoints: [],
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
'axios': (context: GenerationContext): GeneratedApiClient => {
|
|
309
|
+
const client = generateAxiosClient(context);
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
name: 'AxiosClient',
|
|
313
|
+
content: client,
|
|
314
|
+
dependencies: ['axios'],
|
|
315
|
+
exports: ['AxiosApiClient'],
|
|
316
|
+
endpoints: [],
|
|
317
|
+
};
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Plugin Examples
|
|
323
|
+
|
|
324
|
+
### JSDoc Plugin
|
|
325
|
+
|
|
326
|
+
Adds JSDoc comments to generated types:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
const jsdocPlugin: SchemanticPlugin = {
|
|
330
|
+
name: "jsdoc",
|
|
331
|
+
version: "1.0.0",
|
|
332
|
+
description: "Adds JSDoc comments to generated types",
|
|
333
|
+
|
|
334
|
+
afterTypeGeneration: async (
|
|
335
|
+
typeName: string,
|
|
336
|
+
generatedType: GeneratedType,
|
|
337
|
+
context: GenerationContext
|
|
338
|
+
) => {
|
|
339
|
+
if (!generatedType.content.includes("/**")) {
|
|
340
|
+
const comment = `/**\n * ${
|
|
341
|
+
generatedType.sourceSchema.description || `Generated type: ${typeName}`
|
|
342
|
+
}\n */\n`;
|
|
343
|
+
generatedType.content = comment + generatedType.content;
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Validation Plugin
|
|
350
|
+
|
|
351
|
+
Adds validation decorators for class-validator:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
const validationPlugin: SchemanticPlugin = {
|
|
355
|
+
name: "validation",
|
|
356
|
+
version: "1.0.0",
|
|
357
|
+
description: "Adds validation decorators to generated types",
|
|
358
|
+
|
|
359
|
+
afterTypeGeneration: async (
|
|
360
|
+
typeName: string,
|
|
361
|
+
generatedType: GeneratedType,
|
|
362
|
+
context: GenerationContext
|
|
363
|
+
) => {
|
|
364
|
+
if (generatedType.isInterface) {
|
|
365
|
+
// Add validation imports
|
|
366
|
+
const imports = `import { IsString, IsNumber, IsOptional, IsNotEmpty } from 'class-validator';\n\n`;
|
|
367
|
+
|
|
368
|
+
// Add validation decorators to properties
|
|
369
|
+
let content = generatedType.content;
|
|
370
|
+
content = content.replace(
|
|
371
|
+
/(\w+)(\?)?: (\w+);/g,
|
|
372
|
+
(match, propName, optional, type) => {
|
|
373
|
+
const decorators = [];
|
|
374
|
+
|
|
375
|
+
if (type === "string") decorators.push("@IsString()");
|
|
376
|
+
if (type === "number") decorators.push("@IsNumber()");
|
|
377
|
+
if (optional) decorators.push("@IsOptional()");
|
|
378
|
+
else decorators.push("@IsNotEmpty()");
|
|
379
|
+
|
|
380
|
+
return `${decorators.join("\n ")}\n ${match}`;
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
generatedType.content = imports + content;
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### React Hooks Plugin
|
|
391
|
+
|
|
392
|
+
Generates React hooks for API endpoints:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
const reactHooksPlugin: SchemanticPlugin = {
|
|
396
|
+
name: "react-hooks",
|
|
397
|
+
version: "1.0.0",
|
|
398
|
+
description: "Generates React hooks for API endpoints",
|
|
399
|
+
|
|
400
|
+
customClientGenerators: {
|
|
401
|
+
"react-hooks": (context: GenerationContext): GeneratedApiClient => {
|
|
402
|
+
const hooks = generateReactHooks(context);
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
name: "ApiHooks",
|
|
406
|
+
content: hooks,
|
|
407
|
+
dependencies: ["react-query"],
|
|
408
|
+
exports: ["useApiHooks"],
|
|
409
|
+
endpoints: [],
|
|
410
|
+
};
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
function generateReactHooks(context: GenerationContext): string {
|
|
416
|
+
let hooks = `import { useQuery, useMutation, useQueryClient } from 'react-query';\n`;
|
|
417
|
+
hooks += `import { ApiClient } from './api-client';\n\n`;
|
|
418
|
+
|
|
419
|
+
hooks += `export const useApiHooks = (client: ApiClient) => {\n`;
|
|
420
|
+
hooks += ` const queryClient = useQueryClient();\n\n`;
|
|
421
|
+
|
|
422
|
+
// Generate hooks for each endpoint
|
|
423
|
+
for (const [path, pathItem] of Object.entries(context.schema.paths)) {
|
|
424
|
+
if (typeof pathItem === "object" && pathItem !== null) {
|
|
425
|
+
const operations = ["get", "post", "put", "delete", "patch"] as const;
|
|
426
|
+
|
|
427
|
+
for (const method of operations) {
|
|
428
|
+
const operation = pathItem[method];
|
|
429
|
+
if (operation) {
|
|
430
|
+
const hookName = generateHookName(
|
|
431
|
+
operation.operationId || `${method}${path}`
|
|
432
|
+
);
|
|
433
|
+
const hook = generateHook(operation, method, path);
|
|
434
|
+
hooks += ` ${hookName} = ${hook};\n`;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
hooks += `};\n`;
|
|
441
|
+
return hooks;
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Plugin Loading
|
|
446
|
+
|
|
447
|
+
### From File
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
import { PluginLoader } from "schemantic";
|
|
451
|
+
|
|
452
|
+
const loader = new PluginLoader();
|
|
453
|
+
const plugin = await loader.loadPluginFromFile("./my-plugin.js");
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### From Package
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
const plugin = await loader.loadPluginFromPackage("my-Schemantic-plugin");
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### From Directory
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
const plugins = await loader.loadPluginsFromDirectory("./plugins");
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Plugin Registration
|
|
469
|
+
|
|
470
|
+
### In Code
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
import { Schemantic } from "schemantic";
|
|
474
|
+
|
|
475
|
+
const Schemantic = new Schemantic(config);
|
|
476
|
+
const pluginManager = Schemantic.getPluginManager();
|
|
477
|
+
|
|
478
|
+
pluginManager.registerPlugin(myPlugin);
|
|
479
|
+
pluginManager.enablePlugin("my-plugin");
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Via Configuration
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
const config: SchemanticConfig = {
|
|
486
|
+
// ... other config
|
|
487
|
+
plugins: [
|
|
488
|
+
{ name: "my-plugin", enabled: true },
|
|
489
|
+
{ name: "jsdoc", enabled: true },
|
|
490
|
+
],
|
|
491
|
+
};
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Via CLI
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
npx schemantic generate --plugins my-plugin,jsdoc
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## Best Practices
|
|
501
|
+
|
|
502
|
+
### 1. Error Handling
|
|
503
|
+
|
|
504
|
+
Always handle errors gracefully:
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
beforeTypeGeneration: async (typeName: string, schema: ResolvedSchema, context: GenerationContext) => {
|
|
508
|
+
try {
|
|
509
|
+
// Plugin logic
|
|
510
|
+
} catch (error) {
|
|
511
|
+
console.warn(`Plugin ${this.name} failed for type ${typeName}:`, error);
|
|
512
|
+
// Don't throw - let generation continue
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### 2. Performance
|
|
518
|
+
|
|
519
|
+
Consider performance implications:
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
// Cache expensive operations
|
|
523
|
+
const cache = new Map();
|
|
524
|
+
|
|
525
|
+
transformSchema: (schema: ResolvedSchema, context: GenerationContext): ResolvedSchema => {
|
|
526
|
+
const cacheKey = JSON.stringify(schema);
|
|
527
|
+
|
|
528
|
+
if (cache.has(cacheKey)) {
|
|
529
|
+
return cache.get(cacheKey);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const result = expensiveTransformation(schema);
|
|
533
|
+
cache.set(cacheKey, result);
|
|
534
|
+
return result;
|
|
535
|
+
},
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### 3. Documentation
|
|
539
|
+
|
|
540
|
+
Document your plugin thoroughly:
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
const myPlugin: SchemanticPlugin = {
|
|
544
|
+
name: "my-plugin",
|
|
545
|
+
version: "1.0.0",
|
|
546
|
+
description: `
|
|
547
|
+
My custom plugin that adds special functionality.
|
|
548
|
+
|
|
549
|
+
Features:
|
|
550
|
+
- Adds custom decorators
|
|
551
|
+
- Transforms specific types
|
|
552
|
+
- Generates additional files
|
|
553
|
+
|
|
554
|
+
Usage:
|
|
555
|
+
Enable via configuration or CLI: --plugins my-plugin
|
|
556
|
+
`,
|
|
557
|
+
|
|
558
|
+
// ... implementation
|
|
559
|
+
};
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### 4. Testing
|
|
563
|
+
|
|
564
|
+
Test your plugins:
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { Schemantic, SchemanticConfig } from "schemantic";
|
|
568
|
+
|
|
569
|
+
describe("My Plugin", () => {
|
|
570
|
+
it("should transform schemas correctly", async () => {
|
|
571
|
+
const config: SchemanticConfig = {
|
|
572
|
+
schemaData: testSchema,
|
|
573
|
+
outputDir: "./test-output",
|
|
574
|
+
plugins: [{ name: "my-plugin", enabled: true }],
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const Schemantic = new Schemantic(config);
|
|
578
|
+
const result = await Schemantic.generate();
|
|
579
|
+
|
|
580
|
+
expect(result.success).toBe(true);
|
|
581
|
+
expect(result.generatedFiles).toHaveLength(1);
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### 5. Versioning
|
|
587
|
+
|
|
588
|
+
Use semantic versioning for your plugins:
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
const myPlugin: SchemanticPlugin = {
|
|
592
|
+
name: "my-plugin",
|
|
593
|
+
version: "1.2.3", // Major.Minor.Patch
|
|
594
|
+
description: "My plugin description",
|
|
595
|
+
|
|
596
|
+
// ... implementation
|
|
597
|
+
};
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## Publishing Plugins
|
|
601
|
+
|
|
602
|
+
### As NPM Package
|
|
603
|
+
|
|
604
|
+
1. Create a package.json:
|
|
605
|
+
|
|
606
|
+
```json
|
|
607
|
+
{
|
|
608
|
+
"name": "Schemantic-my-plugin",
|
|
609
|
+
"version": "1.0.0",
|
|
610
|
+
"description": "My custom Schemantic plugin",
|
|
611
|
+
"main": "dist/index.js",
|
|
612
|
+
"types": "dist/index.d.ts",
|
|
613
|
+
"keywords": ["Schemantic", "plugin", "typescript", "openapi"],
|
|
614
|
+
"peerDependencies": {
|
|
615
|
+
"schemantic": "^1.0.0"
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
2. Export your plugin:
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
// src/index.ts
|
|
624
|
+
export { myPlugin } from "./plugin";
|
|
625
|
+
export * from "./types";
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
3. Build and publish:
|
|
629
|
+
|
|
630
|
+
```bash
|
|
631
|
+
npm run build
|
|
632
|
+
npm publish
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Usage
|
|
636
|
+
|
|
637
|
+
Users can install and use your plugin:
|
|
638
|
+
|
|
639
|
+
```bash
|
|
640
|
+
npm install Schemantic-my-plugin
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
import { myPlugin } from "Schemantic-my-plugin";
|
|
645
|
+
import { Schemantic } from "schemantic";
|
|
646
|
+
|
|
647
|
+
const Schemantic = new Schemantic(config);
|
|
648
|
+
Schemantic.getPluginManager().registerPlugin(myPlugin);
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
## Troubleshooting
|
|
652
|
+
|
|
653
|
+
### Common Issues
|
|
654
|
+
|
|
655
|
+
1. **Plugin not loading**: Check that the plugin exports the correct interface
|
|
656
|
+
2. **Type errors**: Ensure your plugin implements all required interface properties
|
|
657
|
+
3. **Performance issues**: Use caching and avoid expensive operations in hooks
|
|
658
|
+
4. **Conflicts**: Check for naming conflicts with other plugins
|
|
659
|
+
|
|
660
|
+
### Debug Mode
|
|
661
|
+
|
|
662
|
+
Enable debug mode to see plugin execution:
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
const config: SchemanticConfig = {
|
|
666
|
+
// ... other config
|
|
667
|
+
plugins: [{ name: "my-plugin", enabled: true, options: { debug: true } }],
|
|
668
|
+
};
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### Logging
|
|
672
|
+
|
|
673
|
+
Add logging to your plugin:
|
|
674
|
+
|
|
675
|
+
```typescript
|
|
676
|
+
beforeTypeGeneration: async (typeName: string, schema: ResolvedSchema, context: GenerationContext) => {
|
|
677
|
+
console.log(`[My Plugin] Processing type: ${typeName}`);
|
|
678
|
+
|
|
679
|
+
// Plugin logic
|
|
680
|
+
|
|
681
|
+
console.log(`[My Plugin] Completed processing: ${typeName}`);
|
|
682
|
+
},
|
|
683
|
+
```
|