@scalar/api-client 3.16.1 → 3.17.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +18 -0
  3. package/dist/style.css +27 -38
  4. package/dist/v2/blocks/operation-block/OperationBlock.vue.d.ts +2 -0
  5. package/dist/v2/blocks/operation-block/OperationBlock.vue.d.ts.map +1 -1
  6. package/dist/v2/blocks/operation-block/OperationBlock.vue.js.map +1 -1
  7. package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js +36 -10
  8. package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js.map +1 -1
  9. package/dist/v2/blocks/operation-block/components/Header.vue.d.ts +4 -0
  10. package/dist/v2/blocks/operation-block/components/Header.vue.d.ts.map +1 -1
  11. package/dist/v2/blocks/operation-block/components/Header.vue.js +1 -1
  12. package/dist/v2/blocks/operation-block/components/Header.vue.js.map +1 -1
  13. package/dist/v2/blocks/operation-block/components/Header.vue.script.js +14 -9
  14. package/dist/v2/blocks/operation-block/components/Header.vue.script.js.map +1 -1
  15. package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
  16. package/dist/v2/blocks/request-block/components/RequestBody.vue.d.ts.map +1 -1
  17. package/dist/v2/blocks/request-block/components/RequestBody.vue.js +1 -1
  18. package/dist/v2/blocks/request-block/components/RequestBody.vue.js.map +1 -1
  19. package/dist/v2/blocks/request-block/components/RequestBody.vue.script.js +15 -1
  20. package/dist/v2/blocks/request-block/components/RequestBody.vue.script.js.map +1 -1
  21. package/dist/v2/blocks/request-block/components/RequestTable.vue.d.ts.map +1 -1
  22. package/dist/v2/blocks/request-block/components/RequestTable.vue.js +1 -1
  23. package/dist/v2/blocks/request-block/components/RequestTable.vue.js.map +1 -1
  24. package/dist/v2/blocks/request-block/components/RequestTable.vue.script.js +13 -1
  25. package/dist/v2/blocks/request-block/components/RequestTable.vue.script.js.map +1 -1
  26. package/dist/v2/blocks/request-block/components/RequestTableRow.vue.d.ts.map +1 -1
  27. package/dist/v2/blocks/request-block/components/RequestTableRow.vue.js.map +1 -1
  28. package/dist/v2/blocks/request-block/components/RequestTableRow.vue.script.js +5 -1
  29. package/dist/v2/blocks/request-block/components/RequestTableRow.vue.script.js.map +1 -1
  30. package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.d.ts +4 -0
  31. package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.d.ts.map +1 -1
  32. package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.js +1 -1
  33. package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.js.map +1 -1
  34. package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.script.js +61 -24
  35. package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.script.js.map +1 -1
  36. package/dist/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue.script.js.map +1 -1
  37. package/dist/v2/components/code-input/CodeInputLite.vue.d.ts.map +1 -1
  38. package/dist/v2/components/code-input/CodeInputLite.vue.js +1 -1
  39. package/dist/v2/components/code-input/CodeInputLite.vue.js.map +1 -1
  40. package/dist/v2/components/code-input/CodeInputLite.vue.script.js +7 -0
  41. package/dist/v2/components/code-input/CodeInputLite.vue.script.js.map +1 -1
  42. package/dist/v2/constants.js +1 -1
  43. package/dist/v2/features/modal/Modal.vue.d.ts +2 -0
  44. package/dist/v2/features/modal/Modal.vue.d.ts.map +1 -1
  45. package/dist/v2/features/modal/Modal.vue.js.map +1 -1
  46. package/dist/v2/features/modal/Modal.vue.script.js +3 -0
  47. package/dist/v2/features/modal/Modal.vue.script.js.map +1 -1
  48. package/dist/v2/features/modal/helpers/create-api-client-modal.d.ts.map +1 -1
  49. package/dist/v2/features/modal/helpers/create-api-client-modal.js +6 -6
  50. package/dist/v2/features/modal/helpers/create-api-client-modal.js.map +1 -1
  51. package/dist/v2/features/modal/helpers/resolve-route-parameters.d.ts +12 -3
  52. package/dist/v2/features/modal/helpers/resolve-route-parameters.d.ts.map +1 -1
  53. package/dist/v2/features/modal/helpers/resolve-route-parameters.js +13 -9
  54. package/dist/v2/features/modal/helpers/resolve-route-parameters.js.map +1 -1
  55. package/dist/v2/features/modal/hooks/use-modal-sidebar.d.ts +3 -1
  56. package/dist/v2/features/modal/hooks/use-modal-sidebar.d.ts.map +1 -1
  57. package/dist/v2/features/modal/hooks/use-modal-sidebar.js +28 -7
  58. package/dist/v2/features/modal/hooks/use-modal-sidebar.js.map +1 -1
  59. package/dist/v2/features/modal/modal-events.d.ts.map +1 -1
  60. package/dist/v2/features/modal/modal-events.js +5 -2
  61. package/dist/v2/features/modal/modal-events.js.map +1 -1
  62. package/dist/v2/features/operation/Operation.vue.d.ts +2 -0
  63. package/dist/v2/features/operation/Operation.vue.d.ts.map +1 -1
  64. package/dist/v2/features/operation/Operation.vue.js.map +1 -1
  65. package/dist/v2/features/operation/Operation.vue.script.js +8 -1
  66. package/dist/v2/features/operation/Operation.vue.script.js.map +1 -1
  67. package/dist/v2/features/search/helpers/create-search-index.js +2 -2
  68. package/dist/v2/features/search/helpers/create-search-index.js.map +1 -1
  69. package/dist/v2/helpers/generate-location-id.d.ts +3 -1
  70. package/dist/v2/helpers/generate-location-id.d.ts.map +1 -1
  71. package/dist/v2/helpers/generate-location-id.js +2 -1
  72. package/dist/v2/helpers/generate-location-id.js.map +1 -1
  73. package/dist/vue-styles.css +26 -26
  74. package/package.json +18 -18
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # @scalar/api-client
2
2
 
3
+ ## 3.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - feat: test OpenAPI webhooks from the API reference and API client
8
+
9
+ ### Patch Changes
10
+
11
+ - [#9983](https://github.com/scalar/scalar/pull/9983): Bump the `zod` catalog to `^4.4.3` so the standalone bundle ships a single `zod` instead of two (`4.3.5` from `@scalar/types` plus `4.4.3` from the `ai` / `@ai-sdk` peer). This makes `standalone.js` ~68KB raw / ~18KB gzip smaller.
12
+ - [#10024](https://github.com/scalar/scalar/pull/10024): Fix "Test Request" discarding an edited request body when the operation it opens is already the one on screen. Reopening the entry the modal already shows does not route anywhere, so the request body kept its edited value while the composition selection was replaced with whatever the reference page had selected. The request body read that as a manual `oneOf`/`anyOf` branch switch and regenerated itself from the schema. An open modal now keeps the selection it is already showing, while opening a different entry still re-establishes it.
13
+
14
+ ## 3.16.3
15
+
16
+ ### Patch Changes
17
+
18
+ - [#9941](https://github.com/scalar/scalar/pull/9941): Republish every package through npm trusted publishing. No functional changes.
19
+
20
+ ## 3.16.2
21
+
22
+ ### Patch Changes
23
+
24
+ - [#9930](https://github.com/scalar/scalar/pull/9930): Reset edited structured request bodies when a different `oneOf` or `anyOf` schema is selected, including compositions without discriminators.
25
+ - [#9872](https://github.com/scalar/scalar/pull/9872): Bump shared runtime dependencies: `js-base64` (`^3.7.8` -> `^3.9.2`) and `type-fest` (`^5.3.1` -> `^5.8.0`).
26
+ - [#9896](https://github.com/scalar/scalar/pull/9896): Fix parameter name blanking in the Try It panel on first open.
27
+
28
+ Three related issues caused a parameter name (e.g. `x-scenario-id`) to appear
29
+ blank the first time the Try It panel was opened for a GET endpoint:
30
+ 1. **`RequestTable` used `key: index`** — Vue reused the same `RequestTableRow`
31
+ component instance for the placeholder row `{ name: '' }` that `displayData`
32
+ appends, causing the component to receive an empty `data.name` prop and blank
33
+ its local `name` ref. Fixed by using a stable identity key derived from the
34
+ parameter name and value path.
35
+
36
+ 2. **`RequestTableRow` watch and blur emitted empty names** — the `watch:name`
37
+ handler unconditionally synced the local ref to the incoming prop (including
38
+ `''`), and `handleKeyBlur` forwarded a blank name emitted by `CodeInputLite`
39
+ before it had rendered its initial value. Both now guard against overwriting
40
+ a valid name with an empty string.
41
+
42
+ 3. **`upsertOperationParameter` mutated `param.name` unconditionally** — a
43
+ value-only update carrying `payload.name = ''` permanently blanked the
44
+ reactive parameter name in the store. The mutator now skips the name
45
+ assignment when the payload name is empty and the parameter already has one.
46
+
47
+ - [#9867](https://github.com/scalar/scalar/pull/9867): Fix header parameters showing as empty rows in the API client when an earlier operation has a request body
48
+
3
49
  ## 3.16.1
4
50
 
5
51
  ## 3.16.0
package/README.md CHANGED
@@ -175,9 +175,27 @@ The `createApiClientModal` call returns:
175
175
  method: HttpMethod
176
176
  example?: string
177
177
  documentSlug?: string // For multiple documents
178
+ isWebhook?: boolean // Resolve path as an OpenAPI webhook name
178
179
  }
179
180
  ```
180
181
 
182
+ ### OpenAPI webhooks
183
+
184
+ Set `isWebhook` to open a webhook from an OpenAPI 3.1 document. Pass the
185
+ webhook map key as `path`:
186
+
187
+ ```ts
188
+ modal.open({
189
+ path: 'delivery.created',
190
+ method: 'post',
191
+ isWebhook: true,
192
+ })
193
+ ```
194
+
195
+ The modal prompts for the destination URL. Destination edits apply only to
196
+ the request and do not change the OpenAPI document. In the API reference, the
197
+ **Test Request** button opens webhook entries this way automatically.
198
+
181
199
 
182
200
  ## Plugins
183
201
 
package/dist/style.css CHANGED
@@ -491,6 +491,7 @@
491
491
  }
492
492
 
493
493
  :where(.scalar-app) input:-webkit-autofill {
494
+ -webkit-text-fill-color: var(--scalar-color-1);
494
495
  -webkit-background-clip: text !important;
495
496
  background-clip: text !important;
496
497
  }
@@ -3034,10 +3035,6 @@
3034
3035
  min-width: 100%;
3035
3036
  }
3036
3037
 
3037
- .scalar-app .min-w-min {
3038
- min-width: min-content;
3039
- }
3040
-
3041
3038
  .scalar-app .flex-1 {
3042
3039
  flex: 1;
3043
3040
  }
@@ -4696,10 +4693,6 @@
4696
4693
  min-width: 20px;
4697
4694
  }
4698
4695
 
4699
- :is(.scalar-app .\*\:flex-1 > *) {
4700
- flex: 1;
4701
- }
4702
-
4703
4696
  :is(.scalar-app .\*\:cursor-pointer > *) {
4704
4697
  cursor: pointer;
4705
4698
  }
@@ -4712,10 +4705,6 @@
4712
4705
  justify-content: center;
4713
4706
  }
4714
4707
 
4715
- :is(.scalar-app .\*\:gap-1 > *) {
4716
- gap: 4px;
4717
- }
4718
-
4719
4708
  :is(.scalar-app .\*\:rounded > *) {
4720
4709
  border-radius: var(--scalar-radius-md);
4721
4710
  }
@@ -6314,13 +6303,13 @@ to {
6314
6303
  color: var(--scalar-color-1);
6315
6304
  }
6316
6305
 
6317
- .code-input-lite[data-v-f03d0c9d] {
6306
+ .code-input-lite[data-v-4dfd05a3] {
6318
6307
  /* Flex so the editor centres vertically in whatever height the cell gives us.
6319
6308
  Font properties inherit so we adopt the surrounding typography. */
6320
6309
  display: flex;
6321
6310
  align-items: center;
6322
6311
  }
6323
- .code-input-lite__editor[data-v-f03d0c9d] {
6312
+ .code-input-lite__editor[data-v-4dfd05a3] {
6324
6313
  /* Everything font-related inherits from the surrounding context. */
6325
6314
  font: inherit;
6326
6315
  letter-spacing: inherit;
@@ -6342,7 +6331,7 @@ to {
6342
6331
  /* Positioning context for the absolutely positioned placeholder. */
6343
6332
  position: relative;
6344
6333
  }
6345
- .code-input-lite__editor[data-v-f03d0c9d]::-webkit-scrollbar {
6334
+ .code-input-lite__editor[data-v-4dfd05a3]::-webkit-scrollbar {
6346
6335
  display: none;
6347
6336
  }
6348
6337
 
@@ -6350,7 +6339,7 @@ to {
6350
6339
  contenteditable; otherwise the caret would land after it on focus.
6351
6340
  `pointer-events: none` lets clicks fall through to focus the editor.
6352
6341
  Hidden on focus so it never renders on top of the text caret. */
6353
- .code-input-lite--empty .code-input-lite__editor[data-v-f03d0c9d]:not(:focus)::before {
6342
+ .code-input-lite--empty .code-input-lite__editor[data-v-4dfd05a3]:not(:focus)::before {
6354
6343
  content: attr(data-placeholder);
6355
6344
  color: var(--scalar-color-3);
6356
6345
  pointer-events: none;
@@ -6361,7 +6350,7 @@ to {
6361
6350
  display: flex;
6362
6351
  align-items: center;
6363
6352
  }
6364
- .code-input-lite--error .code-input-lite__editor[data-v-f03d0c9d] {
6353
+ .code-input-lite--error .code-input-lite__editor[data-v-4dfd05a3] {
6365
6354
  outline: 1px solid var(--scalar-color-red);
6366
6355
  }
6367
6356
 
@@ -6405,20 +6394,20 @@ to {
6405
6394
  background: color-mix(in srgb, var(--scalar-background-3), transparent 40%);
6406
6395
  }
6407
6396
 
6408
- [data-v-042c21ca] .code-input-lite__editor {
6397
+ [data-v-13500378] .code-input-lite__editor {
6409
6398
  background-color: transparent;
6410
6399
  font-family: var(--scalar-font);
6411
6400
  font-size: var(--scalar-small);
6412
6401
  padding: 5px 8px;
6413
6402
  }
6414
- [data-v-042c21ca] .scalar-pill:not(:last-of-type) {
6403
+ [data-v-13500378] .scalar-pill:not(:last-of-type) {
6415
6404
  margin-right: 0.15em;
6416
6405
  }
6417
- [data-v-042c21ca] .scalar-pill:not(:first-of-type) {
6406
+ [data-v-13500378] .scalar-pill:not(:first-of-type) {
6418
6407
  margin-left: 0.5px;
6419
6408
  }
6420
6409
 
6421
- [data-v-dcc3af8c] .cm-content {
6410
+ [data-v-c8ab7ba2] .cm-content {
6422
6411
  font-size: var(--scalar-small);
6423
6412
  }
6424
6413
 
@@ -6614,21 +6603,21 @@ to {
6614
6603
  background: var(--scalar-background-2);
6615
6604
  }
6616
6605
 
6617
- [data-v-d37cbfcc] .cm-editor {
6606
+ [data-v-c414dc68] .cm-editor {
6618
6607
  height: 100%;
6619
6608
  outline: none;
6620
6609
  width: 100%;
6621
6610
  }
6622
- [data-v-d37cbfcc] .cm-line {
6611
+ [data-v-c414dc68] .cm-line {
6623
6612
  padding: 0;
6624
6613
  }
6625
- [data-v-d37cbfcc] .cm-content {
6614
+ [data-v-c414dc68] .cm-content {
6626
6615
  padding: 0;
6627
6616
  display: flex;
6628
6617
  align-items: center;
6629
6618
  font-size: var(--scalar-small);
6630
6619
  }
6631
- .scroll-timeline-x[data-v-d37cbfcc] {
6620
+ .scroll-timeline-x[data-v-c414dc68] {
6632
6621
  -ms-overflow-style: none;
6633
6622
  /* Fade the URL at the viewport edges. Mask lives on the scrollport (this
6634
6623
  element) rather than on `.cm-scroller`, whose width follows the URL's
@@ -6641,31 +6630,31 @@ to {
6641
6630
  transparent 100%
6642
6631
  );
6643
6632
  }
6644
- .scroll-timeline-x-hidden[data-v-d37cbfcc] {
6633
+ .scroll-timeline-x-hidden[data-v-c414dc68] {
6645
6634
  overflow-x: auto;
6646
6635
  }
6647
- .scroll-timeline-x-hidden[data-v-d37cbfcc] .cm-scroller {
6636
+ .scroll-timeline-x-hidden[data-v-c414dc68] .cm-scroller {
6648
6637
  scrollbar-width: none;
6649
6638
  -ms-overflow-style: none;
6650
6639
  padding-right: 20px;
6651
6640
  overflow: auto;
6652
6641
  }
6653
- .scroll-timeline-x-hidden[data-v-d37cbfcc]::-webkit-scrollbar {
6642
+ .scroll-timeline-x-hidden[data-v-c414dc68]::-webkit-scrollbar {
6654
6643
  width: 0;
6655
6644
  height: 0;
6656
6645
  display: none;
6657
6646
  }
6658
- .scroll-timeline-x-hidden[data-v-d37cbfcc] .cm-scroller::-webkit-scrollbar {
6647
+ .scroll-timeline-x-hidden[data-v-c414dc68] .cm-scroller::-webkit-scrollbar {
6659
6648
  width: 0;
6660
6649
  height: 0;
6661
6650
  display: none;
6662
6651
  }
6663
- .scroll-timeline-x-address[data-v-d37cbfcc] {
6652
+ .scroll-timeline-x-address[data-v-c414dc68] {
6664
6653
  line-height: 27px;
6665
6654
  scrollbar-width: none; /* Firefox */
6666
6655
  }
6667
6656
  /* make clickable are to left of send button */
6668
- .scroll-timeline-x-address[data-v-d37cbfcc]:after {
6657
+ .scroll-timeline-x-address[data-v-c414dc68]:after {
6669
6658
  content: '';
6670
6659
  position: absolute;
6671
6660
  height: 100%;
@@ -6673,12 +6662,12 @@ to {
6673
6662
  right: 0;
6674
6663
  cursor: text;
6675
6664
  }
6676
- .scroll-timeline-x-address[data-v-d37cbfcc]:empty:before {
6665
+ .scroll-timeline-x-address[data-v-c414dc68]:empty:before {
6677
6666
  content: 'Enter URL or cURL request';
6678
6667
  color: var(--scalar-color-3);
6679
6668
  pointer-events: none;
6680
6669
  }
6681
- .address-bar-bg-states[data-v-d37cbfcc] {
6670
+ .address-bar-bg-states[data-v-c414dc68] {
6682
6671
  --scalar-address-bar-bg: color-mix(
6683
6672
  in srgb,
6684
6673
  var(--scalar-background-1),
@@ -6686,22 +6675,22 @@ to {
6686
6675
  );
6687
6676
  background: var(--scalar-address-bar-bg);
6688
6677
  }
6689
- .address-bar-bg-states[data-v-d37cbfcc]:has(.cm-focused) {
6678
+ .address-bar-bg-states[data-v-c414dc68]:has(.cm-focused) {
6690
6679
  --scalar-address-bar-bg: var(--scalar-background-1);
6691
6680
  border-color: var(--scalar-border-color);
6692
6681
  outline-width: 1px;
6693
6682
  outline-style: solid;
6694
6683
  }
6695
- .address-bar-bg-states:has(.cm-focused) .fade-left[data-v-d37cbfcc],
6696
- .address-bar-bg-states:has(.cm-focused) .fade-right[data-v-d37cbfcc] {
6684
+ .address-bar-bg-states:has(.cm-focused) .fade-left[data-v-c414dc68],
6685
+ .address-bar-bg-states:has(.cm-focused) .fade-right[data-v-c414dc68] {
6697
6686
  --scalar-address-bar-bg: var(--scalar-background-1);
6698
6687
  }
6699
6688
 
6700
- .app-exit-button[data-v-42c563b3] {
6689
+ .app-exit-button[data-v-3769522e] {
6701
6690
  color: white;
6702
6691
  background: rgba(0, 0, 0, 0.1);
6703
6692
  }
6704
- .app-exit-button[data-v-42c563b3]:hover {
6693
+ .app-exit-button[data-v-3769522e]:hover {
6705
6694
  background: rgba(255, 255, 255, 0.1);
6706
6695
  }
6707
6696
  /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@@ -44,6 +44,8 @@ export type OperationBlockProps = {
44
44
  path: string;
45
45
  /** Current request method */
46
46
  method: HttpMethodType;
47
+ /** Whether `path` identifies an OpenAPI webhook. */
48
+ isWebhook?: boolean;
47
49
  /** HTTP clients */
48
50
  httpClients: AvailableClients;
49
51
  /** The history for the operation */
@@ -1 +1 @@
1
- {"version":3,"file":"OperationBlock.vue.d.ts","sourceRoot":"","sources":["../../../../src/v2/blocks/operation-block/OperationBlock.vue"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OperationBlock.vue.d.ts","sourceRoot":"","sources":["../../../../src/v2/blocks/operation-block/OperationBlock.vue"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"OperationBlock.vue.js","names":[],"sources":["../../../../src/v2/blocks/operation-block/OperationBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * OperationBlock\n *\n * Orchestrates the operation view by wiring together the Header, OperationBlock,\n * and ResponseBlock. Forwards user interactions to the workspace event bus and\n * passes through configuration such as auth, servers, plugins, and environment.\n * This component keeps the Operation page lean by centralizing event emission\n * and prop wiring between the blocks.\n *\n * Notable behavior:\n * - Uses operation['x-scalar-method'] and operation['x-scalar-path'] to provide\n * draft overrides for the UI when present.\n */\nexport default {\n name: 'OperationBlock',\n}\n\nexport type OperationBlockProps = {\n /** Event bus */\n eventBus: WorkspaceEventBus\n /** Application version */\n appVersion: string\n /** Openapi document */\n document: OpenApiDocument\n /** Openapi document slug */\n documentSlug: string\n /** Workspace cookies */\n workspaceCookies: XScalarCookie[]\n /** Document cookies */\n documentCookies: XScalarCookie[]\n /** Current request path */\n path: string\n /** Current request method */\n method: HttpMethodType\n /** HTTP clients */\n httpClients: AvailableClients\n /** The history for the operation */\n history?: HistoryEntry[]\n /** Client layout */\n layout: ClientLayout\n /** Currently selected server */\n server: ServerObject | null\n /** Currently selected client */\n selectedClient: WorkspaceStore['workspace']['x-scalar-default-client']\n /** Server list available for operation/document */\n servers: ServerObject[]\n /** Meta information for the server */\n serverMeta: ServerMeta\n /** Hides the client button on the header */\n hideClientButton?: boolean\n /** Client integration */\n integration?: string | null\n /** Openapi document url for `modal` mode to open the client app */\n documentUrl?: string\n /** Client source */\n source?: 'gitbook' | 'api-reference'\n /** Operation object */\n operation: OperationObject\n /** Currently selected example key for the current operation */\n exampleKey: string\n /** Meta information for the auth update */\n authMeta: AuthMeta\n /** Document defined security schemes */\n securitySchemes: MergedSecuritySchemes\n /** Client plugins */\n plugins: ClientPlugin[]\n /** Environment list */\n environments?: string[]\n /** Currently selected environment */\n activeEnvironment?: string\n /** For environment variables in the inputs */\n environment: XScalarEnvironment\n /** The proxy URL for sending requests */\n proxyUrl: string\n /** Currently selected security */\n selectedSecurity: SelectedSecurity\n /** Currently selected security schemes */\n selectedSecuritySchemes: SecuritySchemeObjectSecret[]\n /** Security requirements */\n securityRequirements: OpenApiDocument['security']\n /** Default headers */\n defaultHeaders: Record<string, string>\n /** Selected anyOf/oneOf request-body variants keyed by schema path */\n requestBodyCompositionSelection?: Record<string, number>\n /** Subset of config options for the modal */\n options?: ModalProps['options']\n}\n</script>\n<script setup lang=\"ts\">\nimport { generateClientOptions } from '@scalar/blocks/code-example'\nimport { ERRORS } from '@scalar/helpers/errors/normalize-error'\nimport { isElectron } from '@scalar/helpers/general/is-electron'\nimport { buildSafeBodyRequest } from '@scalar/helpers/http/can-method-have-body'\nimport type { HttpMethod as HttpMethodType } from '@scalar/helpers/http/http-methods'\nimport { executeHook, type ClientPlugin } from '@scalar/oas-utils/helpers'\nimport {\n AVAILABLE_CLIENTS,\n type AvailableClients,\n} from '@scalar/types/snippetz'\nimport { useClipboard } from '@scalar/use-hooks/useClipboard'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { SelectedSecurity } from '@scalar/workspace-store/entities/auth'\nimport type { HistoryEntry } from '@scalar/workspace-store/entities/history/schema'\nimport type {\n AuthMeta,\n ServerMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n buildRequest,\n createVariablesStoreForRequest,\n getEnvironmentVariables,\n requestFactory,\n resolveExecutableRequestUrl,\n type MergedSecuritySchemes,\n type RequestPayload,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCookie } from '@scalar/workspace-store/schemas/extensions/general/x-scalar-cookies'\nimport type {\n OpenApiDocument,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport type { OperationObject } from '@scalar/workspace-store/schemas/v3.1/strict/operation'\nimport { computed, onBeforeUnmount, onMounted, ref, toValue, watch } from 'vue'\n\nimport ViewLayout from '@/components/ViewLayout/ViewLayout.vue'\nimport ViewLayoutContent from '@/components/ViewLayout/ViewLayoutContent.vue'\nimport { harToFetchRequest } from '@/v2/blocks/operation-block/helpers/har-to-fetch-request'\nimport { harToFetchResponse } from '@/v2/blocks/operation-block/helpers/har-to-fetch-response'\nimport {\n getCookieRequestUrl,\n getResponseCookieActions,\n} from '@/v2/blocks/operation-block/helpers/persist-response-cookies'\nimport { getEnvironmentPersistenceActions } from '@/v2/blocks/operation-block/helpers/persist-script-environment'\nimport {\n getOperationExampleKey,\n isStreamingResponse,\n responseCache,\n} from '@/v2/blocks/operation-block/helpers/response-cache'\nimport {\n sendRequest,\n type ResponseInstance,\n} from '@/v2/blocks/operation-block/helpers/send-request'\nimport { validatePathParameters } from '@/v2/blocks/operation-block/helpers/validate-path-parameters'\nimport { RequestBlock } from '@/v2/blocks/request-block'\nimport { ResponseBlock } from '@/v2/blocks/response-block'\nimport { type History } from '@/v2/blocks/scalar-address-bar-block'\nimport type { ModalProps } from '@/v2/features/modal/Modal.vue'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nimport Header from './components/Header.vue'\n\nconst {\n authMeta,\n environment,\n eventBus,\n exampleKey,\n document,\n documentSlug,\n workspaceCookies = [],\n documentCookies = [],\n hideClientButton,\n httpClients = AVAILABLE_CLIENTS,\n history = [],\n layout,\n method,\n operation,\n path,\n plugins = [],\n proxyUrl,\n requestBodyCompositionSelection,\n securitySchemes,\n selectedClient,\n server,\n environments,\n options,\n activeEnvironment,\n serverMeta,\n selectedSecurity,\n selectedSecuritySchemes,\n securityRequirements,\n defaultHeaders,\n} = defineProps<OperationBlockProps>()\n\n/** Hoist up client generation so it doesn't get re-generated on every operation */\nconst clientOptions = computed(() => generateClientOptions(httpClients))\n\nconst { toast } = useToasts()\nconst { copyToClipboard } = useClipboard()\n\n// Refs\nconst abortController = ref<AbortController | null>(null)\nconst response = ref<ResponseInstance | null>(null)\nconst requestPayload = ref<RequestPayload | null>(null)\n\n/** Cancel the request */\nconst cancelRequest = () => abortController.value?.abort(ERRORS.REQUEST_ABORTED)\n\n/**\n * Persist server-set cookies from a response into the document cookie jar.\n *\n * Scalar keeps its own cookie jar because browsers hide `Set-Cookie` from `fetch`.\n * Mirroring browser behavior, cookies are stored scoped to the request host and\n * path and removed when the server expires them, so a value like a Django CSRF\n * token stays available for later PUT/PATCH requests after a page reload.\n */\nconst persistResponseCookies = (sendResult: {\n response: ResponseInstance\n requestPayload: RequestPayload\n}) => {\n const actions = getResponseCookieActions({\n cookieHeaderKeys: sendResult.response.cookieHeaderKeys ?? [],\n documentCookies,\n requestUrl: getCookieRequestUrl(String(sendResult.requestPayload[0])),\n })\n\n for (const action of actions) {\n if (action.type === 'delete') {\n eventBus.emit('cookie:delete:cookie', {\n collectionType: 'document',\n cookieName: action.cookieName,\n index: action.index,\n })\n continue\n }\n\n eventBus.emit('cookie:upsert:cookie', {\n collectionType: 'document',\n payload: action.cookie,\n ...(action.index === undefined ? {} : { index: action.index }),\n })\n }\n}\n\n/**\n * Copy the executable URL — same pipeline as Send (`requestFactory` +\n * `resolveExecutableRequestUrl`), including security query params.\n */\nconst copyAddressBarUrl = async (): Promise<void> => {\n const { request } = requestFactory({\n defaultHeaders,\n environment,\n exampleName: exampleKey,\n globalCookies: [...workspaceCookies, ...documentCookies],\n method,\n operation,\n path,\n proxyUrl,\n server,\n selectedSecuritySchemes,\n isElectron: isElectron(),\n requestBodyCompositionSelection,\n })\n\n await copyToClipboard(\n resolveExecutableRequestUrl(request, getEnvironmentVariables(environment)),\n )\n}\n\n/** Execute the current operation example */\nconst handleExecute = async () => {\n eventBus.flushDebouncedEmits?.()\n\n const pathValidation = validatePathParameters(\n operation.parameters ?? [],\n exampleKey,\n )\n if (pathValidation.ok === false) {\n toast('Path parameters must have values.', 'error')\n return\n }\n\n const globalCookies = [...workspaceCookies, ...documentCookies]\n\n const { request: requestBuilder } = requestFactory({\n defaultHeaders,\n environment,\n exampleName: exampleKey,\n globalCookies,\n method,\n operation,\n path,\n proxyUrl,\n server,\n selectedSecuritySchemes,\n isElectron: isElectron(),\n requestBodyCompositionSelection,\n })\n\n // Stop any previous streaming response\n if (response.value && 'reader' in response.value) {\n response.value.reader.cancel()\n }\n\n const variablesStore = createVariablesStoreForRequest()\n\n // Seed the script variable store with the active environment so pm.environment.get()\n // and pm.variables.get() resolve to the same values as {{…}} placeholders. Without\n // seeding, the store starts empty and every scripted read returns undefined.\n const seededEnvironment = getEnvironmentVariables(environment)\n variablesStore.setEnvironment?.(\n Object.entries(seededEnvironment).map(([key, value]) => ({ key, value })),\n )\n\n // Snapshot the persistence target at request start, alongside the seed. Persistence runs after\n // the request completes (awaits below), and the environment props are reactive — if the active\n // environment is switched mid-flight, or a concurrent request splices these arrays, reading\n // them at persist time would apply the script's writes to the wrong environment or resolve\n // workspace/document indexes against a shifted list. Copy the arrays (not just the reference)\n // so the seeded values and these targets stay fixed to the same point in time.\n const persistenceTarget = activeEnvironment\n ? {\n environmentName: activeEnvironment,\n mergedVariables: [...environment.variables],\n documentVariables: [\n ...(document['x-scalar-environments']?.[activeEnvironment]\n ?.variables ?? []),\n ],\n environmentExistsOnDocument: Boolean(\n document['x-scalar-environments']?.[activeEnvironment],\n ),\n }\n : null\n\n // Execute the beforeRequest hook (plugins receive RequestFactory, not fetch Request)\n await executeHook(\n {\n requestBuilder,\n document,\n operation,\n variablesStore,\n server,\n customFetch: toValue(options)?.customFetch,\n },\n 'beforeRequest',\n plugins,\n )\n\n // Persist environment variables set by scripts (pm.environment.set / unset) back to the active\n // environment so they survive to the next request, mirroring the Environment tab. Scope and\n // index are resolved from the seed-time environment so updates mutate in place. Defined here so\n // it can run on every exit path after the pre-request scripts — including a build failure or a\n // failed send — so a pre-request set, like a token stored before a flaky call, is not lost.\n const persistScriptEnvironment = () => {\n if (!persistenceTarget) {\n return\n }\n\n const environmentActions = getEnvironmentPersistenceActions({\n ...persistenceTarget,\n seededVariables: seededEnvironment,\n scriptVariables: variablesStore.getEnvironment(),\n })\n\n for (const action of environmentActions) {\n if (action.type === 'delete') {\n eventBus.emit('environment:delete:environment-variable', {\n environmentName: action.environmentName,\n index: action.index,\n collectionType: action.collectionType,\n })\n } else {\n eventBus.emit('environment:upsert:environment-variable', {\n environmentName: action.environmentName,\n variable: action.variable,\n index: action.index,\n collectionType: action.collectionType,\n })\n }\n }\n }\n\n // Resolve {{…}} placeholders from the script store so pre-request pm.environment.set() and\n // unset() take effect on this request. The store was seeded from the active environment, so\n // it already holds every value spreading seededEnvironment would add — minus the keys a script\n // unset, which must stay dropped (an object spread cannot remove them again).\n const envVariables = variablesStore.getVariables()\n\n // Build the fetch Request after hooks may have mutated the factory\n const built = buildRequest(requestBuilder, {\n envVariables,\n allowMissingRequestServerBase: layout === 'modal',\n })\n if (!built.ok) {\n // Save any pre-request script writes before bailing out on a build failure.\n persistScriptEnvironment()\n toast(built.message ?? built.error, 'error')\n return\n }\n\n // Store the abort controller for cancellation\n abortController.value = built.data.controller\n\n // Build the fetch Request once so the requestBuilt hook observes the exact bytes that are\n // sent. Rebuilding it later would generate a different multipart boundary, which breaks\n // plugins that hash the body (for example, request signing).\n const request = buildSafeBodyRequest(...built.data.requestPayload)\n\n // Execute the requestBuilt hook (plugins receive the exact fetch Request that will be sent)\n await executeHook(\n {\n request,\n requestBuilder,\n document,\n operation,\n variablesStore,\n },\n 'requestBuilt',\n plugins,\n )\n\n // Execute the hooks\n eventBus.emit('hooks:on:request:sent', {\n meta: {\n method,\n path,\n exampleKey,\n },\n })\n\n /** Execute the request */\n const [sendError, sendResult] = await sendRequest({\n isUsingProxy: built.data.isUsingProxy,\n requestPayload: built.data.requestPayload,\n request,\n plugins,\n customFetch: toValue(options)?.customFetch,\n })\n\n if (sendResult) {\n // Execute the responseReceived hook\n await executeHook(\n {\n response: sendResult.originalResponse.clone(),\n requestBuilder,\n request: buildSafeBodyRequest(...sendResult.requestPayload),\n document,\n operation,\n variablesStore,\n },\n 'responseReceived',\n plugins,\n )\n }\n\n // Save script environment writes (pre-request and, on success, post-response) back to the\n // active environment. Runs even when the send fails so a pre-request set is not lost.\n persistScriptEnvironment()\n\n // Execute the hooks\n eventBus.emit('hooks:on:request:complete', {\n payload: sendResult\n ? {\n response: sendResult.originalResponse.clone(),\n requestPayload: sendResult.requestPayload,\n duration: sendResult.response.duration,\n timestamp: sendResult.timestamp,\n }\n : undefined,\n meta: {\n method,\n path,\n exampleKey,\n },\n })\n\n if (sendError) {\n // clean up the response and request\n response.value = null\n requestPayload.value = null\n abortController.value = null\n\n toast(sendError.message, 'error')\n return\n }\n\n // Store the response\n response.value = sendResult.response\n requestPayload.value = sendResult.requestPayload\n\n // Persist server-set cookies into the document jar so values like a CSRF token\n // survive a page reload and get replayed on the next request, like a browser would.\n persistResponseCookies(sendResult)\n\n // Cache non-streaming responses so they can be restored when navigating back\n if (!isStreamingResponse(sendResult.response)) {\n responseCache.set(getOperationExampleKey(method, path, exampleKey), {\n response: sendResult.response,\n requestPayload: sendResult.requestPayload,\n })\n }\n}\n\nonMounted(() => {\n eventBus.on('operation:send:request:hotkey', handleExecute)\n eventBus.on('operation:cancel:request', cancelRequest)\n eventBus.on('copy-url:address-bar', copyAddressBarUrl)\n\n // Let plugins warm up per-operation resources (e.g. the scripts sandbox) so the first request\n // does not pay their cold-start cost.\n void executeHook({ document, operation }, 'onRequestMount', plugins)\n})\nonBeforeUnmount(() => {\n eventBus.off('operation:send:request:hotkey', handleExecute)\n eventBus.off('operation:cancel:request', cancelRequest)\n eventBus.off('copy-url:address-bar', copyAddressBarUrl)\n})\n\nconst operationHistory = computed<History[]>(() =>\n history\n .map((entry) => ({\n method: entry.request.method as HttpMethodType,\n path: entry.request.url,\n duration: entry.time,\n status: entry.response.status,\n }))\n .reverse(),\n)\n\nconst handleSelectHistoryItem = ({ index }: { index: number }) => {\n const transformedIndex = (history.length ?? 0) - index - 1\n const historyItem = history[transformedIndex]\n if (!historyItem) {\n return\n }\n\n const navigate = () =>\n eventBus.emit('ui:navigate', {\n page: 'example',\n method,\n path,\n exampleName: 'draft',\n callback: (status) => {\n // Do not replace the response if the navigation was not successful\n if (status !== 'success') {\n return\n }\n // Reconstruct the response\n const fetchResponse = harToFetchResponse({\n harResponse: historyItem.response,\n url: historyItem.request.url,\n method,\n path,\n duration: historyItem.time,\n })\n\n // Reconstruct the request\n const fetchRequest = harToFetchRequest({\n harRequest: historyItem.request,\n })\n\n // Update the response and request\n response.value = fetchResponse\n requestPayload.value = fetchRequest\n },\n })\n\n eventBus.emit('operation:reload:history', {\n meta: {\n path,\n method,\n },\n index: transformedIndex,\n callback: navigate,\n })\n}\n\nconst handleNavigateSettings = () => {\n eventBus.emit('ui:navigate', {\n page: 'operation',\n path: 'overview',\n operationPath: path,\n method,\n })\n}\n\n/**\n * When the path, method, or example key changes, restore the response panel\n * for the new operation from the best available source:\n *\n * 1. `responseCache` — in-memory, populated on every successful send.\n * Has the live response object (including streams) so it wins.\n * 2. `history` — persisted in the workspace store. Used as a fallback when\n * the in-memory cache is empty (page reload, fresh app session, etc.)\n * so navigating to an operation that has been called before shows the\n * last response instead of an empty panel.\n * 3. Otherwise clear.\n *\n * Only the response is restored from history — the user's request inputs are\n * left alone, so we never silently overwrite an in-progress draft.\n */\nwatch(\n [() => path, () => method, () => exampleKey],\n ([newPath, newMethod, newExampleKey]) => {\n const cached = responseCache.get(\n getOperationExampleKey(newMethod, newPath, newExampleKey),\n )\n\n if (cached) {\n response.value = cached.response\n requestPayload.value = cached.requestPayload\n } else {\n // History is keyed only by document/path/method but each entry carries\n // the example it came from, so we walk from the end and pick the most\n // recent entry that matches the active example. Otherwise a cache miss\n // on example B would restore example A's response. Only runs on a\n // cache miss — the common case (in-session navigation) skips it.\n const latest = (() => {\n for (let i = history.length - 1; i >= 0; i--) {\n const entry = history[i]\n if (entry?.meta.example === newExampleKey) {\n return entry\n }\n }\n return undefined\n })()\n\n if (latest) {\n response.value = harToFetchResponse({\n harResponse: latest.response,\n url: latest.request.url,\n method: newMethod,\n path: newPath,\n duration: latest.time,\n })\n requestPayload.value = null\n } else {\n response.value = null\n requestPayload.value = null\n }\n }\n\n // Cancel any in-flight request\n cancelRequest()\n },\n { immediate: true },\n)\n\nonBeforeUnmount(() => {\n // We cancel the request if the component is unmounted\n cancelRequest()\n})\n</script>\n<template>\n <div class=\"bg-b-1 flex h-full flex-col\">\n <div\n class=\"lg:min-h-header flex w-full flex-wrap items-center justify-center p-2 lg:p-0\">\n <!-- Address Bar -->\n <Header\n :activeEnvironment\n :documentSlug\n :documentUrl\n :environment\n :environments\n :eventBus\n :exampleKey\n :hideClientButton\n :history=\"operationHistory\"\n :integration\n :layout\n :method\n :path\n :server\n :serverMeta\n :servers\n :source\n @execute=\"handleExecute\"\n @navigate:settings=\"handleNavigateSettings\"\n @select:history:item=\"handleSelectHistoryItem\" />\n </div>\n\n <ViewLayout class=\"border-t\">\n <ViewLayoutContent class=\"flex-1\">\n <!-- Request Section -->\n <RequestBlock\n :authMeta\n :clientOptions\n :defaultHeaders\n :documentCookies\n :environment\n :eventBus\n :exampleKey\n :layout\n :method\n :operation\n :options=\"toValue(options)\"\n :path\n :plugins\n :proxyUrl\n :requestBodyCompositionSelection\n :securityRequirements\n :securitySchemes\n :selectedClient\n :selectedSecurity\n :selectedSecuritySchemes\n :server\n :workspaceCookies />\n\n <!-- Response Section -->\n <ResponseBlock\n :appVersion\n :eventBus\n :layout\n :plugins\n :requestPayload\n :response\n :totalPerformedRequests=\"operationHistory.length\" />\n </ViewLayoutContent>\n </ViewLayout>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"OperationBlock.vue.js","names":[],"sources":["../../../../src/v2/blocks/operation-block/OperationBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * OperationBlock\n *\n * Orchestrates the operation view by wiring together the Header, OperationBlock,\n * and ResponseBlock. Forwards user interactions to the workspace event bus and\n * passes through configuration such as auth, servers, plugins, and environment.\n * This component keeps the Operation page lean by centralizing event emission\n * and prop wiring between the blocks.\n *\n * Notable behavior:\n * - Uses operation['x-scalar-method'] and operation['x-scalar-path'] to provide\n * draft overrides for the UI when present.\n */\nexport default {\n name: 'OperationBlock',\n}\n\nexport type OperationBlockProps = {\n /** Event bus */\n eventBus: WorkspaceEventBus\n /** Application version */\n appVersion: string\n /** Openapi document */\n document: OpenApiDocument\n /** Openapi document slug */\n documentSlug: string\n /** Workspace cookies */\n workspaceCookies: XScalarCookie[]\n /** Document cookies */\n documentCookies: XScalarCookie[]\n /** Current request path */\n path: string\n /** Current request method */\n method: HttpMethodType\n /** Whether `path` identifies an OpenAPI webhook. */\n isWebhook?: boolean\n /** HTTP clients */\n httpClients: AvailableClients\n /** The history for the operation */\n history?: HistoryEntry[]\n /** Client layout */\n layout: ClientLayout\n /** Currently selected server */\n server: ServerObject | null\n /** Currently selected client */\n selectedClient: WorkspaceStore['workspace']['x-scalar-default-client']\n /** Server list available for operation/document */\n servers: ServerObject[]\n /** Meta information for the server */\n serverMeta: ServerMeta\n /** Hides the client button on the header */\n hideClientButton?: boolean\n /** Client integration */\n integration?: string | null\n /** Openapi document url for `modal` mode to open the client app */\n documentUrl?: string\n /** Client source */\n source?: 'gitbook' | 'api-reference'\n /** Operation object */\n operation: OperationObject\n /** Currently selected example key for the current operation */\n exampleKey: string\n /** Meta information for the auth update */\n authMeta: AuthMeta\n /** Document defined security schemes */\n securitySchemes: MergedSecuritySchemes\n /** Client plugins */\n plugins: ClientPlugin[]\n /** Environment list */\n environments?: string[]\n /** Currently selected environment */\n activeEnvironment?: string\n /** For environment variables in the inputs */\n environment: XScalarEnvironment\n /** The proxy URL for sending requests */\n proxyUrl: string\n /** Currently selected security */\n selectedSecurity: SelectedSecurity\n /** Currently selected security schemes */\n selectedSecuritySchemes: SecuritySchemeObjectSecret[]\n /** Security requirements */\n securityRequirements: OpenApiDocument['security']\n /** Default headers */\n defaultHeaders: Record<string, string>\n /** Selected anyOf/oneOf request-body variants keyed by schema path */\n requestBodyCompositionSelection?: Record<string, number>\n /** Subset of config options for the modal */\n options?: ModalProps['options']\n}\n</script>\n<script setup lang=\"ts\">\nimport { generateClientOptions } from '@scalar/blocks/code-example'\nimport { ERRORS } from '@scalar/helpers/errors/normalize-error'\nimport { isElectron } from '@scalar/helpers/general/is-electron'\nimport { buildSafeBodyRequest } from '@scalar/helpers/http/can-method-have-body'\nimport type { HttpMethod as HttpMethodType } from '@scalar/helpers/http/http-methods'\nimport { executeHook, type ClientPlugin } from '@scalar/oas-utils/helpers'\nimport {\n AVAILABLE_CLIENTS,\n type AvailableClients,\n} from '@scalar/types/snippetz'\nimport { useClipboard } from '@scalar/use-hooks/useClipboard'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { SelectedSecurity } from '@scalar/workspace-store/entities/auth'\nimport type { HistoryEntry } from '@scalar/workspace-store/entities/history/schema'\nimport type {\n AuthMeta,\n ServerMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n buildRequest,\n createVariablesStoreForRequest,\n getEnvironmentVariables,\n requestFactory,\n resolveExecutableRequestUrl,\n type MergedSecuritySchemes,\n type RequestPayload,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCookie } from '@scalar/workspace-store/schemas/extensions/general/x-scalar-cookies'\nimport type {\n OpenApiDocument,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport type { OperationObject } from '@scalar/workspace-store/schemas/v3.1/strict/operation'\nimport { computed, onBeforeUnmount, onMounted, ref, toValue, watch } from 'vue'\n\nimport ViewLayout from '@/components/ViewLayout/ViewLayout.vue'\nimport ViewLayoutContent from '@/components/ViewLayout/ViewLayoutContent.vue'\nimport { harToFetchRequest } from '@/v2/blocks/operation-block/helpers/har-to-fetch-request'\nimport { harToFetchResponse } from '@/v2/blocks/operation-block/helpers/har-to-fetch-response'\nimport {\n getCookieRequestUrl,\n getResponseCookieActions,\n} from '@/v2/blocks/operation-block/helpers/persist-response-cookies'\nimport { getEnvironmentPersistenceActions } from '@/v2/blocks/operation-block/helpers/persist-script-environment'\nimport {\n getOperationExampleKey,\n isStreamingResponse,\n responseCache,\n} from '@/v2/blocks/operation-block/helpers/response-cache'\nimport {\n sendRequest,\n type ResponseInstance,\n} from '@/v2/blocks/operation-block/helpers/send-request'\nimport { validatePathParameters } from '@/v2/blocks/operation-block/helpers/validate-path-parameters'\nimport { RequestBlock } from '@/v2/blocks/request-block'\nimport { ResponseBlock } from '@/v2/blocks/response-block'\nimport { type History } from '@/v2/blocks/scalar-address-bar-block'\nimport type { ModalProps } from '@/v2/features/modal/Modal.vue'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nimport Header from './components/Header.vue'\n\nconst {\n authMeta,\n environment,\n eventBus,\n exampleKey,\n document,\n documentSlug,\n workspaceCookies = [],\n documentCookies = [],\n hideClientButton,\n httpClients = AVAILABLE_CLIENTS,\n history = [],\n layout,\n method,\n isWebhook = false,\n operation,\n path,\n plugins = [],\n proxyUrl,\n requestBodyCompositionSelection,\n securitySchemes,\n selectedClient,\n server,\n servers,\n environments,\n options,\n activeEnvironment,\n serverMeta,\n selectedSecurity,\n selectedSecuritySchemes,\n securityRequirements,\n defaultHeaders,\n} = defineProps<OperationBlockProps>()\n\n/** Hoist up client generation so it doesn't get re-generated on every operation */\nconst clientOptions = computed(() => generateClientOptions(httpClients))\n\nconst { toast } = useToasts()\nconst { copyToClipboard } = useClipboard()\n\n// Refs\nconst abortController = ref<AbortController | null>(null)\nconst response = ref<ResponseInstance | null>(null)\nconst requestPayload = ref<RequestPayload | null>(null)\n\n/**\n * A webhook has no OpenAPI server, so its destination is the full URL the user\n * enters at runtime. The field holds that whole URL (not a server-relative path),\n * and the request is sent to it directly with no separate server base.\n */\nconst webhookUrl = ref('')\n\nwatch(\n [() => path, () => method, () => isWebhook],\n () => {\n webhookUrl.value = ''\n },\n { immediate: true },\n)\n\nconst requestPath = computed(() => (isWebhook ? webhookUrl.value : path))\nconst requestServer = computed<ServerObject | null>(() =>\n isWebhook ? null : server,\n)\n\n/** Cancel the request */\nconst cancelRequest = () => abortController.value?.abort(ERRORS.REQUEST_ABORTED)\n\n/**\n * Persist server-set cookies from a response into the document cookie jar.\n *\n * Scalar keeps its own cookie jar because browsers hide `Set-Cookie` from `fetch`.\n * Mirroring browser behavior, cookies are stored scoped to the request host and\n * path and removed when the server expires them, so a value like a Django CSRF\n * token stays available for later PUT/PATCH requests after a page reload.\n */\nconst persistResponseCookies = (sendResult: {\n response: ResponseInstance\n requestPayload: RequestPayload\n}) => {\n const actions = getResponseCookieActions({\n cookieHeaderKeys: sendResult.response.cookieHeaderKeys ?? [],\n documentCookies,\n requestUrl: getCookieRequestUrl(String(sendResult.requestPayload[0])),\n })\n\n for (const action of actions) {\n if (action.type === 'delete') {\n eventBus.emit('cookie:delete:cookie', {\n collectionType: 'document',\n cookieName: action.cookieName,\n index: action.index,\n })\n continue\n }\n\n eventBus.emit('cookie:upsert:cookie', {\n collectionType: 'document',\n payload: action.cookie,\n ...(action.index === undefined ? {} : { index: action.index }),\n })\n }\n}\n\n/**\n * Copy the executable URL — same pipeline as Send (`requestFactory` +\n * `resolveExecutableRequestUrl`), including security query params.\n */\nconst copyAddressBarUrl = async (): Promise<void> => {\n const { request } = requestFactory({\n defaultHeaders,\n environment,\n exampleName: exampleKey,\n globalCookies: [...workspaceCookies, ...documentCookies],\n method,\n operation,\n path: requestPath.value,\n proxyUrl,\n server: requestServer.value,\n selectedSecuritySchemes,\n isElectron: isElectron(),\n requestBodyCompositionSelection,\n })\n\n await copyToClipboard(\n resolveExecutableRequestUrl(request, getEnvironmentVariables(environment)),\n )\n}\n\n/** Execute the current operation example */\nconst handleExecute = async () => {\n eventBus.flushDebouncedEmits?.()\n\n if (isWebhook && !requestPath.value.trim()) {\n toast('Webhook URL required. Enter a destination first.', 'error')\n return\n }\n\n const pathValidation = validatePathParameters(\n operation.parameters ?? [],\n exampleKey,\n )\n if (pathValidation.ok === false) {\n toast('Path parameters must have values.', 'error')\n return\n }\n\n const globalCookies = [...workspaceCookies, ...documentCookies]\n\n const { request: requestBuilder } = requestFactory({\n defaultHeaders,\n environment,\n exampleName: exampleKey,\n globalCookies,\n method,\n operation,\n path: requestPath.value,\n proxyUrl,\n server: requestServer.value,\n selectedSecuritySchemes,\n isElectron: isElectron(),\n requestBodyCompositionSelection,\n })\n\n // Stop any previous streaming response\n if (response.value && 'reader' in response.value) {\n response.value.reader.cancel()\n }\n\n const variablesStore = createVariablesStoreForRequest()\n\n // Seed the script variable store with the active environment so pm.environment.get()\n // and pm.variables.get() resolve to the same values as {{…}} placeholders. Without\n // seeding, the store starts empty and every scripted read returns undefined.\n const seededEnvironment = getEnvironmentVariables(environment)\n variablesStore.setEnvironment?.(\n Object.entries(seededEnvironment).map(([key, value]) => ({ key, value })),\n )\n\n // Snapshot the persistence target at request start, alongside the seed. Persistence runs after\n // the request completes (awaits below), and the environment props are reactive — if the active\n // environment is switched mid-flight, or a concurrent request splices these arrays, reading\n // them at persist time would apply the script's writes to the wrong environment or resolve\n // workspace/document indexes against a shifted list. Copy the arrays (not just the reference)\n // so the seeded values and these targets stay fixed to the same point in time.\n const persistenceTarget = activeEnvironment\n ? {\n environmentName: activeEnvironment,\n mergedVariables: [...environment.variables],\n documentVariables: [\n ...(document['x-scalar-environments']?.[activeEnvironment]\n ?.variables ?? []),\n ],\n environmentExistsOnDocument: Boolean(\n document['x-scalar-environments']?.[activeEnvironment],\n ),\n }\n : null\n\n // Execute the beforeRequest hook (plugins receive RequestFactory, not fetch Request)\n await executeHook(\n {\n requestBuilder,\n document,\n operation,\n variablesStore,\n server: requestServer.value,\n customFetch: toValue(options)?.customFetch,\n },\n 'beforeRequest',\n plugins,\n )\n\n // Persist environment variables set by scripts (pm.environment.set / unset) back to the active\n // environment so they survive to the next request, mirroring the Environment tab. Scope and\n // index are resolved from the seed-time environment so updates mutate in place. Defined here so\n // it can run on every exit path after the pre-request scripts — including a build failure or a\n // failed send — so a pre-request set, like a token stored before a flaky call, is not lost.\n const persistScriptEnvironment = () => {\n if (!persistenceTarget) {\n return\n }\n\n const environmentActions = getEnvironmentPersistenceActions({\n ...persistenceTarget,\n seededVariables: seededEnvironment,\n scriptVariables: variablesStore.getEnvironment(),\n })\n\n for (const action of environmentActions) {\n if (action.type === 'delete') {\n eventBus.emit('environment:delete:environment-variable', {\n environmentName: action.environmentName,\n index: action.index,\n collectionType: action.collectionType,\n })\n } else {\n eventBus.emit('environment:upsert:environment-variable', {\n environmentName: action.environmentName,\n variable: action.variable,\n index: action.index,\n collectionType: action.collectionType,\n })\n }\n }\n }\n\n // Resolve {{…}} placeholders from the script store so pre-request pm.environment.set() and\n // unset() take effect on this request. The store was seeded from the active environment, so\n // it already holds every value spreading seededEnvironment would add — minus the keys a script\n // unset, which must stay dropped (an object spread cannot remove them again).\n const envVariables = variablesStore.getVariables()\n\n // Build the fetch Request after hooks may have mutated the factory\n const built = buildRequest(requestBuilder, {\n envVariables,\n allowMissingRequestServerBase: layout === 'modal',\n })\n if (!built.ok) {\n // Save any pre-request script writes before bailing out on a build failure.\n persistScriptEnvironment()\n toast(built.message ?? built.error, 'error')\n return\n }\n\n // Store the abort controller for cancellation\n abortController.value = built.data.controller\n\n // Build the fetch Request once so the requestBuilt hook observes the exact bytes that are\n // sent. Rebuilding it later would generate a different multipart boundary, which breaks\n // plugins that hash the body (for example, request signing).\n const request = buildSafeBodyRequest(...built.data.requestPayload)\n\n // Execute the requestBuilt hook (plugins receive the exact fetch Request that will be sent)\n await executeHook(\n {\n request,\n requestBuilder,\n document,\n operation,\n variablesStore,\n },\n 'requestBuilt',\n plugins,\n )\n\n // Execute the hooks\n eventBus.emit('hooks:on:request:sent', {\n meta: {\n method,\n path,\n exampleKey,\n },\n })\n\n /** Execute the request */\n const [sendError, sendResult] = await sendRequest({\n isUsingProxy: built.data.isUsingProxy,\n requestPayload: built.data.requestPayload,\n request,\n plugins,\n customFetch: toValue(options)?.customFetch,\n })\n\n if (sendResult) {\n // Execute the responseReceived hook\n await executeHook(\n {\n response: sendResult.originalResponse.clone(),\n requestBuilder,\n request: buildSafeBodyRequest(...sendResult.requestPayload),\n document,\n operation,\n variablesStore,\n },\n 'responseReceived',\n plugins,\n )\n }\n\n // Save script environment writes (pre-request and, on success, post-response) back to the\n // active environment. Runs even when the send fails so a pre-request set is not lost.\n persistScriptEnvironment()\n\n // Execute the hooks\n eventBus.emit('hooks:on:request:complete', {\n payload: sendResult\n ? {\n response: sendResult.originalResponse.clone(),\n requestPayload: sendResult.requestPayload,\n duration: sendResult.response.duration,\n timestamp: sendResult.timestamp,\n }\n : undefined,\n meta: {\n method,\n path,\n exampleKey,\n },\n })\n\n if (sendError) {\n // clean up the response and request\n response.value = null\n requestPayload.value = null\n abortController.value = null\n\n toast(sendError.message, 'error')\n return\n }\n\n // Store the response\n response.value = sendResult.response\n requestPayload.value = sendResult.requestPayload\n\n // Persist server-set cookies into the document jar so values like a CSRF token\n // survive a page reload and get replayed on the next request, like a browser would.\n persistResponseCookies(sendResult)\n\n // Cache non-streaming responses so they can be restored when navigating back\n if (!isStreamingResponse(sendResult.response)) {\n responseCache.set(getOperationExampleKey(method, path, exampleKey), {\n response: sendResult.response,\n requestPayload: sendResult.requestPayload,\n })\n }\n}\n\nonMounted(() => {\n eventBus.on('operation:send:request:hotkey', handleExecute)\n eventBus.on('operation:cancel:request', cancelRequest)\n eventBus.on('copy-url:address-bar', copyAddressBarUrl)\n\n // Let plugins warm up per-operation resources (e.g. the scripts sandbox) so the first request\n // does not pay their cold-start cost.\n void executeHook({ document, operation }, 'onRequestMount', plugins)\n})\nonBeforeUnmount(() => {\n eventBus.off('operation:send:request:hotkey', handleExecute)\n eventBus.off('operation:cancel:request', cancelRequest)\n eventBus.off('copy-url:address-bar', copyAddressBarUrl)\n})\n\nconst operationHistory = computed<History[]>(() =>\n history\n .map((entry) => ({\n method: entry.request.method as HttpMethodType,\n path: entry.request.url,\n duration: entry.time,\n status: entry.response.status,\n }))\n .reverse(),\n)\n\nconst handleSelectHistoryItem = ({ index }: { index: number }) => {\n const transformedIndex = (history.length ?? 0) - index - 1\n const historyItem = history[transformedIndex]\n if (!historyItem) {\n return\n }\n\n const navigate = () =>\n eventBus.emit('ui:navigate', {\n page: 'example',\n method,\n path,\n exampleName: 'draft',\n callback: (status) => {\n // Do not replace the response if the navigation was not successful\n if (status !== 'success') {\n return\n }\n // Reconstruct the response\n const fetchResponse = harToFetchResponse({\n harResponse: historyItem.response,\n url: historyItem.request.url,\n method,\n path,\n duration: historyItem.time,\n })\n\n // Reconstruct the request\n const fetchRequest = harToFetchRequest({\n harRequest: historyItem.request,\n })\n\n // Update the response and request\n response.value = fetchResponse\n requestPayload.value = fetchRequest\n },\n })\n\n eventBus.emit('operation:reload:history', {\n meta: {\n path,\n method,\n },\n index: transformedIndex,\n callback: navigate,\n })\n}\n\nconst handleNavigateSettings = () => {\n eventBus.emit('ui:navigate', {\n page: 'operation',\n path: 'overview',\n operationPath: path,\n method,\n })\n}\n\n/**\n * When the path, method, or example key changes, restore the response panel\n * for the new operation from the best available source:\n *\n * 1. `responseCache` — in-memory, populated on every successful send.\n * Has the live response object (including streams) so it wins.\n * 2. `history` — persisted in the workspace store. Used as a fallback when\n * the in-memory cache is empty (page reload, fresh app session, etc.)\n * so navigating to an operation that has been called before shows the\n * last response instead of an empty panel.\n * 3. Otherwise clear.\n *\n * Only the response is restored from history — the user's request inputs are\n * left alone, so we never silently overwrite an in-progress draft.\n */\nwatch(\n [() => path, () => method, () => exampleKey],\n ([newPath, newMethod, newExampleKey]) => {\n const cached = responseCache.get(\n getOperationExampleKey(newMethod, newPath, newExampleKey),\n )\n\n if (cached) {\n response.value = cached.response\n requestPayload.value = cached.requestPayload\n } else {\n // History is keyed only by document/path/method but each entry carries\n // the example it came from, so we walk from the end and pick the most\n // recent entry that matches the active example. Otherwise a cache miss\n // on example B would restore example A's response. Only runs on a\n // cache miss — the common case (in-session navigation) skips it.\n const latest = (() => {\n for (let i = history.length - 1; i >= 0; i--) {\n const entry = history[i]\n if (entry?.meta.example === newExampleKey) {\n return entry\n }\n }\n return undefined\n })()\n\n if (latest) {\n response.value = harToFetchResponse({\n harResponse: latest.response,\n url: latest.request.url,\n method: newMethod,\n path: newPath,\n duration: latest.time,\n })\n requestPayload.value = null\n } else {\n response.value = null\n requestPayload.value = null\n }\n }\n\n // Cancel any in-flight request\n cancelRequest()\n },\n { immediate: true },\n)\n\nonBeforeUnmount(() => {\n // We cancel the request if the component is unmounted\n cancelRequest()\n})\n</script>\n<template>\n <div class=\"bg-b-1 flex h-full flex-col\">\n <div\n class=\"lg:min-h-header flex w-full flex-wrap items-center justify-center p-2 lg:p-0\">\n <!-- Address Bar -->\n <Header\n :activeEnvironment\n :documentSlug\n :documentUrl\n :environment\n :environments\n :eventBus\n :exampleKey\n :hideClientButton\n :history=\"operationHistory\"\n :integration\n :isWebhook\n :layout\n :method\n :path=\"requestPath\"\n :server=\"requestServer\"\n :serverMeta\n :servers\n :source\n @execute=\"handleExecute\"\n @navigate:settings=\"handleNavigateSettings\"\n @select:history:item=\"handleSelectHistoryItem\"\n @update:webhook-url=\"(value) => (webhookUrl = value)\" />\n </div>\n\n <ViewLayout class=\"border-t\">\n <ViewLayoutContent class=\"flex-1\">\n <!-- Request Section -->\n <RequestBlock\n :authMeta\n :clientOptions\n :defaultHeaders\n :documentCookies\n :environment\n :eventBus\n :exampleKey\n :layout\n :method\n :operation\n :options=\"toValue(options)\"\n :path=\"requestPath\"\n :plugins\n :proxyUrl\n :requestBodyCompositionSelection\n :securityRequirements\n :securitySchemes\n :selectedClient\n :selectedSecurity\n :selectedSecuritySchemes\n :server=\"requestServer\"\n :workspaceCookies />\n\n <!-- Response Section -->\n <ResponseBlock\n :appVersion\n :eventBus\n :layout\n :plugins\n :requestPayload\n :response\n :totalPerformedRequests=\"operationHistory.length\" />\n </ViewLayoutContent>\n </ViewLayout>\n </div>\n</template>\n"],"mappings":""}
@@ -34,6 +34,10 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
34
34
  documentCookies: { default: () => [] },
35
35
  path: {},
36
36
  method: {},
37
+ isWebhook: {
38
+ type: Boolean,
39
+ default: false
40
+ },
37
41
  httpClients: { default: () => AVAILABLE_CLIENTS },
38
42
  history: { default: () => [] },
39
43
  layout: {},
@@ -69,6 +73,21 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
69
73
  const abortController = ref(null);
70
74
  const response = ref(null);
71
75
  const requestPayload = ref(null);
76
+ /**
77
+ * A webhook has no OpenAPI server, so its destination is the full URL the user
78
+ * enters at runtime. The field holds that whole URL (not a server-relative path),
79
+ * and the request is sent to it directly with no separate server base.
80
+ */
81
+ const webhookUrl = ref("");
82
+ watch([
83
+ () => __props.path,
84
+ () => __props.method,
85
+ () => __props.isWebhook
86
+ ], () => {
87
+ webhookUrl.value = "";
88
+ }, { immediate: true });
89
+ const requestPath = computed(() => __props.isWebhook ? webhookUrl.value : __props.path);
90
+ const requestServer = computed(() => __props.isWebhook ? null : __props.server);
72
91
  /** Cancel the request */
73
92
  const cancelRequest = () => abortController.value?.abort(ERRORS.REQUEST_ABORTED);
74
93
  /**
@@ -113,9 +132,9 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
113
132
  globalCookies: [...__props.workspaceCookies, ...__props.documentCookies],
114
133
  method: __props.method,
115
134
  operation: __props.operation,
116
- path: __props.path,
135
+ path: requestPath.value,
117
136
  proxyUrl: __props.proxyUrl,
118
- server: __props.server,
137
+ server: requestServer.value,
119
138
  selectedSecuritySchemes: __props.selectedSecuritySchemes,
120
139
  isElectron: isElectron(),
121
140
  requestBodyCompositionSelection: __props.requestBodyCompositionSelection
@@ -125,6 +144,10 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
125
144
  /** Execute the current operation example */
126
145
  const handleExecute = async () => {
127
146
  __props.eventBus.flushDebouncedEmits?.();
147
+ if (__props.isWebhook && !requestPath.value.trim()) {
148
+ toast("Webhook URL required. Enter a destination first.", "error");
149
+ return;
150
+ }
128
151
  if (validatePathParameters(__props.operation.parameters ?? [], __props.exampleKey).ok === false) {
129
152
  toast("Path parameters must have values.", "error");
130
153
  return;
@@ -137,9 +160,9 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
137
160
  globalCookies,
138
161
  method: __props.method,
139
162
  operation: __props.operation,
140
- path: __props.path,
163
+ path: requestPath.value,
141
164
  proxyUrl: __props.proxyUrl,
142
- server: __props.server,
165
+ server: requestServer.value,
143
166
  selectedSecuritySchemes: __props.selectedSecuritySchemes,
144
167
  isElectron: isElectron(),
145
168
  requestBodyCompositionSelection: __props.requestBodyCompositionSelection
@@ -162,7 +185,7 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
162
185
  document: __props.document,
163
186
  operation: __props.operation,
164
187
  variablesStore,
165
- server: __props.server,
188
+ server: requestServer.value,
166
189
  customFetch: toValue(__props.options)?.customFetch
167
190
  }, "beforeRequest", __props.plugins);
168
191
  const persistScriptEnvironment = () => {
@@ -374,16 +397,18 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
374
397
  hideClientButton: __props.hideClientButton,
375
398
  history: operationHistory.value,
376
399
  integration: __props.integration,
400
+ isWebhook: __props.isWebhook,
377
401
  layout: __props.layout,
378
402
  method: __props.method,
379
- path: __props.path,
380
- server: __props.server,
403
+ path: requestPath.value,
404
+ server: requestServer.value,
381
405
  serverMeta: __props.serverMeta,
382
406
  servers: __props.servers,
383
407
  source: __props.source,
384
408
  onExecute: handleExecute,
385
409
  "onNavigate:settings": handleNavigateSettings,
386
- "onSelect:history:item": handleSelectHistoryItem
410
+ "onSelect:history:item": handleSelectHistoryItem,
411
+ "onUpdate:webhookUrl": _cache[0] || (_cache[0] = (value) => webhookUrl.value = value)
387
412
  }, null, 8, [
388
413
  "activeEnvironment",
389
414
  "documentSlug",
@@ -395,6 +420,7 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
395
420
  "hideClientButton",
396
421
  "history",
397
422
  "integration",
423
+ "isWebhook",
398
424
  "layout",
399
425
  "method",
400
426
  "path",
@@ -416,7 +442,7 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
416
442
  method: __props.method,
417
443
  operation: __props.operation,
418
444
  options: toValue(__props.options),
419
- path: __props.path,
445
+ path: requestPath.value,
420
446
  plugins: __props.plugins,
421
447
  proxyUrl: __props.proxyUrl,
422
448
  requestBodyCompositionSelection: __props.requestBodyCompositionSelection,
@@ -425,7 +451,7 @@ var OperationBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
425
451
  selectedClient: __props.selectedClient,
426
452
  selectedSecurity: __props.selectedSecurity,
427
453
  selectedSecuritySchemes: __props.selectedSecuritySchemes,
428
- server: __props.server,
454
+ server: requestServer.value,
429
455
  workspaceCookies: __props.workspaceCookies
430
456
  }, null, 8, [
431
457
  "authMeta",