@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcteninc/core",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -11,13 +11,19 @@ import { glob } from 'glob';
11
11
 
12
12
  // JSON Schema compatible format
13
13
  interface JsonSchemaProperty {
14
- type: string;
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
- // Check if it's a string literal union (enum)
455
- const stringLiterals = types
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
- if (stringLiterals.length === types.length) {
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: { type: 'string', enum: stringLiterals }
506
+ schema: enumSchema
463
507
  };
464
508
  }
465
509
 
466
- // Check for optional (T | undefined)
467
- const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
468
- const nonUndefinedTypes = types.filter(t => !(t.flags & ts.TypeFlags.Undefined));
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
- // Generic union - just use string representation
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: types.map(t => checker.typeToString(t)).join(' | ') }
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: typeString.length < 100 ? typeString : 'any' }
748
+ schema: { type: 'any' }
540
749
  };
541
750
  }
542
751