@scalar/workspace-store 0.17.1 → 0.18.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 (174) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/client.d.ts +31 -19
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +267 -88
  5. package/dist/client.js.map +3 -3
  6. package/dist/events/bus.d.ts +86 -0
  7. package/dist/events/bus.d.ts.map +1 -0
  8. package/dist/events/bus.js +58 -0
  9. package/dist/events/bus.js.map +7 -0
  10. package/dist/events/definitions/analytics.d.ts +27 -0
  11. package/dist/events/definitions/analytics.d.ts.map +1 -0
  12. package/dist/events/definitions/analytics.js +1 -0
  13. package/dist/events/definitions/analytics.js.map +7 -0
  14. package/dist/events/definitions/auth.d.ts +30 -0
  15. package/dist/events/definitions/auth.d.ts.map +1 -0
  16. package/dist/events/definitions/auth.js +1 -0
  17. package/dist/events/definitions/auth.js.map +7 -0
  18. package/dist/events/definitions/document.d.ts +14 -0
  19. package/dist/events/definitions/document.d.ts.map +1 -0
  20. package/dist/events/definitions/document.js +1 -0
  21. package/dist/events/definitions/document.js.map +7 -0
  22. package/dist/events/definitions/index.d.ts +10 -0
  23. package/dist/events/definitions/index.d.ts.map +1 -0
  24. package/dist/events/definitions/index.js +1 -0
  25. package/dist/events/definitions/index.js.map +7 -0
  26. package/dist/events/definitions/meta.d.ts +11 -0
  27. package/dist/events/definitions/meta.d.ts.map +1 -0
  28. package/dist/events/definitions/meta.js +1 -0
  29. package/dist/events/definitions/meta.js.map +7 -0
  30. package/dist/events/definitions/operation.d.ts +11 -0
  31. package/dist/events/definitions/operation.d.ts.map +1 -0
  32. package/dist/events/definitions/operation.js +1 -0
  33. package/dist/events/definitions/operation.js.map +7 -0
  34. package/dist/events/definitions/server.d.ts +50 -0
  35. package/dist/events/definitions/server.d.ts.map +1 -0
  36. package/dist/events/definitions/server.js +1 -0
  37. package/dist/events/definitions/server.js.map +7 -0
  38. package/dist/events/definitions/ui.d.ts +52 -0
  39. package/dist/events/definitions/ui.d.ts.map +1 -0
  40. package/dist/events/definitions/ui.js +1 -0
  41. package/dist/events/definitions/ui.js.map +7 -0
  42. package/dist/events/index.d.ts +2 -1
  43. package/dist/events/index.d.ts.map +1 -1
  44. package/dist/events/index.js +3 -1
  45. package/dist/events/index.js.map +2 -2
  46. package/dist/events/listeners.d.ts +2 -1
  47. package/dist/events/listeners.d.ts.map +1 -1
  48. package/dist/events/listeners.js.map +2 -2
  49. package/dist/events/{definitions.d.ts → old-definitions.d.ts} +35 -11
  50. package/dist/events/old-definitions.d.ts.map +1 -0
  51. package/dist/events/{definitions.js → old-definitions.js} +1 -1
  52. package/dist/events/old-definitions.js.map +7 -0
  53. package/dist/helpers/debounce.d.ts +28 -0
  54. package/dist/helpers/debounce.d.ts.map +1 -0
  55. package/dist/helpers/debounce.js +31 -0
  56. package/dist/helpers/debounce.js.map +7 -0
  57. package/dist/helpers/detect-changes-proxy.d.ts +47 -0
  58. package/dist/helpers/detect-changes-proxy.d.ts.map +1 -0
  59. package/dist/helpers/detect-changes-proxy.js +59 -0
  60. package/dist/helpers/detect-changes-proxy.js.map +7 -0
  61. package/dist/helpers/overrides-proxy.d.ts +17 -6
  62. package/dist/helpers/overrides-proxy.d.ts.map +1 -1
  63. package/dist/helpers/overrides-proxy.js +33 -18
  64. package/dist/helpers/overrides-proxy.js.map +3 -3
  65. package/dist/helpers/unpack-proxy.d.ts +6 -0
  66. package/dist/helpers/unpack-proxy.d.ts.map +1 -0
  67. package/dist/helpers/unpack-proxy.js +9 -0
  68. package/dist/helpers/unpack-proxy.js.map +7 -0
  69. package/dist/navigation/get-navigation-options.d.ts +1 -1
  70. package/dist/navigation/get-navigation-options.d.ts.map +1 -1
  71. package/dist/navigation/get-navigation-options.js +66 -54
  72. package/dist/navigation/get-navigation-options.js.map +2 -2
  73. package/dist/navigation/helpers/get-tag.d.ts +7 -2
  74. package/dist/navigation/helpers/get-tag.d.ts.map +1 -1
  75. package/dist/navigation/helpers/get-tag.js +16 -2
  76. package/dist/navigation/helpers/get-tag.js.map +2 -2
  77. package/dist/navigation/helpers/traverse-description.d.ts +7 -2
  78. package/dist/navigation/helpers/traverse-description.d.ts.map +1 -1
  79. package/dist/navigation/helpers/traverse-description.js +24 -6
  80. package/dist/navigation/helpers/traverse-description.js.map +2 -2
  81. package/dist/navigation/helpers/traverse-document.d.ts +5 -2
  82. package/dist/navigation/helpers/traverse-document.d.ts.map +1 -1
  83. package/dist/navigation/helpers/traverse-document.js +53 -15
  84. package/dist/navigation/helpers/traverse-document.js.map +2 -2
  85. package/dist/navigation/helpers/traverse-paths.d.ts +9 -3
  86. package/dist/navigation/helpers/traverse-paths.d.ts.map +1 -1
  87. package/dist/navigation/helpers/traverse-paths.js +63 -9
  88. package/dist/navigation/helpers/traverse-paths.js.map +2 -2
  89. package/dist/navigation/helpers/traverse-schemas.d.ts +7 -8
  90. package/dist/navigation/helpers/traverse-schemas.d.ts.map +1 -1
  91. package/dist/navigation/helpers/traverse-schemas.js +35 -7
  92. package/dist/navigation/helpers/traverse-schemas.js.map +2 -2
  93. package/dist/navigation/helpers/traverse-tags.d.ts +8 -4
  94. package/dist/navigation/helpers/traverse-tags.d.ts.map +1 -1
  95. package/dist/navigation/helpers/traverse-tags.js +65 -17
  96. package/dist/navigation/helpers/traverse-tags.js.map +2 -2
  97. package/dist/navigation/helpers/traverse-webhooks.d.ts +9 -3
  98. package/dist/navigation/helpers/traverse-webhooks.d.ts.map +1 -1
  99. package/dist/navigation/helpers/traverse-webhooks.js +43 -17
  100. package/dist/navigation/helpers/traverse-webhooks.js.map +2 -2
  101. package/dist/navigation/types.d.ts +5 -24
  102. package/dist/navigation/types.d.ts.map +1 -1
  103. package/dist/persistence/index.d.ts +86 -0
  104. package/dist/persistence/index.d.ts.map +1 -0
  105. package/dist/persistence/index.js +196 -0
  106. package/dist/persistence/index.js.map +7 -0
  107. package/dist/persistence/indexdb.d.ts +87 -0
  108. package/dist/persistence/indexdb.d.ts.map +1 -0
  109. package/dist/persistence/indexdb.js +125 -0
  110. package/dist/persistence/indexdb.js.map +7 -0
  111. package/dist/{plugins.d.ts → plugins/bundler/index.d.ts} +1 -1
  112. package/dist/plugins/bundler/index.d.ts.map +1 -0
  113. package/dist/{plugins.js → plugins/bundler/index.js} +2 -2
  114. package/dist/{plugins.js.map → plugins/bundler/index.js.map} +1 -1
  115. package/dist/plugins/client/index.d.ts +3 -0
  116. package/dist/plugins/client/index.d.ts.map +1 -0
  117. package/dist/plugins/client/index.js +5 -0
  118. package/dist/plugins/client/index.js.map +7 -0
  119. package/dist/plugins/client/persistence.d.ts +13 -0
  120. package/dist/plugins/client/persistence.d.ts.map +1 -0
  121. package/dist/plugins/client/persistence.js +57 -0
  122. package/dist/plugins/client/persistence.js.map +7 -0
  123. package/dist/schemas/extensions.d.ts +1 -0
  124. package/dist/schemas/extensions.d.ts.map +1 -1
  125. package/dist/schemas/extensions.js +1 -0
  126. package/dist/schemas/extensions.js.map +2 -2
  127. package/dist/schemas/inmemory-workspace.d.ts +71 -12
  128. package/dist/schemas/inmemory-workspace.d.ts.map +1 -1
  129. package/dist/schemas/inmemory-workspace.js +1 -9
  130. package/dist/schemas/inmemory-workspace.js.map +2 -2
  131. package/dist/schemas/navigation.d.ts +128 -1
  132. package/dist/schemas/navigation.d.ts.map +1 -1
  133. package/dist/schemas/navigation.js +20 -1
  134. package/dist/schemas/navigation.js.map +2 -2
  135. package/dist/schemas/reference-config/index.d.ts +231 -198
  136. package/dist/schemas/reference-config/index.d.ts.map +1 -1
  137. package/dist/schemas/reference-config/index.js.map +2 -2
  138. package/dist/schemas/reference-config/settings.d.ts +33 -1
  139. package/dist/schemas/reference-config/settings.d.ts.map +1 -1
  140. package/dist/schemas/reference-config/settings.js +1 -1
  141. package/dist/schemas/reference-config/settings.js.map +1 -1
  142. package/dist/schemas/v3.1/strict/openapi-document.d.ts +1131 -37
  143. package/dist/schemas/v3.1/strict/openapi-document.d.ts.map +1 -1
  144. package/dist/schemas/v3.1/strict/openapi-document.js +16 -9
  145. package/dist/schemas/v3.1/strict/openapi-document.js.map +2 -2
  146. package/dist/schemas/v3.1/strict/parameter.d.ts +23 -1
  147. package/dist/schemas/v3.1/strict/parameter.d.ts.map +1 -1
  148. package/dist/schemas/v3.1/strict/parameter.js +5 -1
  149. package/dist/schemas/v3.1/strict/parameter.js.map +2 -2
  150. package/dist/schemas/v3.1/strict/ref-definitions.d.ts +2 -0
  151. package/dist/schemas/v3.1/strict/ref-definitions.d.ts.map +1 -1
  152. package/dist/schemas/v3.1/strict/ref-definitions.js +4 -1
  153. package/dist/schemas/v3.1/strict/ref-definitions.js.map +2 -2
  154. package/dist/schemas/workspace-specification/config.d.ts +34 -7
  155. package/dist/schemas/workspace-specification/config.d.ts.map +1 -1
  156. package/dist/schemas/workspace-specification/config.js.map +1 -1
  157. package/dist/schemas/workspace-specification/index.d.ts +36 -3
  158. package/dist/schemas/workspace-specification/index.d.ts.map +1 -1
  159. package/dist/schemas/workspace.d.ts +238 -10
  160. package/dist/schemas/workspace.d.ts.map +1 -1
  161. package/dist/schemas/workspace.js +3 -1
  162. package/dist/schemas/workspace.js.map +2 -2
  163. package/dist/server.d.ts +4 -3
  164. package/dist/server.d.ts.map +1 -1
  165. package/dist/server.js +7 -6
  166. package/dist/server.js.map +2 -2
  167. package/dist/workspace-plugin.d.ts +49 -0
  168. package/dist/workspace-plugin.d.ts.map +1 -0
  169. package/dist/workspace-plugin.js +1 -0
  170. package/dist/workspace-plugin.js.map +7 -0
  171. package/package.json +21 -9
  172. package/dist/events/definitions.d.ts.map +0 -1
  173. package/dist/events/definitions.js.map +0 -7
  174. package/dist/plugins.d.ts.map +0 -1
package/dist/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { generateHash } from "@scalar/helpers/crypto/generate-hash";
1
2
  import { measureAsync, measureSync } from "@scalar/helpers/testing/measure";
2
3
  import { bundle } from "@scalar/json-magic/bundle";
3
4
  import { fetchUrls } from "@scalar/json-magic/bundle/plugins/browser";
@@ -9,12 +10,14 @@ import { reactive, toRaw } from "vue";
9
10
  import YAML from "yaml";
10
11
  import { applySelectiveUpdates } from "./helpers/apply-selective-updates.js";
11
12
  import { deepClone } from "./helpers/deep-clone.js";
13
+ import { createDetectChangesProxy } from "./helpers/detect-changes-proxy.js";
12
14
  import { isObject, safeAssign } from "./helpers/general.js";
13
15
  import { getValueByPath } from "./helpers/json-path-utils.js";
14
16
  import { mergeObjects } from "./helpers/merge-object.js";
15
17
  import { createOverridesProxy, unpackOverridesProxy } from "./helpers/overrides-proxy.js";
18
+ import { unpackProxyObject } from "./helpers/unpack-proxy.js";
16
19
  import { createNavigation } from "./navigation/index.js";
17
- import { externalValueResolver, loadingStatus, refsEverywhere, restoreOriginalRefs } from "./plugins.js";
20
+ import { externalValueResolver, loadingStatus, refsEverywhere, restoreOriginalRefs } from "./plugins/bundler/index.js";
18
21
  import { getServersFromDocument } from "./preprocessing/server.js";
19
22
  import { extensions } from "./schemas/extensions.js";
20
23
  import { InMemoryWorkspaceSchema } from "./schemas/inmemory-workspace.js";
@@ -26,14 +29,16 @@ import {
26
29
  const defaultConfig = {
27
30
  "x-scalar-reference-config": defaultReferenceConfig
28
31
  };
29
- async function loadDocument(workspaceDocument) {
32
+ function loadDocument(workspaceDocument) {
30
33
  if ("url" in workspaceDocument) {
31
34
  return fetchUrls({ fetch: workspaceDocument.fetch }).exec(workspaceDocument.url);
32
35
  }
33
- return {
36
+ return Promise.resolve({
34
37
  ok: true,
35
- data: workspaceDocument.document
36
- };
38
+ data: workspaceDocument.document,
39
+ // string version of the raw document for hashing purposes
40
+ raw: JSON.stringify(workspaceDocument.document)
41
+ });
37
42
  }
38
43
  const getDocumentSource = (input) => {
39
44
  if ("url" in input) {
@@ -42,37 +47,176 @@ const getDocumentSource = (input) => {
42
47
  return void 0;
43
48
  };
44
49
  const createWorkspaceStore = (workspaceProps) => {
45
- const originalDocuments = {};
46
- const intermediateDocuments = {};
47
- const documentConfigs = {};
48
- const overrides = {};
49
- const documentMeta = {};
50
50
  const extraDocumentConfigurations = {};
51
- const workspace = reactive({
52
- ...workspaceProps?.meta,
53
- documents: {},
54
- /**
55
- * Returns the currently active document from the workspace.
56
- * The active document is determined by the 'x-scalar-active-document' metadata field,
57
- * falling back to the first document in the workspace if no active document is specified.
58
- *
59
- * @returns The active document or undefined if no document is found
60
- */
61
- get activeDocument() {
62
- const activeDocumentKey = workspace[extensions.workspace.activeDocument] ?? Object.keys(workspace.documents)[0] ?? "";
63
- return workspace.documents[activeDocumentKey];
51
+ const fireWorkspaceChange = (event) => {
52
+ workspaceProps?.plugins?.forEach((plugin) => plugin.hooks?.onWorkspaceStateChanges?.(event));
53
+ };
54
+ const workspace = reactive(
55
+ createDetectChangesProxy(
56
+ {
57
+ ...workspaceProps?.meta,
58
+ documents: {},
59
+ /**
60
+ * Returns the currently active document from the workspace.
61
+ * The active document is determined by the 'x-scalar-active-document' metadata field,
62
+ * falling back to the first document in the workspace if no active document is specified.
63
+ *
64
+ * @returns The active document or undefined if no document is found
65
+ */
66
+ get activeDocument() {
67
+ return workspace.documents[getActiveDocumentName()];
68
+ }
69
+ },
70
+ {
71
+ hooks: {
72
+ onAfterChange(path) {
73
+ const type = path[0];
74
+ if (type === "documents") {
75
+ if (path.length < 2) {
76
+ console.log("[WARN]: Overriding entire documents object is not supported");
77
+ return;
78
+ }
79
+ const documentName = path[1];
80
+ const event2 = {
81
+ type: "documents",
82
+ documentName,
83
+ value: unpackProxyObject(
84
+ workspace.documents[documentName] ?? {
85
+ openapi: "3.1.0",
86
+ info: { title: "", version: "" },
87
+ "x-scalar-original-document-hash": ""
88
+ }
89
+ )
90
+ };
91
+ fireWorkspaceChange(event2);
92
+ return;
93
+ }
94
+ if (type === "activeDocument") {
95
+ const documentName = getActiveDocumentName();
96
+ const event2 = {
97
+ type: "documents",
98
+ documentName,
99
+ value: unpackProxyObject(
100
+ workspace.documents[documentName] ?? {
101
+ openapi: "3.1.0",
102
+ info: { title: "", version: "" },
103
+ "x-scalar-original-document-hash": ""
104
+ }
105
+ )
106
+ };
107
+ fireWorkspaceChange(event2);
108
+ return;
109
+ }
110
+ const { activeDocument: _a, documents: _d, ...meta } = workspace;
111
+ const event = {
112
+ type: "meta",
113
+ value: unpackProxyObject(meta)
114
+ };
115
+ fireWorkspaceChange(event);
116
+ return;
117
+ }
118
+ }
119
+ }
120
+ )
121
+ );
122
+ const { originalDocuments, intermediateDocuments, overrides, documentConfigs } = createDetectChangesProxy(
123
+ {
124
+ /**
125
+ * Holds the original, unmodified documents as they were initially loaded into the workspace.
126
+ * These documents are stored in their raw form—prior to any reactive wrapping, dereferencing, or bundling.
127
+ * This map preserves the pristine structure of each document, using deep clones to ensure that
128
+ * subsequent mutations in the workspace do not affect the originals.
129
+ * The originals are retained so that we can restore, compare, or sync with the remote registry as needed.
130
+ */
131
+ originalDocuments: {},
132
+ /**
133
+ * Stores the intermediate state of documents after local edits but before syncing with the remote registry.
134
+ *
135
+ * This map acts as a local "saved" version of the document, reflecting the user's changes after they hit "save".
136
+ * The `originalDocuments` map, by contrast, always mirrors the document as it exists in the remote registry.
137
+ *
138
+ * Use this map to stage local changes that are ready to be propagated back to the remote registry.
139
+ * This separation allows us to distinguish between:
140
+ * - The last known remote version (`originalDocuments`)
141
+ * - The latest locally saved version (`intermediateDocuments`)
142
+ * - The current in-memory (possibly unsaved) workspace document (`workspace.documents`)
143
+ */
144
+ intermediateDocuments: {},
145
+ /**
146
+ * A map of document configurations keyed by document name.
147
+ * This stores the configuration options for each document in the workspace,
148
+ * allowing for document-specific settings like navigation options, appearance,
149
+ * and other reference configuration.
150
+ */
151
+ documentConfigs: {},
152
+ /**
153
+ * Stores per-document overrides for OpenAPI documents.
154
+ * This object is used to override specific fields of a document
155
+ * when you cannot (or should not) modify the source document directly.
156
+ * For example, this enables UI-driven or temporary changes to be applied
157
+ * on top of the original document, without mutating the source.
158
+ * The key is the document name, and the value is a deep partial
159
+ * OpenAPI document representing the overridden fields.
160
+ */
161
+ overrides: {}
162
+ },
163
+ {
164
+ hooks: {
165
+ onAfterChange(path) {
166
+ const type = path[0];
167
+ if (!type) {
168
+ return;
169
+ }
170
+ if (path.length < 2) {
171
+ return;
172
+ }
173
+ const documentName = path[1];
174
+ if (type === "originalDocuments") {
175
+ const event = {
176
+ type,
177
+ documentName,
178
+ value: unpackProxyObject(originalDocuments[documentName] ?? {})
179
+ };
180
+ fireWorkspaceChange(event);
181
+ }
182
+ if (type === "intermediateDocuments") {
183
+ const event = {
184
+ type,
185
+ documentName,
186
+ value: unpackProxyObject(intermediateDocuments[documentName] ?? {})
187
+ };
188
+ fireWorkspaceChange(event);
189
+ }
190
+ if (type === "documentConfigs") {
191
+ const event = {
192
+ type,
193
+ documentName,
194
+ value: unpackProxyObject(documentConfigs[documentName] ?? {})
195
+ };
196
+ fireWorkspaceChange(event);
197
+ }
198
+ if (type === "overrides") {
199
+ const event = {
200
+ type,
201
+ documentName,
202
+ value: unpackProxyObject(overrides[documentName] ?? {})
203
+ };
204
+ fireWorkspaceChange(event);
205
+ }
206
+ }
207
+ }
64
208
  }
65
- });
209
+ );
66
210
  function getActiveDocumentName() {
67
211
  return workspace[extensions.workspace.activeDocument] ?? Object.keys(workspace.documents)[0] ?? "";
68
212
  }
69
- function exportDocument(documentName, format) {
213
+ function exportDocument(documentName, format, minify) {
70
214
  const intermediateDocument = intermediateDocuments[documentName];
71
215
  if (!intermediateDocument) {
72
216
  return;
73
217
  }
74
218
  if (format === "json") {
75
- return JSON.stringify(intermediateDocument);
219
+ return minify ? JSON.stringify(intermediateDocument) : JSON.stringify(intermediateDocument, null, 2);
76
220
  }
77
221
  return YAML.stringify(intermediateDocument);
78
222
  }
@@ -113,13 +257,20 @@ const createWorkspaceStore = (workspaceProps) => {
113
257
  intermediateDocuments[name] = deepClone(clonedRawInputDocument);
114
258
  documentConfigs[name] = input.config ?? {};
115
259
  overrides[name] = input.overrides ?? {};
116
- documentMeta[name] = { documentSource: input.documentSource };
117
260
  extraDocumentConfigurations[name] = { fetch: input.fetch };
118
261
  }
119
262
  });
120
263
  const inputDocument = measureSync("upgrade", () => upgrade(deepClone(clonedRawInputDocument), "3.1"));
121
- const strictDocument = createMagicProxy({ ...inputDocument, ...meta }, { showInternal: true });
122
- strictDocument["x-original-oas-version"] = input.document.openapi ?? input.document.swagger;
264
+ const strictDocument = createMagicProxy(
265
+ {
266
+ ...inputDocument,
267
+ ...meta,
268
+ "x-original-oas-version": originalDocuments[name]?.openapi ?? originalDocuments[name]?.swagger,
269
+ "x-scalar-original-document-hash": input.documentHash,
270
+ "x-scalar-original-source-url": input.documentSource
271
+ },
272
+ { showInternal: true }
273
+ );
123
274
  if (strictDocument[extensions.document.navigation] === void 0) {
124
275
  await measureAsync(
125
276
  "bundle",
@@ -133,7 +284,7 @@ const createWorkspaceStore = (workspaceProps) => {
133
284
  refsEverywhere()
134
285
  ],
135
286
  urlMap: true,
136
- origin: documentMeta[name]?.documentSource
287
+ origin: input.documentSource
137
288
  // use the document origin (if provided) as the base URL for resolution
138
289
  })
139
290
  );
@@ -157,17 +308,16 @@ const createWorkspaceStore = (workspaceProps) => {
157
308
  );
158
309
  }
159
310
  if (strictDocument[extensions.document.navigation] === void 0) {
160
- const navigation = createNavigation(strictDocument, input.config);
161
- strictDocument[extensions.document.navigation] = navigation.entries;
311
+ const navigation = createNavigation(name, strictDocument, input.config);
312
+ strictDocument[extensions.document.navigation] = navigation;
162
313
  processDocument(getRaw(strictDocument), {
163
314
  ...documentConfigs[name] ?? {},
164
315
  documentSource: input.documentSource
165
316
  });
166
317
  }
167
- workspace.documents[name] = createOverridesProxy(
168
- createMagicProxy(getRaw(strictDocument)),
169
- overrides[name]
170
- );
318
+ workspace.documents[name] = createOverridesProxy(createMagicProxy(getRaw(strictDocument)), {
319
+ overrides: overrides[name]
320
+ });
171
321
  }
172
322
  async function addDocument(input) {
173
323
  const { name, meta } = input;
@@ -184,7 +334,8 @@ const createWorkspaceStore = (workspaceProps) => {
184
334
  info: {
185
335
  title: `Document '${name}' could not be loaded`,
186
336
  version: "unknown"
187
- }
337
+ },
338
+ "x-scalar-original-document-hash": "not-a-hash"
188
339
  };
189
340
  return;
190
341
  }
@@ -196,14 +347,16 @@ const createWorkspaceStore = (workspaceProps) => {
196
347
  info: {
197
348
  title: `Document '${name}' could not be loaded`,
198
349
  version: "unknown"
199
- }
350
+ },
351
+ "x-scalar-original-document-hash": "not-a-hash"
200
352
  };
201
353
  return;
202
354
  }
203
355
  await addInMemoryDocument({
204
356
  ...input,
205
357
  document: resolve.data,
206
- documentSource: getDocumentSource(input)
358
+ documentSource: getDocumentSource(input),
359
+ documentHash: await generateHash(resolve.raw)
207
360
  });
208
361
  });
209
362
  }
@@ -240,6 +393,8 @@ const createWorkspaceStore = (workspaceProps) => {
240
393
  name: documentName,
241
394
  document: input,
242
395
  // Preserve the current metadata
396
+ documentSource: currentDocument["x-scalar-original-source-url"],
397
+ documentHash: currentDocument["x-scalar-original-document-hash"],
243
398
  meta: {
244
399
  "x-scalar-active-auth": currentDocument["x-scalar-active-auth"],
245
400
  "x-scalar-active-server": currentDocument["x-scalar-active-server"]
@@ -247,14 +402,14 @@ const createWorkspaceStore = (workspaceProps) => {
247
402
  initialize: false
248
403
  });
249
404
  },
250
- resolve: async (path) => {
405
+ resolve: (path) => {
251
406
  const activeDocument = workspace.activeDocument;
252
407
  const target = getValueByPath(activeDocument, path);
253
408
  if (!isObject(target)) {
254
409
  console.error(
255
410
  `Invalid path provided for resolution. Path: [${path.join(", ")}]. Found value of type: ${typeof target}. Expected an object.`
256
411
  );
257
- return;
412
+ return Promise.resolve();
258
413
  }
259
414
  return bundle(target, {
260
415
  root: activeDocument,
@@ -269,7 +424,7 @@ const createWorkspaceStore = (workspaceProps) => {
269
424
  return getDocumentConfiguration(getActiveDocumentName());
270
425
  },
271
426
  exportDocument,
272
- exportActiveDocument: (format) => exportDocument(getActiveDocumentName(), format),
427
+ exportActiveDocument: (format, minify) => exportDocument(getActiveDocumentName(), format, minify),
273
428
  saveDocument,
274
429
  async revertDocumentChanges(documentName) {
275
430
  const workspaceDocument = workspace.documents[documentName];
@@ -280,6 +435,8 @@ const createWorkspaceStore = (workspaceProps) => {
280
435
  await addInMemoryDocument({
281
436
  name: documentName,
282
437
  document: intermediate,
438
+ documentSource: workspaceDocument["x-scalar-original-source-url"],
439
+ documentHash: workspaceDocument["x-scalar-original-document-hash"],
283
440
  initialize: false
284
441
  });
285
442
  },
@@ -287,14 +444,14 @@ const createWorkspaceStore = (workspaceProps) => {
287
444
  console.warn(`Commit operation for document '${documentName}' is not implemented yet.`);
288
445
  },
289
446
  exportWorkspace() {
290
- return JSON.stringify({
447
+ return {
291
448
  documents: {
292
449
  ...Object.fromEntries(
293
450
  Object.entries(workspace.documents).map(([name, doc]) => [
294
451
  name,
295
452
  // Extract the raw document data for export, removing any Vue reactivity wrappers.
296
453
  // When importing, the document can be wrapped again in a magic proxy.
297
- getRaw(doc)
454
+ toRaw(getRaw(unpackOverridesProxy(doc)))
298
455
  ])
299
456
  )
300
457
  },
@@ -302,18 +459,19 @@ const createWorkspaceStore = (workspaceProps) => {
302
459
  documentConfigs,
303
460
  originalDocuments,
304
461
  intermediateDocuments,
305
- overrides,
306
- documentMeta
307
- });
462
+ overrides
463
+ };
308
464
  },
309
465
  loadWorkspace(input) {
310
- const result = coerceValue(InMemoryWorkspaceSchema, JSON.parse(input));
466
+ const result = coerceValue(InMemoryWorkspaceSchema, input);
311
467
  safeAssign(
312
468
  workspace.documents,
313
469
  Object.fromEntries(
314
470
  Object.entries(result.documents).map(([name, doc]) => [
315
471
  name,
316
- createOverridesProxy(createMagicProxy(doc), result.overrides[name])
472
+ createOverridesProxy(createMagicProxy(doc), {
473
+ overrides: result.overrides[name]
474
+ })
317
475
  ])
318
476
  )
319
477
  );
@@ -322,7 +480,6 @@ const createWorkspaceStore = (workspaceProps) => {
322
480
  safeAssign(documentConfigs, result.documentConfigs);
323
481
  safeAssign(overrides, result.overrides);
324
482
  safeAssign(workspace, result.meta);
325
- safeAssign(documentMeta, result.documentMeta);
326
483
  },
327
484
  importWorkspaceFromSpecification: (specification) => {
328
485
  const { documents, overrides: overrides2, info: _info, workspace: _workspaceVersion, ...meta } = specification;
@@ -333,60 +490,82 @@ const createWorkspaceStore = (workspaceProps) => {
333
490
  )
334
491
  );
335
492
  },
336
- rebaseDocument: async (input, resolvedConflicts) => {
493
+ rebaseDocument: async (input) => {
337
494
  const { name } = input;
495
+ const originalDocument = originalDocuments[name];
496
+ const intermediateDocument = intermediateDocuments[name];
497
+ const activeDocument = workspace.documents[name] ? toRaw(getRaw(unpackOverridesProxy(workspace.documents[name]))) : void 0;
498
+ if (!originalDocument || !intermediateDocument || !activeDocument) {
499
+ return {
500
+ ok: false,
501
+ type: "CORRUPTED_STATE",
502
+ message: `Cannot rebase document '${name}': missing original, intermediate, or active document state`
503
+ };
504
+ }
338
505
  const resolve = await measureAsync(
339
506
  "loadDocument",
340
507
  async () => await loadDocument({ ...input, fetch: input.fetch ?? workspaceProps?.fetch })
341
508
  );
342
509
  if (!resolve.ok || !isObject(resolve.data)) {
343
- return console.error(`[ERROR]: Failed to load document '${name}': response data is not a valid object`);
510
+ return {
511
+ ok: false,
512
+ type: "FETCH_FAILED",
513
+ message: `Failed to fetch document '${name}': request was not successful or returned invalid data`
514
+ };
344
515
  }
345
- const newDocumentOrigin = resolve.data;
346
- const originalDocument = originalDocuments[name];
347
- const intermediateDocument = intermediateDocuments[name];
348
- const activeDocument = workspace.documents[name] ? toRaw(getRaw(unpackOverridesProxy(workspace.documents[name]))) : void 0;
349
- if (!originalDocument || !intermediateDocument || !activeDocument) {
350
- return console.error("[ERROR]: Specified document is missing or internal corrupted workspace state");
516
+ const newHash = await generateHash(resolve.raw);
517
+ if (activeDocument["x-scalar-original-document-hash"] === newHash) {
518
+ return {
519
+ ok: false,
520
+ type: "NO_CHANGES_DETECTED",
521
+ message: `No changes detected for document '${name}': document hash matches the active document`
522
+ };
351
523
  }
352
- const documentSource = getDocumentSource(input);
524
+ const newDocumentOrigin = resolve.data;
353
525
  documentConfigs[name] = input.config ?? {};
354
526
  overrides[name] = input.overrides ?? {};
355
- documentMeta[name] = { documentSource };
356
527
  extraDocumentConfigurations[name] = { fetch: input.fetch };
357
528
  const changelogAA = diff(originalDocument, newDocumentOrigin);
358
529
  if (changelogAA.length === 0) {
359
- return;
530
+ return {
531
+ ok: false,
532
+ type: "NO_CHANGES_DETECTED",
533
+ message: `No changes detected for document '${name}' after fetching the latest version.`
534
+ };
360
535
  }
361
536
  const changelogAB = diff(originalDocument, intermediateDocument);
362
537
  const changesA = merge(changelogAA, changelogAB);
363
- if (resolvedConflicts === void 0) {
364
- return changesA.conflicts;
365
- }
366
- const changesetA = changesA.diffs.concat(resolvedConflicts);
367
- const newIntermediateDocument = apply(deepClone(originalDocument), changesetA);
368
- intermediateDocuments[name] = newIntermediateDocument;
369
- originalDocuments[name] = newDocumentOrigin;
370
- const changelogBA = diff(intermediateDocument, newIntermediateDocument);
371
- const changelogBB = diff(intermediateDocument, activeDocument);
372
- const changesB = merge(changelogBA, changelogBB);
373
- const changesetB = changesB.diffs.concat(changesB.conflicts.flatMap((it) => it[0]));
374
- const newActiveDocument = coerceValue(
375
- OpenAPIDocumentSchemaStrict,
376
- apply(deepClone(newIntermediateDocument), changesetB)
377
- );
378
- await addInMemoryDocument({
379
- ...input,
380
- document: {
381
- ...newActiveDocument,
382
- // force regeneration of navigation
383
- // when we are rebasing, we want to ensure that the navigation is always up to date
384
- [extensions.document.navigation]: void 0
385
- },
386
- documentSource,
387
- initialize: false
388
- });
389
- return;
538
+ return {
539
+ ok: true,
540
+ conflicts: changesA.conflicts,
541
+ applyChanges: async (resolvedConflicts) => {
542
+ const changesetA = changesA.diffs.concat(resolvedConflicts);
543
+ const newIntermediateDocument = apply(deepClone(originalDocument), changesetA);
544
+ intermediateDocuments[name] = newIntermediateDocument;
545
+ originalDocuments[name] = newDocumentOrigin;
546
+ const changelogBA = diff(intermediateDocument, newIntermediateDocument);
547
+ const changelogBB = diff(intermediateDocument, activeDocument);
548
+ const changesB = merge(changelogBA, changelogBB);
549
+ const changesetB = changesB.diffs.concat(changesB.conflicts.flatMap((it) => it[0]));
550
+ const newActiveDocument = coerceValue(
551
+ OpenAPIDocumentSchemaStrict,
552
+ apply(deepClone(newIntermediateDocument), changesetB)
553
+ );
554
+ await addInMemoryDocument({
555
+ ...input,
556
+ document: {
557
+ ...newActiveDocument,
558
+ // force regeneration of navigation
559
+ // when we are rebasing, we want to ensure that the navigation is always up to date
560
+ [extensions.document.navigation]: void 0
561
+ },
562
+ documentSource: getDocumentSource(input),
563
+ // Update the original document hash
564
+ documentHash: await generateHash(resolve.raw),
565
+ initialize: false
566
+ });
567
+ }
568
+ };
390
569
  }
391
570
  };
392
571
  };