@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.mjs CHANGED
@@ -33,8 +33,18 @@ var OpenAPIParser = class {
33
33
  await SwaggerParser.validate(api);
34
34
  }
35
35
  let document;
36
+ let dereferenced = false;
36
37
  if (dereference) {
37
- document = await SwaggerParser.dereference(api);
38
+ try {
39
+ document = await SwaggerParser.dereference(api);
40
+ dereferenced = true;
41
+ } catch {
42
+ console.warn(
43
+ `[OpenAPIParser] Warning: Could not fully dereference "${specPath}" (dangling $ref or circular ref). Proceeding without full dereferencing \u2014 some schema references may not resolve.`
44
+ );
45
+ document = api;
46
+ dereferenced = false;
47
+ }
38
48
  } else {
39
49
  document = api;
40
50
  }
@@ -42,7 +52,7 @@ var OpenAPIParser = class {
42
52
  return {
43
53
  document,
44
54
  version,
45
- dereferenced: dereference
55
+ dereferenced
46
56
  };
47
57
  } catch (error) {
48
58
  throw this.enhanceError(error, specPath);
@@ -138,8 +148,14 @@ var SchemaResolver = class {
138
148
  const response = responses[responseCode];
139
149
  if (!response) return void 0;
140
150
  for (const contentType of SUPPORTED_CONTENT_TYPES) {
141
- const content = response.content?.[contentType];
142
- if (!content?.schema) continue;
151
+ const responseContent = response.content;
152
+ if (!responseContent) continue;
153
+ const exactContent = responseContent[contentType];
154
+ const normalizedEntry = exactContent ? void 0 : Object.entries(responseContent).find(
155
+ ([k]) => k === contentType || k.startsWith(`${contentType};`)
156
+ );
157
+ const content = exactContent ?? normalizedEntry?.[1];
158
+ if (!content || !content.schema) continue;
143
159
  const schema = content.schema;
144
160
  if ("$ref" in schema) {
145
161
  console.warn(
@@ -269,7 +285,7 @@ ${body}`;
269
285
  const isNullable = "nullable" in schema && schema.nullable === true;
270
286
  let baseSchema = this.schemaTypeToZod(schema);
271
287
  if (schema.description) {
272
- const escapedDesc = schema.description.replace(/'/g, "\\'");
288
+ const escapedDesc = schema.description.replace(/'/g, "\\'").replace(/\r?\n/g, " ");
273
289
  baseSchema += `.describe('${escapedDesc}')`;
274
290
  }
275
291
  if (isNullable) {
@@ -407,7 +423,7 @@ ${body}`;
407
423
  if (!schema.properties || Object.keys(schema.properties).length === 0) {
408
424
  if (schema.additionalProperties) {
409
425
  const valueSchema = typeof schema.additionalProperties === "object" ? this.schemaToZod(schema.additionalProperties) : "z.unknown()";
410
- return `z.record(${valueSchema})`;
426
+ return `z.record(z.string(), ${valueSchema})`;
411
427
  }
412
428
  return "z.object({})";
413
429
  }
@@ -604,10 +620,10 @@ var CollectionProviderGenerator = class {
604
620
  const authHeader = this.generateAuthHeader(auth);
605
621
  const arraySchemaName = `${schemaName.replace(/Schema$/, "")}ArraySchema`;
606
622
  const validationSchema = unknownFallback ? "z.unknown()" : isArray ? arraySchemaName : schemaName;
607
- const imports = bare ? "" : unknownFallback ? `import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
623
+ const imports = bare ? "" : unknownFallback ? `import type { CollectionProvider, CollectionEntry, CollectionListOptions, CollectionListResult } from '@stackwright/collections';
608
624
  import { z } from 'zod';
609
625
 
610
- ` : `import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
626
+ ` : `import type { CollectionProvider, CollectionEntry, CollectionListOptions, CollectionListResult } from '@stackwright/collections';
611
627
  import { ${isArray ? arraySchemaName : schemaName} } from './schemas';
612
628
 
613
629
  `;
@@ -626,29 +642,20 @@ export class ${providerName} implements CollectionProvider {
626
642
  }
627
643
 
628
644
  /**
629
- * Get collection name
645
+ * List available collection names.
630
646
  */
631
- getName(): string {
632
- return '${collectionName}';
647
+ async collections(): Promise<string[]> {
648
+ return ['${collectionName}'];
633
649
  }
634
650
 
635
651
  /**
636
652
  * List all items in the collection
637
653
  */
638
- async list(options?: {
639
- limit?: number;
640
- offset?: number;
641
- filters?: Record<string, unknown>;
642
- }): Promise<CollectionItem[]> {
654
+ async list(_collection: string, opts?: CollectionListOptions): Promise<CollectionListResult> {
643
655
  const url = new URL(\`\${this.baseUrl}${endpoint}\`);
644
656
 
645
- // Add pagination/filter params if supported
646
- if (options?.limit) {
647
- url.searchParams.set('limit', String(options.limit));
648
- }
649
- if (options?.offset) {
650
- url.searchParams.set('offset', String(options.offset));
651
- }
657
+ if (opts?.limit) url.searchParams.set('limit', String(opts.limit));
658
+ if (opts?.offset) url.searchParams.set('offset', String(opts.offset));
652
659
 
653
660
  const response = await fetch(url.toString(), {
654
661
  method: '${method.toUpperCase()}',
@@ -664,29 +671,24 @@ export class ${providerName} implements CollectionProvider {
664
671
  // Validate response with Zod schema
665
672
  const validated = ${validationSchema}.parse(data);
666
673
 
667
- // Convert to CollectionItem format
668
- return ${isArray ? "validated" : "[validated]"}.map((item: any) => this.toCollectionItem(item));
674
+ const entries = ${isArray ? "validated" : "[validated]"}.map((item: any) => this.toCollectionEntry(item));
675
+ return { entries, total: entries.length };
669
676
  }
670
677
 
671
678
  /**
672
679
  * Get a single item by slug
673
680
  */
674
- async get(slug: string): Promise<CollectionItem | null> {
681
+ async get(_collection: string, slug: string): Promise<CollectionEntry | null> {
675
682
  ${this.generateGetMethod(endpoint, slugField, isArray, collectionName, schemaName, unknownFallback)}
676
683
  }
677
684
 
678
685
  /**
679
- * Convert API response to CollectionItem
686
+ * Convert API response to CollectionEntry
680
687
  */
681
- private toCollectionItem(item: any): CollectionItem {
688
+ private toCollectionEntry(item: any): CollectionEntry {
682
689
  return {
683
690
  slug: String(item.${slugField}),
684
- title: item.${this.guessTitle(slugField)},
685
- data: item,
686
- metadata: {
687
- source: '${collectionName}',
688
- _raw: item,
689
- },
691
+ ...item,
690
692
  };
691
693
  }
692
694
 
@@ -729,10 +731,10 @@ export class ${providerName} implements CollectionProvider {
729
731
  const data = await response.json();
730
732
  const validated = ${validationExpr}.parse(data);
731
733
 
732
- return this.toCollectionItem(validated);`;
734
+ return this.toCollectionEntry(validated);`;
733
735
  } else {
734
- return `const items = await this.list();
735
- return items.find((item) => item.slug === slug) || null;`;
736
+ return `const result = await this.list(_collection);
737
+ return result.entries.find((item) => item.slug === slug) ?? null;`;
736
738
  }
737
739
  }
738
740
  /**
@@ -980,13 +982,25 @@ import type { CollectionActionRegistry } from '@stackwright-pro/workflow';
980
982
  export const actions: CollectionActionRegistry = {};
981
983
  `;
982
984
  }
983
- /** Derives the client method name from endpoint + method (best-effort) */
985
+ /** Derives the client method name from endpoint + method.
986
+ * Prefers operationId (matching ClientGenerator.getMethodName()), falls back
987
+ * to path+method derivation for specs that omit operationIds. */
984
988
  endpointToClientMethod(endpoint, method) {
989
+ const pathItem = this.document.paths?.[endpoint];
990
+ const operation = pathItem?.[method.toLowerCase()];
991
+ if (operation?.operationId) {
992
+ return this.camelCase(operation.operationId);
993
+ }
985
994
  const parts = endpoint.split("/").filter(Boolean).filter((p) => !p.startsWith("{")).map((p) => p.replace(/-/g, "_"));
986
995
  const prefix = method.toLowerCase();
987
996
  const suffix = parts.map((p) => this.capitalize(p)).join("");
988
997
  return `${prefix}${suffix}`;
989
998
  }
999
+ /** Convert any string to camelCase — handles operationId values like
1000
+ * createOrder, list_items, get-by-id, etc. */
1001
+ camelCase(str) {
1002
+ return str.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()).replace(/^[A-Z]/, (chr) => chr.toLowerCase());
1003
+ }
990
1004
  toCamelCase(str) {
991
1005
  const parts = str.split(/[-_]/);
992
1006
  return parts.map((p, i) => i === 0 ? p.toLowerCase() : this.capitalize(p)).join("");
@@ -1007,11 +1021,13 @@ var ClientGenerator = class {
1007
1021
  }
1008
1022
  this.requiredSchemas = /* @__PURE__ */ new Set();
1009
1023
  this.generatedRequestSchemas = /* @__PURE__ */ new Set();
1024
+ this.generatedRequestTypes = /* @__PURE__ */ new Set();
1010
1025
  }
1011
1026
  resolver;
1012
1027
  schemaMapping;
1013
1028
  requiredSchemas;
1014
1029
  generatedRequestSchemas;
1030
+ generatedRequestTypes;
1015
1031
  /**
1016
1032
  * Generate typed API client code from OpenAPI document
1017
1033
  */
@@ -1030,12 +1046,11 @@ var ClientGenerator = class {
1030
1046
  }
1031
1047
  this.requiredSchemas.clear();
1032
1048
  this.generatedRequestSchemas.clear();
1049
+ this.generatedRequestTypes.clear();
1033
1050
  let code = this.generateImports(!!schemaMapping, validateResponses);
1034
1051
  code += "\n";
1035
- if (schemaMapping) {
1036
- code += this.generateRequestSchemas(endpoints);
1037
- code += "\n";
1038
- }
1052
+ code += this.generateRequestSchemas(endpoints);
1053
+ code += "\n";
1039
1054
  code += this.generateTypes(endpoints, schemaMapping);
1040
1055
  code += "\n";
1041
1056
  code += this.generateErrorClasses();
@@ -1066,9 +1081,9 @@ var ClientGenerator = class {
1066
1081
  */
1067
1082
 
1068
1083
  `;
1069
- if (useSchemas && validateResponses) {
1070
- code += `import { z } from 'zod';
1084
+ code += `import { z } from 'zod';
1071
1085
  `;
1086
+ if (useSchemas && validateResponses) {
1072
1087
  code += `import * as schemas from './schemas';
1073
1088
  `;
1074
1089
  }
@@ -1101,6 +1116,9 @@ var ClientGenerator = class {
1101
1116
  const schemaName = `${typeName}RequestSchema`;
1102
1117
  const schemaCode = this.generateRequestSchemaForEndpoint(endpoint);
1103
1118
  if (schemaCode) {
1119
+ if (this.generatedRequestSchemas.has(schemaName)) {
1120
+ continue;
1121
+ }
1104
1122
  code += `export const ${schemaName} = ${schemaCode};
1105
1123
 
1106
1124
  `;
@@ -1152,7 +1170,8 @@ ${parts.join(",\n")}
1152
1170
  for (const param of params) {
1153
1171
  const zodSchema = this.parameterSchemaToZod(param);
1154
1172
  const desc = param.description ? `.describe('${this.escapeString(param.description)}')` : "";
1155
- paramSchemas.push(` ${param.name}: ${zodSchema}${desc}`);
1173
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1174
+ paramSchemas.push(` ${safeParamKey}: ${zodSchema}${desc}`);
1156
1175
  }
1157
1176
  return `z.object({
1158
1177
  ${paramSchemas.join(",\n")}
@@ -1171,7 +1190,8 @@ ${paramSchemas.join(",\n")}
1171
1190
  if (!param.required) {
1172
1191
  zodSchema += ".optional()";
1173
1192
  }
1174
- paramSchemas.push(` ${param.name}: ${zodSchema}`);
1193
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1194
+ paramSchemas.push(` ${safeParamKey}: ${zodSchema}`);
1175
1195
  }
1176
1196
  return `z.object({
1177
1197
  ${paramSchemas.join(",\n")}
@@ -1244,7 +1264,7 @@ ${paramSchemas.join(",\n")}
1244
1264
  }
1245
1265
  if (schema.type === "object") {
1246
1266
  if (!schema.properties) {
1247
- return "z.record(z.unknown())";
1267
+ return "z.record(z.string(), z.unknown())";
1248
1268
  }
1249
1269
  const props = [];
1250
1270
  const required = schema.required || [];
@@ -1254,7 +1274,8 @@ ${paramSchemas.join(",\n")}
1254
1274
  if (!isRequired) {
1255
1275
  propZod += ".optional()";
1256
1276
  }
1257
- props.push(`${key}: ${propZod}`);
1277
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
1278
+ props.push(`${safeKey}: ${propZod}`);
1258
1279
  }
1259
1280
  return `z.object({ ${props.join(", ")} })`;
1260
1281
  }
@@ -1412,13 +1433,24 @@ ${paramSchemas.join(",\n")}
1412
1433
  const operationId = endpoint.operationId || this.getMethodName(endpoint);
1413
1434
  const mapping = schemaMapping[operationId];
1414
1435
  if (!mapping) {
1436
+ const typeName2 = this.getOperationTypeName(endpoint);
1437
+ const requestSchemaName2 = `${typeName2}RequestSchema`;
1438
+ if (this.generatedRequestSchemas.has(requestSchemaName2) && !this.generatedRequestTypes.has(requestSchemaName2)) {
1439
+ code += `export type ${typeName2}Request = z.infer<typeof ${requestSchemaName2}>;
1440
+ `;
1441
+ this.generatedRequestTypes.add(requestSchemaName2);
1442
+ }
1443
+ code += `export type ${typeName2}Response = unknown;
1444
+
1445
+ `;
1415
1446
  continue;
1416
1447
  }
1417
1448
  const typeName = this.getOperationTypeName(endpoint);
1418
1449
  const requestSchemaName = mapping.requestSchema || typeName + "RequestSchema";
1419
- if (this.generatedRequestSchemas.has(requestSchemaName)) {
1450
+ if (this.generatedRequestSchemas.has(requestSchemaName) && !this.generatedRequestTypes.has(requestSchemaName)) {
1420
1451
  this.addRequiredSchema(requestSchemaName);
1421
1452
  code += "export type " + typeName + "Request = z.infer<typeof " + requestSchemaName + ">;\n";
1453
+ this.generatedRequestTypes.add(requestSchemaName);
1422
1454
  }
1423
1455
  this.addRequiredSchema(mapping.responseSchema);
1424
1456
  code += `export type ${typeName}Response = z.infer<typeof schemas.${mapping.responseSchema}>;
@@ -1463,8 +1495,9 @@ ${paramSchemas.join(",\n")}
1463
1495
  const type = this.getTypeFromSchemaLegacy(param.schema);
1464
1496
  const desc = param.description ? ` /** ${param.description} */
1465
1497
  ` : "";
1498
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1466
1499
  code += desc;
1467
- code += ` ${param.name}${required}: ${type};
1500
+ code += ` ${safeParamKey}${required}: ${type};
1468
1501
  `;
1469
1502
  }
1470
1503
  code += " };\n";
@@ -1477,8 +1510,9 @@ ${paramSchemas.join(",\n")}
1477
1510
  const type = this.getTypeFromSchemaLegacy(param.schema);
1478
1511
  const desc = param.description ? ` /** ${param.description} */
1479
1512
  ` : "";
1513
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1480
1514
  code += desc;
1481
- code += ` ${param.name}${required}: ${type};
1515
+ code += ` ${safeParamKey}${required}: ${type};
1482
1516
  `;
1483
1517
  }
1484
1518
  code += " };\n";
@@ -1491,8 +1525,9 @@ ${paramSchemas.join(",\n")}
1491
1525
  const type = this.getTypeFromSchemaLegacy(param.schema);
1492
1526
  const desc = param.description ? ` /** ${param.description} */
1493
1527
  ` : "";
1528
+ const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
1494
1529
  code += desc;
1495
- code += ` ${param.name}${required}: ${type};
1530
+ code += ` ${safeParamKey}${required}: ${type};
1496
1531
  `;
1497
1532
  }
1498
1533
  code += " };\n";
@@ -1839,9 +1874,10 @@ ${paramSchemas.join(",\n")}
1839
1874
  code += ` if (request.query) {
1840
1875
  `;
1841
1876
  for (const param of queryParams) {
1842
- code += ` if (request.query.${param.name} != null) {
1877
+ const safeAccess = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? `.${param.name}` : `['${param.name}']`;
1878
+ code += ` if (request.query${safeAccess} != null) {
1843
1879
  `;
1844
- code += ` searchParams.append('${param.name}', String(request.query.${param.name}));
1880
+ code += ` searchParams.append('${param.name}', String(request.query${safeAccess}));
1845
1881
  `;
1846
1882
  code += ` }
1847
1883
  `;
@@ -1865,7 +1901,7 @@ ${paramSchemas.join(",\n")}
1865
1901
  code += ` method: '${method.toUpperCase()}',
1866
1902
  `;
1867
1903
  if (hasBody) {
1868
- code += ` body: request.body ? JSON.stringify(request.body) : null,
1904
+ code += ` body: request.body ? JSON.stringify(request.body) : undefined,
1869
1905
  `;
1870
1906
  }
1871
1907
  if (headerParams.length > 0) {
@@ -1982,9 +2018,10 @@ ${paramSchemas.join(",\n")}
1982
2018
  code += ` if (request.query) {
1983
2019
  `;
1984
2020
  for (const param of queryParams) {
1985
- code += ` if (request.query.${param.name} != null) {
2021
+ const safeAccess = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? `.${param.name}` : `['${param.name}']`;
2022
+ code += ` if (request.query${safeAccess} != null) {
1986
2023
  `;
1987
- code += ` searchParams.append('${param.name}', String(request.query.${param.name}));
2024
+ code += ` searchParams.append('${param.name}', String(request.query${safeAccess}));
1988
2025
  `;
1989
2026
  code += ` }
1990
2027
  `;
@@ -2006,7 +2043,7 @@ ${paramSchemas.join(",\n")}
2006
2043
  code += ` method: '${method.toUpperCase()}',
2007
2044
  `;
2008
2045
  if (hasBody) {
2009
- code += ` body: request.body ? JSON.stringify(request.body) : null,
2046
+ code += ` body: request.body ? JSON.stringify(request.body) : undefined,
2010
2047
  `;
2011
2048
  }
2012
2049
  const headerParams = params.filter((p) => p.in === "header");
@@ -2172,7 +2209,7 @@ ${paramSchemas.join(",\n")}
2172
2209
  if (endpoint.operationId) {
2173
2210
  return this.camelCase(endpoint.operationId);
2174
2211
  }
2175
- const pathParts = endpoint.path.split("/").filter((p) => p && !p.startsWith("{")).join("-");
2212
+ const pathParts = endpoint.path.split("/").filter((p) => p).map((p) => p.startsWith("{") && p.endsWith("}") ? p.slice(1, -1) : p).join("-");
2176
2213
  return this.camelCase(`${endpoint.method}-${pathParts}`);
2177
2214
  }
2178
2215
  /**
@@ -2829,6 +2866,23 @@ var OpenAPIPlugin = class {
2829
2866
  async processIntegration(config, projectRoot) {
2830
2867
  const { name, spec, auth, mockUrl, collections, endpoints, actions } = config;
2831
2868
  console.log(` - Processing integration: ${name}`);
2869
+ const httpCollections = (collections || []).filter((c) => {
2870
+ if (c.transport === "websocket") {
2871
+ console.warn(
2872
+ ` > Skipping collection "${c.endpoint}" (transport: websocket \u2014 not yet supported)`
2873
+ );
2874
+ return false;
2875
+ }
2876
+ return true;
2877
+ });
2878
+ const hasEndpoints = endpoints && (endpoints.include?.length || endpoints.exclude?.length);
2879
+ const hasActions = actions && actions.length > 0;
2880
+ if (httpCollections.length === 0 && !hasEndpoints && !hasActions) {
2881
+ console.log(
2882
+ " > No HTTP endpoints or REST collections found \u2014 skipping (WebSocket-only integration not yet supported)"
2883
+ );
2884
+ return;
2885
+ }
2832
2886
  const specPath = spec.startsWith("http") ? spec : path2.resolve(projectRoot, spec);
2833
2887
  const parser = new OpenAPIParser();
2834
2888
  const { document } = await parser.parse(specPath);
@@ -2847,13 +2901,13 @@ var OpenAPIPlugin = class {
2847
2901
  fs2.mkdirSync(outputDir, { recursive: true });
2848
2902
  const schemaMapping = await this.generateSchemas(
2849
2903
  document,
2850
- collections || [],
2904
+ httpCollections,
2851
2905
  outputDir,
2852
2906
  name,
2853
2907
  endpointFilter
2854
2908
  );
2855
- await this.generateTypes(document, collections || [], outputDir, name);
2856
- if (collections && collections.length > 0) {
2909
+ await this.generateTypes(document, httpCollections, outputDir, name);
2910
+ if (httpCollections.length > 0) {
2857
2911
  await this.generateProvider(document, config, outputDir, name);
2858
2912
  }
2859
2913
  await this.generateClient(document, outputDir, name, schemaMapping, endpointFilter);
@@ -2961,6 +3015,7 @@ import { z } from 'zod';
2961
3015
  if (!operation) continue;
2962
3016
  const opId = operation.operationId || this.generateOperationId(pathStr, method);
2963
3017
  const responseSchemaName = `${this.getOperationTypeName(opId)}ResponseSchema`;
3018
+ if (!generatedSchemas.has(responseSchemaName)) continue;
2964
3019
  schemaMapping[opId] = {
2965
3020
  requestSchema: null,
2966
3021
  // ClientGenerator handles request schema generation + type inference
@@ -2985,7 +3040,13 @@ import type * as schemas from './schemas';
2985
3040
 
2986
3041
  `;
2987
3042
  if (collections) {
3043
+ const resolver = new SchemaResolver(document);
2988
3044
  for (const collection of collections) {
3045
+ const schema = resolver.getResponseSchema(
3046
+ collection.endpoint,
3047
+ collection.method?.toLowerCase() ?? "get"
3048
+ );
3049
+ if (!schema) continue;
2989
3050
  const collectionName = this.sanitizeName(collection.endpoint);
2990
3051
  const typeName = this.capitalize(collectionName);
2991
3052
  const schemaName = `${typeName}Schema`;
@@ -3031,13 +3092,20 @@ import type * as schemas from './schemas';
3031
3092
  collection.endpoint,
3032
3093
  collection.method?.toLowerCase() ?? "get"
3033
3094
  );
3034
- const isArray = schema?.type === "array";
3035
- if (isArray) {
3036
- schemaImports.add(`${this.capitalize(collectionName)}ArraySchema`);
3037
- } else {
3038
- schemaImports.add(`${this.capitalize(collectionName)}Schema`);
3095
+ if (schema) {
3096
+ const isArray = schema.type === "array";
3097
+ if (isArray) {
3098
+ schemaImports.add(`${this.capitalize(collectionName)}ArraySchema`);
3099
+ } else {
3100
+ schemaImports.add(`${this.capitalize(collectionName)}Schema`);
3101
+ }
3039
3102
  }
3040
3103
  }
3104
+ const needsZodImport = classBlocks.some((block) => block.includes("z.unknown()"));
3105
+ const zodImportLine = needsZodImport ? `import { z } from 'zod';
3106
+ ` : "";
3107
+ const schemaImportLine = schemaImports.size > 0 ? `import { ${Array.from(schemaImports).join(", ")} } from './schemas';
3108
+ ` : "";
3041
3109
  let providerCode = `/**
3042
3110
  * Generated CollectionProvider from OpenAPI spec
3043
3111
  * Integration: ${integrationName}
@@ -3046,9 +3114,8 @@ import type * as schemas from './schemas';
3046
3114
  * Regenerate by running: pnpm prebuild
3047
3115
  */
3048
3116
 
3049
- import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
3050
- import { ${Array.from(schemaImports).join(", ")} } from './schemas';
3051
-
3117
+ import type { CollectionProvider, CollectionEntry, CollectionListOptions, CollectionListResult } from '@stackwright/collections';
3118
+ ${zodImportLine}${schemaImportLine}
3052
3119
  `;
3053
3120
  providerCode += classBlocks.join("\n");
3054
3121
  fs2.writeFileSync(path2.join(outputDir, "provider.ts"), providerCode);