@conduit-client/model 3.10.0 → 3.11.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.
@@ -10,6 +10,7 @@ import { SourcePositionMap } from '../swagger/source-position-map';
10
10
  import { SwaggerAPI } from '../swagger/SwaggerAPI';
11
11
  import { AmfEndPoint } from '../amf/endpoints/amf-endpoint';
12
12
  import { API, Server } from '../../api';
13
+ import type SwaggerParser from '@apidevtools/swagger-parser';
13
14
  export type ParserKind = 'amf' | 'swagger';
14
15
  type ClassOfConstructor<T> = T extends new () => infer R ? R : never;
15
16
  /**
@@ -37,6 +38,7 @@ type TestSwaggerOAS<T extends Record<string, new () => amf.AnyShape>, E extends
37
38
  document: OpenAPIV3.Document;
38
39
  sourceUrl: URL;
39
40
  positionMap: SourcePositionMap;
41
+ $refs: SwaggerParser.$Refs;
40
42
  } & {
41
43
  [P in keyof T]: OpenAPIV3.SchemaObject;
42
44
  } & {
@@ -6,6 +6,7 @@ import type { API, EndPoint, Server } from '../../api';
6
6
  import type { ReadOnlyTypeRegistry, TypeRegistry } from '../../types';
7
7
  import type { OpenAPIV3 } from 'openapi-types';
8
8
  import type { NamedFeatureFlagsService } from '@conduit-client/service-feature-flags/v1';
9
+ import type SwaggerParser from '@apidevtools/swagger-parser';
9
10
  export declare const V1_VERSION = "1.0.0";
10
11
  export declare const ANONYMOUS_TYPE_NAME = "<anonymous>";
11
12
  export declare class SwaggerAPI implements API {
@@ -14,6 +15,7 @@ export declare class SwaggerAPI implements API {
14
15
  private services;
15
16
  private fileParserLogger;
16
17
  private positionMap;
18
+ private $refs;
17
19
  typeRegistry?: TypeRegistry<SwaggerType>;
18
20
  _endpoints?: EndPoint[];
19
21
  _servers?: Server[];
@@ -25,13 +27,27 @@ export declare class SwaggerAPI implements API {
25
27
  private parsedNamespace?;
26
28
  private defaultedNamespace;
27
29
  serviceOverrides?: ResolvedServiceOverrides;
28
- constructor(document: OpenAPIV3.Document, sourceUrl: URL, services: NamedLoggerService & NamedFeatureFlagsService, fileParserLogger: FileParserLogger, positionMap: SourcePositionMap);
30
+ constructor(document: OpenAPIV3.Document, sourceUrl: URL, services: NamedLoggerService & NamedFeatureFlagsService, fileParserLogger: FileParserLogger, positionMap: SourcePositionMap, $refs: SwaggerParser.$Refs);
29
31
  /**
30
32
  * Get the source position for a given JSON path in the OpenAPI document.
31
33
  * @param jsonPath - The JSON path (e.g., "paths//users/get" or "components/schemas/User")
32
34
  * @returns The position if found, undefined otherwise
33
35
  */
34
36
  getPosition(jsonPath: string): Position | undefined;
37
+ /**
38
+ * Find the position of a $ref declaration by its target value.
39
+ * @param refValue - The $ref target string (e.g., "./schemas/User.yaml#/TypeThatDoesNotExist")
40
+ * @returns The position where the $ref is declared, if found
41
+ */
42
+ getRefPosition(refValue: string): Position | undefined;
43
+ /**
44
+ * Resolve a $ref target path with respect to the directory of the source.
45
+ * This is particularly necessary for relative paths since this.$refs.get() resolves
46
+ * paths relative to the root file and not the ref source file.
47
+ * @param refPath - The $ref target path (e.g., "./schemas/User.yaml#/User")
48
+ * @returns The path from where the $ref is declared (e.g, "file:///path/to/source/./schemas/User.yaml#/User")
49
+ */
50
+ resolveRefTargetPath(refTarget: string): any;
35
51
  private build;
36
52
  private buildEndpoints;
37
53
  private buildServers;
@@ -1,3 +1,4 @@
1
+ import SwaggerParser from '@apidevtools/swagger-parser';
1
2
  import { SwaggerAPI } from './SwaggerAPI';
2
3
  import { SourcePositionMap } from './source-position-map';
3
4
  import type { OpenAPIV3 } from 'openapi-types';
@@ -6,6 +7,7 @@ import type { NamedFeatureFlagsService } from '@conduit-client/service-feature-f
6
7
  export interface ParsedSwaggerDocument {
7
8
  document: OpenAPIV3.Document;
8
9
  positionMap: SourcePositionMap;
10
+ $refs: SwaggerParser.$Refs;
9
11
  }
10
12
  /**
11
13
  * Parse and validate an OpenAPI document using swagger-parser
@@ -20,6 +20,11 @@ export declare class SourcePositionMap {
20
20
  * @param jsonPath - The JSON path (e.g., "paths//users/get" or "components/schemas/User")
21
21
  */
22
22
  get(jsonPath: string): Position | undefined;
23
+ /**
24
+ * Get the position for a given $ref path
25
+ * @param refPath - The $ref path (e.g., "./schemas/User.yaml#/User")
26
+ */
27
+ getRef(refPath: string): Position | undefined;
23
28
  /**
24
29
  * Set the position for a given JSON path
25
30
  */
@@ -6,7 +6,8 @@ export type OpenAPISchema = OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject;
6
6
  * Works with any OpenAPI object type that could be a reference
7
7
  */
8
8
  export declare function isReferenceObject<T>(obj: T | OpenAPIV3.ReferenceObject): obj is OpenAPIV3.ReferenceObject;
9
- export declare function isSchemaObject(schema: OpenAPISchema): schema is OpenAPIV3.SchemaObject;
9
+ export declare function isSchemaObject(obj: unknown): obj is OpenAPIV3.SchemaObject;
10
+ export declare function isOpenAPISchema(obj: unknown): obj is OpenAPISchema;
10
11
  export declare function isArraySchema(schema: OpenAPIV3.SchemaObject): boolean;
11
12
  export declare function isObjectSchema(schema: OpenAPIV3.SchemaObject): boolean;
12
13
  export declare function isScalarSchema(schema: OpenAPIV3.SchemaObject): boolean;
@@ -23,7 +24,10 @@ export declare function hasDiscriminator(schema: OpenAPIV3.SchemaObject): boolea
23
24
  export declare function mapScalarType(schema: OpenAPIV3.SchemaObject): ScalarType['type'];
24
25
  /**
25
26
  * Extracts the type name from a $ref string
26
- * e.g., "#/components/schemas/User" -> "User"
27
+ * Handles both internal refs and external file refs:
28
+ * - "#/components/schemas/User" -> "User"
29
+ * - "./schemas/User.yaml#/User" -> "User"
30
+ * - "../../path/to/File.yaml#TypeName" -> "TypeName"
27
31
  */
28
32
  export declare function getRefTypeName(ref: string): string;
29
33
  /**
@@ -35,3 +39,13 @@ export declare function coerceValueByType(value: unknown, type: ScalarType['type
35
39
  * Throws an error if non-scalar values (objects/arrays) are found.
36
40
  */
37
41
  export declare function getEnumValues(schema: OpenAPIV3.SchemaObject, schemaName?: string): Array<string | number | boolean | null>;
42
+ export declare function isReferencePath(obj: OpenAPIV3.PathItemObject): obj is OpenAPIV3.PathItemObject & {
43
+ $ref: string;
44
+ };
45
+ /**
46
+ * Extracts root-level schemas from a file.
47
+ * External schema files may define schemas at the root level (not under components/schemas).
48
+ * This function identifies such schemas by excluding known OpenAPI document properties
49
+ * and checking for schema-specific properties.
50
+ */
51
+ export declare function extractRootLevelSchemas(file: OpenAPIV3.Document): Record<string, OpenAPISchema>;
package/dist/v1/index.js CHANGED
@@ -13523,6 +13523,12 @@ class SwaggerNilTypeImpl extends SwaggerBaseType {
13523
13523
  function isReferenceObject(obj) {
13524
13524
  return obj !== null && typeof obj === "object" && "$ref" in obj;
13525
13525
  }
13526
+ function isSchemaObject(obj) {
13527
+ return obj !== null && typeof obj === "object" && !("$ref" in obj) && ("type" in obj || "properties" in obj || "allOf" in obj || "anyOf" in obj || "oneOf" in obj || "items" in obj);
13528
+ }
13529
+ function isOpenAPISchema(obj) {
13530
+ return isSchemaObject(obj) || isReferenceObject(obj);
13531
+ }
13526
13532
  function isArraySchema(schema2) {
13527
13533
  return schema2.type === "array";
13528
13534
  }
@@ -13597,6 +13603,14 @@ function mapScalarType(schema2) {
13597
13603
  }
13598
13604
  }
13599
13605
  function getRefTypeName(ref) {
13606
+ const hashIndex = ref.lastIndexOf("#");
13607
+ if (hashIndex !== -1) {
13608
+ const fragment = ref.substring(hashIndex + 1);
13609
+ const parts2 = fragment.split("/").filter((p) => p !== "");
13610
+ if (parts2.length > 0) {
13611
+ return parts2[parts2.length - 1];
13612
+ }
13613
+ }
13600
13614
  const parts = ref.split("/");
13601
13615
  return parts[parts.length - 1];
13602
13616
  }
@@ -13614,6 +13628,28 @@ function getEnumValues(schema2, schemaName) {
13614
13628
  );
13615
13629
  });
13616
13630
  }
13631
+ const OPENAPI_DOCUMENT_PROPERTIES = /* @__PURE__ */ new Set([
13632
+ "openapi",
13633
+ "info",
13634
+ "servers",
13635
+ "paths",
13636
+ "components",
13637
+ "security",
13638
+ "tags",
13639
+ "externalDocs",
13640
+ "webhooks",
13641
+ "jsonSchemaDialect"
13642
+ ]);
13643
+ function extractRootLevelSchemas(file) {
13644
+ const schemas2 = {};
13645
+ for (const [key, value] of Object.entries(file)) {
13646
+ if (OPENAPI_DOCUMENT_PROPERTIES.has(key) || key.startsWith("x-")) continue;
13647
+ if (isOpenAPISchema(value)) {
13648
+ schemas2[key] = value;
13649
+ }
13650
+ }
13651
+ return schemas2;
13652
+ }
13617
13653
  class SwaggerEnumerableScalarType extends SwaggerBaseType {
13618
13654
  typeResolve() {
13619
13655
  if (this.schema.enum && this.schema.enum.length > 0) {
@@ -13901,6 +13937,12 @@ class SwaggerRefTypeImpl extends SwaggerBaseType {
13901
13937
  };
13902
13938
  this.resolved = false;
13903
13939
  this.refString = this.schema.$ref || "";
13940
+ if (this.schemaPosition.line === 0 && this.schemaPosition.column === 0 && this.refString && this.api) {
13941
+ const refPosition = this.api.getRefPosition(this.refString);
13942
+ if (refPosition) {
13943
+ this.schemaPosition = refPosition;
13944
+ }
13945
+ }
13904
13946
  }
13905
13947
  typeResolve() {
13906
13948
  if (this.refString) {
@@ -13912,7 +13954,7 @@ class SwaggerRefTypeImpl extends SwaggerBaseType {
13912
13954
  inheritedType.resolve();
13913
13955
  this.$ref = inheritedType;
13914
13956
  } else {
13915
- const errorMessage = `Failed to resolve $ref: ${typeName}`;
13957
+ const errorMessage = `Failed to resolve $ref: ${this.refString}`;
13916
13958
  this.fileParserLogger.error(this.schemaPosition, errorMessage);
13917
13959
  throw new Error(errorMessage);
13918
13960
  }
@@ -14204,10 +14246,14 @@ ${message}`);
14204
14246
  headers: {}
14205
14247
  };
14206
14248
  const parameters = this.operation.parameters || [];
14207
- for (const param of parameters) {
14208
- if (isReferenceObject(param)) continue;
14249
+ for (let param of parameters) {
14250
+ if (isReferenceObject(param)) {
14251
+ param = this.endpoint.api.resolveRefTargetPath(
14252
+ param.$ref
14253
+ );
14254
+ }
14209
14255
  const paramType = this.swaggerTypeFactory(
14210
- {},
14256
+ this.endpoint.api,
14211
14257
  param.schema || { type: "string" },
14212
14258
  void 0,
14213
14259
  this.typeRegistry,
@@ -14236,12 +14282,17 @@ ${message}`);
14236
14282
  }
14237
14283
  }
14238
14284
  if (this.operation.requestBody) {
14239
- const requestBody = isReferenceObject(this.operation.requestBody) ? void 0 : this.operation.requestBody;
14285
+ let requestBody = this.operation.requestBody;
14286
+ if (isReferenceObject(requestBody)) {
14287
+ requestBody = this.endpoint.api.resolveRefTargetPath(
14288
+ requestBody.$ref
14289
+ );
14290
+ }
14240
14291
  if (requestBody == null ? void 0 : requestBody.content) {
14241
14292
  for (const [mediaType, content] of Object.entries(requestBody.content)) {
14242
14293
  if (content.schema) {
14243
14294
  const dataType = this.swaggerTypeFactory(
14244
- {},
14295
+ this.endpoint.api,
14245
14296
  content.schema,
14246
14297
  void 0,
14247
14298
  this.typeRegistry,
@@ -14263,18 +14314,28 @@ ${message}`);
14263
14314
  buildResponses() {
14264
14315
  const responses = [];
14265
14316
  const opResponses = this.operation.responses || {};
14266
- for (const [statusCode, responseObj] of Object.entries(opResponses)) {
14267
- if (isReferenceObject(responseObj)) continue;
14317
+ for (const [statusCode, opResponse] of Object.entries(opResponses)) {
14318
+ let responseObj = opResponse;
14319
+ if (isReferenceObject(responseObj)) {
14320
+ responseObj = this.endpoint.api.resolveRefTargetPath(
14321
+ responseObj.$ref
14322
+ );
14323
+ }
14268
14324
  const response = {
14269
14325
  statusCode,
14270
14326
  payloads: [],
14271
14327
  headers: {}
14272
14328
  };
14273
14329
  if (responseObj.headers) {
14274
- for (const [headerName, headerObj] of Object.entries(responseObj.headers)) {
14275
- if (isReferenceObject(headerObj)) continue;
14330
+ for (const [headerName, headerValue] of Object.entries(responseObj.headers)) {
14331
+ let headerObj = headerValue;
14332
+ if (isReferenceObject(headerObj)) {
14333
+ headerObj = this.endpoint.api.resolveRefTargetPath(
14334
+ headerObj.$ref
14335
+ );
14336
+ }
14276
14337
  const headerType = this.swaggerTypeFactory(
14277
- {},
14338
+ this.endpoint.api,
14278
14339
  headerObj.schema || { type: "string" },
14279
14340
  void 0,
14280
14341
  this.typeRegistry,
@@ -14292,7 +14353,7 @@ ${message}`);
14292
14353
  for (const [mediaType, content] of Object.entries(responseObj.content)) {
14293
14354
  if (content.schema) {
14294
14355
  const dataType = this.swaggerTypeFactory(
14295
- {},
14356
+ this.endpoint.api,
14296
14357
  content.schema,
14297
14358
  void 0,
14298
14359
  this.typeRegistry,
@@ -14623,8 +14684,10 @@ class SwaggerBaseEndpoint {
14623
14684
  this.path = pathStr;
14624
14685
  this.uriParameters = {};
14625
14686
  const parameters = pathItem.parameters || [];
14626
- for (const param of parameters) {
14627
- if (isReferenceObject(param)) continue;
14687
+ for (let param of parameters) {
14688
+ if (isReferenceObject(param)) {
14689
+ param = api.resolveRefTargetPath(param.$ref);
14690
+ }
14628
14691
  if (param.in !== "path") continue;
14629
14692
  const paramType = swaggerTypeFactory2(
14630
14693
  api,
@@ -14761,12 +14824,13 @@ const oneStoreExtensionsSchema = z.object({
14761
14824
  }).strict()
14762
14825
  });
14763
14826
  class SwaggerAPI {
14764
- constructor(document, sourceUrl, services, fileParserLogger, positionMap) {
14827
+ constructor(document, sourceUrl, services, fileParserLogger, positionMap, $refs) {
14765
14828
  this.document = document;
14766
14829
  this.sourceUrl = sourceUrl;
14767
14830
  this.services = services;
14768
14831
  this.fileParserLogger = fileParserLogger;
14769
14832
  this.positionMap = positionMap;
14833
+ this.$refs = $refs;
14770
14834
  this.built = false;
14771
14835
  const urlPath = sourceUrl.pathname;
14772
14836
  this.defaultedNamespace = path.parse(urlPath).name;
@@ -14782,6 +14846,41 @@ class SwaggerAPI {
14782
14846
  getPosition(jsonPath) {
14783
14847
  return this.positionMap.get(jsonPath);
14784
14848
  }
14849
+ /**
14850
+ * Find the position of a $ref declaration by its target value.
14851
+ * @param refValue - The $ref target string (e.g., "./schemas/User.yaml#/TypeThatDoesNotExist")
14852
+ * @returns The position where the $ref is declared, if found
14853
+ */
14854
+ getRefPosition(refValue) {
14855
+ const result = this.positionMap.findRefPosition(refValue);
14856
+ return result == null ? void 0 : result.position;
14857
+ }
14858
+ /**
14859
+ * Resolve a $ref target path with respect to the directory of the source.
14860
+ * This is particularly necessary for relative paths since this.$refs.get() resolves
14861
+ * paths relative to the root file and not the ref source file.
14862
+ * @param refPath - The $ref target path (e.g., "./schemas/User.yaml#/User")
14863
+ * @returns The path from where the $ref is declared (e.g, "file:///path/to/source/./schemas/User.yaml#/User")
14864
+ */
14865
+ resolveRefTargetPath(refTarget) {
14866
+ var _a;
14867
+ const hashIndex = refTarget.indexOf("#");
14868
+ const refTargetFilePath = hashIndex === -1 ? refTarget : refTarget.substring(0, hashIndex);
14869
+ let resolvedPath;
14870
+ if (!refTargetFilePath) {
14871
+ resolvedPath = refTarget;
14872
+ } else if (/^(https?|file):\/\//.test(refTargetFilePath)) {
14873
+ resolvedPath = refTargetFilePath;
14874
+ } else {
14875
+ const refSourceFilePath = (_a = this.positionMap.getRef(refTarget)) == null ? void 0 : _a.filePath;
14876
+ if (!refSourceFilePath) {
14877
+ throw new Error(`Failed to resolve $ref: ${refTarget}`);
14878
+ }
14879
+ const refSourceBasePath = path.dirname(refSourceFilePath);
14880
+ resolvedPath = path.join(refSourceBasePath, refTarget);
14881
+ }
14882
+ return this.$refs.get(resolvedPath);
14883
+ }
14785
14884
  build() {
14786
14885
  if (!this.built) {
14787
14886
  this.built = true;
@@ -14792,9 +14891,14 @@ class SwaggerAPI {
14792
14891
  }
14793
14892
  }
14794
14893
  buildEndpoints() {
14795
- const paths = this.document.paths || {};
14796
- this._endpoints = Object.entries(paths).map(([pathStr, pathItem]) => {
14894
+ var _a;
14895
+ const paths = ((_a = this.$refs.values()[this.sourceUrl.toString()]) == null ? void 0 : _a.paths) || {};
14896
+ this._endpoints = Object.entries(paths).map(([pathStr, pathValue]) => {
14897
+ let pathItem = pathValue;
14797
14898
  if (!pathItem) return void 0;
14899
+ if (isReferenceObject(pathItem)) {
14900
+ pathItem = this.resolveRefTargetPath(pathItem.$ref);
14901
+ }
14798
14902
  return swaggerEndpointFactory(
14799
14903
  pathStr,
14800
14904
  pathItem,
@@ -14839,10 +14943,17 @@ servers:
14839
14943
  });
14840
14944
  }
14841
14945
  buildTypes() {
14842
- var _a;
14843
14946
  this.services.logger.debug("SwaggerAPI - Building the type registry");
14844
14947
  const typeRegistry = this.typeRegistry = new BaseTypeRegistry();
14845
- const schemas2 = ((_a = this.document.components) == null ? void 0 : _a.schemas) || {};
14948
+ const schemas2 = Object.values(this.$refs.values()).reduce(
14949
+ (acc, file) => {
14950
+ var _a;
14951
+ const componentSchemas = ((_a = file.components) == null ? void 0 : _a.schemas) ?? {};
14952
+ const rootSchemas = extractRootLevelSchemas(file);
14953
+ return { ...acc, ...componentSchemas, ...rootSchemas };
14954
+ },
14955
+ {}
14956
+ );
14846
14957
  for (const [name, schema2] of Object.entries(schemas2)) {
14847
14958
  this.services.logger.debug(`SwaggerAPI - Building Swagger Type for ${name}`);
14848
14959
  const swaggerType = swaggerTypeFactory(
@@ -14942,6 +15053,13 @@ class SourcePositionMap {
14942
15053
  get(jsonPath) {
14943
15054
  return this.positions.get(jsonPath);
14944
15055
  }
15056
+ /**
15057
+ * Get the position for a given $ref path
15058
+ * @param refPath - The $ref path (e.g., "./schemas/User.yaml#/User")
15059
+ */
15060
+ getRef(refPath) {
15061
+ return this.refTargets.get(refPath);
15062
+ }
14945
15063
  /**
14946
15064
  * Set the position for a given JSON path
14947
15065
  */
@@ -15105,7 +15223,9 @@ function findRefPositionInCache(targetPath, token) {
15105
15223
  async function parseSwaggerDocument(source, logger) {
15106
15224
  const sourceUrl = source.toString();
15107
15225
  try {
15108
- const api = await SwaggerParser.bundle(sourceUrl, PARSER_OPTIONS);
15226
+ const parser = new SwaggerParser();
15227
+ const api = await parser.parse(sourceUrl, PARSER_OPTIONS);
15228
+ const $refs = await parser.resolve(sourceUrl, PARSER_OPTIONS);
15109
15229
  const positionMap = new SourcePositionMap();
15110
15230
  for (const [fileUrl, filePositionMap] of positionMapCache) {
15111
15231
  positionMap.merge(filePositionMap, fileUrl);
@@ -15118,7 +15238,8 @@ async function parseSwaggerDocument(source, logger) {
15118
15238
  }
15119
15239
  return {
15120
15240
  document: api,
15121
- positionMap
15241
+ positionMap,
15242
+ $refs
15122
15243
  };
15123
15244
  } catch (error) {
15124
15245
  if (error instanceof MissingPointerError) {
@@ -15170,8 +15291,8 @@ async function parseSwaggerDocument(source, logger) {
15170
15291
  }
15171
15292
  }
15172
15293
  async function parseUrl(source, services, fileParserLogger) {
15173
- const { document, positionMap } = await parseSwaggerDocument(source, fileParserLogger);
15174
- const api = new SwaggerAPI(document, source, services, fileParserLogger, positionMap);
15294
+ const { document, positionMap, $refs } = await parseSwaggerDocument(source, fileParserLogger);
15295
+ const api = new SwaggerAPI(document, source, services, fileParserLogger, positionMap, $refs);
15175
15296
  api.validate();
15176
15297
  return api;
15177
15298
  }