@arcteninc/core 0.0.31 → 0.0.33
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/package.json +1 -1
- package/scripts/cli-extract-types-auto.ts +226 -17
package/package.json
CHANGED
|
@@ -11,13 +11,19 @@ import { glob } from 'glob';
|
|
|
11
11
|
|
|
12
12
|
// JSON Schema compatible format
|
|
13
13
|
interface JsonSchemaProperty {
|
|
14
|
-
type
|
|
14
|
+
type?: string | string[]; // Can be string, array of strings, or omitted for anyOf/oneOf/$ref
|
|
15
15
|
description?: string;
|
|
16
16
|
default?: string;
|
|
17
|
-
enum?: string[]; // For enum types
|
|
17
|
+
enum?: (string | number)[]; // For enum types (can be string or number enums)
|
|
18
|
+
const?: string | number | boolean; // For literal values
|
|
18
19
|
items?: JsonSchemaProperty; // For array types
|
|
19
20
|
properties?: Record<string, JsonSchemaProperty>; // For object types
|
|
20
21
|
required?: string[]; // For nested objects
|
|
22
|
+
anyOf?: JsonSchemaProperty[]; // For union types
|
|
23
|
+
oneOf?: JsonSchemaProperty[]; // For discriminated unions
|
|
24
|
+
$ref?: string; // For type references
|
|
25
|
+
// Allow any additional properties for complex schemas
|
|
26
|
+
[key: string]: any;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
interface FunctionMetadata {
|
|
@@ -423,7 +429,7 @@ function serializeType(
|
|
|
423
429
|
depth = 0
|
|
424
430
|
): { isOptional: boolean; schema: JsonSchemaProperty } {
|
|
425
431
|
const typeString = checker.typeToString(type);
|
|
426
|
-
|
|
432
|
+
|
|
427
433
|
if (depth > 10) {
|
|
428
434
|
return { isOptional: false, schema: { type: 'any' } };
|
|
429
435
|
}
|
|
@@ -443,6 +449,26 @@ function serializeType(
|
|
|
443
449
|
if (type.flags & ts.TypeFlags.Boolean) {
|
|
444
450
|
return { isOptional: false, schema: { type: 'boolean' } };
|
|
445
451
|
}
|
|
452
|
+
|
|
453
|
+
// Handle boolean literals (true/false)
|
|
454
|
+
if (type.flags & ts.TypeFlags.BooleanLiteral) {
|
|
455
|
+
// Access the value through the intrinsicName property
|
|
456
|
+
const value = (type as any).intrinsicName === 'true';
|
|
457
|
+
return { isOptional: false, schema: { type: 'boolean', const: value } };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Handle number literals
|
|
461
|
+
if (type.flags & ts.TypeFlags.NumberLiteral) {
|
|
462
|
+
const value = (type as any).value;
|
|
463
|
+
return { isOptional: false, schema: { type: 'number', const: value } };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Handle string literals (enum members)
|
|
467
|
+
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
468
|
+
const value = (type as any).value;
|
|
469
|
+
return { isOptional: false, schema: { type: 'string', const: value } };
|
|
470
|
+
}
|
|
471
|
+
|
|
446
472
|
if (type.flags & ts.TypeFlags.Undefined || type.flags & ts.TypeFlags.Void) {
|
|
447
473
|
return { isOptional: true, schema: { type: 'null' } };
|
|
448
474
|
}
|
|
@@ -451,31 +477,150 @@ function serializeType(
|
|
|
451
477
|
if (type.isUnion()) {
|
|
452
478
|
const types = type.types;
|
|
453
479
|
|
|
454
|
-
//
|
|
455
|
-
const
|
|
480
|
+
// Separate null, undefined, and other types
|
|
481
|
+
const hasNull = types.some(t => t.flags & ts.TypeFlags.Null);
|
|
482
|
+
const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
|
|
483
|
+
const nonNullUndefinedTypes = types.filter(
|
|
484
|
+
t => !(t.flags & ts.TypeFlags.Null) && !(t.flags & ts.TypeFlags.Undefined)
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// Check if it's a string literal union (enum) - possibly with null/undefined
|
|
488
|
+
const stringLiterals = nonNullUndefinedTypes
|
|
456
489
|
.filter(t => t.flags & ts.TypeFlags.StringLiteral)
|
|
457
490
|
.map(t => (t as ts.StringLiteralType).value);
|
|
458
491
|
|
|
459
|
-
|
|
492
|
+
// If all non-null/undefined types are string literals, use enum
|
|
493
|
+
if (stringLiterals.length > 0 && stringLiterals.length === nonNullUndefinedTypes.length) {
|
|
494
|
+
const enumSchema: JsonSchemaProperty = { type: 'string', enum: stringLiterals };
|
|
495
|
+
|
|
496
|
+
// If null or undefined is also present, wrap in anyOf
|
|
497
|
+
if (hasNull || hasUndefined) {
|
|
498
|
+
return {
|
|
499
|
+
isOptional: false,
|
|
500
|
+
schema: { anyOf: [enumSchema, { type: 'null' }] }
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
460
504
|
return {
|
|
461
505
|
isOptional: false,
|
|
462
|
-
schema:
|
|
506
|
+
schema: enumSchema
|
|
463
507
|
};
|
|
464
508
|
}
|
|
465
509
|
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (hasUndefined && nonUndefinedTypes.length === 1) {
|
|
471
|
-
const result = serializeType(nonUndefinedTypes[0], checker, visited, depth + 1);
|
|
510
|
+
// Handle optional (T | undefined) - make it optional instead of union
|
|
511
|
+
if (hasUndefined && nonNullUndefinedTypes.length === 1 && !hasNull) {
|
|
512
|
+
const result = serializeType(nonNullUndefinedTypes[0], checker, visited, depth + 1);
|
|
472
513
|
return { isOptional: true, schema: result.schema };
|
|
473
514
|
}
|
|
474
515
|
|
|
475
|
-
//
|
|
516
|
+
// Build anyOf array for proper JSON Schema union
|
|
517
|
+
const anyOf: JsonSchemaProperty[] = [];
|
|
518
|
+
|
|
519
|
+
// Add null if present
|
|
520
|
+
if (hasNull) {
|
|
521
|
+
anyOf.push({ type: 'null' });
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Add undefined as null (JSON doesn't have undefined)
|
|
525
|
+
if (hasUndefined) {
|
|
526
|
+
anyOf.push({ type: 'null' });
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Process non-null/undefined types
|
|
530
|
+
for (const unionType of nonNullUndefinedTypes) {
|
|
531
|
+
// Check if it's a primitive type
|
|
532
|
+
if (unionType.flags & ts.TypeFlags.String) {
|
|
533
|
+
anyOf.push({ type: 'string' });
|
|
534
|
+
} else if (unionType.flags & ts.TypeFlags.Number) {
|
|
535
|
+
anyOf.push({ type: 'number' });
|
|
536
|
+
} else if (unionType.flags & ts.TypeFlags.Boolean) {
|
|
537
|
+
anyOf.push({ type: 'boolean' });
|
|
538
|
+
} else if (unionType.flags & ts.TypeFlags.BooleanLiteral) {
|
|
539
|
+
// Boolean literal (true/false)
|
|
540
|
+
const value = (unionType as any).intrinsicName === 'true';
|
|
541
|
+
anyOf.push({ type: 'boolean', const: value });
|
|
542
|
+
} else if (unionType.flags & ts.TypeFlags.NumberLiteral) {
|
|
543
|
+
// Number literal
|
|
544
|
+
const value = (unionType as any).value;
|
|
545
|
+
anyOf.push({ type: 'number', const: value });
|
|
546
|
+
} else if (unionType.flags & ts.TypeFlags.StringLiteral) {
|
|
547
|
+
// String literal (enum member)
|
|
548
|
+
const value = (unionType as any).value;
|
|
549
|
+
anyOf.push({ type: 'string', const: value });
|
|
550
|
+
} else if (checker.isArrayType(unionType)) {
|
|
551
|
+
// Handle array types
|
|
552
|
+
const typeArgs = (unionType as ts.TypeReference).typeArguments;
|
|
553
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
554
|
+
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1);
|
|
555
|
+
anyOf.push({ type: 'array', items: itemResult.schema });
|
|
556
|
+
} else {
|
|
557
|
+
anyOf.push({ type: 'array' });
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
// Try to serialize the type (handles objects, type references, etc.)
|
|
561
|
+
// This will recursively resolve type aliases and interfaces
|
|
562
|
+
try {
|
|
563
|
+
const refResult = serializeType(unionType, checker, visited, depth + 1);
|
|
564
|
+
|
|
565
|
+
// Check if we got a valid schema (not just 'any' fallback)
|
|
566
|
+
const typeString = checker.typeToString(unionType);
|
|
567
|
+
const isFallbackAny = refResult.schema.type === 'any' &&
|
|
568
|
+
!refResult.schema.properties &&
|
|
569
|
+
!refResult.schema.anyOf &&
|
|
570
|
+
!refResult.schema.items &&
|
|
571
|
+
typeString.length < 100; // If type string is short, it's probably a simple reference
|
|
572
|
+
|
|
573
|
+
if (!isFallbackAny) {
|
|
574
|
+
anyOf.push(refResult.schema);
|
|
575
|
+
} else {
|
|
576
|
+
// For type references we can't fully resolve (like FilterTreeNode),
|
|
577
|
+
// check if it's a named type that might be defined elsewhere
|
|
578
|
+
// For now, we'll use 'any' but in a more advanced version,
|
|
579
|
+
// we'd track these and generate $defs or inline them
|
|
580
|
+
// Check if it looks like a type reference (starts with capital, no special chars)
|
|
581
|
+
if (/^[A-Z][a-zA-Z0-9]*$/.test(typeString)) {
|
|
582
|
+
// It's a type reference - use any for now
|
|
583
|
+
// TODO: Resolve and inline type definitions or use $ref with $defs
|
|
584
|
+
anyOf.push({ type: 'any' });
|
|
585
|
+
} else {
|
|
586
|
+
// It's something else - try to use the serialized result anyway
|
|
587
|
+
anyOf.push(refResult.schema);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} catch (e) {
|
|
591
|
+
// Fallback for unresolvable types
|
|
592
|
+
const typeString = checker.typeToString(unionType);
|
|
593
|
+
console.warn(`Warning: Could not serialize union type: ${typeString}`, e);
|
|
594
|
+
anyOf.push({ type: 'any' });
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// If we only have one type (plus null/undefined), simplify
|
|
600
|
+
if (anyOf.length === 1) {
|
|
601
|
+
return { isOptional: hasNull || hasUndefined, schema: anyOf[0] };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// If we have multiple types, use anyOf
|
|
605
|
+
if (anyOf.length > 1) {
|
|
606
|
+
// Remove duplicate null entries
|
|
607
|
+
const uniqueAnyOf = anyOf.filter((schema, index, self) => {
|
|
608
|
+
if (schema.type === 'null') {
|
|
609
|
+
return index === self.findIndex(s => s.type === 'null');
|
|
610
|
+
}
|
|
611
|
+
return true;
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
isOptional: false,
|
|
616
|
+
schema: { anyOf: uniqueAnyOf }
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Fallback
|
|
476
621
|
return {
|
|
477
622
|
isOptional: false,
|
|
478
|
-
schema: { type:
|
|
623
|
+
schema: { type: 'any' }
|
|
479
624
|
};
|
|
480
625
|
}
|
|
481
626
|
|
|
@@ -533,10 +678,74 @@ function serializeType(
|
|
|
533
678
|
}
|
|
534
679
|
}
|
|
535
680
|
|
|
536
|
-
// Fallback
|
|
681
|
+
// Fallback - check type flags to determine if it's a valid JSON Schema type
|
|
682
|
+
// Valid JSON Schema types: string, number, integer, boolean, object, array, null
|
|
683
|
+
|
|
684
|
+
// Check if it's an enum type (TypeScript enum)
|
|
685
|
+
if (type.flags & ts.TypeFlags.Enum) {
|
|
686
|
+
// Try to get enum members
|
|
687
|
+
const symbol = type.symbol || (type as any).symbol;
|
|
688
|
+
if (symbol) {
|
|
689
|
+
const enumMembers = checker.getPropertiesOfType(type);
|
|
690
|
+
if (enumMembers.length > 0) {
|
|
691
|
+
// Extract enum values - enum members are typically string or number literals
|
|
692
|
+
const values: (string | number)[] = [];
|
|
693
|
+
for (const member of enumMembers) {
|
|
694
|
+
const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration || member.declarations?.[0] || type as any);
|
|
695
|
+
if (memberType.flags & ts.TypeFlags.StringLiteral) {
|
|
696
|
+
values.push((memberType as any).value);
|
|
697
|
+
} else if (memberType.flags & ts.TypeFlags.NumberLiteral) {
|
|
698
|
+
values.push((memberType as any).value);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (values.length > 0) {
|
|
702
|
+
// Check if all values are strings or all are numbers
|
|
703
|
+
const allStrings = values.every(v => typeof v === 'string');
|
|
704
|
+
const allNumbers = values.every(v => typeof v === 'number');
|
|
705
|
+
if (allStrings) {
|
|
706
|
+
return { isOptional: false, schema: { type: 'string', enum: values.filter((v): v is string => typeof v === 'string') } };
|
|
707
|
+
} else if (allNumbers) {
|
|
708
|
+
const numberValues = values.filter((v): v is number => typeof v === 'number');
|
|
709
|
+
return { isOptional: false, schema: { type: 'number', enum: numberValues } };
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
// If we can't extract enum values, use 'any'
|
|
715
|
+
return { isOptional: false, schema: { type: 'any' } };
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Check if it's a type reference (like InternalFilterType.StringFilter)
|
|
719
|
+
// Type references that aren't objects/arrays/primitives should use 'any'
|
|
720
|
+
if (type.flags & ts.TypeFlags.Object) {
|
|
721
|
+
// Already handled above, but check if it's an empty object type
|
|
722
|
+
const props = checker.getPropertiesOfType(type);
|
|
723
|
+
if (props.length === 0) {
|
|
724
|
+
// Empty object or type reference we can't resolve
|
|
725
|
+
// Check if typeString suggests it's a named type reference
|
|
726
|
+
if (typeString && typeString.includes('.')) {
|
|
727
|
+
return { isOptional: false, schema: { type: 'any' } };
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Check if typeString is a valid JSON Schema primitive type
|
|
733
|
+
const validJsonSchemaTypes = new Set(['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']);
|
|
734
|
+
const lowerTypeString = typeString?.toLowerCase();
|
|
735
|
+
|
|
736
|
+
// If it's a valid JSON Schema type, use it (shouldn't happen here, but safety check)
|
|
737
|
+
if (lowerTypeString && validJsonSchemaTypes.has(lowerTypeString)) {
|
|
738
|
+
return {
|
|
739
|
+
isOptional: false,
|
|
740
|
+
schema: { type: lowerTypeString as any }
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// For any other unrecognized type, use 'any' instead of invalid type strings
|
|
745
|
+
// This prevents errors like "type": "InternalFilterType.StringFilter"
|
|
537
746
|
return {
|
|
538
747
|
isOptional: false,
|
|
539
|
-
schema: { type:
|
|
748
|
+
schema: { type: 'any' }
|
|
540
749
|
};
|
|
541
750
|
}
|
|
542
751
|
|