@sdk-it/typescript 0.3.1

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 ADDED
@@ -0,0 +1,858 @@
1
+ // packages/typescript/src/lib/generate.ts
2
+ import { join } from "node:path";
3
+
4
+ // packages/core/dist/index.js
5
+ import ts, { TypeFlags, symbolName } from "typescript";
6
+ import debug from "debug";
7
+ import ts2 from "typescript";
8
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
9
+ import { dirname as dirname2, isAbsolute, join as join2 } from "node:path";
10
+ var deriveSymbol = Symbol.for("serialize");
11
+ var $types = Symbol.for("types");
12
+ var logger = debug("january:client");
13
+ async function exist(file) {
14
+ return stat(file).then(() => true).catch(() => false);
15
+ }
16
+ async function writeFiles(dir, contents) {
17
+ return Promise.all(
18
+ Object.entries(contents).map(async ([file, content]) => {
19
+ const filePath = isAbsolute(file) ? file : join2(dir, file);
20
+ await mkdir(dirname2(filePath), { recursive: true });
21
+ if (typeof content === "string") {
22
+ await writeFile(filePath, content, "utf-8");
23
+ } else {
24
+ if (content.ignoreIfExists) {
25
+ if (!await exist(filePath)) {
26
+ await writeFile(filePath, content.content, "utf-8");
27
+ }
28
+ }
29
+ }
30
+ })
31
+ );
32
+ }
33
+ async function getFolderExports(folder, extensions = ["ts"], keepExtensions = false) {
34
+ const files = await readdir(folder, { withFileTypes: true });
35
+ const exports = [];
36
+ for (const file of files) {
37
+ if (file.isDirectory()) {
38
+ exports.push(`export * from './${file.name}';`);
39
+ } else if (file.name !== "index.ts" && extensions.includes(getExt(file.name))) {
40
+ if (keepExtensions) {
41
+ exports.push(`export * from './${file.name}';`);
42
+ } else {
43
+ exports.push(
44
+ `export * from './${file.name.replace(`.${getExt(file.name)}`, "")}';`
45
+ );
46
+ }
47
+ }
48
+ }
49
+ return exports.join("\n");
50
+ }
51
+ var getExt = (fileName) => {
52
+ if (!fileName) {
53
+ return "";
54
+ }
55
+ const lastDot = fileName.lastIndexOf(".");
56
+ if (lastDot === -1) {
57
+ return "";
58
+ }
59
+ const ext = fileName.slice(lastDot + 1).split("/").filter(Boolean).join("");
60
+ if (ext === fileName) {
61
+ return "";
62
+ }
63
+ return ext || "txt";
64
+ };
65
+ function removeDuplicates(data, accessor) {
66
+ return [...new Map(data.map((x) => [accessor(x), x])).values()];
67
+ }
68
+ function toLitObject(obj, accessor = (value) => value) {
69
+ return `{${Object.keys(obj).map((key) => `${key}: ${accessor(obj[key])}`).join(", ")}}`;
70
+ }
71
+
72
+ // packages/typescript/src/lib/generator.ts
73
+ import { get as get2, merge } from "lodash-es";
74
+ import { camelcase as camelcase2, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
75
+
76
+ // packages/typescript/src/lib/json-zod.ts
77
+ import { get } from "lodash";
78
+ function cleanRef(ref) {
79
+ return ref.replace(/^#\//, "");
80
+ }
81
+ function followRef(spec, ref) {
82
+ const pathParts = cleanRef(ref).split("/");
83
+ const entry = get(spec, pathParts);
84
+ if (entry && "$ref" in entry) {
85
+ return followRef(spec, entry.$ref);
86
+ }
87
+ return entry;
88
+ }
89
+ function jsonSchemaToZod(spec, schema, required = false, onRef, refProcessingStack = /* @__PURE__ */ new Set()) {
90
+ if ("$ref" in schema) {
91
+ const schemaName = cleanRef(schema.$ref).split("/").pop();
92
+ if (refProcessingStack.has(schemaName)) {
93
+ return schemaName;
94
+ }
95
+ refProcessingStack.add(schemaName);
96
+ onRef(
97
+ schemaName,
98
+ jsonSchemaToZod(
99
+ spec,
100
+ followRef(spec, schema.$ref),
101
+ required,
102
+ onRef,
103
+ refProcessingStack
104
+ )
105
+ );
106
+ refProcessingStack.delete(schemaName);
107
+ return schemaName;
108
+ }
109
+ if (schema.allOf && Array.isArray(schema.allOf)) {
110
+ const allOfSchemas = schema.allOf.map(
111
+ (sub) => jsonSchemaToZod(spec, sub, true, onRef, refProcessingStack)
112
+ );
113
+ return `z.intersection(${allOfSchemas.join(", ")})`;
114
+ }
115
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
116
+ const anyOfSchemas = schema.anyOf.map(
117
+ (sub) => jsonSchemaToZod(spec, sub, false, onRef, refProcessingStack)
118
+ );
119
+ return `z.union([${anyOfSchemas.join(", ")}])${appendOptional(required)}`;
120
+ }
121
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
122
+ const oneOfSchemas = schema.oneOf.map((sub) => {
123
+ if ("$ref" in sub) {
124
+ const refName = cleanRef(sub.$ref).split("/").pop();
125
+ if (refProcessingStack.has(refName)) {
126
+ return refName;
127
+ }
128
+ }
129
+ return jsonSchemaToZod(spec, sub, false, onRef, refProcessingStack);
130
+ });
131
+ return `z.union([${oneOfSchemas.join(", ")}])${appendOptional(required)}`;
132
+ }
133
+ if (schema.enum && Array.isArray(schema.enum)) {
134
+ const enumVals = schema.enum.map((val) => JSON.stringify(val)).join(", ");
135
+ return `z.enum([${enumVals}])${appendOptional(required)}`;
136
+ }
137
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
138
+ if (!types.length) {
139
+ return `z.unknown()${appendOptional(required)}`;
140
+ }
141
+ if (types.length > 1) {
142
+ const realTypes = types.filter((t) => t !== "null");
143
+ if (realTypes.length === 1 && types.includes("null")) {
144
+ const typeZod = basicTypeToZod(
145
+ realTypes[0],
146
+ schema,
147
+ spec,
148
+ false,
149
+ onRef,
150
+ refProcessingStack
151
+ );
152
+ return `${typeZod}.nullable()${appendOptional(required)}`;
153
+ }
154
+ const subSchemas = types.map(
155
+ (t) => basicTypeToZod(t, schema, spec, false, onRef, refProcessingStack)
156
+ );
157
+ return `z.union([${subSchemas.join(", ")}])${appendOptional(required)}`;
158
+ }
159
+ return basicTypeToZod(
160
+ types[0],
161
+ schema,
162
+ spec,
163
+ required,
164
+ onRef,
165
+ refProcessingStack
166
+ );
167
+ }
168
+ function basicTypeToZod(type, schema, spec, required = false, onRef, refProcessingStack) {
169
+ switch (type) {
170
+ case "string":
171
+ return handleString(schema, required);
172
+ case "number":
173
+ case "integer":
174
+ return handleNumber(schema, required);
175
+ case "boolean":
176
+ return `z.boolean()${appendDefault(schema.default)}${appendOptional(required)}`;
177
+ case "object":
178
+ return handleObject(schema, spec, required, onRef, refProcessingStack);
179
+ case "array":
180
+ return handleArray(schema, spec, required, onRef, refProcessingStack);
181
+ case "null":
182
+ return `z.null()${appendOptional(required)}`;
183
+ default:
184
+ return `z.unknown()${appendOptional(required)}`;
185
+ }
186
+ }
187
+ function handleString(schema, required) {
188
+ let base = "z.string()";
189
+ switch (schema.format) {
190
+ case "date-time":
191
+ case "datetime":
192
+ base = "z.coerce.date()";
193
+ break;
194
+ case "date":
195
+ base = "z.coerce.date() /* or z.string() if you want raw date strings */";
196
+ break;
197
+ case "time":
198
+ base = "z.string() /* optionally add .regex(...) for HH:MM:SS format */";
199
+ break;
200
+ case "email":
201
+ base = "z.string().email()";
202
+ break;
203
+ case "uuid":
204
+ base = "z.string().uuid()";
205
+ break;
206
+ case "url":
207
+ case "uri":
208
+ base = "z.string().url()";
209
+ break;
210
+ case "ipv4":
211
+ base = 'z.string().ip({version: "v4"})';
212
+ break;
213
+ case "ipv6":
214
+ base = 'z.string().ip({version: "v6"})';
215
+ break;
216
+ case "phone":
217
+ base = "z.string() /* or add .regex(...) for phone formats */";
218
+ break;
219
+ case "byte":
220
+ case "binary":
221
+ base = "z.instanceof(Blob) /* consider base64 check if needed */";
222
+ break;
223
+ case "int64":
224
+ base = "z.string() /* or z.bigint() if your app can handle it */";
225
+ break;
226
+ default:
227
+ break;
228
+ }
229
+ return `${base}${appendDefault(schema.default)}${appendOptional(required)}`;
230
+ }
231
+ function handleNumber(schema, required) {
232
+ let defaultValue = schema.default !== void 0 ? `.default(${schema.default})` : ``;
233
+ let base = "z.number()";
234
+ if (schema.format === "int64") {
235
+ base = "z.bigint()";
236
+ if (schema.default !== void 0) {
237
+ defaultValue = `.default(BigInt(${schema.default}))`;
238
+ }
239
+ }
240
+ if (schema.format === "int32") {
241
+ base += ".int()";
242
+ }
243
+ if (typeof schema.exclusiveMinimum === "number") {
244
+ base += `.gt(${schema.exclusiveMinimum})`;
245
+ }
246
+ if (typeof schema.exclusiveMaximum === "number") {
247
+ base += `.lt(${schema.exclusiveMaximum})`;
248
+ }
249
+ if (typeof schema.minimum === "number") {
250
+ base += schema.format === "int64" ? `.min(BigInt(${schema.minimum}))` : `.min(${schema.minimum})`;
251
+ }
252
+ if (typeof schema.maximum === "number") {
253
+ base += schema.format === "int64" ? `.max(BigInt(${schema.maximum}))` : `.max(${schema.maximum})`;
254
+ }
255
+ if (typeof schema.multipleOf === "number") {
256
+ base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
257
+ }
258
+ return `${base}${defaultValue}${appendOptional(required)}`;
259
+ }
260
+ function handleObject(schema, spec, required = false, onRef, refProcessingStack) {
261
+ const properties = schema.properties || {};
262
+ const propEntries = Object.entries(properties).map(([key, propSchema]) => {
263
+ const isRequired = (schema.required ?? []).includes(key);
264
+ const zodPart = jsonSchemaToZod(
265
+ spec,
266
+ propSchema,
267
+ isRequired,
268
+ onRef,
269
+ refProcessingStack
270
+ );
271
+ return `'${key}': ${zodPart}`;
272
+ });
273
+ let additionalProps = "";
274
+ if (schema.additionalProperties) {
275
+ if (typeof schema.additionalProperties === "object") {
276
+ const addPropZod = jsonSchemaToZod(
277
+ spec,
278
+ schema.additionalProperties,
279
+ true,
280
+ onRef,
281
+ refProcessingStack
282
+ );
283
+ additionalProps = `.catchall(${addPropZod})`;
284
+ } else if (schema.additionalProperties === true) {
285
+ additionalProps = `.catchall(z.unknown())`;
286
+ }
287
+ }
288
+ const objectSchema = `z.object({${propEntries.join(", ")}})${additionalProps}`;
289
+ return `${objectSchema}${appendOptional(required)}`;
290
+ }
291
+ function handleArray(schema, spec, required = false, onRef, refProcessingStack) {
292
+ const { items } = schema;
293
+ if (!items) {
294
+ return `z.array(z.unknown())${appendOptional(required)}`;
295
+ }
296
+ if (Array.isArray(items)) {
297
+ const tupleItems = items.map(
298
+ (sub) => jsonSchemaToZod(spec, sub, true, onRef, refProcessingStack)
299
+ );
300
+ const base = `z.tuple([${tupleItems.join(", ")}])`;
301
+ return `${base}${appendOptional(required)}`;
302
+ }
303
+ const itemsSchema = jsonSchemaToZod(
304
+ spec,
305
+ items,
306
+ true,
307
+ onRef,
308
+ refProcessingStack
309
+ );
310
+ return `z.array(${itemsSchema})${appendOptional(required)}`;
311
+ }
312
+ function appendOptional(isRequired) {
313
+ return isRequired ? "" : ".optional()";
314
+ }
315
+ function appendDefault(defaultValue) {
316
+ return defaultValue !== void 0 ? `.default(${JSON.stringify(defaultValue)})` : "";
317
+ }
318
+
319
+ // packages/typescript/src/lib/sdk.ts
320
+ import { camelcase } from "stringcase";
321
+ import { pascalcase, spinalcase } from "stringcase";
322
+
323
+ // packages/typescript/src/lib/backend.ts
324
+ import { titlecase } from "stringcase";
325
+ var backend_default = (spec) => {
326
+ const specOptions = {
327
+ ...spec.options ?? {},
328
+ fetch: {
329
+ schema: "z.function().args(z.instanceof(Request)).returns(z.promise(z.instanceof(Response))).optional()"
330
+ },
331
+ baseUrl: { schema: "z.string().url()" }
332
+ };
333
+ if (spec.securityScheme) {
334
+ specOptions["token"] = { schema: "z.string().optional()" };
335
+ }
336
+ const defaultHeaders = spec.securityScheme ? `{Authorization: \`${titlecase(spec.securityScheme.bearerAuth.scheme)} \${this.options.token}\`}` : `{}`;
337
+ return `
338
+
339
+ import z from 'zod';
340
+ import type { Endpoints } from './endpoints';
341
+ import type { StreamEndpoints } from './stream-endpoints';
342
+ import schemas from './schemas';
343
+ import { parse } from './parser';
344
+ import { handleError, parseResponse } from './client';
345
+
346
+ const optionsSchema = z.object(${toLitObject(specOptions, (x) => x.schema)});
347
+ type ${spec.name}Options = z.infer<typeof optionsSchema>;
348
+ export class ${spec.name} {
349
+
350
+ constructor(public options: ${spec.name}Options) {}
351
+
352
+ async request<E extends keyof Endpoints>(
353
+ endpoint: E,
354
+ input: Endpoints[E]['input'],
355
+ ): Promise<readonly [Endpoints[E]['output'], Endpoints[E]['error'] | null]> {
356
+ const route = schemas[endpoint];
357
+ const [parsedInput, parseError] = parse(route.schema, input);
358
+ if (parseError) {
359
+ return [
360
+ null as never,
361
+ { ...parseError, kind: 'parse' } as never,
362
+ ] as const;
363
+ }
364
+ const request = route.toRequest(parsedInput as never, {
365
+ headers: this.defaultHeaders,
366
+ baseUrl: this.options.baseUrl,
367
+ });
368
+ const response = await (this.options.fetch ?? fetch)(request);
369
+ if (response.ok) {
370
+ const data = await parseResponse(response);
371
+ return [data as Endpoints[E]['output'], null] as const;
372
+ }
373
+ const error = await handleError(response);
374
+ return [null as never, { ...error, kind: 'response' }] as const;
375
+ }
376
+
377
+ get defaultHeaders() {
378
+ return ${defaultHeaders}
379
+ }
380
+
381
+ setOptions(options: Partial<${spec.name}Options>) {
382
+ const validated = optionsSchema.partial().parse(options);
383
+
384
+ for (const key of Object.keys(validated) as (keyof ${spec.name}Options)[]) {
385
+ if (validated[key] !== undefined) {
386
+ (this.options[key] as typeof validated[typeof key]) = validated[key]!;
387
+ }
388
+ }
389
+ }
390
+ }`;
391
+ };
392
+
393
+ // packages/typescript/src/lib/client.txt
394
+ var client_default = "import { parse } from 'fast-content-type-parse';\n\nimport type { Endpoints } from './endpoints';\n\nexport interface RequestInterface<D extends object = object> {\n /**\n * Sends a request based on endpoint options\n *\n * @param {string} route Request method + URL. Example: 'GET /orgs/{org}'\n * @param {object} [parameters] URL, query or body parameters, as well as headers, mediaType.{format|previews}, request, or baseUrl.\n */\n <R extends keyof Endpoints>(\n route: R,\n options?: Endpoints[R]['input'],\n ): Promise<Endpoints[R]['output']>;\n}\n\nexport async function handleError(response: Response) {\n try {\n if (response.status >= 400 && response.status < 500) {\n const body = (await response.json()) as Record<string, any>;\n return {\n status: response.status,\n body: body,\n };\n }\n return new Error(\n `An error occurred while fetching the data. Status: ${response.status}`,\n );\n } catch (error) {\n return error as any;\n }\n}\n\nasync function handleChunkedResponse(response: Response, contentType: string) {\n const { type } = parse(contentType);\n\n switch (type) {\n case 'application/json': {\n let buffer = '';\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return JSON.parse(buffer);\n }\n case 'text/html':\n case 'text/plain': {\n let buffer = '';\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return buffer;\n }\n default:\n return response.body;\n }\n}\n\nexport async function parseResponse(response: Response) {\n const contentType = response.headers.get('Content-Type');\n if (!contentType) {\n throw new Error('Content-Type header is missing');\n }\n\n if (response.status === 204) {\n return null;\n }\n const isChunked = response.headers.get('Transfer-Encoding') === 'chunked';\n if (isChunked) {\n return response.body!;\n // return handleChunkedResponse(response, contentType);\n }\n\n const { type } = parse(contentType);\n switch (type) {\n case 'application/json':\n return response.json();\n case 'text/plain':\n return response.text();\n case 'text/html':\n return response.text();\n case 'text/xml':\n case 'application/xml':\n return response.text();\n case 'application/x-www-form-urlencoded':\n const text = await response.text();\n return Object.fromEntries(new URLSearchParams(text));\n case 'multipart/form-data':\n return response.formData();\n default:\n throw new Error(`Unsupported content type: ${contentType}`);\n }\n}\n";
395
+
396
+ // packages/typescript/src/lib/parser.txt
397
+ var parser_default = "import { z } from 'zod';\n\nexport type ParseError<T extends z.ZodType<any, any, any>> = {\n kind: 'parse';\n} & z.inferFlattenedErrors<T>;\n\nexport function parse<T extends z.ZodType>(\n schema: T,\n input: unknown,\n) {\n const result = schema.safeParse(input);\n if (!result.success) {\n const errors = result.error.flatten((issue) => issue);\n return [null, errors];\n }\n return [result.data as z.infer<T>, null];\n}\n";
398
+
399
+ // packages/typescript/src/lib/request.txt
400
+ var request_default = "type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\ntype ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart';\ntype Endpoint = `${ContentType} ${Method} ${string}` | `${Method} ${string}`;\n\nexport function createUrl(base: string, path: string, query: URLSearchParams) {\n const url = new URL(path, base);\n url.search = query.toString();\n return url;\n}\nfunction template(\n templateString: string,\n templateVariables: Record<string, any>,\n): string {\n const nargs = /{([0-9a-zA-Z_]+)}/g;\n return templateString.replace(nargs, (match, key: string, index: number) => {\n // Handle escaped double braces\n if (\n templateString[index - 1] === '{' &&\n templateString[index + match.length] === '}'\n ) {\n return key;\n }\n\n const result = key in templateVariables ? templateVariables[key] : null;\n return result === null || result === undefined ? '' : String(result);\n });\n}\n\ninterface ToRequest {\n <T extends Endpoint>(\n endpoint: T,\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n defaults: {\n baseUrl: string;\n headers?: Record<string, string>;\n },\n ): Request;\n urlencoded: <T extends Endpoint>(\n endpoint: T,\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n defaults: {\n baseUrl: string;\n headers?: Record<string, string>;\n },\n ) => Request;\n}\n\nfunction _json(\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n) {\n const headers = new Headers({});\n for (const header of props.inputHeaders) {\n headers.set(header, input[header]);\n }\n\n const body: Record<string, any> = {};\n for (const prop of props.inputBody) {\n body[prop] = input[prop];\n }\n\n const query = new URLSearchParams();\n for (const key of props.inputQuery) {\n const value = input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = props.inputParams.reduce<Record<string, any>>((acc, key) => {\n acc[key] = input[key];\n return acc;\n }, {});\n\n return {\n body: JSON.stringify(body),\n query,\n params,\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n };\n}\n\ntype Input = Record<string, any>;\ntype Props = {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n};\n\nabstract class Serializer {\n constructor(\n protected input: Input,\n protected props: Props,\n ) {}\n abstract getBody(): BodyInit | null;\n abstract getHeaders(): Record<string, string>;\n serialize(): Serialized {\n const headers = new Headers({});\n for (const header of this.props.inputHeaders) {\n headers.set(header, this.input[header]);\n }\n\n const query = new URLSearchParams();\n for (const key of this.props.inputQuery) {\n const value = this.input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = this.props.inputParams.reduce<Record<string, any>>(\n (acc, key) => {\n acc[key] = this.input[key];\n return acc;\n },\n {},\n );\n\n return {\n body: this.getBody(),\n query,\n params,\n headers: this.getHeaders(),\n };\n }\n}\n\ninterface Serialized {\n body: BodyInit | null;\n query: URLSearchParams;\n params: Record<string, any>;\n headers: Record<string, string>;\n}\n\nclass JsonSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body: Record<string, any> = {};\n for (const prop of this.props.inputBody) {\n body[prop] = this.input[prop];\n }\n return JSON.stringify(body);\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n}\n\nclass UrlencodedSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new URLSearchParams();\n for (const prop of this.props.inputBody) {\n body.set(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nclass FormDataSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new FormData();\n for (const prop of this.props.inputBody) {\n body.append(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nexport function json(input: Input, props: Props) {\n return new JsonSerializer(input, props).serialize();\n}\nexport function urlencoded(input: Input, props: Props) {\n return new UrlencodedSerializer(input, props).serialize();\n}\nexport function formdata(input: Input, props: Props) {\n return new FormDataSerializer(input, props).serialize();\n}\n\nexport function _urlencoded(\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n) {\n const headers = new Headers({});\n for (const header of props.inputHeaders) {\n headers.set(header, input[header]);\n }\n\n const body = new URLSearchParams();\n for (const prop of props.inputBody) {\n body.set(prop, input[prop]);\n }\n\n const query = new URLSearchParams();\n for (const key of props.inputQuery) {\n const value = input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = props.inputParams.reduce<Record<string, any>>((acc, key) => {\n acc[key] = input[key];\n return acc;\n }, {});\n\n return {\n body,\n query,\n params,\n headers: {},\n };\n}\n\nexport function toRequest<T extends Endpoint>(\n endpoint: T,\n input: Serialized,\n defaults: {\n baseUrl: string;\n headers?: Record<string, string>;\n },\n): Request {\n const [method, path] = endpoint.split(' ');\n\n const headers = new Headers({\n ...defaults?.headers,\n ...input.headers,\n });\n const pathVariable = template(path, input.params);\n\n const url = createUrl(defaults.baseUrl, pathVariable, input.query);\n return new Request(url, {\n method: method,\n headers: headers,\n body: method === 'GET' ? undefined : input.body,\n });\n}\n";
401
+
402
+ // packages/typescript/src/lib/response.txt
403
+ var response_default = "export interface ApiResponse<Status extends number, Body extends unknown> {\n kind: 'response';\n status: Status;\n body: Body;\n}\n\n// 4xx Client Errors\nexport type BadRequest = ApiResponse<400, { message: string }>;\nexport type Unauthorized = ApiResponse<401, { message: string }>;\nexport type PaymentRequired = ApiResponse<402, { message: string }>;\nexport type Forbidden = ApiResponse<403, { message: string }>;\nexport type NotFound = ApiResponse<404, { message: string }>;\nexport type MethodNotAllowed = ApiResponse<405, { message: string }>;\nexport type NotAcceptable = ApiResponse<406, { message: string }>;\nexport type Conflict = ApiResponse<409, { message: string }>;\nexport type Gone = ApiResponse<410, { message: string }>;\nexport type UnprocessableEntity = ApiResponse<422, { message: string; errors?: Record<string, string[]> }>;\nexport type TooManyRequests = ApiResponse<429, { message: string; retryAfter?: string }>;\nexport type PayloadTooLarge = ApiResponse<413, { message: string; }>;\nexport type UnsupportedMediaType = ApiResponse<415, { message: string; }>;\n\n// 5xx Server Errors\nexport type InternalServerError = ApiResponse<500, { message: string }>;\nexport type NotImplemented = ApiResponse<501, { message: string }>;\nexport type BadGateway = ApiResponse<502, { message: string }>;\nexport type ServiceUnavailable = ApiResponse<503, { message: string; retryAfter?: string }>;\nexport type GatewayTimeout = ApiResponse<504, { message: string }>;\n\nexport type ClientError =\n | BadRequest\n | Unauthorized\n | PaymentRequired\n | Forbidden\n | NotFound\n | MethodNotAllowed\n | NotAcceptable\n | Conflict\n | Gone\n | UnprocessableEntity\n | TooManyRequests;\n\nexport type ServerError =\n | InternalServerError\n | NotImplemented\n | BadGateway\n | ServiceUnavailable\n | GatewayTimeout;\n\nexport type ProblematicResponse = ClientError | ServerError;\n";
404
+
405
+ // packages/typescript/src/lib/sdk.ts
406
+ var SchemaEndpoint = class {
407
+ #imports = [
408
+ `import z from 'zod';`,
409
+ 'import type { Endpoints } from "./endpoints";',
410
+ 'import type { StreamEndpoints } from "./stream-endpoints";',
411
+ `import { toRequest, json, urlencoded, formdata, createUrl } from './request';`,
412
+ `import type { ParseError } from './parser';`
413
+ ];
414
+ #endpoints = [];
415
+ addEndpoint(endpoint, operation) {
416
+ this.#endpoints.push(` "${endpoint}": ${operation},`);
417
+ }
418
+ addImport(value) {
419
+ this.#imports.push(value);
420
+ }
421
+ complete() {
422
+ return `${this.#imports.join("\n")}
423
+ export default {
424
+ ${this.#endpoints.join("\n")}
425
+ }`;
426
+ }
427
+ };
428
+ var Emitter = class {
429
+ imports = [
430
+ `import z from 'zod';`,
431
+ `import type { ParseError } from './parser';`
432
+ ];
433
+ endpoints = [];
434
+ addEndpoint(endpoint, operation) {
435
+ this.endpoints.push(` "${endpoint}": ${operation};`);
436
+ }
437
+ addImport(value) {
438
+ this.imports.push(value);
439
+ }
440
+ complete() {
441
+ return `${this.imports.join("\n")}
442
+ export interface Endpoints {
443
+ ${this.endpoints.join("\n")}
444
+ }`;
445
+ }
446
+ };
447
+ var StreamEmitter = class extends Emitter {
448
+ complete() {
449
+ return `${this.imports.join("\n")}
450
+ export interface StreamEndpoints {
451
+ ${this.endpoints.join("\n")}
452
+ }`;
453
+ }
454
+ };
455
+ function generateClientSdk(spec) {
456
+ const emitter = new Emitter();
457
+ const streamEmitter = new StreamEmitter();
458
+ const schemas = {};
459
+ const schemasImports = [];
460
+ const schemaEndpoint = new SchemaEndpoint();
461
+ const errors = [];
462
+ for (const [name, operations] of Object.entries(spec.groups)) {
463
+ const featureSchemaFileName = camelcase(name);
464
+ schemas[featureSchemaFileName] = [`import z from 'zod';`];
465
+ emitter.addImport(
466
+ `import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}';`
467
+ );
468
+ streamEmitter.addImport(
469
+ `import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}';`
470
+ );
471
+ schemaEndpoint.addImport(
472
+ `import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}';`
473
+ );
474
+ for (const operation of operations) {
475
+ const schemaName = camelcase(`${operation.name} schema`);
476
+ const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject(operation.schemas)};`;
477
+ schemas[featureSchemaFileName].push(schema);
478
+ schemasImports.push(
479
+ ...operation.imports.map((it) => (it.namedImports ?? []).map((it2) => it2.name)).flat()
480
+ );
481
+ const schemaRef = `${featureSchemaFileName}.${schemaName}`;
482
+ const output = operation.formatOutput();
483
+ const inputHeaders = [];
484
+ const inputQuery = [];
485
+ const inputBody = [];
486
+ const inputParams = [];
487
+ for (const [name2, prop] of Object.entries(operation.inputs)) {
488
+ if (prop.source === "headers" || prop.source === "header") {
489
+ inputHeaders.push(`"${name2}"`);
490
+ } else if (prop.source === "query") {
491
+ inputQuery.push(`"${name2}"`);
492
+ } else if (prop.source === "body") {
493
+ inputBody.push(`"${name2}"`);
494
+ } else if (prop.source === "path") {
495
+ inputParams.push(`"${name2}"`);
496
+ } else if (prop.source === "internal") {
497
+ continue;
498
+ } else {
499
+ throw new Error(
500
+ `Unknown source ${prop.source} in ${name2} ${JSON.stringify(
501
+ prop
502
+ )} in ${operation.name}`
503
+ );
504
+ }
505
+ }
506
+ if (operation.type === "sse") {
507
+ const input = `z.infer<typeof ${schemaRef}>`;
508
+ const endpoint = `${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
509
+ streamEmitter.addImport(
510
+ `import type {${pascalcase(operation.name)}} from './outputs/${spinalcase(operation.name)}';`
511
+ );
512
+ streamEmitter.addEndpoint(
513
+ endpoint,
514
+ `{input: ${input}, output: ${output.use}}`
515
+ );
516
+ schemaEndpoint.addEndpoint(
517
+ endpoint,
518
+ `{
519
+ schema: ${schemaRef},
520
+ toRequest(input: StreamEndpoints['${endpoint}']['input'], init: {baseUrl:string; headers?: Record<string, string>}) {
521
+ const endpoint = '${endpoint}';
522
+ return toRequest(endpoint, json(input, {
523
+ inputHeaders: [${inputHeaders}],
524
+ inputQuery: [${inputQuery}],
525
+ inputBody: [${inputBody}],
526
+ inputParams: [${inputParams}],
527
+ }), init);
528
+ },
529
+ }`
530
+ );
531
+ } else {
532
+ emitter.addImport(
533
+ `import type {${output.import}} from './outputs/${spinalcase(operation.name)}';`
534
+ );
535
+ errors.push(...operation.errors ?? []);
536
+ const addTypeParser = Object.keys(operation.schemas).length > 1;
537
+ for (const type in operation.schemas ?? {}) {
538
+ let typePrefix = "";
539
+ if (addTypeParser && type !== "json") {
540
+ typePrefix = `${type} `;
541
+ }
542
+ const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
543
+ const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
544
+ emitter.addEndpoint(
545
+ endpoint,
546
+ `{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
547
+ );
548
+ schemaEndpoint.addEndpoint(
549
+ endpoint,
550
+ `{
551
+ schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
552
+ toRequest(input: Endpoints['${endpoint}']['input'], init: {baseUrl:string; headers?: Record<string, string>}) {
553
+ const endpoint = '${endpoint}';
554
+ return toRequest(endpoint, ${operation.contentType || "json"}(input, {
555
+ inputHeaders: [${inputHeaders}],
556
+ inputQuery: [${inputQuery}],
557
+ inputBody: [${inputBody}],
558
+ inputParams: [${inputParams}],
559
+ }), init);
560
+ },
561
+ }`
562
+ );
563
+ }
564
+ }
565
+ }
566
+ }
567
+ emitter.addImport(
568
+ `import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from './response';`
569
+ );
570
+ return {
571
+ ...Object.fromEntries(
572
+ Object.entries(schemas).map(([key, value]) => [
573
+ `inputs/${key}.ts`,
574
+ [
575
+ schemasImports.length ? `import {${removeDuplicates(schemasImports, (it) => it)}} from '../zod';` : "",
576
+ spec.commonZod ? 'import * as commonZod from "../zod";' : "",
577
+ ...value
578
+ ].map((it) => it.trim()).filter(Boolean).join("\n") + "\n"
579
+ // add a newline at the end
580
+ ])
581
+ ),
582
+ "backend.ts": backend_default(spec),
583
+ "parser.ts": parser_default,
584
+ "client.ts": client_default,
585
+ "request.ts": request_default,
586
+ "schemas.ts": schemaEndpoint.complete(),
587
+ "endpoints.ts": emitter.complete(),
588
+ "stream-endpoints.ts": streamEmitter.complete(),
589
+ "response.ts": response_default
590
+ };
591
+ }
592
+
593
+ // packages/typescript/src/lib/generator.ts
594
+ function isRef(obj) {
595
+ return "$ref" in obj;
596
+ }
597
+ var responses = {
598
+ "400": "BadRequest",
599
+ "401": "Unauthorized",
600
+ "402": "PaymentRequired",
601
+ "403": "Forbidden",
602
+ "404": "NotFound",
603
+ "405": "MethodNotAllowed",
604
+ "406": "NotAcceptable",
605
+ "409": "Conflict",
606
+ "413": "PayloadTooLarge",
607
+ "410": "Gone",
608
+ "422": "UnprocessableEntity",
609
+ "429": "TooManyRequests",
610
+ "500": "InternalServerError",
611
+ "501": "NotImplemented",
612
+ "502": "BadGateway",
613
+ "503": "ServiceUnavailable",
614
+ "504": "GatewayTimeout"
615
+ };
616
+ var defaults = {
617
+ target: "javascript",
618
+ style: "github",
619
+ operationId: (operation, path, method) => {
620
+ if (operation.operationId) {
621
+ return spinalcase2(operation.operationId);
622
+ }
623
+ return operation.operationId || camelcase2(`${method} ${path.replace(/\//g, " ")}`);
624
+ }
625
+ };
626
+ function generateCode(config) {
627
+ const groups = {};
628
+ const commonSchemas = {};
629
+ const outputs = {};
630
+ for (const [path, methods] of Object.entries(config.spec.paths ?? {})) {
631
+ for (const [method, operation] of Object.entries(methods)) {
632
+ const formatOperationId = config.operationId ?? defaults.operationId;
633
+ const operationName = formatOperationId(operation, path, method);
634
+ console.log(`Processing ${method} ${path}`);
635
+ const groupName = (operation.tags ?? ["unknown"])[0];
636
+ groups[groupName] ??= [];
637
+ const inputs = {};
638
+ const imports = [];
639
+ const additionalProperties = [];
640
+ for (const param of operation.parameters ?? []) {
641
+ if (isRef(param)) {
642
+ throw new Error(`Found reference in parameter ${param.$ref}`);
643
+ }
644
+ if (!param.schema) {
645
+ throw new Error(`Schema not found for parameter ${param.name}`);
646
+ }
647
+ inputs[param.name] = {
648
+ source: param.in,
649
+ schema: ""
650
+ };
651
+ additionalProperties.push(param);
652
+ }
653
+ const types = {};
654
+ const shortContenTypeMap = {
655
+ "application/json": "json",
656
+ "application/x-www-form-urlencoded": "urlencoded",
657
+ "multipart/form-data": "formdata",
658
+ "application/xml": "xml",
659
+ "text/plain": "text"
660
+ };
661
+ let contentType;
662
+ if (operation.requestBody && Object.keys(operation.requestBody).length) {
663
+ const content = isRef(operation.requestBody) ? get2(followRef(config.spec, operation.requestBody.$ref), ["content"]) : operation.requestBody.content;
664
+ for (const type in content) {
665
+ const schema = isRef(content[type].schema) ? followRef(config.spec, content[type].schema.$ref) : content[type].schema;
666
+ types[shortContenTypeMap[type]] = jsonSchemaToZod(
667
+ config.spec,
668
+ merge(schema, {
669
+ required: additionalProperties.filter((p) => p.required).map((p) => p.name),
670
+ properties: additionalProperties.reduce(
671
+ (acc, p) => ({
672
+ ...acc,
673
+ [p.name]: p.schema
674
+ }),
675
+ {}
676
+ )
677
+ }),
678
+ true,
679
+ (schemaName, zod) => {
680
+ commonSchemas[schemaName] = zod;
681
+ imports.push({
682
+ defaultImport: void 0,
683
+ isTypeOnly: false,
684
+ moduleSpecifier: "../zod",
685
+ namedImports: [{ isTypeOnly: false, name: schemaName }],
686
+ namespaceImport: void 0
687
+ });
688
+ }
689
+ );
690
+ }
691
+ if (content["application/json"]) {
692
+ contentType = "json";
693
+ } else if (content["application/x-www-form-urlencoded"]) {
694
+ contentType = "urlencoded";
695
+ } else if (content["multipart/form-data"]) {
696
+ contentType = "formdata";
697
+ } else {
698
+ contentType = "json";
699
+ }
700
+ } else {
701
+ types[shortContenTypeMap["application/json"]] = jsonSchemaToZod(
702
+ config.spec,
703
+ {
704
+ type: "object",
705
+ required: additionalProperties.filter((p) => p.required).map((p) => p.name),
706
+ properties: additionalProperties.reduce(
707
+ (acc, p) => ({
708
+ ...acc,
709
+ [p.name]: p.schema
710
+ }),
711
+ {}
712
+ )
713
+ },
714
+ true,
715
+ (schemaName, zod) => {
716
+ commonSchemas[schemaName] = zod;
717
+ imports.push({
718
+ defaultImport: void 0,
719
+ isTypeOnly: false,
720
+ moduleSpecifier: "./zod",
721
+ namedImports: [{ isTypeOnly: false, name: schemaName }],
722
+ namespaceImport: void 0
723
+ });
724
+ }
725
+ );
726
+ }
727
+ const errors = [];
728
+ operation.responses ??= {};
729
+ let foundResponse = false;
730
+ const output = [`import z from 'zod';`];
731
+ for (const status in operation.responses) {
732
+ const response = operation.responses[status];
733
+ const statusCode = +status;
734
+ if (statusCode >= 400) {
735
+ errors.push(responses[status] ?? "ProblematicResponse");
736
+ }
737
+ if (statusCode >= 200 && statusCode < 300) {
738
+ foundResponse = true;
739
+ const responseContent = get2(response, ["content"]);
740
+ const isJson = responseContent && responseContent["application/json"];
741
+ const responseSchema = isJson ? jsonSchemaToZod(
742
+ config.spec,
743
+ responseContent["application/json"].schema,
744
+ true,
745
+ (schemaName, zod) => {
746
+ commonSchemas[schemaName] = zod;
747
+ imports.push({
748
+ defaultImport: void 0,
749
+ isTypeOnly: false,
750
+ moduleSpecifier: "../zod",
751
+ namedImports: [{ isTypeOnly: false, name: schemaName }],
752
+ namespaceImport: void 0
753
+ });
754
+ }
755
+ ) : "z.instanceof(ReadableStream)";
756
+ output.push(
757
+ importsToString(mergeImports(Object.values(imports).flat())).join(
758
+ "\n"
759
+ )
760
+ );
761
+ output.push(
762
+ `export const ${pascalcase2(operationName + " output")} = ${responseSchema}`
763
+ );
764
+ }
765
+ }
766
+ if (!foundResponse) {
767
+ output.push(
768
+ `export const ${pascalcase2(operationName + " output")} = z.void()`
769
+ );
770
+ }
771
+ outputs[`${spinalcase2(operationName)}.ts`] = output.join("\n");
772
+ groups[groupName].push({
773
+ name: operationName,
774
+ type: "http",
775
+ imports: mergeImports(Object.values(imports).flat()),
776
+ inputs,
777
+ errors: errors.length ? errors : ["ServerError"],
778
+ contentType,
779
+ schemas: types,
780
+ formatOutput: () => ({
781
+ import: pascalcase2(operationName + " output"),
782
+ use: `z.infer<typeof ${pascalcase2(operationName + " output")}>`
783
+ }),
784
+ trigger: {
785
+ path,
786
+ method
787
+ }
788
+ });
789
+ }
790
+ }
791
+ return { groups, commonSchemas, outputs };
792
+ }
793
+ function mergeImports(imports) {
794
+ const merged = {};
795
+ for (const i of imports) {
796
+ merged[i.moduleSpecifier] = merged[i.moduleSpecifier] ?? {
797
+ moduleSpecifier: i.moduleSpecifier,
798
+ defaultImport: i.defaultImport,
799
+ namespaceImport: i.namespaceImport,
800
+ namedImports: []
801
+ };
802
+ if (i.namedImports) {
803
+ merged[i.moduleSpecifier].namedImports.push(...i.namedImports);
804
+ }
805
+ }
806
+ return Object.values(merged);
807
+ }
808
+ function importsToString(imports) {
809
+ return imports.map((i) => {
810
+ if (i.defaultImport) {
811
+ return `import ${i.defaultImport} from '${i.moduleSpecifier}'`;
812
+ }
813
+ if (i.namespaceImport) {
814
+ return `import * as ${i.namespaceImport} from '${i.moduleSpecifier}'`;
815
+ }
816
+ if (i.namedImports) {
817
+ return `import {${removeDuplicates(i.namedImports, (it) => it.name).map((n) => n.name).join(", ")}} from '${i.moduleSpecifier}'`;
818
+ }
819
+ throw new Error(`Invalid import ${JSON.stringify(i)}`);
820
+ });
821
+ }
822
+
823
+ // packages/typescript/src/lib/generate.ts
824
+ async function generate(spec, settings) {
825
+ const { commonSchemas, groups, outputs } = generateCode({
826
+ spec,
827
+ style: "github",
828
+ target: "javascript"
829
+ });
830
+ const clientFiles = generateClientSdk({
831
+ name: "Client",
832
+ groups
833
+ });
834
+ await writeFiles(settings.output, {
835
+ "outputs/index.ts": "",
836
+ "inputs/index.ts": ""
837
+ });
838
+ await writeFiles(join(settings.output, "outputs"), outputs);
839
+ await writeFiles(settings.output, {
840
+ ...clientFiles,
841
+ "zod.ts": `import z from 'zod';
842
+ ${Object.entries(commonSchemas).map(([name, schema]) => `export const ${name} = ${schema};`).join("\n")}`
843
+ });
844
+ const [index, outputIndex, inputsIndex] = await Promise.all([
845
+ getFolderExports(settings.output),
846
+ getFolderExports(join(settings.output, "outputs")),
847
+ getFolderExports(join(settings.output, "inputs"))
848
+ ]);
849
+ await writeFiles(settings.output, {
850
+ "index.ts": index,
851
+ "outputs/index.ts": outputIndex,
852
+ "inputs/index.ts": inputsIndex
853
+ });
854
+ }
855
+ export {
856
+ generate
857
+ };
858
+ //# sourceMappingURL=index.js.map