@sdk-it/typescript 0.20.0 → 0.21.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 CHANGED
@@ -1,156 +1,1315 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
+ }) : x)(function(x) {
10
+ if (typeof require !== "undefined")
11
+ return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
14
+ var __commonJS = (cb, mod) => function __require2() {
15
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+
34
+ // node_modules/pluralize/pluralize.js
35
+ var require_pluralize = __commonJS({
36
+ "node_modules/pluralize/pluralize.js"(exports, module) {
37
+ "use strict";
38
+ (function(root, pluralize3) {
39
+ if (typeof __require === "function" && typeof exports === "object" && typeof module === "object") {
40
+ module.exports = pluralize3();
41
+ } else if (typeof define === "function" && define.amd) {
42
+ define(function() {
43
+ return pluralize3();
44
+ });
45
+ } else {
46
+ root.pluralize = pluralize3();
47
+ }
48
+ })(exports, function() {
49
+ var pluralRules = [];
50
+ var singularRules = [];
51
+ var uncountables = {};
52
+ var irregularPlurals = {};
53
+ var irregularSingles = {};
54
+ function sanitizeRule(rule) {
55
+ if (typeof rule === "string") {
56
+ return new RegExp("^" + rule + "$", "i");
57
+ }
58
+ return rule;
59
+ }
60
+ function restoreCase(word, token) {
61
+ if (word === token)
62
+ return token;
63
+ if (word === word.toLowerCase())
64
+ return token.toLowerCase();
65
+ if (word === word.toUpperCase())
66
+ return token.toUpperCase();
67
+ if (word[0] === word[0].toUpperCase()) {
68
+ return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase();
69
+ }
70
+ return token.toLowerCase();
71
+ }
72
+ function interpolate(str, args) {
73
+ return str.replace(/\$(\d{1,2})/g, function(match, index) {
74
+ return args[index] || "";
75
+ });
76
+ }
77
+ function replace(word, rule) {
78
+ return word.replace(rule[0], function(match, index) {
79
+ var result = interpolate(rule[1], arguments);
80
+ if (match === "") {
81
+ return restoreCase(word[index - 1], result);
82
+ }
83
+ return restoreCase(match, result);
84
+ });
85
+ }
86
+ function sanitizeWord(token, word, rules) {
87
+ if (!token.length || uncountables.hasOwnProperty(token)) {
88
+ return word;
89
+ }
90
+ var len = rules.length;
91
+ while (len--) {
92
+ var rule = rules[len];
93
+ if (rule[0].test(word))
94
+ return replace(word, rule);
95
+ }
96
+ return word;
97
+ }
98
+ function replaceWord(replaceMap, keepMap, rules) {
99
+ return function(word) {
100
+ var token = word.toLowerCase();
101
+ if (keepMap.hasOwnProperty(token)) {
102
+ return restoreCase(word, token);
103
+ }
104
+ if (replaceMap.hasOwnProperty(token)) {
105
+ return restoreCase(word, replaceMap[token]);
106
+ }
107
+ return sanitizeWord(token, word, rules);
108
+ };
109
+ }
110
+ function checkWord(replaceMap, keepMap, rules, bool) {
111
+ return function(word) {
112
+ var token = word.toLowerCase();
113
+ if (keepMap.hasOwnProperty(token))
114
+ return true;
115
+ if (replaceMap.hasOwnProperty(token))
116
+ return false;
117
+ return sanitizeWord(token, token, rules) === token;
118
+ };
119
+ }
120
+ function pluralize3(word, count, inclusive) {
121
+ var pluralized = count === 1 ? pluralize3.singular(word) : pluralize3.plural(word);
122
+ return (inclusive ? count + " " : "") + pluralized;
123
+ }
124
+ pluralize3.plural = replaceWord(
125
+ irregularSingles,
126
+ irregularPlurals,
127
+ pluralRules
128
+ );
129
+ pluralize3.isPlural = checkWord(
130
+ irregularSingles,
131
+ irregularPlurals,
132
+ pluralRules
133
+ );
134
+ pluralize3.singular = replaceWord(
135
+ irregularPlurals,
136
+ irregularSingles,
137
+ singularRules
138
+ );
139
+ pluralize3.isSingular = checkWord(
140
+ irregularPlurals,
141
+ irregularSingles,
142
+ singularRules
143
+ );
144
+ pluralize3.addPluralRule = function(rule, replacement) {
145
+ pluralRules.push([sanitizeRule(rule), replacement]);
146
+ };
147
+ pluralize3.addSingularRule = function(rule, replacement) {
148
+ singularRules.push([sanitizeRule(rule), replacement]);
149
+ };
150
+ pluralize3.addUncountableRule = function(word) {
151
+ if (typeof word === "string") {
152
+ uncountables[word.toLowerCase()] = true;
153
+ return;
154
+ }
155
+ pluralize3.addPluralRule(word, "$0");
156
+ pluralize3.addSingularRule(word, "$0");
157
+ };
158
+ pluralize3.addIrregularRule = function(single, plural) {
159
+ plural = plural.toLowerCase();
160
+ single = single.toLowerCase();
161
+ irregularSingles[single] = plural;
162
+ irregularPlurals[plural] = single;
163
+ };
164
+ [
165
+ // Pronouns.
166
+ ["I", "we"],
167
+ ["me", "us"],
168
+ ["he", "they"],
169
+ ["she", "they"],
170
+ ["them", "them"],
171
+ ["myself", "ourselves"],
172
+ ["yourself", "yourselves"],
173
+ ["itself", "themselves"],
174
+ ["herself", "themselves"],
175
+ ["himself", "themselves"],
176
+ ["themself", "themselves"],
177
+ ["is", "are"],
178
+ ["was", "were"],
179
+ ["has", "have"],
180
+ ["this", "these"],
181
+ ["that", "those"],
182
+ // Words ending in with a consonant and `o`.
183
+ ["echo", "echoes"],
184
+ ["dingo", "dingoes"],
185
+ ["volcano", "volcanoes"],
186
+ ["tornado", "tornadoes"],
187
+ ["torpedo", "torpedoes"],
188
+ // Ends with `us`.
189
+ ["genus", "genera"],
190
+ ["viscus", "viscera"],
191
+ // Ends with `ma`.
192
+ ["stigma", "stigmata"],
193
+ ["stoma", "stomata"],
194
+ ["dogma", "dogmata"],
195
+ ["lemma", "lemmata"],
196
+ ["schema", "schemata"],
197
+ ["anathema", "anathemata"],
198
+ // Other irregular rules.
199
+ ["ox", "oxen"],
200
+ ["axe", "axes"],
201
+ ["die", "dice"],
202
+ ["yes", "yeses"],
203
+ ["foot", "feet"],
204
+ ["eave", "eaves"],
205
+ ["goose", "geese"],
206
+ ["tooth", "teeth"],
207
+ ["quiz", "quizzes"],
208
+ ["human", "humans"],
209
+ ["proof", "proofs"],
210
+ ["carve", "carves"],
211
+ ["valve", "valves"],
212
+ ["looey", "looies"],
213
+ ["thief", "thieves"],
214
+ ["groove", "grooves"],
215
+ ["pickaxe", "pickaxes"],
216
+ ["passerby", "passersby"]
217
+ ].forEach(function(rule) {
218
+ return pluralize3.addIrregularRule(rule[0], rule[1]);
219
+ });
220
+ [
221
+ [/s?$/i, "s"],
222
+ [/[^\u0000-\u007F]$/i, "$0"],
223
+ [/([^aeiou]ese)$/i, "$1"],
224
+ [/(ax|test)is$/i, "$1es"],
225
+ [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, "$1es"],
226
+ [/(e[mn]u)s?$/i, "$1s"],
227
+ [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, "$1"],
228
+ [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, "$1i"],
229
+ [/(alumn|alg|vertebr)(?:a|ae)$/i, "$1ae"],
230
+ [/(seraph|cherub)(?:im)?$/i, "$1im"],
231
+ [/(her|at|gr)o$/i, "$1oes"],
232
+ [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, "$1a"],
233
+ [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, "$1a"],
234
+ [/sis$/i, "ses"],
235
+ [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, "$1$2ves"],
236
+ [/([^aeiouy]|qu)y$/i, "$1ies"],
237
+ [/([^ch][ieo][ln])ey$/i, "$1ies"],
238
+ [/(x|ch|ss|sh|zz)$/i, "$1es"],
239
+ [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, "$1ices"],
240
+ [/\b((?:tit)?m|l)(?:ice|ouse)$/i, "$1ice"],
241
+ [/(pe)(?:rson|ople)$/i, "$1ople"],
242
+ [/(child)(?:ren)?$/i, "$1ren"],
243
+ [/eaux$/i, "$0"],
244
+ [/m[ae]n$/i, "men"],
245
+ ["thou", "you"]
246
+ ].forEach(function(rule) {
247
+ return pluralize3.addPluralRule(rule[0], rule[1]);
248
+ });
249
+ [
250
+ [/s$/i, ""],
251
+ [/(ss)$/i, "$1"],
252
+ [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, "$1fe"],
253
+ [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, "$1f"],
254
+ [/ies$/i, "y"],
255
+ [/\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i, "$1ie"],
256
+ [/\b(mon|smil)ies$/i, "$1ey"],
257
+ [/\b((?:tit)?m|l)ice$/i, "$1ouse"],
258
+ [/(seraph|cherub)im$/i, "$1"],
259
+ [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, "$1"],
260
+ [/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, "$1sis"],
261
+ [/(movie|twelve|abuse|e[mn]u)s$/i, "$1"],
262
+ [/(test)(?:is|es)$/i, "$1is"],
263
+ [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, "$1us"],
264
+ [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, "$1um"],
265
+ [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, "$1on"],
266
+ [/(alumn|alg|vertebr)ae$/i, "$1a"],
267
+ [/(cod|mur|sil|vert|ind)ices$/i, "$1ex"],
268
+ [/(matr|append)ices$/i, "$1ix"],
269
+ [/(pe)(rson|ople)$/i, "$1rson"],
270
+ [/(child)ren$/i, "$1"],
271
+ [/(eau)x?$/i, "$1"],
272
+ [/men$/i, "man"]
273
+ ].forEach(function(rule) {
274
+ return pluralize3.addSingularRule(rule[0], rule[1]);
275
+ });
276
+ [
277
+ // Singular words with no plurals.
278
+ "adulthood",
279
+ "advice",
280
+ "agenda",
281
+ "aid",
282
+ "aircraft",
283
+ "alcohol",
284
+ "ammo",
285
+ "analytics",
286
+ "anime",
287
+ "athletics",
288
+ "audio",
289
+ "bison",
290
+ "blood",
291
+ "bream",
292
+ "buffalo",
293
+ "butter",
294
+ "carp",
295
+ "cash",
296
+ "chassis",
297
+ "chess",
298
+ "clothing",
299
+ "cod",
300
+ "commerce",
301
+ "cooperation",
302
+ "corps",
303
+ "debris",
304
+ "diabetes",
305
+ "digestion",
306
+ "elk",
307
+ "energy",
308
+ "equipment",
309
+ "excretion",
310
+ "expertise",
311
+ "firmware",
312
+ "flounder",
313
+ "fun",
314
+ "gallows",
315
+ "garbage",
316
+ "graffiti",
317
+ "hardware",
318
+ "headquarters",
319
+ "health",
320
+ "herpes",
321
+ "highjinks",
322
+ "homework",
323
+ "housework",
324
+ "information",
325
+ "jeans",
326
+ "justice",
327
+ "kudos",
328
+ "labour",
329
+ "literature",
330
+ "machinery",
331
+ "mackerel",
332
+ "mail",
333
+ "media",
334
+ "mews",
335
+ "moose",
336
+ "music",
337
+ "mud",
338
+ "manga",
339
+ "news",
340
+ "only",
341
+ "personnel",
342
+ "pike",
343
+ "plankton",
344
+ "pliers",
345
+ "police",
346
+ "pollution",
347
+ "premises",
348
+ "rain",
349
+ "research",
350
+ "rice",
351
+ "salmon",
352
+ "scissors",
353
+ "series",
354
+ "sewage",
355
+ "shambles",
356
+ "shrimp",
357
+ "software",
358
+ "species",
359
+ "staff",
360
+ "swine",
361
+ "tennis",
362
+ "traffic",
363
+ "transportation",
364
+ "trout",
365
+ "tuna",
366
+ "wealth",
367
+ "welfare",
368
+ "whiting",
369
+ "wildebeest",
370
+ "wildlife",
371
+ "you",
372
+ /pok[eé]mon$/i,
373
+ // Regexes.
374
+ /[^aeiou]ese$/i,
375
+ // "chinese", "japanese"
376
+ /deer$/i,
377
+ // "deer", "reindeer"
378
+ /fish$/i,
379
+ // "fish", "blowfish", "angelfish"
380
+ /measles$/i,
381
+ /o[iu]s$/i,
382
+ // "carnivorous"
383
+ /pox$/i,
384
+ // "chickpox", "smallpox"
385
+ /sheep$/i
386
+ ].forEach(pluralize3.addUncountableRule);
387
+ return pluralize3;
388
+ });
389
+ }
390
+ });
391
+
1
392
  // packages/typescript/src/lib/generate.ts
2
393
  import { template as template2 } from "lodash-es";
3
- import { join as join2 } from "node:path";
394
+ import { readFile, readdir, unlink, writeFile } from "node:fs/promises";
395
+ import { join as join2, relative, sep } from "node:path";
4
396
  import { npmRunPathEnv } from "npm-run-path";
5
- import { spinalcase as spinalcase3 } from "stringcase";
6
- import { methods, pascalcase as pascalcase3 } from "@sdk-it/core";
397
+ import { spinalcase as spinalcase4 } from "stringcase";
398
+ import { methods, pascalcase as pascalcase4 } from "@sdk-it/core";
7
399
  import {
400
+ addLeadingSlash,
401
+ exist,
8
402
  getFolderExports,
403
+ readFolder,
9
404
  writeFiles
10
405
  } from "@sdk-it/core/file-system.js";
11
406
 
12
- // packages/typescript/src/lib/client.ts
13
- import { toLitObject } from "@sdk-it/core";
14
- var client_default = (spec, style) => {
15
- const optionsEntries = Object.entries(spec.options).map(
16
- ([key, value]) => [`'${key}'`, value]
17
- );
18
- const defaultHeaders = `{${optionsEntries.filter(([, value]) => value.in === "header").map(
19
- ([key, value]) => `${key}: this.options[${value.optionName ? `'${value.optionName}'` : key}]`
20
- ).join(",\n")}}`;
21
- const defaultInputs = `{${optionsEntries.filter(([, value]) => value.in === "input").map(
22
- ([key, value]) => `${key}: this.options[${value.optionName ? `'${value.optionName}'` : key}]`
23
- ).join(",\n")}}`;
24
- const specOptions = {
25
- ...Object.fromEntries(
26
- optionsEntries.map(([key, value]) => [value.optionName ?? key, value])
27
- ),
28
- fetch: {
29
- schema: "fetchType"
30
- },
31
- baseUrl: {
32
- schema: spec.servers.length ? `z.enum(servers).default(servers[0])` : "z.string()"
407
+ // packages/readme/dist/index.js
408
+ var import_pluralize = __toESM(require_pluralize(), 1);
409
+ import { isEmpty as isEmpty2 } from "@sdk-it/core";
410
+ import { camelcase } from "stringcase";
411
+ import { followRef, isRef as isRef2 } from "@sdk-it/core/ref.js";
412
+ import { isRef } from "@sdk-it/core/ref.js";
413
+ import { isEmpty } from "@sdk-it/core/utils.js";
414
+ import { followRef as followRef2, isRef as isRef3 } from "@sdk-it/core";
415
+ var HAS_MORE_POSITIVE_REGEX_PATTERNS = [
416
+ "\\bhas_?more\\b",
417
+ "\\bhas_?next\\b",
418
+ // e.g., itemsHasNext, items_has_next
419
+ "\\bmore_?items\\b",
420
+ "\\bnext_?page\\b",
421
+ // e.g., userNextPageFlag
422
+ "\\badditional\\b",
423
+ // e.g., hasAdditionalData, additional_results_exist
424
+ "\\bcontinuation\\b",
425
+ // e.g., continuationAvailable, has_continuation_token
426
+ "\\bmore_?results\\b",
427
+ "\\bpage_?available\\b",
428
+ "\\bnext(?:_?(page))?\\b"
429
+ ];
430
+ var COMPILED_HAS_MORE_POSITIVE_REGEXES = HAS_MORE_POSITIVE_REGEX_PATTERNS.map((p) => new RegExp(p, "i"));
431
+ var HAS_MORE_INVERTED_REGEX_PATTERNS = [
432
+ "\\bis_?last\\b",
433
+ // e.g., pageIsLast
434
+ "\\blast_?page\\b",
435
+ // e.g., resultsAreLastPage
436
+ "\\bend_?of_?(data|results|list|items|stream)\\b",
437
+ "\\bno_?more_?(items|data|results)?\\b",
438
+ "\\ball_?(items_?)?loaded\\b",
439
+ "\\bis_?complete\\b"
440
+ ];
441
+ var COMPILED_HAS_MORE_INVERTED_REGEXES = HAS_MORE_INVERTED_REGEX_PATTERNS.map((p) => new RegExp(p, "i"));
442
+ function forEachOperation(config, callback) {
443
+ const result = [];
444
+ for (const [path, pathItem] of Object.entries(config.spec.paths ?? {})) {
445
+ const { parameters = [], ...methods2 } = pathItem;
446
+ for (const [method, operation] of Object.entries(methods2)) {
447
+ const metadata = operation["x-oaiMeta"] ?? {};
448
+ const operationTag = operation.tags?.[0];
449
+ result.push(
450
+ callback(
451
+ {
452
+ name: metadata.name,
453
+ method,
454
+ path,
455
+ groupName: operationTag,
456
+ tag: operationTag
457
+ },
458
+ operation
459
+ )
460
+ );
461
+ }
462
+ }
463
+ return result;
464
+ }
465
+ var PropEmitter = class {
466
+ #spec;
467
+ constructor(spec) {
468
+ this.#spec = spec;
469
+ }
470
+ /**
471
+ * Handle objects (properties)
472
+ */
473
+ #object(schema) {
474
+ const lines = [];
475
+ const properties = schema.properties || {};
476
+ if (Object.keys(properties).length > 0) {
477
+ lines.push(`**Properties:**`);
478
+ for (const [propName, propSchema] of Object.entries(properties)) {
479
+ const isRequired = (schema.required ?? []).includes(propName);
480
+ lines.push(...this.#property(propName, propSchema, isRequired));
481
+ }
482
+ }
483
+ if (schema.additionalProperties) {
484
+ lines.push(`**Additional Properties:**`);
485
+ if (typeof schema.additionalProperties === "boolean") {
486
+ lines.push(`- Allowed: ${schema.additionalProperties}`);
487
+ } else {
488
+ lines.push(
489
+ ...this.handle(schema.additionalProperties).map((l) => ` ${l}`)
490
+ );
491
+ }
492
+ }
493
+ return lines;
494
+ }
495
+ /**
496
+ * Format a property with its type and description
497
+ */
498
+ #property(name, schema, required) {
499
+ const docs = this.handle(schema);
500
+ const rawType = docs[0].replace("**Type:** ", "").replace(" (nullable)", "|null");
501
+ const defaultVal = !isRef3(schema) && schema.default !== void 0 ? ` default: ${JSON.stringify(schema.default)}` : "";
502
+ const reqMark = required ? " required" : "";
503
+ const summary = `- \`${name}\` ${rawType}${reqMark}${defaultVal}:`;
504
+ const detailLines = docs.slice(1).filter((l) => !l.startsWith("**Default:**")).map((l) => ` ${l}`);
505
+ return [summary, ...detailLines];
506
+ }
507
+ /**
508
+ * Handle array schemas
509
+ */
510
+ #array(schema) {
511
+ const lines = [];
512
+ lines.push(`**Array items:**`);
513
+ if (schema.items) {
514
+ const itemDocs = this.handle(schema.items);
515
+ lines.push(...itemDocs.map((line) => ` ${line}`));
516
+ } else {
517
+ lines.push(` **Type:** \`unknown\``);
518
+ }
519
+ if (schema.minItems !== void 0)
520
+ lines.push(`- Minimum items: ${schema.minItems}`);
521
+ if (schema.maxItems !== void 0)
522
+ lines.push(`- Maximum items: ${schema.maxItems}`);
523
+ if (schema.uniqueItems)
524
+ lines.push(`- Items must be unique.`);
525
+ return lines;
526
+ }
527
+ #ref($ref) {
528
+ const schemaName = $ref.split("/").pop() || "object";
529
+ const resolved = followRef2(this.#spec, $ref);
530
+ const lines = [
531
+ `**Type:** [\`${schemaName}\`](#${schemaName.toLowerCase()})`
532
+ ];
533
+ if (resolved.description) {
534
+ lines.push(resolved.description);
535
+ }
536
+ return lines;
537
+ }
538
+ #allOf(schemas) {
539
+ const lines = ["**All of (Intersection):**"];
540
+ schemas.forEach((subSchema, index) => {
541
+ lines.push(`- **Constraint ${index + 1}:**`);
542
+ const subLines = this.handle(subSchema);
543
+ lines.push(...subLines.map((l) => ` ${l}`));
544
+ });
545
+ return lines;
546
+ }
547
+ #anyOf(schemas) {
548
+ const lines = ["**Any of (Union):**"];
549
+ schemas.forEach((subSchema, index) => {
550
+ lines.push(`- **Option ${index + 1}:**`);
551
+ const subLines = this.handle(subSchema);
552
+ lines.push(...subLines.map((l) => ` ${l}`));
553
+ });
554
+ return lines;
555
+ }
556
+ #oneOf(schemas) {
557
+ const lines = ["**One of (Exclusive Union):**"];
558
+ schemas.forEach((subSchema, index) => {
559
+ lines.push(`- **Option ${index + 1}:**`);
560
+ const subLines = this.handle(subSchema);
561
+ lines.push(...subLines.map((l) => ` ${l}`));
562
+ });
563
+ return lines;
564
+ }
565
+ #enum(schema) {
566
+ const lines = [`**Type:** \`${schema.type || "unknown"}\` (enum)`];
567
+ if (schema.description)
568
+ lines.push(schema.description);
569
+ lines.push("**Allowed values:**");
570
+ lines.push(
571
+ ...(schema.enum || []).map((val) => `- \`${JSON.stringify(val)}\``)
572
+ );
573
+ if (schema.default !== void 0) {
574
+ lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
575
+ }
576
+ return lines;
577
+ }
578
+ #normal(type, schema, nullable) {
579
+ const lines = [];
580
+ const nullableSuffix = nullable ? " (nullable)" : "";
581
+ const description = schema.description ? [schema.description] : [];
582
+ switch (type) {
583
+ case "string":
584
+ lines.push(
585
+ `**Type:** \`string\`${schema.format ? ` (format: ${schema.format})` : ""}${nullableSuffix}`
586
+ );
587
+ lines.push(...description);
588
+ if (schema.minLength !== void 0)
589
+ lines.push(`- Minimum length: ${schema.minLength}`);
590
+ if (schema.maxLength !== void 0)
591
+ lines.push(`- Maximum length: ${schema.maxLength}`);
592
+ if (schema.pattern !== void 0)
593
+ lines.push(`- Pattern: \`${schema.pattern}\``);
594
+ break;
595
+ case "number":
596
+ case "integer":
597
+ lines.push(
598
+ `**Type:** \`${type}\`${schema.format ? ` (format: ${schema.format})` : ""}${nullableSuffix}`
599
+ );
600
+ lines.push(...description);
601
+ if (schema.minimum !== void 0) {
602
+ const exclusiveMin = typeof schema.exclusiveMinimum === "number";
603
+ lines.push(
604
+ `- Minimum: ${schema.minimum}${exclusiveMin ? " (exclusive)" : ""}`
605
+ );
606
+ if (exclusiveMin) {
607
+ lines.push(
608
+ `- Must be strictly greater than: ${schema.exclusiveMinimum}`
609
+ );
610
+ }
611
+ } else if (typeof schema.exclusiveMinimum === "number") {
612
+ lines.push(
613
+ `- Must be strictly greater than: ${schema.exclusiveMinimum}`
614
+ );
615
+ }
616
+ if (schema.maximum !== void 0) {
617
+ const exclusiveMax = typeof schema.exclusiveMaximum === "number";
618
+ lines.push(
619
+ `- Maximum: ${schema.maximum}${exclusiveMax ? " (exclusive)" : ""}`
620
+ );
621
+ if (exclusiveMax) {
622
+ lines.push(
623
+ `- Must be strictly less than: ${schema.exclusiveMaximum}`
624
+ );
625
+ }
626
+ } else if (typeof schema.exclusiveMaximum === "number") {
627
+ lines.push(
628
+ `- Must be strictly less than: ${schema.exclusiveMaximum}`
629
+ );
630
+ }
631
+ if (schema.multipleOf !== void 0)
632
+ lines.push(`- Must be a multiple of: ${schema.multipleOf}`);
633
+ break;
634
+ case "boolean":
635
+ lines.push(`**Type:** \`boolean\`${nullableSuffix}`);
636
+ lines.push(...description);
637
+ break;
638
+ case "object":
639
+ lines.push(`**Type:** \`object\`${nullableSuffix}`);
640
+ lines.push(...description);
641
+ lines.push(...this.#object(schema));
642
+ break;
643
+ case "array":
644
+ lines.push(`**Type:** \`array\`${nullableSuffix}`);
645
+ lines.push(...description);
646
+ lines.push(...this.#array(schema));
647
+ break;
648
+ case "null":
649
+ lines.push(`**Type:** \`null\``);
650
+ lines.push(...description);
651
+ break;
652
+ default:
653
+ lines.push(`**Type:** \`${type}\`${nullableSuffix}`);
654
+ lines.push(...description);
655
+ }
656
+ if (schema.default !== void 0) {
657
+ lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
658
+ }
659
+ return lines.filter((l) => l);
660
+ }
661
+ /**
662
+ * Handle schemas by resolving references and delegating to appropriate handler
663
+ */
664
+ handle(schemaOrRef) {
665
+ if (isRef3(schemaOrRef)) {
666
+ return this.#ref(schemaOrRef.$ref);
667
+ }
668
+ const schema = schemaOrRef;
669
+ if (schema.allOf && Array.isArray(schema.allOf)) {
670
+ return this.#allOf(schema.allOf);
671
+ }
672
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
673
+ return this.#anyOf(schema.anyOf);
674
+ }
675
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
676
+ return this.#oneOf(schema.oneOf);
677
+ }
678
+ if (schema.enum && Array.isArray(schema.enum)) {
679
+ return this.#enum(schema);
680
+ }
681
+ let types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
682
+ let nullable = false;
683
+ if (types.includes("null")) {
684
+ nullable = true;
685
+ types = types.filter((t) => t !== "null");
686
+ }
687
+ if (types.length === 0) {
688
+ if (schema.properties || schema.additionalProperties) {
689
+ types = ["object"];
690
+ } else if (schema.items) {
691
+ types = ["array"];
692
+ }
693
+ }
694
+ if (types.length === 0) {
695
+ const lines2 = ["**Type:** `unknown`"];
696
+ if (schema.description)
697
+ lines2.push(schema.description);
698
+ if (schema.default !== void 0)
699
+ lines2.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
700
+ return lines2;
701
+ }
702
+ if (types.length === 1) {
703
+ return this.#normal(types[0], schema, nullable);
704
+ }
705
+ const typeString = types.join(" | ");
706
+ const nullableSuffix = nullable ? " (nullable)" : "";
707
+ const lines = [`**Type:** \`${typeString}\`${nullableSuffix}`];
708
+ if (schema.description)
709
+ lines.push(schema.description);
710
+ if (schema.default !== void 0)
711
+ lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
712
+ return lines;
713
+ }
714
+ /**
715
+ * Process a request body and return markdown documentation
716
+ */
717
+ requestBody(requestBody) {
718
+ if (!requestBody)
719
+ return [];
720
+ const lines = [];
721
+ lines.push(`##### Request Body`);
722
+ if (requestBody.description) {
723
+ lines.push(requestBody.description);
33
724
  }
34
- };
35
- return `
36
- import type { HeadersInit, RequestConfig } from './http/${spec.makeImport("request")}';
37
- import { fetchType, dispatch, parse } from './http/${spec.makeImport("send-request")}';
38
- import z from 'zod';
39
- import type { Endpoints } from './api/${spec.makeImport("endpoints")}';
40
- import schemas from './api/${spec.makeImport("schemas")}';
41
- import {
42
- createBaseUrlInterceptor,
43
- createHeadersInterceptor,
44
- } from './http/${spec.makeImport("interceptors")}';
45
-
46
- import { parseInput, type ParseError } from './http/${spec.makeImport("parser")}';
47
-
48
- ${spec.servers.length ? `export const servers = ${JSON.stringify(spec.servers, null, 2)} as const` : ""}
49
- const optionsSchema = z.object(${toLitObject(specOptions, (x) => x.schema)});
50
- ${spec.servers.length ? `export type Servers = typeof servers[number];` : ""}
51
-
52
- type ${spec.name}Options = z.infer<typeof optionsSchema>;
53
-
54
- export class ${spec.name} {
55
- public options: ${spec.name}Options
56
- constructor(options: ${spec.name}Options) {
57
- this.options = optionsSchema.parse(options);
725
+ if (requestBody.required) {
726
+ lines.push(`*This request body is required.*`);
727
+ }
728
+ if (requestBody.content) {
729
+ for (const [contentType, mediaType] of Object.entries(
730
+ requestBody.content
731
+ )) {
732
+ lines.push(`**Content Type:** \`${contentType}\``);
733
+ if (mediaType.schema) {
734
+ const schemaDocs = this.handle(mediaType.schema);
735
+ lines.push(...schemaDocs);
736
+ }
737
+ }
738
+ }
739
+ return lines;
58
740
  }
741
+ };
742
+ function toReadme(spec, generators) {
743
+ const markdown = [];
744
+ const propEmitter = new PropEmitter(spec);
745
+ forEachOperation({ spec }, (entry, operation) => {
746
+ const { method, path, name } = entry;
747
+ spec.components ??= {};
748
+ spec.components.schemas ??= {};
749
+ markdown.push(
750
+ `#### ${name || operation.operationId} | ${`_${method.toUpperCase()} ${path}_`}`
751
+ );
752
+ markdown.push(operation.summary || "");
753
+ const snippet = generators.generateSnippet(entry, operation);
754
+ markdown.push(`##### Example usage`);
755
+ markdown.push(snippet);
756
+ const requestBodyContent = propEmitter.requestBody(operation.requestBody);
757
+ if (requestBodyContent.length > 1) {
758
+ markdown.push(requestBodyContent.join("\n\n"));
759
+ }
760
+ markdown.push(`##### Responses`);
761
+ for (const status in operation.responses) {
762
+ const response = operation.responses[status];
763
+ markdown.push(`<details>`);
764
+ markdown.push(
765
+ `<summary><b>${status}</b> <i>${response.description}</i></summary>`
766
+ );
767
+ if (!isEmpty2(response.content)) {
768
+ for (const [contentType, mediaType] of Object.entries(
769
+ response.content
770
+ )) {
771
+ markdown.push(`
772
+ **Content Type:** \`${contentType}\``);
773
+ if (mediaType.schema) {
774
+ const schemaDocs = propEmitter.handle(mediaType.schema);
775
+ markdown.push(...schemaDocs.map((l) => `
776
+ ${l}`));
777
+ }
778
+ }
779
+ }
780
+ markdown.push(`</details>`);
781
+ }
782
+ });
783
+ return markdown.join("\n\n");
784
+ }
59
785
 
60
- async request<E extends keyof Endpoints>(
61
- endpoint: E,
62
- input: Endpoints[E]['input'],
63
- options?: { signal?: AbortSignal, headers?: HeadersInit },
64
- ) ${style.errorAsValue ? `: Promise<readonly [Endpoints[E]['output'], Endpoints[E]['error'] | null]>` : `: Promise<Endpoints[E]['output']>`} {
65
- const route = schemas[endpoint];
66
- const result = await dispatch(Object.assign(this.#defaultInputs, input), route, {
67
- fetch: this.options.fetch,
68
- interceptors: [
69
- createHeadersInterceptor(() => this.defaultHeaders, options?.headers ?? {}),
70
- createBaseUrlInterceptor(() => this.options.baseUrl),
71
- ],
72
- signal: options?.signal,
73
- });
74
- return ${style.errorAsValue ? `result as [Endpoints[E]['output'], Endpoints[E]['error'] | null]` : `result as Endpoints[E]['output']`};
786
+ // packages/spec/dist/lib/operation.js
787
+ import { camelcase as camelcase2 } from "stringcase";
788
+ import { followRef as followRef3, isRef as isRef5 } from "@sdk-it/core/ref.js";
789
+
790
+ // packages/spec/dist/lib/pagination/pagination.js
791
+ import { isRef as isRef4 } from "@sdk-it/core/ref.js";
792
+ import { isEmpty as isEmpty3 } from "@sdk-it/core/utils.js";
793
+
794
+ // packages/spec/dist/lib/pagination/pagination-result.js
795
+ var import_pluralize2 = __toESM(require_pluralize(), 1);
796
+ var PRIMARY_TOP_TIER_KEYWORDS = [
797
+ "data",
798
+ "items",
799
+ "results",
800
+ "value"
801
+ ];
802
+ var PRIMARY_OTHER_KEYWORDS = [
803
+ "records",
804
+ "content",
805
+ "list",
806
+ "payload",
807
+ "entities",
808
+ "collection",
809
+ "users",
810
+ "products",
811
+ "orders",
812
+ "bookings",
813
+ "articles",
814
+ "posts",
815
+ "documents",
816
+ "events"
817
+ ];
818
+ var SECONDARY_KEYWORDS = ["entries", "rows", "elements"];
819
+ var PLURAL_DEPRIORITIZE_LIST = [
820
+ "status",
821
+ "success",
822
+ "address",
823
+ "details",
824
+ "properties",
825
+ "params",
826
+ "headers",
827
+ "cookies",
828
+ "series",
829
+ "links",
830
+ "meta",
831
+ "metadata",
832
+ "statistics",
833
+ "settings",
834
+ "options",
835
+ "permissions",
836
+ "credentials",
837
+ "diagnostics",
838
+ "warnings",
839
+ "errors",
840
+ "actions",
841
+ "attributes",
842
+ "categories",
843
+ "features",
844
+ "includes",
845
+ "tags"
846
+ ];
847
+ var HAS_MORE_PRIMARY_POSITIVE_EXACT = [
848
+ "hasmore",
849
+ "hasnext",
850
+ "hasnextpage",
851
+ "moreitems",
852
+ "moreitemsavailable",
853
+ "nextpage",
854
+ "nextpageexists",
855
+ "nextpageavailable",
856
+ "hasadditionalresults",
857
+ "moreresultsavailable",
858
+ "canloadmore",
859
+ "hasadditional",
860
+ "additionalitems",
861
+ "fetchmore"
862
+ ];
863
+ var HAS_MORE_SECONDARY_POSITIVE_EXACT = ["more", "next"];
864
+ var HAS_MORE_PRIMARY_INVERTED_EXACT = [
865
+ "islast",
866
+ "lastpage",
867
+ "endofresults",
868
+ "endoflist",
869
+ "nomoreitems",
870
+ "nomoredata",
871
+ "allitemsloaded",
872
+ "iscomplete",
873
+ "completed"
874
+ ];
875
+ var HAS_MORE_POSITIVE_REGEX_PATTERNS2 = [
876
+ "\\bhas_?more\\b",
877
+ "\\bhas_?next\\b",
878
+ // e.g., itemsHasNext, items_has_next
879
+ "\\bmore_?items\\b",
880
+ "\\bnext_?page\\b",
881
+ // e.g., userNextPageFlag
882
+ "\\badditional\\b",
883
+ // e.g., hasAdditionalData, additional_results_exist
884
+ "\\bcontinuation\\b",
885
+ // e.g., continuationAvailable, has_continuation_token
886
+ "\\bmore_?results\\b",
887
+ "\\bpage_?available\\b",
888
+ "\\bnext(?:_?(page))?\\b"
889
+ ];
890
+ var COMPILED_HAS_MORE_POSITIVE_REGEXES2 = HAS_MORE_POSITIVE_REGEX_PATTERNS2.map((p) => new RegExp(p, "i"));
891
+ var HAS_MORE_INVERTED_REGEX_PATTERNS2 = [
892
+ "\\bis_?last\\b",
893
+ // e.g., pageIsLast
894
+ "\\blast_?page\\b",
895
+ // e.g., resultsAreLastPage
896
+ "\\bend_?of_?(data|results|list|items|stream)\\b",
897
+ "\\bno_?more_?(items|data|results)?\\b",
898
+ "\\ball_?(items_?)?loaded\\b",
899
+ "\\bis_?complete\\b"
900
+ ];
901
+ var COMPILED_HAS_MORE_INVERTED_REGEXES2 = HAS_MORE_INVERTED_REGEX_PATTERNS2.map((p) => new RegExp(p, "i"));
902
+ function getItemsName(properties) {
903
+ const arrayPropertyNames = [];
904
+ for (const propName in properties) {
905
+ if (propName in properties) {
906
+ const propSchema = properties[propName];
907
+ if (propSchema && propSchema.type === "array") {
908
+ arrayPropertyNames.push(propName);
909
+ }
910
+ }
75
911
  }
76
-
77
- async prepare<E extends keyof Endpoints>(
78
- endpoint: E,
79
- input: Endpoints[E]['input'],
80
- options?: { headers?: HeadersInit },
81
- ): ${style.errorAsValue ? `Promise<
82
- readonly [
83
- RequestConfig & {
84
- parse: (response: Response) => ReturnType<typeof parse>;
85
- },
86
- ParseError<(typeof schemas)[E]['schema']> | null,
87
- ]
88
- >` : `Promise<RequestConfig & {
89
- parse: (response: Response) => ReturnType<typeof parse>;
90
- }>`} {
91
- const route = schemas[endpoint];
92
-
93
- const interceptors = [
94
- createHeadersInterceptor(
95
- () => this.defaultHeaders,
96
- options?.headers ?? {},
97
- ),
98
- createBaseUrlInterceptor(() => this.options.baseUrl),
99
- ];
100
- const [parsedInput, parseError] = parseInput(route.schema, input);
101
- if (parseError) {
102
- ${style.errorAsValue ? "return [null as never, parseError as never] as const;" : "throw parseError;"}
912
+ if (arrayPropertyNames.length === 0) {
913
+ return null;
914
+ }
915
+ if (arrayPropertyNames.length === 1) {
916
+ return arrayPropertyNames[0];
917
+ }
918
+ let bestCandidate = null;
919
+ let candidateRank = Infinity;
920
+ const updateCandidate = (propName, rank) => {
921
+ if (rank < candidateRank) {
922
+ bestCandidate = propName;
923
+ candidateRank = rank;
103
924
  }
104
-
105
- let config = route.toRequest(parsedInput as never);
106
- for (const interceptor of interceptors) {
107
- if (interceptor.before) {
108
- config = await interceptor.before(config);
925
+ };
926
+ for (const propName of arrayPropertyNames) {
927
+ const lowerPropName = propName.toLowerCase();
928
+ if (PRIMARY_TOP_TIER_KEYWORDS.includes(lowerPropName)) {
929
+ updateCandidate(propName, 2);
930
+ continue;
931
+ }
932
+ if (candidateRank > 3 && PRIMARY_OTHER_KEYWORDS.includes(lowerPropName)) {
933
+ updateCandidate(propName, 3);
934
+ continue;
935
+ }
936
+ if (candidateRank > 4 && SECONDARY_KEYWORDS.includes(lowerPropName)) {
937
+ updateCandidate(propName, 4);
938
+ continue;
939
+ }
940
+ if (candidateRank > 5 && import_pluralize2.default.isPlural(propName) && !PLURAL_DEPRIORITIZE_LIST.includes(lowerPropName)) {
941
+ updateCandidate(propName, 5);
942
+ continue;
943
+ }
944
+ if (candidateRank > 6 && import_pluralize2.default.isPlural(propName) && PLURAL_DEPRIORITIZE_LIST.includes(lowerPropName)) {
945
+ updateCandidate(propName, 6);
946
+ continue;
947
+ }
948
+ }
949
+ if (bestCandidate) {
950
+ return bestCandidate;
951
+ }
952
+ return arrayPropertyNames[0];
953
+ }
954
+ function guess(properties) {
955
+ const booleanPropertyNames = [];
956
+ for (const propName in properties) {
957
+ if (Object.prototype.hasOwnProperty.call(properties, propName)) {
958
+ const propSchema = properties[propName];
959
+ if (propSchema && propSchema.type === "boolean" || propSchema.type === "integer") {
960
+ booleanPropertyNames.push(propName);
109
961
  }
110
962
  }
111
- const prepared = { ...config, parse: (response: Response) => parse(route, response) };
112
- return ${style.errorAsValue ? "[prepared, null as never] as const;" : "prepared"}
113
963
  }
114
-
115
- get defaultHeaders() {
116
- return ${defaultHeaders}
964
+ if (booleanPropertyNames.length === 0) {
965
+ return null;
117
966
  }
118
-
119
- get #defaultInputs() {
120
- return ${defaultInputs}
967
+ if (booleanPropertyNames.length === 1) {
968
+ return booleanPropertyNames[0];
121
969
  }
122
-
123
- setOptions(options: Partial<${spec.name}Options>) {
124
- const validated = optionsSchema.partial().parse(options);
125
-
126
- for (const key of Object.keys(validated) as (keyof ${spec.name}Options)[]) {
127
- if (validated[key] !== undefined) {
128
- (this.options[key] as typeof validated[typeof key]) = validated[key]!;
970
+ let bestCandidate = null;
971
+ let candidateRank = Infinity;
972
+ const updateCandidate = (propName, rank) => {
973
+ if (rank < candidateRank) {
974
+ bestCandidate = propName;
975
+ candidateRank = rank;
976
+ }
977
+ };
978
+ for (const propName of booleanPropertyNames) {
979
+ const normalizedForExactMatch = propName.toLowerCase().replace(/[-_]/g, "");
980
+ let currentPropRank = Infinity;
981
+ if (HAS_MORE_PRIMARY_POSITIVE_EXACT.includes(normalizedForExactMatch)) {
982
+ currentPropRank = 1;
983
+ } else if (HAS_MORE_SECONDARY_POSITIVE_EXACT.includes(normalizedForExactMatch)) {
984
+ currentPropRank = 2;
985
+ } else {
986
+ let foundPositiveRegex = false;
987
+ for (const regex of COMPILED_HAS_MORE_POSITIVE_REGEXES2) {
988
+ if (regex.test(propName)) {
989
+ currentPropRank = 3;
990
+ foundPositiveRegex = true;
991
+ break;
992
+ }
993
+ }
994
+ if (!foundPositiveRegex) {
995
+ if (HAS_MORE_PRIMARY_INVERTED_EXACT.includes(normalizedForExactMatch)) {
996
+ currentPropRank = 4;
997
+ } else {
998
+ for (const regex of COMPILED_HAS_MORE_INVERTED_REGEXES2) {
999
+ if (regex.test(propName)) {
1000
+ currentPropRank = 5;
1001
+ break;
1002
+ }
1003
+ }
1004
+ }
129
1005
  }
130
1006
  }
1007
+ updateCandidate(propName, currentPropRank);
131
1008
  }
132
- }`;
133
- };
1009
+ return bestCandidate;
1010
+ }
1011
+ function getHasMoreName(properties) {
1012
+ const rootGuess = guess(properties);
1013
+ if (rootGuess) {
1014
+ return rootGuess;
1015
+ }
1016
+ for (const propName in properties) {
1017
+ const propSchema = properties[propName];
1018
+ if (propSchema.type === "object" && propSchema.properties) {
1019
+ const nested = getHasMoreName(propSchema.properties);
1020
+ if (nested) {
1021
+ return propName + "." + nested;
1022
+ }
1023
+ }
1024
+ }
1025
+ return null;
1026
+ }
134
1027
 
135
- // packages/typescript/src/lib/generator.ts
136
- import { merge, template } from "lodash-es";
137
- import { join } from "node:path";
138
- import { camelcase as camelcase3, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
139
- import { followRef as followRef5, isEmpty, isRef as isRef6 } from "@sdk-it/core";
1028
+ // packages/spec/dist/lib/pagination/pagination.js
1029
+ var OFFSET_PARAM_REGEXES = [
1030
+ /\boffset\b/i,
1031
+ /\bskip\b/i,
1032
+ /\bstart(?:ing_at|_index)?\b/i,
1033
+ // e.g., start, starting_at, start_index
1034
+ /\bfrom\b/i
1035
+ ];
1036
+ var GENERIC_LIMIT_PARAM_REGEXES = [
1037
+ /\blimit\b/i,
1038
+ /\bcount\b/i,
1039
+ /\b(?:page_?)?size\b/i,
1040
+ // e.g., size, page_size, pagesize
1041
+ /\bmax_results\b/i,
1042
+ /\bnum_results\b/i,
1043
+ /\bshow\b/i,
1044
+ // Can sometimes mean limit
1045
+ /\bper_?page\b/i,
1046
+ // e.g., per_page, perpage
1047
+ /\bper-page\b/i,
1048
+ /\btake\b/i
1049
+ ];
1050
+ var PAGE_NUMBER_REGEXES = [
1051
+ /^page$/i,
1052
+ // Exact match for "page"
1053
+ /^p$/i,
1054
+ // Exact match for "p" (common shorthand)
1055
+ /\bpage_?(?:number|num|idx|index)\b/i
1056
+ // e.g., page_number, pageNumber, page_num, page_idx
1057
+ ];
1058
+ var PAGE_SIZE_REGEXES = [
1059
+ /\bpage_?size\b/i,
1060
+ // e.g., page_size, pagesize
1061
+ /^size$/i,
1062
+ // Exact "size"
1063
+ // /\bsize\b/i, // Broader "size" - can be ambiguous, prefer more specific ones first
1064
+ /\blimit\b/i,
1065
+ // Limit is often used for page size
1066
+ /\bcount\b/i,
1067
+ // Count can also be used for page size
1068
+ /\bper_?page\b/i,
1069
+ // e.g., per_page, perpage
1070
+ /\bper-page\b/i,
1071
+ /\bnum_?(?:items|records|results)\b/i,
1072
+ // e.g., num_items, numitems
1073
+ /\bresults_?per_?page\b/i
1074
+ ];
1075
+ var CURSOR_REGEXES = [
1076
+ /\bcursor\b/i,
1077
+ /\bafter(?:_?cursor)?\b/i,
1078
+ // e.g., after, after_cursor
1079
+ /\bbefore(?:_?cursor)?\b/i,
1080
+ // e.g., before, before_cursor
1081
+ /\b(next|prev|previous)_?(?:page_?)?token\b/i,
1082
+ // e.g., next_page_token, nextPageToken, prev_token
1083
+ /\b(next|prev|previous)_?cursor\b/i,
1084
+ // e.g., next_cursor, previousCursor
1085
+ /\bcontinuation(?:_?token)?\b/i,
1086
+ // e.g., continuation, continuation_token
1087
+ /\bpage(?:_?(token|id))?\b/i,
1088
+ // e.g., after, after_cursor
1089
+ /\bstart_?(?:key|cursor|token|after)\b/i
1090
+ // e.g., start_key, startCursor, startToken, startAfter
1091
+ ];
1092
+ var CURSOR_LIMIT_REGEXES = [
1093
+ /\blimit\b/i,
1094
+ /\bcount\b/i,
1095
+ /\bsize\b/i,
1096
+ // General size
1097
+ /\bfirst\b/i,
1098
+ // Common in Relay-style cursor pagination (forward pagination)
1099
+ /\blast\b/i,
1100
+ // Common in Relay-style cursor pagination (backward pagination)
1101
+ /\bpage_?size\b/i,
1102
+ // Sometimes page_size is used with cursors
1103
+ /\bnum_?(?:items|records|results)\b/i,
1104
+ // e.g., num_items
1105
+ /\bmax_?items\b/i,
1106
+ /\btake\b/i
1107
+ ];
1108
+ function findParamAndKeyword(queryParams, regexes, excludeParamName) {
1109
+ for (const param of queryParams) {
1110
+ if (param.name === excludeParamName) {
1111
+ continue;
1112
+ }
1113
+ for (const regex of regexes) {
1114
+ const match = param.name.match(regex);
1115
+ if (match) {
1116
+ return { param, keyword: match[0] };
1117
+ }
1118
+ }
1119
+ }
1120
+ return null;
1121
+ }
1122
+ function isOffsetPagination(operation, parameters) {
1123
+ const offsetMatch = findParamAndKeyword(parameters, OFFSET_PARAM_REGEXES);
1124
+ if (!offsetMatch)
1125
+ return null;
1126
+ const limitMatch = findParamAndKeyword(
1127
+ parameters,
1128
+ GENERIC_LIMIT_PARAM_REGEXES,
1129
+ offsetMatch.param.name
1130
+ );
1131
+ if (!limitMatch)
1132
+ return null;
1133
+ return {
1134
+ type: "offset",
1135
+ offsetParamName: offsetMatch.param.name,
1136
+ offsetKeyword: offsetMatch.keyword,
1137
+ limitParamName: limitMatch.param.name,
1138
+ limitKeyword: limitMatch.keyword
1139
+ };
1140
+ }
1141
+ function isPagePagination(operation) {
1142
+ const queryParams = operation.parameters.filter((p) => p.in === "query").filter(
1143
+ (it) => it.schema && !isRef4(it.schema) && it.schema.type === "integer"
1144
+ );
1145
+ if (queryParams.length < 2)
1146
+ return null;
1147
+ const pageNoMatch = findParamAndKeyword(queryParams, PAGE_NUMBER_REGEXES);
1148
+ if (!pageNoMatch)
1149
+ return null;
1150
+ const pageSizeMatch = findParamAndKeyword(
1151
+ queryParams,
1152
+ PAGE_SIZE_REGEXES,
1153
+ pageNoMatch.param.name
1154
+ );
1155
+ if (!pageSizeMatch)
1156
+ return null;
1157
+ return {
1158
+ type: "page",
1159
+ pageNumberParamName: pageNoMatch.param.name,
1160
+ pageNumberKeyword: pageNoMatch.keyword,
1161
+ pageSizeParamName: pageSizeMatch.param.name,
1162
+ pageSizeKeyword: pageSizeMatch.keyword
1163
+ };
1164
+ }
1165
+ function isCursorPagination(operation) {
1166
+ const queryParams = operation.parameters.filter((p) => p.in === "query");
1167
+ if (queryParams.length < 2)
1168
+ return null;
1169
+ const cursorMatch = findParamAndKeyword(queryParams, CURSOR_REGEXES);
1170
+ if (!cursorMatch)
1171
+ return null;
1172
+ const limitMatch = findParamAndKeyword(
1173
+ queryParams,
1174
+ CURSOR_LIMIT_REGEXES,
1175
+ cursorMatch.param.name
1176
+ );
1177
+ if (!limitMatch)
1178
+ return null;
1179
+ return {
1180
+ type: "cursor",
1181
+ cursorParamName: cursorMatch.param.name,
1182
+ cursorKeyword: cursorMatch.keyword,
1183
+ limitParamName: limitMatch.param.name,
1184
+ limitKeyword: limitMatch.keyword
1185
+ };
1186
+ }
1187
+ function guessPagination(operation, body, response) {
1188
+ const bodyParameters = body && body.properties ? Object.keys(body.properties).map((it) => ({ name: it })) : [];
1189
+ const parameters = operation.parameters;
1190
+ if (isEmpty3(operation.parameters) && isEmpty3(bodyParameters)) {
1191
+ return { type: "none", reason: "no parameters" };
1192
+ }
1193
+ if (!response) {
1194
+ return { type: "none", reason: "no response" };
1195
+ }
1196
+ if (!response.properties) {
1197
+ return { type: "none", reason: "empty response" };
1198
+ }
1199
+ const properties = response.properties;
1200
+ const itemsKey = getItemsName(properties);
1201
+ if (!itemsKey) {
1202
+ return { type: "none", reason: "no items key" };
1203
+ }
1204
+ const hasMoreKey = getHasMoreName(excludeKey(properties, itemsKey));
1205
+ if (!hasMoreKey) {
1206
+ return { type: "none", reason: "no hasMore key" };
1207
+ }
1208
+ const pagination = isOffsetPagination(operation, [...parameters, ...bodyParameters]) || isPagePagination(operation) || isCursorPagination(operation);
1209
+ return pagination ? { ...pagination, items: itemsKey, hasMore: hasMoreKey } : { type: "none", reason: "no pagination" };
1210
+ }
1211
+ function excludeKey(obj, key) {
1212
+ const { [key]: _, ...rest } = obj;
1213
+ return rest;
1214
+ }
140
1215
 
141
1216
  // packages/spec/dist/lib/operation.js
142
- import { camelcase } from "stringcase";
143
- import { followRef, isRef } from "@sdk-it/core";
1217
+ function augmentSpec(config) {
1218
+ config.spec.paths ??= {};
1219
+ const paths = {};
1220
+ for (const [path, pathItem] of Object.entries(config.spec.paths)) {
1221
+ const { parameters = [], ...methods2 } = pathItem;
1222
+ const fixedPath = path.replace(/:([^/]+)/g, "{$1}");
1223
+ for (const [method, operation] of Object.entries(methods2)) {
1224
+ const formatOperationId = config.operationId ?? defaults.operationId;
1225
+ const formatTag = config.tag ?? defaults.tag;
1226
+ const operationId = formatOperationId(operation, fixedPath, method);
1227
+ const operationTag = formatTag(operation, fixedPath);
1228
+ const requestBody = isRef5(operation.requestBody) ? followRef3(config.spec, operation.requestBody.$ref) : operation.requestBody;
1229
+ const tunedOperation = {
1230
+ ...operation,
1231
+ parameters: [...parameters, ...operation.parameters ?? []].map(
1232
+ (it) => isRef5(it) ? followRef3(config.spec, it.$ref) : it
1233
+ ),
1234
+ tags: [operationTag],
1235
+ operationId,
1236
+ responses: resolveResponses(config.spec, operation),
1237
+ requestBody
1238
+ };
1239
+ tunedOperation["x-pagination"] = toPagination(
1240
+ config.spec,
1241
+ tunedOperation
1242
+ );
1243
+ Object.assign(paths, {
1244
+ [fixedPath]: {
1245
+ ...paths[fixedPath],
1246
+ [method]: tunedOperation
1247
+ }
1248
+ });
1249
+ }
1250
+ }
1251
+ return { ...config.spec, paths };
1252
+ }
1253
+ function toPagination(spec, tunedOperation) {
1254
+ if (tunedOperation["x-pagination"]) {
1255
+ return tunedOperation["x-pagination"];
1256
+ }
1257
+ const schema = getResponseContentSchema(
1258
+ spec,
1259
+ tunedOperation.responses["200"],
1260
+ "application/json"
1261
+ );
1262
+ const pagination = guessPagination(
1263
+ tunedOperation,
1264
+ tunedOperation.requestBody ? getRequestContentSchema(
1265
+ spec,
1266
+ tunedOperation.requestBody,
1267
+ "application/json"
1268
+ ) : void 0,
1269
+ schema
1270
+ );
1271
+ if (pagination && pagination.type !== "none" && schema) {
1272
+ return pagination;
1273
+ }
1274
+ return void 0;
1275
+ }
1276
+ function getResponseContentSchema(spec, response, type) {
1277
+ if (!response) {
1278
+ return void 0;
1279
+ }
1280
+ const content = response.content;
1281
+ if (!content) {
1282
+ return void 0;
1283
+ }
1284
+ for (const contentType in content) {
1285
+ if (contentType.toLowerCase() === type.toLowerCase()) {
1286
+ return isRef5(content[contentType].schema) ? followRef3(spec, content[contentType].schema.$ref) : content[contentType].schema;
1287
+ }
1288
+ }
1289
+ return void 0;
1290
+ }
1291
+ function getRequestContentSchema(spec, requestBody, type) {
1292
+ const content = requestBody.content;
1293
+ if (!content) {
1294
+ return void 0;
1295
+ }
1296
+ for (const contentType in content) {
1297
+ if (contentType.toLowerCase() === type.toLowerCase()) {
1298
+ return isRef5(content[contentType].schema) ? followRef3(spec, content[contentType].schema.$ref) : content[contentType].schema;
1299
+ }
1300
+ }
1301
+ return void 0;
1302
+ }
144
1303
  var defaults = {
145
1304
  operationId: (operation, path, method) => {
146
1305
  if (operation.operationId) {
147
- return camelcase(operation.operationId);
1306
+ return camelcase2(operation.operationId);
148
1307
  }
149
1308
  const metadata = operation["x-oaiMeta"];
150
1309
  if (metadata && metadata.name) {
151
- return camelcase(metadata.name);
1310
+ return camelcase2(metadata.name);
152
1311
  }
153
- return camelcase(
1312
+ return camelcase2(
154
1313
  [method, ...path.replace(/[\\/\\{\\}]/g, " ").split(" ")].filter(Boolean).join(" ").trim()
155
1314
  );
156
1315
  },
@@ -162,37 +1321,28 @@ function resolveResponses(spec, operation) {
162
1321
  const responses = operation.responses ?? {};
163
1322
  const resolved = {};
164
1323
  for (const status in responses) {
165
- const response = isRef(responses[status]) ? followRef(spec, responses[status].$ref) : responses[status];
1324
+ const response = isRef5(responses[status]) ? followRef3(spec, responses[status].$ref) : responses[status];
166
1325
  resolved[status] = response;
167
1326
  }
168
1327
  return resolved;
169
1328
  }
170
- function forEachOperation(config, callback) {
1329
+ function forEachOperation2(config, callback) {
171
1330
  const result = [];
172
1331
  for (const [path, pathItem] of Object.entries(config.spec.paths ?? {})) {
173
1332
  const { parameters = [], ...methods2 } = pathItem;
174
- const fixedPath = path.replace(/:([^/]+)/g, "{$1}");
175
1333
  for (const [method, operation] of Object.entries(methods2)) {
176
- const formatOperationId = config.operationId ?? defaults.operationId;
177
- const formatTag = config.tag ?? defaults.tag;
178
- const operationName = formatOperationId(operation, fixedPath, method);
179
- const operationTag = formatTag(operation, fixedPath);
180
1334
  const metadata = operation["x-oaiMeta"] ?? {};
1335
+ const operationTag = operation.tags?.[0];
181
1336
  result.push(
182
1337
  callback(
183
1338
  {
184
1339
  name: metadata.name,
185
1340
  method,
186
- path: fixedPath,
1341
+ path,
187
1342
  groupName: operationTag,
188
1343
  tag: operationTag
189
1344
  },
190
- {
191
- ...operation,
192
- parameters: [...parameters, ...operation.parameters ?? []],
193
- operationId: operationName,
194
- responses: resolveResponses(config.spec, operation)
195
- }
1345
+ operation
196
1346
  )
197
1347
  );
198
1348
  }
@@ -264,7 +1414,7 @@ function sanitizeTag(camelCasedTag) {
264
1414
  if (/^\d/.test(camelCasedTag)) {
265
1415
  return `_${camelCasedTag}`;
266
1416
  }
267
- return reservedKeywords.has(camelcase(camelCasedTag)) ? `${camelCasedTag}_` : camelCasedTag;
1417
+ return reservedKeywords.has(camelcase2(camelCasedTag)) ? `${camelCasedTag}_` : camelCasedTag;
268
1418
  }
269
1419
  function determineGenericTag(pathString, operation) {
270
1420
  const operationId = operation.operationId || "";
@@ -296,7 +1446,7 @@ function determineGenericTag(pathString, operation) {
296
1446
  for (let i = potentialCandidates.length - 1; i >= 0; i--) {
297
1447
  const segment = potentialCandidates[i];
298
1448
  if (!segment.startsWith("@")) {
299
- return sanitizeTag(camelcase(segment));
1449
+ return sanitizeTag(camelcase2(segment));
300
1450
  }
301
1451
  }
302
1452
  const canFallbackToPathSegment = potentialCandidates.length > 0;
@@ -331,17 +1481,17 @@ function determineGenericTag(pathString, operation) {
331
1481
  }
332
1482
  if (nextPartStartIndex !== -1 && nextPartStartIndex < operationId.length) {
333
1483
  const remainingOriginalSubstring = operationId.substring(nextPartStartIndex);
334
- const potentialTag = camelcase(remainingOriginalSubstring);
1484
+ const potentialTag = camelcase2(remainingOriginalSubstring);
335
1485
  if (potentialTag) {
336
1486
  return sanitizeTag(potentialTag);
337
1487
  }
338
1488
  }
339
- const potentialTagJoined = camelcase(validParts.slice(1).join("_"));
1489
+ const potentialTagJoined = camelcase2(validParts.slice(1).join("_"));
340
1490
  if (potentialTagJoined) {
341
1491
  return sanitizeTag(potentialTagJoined);
342
1492
  }
343
1493
  }
344
- const potentialTagFull = camelcase(operationId);
1494
+ const potentialTagFull = camelcase2(operationId);
345
1495
  if (potentialTagFull) {
346
1496
  const isResultSingleVerb = validParts.length === 1 && isFirstPartVerb;
347
1497
  if (!(isResultSingleVerb && canFallbackToPathSegment)) {
@@ -350,39 +1500,227 @@ function determineGenericTag(pathString, operation) {
350
1500
  }
351
1501
  }
352
1502
  }
353
- const firstPartCamel = camelcase(firstPart);
1503
+ const firstPartCamel = camelcase2(firstPart);
354
1504
  if (firstPartCamel) {
355
1505
  const isFirstPartCamelVerb = commonVerbs.has(firstPartCamel);
356
1506
  if (!isFirstPartCamelVerb || validParts.length === 1 || !canFallbackToPathSegment) {
357
1507
  return sanitizeTag(firstPartCamel);
358
1508
  }
359
1509
  }
360
- if (isFirstPartVerb && validParts.length > 1 && validParts[1] && canFallbackToPathSegment) {
361
- const secondPartCamel = camelcase(validParts[1]);
362
- if (secondPartCamel) {
363
- return sanitizeTag(secondPartCamel);
364
- }
1510
+ if (isFirstPartVerb && validParts.length > 1 && validParts[1] && canFallbackToPathSegment) {
1511
+ const secondPartCamel = camelcase2(validParts[1]);
1512
+ if (secondPartCamel) {
1513
+ return sanitizeTag(secondPartCamel);
1514
+ }
1515
+ }
1516
+ }
1517
+ }
1518
+ if (potentialCandidates.length > 0) {
1519
+ let firstCandidate = potentialCandidates[0];
1520
+ if (firstCandidate.startsWith("@")) {
1521
+ firstCandidate = firstCandidate.substring(1);
1522
+ }
1523
+ if (firstCandidate) {
1524
+ return sanitizeTag(camelcase2(firstCandidate));
1525
+ }
1526
+ }
1527
+ console.warn(
1528
+ `Could not determine a suitable tag for path: ${pathString}, operationId: ${operationId}. Using 'unknown'.`
1529
+ );
1530
+ return "unknown";
1531
+ }
1532
+ function patchParameters(spec, objectSchema, operation) {
1533
+ const securitySchemes = spec.components?.securitySchemes ?? {};
1534
+ const securityOptions = securityToOptions(
1535
+ spec,
1536
+ operation.security ?? [],
1537
+ securitySchemes
1538
+ );
1539
+ objectSchema.properties ??= {};
1540
+ objectSchema.required ??= [];
1541
+ for (const param of operation.parameters) {
1542
+ if (param.required) {
1543
+ objectSchema.required.push(param.name);
1544
+ }
1545
+ objectSchema.properties[param.name] = isRef5(param.schema) ? followRef3(spec, param.schema.$ref) : param.schema ?? { type: "string" };
1546
+ }
1547
+ for (const param of securityOptions) {
1548
+ objectSchema.required = (objectSchema.required ?? []).filter(
1549
+ (name) => name !== param.name
1550
+ );
1551
+ objectSchema.properties[param.name] = isRef5(param.schema) ? followRef3(spec, param.schema.$ref) : param.schema ?? { type: "string" };
1552
+ }
1553
+ }
1554
+ function securityToOptions(spec, security2, securitySchemes, staticIn) {
1555
+ securitySchemes ??= {};
1556
+ const parameters = [];
1557
+ for (const it of security2) {
1558
+ const [name] = Object.keys(it);
1559
+ if (!name) {
1560
+ continue;
1561
+ }
1562
+ const schema = isRef5(securitySchemes[name]) ? followRef3(spec, securitySchemes[name].$ref) : securitySchemes[name];
1563
+ if (schema.type === "http") {
1564
+ parameters.push({
1565
+ in: staticIn ?? "header",
1566
+ name: "authorization",
1567
+ schema: { type: "string" }
1568
+ });
1569
+ continue;
1570
+ }
1571
+ if (schema.type === "apiKey") {
1572
+ if (!schema.in) {
1573
+ throw new Error(`apiKey security schema must have an "in" field`);
1574
+ }
1575
+ if (!schema.name) {
1576
+ throw new Error(`apiKey security schema must have a "name" field`);
365
1577
  }
1578
+ parameters.push({
1579
+ in: staticIn ?? schema.in,
1580
+ name: schema.name,
1581
+ schema: { type: "string" }
1582
+ });
1583
+ continue;
366
1584
  }
367
1585
  }
368
- if (potentialCandidates.length > 0) {
369
- let firstCandidate = potentialCandidates[0];
370
- if (firstCandidate.startsWith("@")) {
371
- firstCandidate = firstCandidate.substring(1);
1586
+ return parameters;
1587
+ }
1588
+
1589
+ // packages/typescript/src/lib/client.ts
1590
+ import { toLitObject } from "@sdk-it/core";
1591
+ var client_default = (spec, style) => {
1592
+ const optionsEntries = Object.entries(spec.options).map(
1593
+ ([key, value]) => [`'${key}'`, value]
1594
+ );
1595
+ const defaultHeaders = `{${optionsEntries.filter(([, value]) => value.in === "header").map(
1596
+ ([key, value]) => `${key}: this.options[${value.optionName ? `'${value.optionName}'` : key}]`
1597
+ ).join(",\n")}}`;
1598
+ const defaultInputs = `{${optionsEntries.filter(([, value]) => value.in === "input").map(
1599
+ ([key, value]) => `${key}: this.options[${value.optionName ? `'${value.optionName}'` : key}]`
1600
+ ).join(",\n")}}`;
1601
+ const specOptions = {
1602
+ ...Object.fromEntries(
1603
+ optionsEntries.map(([key, value]) => [value.optionName ?? key, value])
1604
+ ),
1605
+ fetch: {
1606
+ schema: "fetchType"
1607
+ },
1608
+ baseUrl: {
1609
+ schema: spec.servers.length ? `z.enum(servers).default(servers[0])` : "z.string()"
372
1610
  }
373
- if (firstCandidate) {
374
- return sanitizeTag(camelcase(firstCandidate));
1611
+ };
1612
+ return `import z from 'zod';
1613
+ import type { HeadersInit, RequestConfig } from './http/${spec.makeImport("request")}';
1614
+ import { fetchType, parse } from './http/${spec.makeImport("dispatcher")}';
1615
+ import schemas from './api/${spec.makeImport("schemas")}';
1616
+ import {
1617
+ createBaseUrlInterceptor,
1618
+ createHeadersInterceptor,
1619
+ } from './http/${spec.makeImport("interceptors")}';
1620
+
1621
+ import { parseInput, type ParseError } from './http/${spec.makeImport("parser")}';
1622
+
1623
+ ${spec.servers.length ? `export const servers = ${JSON.stringify(spec.servers, null, 2)} as const` : ""}
1624
+ const optionsSchema = z.object(${toLitObject(specOptions, (x) => x.schema)});
1625
+ ${spec.servers.length ? `export type Servers = typeof servers[number];` : ""}
1626
+
1627
+ type ${spec.name}Options = z.infer<typeof optionsSchema>;
1628
+
1629
+ export class ${spec.name} {
1630
+ public options: ${spec.name}Options
1631
+ constructor(options: ${spec.name}Options) {
1632
+ this.options = optionsSchema.parse(options);
1633
+ }
1634
+
1635
+ async request<const E extends keyof typeof schemas>(
1636
+ endpoint: E,
1637
+ input: z.infer<(typeof schemas)[E]['schema']>,
1638
+ options?: { signal?: AbortSignal, headers?: HeadersInit },
1639
+ ) ${style.errorAsValue ? `: Promise<Awaited<ReturnType<(typeof schemas)[E]['dispatch']>>| [never, ParseError<(typeof schemas)[E]['schema']>]>` : `: Promise<Awaited<ReturnType<(typeof schemas)[E]['dispatch']>>>`} {
1640
+ const route = schemas[endpoint];
1641
+ const withDefaultInputs = Object.assign({}, this.#defaultInputs, input);
1642
+ const [parsedInput, parseError] = parseInput(route.schema, withDefaultInputs);
1643
+ if (parseError) {
1644
+ ${style.errorAsValue ? "return [null as never, parseError as never] as const;" : "throw parseError;"}
375
1645
  }
1646
+ const result = await route.dispatch(parsedInput as never, {
1647
+ fetch: this.options.fetch,
1648
+ interceptors: [
1649
+ createHeadersInterceptor(() => this.defaultHeaders, options?.headers ?? {}),
1650
+ createBaseUrlInterceptor(() => this.options.baseUrl),
1651
+ ],
1652
+ signal: options?.signal,
1653
+ });
1654
+ return result as Awaited<ReturnType<(typeof schemas)[E]['dispatch']>>;
376
1655
  }
377
- console.warn(
378
- `Could not determine a suitable tag for path: ${pathString}, operationId: ${operationId}. Using 'unknown'.`
379
- );
380
- return "unknown";
381
- }
1656
+
1657
+ async prepare<const E extends keyof typeof schemas>(
1658
+ endpoint: E,
1659
+ input: z.infer<(typeof schemas)[E]['schema']>,
1660
+ options?: { headers?: HeadersInit },
1661
+ ): ${style.errorAsValue ? `Promise<
1662
+ readonly [
1663
+ RequestConfig & {
1664
+ parse: (response: Response) => ReturnType<typeof parse>;
1665
+ },
1666
+ ParseError<(typeof schemas)[E]['schema']> | null,
1667
+ ]
1668
+ >` : `Promise<RequestConfig & {
1669
+ parse: (response: Response) => ReturnType<typeof parse>;
1670
+ }>`} {
1671
+ const route = schemas[endpoint];
1672
+
1673
+ const interceptors = [
1674
+ createHeadersInterceptor(
1675
+ () => this.defaultHeaders,
1676
+ options?.headers ?? {},
1677
+ ),
1678
+ createBaseUrlInterceptor(() => this.options.baseUrl),
1679
+ ];
1680
+ const [parsedInput, parseError] = parseInput(route.schema, input);
1681
+ if (parseError) {
1682
+ ${style.errorAsValue ? "return [null as never, parseError as never] as const;" : "throw parseError;"}
1683
+ }
1684
+
1685
+ let config = route.toRequest(parsedInput as never);
1686
+ for (const interceptor of interceptors) {
1687
+ if (interceptor.before) {
1688
+ config = await interceptor.before(config);
1689
+ }
1690
+ }
1691
+ const prepared = { ...config, parse: (response: Response) => parse(route.output, response) as never };
1692
+ return ${style.errorAsValue ? "[prepared, null as never] as const;" : "prepared as any"}
1693
+ }
1694
+
1695
+ get defaultHeaders() {
1696
+ return ${defaultHeaders}
1697
+ }
1698
+
1699
+ get #defaultInputs() {
1700
+ return ${defaultInputs}
1701
+ }
1702
+
1703
+ setOptions(options: Partial<${spec.name}Options>) {
1704
+ const validated = optionsSchema.partial().parse(options);
1705
+
1706
+ for (const key of Object.keys(validated) as (keyof ${spec.name}Options)[]) {
1707
+ if (validated[key] !== undefined) {
1708
+ (this.options[key] as typeof validated[typeof key]) = validated[key]!;
1709
+ }
1710
+ }
1711
+ }
1712
+ }`;
1713
+ };
1714
+
1715
+ // packages/typescript/src/lib/generator.ts
1716
+ import { merge, template } from "lodash-es";
1717
+ import { join } from "node:path";
1718
+ import { camelcase as camelcase4, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
1719
+ import { followRef as followRef7, isEmpty as isEmpty4, isRef as isRef10 } from "@sdk-it/core";
382
1720
 
383
1721
  // packages/typescript/src/lib/emitters/zod.ts
384
- import { cleanRef, followRef as followRef2, isRef as isRef2 } from "@sdk-it/core";
385
- var ZodDeserialzer = class {
1722
+ import { cleanRef, followRef as followRef4, isRef as isRef6 } from "@sdk-it/core";
1723
+ var ZodEmitter = class {
386
1724
  generatedRefs = /* @__PURE__ */ new Set();
387
1725
  #spec;
388
1726
  #onRef;
@@ -463,7 +1801,7 @@ var ZodDeserialzer = class {
463
1801
  this.generatedRefs.add(schemaName);
464
1802
  this.#onRef?.(
465
1803
  schemaName,
466
- this.handle(followRef2(this.#spec, $ref), required)
1804
+ this.handle(followRef4(this.#spec, $ref), required)
467
1805
  );
468
1806
  return schemaName;
469
1807
  }
@@ -593,7 +1931,7 @@ var ZodDeserialzer = class {
593
1931
  return { base, defaultValue };
594
1932
  }
595
1933
  handle(schema, required) {
596
- if (isRef2(schema)) {
1934
+ if (isRef6(schema)) {
597
1935
  return `${this.ref(schema.$ref, true)}${appendOptional(required)}`;
598
1936
  }
599
1937
  if (schema.allOf && Array.isArray(schema.allOf)) {
@@ -639,12 +1977,12 @@ function appendDefault(defaultValue) {
639
1977
 
640
1978
  // packages/typescript/src/lib/sdk.ts
641
1979
  import { get } from "lodash-es";
642
- import { camelcase as camelcase2, pascalcase, spinalcase } from "stringcase";
643
- import { followRef as followRef4, isRef as isRef5, toLitObject as toLitObject2 } from "@sdk-it/core";
1980
+ import { camelcase as camelcase3, pascalcase, spinalcase } from "stringcase";
1981
+ import { isRef as isRef9, toLitObject as toLitObject2 } from "@sdk-it/core";
644
1982
 
645
1983
  // packages/typescript/src/lib/emitters/interface.ts
646
- import { cleanRef as cleanRef2, followRef as followRef3, isRef as isRef3 } from "@sdk-it/core";
647
- var TypeScriptDeserialzer = class {
1984
+ import { cleanRef as cleanRef2, followRef as followRef5, isRef as isRef7 } from "@sdk-it/core";
1985
+ var TypeScriptEmitter = class {
648
1986
  generatedRefs = /* @__PURE__ */ new Set();
649
1987
  #spec;
650
1988
  #onRef;
@@ -656,7 +1994,7 @@ var TypeScriptDeserialzer = class {
656
1994
  return `'${value}'`;
657
1995
  };
658
1996
  #isInternal = (schema) => {
659
- return isRef3(schema) ? false : !!schema["x-internal"];
1997
+ return isRef7(schema) ? false : !!schema["x-internal"];
660
1998
  };
661
1999
  /**
662
2000
  * Handle objects (properties)
@@ -676,7 +2014,7 @@ var TypeScriptDeserialzer = class {
676
2014
  propEntries.push("[key: string]: any");
677
2015
  }
678
2016
  }
679
- return `{ ${propEntries.join("; ")} }`;
2017
+ return `${propEntries.length ? `{ ${propEntries.join("; ")} }` : "unknown"}`;
680
2018
  }
681
2019
  /**
682
2020
  * Handle arrays (items could be a single schema or a tuple)
@@ -724,7 +2062,7 @@ var TypeScriptDeserialzer = class {
724
2062
  this.generatedRefs.add(schemaName);
725
2063
  this.#onRef?.(
726
2064
  schemaName,
727
- this.handle(followRef3(this.#spec, $ref), required)
2065
+ this.handle(followRef5(this.#spec, $ref), required)
728
2066
  );
729
2067
  return appendOptional2(schemaName, required);
730
2068
  }
@@ -786,7 +2124,7 @@ var TypeScriptDeserialzer = class {
786
2124
  return appendOptional2(type, required);
787
2125
  }
788
2126
  handle(schema, required) {
789
- if (isRef3(schema)) {
2127
+ if (isRef7(schema)) {
790
2128
  return this.ref(schema.$ref, required);
791
2129
  }
792
2130
  if (schema.allOf && Array.isArray(schema.allOf)) {
@@ -831,8 +2169,8 @@ function appendOptional2(type, isRequired) {
831
2169
  }
832
2170
 
833
2171
  // packages/typescript/src/lib/utils.ts
834
- import { isRef as isRef4, removeDuplicates } from "@sdk-it/core";
835
- function securityToOptions(security2, securitySchemes, staticIn) {
2172
+ import { followRef as followRef6, isRef as isRef8, removeDuplicates } from "@sdk-it/core";
2173
+ function securityToOptions2(spec, security2, securitySchemes, staticIn) {
836
2174
  securitySchemes ??= {};
837
2175
  const options = {};
838
2176
  for (const it of security2) {
@@ -840,10 +2178,7 @@ function securityToOptions(security2, securitySchemes, staticIn) {
840
2178
  if (!name) {
841
2179
  continue;
842
2180
  }
843
- const schema = securitySchemes[name];
844
- if (isRef4(schema)) {
845
- throw new Error(`Ref security schemas are not supported`);
846
- }
2181
+ const schema = isRef8(securitySchemes[name]) ? followRef6(spec, securitySchemes[name].$ref) : securitySchemes[name];
847
2182
  if (schema.type === "http") {
848
2183
  options["authorization"] = {
849
2184
  in: staticIn ?? "header",
@@ -929,7 +2264,7 @@ function generateInputs(operationsSet, commonZod, makeImport) {
929
2264
  const output = [];
930
2265
  const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
931
2266
  for (const operation of operations) {
932
- const schemaName = camelcase2(`${operation.name} schema`);
2267
+ const schemaName = camelcase3(`${operation.name} schema`);
933
2268
  const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
934
2269
  const inputContent = schema;
935
2270
  for (const schema2 of commonImports) {
@@ -966,8 +2301,8 @@ function generateInputs(operationsSet, commonZod, makeImport) {
966
2301
  };
967
2302
  }
968
2303
  function toEndpoint(groupName, spec, specOperation, operation, utils) {
969
- const schemaName = camelcase2(`${operation.name} schema`);
970
- const schemaRef = `${camelcase2(groupName)}.${schemaName}`;
2304
+ const schemaName = camelcase3(`${operation.name} schema`);
2305
+ const schemaRef = `${camelcase3(groupName)}.${schemaName}`;
971
2306
  const inputHeaders = [];
972
2307
  const inputQuery = [];
973
2308
  const inputBody = [];
@@ -999,13 +2334,25 @@ function toEndpoint(groupName, spec, specOperation, operation, utils) {
999
2334
  const statusCode = +status;
1000
2335
  return statusCode >= 200 && statusCode < 300;
1001
2336
  }).length > 1;
1002
- for (const status in specOperation.responses) {
1003
- const response = isRef5(specOperation.responses[status]) ? followRef4(spec, specOperation.responses[status].$ref) : specOperation.responses[status];
2337
+ const responseWithAtLeast200 = statusesCount ? specOperation.responses : Object.assign(
2338
+ {
2339
+ "200": {
2340
+ description: "OK",
2341
+ content: {
2342
+ "application/json": {
2343
+ schema: { type: "object" }
2344
+ }
2345
+ }
2346
+ }
2347
+ },
2348
+ specOperation.responses
2349
+ );
2350
+ for (const status in responseWithAtLeast200) {
1004
2351
  const handled = handleResponse(
1005
2352
  spec,
1006
2353
  operation.name,
1007
2354
  status,
1008
- response,
2355
+ responseWithAtLeast200[status],
1009
2356
  utils,
1010
2357
  true
1011
2358
  // statusesCount,
@@ -1025,19 +2372,104 @@ function toEndpoint(groupName, spec, specOperation, operation, utils) {
1025
2372
  schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
1026
2373
  output:[${outputs.join(",")}],
1027
2374
  toRequest(input: z.infer<typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}>) {
1028
- const endpoint = '${endpoint}';
1029
- return toRequest(endpoint, ${operation.outgoingContentType || "nobody"}(input, {
1030
- inputHeaders: [${inputHeaders}],
1031
- inputQuery: [${inputQuery}],
1032
- inputBody: [${inputBody}],
1033
- inputParams: [${inputParams}],
1034
- }));
1035
- },
1036
- }`
2375
+ return toRequest('${endpoint}', ${operation.outgoingContentType || "empty"}(input, {
2376
+ inputHeaders: [${inputHeaders}],
2377
+ inputQuery: [${inputQuery}],
2378
+ inputBody: [${inputBody}],
2379
+ inputParams: [${inputParams}],
2380
+ }));},
2381
+ async dispatch(input: z.infer<typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}>,options: {
2382
+ signal?: AbortSignal;
2383
+ interceptors: Interceptor[];
2384
+ fetch: z.infer<typeof fetchType>;
2385
+ })${specOperation["x-pagination"] ? paginationOperation(specOperation, utils.style) : normalOperation(utils.style)}`
1037
2386
  );
1038
2387
  }
1039
2388
  return { responses, schemas };
1040
2389
  }
2390
+ function normalOperation(style) {
2391
+ return `{
2392
+ const dispatcher = new Dispatcher(options.interceptors, options.fetch);
2393
+ const result = await dispatcher.send(this.toRequest(input), this.output);
2394
+ return ${style?.outputType === "status" ? "result" : style?.errorAsValue ? `result` : "result.data;"}
2395
+ },
2396
+ }`;
2397
+ }
2398
+ function paginationOperation(operation, style) {
2399
+ const pagination = operation["x-pagination"];
2400
+ const data = `${style?.errorAsValue ? `result[0]${style.outputType === "status" ? "" : ""}` : `${style?.outputType === "default" ? "result.data" : "result.data"}`}`;
2401
+ if (pagination.type === "offset") {
2402
+ const sameInputNames = pagination.limitParamName === "limit" && pagination.offsetParamName === "offset";
2403
+ const initialParams = sameInputNames ? "input" : `{...input, limit: input.${pagination.limitParamName}, offset: input.${pagination.offsetParamName}}`;
2404
+ const nextPageParams = sameInputNames ? "...nextPageParams" : `${pagination.offsetParamName}: nextPageParams.offset, ${pagination.limitParamName}: nextPageParams.limit`;
2405
+ const logic = `const pagination = new OffsetPagination(${initialParams}, async (nextPageParams) => {
2406
+ const dispatcher = new Dispatcher(options.interceptors, options.fetch);
2407
+ const result = await dispatcher.send(
2408
+ this.toRequest({...input, ${nextPageParams}}),
2409
+ this.output,
2410
+ );
2411
+ return {
2412
+ data: ${data}.${pagination.items},
2413
+ meta: {
2414
+ hasMore: Boolean(${data}.${pagination.hasMore}),
2415
+ },
2416
+ };
2417
+ });
2418
+ await pagination.getNextPage();
2419
+ return ${style?.outputType === "status" ? "new http.Ok(pagination);" : "pagination"}
2420
+ `;
2421
+ return style?.errorAsValue ? `{try {${logic}} catch (error) {return [null as never, error] as const;}}}` : `{${logic}}}`;
2422
+ }
2423
+ if (pagination.type === "cursor") {
2424
+ const sameInputNames = pagination.cursorParamName === "cursor";
2425
+ const initialParams = sameInputNames ? "input" : `{...input, cursor: input.${pagination.cursorParamName}}`;
2426
+ const nextPageParams = sameInputNames ? "...nextPageParams" : `${pagination.cursorParamName}: nextPageParams.cursor`;
2427
+ const logic = `
2428
+ const pagination = new CursorPagination(${initialParams}, async (nextPageParams) => {
2429
+ const dispatcher = new Dispatcher(options.interceptors, options.fetch);
2430
+ const result = await dispatcher.send(
2431
+ this.toRequest({...input, ${nextPageParams}}),
2432
+ this.output,
2433
+ );
2434
+ ${style?.errorAsValue ? `if (result[1]) {throw result[1];}` : ""}
2435
+ return {
2436
+ data: ${data}.${pagination.items},
2437
+ meta: {
2438
+ hasMore: Boolean(${data}.${pagination.hasMore}),
2439
+ },
2440
+ };
2441
+ });
2442
+ await pagination.getNextPage();
2443
+ return ${style?.outputType === "status" ? "new http.Ok(pagination);" : "pagination"}
2444
+ `;
2445
+ return style?.errorAsValue ? `{try {${logic}} catch (error) {return [null as never, error] as const;}}}` : `{${logic}}}`;
2446
+ }
2447
+ if (pagination.type === "page") {
2448
+ const sameInputNames = pagination.pageNumberParamName === "page" && pagination.pageSizeParamName === "pageSize";
2449
+ const initialParams = sameInputNames ? "input" : `{...input, page: input.${pagination.pageNumberParamName}, pageSize: input.${pagination.pageSizeParamName}}`;
2450
+ const nextPageParams = sameInputNames ? "...nextPageParams" : `${pagination.pageNumberParamName}: nextPageParams.page, ${pagination.pageSizeParamName}: nextPageParams.pageSize`;
2451
+ const logic = `
2452
+ const pagination = new Pagination(${initialParams}, async (nextPageParams) => {
2453
+ const dispatcher = new Dispatcher(options.interceptors, options.fetch);
2454
+ const result = await dispatcher.send(
2455
+ this.toRequest({...input, ${nextPageParams}}),
2456
+ this.output,
2457
+ );
2458
+ ${style?.errorAsValue ? `if (result[1]) {throw result[1];}` : ""}
2459
+ return {
2460
+ data: ${data}.${pagination.items},
2461
+ meta: {
2462
+ hasMore: Boolean(${data}.${pagination.hasMore}),
2463
+ },
2464
+ };
2465
+ });
2466
+ await pagination.getNextPage();
2467
+ return ${style?.outputType === "status" ? "new http.Ok(pagination);" : "pagination"}
2468
+ `;
2469
+ return style?.errorAsValue ? `{try {${logic}} catch (error) {return [null as never, error] as const;}}}` : `{${logic}}}`;
2470
+ }
2471
+ return normalOperation(style);
2472
+ }
1041
2473
  var statusCodeToResponseMap = {
1042
2474
  "200": "Ok",
1043
2475
  "201": "Created",
@@ -1075,7 +2507,7 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
1075
2507
  };
1076
2508
  const responses = [];
1077
2509
  const outputs = [];
1078
- const typeScriptDeserialzer = new TypeScriptDeserialzer(
2510
+ const typeScriptDeserialzer = new TypeScriptEmitter(
1079
2511
  spec,
1080
2512
  (schemaName, zod) => {
1081
2513
  schemas[schemaName] = zod;
@@ -1110,7 +2542,7 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
1110
2542
  let responseSchema = parser === "chunked" ? "ReadableStream" : "void";
1111
2543
  if (isJson) {
1112
2544
  const schema = responseContent["application/json"].schema;
1113
- const isObject = !isRef5(schema) && schema.type === "object";
2545
+ const isObject = !isRef9(schema) && schema.type === "object";
1114
2546
  if (isObject && schema.properties) {
1115
2547
  schema.properties["[http.KIND]"] = {
1116
2548
  "x-internal": true,
@@ -1151,13 +2583,13 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
1151
2583
  }
1152
2584
 
1153
2585
  // packages/typescript/src/lib/styles/github/endpoints.txt
1154
- var endpoints_default = "type Output<T extends OutputType> = T extends {\n parser: Parser;\n type: Type<unknown>;\n}\n ? InstanceType<T['type']>\n : T extends Type<unknown>\n ? InstanceType<T>\n : never;\n\ntype Unionize<T> = T extends [infer Single extends OutputType]\n ? Output<Single>\n : T extends readonly [...infer Tuple extends OutputType[]]\n ? { [I in keyof Tuple]: Output<Tuple[I]> }[number]\n : never;\n\ntype EndpointOutput<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n SuccessfulResponse\n>;\n\ntype EndpointError<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n ProblematicResponse\n>;\n\nexport type Endpoints = {\n [K in keyof typeof schemas]: {\n input: z.infer<(typeof schemas)[K]['schema']>;\n output: <% if (outputType === 'default') { %>EndpointOutput<K>['data']<% } else { %>EndpointOutput<K><% } %>;\n error: EndpointError<K> | ParseError<(typeof schemas)[K]['schema']>;\n };\n};";
2586
+ var endpoints_default = "type EndpointOutput<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n SuccessfulResponse\n>;\n\ntype EndpointError<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n ProblematicResponse\n>;\n\nexport type Endpoints = {\n [K in keyof typeof schemas]: {\n input: z.infer<(typeof schemas)[K]['schema']>;\n output: <% if (outputType === 'default') { %>EndpointOutput<K>['data']<% } else { %>EndpointOutput<K><% } %>;\n error: EndpointError<K> | ParseError<(typeof schemas)[K]['schema']>;\n };\n};";
1155
2587
 
1156
2588
  // packages/typescript/src/lib/generator.ts
1157
2589
  function generateCode(config) {
1158
2590
  const commonZod = /* @__PURE__ */ new Map();
1159
2591
  const commonZodImports = [];
1160
- const zodDeserialzer = new ZodDeserialzer(config.spec, (model, schema) => {
2592
+ const zodDeserialzer = new ZodEmitter(config.spec, (model, schema) => {
1161
2593
  commonZod.set(model, schema);
1162
2594
  commonZodImports.push({
1163
2595
  defaultImport: void 0,
@@ -1170,18 +2602,17 @@ function generateCode(config) {
1170
2602
  const groups = {};
1171
2603
  const outputs = {};
1172
2604
  const endpoints = {};
1173
- forEachOperation(config, (entry, operation) => {
2605
+ forEachOperation2(config, (entry, operation) => {
1174
2606
  console.log(`Processing ${entry.method} ${entry.path}`);
1175
2607
  groups[entry.groupName] ??= [];
1176
2608
  endpoints[entry.groupName] ??= [];
1177
2609
  const inputs = {};
1178
2610
  const additionalProperties = {};
1179
- for (const param of operation.parameters ?? []) {
1180
- if (isRef6(param)) {
1181
- throw new Error(`Found reference in parameter ${param.$ref}`);
1182
- }
2611
+ for (const param of operation.parameters) {
1183
2612
  if (!param.schema) {
1184
- throw new Error(`Schema not found for parameter ${param.name}`);
2613
+ param.schema = {
2614
+ type: "string"
2615
+ };
1185
2616
  }
1186
2617
  inputs[param.name] = {
1187
2618
  in: param.in,
@@ -1189,9 +2620,12 @@ function generateCode(config) {
1189
2620
  };
1190
2621
  additionalProperties[param.name] = param;
1191
2622
  }
1192
- const security2 = operation.security ?? [];
1193
2623
  const securitySchemes = config.spec.components?.securitySchemes ?? {};
1194
- const securityOptions = securityToOptions(security2, securitySchemes);
2624
+ const securityOptions = securityToOptions2(
2625
+ config.spec,
2626
+ operation.security ?? [],
2627
+ securitySchemes
2628
+ );
1195
2629
  Object.assign(inputs, securityOptions);
1196
2630
  Object.entries(securityOptions).forEach(([name, value]) => {
1197
2631
  additionalProperties[name] = {
@@ -1216,10 +2650,12 @@ function generateCode(config) {
1216
2650
  "text/plain": "text"
1217
2651
  };
1218
2652
  let outgoingContentType;
1219
- if (!isEmpty(operation.requestBody)) {
1220
- const requestBody = isRef6(operation.requestBody) ? followRef5(config.spec, operation.requestBody.$ref) : operation.requestBody;
1221
- for (const type in requestBody.content) {
1222
- const ctSchema = isRef6(requestBody.content[type].schema) ? followRef5(config.spec, requestBody.content[type].schema.$ref) : requestBody.content[type].schema;
2653
+ if (!isEmpty4(operation.requestBody)) {
2654
+ for (const type in operation.requestBody.content) {
2655
+ const ctSchema = isRef10(operation.requestBody.content[type].schema) ? followRef7(
2656
+ config.spec,
2657
+ operation.requestBody.content[type].schema.$ref
2658
+ ) : operation.requestBody.content[type].schema;
1223
2659
  if (!ctSchema) {
1224
2660
  console.warn(
1225
2661
  `Schema not found for ${type} in ${entry.method} ${entry.path}`
@@ -1230,7 +2666,7 @@ function generateCode(config) {
1230
2666
  if (objectSchema.type !== "object") {
1231
2667
  objectSchema = {
1232
2668
  type: "object",
1233
- required: [requestBody.required ? "$body" : ""],
2669
+ required: [operation.requestBody.required ? "$body" : ""],
1234
2670
  properties: {
1235
2671
  $body: ctSchema
1236
2672
  }
@@ -1249,11 +2685,11 @@ function generateCode(config) {
1249
2685
  Object.assign(inputs, bodyInputs(config, objectSchema));
1250
2686
  schemas[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
1251
2687
  }
1252
- if (requestBody.content["application/json"]) {
2688
+ if (operation.requestBody.content["application/json"]) {
1253
2689
  outgoingContentType = "json";
1254
- } else if (requestBody.content["application/x-www-form-urlencoded"]) {
2690
+ } else if (operation.requestBody.content["application/x-www-form-urlencoded"]) {
1255
2691
  outgoingContentType = "urlencoded";
1256
- } else if (requestBody.content["multipart/form-data"]) {
2692
+ } else if (operation.requestBody.content["multipart/form-data"]) {
1257
2693
  outgoingContentType = "formdata";
1258
2694
  } else {
1259
2695
  outgoingContentType = "json";
@@ -1287,11 +2723,11 @@ function generateCode(config) {
1287
2723
  schemas,
1288
2724
  inputs
1289
2725
  },
1290
- { makeImport: config.makeImport }
2726
+ { makeImport: config.makeImport, style: config.style }
1291
2727
  );
1292
2728
  const output = [
1293
2729
  `import z from 'zod';`,
1294
- `import type * as http from '../http';`
2730
+ `import type * as http from '${config.makeImport("../http/index")}';`
1295
2731
  ];
1296
2732
  const responses = endpoint.responses.flatMap((it) => it.responses);
1297
2733
  const responsesImports = endpoint.responses.flatMap(
@@ -1341,14 +2777,13 @@ function generateCode(config) {
1341
2777
  {}
1342
2778
  );
1343
2779
  const allSchemas = Object.keys(endpoints).map((it) => ({
1344
- import: `import ${camelcase3(it)} from './${config.makeImport(spinalcase2(it))}';`,
1345
- use: ` ...${camelcase3(it)}`
2780
+ import: `import ${camelcase4(it)} from './${config.makeImport(spinalcase2(it))}';`,
2781
+ use: ` ...${camelcase4(it)}`
1346
2782
  }));
1347
2783
  const imports = [
1348
2784
  'import z from "zod";',
1349
2785
  `import type { ParseError } from '${config.makeImport("../http/parser")}';`,
1350
- `import type { ServerError } from '${config.makeImport("../http/response")}';`,
1351
- `import type { OutputType, Parser, Type } from '../http/send-request.ts';`
2786
+ `import type { ServerError } from '${config.makeImport("../http/response")}';`
1352
2787
  ];
1353
2788
  return {
1354
2789
  groups,
@@ -1364,12 +2799,9 @@ import type { ParseError } from '${config.makeImport("../http/parser")}';
1364
2799
  import type { ProblematicResponse, SuccessfulResponse } from '${config.makeImport(
1365
2800
  "../http/response"
1366
2801
  )}';
1367
- import type { OutputType, Parser, Type } from '${config.makeImport(
1368
- "../http/send-request"
1369
- )}';
1370
2802
 
1371
2803
  import schemas from '${config.makeImport("./schemas")}';
1372
-
2804
+ import type { Unionize } from '${config.makeImport("../http/dispatcher")}';
1373
2805
  ${template(endpoints_default)({ outputType: config.style?.outputType })}`,
1374
2806
  [`${join("api", "schemas.ts")}`]: `${allSchemas.map((it) => it.import).join("\n")}
1375
2807
  import { KIND } from "${config.makeImport("../http/index")}";
@@ -1396,9 +2828,12 @@ ${allSchemas.map((it) => it.use).join(",\n")}
1396
2828
  ...imps,
1397
2829
  `import z from 'zod';`,
1398
2830
  `import * as http from '${config.makeImport("../http/response")}';`,
1399
- `import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${config.makeImport("../http/request")}';`,
2831
+ `import { toRequest, json, urlencoded, empty, formdata, createUrl, type HeadersInit } from '${config.makeImport("../http/request")}';`,
1400
2832
  `import { chunked, buffered } from "${config.makeImport("../http/parse-response")}";`,
1401
- `import * as ${camelcase3(name)} from '../inputs/${config.makeImport(spinalcase2(name))}';`
2833
+ `import * as ${camelcase4(name)} from '../inputs/${config.makeImport(spinalcase2(name))}';`,
2834
+ `import { createBaseUrlInterceptor, createHeadersInterceptor, type Interceptor } from '${config.makeImport("../http/interceptors")}';`,
2835
+ `import { Dispatcher, fetchType, type InstanceType } from '${config.makeImport("../http/dispatcher")}';`,
2836
+ `import { Pagination, OffsetPagination, CursorPagination } from "${config.makeImport("../pagination/index")}";`
1402
2837
  ].join(
1403
2838
  "\n"
1404
2839
  )}
@@ -1413,8 +2848,8 @@ ${endpoint.flatMap((it) => it.schemas).join(",\n")}
1413
2848
  };
1414
2849
  }
1415
2850
  function toProps(spec, schemaOrRef, aggregator = []) {
1416
- if (isRef6(schemaOrRef)) {
1417
- const schema = followRef5(spec, schemaOrRef.$ref);
2851
+ if (isRef10(schemaOrRef)) {
2852
+ const schema = followRef7(spec, schemaOrRef.$ref);
1418
2853
  return toProps(spec, schema, aggregator);
1419
2854
  } else if (schemaOrRef.type === "object") {
1420
2855
  for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
@@ -1458,6 +2893,9 @@ function bodyInputs(config, ctSchema) {
1458
2893
  );
1459
2894
  }
1460
2895
 
2896
+ // packages/typescript/src/lib/http/dispatcher.txt
2897
+ var dispatcher_default = "export type Unionize<T> = T extends [infer Single extends OutputType]\n ? InstanceType<Single>\n : T extends readonly [...infer Tuple extends OutputType[]]\n ? { [I in keyof Tuple]: InstanceType<Tuple[I]> }[number]\n : never;\n\nexport type InstanceType<T> =\n T extends Type<infer U>\n ? U\n : T extends { type: Type<infer U> }\n ? U\n : T extends Array<unknown>\n ? Unionize<T>\n : never;\n\nexport interface Type<T> {\n new (...args: any[]): T;\n}\nexport type Parser = (\n response: Response,\n) => Promise<unknown> | ReadableStream<any>;\nexport type OutputType =\n | Type<APIResponse>\n | { parser: Parser; type: Type<APIResponse> };\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function parse<T extends OutputType[]>(\n outputs: T,\n response: Response,\n) <% if(!throwError) { %>\n: Promise<\n [\n Extract<InstanceType<T>, SuccessfulResponse>['data'],\n Extract<InstanceType<T>, ProblematicResponse>['data'],\n ]\n>\n <% } %>\n\n\n\n {\n let output: typeof APIResponse | null = null;\n let parser: Parser = buffered;\n for (const outputType of outputs) {\n if ('parser' in outputType) {\n parser = outputType.parser;\n if (isTypeOf(outputType.type, APIResponse)) {\n if (response.status === outputType.type.status) {\n output = outputType.type;\n break;\n }\n }\n } else if (isTypeOf(outputType, APIResponse)) {\n if (response.status === outputType.status) {\n output = outputType;\n break;\n }\n }\n }\n\n\n if (response.ok) {\n const apiresponse = (output || APIResponse).create(\n response.status,\n await parser(response),\n );\n <% if(throwError) { %>\n return <% if (outputType === 'default') { %>apiresponse as Extract<InstanceType<T>, SuccessfulResponse><% } else { %>apiresponse as Extract<InstanceType<T>, SuccessfulResponse>;<% } %>;\n <% } else { %>\n return [<% if (outputType === 'default') { %>apiresponse.data as Extract<InstanceType<T>, SuccessfulResponse>['data']<% } else { %>apiresponse as Extract<InstanceType<T>, SuccessfulResponse><% } %>, null] as const;\n <% } %>\n }\n<% if(throwError) { %>\n throw (output || APIError).create(\n response.status,\n await parser(response),\n );\n<% } else { %>\n const data = (output || APIError).create(\n response.status,\n await parser(response),\n );\n return [null, data] as const;\n<% } %>\n}\n\nexport function isTypeOf<T extends Type<APIResponse>>(\n instance: any,\n baseType: T,\n): instance is T {\n if (instance === baseType) {\n return true;\n }\n const prototype = Object.getPrototypeOf(instance);\n if (prototype === null) {\n return false;\n }\n return isTypeOf(prototype, baseType);\n}\n\nexport class Dispatcher {\n #interceptors: Interceptor[] = [];\n #fetch: z.infer<typeof fetchType>;\n constructor(interceptors: Interceptor[], fetch?: z.infer<typeof fetchType>) {\n this.#interceptors = interceptors;\n this.#fetch = fetch;\n }\n\n async send<T extends OutputType[]>(\n config: RequestConfig,\n outputs: T,\n signal?: AbortSignal,\n ) {\n for (const interceptor of this.#interceptors) {\n if (interceptor.before) {\n config = await interceptor.before(config);\n }\n }\n\n let response = await (this.#fetch ?? fetch)(\n new Request(config.url, config.init),\n {\n ...config.init,\n signal: signal,\n },\n );\n\n for (let i = this.#interceptors.length - 1; i >= 0; i--) {\n const interceptor = this.#interceptors[i];\n if (interceptor.after) {\n response = await interceptor.after(response.clone());\n }\n }\n\n return await parse(outputs, response);\n }\n}\n";
2898
+
1461
2899
  // packages/typescript/src/lib/http/interceptors.txt
1462
2900
  var interceptors_default = "export interface Interceptor {\n before?: (config: RequestConfig) => Promise<RequestConfig> | RequestConfig;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createHeadersInterceptor = (\n defaultHeaders: () => Record<string, string | undefined>,\n requestHeaders: HeadersInit,\n):Interceptor => {\n return {\n before({init, url}) {\n // Priority Levels\n // 1. Headers Input\n // 2. Request Headers\n // 3. Default Headers\n const headers = defaultHeaders();\n\n for (const [key, value] of new Headers(requestHeaders)) {\n // Only set the header if it doesn't already exist and has a value\n // even though these headers are passed at operation level\n // still they are lower priority compared to the headers input\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n for (const [key, value] of Object.entries(headers)) {\n // Only set the header if it doesn't already exist and has a value\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n return {init, url};\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (\n getBaseUrl: () => string,\n): Interceptor => {\n return {\n before({ init, url }) {\n const baseUrl = getBaseUrl();\n if (url.protocol === 'local:') {\n return {\n init,\n url: new URL(url.href.replace('local://', baseUrl))\n };\n }\n return { init, url };\n },\n };\n};\n\nexport const logInterceptor: Interceptor = {\n before({ url, init }) {\n console.log('Request:', { url, init });\n return { url, init };\n },\n after(response) {\n console.log('Response:', response);\n return response;\n },\n};\n\n/**\n * Creates an interceptor that logs detailed information about requests and responses.\n * @param options Configuration options for the logger\n * @returns An interceptor object with before and after handlers\n */\nexport const createDetailedLogInterceptor = (options?: {\n logLevel?: 'debug' | 'info' | 'warn' | 'error';\n includeRequestBody?: boolean;\n includeResponseBody?: boolean;\n}) => {\n const logLevel = options?.logLevel || 'info';\n const includeRequestBody = options?.includeRequestBody || false;\n const includeResponseBody = options?.includeResponseBody || false;\n\n return {\n async before(request: Request) {\n const logData = {\n url: request.url,\n method: request.method,\n contentType: request.headers.get('Content-Type'),\n headers: Object.fromEntries([...request.headers.entries()]),\n };\n\n console[logLevel]('\u{1F680} Outgoing Request:', logData);\n\n if (includeRequestBody) {\n try {\n // Clone the request to avoid consuming the body stream\n const clonedRequest = request.clone();\n if (clonedRequest.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedRequest.json().catch(() => null);\n console[logLevel]('Request Body:', body);\n } else {\n const body = await clonedRequest.text().catch(() => null);\n console[logLevel]('Request Body:', body);\n }\n } catch (error) {\n console.error('Could not log request body:', error);\n }\n }\n\n return request;\n },\n\n async after(response: Response) {\n const logData = {\n status: response.status,\n statusText: response.statusText,\n url: response.url,\n headers: Object.fromEntries([...response.headers.entries()]),\n };\n\n console[logLevel]('\u{1F4E5} Incoming Response:', logData);\n\n if (includeResponseBody && response.body) {\n try {\n // Clone the response to avoid consuming the body stream\n const clonedResponse = response.clone();\n if (clonedResponse.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedResponse.json().catch(() => null);\n console[logLevel]('Response Body:', body);\n } else {\n const body = await clonedResponse.text().catch(() => null);\n if (body) {\n console[logLevel]('Response Body:', body.substring(0, 500) + (body.length > 500 ? '...' : ''));\n } else {\n console[logLevel]('No response body');\n }\n }\n } catch (error) {\n console.error('Could not log response body:', error);\n }\n }\n\n return response;\n },\n };\n};\n";
1463
2901
 
@@ -1468,7 +2906,7 @@ var parse_response_default = 'import { parse } from "fast-content-type-parse";\n
1468
2906
  var parser_default = "import { z } from 'zod';\n\nexport class ParseError<T extends z.ZodType<any, any, any>> {\n public data: z.typeToFlattenedError<T, z.ZodIssue>;\n constructor(data: z.typeToFlattenedError<T, z.ZodIssue>) {\n this.data = data;\n }\n}\n\nexport function parseInput<T extends z.ZodType<any, any, any>>(\n schema: T,\n input: unknown,\n) {\n const result = schema.safeParse(input);\n if (!result.success) {\n const error = result.error.flatten((issue) => issue);\n return [null, new ParseError(error)];\n }\n return [result.data as z.infer<T>, null];\n}\n";
1469
2907
 
1470
2908
  // packages/typescript/src/lib/http/request.txt
1471
- var request_default = "type Init = Omit<RequestInit, 'headers'> & { headers: Headers; };\nexport type RequestConfig = { init: Init; url: URL };\nexport type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart' | 'formdata';\nexport type HeadersInit = [string, string][] | Record<string, string>;\nexport type Endpoint =\n | `${ContentType} ${Method} ${string}`\n | `${Method} ${string}`;\n\nexport type BodyInit =\n | ArrayBuffer\n | Blob\n | FormData\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(path: string, query: URLSearchParams) {\n const url = new URL(path, `local://`);\n url.search = query.toString();\n return url;\n}\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\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 protected input: Input;\n protected props: Props;\n\n constructor(\n input: Input,\n props: Props,\n ) {\n this.input = input;\n this.props = props;\n }\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 if (\n this.props.inputBody.length === 1 &&\n this.props.inputBody[0] === '$body'\n ) {\n return JSON.stringify(this.input.$body);\n }\n\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 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n };\n }\n}\n\nclass NoBodySerializer extends Serializer {\n getBody(): BodyInit | null {\n return null;\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 Accept: 'application/json',\n };\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 nobody(input: Input, props: Props) {\n return new NoBodySerializer(input, props).serialize();\n}\nexport function formdata(input: Input, props: Props) {\n return new FormDataSerializer(input, props).serialize();\n}\n\nexport function toRequest<T extends Endpoint>(\n endpoint: T,\n input: Serialized,\n): RequestConfig {\n const [method, path] = endpoint.split(' ');\n const pathVariable = template(path, input.params);\n\n return {\n url: createUrl(pathVariable, input.query),\n init: {\n method: method,\n headers: new Headers(input.headers),\n body: method === 'GET' ? undefined : input.body,\n },\n }\n}\n";
2909
+ var request_default = "type Init = Omit<RequestInit, 'headers'> & { headers: Headers; };\nexport type RequestConfig = { init: Init; url: URL };\nexport type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart' | 'formdata';\nexport type HeadersInit = [string, string][] | Record<string, string>;\nexport type Endpoint =\n | `${ContentType} ${Method} ${string}`\n | `${Method} ${string}`;\n\nexport type BodyInit =\n | ArrayBuffer\n | Blob\n | FormData\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(path: string, query: URLSearchParams) {\n const url = new URL(path, `local://`);\n url.search = query.toString();\n return url;\n}\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\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 protected input: Input;\n protected props: Props;\n\n constructor(\n input: Input,\n props: Props,\n ) {\n this.input = input;\n this.props = props;\n }\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 if (\n this.props.inputBody.length === 1 &&\n this.props.inputBody[0] === '$body'\n ) {\n return JSON.stringify(this.input.$body);\n }\n\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 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n };\n }\n}\n\nclass EmptySerializer extends Serializer {\n getBody(): BodyInit | null {\n return null;\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 Accept: 'application/json',\n };\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 empty(input: Input, props: Props) {\n return new EmptySerializer(input, props).serialize();\n}\nexport function formdata(input: Input, props: Props) {\n return new FormDataSerializer(input, props).serialize();\n}\n\nexport function toRequest<T extends Endpoint>(\n endpoint: T,\n input: Serialized,\n): RequestConfig {\n const [method, path] = endpoint.split(' ');\n const pathVariable = template(path, input.params);\n\n return {\n url: createUrl(pathVariable, input.query),\n init: {\n method: method,\n headers: new Headers(input.headers),\n body: method === 'GET' ? undefined : input.body,\n },\n }\n}\n";
1472
2910
 
1473
2911
  // packages/typescript/src/lib/http/response.txt
1474
2912
  var response_default = `export const KIND = Symbol('APIDATA');
@@ -1881,321 +3319,323 @@ export type SuccessfulResponse =
1881
3319
  | NoContent;
1882
3320
  `;
1883
3321
 
1884
- // packages/typescript/src/lib/http/send-request.txt
1885
- var send_request_default = "export interface Type<T> {\n new (...args: any[]): T;\n}\nexport type Parser = (\n response: Response,\n) => Promise<unknown> | ReadableStream<any>;\nexport type OutputType =\n | Type<APIResponse>\n | { parser: Parser; type: Type<APIResponse> };\n\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => RequestConfig;\n output: OutputType[];\n}\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function dispatch(\n input: unknown,\n route: RequestSchema,\n options: {\n fetch?: z.infer<typeof fetchType>;\n interceptors?: Interceptor[];\n signal?: AbortSignal;\n },\n) {\n const { interceptors = [] } = options;\n const [parsedInput, parseError] = parseInput(route.schema, input);\n if (parseError) {\n <% if(throwError) { %>\n throw parseError;\n <% } else { %>\n return [null as never, parseError as never] as const;\n <% } %>\n }\n\n let config = route.toRequest(parsedInput as never);\n for (const interceptor of interceptors) {\n if (interceptor.before) {\n config = await interceptor.before(config);\n }\n }\n\n let response = await (options.fetch ?? fetch)(\n new Request(config.url, config.init),\n {\n ...config.init,\n signal: options.signal,\n },\n );\n\n for (let i = interceptors.length - 1; i >= 0; i--) {\n const interceptor = interceptors[i];\n if (interceptor.after) {\n response = await interceptor.after(response.clone());\n }\n }\n return await parse(route, response);\n}\n\nexport async function parse(route: RequestSchema, response: Response) {\n let output: typeof APIResponse | null = null;\n let parser: Parser = buffered;\n for (const outputType of route.output) {\n if ('parser' in outputType) {\n parser = outputType.parser;\n if (isTypeOf(outputType.type, APIResponse)) {\n if (response.status === outputType.type.status) {\n output = outputType.type;\n break;\n }\n }\n } else if (isTypeOf(outputType, APIResponse)) {\n if (response.status === outputType.status) {\n output = outputType;\n break;\n }\n }\n }\n\n if (response.ok) {\n const apiresponse = (output || APIResponse).create(\n response.status,\n await parser(response),\n );\n <% if(throwError) { %>\n return <% if (outputType === 'default') { %>apiresponse.data<% } else { %>apiresponse<% } %>;\n <% } else { %>\n return [<% if (outputType === 'default') { %>apiresponse.data<% } else { %>apiresponse<% } %> , null] as const;\n <% } %>\n }\n<% if(throwError) { %>\n throw (output || APIError).create(\n response.status,\n await parser(response),\n );\n<% } else { %>\n const data = (output || APIError).create(\n response.status,\n await parser(response),\n );\n return [null as never, data as never] as const;\n<% } %>\n}\n\nexport function isTypeOf<T extends Type<APIResponse>>(\n instance: any,\n baseType: T,\n): instance is T {\n if (instance === baseType) {\n return true;\n }\n const prototype = Object.getPrototypeOf(instance);\n if (prototype === null) {\n return false;\n }\n return isTypeOf(prototype, baseType);\n}\n";
3322
+ // packages/typescript/src/lib/paginations/cursor-pagination.txt
3323
+ var cursor_pagination_default = "type CursorPaginationParams = {\n cursor?: string;\n};\n\ninterface CursorMetadata extends Metadata {\n nextCursor?: string;\n}\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationResult<T, M extends CursorMetadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<T, M extends CursorMetadata> = (\n input: CursorPaginationParams,\n) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class CursorPagination<T, M extends CursorMetadata> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: CursorPaginationParams;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M>;\n\n constructor(\n initialParams: PartialNullable<CursorPaginationParams>,\n fetchFn: FetchFn<T, M>,\n ) {\n this.#fetchFn = fetchFn;\n this.#params = {\n cursor: initialParams.cursor ?? undefined,\n };\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 cursor: result.meta.nextCursor,\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\ntype PartialNullable<T> = {\n [K in keyof T]?: T[K] | null;\n};\n";
1886
3324
 
1887
- // packages/typescript/src/lib/readme.ts
1888
- import { followRef as followRef6, isRef as isRef7 } from "@sdk-it/core";
1889
- var PropEmitter = class {
1890
- #spec;
3325
+ // packages/typescript/src/lib/paginations/offset-pagination.txt
3326
+ var offset_pagination_default = "type OffsetPaginationParams = {\n offset: number;\n limit: number;\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<T, M extends Metadata> = (\n input: OffsetPaginationParams,\n) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class OffsetPagination<T, M extends Metadata> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: OffsetPaginationParams;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M>;\n\n constructor(\n initialParams: Partial<OffsetPaginationParams>,\n fetchFn: FetchFn<T, M>,\n ) {\n this.#fetchFn = fetchFn;\n this.#params = {\n limit: initialParams.limit ?? 0,\n offset: initialParams.offset ?? 0,\n };\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 offset: this.#params.offset + this.#params.limit,\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 reset(params?: Partial<OffsetPaginationParams>) {\n this.#meta = null;\n this.#currentPage = null;\n if (params) {\n this.#params = { ...this.#params, ...params };\n } else {\n this.#params.offset = 0;\n }\n return this;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n";
3327
+
3328
+ // packages/typescript/src/lib/paginations/page-pagination.txt
3329
+ 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";
3330
+
3331
+ // packages/typescript/src/lib/typescript-snippet.ts
3332
+ import { camelcase as camelcase5, spinalcase as spinalcase3 } from "stringcase";
3333
+ import { followRef as followRef9, isEmpty as isEmpty5, isRef as isRef12, pascalcase as pascalcase3 } from "@sdk-it/core";
3334
+
3335
+ // packages/typescript/src/lib/emitters/snippet.ts
3336
+ import { followRef as followRef8, isRef as isRef11 } from "@sdk-it/core";
3337
+ var SnippetEmitter = class {
3338
+ spec;
3339
+ generatedRefs = /* @__PURE__ */ new Set();
3340
+ cache = /* @__PURE__ */ new Map();
1891
3341
  constructor(spec) {
1892
- this.#spec = spec;
3342
+ this.spec = spec;
1893
3343
  }
1894
- /**
1895
- * Handle objects (properties)
1896
- */
1897
- #object(schema) {
1898
- const lines = [];
1899
- const properties = schema.properties || {};
1900
- if (Object.keys(properties).length > 0) {
1901
- lines.push(`**Properties:**`);
1902
- for (const [propName, propSchema] of Object.entries(properties)) {
1903
- const isRequired = (schema.required ?? []).includes(propName);
1904
- lines.push(...this.#property(propName, propSchema, isRequired));
3344
+ object(schema) {
3345
+ const schemaObj = isRef11(schema) ? followRef8(this.spec, schema.$ref) : schema;
3346
+ const result = {};
3347
+ const properties = schemaObj.properties || {};
3348
+ for (const [propName, propSchema] of Object.entries(properties)) {
3349
+ const isRequired = (schemaObj.required ?? []).includes(propName);
3350
+ const resolvedProp = isRef11(propSchema) ? followRef8(this.spec, propSchema.$ref) : propSchema;
3351
+ if (isRequired || resolvedProp.example !== void 0 || resolvedProp.default !== void 0 || Math.random() > 0.5) {
3352
+ result[propName] = this.handle(propSchema);
1905
3353
  }
1906
3354
  }
1907
- if (schema.additionalProperties) {
1908
- lines.push(`**Additional Properties:**`);
1909
- if (typeof schema.additionalProperties === "boolean") {
1910
- lines.push(`- Allowed: ${schema.additionalProperties}`);
1911
- } else {
1912
- lines.push(
1913
- ...this.handle(schema.additionalProperties).map((l) => ` ${l}`)
1914
- );
1915
- }
3355
+ if (schemaObj.additionalProperties && typeof schemaObj.additionalProperties === "object") {
3356
+ result["additionalPropExample"] = this.handle(
3357
+ schemaObj.additionalProperties
3358
+ );
1916
3359
  }
1917
- return lines;
3360
+ return result;
1918
3361
  }
1919
- /**
1920
- * Format a property with its type and description
1921
- */
1922
- #property(name, schema, required) {
1923
- const requiredMark = required ? " (required)" : "";
1924
- const propNameLine = `- \`${name}\`${requiredMark}:`;
1925
- const lines = [propNameLine];
1926
- const schemaDocs = this.handle(schema);
1927
- lines.push(...schemaDocs.map((line) => ` ${line}`));
1928
- return lines;
3362
+ array(schema) {
3363
+ const schemaObj = isRef11(schema) ? followRef8(this.spec, schema.$ref) : schema;
3364
+ const itemsSchema = schemaObj.items;
3365
+ if (!itemsSchema) {
3366
+ return [];
3367
+ }
3368
+ const count = Math.min(schemaObj.minItems ?? 1, 2);
3369
+ const result = [];
3370
+ for (let i = 0; i < count; i++) {
3371
+ result.push(this.handle(itemsSchema));
3372
+ }
3373
+ return result;
1929
3374
  }
1930
- /**
1931
- * Handle array schemas
1932
- */
1933
- #array(schema) {
1934
- const lines = [];
1935
- lines.push(`**Array items:**`);
1936
- if (schema.items) {
1937
- const itemDocs = this.handle(schema.items);
1938
- lines.push(...itemDocs.map((line) => ` ${line}`));
3375
+ string(schema) {
3376
+ if (schema.example !== void 0)
3377
+ return String(schema.example);
3378
+ if (schema.default !== void 0)
3379
+ return String(schema.default);
3380
+ switch (schema.format) {
3381
+ case "date-time":
3382
+ case "datetime":
3383
+ return (/* @__PURE__ */ new Date()).toISOString();
3384
+ case "date":
3385
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3386
+ case "time":
3387
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[1];
3388
+ case "email":
3389
+ return "user@example.com";
3390
+ case "uuid":
3391
+ return "123e4567-e89b-12d3-a456-426614174000";
3392
+ case "uri":
3393
+ case "url":
3394
+ return "https://example.com";
3395
+ case "ipv4":
3396
+ return "192.168.1.1";
3397
+ case "ipv6":
3398
+ return "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
3399
+ case "hostname":
3400
+ return "example.com";
3401
+ case "binary":
3402
+ case "byte":
3403
+ return "[binary data]";
3404
+ default:
3405
+ if (schema.enum && schema.enum.length > 0) {
3406
+ return String(schema.enum[0]);
3407
+ }
3408
+ return schema.pattern ? `string matching ${schema.pattern}` : "example";
3409
+ }
3410
+ }
3411
+ number(schema) {
3412
+ if (schema.example !== void 0)
3413
+ return Number(schema.example);
3414
+ if (schema.default !== void 0)
3415
+ return Number(schema.default);
3416
+ let value;
3417
+ if (typeof schema.exclusiveMinimum === "number") {
3418
+ value = schema.exclusiveMinimum + 1;
3419
+ } else if (typeof schema.minimum === "number") {
3420
+ value = schema.minimum;
1939
3421
  } else {
1940
- lines.push(` **Type:** \`unknown\``);
3422
+ value = schema.type === "integer" ? 42 : 42.42;
1941
3423
  }
1942
- if (schema.minItems !== void 0)
1943
- lines.push(`- Minimum items: ${schema.minItems}`);
1944
- if (schema.maxItems !== void 0)
1945
- lines.push(`- Maximum items: ${schema.maxItems}`);
1946
- if (schema.uniqueItems)
1947
- lines.push(`- Items must be unique.`);
1948
- return lines;
3424
+ if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum) {
3425
+ value = schema.exclusiveMaximum - 1;
3426
+ } else if (typeof schema.maximum === "number" && value > schema.maximum) {
3427
+ value = schema.maximum;
3428
+ }
3429
+ if (typeof schema.multipleOf === "number" && value % schema.multipleOf !== 0) {
3430
+ value = Math.floor(value / schema.multipleOf) * schema.multipleOf;
3431
+ }
3432
+ return schema.type === "integer" ? Math.floor(value) : value;
1949
3433
  }
1950
- #ref($ref) {
1951
- const schemaName = $ref.split("/").pop() || "object";
1952
- const resolved = followRef6(this.#spec, $ref);
1953
- const lines = [
1954
- `**Type:** [\`${schemaName}\`](#${schemaName.toLowerCase()})`
1955
- ];
1956
- if (resolved.description) {
1957
- lines.push(resolved.description);
3434
+ boolean(schema) {
3435
+ if (schema.example !== void 0)
3436
+ return Boolean(schema.example);
3437
+ if (schema.default !== void 0)
3438
+ return Boolean(schema.default);
3439
+ return true;
3440
+ }
3441
+ null() {
3442
+ return null;
3443
+ }
3444
+ ref($ref) {
3445
+ const parts = $ref.split("/");
3446
+ const refKey = parts[parts.length - 1] || "";
3447
+ if (this.cache.has($ref)) {
3448
+ return this.cache.get($ref);
1958
3449
  }
1959
- return lines;
3450
+ this.cache.set($ref, { _ref: refKey });
3451
+ const resolved = followRef8(this.spec, $ref);
3452
+ const result = this.handle(resolved);
3453
+ this.cache.set($ref, result);
3454
+ return result;
1960
3455
  }
1961
- #allOf(schemas) {
1962
- const lines = ["**All of (Intersection):**"];
1963
- schemas.forEach((subSchema, index) => {
1964
- lines.push(`- **Constraint ${index + 1}:**`);
1965
- const subLines = this.handle(subSchema);
1966
- lines.push(...subLines.map((l) => ` ${l}`));
1967
- });
1968
- return lines;
3456
+ allOf(schemas) {
3457
+ const initial = {};
3458
+ return schemas.reduce((result, schema) => {
3459
+ const example = this.handle(schema);
3460
+ if (typeof example === "object" && example !== null) {
3461
+ return { ...result, ...example };
3462
+ }
3463
+ return result;
3464
+ }, initial);
1969
3465
  }
1970
- #anyOf(schemas) {
1971
- const lines = ["**Any of (Union):**"];
1972
- schemas.forEach((subSchema, index) => {
1973
- lines.push(`- **Option ${index + 1}:**`);
1974
- const subLines = this.handle(subSchema);
1975
- lines.push(...subLines.map((l) => ` ${l}`));
1976
- });
1977
- return lines;
3466
+ anyOf(schemas) {
3467
+ if (schemas.length === 0)
3468
+ return {};
3469
+ return this.handle(schemas[0]);
1978
3470
  }
1979
- #oneOf(schemas) {
1980
- const lines = ["**One of (Exclusive Union):**"];
1981
- schemas.forEach((subSchema, index) => {
1982
- lines.push(`- **Option ${index + 1}:**`);
1983
- const subLines = this.handle(subSchema);
1984
- lines.push(...subLines.map((l) => ` ${l}`));
1985
- });
1986
- return lines;
3471
+ oneOf(schemas) {
3472
+ if (schemas.length === 0)
3473
+ return {};
3474
+ return this.handle(schemas[0]);
1987
3475
  }
1988
- #enum(schema) {
1989
- const lines = [`**Type:** \`${schema.type || "unknown"}\` (enum)`];
1990
- if (schema.description)
1991
- lines.push(schema.description);
1992
- lines.push("**Allowed values:**");
1993
- lines.push(
1994
- ...(schema.enum || []).map((val) => `- \`${JSON.stringify(val)}\``)
1995
- );
1996
- if (schema.default !== void 0) {
1997
- lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
1998
- }
1999
- return lines;
3476
+ enum(schema) {
3477
+ return Array.isArray(schema.enum) && schema.enum.length > 0 ? schema.enum[0] : void 0;
2000
3478
  }
2001
- #normal(type, schema, nullable) {
2002
- const lines = [];
2003
- const nullableSuffix = nullable ? " (nullable)" : "";
2004
- const description = schema.description ? [schema.description] : [];
2005
- switch (type) {
2006
- case "string":
2007
- lines.push(
2008
- `**Type:** \`string\`${schema.format ? ` (format: ${schema.format})` : ""}${nullableSuffix}`
2009
- );
2010
- lines.push(...description);
2011
- if (schema.minLength !== void 0)
2012
- lines.push(`- Minimum length: ${schema.minLength}`);
2013
- if (schema.maxLength !== void 0)
2014
- lines.push(`- Maximum length: ${schema.maxLength}`);
2015
- if (schema.pattern !== void 0)
2016
- lines.push(`- Pattern: \`${schema.pattern}\``);
2017
- break;
2018
- case "number":
2019
- case "integer":
2020
- lines.push(
2021
- `**Type:** \`${type}\`${schema.format ? ` (format: ${schema.format})` : ""}${nullableSuffix}`
2022
- );
2023
- lines.push(...description);
2024
- if (schema.minimum !== void 0) {
2025
- const exclusiveMin = typeof schema.exclusiveMinimum === "number";
2026
- lines.push(
2027
- `- Minimum: ${schema.minimum}${exclusiveMin ? " (exclusive)" : ""}`
2028
- );
2029
- if (exclusiveMin) {
2030
- lines.push(
2031
- `- Must be strictly greater than: ${schema.exclusiveMinimum}`
2032
- );
2033
- }
2034
- } else if (typeof schema.exclusiveMinimum === "number") {
2035
- lines.push(
2036
- `- Must be strictly greater than: ${schema.exclusiveMinimum}`
2037
- );
2038
- }
2039
- if (schema.maximum !== void 0) {
2040
- const exclusiveMax = typeof schema.exclusiveMaximum === "number";
2041
- lines.push(
2042
- `- Maximum: ${schema.maximum}${exclusiveMax ? " (exclusive)" : ""}`
2043
- );
2044
- if (exclusiveMax) {
2045
- lines.push(
2046
- `- Must be strictly less than: ${schema.exclusiveMaximum}`
2047
- );
2048
- }
2049
- } else if (typeof schema.exclusiveMaximum === "number") {
2050
- lines.push(
2051
- `- Must be strictly less than: ${schema.exclusiveMaximum}`
2052
- );
2053
- }
2054
- if (schema.multipleOf !== void 0)
2055
- lines.push(`- Must be a multiple of: ${schema.multipleOf}`);
2056
- break;
2057
- case "boolean":
2058
- lines.push(`**Type:** \`boolean\`${nullableSuffix}`);
2059
- lines.push(...description);
2060
- break;
2061
- case "object":
2062
- lines.push(`**Type:** \`object\`${nullableSuffix}`);
2063
- lines.push(...description);
2064
- lines.push(...this.#object(schema));
2065
- break;
2066
- case "array":
2067
- lines.push(`**Type:** \`array\`${nullableSuffix}`);
2068
- lines.push(...description);
2069
- lines.push(...this.#array(schema));
2070
- break;
2071
- case "null":
2072
- lines.push(`**Type:** \`null\``);
2073
- lines.push(...description);
2074
- break;
2075
- default:
2076
- lines.push(`**Type:** \`${type}\`${nullableSuffix}`);
2077
- lines.push(...description);
3479
+ handle(schemaOrRef) {
3480
+ if (isRef11(schemaOrRef)) {
3481
+ return this.ref(schemaOrRef.$ref);
2078
3482
  }
2079
- if (schema.default !== void 0) {
2080
- lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
3483
+ const schema = isRef11(schemaOrRef) ? followRef8(this.spec, schemaOrRef.$ref) : schemaOrRef;
3484
+ if (schema.example !== void 0) {
3485
+ return schema.example;
2081
3486
  }
2082
- return lines.filter((l) => l);
2083
- }
2084
- /**
2085
- * Handle schemas by resolving references and delegating to appropriate handler
2086
- */
2087
- handle(schemaOrRef) {
2088
- if (isRef7(schemaOrRef)) {
2089
- return this.#ref(schemaOrRef.$ref);
3487
+ if (schema.default !== void 0) {
3488
+ return schema.default;
2090
3489
  }
2091
- const schema = schemaOrRef;
2092
3490
  if (schema.allOf && Array.isArray(schema.allOf)) {
2093
- return this.#allOf(schema.allOf);
3491
+ return this.allOf(schema.allOf);
2094
3492
  }
2095
3493
  if (schema.anyOf && Array.isArray(schema.anyOf)) {
2096
- return this.#anyOf(schema.anyOf);
3494
+ return this.anyOf(schema.anyOf);
2097
3495
  }
2098
3496
  if (schema.oneOf && Array.isArray(schema.oneOf)) {
2099
- return this.#oneOf(schema.oneOf);
3497
+ return this.oneOf(schema.oneOf);
2100
3498
  }
2101
- if (schema.enum && Array.isArray(schema.enum)) {
2102
- return this.#enum(schema);
2103
- }
2104
- let types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
2105
- let nullable = false;
2106
- if (types.includes("null")) {
2107
- nullable = true;
2108
- types = types.filter((t) => t !== "null");
3499
+ if (schema.enum && Array.isArray(schema.enum) && schema.enum.length > 0) {
3500
+ return this.enum(schema);
2109
3501
  }
3502
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
2110
3503
  if (types.length === 0) {
2111
3504
  if (schema.properties || schema.additionalProperties) {
2112
- types = ["object"];
3505
+ return this.object(schema);
2113
3506
  } else if (schema.items) {
2114
- types = ["array"];
3507
+ return this.array(schema);
2115
3508
  }
3509
+ return "example";
2116
3510
  }
2117
- if (types.length === 0) {
2118
- const lines2 = ["**Type:** `unknown`"];
2119
- if (schema.description)
2120
- lines2.push(schema.description);
2121
- if (schema.default !== void 0)
2122
- lines2.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
2123
- return lines2;
2124
- }
2125
- if (types.length === 1) {
2126
- return this.#normal(types[0], schema, nullable);
3511
+ const primaryType = types.find((t) => t !== "null") || types[0];
3512
+ switch (primaryType) {
3513
+ case "string":
3514
+ return this.string(schema);
3515
+ case "number":
3516
+ case "integer":
3517
+ return this.number(schema);
3518
+ case "boolean":
3519
+ return this.boolean(schema);
3520
+ case "object":
3521
+ return this.object(schema);
3522
+ case "array":
3523
+ return this.array(schema);
3524
+ case "null":
3525
+ return this.null();
3526
+ default:
3527
+ return "unknown";
2127
3528
  }
2128
- const typeString = types.join(" | ");
2129
- const nullableSuffix = nullable ? " (nullable)" : "";
2130
- const lines = [`**Type:** \`${typeString}\`${nullableSuffix}`];
2131
- if (schema.description)
2132
- lines.push(schema.description);
2133
- if (schema.default !== void 0)
2134
- lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
2135
- return lines;
2136
3529
  }
2137
- /**
2138
- * Process a request body and return markdown documentation
2139
- */
2140
- requestBody(requestBody) {
2141
- if (!requestBody)
2142
- return [];
2143
- const resolvedBody = isRef7(requestBody) ? followRef6(this.#spec, requestBody.$ref) : requestBody;
2144
- const lines = [];
2145
- lines.push(`##### Request Body`);
2146
- if (resolvedBody.description) {
2147
- lines.push(resolvedBody.description);
2148
- }
2149
- if (resolvedBody.required) {
2150
- lines.push(`*This request body is required.*`);
2151
- }
2152
- if (resolvedBody.content) {
2153
- for (const [contentType, mediaType] of Object.entries(
2154
- resolvedBody.content
2155
- )) {
2156
- lines.push(`**Content Type:** \`${contentType}\``);
2157
- if (mediaType.schema) {
2158
- const schemaDocs = this.handle(mediaType.schema);
2159
- lines.push(...schemaDocs);
3530
+ };
3531
+
3532
+ // packages/typescript/src/lib/typescript-snippet.ts
3533
+ var TypeScriptGenerator = class {
3534
+ #spec;
3535
+ #settings;
3536
+ #snippetEmitter;
3537
+ #clientName;
3538
+ #packageName;
3539
+ constructor(spec, settings) {
3540
+ this.#spec = spec;
3541
+ this.#settings = settings;
3542
+ this.#snippetEmitter = new SnippetEmitter(spec);
3543
+ this.#clientName = settings.name?.trim() ? pascalcase3(settings.name) : "Client";
3544
+ this.#packageName = settings.name ? `@${spinalcase3(this.#clientName.toLowerCase())}/sdk` : "sdk";
3545
+ }
3546
+ succinct(entry, operation, values) {
3547
+ let payload = "{}";
3548
+ if (!isEmpty5(operation.requestBody)) {
3549
+ const contentTypes = Object.keys(operation.requestBody.content || {});
3550
+ if (contentTypes.length > 0) {
3551
+ const firstContent = operation.requestBody.content[contentTypes[0]];
3552
+ let schema = isRef12(firstContent.schema) ? followRef9(this.#spec, firstContent.schema.$ref) : firstContent.schema;
3553
+ if (schema) {
3554
+ if (schema.type !== "object") {
3555
+ schema = {
3556
+ type: "object",
3557
+ required: [operation.requestBody.required ? "$body" : ""],
3558
+ properties: {
3559
+ $body: schema
3560
+ }
3561
+ };
3562
+ }
3563
+ const properties = {};
3564
+ patchParameters(
3565
+ this.#spec,
3566
+ { type: "object", properties },
3567
+ operation
3568
+ );
3569
+ const examplePayload = this.#snippetEmitter.handle({
3570
+ ...schema,
3571
+ properties: Object.assign({}, properties, schema.properties)
3572
+ });
3573
+ Object.assign(
3574
+ examplePayload,
3575
+ values.requestBody ?? {},
3576
+ values.pathParameters ?? {},
3577
+ values.queryParameters ?? {},
3578
+ values.headers ?? {},
3579
+ values.cookies ?? {}
3580
+ );
3581
+ payload = JSON.stringify(examplePayload, null, 2);
2160
3582
  }
2161
3583
  }
3584
+ } else {
3585
+ const properties = {};
3586
+ patchParameters(this.#spec, { type: "object", properties }, operation);
3587
+ const examplePayload = this.#snippetEmitter.handle({
3588
+ properties
3589
+ });
3590
+ Object.assign(
3591
+ examplePayload,
3592
+ values.pathParameters ?? {},
3593
+ values.queryParameters ?? {},
3594
+ values.headers ?? {},
3595
+ values.cookies ?? {}
3596
+ );
3597
+ payload = JSON.stringify(examplePayload, null, 2);
2162
3598
  }
2163
- return lines;
3599
+ return `const result = await ${camelcase5(this.#clientName)}.request('${entry.method.toUpperCase()} ${entry.path}', ${payload});`;
3600
+ }
3601
+ snippet(entry, operation) {
3602
+ const payload = this.succinct(entry, operation, {});
3603
+ return [
3604
+ "```typescript",
3605
+ `${this.client()}
3606
+ ${payload}
3607
+
3608
+ console.log(result.data);
3609
+ `,
3610
+ "```"
3611
+ ].join("\n");
3612
+ }
3613
+ client() {
3614
+ return `import { ${this.#clientName} } from '${this.#packageName}';
3615
+
3616
+ const ${camelcase5(this.#clientName)} = new ${this.#clientName}({
3617
+ baseUrl: '${this.#spec.servers?.[0]?.url ?? "http://localhost:3000"}',
3618
+ });`;
2164
3619
  }
2165
3620
  };
2166
- function toReadme(spec) {
2167
- const markdown = [];
2168
- const propEmitter = new PropEmitter(spec);
2169
- forEachOperation({ spec }, ({ method, path, name }, operation) => {
2170
- spec.components ??= {};
2171
- spec.components.schemas ??= {};
2172
- const statuses = [];
2173
- markdown.push(
2174
- `#### ${name || operation.operationId} | ${`_${method.toUpperCase()} ${path}_`}`
2175
- );
2176
- markdown.push(operation.summary || "");
2177
- const requestBodyContent = propEmitter.requestBody(operation.requestBody);
2178
- if (requestBodyContent.length > 1) {
2179
- markdown.push(requestBodyContent.join("\n\n"));
2180
- }
2181
- markdown.push(`##### Responses`);
2182
- for (const status in operation.responses) {
2183
- const response = operation.responses[status];
2184
- const resolvedResponse = isRef7(response) ? followRef6(spec, response.$ref) : response;
2185
- statuses.push(`**${status}** _${resolvedResponse.description}_`);
2186
- }
2187
- markdown.push(`<small>${statuses.join("\n\n")}</small>`);
2188
- });
2189
- return markdown.join("\n\n");
2190
- }
2191
3621
 
2192
3622
  // packages/typescript/src/lib/generate.ts
3623
+ var ALWAYS_AVAILABLE_FILES = [
3624
+ /readme\.md$/i,
3625
+ // match readme.md, case-insensitive
3626
+ /^tsconfig.*\.json$/,
3627
+ // match any tsconfig*.json
3628
+ /package\.json$/,
3629
+ // exact package.json
3630
+ /metadata\.json$/
3631
+ // exact metadata.json
3632
+ ];
2193
3633
  function security(spec) {
2194
3634
  const security2 = spec.security || [];
2195
3635
  const components = spec.components || {};
2196
3636
  const securitySchemes = components.securitySchemes || {};
2197
3637
  const paths = Object.values(spec.paths ?? {});
2198
- const options = securityToOptions(security2, securitySchemes);
3638
+ const options = securityToOptions2(spec, security2, securitySchemes);
2199
3639
  for (const it of paths) {
2200
3640
  for (const method of methods) {
2201
3641
  const operation = it[method];
@@ -2204,13 +3644,20 @@ function security(spec) {
2204
3644
  }
2205
3645
  Object.assign(
2206
3646
  options,
2207
- securityToOptions(operation.security || [], securitySchemes, "input")
3647
+ securityToOptions2(
3648
+ spec,
3649
+ operation.security || [],
3650
+ securitySchemes,
3651
+ "input"
3652
+ )
2208
3653
  );
2209
3654
  }
2210
3655
  }
2211
3656
  return options;
2212
3657
  }
2213
3658
  async function generate(spec, settings) {
3659
+ spec = "x-sdk-augmented" in spec ? spec : augmentSpec({ spec });
3660
+ const generator = new TypeScriptGenerator(spec, settings);
2214
3661
  const style = Object.assign(
2215
3662
  {},
2216
3663
  {
@@ -2220,7 +3667,29 @@ async function generate(spec, settings) {
2220
3667
  },
2221
3668
  settings.style ?? {}
2222
3669
  );
3670
+ const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
2223
3671
  settings.useTsExtension ??= true;
3672
+ const writtenFiles = /* @__PURE__ */ new Set();
3673
+ settings.writer ??= writeFiles;
3674
+ const originalWriter = settings.writer;
3675
+ settings.writer = async (dir, contents) => {
3676
+ await originalWriter(dir, contents);
3677
+ for (const file of Object.keys(contents)) {
3678
+ if (contents[file] !== null) {
3679
+ writtenFiles.add(
3680
+ addLeadingSlash(`${relative(settings.output, dir)}/${file}`)
3681
+ );
3682
+ }
3683
+ }
3684
+ };
3685
+ settings.readFolder ??= async (folder) => {
3686
+ const files = await readdir(folder, { withFileTypes: true });
3687
+ return files.map((file) => ({
3688
+ fileName: file.name,
3689
+ filePath: join2(file.parentPath, file.name),
3690
+ isFolder: file.isDirectory()
3691
+ }));
3692
+ };
2224
3693
  const makeImport = (moduleSpecifier) => {
2225
3694
  return settings.useTsExtension ? `${moduleSpecifier}.ts` : moduleSpecifier;
2226
3695
  };
@@ -2231,37 +3700,34 @@ async function generate(spec, settings) {
2231
3700
  makeImport
2232
3701
  }
2233
3702
  );
2234
- const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
2235
3703
  const options = security(spec);
2236
- const clientName = settings.name?.trim() ? pascalcase3(settings.name) : "Client";
2237
- const readme = settings.readme ? toReadme(spec) : "";
3704
+ const clientName = pascalcase4((settings.name || "client").trim());
3705
+ const packageName = settings.name ? `@${spinalcase4(settings.name.trim().toLowerCase())}/sdk` : "sdk";
2238
3706
  const inputFiles = generateInputs(groups, commonZod, makeImport);
2239
- console.log("Writing to", output);
2240
- await writeFiles(output, {
3707
+ await settings.writer(output, {
2241
3708
  "outputs/.gitkeep": "",
2242
3709
  "inputs/.gitkeep": "",
2243
3710
  "models/.getkeep": ""
2244
3711
  });
2245
- await writeFiles(join2(output, "http"), {
2246
- "interceptors.ts": `
2247
- import type { RequestConfig, HeadersInit } from './${makeImport("request")}';
2248
- ${interceptors_default}`,
3712
+ await settings.writer(join2(output, "http"), {
2249
3713
  "parse-response.ts": parse_response_default,
2250
- "send-request.ts": `import z from 'zod';
2251
- import type { Interceptor } from './${makeImport("interceptors")}';
2252
- import { buffered } from './${makeImport("parse-response")}';
2253
- import { parseInput } from './${makeImport("parser")}';
2254
- import type { RequestConfig } from './${makeImport("request")}';
2255
- import { APIError, APIResponse } from './${makeImport("response")}';
2256
-
2257
- ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputType: style.outputType })}`,
2258
3714
  "response.ts": response_default,
2259
3715
  "parser.ts": parser_default,
2260
- "request.ts": request_default
3716
+ "request.ts": request_default,
3717
+ "dispatcher.ts": `import z from 'zod';
3718
+ import { type Interceptor } from '${makeImport("../http/interceptors")}';
3719
+ import { type RequestConfig } from '${makeImport("../http/request")}';
3720
+ import { buffered } from '${makeImport("./parse-response")}';
3721
+ import { APIError, APIResponse, type SuccessfulResponse, type ProblematicResponse } from '${makeImport("./response")}';
3722
+
3723
+ ${template2(dispatcher_default, {})({ throwError: !style.errorAsValue, outputType: style.outputType })}`,
3724
+ "interceptors.ts": `
3725
+ import type { RequestConfig, HeadersInit } from './${makeImport("request")}';
3726
+ ${interceptors_default}`
2261
3727
  });
2262
- await writeFiles(join2(output, "outputs"), outputs);
3728
+ await settings.writer(join2(output, "outputs"), outputs);
2263
3729
  const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
2264
- await writeFiles(output, {
3730
+ await settings.writer(output, {
2265
3731
  "client.ts": client_default(
2266
3732
  {
2267
3733
  name: clientName,
@@ -2286,37 +3752,90 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2286
3752
  ])
2287
3753
  )
2288
3754
  });
3755
+ await settings.writer(join2(output, "pagination"), {
3756
+ "cursor-pagination.ts": cursor_pagination_default,
3757
+ "offset-pagination.ts": offset_pagination_default,
3758
+ "page-pagination.ts": page_pagination_default
3759
+ });
3760
+ const metadata = await readJson(join2(settings.output, "metadata.json"));
3761
+ metadata.content.generatedFiles = Array.from(writtenFiles);
3762
+ metadata.content.userFiles ??= [];
3763
+ await metadata.write(metadata.content);
3764
+ if (settings.cleanup !== false && metadata.content.generatedFiles) {
3765
+ const generated = metadata.content.generatedFiles;
3766
+ const user = metadata.content.userFiles;
3767
+ const keep = /* @__PURE__ */ new Set([...generated, ...user]);
3768
+ const actualFiles = await readFolder(settings.output, true);
3769
+ const toRemove = actualFiles.filter((f) => !keep.has(addLeadingSlash(f))).filter(
3770
+ (f) => !ALWAYS_AVAILABLE_FILES.some((pattern) => pattern.test(f))
3771
+ );
3772
+ for (const file of toRemove) {
3773
+ if (file.endsWith(`${sep}index.ts`)) {
3774
+ continue;
3775
+ }
3776
+ const filePath = join2(settings.output, file);
3777
+ await unlink(filePath);
3778
+ }
3779
+ }
2289
3780
  const folders = [
2290
- getFolderExports(join2(output, "outputs"), settings.useTsExtension),
3781
+ getFolderExports(
3782
+ join2(output, "outputs"),
3783
+ settings.readFolder,
3784
+ settings.useTsExtension
3785
+ ),
2291
3786
  getFolderExports(
2292
3787
  join2(output, "inputs"),
3788
+ settings.readFolder,
2293
3789
  settings.useTsExtension,
2294
3790
  ["ts"],
2295
- (dirent) => dirent.isDirectory() && ["schemas"].includes(dirent.name)
3791
+ (dirent) => dirent.isFolder && ["schemas"].includes(dirent.fileName)
3792
+ ),
3793
+ getFolderExports(
3794
+ join2(output, "api"),
3795
+ settings.readFolder,
3796
+ settings.useTsExtension
2296
3797
  ),
2297
- getFolderExports(join2(output, "api"), settings.useTsExtension),
2298
3798
  getFolderExports(
2299
3799
  join2(output, "http"),
3800
+ settings.readFolder,
2300
3801
  settings.useTsExtension,
2301
3802
  ["ts"],
2302
- (dirent) => !["response.ts", "parser.ts"].includes(dirent.name)
3803
+ (dirent) => !["response.ts", "parser.ts"].includes(dirent.fileName)
2303
3804
  )
2304
3805
  ];
2305
3806
  if (modelsImports.length) {
2306
3807
  folders.push(
2307
- getFolderExports(join2(output, "models"), settings.useTsExtension)
3808
+ getFolderExports(
3809
+ join2(output, "models"),
3810
+ settings.readFolder,
3811
+ settings.useTsExtension
3812
+ )
2308
3813
  );
2309
3814
  }
2310
3815
  const [outputIndex, inputsIndex, apiIndex, httpIndex, modelsIndex] = await Promise.all(folders);
2311
- await writeFiles(output, {
3816
+ await settings.writer(join2(output, "pagination"), {
3817
+ "index.ts": await getFolderExports(
3818
+ join2(output, "pagination"),
3819
+ settings.readFolder,
3820
+ settings.useTsExtension,
3821
+ ["ts"]
3822
+ )
3823
+ });
3824
+ await settings.writer(output, {
2312
3825
  "api/index.ts": apiIndex,
2313
3826
  "outputs/index.ts": outputIndex,
2314
3827
  "inputs/index.ts": inputsIndex || null,
2315
3828
  "http/index.ts": httpIndex,
2316
3829
  ...modelsImports.length ? { "models/index.ts": modelsIndex } : {}
2317
3830
  });
2318
- await writeFiles(output, {
2319
- "index.ts": await getFolderExports(output, settings.useTsExtension, ["ts"])
3831
+ await settings.writer(output, {
3832
+ "index.ts": await getFolderExports(
3833
+ output,
3834
+ settings.readFolder,
3835
+ settings.useTsExtension,
3836
+ ["ts"],
3837
+ (config) => config.fileName.endsWith("pagination")
3838
+ )
2320
3839
  });
2321
3840
  if (settings.mode === "full") {
2322
3841
  const configFiles = {
@@ -2324,9 +3843,23 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2324
3843
  ignoreIfExists: true,
2325
3844
  content: JSON.stringify(
2326
3845
  {
2327
- name: settings.name ? `@${spinalcase3(clientName.toLowerCase())}/sdk` : "sdk",
3846
+ name: packageName,
3847
+ version: "0.0.1",
2328
3848
  type: "module",
2329
3849
  main: "./src/index.ts",
3850
+ module: "./src/index.ts",
3851
+ types: "./src/index.ts",
3852
+ publishConfig: {
3853
+ access: "public"
3854
+ },
3855
+ exports: {
3856
+ "./package.json": "./package.json",
3857
+ ".": {
3858
+ types: "./src/index.ts",
3859
+ import: "./src/index.ts",
3860
+ default: "./src/index.ts"
3861
+ }
3862
+ },
2330
3863
  dependencies: {
2331
3864
  "fast-content-type-parse": "^3.0.0",
2332
3865
  zod: "^3.24.2"
@@ -2359,33 +3892,31 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2359
3892
  )
2360
3893
  }
2361
3894
  };
2362
- if (readme) {
3895
+ if (settings.readme) {
2363
3896
  configFiles["README.md"] = {
2364
- ignoreIfExists: true,
2365
- content: readme
3897
+ ignoreIfExists: false,
3898
+ content: toReadme(spec, {
3899
+ generateSnippet: (...args) => generator.snippet(...args)
3900
+ })
2366
3901
  };
2367
3902
  }
2368
- await writeFiles(settings.output, configFiles);
3903
+ await settings.writer(settings.output, configFiles);
2369
3904
  }
2370
3905
  await settings.formatCode?.({
2371
3906
  output,
2372
3907
  env: npmRunPathEnv()
2373
3908
  });
2374
3909
  }
2375
-
2376
- // packages/typescript/src/lib/watcher.ts
2377
- import { watch as nodeWatch } from "node:fs/promises";
2378
- import { debounceTime, from } from "rxjs";
2379
- function watch(path) {
2380
- return from(
2381
- nodeWatch(path, {
2382
- persistent: true,
2383
- recursive: true
2384
- })
2385
- ).pipe(debounceTime(400));
3910
+ async function readJson(path) {
3911
+ const content = await exist(path) ? JSON.parse(await readFile(path, "utf-8")) : { content: {} };
3912
+ return {
3913
+ content,
3914
+ write: (value = content) => writeFile(path, JSON.stringify(value, null, 2), "utf-8")
3915
+ };
2386
3916
  }
2387
3917
  export {
3918
+ TypeScriptGenerator,
2388
3919
  generate,
2389
- watch
3920
+ readJson
2390
3921
  };
2391
3922
  //# sourceMappingURL=index.js.map