@stackwright-pro/openapi 0.3.0-alpha.5 → 0.3.0-alpha.7

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.d.mts CHANGED
@@ -110,6 +110,8 @@ interface OpenAPIConfig {
110
110
  slug_field: string;
111
111
  /** HTTP method for this collection endpoint — defaults to 'GET' */
112
112
  method?: string;
113
+ /** Transport protocol — 'websocket' collections are skipped during prebuild */
114
+ transport?: string;
113
115
  /** Optional filters */
114
116
  filters?: Record<string, unknown>;
115
117
  }>;
@@ -468,8 +470,13 @@ declare class ActionGenerator {
468
470
  private generateActionExports;
469
471
  private generateRegistry;
470
472
  private generateEmptyFile;
471
- /** Derives the client method name from endpoint + method (best-effort) */
473
+ /** Derives the client method name from endpoint + method.
474
+ * Prefers operationId (matching ClientGenerator.getMethodName()), falls back
475
+ * to path+method derivation for specs that omit operationIds. */
472
476
  private endpointToClientMethod;
477
+ /** Convert any string to camelCase — handles operationId values like
478
+ * createOrder, list_items, get-by-id, etc. */
479
+ private camelCase;
473
480
  private toCamelCase;
474
481
  private toPascalCase;
475
482
  private capitalize;
@@ -521,6 +528,7 @@ declare class ClientGenerator {
521
528
  private schemaMapping?;
522
529
  private requiredSchemas;
523
530
  private generatedRequestSchemas;
531
+ private generatedRequestTypes;
524
532
  constructor(document: OpenAPIDocument, schemaMapping?: SchemaMapping);
525
533
  /**
526
534
  * Generate typed API client code from OpenAPI document
package/dist/index.d.ts CHANGED
@@ -110,6 +110,8 @@ interface OpenAPIConfig {
110
110
  slug_field: string;
111
111
  /** HTTP method for this collection endpoint — defaults to 'GET' */
112
112
  method?: string;
113
+ /** Transport protocol — 'websocket' collections are skipped during prebuild */
114
+ transport?: string;
113
115
  /** Optional filters */
114
116
  filters?: Record<string, unknown>;
115
117
  }>;
@@ -468,8 +470,13 @@ declare class ActionGenerator {
468
470
  private generateActionExports;
469
471
  private generateRegistry;
470
472
  private generateEmptyFile;
471
- /** Derives the client method name from endpoint + method (best-effort) */
473
+ /** Derives the client method name from endpoint + method.
474
+ * Prefers operationId (matching ClientGenerator.getMethodName()), falls back
475
+ * to path+method derivation for specs that omit operationIds. */
472
476
  private endpointToClientMethod;
477
+ /** Convert any string to camelCase — handles operationId values like
478
+ * createOrder, list_items, get-by-id, etc. */
479
+ private camelCase;
473
480
  private toCamelCase;
474
481
  private toPascalCase;
475
482
  private capitalize;
@@ -521,6 +528,7 @@ declare class ClientGenerator {
521
528
  private schemaMapping?;
522
529
  private requiredSchemas;
523
530
  private generatedRequestSchemas;
531
+ private generatedRequestTypes;
524
532
  constructor(document: OpenAPIDocument, schemaMapping?: SchemaMapping);
525
533
  /**
526
534
  * Generate typed API client code from OpenAPI document
package/dist/index.js CHANGED
@@ -45,8 +45,18 @@ var OpenAPIParser = class {
45
45
  await SwaggerParser__default.default.validate(api);
46
46
  }
47
47
  let document;
48
+ let dereferenced = false;
48
49
  if (dereference) {
49
- document = await SwaggerParser__default.default.dereference(api);
50
+ try {
51
+ document = await SwaggerParser__default.default.dereference(api);
52
+ dereferenced = true;
53
+ } catch {
54
+ console.warn(
55
+ `[OpenAPIParser] Warning: Could not fully dereference "${specPath}" (dangling $ref or circular ref). Proceeding without full dereferencing \u2014 some schema references may not resolve.`
56
+ );
57
+ document = api;
58
+ dereferenced = false;
59
+ }
50
60
  } else {
51
61
  document = api;
52
62
  }
@@ -54,7 +64,7 @@ var OpenAPIParser = class {
54
64
  return {
55
65
  document,
56
66
  version,
57
- dereferenced: dereference
67
+ dereferenced
58
68
  };
59
69
  } catch (error) {
60
70
  throw this.enhanceError(error, specPath);
@@ -150,8 +160,14 @@ var SchemaResolver = class {
150
160
  const response = responses[responseCode];
151
161
  if (!response) return void 0;
152
162
  for (const contentType of SUPPORTED_CONTENT_TYPES) {
153
- const content = response.content?.[contentType];
154
- if (!content?.schema) continue;
163
+ const responseContent = response.content;
164
+ if (!responseContent) continue;
165
+ const exactContent = responseContent[contentType];
166
+ const normalizedEntry = exactContent ? void 0 : Object.entries(responseContent).find(
167
+ ([k]) => k === contentType || k.startsWith(`${contentType};`)
168
+ );
169
+ const content = exactContent ?? normalizedEntry?.[1];
170
+ if (!content || !content.schema) continue;
155
171
  const schema = content.schema;
156
172
  if ("$ref" in schema) {
157
173
  console.warn(
@@ -281,7 +297,7 @@ ${body}`;
281
297
  const isNullable = "nullable" in schema && schema.nullable === true;
282
298
  let baseSchema = this.schemaTypeToZod(schema);
283
299
  if (schema.description) {
284
- const escapedDesc = schema.description.replace(/'/g, "\\'");
300
+ const escapedDesc = schema.description.replace(/'/g, "\\'").replace(/\r?\n/g, " ");
285
301
  baseSchema += `.describe('${escapedDesc}')`;
286
302
  }
287
303
  if (isNullable) {
@@ -419,7 +435,7 @@ ${body}`;
419
435
  if (!schema.properties || Object.keys(schema.properties).length === 0) {
420
436
  if (schema.additionalProperties) {
421
437
  const valueSchema = typeof schema.additionalProperties === "object" ? this.schemaToZod(schema.additionalProperties) : "z.unknown()";
422
- return `z.record(${valueSchema})`;
438
+ return `z.record(z.string(), ${valueSchema})`;
423
439
  }
424
440
  return "z.object({})";
425
441
  }
@@ -616,10 +632,10 @@ var CollectionProviderGenerator = class {
616
632
  const authHeader = this.generateAuthHeader(auth);
617
633
  const arraySchemaName = `${schemaName.replace(/Schema$/, "")}ArraySchema`;
618
634
  const validationSchema = unknownFallback ? "z.unknown()" : isArray ? arraySchemaName : schemaName;
619
- const imports = bare ? "" : unknownFallback ? `import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
635
+ const imports = bare ? "" : unknownFallback ? `import type { CollectionProvider, CollectionEntry, CollectionListOptions, CollectionListResult } from '@stackwright/collections';
620
636
  import { z } from 'zod';
621
637
 
622
- ` : `import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
638
+ ` : `import type { CollectionProvider, CollectionEntry, CollectionListOptions, CollectionListResult } from '@stackwright/collections';
623
639
  import { ${isArray ? arraySchemaName : schemaName} } from './schemas';
624
640
 
625
641
  `;
@@ -638,29 +654,20 @@ export class ${providerName} implements CollectionProvider {
638
654
  }
639
655
 
640
656
  /**
641
- * Get collection name
657
+ * List available collection names.
642
658
  */
643
- getName(): string {
644
- return '${collectionName}';
659
+ async collections(): Promise<string[]> {
660
+ return ['${collectionName}'];
645
661
  }
646
662
 
647
663
  /**
648
664
  * List all items in the collection
649
665
  */
650
- async list(options?: {
651
- limit?: number;
652
- offset?: number;
653
- filters?: Record<string, unknown>;
654
- }): Promise<CollectionItem[]> {
666
+ async list(_collection: string, opts?: CollectionListOptions): Promise<CollectionListResult> {
655
667
  const url = new URL(\`\${this.baseUrl}${endpoint}\`);
656
668
 
657
- // Add pagination/filter params if supported
658
- if (options?.limit) {
659
- url.searchParams.set('limit', String(options.limit));
660
- }
661
- if (options?.offset) {
662
- url.searchParams.set('offset', String(options.offset));
663
- }
669
+ if (opts?.limit) url.searchParams.set('limit', String(opts.limit));
670
+ if (opts?.offset) url.searchParams.set('offset', String(opts.offset));
664
671
 
665
672
  const response = await fetch(url.toString(), {
666
673
  method: '${method.toUpperCase()}',
@@ -676,29 +683,24 @@ export class ${providerName} implements CollectionProvider {
676
683
  // Validate response with Zod schema
677
684
  const validated = ${validationSchema}.parse(data);
678
685
 
679
- // Convert to CollectionItem format
680
- return ${isArray ? "validated" : "[validated]"}.map((item: any) => this.toCollectionItem(item));
686
+ const entries = ${isArray ? "validated" : "[validated]"}.map((item: any) => this.toCollectionEntry(item));
687
+ return { entries, total: entries.length };
681
688
  }
682
689
 
683
690
  /**
684
691
  * Get a single item by slug
685
692
  */
686
- async get(slug: string): Promise<CollectionItem | null> {
693
+ async get(_collection: string, slug: string): Promise<CollectionEntry | null> {
687
694
  ${this.generateGetMethod(endpoint, slugField, isArray, collectionName, schemaName, unknownFallback)}
688
695
  }
689
696
 
690
697
  /**
691
- * Convert API response to CollectionItem
698
+ * Convert API response to CollectionEntry
692
699
  */
693
- private toCollectionItem(item: any): CollectionItem {
700
+ private toCollectionEntry(item: any): CollectionEntry {
694
701
  return {
695
702
  slug: String(item.${slugField}),
696
- title: item.${this.guessTitle(slugField)},
697
- data: item,
698
- metadata: {
699
- source: '${collectionName}',
700
- _raw: item,
701
- },
703
+ ...item,
702
704
  };
703
705
  }
704
706
 
@@ -741,10 +743,10 @@ export class ${providerName} implements CollectionProvider {
741
743
  const data = await response.json();
742
744
  const validated = ${validationExpr}.parse(data);
743
745
 
744
- return this.toCollectionItem(validated);`;
746
+ return this.toCollectionEntry(validated);`;
745
747
  } else {
746
- return `const items = await this.list();
747
- return items.find((item) => item.slug === slug) || null;`;
748
+ return `const result = await this.list(_collection);
749
+ return result.entries.find((item) => item.slug === slug) ?? null;`;
748
750
  }
749
751
  }
750
752
  /**
@@ -992,13 +994,25 @@ import type { CollectionActionRegistry } from '@stackwright-pro/workflow';
992
994
  export const actions: CollectionActionRegistry = {};
993
995
  `;
994
996
  }
995
- /** Derives the client method name from endpoint + method (best-effort) */
997
+ /** Derives the client method name from endpoint + method.
998
+ * Prefers operationId (matching ClientGenerator.getMethodName()), falls back
999
+ * to path+method derivation for specs that omit operationIds. */
996
1000
  endpointToClientMethod(endpoint, method) {
1001
+ const pathItem = this.document.paths?.[endpoint];
1002
+ const operation = pathItem?.[method.toLowerCase()];
1003
+ if (operation?.operationId) {
1004
+ return this.camelCase(operation.operationId);
1005
+ }
997
1006
  const parts = endpoint.split("/").filter(Boolean).filter((p) => !p.startsWith("{")).map((p) => p.replace(/-/g, "_"));
998
1007
  const prefix = method.toLowerCase();
999
1008
  const suffix = parts.map((p) => this.capitalize(p)).join("");
1000
1009
  return `${prefix}${suffix}`;
1001
1010
  }
1011
+ /** Convert any string to camelCase — handles operationId values like
1012
+ * createOrder, list_items, get-by-id, etc. */
1013
+ camelCase(str) {
1014
+ return str.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()).replace(/^[A-Z]/, (chr) => chr.toLowerCase());
1015
+ }
1002
1016
  toCamelCase(str) {
1003
1017
  const parts = str.split(/[-_]/);
1004
1018
  return parts.map((p, i) => i === 0 ? p.toLowerCase() : this.capitalize(p)).join("");
@@ -1019,11 +1033,13 @@ var ClientGenerator = class {
1019
1033
  }
1020
1034
  this.requiredSchemas = /* @__PURE__ */ new Set();
1021
1035
  this.generatedRequestSchemas = /* @__PURE__ */ new Set();
1036
+ this.generatedRequestTypes = /* @__PURE__ */ new Set();
1022
1037
  }
1023
1038
  resolver;
1024
1039
  schemaMapping;
1025
1040
  requiredSchemas;
1026
1041
  generatedRequestSchemas;
1042
+ generatedRequestTypes;
1027
1043
  /**
1028
1044
  * Generate typed API client code from OpenAPI document
1029
1045
  */
@@ -1042,12 +1058,11 @@ var ClientGenerator = class {
1042
1058
  }
1043
1059
  this.requiredSchemas.clear();
1044
1060
  this.generatedRequestSchemas.clear();
1061
+ this.generatedRequestTypes.clear();
1045
1062
  let code = this.generateImports(!!schemaMapping, validateResponses);
1046
1063
  code += "\n";
1047
- if (schemaMapping) {
1048
- code += this.generateRequestSchemas(endpoints);
1049
- code += "\n";
1050
- }
1064
+ code += this.generateRequestSchemas(endpoints);
1065
+ code += "\n";
1051
1066
  code += this.generateTypes(endpoints, schemaMapping);
1052
1067
  code += "\n";
1053
1068
  code += this.generateErrorClasses();
@@ -1078,9 +1093,9 @@ var ClientGenerator = class {
1078
1093
  */
1079
1094
 
1080
1095
  `;
1081
- if (useSchemas && validateResponses) {
1082
- code += `import { z } from 'zod';
1096
+ code += `import { z } from 'zod';
1083
1097
  `;
1098
+ if (useSchemas && validateResponses) {
1084
1099
  code += `import * as schemas from './schemas';
1085
1100
  `;
1086
1101
  }
@@ -1113,6 +1128,9 @@ var ClientGenerator = class {
1113
1128
  const schemaName = `${typeName}RequestSchema`;
1114
1129
  const schemaCode = this.generateRequestSchemaForEndpoint(endpoint);
1115
1130
  if (schemaCode) {
1131
+ if (this.generatedRequestSchemas.has(schemaName)) {
1132
+ continue;
1133
+ }
1116
1134
  code += `export const ${schemaName} = ${schemaCode};
1117
1135
 
1118
1136
  `;
@@ -1164,7 +1182,8 @@ ${parts.join(",\n")}
1164
1182
  for (const param of params) {
1165
1183
  const zodSchema = this.parameterSchemaToZod(param);
1166
1184
  const desc = param.description ? `.describe('${this.escapeString(param.description)}')` : "";
1167
- paramSchemas.push(` ${param.name}: ${zodSchema}${desc}`);
1185
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1186
+ paramSchemas.push(` ${safeParamKey}: ${zodSchema}${desc}`);
1168
1187
  }
1169
1188
  return `z.object({
1170
1189
  ${paramSchemas.join(",\n")}
@@ -1183,7 +1202,8 @@ ${paramSchemas.join(",\n")}
1183
1202
  if (!param.required) {
1184
1203
  zodSchema += ".optional()";
1185
1204
  }
1186
- paramSchemas.push(` ${param.name}: ${zodSchema}`);
1205
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1206
+ paramSchemas.push(` ${safeParamKey}: ${zodSchema}`);
1187
1207
  }
1188
1208
  return `z.object({
1189
1209
  ${paramSchemas.join(",\n")}
@@ -1256,7 +1276,7 @@ ${paramSchemas.join(",\n")}
1256
1276
  }
1257
1277
  if (schema.type === "object") {
1258
1278
  if (!schema.properties) {
1259
- return "z.record(z.unknown())";
1279
+ return "z.record(z.string(), z.unknown())";
1260
1280
  }
1261
1281
  const props = [];
1262
1282
  const required = schema.required || [];
@@ -1266,7 +1286,8 @@ ${paramSchemas.join(",\n")}
1266
1286
  if (!isRequired) {
1267
1287
  propZod += ".optional()";
1268
1288
  }
1269
- props.push(`${key}: ${propZod}`);
1289
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
1290
+ props.push(`${safeKey}: ${propZod}`);
1270
1291
  }
1271
1292
  return `z.object({ ${props.join(", ")} })`;
1272
1293
  }
@@ -1424,13 +1445,24 @@ ${paramSchemas.join(",\n")}
1424
1445
  const operationId = endpoint.operationId || this.getMethodName(endpoint);
1425
1446
  const mapping = schemaMapping[operationId];
1426
1447
  if (!mapping) {
1448
+ const typeName2 = this.getOperationTypeName(endpoint);
1449
+ const requestSchemaName2 = `${typeName2}RequestSchema`;
1450
+ if (this.generatedRequestSchemas.has(requestSchemaName2) && !this.generatedRequestTypes.has(requestSchemaName2)) {
1451
+ code += `export type ${typeName2}Request = z.infer<typeof ${requestSchemaName2}>;
1452
+ `;
1453
+ this.generatedRequestTypes.add(requestSchemaName2);
1454
+ }
1455
+ code += `export type ${typeName2}Response = unknown;
1456
+
1457
+ `;
1427
1458
  continue;
1428
1459
  }
1429
1460
  const typeName = this.getOperationTypeName(endpoint);
1430
1461
  const requestSchemaName = mapping.requestSchema || typeName + "RequestSchema";
1431
- if (this.generatedRequestSchemas.has(requestSchemaName)) {
1462
+ if (this.generatedRequestSchemas.has(requestSchemaName) && !this.generatedRequestTypes.has(requestSchemaName)) {
1432
1463
  this.addRequiredSchema(requestSchemaName);
1433
1464
  code += "export type " + typeName + "Request = z.infer<typeof " + requestSchemaName + ">;\n";
1465
+ this.generatedRequestTypes.add(requestSchemaName);
1434
1466
  }
1435
1467
  this.addRequiredSchema(mapping.responseSchema);
1436
1468
  code += `export type ${typeName}Response = z.infer<typeof schemas.${mapping.responseSchema}>;
@@ -1475,8 +1507,9 @@ ${paramSchemas.join(",\n")}
1475
1507
  const type = this.getTypeFromSchemaLegacy(param.schema);
1476
1508
  const desc = param.description ? ` /** ${param.description} */
1477
1509
  ` : "";
1510
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1478
1511
  code += desc;
1479
- code += ` ${param.name}${required}: ${type};
1512
+ code += ` ${safeParamKey}${required}: ${type};
1480
1513
  `;
1481
1514
  }
1482
1515
  code += " };\n";
@@ -1489,8 +1522,9 @@ ${paramSchemas.join(",\n")}
1489
1522
  const type = this.getTypeFromSchemaLegacy(param.schema);
1490
1523
  const desc = param.description ? ` /** ${param.description} */
1491
1524
  ` : "";
1525
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1492
1526
  code += desc;
1493
- code += ` ${param.name}${required}: ${type};
1527
+ code += ` ${safeParamKey}${required}: ${type};
1494
1528
  `;
1495
1529
  }
1496
1530
  code += " };\n";
@@ -1503,8 +1537,9 @@ ${paramSchemas.join(",\n")}
1503
1537
  const type = this.getTypeFromSchemaLegacy(param.schema);
1504
1538
  const desc = param.description ? ` /** ${param.description} */
1505
1539
  ` : "";
1540
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1506
1541
  code += desc;
1507
- code += ` ${param.name}${required}: ${type};
1542
+ code += ` ${safeParamKey}${required}: ${type};
1508
1543
  `;
1509
1544
  }
1510
1545
  code += " };\n";
@@ -1851,9 +1886,10 @@ ${paramSchemas.join(",\n")}
1851
1886
  code += ` if (request.query) {
1852
1887
  `;
1853
1888
  for (const param of queryParams) {
1854
- code += ` if (request.query.${param.name} != null) {
1889
+ const safeAccess = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? `.${param.name}` : `['${param.name}']`;
1890
+ code += ` if (request.query${safeAccess} != null) {
1855
1891
  `;
1856
- code += ` searchParams.append('${param.name}', String(request.query.${param.name}));
1892
+ code += ` searchParams.append('${param.name}', String(request.query${safeAccess}));
1857
1893
  `;
1858
1894
  code += ` }
1859
1895
  `;
@@ -1877,7 +1913,7 @@ ${paramSchemas.join(",\n")}
1877
1913
  code += ` method: '${method.toUpperCase()}',
1878
1914
  `;
1879
1915
  if (hasBody) {
1880
- code += ` body: request.body ? JSON.stringify(request.body) : null,
1916
+ code += ` body: request.body ? JSON.stringify(request.body) : undefined,
1881
1917
  `;
1882
1918
  }
1883
1919
  if (headerParams.length > 0) {
@@ -1994,9 +2030,10 @@ ${paramSchemas.join(",\n")}
1994
2030
  code += ` if (request.query) {
1995
2031
  `;
1996
2032
  for (const param of queryParams) {
1997
- code += ` if (request.query.${param.name} != null) {
2033
+ const safeAccess = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? `.${param.name}` : `['${param.name}']`;
2034
+ code += ` if (request.query${safeAccess} != null) {
1998
2035
  `;
1999
- code += ` searchParams.append('${param.name}', String(request.query.${param.name}));
2036
+ code += ` searchParams.append('${param.name}', String(request.query${safeAccess}));
2000
2037
  `;
2001
2038
  code += ` }
2002
2039
  `;
@@ -2018,7 +2055,7 @@ ${paramSchemas.join(",\n")}
2018
2055
  code += ` method: '${method.toUpperCase()}',
2019
2056
  `;
2020
2057
  if (hasBody) {
2021
- code += ` body: request.body ? JSON.stringify(request.body) : null,
2058
+ code += ` body: request.body ? JSON.stringify(request.body) : undefined,
2022
2059
  `;
2023
2060
  }
2024
2061
  const headerParams = params.filter((p) => p.in === "header");
@@ -2184,7 +2221,7 @@ ${paramSchemas.join(",\n")}
2184
2221
  if (endpoint.operationId) {
2185
2222
  return this.camelCase(endpoint.operationId);
2186
2223
  }
2187
- const pathParts = endpoint.path.split("/").filter((p) => p && !p.startsWith("{")).join("-");
2224
+ const pathParts = endpoint.path.split("/").filter((p) => p).map((p) => p.startsWith("{") && p.endsWith("}") ? p.slice(1, -1) : p).join("-");
2188
2225
  return this.camelCase(`${endpoint.method}-${pathParts}`);
2189
2226
  }
2190
2227
  /**
@@ -2841,6 +2878,23 @@ var OpenAPIPlugin = class {
2841
2878
  async processIntegration(config, projectRoot) {
2842
2879
  const { name, spec, auth, mockUrl, collections, endpoints, actions } = config;
2843
2880
  console.log(` - Processing integration: ${name}`);
2881
+ const httpCollections = (collections || []).filter((c) => {
2882
+ if (c.transport === "websocket") {
2883
+ console.warn(
2884
+ ` > Skipping collection "${c.endpoint}" (transport: websocket \u2014 not yet supported)`
2885
+ );
2886
+ return false;
2887
+ }
2888
+ return true;
2889
+ });
2890
+ const hasEndpoints = endpoints && (endpoints.include?.length || endpoints.exclude?.length);
2891
+ const hasActions = actions && actions.length > 0;
2892
+ if (httpCollections.length === 0 && !hasEndpoints && !hasActions) {
2893
+ console.log(
2894
+ " > No HTTP endpoints or REST collections found \u2014 skipping (WebSocket-only integration not yet supported)"
2895
+ );
2896
+ return;
2897
+ }
2844
2898
  const specPath = spec.startsWith("http") ? spec : path2__default.default.resolve(projectRoot, spec);
2845
2899
  const parser = new OpenAPIParser();
2846
2900
  const { document } = await parser.parse(specPath);
@@ -2859,13 +2913,13 @@ var OpenAPIPlugin = class {
2859
2913
  fs2__default.default.mkdirSync(outputDir, { recursive: true });
2860
2914
  const schemaMapping = await this.generateSchemas(
2861
2915
  document,
2862
- collections || [],
2916
+ httpCollections,
2863
2917
  outputDir,
2864
2918
  name,
2865
2919
  endpointFilter
2866
2920
  );
2867
- await this.generateTypes(document, collections || [], outputDir, name);
2868
- if (collections && collections.length > 0) {
2921
+ await this.generateTypes(document, httpCollections, outputDir, name);
2922
+ if (httpCollections.length > 0) {
2869
2923
  await this.generateProvider(document, config, outputDir, name);
2870
2924
  }
2871
2925
  await this.generateClient(document, outputDir, name, schemaMapping, endpointFilter);
@@ -2973,6 +3027,7 @@ import { z } from 'zod';
2973
3027
  if (!operation) continue;
2974
3028
  const opId = operation.operationId || this.generateOperationId(pathStr, method);
2975
3029
  const responseSchemaName = `${this.getOperationTypeName(opId)}ResponseSchema`;
3030
+ if (!generatedSchemas.has(responseSchemaName)) continue;
2976
3031
  schemaMapping[opId] = {
2977
3032
  requestSchema: null,
2978
3033
  // ClientGenerator handles request schema generation + type inference
@@ -2997,7 +3052,13 @@ import type * as schemas from './schemas';
2997
3052
 
2998
3053
  `;
2999
3054
  if (collections) {
3055
+ const resolver = new SchemaResolver(document);
3000
3056
  for (const collection of collections) {
3057
+ const schema = resolver.getResponseSchema(
3058
+ collection.endpoint,
3059
+ collection.method?.toLowerCase() ?? "get"
3060
+ );
3061
+ if (!schema) continue;
3001
3062
  const collectionName = this.sanitizeName(collection.endpoint);
3002
3063
  const typeName = this.capitalize(collectionName);
3003
3064
  const schemaName = `${typeName}Schema`;
@@ -3043,13 +3104,20 @@ import type * as schemas from './schemas';
3043
3104
  collection.endpoint,
3044
3105
  collection.method?.toLowerCase() ?? "get"
3045
3106
  );
3046
- const isArray = schema?.type === "array";
3047
- if (isArray) {
3048
- schemaImports.add(`${this.capitalize(collectionName)}ArraySchema`);
3049
- } else {
3050
- schemaImports.add(`${this.capitalize(collectionName)}Schema`);
3107
+ if (schema) {
3108
+ const isArray = schema.type === "array";
3109
+ if (isArray) {
3110
+ schemaImports.add(`${this.capitalize(collectionName)}ArraySchema`);
3111
+ } else {
3112
+ schemaImports.add(`${this.capitalize(collectionName)}Schema`);
3113
+ }
3051
3114
  }
3052
3115
  }
3116
+ const needsZodImport = classBlocks.some((block) => block.includes("z.unknown()"));
3117
+ const zodImportLine = needsZodImport ? `import { z } from 'zod';
3118
+ ` : "";
3119
+ const schemaImportLine = schemaImports.size > 0 ? `import { ${Array.from(schemaImports).join(", ")} } from './schemas';
3120
+ ` : "";
3053
3121
  let providerCode = `/**
3054
3122
  * Generated CollectionProvider from OpenAPI spec
3055
3123
  * Integration: ${integrationName}
@@ -3058,9 +3126,8 @@ import type * as schemas from './schemas';
3058
3126
  * Regenerate by running: pnpm prebuild
3059
3127
  */
3060
3128
 
3061
- import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
3062
- import { ${Array.from(schemaImports).join(", ")} } from './schemas';
3063
-
3129
+ import type { CollectionProvider, CollectionEntry, CollectionListOptions, CollectionListResult } from '@stackwright/collections';
3130
+ ${zodImportLine}${schemaImportLine}
3064
3131
  `;
3065
3132
  providerCode += classBlocks.join("\n");
3066
3133
  fs2__default.default.writeFileSync(path2__default.default.join(outputDir, "provider.ts"), providerCode);