@gati-framework/cli
Version:
CLI tool for Gati framework - create, develop, build and deploy cloud-native applications
333 lines • 13.6 kB
JavaScript
/**
* @module cli/extractor/constraint-extractor
* @description Extract branded types and constraint combinators from TypeScript types
*/
/**
* Constraint extractor for branded types and combinators
*/
export class ConstraintExtractor {
/**
* Extract brand from type intersection
*
* Detects patterns like:
* - string & Brand<"email">
* - number & Brand<"positive">
*/
extractBrand(type) {
if (!type.isIntersection()) {
return null;
}
const intersectionTypes = type.getIntersectionTypes();
for (const intersectType of intersectionTypes) {
const symbol = intersectType.getSymbol();
if (!symbol)
continue;
const name = symbol.getName();
// Prefer alias-based Brand<T> extraction for imported types
const aliasSymbol = intersectType.getAliasSymbol?.();
const aliasName = aliasSymbol ? aliasSymbol.getName() : undefined;
const aliasArgs = typeof intersectType.getAliasTypeArguments === 'function' ? intersectType.getAliasTypeArguments() : [];
if (aliasName === 'Brand') {
const brandArg = aliasArgs[0];
if (brandArg?.isStringLiteral()) {
const brandName = brandArg.getLiteralValue();
return { name: brandName, isBranded: true };
}
}
// Check if this is a Brand<T> type
if (name === '__type' || name.includes('Brand')) {
// Get type arguments
const typeArgs = intersectType.getTypeArguments();
if (typeArgs.length > 0) {
const brandArg = typeArgs[0];
if (brandArg?.isStringLiteral()) {
const brandName = brandArg.getLiteralValue();
return { name: brandName, isBranded: true };
}
}
// Fallback: check for __brand or __gati_brand property
const properties = intersectType.getProperties();
const brandProp = properties.find(p => p.getName() === '__brand' || p.getName() === '__gati_brand');
if (brandProp) {
const propName = brandProp.getName();
const propType = intersectType.getProperty(propName);
const valueDecl = brandProp.getValueDeclaration();
if (valueDecl && propType) {
const propTypeNode = propType.getTypeAtLocation(valueDecl);
if (propTypeNode.isStringLiteral()) {
const brandName = propTypeNode.getLiteralValue();
return { name: brandName, isBranded: true };
}
}
}
}
}
return null;
}
/**
* Extract string constraints from type intersection
*
* Detects patterns like:
* - string & MinLen<8>
* - string & MaxLen<100>
* - string & Pattern<"^[a-z]+$">
*/
extractStringConstraints(type) {
const constraints = {};
if (!type.isIntersection()) {
return constraints;
}
const intersectionTypes = type.getIntersectionTypes();
for (const intersectType of intersectionTypes) {
const symbol = intersectType.getSymbol();
if (!symbol)
continue;
const name = symbol.getName();
// Prefer alias-based generic extraction for imported combinators
const aliasSymbol = intersectType.getAliasSymbol?.();
const aliasName = aliasSymbol ? aliasSymbol.getName() : undefined;
const aliasArgs = typeof intersectType.getAliasTypeArguments === 'function' ? intersectType.getAliasTypeArguments() : [];
if (aliasName === 'MinLen') {
const arg = aliasArgs[0];
if (arg?.isNumberLiteral()) {
constraints.minLength = arg.getLiteralValue();
}
}
if (aliasName === 'MaxLen') {
const arg = aliasArgs[0];
if (arg?.isNumberLiteral()) {
constraints.maxLength = arg.getLiteralValue();
}
}
if (aliasName === 'Pattern') {
const arg = aliasArgs[0];
if (arg?.isStringLiteral()) {
constraints.pattern = arg.getLiteralValue();
}
}
// MinLen<N>
if (name === '__type' || name.includes('MinLen')) {
const minLen = this.extractNumericTypeArg(intersectType);
if (minLen !== null) {
constraints.minLength = minLen;
}
}
// MaxLen<N>
if (name === '__type' || name.includes('MaxLen')) {
const maxLen = this.extractNumericTypeArg(intersectType);
if (maxLen !== null) {
constraints.maxLength = maxLen;
}
}
// Pattern<S>
if (name === '__type' || name.includes('Pattern')) {
const pattern = this.extractStringTypeArg(intersectType);
if (pattern !== null) {
constraints.pattern = pattern;
}
}
// Check for constraint properties
const properties = intersectType.getProperties();
for (const prop of properties) {
const propName = prop.getName();
const valueDecl = prop.getValueDeclaration();
if (!valueDecl)
continue;
const propType = intersectType.getPropertyOrThrow(propName);
const propTypeNode = propType.getTypeAtLocation(valueDecl);
// __minLen or __gati_minLen property
if ((propName === '__minLen' || propName === '__gati_minLen') && propTypeNode.isNumberLiteral()) {
constraints.minLength = propTypeNode.getLiteralValue();
}
// __maxLen or __gati_maxLen property
if ((propName === '__maxLen' || propName === '__gati_maxLen') && propTypeNode.isNumberLiteral()) {
constraints.maxLength = propTypeNode.getLiteralValue();
}
// __pattern or __gati_pattern property
if ((propName === '__pattern' || propName === '__gati_pattern') && propTypeNode.isStringLiteral()) {
constraints.pattern = propTypeNode.getLiteralValue();
}
}
}
return constraints;
}
/**
* Extract number constraints from type intersection
*
* Detects patterns like:
* - number & Min<0>
* - number & Max<100>
* - number & MultipleOf<5>
*/
extractNumberConstraints(type) {
const constraints = {};
if (!type.isIntersection()) {
return constraints;
}
const intersectionTypes = type.getIntersectionTypes();
for (const intersectType of intersectionTypes) {
const symbol = intersectType.getSymbol();
if (!symbol)
continue;
const name = symbol.getName();
// Prefer alias-based generic extraction for imported number combinators
const aliasSymbol = intersectType.getAliasSymbol?.();
const aliasName = aliasSymbol ? aliasSymbol.getName() : undefined;
const aliasArgs = typeof intersectType.getAliasTypeArguments === 'function' ? intersectType.getAliasTypeArguments() : [];
if (aliasName === 'Min') {
const arg = aliasArgs[0];
if (arg?.isNumberLiteral()) {
constraints.minimum = arg.getLiteralValue();
}
}
if (aliasName === 'Max') {
const arg = aliasArgs[0];
if (arg?.isNumberLiteral()) {
constraints.maximum = arg.getLiteralValue();
}
}
if (aliasName === 'MultipleOf') {
const arg = aliasArgs[0];
if (arg?.isNumberLiteral()) {
constraints.multipleOf = arg.getLiteralValue();
}
}
// Min<N>
if (name === '__type' || name.includes('Min')) {
const min = this.extractNumericTypeArg(intersectType);
if (min !== null) {
constraints.minimum = min;
}
}
// Max<N>
if (name === '__type' || name.includes('Max')) {
const max = this.extractNumericTypeArg(intersectType);
if (max !== null) {
constraints.maximum = max;
}
}
// MultipleOf<N>
if (name === '__type' || name.includes('MultipleOf')) {
const multipleOf = this.extractNumericTypeArg(intersectType);
if (multipleOf !== null) {
constraints.multipleOf = multipleOf;
}
}
// Check for constraint properties
const properties = intersectType.getProperties();
for (const prop of properties) {
const propName = prop.getName();
const valueDecl = prop.getValueDeclaration();
if (!valueDecl)
continue;
const propType = intersectType.getPropertyOrThrow(propName);
const propTypeNode = propType.getTypeAtLocation(valueDecl);
// __min or __gati_min property
if ((propName === '__min' || propName === '__gati_min') && propTypeNode.isNumberLiteral()) {
constraints.minimum = propTypeNode.getLiteralValue();
}
// __max or __gati_max property
if ((propName === '__max' || propName === '__gati_max') && propTypeNode.isNumberLiteral()) {
constraints.maximum = propTypeNode.getLiteralValue();
}
// __multipleOf property
if (propName === '__multipleOf' && propTypeNode.isNumberLiteral()) {
constraints.multipleOf = propTypeNode.getLiteralValue();
}
// __integer property (boolean flag)
if (propName === '__integer') {
if (propTypeNode.isLiteral()) {
const typeText = propTypeNode.getText();
if (typeText === 'true') {
constraints.integer = true;
}
}
}
// __positive property (boolean flag)
if (propName === '__positive') {
if (propTypeNode.isLiteral()) {
const typeText = propTypeNode.getText();
if (typeText === 'true') {
constraints.minimum = 0;
}
}
}
// __negative property (boolean flag)
if (propName === '__negative') {
if (propTypeNode.isLiteral()) {
const typeText = propTypeNode.getText();
if (typeText === 'true') {
constraints.maximum = 0;
}
}
}
}
}
return constraints;
}
/**
* Extract numeric type argument from generic type
*/
extractNumericTypeArg(type) {
const typeArgs = type.getTypeArguments();
if (typeArgs.length > 0) {
const arg = typeArgs[0];
if (arg?.isNumberLiteral()) {
return arg.getLiteralValue();
}
}
return null;
}
/**
* Extract string type argument from generic type
*/
extractStringTypeArg(type) {
const typeArgs = type.getTypeArguments();
if (typeArgs.length > 0) {
const arg = typeArgs[0];
if (arg?.isStringLiteral()) {
return arg.getLiteralValue();
}
}
return null;
}
/**
* Check if type has Nullable<T> wrapper
*/
isNullable(type) {
if (!type.isIntersection() && !type.isUnion()) {
return false;
}
// Check for Nullable<T> pattern
const symbol = type.getSymbol();
if (symbol?.getName().includes('Nullable')) {
return true;
}
// Check for union with null
if (type.isUnion()) {
const unionTypes = type.getUnionTypes();
return unionTypes.some(t => t.isNull());
}
return false;
}
/**
* Check if type has Optional<T> wrapper
*/
isOptional(type) {
if (!type.isIntersection() && !type.isUnion()) {
return false;
}
// Check for Optional<T> pattern
const symbol = type.getSymbol();
if (symbol?.getName().includes('Optional')) {
return true;
}
// Check for union with undefined
if (type.isUnion()) {
const unionTypes = type.getUnionTypes();
return unionTypes.some(t => t.isUndefined());
}
return false;
}
}
//# sourceMappingURL=constraint-extractor.js.map