schemantic 0.3.0 → 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 -174
- 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 +185 -61
- 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 -2
- package/dist/core/schemantic.d.ts.map +1 -1
- package/dist/core/schemantic.js +196 -71
- 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 -3
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js +2 -0
- 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 -601
- package/docs/AUDIT.md +49 -0
- package/docs/CONFIGURATION.md +81 -715
- 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 -562
- package/dist/__tests__/test-config.js.map +0 -1
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
exports.brandedTypesPlugin = void 0;
|
|
25
25
|
const schema_1 = require("../types/schema");
|
|
26
|
+
const zod_validation_1 = require("./zod-validation");
|
|
26
27
|
/**
|
|
27
28
|
* Advanced Branded Types Plugin Implementation
|
|
28
29
|
*/
|
|
@@ -62,25 +63,29 @@ exports.brandedTypesPlugin = {
|
|
|
62
63
|
*/
|
|
63
64
|
afterTypeGeneration: async (typeName, generatedType, context) => {
|
|
64
65
|
const options = getPluginOptions(context);
|
|
65
|
-
if (!options.enabled
|
|
66
|
+
if (!options.enabled) {
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
const brandedCode = generateBrandedTypeEnhancements(typeName, generatedType, options, context);
|
|
70
|
+
if (brandedCode) {
|
|
71
|
+
generatedType.content += brandedCode;
|
|
72
|
+
generatedType.dependencies.push("zod");
|
|
73
|
+
generatedType.exports.push(`Branded${typeName}`, `create${typeName}`);
|
|
74
|
+
if (options.enableRuntimeGuards)
|
|
75
|
+
generatedType.exports.push(`is${typeName}`);
|
|
76
|
+
if (options.enableAssertionFunctions)
|
|
77
|
+
generatedType.exports.push(`assert${typeName}`);
|
|
78
|
+
if (options.enableOpaqueTypes)
|
|
79
|
+
generatedType.exports.push(`Opaque${typeName}`, `createOpaque${typeName}`, `unwrapOpaque${typeName}`);
|
|
80
|
+
if (options.enablePhantomTypes && generatedType.isInterface) {
|
|
81
|
+
generatedType.exports.push(`${typeName}WithAuth`, `authorize${typeName}`, `unauthorize${typeName}`, `isAuthenticated${typeName}`);
|
|
82
|
+
if (!Array.from(context.generatedTypes.values()).some(type => type !== generatedType && type.exports.includes("AuthState"))) {
|
|
83
|
+
generatedType.content += "\nexport type AuthState = 'authenticated' | 'unauthenticated';\n";
|
|
84
|
+
generatedType.exports.push("AuthState");
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
console.warn(`Branded types plugin afterTypeGeneration failed for ${typeName}:`, error);
|
|
87
|
+
if (options.enableDiscriminatedUnions)
|
|
88
|
+
generatedType.exports.push(`${typeName}Union`, `match${typeName}`, `is${typeName}Variant`);
|
|
84
89
|
}
|
|
85
90
|
},
|
|
86
91
|
/**
|
|
@@ -91,20 +96,15 @@ exports.brandedTypesPlugin = {
|
|
|
91
96
|
if (!options.enabled) {
|
|
92
97
|
return;
|
|
93
98
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
generatedClient.exports.push("AuthenticatedApiClient", "UnauthenticatedApiClient");
|
|
102
|
-
}
|
|
99
|
+
const clientEnhancements = generateClientBrandedTypeIntegration(generatedClient, options);
|
|
100
|
+
if (clientEnhancements) {
|
|
101
|
+
generatedClient.content += clientEnhancements;
|
|
102
|
+
// Update exports
|
|
103
|
+
generatedClient.exports.push("BrandedApiClient", "withBrandValidation");
|
|
104
|
+
if (options.enablePhantomTypes) {
|
|
105
|
+
generatedClient.exports.push("TypeSafeApiClient", "AuthenticatedApiClient", "UnauthenticatedApiClient");
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
console.warn("Branded types plugin afterClientGeneration failed:", error);
|
|
107
|
-
}
|
|
108
108
|
},
|
|
109
109
|
};
|
|
110
110
|
/**
|
|
@@ -188,379 +188,245 @@ function extractValidationConstraints(schema) {
|
|
|
188
188
|
/**
|
|
189
189
|
* Generate branded type enhancements for a given type
|
|
190
190
|
*/
|
|
191
|
-
function generateBrandedTypeEnhancements(typeName, generatedType, options) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
191
|
+
function generateBrandedTypeEnhancements(typeName, generatedType, options, context) {
|
|
192
|
+
const marker = options.brandIdentifier ?? "Brand";
|
|
193
|
+
const brandName = options.brandNamingConvention === "suffix" ? `${typeName}${marker}`
|
|
194
|
+
: options.brandNamingConvention === "namespace" ? `${marker}.${typeName}` : `${marker}${typeName}`;
|
|
195
|
+
const validator = (0, zod_validation_1.buildZodSchema)(generatedType.sourceSchema, { strictMode: false }, 0, context).replace(/z\.lazy\(\(\) => (\w+)Schema\)/g, "z.lazy(() => __$1BrandSchema)");
|
|
196
|
+
const constraints = options.enableTypeLevelValidations && (0, schema_1.isOpenAPISchemaObject)(generatedType.sourceSchema)
|
|
197
|
+
? `; readonly __constraints: ${JSON.stringify(extractValidationConstraints(generatedType.sourceSchema))}` : "";
|
|
198
|
+
return `
|
|
199
|
+
import { z } from 'zod';
|
|
200
|
+
const __${typeName}BrandSchema: z.ZodType = ${validator};
|
|
201
|
+
export type Branded${typeName} = ${typeName} & { readonly __brand: ${JSON.stringify(brandName)}${constraints} };
|
|
202
|
+
/** Validates the source schema before applying a compile-time brand. */
|
|
203
|
+
export function create${typeName}(data: ${typeName}): Branded${typeName} {
|
|
204
|
+
__${typeName}BrandSchema.parse(data); return data as Branded${typeName};
|
|
201
205
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Branded type for ${typeName} with compile-time safety guarantees
|
|
211
|
-
*/
|
|
212
|
-
export type Branded${typeName} = ${typeName} & { readonly __brand: '${brandName}' };
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Create a branded ${typeName} instance
|
|
216
|
-
*/
|
|
217
|
-
export function create${typeName}(data: ${typeName}): Branded${typeName} {
|
|
218
|
-
// Runtime validation would go here
|
|
219
|
-
return data as Branded${typeName};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Type guard for Branded${typeName}
|
|
224
|
-
*/
|
|
225
|
-
export function is${typeName}(value: unknown): value is Branded${typeName} {
|
|
226
|
-
// Basic type checking
|
|
227
|
-
return typeof value === 'object' && value !== null;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Assertion function for ${typeName}
|
|
232
|
-
*/
|
|
233
|
-
export function assert${typeName}(value: unknown): asserts value is Branded${typeName} {
|
|
234
|
-
if (!is${typeName}(value)) {
|
|
235
|
-
throw new Error(\`Expected ${typeName}, got \${typeof value}\`);
|
|
236
|
-
}
|
|
237
|
-
}${options.enablePhantomTypes ? generatePhantomTypes(typeName) : ""}${options.enableDiscriminatedUnions
|
|
238
|
-
? generateDiscriminatedUnions(typeName)
|
|
239
|
-
: ""}`;
|
|
206
|
+
${options.enableRuntimeGuards ? `export function is${typeName}(value: unknown): ${options.enableTypePredicates ? `value is Branded${typeName}` : 'boolean'} { return __${typeName}BrandSchema.safeParse(value).success; }` : ''}
|
|
207
|
+
${options.enableAssertionFunctions ? `export function assert${typeName}(value: unknown): asserts value is Branded${typeName} { __${typeName}BrandSchema.parse(value); }` : ''}
|
|
208
|
+
${options.enableOpaqueTypes ? generateOpaqueTypeHelpers(typeName) : ''}
|
|
209
|
+
${options.enablePhantomTypes && generatedType.isInterface ? generatePhantomTypes(typeName) : ''}
|
|
210
|
+
${options.enableDiscriminatedUnions ? generateDiscriminatedUnions(typeName) : ''}
|
|
211
|
+
`;
|
|
240
212
|
}
|
|
241
213
|
/**
|
|
242
|
-
* Generate
|
|
214
|
+
* Generate opaque type helpers
|
|
243
215
|
*/
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return `
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Advanced branded type for ${typeName} with validation constraints
|
|
251
|
-
* Constraints: ${Array.isArray(brandMetadata.validationConstraints)
|
|
252
|
-
? brandMetadata.validationConstraints.join(", ")
|
|
253
|
-
: "none"}
|
|
254
|
-
*/
|
|
255
|
-
export type Branded${typeName} = ${typeName} & {
|
|
256
|
-
readonly __brand: '${brandName}';
|
|
257
|
-
readonly __constraints: {${generateConstraintTypes(schema)}};
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Create a validated branded ${typeName} instance
|
|
262
|
-
*/
|
|
263
|
-
export function create${typeName}(data: ${typeName}): Branded${typeName} {
|
|
264
|
-
${validationCode}
|
|
265
|
-
return data as Branded${typeName};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Advanced type guard with constraint validation
|
|
270
|
-
*/
|
|
271
|
-
export function is${typeName}(value: unknown): value is Branded${typeName} {
|
|
272
|
-
if (typeof value !== 'object' || value === null) {
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
create${typeName}(value as ${typeName});
|
|
278
|
-
return true;
|
|
279
|
-
} catch {
|
|
280
|
-
return false;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Assertion function with detailed error messages
|
|
286
|
-
*/
|
|
287
|
-
export function assert${typeName}(value: unknown): asserts value is Branded${typeName} {
|
|
288
|
-
if (!is${typeName}(value)) {
|
|
289
|
-
throw new TypeError(\`Value does not satisfy ${typeName} constraints\`);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
${options.enableOpaqueTypes ? generateOpaqueTypeHelpers(typeName) : ""}${options.enablePhantomTypes ? generatePhantomTypes(typeName) : ""}${options.enableDiscriminatedUnions
|
|
294
|
-
? generateDiscriminatedUnions(typeName)
|
|
295
|
-
: ""}`;
|
|
296
|
-
}
|
|
216
|
+
function generateOpaqueTypeHelpers(typeName) {
|
|
217
|
+
return `
|
|
218
|
+
|
|
297
219
|
/**
|
|
298
|
-
*
|
|
220
|
+
* Opaque type for ${typeName} - completely hides the underlying type
|
|
299
221
|
*/
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (schema.minLength !== undefined) {
|
|
304
|
-
validations.push(`if (typeof data !== 'string' || data.length < ${schema.minLength}) throw new Error('String too short');`);
|
|
305
|
-
}
|
|
306
|
-
if (schema.maxLength !== undefined) {
|
|
307
|
-
validations.push(`if (typeof data !== 'string' || data.length > ${schema.maxLength}) throw new Error('String too long');`);
|
|
308
|
-
}
|
|
309
|
-
if (schema.pattern) {
|
|
310
|
-
validations.push(`if (typeof data !== 'string' || !/${schema.pattern}/.test(data)) throw new Error('Pattern mismatch');`);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
if (schema.type === "number" || schema.type === "integer") {
|
|
314
|
-
if (schema.minimum !== undefined) {
|
|
315
|
-
validations.push(`if (typeof data !== 'number' || data < ${schema.minimum}) throw new Error('Number too small');`);
|
|
316
|
-
}
|
|
317
|
-
if (schema.maximum !== undefined) {
|
|
318
|
-
validations.push(`if (typeof data !== 'number' || data > ${schema.maximum}) throw new Error('Number too large');`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return validations.join("\n ");
|
|
322
|
-
}
|
|
222
|
+
declare const __${typeName}Opaque: unique symbol;
|
|
223
|
+
export type Opaque${typeName} = ${typeName} & { readonly [__${typeName}Opaque]: true };
|
|
224
|
+
|
|
323
225
|
/**
|
|
324
|
-
*
|
|
226
|
+
* Create opaque ${typeName}
|
|
325
227
|
*/
|
|
326
|
-
function
|
|
327
|
-
|
|
328
|
-
if (schema.minLength !== undefined) {
|
|
329
|
-
constraints.push(`minLength: ${schema.minLength}`);
|
|
330
|
-
}
|
|
331
|
-
if (schema.maxLength !== undefined) {
|
|
332
|
-
constraints.push(`maxLength: ${schema.maxLength}`);
|
|
333
|
-
}
|
|
334
|
-
if (schema.minimum !== undefined) {
|
|
335
|
-
constraints.push(`minimum: ${schema.minimum}`);
|
|
336
|
-
}
|
|
337
|
-
if (schema.maximum !== undefined) {
|
|
338
|
-
constraints.push(`maximum: ${schema.maximum}`);
|
|
339
|
-
}
|
|
340
|
-
if (schema.pattern) {
|
|
341
|
-
constraints.push(`pattern: '${schema.pattern}'`);
|
|
342
|
-
}
|
|
343
|
-
return constraints.join("; ");
|
|
228
|
+
export function createOpaque${typeName}(data: ${typeName}): Opaque${typeName} {
|
|
229
|
+
__${typeName}BrandSchema.parse(data); return data as Opaque${typeName};
|
|
344
230
|
}
|
|
231
|
+
|
|
345
232
|
/**
|
|
346
|
-
*
|
|
233
|
+
* Unwrap opaque ${typeName}
|
|
347
234
|
*/
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Opaque type for ${typeName} - completely hides the underlying type
|
|
353
|
-
*/
|
|
354
|
-
declare const __${typeName}Opaque: unique symbol;
|
|
355
|
-
export type Opaque${typeName} = ${typeName} & { readonly [__${typeName}Opaque]: true };
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Create opaque ${typeName}
|
|
359
|
-
*/
|
|
360
|
-
export function createOpaque${typeName}(data: ${typeName}): Opaque${typeName} {
|
|
361
|
-
return data as Opaque${typeName};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Unwrap opaque ${typeName}
|
|
366
|
-
*/
|
|
367
|
-
export function unwrapOpaque${typeName}(data: Opaque${typeName}): ${typeName} {
|
|
368
|
-
return data as ${typeName};
|
|
235
|
+
export function unwrapOpaque${typeName}(data: Opaque${typeName}): ${typeName} {
|
|
236
|
+
return data as ${typeName};
|
|
369
237
|
}`;
|
|
370
238
|
}
|
|
371
239
|
/**
|
|
372
240
|
* Generate phantom types for authentication state management
|
|
373
241
|
*/
|
|
374
242
|
function generatePhantomTypes(typeName) {
|
|
375
|
-
return `
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Phantom types for authentication state management
|
|
379
|
-
*/
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// In practice, this would check actual auth state
|
|
410
|
-
return true; // Placeholder implementation
|
|
243
|
+
return `
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Phantom types for authentication state management
|
|
247
|
+
*/
|
|
248
|
+
|
|
249
|
+
export type ${typeName}WithAuth<TAuth extends AuthState = 'unauthenticated'> =
|
|
250
|
+
Branded${typeName} & { readonly __auth: TAuth };
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Authorize a ${typeName} instance
|
|
254
|
+
*/
|
|
255
|
+
export function authorize${typeName}(
|
|
256
|
+
data: ${typeName}WithAuth<'unauthenticated'>
|
|
257
|
+
): ${typeName}WithAuth<'authenticated'> {
|
|
258
|
+
return { ...data, __auth: 'authenticated' };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Remove authorization from ${typeName} instance
|
|
263
|
+
*/
|
|
264
|
+
export function unauthorize${typeName}(
|
|
265
|
+
data: ${typeName}WithAuth<'authenticated'>
|
|
266
|
+
): ${typeName}WithAuth<'unauthenticated'> {
|
|
267
|
+
return { ...data, __auth: 'unauthenticated' };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Type guard for authenticated ${typeName}
|
|
272
|
+
*/
|
|
273
|
+
export function isAuthenticated${typeName}(
|
|
274
|
+
value: ${typeName}WithAuth<AuthState>
|
|
275
|
+
): value is ${typeName}WithAuth<'authenticated'> {
|
|
276
|
+
return value.__auth === 'authenticated';
|
|
411
277
|
}`;
|
|
412
278
|
}
|
|
413
279
|
/**
|
|
414
280
|
* Generate discriminated unions
|
|
415
281
|
*/
|
|
416
282
|
function generateDiscriminatedUnions(typeName) {
|
|
417
|
-
return `
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Discriminated union variants for ${typeName}
|
|
421
|
-
*/
|
|
422
|
-
export type ${typeName}Union =
|
|
423
|
-
| { type: 'valid'; data: Branded${typeName} }
|
|
424
|
-
| { type: 'invalid'; error: string }
|
|
425
|
-
| { type: 'pending'; progress?: number };
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Pattern matching for ${typeName}Union
|
|
429
|
-
*/
|
|
430
|
-
export function match${typeName}<T>(
|
|
431
|
-
union: ${typeName}Union,
|
|
432
|
-
handlers: {
|
|
433
|
-
valid: (data: Branded${typeName}) => T;
|
|
434
|
-
invalid: (error: string) => T;
|
|
435
|
-
pending: (progress?: number) => T;
|
|
436
|
-
}
|
|
437
|
-
): T {
|
|
438
|
-
switch (union.type) {
|
|
439
|
-
case 'valid':
|
|
440
|
-
return handlers.valid(union.data);
|
|
441
|
-
case 'invalid':
|
|
442
|
-
return handlers.invalid(union.error);
|
|
443
|
-
case 'pending':
|
|
444
|
-
return handlers.pending(union.progress);
|
|
445
|
-
default:
|
|
446
|
-
// Exhaustiveness check
|
|
447
|
-
const _exhaustive: never = union;
|
|
448
|
-
throw new Error(\`Unhandled union type: \${JSON.stringify(_exhaustive)}\`);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Type guard for ${typeName}Union variants
|
|
454
|
-
*/
|
|
455
|
-
export function is${typeName}Variant<T extends ${typeName}Union['type']>(
|
|
456
|
-
union: ${typeName}Union,
|
|
457
|
-
type: T
|
|
458
|
-
): union is Extract<${typeName}Union, { type: T }> {
|
|
459
|
-
return union.type === type;
|
|
283
|
+
return `
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Discriminated union variants for ${typeName}
|
|
287
|
+
*/
|
|
288
|
+
export type ${typeName}Union =
|
|
289
|
+
| { type: 'valid'; data: Branded${typeName} }
|
|
290
|
+
| { type: 'invalid'; error: string }
|
|
291
|
+
| { type: 'pending'; progress?: number };
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Pattern matching for ${typeName}Union
|
|
295
|
+
*/
|
|
296
|
+
export function match${typeName}<T>(
|
|
297
|
+
union: ${typeName}Union,
|
|
298
|
+
handlers: {
|
|
299
|
+
valid: (data: Branded${typeName}) => T;
|
|
300
|
+
invalid: (error: string) => T;
|
|
301
|
+
pending: (progress?: number) => T;
|
|
302
|
+
}
|
|
303
|
+
): T {
|
|
304
|
+
switch (union.type) {
|
|
305
|
+
case 'valid':
|
|
306
|
+
return handlers.valid(union.data);
|
|
307
|
+
case 'invalid':
|
|
308
|
+
return handlers.invalid(union.error);
|
|
309
|
+
case 'pending':
|
|
310
|
+
return handlers.pending(union.progress);
|
|
311
|
+
default:
|
|
312
|
+
// Exhaustiveness check
|
|
313
|
+
const _exhaustive: never = union;
|
|
314
|
+
throw new Error(\`Unhandled union type: \${JSON.stringify(_exhaustive)}\`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Type guard for ${typeName}Union variants
|
|
320
|
+
*/
|
|
321
|
+
export function is${typeName}Variant<T extends ${typeName}Union['type']>(
|
|
322
|
+
union: ${typeName}Union,
|
|
323
|
+
type: T
|
|
324
|
+
): union is Extract<${typeName}Union, { type: T }> {
|
|
325
|
+
return union.type === type;
|
|
460
326
|
}`;
|
|
461
327
|
}
|
|
462
328
|
/**
|
|
463
329
|
* Generate client integration with branded types
|
|
464
330
|
*/
|
|
465
331
|
function generateClientBrandedTypeIntegration(_generatedClient, options) {
|
|
466
|
-
return `
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Branded type integration for API client
|
|
470
|
-
*/
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Type-safe API client with branded type validation
|
|
474
|
-
*/
|
|
475
|
-
export class BrandedApiClient {
|
|
476
|
-
/**
|
|
477
|
-
* Validate and brand response data
|
|
478
|
-
*/
|
|
479
|
-
static validateResponse<T, B extends T & { __brand: any }>(
|
|
480
|
-
data: T,
|
|
481
|
-
validator: (data: T) => B
|
|
482
|
-
): B {
|
|
483
|
-
try {
|
|
484
|
-
return validator(data);
|
|
485
|
-
} catch (error) {
|
|
486
|
-
throw new Error(\`Response validation failed: \${error instanceof Error ? error.message : 'Unknown error'}\`);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Wrap request with branded type validation
|
|
492
|
-
*/
|
|
493
|
-
static withBrandValidation<TRequest, TResponse, TBrandedResponse extends TResponse & { __brand: any }>(
|
|
494
|
-
requestFn: (data: TRequest) => Promise<TResponse>,
|
|
495
|
-
responseValidator: (data: TResponse) => TBrandedResponse
|
|
496
|
-
) {
|
|
497
|
-
return async (data: TRequest): Promise<TBrandedResponse> => {
|
|
498
|
-
const response = await requestFn(data);
|
|
499
|
-
return this.validateResponse(response, responseValidator);
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* Type-safe wrapper function
|
|
506
|
-
*/
|
|
507
|
-
export function withBrandValidation<T extends (...args: any[]) => Promise<any>, R>(
|
|
508
|
-
fn: T,
|
|
509
|
-
validator: (result: Awaited<ReturnType<T>>) => R
|
|
510
|
-
): (...args: Parameters<T>) => Promise<R> {
|
|
511
|
-
return async (...args: Parameters<T>): Promise<R> => {
|
|
512
|
-
const result = await fn(...args);
|
|
513
|
-
return validator(result);
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
|
|
332
|
+
return `
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Branded type integration for API client
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Type-safe API client with branded type validation
|
|
340
|
+
*/
|
|
341
|
+
export class BrandedApiClient {
|
|
342
|
+
/**
|
|
343
|
+
* Validate and brand response data
|
|
344
|
+
*/
|
|
345
|
+
static validateResponse<T, B extends T & { __brand: any }>(
|
|
346
|
+
data: T,
|
|
347
|
+
validator: (data: T) => B
|
|
348
|
+
): B {
|
|
349
|
+
try {
|
|
350
|
+
return validator(data);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
throw new Error(\`Response validation failed: \${error instanceof Error ? error.message : 'Unknown error'}\`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Wrap request with branded type validation
|
|
358
|
+
*/
|
|
359
|
+
static withBrandValidation<TRequest, TResponse, TBrandedResponse extends TResponse & { __brand: any }>(
|
|
360
|
+
requestFn: (data: TRequest) => Promise<TResponse>,
|
|
361
|
+
responseValidator: (data: TResponse) => TBrandedResponse
|
|
362
|
+
) {
|
|
363
|
+
return async (data: TRequest): Promise<TBrandedResponse> => {
|
|
364
|
+
const response = await requestFn(data);
|
|
365
|
+
return this.validateResponse<TResponse, TBrandedResponse>(response, responseValidator);
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Type-safe wrapper function
|
|
372
|
+
*/
|
|
373
|
+
export function withBrandValidation<T extends (...args: any[]) => Promise<any>, R>(
|
|
374
|
+
fn: T,
|
|
375
|
+
validator: (result: Awaited<ReturnType<T>>) => R
|
|
376
|
+
): (...args: Parameters<T>) => Promise<R> {
|
|
377
|
+
return async (...args: Parameters<T>): Promise<R> => {
|
|
378
|
+
const result = await fn(...args);
|
|
379
|
+
return validator(result);
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
517
383
|
${options.enablePhantomTypes ? generatePhantomApiClient() : ""}`;
|
|
518
384
|
}
|
|
519
385
|
/**
|
|
520
386
|
* Generate phantom type API client integration
|
|
521
387
|
*/
|
|
522
388
|
function generatePhantomApiClient() {
|
|
523
|
-
return `
|
|
524
|
-
|
|
525
|
-
/**
|
|
526
|
-
* API client with phantom authentication state
|
|
527
|
-
*/
|
|
528
|
-
export
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
return new AuthenticatedApiClient();
|
|
563
|
-
}
|
|
389
|
+
return `
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* API client with phantom authentication state
|
|
393
|
+
*/
|
|
394
|
+
export { BrandedApiClient as TypeSafeApiClient };
|
|
395
|
+
|
|
396
|
+
export class AuthenticatedApiClient {
|
|
397
|
+
private readonly isAuthenticated = true;
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Execute authenticated request
|
|
401
|
+
*/
|
|
402
|
+
async executeAuthenticatedRequest<T>(
|
|
403
|
+
request: () => Promise<T>
|
|
404
|
+
): Promise<T> {
|
|
405
|
+
if (!this.isAuthenticated) {
|
|
406
|
+
throw new Error('Client is not authenticated');
|
|
407
|
+
}
|
|
408
|
+
return request();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export class UnauthenticatedApiClient {
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Execute public request
|
|
416
|
+
*/
|
|
417
|
+
async executePublicRequest<T>(
|
|
418
|
+
request: () => Promise<T>
|
|
419
|
+
): Promise<T> {
|
|
420
|
+
return request();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Authenticate and return authenticated client
|
|
425
|
+
*/
|
|
426
|
+
async authenticate(credentials: { token: string }): Promise<AuthenticatedApiClient> {
|
|
427
|
+
if (!credentials.token) throw new Error('A token is required for client authentication state metadata');
|
|
428
|
+
return new AuthenticatedApiClient();
|
|
429
|
+
}
|
|
564
430
|
}`;
|
|
565
431
|
}
|
|
566
432
|
// Export plugin for registration
|