@openpkg-ts/extract 0.13.0 → 0.14.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/dist/bin/tspec.js +1 -1
- package/dist/shared/{chunk-ksf9k654.js → chunk-v4cnenxs.js} +256 -25
- package/dist/src/index.d.ts +198 -47
- package/dist/src/index.js +361 -43
- package/package.json +1 -1
package/dist/bin/tspec.js
CHANGED
|
@@ -248,7 +248,7 @@ class TypeRegistry {
|
|
|
248
248
|
return { type: checker.typeToString(t) };
|
|
249
249
|
}
|
|
250
250
|
if (sym && !sym.getName().startsWith("__")) {
|
|
251
|
-
return { $ref: sym.getName() };
|
|
251
|
+
return { $ref: `#/types/${sym.getName()}` };
|
|
252
252
|
}
|
|
253
253
|
if (t.flags & ts.TypeFlags.StringLiteral) {
|
|
254
254
|
return { type: "string", enum: [t.value] };
|
|
@@ -265,7 +265,7 @@ class TypeRegistry {
|
|
|
265
265
|
allOf: type.types.map((t) => {
|
|
266
266
|
const sym = t.getSymbol() || t.aliasSymbol;
|
|
267
267
|
if (sym && !sym.getName().startsWith("__")) {
|
|
268
|
-
return { $ref: sym.getName() };
|
|
268
|
+
return { $ref: `#/types/${sym.getName()}` };
|
|
269
269
|
}
|
|
270
270
|
return { type: checker.typeToString(t) };
|
|
271
271
|
})
|
|
@@ -287,7 +287,7 @@ class TypeRegistry {
|
|
|
287
287
|
const propSym = propType.getSymbol() || propType.aliasSymbol;
|
|
288
288
|
const symName = propSym?.getName();
|
|
289
289
|
if (propSym && symName && !symName.startsWith("__") && symName !== propName) {
|
|
290
|
-
properties[propName] = { $ref: symName };
|
|
290
|
+
properties[propName] = { $ref: `#/types/${symName}` };
|
|
291
291
|
} else {
|
|
292
292
|
properties[propName] = { type: checker.typeToString(propType) };
|
|
293
293
|
}
|
|
@@ -316,7 +316,7 @@ class TypeRegistry {
|
|
|
316
316
|
members.push({
|
|
317
317
|
name,
|
|
318
318
|
kind: "property",
|
|
319
|
-
schema: sym && !sym.getName().startsWith("__") ? { $ref: sym.getName() } : { type: checker.typeToString(type) }
|
|
319
|
+
schema: sym && !sym.getName().startsWith("__") ? { $ref: `#/types/${sym.getName()}` } : { type: checker.typeToString(type) }
|
|
320
320
|
});
|
|
321
321
|
} else if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
|
|
322
322
|
const name = member.name?.getText();
|
|
@@ -428,6 +428,21 @@ function extractTypeParameters(node, checker) {
|
|
|
428
428
|
};
|
|
429
429
|
});
|
|
430
430
|
}
|
|
431
|
+
function isSymbolDeprecated(symbol) {
|
|
432
|
+
if (!symbol) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
const jsDocTags = symbol.getJsDocTags();
|
|
436
|
+
if (jsDocTags.some((tag) => tag.name.toLowerCase() === "deprecated")) {
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
for (const declaration of symbol.getDeclarations() ?? []) {
|
|
440
|
+
if (ts2.getJSDocDeprecatedTag(declaration)) {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
431
446
|
|
|
432
447
|
// src/compiler/program.ts
|
|
433
448
|
import * as path2 from "node:path";
|
|
@@ -480,6 +495,30 @@ function createProgram({
|
|
|
480
495
|
|
|
481
496
|
// src/types/schema-builder.ts
|
|
482
497
|
import ts4 from "typescript";
|
|
498
|
+
var BUILTIN_TYPE_SCHEMAS = {
|
|
499
|
+
Date: { type: "string", format: "date-time" },
|
|
500
|
+
RegExp: { type: "object", description: "RegExp" },
|
|
501
|
+
Error: { type: "object" },
|
|
502
|
+
Promise: { type: "object" },
|
|
503
|
+
Map: { type: "object" },
|
|
504
|
+
Set: { type: "object" },
|
|
505
|
+
WeakMap: { type: "object" },
|
|
506
|
+
WeakSet: { type: "object" },
|
|
507
|
+
Function: { type: "object" },
|
|
508
|
+
ArrayBuffer: { type: "string", format: "binary" },
|
|
509
|
+
ArrayBufferLike: { type: "string", format: "binary" },
|
|
510
|
+
DataView: { type: "string", format: "binary" },
|
|
511
|
+
Uint8Array: { type: "string", format: "byte" },
|
|
512
|
+
Uint16Array: { type: "string", format: "byte" },
|
|
513
|
+
Uint32Array: { type: "string", format: "byte" },
|
|
514
|
+
Int8Array: { type: "string", format: "byte" },
|
|
515
|
+
Int16Array: { type: "string", format: "byte" },
|
|
516
|
+
Int32Array: { type: "string", format: "byte" },
|
|
517
|
+
Float32Array: { type: "string", format: "byte" },
|
|
518
|
+
Float64Array: { type: "string", format: "byte" },
|
|
519
|
+
BigInt64Array: { type: "string", format: "byte" },
|
|
520
|
+
BigUint64Array: { type: "string", format: "byte" }
|
|
521
|
+
};
|
|
483
522
|
var PRIMITIVES2 = new Set([
|
|
484
523
|
"string",
|
|
485
524
|
"number",
|
|
@@ -575,10 +614,10 @@ function buildSchema(type, checker, ctx, _depth = 0) {
|
|
|
575
614
|
if (isAtMaxDepth(ctx)) {
|
|
576
615
|
return { type: checker.typeToString(type) };
|
|
577
616
|
}
|
|
578
|
-
if (ctx
|
|
617
|
+
if (ctx?.visitedTypes.has(type)) {
|
|
579
618
|
const symbol2 = type.getSymbol() || type.aliasSymbol;
|
|
580
619
|
if (symbol2) {
|
|
581
|
-
return { $ref: symbol2.getName() };
|
|
620
|
+
return { $ref: `#/types/${symbol2.getName()}` };
|
|
582
621
|
}
|
|
583
622
|
return { type: checker.typeToString(type) };
|
|
584
623
|
}
|
|
@@ -680,17 +719,17 @@ function buildSchema(type, checker, ctx, _depth = 0) {
|
|
|
680
719
|
const symbol2 = typeRef.target.getSymbol();
|
|
681
720
|
const name = symbol2?.getName();
|
|
682
721
|
if (name && BUILTIN_TYPES.has(name)) {
|
|
683
|
-
return { $ref: name };
|
|
722
|
+
return { $ref: `#/types/${name}` };
|
|
684
723
|
}
|
|
685
724
|
if (name && (isBuiltinGeneric(name) || !isAnonymous(typeRef.target))) {
|
|
686
725
|
if (ctx) {
|
|
687
726
|
return withDepth(ctx, () => ({
|
|
688
|
-
$ref: name
|
|
727
|
+
$ref: `#/types/${name}`,
|
|
689
728
|
typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
|
|
690
729
|
}));
|
|
691
730
|
}
|
|
692
731
|
return {
|
|
693
|
-
$ref: name
|
|
732
|
+
$ref: `#/types/${name}`,
|
|
694
733
|
typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
|
|
695
734
|
};
|
|
696
735
|
}
|
|
@@ -702,10 +741,10 @@ function buildSchema(type, checker, ctx, _depth = 0) {
|
|
|
702
741
|
return { type: name };
|
|
703
742
|
}
|
|
704
743
|
if (BUILTIN_TYPES.has(name)) {
|
|
705
|
-
return { $ref: name };
|
|
744
|
+
return { $ref: `#/types/${name}` };
|
|
706
745
|
}
|
|
707
746
|
if (!name.startsWith("__")) {
|
|
708
|
-
return { $ref: name };
|
|
747
|
+
return { $ref: `#/types/${name}` };
|
|
709
748
|
}
|
|
710
749
|
}
|
|
711
750
|
if (type.flags & ts4.TypeFlags.Object) {
|
|
@@ -772,6 +811,110 @@ function buildObjectSchema(properties, checker, ctx) {
|
|
|
772
811
|
}
|
|
773
812
|
return buildProps();
|
|
774
813
|
}
|
|
814
|
+
function isPureRefSchema(schema) {
|
|
815
|
+
return typeof schema === "object" && Object.keys(schema).length === 1 && "$ref" in schema;
|
|
816
|
+
}
|
|
817
|
+
function withDescription(schema, description) {
|
|
818
|
+
if (isPureRefSchema(schema)) {
|
|
819
|
+
return {
|
|
820
|
+
allOf: [schema],
|
|
821
|
+
description
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
return { ...schema, description };
|
|
825
|
+
}
|
|
826
|
+
function schemaIsAny(schema) {
|
|
827
|
+
if (typeof schema === "string") {
|
|
828
|
+
return schema === "any";
|
|
829
|
+
}
|
|
830
|
+
if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
function schemasAreEqual(left, right) {
|
|
836
|
+
if (typeof left !== typeof right) {
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
840
|
+
return left === right;
|
|
841
|
+
}
|
|
842
|
+
if (left == null || right == null) {
|
|
843
|
+
return left === right;
|
|
844
|
+
}
|
|
845
|
+
const normalize = (value) => {
|
|
846
|
+
if (Array.isArray(value)) {
|
|
847
|
+
return value.map((item) => normalize(item));
|
|
848
|
+
}
|
|
849
|
+
if (value && typeof value === "object") {
|
|
850
|
+
const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
|
|
851
|
+
return Object.fromEntries(sortedEntries);
|
|
852
|
+
}
|
|
853
|
+
return value;
|
|
854
|
+
};
|
|
855
|
+
return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
|
|
856
|
+
}
|
|
857
|
+
function deduplicateSchemas(schemas) {
|
|
858
|
+
const result = [];
|
|
859
|
+
for (const schema of schemas) {
|
|
860
|
+
const isDuplicate = result.some((existing) => schemasAreEqual(existing, schema));
|
|
861
|
+
if (!isDuplicate) {
|
|
862
|
+
result.push(schema);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return result;
|
|
866
|
+
}
|
|
867
|
+
function findDiscriminatorProperty(unionTypes, checker) {
|
|
868
|
+
const memberProps = [];
|
|
869
|
+
for (const t of unionTypes) {
|
|
870
|
+
if (t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined)) {
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
const props = t.getProperties();
|
|
874
|
+
if (!props || props.length === 0) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const propValues = new Map;
|
|
878
|
+
for (const prop of props) {
|
|
879
|
+
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
880
|
+
if (!declaration)
|
|
881
|
+
continue;
|
|
882
|
+
try {
|
|
883
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
884
|
+
if (propType.isStringLiteral()) {
|
|
885
|
+
propValues.set(prop.getName(), propType.value);
|
|
886
|
+
} else if (propType.isNumberLiteral()) {
|
|
887
|
+
propValues.set(prop.getName(), propType.value);
|
|
888
|
+
}
|
|
889
|
+
} catch {}
|
|
890
|
+
}
|
|
891
|
+
memberProps.push(propValues);
|
|
892
|
+
}
|
|
893
|
+
if (memberProps.length < 2) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const firstMember = memberProps[0];
|
|
897
|
+
for (const [propName, firstValue] of firstMember) {
|
|
898
|
+
const values = new Set([firstValue]);
|
|
899
|
+
let isDiscriminator = true;
|
|
900
|
+
for (let i = 1;i < memberProps.length; i++) {
|
|
901
|
+
const value = memberProps[i].get(propName);
|
|
902
|
+
if (value === undefined) {
|
|
903
|
+
isDiscriminator = false;
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
if (values.has(value)) {
|
|
907
|
+
isDiscriminator = false;
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
910
|
+
values.add(value);
|
|
911
|
+
}
|
|
912
|
+
if (isDiscriminator) {
|
|
913
|
+
return propName;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
775
918
|
|
|
776
919
|
// src/types/parameters.ts
|
|
777
920
|
import ts5 from "typescript";
|
|
@@ -941,7 +1084,7 @@ function serializeClass(node, ctx) {
|
|
|
941
1084
|
members.push(propMember);
|
|
942
1085
|
} else if (ts6.isMethodDeclaration(member)) {
|
|
943
1086
|
const methodMember = serializeMethod(member, ctx);
|
|
944
|
-
if (methodMember
|
|
1087
|
+
if (methodMember?.name) {
|
|
945
1088
|
if (!methodsByName.has(methodMember.name)) {
|
|
946
1089
|
methodsByName.set(methodMember.name, methodMember);
|
|
947
1090
|
} else {
|
|
@@ -1247,7 +1390,7 @@ function serializeInterface(node, ctx) {
|
|
|
1247
1390
|
members.push(propMember);
|
|
1248
1391
|
} else if (ts7.isMethodSignature(member)) {
|
|
1249
1392
|
const methodMember = serializeMethodSignature(member, ctx);
|
|
1250
|
-
if (methodMember
|
|
1393
|
+
if (methodMember?.name) {
|
|
1251
1394
|
if (!methodsByName.has(methodMember.name)) {
|
|
1252
1395
|
methodsByName.set(methodMember.name, methodMember);
|
|
1253
1396
|
}
|
|
@@ -1440,7 +1583,7 @@ function serializeVariable(node, statement, ctx) {
|
|
|
1440
1583
|
// src/builder/spec-builder.ts
|
|
1441
1584
|
import * as fs2 from "node:fs";
|
|
1442
1585
|
import * as path3 from "node:path";
|
|
1443
|
-
import { SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
1586
|
+
import { SCHEMA_URL, SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
1444
1587
|
import ts8 from "typescript";
|
|
1445
1588
|
|
|
1446
1589
|
// src/serializers/context.ts
|
|
@@ -1460,15 +1603,52 @@ function createContext(program, sourceFile, options = {}) {
|
|
|
1460
1603
|
}
|
|
1461
1604
|
|
|
1462
1605
|
// src/builder/spec-builder.ts
|
|
1606
|
+
var BUILTIN_TYPES2 = new Set([
|
|
1607
|
+
"Array",
|
|
1608
|
+
"Promise",
|
|
1609
|
+
"Map",
|
|
1610
|
+
"Set",
|
|
1611
|
+
"Record",
|
|
1612
|
+
"Partial",
|
|
1613
|
+
"Required",
|
|
1614
|
+
"Pick",
|
|
1615
|
+
"Omit",
|
|
1616
|
+
"Exclude",
|
|
1617
|
+
"Extract",
|
|
1618
|
+
"NonNullable",
|
|
1619
|
+
"Parameters",
|
|
1620
|
+
"ReturnType",
|
|
1621
|
+
"Readonly",
|
|
1622
|
+
"ReadonlyArray",
|
|
1623
|
+
"Awaited",
|
|
1624
|
+
"Date",
|
|
1625
|
+
"RegExp",
|
|
1626
|
+
"Error",
|
|
1627
|
+
"Function",
|
|
1628
|
+
"Object",
|
|
1629
|
+
"String",
|
|
1630
|
+
"Number",
|
|
1631
|
+
"Boolean",
|
|
1632
|
+
"Symbol",
|
|
1633
|
+
"BigInt"
|
|
1634
|
+
]);
|
|
1463
1635
|
async function extract(options) {
|
|
1464
|
-
const {
|
|
1636
|
+
const {
|
|
1637
|
+
entryFile,
|
|
1638
|
+
baseDir,
|
|
1639
|
+
content,
|
|
1640
|
+
maxTypeDepth,
|
|
1641
|
+
maxExternalTypeDepth,
|
|
1642
|
+
resolveExternalTypes,
|
|
1643
|
+
includeSchema
|
|
1644
|
+
} = options;
|
|
1465
1645
|
const diagnostics = [];
|
|
1466
1646
|
const exports = [];
|
|
1467
1647
|
const result = createProgram({ entryFile, baseDir, content });
|
|
1468
1648
|
const { program, sourceFile } = result;
|
|
1469
1649
|
if (!sourceFile) {
|
|
1470
1650
|
return {
|
|
1471
|
-
spec: createEmptySpec(entryFile),
|
|
1651
|
+
spec: createEmptySpec(entryFile, includeSchema),
|
|
1472
1652
|
diagnostics: [{ message: `Could not load source file: ${entryFile}`, severity: "error" }]
|
|
1473
1653
|
};
|
|
1474
1654
|
}
|
|
@@ -1476,7 +1656,7 @@ async function extract(options) {
|
|
|
1476
1656
|
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
1477
1657
|
if (!moduleSymbol) {
|
|
1478
1658
|
return {
|
|
1479
|
-
spec: createEmptySpec(entryFile),
|
|
1659
|
+
spec: createEmptySpec(entryFile, includeSchema),
|
|
1480
1660
|
diagnostics: [{ message: "Could not get module symbol", severity: "warning" }]
|
|
1481
1661
|
};
|
|
1482
1662
|
}
|
|
@@ -1485,7 +1665,11 @@ async function extract(options) {
|
|
|
1485
1665
|
for (const symbol of exportedSymbols) {
|
|
1486
1666
|
exportedIds.add(symbol.getName());
|
|
1487
1667
|
}
|
|
1488
|
-
const ctx = createContext(program, sourceFile, {
|
|
1668
|
+
const ctx = createContext(program, sourceFile, {
|
|
1669
|
+
maxTypeDepth,
|
|
1670
|
+
maxExternalTypeDepth,
|
|
1671
|
+
resolveExternalTypes
|
|
1672
|
+
});
|
|
1489
1673
|
ctx.exportedIds = exportedIds;
|
|
1490
1674
|
for (const symbol of exportedSymbols) {
|
|
1491
1675
|
try {
|
|
@@ -1504,11 +1688,31 @@ async function extract(options) {
|
|
|
1504
1688
|
}
|
|
1505
1689
|
}
|
|
1506
1690
|
const meta = await getPackageMeta(entryFile, baseDir);
|
|
1691
|
+
const types = ctx.typeRegistry.getAll();
|
|
1692
|
+
const danglingRefs = collectDanglingRefs(exports, types);
|
|
1693
|
+
for (const ref of danglingRefs) {
|
|
1694
|
+
diagnostics.push({
|
|
1695
|
+
message: `Type '${ref}' is referenced but not defined in types[].`,
|
|
1696
|
+
severity: "warning",
|
|
1697
|
+
code: "DANGLING_REF",
|
|
1698
|
+
suggestion: "The type may be from an external package. Check import paths."
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
const externalTypes = types.filter((t) => t.kind === "external");
|
|
1702
|
+
if (externalTypes.length > 0) {
|
|
1703
|
+
diagnostics.push({
|
|
1704
|
+
message: `${externalTypes.length} external type(s) could not be fully resolved: ${externalTypes.slice(0, 5).map((t) => t.id).join(", ")}${externalTypes.length > 5 ? "..." : ""}`,
|
|
1705
|
+
severity: "warning",
|
|
1706
|
+
code: "EXTERNAL_TYPE_STUBS",
|
|
1707
|
+
suggestion: "Types are from external packages. Full resolution requires type declarations."
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1507
1710
|
const spec = {
|
|
1711
|
+
...includeSchema ? { $schema: SCHEMA_URL } : {},
|
|
1508
1712
|
openpkg: SCHEMA_VERSION,
|
|
1509
1713
|
meta,
|
|
1510
1714
|
exports,
|
|
1511
|
-
types
|
|
1715
|
+
types,
|
|
1512
1716
|
generation: {
|
|
1513
1717
|
generator: "@openpkg-ts/extract",
|
|
1514
1718
|
timestamp: new Date().toISOString()
|
|
@@ -1516,6 +1720,32 @@ async function extract(options) {
|
|
|
1516
1720
|
};
|
|
1517
1721
|
return { spec, diagnostics };
|
|
1518
1722
|
}
|
|
1723
|
+
function collectAllRefs(obj, refs) {
|
|
1724
|
+
if (obj === null || obj === undefined)
|
|
1725
|
+
return;
|
|
1726
|
+
if (Array.isArray(obj)) {
|
|
1727
|
+
for (const item of obj) {
|
|
1728
|
+
collectAllRefs(item, refs);
|
|
1729
|
+
}
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (typeof obj === "object") {
|
|
1733
|
+
const record = obj;
|
|
1734
|
+
if (typeof record.$ref === "string" && record.$ref.startsWith("#/types/")) {
|
|
1735
|
+
refs.add(record.$ref.slice("#/types/".length));
|
|
1736
|
+
}
|
|
1737
|
+
for (const value of Object.values(record)) {
|
|
1738
|
+
collectAllRefs(value, refs);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function collectDanglingRefs(exports, types) {
|
|
1743
|
+
const definedTypes = new Set(types.map((t) => t.id));
|
|
1744
|
+
const referencedTypes = new Set;
|
|
1745
|
+
collectAllRefs(exports, referencedTypes);
|
|
1746
|
+
collectAllRefs(types, referencedTypes);
|
|
1747
|
+
return Array.from(referencedTypes).filter((ref) => !definedTypes.has(ref) && !BUILTIN_TYPES2.has(ref));
|
|
1748
|
+
}
|
|
1519
1749
|
function resolveExportTarget(symbol, checker) {
|
|
1520
1750
|
let targetSymbol = symbol;
|
|
1521
1751
|
if (symbol.flags & ts8.SymbolFlags.Alias) {
|
|
@@ -1528,7 +1758,7 @@ function resolveExportTarget(symbol, checker) {
|
|
|
1528
1758
|
const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts8.SyntaxKind.ExportSpecifier) || declarations[0];
|
|
1529
1759
|
return { declaration, targetSymbol };
|
|
1530
1760
|
}
|
|
1531
|
-
function serializeDeclaration(declaration, exportSymbol,
|
|
1761
|
+
function serializeDeclaration(declaration, exportSymbol, _targetSymbol, exportName, ctx) {
|
|
1532
1762
|
let result = null;
|
|
1533
1763
|
if (ts8.isFunctionDeclaration(declaration)) {
|
|
1534
1764
|
result = serializeFunctionExport(declaration, ctx);
|
|
@@ -1546,16 +1776,16 @@ function serializeDeclaration(declaration, exportSymbol, targetSymbol, exportNam
|
|
|
1546
1776
|
result = serializeVariable(declaration, varStatement, ctx);
|
|
1547
1777
|
}
|
|
1548
1778
|
} else if (ts8.isNamespaceExport(declaration) || ts8.isModuleDeclaration(declaration)) {
|
|
1549
|
-
result = serializeNamespaceExport(exportSymbol, exportName
|
|
1779
|
+
result = serializeNamespaceExport(exportSymbol, exportName);
|
|
1550
1780
|
} else if (ts8.isSourceFile(declaration)) {
|
|
1551
|
-
result = serializeNamespaceExport(exportSymbol, exportName
|
|
1781
|
+
result = serializeNamespaceExport(exportSymbol, exportName);
|
|
1552
1782
|
}
|
|
1553
1783
|
if (result) {
|
|
1554
1784
|
result = withExportName(result, exportName);
|
|
1555
1785
|
}
|
|
1556
1786
|
return result;
|
|
1557
1787
|
}
|
|
1558
|
-
function serializeNamespaceExport(symbol, exportName
|
|
1788
|
+
function serializeNamespaceExport(symbol, exportName) {
|
|
1559
1789
|
const { description, tags, examples } = getJSDocFromExportSymbol(symbol);
|
|
1560
1790
|
return {
|
|
1561
1791
|
id: exportName,
|
|
@@ -1633,8 +1863,9 @@ function withExportName(entry, exportName) {
|
|
|
1633
1863
|
name: entry.name
|
|
1634
1864
|
};
|
|
1635
1865
|
}
|
|
1636
|
-
function createEmptySpec(entryFile) {
|
|
1866
|
+
function createEmptySpec(entryFile, includeSchema) {
|
|
1637
1867
|
return {
|
|
1868
|
+
...includeSchema ? { $schema: SCHEMA_URL } : {},
|
|
1638
1869
|
openpkg: SCHEMA_VERSION,
|
|
1639
1870
|
meta: { name: path3.basename(entryFile, path3.extname(entryFile)) },
|
|
1640
1871
|
exports: [],
|
|
@@ -1659,4 +1890,4 @@ async function getPackageMeta(entryFile, baseDir) {
|
|
|
1659
1890
|
} catch {}
|
|
1660
1891
|
return { name: path3.basename(searchDir) };
|
|
1661
1892
|
}
|
|
1662
|
-
export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, extractParameters, registerReferencedTypes, serializeClass, serializeEnum, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
|
|
1893
|
+
export { TypeRegistry, getJSDocComment, getSourceLocation, getParamDescription, extractTypeParameters, isSymbolDeprecated, createProgram, BUILTIN_TYPE_SCHEMAS, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, isPureRefSchema, withDescription, schemaIsAny, schemasAreEqual, deduplicateSchemas, findDiscriminatorProperty, extractParameters, registerReferencedTypes, serializeClass, serializeEnum, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ declare class TypeRegistry {
|
|
|
25
25
|
private extractShallowMembers;
|
|
26
26
|
registerFromSymbol(symbol: ts.Symbol, checker: ts.TypeChecker): SpecType | undefined;
|
|
27
27
|
}
|
|
28
|
-
import { SpecExample, SpecSource, SpecTag } from "@openpkg-ts/spec";
|
|
28
|
+
import { SpecExample, SpecSource, SpecTag, SpecTypeParameter } from "@openpkg-ts/spec";
|
|
29
29
|
import ts2 from "typescript";
|
|
30
30
|
declare function getJSDocComment(node: ts2.Node): {
|
|
31
31
|
description?: string;
|
|
@@ -33,6 +33,23 @@ declare function getJSDocComment(node: ts2.Node): {
|
|
|
33
33
|
examples: SpecExample[];
|
|
34
34
|
};
|
|
35
35
|
declare function getSourceLocation(node: ts2.Node, sourceFile: ts2.SourceFile): SpecSource;
|
|
36
|
+
/**
|
|
37
|
+
* Get description for a destructured parameter property from JSDoc @param tags.
|
|
38
|
+
* Matches patterns like:
|
|
39
|
+
* - @param paramName - exact match
|
|
40
|
+
* - @param opts.paramName - dotted notation with alias
|
|
41
|
+
* - @param {type} paramName - type annotation format
|
|
42
|
+
*/
|
|
43
|
+
declare function getParamDescription(propertyName: string, jsdocTags: readonly ts2.JSDocTag[], inferredAlias?: string): string | undefined;
|
|
44
|
+
type DeclarationWithTypeParams = ts2.FunctionDeclaration | ts2.ClassDeclaration | ts2.InterfaceDeclaration | ts2.TypeAliasDeclaration | ts2.MethodDeclaration | ts2.ArrowFunction;
|
|
45
|
+
/**
|
|
46
|
+
* Extract type parameters from declarations like `<T extends Base, K = Default>`
|
|
47
|
+
*/
|
|
48
|
+
declare function extractTypeParameters(node: DeclarationWithTypeParams, checker: ts2.TypeChecker): SpecTypeParameter[] | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a symbol is marked as deprecated via @deprecated JSDoc tag.
|
|
51
|
+
*/
|
|
52
|
+
declare function isSymbolDeprecated(symbol: ts2.Symbol | undefined): boolean;
|
|
36
53
|
import { OpenPkg } from "@openpkg-ts/spec";
|
|
37
54
|
interface ExtractOptions {
|
|
38
55
|
entryFile: string;
|
|
@@ -42,6 +59,8 @@ interface ExtractOptions {
|
|
|
42
59
|
maxExternalTypeDepth?: number;
|
|
43
60
|
resolveExternalTypes?: boolean;
|
|
44
61
|
schemaExtraction?: "static" | "hybrid";
|
|
62
|
+
/** Include $schema URL in output */
|
|
63
|
+
includeSchema?: boolean;
|
|
45
64
|
}
|
|
46
65
|
interface ExtractResult {
|
|
47
66
|
spec: OpenPkg;
|
|
@@ -50,6 +69,8 @@ interface ExtractResult {
|
|
|
50
69
|
interface Diagnostic {
|
|
51
70
|
message: string;
|
|
52
71
|
severity: "error" | "warning" | "info";
|
|
72
|
+
code?: string;
|
|
73
|
+
suggestion?: string;
|
|
53
74
|
location?: {
|
|
54
75
|
file?: string;
|
|
55
76
|
line?: number;
|
|
@@ -71,35 +92,135 @@ interface ProgramResult {
|
|
|
71
92
|
configPath?: string;
|
|
72
93
|
}
|
|
73
94
|
declare function createProgram({ entryFile, baseDir, content }: ProgramOptions): ProgramResult;
|
|
74
|
-
import
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
import * as TS from "typescript";
|
|
96
|
+
/**
|
|
97
|
+
* A schema adapter can detect and extract output types from a specific
|
|
98
|
+
* schema validation library.
|
|
99
|
+
*/
|
|
100
|
+
interface SchemaAdapter {
|
|
101
|
+
/** Unique identifier for this adapter */
|
|
102
|
+
readonly id: string;
|
|
103
|
+
/** npm package name(s) this adapter handles */
|
|
104
|
+
readonly packages: readonly string[];
|
|
105
|
+
/**
|
|
106
|
+
* Check if a type matches this adapter's schema library.
|
|
107
|
+
* Should be fast - called for every export.
|
|
108
|
+
*/
|
|
109
|
+
matches(type: TS.Type, checker: TS.TypeChecker): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Extract the output type from a schema type.
|
|
112
|
+
* Returns null if extraction fails.
|
|
113
|
+
*/
|
|
114
|
+
extractOutputType(type: TS.Type, checker: TS.TypeChecker): TS.Type | null;
|
|
115
|
+
/**
|
|
116
|
+
* Extract the input type from a schema type (optional).
|
|
117
|
+
* Useful for transforms where input differs from output.
|
|
118
|
+
*/
|
|
119
|
+
extractInputType?(type: TS.Type, checker: TS.TypeChecker): TS.Type | null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Result of schema type extraction
|
|
123
|
+
*/
|
|
124
|
+
interface SchemaExtractionResult {
|
|
125
|
+
/** The adapter that matched */
|
|
126
|
+
adapter: SchemaAdapter;
|
|
127
|
+
/** The extracted output type */
|
|
128
|
+
outputType: TS.Type;
|
|
129
|
+
/** The extracted input type (if different from output) */
|
|
130
|
+
inputType?: TS.Type;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Utility: Check if type is an object type reference (has type arguments)
|
|
134
|
+
*/
|
|
135
|
+
declare function isTypeReference(type: TS.Type): type is TS.TypeReference;
|
|
136
|
+
/**
|
|
137
|
+
* Utility: Remove undefined/null from a union type
|
|
138
|
+
*/
|
|
139
|
+
declare function getNonNullableType(type: TS.Type): TS.Type;
|
|
81
140
|
declare function registerAdapter(adapter: SchemaAdapter): void;
|
|
82
|
-
declare function findAdapter(
|
|
83
|
-
declare function isSchemaType(
|
|
84
|
-
declare function extractSchemaType(
|
|
141
|
+
declare function findAdapter(type: TS.Type, checker: TS.TypeChecker): SchemaAdapter | undefined;
|
|
142
|
+
declare function isSchemaType(type: TS.Type, checker: TS.TypeChecker): boolean;
|
|
143
|
+
declare function extractSchemaType(type: TS.Type, checker: TS.TypeChecker): SchemaExtractionResult | null;
|
|
85
144
|
declare const arktypeAdapter: SchemaAdapter;
|
|
86
145
|
declare const typeboxAdapter: SchemaAdapter;
|
|
87
146
|
declare const valibotAdapter: SchemaAdapter;
|
|
88
147
|
declare const zodAdapter: SchemaAdapter;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Standard JSON Schema v1 interface (minimal for detection).
|
|
150
|
+
*/
|
|
151
|
+
interface StandardJSONSchemaV1 {
|
|
152
|
+
"~standard": {
|
|
153
|
+
version: number;
|
|
154
|
+
vendor: string;
|
|
155
|
+
jsonSchema?: {
|
|
156
|
+
output: (target?: string) => Record<string, unknown>;
|
|
157
|
+
input?: (target?: string) => Record<string, unknown>;
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Result of extracting Standard Schema from an export.
|
|
163
|
+
*/
|
|
164
|
+
interface StandardSchemaExtractionResult {
|
|
165
|
+
exportName: string;
|
|
93
166
|
vendor: string;
|
|
167
|
+
outputSchema: Record<string, unknown>;
|
|
168
|
+
inputSchema?: Record<string, unknown>;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Options for runtime Standard Schema extraction.
|
|
172
|
+
*/
|
|
173
|
+
interface ExtractStandardSchemasOptions {
|
|
174
|
+
/** Timeout in milliseconds (default: 10000) */
|
|
175
|
+
timeout?: number;
|
|
176
|
+
/** JSON Schema target version (default: 'draft-2020-12') */
|
|
177
|
+
target?: "draft-2020-12" | "draft-07" | "openapi-3.0";
|
|
94
178
|
}
|
|
95
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Result of Standard Schema extraction.
|
|
181
|
+
*/
|
|
182
|
+
interface StandardSchemaExtractionOutput {
|
|
183
|
+
schemas: Map<string, StandardSchemaExtractionResult>;
|
|
184
|
+
errors: string[];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Check if an object implements StandardJSONSchemaV1.
|
|
188
|
+
* This is a static type guard - doesn't require runtime.
|
|
189
|
+
*/
|
|
190
|
+
declare function isStandardJSONSchema(obj: unknown): obj is StandardJSONSchemaV1;
|
|
191
|
+
/**
|
|
192
|
+
* Resolve compiled JS path from TypeScript source.
|
|
193
|
+
* Tries common output locations: dist/, build/, lib/, same dir.
|
|
194
|
+
*/
|
|
195
|
+
declare function resolveCompiledPath(tsPath: string, baseDir: string): string | null;
|
|
196
|
+
/**
|
|
197
|
+
* Extract Standard Schema JSON Schemas from a compiled JS module.
|
|
198
|
+
*
|
|
199
|
+
* **Security Note**: This executes the module in a subprocess.
|
|
200
|
+
* Only use with trusted code (user's own packages).
|
|
201
|
+
*
|
|
202
|
+
* @param compiledJsPath - Path to compiled .js file
|
|
203
|
+
* @param options - Extraction options
|
|
204
|
+
* @returns Extraction results with schemas and any errors
|
|
205
|
+
*/
|
|
206
|
+
declare function extractStandardSchemas(compiledJsPath: string, options?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
|
|
207
|
+
/**
|
|
208
|
+
* Extract Standard Schema from a TypeScript project.
|
|
209
|
+
*
|
|
210
|
+
* Convenience function that resolves compiled JS and extracts schemas.
|
|
211
|
+
*
|
|
212
|
+
* @param entryFile - TypeScript entry file path
|
|
213
|
+
* @param baseDir - Project base directory
|
|
214
|
+
* @param options - Extraction options
|
|
215
|
+
*/
|
|
216
|
+
declare function extractStandardSchemasFromProject(entryFile: string, baseDir: string, options?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
|
|
96
217
|
import { SpecExport } from "@openpkg-ts/spec";
|
|
97
|
-
import
|
|
98
|
-
import
|
|
218
|
+
import ts5 from "typescript";
|
|
219
|
+
import ts4 from "typescript";
|
|
99
220
|
interface SerializerContext {
|
|
100
|
-
typeChecker:
|
|
101
|
-
program:
|
|
102
|
-
sourceFile:
|
|
221
|
+
typeChecker: ts4.TypeChecker;
|
|
222
|
+
program: ts4.Program;
|
|
223
|
+
sourceFile: ts4.SourceFile;
|
|
103
224
|
maxTypeDepth: number;
|
|
104
225
|
maxExternalTypeDepth: number;
|
|
105
226
|
currentDepth: number;
|
|
@@ -107,37 +228,39 @@ interface SerializerContext {
|
|
|
107
228
|
typeRegistry: TypeRegistry;
|
|
108
229
|
exportedIds: Set<string>;
|
|
109
230
|
/** Track visited types to prevent infinite recursion */
|
|
110
|
-
visitedTypes: Set<
|
|
231
|
+
visitedTypes: Set<ts4.Type>;
|
|
111
232
|
}
|
|
112
|
-
declare function serializeClass(node:
|
|
233
|
+
declare function serializeClass(node: ts5.ClassDeclaration, ctx: SerializerContext): SpecExport | null;
|
|
113
234
|
import { SpecExport as SpecExport2 } from "@openpkg-ts/spec";
|
|
114
|
-
import
|
|
115
|
-
declare function serializeEnum(node:
|
|
235
|
+
import ts6 from "typescript";
|
|
236
|
+
declare function serializeEnum(node: ts6.EnumDeclaration, ctx: SerializerContext): SpecExport2 | null;
|
|
116
237
|
import { SpecExport as SpecExport3 } from "@openpkg-ts/spec";
|
|
117
|
-
import
|
|
118
|
-
declare function serializeFunctionExport(node:
|
|
238
|
+
import ts7 from "typescript";
|
|
239
|
+
declare function serializeFunctionExport(node: ts7.FunctionDeclaration | ts7.ArrowFunction, ctx: SerializerContext): SpecExport3 | null;
|
|
119
240
|
import { SpecExport as SpecExport4 } from "@openpkg-ts/spec";
|
|
120
|
-
import
|
|
121
|
-
declare function serializeInterface(node:
|
|
241
|
+
import ts8 from "typescript";
|
|
242
|
+
declare function serializeInterface(node: ts8.InterfaceDeclaration, ctx: SerializerContext): SpecExport4 | null;
|
|
122
243
|
import { SpecExport as SpecExport5 } from "@openpkg-ts/spec";
|
|
123
|
-
import
|
|
124
|
-
declare function serializeTypeAlias(node:
|
|
244
|
+
import ts9 from "typescript";
|
|
245
|
+
declare function serializeTypeAlias(node: ts9.TypeAliasDeclaration, ctx: SerializerContext): SpecExport5 | null;
|
|
125
246
|
import { SpecExport as SpecExport6 } from "@openpkg-ts/spec";
|
|
126
|
-
import
|
|
127
|
-
declare function serializeVariable(node:
|
|
128
|
-
import ts13 from "typescript";
|
|
129
|
-
declare function formatTypeReference(type: ts13.Type, checker: ts13.TypeChecker): string;
|
|
130
|
-
declare function collectReferencedTypes(type: ts13.Type, checker: ts13.TypeChecker, visited?: Set<string>): string[];
|
|
247
|
+
import ts10 from "typescript";
|
|
248
|
+
declare function serializeVariable(node: ts10.VariableDeclaration, statement: ts10.VariableStatement, ctx: SerializerContext): SpecExport6 | null;
|
|
131
249
|
import { SpecSignatureParameter } from "@openpkg-ts/spec";
|
|
132
|
-
import
|
|
133
|
-
declare function extractParameters(signature:
|
|
250
|
+
import ts11 from "typescript";
|
|
251
|
+
declare function extractParameters(signature: ts11.Signature, ctx: SerializerContext): SpecSignatureParameter[];
|
|
134
252
|
/**
|
|
135
253
|
* Recursively register types referenced by a ts.Type.
|
|
136
254
|
* Uses ctx.visitedTypes to prevent infinite recursion on circular types.
|
|
137
255
|
*/
|
|
138
|
-
declare function registerReferencedTypes(type:
|
|
139
|
-
import { SpecSchema
|
|
140
|
-
import
|
|
256
|
+
declare function registerReferencedTypes(type: ts11.Type, ctx: SerializerContext): void;
|
|
257
|
+
import { SpecSchema } from "@openpkg-ts/spec";
|
|
258
|
+
import ts12 from "typescript";
|
|
259
|
+
/**
|
|
260
|
+
* Built-in type schemas with JSON Schema format hints.
|
|
261
|
+
* Used for types that have specific serialization formats.
|
|
262
|
+
*/
|
|
263
|
+
declare const BUILTIN_TYPE_SCHEMAS: Record<string, SpecSchema>;
|
|
141
264
|
/**
|
|
142
265
|
* Check if a name is a primitive type
|
|
143
266
|
*/
|
|
@@ -149,13 +272,41 @@ declare function isBuiltinGeneric(name: string): boolean;
|
|
|
149
272
|
/**
|
|
150
273
|
* Check if a type is anonymous (no meaningful symbol name)
|
|
151
274
|
*/
|
|
152
|
-
declare function isAnonymous(type:
|
|
275
|
+
declare function isAnonymous(type: ts12.Type): boolean;
|
|
153
276
|
/**
|
|
154
277
|
* Build a structured SpecSchema from a TypeScript type.
|
|
155
278
|
* Uses $ref for named types and typeArguments for generics.
|
|
156
279
|
*/
|
|
157
|
-
declare function buildSchema(type:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
280
|
+
declare function buildSchema(type: ts12.Type, checker: ts12.TypeChecker, ctx?: SerializerContext, _depth?: number): SpecSchema;
|
|
281
|
+
/**
|
|
282
|
+
* Check if a schema is a pure $ref (only has $ref property)
|
|
283
|
+
*/
|
|
284
|
+
declare function isPureRefSchema(schema: SpecSchema): schema is {
|
|
285
|
+
$ref: string;
|
|
286
|
+
};
|
|
287
|
+
/**
|
|
288
|
+
* Add description to a schema, handling $ref properly.
|
|
289
|
+
* For pure $ref schemas, wraps in allOf to preserve the reference.
|
|
290
|
+
*/
|
|
291
|
+
declare function withDescription(schema: SpecSchema, description: string): SpecSchema;
|
|
292
|
+
/**
|
|
293
|
+
* Check if a schema represents the 'any' type
|
|
294
|
+
*/
|
|
295
|
+
declare function schemaIsAny(schema: SpecSchema): boolean;
|
|
296
|
+
/**
|
|
297
|
+
* Deep equality comparison for schemas
|
|
298
|
+
*/
|
|
299
|
+
declare function schemasAreEqual(left: SpecSchema, right: SpecSchema): boolean;
|
|
300
|
+
/**
|
|
301
|
+
* Remove duplicate schemas from an array while preserving order.
|
|
302
|
+
*/
|
|
303
|
+
declare function deduplicateSchemas(schemas: SpecSchema[]): SpecSchema[];
|
|
304
|
+
/**
|
|
305
|
+
* Find a discriminator property in a union of object types (tagged union pattern).
|
|
306
|
+
* A valid discriminator has a unique literal value in each union member.
|
|
307
|
+
*/
|
|
308
|
+
declare function findDiscriminatorProperty(unionTypes: ts12.Type[], checker: ts12.TypeChecker): string | undefined;
|
|
309
|
+
import ts13 from "typescript";
|
|
310
|
+
declare function isExported(node: ts13.Node): boolean;
|
|
311
|
+
declare function getNodeName(node: ts13.Node): string | undefined;
|
|
312
|
+
export { zodAdapter, withDescription, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, schemasAreEqual, schemaIsAny, resolveCompiledPath, registerReferencedTypes, registerAdapter, isTypeReference, isSymbolDeprecated, isStandardJSONSchema, isSchemaType, isPureRefSchema, isPrimitiveName, isExported, isBuiltinGeneric, isAnonymous, getSourceLocation, getParamDescription, getNonNullableType, getNodeName, getJSDocComment, findDiscriminatorProperty, findAdapter, extractTypeParameters, extractStandardSchemasFromProject, extractStandardSchemas, extractSchemaType, extractParameters, extract, deduplicateSchemas, createProgram, buildSchema, arktypeAdapter, TypeRegistry, StandardSchemaExtractionResult, StandardSchemaExtractionOutput, StandardJSONSchemaV1, SerializerContext, SchemaExtractionResult, SchemaAdapter, ProgramResult, ProgramOptions, ExtractStandardSchemasOptions, ExtractResult, ExtractOptions, Diagnostic, BUILTIN_TYPE_SCHEMAS };
|
package/dist/src/index.js
CHANGED
|
@@ -1,78 +1,383 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BUILTIN_TYPE_SCHEMAS,
|
|
2
3
|
TypeRegistry,
|
|
3
4
|
buildSchema,
|
|
4
5
|
createProgram,
|
|
6
|
+
deduplicateSchemas,
|
|
5
7
|
extract,
|
|
6
8
|
extractParameters,
|
|
9
|
+
extractTypeParameters,
|
|
10
|
+
findDiscriminatorProperty,
|
|
7
11
|
getJSDocComment,
|
|
12
|
+
getParamDescription,
|
|
8
13
|
getSourceLocation,
|
|
9
14
|
isAnonymous,
|
|
10
15
|
isBuiltinGeneric,
|
|
11
16
|
isPrimitiveName,
|
|
17
|
+
isPureRefSchema,
|
|
18
|
+
isSymbolDeprecated,
|
|
12
19
|
registerReferencedTypes,
|
|
20
|
+
schemaIsAny,
|
|
21
|
+
schemasAreEqual,
|
|
13
22
|
serializeClass,
|
|
14
23
|
serializeEnum,
|
|
15
24
|
serializeFunctionExport,
|
|
16
25
|
serializeInterface,
|
|
17
26
|
serializeTypeAlias,
|
|
18
|
-
serializeVariable
|
|
19
|
-
|
|
27
|
+
serializeVariable,
|
|
28
|
+
withDescription
|
|
29
|
+
} from "../shared/chunk-v4cnenxs.js";
|
|
30
|
+
// src/schema/registry.ts
|
|
31
|
+
function isTypeReference(type) {
|
|
32
|
+
return !!(type.flags & 524288 && type.objectFlags && type.objectFlags & 4);
|
|
33
|
+
}
|
|
34
|
+
function getNonNullableType(type) {
|
|
35
|
+
if (type.isUnion()) {
|
|
36
|
+
const nonNullable = type.types.filter((t) => !(t.flags & 32768) && !(t.flags & 65536));
|
|
37
|
+
if (nonNullable.length === 1) {
|
|
38
|
+
return nonNullable[0];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return type;
|
|
42
|
+
}
|
|
43
|
+
var adapters = [];
|
|
44
|
+
function registerAdapter(adapter) {
|
|
45
|
+
adapters.push(adapter);
|
|
46
|
+
}
|
|
47
|
+
function findAdapter(type, checker) {
|
|
48
|
+
return adapters.find((a) => a.matches(type, checker));
|
|
49
|
+
}
|
|
50
|
+
function isSchemaType(type, checker) {
|
|
51
|
+
return adapters.some((a) => a.matches(type, checker));
|
|
52
|
+
}
|
|
53
|
+
function extractSchemaType(type, checker) {
|
|
54
|
+
const adapter = findAdapter(type, checker);
|
|
55
|
+
if (!adapter)
|
|
56
|
+
return null;
|
|
57
|
+
const outputType = adapter.extractOutputType(type, checker);
|
|
58
|
+
if (!outputType)
|
|
59
|
+
return null;
|
|
60
|
+
const inputType = adapter.extractInputType?.(type, checker);
|
|
61
|
+
return {
|
|
62
|
+
adapter,
|
|
63
|
+
outputType,
|
|
64
|
+
inputType
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
20
68
|
// src/schema/adapters/arktype.ts
|
|
69
|
+
var ARKTYPE_TYPE_PATTERN = /^Type</;
|
|
21
70
|
var arktypeAdapter = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
71
|
+
id: "arktype",
|
|
72
|
+
packages: ["arktype"],
|
|
73
|
+
matches(type, checker) {
|
|
74
|
+
const typeName = checker.typeToString(type);
|
|
75
|
+
return ARKTYPE_TYPE_PATTERN.test(typeName);
|
|
76
|
+
},
|
|
77
|
+
extractOutputType(type, checker) {
|
|
78
|
+
if (!isTypeReference(type)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const args = checker.getTypeArguments(type);
|
|
82
|
+
if (args.length < 1) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return args[0];
|
|
86
|
+
},
|
|
87
|
+
extractInputType(type, checker) {
|
|
88
|
+
if (!isTypeReference(type)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const args = checker.getTypeArguments(type);
|
|
92
|
+
if (args.length < 2) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return args[1];
|
|
96
|
+
}
|
|
25
97
|
};
|
|
98
|
+
|
|
26
99
|
// src/schema/adapters/typebox.ts
|
|
100
|
+
var TYPEBOX_TYPE_PATTERN = /^T[A-Z]/;
|
|
27
101
|
var typeboxAdapter = {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
102
|
+
id: "typebox",
|
|
103
|
+
packages: ["@sinclair/typebox"],
|
|
104
|
+
matches(type, checker) {
|
|
105
|
+
const typeName = checker.typeToString(type);
|
|
106
|
+
if (!TYPEBOX_TYPE_PATTERN.test(typeName)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const typeProperty = type.getProperty("type");
|
|
110
|
+
return typeProperty !== undefined;
|
|
111
|
+
},
|
|
112
|
+
extractOutputType(type, checker) {
|
|
113
|
+
const staticSymbol = type.getProperty("static");
|
|
114
|
+
if (staticSymbol) {
|
|
115
|
+
return checker.getTypeOfSymbol(staticSymbol);
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
31
119
|
};
|
|
120
|
+
|
|
32
121
|
// src/schema/adapters/valibot.ts
|
|
122
|
+
var VALIBOT_TYPE_PATTERN = /Schema(<|$)/;
|
|
33
123
|
var valibotAdapter = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
124
|
+
id: "valibot",
|
|
125
|
+
packages: ["valibot"],
|
|
126
|
+
matches(type, checker) {
|
|
127
|
+
const typeName = checker.typeToString(type);
|
|
128
|
+
return VALIBOT_TYPE_PATTERN.test(typeName) && !typeName.includes("Zod");
|
|
129
|
+
},
|
|
130
|
+
extractOutputType(type, checker) {
|
|
131
|
+
const typesSymbol = type.getProperty("~types");
|
|
132
|
+
if (!typesSymbol) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
let typesType = checker.getTypeOfSymbol(typesSymbol);
|
|
136
|
+
typesType = getNonNullableType(typesType);
|
|
137
|
+
const outputSymbol = typesType.getProperty("output");
|
|
138
|
+
if (!outputSymbol) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return checker.getTypeOfSymbol(outputSymbol);
|
|
142
|
+
},
|
|
143
|
+
extractInputType(type, checker) {
|
|
144
|
+
const typesSymbol = type.getProperty("~types");
|
|
145
|
+
if (!typesSymbol) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
let typesType = checker.getTypeOfSymbol(typesSymbol);
|
|
149
|
+
typesType = getNonNullableType(typesType);
|
|
150
|
+
const inputSymbol = typesType.getProperty("input");
|
|
151
|
+
if (!inputSymbol) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return checker.getTypeOfSymbol(inputSymbol);
|
|
155
|
+
}
|
|
37
156
|
};
|
|
157
|
+
|
|
38
158
|
// src/schema/adapters/zod.ts
|
|
159
|
+
var ZOD_TYPE_PATTERN = /^Zod[A-Z]/;
|
|
39
160
|
var zodAdapter = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
161
|
+
id: "zod",
|
|
162
|
+
packages: ["zod"],
|
|
163
|
+
matches(type, checker) {
|
|
164
|
+
const typeName = checker.typeToString(type);
|
|
165
|
+
return ZOD_TYPE_PATTERN.test(typeName);
|
|
166
|
+
},
|
|
167
|
+
extractOutputType(type, checker) {
|
|
168
|
+
const outputSymbol = type.getProperty("_output");
|
|
169
|
+
if (outputSymbol) {
|
|
170
|
+
return checker.getTypeOfSymbol(outputSymbol);
|
|
171
|
+
}
|
|
172
|
+
const typeSymbol = type.getProperty("_type");
|
|
173
|
+
if (typeSymbol) {
|
|
174
|
+
return checker.getTypeOfSymbol(typeSymbol);
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
},
|
|
178
|
+
extractInputType(type, checker) {
|
|
179
|
+
const inputSymbol = type.getProperty("_input");
|
|
180
|
+
if (inputSymbol) {
|
|
181
|
+
return checker.getTypeOfSymbol(inputSymbol);
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
43
185
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
186
|
+
|
|
187
|
+
// src/schema/adapters/index.ts
|
|
188
|
+
registerAdapter(zodAdapter);
|
|
189
|
+
registerAdapter(valibotAdapter);
|
|
190
|
+
registerAdapter(arktypeAdapter);
|
|
191
|
+
registerAdapter(typeboxAdapter);
|
|
192
|
+
// src/schema/standard-schema.ts
|
|
193
|
+
import { spawn } from "node:child_process";
|
|
194
|
+
import * as fs from "node:fs";
|
|
195
|
+
import * as path from "node:path";
|
|
196
|
+
function isStandardJSONSchema(obj) {
|
|
197
|
+
if (typeof obj !== "object" || obj === null)
|
|
198
|
+
return false;
|
|
199
|
+
const std = obj["~standard"];
|
|
200
|
+
if (typeof std !== "object" || std === null)
|
|
201
|
+
return false;
|
|
202
|
+
const stdObj = std;
|
|
203
|
+
if (typeof stdObj.version !== "number")
|
|
204
|
+
return false;
|
|
205
|
+
if (typeof stdObj.vendor !== "string")
|
|
206
|
+
return false;
|
|
207
|
+
const jsonSchema = stdObj.jsonSchema;
|
|
208
|
+
if (typeof jsonSchema !== "object" || jsonSchema === null)
|
|
209
|
+
return false;
|
|
210
|
+
const jsObj = jsonSchema;
|
|
211
|
+
return typeof jsObj.output === "function";
|
|
48
212
|
}
|
|
49
|
-
|
|
50
|
-
|
|
213
|
+
var WORKER_SCRIPT = `
|
|
214
|
+
const path = require('path');
|
|
215
|
+
const { pathToFileURL } = require('url');
|
|
216
|
+
|
|
217
|
+
// TypeBox detection: schemas have Symbol.for('TypeBox.Kind') and are JSON Schema
|
|
218
|
+
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind');
|
|
219
|
+
|
|
220
|
+
function isTypeBoxSchema(obj) {
|
|
221
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
222
|
+
// TypeBox schemas always have Kind symbol (Union, Object, String, etc.)
|
|
223
|
+
// Also check for common JSON Schema props to avoid false positives
|
|
224
|
+
if (!obj[TYPEBOX_KIND]) return false;
|
|
225
|
+
return typeof obj.type === 'string' || 'anyOf' in obj || 'oneOf' in obj || 'allOf' in obj;
|
|
51
226
|
}
|
|
52
|
-
|
|
53
|
-
|
|
227
|
+
|
|
228
|
+
function sanitizeTypeBoxSchema(schema) {
|
|
229
|
+
// JSON.stringify removes symbol keys, keeping only JSON Schema props
|
|
230
|
+
return JSON.parse(JSON.stringify(schema));
|
|
54
231
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
232
|
+
|
|
233
|
+
async function extract() {
|
|
234
|
+
// With node -e, argv is: [node, arg1, arg2, ...]
|
|
235
|
+
// (the -e script is NOT in argv)
|
|
236
|
+
const [modulePath, target] = process.argv.slice(1);
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
// Import the module using dynamic import (works with ESM and CJS)
|
|
240
|
+
const absPath = path.resolve(modulePath);
|
|
241
|
+
const mod = await import(pathToFileURL(absPath).href);
|
|
242
|
+
const results = [];
|
|
243
|
+
|
|
244
|
+
// Build exports map - handle both ESM and CJS (where exports are in mod.default)
|
|
245
|
+
const exports = {};
|
|
246
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
247
|
+
if (name === 'default' && typeof value === 'object' && value !== null) {
|
|
248
|
+
// CJS module: spread default exports
|
|
249
|
+
Object.assign(exports, value);
|
|
250
|
+
} else if (name !== 'default') {
|
|
251
|
+
exports[name] = value;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check each export
|
|
256
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
257
|
+
if (name.startsWith('_')) continue;
|
|
258
|
+
if (typeof value !== 'object' || value === null) continue;
|
|
259
|
+
|
|
260
|
+
// Priority 1: Standard Schema (Zod 4.2+, ArkType, etc.)
|
|
261
|
+
const std = value['~standard'];
|
|
262
|
+
if (std && typeof std === 'object' && typeof std.version === 'number' && typeof std.vendor === 'string' && std.jsonSchema && typeof std.jsonSchema.output === 'function') {
|
|
263
|
+
try {
|
|
264
|
+
const outputSchema = std.jsonSchema.output(target);
|
|
265
|
+
const inputSchema = std.jsonSchema.input ? std.jsonSchema.input(target) : undefined;
|
|
266
|
+
results.push({
|
|
267
|
+
exportName: name,
|
|
268
|
+
vendor: std.vendor,
|
|
269
|
+
outputSchema,
|
|
270
|
+
inputSchema
|
|
271
|
+
});
|
|
272
|
+
} catch (e) {
|
|
273
|
+
// Skip schemas that fail to extract
|
|
274
|
+
}
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Priority 2: TypeBox (schema IS JSON Schema)
|
|
279
|
+
if (isTypeBoxSchema(value)) {
|
|
280
|
+
try {
|
|
281
|
+
results.push({
|
|
282
|
+
exportName: name,
|
|
283
|
+
vendor: 'typebox',
|
|
284
|
+
outputSchema: sanitizeTypeBoxSchema(value)
|
|
285
|
+
});
|
|
286
|
+
} catch (e) {
|
|
287
|
+
// Skip schemas that fail to extract
|
|
288
|
+
}
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log(JSON.stringify({ success: true, results }));
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.log(JSON.stringify({ success: false, error: e.message }));
|
|
296
|
+
}
|
|
58
297
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
298
|
+
|
|
299
|
+
extract();
|
|
300
|
+
`;
|
|
301
|
+
function resolveCompiledPath(tsPath, baseDir) {
|
|
302
|
+
const relativePath = path.relative(baseDir, tsPath);
|
|
303
|
+
const withoutExt = relativePath.replace(/\.tsx?$/, "");
|
|
304
|
+
const candidates = [
|
|
305
|
+
path.join(baseDir, `${withoutExt}.js`),
|
|
306
|
+
path.join(baseDir, "dist", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
307
|
+
path.join(baseDir, "build", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
308
|
+
path.join(baseDir, "lib", `${withoutExt.replace(/^src\//, "")}.js`)
|
|
309
|
+
];
|
|
310
|
+
for (const candidate of candidates) {
|
|
311
|
+
if (fs.existsSync(candidate)) {
|
|
312
|
+
return candidate;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
62
316
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
317
|
+
async function extractStandardSchemas(compiledJsPath, options = {}) {
|
|
318
|
+
const { timeout = 1e4, target = "draft-2020-12" } = options;
|
|
319
|
+
const result = {
|
|
320
|
+
schemas: new Map,
|
|
321
|
+
errors: []
|
|
322
|
+
};
|
|
323
|
+
if (!fs.existsSync(compiledJsPath)) {
|
|
324
|
+
result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
return new Promise((resolve) => {
|
|
328
|
+
const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, target], {
|
|
329
|
+
timeout,
|
|
330
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
331
|
+
});
|
|
332
|
+
let stdout = "";
|
|
333
|
+
let stderr = "";
|
|
334
|
+
child.stdout.on("data", (data) => {
|
|
335
|
+
stdout += data.toString();
|
|
336
|
+
});
|
|
337
|
+
child.stderr.on("data", (data) => {
|
|
338
|
+
stderr += data.toString();
|
|
339
|
+
});
|
|
340
|
+
child.on("close", (code) => {
|
|
341
|
+
if (code !== 0) {
|
|
342
|
+
result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
|
|
343
|
+
resolve(result);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const parsed = JSON.parse(stdout);
|
|
348
|
+
if (!parsed.success) {
|
|
349
|
+
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
350
|
+
resolve(result);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
for (const item of parsed.results) {
|
|
354
|
+
result.schemas.set(item.exportName, {
|
|
355
|
+
exportName: item.exportName,
|
|
356
|
+
vendor: item.vendor,
|
|
357
|
+
outputSchema: item.outputSchema,
|
|
358
|
+
inputSchema: item.inputSchema
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
} catch (e) {
|
|
362
|
+
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
363
|
+
}
|
|
364
|
+
resolve(result);
|
|
365
|
+
});
|
|
366
|
+
child.on("error", (err) => {
|
|
367
|
+
result.errors.push(`Subprocess error: ${err.message}`);
|
|
368
|
+
resolve(result);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
66
371
|
}
|
|
67
|
-
function
|
|
68
|
-
const
|
|
69
|
-
if (!
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
372
|
+
async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
|
|
373
|
+
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
374
|
+
if (!compiledPath) {
|
|
375
|
+
return {
|
|
376
|
+
schemas: new Map,
|
|
377
|
+
errors: [`Could not find compiled JS for ${entryFile}. Build the project first.`]
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return extractStandardSchemas(compiledPath, options);
|
|
76
381
|
}
|
|
77
382
|
// src/types/utils.ts
|
|
78
383
|
function isExported(node) {
|
|
@@ -90,6 +395,7 @@ function getNodeName(node) {
|
|
|
90
395
|
}
|
|
91
396
|
export {
|
|
92
397
|
zodAdapter,
|
|
398
|
+
withDescription,
|
|
93
399
|
valibotAdapter,
|
|
94
400
|
typeboxAdapter,
|
|
95
401
|
serializeVariable,
|
|
@@ -98,25 +404,37 @@ export {
|
|
|
98
404
|
serializeFunctionExport,
|
|
99
405
|
serializeEnum,
|
|
100
406
|
serializeClass,
|
|
407
|
+
schemasAreEqual,
|
|
408
|
+
schemaIsAny,
|
|
409
|
+
resolveCompiledPath,
|
|
101
410
|
registerReferencedTypes,
|
|
102
411
|
registerAdapter,
|
|
412
|
+
isTypeReference,
|
|
413
|
+
isSymbolDeprecated,
|
|
414
|
+
isStandardJSONSchema,
|
|
103
415
|
isSchemaType,
|
|
416
|
+
isPureRefSchema,
|
|
104
417
|
isPrimitiveName,
|
|
105
418
|
isExported,
|
|
106
419
|
isBuiltinGeneric,
|
|
107
420
|
isAnonymous,
|
|
108
421
|
getSourceLocation,
|
|
422
|
+
getParamDescription,
|
|
423
|
+
getNonNullableType,
|
|
109
424
|
getNodeName,
|
|
110
425
|
getJSDocComment,
|
|
111
|
-
|
|
426
|
+
findDiscriminatorProperty,
|
|
112
427
|
findAdapter,
|
|
428
|
+
extractTypeParameters,
|
|
429
|
+
extractStandardSchemasFromProject,
|
|
113
430
|
extractStandardSchemas,
|
|
114
431
|
extractSchemaType,
|
|
115
432
|
extractParameters,
|
|
116
433
|
extract,
|
|
434
|
+
deduplicateSchemas,
|
|
117
435
|
createProgram,
|
|
118
|
-
collectReferencedTypes,
|
|
119
436
|
buildSchema,
|
|
120
437
|
arktypeAdapter,
|
|
121
|
-
TypeRegistry
|
|
438
|
+
TypeRegistry,
|
|
439
|
+
BUILTIN_TYPE_SCHEMAS
|
|
122
440
|
};
|