@arcteninc/core 0.0.31 → 0.0.32

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.32",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -11,13 +11,18 @@ 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
17
  enum?: string[]; // For enum types
18
18
  items?: JsonSchemaProperty; // For array types
19
19
  properties?: Record<string, JsonSchemaProperty>; // For object types
20
20
  required?: string[]; // For nested objects
21
+ anyOf?: JsonSchemaProperty[]; // For union types
22
+ oneOf?: JsonSchemaProperty[]; // For discriminated unions
23
+ $ref?: string; // For type references
24
+ // Allow any additional properties for complex schemas
25
+ [key: string]: any;
21
26
  }
22
27
 
23
28
  interface FunctionMetadata {
@@ -451,31 +456,138 @@ function serializeType(
451
456
  if (type.isUnion()) {
452
457
  const types = type.types;
453
458
 
454
- // Check if it's a string literal union (enum)
455
- const stringLiterals = types
459
+ // Separate null, undefined, and other types
460
+ const hasNull = types.some(t => t.flags & ts.TypeFlags.Null);
461
+ const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
462
+ const nonNullUndefinedTypes = types.filter(
463
+ t => !(t.flags & ts.TypeFlags.Null) && !(t.flags & ts.TypeFlags.Undefined)
464
+ );
465
+
466
+ // Check if it's a string literal union (enum) - possibly with null/undefined
467
+ const stringLiterals = nonNullUndefinedTypes
456
468
  .filter(t => t.flags & ts.TypeFlags.StringLiteral)
457
469
  .map(t => (t as ts.StringLiteralType).value);
458
470
 
459
- if (stringLiterals.length === types.length) {
471
+ // If all non-null/undefined types are string literals, use enum
472
+ if (stringLiterals.length > 0 && stringLiterals.length === nonNullUndefinedTypes.length) {
473
+ const enumSchema: JsonSchemaProperty = { type: 'string', enum: stringLiterals };
474
+
475
+ // If null or undefined is also present, wrap in anyOf
476
+ if (hasNull || hasUndefined) {
477
+ return {
478
+ isOptional: false,
479
+ schema: { anyOf: [enumSchema, { type: 'null' }] }
480
+ };
481
+ }
482
+
460
483
  return {
461
484
  isOptional: false,
462
- schema: { type: 'string', enum: stringLiterals }
485
+ schema: enumSchema
463
486
  };
464
487
  }
465
488
 
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);
489
+ // Handle optional (T | undefined) - make it optional instead of union
490
+ if (hasUndefined && nonNullUndefinedTypes.length === 1 && !hasNull) {
491
+ const result = serializeType(nonNullUndefinedTypes[0], checker, visited, depth + 1);
472
492
  return { isOptional: true, schema: result.schema };
473
493
  }
474
494
 
475
- // Generic union - just use string representation
495
+ // Build anyOf array for proper JSON Schema union
496
+ const anyOf: JsonSchemaProperty[] = [];
497
+
498
+ // Add null if present
499
+ if (hasNull) {
500
+ anyOf.push({ type: 'null' });
501
+ }
502
+
503
+ // Add undefined as null (JSON doesn't have undefined)
504
+ if (hasUndefined) {
505
+ anyOf.push({ type: 'null' });
506
+ }
507
+
508
+ // Process non-null/undefined types
509
+ for (const unionType of nonNullUndefinedTypes) {
510
+ // Check if it's a primitive type
511
+ if (unionType.flags & ts.TypeFlags.String) {
512
+ anyOf.push({ type: 'string' });
513
+ } else if (unionType.flags & ts.TypeFlags.Number) {
514
+ anyOf.push({ type: 'number' });
515
+ } else if (unionType.flags & ts.TypeFlags.Boolean) {
516
+ anyOf.push({ type: 'boolean' });
517
+ } else if (checker.isArrayType(unionType)) {
518
+ // Handle array types
519
+ const typeArgs = (unionType as ts.TypeReference).typeArguments;
520
+ if (typeArgs && typeArgs.length > 0) {
521
+ const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1);
522
+ anyOf.push({ type: 'array', items: itemResult.schema });
523
+ } else {
524
+ anyOf.push({ type: 'array' });
525
+ }
526
+ } else {
527
+ // Try to serialize the type (handles objects, type references, etc.)
528
+ // This will recursively resolve type aliases and interfaces
529
+ try {
530
+ const refResult = serializeType(unionType, checker, visited, depth + 1);
531
+
532
+ // Check if we got a valid schema (not just 'any' fallback)
533
+ const typeString = checker.typeToString(unionType);
534
+ const isFallbackAny = refResult.schema.type === 'any' &&
535
+ !refResult.schema.properties &&
536
+ !refResult.schema.anyOf &&
537
+ !refResult.schema.items &&
538
+ typeString.length < 100; // If type string is short, it's probably a simple reference
539
+
540
+ if (!isFallbackAny) {
541
+ anyOf.push(refResult.schema);
542
+ } else {
543
+ // For type references we can't fully resolve (like FilterTreeNode),
544
+ // check if it's a named type that might be defined elsewhere
545
+ // For now, we'll use 'any' but in a more advanced version,
546
+ // we'd track these and generate $defs or inline them
547
+ // Check if it looks like a type reference (starts with capital, no special chars)
548
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(typeString)) {
549
+ // It's a type reference - use any for now
550
+ // TODO: Resolve and inline type definitions or use $ref with $defs
551
+ anyOf.push({ type: 'any' });
552
+ } else {
553
+ // It's something else - try to use the serialized result anyway
554
+ anyOf.push(refResult.schema);
555
+ }
556
+ }
557
+ } catch (e) {
558
+ // Fallback for unresolvable types
559
+ const typeString = checker.typeToString(unionType);
560
+ console.warn(`Warning: Could not serialize union type: ${typeString}`, e);
561
+ anyOf.push({ type: 'any' });
562
+ }
563
+ }
564
+ }
565
+
566
+ // If we only have one type (plus null/undefined), simplify
567
+ if (anyOf.length === 1) {
568
+ return { isOptional: hasNull || hasUndefined, schema: anyOf[0] };
569
+ }
570
+
571
+ // If we have multiple types, use anyOf
572
+ if (anyOf.length > 1) {
573
+ // Remove duplicate null entries
574
+ const uniqueAnyOf = anyOf.filter((schema, index, self) => {
575
+ if (schema.type === 'null') {
576
+ return index === self.findIndex(s => s.type === 'null');
577
+ }
578
+ return true;
579
+ });
580
+
581
+ return {
582
+ isOptional: false,
583
+ schema: { anyOf: uniqueAnyOf }
584
+ };
585
+ }
586
+
587
+ // Fallback
476
588
  return {
477
589
  isOptional: false,
478
- schema: { type: types.map(t => checker.typeToString(t)).join(' | ') }
590
+ schema: { type: 'any' }
479
591
  };
480
592
  }
481
593