postgresdk 0.9.0 → 0.9.2
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/cli.js +201 -184
- package/dist/emit-sdk-contract.d.ts +3 -2
- package/dist/index.js +201 -184
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -486,6 +486,157 @@ async function ensureDirs(dirs) {
|
|
|
486
486
|
var pascal = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
|
|
487
487
|
var init_utils = () => {};
|
|
488
488
|
|
|
489
|
+
// src/emit-include-methods.ts
|
|
490
|
+
function isJunctionTable(table) {
|
|
491
|
+
if (!table.name.includes("_"))
|
|
492
|
+
return false;
|
|
493
|
+
const fkColumns = new Set(table.fks.flatMap((fk) => fk.from));
|
|
494
|
+
const nonPkColumns = table.columns.filter((c) => !table.pk.includes(c.name));
|
|
495
|
+
return nonPkColumns.every((c) => fkColumns.has(c.name));
|
|
496
|
+
}
|
|
497
|
+
function pathToMethodSuffix(path) {
|
|
498
|
+
return "With" + path.map((p) => pascal(p)).join("And");
|
|
499
|
+
}
|
|
500
|
+
function buildReturnType(baseTable, path, isMany, targets, graph) {
|
|
501
|
+
const BaseType = `Select${pascal(baseTable)}`;
|
|
502
|
+
if (path.length === 0)
|
|
503
|
+
return BaseType;
|
|
504
|
+
let type = BaseType;
|
|
505
|
+
let currentTable = baseTable;
|
|
506
|
+
const parts = [];
|
|
507
|
+
for (let i = 0;i < path.length; i++) {
|
|
508
|
+
const key = path[i];
|
|
509
|
+
const target = targets[i];
|
|
510
|
+
if (!key || !target)
|
|
511
|
+
continue;
|
|
512
|
+
const targetType = `Select${pascal(target)}`;
|
|
513
|
+
if (i === 0) {
|
|
514
|
+
parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : targetType}`);
|
|
515
|
+
} else {
|
|
516
|
+
let nestedType = targetType;
|
|
517
|
+
for (let j = i;j < path.length; j++) {
|
|
518
|
+
if (j > i) {
|
|
519
|
+
const nestedKey = path[j];
|
|
520
|
+
const nestedTarget = targets[j];
|
|
521
|
+
if (!nestedKey || !nestedTarget)
|
|
522
|
+
continue;
|
|
523
|
+
const nestedTargetType = `Select${pascal(nestedTarget)}`;
|
|
524
|
+
nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : nestedTargetType} }`;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const prevKey = path[i - 1];
|
|
528
|
+
const prevTarget = targets[i - 1];
|
|
529
|
+
if (prevKey && prevTarget) {
|
|
530
|
+
parts[parts.length - 1] = `${prevKey}: ${isMany[i - 1] ? `(Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} })[]` : `Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} }`}`;
|
|
531
|
+
}
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return `${type} & { ${parts.join("; ")} }`;
|
|
536
|
+
}
|
|
537
|
+
function buildIncludeSpec(path) {
|
|
538
|
+
if (path.length === 0)
|
|
539
|
+
return {};
|
|
540
|
+
if (path.length === 1)
|
|
541
|
+
return { [path[0]]: true };
|
|
542
|
+
let spec = true;
|
|
543
|
+
for (let i = path.length - 1;i > 0; i--) {
|
|
544
|
+
const key = path[i];
|
|
545
|
+
if (!key)
|
|
546
|
+
continue;
|
|
547
|
+
spec = { [key]: spec };
|
|
548
|
+
}
|
|
549
|
+
const rootKey = path[0];
|
|
550
|
+
return rootKey ? { [rootKey]: spec } : {};
|
|
551
|
+
}
|
|
552
|
+
function generateIncludeMethods(table, graph, opts, allTables) {
|
|
553
|
+
const methods = [];
|
|
554
|
+
const baseTableName = table.name;
|
|
555
|
+
if (opts.skipJunctionTables && isJunctionTable(table)) {
|
|
556
|
+
return methods;
|
|
557
|
+
}
|
|
558
|
+
const edges = graph[baseTableName] || {};
|
|
559
|
+
function explore(currentTable, path, isMany, targets, visited, depth) {
|
|
560
|
+
if (depth > opts.maxDepth)
|
|
561
|
+
return;
|
|
562
|
+
const currentEdges = graph[currentTable] || {};
|
|
563
|
+
for (const [key, edge] of Object.entries(currentEdges)) {
|
|
564
|
+
if (visited.has(edge.target))
|
|
565
|
+
continue;
|
|
566
|
+
if (opts.skipJunctionTables && allTables) {
|
|
567
|
+
const targetTable = allTables.find((t) => t.name === edge.target);
|
|
568
|
+
if (targetTable && isJunctionTable(targetTable)) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const newPath = [...path, key];
|
|
573
|
+
const newIsMany = [...isMany, edge.kind === "many"];
|
|
574
|
+
const newTargets = [...targets, edge.target];
|
|
575
|
+
const methodSuffix = pathToMethodSuffix(newPath);
|
|
576
|
+
methods.push({
|
|
577
|
+
name: `list${methodSuffix}`,
|
|
578
|
+
path: newPath,
|
|
579
|
+
isMany: newIsMany,
|
|
580
|
+
targets: newTargets,
|
|
581
|
+
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
582
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
583
|
+
});
|
|
584
|
+
methods.push({
|
|
585
|
+
name: `getByPk${methodSuffix}`,
|
|
586
|
+
path: newPath,
|
|
587
|
+
isMany: newIsMany,
|
|
588
|
+
targets: newTargets,
|
|
589
|
+
returnType: `${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)} | null`,
|
|
590
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
591
|
+
});
|
|
592
|
+
explore(edge.target, newPath, newIsMany, newTargets, new Set([...visited, edge.target]), depth + 1);
|
|
593
|
+
}
|
|
594
|
+
if (depth === 1 && Object.keys(currentEdges).length > 1 && Object.keys(currentEdges).length <= 3) {
|
|
595
|
+
const edgeEntries = Object.entries(currentEdges);
|
|
596
|
+
if (edgeEntries.length >= 2) {
|
|
597
|
+
for (let i = 0;i < edgeEntries.length - 1; i++) {
|
|
598
|
+
for (let j = i + 1;j < edgeEntries.length; j++) {
|
|
599
|
+
const entry1 = edgeEntries[i];
|
|
600
|
+
const entry2 = edgeEntries[j];
|
|
601
|
+
if (!entry1 || !entry2)
|
|
602
|
+
continue;
|
|
603
|
+
const [key1, edge1] = entry1;
|
|
604
|
+
const [key2, edge2] = entry2;
|
|
605
|
+
if (opts.skipJunctionTables && (edge1.target.includes("_") || edge2.target.includes("_"))) {
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const combinedPath = [key1, key2];
|
|
609
|
+
const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
|
|
610
|
+
const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
|
|
611
|
+
const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
|
|
612
|
+
methods.push({
|
|
613
|
+
name: `list${combinedSuffix}`,
|
|
614
|
+
path: combinedPath,
|
|
615
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
616
|
+
targets: [edge1.target, edge2.target],
|
|
617
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
618
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
619
|
+
});
|
|
620
|
+
methods.push({
|
|
621
|
+
name: `getByPk${combinedSuffix}`,
|
|
622
|
+
path: combinedPath,
|
|
623
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
624
|
+
targets: [edge1.target, edge2.target],
|
|
625
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} }) | null`,
|
|
626
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
explore(baseTableName, [], [], [], new Set([baseTableName]), 1);
|
|
634
|
+
return methods;
|
|
635
|
+
}
|
|
636
|
+
var init_emit_include_methods = __esm(() => {
|
|
637
|
+
init_utils();
|
|
638
|
+
});
|
|
639
|
+
|
|
489
640
|
// src/emit-sdk-contract.ts
|
|
490
641
|
var exports_emit_sdk_contract = {};
|
|
491
642
|
__export(exports_emit_sdk_contract, {
|
|
@@ -493,7 +644,7 @@ __export(exports_emit_sdk_contract, {
|
|
|
493
644
|
generateUnifiedContract: () => generateUnifiedContract,
|
|
494
645
|
emitUnifiedContract: () => emitUnifiedContract
|
|
495
646
|
});
|
|
496
|
-
function generateUnifiedContract(model, config) {
|
|
647
|
+
function generateUnifiedContract(model, config, graph) {
|
|
497
648
|
const resources = [];
|
|
498
649
|
const relationships = [];
|
|
499
650
|
const tables = model && model.tables ? Object.values(model.tables) : [];
|
|
@@ -501,7 +652,7 @@ function generateUnifiedContract(model, config) {
|
|
|
501
652
|
console.log(`[SDK Contract] Processing ${tables.length} tables`);
|
|
502
653
|
}
|
|
503
654
|
for (const table of tables) {
|
|
504
|
-
resources.push(generateResourceWithSDK(table, model));
|
|
655
|
+
resources.push(generateResourceWithSDK(table, model, graph, config));
|
|
505
656
|
for (const fk of table.fks) {
|
|
506
657
|
relationships.push({
|
|
507
658
|
from: table.name,
|
|
@@ -619,7 +770,7 @@ function generateSDKAuthExamples(auth) {
|
|
|
619
770
|
});
|
|
620
771
|
return examples;
|
|
621
772
|
}
|
|
622
|
-
function generateResourceWithSDK(table, model) {
|
|
773
|
+
function generateResourceWithSDK(table, model, graph, config) {
|
|
623
774
|
const Type = pascal(table.name);
|
|
624
775
|
const tableName = table.name;
|
|
625
776
|
const basePath = `/v1/${tableName}`;
|
|
@@ -641,11 +792,6 @@ const filtered = await sdk.${tableName}.list({
|
|
|
641
792
|
${table.columns[0]?.name || "field"}_like: 'search',
|
|
642
793
|
order_by: '${table.columns[0]?.name || "created_at"}',
|
|
643
794
|
order_dir: 'desc'
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
// With related data
|
|
647
|
-
const withRelations = await sdk.${tableName}.list({
|
|
648
|
-
include: '${table.fks[0]?.toTable || "related_table"}'
|
|
649
795
|
});`,
|
|
650
796
|
correspondsTo: `GET ${basePath}`
|
|
651
797
|
});
|
|
@@ -659,16 +805,11 @@ const withRelations = await sdk.${tableName}.list({
|
|
|
659
805
|
if (hasSinglePK) {
|
|
660
806
|
sdkMethods.push({
|
|
661
807
|
name: "getByPk",
|
|
662
|
-
signature: `getByPk(${pkField}: string
|
|
808
|
+
signature: `getByPk(${pkField}: string): Promise<${Type} | null>`,
|
|
663
809
|
description: `Get a single ${tableName} by primary key`,
|
|
664
810
|
example: `// Get by ID
|
|
665
811
|
const item = await sdk.${tableName}.getByPk('123e4567-e89b-12d3-a456-426614174000');
|
|
666
812
|
|
|
667
|
-
// With related data
|
|
668
|
-
const withRelations = await sdk.${tableName}.getByPk('123', {
|
|
669
|
-
include: '${table.fks[0]?.toTable || "related_table"}'
|
|
670
|
-
});
|
|
671
|
-
|
|
672
813
|
// Check if exists
|
|
673
814
|
if (item === null) {
|
|
674
815
|
console.log('Not found');
|
|
@@ -679,9 +820,6 @@ if (item === null) {
|
|
|
679
820
|
method: "GET",
|
|
680
821
|
path: `${basePath}/:${pkField}`,
|
|
681
822
|
description: `Get ${tableName} by ID`,
|
|
682
|
-
queryParameters: {
|
|
683
|
-
include: "string - Comma-separated list of related resources"
|
|
684
|
-
},
|
|
685
823
|
responseBody: `${Type}`
|
|
686
824
|
});
|
|
687
825
|
}
|
|
@@ -744,6 +882,31 @@ console.log('Deleted:', deleted);`,
|
|
|
744
882
|
responseBody: `${Type}`
|
|
745
883
|
});
|
|
746
884
|
}
|
|
885
|
+
if (graph && config) {
|
|
886
|
+
const allTables = model && model.tables ? Object.values(model.tables) : undefined;
|
|
887
|
+
const includeMethods = generateIncludeMethods(table, graph, {
|
|
888
|
+
maxDepth: config.includeMethodsDepth ?? 2,
|
|
889
|
+
skipJunctionTables: config.skipJunctionTables ?? true
|
|
890
|
+
}, allTables);
|
|
891
|
+
for (const method of includeMethods) {
|
|
892
|
+
const isGetByPk = method.name.startsWith("getByPk");
|
|
893
|
+
const exampleCall = isGetByPk ? `const result = await sdk.${tableName}.${method.name}('123e4567-e89b-12d3-a456-426614174000');` : `const results = await sdk.${tableName}.${method.name}();
|
|
894
|
+
|
|
895
|
+
// With filters and pagination
|
|
896
|
+
const filtered = await sdk.${tableName}.${method.name}({
|
|
897
|
+
limit: 20,
|
|
898
|
+
offset: 0,
|
|
899
|
+
where: { /* filter conditions */ }
|
|
900
|
+
});`;
|
|
901
|
+
sdkMethods.push({
|
|
902
|
+
name: method.name,
|
|
903
|
+
signature: `${method.name}(${isGetByPk ? `${pkField}: string` : "params?: ListParams"}): ${method.returnType}`,
|
|
904
|
+
description: `Get ${tableName} with included ${method.path.join(", ")} data`,
|
|
905
|
+
example: exampleCall,
|
|
906
|
+
correspondsTo: `POST ${basePath}/list`
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
}
|
|
747
910
|
const fields = table.columns.map((col) => generateFieldContract(col, table));
|
|
748
911
|
return {
|
|
749
912
|
name: Type,
|
|
@@ -892,8 +1055,7 @@ function generateQueryParams(table) {
|
|
|
892
1055
|
limit: "number - Max records to return (default: 50)",
|
|
893
1056
|
offset: "number - Records to skip",
|
|
894
1057
|
order_by: "string - Field to sort by",
|
|
895
|
-
order_dir: "'asc' | 'desc' - Sort direction"
|
|
896
|
-
include: "string - Related resources to include"
|
|
1058
|
+
order_dir: "'asc' | 'desc' - Sort direction"
|
|
897
1059
|
};
|
|
898
1060
|
let filterCount = 0;
|
|
899
1061
|
for (const col of table.columns) {
|
|
@@ -1080,8 +1242,8 @@ function generateUnifiedContractMarkdown(contract) {
|
|
|
1080
1242
|
return lines.join(`
|
|
1081
1243
|
`);
|
|
1082
1244
|
}
|
|
1083
|
-
function emitUnifiedContract(model, config) {
|
|
1084
|
-
const contract = generateUnifiedContract(model, config);
|
|
1245
|
+
function emitUnifiedContract(model, config, graph) {
|
|
1246
|
+
const contract = generateUnifiedContract(model, config, graph);
|
|
1085
1247
|
const contractJson = JSON.stringify(contract, null, 2);
|
|
1086
1248
|
return `/**
|
|
1087
1249
|
* Unified API & SDK Contract
|
|
@@ -1137,6 +1299,7 @@ import type * as Types from './client/types';
|
|
|
1137
1299
|
}
|
|
1138
1300
|
var init_emit_sdk_contract = __esm(() => {
|
|
1139
1301
|
init_utils();
|
|
1302
|
+
init_emit_include_methods();
|
|
1140
1303
|
});
|
|
1141
1304
|
|
|
1142
1305
|
// src/cli-init.ts
|
|
@@ -1914,157 +2077,7 @@ ${hasAuth ? `
|
|
|
1914
2077
|
|
|
1915
2078
|
// src/emit-client.ts
|
|
1916
2079
|
init_utils();
|
|
1917
|
-
|
|
1918
|
-
// src/emit-include-methods.ts
|
|
1919
|
-
init_utils();
|
|
1920
|
-
function isJunctionTable(table) {
|
|
1921
|
-
if (!table.name.includes("_"))
|
|
1922
|
-
return false;
|
|
1923
|
-
const fkColumns = new Set(table.fks.flatMap((fk) => fk.from));
|
|
1924
|
-
const nonPkColumns = table.columns.filter((c) => !table.pk.includes(c.name));
|
|
1925
|
-
return nonPkColumns.every((c) => fkColumns.has(c.name));
|
|
1926
|
-
}
|
|
1927
|
-
function pathToMethodSuffix(path) {
|
|
1928
|
-
return "With" + path.map((p) => pascal(p)).join("And");
|
|
1929
|
-
}
|
|
1930
|
-
function buildReturnType(baseTable, path, isMany, targets, graph) {
|
|
1931
|
-
const BaseType = `Select${pascal(baseTable)}`;
|
|
1932
|
-
if (path.length === 0)
|
|
1933
|
-
return BaseType;
|
|
1934
|
-
let type = BaseType;
|
|
1935
|
-
let currentTable = baseTable;
|
|
1936
|
-
const parts = [];
|
|
1937
|
-
for (let i = 0;i < path.length; i++) {
|
|
1938
|
-
const key = path[i];
|
|
1939
|
-
const target = targets[i];
|
|
1940
|
-
if (!key || !target)
|
|
1941
|
-
continue;
|
|
1942
|
-
const targetType = `Select${pascal(target)}`;
|
|
1943
|
-
if (i === 0) {
|
|
1944
|
-
parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : targetType}`);
|
|
1945
|
-
} else {
|
|
1946
|
-
let nestedType = targetType;
|
|
1947
|
-
for (let j = i;j < path.length; j++) {
|
|
1948
|
-
if (j > i) {
|
|
1949
|
-
const nestedKey = path[j];
|
|
1950
|
-
const nestedTarget = targets[j];
|
|
1951
|
-
if (!nestedKey || !nestedTarget)
|
|
1952
|
-
continue;
|
|
1953
|
-
const nestedTargetType = `Select${pascal(nestedTarget)}`;
|
|
1954
|
-
nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : nestedTargetType} }`;
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
const prevKey = path[i - 1];
|
|
1958
|
-
const prevTarget = targets[i - 1];
|
|
1959
|
-
if (prevKey && prevTarget) {
|
|
1960
|
-
parts[parts.length - 1] = `${prevKey}: ${isMany[i - 1] ? `(Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} })[]` : `Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} }`}`;
|
|
1961
|
-
}
|
|
1962
|
-
break;
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
return `${type} & { ${parts.join("; ")} }`;
|
|
1966
|
-
}
|
|
1967
|
-
function buildIncludeSpec(path) {
|
|
1968
|
-
if (path.length === 0)
|
|
1969
|
-
return {};
|
|
1970
|
-
if (path.length === 1)
|
|
1971
|
-
return { [path[0]]: true };
|
|
1972
|
-
let spec = true;
|
|
1973
|
-
for (let i = path.length - 1;i > 0; i--) {
|
|
1974
|
-
const key = path[i];
|
|
1975
|
-
if (!key)
|
|
1976
|
-
continue;
|
|
1977
|
-
spec = { [key]: spec };
|
|
1978
|
-
}
|
|
1979
|
-
const rootKey = path[0];
|
|
1980
|
-
return rootKey ? { [rootKey]: spec } : {};
|
|
1981
|
-
}
|
|
1982
|
-
function generateIncludeMethods(table, graph, opts, allTables) {
|
|
1983
|
-
const methods = [];
|
|
1984
|
-
const baseTableName = table.name;
|
|
1985
|
-
if (opts.skipJunctionTables && isJunctionTable(table)) {
|
|
1986
|
-
return methods;
|
|
1987
|
-
}
|
|
1988
|
-
const edges = graph[baseTableName] || {};
|
|
1989
|
-
function explore(currentTable, path, isMany, targets, visited, depth) {
|
|
1990
|
-
if (depth > opts.maxDepth)
|
|
1991
|
-
return;
|
|
1992
|
-
const currentEdges = graph[currentTable] || {};
|
|
1993
|
-
for (const [key, edge] of Object.entries(currentEdges)) {
|
|
1994
|
-
if (visited.has(edge.target))
|
|
1995
|
-
continue;
|
|
1996
|
-
if (opts.skipJunctionTables && allTables) {
|
|
1997
|
-
const targetTable = allTables.find((t) => t.name === edge.target);
|
|
1998
|
-
if (targetTable && isJunctionTable(targetTable)) {
|
|
1999
|
-
continue;
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
const newPath = [...path, key];
|
|
2003
|
-
const newIsMany = [...isMany, edge.kind === "many"];
|
|
2004
|
-
const newTargets = [...targets, edge.target];
|
|
2005
|
-
const methodSuffix = pathToMethodSuffix(newPath);
|
|
2006
|
-
methods.push({
|
|
2007
|
-
name: `list${methodSuffix}`,
|
|
2008
|
-
path: newPath,
|
|
2009
|
-
isMany: newIsMany,
|
|
2010
|
-
targets: newTargets,
|
|
2011
|
-
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
2012
|
-
includeSpec: buildIncludeSpec(newPath)
|
|
2013
|
-
});
|
|
2014
|
-
methods.push({
|
|
2015
|
-
name: `getByPk${methodSuffix}`,
|
|
2016
|
-
path: newPath,
|
|
2017
|
-
isMany: newIsMany,
|
|
2018
|
-
targets: newTargets,
|
|
2019
|
-
returnType: `${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)} | null`,
|
|
2020
|
-
includeSpec: buildIncludeSpec(newPath)
|
|
2021
|
-
});
|
|
2022
|
-
explore(edge.target, newPath, newIsMany, newTargets, new Set([...visited, edge.target]), depth + 1);
|
|
2023
|
-
}
|
|
2024
|
-
if (depth === 1 && Object.keys(currentEdges).length > 1 && Object.keys(currentEdges).length <= 3) {
|
|
2025
|
-
const edgeEntries = Object.entries(currentEdges);
|
|
2026
|
-
if (edgeEntries.length >= 2) {
|
|
2027
|
-
for (let i = 0;i < edgeEntries.length - 1; i++) {
|
|
2028
|
-
for (let j = i + 1;j < edgeEntries.length; j++) {
|
|
2029
|
-
const entry1 = edgeEntries[i];
|
|
2030
|
-
const entry2 = edgeEntries[j];
|
|
2031
|
-
if (!entry1 || !entry2)
|
|
2032
|
-
continue;
|
|
2033
|
-
const [key1, edge1] = entry1;
|
|
2034
|
-
const [key2, edge2] = entry2;
|
|
2035
|
-
if (opts.skipJunctionTables && (edge1.target.includes("_") || edge2.target.includes("_"))) {
|
|
2036
|
-
continue;
|
|
2037
|
-
}
|
|
2038
|
-
const combinedPath = [key1, key2];
|
|
2039
|
-
const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
|
|
2040
|
-
const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
|
|
2041
|
-
const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
|
|
2042
|
-
methods.push({
|
|
2043
|
-
name: `list${combinedSuffix}`,
|
|
2044
|
-
path: combinedPath,
|
|
2045
|
-
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
2046
|
-
targets: [edge1.target, edge2.target],
|
|
2047
|
-
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
2048
|
-
includeSpec: { [key1]: true, [key2]: true }
|
|
2049
|
-
});
|
|
2050
|
-
methods.push({
|
|
2051
|
-
name: `getByPk${combinedSuffix}`,
|
|
2052
|
-
path: combinedPath,
|
|
2053
|
-
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
2054
|
-
targets: [edge1.target, edge2.target],
|
|
2055
|
-
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} }) | null`,
|
|
2056
|
-
includeSpec: { [key1]: true, [key2]: true }
|
|
2057
|
-
});
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
explore(baseTableName, [], [], [], new Set([baseTableName]), 1);
|
|
2064
|
-
return methods;
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
// src/emit-client.ts
|
|
2080
|
+
init_emit_include_methods();
|
|
2068
2081
|
function emitClient(table, graph, opts, model) {
|
|
2069
2082
|
const Type = pascal(table.name);
|
|
2070
2083
|
const ext = opts.useJsExtensions ? ".js" : "";
|
|
@@ -4146,26 +4159,30 @@ async function generate(configPath) {
|
|
|
4146
4159
|
content: emitHonoRouter(Object.values(model.tables), !!normalizedAuth?.strategy && normalizedAuth.strategy !== "none", cfg.useJsExtensions)
|
|
4147
4160
|
});
|
|
4148
4161
|
}
|
|
4149
|
-
const
|
|
4150
|
-
|
|
4162
|
+
const { generateUnifiedContract: generateUnifiedContract2, generateUnifiedContractMarkdown: generateUnifiedContractMarkdown2 } = await Promise.resolve().then(() => (init_emit_sdk_contract(), exports_emit_sdk_contract));
|
|
4163
|
+
if (process.env.SDK_DEBUG) {
|
|
4164
|
+
console.log(`[Index] Model has ${Object.keys(model.tables || {}).length} tables before contract generation`);
|
|
4165
|
+
}
|
|
4166
|
+
const contract = generateUnifiedContract2(model, cfg, graph);
|
|
4167
|
+
files.push({
|
|
4168
|
+
path: join(serverDir, "CONTRACT.md"),
|
|
4169
|
+
content: generateUnifiedContractMarkdown2(contract)
|
|
4151
4170
|
});
|
|
4152
4171
|
files.push({
|
|
4153
|
-
path: join(
|
|
4154
|
-
content:
|
|
4172
|
+
path: join(clientDir, "CONTRACT.md"),
|
|
4173
|
+
content: generateUnifiedContractMarkdown2(contract)
|
|
4155
4174
|
});
|
|
4156
|
-
const contractCode = emitUnifiedContract(model, cfg);
|
|
4175
|
+
const contractCode = emitUnifiedContract(model, cfg, graph);
|
|
4157
4176
|
files.push({
|
|
4158
4177
|
path: join(serverDir, "contract.ts"),
|
|
4159
4178
|
content: contractCode
|
|
4160
4179
|
});
|
|
4161
|
-
const
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
}
|
|
4165
|
-
const contract = generateUnifiedContract2(model, cfg);
|
|
4180
|
+
const clientFiles = files.filter((f) => {
|
|
4181
|
+
return f.path.includes(clientDir);
|
|
4182
|
+
});
|
|
4166
4183
|
files.push({
|
|
4167
|
-
path: join(serverDir, "
|
|
4168
|
-
content:
|
|
4184
|
+
path: join(serverDir, "sdk-bundle.ts"),
|
|
4185
|
+
content: emitSdkBundle(clientFiles, clientDir)
|
|
4169
4186
|
});
|
|
4170
4187
|
if (generateTests) {
|
|
4171
4188
|
console.log("\uD83E\uDDEA Generating tests...");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Model } from "./introspect";
|
|
2
2
|
import type { Config, AuthConfig } from "./types";
|
|
3
|
+
import type { Graph } from "./rel-classify";
|
|
3
4
|
export interface UnifiedContract {
|
|
4
5
|
version: string;
|
|
5
6
|
generatedAt: string;
|
|
@@ -70,7 +71,7 @@ export interface RelationshipContract {
|
|
|
70
71
|
*/
|
|
71
72
|
export declare function generateUnifiedContract(model: Model, config: Config & {
|
|
72
73
|
auth?: AuthConfig;
|
|
73
|
-
}): UnifiedContract;
|
|
74
|
+
}, graph?: Graph): UnifiedContract;
|
|
74
75
|
/**
|
|
75
76
|
* Generate markdown documentation for the unified contract
|
|
76
77
|
*/
|
|
@@ -80,4 +81,4 @@ export declare function generateUnifiedContractMarkdown(contract: UnifiedContrac
|
|
|
80
81
|
*/
|
|
81
82
|
export declare function emitUnifiedContract(model: Model, config: Config & {
|
|
82
83
|
auth?: AuthConfig;
|
|
83
|
-
}): string;
|
|
84
|
+
}, graph?: Graph): string;
|
package/dist/index.js
CHANGED
|
@@ -485,6 +485,157 @@ async function ensureDirs(dirs) {
|
|
|
485
485
|
var pascal = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
|
|
486
486
|
var init_utils = () => {};
|
|
487
487
|
|
|
488
|
+
// src/emit-include-methods.ts
|
|
489
|
+
function isJunctionTable(table) {
|
|
490
|
+
if (!table.name.includes("_"))
|
|
491
|
+
return false;
|
|
492
|
+
const fkColumns = new Set(table.fks.flatMap((fk) => fk.from));
|
|
493
|
+
const nonPkColumns = table.columns.filter((c) => !table.pk.includes(c.name));
|
|
494
|
+
return nonPkColumns.every((c) => fkColumns.has(c.name));
|
|
495
|
+
}
|
|
496
|
+
function pathToMethodSuffix(path) {
|
|
497
|
+
return "With" + path.map((p) => pascal(p)).join("And");
|
|
498
|
+
}
|
|
499
|
+
function buildReturnType(baseTable, path, isMany, targets, graph) {
|
|
500
|
+
const BaseType = `Select${pascal(baseTable)}`;
|
|
501
|
+
if (path.length === 0)
|
|
502
|
+
return BaseType;
|
|
503
|
+
let type = BaseType;
|
|
504
|
+
let currentTable = baseTable;
|
|
505
|
+
const parts = [];
|
|
506
|
+
for (let i = 0;i < path.length; i++) {
|
|
507
|
+
const key = path[i];
|
|
508
|
+
const target = targets[i];
|
|
509
|
+
if (!key || !target)
|
|
510
|
+
continue;
|
|
511
|
+
const targetType = `Select${pascal(target)}`;
|
|
512
|
+
if (i === 0) {
|
|
513
|
+
parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : targetType}`);
|
|
514
|
+
} else {
|
|
515
|
+
let nestedType = targetType;
|
|
516
|
+
for (let j = i;j < path.length; j++) {
|
|
517
|
+
if (j > i) {
|
|
518
|
+
const nestedKey = path[j];
|
|
519
|
+
const nestedTarget = targets[j];
|
|
520
|
+
if (!nestedKey || !nestedTarget)
|
|
521
|
+
continue;
|
|
522
|
+
const nestedTargetType = `Select${pascal(nestedTarget)}`;
|
|
523
|
+
nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : nestedTargetType} }`;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const prevKey = path[i - 1];
|
|
527
|
+
const prevTarget = targets[i - 1];
|
|
528
|
+
if (prevKey && prevTarget) {
|
|
529
|
+
parts[parts.length - 1] = `${prevKey}: ${isMany[i - 1] ? `(Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} })[]` : `Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} }`}`;
|
|
530
|
+
}
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return `${type} & { ${parts.join("; ")} }`;
|
|
535
|
+
}
|
|
536
|
+
function buildIncludeSpec(path) {
|
|
537
|
+
if (path.length === 0)
|
|
538
|
+
return {};
|
|
539
|
+
if (path.length === 1)
|
|
540
|
+
return { [path[0]]: true };
|
|
541
|
+
let spec = true;
|
|
542
|
+
for (let i = path.length - 1;i > 0; i--) {
|
|
543
|
+
const key = path[i];
|
|
544
|
+
if (!key)
|
|
545
|
+
continue;
|
|
546
|
+
spec = { [key]: spec };
|
|
547
|
+
}
|
|
548
|
+
const rootKey = path[0];
|
|
549
|
+
return rootKey ? { [rootKey]: spec } : {};
|
|
550
|
+
}
|
|
551
|
+
function generateIncludeMethods(table, graph, opts, allTables) {
|
|
552
|
+
const methods = [];
|
|
553
|
+
const baseTableName = table.name;
|
|
554
|
+
if (opts.skipJunctionTables && isJunctionTable(table)) {
|
|
555
|
+
return methods;
|
|
556
|
+
}
|
|
557
|
+
const edges = graph[baseTableName] || {};
|
|
558
|
+
function explore(currentTable, path, isMany, targets, visited, depth) {
|
|
559
|
+
if (depth > opts.maxDepth)
|
|
560
|
+
return;
|
|
561
|
+
const currentEdges = graph[currentTable] || {};
|
|
562
|
+
for (const [key, edge] of Object.entries(currentEdges)) {
|
|
563
|
+
if (visited.has(edge.target))
|
|
564
|
+
continue;
|
|
565
|
+
if (opts.skipJunctionTables && allTables) {
|
|
566
|
+
const targetTable = allTables.find((t) => t.name === edge.target);
|
|
567
|
+
if (targetTable && isJunctionTable(targetTable)) {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const newPath = [...path, key];
|
|
572
|
+
const newIsMany = [...isMany, edge.kind === "many"];
|
|
573
|
+
const newTargets = [...targets, edge.target];
|
|
574
|
+
const methodSuffix = pathToMethodSuffix(newPath);
|
|
575
|
+
methods.push({
|
|
576
|
+
name: `list${methodSuffix}`,
|
|
577
|
+
path: newPath,
|
|
578
|
+
isMany: newIsMany,
|
|
579
|
+
targets: newTargets,
|
|
580
|
+
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
581
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
582
|
+
});
|
|
583
|
+
methods.push({
|
|
584
|
+
name: `getByPk${methodSuffix}`,
|
|
585
|
+
path: newPath,
|
|
586
|
+
isMany: newIsMany,
|
|
587
|
+
targets: newTargets,
|
|
588
|
+
returnType: `${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)} | null`,
|
|
589
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
590
|
+
});
|
|
591
|
+
explore(edge.target, newPath, newIsMany, newTargets, new Set([...visited, edge.target]), depth + 1);
|
|
592
|
+
}
|
|
593
|
+
if (depth === 1 && Object.keys(currentEdges).length > 1 && Object.keys(currentEdges).length <= 3) {
|
|
594
|
+
const edgeEntries = Object.entries(currentEdges);
|
|
595
|
+
if (edgeEntries.length >= 2) {
|
|
596
|
+
for (let i = 0;i < edgeEntries.length - 1; i++) {
|
|
597
|
+
for (let j = i + 1;j < edgeEntries.length; j++) {
|
|
598
|
+
const entry1 = edgeEntries[i];
|
|
599
|
+
const entry2 = edgeEntries[j];
|
|
600
|
+
if (!entry1 || !entry2)
|
|
601
|
+
continue;
|
|
602
|
+
const [key1, edge1] = entry1;
|
|
603
|
+
const [key2, edge2] = entry2;
|
|
604
|
+
if (opts.skipJunctionTables && (edge1.target.includes("_") || edge2.target.includes("_"))) {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const combinedPath = [key1, key2];
|
|
608
|
+
const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
|
|
609
|
+
const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
|
|
610
|
+
const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
|
|
611
|
+
methods.push({
|
|
612
|
+
name: `list${combinedSuffix}`,
|
|
613
|
+
path: combinedPath,
|
|
614
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
615
|
+
targets: [edge1.target, edge2.target],
|
|
616
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
617
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
618
|
+
});
|
|
619
|
+
methods.push({
|
|
620
|
+
name: `getByPk${combinedSuffix}`,
|
|
621
|
+
path: combinedPath,
|
|
622
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
623
|
+
targets: [edge1.target, edge2.target],
|
|
624
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} }) | null`,
|
|
625
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
explore(baseTableName, [], [], [], new Set([baseTableName]), 1);
|
|
633
|
+
return methods;
|
|
634
|
+
}
|
|
635
|
+
var init_emit_include_methods = __esm(() => {
|
|
636
|
+
init_utils();
|
|
637
|
+
});
|
|
638
|
+
|
|
488
639
|
// src/emit-sdk-contract.ts
|
|
489
640
|
var exports_emit_sdk_contract = {};
|
|
490
641
|
__export(exports_emit_sdk_contract, {
|
|
@@ -492,7 +643,7 @@ __export(exports_emit_sdk_contract, {
|
|
|
492
643
|
generateUnifiedContract: () => generateUnifiedContract,
|
|
493
644
|
emitUnifiedContract: () => emitUnifiedContract
|
|
494
645
|
});
|
|
495
|
-
function generateUnifiedContract(model, config) {
|
|
646
|
+
function generateUnifiedContract(model, config, graph) {
|
|
496
647
|
const resources = [];
|
|
497
648
|
const relationships = [];
|
|
498
649
|
const tables = model && model.tables ? Object.values(model.tables) : [];
|
|
@@ -500,7 +651,7 @@ function generateUnifiedContract(model, config) {
|
|
|
500
651
|
console.log(`[SDK Contract] Processing ${tables.length} tables`);
|
|
501
652
|
}
|
|
502
653
|
for (const table of tables) {
|
|
503
|
-
resources.push(generateResourceWithSDK(table, model));
|
|
654
|
+
resources.push(generateResourceWithSDK(table, model, graph, config));
|
|
504
655
|
for (const fk of table.fks) {
|
|
505
656
|
relationships.push({
|
|
506
657
|
from: table.name,
|
|
@@ -618,7 +769,7 @@ function generateSDKAuthExamples(auth) {
|
|
|
618
769
|
});
|
|
619
770
|
return examples;
|
|
620
771
|
}
|
|
621
|
-
function generateResourceWithSDK(table, model) {
|
|
772
|
+
function generateResourceWithSDK(table, model, graph, config) {
|
|
622
773
|
const Type = pascal(table.name);
|
|
623
774
|
const tableName = table.name;
|
|
624
775
|
const basePath = `/v1/${tableName}`;
|
|
@@ -640,11 +791,6 @@ const filtered = await sdk.${tableName}.list({
|
|
|
640
791
|
${table.columns[0]?.name || "field"}_like: 'search',
|
|
641
792
|
order_by: '${table.columns[0]?.name || "created_at"}',
|
|
642
793
|
order_dir: 'desc'
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
// With related data
|
|
646
|
-
const withRelations = await sdk.${tableName}.list({
|
|
647
|
-
include: '${table.fks[0]?.toTable || "related_table"}'
|
|
648
794
|
});`,
|
|
649
795
|
correspondsTo: `GET ${basePath}`
|
|
650
796
|
});
|
|
@@ -658,16 +804,11 @@ const withRelations = await sdk.${tableName}.list({
|
|
|
658
804
|
if (hasSinglePK) {
|
|
659
805
|
sdkMethods.push({
|
|
660
806
|
name: "getByPk",
|
|
661
|
-
signature: `getByPk(${pkField}: string
|
|
807
|
+
signature: `getByPk(${pkField}: string): Promise<${Type} | null>`,
|
|
662
808
|
description: `Get a single ${tableName} by primary key`,
|
|
663
809
|
example: `// Get by ID
|
|
664
810
|
const item = await sdk.${tableName}.getByPk('123e4567-e89b-12d3-a456-426614174000');
|
|
665
811
|
|
|
666
|
-
// With related data
|
|
667
|
-
const withRelations = await sdk.${tableName}.getByPk('123', {
|
|
668
|
-
include: '${table.fks[0]?.toTable || "related_table"}'
|
|
669
|
-
});
|
|
670
|
-
|
|
671
812
|
// Check if exists
|
|
672
813
|
if (item === null) {
|
|
673
814
|
console.log('Not found');
|
|
@@ -678,9 +819,6 @@ if (item === null) {
|
|
|
678
819
|
method: "GET",
|
|
679
820
|
path: `${basePath}/:${pkField}`,
|
|
680
821
|
description: `Get ${tableName} by ID`,
|
|
681
|
-
queryParameters: {
|
|
682
|
-
include: "string - Comma-separated list of related resources"
|
|
683
|
-
},
|
|
684
822
|
responseBody: `${Type}`
|
|
685
823
|
});
|
|
686
824
|
}
|
|
@@ -743,6 +881,31 @@ console.log('Deleted:', deleted);`,
|
|
|
743
881
|
responseBody: `${Type}`
|
|
744
882
|
});
|
|
745
883
|
}
|
|
884
|
+
if (graph && config) {
|
|
885
|
+
const allTables = model && model.tables ? Object.values(model.tables) : undefined;
|
|
886
|
+
const includeMethods = generateIncludeMethods(table, graph, {
|
|
887
|
+
maxDepth: config.includeMethodsDepth ?? 2,
|
|
888
|
+
skipJunctionTables: config.skipJunctionTables ?? true
|
|
889
|
+
}, allTables);
|
|
890
|
+
for (const method of includeMethods) {
|
|
891
|
+
const isGetByPk = method.name.startsWith("getByPk");
|
|
892
|
+
const exampleCall = isGetByPk ? `const result = await sdk.${tableName}.${method.name}('123e4567-e89b-12d3-a456-426614174000');` : `const results = await sdk.${tableName}.${method.name}();
|
|
893
|
+
|
|
894
|
+
// With filters and pagination
|
|
895
|
+
const filtered = await sdk.${tableName}.${method.name}({
|
|
896
|
+
limit: 20,
|
|
897
|
+
offset: 0,
|
|
898
|
+
where: { /* filter conditions */ }
|
|
899
|
+
});`;
|
|
900
|
+
sdkMethods.push({
|
|
901
|
+
name: method.name,
|
|
902
|
+
signature: `${method.name}(${isGetByPk ? `${pkField}: string` : "params?: ListParams"}): ${method.returnType}`,
|
|
903
|
+
description: `Get ${tableName} with included ${method.path.join(", ")} data`,
|
|
904
|
+
example: exampleCall,
|
|
905
|
+
correspondsTo: `POST ${basePath}/list`
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
746
909
|
const fields = table.columns.map((col) => generateFieldContract(col, table));
|
|
747
910
|
return {
|
|
748
911
|
name: Type,
|
|
@@ -891,8 +1054,7 @@ function generateQueryParams(table) {
|
|
|
891
1054
|
limit: "number - Max records to return (default: 50)",
|
|
892
1055
|
offset: "number - Records to skip",
|
|
893
1056
|
order_by: "string - Field to sort by",
|
|
894
|
-
order_dir: "'asc' | 'desc' - Sort direction"
|
|
895
|
-
include: "string - Related resources to include"
|
|
1057
|
+
order_dir: "'asc' | 'desc' - Sort direction"
|
|
896
1058
|
};
|
|
897
1059
|
let filterCount = 0;
|
|
898
1060
|
for (const col of table.columns) {
|
|
@@ -1079,8 +1241,8 @@ function generateUnifiedContractMarkdown(contract) {
|
|
|
1079
1241
|
return lines.join(`
|
|
1080
1242
|
`);
|
|
1081
1243
|
}
|
|
1082
|
-
function emitUnifiedContract(model, config) {
|
|
1083
|
-
const contract = generateUnifiedContract(model, config);
|
|
1244
|
+
function emitUnifiedContract(model, config, graph) {
|
|
1245
|
+
const contract = generateUnifiedContract(model, config, graph);
|
|
1084
1246
|
const contractJson = JSON.stringify(contract, null, 2);
|
|
1085
1247
|
return `/**
|
|
1086
1248
|
* Unified API & SDK Contract
|
|
@@ -1136,6 +1298,7 @@ import type * as Types from './client/types';
|
|
|
1136
1298
|
}
|
|
1137
1299
|
var init_emit_sdk_contract = __esm(() => {
|
|
1138
1300
|
init_utils();
|
|
1301
|
+
init_emit_include_methods();
|
|
1139
1302
|
});
|
|
1140
1303
|
|
|
1141
1304
|
// src/index.ts
|
|
@@ -1651,157 +1814,7 @@ ${hasAuth ? `
|
|
|
1651
1814
|
|
|
1652
1815
|
// src/emit-client.ts
|
|
1653
1816
|
init_utils();
|
|
1654
|
-
|
|
1655
|
-
// src/emit-include-methods.ts
|
|
1656
|
-
init_utils();
|
|
1657
|
-
function isJunctionTable(table) {
|
|
1658
|
-
if (!table.name.includes("_"))
|
|
1659
|
-
return false;
|
|
1660
|
-
const fkColumns = new Set(table.fks.flatMap((fk) => fk.from));
|
|
1661
|
-
const nonPkColumns = table.columns.filter((c) => !table.pk.includes(c.name));
|
|
1662
|
-
return nonPkColumns.every((c) => fkColumns.has(c.name));
|
|
1663
|
-
}
|
|
1664
|
-
function pathToMethodSuffix(path) {
|
|
1665
|
-
return "With" + path.map((p) => pascal(p)).join("And");
|
|
1666
|
-
}
|
|
1667
|
-
function buildReturnType(baseTable, path, isMany, targets, graph) {
|
|
1668
|
-
const BaseType = `Select${pascal(baseTable)}`;
|
|
1669
|
-
if (path.length === 0)
|
|
1670
|
-
return BaseType;
|
|
1671
|
-
let type = BaseType;
|
|
1672
|
-
let currentTable = baseTable;
|
|
1673
|
-
const parts = [];
|
|
1674
|
-
for (let i = 0;i < path.length; i++) {
|
|
1675
|
-
const key = path[i];
|
|
1676
|
-
const target = targets[i];
|
|
1677
|
-
if (!key || !target)
|
|
1678
|
-
continue;
|
|
1679
|
-
const targetType = `Select${pascal(target)}`;
|
|
1680
|
-
if (i === 0) {
|
|
1681
|
-
parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : targetType}`);
|
|
1682
|
-
} else {
|
|
1683
|
-
let nestedType = targetType;
|
|
1684
|
-
for (let j = i;j < path.length; j++) {
|
|
1685
|
-
if (j > i) {
|
|
1686
|
-
const nestedKey = path[j];
|
|
1687
|
-
const nestedTarget = targets[j];
|
|
1688
|
-
if (!nestedKey || !nestedTarget)
|
|
1689
|
-
continue;
|
|
1690
|
-
const nestedTargetType = `Select${pascal(nestedTarget)}`;
|
|
1691
|
-
nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : nestedTargetType} }`;
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
const prevKey = path[i - 1];
|
|
1695
|
-
const prevTarget = targets[i - 1];
|
|
1696
|
-
if (prevKey && prevTarget) {
|
|
1697
|
-
parts[parts.length - 1] = `${prevKey}: ${isMany[i - 1] ? `(Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} })[]` : `Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} }`}`;
|
|
1698
|
-
}
|
|
1699
|
-
break;
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
return `${type} & { ${parts.join("; ")} }`;
|
|
1703
|
-
}
|
|
1704
|
-
function buildIncludeSpec(path) {
|
|
1705
|
-
if (path.length === 0)
|
|
1706
|
-
return {};
|
|
1707
|
-
if (path.length === 1)
|
|
1708
|
-
return { [path[0]]: true };
|
|
1709
|
-
let spec = true;
|
|
1710
|
-
for (let i = path.length - 1;i > 0; i--) {
|
|
1711
|
-
const key = path[i];
|
|
1712
|
-
if (!key)
|
|
1713
|
-
continue;
|
|
1714
|
-
spec = { [key]: spec };
|
|
1715
|
-
}
|
|
1716
|
-
const rootKey = path[0];
|
|
1717
|
-
return rootKey ? { [rootKey]: spec } : {};
|
|
1718
|
-
}
|
|
1719
|
-
function generateIncludeMethods(table, graph, opts, allTables) {
|
|
1720
|
-
const methods = [];
|
|
1721
|
-
const baseTableName = table.name;
|
|
1722
|
-
if (opts.skipJunctionTables && isJunctionTable(table)) {
|
|
1723
|
-
return methods;
|
|
1724
|
-
}
|
|
1725
|
-
const edges = graph[baseTableName] || {};
|
|
1726
|
-
function explore(currentTable, path, isMany, targets, visited, depth) {
|
|
1727
|
-
if (depth > opts.maxDepth)
|
|
1728
|
-
return;
|
|
1729
|
-
const currentEdges = graph[currentTable] || {};
|
|
1730
|
-
for (const [key, edge] of Object.entries(currentEdges)) {
|
|
1731
|
-
if (visited.has(edge.target))
|
|
1732
|
-
continue;
|
|
1733
|
-
if (opts.skipJunctionTables && allTables) {
|
|
1734
|
-
const targetTable = allTables.find((t) => t.name === edge.target);
|
|
1735
|
-
if (targetTable && isJunctionTable(targetTable)) {
|
|
1736
|
-
continue;
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
const newPath = [...path, key];
|
|
1740
|
-
const newIsMany = [...isMany, edge.kind === "many"];
|
|
1741
|
-
const newTargets = [...targets, edge.target];
|
|
1742
|
-
const methodSuffix = pathToMethodSuffix(newPath);
|
|
1743
|
-
methods.push({
|
|
1744
|
-
name: `list${methodSuffix}`,
|
|
1745
|
-
path: newPath,
|
|
1746
|
-
isMany: newIsMany,
|
|
1747
|
-
targets: newTargets,
|
|
1748
|
-
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
1749
|
-
includeSpec: buildIncludeSpec(newPath)
|
|
1750
|
-
});
|
|
1751
|
-
methods.push({
|
|
1752
|
-
name: `getByPk${methodSuffix}`,
|
|
1753
|
-
path: newPath,
|
|
1754
|
-
isMany: newIsMany,
|
|
1755
|
-
targets: newTargets,
|
|
1756
|
-
returnType: `${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)} | null`,
|
|
1757
|
-
includeSpec: buildIncludeSpec(newPath)
|
|
1758
|
-
});
|
|
1759
|
-
explore(edge.target, newPath, newIsMany, newTargets, new Set([...visited, edge.target]), depth + 1);
|
|
1760
|
-
}
|
|
1761
|
-
if (depth === 1 && Object.keys(currentEdges).length > 1 && Object.keys(currentEdges).length <= 3) {
|
|
1762
|
-
const edgeEntries = Object.entries(currentEdges);
|
|
1763
|
-
if (edgeEntries.length >= 2) {
|
|
1764
|
-
for (let i = 0;i < edgeEntries.length - 1; i++) {
|
|
1765
|
-
for (let j = i + 1;j < edgeEntries.length; j++) {
|
|
1766
|
-
const entry1 = edgeEntries[i];
|
|
1767
|
-
const entry2 = edgeEntries[j];
|
|
1768
|
-
if (!entry1 || !entry2)
|
|
1769
|
-
continue;
|
|
1770
|
-
const [key1, edge1] = entry1;
|
|
1771
|
-
const [key2, edge2] = entry2;
|
|
1772
|
-
if (opts.skipJunctionTables && (edge1.target.includes("_") || edge2.target.includes("_"))) {
|
|
1773
|
-
continue;
|
|
1774
|
-
}
|
|
1775
|
-
const combinedPath = [key1, key2];
|
|
1776
|
-
const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
|
|
1777
|
-
const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
|
|
1778
|
-
const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
|
|
1779
|
-
methods.push({
|
|
1780
|
-
name: `list${combinedSuffix}`,
|
|
1781
|
-
path: combinedPath,
|
|
1782
|
-
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
1783
|
-
targets: [edge1.target, edge2.target],
|
|
1784
|
-
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
1785
|
-
includeSpec: { [key1]: true, [key2]: true }
|
|
1786
|
-
});
|
|
1787
|
-
methods.push({
|
|
1788
|
-
name: `getByPk${combinedSuffix}`,
|
|
1789
|
-
path: combinedPath,
|
|
1790
|
-
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
1791
|
-
targets: [edge1.target, edge2.target],
|
|
1792
|
-
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} }) | null`,
|
|
1793
|
-
includeSpec: { [key1]: true, [key2]: true }
|
|
1794
|
-
});
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
explore(baseTableName, [], [], [], new Set([baseTableName]), 1);
|
|
1801
|
-
return methods;
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
// src/emit-client.ts
|
|
1817
|
+
init_emit_include_methods();
|
|
1805
1818
|
function emitClient(table, graph, opts, model) {
|
|
1806
1819
|
const Type = pascal(table.name);
|
|
1807
1820
|
const ext = opts.useJsExtensions ? ".js" : "";
|
|
@@ -3883,26 +3896,30 @@ async function generate(configPath) {
|
|
|
3883
3896
|
content: emitHonoRouter(Object.values(model.tables), !!normalizedAuth?.strategy && normalizedAuth.strategy !== "none", cfg.useJsExtensions)
|
|
3884
3897
|
});
|
|
3885
3898
|
}
|
|
3886
|
-
const
|
|
3887
|
-
|
|
3899
|
+
const { generateUnifiedContract: generateUnifiedContract2, generateUnifiedContractMarkdown: generateUnifiedContractMarkdown2 } = await Promise.resolve().then(() => (init_emit_sdk_contract(), exports_emit_sdk_contract));
|
|
3900
|
+
if (process.env.SDK_DEBUG) {
|
|
3901
|
+
console.log(`[Index] Model has ${Object.keys(model.tables || {}).length} tables before contract generation`);
|
|
3902
|
+
}
|
|
3903
|
+
const contract = generateUnifiedContract2(model, cfg, graph);
|
|
3904
|
+
files.push({
|
|
3905
|
+
path: join(serverDir, "CONTRACT.md"),
|
|
3906
|
+
content: generateUnifiedContractMarkdown2(contract)
|
|
3888
3907
|
});
|
|
3889
3908
|
files.push({
|
|
3890
|
-
path: join(
|
|
3891
|
-
content:
|
|
3909
|
+
path: join(clientDir, "CONTRACT.md"),
|
|
3910
|
+
content: generateUnifiedContractMarkdown2(contract)
|
|
3892
3911
|
});
|
|
3893
|
-
const contractCode = emitUnifiedContract(model, cfg);
|
|
3912
|
+
const contractCode = emitUnifiedContract(model, cfg, graph);
|
|
3894
3913
|
files.push({
|
|
3895
3914
|
path: join(serverDir, "contract.ts"),
|
|
3896
3915
|
content: contractCode
|
|
3897
3916
|
});
|
|
3898
|
-
const
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
}
|
|
3902
|
-
const contract = generateUnifiedContract2(model, cfg);
|
|
3917
|
+
const clientFiles = files.filter((f) => {
|
|
3918
|
+
return f.path.includes(clientDir);
|
|
3919
|
+
});
|
|
3903
3920
|
files.push({
|
|
3904
|
-
path: join(serverDir, "
|
|
3905
|
-
content:
|
|
3921
|
+
path: join(serverDir, "sdk-bundle.ts"),
|
|
3922
|
+
content: emitSdkBundle(clientFiles, clientDir)
|
|
3906
3923
|
});
|
|
3907
3924
|
if (generateTests) {
|
|
3908
3925
|
console.log("\uD83E\uDDEA Generating tests...");
|