@c4a/extract-contract 0.7.4 → 0.7.8

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.
Files changed (2) hide show
  1. package/index.js +142 -14
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -21512,6 +21512,12 @@ function parseGraphQlSources(files) {
21512
21512
  name,
21513
21513
  extension,
21514
21514
  field_names: fields.map((field) => field.name.value).sort(),
21515
+ fields: fields.map((field) => ({
21516
+ name: field.name.value,
21517
+ type: import_graphql.print(field.type),
21518
+ optional: field.type.kind !== import_graphql.Kind.NON_NULL_TYPE,
21519
+ ...field.description === undefined ? {} : { description: field.description.value }
21520
+ })),
21515
21521
  locator: locator(document.path, definition, `type:${name}${extension ? ":extension" : ""}`)
21516
21522
  };
21517
21523
  catalog.types.push(type);
@@ -21539,6 +21545,13 @@ function parseGraphQlSources(files) {
21539
21545
  const fieldName = field.name.value;
21540
21546
  const operation = { operation_ref: `${document.path}#root:${name}:field:${fieldName}`, protocol: "graphql", operation_kind: operationKind, name: fieldName, parent: name, deprecated: deprecated(field), locator: locator(document.path, field, `operation:${operationKind}:${name}.${fieldName}`) };
21541
21547
  catalog.operations.push(operation);
21548
+ operation.fields = [...(field.arguments ?? []).map((argument) => ({
21549
+ name: argument.name.value,
21550
+ type: import_graphql.print(argument.type),
21551
+ optional: argument.type.kind !== import_graphql.Kind.NON_NULL_TYPE || argument.defaultValue !== undefined,
21552
+ location: "argument",
21553
+ ...argument.defaultValue === undefined ? {} : { defaultValue: import_graphql.print(argument.defaultValue) }
21554
+ })), { name: "return", type: import_graphql.print(field.type), optional: field.type.kind !== import_graphql.Kind.NON_NULL_TYPE, location: "response" }];
21542
21555
  }
21543
21556
  }
21544
21557
  }
@@ -21546,10 +21559,123 @@ function parseGraphQlSources(files) {
21546
21559
  }
21547
21560
 
21548
21561
  // src/openApiParser.ts
21549
- var import_yaml = __toESM(require_dist(), 1);
21550
21562
  import { posix } from "node:path";
21563
+
21564
+ // src/openApiFields.ts
21565
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
21566
+ function resolved(value, root, seen = new Set) {
21567
+ const schema = record(value);
21568
+ const ref = schema.$ref;
21569
+ if (typeof ref !== "string" || !ref.startsWith("#/") || seen.has(ref))
21570
+ return schema;
21571
+ seen.add(ref);
21572
+ let target = root;
21573
+ for (const part of ref.slice(2).split("/"))
21574
+ target = record(target)[part.replace(/~1/gu, "/").replace(/~0/gu, "~")];
21575
+ return target === undefined ? schema : { ...resolved(target, root, seen), ...schema };
21576
+ }
21577
+ function declaredType(schema) {
21578
+ if (typeof schema.$ref === "string")
21579
+ return schema.$ref;
21580
+ if (schema.type === "array")
21581
+ return `Array<${declaredType(record(schema.items))}>`;
21582
+ if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
21583
+ return (schema.oneOf ?? schema.anyOf).map((item) => declaredType(record(item))).join(" | ");
21584
+ }
21585
+ return Array.isArray(schema.type) ? schema.type.join(" | ") : typeof schema.type === "string" ? schema.type : "not declared";
21586
+ }
21587
+ function constraints(schema) {
21588
+ return Object.fromEntries([
21589
+ "enum",
21590
+ "const",
21591
+ "format",
21592
+ "pattern",
21593
+ "minimum",
21594
+ "maximum",
21595
+ "minLength",
21596
+ "maxLength",
21597
+ "minItems",
21598
+ "maxItems",
21599
+ "nullable",
21600
+ "readOnly",
21601
+ "writeOnly",
21602
+ "additionalProperties"
21603
+ ].filter((key) => schema[key] !== undefined).map((key) => [key, schema[key]]));
21604
+ }
21605
+ function openApiFields(value, depth = 0, root = value) {
21606
+ if (depth > 8)
21607
+ return [];
21608
+ const schema = resolved(value, root);
21609
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
21610
+ const own = Object.entries(record(schema.properties)).map(([name, raw]) => {
21611
+ const field = resolved(raw, root);
21612
+ return {
21613
+ name,
21614
+ type: declaredType(field),
21615
+ constraints: constraints(field),
21616
+ optional: !required.has(name),
21617
+ ...field.default === undefined ? {} : { defaultValue: JSON.stringify(field.default) },
21618
+ ...typeof field.description === "string" ? { description: field.description } : {},
21619
+ ...field.properties === undefined ? {} : { fields: openApiFields(field, depth + 1, root) }
21620
+ };
21621
+ });
21622
+ const inherited = Array.isArray(schema.allOf) ? schema.allOf.flatMap((part) => openApiFields(part, depth + 1, root)) : [];
21623
+ return [...new Map([...inherited, ...own].map((field) => [field.name, field])).values()];
21624
+ }
21625
+ function openApiOperationFields(path, operation, root = {}) {
21626
+ const parameters = new Map;
21627
+ for (const raw of [
21628
+ ...Array.isArray(path.parameters) ? path.parameters : [],
21629
+ ...Array.isArray(operation.parameters) ? operation.parameters : []
21630
+ ]) {
21631
+ const parameter = resolved(raw, root);
21632
+ parameters.set(`${parameter.in}/${parameter.name}`, parameter);
21633
+ }
21634
+ const fields = [...parameters.values()].map((parameter) => {
21635
+ const schema = resolved(parameter.schema ?? parameter, root);
21636
+ return {
21637
+ name: String(parameter.name ?? parameter.$ref ?? "parameter"),
21638
+ type: declaredType(schema),
21639
+ constraints: constraints(schema),
21640
+ optional: parameter.required !== true,
21641
+ location: String(parameter.in ?? "reference"),
21642
+ ...schema.default === undefined ? {} : { defaultValue: JSON.stringify(schema.default) },
21643
+ ...typeof parameter.description === "string" ? { description: parameter.description } : {}
21644
+ };
21645
+ });
21646
+ const body = resolved(operation.requestBody, root);
21647
+ for (const [media, value] of Object.entries(record(body.content))) {
21648
+ const schema = resolved(record(value).schema, root);
21649
+ fields.push({
21650
+ name: `request (${media})`,
21651
+ type: declaredType(schema),
21652
+ constraints: constraints(schema),
21653
+ optional: body.required !== true,
21654
+ location: "body",
21655
+ fields: openApiFields(schema, 0, root)
21656
+ });
21657
+ }
21658
+ for (const [status, response] of Object.entries(record(operation.responses))) {
21659
+ const content = record(resolved(response, root).content);
21660
+ for (const [media, value] of Object.entries(content)) {
21661
+ const schema = resolved(record(value).schema, root);
21662
+ fields.push({
21663
+ name: `response ${status} (${media})`,
21664
+ type: declaredType(schema),
21665
+ constraints: constraints(schema),
21666
+ optional: false,
21667
+ location: "response",
21668
+ fields: openApiFields(schema, 0, root)
21669
+ });
21670
+ }
21671
+ }
21672
+ return fields;
21673
+ }
21674
+
21675
+ // src/openApiParser.ts
21676
+ var import_yaml = __toESM(require_dist(), 1);
21551
21677
  var HTTP_METHODS = new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace", "query"]);
21552
- function record(value) {
21678
+ function record2(value) {
21553
21679
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
21554
21680
  }
21555
21681
  function portablePath(path) {
@@ -21580,7 +21706,7 @@ function valueAt(root, path) {
21580
21706
  return;
21581
21707
  continue;
21582
21708
  }
21583
- const item = record(current);
21709
+ const item = record2(current);
21584
21710
  if (item === null || !(part in item))
21585
21711
  return;
21586
21712
  current = item[part];
@@ -21615,7 +21741,7 @@ function collectRefs(parsed) {
21615
21741
  value.forEach((item2, index) => visit(item2, [...path, String(index)], inheritedBaseId));
21616
21742
  return;
21617
21743
  }
21618
- const item = record(value);
21744
+ const item = record2(value);
21619
21745
  if (item === null)
21620
21746
  return;
21621
21747
  const underBaseId = inheritedBaseId || typeof item.$id === "string";
@@ -21632,23 +21758,24 @@ function collectRefs(parsed) {
21632
21758
  }
21633
21759
  function schemaTypes(parsed, root) {
21634
21760
  const candidates = [];
21635
- const components = record(root.components);
21636
- const schemas = record(components?.schemas);
21637
- const definitions = record(root.definitions);
21761
+ const components = record2(root.components);
21762
+ const schemas = record2(components?.schemas);
21763
+ const definitions = record2(root.definitions);
21638
21764
  if (schemas !== null)
21639
21765
  candidates.push(...Object.entries(schemas).map(([name, value]) => ({ path: ["components", "schemas", name], value })));
21640
21766
  if (definitions !== null)
21641
21767
  candidates.push(...Object.entries(definitions).map(([name, value]) => ({ path: ["definitions", name], value })));
21642
21768
  return candidates.map(({ path, value }) => {
21643
21769
  const name = path.at(-1);
21644
- const schema = record(value);
21770
+ const schema = record2(value);
21645
21771
  return {
21646
21772
  type_ref: `${parsed.path}#/${path.join("/")}`,
21647
21773
  protocol: "openapi",
21648
21774
  kind: typeof schema?.type === "string" ? schema.type : "schema",
21649
21775
  name,
21650
21776
  extension: false,
21651
- field_names: Object.keys(record(schema?.properties) ?? {}).sort(),
21777
+ field_names: Object.keys(record2(schema?.properties) ?? {}).sort(),
21778
+ fields: openApiFields(schema, 0, root),
21652
21779
  locator: yamlLocator(parsed, path, `schema:${name}`)
21653
21780
  };
21654
21781
  });
@@ -21656,11 +21783,11 @@ function schemaTypes(parsed, root) {
21656
21783
  function pathCatalog(parsed, root, key) {
21657
21784
  const endpoints = [];
21658
21785
  const operations = [];
21659
- const paths = record(root[key]);
21786
+ const paths = record2(root[key]);
21660
21787
  if (paths === null)
21661
21788
  return { endpoints, operations };
21662
21789
  for (const [route, rawPathItem] of Object.entries(paths).sort(([left], [right]) => left.localeCompare(right))) {
21663
- const pathItem = record(rawPathItem);
21790
+ const pathItem = record2(rawPathItem);
21664
21791
  if (pathItem === null)
21665
21792
  continue;
21666
21793
  const parent = key === "paths" ? route : `webhook:${route}`;
@@ -21668,7 +21795,7 @@ function pathCatalog(parsed, root, key) {
21668
21795
  for (const [method, rawOperation] of Object.entries(pathItem)) {
21669
21796
  if (!HTTP_METHODS.has(method.toLowerCase()))
21670
21797
  continue;
21671
- const operation = record(rawOperation);
21798
+ const operation = record2(rawOperation);
21672
21799
  if (operation === null)
21673
21800
  continue;
21674
21801
  const operationId = typeof operation.operationId === "string" ? operation.operationId : `${method.toLowerCase()} ${parent}`;
@@ -21679,6 +21806,7 @@ function pathCatalog(parsed, root, key) {
21679
21806
  name: operationId,
21680
21807
  parent,
21681
21808
  deprecated: operation.deprecated === true,
21809
+ fields: openApiOperationFields(pathItem, operation, root),
21682
21810
  locator: yamlLocator(parsed, [key, route, method], `operation:${method.toLowerCase()}:${parent}`)
21683
21811
  });
21684
21812
  }
@@ -21732,7 +21860,7 @@ function analyzeDocument(input) {
21732
21860
  catalog.diagnostics.push({ code: "contract-source-unsupported", severity: "error", locator: { path: parsed.path, line: 1, column: 1, qualified_item_path: "file" }, detail: parsed.parseError });
21733
21861
  return catalog;
21734
21862
  }
21735
- const root = record(parsed.root);
21863
+ const root = record2(parsed.root);
21736
21864
  if (input.primary && root === null) {
21737
21865
  catalog.disposition = "unsupported";
21738
21866
  catalog.diagnostics.push({ code: "contract-source-unsupported", severity: "error", locator: { path: parsed.path, line: 1, column: 1, qualified_item_path: "file" }, detail: "contract document root must be an object" });
@@ -21795,7 +21923,7 @@ function parseOpenApiSources(files) {
21795
21923
  if (/\.(?:json|ya?ml)$/iu.test(path))
21796
21924
  candidates.set(path, parseYaml(path, source));
21797
21925
  }
21798
- const roots = [...candidates.values()].filter((item) => record(item.root)?.openapi !== undefined || record(item.root)?.swagger !== undefined || item.marker && item.parseError !== null);
21926
+ const roots = [...candidates.values()].filter((item) => record2(item.root)?.openapi !== undefined || record2(item.root)?.swagger !== undefined || item.marker && item.parseError !== null);
21799
21927
  const rootPaths = new Set(roots.map((item) => item.path));
21800
21928
  const reachable = new Set(rootPaths);
21801
21929
  const graph = new Map;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c4a/extract-contract",
3
- "version": "0.7.4",
3
+ "version": "0.7.8",
4
4
  "type": "module",
5
5
  "description": "OpenAPI and GraphQL contract catalog adapter for Context",
6
6
  "license": "MIT",