@gitbook/react-openapi 1.0.5 → 1.1.1

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 (41) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/OpenAPIDisclosure.d.ts +5 -9
  3. package/dist/OpenAPIDisclosure.jsx +24 -27
  4. package/dist/OpenAPIDisclosureGroup.d.ts +1 -1
  5. package/dist/OpenAPIDisclosureGroup.jsx +5 -5
  6. package/dist/OpenAPIPath.jsx +5 -1
  7. package/dist/OpenAPISchema.d.ts +3 -26
  8. package/dist/OpenAPISchema.jsx +81 -132
  9. package/dist/ScalarApiButton.d.ts +3 -2
  10. package/dist/ScalarApiButton.jsx +22 -18
  11. package/dist/dereference.d.ts +5 -0
  12. package/dist/dereference.js +68 -0
  13. package/dist/index.d.ts +3 -2
  14. package/dist/index.js +2 -1
  15. package/dist/models/OpenAPIModels.d.ts +9 -0
  16. package/dist/models/OpenAPIModels.jsx +62 -0
  17. package/dist/models/index.d.ts +2 -0
  18. package/dist/models/index.js +2 -0
  19. package/dist/models/resolveOpenAPIModels.d.ts +7 -0
  20. package/dist/models/resolveOpenAPIModels.js +73 -0
  21. package/dist/resolveOpenAPIOperation.d.ts +2 -2
  22. package/dist/resolveOpenAPIOperation.js +3 -34
  23. package/dist/tsconfig.build.tsbuildinfo +1 -1
  24. package/dist/types.d.ts +8 -0
  25. package/dist/utils.d.ts +1 -1
  26. package/dist/utils.js +43 -4
  27. package/package.json +3 -3
  28. package/src/OpenAPIDisclosure.tsx +34 -42
  29. package/src/OpenAPIDisclosureGroup.tsx +2 -2
  30. package/src/OpenAPIPath.tsx +7 -1
  31. package/src/OpenAPISchema.test.ts +26 -35
  32. package/src/OpenAPISchema.tsx +137 -226
  33. package/src/ScalarApiButton.tsx +26 -28
  34. package/src/dereference.ts +29 -0
  35. package/src/index.ts +3 -2
  36. package/src/models/OpenAPIModels.tsx +89 -0
  37. package/src/models/index.ts +2 -0
  38. package/src/models/resolveOpenAPIModels.ts +35 -0
  39. package/src/resolveOpenAPIOperation.ts +8 -36
  40. package/src/types.ts +10 -0
  41. package/src/utils.ts +53 -5
@@ -1,18 +1,16 @@
1
1
  import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2
- import clsx from 'clsx';
3
2
  import { useId } from 'react';
4
3
 
5
- import { InteractiveSection } from './InteractiveSection';
4
+ import clsx from 'clsx';
6
5
  import { Markdown } from './Markdown';
7
6
  import { OpenAPIDisclosure } from './OpenAPIDisclosure';
8
7
  import { OpenAPISchemaName } from './OpenAPISchemaName';
9
- import { stringifyOpenAPI } from './stringifyOpenAPI';
10
8
  import type { OpenAPIClientContext } from './types';
11
- import { checkIsReference, resolveDescription } from './utils';
9
+ import { checkIsReference, resolveDescription, resolveFirstExample } from './utils';
12
10
 
13
11
  type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
14
12
 
15
- export interface OpenAPISchemaPropertyEntry {
13
+ interface OpenAPISchemaPropertyEntry {
16
14
  propertyName?: string | undefined;
17
15
  required?: boolean | undefined;
18
16
  schema: OpenAPIV3.SchemaObject;
@@ -21,84 +19,60 @@ export interface OpenAPISchemaPropertyEntry {
21
19
  /**
22
20
  * Render a property of an OpenAPI schema.
23
21
  */
24
- export function OpenAPISchemaProperty(
25
- props: OpenAPISchemaPropertyEntry & {
26
- /** Set of objects already observed as parents */
27
- circularRefs?: CircularRefsIds;
28
- context: OpenAPIClientContext;
29
- className?: string;
30
- }
31
- ) {
22
+ function OpenAPISchemaProperty(props: {
23
+ property: OpenAPISchemaPropertyEntry;
24
+ context: OpenAPIClientContext;
25
+ circularRefs?: CircularRefsIds;
26
+ className?: string;
27
+ }) {
32
28
  const {
33
- schema,
29
+ property,
34
30
  circularRefs: parentCircularRefs = new Map<OpenAPIV3.SchemaObject, string>(),
35
31
  context,
36
32
  className,
37
33
  } = props;
38
34
 
39
- const id = useId();
40
-
41
- const parentCircularRef = parentCircularRefs.get(schema);
42
- const circularRefs = new Map(parentCircularRefs).set(schema, id);
43
-
44
- // Avoid recursing infinitely, and instead render a link to the parent schema
45
- const properties = parentCircularRef ? null : getSchemaProperties(schema);
46
- const alternatives = parentCircularRef
47
- ? null
48
- : getSchemaAlternatives(schema, new Set(circularRefs.keys()));
49
-
50
- if (alternatives?.[0]?.length) {
51
- return (
52
- <OpenAPISchemaAlternativesItem
53
- {...props}
54
- circularRefs={circularRefs}
55
- context={context}
56
- alternatives={alternatives}
57
- parentCircularRef={parentCircularRef}
58
- />
59
- );
60
- }
35
+ const { schema } = property;
61
36
 
62
- if ((properties && properties.length > 0) || schema.type === 'object') {
63
- return (
64
- <InteractiveSection id={id} className={clsx('openapi-schema', className)}>
65
- <OpenAPISchemaPresentation {...props} />
66
- {properties && properties.length > 0 ? (
67
- <OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
68
- <OpenAPISchemaProperties
69
- properties={properties}
70
- circularRefs={circularRefs}
71
- context={context}
72
- />
73
- </OpenAPIDisclosure>
74
- ) : null}
75
- {parentCircularRef ? (
76
- <OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
77
- ) : null}
78
- </InteractiveSection>
79
- );
80
- }
37
+ const id = useId();
81
38
 
82
39
  return (
83
- <InteractiveSection id={id} className={clsx('openapi-schema', className)}>
84
- <OpenAPISchemaPresentation {...props} />
85
- {(properties && properties.length > 0) ||
86
- (schema.enum && schema.enum.length > 0) ||
87
- parentCircularRef ? (
88
- <>
89
- {properties?.length ? (
90
- <OpenAPISchemaProperties
91
- properties={properties}
92
- circularRefs={circularRefs}
93
- context={context}
94
- />
95
- ) : null}
96
- {parentCircularRef ? (
97
- <OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
98
- ) : null}
99
- </>
100
- ) : null}
101
- </InteractiveSection>
40
+ <div id={id} className={clsx('openapi-schema', className)}>
41
+ <OpenAPISchemaPresentation property={property} />
42
+ {(() => {
43
+ const parentCircularRef = parentCircularRefs.get(schema);
44
+
45
+ // Avoid recursing infinitely, and instead render a link to the parent schema
46
+ if (parentCircularRef) {
47
+ return <OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />;
48
+ }
49
+
50
+ const circularRefs = parentCircularRefs.set(schema, id);
51
+ const properties = getSchemaProperties(schema);
52
+ const alternatives = getSchemaAlternatives(schema, new Set(circularRefs.keys()));
53
+ return (
54
+ <>
55
+ {alternatives?.map((schema, index) => (
56
+ <OpenAPISchemaAlternative
57
+ key={index}
58
+ schema={schema}
59
+ circularRefs={circularRefs}
60
+ context={context}
61
+ />
62
+ ))}
63
+ {properties?.length ? (
64
+ <OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
65
+ <OpenAPISchemaProperties
66
+ properties={properties}
67
+ circularRefs={circularRefs}
68
+ context={context}
69
+ />
70
+ </OpenAPIDisclosure>
71
+ ) : null}
72
+ </>
73
+ );
74
+ })()}
75
+ </div>
102
76
  );
103
77
  }
104
78
 
@@ -113,17 +87,13 @@ export function OpenAPISchemaProperties(props: {
113
87
  }) {
114
88
  const { id, properties, circularRefs, context } = props;
115
89
 
116
- if (!properties.length) {
117
- return null;
118
- }
119
-
120
90
  return (
121
91
  <div id={id} className="openapi-schema-properties">
122
92
  {properties.map((property, index) => (
123
93
  <OpenAPISchemaProperty
124
94
  key={index}
125
95
  circularRefs={circularRefs}
126
- {...property}
96
+ property={property}
127
97
  context={context}
128
98
  />
129
99
  ))}
@@ -140,15 +110,18 @@ export function OpenAPIRootSchema(props: {
140
110
  }) {
141
111
  const { schema, context } = props;
142
112
 
143
- // Avoid recursing infinitely, and instead render a link to the parent schema
144
113
  const properties = getSchemaProperties(schema);
145
114
 
146
- if (properties && properties.length > 0) {
115
+ if (properties?.length) {
147
116
  return <OpenAPISchemaProperties properties={properties} context={context} />;
148
117
  }
149
118
 
150
119
  return (
151
- <OpenAPISchemaProperty schema={schema} context={context} className="openapi-schema-root" />
120
+ <OpenAPISchemaProperty
121
+ className="openapi-schema-root"
122
+ property={{ schema }}
123
+ context={context}
124
+ />
152
125
  );
153
126
  }
154
127
 
@@ -159,32 +132,12 @@ export function OpenAPIRootSchema(props: {
159
132
  */
160
133
  function OpenAPISchemaAlternative(props: {
161
134
  schema: OpenAPIV3.SchemaObject;
162
- circularRefs?: CircularRefsIds;
135
+ circularRefs: CircularRefsIds;
163
136
  context: OpenAPIClientContext;
164
137
  }) {
165
138
  const { schema, circularRefs, context } = props;
166
- const id = useId();
167
- const subProperties = getSchemaProperties(schema);
168
139
  const description = resolveDescription(schema);
169
- const alternatives = getSchemaAlternatives(schema, new Set(circularRefs?.keys()));
170
-
171
- if (alternatives?.[0]?.length && !subProperties?.length) {
172
- return (
173
- <>
174
- {description ? (
175
- <Markdown source={description} className="openapi-schema-description" />
176
- ) : null}
177
- <OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
178
- <OpenAPISchemaAlternativesItem
179
- schema={schema}
180
- circularRefs={circularRefs}
181
- context={context}
182
- alternatives={alternatives}
183
- />
184
- </OpenAPIDisclosure>
185
- </>
186
- );
187
- }
140
+ const properties = getSchemaProperties(schema);
188
141
 
189
142
  return (
190
143
  <>
@@ -192,48 +145,24 @@ function OpenAPISchemaAlternative(props: {
192
145
  <Markdown source={description} className="openapi-schema-description" />
193
146
  ) : null}
194
147
  <OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
195
- <OpenAPISchemaProperties
196
- id={id}
197
- properties={subProperties ?? [{ schema }]}
198
- circularRefs={
199
- subProperties ? new Map(circularRefs).set(schema, id) : circularRefs
200
- }
201
- context={context}
202
- />
148
+ {properties?.length ? (
149
+ <OpenAPISchemaProperties
150
+ properties={properties}
151
+ circularRefs={circularRefs}
152
+ context={context}
153
+ />
154
+ ) : (
155
+ <OpenAPISchemaProperty
156
+ property={{ schema }}
157
+ circularRefs={circularRefs}
158
+ context={context}
159
+ />
160
+ )}
203
161
  </OpenAPIDisclosure>
204
162
  </>
205
163
  );
206
164
  }
207
165
 
208
- function OpenAPISchemaAlternativesItem(
209
- props: OpenAPISchemaPropertyEntry & {
210
- circularRefs?: CircularRefsIds;
211
- context: OpenAPIClientContext;
212
- alternatives: OpenAPISchemaAlternatives;
213
- parentCircularRef?: string;
214
- }
215
- ) {
216
- const id = useId();
217
- const { schema, circularRefs, context, alternatives, parentCircularRef } = props;
218
-
219
- return (
220
- <InteractiveSection id={id} className={clsx('openapi-schema')}>
221
- <OpenAPISchemaPresentation {...props} />
222
- {alternatives[0].map((alternative, index) => (
223
- <OpenAPISchemaAlternative
224
- key={`alternative-${index}`}
225
- schema={alternative}
226
- circularRefs={circularRefs}
227
- context={context}
228
- />
229
- ))}
230
- {parentCircularRef ? (
231
- <OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
232
- ) : null}
233
- </InteractiveSection>
234
- );
235
- }
236
-
237
166
  /**
238
167
  * Render a circular reference to a schema.
239
168
  */
@@ -251,7 +180,7 @@ function OpenAPISchemaCircularRef(props: { id: string; schema: OpenAPIV3.SchemaO
251
180
  /**
252
181
  * Render the enum value for a schema.
253
182
  */
254
- export function OpenAPISchemaEnum(props: { enumValues: any[] }) {
183
+ function OpenAPISchemaEnum(props: { enumValues: any[] }) {
255
184
  const { enumValues } = props;
256
185
 
257
186
  return (
@@ -269,22 +198,16 @@ export function OpenAPISchemaEnum(props: { enumValues: any[] }) {
269
198
  );
270
199
  }
271
200
 
272
- export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
273
- const { schema, propertyName, required } = props;
274
-
275
- const shouldDisplayExample = (schema: OpenAPIV3.SchemaObject): boolean => {
276
- return (
277
- (typeof schema.example === 'string' && !!schema.example) ||
278
- typeof schema.example === 'number' ||
279
- typeof schema.example === 'boolean' ||
280
- (Array.isArray(schema.example) && schema.example.length > 0) ||
281
- (typeof schema.example === 'object' &&
282
- schema.example !== null &&
283
- Object.keys(schema.example).length > 0)
284
- );
285
- };
201
+ /**
202
+ * Render the top row of a schema. e.g: name, type, and required status.
203
+ */
204
+ function OpenAPISchemaPresentation(props: { property: OpenAPISchemaPropertyEntry }) {
205
+ const {
206
+ property: { schema, propertyName, required },
207
+ } = props;
286
208
 
287
209
  const description = resolveDescription(schema);
210
+ const example = resolveFirstExample(schema);
288
211
 
289
212
  return (
290
213
  <div className="openapi-schema-presentation">
@@ -294,7 +217,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
294
217
  propertyName={propertyName}
295
218
  required={required}
296
219
  />
297
- {schema['x-deprecated-sunset'] ? (
220
+ {typeof schema['x-deprecated-sunset'] === 'string' ? (
298
221
  <div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown">
299
222
  Sunset date:{' '}
300
223
  <span className="openapi-deprecated-sunset-date">
@@ -305,9 +228,9 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
305
228
  {description ? (
306
229
  <Markdown source={description} className="openapi-schema-description" />
307
230
  ) : null}
308
- {shouldDisplayExample(schema) ? (
231
+ {typeof example === 'string' ? (
309
232
  <div className="openapi-schema-example">
310
- Example: <code>{formatExample(schema.example)}</code>
233
+ Example: <code>{example}</code>
311
234
  </div>
312
235
  ) : null}
313
236
  {schema.pattern ? (
@@ -327,7 +250,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
327
250
  */
328
251
  function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISchemaPropertyEntry[] {
329
252
  // check array AND schema.items as this is sometimes null despite what the type indicates
330
- if (schema.type === 'array' && !!schema.items) {
253
+ if (schema.type === 'array' && schema.items && !checkIsReference(schema.items)) {
331
254
  const items = schema.items;
332
255
  const itemProperties = getSchemaProperties(items);
333
256
  if (itemProperties) {
@@ -335,16 +258,17 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
335
258
  }
336
259
 
337
260
  // If the items are a primitive type, we don't need to display them
338
- if (['string', 'number', 'boolean', 'integer'].includes(items.type) && !items.enum) {
261
+ if (
262
+ (items.type === 'string' ||
263
+ items.type === 'number' ||
264
+ items.type === 'boolean' ||
265
+ items.type === 'integer') &&
266
+ !items.enum
267
+ ) {
339
268
  return null;
340
269
  }
341
270
 
342
- return [
343
- {
344
- propertyName: 'items',
345
- schema: items,
346
- },
347
- ];
271
+ return [{ propertyName: 'items', schema: items }];
348
272
  }
349
273
 
350
274
  if (schema.type === 'object' || schema.properties) {
@@ -352,6 +276,10 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
352
276
 
353
277
  if (schema.properties) {
354
278
  Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
279
+ if (checkIsReference(propertySchema)) {
280
+ return;
281
+ }
282
+
355
283
  result.push({
356
284
  propertyName,
357
285
  required: Array.isArray(schema.required)
@@ -362,12 +290,10 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
362
290
  });
363
291
  }
364
292
 
365
- if (schema.additionalProperties) {
366
- const additionalProperties = schema.additionalProperties;
367
-
293
+ if (schema.additionalProperties && !checkIsReference(schema.additionalProperties)) {
368
294
  result.push({
369
295
  propertyName: 'Other properties',
370
- schema: additionalProperties === true ? {} : additionalProperties,
296
+ schema: schema.additionalProperties === true ? {} : schema.additionalProperties,
371
297
  });
372
298
  }
373
299
 
@@ -377,10 +303,7 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
377
303
  return null;
378
304
  }
379
305
 
380
- type OpenAPISchemaAlternatives = [
381
- OpenAPIV3.SchemaObject[],
382
- OpenAPIV3.DiscriminatorObject | undefined,
383
- ];
306
+ type AlternativeType = 'oneOf' | 'allOf' | 'anyOf';
384
307
 
385
308
  /**
386
309
  * Get the alternatives to display for a schema.
@@ -388,56 +311,57 @@ type OpenAPISchemaAlternatives = [
388
311
  export function getSchemaAlternatives(
389
312
  schema: OpenAPIV3.SchemaObject,
390
313
  ancestors: Set<OpenAPIV3.SchemaObject> = new Set()
391
- ): null | OpenAPISchemaAlternatives {
392
- const downAncestors = new Set(ancestors).add(schema);
314
+ ): OpenAPIV3.SchemaObject[] | null {
315
+ const alternatives:
316
+ | [AlternativeType, (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[]]
317
+ | null = (() => {
318
+ if (schema.anyOf) {
319
+ return ['anyOf', schema.anyOf];
320
+ }
393
321
 
394
- if (schema.anyOf) {
395
- return [flattenAlternatives('anyOf', schema.anyOf, downAncestors), schema.discriminator];
396
- }
322
+ if (schema.oneOf) {
323
+ return ['oneOf', schema.oneOf];
324
+ }
397
325
 
398
- if (schema.oneOf) {
399
- return [flattenAlternatives('oneOf', schema.oneOf, downAncestors), schema.discriminator];
400
- }
326
+ if (schema.allOf) {
327
+ return ['allOf', schema.allOf];
328
+ }
329
+
330
+ return null;
331
+ })();
401
332
 
402
- if (schema.allOf) {
403
- return [flattenAlternatives('allOf', schema.allOf, downAncestors), schema.discriminator];
333
+ if (!alternatives) {
334
+ return null;
404
335
  }
405
336
 
406
- return null;
337
+ const [type, schemas] = alternatives;
338
+ return flattenAlternatives(type, schemas, new Set(ancestors).add(schema));
407
339
  }
408
340
 
409
341
  function flattenAlternatives(
410
- alternativeType: 'oneOf' | 'allOf' | 'anyOf',
411
- alternatives: OpenAPIV3.SchemaObject[],
342
+ alternativeType: AlternativeType,
343
+ schemasOrRefs: (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[],
412
344
  ancestors: Set<OpenAPIV3.SchemaObject>
413
345
  ): OpenAPIV3.SchemaObject[] {
414
- return alternatives.reduce((acc, alternative) => {
415
- if (!!alternative[alternativeType] && !ancestors.has(alternative)) {
416
- acc.push(...(getSchemaAlternatives(alternative, ancestors)?.[0] || []));
417
- } else {
418
- acc.push(alternative);
346
+ return schemasOrRefs.reduce<OpenAPIV3.SchemaObject[]>((acc, schemaOrRef) => {
347
+ if (checkIsReference(schemaOrRef)) {
348
+ return acc;
419
349
  }
420
350
 
421
- return acc;
422
- }, [] as OpenAPIV3.SchemaObject[]);
423
- }
424
-
425
- export function getSchemaTitle(
426
- schema: OpenAPIV3.SchemaObject,
427
-
428
- /** If the title is inferred in a oneOf with discriminator, we can use it to optimize the title */
429
- discriminator?: OpenAPIV3.DiscriminatorObject
430
- ): string {
431
- // Try using the discriminator
432
- if (discriminator?.propertyName && schema.properties) {
433
- const discriminatorProperty = schema.properties[discriminator.propertyName];
434
- if (discriminatorProperty && !checkIsReference(discriminatorProperty)) {
435
- if (discriminatorProperty.enum) {
436
- return discriminatorProperty.enum.map((value) => value.toString()).join(' | ');
351
+ if (schemaOrRef[alternativeType] && !ancestors.has(schemaOrRef)) {
352
+ const schemas = getSchemaAlternatives(schemaOrRef, ancestors);
353
+ if (schemas) {
354
+ acc.push(...schemas);
437
355
  }
356
+ return acc;
438
357
  }
439
- }
440
358
 
359
+ acc.push(schemaOrRef);
360
+ return acc;
361
+ }, []);
362
+ }
363
+
364
+ function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
441
365
  // Otherwise try to infer a nice title
442
366
  let type = 'any';
443
367
 
@@ -469,7 +393,7 @@ export function getSchemaTitle(
469
393
  return type;
470
394
  }
471
395
 
472
- function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string | undefined {
396
+ function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string {
473
397
  if (schema.type === 'array' && !!schema.items) {
474
398
  if (schema.items.oneOf) {
475
399
  return 'available items';
@@ -477,24 +401,11 @@ function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string | undefined
477
401
 
478
402
  // Fallback to "child attributes" for enums and objects
479
403
  if (schema.items.enum || schema.items.type === 'object') {
480
- return;
404
+ return 'child attributes';
481
405
  }
482
406
 
483
407
  return schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
484
408
  }
485
409
 
486
- return schema.title;
487
- }
488
-
489
- function formatExample(example: any): string {
490
- if (typeof example === 'string') {
491
- return example
492
- .replace(/\n/g, ' ') // Replace newlines with spaces
493
- .replace(/\s+/g, ' ') // Collapse multiple spaces/newlines into a single space
494
- .replace(/([\{\}:,])\s+/g, '$1 ') // Ensure a space after {, }, :, and ,
495
- .replace(/\s+([\{\}:,])/g, ' $1') // Ensure a space before {, }, :, and ,
496
- .trim();
497
- }
498
-
499
- return stringifyOpenAPI(example);
410
+ return schema.title || 'child attributes';
500
411
  }
@@ -1,24 +1,21 @@
1
1
  'use client';
2
2
 
3
3
  import { ApiClientModalProvider, useApiClientModal } from '@scalar/api-client-react';
4
- import { useEffect, useImperativeHandle, useRef, useState } from 'react';
4
+ import { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
5
5
  import { createPortal } from 'react-dom';
6
6
 
7
- import { useEventCallback } from 'usehooks-ts';
7
+ import type { OpenAPIV3_1 } from '@gitbook/openapi-parser';
8
8
  import { useOpenAPIOperationContext } from './OpenAPIOperationContext';
9
9
 
10
10
  /**
11
11
  * Button which launches the Scalar API Client
12
12
  */
13
- export function ScalarApiButton({
14
- method,
15
- path,
16
- specUrl,
17
- }: {
18
- method: string;
13
+ export function ScalarApiButton(props: {
14
+ method: OpenAPIV3_1.HttpMethods;
19
15
  path: string;
20
16
  specUrl: string;
21
17
  }) {
18
+ const { method, path, specUrl } = props;
22
19
  const [isOpen, setIsOpen] = useState(false);
23
20
  const controllerRef = useRef<ScalarModalControllerRef>(null);
24
21
  return (
@@ -55,21 +52,18 @@ export function ScalarApiButton({
55
52
  }
56
53
 
57
54
  function ScalarModal(props: {
58
- method: string;
55
+ method: OpenAPIV3_1.HttpMethods;
59
56
  path: string;
60
57
  specUrl: string;
61
58
  controllerRef: React.Ref<ScalarModalControllerRef>;
62
59
  }) {
60
+ const { method, path, specUrl, controllerRef } = props;
63
61
  return (
64
62
  <ApiClientModalProvider
65
- configuration={{ spec: { url: props.specUrl } }}
66
- initialRequest={{ path: props.path, method: props.method }}
63
+ configuration={{ spec: { url: specUrl } }}
64
+ initialRequest={{ method, path }}
67
65
  >
68
- <ScalarModalController
69
- method={props.method}
70
- path={props.path}
71
- controllerRef={props.controllerRef}
72
- />
66
+ <ScalarModalController method={method} path={path} controllerRef={controllerRef} />
73
67
  </ApiClientModalProvider>
74
68
  );
75
69
  }
@@ -79,28 +73,32 @@ type ScalarModalControllerRef = {
79
73
  };
80
74
 
81
75
  function ScalarModalController(props: {
82
- method: string;
76
+ method: OpenAPIV3_1.HttpMethods;
83
77
  path: string;
84
78
  controllerRef: React.Ref<ScalarModalControllerRef>;
85
79
  }) {
80
+ const { method, path, controllerRef } = props;
86
81
  const client = useApiClientModal();
87
- const openClient = client?.open;
82
+ const openScalarClient = client?.open;
83
+ const { onOpenClient: trackClientOpening } = useOpenAPIOperationContext();
84
+ const openClient = useMemo(() => {
85
+ if (openScalarClient) {
86
+ return () => {
87
+ openScalarClient({ method, path, _source: 'gitbook' });
88
+ trackClientOpening({ method, path });
89
+ };
90
+ }
91
+ return null;
92
+ }, [openScalarClient, method, path, trackClientOpening]);
88
93
  useImperativeHandle(
89
- props.controllerRef,
94
+ controllerRef,
90
95
  () => ({ openClient: openClient ? () => openClient() : undefined }),
91
96
  [openClient]
92
97
  );
93
98
 
94
- // Open the client when the component is mounted.
95
- const { onOpenClient } = useOpenAPIOperationContext();
96
- const trackOpening = useEventCallback(() => {
97
- onOpenClient({ method: props.method, path: props.path });
98
- });
99
+ // Open at mount
99
100
  useEffect(() => {
100
- if (openClient) {
101
- openClient();
102
- trackOpening();
103
- }
101
+ openClient?.();
104
102
  }, [openClient]);
105
103
  return null;
106
104
  }
@@ -0,0 +1,29 @@
1
+ import { type Filesystem, type OpenAPIV3xDocument, dereference } from '@gitbook/openapi-parser';
2
+
3
+ const dereferenceCache = new WeakMap<Filesystem, Promise<OpenAPIV3xDocument>>();
4
+
5
+ /**
6
+ * Memoized version of `dereferenceSchema`.
7
+ */
8
+ export function dereferenceFilesystem(filesystem: Filesystem): Promise<OpenAPIV3xDocument> {
9
+ if (dereferenceCache.has(filesystem)) {
10
+ return dereferenceCache.get(filesystem) as Promise<OpenAPIV3xDocument>;
11
+ }
12
+
13
+ const promise = baseDereferenceFilesystem(filesystem);
14
+ dereferenceCache.set(filesystem, promise);
15
+ return promise;
16
+ }
17
+
18
+ /**
19
+ * Dereference an OpenAPI schema.
20
+ */
21
+ async function baseDereferenceFilesystem(filesystem: Filesystem): Promise<OpenAPIV3xDocument> {
22
+ const result = await dereference(filesystem);
23
+
24
+ if (!result.schema) {
25
+ throw new Error('Failed to dereference OpenAPI document');
26
+ }
27
+
28
+ return result.schema as OpenAPIV3xDocument;
29
+ }