@api-components/api-navigation 4.3.6 → 4.3.20

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@api-components/api-navigation",
3
3
  "description": "An element to display the response body",
4
- "version": "4.3.6",
4
+ "version": "4.3.20",
5
5
  "license": "Apache-2.0",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
@@ -26,10 +26,11 @@
26
26
  "email": "arc@mulesoft.com"
27
27
  },
28
28
  "dependencies": {
29
+ "@advanced-rest-client/arc-icons": "^3.3.4",
29
30
  "@advanced-rest-client/icons": "^4.0.2",
30
- "@anypoint-web-components/anypoint-button": "^1.2.3",
31
+ "@anypoint-web-components/anypoint-button": "^1.1.1",
31
32
  "@anypoint-web-components/anypoint-collapse": "^0.1.0",
32
- "@api-components/amf-helper-mixin": "^4.5.31",
33
+ "@api-components/amf-helper-mixin": "^4.5.34",
33
34
  "@api-components/http-method-label": "^3.1.5",
34
35
  "@api-components/raml-aware": "^3.0.0",
35
36
  "lit-element": "^2.3.1",
@@ -388,6 +388,13 @@ export declare class ApiNavigation {
388
388
  */
389
389
  _createOperationModel(item: object): MethodItem;
390
390
 
391
+ /** Detects whether current API is gRPC by inspecting media types */
392
+ __detectGrpcInternal(): boolean;
393
+ /** Maps AMF operation to gRPC stream type, defaults to 'unary' */
394
+ __getGrpcStreamTypeInternal(operation: object): 'unary' | 'server_streaming' | 'client_streaming' | 'bidi_streaming';
395
+ /** Returns request/response schema names for an operation */
396
+ __getOperationSchemasInternal(operationOrId: object | string): { request?: string; response?: string } | undefined;
397
+
391
398
  /**
392
399
  * Click handler for section name item.
393
400
  * Toggles the view.
@@ -3,7 +3,7 @@ import { LitElement, html } from 'lit-element';
3
3
  import { AmfHelperMixin } from '@api-components/amf-helper-mixin/amf-helper-mixin.js';
4
4
  import '@api-components/raml-aware/raml-aware.js';
5
5
  import '@anypoint-web-components/anypoint-button/anypoint-icon-button.js';
6
- import { keyboardArrowDown, codegenie, openInNew } from '@advanced-rest-client/icons/ArcIcons.js';
6
+ import { codegenie, keyboardArrowDown, openInNew } from '@advanced-rest-client/icons/ArcIcons.js';
7
7
  import '@anypoint-web-components/anypoint-collapse/anypoint-collapse.js';
8
8
  import httpMethodStyles from '@api-components/http-method-label/http-method-label-common-styles.js';
9
9
  import navStyles from './Styles.js';
@@ -479,6 +479,7 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
479
479
  this.summary = false;
480
480
  this.noink = false;
481
481
  this.allowPaths = false;
482
+ this._isGrpc = false;
482
483
  this.compatibility = false;
483
484
  this.rearrangeEndpoints = false;
484
485
  this.indentSize = 8;
@@ -547,7 +548,8 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
547
548
  * @override
548
549
  */
549
550
  __amfChanged(api) {
550
- if (!api) {
551
+ // Guard against initial empty-array AMF updates from data loaders
552
+ if (!api || (Array.isArray(api) && (api.length === 0 || !api[0]))) {
551
553
  return;
552
554
  }
553
555
  let model = api;
@@ -555,13 +557,17 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
555
557
  [model] = model;
556
558
  }
557
559
  let data = {};
560
+ this.__operationById = {};
558
561
  let isFragment = true;
559
562
  this._items = null;
560
563
  const moduleKey = this._getAmfKey(this.ns.aml.vocabularies.document.Module);
561
564
  if (this._hasType(model, this.ns.aml.vocabularies.document.Document)) {
562
565
  isFragment = false;
563
566
  model = this._ensureAmfModel(model);
564
- data = this._collectData(model);
567
+ // Decide collection strategy based on whether this is a gRPC API
568
+ const isGrpcApi = typeof this._isGrpcApi === 'function' ? !!this._isGrpcApi(model) : false;
569
+ this._isGrpc = isGrpcApi;
570
+ data = isGrpcApi ? this._collectGrpcNavigationData(model) : this._collectData(model);
565
571
  } else if (
566
572
  this._hasType(
567
573
  model,
@@ -593,6 +599,13 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
593
599
  this._types = data.types;
594
600
  this._security = data.securitySchemes;
595
601
  this._endpoints = data.endpoints;
602
+ try {
603
+ const webApi = this._computeApi(model);
604
+ const apiName = webApi ? this._getValue(webApi, this.ns.aml.vocabularies.core.name) : undefined;
605
+ if (this._renderSummary && apiName) {
606
+ this.summaryLabel = `${apiName} Overview`;
607
+ }
608
+ } catch (_) {}
596
609
  this._closeCollapses();
597
610
  setTimeout(() => {
598
611
  this._selectedChanged(this.selected);
@@ -618,6 +631,10 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
618
631
  if (!model) {
619
632
  return result;
620
633
  }
634
+ // Initialize __operationById if not already initialized
635
+ if (!this.__operationById) {
636
+ this.__operationById = {};
637
+ }
621
638
  result._typeIds = [];
622
639
  result._basePaths = [];
623
640
  this._traverseDeclarations(model, result);
@@ -628,6 +645,92 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
628
645
  return result;
629
646
  }
630
647
 
648
+ /**
649
+ * Collects navigation data for gRPC APIs using AmfHelperMixin helpers.
650
+ * Produces the same view-model shape used by the renderer.
651
+ *
652
+ * @param {object} model AMF model
653
+ * @return {TargetModel}
654
+ */
655
+ _collectGrpcNavigationData(model) {
656
+ const result = {
657
+ documentation: [],
658
+ types: [],
659
+ securitySchemes: [],
660
+ endpoints: [],
661
+ };
662
+ if (!model) {
663
+ return result;
664
+ }
665
+ const webApi = this._computeApi(model);
666
+ if (!webApi) {
667
+ return result;
668
+ }
669
+ // Build services -> methods as endpoint-like items
670
+ const services = typeof this._computeGrpcServices === 'function' ? this._computeGrpcServices(webApi) : undefined;
671
+ const servicesArray = services || [];
672
+ if (servicesArray && servicesArray.length) {
673
+ servicesArray.forEach(service => {
674
+ const serviceId = service && service['@id'];
675
+ const serviceName = typeof this._computeGrpcServiceName === 'function'
676
+ ? this._computeGrpcServiceName(service)
677
+ : this._getValue(service, this.ns.aml.vocabularies.core.name);
678
+ const operations = typeof this._computeGrpcMethods === 'function' ? this._computeGrpcMethods(service) : undefined;
679
+ const opsArray = operations || [];
680
+ const methods = opsArray.map(op => {
681
+ if (op && op['@id']) {
682
+ this.__operationById[op['@id']] = op;
683
+ }
684
+ const methodModel = this._createOperationModel(op);
685
+ // Replace method chip label with simplified type for gRPC
686
+ if (methodModel && methodModel.grpcStreamType) {
687
+ // Map to HTTP method colors: unary→patch(violet), client→publish(green), server→subscribe(blue), bidi→options(gray)
688
+ const colorMethodMap = {
689
+ 'unary': 'patch',
690
+ 'client_streaming': 'publish',
691
+ 'server_streaming': 'subscribe',
692
+ 'bidi_streaming': 'options'
693
+ };
694
+ const labelMap = {
695
+ 'unary': 'UNARY',
696
+ 'client_streaming': 'CLIENT',
697
+ 'server_streaming': 'SERVER',
698
+ 'bidi_streaming': 'BIDIRECTIONAL'
699
+ };
700
+ methodModel.method = labelMap[methodModel.grpcStreamType] || 'UNARY';
701
+ methodModel.methodForColor = colorMethodMap[methodModel.grpcStreamType] || 'patch';
702
+ }
703
+ return methodModel;
704
+ });
705
+ result.endpoints.push({
706
+ label: String(serviceName || 'Service'),
707
+ id: serviceId,
708
+ indent: 0,
709
+ methods,
710
+ });
711
+ });
712
+ }
713
+ // Populate Messages in the Types section
714
+ const messages = this._computeGrpcMessageTypes(model);
715
+ const msgArray = messages || [];
716
+ if (msgArray && msgArray.length) {
717
+ msgArray.forEach(shape => {
718
+ const id = shape && shape['@id'];
719
+ let name = this._getValue(shape, this.ns.aml.vocabularies.core.name);
720
+ if (!name) {
721
+ name = this._getValue(shape, this.ns.w3.shacl.name);
722
+ }
723
+ if (id && name) {
724
+ result.types.push({
725
+ label: String(name),
726
+ id,
727
+ });
728
+ }
729
+ });
730
+ }
731
+ return result;
732
+ }
733
+
631
734
  /**
632
735
  * Collects the data from the security fragment
633
736
  * @param {object} model Security fragment model
@@ -917,10 +1020,13 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
917
1020
  const result = {};
918
1021
 
919
1022
  let name = this._getValue(item, this.ns.aml.vocabularies.core.name);
920
- const path = /** @type string */ (this._getValue(
1023
+ let path = /** @type string */ (this._getValue(
921
1024
  item,
922
1025
  this.ns.raml.vocabularies.apiContract.path
923
1026
  ));
1027
+ if (!path && name) {
1028
+ path = `/${name}`;
1029
+ }
924
1030
  result.path = path;
925
1031
 
926
1032
  let tmpPath = path;
@@ -964,7 +1070,13 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
964
1070
  const id = item['@id'];
965
1071
  const key = this._getAmfKey(this.ns.aml.vocabularies.apiContract.supportedOperation);
966
1072
  const operations = this._ensureArray(item[key]) || [];
967
- const methods = operations.map(op => this._createOperationModel(op));
1073
+ const methods = operations.map(op => {
1074
+ const model = this._createOperationModel(op);
1075
+ if (op && op['@id']) {
1076
+ this.__operationById[op['@id']] = op;
1077
+ }
1078
+ return model;
1079
+ });
968
1080
  result.label = String(name);
969
1081
  result.id = id;
970
1082
  result.indent = indent;
@@ -985,11 +1097,37 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
985
1097
  ));
986
1098
  const methodKey = this.ns.aml.vocabularies.apiContract.method;
987
1099
  const id = item['@id'];
988
- const method = /** @type string */ (this._getValue(item, methodKey));
1100
+ let method = /** @type string */ (this._getValue(item, methodKey));
1101
+ // Populate gRPC fields via helpers when available
1102
+ /** @type {'unary'|'server_streaming'|'client_streaming'|'bidi_streaming'|undefined} */
1103
+ const grpcStreamType = this._isGrpc && /** @type any */ (this._getGrpcStreamType(item))
1104
+ const requestShape = this._isGrpc && this._computeGrpcRequestSchema(item)
1105
+ const responseShape = this._isGrpc && this._computeGrpcResponseSchema(item)
1106
+
1107
+ let requestSchema;
1108
+ if (requestShape) {
1109
+ const rn = this._getValue(requestShape, this.ns.aml.vocabularies.core.name) || this._getValue(requestShape, this.ns.w3.shacl.name);
1110
+ if (typeof rn === 'string') {
1111
+ requestSchema = rn;
1112
+ }
1113
+ }
1114
+ let responseSchema;
1115
+ if (responseShape) {
1116
+ const rsn = this._getValue(responseShape, this.ns.aml.vocabularies.core.name) || this._getValue(responseShape, this.ns.w3.shacl.name);
1117
+ if (typeof rsn === 'string') {
1118
+ responseSchema = rsn;
1119
+ }
1120
+ }
1121
+ if (this._isGrpc && grpcStreamType) {
1122
+ method = this._getGrpcStreamTypeDisplayName(grpcStreamType);
1123
+ }
989
1124
  return {
990
1125
  label,
991
1126
  id,
992
1127
  method,
1128
+ grpcStreamType,
1129
+ requestSchema,
1130
+ responseSchema,
993
1131
  };
994
1132
  }
995
1133
 
@@ -1298,7 +1436,7 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
1298
1436
  }
1299
1437
  node.classList.add('passive-selected');
1300
1438
  this.__hasPassiveSelection = true;
1301
- const collapse = /** @type IronCollapseElement */ (node.parentElement);
1439
+ const collapse = /** @type AnypointCollapseElement */ (node.parentElement);
1302
1440
  if (!collapse.opened) {
1303
1441
  collapse.opened = true;
1304
1442
  }
@@ -1847,7 +1985,14 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
1847
1985
  }
1848
1986
  const toggleState = this.endpointsOpened ? 'Expanded' : 'Collapsed';
1849
1987
 
1850
- const sectionLabel = this._isWebAPI(this.amf) ? 'Endpoints' : 'Channels';
1988
+ let sectionLabel;
1989
+ if (this._isGrpc) {
1990
+ sectionLabel = 'Methods';
1991
+ } else if (this._isWebAPI(this.amf)) {
1992
+ sectionLabel = 'Endpoints';
1993
+ } else {
1994
+ sectionLabel = 'Channels';
1995
+ }
1851
1996
  const lowercaseSectionLabel = sectionLabel.toLowerCase();
1852
1997
 
1853
1998
  return html` <section
@@ -1895,7 +2040,7 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
1895
2040
  */
1896
2041
  _overviewTemplate(item) {
1897
2042
  if (this.noOverview) {
1898
- return '';
2043
+ return html``;
1899
2044
  }
1900
2045
  return html`<div
1901
2046
  part="api-navigation-list-item"
@@ -2003,13 +2148,13 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
2003
2148
  class="method-label ${methodItem.hasAgent
2004
2149
  ? 'method-label-with-icon'
2005
2150
  : ''}"
2006
- data-method="${methodItem.method}"
2151
+ data-method="${methodItem.methodForColor || methodItem.method}"
2007
2152
  >${methodItem.method}
2008
2153
  ${methodItem.hasAgent
2009
2154
  ? html`<span class="method-icon">${codegenie}</span>`
2010
2155
  : ''}</span
2011
2156
  >
2012
- ${methodItem.label}
2157
+ ${!this._isGrpc ? methodItem.label : ''}
2013
2158
  </div>`;
2014
2159
  }
2015
2160
 
@@ -2108,6 +2253,7 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
2108
2253
  return '';
2109
2254
  }
2110
2255
  const toggleState = this.typesOpened ? 'Expanded' : 'Collapsed';
2256
+ const typesLabel = this._isGrpc ? 'Messages' : 'Types';
2111
2257
 
2112
2258
  return html`
2113
2259
  <section class="types" ?data-opened="${this.typesOpened}">
@@ -2117,7 +2263,7 @@ export class ApiNavigation extends AmfHelperMixin(LitElement) {
2117
2263
  @click="${this._toggleSectionHandler}"
2118
2264
  title="Toggle types list"
2119
2265
  >
2120
- <div class="title-h3">Types</div>
2266
+ <div class="title-h3">${typesLabel}</div>
2121
2267
  <anypoint-icon-button
2122
2268
  part="toggle-button"
2123
2269
  class="toggle-button"
package/src/types.d.ts CHANGED
@@ -5,7 +5,11 @@ export declare interface NavigationItem {
5
5
 
6
6
  export declare interface MethodItem extends NavigationItem {
7
7
  method: string;
8
+ methodForColor?: string;
8
9
  hasAgent?: boolean;
10
+ grpcStreamType?: 'unary' | 'server_streaming' | 'client_streaming' | 'bidi_streaming';
11
+ requestSchema?: string;
12
+ responseSchema?: string;
9
13
  }
10
14
 
11
15
  export declare interface EndpointItem extends NavigationItem {