@sdk-it/typescript 0.25.0 → 0.27.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/index.js +984 -54
- package/dist/index.js.map +4 -4
- package/dist/lib/emitters/snippet.d.ts +3 -2
- package/dist/lib/emitters/snippet.d.ts.map +1 -1
- package/dist/lib/emitters/zod.d.ts.map +1 -1
- package/dist/lib/generate.d.ts.map +1 -1
- package/dist/lib/readme/prop.emitter.d.ts +17 -0
- package/dist/lib/readme/prop.emitter.d.ts.map +1 -0
- package/dist/lib/readme/readme.d.ts +4 -0
- package/dist/lib/readme/readme.d.ts.map +1 -0
- package/dist/lib/typescript-snippet.d.ts +15 -2
- package/dist/lib/typescript-snippet.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
getFolderExports,
|
|
11
11
|
writeFiles
|
|
12
12
|
} from "@sdk-it/core/file-system.js";
|
|
13
|
-
import { toReadme } from "@sdk-it/readme";
|
|
14
13
|
import {
|
|
15
14
|
augmentSpec,
|
|
16
15
|
cleanFiles,
|
|
@@ -159,10 +158,10 @@ var ZodEmitter = class {
|
|
|
159
158
|
switch (schema.format) {
|
|
160
159
|
case "date-time":
|
|
161
160
|
case "datetime":
|
|
162
|
-
base = "z.
|
|
161
|
+
base = "z.string().datetime()";
|
|
163
162
|
break;
|
|
164
163
|
case "date":
|
|
165
|
-
base = "z.coerce.date()
|
|
164
|
+
base = "z.coerce.date()";
|
|
166
165
|
break;
|
|
167
166
|
case "time":
|
|
168
167
|
base = "z.string() /* optionally add .regex(...) for HH:MM:SS format */";
|
|
@@ -931,8 +930,8 @@ function generateCode(config) {
|
|
|
931
930
|
const endpoints = {};
|
|
932
931
|
forEachOperation(config.spec, (entry, operation) => {
|
|
933
932
|
console.log(`Processing ${entry.method} ${entry.path}`);
|
|
934
|
-
groups[entry.
|
|
935
|
-
endpoints[entry.
|
|
933
|
+
groups[entry.tag] ??= [];
|
|
934
|
+
endpoints[entry.tag] ??= [];
|
|
936
935
|
const inputs = {};
|
|
937
936
|
const schemas = {};
|
|
938
937
|
const shortContenTypeMap = {
|
|
@@ -1001,7 +1000,7 @@ function generateCode(config) {
|
|
|
1001
1000
|
outgoingContentType = "formdata";
|
|
1002
1001
|
}
|
|
1003
1002
|
const endpoint = toEndpoint(
|
|
1004
|
-
entry.
|
|
1003
|
+
entry.tag,
|
|
1005
1004
|
config.spec,
|
|
1006
1005
|
operation,
|
|
1007
1006
|
{
|
|
@@ -1014,8 +1013,8 @@ function generateCode(config) {
|
|
|
1014
1013
|
},
|
|
1015
1014
|
{ makeImport: config.makeImport, style: config.style }
|
|
1016
1015
|
);
|
|
1017
|
-
endpoints[entry.
|
|
1018
|
-
groups[entry.
|
|
1016
|
+
endpoints[entry.tag].push(endpoint);
|
|
1017
|
+
groups[entry.tag].push({
|
|
1019
1018
|
name: operation.operationId,
|
|
1020
1019
|
inputs,
|
|
1021
1020
|
outgoingContentType,
|
|
@@ -1594,16 +1593,397 @@ var offset_pagination_default = "type OffsetPaginationParams = {\n offset: numb
|
|
|
1594
1593
|
// packages/typescript/src/lib/paginations/page-pagination.txt
|
|
1595
1594
|
var page_pagination_default = "type InferPage<T> = T extends Page<infer U> ? U : never;\ntype PaginationParams<P extends number | bigint, S extends number | bigint> = {\n page?: P;\n pageSize?: S;\n};\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationResult<T, M extends Metadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<\n T,\n M extends Metadata,\n P extends number | bigint,\n S extends number | bigint,\n> = (input: Partial<PaginationParams<P, S>>) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class Pagination<\n T,\n M extends Metadata,\n P extends number | bigint,\n S extends number | bigint,\n> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: PaginationParams<P, S>;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M, P, S>;\n\n constructor(\n initialParams: Partial<PaginationParams<P, S>>,\n fetchFn: FetchFn<T, M, P, S>,\n ) {\n this.#fetchFn = fetchFn;\n this.#params = { ...initialParams, page: initialParams.page };\n }\n\n async getNextPage() {\n const result = await this.#fetchFn(this.#params);\n this.#currentPage = new Page(result.data);\n this.#meta = result.meta;\n this.#params = {\n ...this.#params,\n page: ((this.#params.page as number) || 0 + 1) as never,\n };\n return this;\n }\n\n getCurrentPage() {\n if (!this.#currentPage) {\n throw new Error(\n 'No page data available. Please call getNextPage() first.',\n );\n }\n return this.#currentPage;\n }\n\n get hasMore() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta.hasMore;\n }\n\n async *[Symbol.asyncIterator]() {\n for await (const page of this.iter()) {\n yield page.getCurrentPage();\n }\n }\n\n async *iter() {\n if (!this.#currentPage) {\n yield await this.getNextPage();\n }\n\n while (this.hasMore) {\n yield await this.getNextPage();\n }\n }\n\n get metadata() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n";
|
|
1596
1595
|
|
|
1596
|
+
// packages/typescript/src/lib/readme/readme.ts
|
|
1597
|
+
import { isEmpty as isEmpty3 } from "@sdk-it/core";
|
|
1598
|
+
import { forEachOperation as forEachOperation2 } from "@sdk-it/spec";
|
|
1599
|
+
|
|
1600
|
+
// packages/typescript/src/lib/readme/prop.emitter.ts
|
|
1601
|
+
import { followRef as followRef4, isRef as isRef4 } from "@sdk-it/core";
|
|
1602
|
+
import { coerceTypes } from "@sdk-it/spec";
|
|
1603
|
+
var PropEmitter = class {
|
|
1604
|
+
#spec;
|
|
1605
|
+
constructor(spec) {
|
|
1606
|
+
this.#spec = spec;
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Handle objects (properties)
|
|
1610
|
+
*/
|
|
1611
|
+
#object(schema) {
|
|
1612
|
+
const lines = [];
|
|
1613
|
+
const properties = schema.properties || {};
|
|
1614
|
+
if (Object.keys(properties).length > 0) {
|
|
1615
|
+
lines.push(`**Properties:**`);
|
|
1616
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
1617
|
+
const isRequired = (schema.required ?? []).includes(propName);
|
|
1618
|
+
lines.push(...this.#property(propName, propSchema, isRequired));
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
if (schema.additionalProperties) {
|
|
1622
|
+
lines.push(`**Additional Properties:**`);
|
|
1623
|
+
if (typeof schema.additionalProperties === "boolean") {
|
|
1624
|
+
lines.push(`- Allowed: ${schema.additionalProperties}`);
|
|
1625
|
+
} else {
|
|
1626
|
+
lines.push(
|
|
1627
|
+
...this.handle(schema.additionalProperties).map((l) => ` ${l}`)
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
return lines;
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Format a property with its type and description
|
|
1635
|
+
*/
|
|
1636
|
+
#property(name, schema, required) {
|
|
1637
|
+
const docs = this.handle(schema);
|
|
1638
|
+
const rawType = docs[0].replace("**Type:** ", "").replace(" (nullable)", "|null");
|
|
1639
|
+
const defaultVal = !isRef4(schema) && schema.default !== void 0 ? ` default: ${JSON.stringify(schema.default)}` : "";
|
|
1640
|
+
const reqMark = required ? " required" : "";
|
|
1641
|
+
const summary = `- \`${name}\` ${rawType}${reqMark}${defaultVal}:`;
|
|
1642
|
+
const detailLines = docs.slice(1).filter((l) => !l.startsWith("**Default:**")).map((l) => ` ${l}`);
|
|
1643
|
+
return [summary, ...detailLines];
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Handle array schemas
|
|
1647
|
+
*/
|
|
1648
|
+
#array(schema) {
|
|
1649
|
+
const lines = [];
|
|
1650
|
+
lines.push(`**Array items:**`);
|
|
1651
|
+
if (schema.items) {
|
|
1652
|
+
const itemDocs = this.handle(schema.items);
|
|
1653
|
+
lines.push(...itemDocs.map((line) => ` ${line}`));
|
|
1654
|
+
} else {
|
|
1655
|
+
lines.push(` **Type:** \`unknown\``);
|
|
1656
|
+
}
|
|
1657
|
+
if (schema.minItems !== void 0)
|
|
1658
|
+
lines.push(`- Minimum items: ${schema.minItems}`);
|
|
1659
|
+
if (schema.maxItems !== void 0)
|
|
1660
|
+
lines.push(`- Maximum items: ${schema.maxItems}`);
|
|
1661
|
+
if (schema.uniqueItems) lines.push(`- Items must be unique.`);
|
|
1662
|
+
return lines;
|
|
1663
|
+
}
|
|
1664
|
+
#ref($ref) {
|
|
1665
|
+
const schemaName = $ref.split("/").pop() || "object";
|
|
1666
|
+
const resolved = followRef4(this.#spec, $ref);
|
|
1667
|
+
const lines = [
|
|
1668
|
+
`**Type:** [\`${schemaName}\`](#${schemaName.toLowerCase()})`
|
|
1669
|
+
];
|
|
1670
|
+
if (resolved.description) {
|
|
1671
|
+
lines.push(resolved.description);
|
|
1672
|
+
}
|
|
1673
|
+
return lines;
|
|
1674
|
+
}
|
|
1675
|
+
#allOf(schemas) {
|
|
1676
|
+
const lines = ["**All of (Intersection):**"];
|
|
1677
|
+
schemas.forEach((subSchema, index) => {
|
|
1678
|
+
lines.push(`- **Constraint ${index + 1}:**`);
|
|
1679
|
+
const subLines = this.handle(subSchema);
|
|
1680
|
+
lines.push(...subLines.map((l) => ` ${l}`));
|
|
1681
|
+
});
|
|
1682
|
+
return lines;
|
|
1683
|
+
}
|
|
1684
|
+
#anyOf(schemas) {
|
|
1685
|
+
const lines = ["**Any of (Union):**"];
|
|
1686
|
+
schemas.forEach((subSchema, index) => {
|
|
1687
|
+
lines.push(`- **Option ${index + 1}:**`);
|
|
1688
|
+
const subLines = this.handle(subSchema);
|
|
1689
|
+
lines.push(...subLines.map((l) => ` ${l}`));
|
|
1690
|
+
});
|
|
1691
|
+
return lines;
|
|
1692
|
+
}
|
|
1693
|
+
#oneOf(schemas) {
|
|
1694
|
+
const lines = ["**One of (Exclusive Union):**"];
|
|
1695
|
+
schemas.forEach((subSchema, index) => {
|
|
1696
|
+
lines.push(`- **Option ${index + 1}:**`);
|
|
1697
|
+
const subLines = this.handle(subSchema);
|
|
1698
|
+
lines.push(...subLines.map((l) => ` ${l}`));
|
|
1699
|
+
});
|
|
1700
|
+
return lines;
|
|
1701
|
+
}
|
|
1702
|
+
#enum(schema) {
|
|
1703
|
+
const lines = [`**Type:** \`${schema.type || "unknown"}\` (enum)`];
|
|
1704
|
+
if (schema.description) lines.push(schema.description);
|
|
1705
|
+
lines.push("**Allowed values:**");
|
|
1706
|
+
lines.push(
|
|
1707
|
+
...(schema.enum || []).map((val) => `- \`${JSON.stringify(val)}\``)
|
|
1708
|
+
);
|
|
1709
|
+
if (schema.default !== void 0) {
|
|
1710
|
+
lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
|
|
1711
|
+
}
|
|
1712
|
+
return lines;
|
|
1713
|
+
}
|
|
1714
|
+
#normal(type, schema, nullable) {
|
|
1715
|
+
const lines = [];
|
|
1716
|
+
const nullableSuffix = nullable ? " (nullable)" : "";
|
|
1717
|
+
const description = schema.description ? [schema.description] : [];
|
|
1718
|
+
switch (type) {
|
|
1719
|
+
case "string":
|
|
1720
|
+
lines.push(
|
|
1721
|
+
`**Type:** \`string\`${schema.format ? ` (format: ${schema.format})` : ""}${nullableSuffix}`
|
|
1722
|
+
);
|
|
1723
|
+
lines.push(...description);
|
|
1724
|
+
if (schema.minLength !== void 0)
|
|
1725
|
+
lines.push(`- Minimum length: ${schema.minLength}`);
|
|
1726
|
+
if (schema.maxLength !== void 0)
|
|
1727
|
+
lines.push(`- Maximum length: ${schema.maxLength}`);
|
|
1728
|
+
if (schema.pattern !== void 0)
|
|
1729
|
+
lines.push(`- Pattern: \`${schema.pattern}\``);
|
|
1730
|
+
break;
|
|
1731
|
+
case "number":
|
|
1732
|
+
case "integer":
|
|
1733
|
+
lines.push(
|
|
1734
|
+
`**Type:** \`${type}\`${schema.format ? ` (format: ${schema.format})` : ""}${nullableSuffix}`
|
|
1735
|
+
);
|
|
1736
|
+
lines.push(...description);
|
|
1737
|
+
if (schema.minimum !== void 0) {
|
|
1738
|
+
const exclusiveMin = typeof schema.exclusiveMinimum === "number";
|
|
1739
|
+
lines.push(
|
|
1740
|
+
`- Minimum: ${schema.minimum}${exclusiveMin ? " (exclusive)" : ""}`
|
|
1741
|
+
);
|
|
1742
|
+
if (exclusiveMin) {
|
|
1743
|
+
lines.push(
|
|
1744
|
+
`- Must be strictly greater than: ${schema.exclusiveMinimum}`
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
} else if (typeof schema.exclusiveMinimum === "number") {
|
|
1748
|
+
lines.push(
|
|
1749
|
+
`- Must be strictly greater than: ${schema.exclusiveMinimum}`
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
if (schema.maximum !== void 0) {
|
|
1753
|
+
const exclusiveMax = typeof schema.exclusiveMaximum === "number";
|
|
1754
|
+
lines.push(
|
|
1755
|
+
`- Maximum: ${schema.maximum}${exclusiveMax ? " (exclusive)" : ""}`
|
|
1756
|
+
);
|
|
1757
|
+
if (exclusiveMax) {
|
|
1758
|
+
lines.push(
|
|
1759
|
+
`- Must be strictly less than: ${schema.exclusiveMaximum}`
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
} else if (typeof schema.exclusiveMaximum === "number") {
|
|
1763
|
+
lines.push(
|
|
1764
|
+
`- Must be strictly less than: ${schema.exclusiveMaximum}`
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
if (schema.multipleOf !== void 0)
|
|
1768
|
+
lines.push(`- Must be a multiple of: ${schema.multipleOf}`);
|
|
1769
|
+
break;
|
|
1770
|
+
case "boolean":
|
|
1771
|
+
lines.push(`**Type:** \`boolean\`${nullableSuffix}`);
|
|
1772
|
+
lines.push(...description);
|
|
1773
|
+
break;
|
|
1774
|
+
case "object":
|
|
1775
|
+
lines.push(`**Type:** \`object\`${nullableSuffix}`);
|
|
1776
|
+
lines.push(...description);
|
|
1777
|
+
lines.push(...this.#object(schema));
|
|
1778
|
+
break;
|
|
1779
|
+
case "array":
|
|
1780
|
+
lines.push(`**Type:** \`array\`${nullableSuffix}`);
|
|
1781
|
+
lines.push(...description);
|
|
1782
|
+
lines.push(...this.#array(schema));
|
|
1783
|
+
break;
|
|
1784
|
+
case "null":
|
|
1785
|
+
lines.push(`**Type:** \`null\``);
|
|
1786
|
+
lines.push(...description);
|
|
1787
|
+
break;
|
|
1788
|
+
default:
|
|
1789
|
+
lines.push(`**Type:** \`${type}\`${nullableSuffix}`);
|
|
1790
|
+
lines.push(...description);
|
|
1791
|
+
}
|
|
1792
|
+
if (schema.default !== void 0) {
|
|
1793
|
+
lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
|
|
1794
|
+
}
|
|
1795
|
+
return lines.filter((l) => l);
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Handle schemas by resolving references and delegating to appropriate handler
|
|
1799
|
+
*/
|
|
1800
|
+
handle(schemaOrRef) {
|
|
1801
|
+
if (isRef4(schemaOrRef)) {
|
|
1802
|
+
return this.#ref(schemaOrRef.$ref);
|
|
1803
|
+
}
|
|
1804
|
+
const schema = schemaOrRef;
|
|
1805
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
1806
|
+
return this.#allOf(schema.allOf);
|
|
1807
|
+
}
|
|
1808
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
1809
|
+
return this.#anyOf(schema.anyOf);
|
|
1810
|
+
}
|
|
1811
|
+
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
1812
|
+
return this.#oneOf(schema.oneOf);
|
|
1813
|
+
}
|
|
1814
|
+
if (schema.enum && Array.isArray(schema.enum)) {
|
|
1815
|
+
return this.#enum(schema);
|
|
1816
|
+
}
|
|
1817
|
+
let types = coerceTypes(schema);
|
|
1818
|
+
let nullable = false;
|
|
1819
|
+
if (types.includes("null")) {
|
|
1820
|
+
nullable = true;
|
|
1821
|
+
types = types.filter((t) => t !== "null");
|
|
1822
|
+
}
|
|
1823
|
+
if (types.length === 0) {
|
|
1824
|
+
if (schema.properties || schema.additionalProperties) {
|
|
1825
|
+
types = ["object"];
|
|
1826
|
+
} else if (schema.items) {
|
|
1827
|
+
types = ["array"];
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
if (types.length === 0) {
|
|
1831
|
+
const lines2 = ["**Type:** `unknown`"];
|
|
1832
|
+
if (schema.description) lines2.push(schema.description);
|
|
1833
|
+
if (schema.default !== void 0)
|
|
1834
|
+
lines2.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
|
|
1835
|
+
return lines2;
|
|
1836
|
+
}
|
|
1837
|
+
if (types.length === 1) {
|
|
1838
|
+
return this.#normal(types[0], schema, nullable);
|
|
1839
|
+
}
|
|
1840
|
+
const typeString = types.join(" | ");
|
|
1841
|
+
const nullableSuffix = nullable ? " (nullable)" : "";
|
|
1842
|
+
const lines = [`**Type:** \`${typeString}\`${nullableSuffix}`];
|
|
1843
|
+
if (schema.description) lines.push(schema.description);
|
|
1844
|
+
if (schema.default !== void 0)
|
|
1845
|
+
lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
|
|
1846
|
+
return lines;
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Process a request body and return markdown documentation
|
|
1850
|
+
*/
|
|
1851
|
+
requestBody(requestBody) {
|
|
1852
|
+
if (!requestBody) return [];
|
|
1853
|
+
const lines = [];
|
|
1854
|
+
lines.push(`##### Request Body`);
|
|
1855
|
+
if (requestBody.description) {
|
|
1856
|
+
lines.push(requestBody.description);
|
|
1857
|
+
}
|
|
1858
|
+
if (requestBody.content) {
|
|
1859
|
+
const contentEntries = Object.entries(requestBody.content);
|
|
1860
|
+
if (contentEntries.length === 1) {
|
|
1861
|
+
const [contentType, mediaType] = contentEntries[0];
|
|
1862
|
+
lines.push(`**Content Type:** \`${contentType}\``);
|
|
1863
|
+
if (mediaType.schema) {
|
|
1864
|
+
const schemaDocs = this.handle(mediaType.schema);
|
|
1865
|
+
lines.push(...schemaDocs);
|
|
1866
|
+
}
|
|
1867
|
+
} else {
|
|
1868
|
+
for (const [contentType, mediaType] of contentEntries) {
|
|
1869
|
+
lines.push(`<details>`);
|
|
1870
|
+
lines.push(
|
|
1871
|
+
`<summary><b>Content Type:</b> \`${contentType}\`</summary>`
|
|
1872
|
+
);
|
|
1873
|
+
lines.push("");
|
|
1874
|
+
if (mediaType.schema) {
|
|
1875
|
+
const schemaDocs = this.handle(mediaType.schema);
|
|
1876
|
+
lines.push(...schemaDocs.map((l) => l));
|
|
1877
|
+
}
|
|
1878
|
+
lines.push("");
|
|
1879
|
+
lines.push(`</details>`);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
return lines;
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
// packages/typescript/src/lib/readme/readme.ts
|
|
1888
|
+
function toReadme(spec, generator) {
|
|
1889
|
+
const propEmitter = new PropEmitter(spec);
|
|
1890
|
+
const markdown = [];
|
|
1891
|
+
markdown.push(`# ${spec.info.title} TypeScript SDK`);
|
|
1892
|
+
markdown.push("");
|
|
1893
|
+
markdown.push(
|
|
1894
|
+
"A fully-typed TypeScript SDK with comprehensive IntelliSense support, automatic request/response validation, and modern async/await patterns. Built for seamless integration with TypeScript and JavaScript projects."
|
|
1895
|
+
);
|
|
1896
|
+
markdown.push("");
|
|
1897
|
+
markdown.push(generator.clientSetupDocs());
|
|
1898
|
+
markdown.push("");
|
|
1899
|
+
const securitySchemes = spec.components?.securitySchemes || {};
|
|
1900
|
+
if (Object.keys(securitySchemes).length > 0) {
|
|
1901
|
+
markdown.push(generator.authenticationDocs());
|
|
1902
|
+
markdown.push("");
|
|
1903
|
+
}
|
|
1904
|
+
const paginationDocs = generator.paginationDocs();
|
|
1905
|
+
if (paginationDocs) {
|
|
1906
|
+
markdown.push(paginationDocs);
|
|
1907
|
+
markdown.push("");
|
|
1908
|
+
}
|
|
1909
|
+
markdown.push(generator.errorHandlingDocs());
|
|
1910
|
+
markdown.push("");
|
|
1911
|
+
markdown.push(generator.generalUsageDocs());
|
|
1912
|
+
markdown.push("");
|
|
1913
|
+
markdown.push("## API Reference");
|
|
1914
|
+
markdown.push("");
|
|
1915
|
+
forEachOperation2(spec, (entry, operation) => {
|
|
1916
|
+
const { method, path } = entry;
|
|
1917
|
+
markdown.push(
|
|
1918
|
+
`#### ${operation["x-fn-name"]} | ${`_${method.toUpperCase()} ${path}_`}`
|
|
1919
|
+
);
|
|
1920
|
+
markdown.push(operation.summary || "");
|
|
1921
|
+
const snippet = generator.snippet(entry, operation);
|
|
1922
|
+
markdown.push(`##### Example usage`);
|
|
1923
|
+
markdown.push(snippet);
|
|
1924
|
+
const requestBodyContent = propEmitter.requestBody(operation.requestBody);
|
|
1925
|
+
if (requestBodyContent.length > 1) {
|
|
1926
|
+
markdown.push(requestBodyContent.join("\n\n"));
|
|
1927
|
+
}
|
|
1928
|
+
markdown.push(`##### Responses`);
|
|
1929
|
+
for (const status in operation.responses) {
|
|
1930
|
+
const response = operation.responses[status];
|
|
1931
|
+
markdown.push(`<details>`);
|
|
1932
|
+
markdown.push(
|
|
1933
|
+
`<summary><b>${status}</b> <i>${response.description}</i></summary>`
|
|
1934
|
+
);
|
|
1935
|
+
if (!isEmpty3(response.content)) {
|
|
1936
|
+
for (const [contentType, mediaType] of Object.entries(
|
|
1937
|
+
response.content
|
|
1938
|
+
)) {
|
|
1939
|
+
markdown.push(`
|
|
1940
|
+
**Content Type:** \`${contentType}\``);
|
|
1941
|
+
if (mediaType.schema) {
|
|
1942
|
+
const schemaDocs = propEmitter.handle(mediaType.schema);
|
|
1943
|
+
markdown.push(...schemaDocs.map((l) => `
|
|
1944
|
+
${l}`));
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
markdown.push(`</details>`);
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
if (spec.components?.schemas) {
|
|
1952
|
+
markdown.push("## Schemas");
|
|
1953
|
+
markdown.push("");
|
|
1954
|
+
for (const [schemaName, schema] of Object.entries(
|
|
1955
|
+
spec.components.schemas
|
|
1956
|
+
)) {
|
|
1957
|
+
if (schemaName === "ValidationError") {
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
markdown.push(`<details>`);
|
|
1961
|
+
markdown.push(
|
|
1962
|
+
`<summary><h3 id="${schemaName.toLowerCase()}">${schemaName}</h3></summary>`
|
|
1963
|
+
);
|
|
1964
|
+
markdown.push("");
|
|
1965
|
+
const schemaDocs = propEmitter.handle(schema);
|
|
1966
|
+
markdown.push(...schemaDocs.map((line) => line.trim()));
|
|
1967
|
+
markdown.push("");
|
|
1968
|
+
markdown.push(`</details>`);
|
|
1969
|
+
markdown.push("");
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
return markdown.join("\n\n");
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1597
1975
|
// packages/typescript/src/lib/typescript-snippet.ts
|
|
1598
1976
|
import { camelcase as camelcase3, spinalcase as spinalcase2 } from "stringcase";
|
|
1599
|
-
import { isEmpty as
|
|
1977
|
+
import { isEmpty as isEmpty4, pascalcase as pascalcase4, resolveRef as resolveRef3 } from "@sdk-it/core";
|
|
1978
|
+
import "@sdk-it/readme";
|
|
1600
1979
|
import {
|
|
1980
|
+
forEachOperation as forEachOperation3,
|
|
1601
1981
|
patchParameters,
|
|
1602
1982
|
securityToOptions
|
|
1603
1983
|
} from "@sdk-it/spec";
|
|
1604
1984
|
|
|
1605
1985
|
// packages/typescript/src/lib/emitters/snippet.ts
|
|
1606
|
-
import { followRef as
|
|
1986
|
+
import { followRef as followRef5, isRef as isRef5, resolveRef as resolveRef2 } from "@sdk-it/core";
|
|
1607
1987
|
var SnippetEmitter = class {
|
|
1608
1988
|
spec;
|
|
1609
1989
|
generatedRefs = /* @__PURE__ */ new Set();
|
|
@@ -1612,12 +1992,12 @@ var SnippetEmitter = class {
|
|
|
1612
1992
|
this.spec = spec;
|
|
1613
1993
|
}
|
|
1614
1994
|
object(schema) {
|
|
1615
|
-
const schemaObj =
|
|
1995
|
+
const schemaObj = resolveRef2(this.spec, schema);
|
|
1616
1996
|
const result = {};
|
|
1617
1997
|
const properties = schemaObj.properties || {};
|
|
1618
1998
|
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
1619
1999
|
const isRequired = (schemaObj.required ?? []).includes(propName);
|
|
1620
|
-
const resolvedProp =
|
|
2000
|
+
const resolvedProp = resolveRef2(this.spec, propSchema);
|
|
1621
2001
|
if (isRequired || resolvedProp.example !== void 0 || resolvedProp.default !== void 0 || Math.random() > 0.5) {
|
|
1622
2002
|
result[propName] = this.handle(propSchema);
|
|
1623
2003
|
}
|
|
@@ -1630,7 +2010,7 @@ var SnippetEmitter = class {
|
|
|
1630
2010
|
return result;
|
|
1631
2011
|
}
|
|
1632
2012
|
array(schema) {
|
|
1633
|
-
const schemaObj =
|
|
2013
|
+
const schemaObj = resolveRef2(this.spec, schema);
|
|
1634
2014
|
const itemsSchema = schemaObj.items;
|
|
1635
2015
|
if (!itemsSchema) {
|
|
1636
2016
|
return [];
|
|
@@ -1712,7 +2092,7 @@ var SnippetEmitter = class {
|
|
|
1712
2092
|
return this.cache.get($ref);
|
|
1713
2093
|
}
|
|
1714
2094
|
this.cache.set($ref, { _ref: refKey });
|
|
1715
|
-
const resolved =
|
|
2095
|
+
const resolved = followRef5(this.spec, $ref);
|
|
1716
2096
|
const result = this.handle(resolved);
|
|
1717
2097
|
this.cache.set($ref, result);
|
|
1718
2098
|
return result;
|
|
@@ -1739,10 +2119,10 @@ var SnippetEmitter = class {
|
|
|
1739
2119
|
return Array.isArray(schema.enum) && schema.enum.length > 0 ? schema.enum[0] : void 0;
|
|
1740
2120
|
}
|
|
1741
2121
|
handle(schemaOrRef) {
|
|
1742
|
-
if (
|
|
2122
|
+
if (isRef5(schemaOrRef)) {
|
|
1743
2123
|
return this.ref(schemaOrRef.$ref);
|
|
1744
2124
|
}
|
|
1745
|
-
const schema =
|
|
2125
|
+
const schema = resolveRef2(this.spec, schemaOrRef);
|
|
1746
2126
|
if (schema.example !== void 0) {
|
|
1747
2127
|
return schema.example;
|
|
1748
2128
|
}
|
|
@@ -1807,9 +2187,9 @@ var TypeScriptGenerator = class {
|
|
|
1807
2187
|
}
|
|
1808
2188
|
succinct(entry, operation, values) {
|
|
1809
2189
|
let payload = "{}";
|
|
1810
|
-
if (!
|
|
2190
|
+
if (!isEmpty4(operation.requestBody)) {
|
|
1811
2191
|
const contentTypes = Object.keys(operation.requestBody.content || {});
|
|
1812
|
-
const schema =
|
|
2192
|
+
const schema = resolveRef3(
|
|
1813
2193
|
this.#spec,
|
|
1814
2194
|
operation.requestBody.content[contentTypes[0]].schema
|
|
1815
2195
|
);
|
|
@@ -1869,33 +2249,33 @@ var TypeScriptGenerator = class {
|
|
|
1869
2249
|
return this.#streamDownload(entry, payload);
|
|
1870
2250
|
}
|
|
1871
2251
|
}
|
|
1872
|
-
if (!
|
|
2252
|
+
if (!isEmpty4(operation["x-pagination"])) {
|
|
1873
2253
|
return this.#pagination(operation, entry, payload);
|
|
1874
2254
|
}
|
|
1875
2255
|
return this.#normal(entry, payload);
|
|
1876
2256
|
}
|
|
1877
|
-
#pagination(
|
|
1878
|
-
const pagination =
|
|
2257
|
+
#pagination(operation, entry, payload) {
|
|
2258
|
+
const pagination = operation["x-pagination"];
|
|
1879
2259
|
switch (pagination.type) {
|
|
1880
2260
|
case "page":
|
|
1881
2261
|
return {
|
|
1882
|
-
content: `const result = ${this.#
|
|
2262
|
+
content: `const result = ${this.#toRequest(entry, payload)}`,
|
|
1883
2263
|
footer: `for await (const page of result) {
|
|
1884
|
-
|
|
2264
|
+
console.log(page);
|
|
1885
2265
|
}`
|
|
1886
2266
|
};
|
|
1887
2267
|
case "offset":
|
|
1888
2268
|
return {
|
|
1889
|
-
content: `const result = ${this.#
|
|
2269
|
+
content: `const result = ${this.#toRequest(entry, payload)}`,
|
|
1890
2270
|
footer: `for await (const page of result) {
|
|
1891
|
-
|
|
2271
|
+
console.log(page);
|
|
1892
2272
|
}`
|
|
1893
2273
|
};
|
|
1894
2274
|
case "cursor":
|
|
1895
2275
|
return {
|
|
1896
|
-
content: `const result = ${this.#
|
|
2276
|
+
content: `const result = ${this.#toRequest(entry, payload)}`,
|
|
1897
2277
|
footer: `for await (const page of result) {
|
|
1898
|
-
|
|
2278
|
+
console.log(page);
|
|
1899
2279
|
}`
|
|
1900
2280
|
};
|
|
1901
2281
|
}
|
|
@@ -1903,25 +2283,25 @@ var TypeScriptGenerator = class {
|
|
|
1903
2283
|
}
|
|
1904
2284
|
#normal(entry, payload) {
|
|
1905
2285
|
return {
|
|
1906
|
-
content: `const result = ${this.#
|
|
2286
|
+
content: `const result = ${this.#toRequest(entry, payload)};`,
|
|
1907
2287
|
footer: "console.log(result.data)"
|
|
1908
2288
|
};
|
|
1909
2289
|
}
|
|
1910
2290
|
#streamDownload(entry, payload) {
|
|
1911
2291
|
return {
|
|
1912
|
-
content: `const stream = ${this.#
|
|
2292
|
+
content: `const stream = ${this.#toRequest(entry, payload)}`,
|
|
1913
2293
|
footer: `await writeFile('./report.pdf', stream);`
|
|
1914
2294
|
};
|
|
1915
2295
|
}
|
|
1916
2296
|
#httpStreaming(entry, payload) {
|
|
1917
2297
|
return {
|
|
1918
|
-
content: `const stream = ${this.#
|
|
2298
|
+
content: `const stream = ${this.#toRequest(entry, payload)}`,
|
|
1919
2299
|
footer: `for await (const chunk of stream) {
|
|
1920
|
-
|
|
2300
|
+
console.log(chunk);
|
|
1921
2301
|
}`
|
|
1922
2302
|
};
|
|
1923
2303
|
}
|
|
1924
|
-
#
|
|
2304
|
+
#toRequest(entry, payload) {
|
|
1925
2305
|
return `await ${camelcase3(this.#clientName)}.request('${entry.method.toUpperCase()} ${entry.path}', ${payload});`;
|
|
1926
2306
|
}
|
|
1927
2307
|
snippet(entry, operation, config = {}) {
|
|
@@ -1934,8 +2314,7 @@ var TypeScriptGenerator = class {
|
|
|
1934
2314
|
payload.footer
|
|
1935
2315
|
];
|
|
1936
2316
|
if (config.frame !== false) {
|
|
1937
|
-
|
|
1938
|
-
content.push("```");
|
|
2317
|
+
return createCodeBlock("typescript", content);
|
|
1939
2318
|
}
|
|
1940
2319
|
return content.join("\n");
|
|
1941
2320
|
}
|
|
@@ -1947,26 +2326,578 @@ var TypeScriptGenerator = class {
|
|
|
1947
2326
|
);
|
|
1948
2327
|
}
|
|
1949
2328
|
client() {
|
|
1950
|
-
const
|
|
1951
|
-
|
|
1952
|
-
|
|
2329
|
+
const options = {
|
|
2330
|
+
baseUrl: this.#spec.servers?.[0]?.url ?? "http://localhost:3000"
|
|
2331
|
+
};
|
|
1953
2332
|
const authOptions = this.#authentication();
|
|
1954
|
-
if (!
|
|
2333
|
+
if (!isEmpty4(authOptions)) {
|
|
1955
2334
|
const [firstAuth] = authOptions;
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
);
|
|
2335
|
+
const optionName = firstAuth["x-optionName"] ?? firstAuth.name;
|
|
2336
|
+
options[optionName] = firstAuth.example;
|
|
1959
2337
|
}
|
|
1960
|
-
|
|
2338
|
+
const client = this.#constructClient(options);
|
|
2339
|
+
return `${client.import}
|
|
1961
2340
|
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
})
|
|
2341
|
+
${client.use}`;
|
|
2342
|
+
}
|
|
2343
|
+
#constructClient(options = {}) {
|
|
2344
|
+
return {
|
|
2345
|
+
import: `import { ${this.#clientName} } from '${this.#packageName}';`,
|
|
2346
|
+
use: `const ${camelcase3(this.#clientName)} = new ${this.#clientName}({
|
|
2347
|
+
${Object.entries(
|
|
2348
|
+
options
|
|
2349
|
+
).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(",\n ")}
|
|
2350
|
+
});`
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
clientInstallDocs() {
|
|
2354
|
+
const sections = [];
|
|
2355
|
+
sections.push("## Installation");
|
|
2356
|
+
sections.push("");
|
|
2357
|
+
sections.push(
|
|
2358
|
+
createCodeBlock("bash", [`npm install ${this.#packageName}`])
|
|
2359
|
+
);
|
|
2360
|
+
sections.push("");
|
|
2361
|
+
sections.push("## Basic Usage");
|
|
2362
|
+
sections.push("");
|
|
2363
|
+
sections.push(createCodeBlock("typescript", [this.client()]));
|
|
2364
|
+
return sections.join("\n");
|
|
2365
|
+
}
|
|
2366
|
+
configurationOptions() {
|
|
2367
|
+
const sections = [];
|
|
2368
|
+
const hasServers = Boolean(
|
|
2369
|
+
this.#spec.servers && this.#spec.servers.length > 0
|
|
2370
|
+
);
|
|
2371
|
+
const baseUrl = this.#spec.servers?.[0]?.url || "https://api.example.com";
|
|
2372
|
+
const authOptions = this.#authentication();
|
|
2373
|
+
const hasApiKey = !isEmpty4(authOptions);
|
|
2374
|
+
sections.push("### Configuration Options");
|
|
2375
|
+
sections.push("");
|
|
2376
|
+
sections.push("| Option | Type | Required | Description |");
|
|
2377
|
+
sections.push("|--------|------|----------|-------------|");
|
|
2378
|
+
sections.push(
|
|
2379
|
+
"| `fetch` | `fetch compatible` | No | Fetch implementation to use for HTTP requests |"
|
|
2380
|
+
);
|
|
2381
|
+
if (hasServers) {
|
|
2382
|
+
sections.push(
|
|
2383
|
+
"| `baseUrl` | `string` | No | API base URL (default: `" + baseUrl + "`) |"
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
for (const authOption of authOptions) {
|
|
2387
|
+
const optionName = authOption["x-optionName"] ?? authOption.name;
|
|
2388
|
+
const description = authOption.in === "header" && authOption.name === "authorization" ? "Bearer token for authentication" : `API key for authentication (${authOption.in}: ${authOption.name})`;
|
|
2389
|
+
sections.push(`| \`${optionName}\` | \`string\` | No | ${description} |`);
|
|
2390
|
+
}
|
|
2391
|
+
return { sections, hasServers, baseUrl, hasApiKey };
|
|
2392
|
+
}
|
|
2393
|
+
clientSetupDocs() {
|
|
2394
|
+
const sections = [];
|
|
2395
|
+
sections.push(this.clientInstallDocs());
|
|
2396
|
+
sections.push("");
|
|
2397
|
+
const config = this.configurationOptions();
|
|
2398
|
+
sections.push(...config.sections);
|
|
2399
|
+
sections.push("");
|
|
2400
|
+
sections.push(this.configurationUpdateDocs());
|
|
2401
|
+
return sections.join("\n");
|
|
2402
|
+
}
|
|
2403
|
+
paginationDocs() {
|
|
2404
|
+
const paginationTypes = availablePaginationTypes(this.#spec);
|
|
2405
|
+
if (!paginationTypes.offset && !paginationTypes.page && !paginationTypes.cursor) {
|
|
2406
|
+
return "";
|
|
2407
|
+
}
|
|
2408
|
+
const sections = [];
|
|
2409
|
+
sections.push("## Pagination");
|
|
2410
|
+
sections.push("");
|
|
2411
|
+
sections.push(
|
|
2412
|
+
"This SDK automatically handles pagination for endpoints that return multiple items."
|
|
2413
|
+
);
|
|
2414
|
+
sections.push("");
|
|
2415
|
+
sections.push("### How it Works");
|
|
2416
|
+
sections.push("");
|
|
2417
|
+
sections.push(
|
|
2418
|
+
"When you call a paginated endpoint, the SDK returns a pagination object that allows you to iterate through all results:"
|
|
2419
|
+
);
|
|
2420
|
+
sections.push("");
|
|
2421
|
+
const availableTypes = [];
|
|
2422
|
+
if (paginationTypes.offset) availableTypes.push("offset");
|
|
2423
|
+
if (paginationTypes.page) availableTypes.push("page");
|
|
2424
|
+
if (paginationTypes.cursor) availableTypes.push("cursor");
|
|
2425
|
+
const primaryPaginationType = availableTypes[0];
|
|
2426
|
+
const mockPaginatedOperation = {
|
|
2427
|
+
"x-pagination": { type: primaryPaginationType },
|
|
2428
|
+
tags: ["Products"],
|
|
2429
|
+
operationId: "listProducts",
|
|
2430
|
+
parameters: [],
|
|
2431
|
+
"x-fn-name": "listProducts",
|
|
2432
|
+
responses: {},
|
|
2433
|
+
requestBody: {}
|
|
2434
|
+
};
|
|
2435
|
+
const mockOperationEntry = {
|
|
2436
|
+
method: "get",
|
|
2437
|
+
path: "/products",
|
|
2438
|
+
tag: "Products"
|
|
2439
|
+
};
|
|
2440
|
+
const initialRequestPayload = createObjectLiteral({ limit: 20 });
|
|
2441
|
+
const paginationExample = this.#pagination(
|
|
2442
|
+
mockPaginatedOperation,
|
|
2443
|
+
mockOperationEntry,
|
|
2444
|
+
initialRequestPayload
|
|
2445
|
+
);
|
|
2446
|
+
sections.push(
|
|
2447
|
+
createCodeBlock("typescript", [
|
|
2448
|
+
"// The SDK automatically handles pagination",
|
|
2449
|
+
paginationExample.content,
|
|
2450
|
+
"",
|
|
2451
|
+
"// Access the current page data",
|
|
2452
|
+
"const currentPage = result.getCurrentPage();",
|
|
2453
|
+
"console.log(currentPage.data); // Array of product items",
|
|
2454
|
+
"",
|
|
2455
|
+
"// Check if more pages exist",
|
|
2456
|
+
"if (result.hasMore) {",
|
|
2457
|
+
" await result.getNextPage();",
|
|
2458
|
+
"}",
|
|
2459
|
+
"",
|
|
2460
|
+
"// Or iterate through all pages automatically",
|
|
2461
|
+
paginationExample.footer
|
|
2462
|
+
])
|
|
2463
|
+
);
|
|
2464
|
+
sections.push("");
|
|
2465
|
+
sections.push("### Iterating Through All Pages");
|
|
2466
|
+
sections.push("");
|
|
2467
|
+
const iterationExample = this.#pagination(
|
|
2468
|
+
mockPaginatedOperation,
|
|
2469
|
+
mockOperationEntry,
|
|
2470
|
+
createObjectLiteral({ limit: 100 })
|
|
2471
|
+
);
|
|
2472
|
+
sections.push(
|
|
2473
|
+
createCodeBlock("typescript", [
|
|
2474
|
+
"// Using async iteration to process all pages",
|
|
2475
|
+
iterationExample.content,
|
|
2476
|
+
"",
|
|
2477
|
+
iterationExample.footer
|
|
2478
|
+
])
|
|
2479
|
+
);
|
|
2480
|
+
sections.push("");
|
|
2481
|
+
if (availableTypes.length > 1) {
|
|
2482
|
+
sections.push("### Pagination Types");
|
|
2483
|
+
sections.push("");
|
|
2484
|
+
sections.push(
|
|
2485
|
+
"Your API uses the following pagination strategies, automatically detected by the SDK:"
|
|
2486
|
+
);
|
|
2487
|
+
sections.push("");
|
|
2488
|
+
} else {
|
|
2489
|
+
sections.push("### Pagination Strategy");
|
|
2490
|
+
sections.push("");
|
|
2491
|
+
sections.push(
|
|
2492
|
+
"Your API uses the following pagination strategy, automatically detected by the SDK:"
|
|
2493
|
+
);
|
|
2494
|
+
sections.push("");
|
|
2495
|
+
}
|
|
2496
|
+
for (const paginationType of availableTypes) {
|
|
2497
|
+
const typeSpecificOperation = {
|
|
2498
|
+
"x-pagination": { type: paginationType },
|
|
2499
|
+
tags: ["Products"],
|
|
2500
|
+
operationId: "listProducts",
|
|
2501
|
+
parameters: [],
|
|
2502
|
+
"x-fn-name": "listProducts",
|
|
2503
|
+
responses: {},
|
|
2504
|
+
requestBody: {}
|
|
2505
|
+
};
|
|
2506
|
+
const typeSpecificEntry = {
|
|
2507
|
+
method: "get",
|
|
2508
|
+
path: "/products",
|
|
2509
|
+
tag: "Products"
|
|
2510
|
+
};
|
|
2511
|
+
if (paginationType === "cursor") {
|
|
2512
|
+
sections.push("#### Cursor Pagination");
|
|
2513
|
+
sections.push("");
|
|
2514
|
+
sections.push("Uses a cursor token to fetch the next page:");
|
|
2515
|
+
sections.push("");
|
|
2516
|
+
const cursorPaginationExample = this.#pagination(
|
|
2517
|
+
typeSpecificOperation,
|
|
2518
|
+
typeSpecificEntry,
|
|
2519
|
+
createObjectLiteral({ limit: 20 })
|
|
2520
|
+
);
|
|
2521
|
+
sections.push(
|
|
2522
|
+
createCodeBlock("typescript", [
|
|
2523
|
+
cursorPaginationExample.content,
|
|
2524
|
+
"",
|
|
2525
|
+
"// Iterate through all pages using cursor",
|
|
2526
|
+
cursorPaginationExample.footer
|
|
2527
|
+
])
|
|
2528
|
+
);
|
|
2529
|
+
sections.push("");
|
|
2530
|
+
}
|
|
2531
|
+
if (paginationType === "offset") {
|
|
2532
|
+
sections.push("#### Offset Pagination");
|
|
2533
|
+
sections.push("");
|
|
2534
|
+
sections.push("Uses offset and limit parameters:");
|
|
2535
|
+
sections.push("");
|
|
2536
|
+
const offsetPaginationExample = this.#pagination(
|
|
2537
|
+
typeSpecificOperation,
|
|
2538
|
+
typeSpecificEntry,
|
|
2539
|
+
createObjectLiteral({ limit: 20, offset: 0 })
|
|
2540
|
+
);
|
|
2541
|
+
sections.push(
|
|
2542
|
+
createCodeBlock("typescript", [
|
|
2543
|
+
offsetPaginationExample.content,
|
|
2544
|
+
"",
|
|
2545
|
+
"// Iterate through all pages using offset",
|
|
2546
|
+
offsetPaginationExample.footer
|
|
2547
|
+
])
|
|
2548
|
+
);
|
|
2549
|
+
sections.push("");
|
|
2550
|
+
}
|
|
2551
|
+
if (paginationType === "page") {
|
|
2552
|
+
sections.push("#### Page Pagination");
|
|
2553
|
+
sections.push("");
|
|
2554
|
+
sections.push("Uses page number and page size:");
|
|
2555
|
+
sections.push("");
|
|
2556
|
+
const pagePaginationExample = this.#pagination(
|
|
2557
|
+
typeSpecificOperation,
|
|
2558
|
+
typeSpecificEntry,
|
|
2559
|
+
createObjectLiteral({ page: 1, pageSize: 20 })
|
|
2560
|
+
);
|
|
2561
|
+
sections.push(
|
|
2562
|
+
createCodeBlock("typescript", [
|
|
2563
|
+
pagePaginationExample.content,
|
|
2564
|
+
"",
|
|
2565
|
+
"// Iterate through all pages using page numbers",
|
|
2566
|
+
pagePaginationExample.footer
|
|
2567
|
+
])
|
|
2568
|
+
);
|
|
2569
|
+
sections.push("");
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
if (availableTypes.length > 1) {
|
|
2573
|
+
sections.push(
|
|
2574
|
+
"The SDK handles the differences transparently, providing a consistent interface regardless of the underlying pagination type."
|
|
2575
|
+
);
|
|
2576
|
+
}
|
|
2577
|
+
return sections.join("\n");
|
|
2578
|
+
}
|
|
2579
|
+
errorHandlingDocs() {
|
|
2580
|
+
const sections = [];
|
|
2581
|
+
sections.push("## Error Handling");
|
|
2582
|
+
sections.push("");
|
|
2583
|
+
sections.push(
|
|
2584
|
+
"The SDK provides structured error handling with typed HTTP error responses."
|
|
2585
|
+
);
|
|
2586
|
+
sections.push("");
|
|
2587
|
+
sections.push("### Error Response Types");
|
|
2588
|
+
sections.push("");
|
|
2589
|
+
sections.push(
|
|
2590
|
+
"All API errors extend from `APIError` and include the HTTP status code and response data:"
|
|
2591
|
+
);
|
|
2592
|
+
sections.push("");
|
|
2593
|
+
sections.push(
|
|
2594
|
+
createCodeBlock("typescript", [
|
|
2595
|
+
`import { BadRequest, Unauthorized, NotFound, TooManyRequests, InternalServerError, ParseError } from "${this.#packageName}";`,
|
|
2596
|
+
"try {",
|
|
2597
|
+
"const usersList = ",
|
|
2598
|
+
this.#toRequest(
|
|
2599
|
+
{
|
|
2600
|
+
method: "get",
|
|
2601
|
+
path: "/users",
|
|
2602
|
+
tag: "Users"
|
|
2603
|
+
},
|
|
2604
|
+
createObjectLiteral({})
|
|
2605
|
+
),
|
|
2606
|
+
" // Handle successful response",
|
|
2607
|
+
"} catch (error) {",
|
|
2608
|
+
" // Handle different error types",
|
|
2609
|
+
" if (error instanceof BadRequest) {",
|
|
2610
|
+
' console.error("Bad request:", error.data);',
|
|
2611
|
+
' console.log("Status:", error.status); // 400',
|
|
2612
|
+
" } else if (error instanceof Unauthorized) {",
|
|
2613
|
+
' console.error("Authentication failed:", error.data);',
|
|
2614
|
+
' console.log("Status:", error.status); // 401',
|
|
2615
|
+
" } else if (error instanceof NotFound) {",
|
|
2616
|
+
' console.error("Resource not found:", error.data);',
|
|
2617
|
+
' console.log("Status:", error.status); // 404',
|
|
2618
|
+
" } else if (error instanceof TooManyRequests) {",
|
|
2619
|
+
' console.error("Rate limited:", error.data);',
|
|
2620
|
+
" if (error.data.retryAfter) {",
|
|
2621
|
+
' console.log("Retry after:", error.data.retryAfter);',
|
|
2622
|
+
" }",
|
|
2623
|
+
" } else if (error instanceof InternalServerError) {",
|
|
2624
|
+
' console.error("Server error:", error.data);',
|
|
2625
|
+
' console.log("Status:", error.status); // 500',
|
|
2626
|
+
" } else if (error instanceof ParseError) {",
|
|
2627
|
+
' console.error("Input validation failed:", error.data);',
|
|
2628
|
+
" }",
|
|
2629
|
+
"}"
|
|
2630
|
+
])
|
|
2631
|
+
);
|
|
2632
|
+
sections.push("");
|
|
2633
|
+
sections.push("### Available Error Classes");
|
|
2634
|
+
sections.push("");
|
|
2635
|
+
sections.push("#### Input Validation Errors");
|
|
2636
|
+
sections.push(
|
|
2637
|
+
"- `ParseError` - Request input validation failed against API schema"
|
|
2638
|
+
);
|
|
2639
|
+
sections.push("");
|
|
2640
|
+
sections.push("#### Client Errors (4xx)");
|
|
2641
|
+
sections.push("- `BadRequest` (400) - Invalid request data");
|
|
2642
|
+
sections.push("- `Unauthorized` (401) - Authentication required");
|
|
2643
|
+
sections.push("- `PaymentRequired` (402) - Payment required");
|
|
2644
|
+
sections.push("- `Forbidden` (403) - Access denied");
|
|
2645
|
+
sections.push("- `NotFound` (404) - Resource not found");
|
|
2646
|
+
sections.push("- `MethodNotAllowed` (405) - HTTP method not allowed");
|
|
2647
|
+
sections.push("- `NotAcceptable` (406) - Content type not acceptable");
|
|
2648
|
+
sections.push("- `Conflict` (409) - Resource conflict");
|
|
2649
|
+
sections.push("- `Gone` (410) - Resource no longer available");
|
|
2650
|
+
sections.push("- `PreconditionFailed` (412) - Precondition failed");
|
|
2651
|
+
sections.push("- `PayloadTooLarge` (413) - Request payload too large");
|
|
2652
|
+
sections.push("- `UnsupportedMediaType` (415) - Unsupported content type");
|
|
2653
|
+
sections.push("- `UnprocessableEntity` (422) - Validation errors");
|
|
2654
|
+
sections.push("- `TooManyRequests` (429) - Rate limit exceeded");
|
|
2655
|
+
sections.push("");
|
|
2656
|
+
sections.push("#### Server Errors (5xx)");
|
|
2657
|
+
sections.push("- `InternalServerError` (500) - Server error");
|
|
2658
|
+
sections.push("- `NotImplemented` (501) - Not implemented");
|
|
2659
|
+
sections.push("- `BadGateway` (502) - Bad gateway");
|
|
2660
|
+
sections.push("- `ServiceUnavailable` (503) - Service unavailable");
|
|
2661
|
+
sections.push("- `GatewayTimeout` (504) - Gateway timeout");
|
|
2662
|
+
sections.push("");
|
|
2663
|
+
sections.push("### Validation Errors");
|
|
2664
|
+
sections.push("");
|
|
2665
|
+
sections.push(
|
|
2666
|
+
"Validation errors (422) include detailed field-level error information:"
|
|
2667
|
+
);
|
|
2668
|
+
sections.push("### Input Validation Errors");
|
|
2669
|
+
sections.push("");
|
|
2670
|
+
sections.push(
|
|
2671
|
+
"When request input fails validation against the API schema, a `ParseError` is thrown:"
|
|
2672
|
+
);
|
|
2673
|
+
sections.push("");
|
|
2674
|
+
sections.push(
|
|
2675
|
+
createCodeBlock("typescript", [
|
|
2676
|
+
`import { ParseError } from "${this.#packageName}";`,
|
|
2677
|
+
"",
|
|
2678
|
+
"try {",
|
|
2679
|
+
" // Invalid input that doesn't match the expected schema",
|
|
2680
|
+
" const newUser = ",
|
|
2681
|
+
this.#toRequest(
|
|
2682
|
+
{
|
|
2683
|
+
method: "post",
|
|
2684
|
+
path: "/users",
|
|
2685
|
+
tag: "Users"
|
|
2686
|
+
},
|
|
2687
|
+
createObjectLiteral({
|
|
2688
|
+
email: 123,
|
|
2689
|
+
// should be string
|
|
2690
|
+
firstName: "",
|
|
2691
|
+
// empty required field
|
|
2692
|
+
age: -5
|
|
2693
|
+
// invalid age value
|
|
2694
|
+
})
|
|
2695
|
+
),
|
|
2696
|
+
"} catch (error) {",
|
|
2697
|
+
" if (error instanceof ParseError) {",
|
|
2698
|
+
' console.log("Input validation failed:");',
|
|
2699
|
+
" ",
|
|
2700
|
+
" // Field-level errors",
|
|
2701
|
+
" if (error.data.fieldErrors) {",
|
|
2702
|
+
" Object.entries(error.data.fieldErrors).forEach(([fieldName, validationIssues]) => {",
|
|
2703
|
+
' console.log(` ${fieldName}: ${validationIssues.map(issue => issue.message).join(", ")}`);',
|
|
2704
|
+
" });",
|
|
2705
|
+
" }",
|
|
2706
|
+
" ",
|
|
2707
|
+
" // Form-level errors",
|
|
2708
|
+
" if (error.data.formErrors.length > 0) {",
|
|
2709
|
+
' console.log(` Form errors: ${error.data.formErrors.map(issue => issue.message).join(", ")}`);',
|
|
2710
|
+
" }",
|
|
2711
|
+
" }",
|
|
2712
|
+
"}"
|
|
2713
|
+
])
|
|
2714
|
+
);
|
|
2715
|
+
sections.push("");
|
|
2716
|
+
sections.push(
|
|
2717
|
+
"`ParseError` contains detailed validation information using Zod's flattened error format, providing specific field-level and form-level validation messages."
|
|
2718
|
+
);
|
|
2719
|
+
sections.push("");
|
|
2720
|
+
sections.push("### Rate Limiting");
|
|
2721
|
+
sections.push("");
|
|
2722
|
+
sections.push(
|
|
2723
|
+
"Rate limit responses may include a `retryAfter` field indicating when to retry:"
|
|
2724
|
+
);
|
|
2725
|
+
sections.push("");
|
|
2726
|
+
sections.push(
|
|
2727
|
+
createCodeBlock("typescript", [
|
|
2728
|
+
`import { TooManyRequests } from "${this.#packageName}";`,
|
|
2729
|
+
"",
|
|
2730
|
+
"try {",
|
|
2731
|
+
" const apiResponse = ",
|
|
2732
|
+
this.#toRequest(
|
|
2733
|
+
{
|
|
2734
|
+
method: "get",
|
|
2735
|
+
path: "/api/data",
|
|
2736
|
+
tag: "Data"
|
|
2737
|
+
},
|
|
2738
|
+
createObjectLiteral({})
|
|
2739
|
+
),
|
|
2740
|
+
"} catch (error) {",
|
|
2741
|
+
" if (error instanceof TooManyRequests) {",
|
|
2742
|
+
" const retryAfterSeconds = error.data.retryAfter;",
|
|
2743
|
+
" if (retryAfterSeconds) {",
|
|
2744
|
+
" console.log(`Rate limited. Retry after: ${retryAfterSeconds} seconds`);",
|
|
2745
|
+
" // Implement your own retry logic",
|
|
2746
|
+
" setTimeout(() => {",
|
|
2747
|
+
" // Retry the request",
|
|
2748
|
+
" }, retryAfterSeconds * 1000);",
|
|
2749
|
+
" }",
|
|
2750
|
+
" }",
|
|
2751
|
+
"}"
|
|
2752
|
+
])
|
|
2753
|
+
);
|
|
2754
|
+
return sections.join("\n");
|
|
2755
|
+
}
|
|
2756
|
+
authenticationDocs() {
|
|
2757
|
+
const authOptions = this.#authentication();
|
|
2758
|
+
if (isEmpty4(authOptions)) {
|
|
2759
|
+
return "";
|
|
2760
|
+
}
|
|
2761
|
+
const sections = [];
|
|
2762
|
+
sections.push("## Authentication");
|
|
2763
|
+
sections.push("");
|
|
2764
|
+
if (authOptions.length === 1) {
|
|
2765
|
+
sections.push(
|
|
2766
|
+
"The SDK requires authentication to access the API. Configure your client with the required credentials:"
|
|
2767
|
+
);
|
|
2768
|
+
} else {
|
|
2769
|
+
sections.push("The SDK supports the following authentication methods:");
|
|
2770
|
+
}
|
|
2771
|
+
sections.push("");
|
|
2772
|
+
for (const authOption of authOptions) {
|
|
2773
|
+
const optionName = authOption["x-optionName"] ?? authOption.name;
|
|
2774
|
+
const isBearer = authOption.in === "header" && authOption.name === "authorization";
|
|
2775
|
+
const isApiKey = authOption.in === "header" && authOption.name !== "authorization";
|
|
2776
|
+
const isQueryParam = authOption.in === "query";
|
|
2777
|
+
const headingLevel = authOptions.length === 1 ? "###" : "###";
|
|
2778
|
+
if (isBearer) {
|
|
2779
|
+
const authenticationHeading = authOptions.length === 1 ? "Bearer Token" : "Bearer Token Authentication";
|
|
2780
|
+
sections.push(`${headingLevel} ${authenticationHeading}`);
|
|
2781
|
+
sections.push("");
|
|
2782
|
+
sections.push(
|
|
2783
|
+
'Pass your bearer token directly - the "Bearer" prefix is automatically added:'
|
|
2784
|
+
);
|
|
2785
|
+
sections.push("");
|
|
2786
|
+
const bearerAuthClient = this.#constructClient({
|
|
2787
|
+
[optionName]: "sk_live_51234567890abcdef1234567890abcdef"
|
|
2788
|
+
});
|
|
2789
|
+
sections.push(createCodeBlock("typescript", [bearerAuthClient.use]));
|
|
2790
|
+
sections.push("");
|
|
2791
|
+
} else if (isApiKey) {
|
|
2792
|
+
const apiKeyHeading = authOptions.length === 1 ? "API Key (Header)" : "API Key Authentication (Header)";
|
|
2793
|
+
sections.push(`${headingLevel} ${apiKeyHeading}`);
|
|
2794
|
+
sections.push("");
|
|
2795
|
+
const apiKeyAuthClient = this.#constructClient({
|
|
2796
|
+
[optionName]: "api_key_1234567890abcdef1234567890abcdef"
|
|
2797
|
+
});
|
|
2798
|
+
sections.push(createCodeBlock("typescript", [apiKeyAuthClient.use]));
|
|
2799
|
+
sections.push("");
|
|
2800
|
+
} else if (isQueryParam) {
|
|
2801
|
+
const queryParamHeading = authOptions.length === 1 ? "API Key (Query Parameter)" : "API Key Authentication (Query Parameter)";
|
|
2802
|
+
sections.push(`${headingLevel} ${queryParamHeading}`);
|
|
2803
|
+
sections.push("");
|
|
2804
|
+
const queryParamAuthClient = this.#constructClient({
|
|
2805
|
+
[optionName]: "qp_key_1234567890abcdef1234567890abcdef"
|
|
2806
|
+
});
|
|
2807
|
+
sections.push(
|
|
2808
|
+
createCodeBlock("typescript", [queryParamAuthClient.use])
|
|
2809
|
+
);
|
|
2810
|
+
sections.push("");
|
|
2811
|
+
} else {
|
|
2812
|
+
const genericAuthHeading = authOptions.length === 1 ? authOption.name : `${authOption.name} Authentication`;
|
|
2813
|
+
sections.push(`${headingLevel} ${genericAuthHeading}`);
|
|
2814
|
+
sections.push("");
|
|
2815
|
+
const genericAuthClient = this.#constructClient({
|
|
2816
|
+
[optionName]: "auth_token_1234567890abcdef1234567890abcdef"
|
|
2817
|
+
});
|
|
2818
|
+
sections.push(createCodeBlock("typescript", [genericAuthClient.use]));
|
|
2819
|
+
sections.push("");
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
return sections.join("\n");
|
|
2823
|
+
}
|
|
2824
|
+
generalUsageDocs() {
|
|
2825
|
+
const sections = [];
|
|
2826
|
+
return sections.join("\n");
|
|
2827
|
+
}
|
|
2828
|
+
configurationUpdateDocs() {
|
|
2829
|
+
const sections = [];
|
|
2830
|
+
const authOptions = this.#authentication();
|
|
2831
|
+
sections.push("### Updating Configuration");
|
|
2832
|
+
sections.push("");
|
|
2833
|
+
sections.push(
|
|
2834
|
+
"You can update client configuration after initialization using the `setOptions` method:"
|
|
2835
|
+
);
|
|
2836
|
+
sections.push("");
|
|
2837
|
+
const initialClientOptions = {
|
|
2838
|
+
baseUrl: "https://api.production-service.com"
|
|
2839
|
+
};
|
|
2840
|
+
if (!isEmpty4(authOptions)) {
|
|
2841
|
+
const [primaryAuth] = authOptions;
|
|
2842
|
+
const authOptionName = primaryAuth["x-optionName"] ?? primaryAuth.name;
|
|
2843
|
+
initialClientOptions[authOptionName] = "prod_sk_1234567890abcdef";
|
|
2844
|
+
}
|
|
2845
|
+
const initialClientSetup = this.#constructClient(initialClientOptions);
|
|
2846
|
+
const configurationUpdateCode = [
|
|
2847
|
+
"// Initial client setup",
|
|
2848
|
+
initialClientSetup.use,
|
|
2849
|
+
"",
|
|
2850
|
+
"// Later, update specific options",
|
|
2851
|
+
"client.setOptions({",
|
|
2852
|
+
" baseUrl: 'https://api.staging-service.com',"
|
|
2853
|
+
];
|
|
2854
|
+
if (!isEmpty4(authOptions)) {
|
|
2855
|
+
const [primaryAuth] = authOptions;
|
|
2856
|
+
const authOptionName = primaryAuth["x-optionName"] ?? primaryAuth.name;
|
|
2857
|
+
configurationUpdateCode.push(
|
|
2858
|
+
` ${authOptionName}: 'staging_sk_abcdef1234567890'`
|
|
2859
|
+
);
|
|
2860
|
+
}
|
|
2861
|
+
configurationUpdateCode.push("});");
|
|
2862
|
+
sections.push(createCodeBlock("typescript", configurationUpdateCode));
|
|
2863
|
+
sections.push("");
|
|
2864
|
+
sections.push(
|
|
2865
|
+
"The `setOptions` method validates the provided options and only updates the specified fields, leaving other configuration unchanged."
|
|
2866
|
+
);
|
|
2867
|
+
return sections.join("\n");
|
|
1965
2868
|
}
|
|
1966
2869
|
};
|
|
1967
|
-
function
|
|
1968
|
-
|
|
1969
|
-
|
|
2870
|
+
function createCodeBlock(language, content) {
|
|
2871
|
+
return ["```" + language, ...content, "```"].join("\n");
|
|
2872
|
+
}
|
|
2873
|
+
function createObjectLiteral(obj, indent = " ") {
|
|
2874
|
+
return "{\n" + Object.entries(obj).map(([key, value]) => `${indent}${key}: ${JSON.stringify(value)}`).join(",\n") + `
|
|
2875
|
+
${indent.slice(0, -1)}}`;
|
|
2876
|
+
}
|
|
2877
|
+
function availablePaginationTypes(spec) {
|
|
2878
|
+
let offset = false;
|
|
2879
|
+
let page = false;
|
|
2880
|
+
let cursor = false;
|
|
2881
|
+
forEachOperation3(spec, (entry, operation) => {
|
|
2882
|
+
if (operation["x-pagination"]) {
|
|
2883
|
+
switch (operation["x-pagination"].type) {
|
|
2884
|
+
case "offset":
|
|
2885
|
+
offset = true;
|
|
2886
|
+
break;
|
|
2887
|
+
case "page":
|
|
2888
|
+
page = true;
|
|
2889
|
+
break;
|
|
2890
|
+
case "cursor":
|
|
2891
|
+
cursor = true;
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
});
|
|
2896
|
+
return {
|
|
2897
|
+
offset,
|
|
2898
|
+
page,
|
|
2899
|
+
cursor
|
|
2900
|
+
};
|
|
1970
2901
|
}
|
|
1971
2902
|
|
|
1972
2903
|
// packages/typescript/src/lib/generate.ts
|
|
@@ -2203,13 +3134,13 @@ ${template2(dispatcher_default, {})({ throwError: !style.errorAsValue, outputTyp
|
|
|
2203
3134
|
)
|
|
2204
3135
|
}
|
|
2205
3136
|
};
|
|
2206
|
-
if (settings.readme) {
|
|
2207
|
-
configFiles["README.md"] = toReadme(spec, {
|
|
2208
|
-
generateSnippet: (...args) => generator.snippet(...args)
|
|
2209
|
-
});
|
|
2210
|
-
}
|
|
2211
3137
|
await settings.writer(settings.output, configFiles);
|
|
2212
3138
|
}
|
|
3139
|
+
if (settings.readme) {
|
|
3140
|
+
await settings.writer(settings.mode === "full" ? settings.output : output, {
|
|
3141
|
+
"README.md": toReadme(spec, generator)
|
|
3142
|
+
});
|
|
3143
|
+
}
|
|
2213
3144
|
await settings.formatCode?.({
|
|
2214
3145
|
output,
|
|
2215
3146
|
env: npmRunPathEnv()
|
|
@@ -2300,7 +3231,6 @@ function toInputs(operationsSet, commonZod, makeImport) {
|
|
|
2300
3231
|
export {
|
|
2301
3232
|
TypeScriptGenerator,
|
|
2302
3233
|
generate,
|
|
2303
|
-
generateSnippet,
|
|
2304
3234
|
toInputs
|
|
2305
3235
|
};
|
|
2306
3236
|
//# sourceMappingURL=index.js.map
|