typed-openapi 0.10.0 → 1.0.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/LICENSE +7 -0
- package/dist/{chunk-WT2PCM73.js → chunk-STLPDNLW.js} +146 -62
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +23 -16
- package/dist/index.d.ts +21 -13
- package/dist/index.js +3 -1
- package/package.json +15 -16
- package/src/asserts.ts +2 -2
- package/src/box-factory.ts +4 -4
- package/src/box.ts +4 -4
- package/src/cli.ts +25 -10
- package/src/format.ts +1 -1
- package/src/generator.ts +71 -75
- package/src/index.ts +8 -7
- package/src/map-openapi-endpoints.ts +10 -10
- package/src/openapi-schema-to-ts.ts +17 -13
- package/src/ref-resolver.ts +11 -11
- package/src/tanstack-query.generator.ts +103 -0
- package/src/ts-factory.ts +3 -3
- package/src/types.ts +6 -3
- package/dist/cli.cjs +0 -897
- package/dist/cli.d.cts +0 -2
- package/dist/index.cjs +0 -889
- package/dist/index.d.cts +0 -268
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2025 Alexandre Stahmer
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -15,8 +15,7 @@ function normalizeString(text) {
|
|
|
15
15
|
}
|
|
16
16
|
var onlyWordRegex = /^\w+$/;
|
|
17
17
|
var wrapWithQuotesIfNeeded = (str) => {
|
|
18
|
-
if (str[0] === '"' && str[str.length - 1] === '"')
|
|
19
|
-
return str;
|
|
18
|
+
if (str[0] === '"' && str[str.length - 1] === '"') return str;
|
|
20
19
|
if (onlyWordRegex.test(str)) {
|
|
21
20
|
return str;
|
|
22
21
|
}
|
|
@@ -52,7 +51,7 @@ var openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }) => {
|
|
|
52
51
|
return t.union(schema.type.map((prop) => openApiSchemaToTs({ schema: { ...schema, type: prop }, ctx, meta })));
|
|
53
52
|
}
|
|
54
53
|
if (schema.type === "null") {
|
|
55
|
-
return t.
|
|
54
|
+
return t.literal("null");
|
|
56
55
|
}
|
|
57
56
|
if (schema.oneOf) {
|
|
58
57
|
if (schema.oneOf.length === 1) {
|
|
@@ -89,14 +88,10 @@ var openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }) => {
|
|
|
89
88
|
}
|
|
90
89
|
return t.union(schema.enum.map((value) => t.literal(value === null ? "null" : value)));
|
|
91
90
|
}
|
|
92
|
-
if (schemaType === "string")
|
|
93
|
-
|
|
94
|
-
if (schemaType === "
|
|
95
|
-
|
|
96
|
-
if (schemaType === "number" || schemaType === "integer")
|
|
97
|
-
return t.number();
|
|
98
|
-
if (schemaType === "null")
|
|
99
|
-
return t.reference("null");
|
|
91
|
+
if (schemaType === "string") return t.string();
|
|
92
|
+
if (schemaType === "boolean") return t.boolean();
|
|
93
|
+
if (schemaType === "number" || schemaType === "integer") return t.number();
|
|
94
|
+
if (schemaType === "null") return t.literal("null");
|
|
100
95
|
}
|
|
101
96
|
if (schemaType === "array") {
|
|
102
97
|
if (schema.items) {
|
|
@@ -110,7 +105,11 @@ var openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }) => {
|
|
|
110
105
|
}
|
|
111
106
|
if (schemaType === "object" || schema.properties || schema.additionalProperties) {
|
|
112
107
|
if (!schema.properties) {
|
|
113
|
-
|
|
108
|
+
if (schema.additionalProperties && !isReferenceObject(schema.additionalProperties) && typeof schema.additionalProperties !== "boolean" && schema.additionalProperties.type) {
|
|
109
|
+
const valueSchema = openApiSchemaToTs({ schema: schema.additionalProperties, ctx, meta });
|
|
110
|
+
return t.literal(`Record<string, ${valueSchema.value}>`);
|
|
111
|
+
}
|
|
112
|
+
return t.literal("Record<string, unknown>");
|
|
114
113
|
}
|
|
115
114
|
let additionalProperties;
|
|
116
115
|
if (schema.additionalProperties) {
|
|
@@ -142,14 +141,13 @@ var openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }) => {
|
|
|
142
141
|
const objectType = additionalProperties ? t.intersection([t.object(props), additionalProperties]) : t.object(props);
|
|
143
142
|
return isPartial ? t.reference("Partial", [objectType]) : objectType;
|
|
144
143
|
}
|
|
145
|
-
if (!schemaType)
|
|
146
|
-
return t.unknown();
|
|
144
|
+
if (!schemaType) return t.unknown();
|
|
147
145
|
throw new Error(`Unsupported schema type: ${schemaType}`);
|
|
148
146
|
};
|
|
149
147
|
let output = getTs();
|
|
150
148
|
if (!isReferenceObject(schema)) {
|
|
151
149
|
if (schema.nullable) {
|
|
152
|
-
output = t.union([output, t.
|
|
150
|
+
output = t.union([output, t.literal("null")]);
|
|
153
151
|
}
|
|
154
152
|
}
|
|
155
153
|
return output;
|
|
@@ -254,26 +252,6 @@ var createBoxFactory = (schema, ctx) => {
|
|
|
254
252
|
|
|
255
253
|
// src/generator.ts
|
|
256
254
|
import { capitalize as capitalize2, groupBy } from "pastable/server";
|
|
257
|
-
|
|
258
|
-
// src/format.ts
|
|
259
|
-
import prettier from "prettier";
|
|
260
|
-
import parserTypescript from "prettier/parser-typescript";
|
|
261
|
-
function maybePretty(input, options) {
|
|
262
|
-
try {
|
|
263
|
-
return prettier.format(input, {
|
|
264
|
-
parser: "typescript",
|
|
265
|
-
plugins: [parserTypescript],
|
|
266
|
-
...options
|
|
267
|
-
});
|
|
268
|
-
} catch (err) {
|
|
269
|
-
console.warn("Failed to format code");
|
|
270
|
-
console.warn(err);
|
|
271
|
-
return input;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
var prettify = (str, options) => maybePretty(str, { printWidth: 120, trailingComma: "all", ...options });
|
|
275
|
-
|
|
276
|
-
// src/generator.ts
|
|
277
255
|
import * as Codegen from "@sinclair/typebox-codegen";
|
|
278
256
|
import { match } from "ts-pattern";
|
|
279
257
|
import { type } from "arktype";
|
|
@@ -328,7 +306,7 @@ var generateFile = (options) => {
|
|
|
328
306
|
${transform(schemaList + endpointSchemaList)}
|
|
329
307
|
${apiClient}
|
|
330
308
|
`;
|
|
331
|
-
return
|
|
309
|
+
return file;
|
|
332
310
|
};
|
|
333
311
|
var generateSchemaList = ({ refs, runtime }) => {
|
|
334
312
|
let file = `
|
|
@@ -336,10 +314,8 @@ var generateSchemaList = ({ refs, runtime }) => {
|
|
|
336
314
|
// <Schemas>
|
|
337
315
|
`;
|
|
338
316
|
refs.getOrderedSchemas().forEach(([schema, infos]) => {
|
|
339
|
-
if (!infos?.name)
|
|
340
|
-
|
|
341
|
-
if (infos.kind !== "schemas")
|
|
342
|
-
return;
|
|
317
|
+
if (!infos?.name) return;
|
|
318
|
+
if (infos.kind !== "schemas") return;
|
|
343
319
|
file += `export type ${infos.normalized} = ${schema.value}
|
|
344
320
|
`;
|
|
345
321
|
});
|
|
@@ -349,8 +325,7 @@ var generateSchemaList = ({ refs, runtime }) => {
|
|
|
349
325
|
`;
|
|
350
326
|
};
|
|
351
327
|
var parameterObjectToString = (parameters) => {
|
|
352
|
-
if (parameters instanceof Box)
|
|
353
|
-
return parameters.value;
|
|
328
|
+
if (parameters instanceof Box) return parameters.value;
|
|
354
329
|
let str = "{";
|
|
355
330
|
for (const [key, box] of Object.entries(parameters)) {
|
|
356
331
|
str += `${wrapWithQuotesIfNeeded(key)}: ${box.value},
|
|
@@ -384,7 +359,7 @@ var generateEndpointSchemaList = (ctx) => {
|
|
|
384
359
|
)},` : ""}
|
|
385
360
|
}` : "parameters: never,"}
|
|
386
361
|
response: ${ctx.runtime === "none" ? endpoint.response.recompute((box) => {
|
|
387
|
-
if (Box.isReference(box) && !box.params.generics) {
|
|
362
|
+
if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
|
|
388
363
|
box.value = `Schemas.${box.value}`;
|
|
389
364
|
}
|
|
390
365
|
return box;
|
|
@@ -525,8 +500,7 @@ import { get } from "pastable/server";
|
|
|
525
500
|
function topologicalSort(graph) {
|
|
526
501
|
const sorted = [], visited = {};
|
|
527
502
|
function visit(name, ancestors) {
|
|
528
|
-
if (!Array.isArray(ancestors))
|
|
529
|
-
ancestors = [];
|
|
503
|
+
if (!Array.isArray(ancestors)) ancestors = [];
|
|
530
504
|
ancestors.push(name);
|
|
531
505
|
visited[name] = true;
|
|
532
506
|
const deps = graph.get(name);
|
|
@@ -535,13 +509,11 @@ function topologicalSort(graph) {
|
|
|
535
509
|
if (ancestors.includes(dep)) {
|
|
536
510
|
return;
|
|
537
511
|
}
|
|
538
|
-
if (visited[dep])
|
|
539
|
-
return;
|
|
512
|
+
if (visited[dep]) return;
|
|
540
513
|
visit(dep, ancestors.slice(0));
|
|
541
514
|
});
|
|
542
515
|
}
|
|
543
|
-
if (!sorted.includes(name))
|
|
544
|
-
sorted.push(name);
|
|
516
|
+
if (!sorted.includes(name)) sorted.push(name);
|
|
545
517
|
}
|
|
546
518
|
graph.forEach((_, name) => visit(name, []));
|
|
547
519
|
return sorted;
|
|
@@ -620,8 +592,7 @@ var createRefResolver = (doc, factory2) => {
|
|
|
620
592
|
};
|
|
621
593
|
var setSchemaDependencies = (schema, deps) => {
|
|
622
594
|
const visit = (schema2) => {
|
|
623
|
-
if (!schema2)
|
|
624
|
-
return;
|
|
595
|
+
if (!schema2) return;
|
|
625
596
|
if (isReferenceObject(schema2)) {
|
|
626
597
|
deps.add(schema2.$ref);
|
|
627
598
|
return;
|
|
@@ -645,8 +616,7 @@ var setSchemaDependencies = (schema, deps) => {
|
|
|
645
616
|
return;
|
|
646
617
|
}
|
|
647
618
|
if (schema2.type === "array") {
|
|
648
|
-
if (!schema2.items)
|
|
649
|
-
return;
|
|
619
|
+
if (!schema2.items) return;
|
|
650
620
|
return void visit(schema2.items);
|
|
651
621
|
}
|
|
652
622
|
if (schema2.type === "object" || schema2.properties || schema2.additionalProperties) {
|
|
@@ -675,8 +645,7 @@ var getTransitiveDependencies = (directDependencies) => {
|
|
|
675
645
|
if (deps2 && ref !== depRef) {
|
|
676
646
|
deps2.forEach((transitive) => {
|
|
677
647
|
const key = ref + "__" + transitive;
|
|
678
|
-
if (visitedsDeepRefs.has(key))
|
|
679
|
-
return;
|
|
648
|
+
if (visitedsDeepRefs.has(key)) return;
|
|
680
649
|
visitedsDeepRefs.add(key);
|
|
681
650
|
visit(transitive);
|
|
682
651
|
});
|
|
@@ -722,8 +691,7 @@ var mapOpenApiEndpoints = (doc) => {
|
|
|
722
691
|
Object.entries(doc.paths ?? {}).forEach(([path, pathItemObj]) => {
|
|
723
692
|
const pathItem = pick(pathItemObj, ["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
724
693
|
Object.entries(pathItem).forEach(([method, operation]) => {
|
|
725
|
-
if (operation.deprecated)
|
|
726
|
-
return;
|
|
694
|
+
if (operation.deprecated) return;
|
|
727
695
|
const endpoint = {
|
|
728
696
|
operation,
|
|
729
697
|
method,
|
|
@@ -741,8 +709,7 @@ var mapOpenApiEndpoints = (doc) => {
|
|
|
741
709
|
(acc, paramOrRef) => {
|
|
742
710
|
const param = refs.unwrap(paramOrRef);
|
|
743
711
|
const schema = openApiSchemaToTs({ schema: refs.unwrap(param.schema ?? {}), ctx });
|
|
744
|
-
if (param.required)
|
|
745
|
-
endpoint.meta.areParametersRequired = true;
|
|
712
|
+
if (param.required) endpoint.meta.areParametersRequired = true;
|
|
746
713
|
endpoint.meta.hasParameters = true;
|
|
747
714
|
if (param.in === "query") {
|
|
748
715
|
lists.query.push(param);
|
|
@@ -798,13 +765,13 @@ var mapOpenApiEndpoints = (doc) => {
|
|
|
798
765
|
endpoint.parameters = Object.keys(params).length ? params : void 0;
|
|
799
766
|
}
|
|
800
767
|
let responseObject;
|
|
801
|
-
Object.entries(operation.responses).map(([status, responseOrRef]) => {
|
|
768
|
+
Object.entries(operation.responses ?? {}).map(([status, responseOrRef]) => {
|
|
802
769
|
const statusCode = Number(status);
|
|
803
770
|
if (statusCode >= 200 && statusCode < 300) {
|
|
804
771
|
responseObject = refs.unwrap(responseOrRef);
|
|
805
772
|
}
|
|
806
773
|
});
|
|
807
|
-
if (!responseObject && operation.responses
|
|
774
|
+
if (!responseObject && operation.responses?.default) {
|
|
808
775
|
responseObject = refs.unwrap(operation.responses.default);
|
|
809
776
|
}
|
|
810
777
|
const content = responseObject?.content;
|
|
@@ -832,6 +799,121 @@ var isAllowedParamMediaTypes = (mediaType) => mediaType.includes("application/")
|
|
|
832
799
|
var isResponseMediaType = (mediaType) => mediaType === "application/json";
|
|
833
800
|
var getAlias = ({ path, method, operation }) => (method + "_" + capitalize3(operation.operationId ?? pathToVariableName(path))).replace(/-/g, "__");
|
|
834
801
|
|
|
802
|
+
// src/format.ts
|
|
803
|
+
import prettier from "prettier";
|
|
804
|
+
import parserTypescript from "prettier/parser-typescript";
|
|
805
|
+
function maybePretty(input, options) {
|
|
806
|
+
try {
|
|
807
|
+
return prettier.format(input, {
|
|
808
|
+
parser: "typescript",
|
|
809
|
+
plugins: [parserTypescript],
|
|
810
|
+
...options
|
|
811
|
+
});
|
|
812
|
+
} catch (err) {
|
|
813
|
+
console.warn("Failed to format code");
|
|
814
|
+
console.warn(err);
|
|
815
|
+
return input;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
var prettify = (str, options) => maybePretty(str, { printWidth: 120, trailingComma: "all", ...options });
|
|
819
|
+
|
|
820
|
+
// src/tanstack-query.generator.ts
|
|
821
|
+
import { capitalize as capitalize4 } from "pastable/server";
|
|
822
|
+
var generateTanstackQueryFile = async (ctx) => {
|
|
823
|
+
const endpointMethods = new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase()));
|
|
824
|
+
const file = `
|
|
825
|
+
import { queryOptions, QueryClient } from "@tanstack/react-query"
|
|
826
|
+
import type { EndpointByMethod, ApiClient } from "${ctx.relativeApiClientPath}"
|
|
827
|
+
|
|
828
|
+
type EndpointQueryKey<TOptions extends EndpointParameters> = [
|
|
829
|
+
TOptions & {
|
|
830
|
+
_id: string;
|
|
831
|
+
_infinite?: boolean;
|
|
832
|
+
}
|
|
833
|
+
];
|
|
834
|
+
|
|
835
|
+
const createQueryKey = <TOptions extends EndpointParameters>(id: string, options?: TOptions, infinite?: boolean): [
|
|
836
|
+
EndpointQueryKey<TOptions>[0]
|
|
837
|
+
] => {
|
|
838
|
+
const params: EndpointQueryKey<TOptions>[0] = { _id: id, } as EndpointQueryKey<TOptions>[0];
|
|
839
|
+
if (infinite) {
|
|
840
|
+
params._infinite = infinite;
|
|
841
|
+
}
|
|
842
|
+
if (options?.body) {
|
|
843
|
+
params.body = options.body;
|
|
844
|
+
}
|
|
845
|
+
if (options?.header) {
|
|
846
|
+
params.header = options.header;
|
|
847
|
+
}
|
|
848
|
+
if (options?.path) {
|
|
849
|
+
params.path = options.path;
|
|
850
|
+
}
|
|
851
|
+
if (options?.query) {
|
|
852
|
+
params.query = options.query;
|
|
853
|
+
}
|
|
854
|
+
return [
|
|
855
|
+
params
|
|
856
|
+
];
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// <EndpointByMethod.Shorthands>
|
|
860
|
+
${Array.from(endpointMethods).map((method) => `export type ${capitalize4(method)}Endpoints = EndpointByMethod["${method}"];`).join("\n")}
|
|
861
|
+
// </EndpointByMethod.Shorthands>
|
|
862
|
+
|
|
863
|
+
// <ApiClientTypes>
|
|
864
|
+
export type EndpointParameters = {
|
|
865
|
+
body?: unknown;
|
|
866
|
+
query?: Record<string, unknown>;
|
|
867
|
+
header?: Record<string, unknown>;
|
|
868
|
+
path?: Record<string, unknown>;
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
type RequiredKeys<T> = {
|
|
872
|
+
[P in keyof T]-?: undefined extends T[P] ? never : P;
|
|
873
|
+
}[keyof T];
|
|
874
|
+
|
|
875
|
+
type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T];
|
|
876
|
+
|
|
877
|
+
// </ApiClientTypes>
|
|
878
|
+
|
|
879
|
+
// <ApiClient>
|
|
880
|
+
export class TanstackQueryApiClient {
|
|
881
|
+
constructor(public client: ApiClient) { }
|
|
882
|
+
|
|
883
|
+
${Array.from(endpointMethods).map((method) => `
|
|
884
|
+
// <ApiClient.${method}>
|
|
885
|
+
${method}<Path extends keyof ${capitalize4(method)}Endpoints, TEndpoint extends ${capitalize4(method)}Endpoints[Path]>(
|
|
886
|
+
path: Path,
|
|
887
|
+
...params: MaybeOptionalArg<TEndpoint["parameters"]>
|
|
888
|
+
) {
|
|
889
|
+
const queryKey = createQueryKey(path, params[0]);
|
|
890
|
+
const query = {
|
|
891
|
+
endpoint: {} as TEndpoint,
|
|
892
|
+
res: {} as TEndpoint["response"],
|
|
893
|
+
queryKey,
|
|
894
|
+
options: queryOptions({
|
|
895
|
+
queryFn: async ({ queryKey, signal, }) => {
|
|
896
|
+
const res = await this.client.${method}(path, {
|
|
897
|
+
...params,
|
|
898
|
+
...queryKey[0],
|
|
899
|
+
signal,
|
|
900
|
+
throwOnError: true
|
|
901
|
+
});
|
|
902
|
+
return res as TEndpoint["response"];
|
|
903
|
+
},
|
|
904
|
+
queryKey: queryKey
|
|
905
|
+
}),
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
return query
|
|
909
|
+
}
|
|
910
|
+
// </ApiClient.get>
|
|
911
|
+
`).join("\n")}
|
|
912
|
+
}
|
|
913
|
+
`;
|
|
914
|
+
return prettify(file);
|
|
915
|
+
};
|
|
916
|
+
|
|
835
917
|
export {
|
|
836
918
|
unwrap,
|
|
837
919
|
createFactory,
|
|
@@ -841,5 +923,7 @@ export {
|
|
|
841
923
|
generateFile,
|
|
842
924
|
createRefResolver,
|
|
843
925
|
tsFactory,
|
|
844
|
-
mapOpenApiEndpoints
|
|
926
|
+
mapOpenApiEndpoints,
|
|
927
|
+
prettify,
|
|
928
|
+
generateTanstackQueryFile
|
|
845
929
|
};
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
|
|
2
|
-
export {
|
|
2
|
+
export { }
|
package/dist/cli.js
CHANGED
|
@@ -1,41 +1,48 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allowedRuntimes,
|
|
3
3
|
generateFile,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
generateTanstackQueryFile,
|
|
5
|
+
mapOpenApiEndpoints,
|
|
6
|
+
prettify
|
|
7
|
+
} from "./chunk-STLPDNLW.js";
|
|
6
8
|
|
|
7
9
|
// src/cli.ts
|
|
8
10
|
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
9
11
|
import { cac } from "cac";
|
|
10
|
-
import { join } from "pathe";
|
|
12
|
+
import { basename, join } from "pathe";
|
|
11
13
|
import { type } from "arktype";
|
|
12
14
|
import { writeFile } from "fs/promises";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
var name = "typed-openapi";
|
|
16
|
-
var version = "0.10.0";
|
|
17
|
-
|
|
18
|
-
// src/cli.ts
|
|
15
|
+
import { readFileSync } from "fs";
|
|
16
|
+
var { name, version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
19
17
|
var cwd = process.cwd();
|
|
20
18
|
var cli = cac(name);
|
|
21
19
|
var now = /* @__PURE__ */ new Date();
|
|
22
|
-
var optionsSchema = type({ "output?": "string", runtime: allowedRuntimes });
|
|
20
|
+
var optionsSchema = type({ "output?": "string", runtime: allowedRuntimes, tanstack: "boolean | string" });
|
|
23
21
|
cli.command("<input>", "Generate").option("-o, --output <path>", "Output path for the api client ts file (defaults to `<input>.<runtime>.ts`)").option(
|
|
24
22
|
"-r, --runtime <name>",
|
|
25
|
-
`Runtime to use for validation; defaults to \`none\`; available: ${allowedRuntimes.
|
|
23
|
+
`Runtime to use for validation; defaults to \`none\`; available: ${allowedRuntimes.toString()}`,
|
|
26
24
|
{ default: "none" }
|
|
27
|
-
).action(async (input, _options) => {
|
|
25
|
+
).option("--tanstack [name]", "Generate tanstack client, defaults to false, can optionally specify a name for the generated file").action(async (input, _options) => {
|
|
28
26
|
const options = optionsSchema.assert(_options);
|
|
29
27
|
const openApiDoc = await SwaggerParser.bundle(input);
|
|
30
28
|
const ctx = mapOpenApiEndpoints(openApiDoc);
|
|
31
29
|
console.log(`Found ${ctx.endpointList.length} endpoints`);
|
|
32
|
-
const content = generateFile({ ...ctx, runtime: options.runtime });
|
|
33
|
-
const
|
|
30
|
+
const content = await prettify(generateFile({ ...ctx, runtime: options.runtime }));
|
|
31
|
+
const outputPath = join(
|
|
34
32
|
cwd,
|
|
35
33
|
options.output ?? input + `.${options.runtime === "none" ? "client" : options.runtime}.ts`
|
|
36
34
|
);
|
|
37
|
-
console.log("Generating...",
|
|
38
|
-
await writeFile(
|
|
35
|
+
console.log("Generating client...", outputPath);
|
|
36
|
+
await writeFile(outputPath, content);
|
|
37
|
+
if (options.tanstack) {
|
|
38
|
+
const tanstackContent = await generateTanstackQueryFile({
|
|
39
|
+
...ctx,
|
|
40
|
+
relativeApiClientPath: "./" + basename(outputPath)
|
|
41
|
+
});
|
|
42
|
+
const tanstackOutputPath = join(cwd, typeof options.tanstack === "string" ? options.tanstack : `tanstack.client.ts`);
|
|
43
|
+
console.log("Generating tanstack client...", tanstackOutputPath);
|
|
44
|
+
await writeFile(tanstackOutputPath, tanstackContent);
|
|
45
|
+
}
|
|
39
46
|
console.log(`Done in ${(/* @__PURE__ */ new Date()).getTime() - now.getTime()}ms !`);
|
|
40
47
|
});
|
|
41
48
|
cli.help();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as openapi3_ts_oas31 from 'openapi3-ts/oas31';
|
|
2
|
-
import { OpenAPIObject,
|
|
3
|
-
import
|
|
2
|
+
import { OpenAPIObject, ReferenceObject, SchemaObject, OperationObject } from 'openapi3-ts/oas31';
|
|
3
|
+
import { SchemaObject as SchemaObject$1 } from 'openapi3-ts/oas30';
|
|
4
|
+
import * as arktype_internal_methods_string_ts from 'arktype/internal/methods/string.ts';
|
|
4
5
|
import * as Codegen from '@sinclair/typebox-codegen';
|
|
5
6
|
|
|
6
7
|
declare class Box<T extends AnyBoxDef = AnyBoxDef> {
|
|
@@ -44,8 +45,8 @@ type RefInfo = {
|
|
|
44
45
|
kind: "schemas" | "responses" | "parameters" | "requestBodies" | "headers";
|
|
45
46
|
};
|
|
46
47
|
declare const createRefResolver: (doc: OpenAPIObject, factory: GenericFactory) => {
|
|
47
|
-
get: <T =
|
|
48
|
-
unwrap: <
|
|
48
|
+
get: <T = LibSchemaObject>(ref: string) => NonNullable<T>;
|
|
49
|
+
unwrap: <T extends ReferenceObject | {}>(component: T) => Exclude<T, ReferenceObject>;
|
|
49
50
|
getInfosByRef: (ref: string) => RefInfo;
|
|
50
51
|
infos: Map<string, RefInfo>;
|
|
51
52
|
/**
|
|
@@ -59,6 +60,7 @@ declare const createRefResolver: (doc: OpenAPIObject, factory: GenericFactory) =
|
|
|
59
60
|
interface RefResolver extends ReturnType<typeof createRefResolver> {
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
type LibSchemaObject = SchemaObject & SchemaObject$1;
|
|
62
64
|
type BoxDefinition = {
|
|
63
65
|
type: string;
|
|
64
66
|
params: unknown;
|
|
@@ -66,7 +68,7 @@ type BoxDefinition = {
|
|
|
66
68
|
};
|
|
67
69
|
type BoxParams = string | BoxDefinition;
|
|
68
70
|
type WithSchema = {
|
|
69
|
-
schema:
|
|
71
|
+
schema: LibSchemaObject | ReferenceObject | undefined;
|
|
70
72
|
ctx: OpenapiSchemaConvertContext;
|
|
71
73
|
};
|
|
72
74
|
type BoxUnion = WithSchema & {
|
|
@@ -171,17 +173,17 @@ type GenericFactory = {
|
|
|
171
173
|
};
|
|
172
174
|
|
|
173
175
|
declare const unwrap: (param: StringOrBox) => string;
|
|
174
|
-
declare const createFactory: <T extends
|
|
176
|
+
declare const createFactory: <T extends OpenapiSchemaConvertContext["factory"]>(f: T) => T;
|
|
175
177
|
/**
|
|
176
178
|
* Create a box-factory using your schema provider and automatically add the input schema to each box.
|
|
177
179
|
*/
|
|
178
|
-
declare const createBoxFactory: (schema:
|
|
180
|
+
declare const createBoxFactory: (schema: LibSchemaObject | ReferenceObject, ctx: OpenapiSchemaConvertContext) => BoxFactory;
|
|
179
181
|
|
|
180
182
|
declare const mapOpenApiEndpoints: (doc: OpenAPIObject) => {
|
|
181
183
|
doc: OpenAPIObject;
|
|
182
184
|
refs: {
|
|
183
|
-
get: <T =
|
|
184
|
-
unwrap: <
|
|
185
|
+
get: <T = LibSchemaObject>(ref: string) => NonNullable<T>;
|
|
186
|
+
unwrap: <T extends openapi3_ts_oas31.ReferenceObject | {}>(component: T) => Exclude<T, openapi3_ts_oas31.ReferenceObject>;
|
|
185
187
|
getInfosByRef: (ref: string) => RefInfo;
|
|
186
188
|
infos: Map<string, RefInfo>;
|
|
187
189
|
getOrderedSchemas: () => [schema: Box<AnyBoxDef>, infos: RefInfo][];
|
|
@@ -232,10 +234,10 @@ type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
|
|
|
232
234
|
response: TConfig["response"];
|
|
233
235
|
};
|
|
234
236
|
|
|
235
|
-
type GeneratorOptions = ReturnType<typeof mapOpenApiEndpoints> & {
|
|
237
|
+
type GeneratorOptions$1 = ReturnType<typeof mapOpenApiEndpoints> & {
|
|
236
238
|
runtime?: "none" | keyof typeof runtimeValidationGenerator;
|
|
237
239
|
};
|
|
238
|
-
declare const allowedRuntimes:
|
|
240
|
+
declare const allowedRuntimes: arktype_internal_methods_string_ts.StringType<"none" | "arktype" | "io-ts" | "typebox" | "valibot" | "yup" | "zod", {}>;
|
|
239
241
|
type OutputRuntime = typeof allowedRuntimes.infer;
|
|
240
242
|
declare const runtimeValidationGenerator: {
|
|
241
243
|
arktype: typeof Codegen.ModelToArkType.Generate;
|
|
@@ -245,7 +247,13 @@ declare const runtimeValidationGenerator: {
|
|
|
245
247
|
yup: typeof Codegen.ModelToYup.Generate;
|
|
246
248
|
zod: typeof Codegen.ModelToZod.Generate;
|
|
247
249
|
};
|
|
248
|
-
declare const generateFile: (options: GeneratorOptions) => string;
|
|
250
|
+
declare const generateFile: (options: GeneratorOptions$1) => string;
|
|
251
|
+
|
|
252
|
+
type GeneratorOptions = ReturnType<typeof mapOpenApiEndpoints>;
|
|
253
|
+
type GeneratorContext = Required<GeneratorOptions>;
|
|
254
|
+
declare const generateTanstackQueryFile: (ctx: GeneratorContext & {
|
|
255
|
+
relativeApiClientPath: string;
|
|
256
|
+
}) => Promise<string>;
|
|
249
257
|
|
|
250
258
|
declare const openApiSchemaToTs: ({ schema, meta: _inheritedMeta, ctx }: OpenapiSchemaConvertArgs) => Box<AnyBoxDef>;
|
|
251
259
|
|
|
@@ -265,4 +273,4 @@ declare const tsFactory: {
|
|
|
265
273
|
object: (props: Record<string, StringOrBox>) => string;
|
|
266
274
|
};
|
|
267
275
|
|
|
268
|
-
export { AnyBox, AnyBoxDef, BoxArray, BoxDefinition, BoxFactory, BoxIntersection, BoxKeyword, BoxLiteral, BoxObject, BoxOptional, BoxParams, BoxRef, BoxUnion, Endpoint, EndpointParameters, FactoryCreator, GenericFactory, OpenapiSchemaConvertArgs, OpenapiSchemaConvertContext, OutputRuntime, RefInfo, RefResolver, StringOrBox, WithSchema, createBoxFactory, createFactory, createRefResolver, generateFile, mapOpenApiEndpoints, openApiSchemaToTs, tsFactory, unwrap };
|
|
276
|
+
export { type AnyBox, type AnyBoxDef, type BoxArray, type BoxDefinition, type BoxFactory, type BoxIntersection, type BoxKeyword, type BoxLiteral, type BoxObject, type BoxOptional, type BoxParams, type BoxRef, type BoxUnion, type Endpoint, type EndpointParameters, type FactoryCreator, type GenericFactory, type LibSchemaObject, type OpenapiSchemaConvertArgs, type OpenapiSchemaConvertContext, type OutputRuntime, type RefInfo, type RefResolver, type StringOrBox, type WithSchema, createBoxFactory, createFactory, createRefResolver, generateFile, generateTanstackQueryFile, mapOpenApiEndpoints, openApiSchemaToTs, tsFactory, unwrap };
|
package/dist/index.js
CHANGED
|
@@ -3,16 +3,18 @@ import {
|
|
|
3
3
|
createFactory,
|
|
4
4
|
createRefResolver,
|
|
5
5
|
generateFile,
|
|
6
|
+
generateTanstackQueryFile,
|
|
6
7
|
mapOpenApiEndpoints,
|
|
7
8
|
openApiSchemaToTs,
|
|
8
9
|
tsFactory,
|
|
9
10
|
unwrap
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-STLPDNLW.js";
|
|
11
12
|
export {
|
|
12
13
|
createBoxFactory,
|
|
13
14
|
createFactory,
|
|
14
15
|
createRefResolver,
|
|
15
16
|
generateFile,
|
|
17
|
+
generateTanstackQueryFile,
|
|
16
18
|
mapOpenApiEndpoints,
|
|
17
19
|
openApiSchemaToTs,
|
|
18
20
|
tsFactory,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typed-openapi",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -13,24 +13,23 @@
|
|
|
13
13
|
"directory": "packages/typed-openapi"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@apidevtools/swagger-parser": "^10.1.
|
|
17
|
-
"@
|
|
18
|
-
"
|
|
19
|
-
"arktype": "1.0.18-alpha",
|
|
16
|
+
"@apidevtools/swagger-parser": "^10.1.1",
|
|
17
|
+
"@sinclair/typebox-codegen": "^0.11.1",
|
|
18
|
+
"arktype": "2.1.20",
|
|
20
19
|
"cac": "^6.7.14",
|
|
21
|
-
"openapi3-ts": "^4.
|
|
20
|
+
"openapi3-ts": "^4.4.0",
|
|
22
21
|
"pastable": "^2.2.1",
|
|
23
|
-
"pathe": "^
|
|
24
|
-
"prettier": "
|
|
25
|
-
"ts-pattern": "^5.0
|
|
22
|
+
"pathe": "^2.0.3",
|
|
23
|
+
"prettier": "3.5.3",
|
|
24
|
+
"ts-pattern": "^5.7.0"
|
|
26
25
|
},
|
|
27
26
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@types/
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"vitest": "^
|
|
27
|
+
"@changesets/cli": "^2.29.4",
|
|
28
|
+
"@types/node": "^22.15.17",
|
|
29
|
+
"@types/prettier": "3.0.0",
|
|
30
|
+
"tsup": "^8.4.0",
|
|
31
|
+
"typescript": "^5.8.3",
|
|
32
|
+
"vitest": "^3.1.3"
|
|
34
33
|
},
|
|
35
34
|
"files": [
|
|
36
35
|
"src",
|
|
@@ -60,6 +59,6 @@
|
|
|
60
59
|
"dev": "tsup --watch",
|
|
61
60
|
"build": "tsup",
|
|
62
61
|
"test": "vitest",
|
|
63
|
-
"typecheck": "tsc
|
|
62
|
+
"typecheck": "tsc -b ./tsconfig.build.json"
|
|
64
63
|
}
|
|
65
64
|
}
|
package/src/asserts.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LibSchemaObject } from "./types.ts";
|
|
2
2
|
|
|
3
|
-
export type SingleType = Exclude<
|
|
3
|
+
export type SingleType = Exclude<LibSchemaObject["type"], any[] | undefined>;
|
|
4
4
|
export const isPrimitiveType = (type: unknown): type is PrimitiveType => primitiveTypeList.includes(type as any);
|
|
5
5
|
|
|
6
6
|
const primitiveTypeList = ["string", "number", "integer", "boolean", "null"] as const;
|
package/src/box-factory.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ReferenceObject
|
|
2
|
-
import { Box } from "./box";
|
|
3
|
-
import { AnyBoxDef, BoxFactory, OpenapiSchemaConvertContext, StringOrBox } from "./types";
|
|
1
|
+
import type { ReferenceObject } from "openapi3-ts/oas31";
|
|
2
|
+
import { Box } from "./box.ts";
|
|
3
|
+
import { AnyBoxDef, BoxFactory, OpenapiSchemaConvertContext, StringOrBox, type LibSchemaObject } from "./types.ts";
|
|
4
4
|
|
|
5
5
|
export const unwrap = (param: StringOrBox) => (typeof param === "string" ? param : param.value);
|
|
6
6
|
export const createFactory = <T extends OpenapiSchemaConvertContext["factory"]>(f: T) => f;
|
|
@@ -8,7 +8,7 @@ export const createFactory = <T extends OpenapiSchemaConvertContext["factory"]>(
|
|
|
8
8
|
/**
|
|
9
9
|
* Create a box-factory using your schema provider and automatically add the input schema to each box.
|
|
10
10
|
*/
|
|
11
|
-
export const createBoxFactory = (schema:
|
|
11
|
+
export const createBoxFactory = (schema: LibSchemaObject | ReferenceObject, ctx: OpenapiSchemaConvertContext) => {
|
|
12
12
|
const f = typeof ctx.factory === "function" ? ctx.factory(schema, ctx) : ctx.factory;
|
|
13
13
|
const callback = <T extends AnyBoxDef>(box: Box<T>) => {
|
|
14
14
|
if (f.callback) {
|
package/src/box.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { openApiSchemaToTs } from "./openapi-schema-to-ts";
|
|
1
|
+
import { openApiSchemaToTs } from "./openapi-schema-to-ts.ts";
|
|
3
2
|
import {
|
|
4
3
|
AnyBoxDef,
|
|
5
4
|
BoxArray,
|
|
@@ -11,7 +10,8 @@ import {
|
|
|
11
10
|
BoxRef,
|
|
12
11
|
BoxUnion,
|
|
13
12
|
OpenapiSchemaConvertContext,
|
|
14
|
-
|
|
13
|
+
type LibSchemaObject,
|
|
14
|
+
} from "./types.ts";
|
|
15
15
|
|
|
16
16
|
// TODO rename SchemaBox
|
|
17
17
|
export class Box<T extends AnyBoxDef = AnyBoxDef> {
|
|
@@ -39,7 +39,7 @@ export class Box<T extends AnyBoxDef = AnyBoxDef> {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
recompute(callback: OpenapiSchemaConvertContext["onBox"]) {
|
|
42
|
-
return openApiSchemaToTs({ schema: this.schema as
|
|
42
|
+
return openApiSchemaToTs({ schema: this.schema as LibSchemaObject, ctx: { ...this.ctx, onBox: callback! } });
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
static fromJSON(json: string) {
|