@api-components/api-type-document 4.2.34 → 4.2.37

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-type-document",
3
3
  "description": "A documentation table for type (resource) properties. Works with AMF data model",
4
- "version": "4.2.34",
4
+ "version": "4.2.37",
5
5
  "license": "Apache-2.0",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
@@ -28,7 +28,7 @@
28
28
  "@advanced-rest-client/arc-marked": "^1.1.0",
29
29
  "@advanced-rest-client/markdown-styles": "^3.1.4",
30
30
  "@anypoint-web-components/anypoint-button": "^1.2.3",
31
- "@api-components/amf-helper-mixin": "^4.5.29",
31
+ "@api-components/amf-helper-mixin": "^4.5.34",
32
32
  "@api-components/api-annotation-document": "^4.1.0",
33
33
  "@api-components/api-resource-example-document": "^4.3.3",
34
34
  "@open-wc/dedupe-mixin": "^1.3.0",
@@ -39,15 +39,15 @@
39
39
  "@anypoint-web-components/anypoint-checkbox": "^1.2.2",
40
40
  "@anypoint-web-components/anypoint-styles": "^1.0.2",
41
41
  "@api-components/api-model-generator": "^0.2.14",
42
- "@commitlint/cli": "^13.2.0",
42
+ "@commitlint/cli": "^13.2.1",
43
43
  "@commitlint/config-conventional": "^13.2.0",
44
44
  "@open-wc/eslint-config": "^4.2.0",
45
- "@open-wc/testing": "^2.5.15",
45
+ "@open-wc/testing": "^2.5.32",
46
46
  "@web/dev-server": "^0.1.24",
47
47
  "@web/test-runner": "^0.13.18",
48
- "@web/test-runner-playwright": "0.8.10",
49
- "eslint": "^7.32.0",
50
- "eslint-config-prettier": "^8.1.0",
48
+ "@web/test-runner-playwright": "^0.11.0",
49
+ "eslint": "^8.0.0",
50
+ "eslint-config-prettier": "^8.3.0",
51
51
  "husky": "^7.0.2",
52
52
  "lint-staged": "^11.1.2",
53
53
  "sinon": "^11.1.2",
@@ -54,6 +54,23 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
54
54
  selectedMediaType: number;
55
55
  // The type after it has been resolved.
56
56
  _resolvedType: Object;
57
+ /**
58
+ * Computed properties for object types.
59
+ * This is a reactive property that is recalculated when type, amf, or renderReadOnly changes.
60
+ */
61
+ _computedProperties: any[] | undefined;
62
+ /**
63
+ * Resolved type for examples with all link-target references resolved
64
+ */
65
+ _resolvedExampleType: Object | undefined;
66
+ /**
67
+ * Whether to show the examples section
68
+ */
69
+ _showExamples: boolean | undefined;
70
+ /**
71
+ * Effective media type for examples
72
+ */
73
+ _exampleMediaType: string | undefined;
57
74
  /**
58
75
  * Should be set if described properties has a parent type.
59
76
  * This is used when recursively iterating over properties.
@@ -242,6 +259,15 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
242
259
  */
243
260
  _computeProperties(item: any): any[]|undefined;
244
261
 
262
+ /**
263
+ * Deeply resolves link-target references in a type and its nested properties.
264
+ * This is essential for rendering complete examples with nested objects.
265
+ *
266
+ * @param type The type to resolve
267
+ * @returns The type with all link-target references resolved
268
+ */
269
+ _deepResolveType(type: any): any|undefined;
270
+
245
271
  /**
246
272
  * Computes list values for `andTypes` property.
247
273
  * @param items List of OAS' "and" properties
@@ -68,6 +68,22 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
68
68
  selectedMediaType: { type: Number },
69
69
  // The type after it has been resolved.
70
70
  _resolvedType: { type: Object },
71
+ /**
72
+ * Computed properties from the resolved type
73
+ */
74
+ _computedProperties: { type: Array },
75
+ /**
76
+ * Resolved type for examples with all link-target references resolved
77
+ */
78
+ _resolvedExampleType: { type: Object },
79
+ /**
80
+ * Whether to show the examples section
81
+ */
82
+ _showExamples: { type: Boolean },
83
+ /**
84
+ * Effective media type for examples
85
+ */
86
+ _exampleMediaType: { type: String },
71
87
  /**
72
88
  * Should be set if described properties has a parent type.
73
89
  * This is used when recursively iterating over properties.
@@ -325,6 +341,10 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
325
341
  */
326
342
  this.selectedAnyOf = undefined;
327
343
  this.renderReadOnly = false;
344
+ this.noMainExample = false;
345
+ this._hasExamples = false;
346
+ this._renderMainExample = false;
347
+ this._cachedDeepResolvedType = undefined;
328
348
 
329
349
  this._isPropertyReadOnly = this._isPropertyReadOnly.bind(this);
330
350
  this.noMediaSelector = false;
@@ -345,6 +365,37 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
345
365
  return isScalar ? false : !!(!noMainExample && hasExamples);
346
366
  }
347
367
 
368
+ /**
369
+ * Called when properties change
370
+ * @param {Map} changedProperties Changed properties
371
+ */
372
+ updated(changedProperties) {
373
+ super.updated(changedProperties);
374
+
375
+ // If amf changed and we have a type, recalculate properties synchronously
376
+ if (changedProperties.has('amf') && this._resolvedType && this.amf) {
377
+ // Cancel any pending debounced calls and recalculate
378
+ this.__typeChangeDebouncer = false;
379
+ this._typeChanged(this._resolvedType);
380
+ // _typeChanged will update _computedProperties, _isGrpcApi, and _deepResolvedType
381
+ }
382
+
383
+ // If renderReadOnly changed and we have an object, recalculate properties
384
+ // This is needed because _filterReadOnlyProperties depends on this.renderReadOnly
385
+ if (changedProperties.has('renderReadOnly') && this._resolvedType && this.isObject) {
386
+ this._computedProperties = this._computeProperties(this._resolvedType);
387
+ }
388
+
389
+ // If noMainExample changed, recalculate whether to render examples
390
+ if (changedProperties.has('noMainExample') && this._resolvedType) {
391
+ this._showExamples = !this.noMainExample && (
392
+ this.renderMediaSelector ||
393
+ this.isObject ||
394
+ this._renderMainExample
395
+ );
396
+ }
397
+ }
398
+
348
399
  /**
349
400
  * Called when resolved type or amf changed.
350
401
  * Creates a debouncer to compute UI values so it's independent of
@@ -455,6 +506,32 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
455
506
  this.isAnd = isAnd;
456
507
  this.isOneOf = isOneOf;
457
508
  this.isAnyOf = isAnyOf;
509
+
510
+ // Compute properties for objects - this needs to be reactive
511
+ if (isObject) {
512
+ this._computedProperties = this._computeProperties(type);
513
+ } else {
514
+ this._computedProperties = undefined;
515
+ }
516
+
517
+ // Always deep resolve for examples (resolves link-target references for gRPC and similar)
518
+ // This is cheap if there are no link-targets
519
+ if (type) {
520
+ this._resolvedExampleType = this._deepResolveType(type);
521
+ } else {
522
+ this._resolvedExampleType = type;
523
+ }
524
+
525
+ // Determine if we should show the examples section
526
+ // Priority: noMainExample (hide) > renderMediaSelector (show) > isObject (show) > _renderMainExample
527
+ this._showExamples = !this.noMainExample && (
528
+ this.renderMediaSelector || // Need to show the section for the media type selector
529
+ isObject || // Objects can generate examples automatically
530
+ this._renderMainExample // Has explicit examples
531
+ );
532
+
533
+ // Effective media type - use 'application/json' as default for objects without mediaType
534
+ this._exampleMediaType = this.mediaType || (isObject ? 'application/json' : undefined);
458
535
  }
459
536
 
460
537
  /**
@@ -526,18 +603,11 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
526
603
  if (Array.isArray(item)) {
527
604
  [item] = item;
528
605
  }
606
+ // For array types in unions, return the array itself instead of unwrapping to items
607
+ // This preserves the "array of" indicator in the UI
529
608
  if (this._hasType(item, this.ns.aml.vocabularies.shapes.ArrayShape)) {
530
609
  item = this._resolve(item);
531
- const itemsKey = this._getAmfKey(this.ns.aml.vocabularies.shapes.items);
532
- const items = this._ensureArray(item[itemsKey]);
533
- if (items && items.length === 1) {
534
- let result = items[0];
535
- if (Array.isArray(result)) {
536
- [result] = result;
537
- }
538
- result = this._resolve(result);
539
- return result;
540
- }
610
+ return item;
541
611
  }
542
612
  if (Array.isArray(item)) {
543
613
  [item] = item;
@@ -546,6 +616,91 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
546
616
  return this._resolve(item);
547
617
  }
548
618
 
619
+
620
+ /**
621
+ * Deeply resolves link-target references in a type for example generation.
622
+ * This ensures that nested objects show their full structure in examples.
623
+ * This is needed for APIs like gRPC where nested types use link-target references.
624
+ *
625
+ * @param {Object} type The type to resolve
626
+ * @return {Object} The deeply resolved type
627
+ */
628
+ _deepResolveType(type) {
629
+ if (!type || !this.amf) {
630
+ return type;
631
+ }
632
+
633
+ const resolved = this._resolve(type);
634
+ if (!resolved) {
635
+ return type;
636
+ }
637
+
638
+ // Get properties
639
+ const propertyKey = this._getAmfKey(this.ns.w3.shacl.property);
640
+ const properties = this._ensureArray(resolved[propertyKey]);
641
+
642
+ if (!properties || !properties.length) {
643
+ return resolved;
644
+ }
645
+
646
+ // Create a new type object with deeply resolved properties
647
+ const deepResolved = { ...resolved };
648
+ const linkTargetKey = this._getAmfKey(this.ns.aml.vocabularies.document.linkTarget);
649
+ const rangeKey = this._getAmfKey(this.ns.raml.vocabularies.shapes.range);
650
+
651
+ // Resolve each property's range
652
+ const resolvedProperties = properties.map(prop => {
653
+ const resolvedProp = this._resolve(prop);
654
+ if (!resolvedProp) {
655
+ return prop;
656
+ }
657
+
658
+ const range = this._ensureArray(resolvedProp[rangeKey])[0];
659
+ if (!range) {
660
+ return resolvedProp;
661
+ }
662
+
663
+ // If the range has a link-target, resolve it
664
+ if (range[linkTargetKey]) {
665
+ const linkTargetId = this._ensureArray(range[linkTargetKey])[0];
666
+ if (linkTargetId && linkTargetId['@id']) {
667
+ const targetId = linkTargetId['@id'];
668
+
669
+ // Find the target
670
+ const declares = this._computeDeclares(this.amf);
671
+ let target = declares ? this._findById(declares, targetId) : undefined;
672
+
673
+ if (!target) {
674
+ const references = this._computeReferences(this.amf);
675
+ if (references && references.length) {
676
+ for (let i = 0; i < references.length && !target; i++) {
677
+ const refDeclares = this._computeDeclares(references[i]);
678
+ if (refDeclares) {
679
+ target = this._findById(refDeclares, targetId);
680
+ }
681
+ }
682
+ }
683
+ }
684
+
685
+ // If target found, replace the range with the resolved target
686
+ if (target) {
687
+ const resolvedTarget = this._resolve(target);
688
+ if (resolvedTarget) {
689
+ const newProp = { ...resolvedProp };
690
+ newProp[rangeKey] = [resolvedTarget];
691
+ return newProp;
692
+ }
693
+ }
694
+ }
695
+ }
696
+
697
+ return resolvedProp;
698
+ });
699
+
700
+ deepResolved[propertyKey] = resolvedProperties;
701
+ return deepResolved;
702
+ }
703
+
549
704
  /**
550
705
  * Helper function for the view. Extracts `http://www.w3.org/ns/shacl#property`
551
706
  * from the shape model
@@ -561,16 +716,65 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
561
716
  return item;
562
717
  }
563
718
 
719
+ // For objects with link-target, we need to find the actual target with properties
720
+ const linkTargetKey = this._getAmfKey(this.ns.aml.vocabularies.document.linkTarget);
721
+ let resolvedItem = item;
722
+
723
+ if (item[linkTargetKey] && this.amf) {
724
+ const linkTargetId = this._ensureArray(item[linkTargetKey])[0];
725
+ if (linkTargetId && linkTargetId['@id']) {
726
+ const targetId = linkTargetId['@id'];
727
+ // Try to find the target in declares
728
+ const declares = this._computeDeclares(this.amf);
729
+ let target = declares ? this._findById(declares, targetId) : undefined;
730
+
731
+ // If not found in declares, search in references
732
+ if (!target) {
733
+ const references = this._computeReferences(this.amf);
734
+ if (references && references.length) {
735
+ for (let i = 0; i < references.length && !target; i++) {
736
+ const refDeclares = this._computeDeclares(references[i]);
737
+ if (refDeclares) {
738
+ target = this._findById(refDeclares, targetId);
739
+ }
740
+ }
741
+ }
742
+ }
743
+
744
+ // If we found the target and it has properties, use it
745
+ // Don't use the target directly to avoid caching issues
746
+ const propertyKey = this._getAmfKey(this.ns.w3.shacl.property);
747
+ if (target && target[propertyKey]) {
748
+ // Use the target's properties but keep the original item structure
749
+ // This prevents issues with cached __apicResolved flags
750
+ resolvedItem = this._resolve(target);
751
+ // If resolve returned the same object or failed, fallback to standard resolve
752
+ if (!resolvedItem || !resolvedItem[propertyKey]) {
753
+ resolvedItem = this._resolve(item);
754
+ }
755
+ }
756
+ }
757
+ }
758
+
759
+ // Fallback to standard resolve if no link-target or target not found
760
+ if (resolvedItem === item) {
761
+ resolvedItem = this._resolve(item);
762
+ }
763
+
764
+ if (!resolvedItem) {
765
+ return undefined;
766
+ }
767
+
564
768
  const propertyKey = this._getAmfKey(this.ns.w3.shacl.property);
565
- const itemProperties = this._ensureArray(item[propertyKey]||[])
769
+ const itemProperties = this._ensureArray(resolvedItem[propertyKey]||[])
566
770
  const additionalPropertiesKey = this._getAmfKey(this.ns.w3.shacl.additionalPropertiesSchema);
567
771
 
568
772
  // If the item doesn't have additional properties, filter the read-only properties and return
569
- if (!item[additionalPropertiesKey]) {
773
+ if (!resolvedItem[additionalPropertiesKey]) {
570
774
  return this._filterReadOnlyProperties(itemProperties)
571
775
  }
572
776
 
573
- const additionalPropertiesSchema = this._ensureArray(item[additionalPropertiesKey])
777
+ const additionalPropertiesSchema = this._ensureArray(resolvedItem[additionalPropertiesKey])
574
778
 
575
779
  // If the item does have additional properties, ensure they are in an array
576
780
  const additionalProperties = this._ensureArray(additionalPropertiesSchema[0][propertyKey] || additionalPropertiesSchema[0])
@@ -688,7 +892,7 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
688
892
  * @return {TemplateResult[]|string} Templates for object properties
689
893
  */
690
894
  _objectTemplate() {
691
- const items = this._computeProperties(this._resolvedType);
895
+ const items = this._computedProperties;
692
896
  if (!items || !items.length) {
693
897
  return '';
694
898
  }
@@ -924,8 +1128,16 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
924
1128
  parts +=
925
1129
  'code-content-action-button-active, code-wrapper, example-code-wrapper, markdown-html';
926
1130
  const mediaTypes = (this.mediaTypes || []);
1131
+ // Use cached values if available, otherwise fallback to computed values
1132
+ const shouldRenderExamples = this._showExamples !== undefined
1133
+ ? this._showExamples
1134
+ : this._renderMainExample;
1135
+ const exampleMediaType = this._exampleMediaType !== undefined
1136
+ ? this._exampleMediaType
1137
+ : (this.mediaType || (this.isObject ? 'application/json' : undefined));
1138
+
927
1139
  return html`<style>${this.styles}</style>
928
- <section class="examples" ?hidden="${!this._renderMainExample}">
1140
+ ${shouldRenderExamples ? html`<section class="examples">
929
1141
  ${this.shouldRenderMediaSelector
930
1142
  ? html`<div class="media-type-selector">
931
1143
  <span>Media type:</span>
@@ -950,18 +1162,18 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
950
1162
  <api-resource-example-document
951
1163
  .amf="${this.amf}"
952
1164
  .payloadId="${this.selectedBodyId}"
953
- .examples="${this._resolvedType}"
954
- .mediaType="${this.mediaType}"
1165
+ .examples="${this._resolvedExampleType || this._resolvedType}"
1166
+ .mediaType="${exampleMediaType}"
955
1167
  .typeName="${this.parentTypeName}"
956
1168
  @has-examples-changed="${this._hasExamplesHandler}"
957
1169
  ?noauto="${!!this.isScalar}"
958
1170
  ?noactions="${this.noExamplesActions}"
959
- ?rawOnly="${!this.mediaType}"
1171
+ ?rawOnly="${!exampleMediaType}"
960
1172
  ?compatibility="${this.compatibility}"
961
1173
  exportParts="${parts}"
962
1174
  ?renderReadOnly="${this.renderReadOnly}"
963
1175
  ></api-resource-example-document>
964
- </section>
1176
+ </section>` : ''}
965
1177
 
966
1178
  ${this.isObject ? this._objectTemplate() : ''}
967
1179
  ${this.isArray ? this._arrayTemplate() : ''}