@scalar/workspace-store 0.63.0 → 0.65.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.
- package/CHANGELOG.md +110 -0
- package/README.md +123 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +320 -144
- package/dist/entities/auth/schema.d.ts +236 -0
- package/dist/entities/auth/schema.d.ts.map +1 -1
- package/dist/entities/auth/schema.js +3 -1
- package/dist/helpers/chunk-index.d.ts +113 -0
- package/dist/helpers/chunk-index.d.ts.map +1 -0
- package/dist/helpers/chunk-index.js +152 -0
- package/dist/helpers/external-examples.d.ts +19 -0
- package/dist/helpers/external-examples.d.ts.map +1 -0
- package/dist/helpers/external-examples.js +51 -0
- package/dist/helpers/for-each-path-item-operation.js +4 -4
- package/dist/helpers/get-example-value.d.ts +15 -0
- package/dist/helpers/get-example-value.d.ts.map +1 -0
- package/dist/helpers/get-example-value.js +28 -0
- package/dist/helpers/get-resolved-ref.d.ts.map +1 -1
- package/dist/helpers/get-resolved-ref.js +12 -1
- package/dist/helpers/normalize-boolean-schemas.d.ts +10 -0
- package/dist/helpers/normalize-boolean-schemas.d.ts.map +1 -0
- package/dist/helpers/normalize-boolean-schemas.js +193 -0
- package/dist/helpers/operation-examples.d.ts +6 -0
- package/dist/helpers/operation-examples.d.ts.map +1 -0
- package/dist/helpers/operation-examples.js +48 -0
- package/dist/helpers/serialize-stream-example.d.ts +8 -0
- package/dist/helpers/serialize-stream-example.d.ts.map +1 -0
- package/dist/helpers/serialize-stream-example.js +52 -0
- package/dist/helpers/use-external-examples.d.ts +15 -0
- package/dist/helpers/use-external-examples.d.ts.map +1 -0
- package/dist/helpers/use-external-examples.js +62 -0
- package/dist/mutators/index.d.ts +3 -3
- package/dist/mutators/operation/body.d.ts.map +1 -1
- package/dist/mutators/operation/body.js +9 -2
- package/dist/mutators/operation/parameters.d.ts.map +1 -1
- package/dist/mutators/operation/parameters.js +28 -9
- package/dist/plugins/bundler/index.d.ts +6 -2
- package/dist/plugins/bundler/index.d.ts.map +1 -1
- package/dist/plugins/bundler/index.js +20 -4
- package/dist/plugins/bundler/openapi-document.d.ts +6 -0
- package/dist/plugins/bundler/openapi-document.d.ts.map +1 -0
- package/dist/plugins/bundler/openapi-document.js +57 -0
- package/dist/request-example/builder/body/build-multipart.d.ts +29 -0
- package/dist/request-example/builder/body/build-multipart.d.ts.map +1 -0
- package/dist/request-example/builder/body/build-multipart.js +191 -0
- package/dist/request-example/builder/body/build-request-body.d.ts +6 -1
- package/dist/request-example/builder/body/build-request-body.d.ts.map +1 -1
- package/dist/request-example/builder/body/build-request-body.js +80 -23
- package/dist/request-example/builder/body/encode-multipart-body.d.ts +8 -8
- package/dist/request-example/builder/body/encode-multipart-body.d.ts.map +1 -1
- package/dist/request-example/builder/body/encode-multipart-body.js +56 -18
- package/dist/request-example/builder/body/get-request-body-example.d.ts.map +1 -1
- package/dist/request-example/builder/body/get-request-body-example.js +20 -6
- package/dist/request-example/builder/body/multipart-limits.d.ts +3 -0
- package/dist/request-example/builder/body/multipart-limits.d.ts.map +1 -0
- package/dist/request-example/builder/body/multipart-limits.js +2 -0
- package/dist/request-example/builder/body/serialize-form-property.d.ts +2 -0
- package/dist/request-example/builder/body/serialize-form-property.d.ts.map +1 -1
- package/dist/request-example/builder/body/serialize-form-property.js +3 -2
- package/dist/request-example/builder/body/serialize-multipart-array.d.ts +1 -1
- package/dist/request-example/builder/build-request.js +5 -0
- package/dist/request-example/builder/header/build-request-parameters.d.ts.map +1 -1
- package/dist/request-example/builder/header/build-request-parameters.js +5 -1
- package/dist/request-example/builder/header/is-param-disabled.d.ts +2 -2
- package/dist/request-example/builder/header/is-param-disabled.d.ts.map +1 -1
- package/dist/request-example/builder/header/is-param-disabled.js +5 -4
- package/dist/request-example/builder/header/serialize-parameter.d.ts +11 -0
- package/dist/request-example/builder/header/serialize-parameter.d.ts.map +1 -1
- package/dist/request-example/builder/header/serialize-parameter.js +22 -0
- package/dist/request-example/builder/helpers/get-example-from-schema.d.ts.map +1 -1
- package/dist/request-example/builder/helpers/get-example-from-schema.js +42 -3
- package/dist/request-example/builder/helpers/get-example.d.ts +2 -0
- package/dist/request-example/builder/helpers/get-example.d.ts.map +1 -1
- package/dist/request-example/builder/helpers/get-example.js +14 -9
- package/dist/request-example/builder/index.d.ts +2 -2
- package/dist/request-example/builder/index.d.ts.map +1 -1
- package/dist/request-example/builder/index.js +1 -1
- package/dist/request-example/builder/security/secret-types.d.ts +4 -1
- package/dist/request-example/builder/security/secret-types.d.ts.map +1 -1
- package/dist/request-example/context/headers.d.ts.map +1 -1
- package/dist/request-example/context/headers.js +3 -4
- package/dist/request-example/context/security/extract-security-scheme-secrets.d.ts.map +1 -1
- package/dist/request-example/context/security/extract-security-scheme-secrets.js +15 -0
- package/dist/request-example/index.d.ts +5 -2
- package/dist/request-example/index.d.ts.map +1 -1
- package/dist/request-example/index.js +4 -1
- package/dist/request-example/xml/serialize-xml-part.d.ts +9 -0
- package/dist/request-example/xml/serialize-xml-part.d.ts.map +1 -0
- package/dist/request-example/xml/serialize-xml-part.js +12 -0
- package/dist/schemas/extensions.d.ts +9 -0
- package/dist/schemas/extensions.d.ts.map +1 -1
- package/dist/schemas/extensions.js +9 -0
- package/dist/schemas/reference-config/index.d.ts +1 -0
- package/dist/schemas/reference-config/index.d.ts.map +1 -1
- package/dist/schemas/reference-config/settings.d.ts +1 -0
- package/dist/schemas/reference-config/settings.d.ts.map +1 -1
- package/dist/schemas/v3.1/strict/oauthflows.d.ts +26 -0
- package/dist/schemas/v3.1/strict/oauthflows.d.ts.map +1 -1
- package/dist/schemas/v3.1/strict/oauthflows.js +3 -0
- package/dist/schemas/v3.1/strict/openapi-document.d.ts +812 -7
- package/dist/schemas/v3.1/strict/openapi-document.d.ts.map +1 -1
- package/dist/schemas/v3.2/openapi/index.d.ts +5 -0
- package/dist/schemas/v3.2/openapi/index.d.ts.map +1 -1
- package/dist/schemas/v3.2/openapi/index.js +46 -12
- package/dist/schemas/v3.2/strict/openapi-document.d.ts +37 -0
- package/dist/schemas/v3.2/strict/openapi-document.d.ts.map +1 -1
- package/dist/schemas/v3.2/strict/openapi-document.js +8 -0
- package/dist/server.d.ts +21 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +60 -34
- package/package.json +19 -9
- package/dist/schemas/v3.1/openapi/index.d.ts +0 -149
- package/dist/schemas/v3.1/openapi/index.d.ts.map +0 -1
- package/dist/schemas/v3.1/openapi/index.js +0 -732
- package/dist/schemas/v3.1/openapi/reference.d.ts +0 -4
- package/dist/schemas/v3.1/openapi/reference.d.ts.map +0 -1
- package/dist/schemas/v3.1/openapi/reference.js +0 -29
package/dist/client.js
CHANGED
|
@@ -16,16 +16,20 @@ import { reactive } from 'vue';
|
|
|
16
16
|
import YAML from 'yaml';
|
|
17
17
|
import { createAuthStore } from './entities/auth/index.js';
|
|
18
18
|
import { createHistoryStore } from './entities/history/index.js';
|
|
19
|
+
import { chunkReference, expandChunkIndex } from './helpers/chunk-index.js';
|
|
19
20
|
import { deepClone } from './helpers/deep-clone.js';
|
|
20
21
|
import { createDetectChangesProxy } from './helpers/detect-changes-proxy.js';
|
|
21
22
|
import { bumpDocumentRevision } from './helpers/document-revision.js';
|
|
23
|
+
import { createExternalExampleResolver } from './helpers/external-examples.js';
|
|
22
24
|
import { safeAssign } from './helpers/general.js';
|
|
23
25
|
import { getFetch } from './helpers/get-fetch.js';
|
|
26
|
+
import { getResolvedRef } from './helpers/get-resolved-ref.js';
|
|
24
27
|
import { mergeObjects } from './helpers/merge-object.js';
|
|
28
|
+
import { normalizeBooleanSchemas } from './helpers/normalize-boolean-schemas.js';
|
|
25
29
|
import { createOverridesProxy } from './helpers/overrides-proxy.js';
|
|
26
30
|
import { unpackProxyObject } from './helpers/unpack-proxy.js';
|
|
27
31
|
import { createNavigation, traverseAsyncApiDocument } from './navigation/index.js';
|
|
28
|
-
import { externalValueResolver, loadingStatus, normalizeAuthSchemes, normalizeRefs, refsEverywhere, removeExtraScalarKeys, restoreOriginalRefs, syncPathParameters, } from './plugins/bundler/index.js';
|
|
32
|
+
import { externalValueResolver, loadingStatus, normalizeAuthSchemes, normalizeRefs, openApiDocument, refsEverywhere, removeExtraScalarKeys, resolveOpenApiDocument, restoreOriginalRefs, syncPathParameters, } from './plugins/bundler/index.js';
|
|
29
33
|
import { extensions } from './schemas/extensions.js';
|
|
30
34
|
import { isAsyncApiDocument, isOpenApiDocument } from './schemas/type-guards.js';
|
|
31
35
|
import { generateSchema } from './schemas/v3.2/openapi/index.js';
|
|
@@ -129,8 +133,10 @@ const purgeInternalDocumentKeys = (input) => {
|
|
|
129
133
|
// Bundler metadata fields added temporarily during document processing
|
|
130
134
|
'x-ext',
|
|
131
135
|
'x-ext-urls',
|
|
136
|
+
'x-scalar-original-refs',
|
|
132
137
|
// Scalar internal/external metadata fields
|
|
133
138
|
'x-scalar-navigation',
|
|
139
|
+
'x-scalar-navigation-chunk',
|
|
134
140
|
'x-scalar-is-dirty',
|
|
135
141
|
'x-original-oas-version',
|
|
136
142
|
'x-scalar-original-document-hash',
|
|
@@ -156,7 +162,7 @@ const purgeInternalDocumentKeys = (input) => {
|
|
|
156
162
|
* @returns An object containing methods and getters for managing the workspace
|
|
157
163
|
*/
|
|
158
164
|
export const createWorkspaceStore = (workspaceProps) => {
|
|
159
|
-
const { verbose = false } = workspaceProps ?? {};
|
|
165
|
+
const { verbose = false, reactive: isReactiveWorkspace = true } = workspaceProps ?? {};
|
|
160
166
|
const withMeasurementSync = (name, fn) => (verbose ? measureSync(name, fn) : fn());
|
|
161
167
|
const withMeasurementAsync = (name, fn) => verbose ? measureAsync(name, fn) : fn();
|
|
162
168
|
/**
|
|
@@ -165,6 +171,11 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
165
171
|
* This can include settings that can not be persisted between sessions (not JSON serializable)
|
|
166
172
|
*/
|
|
167
173
|
const extraDocumentConfigurations = {};
|
|
174
|
+
const externalExampleResolvers = new WeakMap();
|
|
175
|
+
const fallbackExternalExamples = createExternalExampleResolver({
|
|
176
|
+
fetch: workspaceProps?.fetch,
|
|
177
|
+
fileLoader: workspaceProps?.fileLoader,
|
|
178
|
+
});
|
|
168
179
|
/**
|
|
169
180
|
* Notifies all workspace plugins of a workspace state change event.
|
|
170
181
|
*
|
|
@@ -177,20 +188,9 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
177
188
|
workspaceProps?.plugins?.forEach((plugin) => plugin.hooks?.onWorkspaceStateChanges?.(event));
|
|
178
189
|
};
|
|
179
190
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* Every change to the workspace, is tracked and broadcast to all registered plugins.
|
|
183
|
-
* allowing for change tracking.
|
|
184
|
-
*
|
|
185
|
-
* NOTE:
|
|
186
|
-
* The detect changes proxy is applied separately beacause the vue reactitvity proxy have to be the outer most proxy.
|
|
187
|
-
* If the order is reversed, Vue cannot properly track mutations, leading to lost reactivity and bugs.
|
|
188
|
-
* By wrapping the contents with the detect changes proxy first, and then passing the result to Vue's `reactive`,
|
|
189
|
-
* we ensure that Vue manages its reactivity as expected and our change detection hooks
|
|
190
|
-
* are also triggered reliably.
|
|
191
|
-
* Do not reverse this order‼️
|
|
191
|
+
* The plain workspace state, before any observability wrappers are applied.
|
|
192
192
|
*/
|
|
193
|
-
const
|
|
193
|
+
const workspaceState = {
|
|
194
194
|
...workspaceProps?.meta,
|
|
195
195
|
documents: {},
|
|
196
196
|
/**
|
|
@@ -203,88 +203,105 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
203
203
|
get activeDocument() {
|
|
204
204
|
return workspace.documents[getActiveDocumentName()];
|
|
205
205
|
},
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* An object containing the reactive workspace state.
|
|
209
|
+
*
|
|
210
|
+
* Every change to the workspace, is tracked and broadcast to all registered plugins.
|
|
211
|
+
* allowing for change tracking.
|
|
212
|
+
*
|
|
213
|
+
* With `reactive: false` the state is used as-is, so reads cost nothing beyond the plain object and
|
|
214
|
+
* nothing observes a write. See the `reactive` option for the full list of what stops happening.
|
|
215
|
+
*
|
|
216
|
+
* NOTE:
|
|
217
|
+
* The detect changes proxy is applied separately beacause the vue reactitvity proxy have to be the outer most proxy.
|
|
218
|
+
* If the order is reversed, Vue cannot properly track mutations, leading to lost reactivity and bugs.
|
|
219
|
+
* By wrapping the contents with the detect changes proxy first, and then passing the result to Vue's `reactive`,
|
|
220
|
+
* we ensure that Vue manages its reactivity as expected and our change detection hooks
|
|
221
|
+
* are also triggered reliably.
|
|
222
|
+
* Do not reverse this order‼️
|
|
223
|
+
*/
|
|
224
|
+
const workspace = !isReactiveWorkspace
|
|
225
|
+
? workspaceState
|
|
226
|
+
: reactive(createDetectChangesProxy(workspaceState, {
|
|
227
|
+
hooks: {
|
|
228
|
+
onAfterChange(path) {
|
|
229
|
+
const type = path[0];
|
|
230
|
+
/** Document changes */
|
|
231
|
+
if (type === 'documents') {
|
|
232
|
+
// We are overriding the while documents object, ignore. This should not happen
|
|
233
|
+
if (path.length < 2) {
|
|
234
|
+
console.log('[WARN]: Overriding entire documents object is not supported');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const documentName = path[1];
|
|
238
|
+
const document = workspace.documents[documentName] ?? {
|
|
239
|
+
openapi: '3.1.0',
|
|
240
|
+
info: { title: '', version: '' },
|
|
241
|
+
'x-scalar-original-document-hash': '',
|
|
242
|
+
};
|
|
243
|
+
// Every write through the store passes here, which is what makes the revision a
|
|
244
|
+
// complete record of the document changing.
|
|
245
|
+
bumpDocumentRevision(document);
|
|
246
|
+
const event = {
|
|
247
|
+
type: 'documents',
|
|
248
|
+
documentName,
|
|
249
|
+
value: unpackProxyObject(document),
|
|
250
|
+
path: path.slice(2),
|
|
251
|
+
};
|
|
252
|
+
// Don't mark as dirty when the document is first created or when
|
|
253
|
+
// only metadata-only fields change. `x-scalar-registry-meta` is
|
|
254
|
+
// updated programmatically (commit hash, conflict cache) and
|
|
255
|
+
// does not represent a user edit.
|
|
256
|
+
if (event.path.length > 0 && !METADATA_ONLY_DOCUMENT_KEYS.has(event.path[0])) {
|
|
257
|
+
// The document has been modified since it was last saved
|
|
258
|
+
document['x-scalar-is-dirty'] = true;
|
|
259
|
+
}
|
|
260
|
+
fireWorkspaceChange(event);
|
|
215
261
|
return;
|
|
216
262
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
263
|
+
/** Active document changes */
|
|
264
|
+
if (type === 'activeDocument') {
|
|
265
|
+
const documentName = getActiveDocumentName();
|
|
266
|
+
const document = workspace.documents[documentName] ?? {
|
|
267
|
+
openapi: '3.1.0',
|
|
268
|
+
info: { title: '', version: '' },
|
|
269
|
+
'x-scalar-original-document-hash': '',
|
|
270
|
+
};
|
|
271
|
+
bumpDocumentRevision(document);
|
|
272
|
+
// Active document changed
|
|
273
|
+
const event = {
|
|
274
|
+
type: 'documents',
|
|
275
|
+
documentName,
|
|
276
|
+
value: unpackProxyObject(document),
|
|
277
|
+
path: path.slice(2),
|
|
278
|
+
};
|
|
279
|
+
// Don't mark as dirty when the document is first created or when
|
|
280
|
+
// only metadata-only fields change. `x-scalar-registry-meta` is
|
|
281
|
+
// updated programmatically (commit hash, conflict cache) and
|
|
282
|
+
// does not represent a user edit.
|
|
283
|
+
if (event.path.length > 0 && !METADATA_ONLY_DOCUMENT_KEYS.has(event.path[0])) {
|
|
284
|
+
// The document has been modified since it was last saved
|
|
285
|
+
document['x-scalar-is-dirty'] = true;
|
|
286
|
+
}
|
|
287
|
+
fireWorkspaceChange(event);
|
|
288
|
+
return;
|
|
239
289
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
/** Active document changes */
|
|
244
|
-
if (type === 'activeDocument') {
|
|
245
|
-
const documentName = getActiveDocumentName();
|
|
246
|
-
const document = workspace.documents[documentName] ?? {
|
|
247
|
-
openapi: '3.1.0',
|
|
248
|
-
info: { title: '', version: '' },
|
|
249
|
-
'x-scalar-original-document-hash': '',
|
|
250
|
-
};
|
|
251
|
-
bumpDocumentRevision(document);
|
|
252
|
-
// Active document changed
|
|
290
|
+
/** Workspace meta changes */
|
|
291
|
+
const { activeDocument: _a, documents: _d, ...meta } = workspace;
|
|
253
292
|
const event = {
|
|
254
|
-
type: '
|
|
255
|
-
|
|
256
|
-
value: unpackProxyObject(document),
|
|
257
|
-
path: path.slice(2),
|
|
293
|
+
type: 'meta',
|
|
294
|
+
value: unpackProxyObject(meta, { depth: 1 }),
|
|
258
295
|
};
|
|
259
|
-
// Don't mark as dirty when the document is first created or when
|
|
260
|
-
// only metadata-only fields change. `x-scalar-registry-meta` is
|
|
261
|
-
// updated programmatically (commit hash, conflict cache) and
|
|
262
|
-
// does not represent a user edit.
|
|
263
|
-
if (event.path.length > 0 && !METADATA_ONLY_DOCUMENT_KEYS.has(event.path[0])) {
|
|
264
|
-
// The document has been modified since it was last saved
|
|
265
|
-
document['x-scalar-is-dirty'] = true;
|
|
266
|
-
}
|
|
267
296
|
fireWorkspaceChange(event);
|
|
268
297
|
return;
|
|
269
|
-
}
|
|
270
|
-
/** Workspace meta changes */
|
|
271
|
-
const { activeDocument: _a, documents: _d, ...meta } = workspace;
|
|
272
|
-
const event = {
|
|
273
|
-
type: 'meta',
|
|
274
|
-
value: unpackProxyObject(meta, { depth: 1 }),
|
|
275
|
-
};
|
|
276
|
-
fireWorkspaceChange(event);
|
|
277
|
-
return;
|
|
298
|
+
},
|
|
278
299
|
},
|
|
279
|
-
}
|
|
280
|
-
}));
|
|
300
|
+
}));
|
|
281
301
|
/**
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* Every change to the workspace state (documents, configs, metadata, etc.) can be detected here,
|
|
285
|
-
* allowing for change tracking.
|
|
302
|
+
* The plain document snapshot maps, before the detect changes proxy is applied.
|
|
286
303
|
*/
|
|
287
|
-
const
|
|
304
|
+
const documentSnapshots = {
|
|
288
305
|
/**
|
|
289
306
|
* Holds the original, unmodified documents as they were initially loaded into the workspace.
|
|
290
307
|
* These documents are stored in their raw form—prior to any reactive wrapping, dereferencing, or bundling.
|
|
@@ -316,46 +333,57 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
316
333
|
* OpenAPI document representing the overridden fields.
|
|
317
334
|
*/
|
|
318
335
|
overrides: {},
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* An object containing all the workspace state, wrapped in a detect changes proxy.
|
|
339
|
+
*
|
|
340
|
+
* Every change to the workspace state (documents, configs, metadata, etc.) can be detected here,
|
|
341
|
+
* allowing for change tracking.
|
|
342
|
+
*
|
|
343
|
+
* With `reactive: false` the maps are used as-is and a write to them notifies nobody.
|
|
344
|
+
*/
|
|
345
|
+
const { originalDocuments, intermediateDocuments, overrides } = !isReactiveWorkspace
|
|
346
|
+
? documentSnapshots
|
|
347
|
+
: createDetectChangesProxy(documentSnapshots, {
|
|
348
|
+
hooks: {
|
|
349
|
+
onAfterChange(path) {
|
|
350
|
+
const type = path[0];
|
|
351
|
+
if (!type) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (path.length < 2) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const documentName = path[1];
|
|
358
|
+
if (type === 'originalDocuments') {
|
|
359
|
+
const event = {
|
|
360
|
+
type,
|
|
361
|
+
documentName: documentName,
|
|
362
|
+
value: unpackProxyObject(originalDocuments[documentName] ?? {}),
|
|
363
|
+
path: path.splice(2),
|
|
364
|
+
};
|
|
365
|
+
fireWorkspaceChange(event);
|
|
366
|
+
}
|
|
367
|
+
if (type === 'intermediateDocuments') {
|
|
368
|
+
const event = {
|
|
369
|
+
type,
|
|
370
|
+
documentName: documentName,
|
|
371
|
+
value: unpackProxyObject(intermediateDocuments[documentName] ?? {}),
|
|
372
|
+
path: path.splice(2),
|
|
373
|
+
};
|
|
374
|
+
fireWorkspaceChange(event);
|
|
375
|
+
}
|
|
376
|
+
if (type === 'overrides') {
|
|
377
|
+
const event = {
|
|
378
|
+
type,
|
|
379
|
+
documentName: documentName,
|
|
380
|
+
value: unpackProxyObject(overrides[documentName] ?? {}),
|
|
381
|
+
};
|
|
382
|
+
fireWorkspaceChange(event);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
356
385
|
},
|
|
357
|
-
}
|
|
358
|
-
});
|
|
386
|
+
});
|
|
359
387
|
/**
|
|
360
388
|
* This store is used to track the history of requests and responses for documents and operations.
|
|
361
389
|
*/
|
|
@@ -387,6 +415,17 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
387
415
|
},
|
|
388
416
|
},
|
|
389
417
|
});
|
|
418
|
+
/**
|
|
419
|
+
* Whether a document needs to be wrapped in the overrides proxy.
|
|
420
|
+
*
|
|
421
|
+
* A reactive workspace always wraps, so the default mode keeps every document the same shape it has
|
|
422
|
+
* always had. A non-reactive workspace wraps only a document that actually has overrides: with an
|
|
423
|
+
* empty override map the proxy resolves to the target on every read and write anyway, so all it adds
|
|
424
|
+
* is a proxy hop on every nested read. Nothing outside this file reads the proxy's identity, and the
|
|
425
|
+
* store rebuilds the document from the current override map whenever the map changes, so a document
|
|
426
|
+
* that gains overrides later gains the proxy along with them.
|
|
427
|
+
*/
|
|
428
|
+
const needsOverridesProxy = (documentOverrides) => isReactiveWorkspace || (isObject(documentOverrides) && Object.keys(documentOverrides).length > 0);
|
|
390
429
|
/**
|
|
391
430
|
* Returns the name of the currently active document in the workspace.
|
|
392
431
|
* The active document is determined by the 'x-scalar-active-document' metadata field,
|
|
@@ -440,6 +479,10 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
440
479
|
const { name } = input;
|
|
441
480
|
const meta = deepClone(input.meta);
|
|
442
481
|
const clonedRawInputDocument = withMeasurementSync('deepClone', () => deepClone(input.document));
|
|
482
|
+
// A compact sparse document is expanded back into per-node chunk references before anything
|
|
483
|
+
// else reads it, so every step below — including the check for a server-generated navigation —
|
|
484
|
+
// sees the document a non-compact server store would have sent.
|
|
485
|
+
withMeasurementSync('expandChunkIndex', () => expandChunkIndex(clonedRawInputDocument));
|
|
443
486
|
withMeasurementSync('initialize', () => {
|
|
444
487
|
if (input.initialize !== false) {
|
|
445
488
|
// Store the original document in the originalDocuments map
|
|
@@ -502,9 +545,10 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
502
545
|
const navigation = traverseAsyncApiDocument(name, asyncApiDocument, navigationOptions);
|
|
503
546
|
asyncApiDocument[extensions.document.navigation] = navigation;
|
|
504
547
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
548
|
+
const asyncApiOverrides = unpackProxyObject(overrides[name]);
|
|
549
|
+
workspace.documents[name] = needsOverridesProxy(asyncApiOverrides)
|
|
550
|
+
? createOverridesProxy(asyncApiDocument, { overrides: asyncApiOverrides })
|
|
551
|
+
: asyncApiDocument;
|
|
508
552
|
return;
|
|
509
553
|
}
|
|
510
554
|
const inputDocument = withMeasurementSync('upgrade', () => upgrade(deepClone(clonedRawInputDocument), '3.1'));
|
|
@@ -514,7 +558,7 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
514
558
|
'x-original-oas-version': originalDocuments[name]?.openapi ?? originalDocuments[name]?.swagger,
|
|
515
559
|
'x-scalar-original-document-hash': input.documentHash,
|
|
516
560
|
'x-scalar-original-source-url': input.documentSource,
|
|
517
|
-
}, { showInternal: true });
|
|
561
|
+
}, { showInternal: true, documentUri: resolveOpenApiDocument(inputDocument, input.documentSource ?? '/')?.baseUri });
|
|
518
562
|
// If the document navigation is not already present, bundle the entire document to resolve all references.
|
|
519
563
|
// This typically applies when the document is not preprocessed by the server and needs local reference resolution.
|
|
520
564
|
// We need to bundle document first before we validate, so we can also validate the external references
|
|
@@ -523,8 +567,9 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
523
567
|
treeShake: false,
|
|
524
568
|
plugins: [
|
|
525
569
|
...loaders,
|
|
570
|
+
openApiDocument(),
|
|
526
571
|
normalizeRefs(),
|
|
527
|
-
externalValueResolver(),
|
|
572
|
+
externalValueResolver({ lazy: true }),
|
|
528
573
|
refsEverywhere(),
|
|
529
574
|
normalizeAuthSchemes(),
|
|
530
575
|
syncPathParameters(),
|
|
@@ -533,7 +578,7 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
533
578
|
origin: input.documentSource, // use the document origin (if provided) as the base URL for resolution
|
|
534
579
|
}));
|
|
535
580
|
// We coerce the values only when the document is not preprocessed by the server-side-store
|
|
536
|
-
const coerced = withMeasurementSync('coerceValue', () => coerce(openapiSchema, deepClone(strictDocument)));
|
|
581
|
+
const coerced = withMeasurementSync('coerceValue', () => coerce(openapiSchema, normalizeBooleanSchemas(deepClone(strictDocument))));
|
|
537
582
|
withMeasurementSync('mergeObjects', () => mergeObjects(strictDocument, coerced));
|
|
538
583
|
}
|
|
539
584
|
const isValid = Value.Check(OpenAPIDocumentSchemaStrict, strictDocument);
|
|
@@ -555,9 +600,13 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
555
600
|
// Create a proxied document with magic proxy and apply any overrides, then store it in the workspace documents map
|
|
556
601
|
// We create a new proxy here in order to hide internal properties after validation and processing
|
|
557
602
|
// This ensures that the workspace document only exposes the intended OpenAPI properties and extensions
|
|
558
|
-
|
|
559
|
-
|
|
603
|
+
const documentOverrides = unpackProxyObject(overrides[name]);
|
|
604
|
+
const magicDocument = createMagicProxy(getRaw(strictDocument), {
|
|
605
|
+
documentUri: resolveOpenApiDocument(getRaw(strictDocument), input.documentSource ?? '/')?.baseUri,
|
|
560
606
|
});
|
|
607
|
+
workspace.documents[name] = needsOverridesProxy(documentOverrides)
|
|
608
|
+
? createOverridesProxy(magicDocument, { overrides: documentOverrides })
|
|
609
|
+
: magicDocument;
|
|
561
610
|
}
|
|
562
611
|
// Asynchronously adds a new document to the workspace by loading and validating the input.
|
|
563
612
|
// If loading fails, a placeholder error document is added instead.
|
|
@@ -650,9 +699,11 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
650
699
|
// If the document does not exist, return null
|
|
651
700
|
return null;
|
|
652
701
|
}
|
|
653
|
-
//
|
|
702
|
+
// This is the shared cleanup boundary for editing and saving. Both JSON and YAML
|
|
703
|
+
// exports read the cleaned saved baseline, so serializers need no marker filtering.
|
|
704
|
+
// Reverse all external references and restore original $refs.
|
|
654
705
|
const original = (await bundle(deepClone(rawDocument), {
|
|
655
|
-
plugins: [restoreOriginalRefs(), removeExtraScalarKeys()],
|
|
706
|
+
plugins: [openApiDocument(), restoreOriginalRefs(), removeExtraScalarKeys()],
|
|
656
707
|
treeShake: false,
|
|
657
708
|
urlMap: true,
|
|
658
709
|
}));
|
|
@@ -688,10 +739,106 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
688
739
|
document[extensions.document.navigation] = navigation;
|
|
689
740
|
return true;
|
|
690
741
|
};
|
|
742
|
+
/**
|
|
743
|
+
* Fetches the chunk a compact document keeps its navigation children in.
|
|
744
|
+
*
|
|
745
|
+
* The reference is bundled on an object of its own rather than in the document, so nothing of it
|
|
746
|
+
* is left behind: the chunk lands in that object's `x-ext` and is dropped along with it, and the
|
|
747
|
+
* navigation is never a reference, not even while the request is in flight. Navigation has one
|
|
748
|
+
* owner, so the children go on the document as they are and there is nothing for a shared `x-ext`
|
|
749
|
+
* entry to save.
|
|
750
|
+
*
|
|
751
|
+
* `depth: 0` stops the bundler at the reference itself, which is as far as a navigation chunk
|
|
752
|
+
* goes: its entries address the document through their own `ref` strings and carry no `$ref`.
|
|
753
|
+
*
|
|
754
|
+
* The chunk is handed back unwrapped: what it holds goes on the document, and a magic proxy there
|
|
755
|
+
* would enumerate a virtual `$ref-value` and stop the document being structured-cloned into
|
|
756
|
+
* storage. Unwrapping one level is enough, because the proxy wraps lazily.
|
|
757
|
+
*
|
|
758
|
+
* @returns The navigation the chunk holds, or `undefined` when it could not be loaded — the
|
|
759
|
+
* bundler reports that failure the same way it reports an unreachable component chunk.
|
|
760
|
+
*/
|
|
761
|
+
const fetchNavigationChunk = async (documentName, ref, origin) => {
|
|
762
|
+
const holder = createMagicProxy(chunkReference(ref));
|
|
763
|
+
await bundle(getRaw(holder), {
|
|
764
|
+
plugins: [
|
|
765
|
+
fetchUrls({
|
|
766
|
+
fetch: extraDocumentConfigurations[documentName]?.fetch ?? workspaceProps?.fetch,
|
|
767
|
+
limit: EXTERNAL_FETCH_CONCURRENCY_LIMIT,
|
|
768
|
+
}),
|
|
769
|
+
...(workspaceProps?.fileLoader ? [workspaceProps.fileLoader] : []),
|
|
770
|
+
],
|
|
771
|
+
treeShake: false,
|
|
772
|
+
origin,
|
|
773
|
+
depth: 0,
|
|
774
|
+
urlMap: true,
|
|
775
|
+
});
|
|
776
|
+
return getRaw(getResolvedRef(holder));
|
|
777
|
+
};
|
|
778
|
+
/** Share concurrent requests only within the same document instance, including after a workspace reload. */
|
|
779
|
+
const navigationChildrenLoads = new WeakMap();
|
|
780
|
+
/**
|
|
781
|
+
* Loads a compact document's navigation children and assigns them onto its navigation in place.
|
|
782
|
+
*
|
|
783
|
+
* Only a compact document carries `x-scalar-navigation-chunk`, and only until its children are
|
|
784
|
+
* loaded, so the key answers both "is there anything to load" and "has it already happened". It
|
|
785
|
+
* travels with the document, which is what lets a workspace exported before the children were
|
|
786
|
+
* loaded still load them once it has been imported into another store. A failed load leaves the
|
|
787
|
+
* key in place, so asking again retries.
|
|
788
|
+
*/
|
|
789
|
+
const loadNavigationChildren = async (documentName) => {
|
|
790
|
+
const document = workspace.documents[documentName];
|
|
791
|
+
if (!isOpenApiDocument(document)) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const ref = document[extensions.document.navigationChunk];
|
|
795
|
+
const navigation = document[extensions.document.navigation];
|
|
796
|
+
if (ref === undefined || navigation === undefined) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const pending = navigationChildrenLoads.get(document);
|
|
800
|
+
if (pending) {
|
|
801
|
+
return pending;
|
|
802
|
+
}
|
|
803
|
+
const load = (async () => {
|
|
804
|
+
const chunk = await fetchNavigationChunk(documentName, ref, document['x-scalar-original-source-url']);
|
|
805
|
+
// A replaced or deleted document must not publish changes from a stale request.
|
|
806
|
+
if (chunk === undefined || workspace.documents[documentName] !== document) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
// Assigned through the store's document, so the write is observed: a Vue effect reading the
|
|
810
|
+
// children re-runs, and the workspace plugins see the document change.
|
|
811
|
+
navigation.children = chunk.children ?? [];
|
|
812
|
+
delete document[extensions.document.navigationChunk];
|
|
813
|
+
})();
|
|
814
|
+
navigationChildrenLoads.set(document, load);
|
|
815
|
+
try {
|
|
816
|
+
await load;
|
|
817
|
+
}
|
|
818
|
+
finally {
|
|
819
|
+
navigationChildrenLoads.delete(document);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
691
822
|
// Cache to track visited nodes during reference resolution to prevent bundling the same subtree multiple times
|
|
692
823
|
// This is needed because we are doing partial bundle operations
|
|
693
824
|
const visitedNodesCache = new Set();
|
|
694
825
|
return {
|
|
826
|
+
externalExamples: (documentName) => {
|
|
827
|
+
const name = documentName ?? getActiveDocumentName();
|
|
828
|
+
const document = workspace.documents[name];
|
|
829
|
+
if (!document)
|
|
830
|
+
return fallbackExternalExamples;
|
|
831
|
+
const existing = externalExampleResolvers.get(document);
|
|
832
|
+
if (existing)
|
|
833
|
+
return existing;
|
|
834
|
+
const resolver = createExternalExampleResolver({
|
|
835
|
+
origin: document['x-scalar-original-source-url'],
|
|
836
|
+
fileLoader: workspaceProps?.fileLoader,
|
|
837
|
+
fetch: extraDocumentConfigurations[name]?.fetch ?? workspaceProps?.fetch,
|
|
838
|
+
});
|
|
839
|
+
externalExampleResolvers.set(document, resolver);
|
|
840
|
+
return resolver;
|
|
841
|
+
},
|
|
695
842
|
get workspace() {
|
|
696
843
|
return workspace;
|
|
697
844
|
},
|
|
@@ -743,8 +890,15 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
743
890
|
initialize: false,
|
|
744
891
|
});
|
|
745
892
|
},
|
|
746
|
-
resolve: (path) => {
|
|
893
|
+
resolve: async (path) => {
|
|
747
894
|
const activeDocument = workspace.activeDocument;
|
|
895
|
+
// The navigation is never a reference: a compact document externalizes its children alone, and
|
|
896
|
+
// loading them is all there is to resolve here. Everything a navigation entry points at, it
|
|
897
|
+
// points at with a `ref` string of its own, so the bundler has nothing left to follow.
|
|
898
|
+
if (path[0] === extensions.document.navigation) {
|
|
899
|
+
await loadNavigationChildren(getActiveDocumentName());
|
|
900
|
+
return getValueAtPath(activeDocument, path);
|
|
901
|
+
}
|
|
748
902
|
const target = getValueAtPath(activeDocument, path);
|
|
749
903
|
if (!isObject(target)) {
|
|
750
904
|
console.error(`Invalid path provided for resolution. Path: [${path.join(', ')}]. Found value of type: ${typeof target}. Expected an object.`);
|
|
@@ -761,7 +915,16 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
761
915
|
root: activeDocument,
|
|
762
916
|
origin: activeDocument?.['x-scalar-original-source-url'],
|
|
763
917
|
treeShake: false,
|
|
764
|
-
plugins: [
|
|
918
|
+
plugins: [
|
|
919
|
+
openApiDocument(),
|
|
920
|
+
fetchUrls({
|
|
921
|
+
fetch: extraDocumentConfigurations[getActiveDocumentName()]?.fetch ?? workspaceProps?.fetch,
|
|
922
|
+
limit: EXTERNAL_FETCH_CONCURRENCY_LIMIT,
|
|
923
|
+
}),
|
|
924
|
+
...(workspaceProps?.fileLoader ? [workspaceProps.fileLoader] : []),
|
|
925
|
+
loadingStatus(),
|
|
926
|
+
externalValueResolver({ lazy: true }),
|
|
927
|
+
],
|
|
765
928
|
urlMap: true,
|
|
766
929
|
visitedNodes: visitedNodesCache,
|
|
767
930
|
});
|
|
@@ -855,12 +1018,25 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
855
1018
|
};
|
|
856
1019
|
},
|
|
857
1020
|
loadWorkspace(input) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
])
|
|
1021
|
+
// A workspace from a compact server store carries an index rather than per-node chunk
|
|
1022
|
+
// references; expanded here for the same reason `addInMemoryDocument` expands.
|
|
1023
|
+
for (const document of Object.values(input.documents)) {
|
|
1024
|
+
expandChunkIndex(document);
|
|
1025
|
+
}
|
|
1026
|
+
safeAssign(workspace.documents, Object.fromEntries(Object.entries(input.documents).map(([name, doc]) => {
|
|
1027
|
+
// Hydration only rewraps: an exported document has already been upgraded, bundled, coerced
|
|
1028
|
+
// and given its navigation, so nothing here re-processes it.
|
|
1029
|
+
const magicDocument = createMagicProxy(doc, {
|
|
1030
|
+
documentUri: resolveOpenApiDocument(doc, doc['x-scalar-original-source-url'] ?? '/')?.baseUri,
|
|
1031
|
+
});
|
|
1032
|
+
const documentOverrides = input.overrides[name];
|
|
1033
|
+
return [
|
|
1034
|
+
name,
|
|
1035
|
+
needsOverridesProxy(documentOverrides)
|
|
1036
|
+
? createOverridesProxy(magicDocument, { overrides: documentOverrides })
|
|
1037
|
+
: magicDocument,
|
|
1038
|
+
];
|
|
1039
|
+
})));
|
|
864
1040
|
safeAssign(originalDocuments, input.originalDocuments);
|
|
865
1041
|
safeAssign(intermediateDocuments, input.intermediateDocuments);
|
|
866
1042
|
safeAssign(overrides, input.overrides);
|