@scalar/workspace-store 0.49.1 → 0.49.3

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @scalar/workspace-store
2
2
 
3
+ ## 0.49.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#9054](https://github.com/scalar/scalar/pull/9054): fix multipart/form-data request body generation for schema-derived object examples
8
+
9
+ ## 0.49.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [#8921](https://github.com/scalar/scalar/pull/8921): Prevent infinite recursion when validating defaults on circular composed schemas.
14
+ - [#9083](https://github.com/scalar/scalar/pull/9083): fix: prevent proxy leak into document meta breaking IndexedDB persistence
15
+ - [#9059](https://github.com/scalar/scalar/pull/9059): feat: added the scalar-app back to open source
16
+ - [#8921](https://github.com/scalar/scalar/pull/8921): fix nullable numeric schema defaults so empty-string defaults are normalized to null in generated request body examples.
17
+ - [#9050](https://github.com/scalar/scalar/pull/9050): chore: depricate intermediate document state
18
+
3
19
  ## 0.49.1
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -7,6 +7,7 @@ A powerful data store for managing OpenAPI documents. This package provides a fl
7
7
  Server side data store which enables document chunking to reduce initial loading time specially when working with large openapi documents
8
8
 
9
9
  #### Usage
10
+
10
11
  Create a new store in SSR mode
11
12
 
12
13
  ```ts
@@ -44,29 +45,32 @@ const store = await createServerWorkspaceStore({
44
45
  })
45
46
 
46
47
  // Add a new document to the store
47
- await store.addDocument({
48
- openapi: '3.1.1',
49
- info: {
50
- title: 'Hello World',
51
- version: '1.0.0',
52
- },
53
- components: {
54
- schemas: {
55
- Person: {
56
- type: 'object',
57
- properties: {
58
- name: { type: 'string' },
48
+ await store.addDocument(
49
+ {
50
+ openapi: '3.1.1',
51
+ info: {
52
+ title: 'Hello World',
53
+ version: '1.0.0',
54
+ },
55
+ components: {
56
+ schemas: {
57
+ Person: {
58
+ type: 'object',
59
+ properties: {
60
+ name: { type: 'string' },
61
+ },
62
+ },
63
+ User: {
64
+ $ref: '#/components/schemas/Person',
59
65
  },
60
- },
61
- User: {
62
- $ref: '#/components/schemas/Person',
63
66
  },
64
67
  },
65
68
  },
66
- }, {
67
- name: 'document-2',
68
- "x-scalar-selected-server": "server1"
69
- })
69
+ {
70
+ 'name': 'document-2',
71
+ 'x-scalar-selected-server': 'server1',
72
+ },
73
+ )
70
74
 
71
75
  // Get the workspace
72
76
  // Workspace is going to keep all the sparse documents
@@ -74,10 +78,8 @@ const workspace = store.getWorkspace()
74
78
 
75
79
  // Get chucks using json pointers
76
80
  const chunk = store.get('#/document-name/components/schemas/Person')
77
-
78
81
  ```
79
82
 
80
-
81
83
  Create a new store in static mode
82
84
 
83
85
  ```ts
@@ -115,29 +117,32 @@ const store = await createServerWorkspaceStore({
115
117
  })
116
118
 
117
119
  // Add a new document to the store
118
- await store.addDocument({
119
- openapi: '3.1.1',
120
- info: {
121
- title: 'Hello World',
122
- version: '1.0.0',
123
- },
124
- components: {
125
- schemas: {
126
- Person: {
127
- type: 'object',
128
- properties: {
129
- name: { type: 'string' },
120
+ await store.addDocument(
121
+ {
122
+ openapi: '3.1.1',
123
+ info: {
124
+ title: 'Hello World',
125
+ version: '1.0.0',
126
+ },
127
+ components: {
128
+ schemas: {
129
+ Person: {
130
+ type: 'object',
131
+ properties: {
132
+ name: { type: 'string' },
133
+ },
134
+ },
135
+ User: {
136
+ $ref: '#/components/schemas/Person',
130
137
  },
131
- },
132
- User: {
133
- $ref: '#/components/schemas/Person',
134
138
  },
135
139
  },
136
140
  },
137
- }, {
138
- name: 'document-2',
139
- "x-scalar-selected-server": "server1"
140
- })
141
+ {
142
+ 'name': 'document-2',
143
+ 'x-scalar-selected-server': 'server1',
144
+ },
145
+ )
141
146
 
142
147
  // Generate the workspace file system
143
148
  // This will write in the filesystem the workspace and all the chucks
@@ -146,6 +151,7 @@ const workspace = await store.generateWorkspaceChunks()
146
151
  ```
147
152
 
148
153
  ### Load documents from external sources
154
+
149
155
  ```ts
150
156
  // Initialize the store with documents from external sources
151
157
  const store = await createServerWorkspaceStore({
@@ -153,13 +159,13 @@ const store = await createServerWorkspaceStore({
153
159
  documents: [
154
160
  {
155
161
  name: 'remoteFile',
156
- url: 'http://localhost/document.json'
162
+ url: 'http://localhost/document.json',
157
163
  },
158
164
  {
159
165
  name: 'fsFile',
160
- path: './document.json'
161
- }
162
- ]
166
+ path: './document.json',
167
+ },
168
+ ],
163
169
  })
164
170
 
165
171
  // Output: { openapi: 'x.x.x', ... }
@@ -175,35 +181,38 @@ A reactive workspace store for managing OpenAPI documents with automatic referen
175
181
 
176
182
  #### Usage
177
183
 
178
- ```ts
184
+ The client-side store starts empty and exposes `addDocument` for loading documents in. Workspace metadata, plugins, fetch overrides, and a file loader plugin can be supplied at construction time.
179
185
 
180
- // Initialize a new workspace store with default document
186
+ ```ts
187
+ // Initialize a new (empty) workspace store
181
188
  const store = createWorkspaceStore({
182
- documents: [
183
- {
184
- name: 'default',
185
- document: {
186
- info: {
187
- title: 'OpenApi document',
188
- version: '1.0.0',
189
- },
190
- },
191
- },
192
- ],
193
189
  meta: {
194
190
  'x-scalar-active-document': 'default',
195
191
  },
196
192
  })
197
193
 
198
- // Add another OpenAPI document to the workspace
194
+ // Add the default document
199
195
  await store.addDocument({
196
+ name: 'default',
200
197
  document: {
198
+ openapi: '3.1.0',
201
199
  info: {
202
200
  title: 'OpenApi document',
203
201
  version: '1.0.0',
204
202
  },
205
203
  },
204
+ })
205
+
206
+ // Add another OpenAPI document to the workspace
207
+ await store.addDocument({
206
208
  name: 'document',
209
+ document: {
210
+ openapi: '3.1.0',
211
+ info: {
212
+ title: 'Another document',
213
+ version: '1.0.0',
214
+ },
215
+ },
207
216
  })
208
217
 
209
218
  // Get the currently active document
@@ -216,7 +225,7 @@ store.workspace.documents['document']
216
225
  store.update('x-scalar-color-mode', true)
217
226
 
218
227
  // Update settings for the active document
219
- store.updateDocument('active', "x-scalar-watch-mode", '<value>')
228
+ store.updateDocument('active', 'x-scalar-selected-server', 'production')
220
229
 
221
230
  // Resolve and load document chunks including any $ref references
222
231
  await store.resolve(['paths', '/users', 'get'])
@@ -224,7 +233,7 @@ await store.resolve(['paths', '/users', 'get'])
224
233
 
225
234
  #### Load documents from external sources
226
235
 
227
- You can only initialize the store with object literals but if you want to add documents from external sources you can use the addDocument function
236
+ The store can also load documents from a URL or, when a file loader plugin is configured, from the local filesystem. Each call returns a boolean indicating whether the document was added successfully.
228
237
 
229
238
  ```ts
230
239
  const store = createWorkspaceStore()
@@ -232,7 +241,7 @@ const store = createWorkspaceStore()
232
241
  // Load a document into the store from a remote url
233
242
  await store.addDocument({
234
243
  name: 'default',
235
- url: 'http://localhost/document.json'
244
+ url: 'http://localhost/document.json',
236
245
  })
237
246
 
238
247
  // Output: { openapi: 'x.x.x', ... }
@@ -241,11 +250,13 @@ console.log(store.workspace.documents.default)
241
250
 
242
251
  #### Document Persistence and Export
243
252
 
244
- The workspace store provides several methods for managing document persistence and exporting:
253
+ The workspace store keeps two snapshots per document at runtime: the **original** (the last saved baseline that the user committed to with `saveDocument`) and the **active** document (the reactive in-memory state, which may include unsaved edits). Most persistence methods are anchored on those two snapshots.
254
+
255
+ > **Deprecated:** an additional `intermediateDocuments` map and the helper methods `getIntermediateDocument` / `promoteIntermediateToOriginal` still exist for backward compatibility but are no longer authoritative. New code should rely on `getOriginalDocument` and the active document instead. The intermediate map is kept in sync on save / revert / rebase so existing consumers keep working until the layer is removed.
245
256
 
246
257
  ##### Export Document
247
258
 
248
- Export the specified document in JSON or YAML format:
259
+ Export the specified document in JSON or YAML format. The export reads from the saved baseline (the same content `revertDocumentChanges` would restore), so it always reflects the user's last save rather than any unsaved edits.
249
260
 
250
261
  ```ts
251
262
  // Export the specified document as JSON
@@ -253,60 +264,57 @@ const jsonString = store.exportDocument('documentName', 'json')
253
264
 
254
265
  // Export the specified document as YAML
255
266
  const yamlString = store.exportDocument('documentName', 'yaml')
256
- ```
257
267
 
258
- The download method returns the original, unmodified document (before any reactive wrapping) to preserve the initial structure without external references or modifications.
268
+ // Or export the currently active document directly
269
+ const activeJson = store.exportActiveDocument('json')
270
+ ```
259
271
 
260
272
  ##### Save Document Changes
261
273
 
262
- Persist the current state of the specified document back to the intermediate document (local saved version of the document):
274
+ `saveDocument` promotes the current in-memory document to the new saved baseline. It serialises the reactive workspace document back into a plain shape (with bundler-internal keys stripped), writes it into the original-document map, and clears the document's `x-scalar-is-dirty` flag.
263
275
 
264
276
  ```ts
265
277
  // Save the specified document state
266
- const excludedDiffs = store.saveDocument('documentName')
278
+ const ok = await store.saveDocument('documentName')
267
279
 
268
- // Check if any changes were excluded from being applied
269
- if (excludedDiffs) {
270
- console.log('Some changes were excluded:', excludedDiffs)
280
+ if (!ok) {
281
+ console.warn('Document does not exist or could not be serialised')
271
282
  }
272
283
  ```
273
284
 
274
- The `saveDocument` method takes the current reactive document state and persists it. It returns an array of diffs that were excluded from being applied or undefined if no specified document is available.
285
+ `saveDocument` returns `true` on success and `false` when the document does not exist or cannot be serialised back into the original map.
275
286
 
276
287
  ##### Revert Document Changes
277
288
 
278
- Revert the specified document to its most recent local saved state, discarding all unsaved changes:
289
+ Revert the specified document to its most recent saved baseline, discarding all unsaved in-memory changes.
279
290
 
280
291
  ```ts
281
- // Revert the specified document to its original state
282
- store.revertDocumentChanges('documentName')
292
+ // Revert the specified document to its last saved state
293
+ await store.revertDocumentChanges('documentName')
283
294
  ```
284
295
 
285
- The `revertDocumentChanges` method restores the specified document to its initial state by copying the original document (before any modifications) back to the specified document.
296
+ The `revertDocumentChanges` method restores the active document from the original-document map, which is whatever `saveDocument` last wrote (or the document as it was first loaded into the workspace if it has never been saved).
286
297
 
287
298
  **Warning:** This operation will discard all unsaved changes to the specified document.
288
299
 
289
300
  ##### Complete Example
290
301
 
291
302
  ```ts
292
- const store = createWorkspaceStore({
293
- documents: [
294
- {
295
- name: 'api',
296
- document: {
297
- openapi: '3.0.0',
298
- info: { title: 'My API', version: '1.0.0' },
299
- paths: {},
300
- },
301
- },
302
- ],
303
+ const store = createWorkspaceStore()
304
+ await store.addDocument({
305
+ name: 'api',
306
+ document: {
307
+ openapi: '3.0.0',
308
+ info: { title: 'My API', version: '1.0.0' },
309
+ paths: {},
310
+ },
303
311
  })
304
312
 
305
313
  // Make some changes to the document
306
314
  store.workspace.documents['api'].info.title = 'Updated API Title'
307
315
 
308
- // This will restore the original title since we did not commit the changes yet
309
- store.revertDocumentChanges()
316
+ // Restore the saved baseline since the changes were never saved
317
+ await store.revertDocumentChanges('api')
310
318
  ```
311
319
 
312
320
  ### Workspace State Persistence
@@ -329,28 +337,25 @@ client.loadWorkspace(currentWorkspaceState)
329
337
  When you have a new or updated OpenAPI document and want to overwrite the existing one—regardless of which parts have changed—you can use the `replaceDocument` method. This method efficiently and atomically updates the entire document in place, ensuring that only the necessary changes are applied for optimal performance.
330
338
 
331
339
  ```ts
332
- const client = createWorkspaceStore({
333
- documents: [
334
- {
335
- name: 'document-name',
336
- document: {
337
- openapi: '3.1.0',
338
- info: {
339
- title: 'Document Title',
340
- version: '1.0.0',
341
- },
342
- paths: {},
343
- components: {
344
- schemas: {},
345
- },
346
- servers: [],
347
- },
340
+ const client = createWorkspaceStore()
341
+ await client.addDocument({
342
+ name: 'document-name',
343
+ document: {
344
+ openapi: '3.1.0',
345
+ info: {
346
+ title: 'Document Title',
347
+ version: '1.0.0',
348
348
  },
349
- ],
349
+ paths: {},
350
+ components: {
351
+ schemas: {},
352
+ },
353
+ servers: [],
354
+ },
350
355
  })
351
356
 
352
357
  // Update the document with the new changes
353
- client.replaceDocument('document-name', {
358
+ await client.replaceDocument('document-name', {
354
359
  openapi: '3.1.0',
355
360
  info: {
356
361
  title: 'Updated Document',
@@ -370,13 +375,13 @@ Create the workspace from a specification object
370
375
 
371
376
  ```ts
372
377
  await store.importWorkspaceFromSpecification({
373
- workspace: 'draft',
374
- info: { title: 'My Workspace' },
375
- documents: {
378
+ 'workspace': 'draft',
379
+ 'info': { title: 'My Workspace' },
380
+ 'documents': {
376
381
  api: { $ref: '/examples/api.yaml' },
377
382
  petstore: { $ref: '/examples/petstore.yaml' },
378
383
  },
379
- overrides: {
384
+ 'overrides': {
380
385
  api: {
381
386
  servers: [
382
387
  {
@@ -414,10 +419,10 @@ await store.addDocument({
414
419
  servers: [
415
420
  {
416
421
  url: 'http://localhost:8080',
417
- description: 'Default dev server'
418
- }
419
- ]
420
- }
422
+ description: 'Default dev server',
423
+ },
424
+ ],
425
+ },
421
426
  })
422
427
  ```
423
428
 
@@ -425,13 +430,40 @@ When you override specific fields, those changes are applied only in-memory and
425
430
 
426
431
  ### Rebase document origin with the updated remote origin
427
432
 
428
- Rebases a document in the workspace with a new origin, resolving conflicts if provided.
433
+ `rebaseDocument` reconciles a workspace document with a new upstream origin. It performs a two-way merge between:
434
+
435
+ - the **incoming changes** — `diff(originalDocument, newOrigin)`
436
+ - the **local changes** — `diff(originalDocument, activeDocument)`
437
+
438
+ The call returns a discriminated result. On `ok: false` the `type` field describes why the rebase did not run (`CORRUPTED_STATE`, `FETCH_FAILED`, or `NO_CHANGES_DETECTED`). On `ok: true` it exposes the auto-mergeable `changes`, the `conflicts` that need user input, and an `applyChanges` callback that writes the merged result back into the workspace.
429
439
 
430
440
  ```ts
431
- // Example: Rebase a document with a new origin and resolve conflicts
432
- const conflicts = store.rebaseDocument('api', newOriginDoc)
433
- if (conflicts && conflicts.length > 0) {
434
- // User resolves conflicts here...
435
- store.rebaseDocument('api', newOriginDoc, userResolvedConflicts)
441
+ // Fetch the latest origin and start a rebase
442
+ const result = await store.rebaseDocument({
443
+ name: 'api',
444
+ // Any `WorkspaceDocumentInput` is accepted - inline document, url, or path
445
+ url: 'https://example.com/api/openapi.json',
446
+ })
447
+
448
+ if (!result.ok) {
449
+ console.warn(`Rebase did not run: ${result.type}`)
450
+ return
436
451
  }
437
- ```
452
+
453
+ if (result.conflicts.length === 0) {
454
+ // No conflicts - just apply with an empty resolution set
455
+ await result.applyChanges({ resolvedConflicts: [] })
456
+ return
457
+ }
458
+
459
+ // Surface the conflicts to the user. Each conflict is a tuple of
460
+ // [incomingDiffs, localDiffs] - resolve by picking either side, or by
461
+ // providing a fully resolved document.
462
+ const resolvedConflicts = result.conflicts.flatMap(([incoming]) => incoming)
463
+ await result.applyChanges({ resolvedConflicts })
464
+
465
+ // Or, pass a complete document to use as-is (overrides the merge result):
466
+ await result.applyChanges({ resolvedDocument: newDocument })
467
+ ```
468
+
469
+ After `applyChanges` returns, the merged document becomes both the new active document and the new saved baseline, so a subsequent `revertDocumentChanges` rolls back to the post-rebase state rather than the pre-rebase original.
package/dist/client.d.ts CHANGED
@@ -249,26 +249,36 @@ export type WorkspaceStore = {
249
249
  *
250
250
  * @param documentName - The name of the document to get the intermediate version of.
251
251
  * @returns The intermediate version of the document, or undefined if the document does not exist.
252
+ *
253
+ * @deprecated The intermediate document layer is being phased out. The
254
+ * workspace store now keeps only two snapshots per document: the original
255
+ * (last saved / loaded baseline) and the active in-memory state. Use
256
+ * `getOriginalDocument` to read the saved baseline. The `intermediateDocuments`
257
+ * map is no longer updated by `saveDocument` or `rebaseDocument` and only
258
+ * reflects the document as it was first loaded into the workspace.
252
259
  */
253
260
  getIntermediateDocument(documentName: string): Record<string, unknown> | null;
254
261
  /**
255
- * Saves the current state of the specified document to the intermediate documents map.
262
+ * Saves the current state of the specified document as the new baseline.
256
263
  *
257
- * This function captures the latest (reactive) state of the document from the workspace and
258
- * applies its changes to the corresponding entry in the `intermediateDocuments` map.
259
- * The `intermediateDocuments` map represents the most recently "saved" local version of the document,
260
- * which may include edits not yet synced to the remote registry.
264
+ * This function captures the latest (reactive) state of the document from
265
+ * the workspace and writes a deep clone of it back into the
266
+ * `originalDocuments` map - effectively promoting the in-memory state to
267
+ * the new "last saved" snapshot. After saving, `getOriginalDocument`
268
+ * returns the persisted edits and `revertDocumentChanges` rolls back to
269
+ * exactly this state.
261
270
  *
262
271
  * The update is performed in-place. A deep clone of the current document
263
- * state is used to avoid mutating the reactive object directly.
272
+ * state is used to avoid mutating the reactive object directly. The
273
+ * dirty flag (`x-scalar-is-dirty`) is cleared on success.
264
274
  *
265
275
  * @param documentName - The name of the document to save.
266
- * @returns An array of diffs that were excluded from being applied (such as changes to ignored keys),
267
- * or undefined if the document does not exist or cannot be updated.
276
+ * @returns `true` when the save succeeded, `false` if the document does
277
+ * not exist or cannot be serialised back into the original map.
268
278
  *
269
279
  * @example
270
280
  * // Save the current state of the document named 'api'
271
- * const isSuccess = store.saveDocument('api')
281
+ * const isSuccess = await store.saveDocument('api')
272
282
  * if (isSuccess) {
273
283
  * console.log('Document saved successfully')
274
284
  * } else {
@@ -286,10 +296,11 @@ export type WorkspaceStore = {
286
296
  /**
287
297
  * Restores the specified document to its last locally saved state.
288
298
  *
289
- * This method updates the current reactive document (in the workspace) with the contents of the
290
- * corresponding intermediate document (from the `intermediateDocuments` map), effectively discarding
291
- * any unsaved in-memory changes and reverting to the last saved version.
292
- * Vue reactivity is preserved by updating the existing reactive object in place.
299
+ * This method updates the current reactive document (in the workspace)
300
+ * with the contents of the corresponding original document (from the
301
+ * `originalDocuments` map), effectively discarding any unsaved in-memory
302
+ * changes and reverting to the last saved version. Vue reactivity is
303
+ * preserved by updating the existing reactive object in place.
293
304
  *
294
305
  * **Warning:** This operation will discard all unsaved (in-memory) changes to the specified document.
295
306
  *
@@ -318,6 +329,12 @@ export type WorkspaceStore = {
318
329
  * } else {
319
330
  * console.log('Intermediate document does not exist')
320
331
  * }
332
+ *
333
+ * @deprecated The intermediate document layer is being phased out.
334
+ * `saveDocument` already writes directly to `originalDocuments`, so this
335
+ * method has no remaining responsibility once the intermediate map stops
336
+ * being updated. It is kept for API compatibility and will be removed in
337
+ * a future release.
321
338
  */
322
339
  promoteIntermediateToOriginal(documentName: string): boolean;
323
340
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -101,6 +101,40 @@ const METADATA_ONLY_DOCUMENT_KEYS = new Set([
101
101
  // conflict cache).
102
102
  'x-scalar-registry-meta',
103
103
  ]);
104
+ /**
105
+ * Removes internal and metadata keys from the provided document object.
106
+ *
107
+ * This function deletes a list of known internal keys that are only meant for
108
+ * in-memory/document processing use and should not be present in the final bundled document.
109
+ * Most of these keys are injected by the bundler or used for Scalar OpenAPI document state tracking.
110
+ *
111
+ * Note: Nested internal keys are handled elsewhere in the bundler pipeline.
112
+ *
113
+ * @param document - The OpenAPI document object to be sanitized in-place.
114
+ */
115
+ const purgeInternalDocumentKeys = (input) => {
116
+ const result = deepClone(input);
117
+ // Top level keys that need to be excluded from the original document
118
+ // Nested keys are removed during the previous step of the bundler process
119
+ const EXCLUDE_KEYS = [
120
+ // Bundler metadata fields added temporarily during document processing
121
+ 'x-ext',
122
+ 'x-ext-urls',
123
+ // Scalar internal/external metadata fields
124
+ 'x-scalar-navigation',
125
+ 'x-scalar-is-dirty',
126
+ 'x-original-oas-version',
127
+ 'x-scalar-original-document-hash',
128
+ 'x-scalar-original-source-url',
129
+ 'x-scalar-registry-meta',
130
+ ];
131
+ // Remove top-level properties that should only exist temporarily or for internal usage
132
+ // These properties are used for internal purposes and are not needed in the final bundled document
133
+ for (const property of EXCLUDE_KEYS) {
134
+ delete result[property];
135
+ }
136
+ return result;
137
+ };
104
138
  /**
105
139
  * Creates a reactive workspace store that manages documents and their metadata.
106
140
  * The store provides functionality for accessing, updating, and resolving document references.
@@ -351,20 +385,27 @@ export const createWorkspaceStore = (workspaceProps) => {
351
385
  return workspace[extensions.workspace.activeDocument] ?? Object.keys(workspace.documents)[0] ?? '';
352
386
  }
353
387
  function exportDocument(documentName, format, minify) {
354
- const intermediateDocument = intermediateDocuments[documentName];
355
- if (!intermediateDocument) {
388
+ // The original document map now holds the most recently saved state of
389
+ // the document - `saveDocument` writes directly into it and
390
+ // `revertDocumentChanges` reads from it. Exporting therefore mirrors
391
+ // exactly what the user would get if they reverted to the last save.
392
+ const savedDocument = originalDocuments[documentName];
393
+ if (!savedDocument) {
356
394
  return;
357
395
  }
358
396
  if (format === 'json') {
359
- return minify ? JSON.stringify(intermediateDocument) : JSON.stringify(intermediateDocument, null, 2);
397
+ return minify ? JSON.stringify(savedDocument) : JSON.stringify(savedDocument, null, 2);
360
398
  }
361
- return YAML.stringify(intermediateDocument);
399
+ return YAML.stringify(savedDocument);
362
400
  }
363
- // Save the current state of the specified document to the intermediate documents map.
364
- // This function captures the latest (reactive) state of the document from the workspace and
365
- // applies its changes to the corresponding entry in the `intermediateDocuments` map.
366
- // The `intermediateDocuments` map represents the most recently "saved" local version of the document,
367
- // which may include edits not yet synced to the remote registry.
401
+ // Save the current state of the specified document as the new baseline.
402
+ // The reactive workspace document is serialised back into a plain shape
403
+ // (with bundler-internal keys stripped) and stored under
404
+ // `originalDocuments[documentName]` - the original map is now the single
405
+ // source of truth for "what the user has saved". The intermediate map is
406
+ // deprecated, but we keep mirroring writes into it so any consumer still
407
+ // reading from `getIntermediateDocument` observes fresh content while
408
+ // the layer is being phased out.
368
409
  const saveDocument = async (documentName) => {
369
410
  const activeDocument = workspace.documents[documentName];
370
411
  const newDocument = await getEditableDocument(documentName);
@@ -372,15 +413,19 @@ export const createWorkspaceStore = (workspaceProps) => {
372
413
  console.warn('Failed to save document, active document is missing');
373
414
  return false;
374
415
  }
375
- // Store the new document in the intermediate documents map
376
- intermediateDocuments[documentName] = newDocument;
416
+ // Promote the in-memory edits to the new saved baseline. Two stores
417
+ // get a deep clone each so subsequent mutations on either one stay
418
+ // isolated.
419
+ originalDocuments[documentName] = newDocument;
420
+ intermediateDocuments[documentName] = deepClone(newDocument);
377
421
  // Mark the document as not dirty since we are saving it
378
422
  activeDocument['x-scalar-is-dirty'] = false;
379
423
  return true;
380
424
  };
381
425
  // Add a document to the store synchronously from an in-memory OpenAPI document
382
426
  async function addInMemoryDocument(input, navigationOptions) {
383
- const { name, meta } = input;
427
+ const { name } = input;
428
+ const meta = deepClone(input.meta);
384
429
  const clonedRawInputDocument = withMeasurementSync('deepClone', () => deepClone(input.document));
385
430
  withMeasurementSync('initialize', () => {
386
431
  if (input.initialize !== false) {
@@ -524,17 +569,18 @@ export const createWorkspaceStore = (workspaceProps) => {
524
569
  return rawDocument;
525
570
  };
526
571
  /**
527
- * Promotes the intermediate document to the original document so the current
528
- * intermediate becomes the new baseline. Fires workspace change for persistence.
572
+ * Promotes the intermediate document to the original document so the
573
+ * current intermediate becomes the new baseline.
574
+ *
575
+ * The intermediate layer is deprecated: `saveDocument` now writes
576
+ * directly into `originalDocuments`, so by the time anyone could call
577
+ * this method the original map already holds the latest saved baseline.
578
+ * Copying the (now stale) intermediate over the original would clobber
579
+ * the user's save, so this is a no-op for existing documents and only
580
+ * reports `false` when the document does not exist at all.
529
581
  */
530
582
  const promoteIntermediateToOriginal = (documentName) => {
531
- const intermediate = intermediateDocuments[documentName];
532
- if (!intermediate) {
533
- return false;
534
- }
535
- const cloned = deepClone(unpackProxyObject(intermediate, { depth: 1 }));
536
- originalDocuments[documentName] = cloned;
537
- return true;
583
+ return Boolean(intermediateDocuments[documentName]);
538
584
  };
539
585
  /**
540
586
  * Retrieves an editable clone of a workspace document.
@@ -558,25 +604,7 @@ export const createWorkspaceStore = (workspaceProps) => {
558
604
  treeShake: false,
559
605
  urlMap: true,
560
606
  }));
561
- // Top level keys that need to be excluded from the original document
562
- // Nested keys are removed buring the previous step of the bundler process
563
- const EXCLUDE_KEYS = [
564
- // Bundler metadata fields added temporarily during document processing
565
- 'x-ext',
566
- 'x-ext-urls',
567
- // Scalar internal/external metadata fields
568
- 'x-scalar-navigation',
569
- 'x-scalar-is-dirty',
570
- 'x-original-oas-version',
571
- 'x-scalar-original-document-hash',
572
- 'x-scalar-original-source-url',
573
- ];
574
- // Remove top level properties that should only exist in memory for the original document
575
- // These properties are used for internal purposes and are not needed in the final bundled document
576
- for (const property of EXCLUDE_KEYS) {
577
- delete original[property];
578
- }
579
- return original;
607
+ return purgeInternalDocumentKeys(original);
580
608
  };
581
609
  /**
582
610
  * Builds (or updates) the navigation sidebar for the specified document.
@@ -634,7 +662,8 @@ export const createWorkspaceStore = (workspaceProps) => {
634
662
  return true;
635
663
  },
636
664
  async replaceDocument(documentName, input) {
637
- const currentDocument = workspace.documents[documentName];
665
+ // Used to preserve the original metadata of the document (we need to unpack so we don't store a proxy object)
666
+ const currentDocument = unpackProxyObject(workspace.documents[documentName], { depth: 1 });
638
667
  if (!currentDocument) {
639
668
  return console.error(`Document '${documentName}' does not exist in the workspace.`);
640
669
  }
@@ -646,6 +675,8 @@ export const createWorkspaceStore = (workspaceProps) => {
646
675
  documentSource: currentDocument['x-scalar-original-source-url'],
647
676
  documentHash: currentDocument['x-scalar-original-document-hash'],
648
677
  meta: {
678
+ // Preserve the registry meta
679
+ 'x-scalar-registry-meta': currentDocument['x-scalar-registry-meta'],
649
680
  // Set the document as dirty
650
681
  'x-scalar-is-dirty': true,
651
682
  // Clear the navigation to trigger a rebuild
@@ -711,17 +742,30 @@ export const createWorkspaceStore = (workspaceProps) => {
711
742
  saveDocument,
712
743
  promoteIntermediateToOriginal,
713
744
  async revertDocumentChanges(documentName) {
714
- const workspaceDocument = workspace.documents[documentName];
715
- const intermediate = intermediateDocuments[documentName];
716
- if (!workspaceDocument || !intermediate) {
745
+ // Used to preserve the original metadata of the document (we need to unpack so we don't store a proxy object)
746
+ const workspaceDocument = unpackProxyObject(workspace.documents[documentName], { depth: 1 });
747
+ // Restore from the original (last saved) snapshot. `saveDocument`
748
+ // writes here, so this reliably rolls back to whatever the user
749
+ // last persisted - matching the soft-deprecation path away from the
750
+ // intermediate document layer.
751
+ const baseline = unpackProxyObject(originalDocuments[documentName], { depth: 1 });
752
+ if (!workspaceDocument || !baseline) {
717
753
  return;
718
754
  }
755
+ // Keep the deprecated intermediate map aligned with the original so
756
+ // any consumer still reading from `getIntermediateDocument` sees the
757
+ // post-revert state instead of the pre-revert one.
758
+ intermediateDocuments[documentName] = deepClone(baseline);
719
759
  await addInMemoryDocument({
720
760
  name: documentName,
721
- document: intermediate,
761
+ document: baseline,
722
762
  documentSource: workspaceDocument['x-scalar-original-source-url'],
723
763
  documentHash: workspaceDocument['x-scalar-original-document-hash'],
724
764
  initialize: false,
765
+ meta: {
766
+ // Preserve the registry meta
767
+ 'x-scalar-registry-meta': workspaceDocument['x-scalar-registry-meta'],
768
+ },
725
769
  });
726
770
  },
727
771
  commitDocument(documentName) {
@@ -770,18 +814,21 @@ export const createWorkspaceStore = (workspaceProps) => {
770
814
  rebaseDocument: async (input) => {
771
815
  const { name } = input;
772
816
  // ---- Get the current documents
817
+ // The intermediate snapshot has been deprecated. Rebase now reasons
818
+ // about exactly two local snapshots: the original (last saved
819
+ // baseline, written by `saveDocument`) and the active in-memory
820
+ // document (with any unsaved edits on top).
773
821
  const originalDocument = unpackProxyObject(originalDocuments[name], { depth: 1 });
774
- const intermediateDocument = unpackProxyObject(intermediateDocuments[name], { depth: 1 });
775
822
  // raw version without any proxies
776
- const activeDocument = workspace.documents[name]
777
- ? unpackProxyObject(workspace.documents[name], { depth: 1 })
778
- : undefined;
779
- if (!originalDocument || !intermediateDocument || !activeDocument) {
823
+ const activeDocumentRaw = unpackProxyObject(workspace.documents[name], { depth: 1 });
824
+ // editable version of the document (reversing bundling and also remove internal metadata)
825
+ const activeDocument = await getEditableDocument(name);
826
+ if (!originalDocument || !activeDocument || !activeDocumentRaw) {
780
827
  // If any required document state is missing, do nothing
781
828
  return {
782
829
  ok: false,
783
830
  type: 'CORRUPTED_STATE',
784
- message: `Cannot rebase document '${name}': missing original, intermediate, or active document state`,
831
+ message: `Cannot rebase document '${name}': missing original or active document state`,
785
832
  };
786
833
  }
787
834
  // ---- Resolve input document
@@ -800,7 +847,7 @@ export const createWorkspaceStore = (workspaceProps) => {
800
847
  // Compare document hashes to see if the document has changed
801
848
  // When the hashes match, we can skip the rebase process
802
849
  const newHash = generateHash(resolve.raw);
803
- if (activeDocument['x-scalar-original-document-hash'] === newHash) {
850
+ if (activeDocumentRaw['x-scalar-original-document-hash'] === newHash) {
804
851
  return {
805
852
  ok: false,
806
853
  type: 'NO_CHANGES_DETECTED',
@@ -811,46 +858,67 @@ export const createWorkspaceStore = (workspaceProps) => {
811
858
  // ---- Override the configurations and metadata
812
859
  overrides[name] = input.overrides ?? {};
813
860
  extraDocumentConfigurations[name] = { fetch: input.fetch };
814
- // ---- Get the new intermediate document
815
- const changelogAA = diff(originalDocument, newDocumentOrigin);
861
+ // ---- Compute the merge between upstream and local edits
862
+ // Two-way merge anchored on the original (last saved) snapshot:
863
+ // - changelogIncoming: changes the registry made on top of the
864
+ // original (what we want to pull in)
865
+ // - changelogLocal: changes the user has made on top of the
866
+ // original since the last save (what we want to keep)
867
+ const changelogIncoming = diff(originalDocument, newDocumentOrigin);
816
868
  // When there are no changes, we can return early since we don't need to do anything
817
869
  // This is not supposed to happen due to the hash check above, but just in case
818
- if (changelogAA.length === 0) {
870
+ if (changelogIncoming.length === 0) {
819
871
  return {
820
872
  ok: false,
821
873
  type: 'NO_CHANGES_DETECTED',
822
874
  message: `No changes detected for document '${name}' after fetching the latest version.`,
823
875
  };
824
876
  }
825
- const changelogAB = diff(originalDocument, intermediateDocument);
826
- const changesA = merge(changelogAA, changelogAB);
877
+ const changelogLocal = diff(originalDocument, activeDocument);
878
+ const merged = merge(changelogIncoming, changelogLocal);
827
879
  return {
828
880
  ok: true,
829
- conflicts: changesA.conflicts,
830
- changes: changesA.diffs,
881
+ conflicts: merged.conflicts,
882
+ changes: merged.diffs,
831
883
  applyChanges: async (applyChangesInput) => {
832
- // Helper function to compute the new intermediate document based on resolved conflicts or a resolved document
833
- const getNewIntermediateDocument = () => {
884
+ // Helper function to compute the new active document based on
885
+ // resolved conflicts or a resolved document. With the
886
+ // intermediate layer gone, the merged result *is* the new
887
+ // active document - there is no longer an intermediate hop.
888
+ const getNewActiveDocument = () => {
834
889
  if ('resolvedConflicts' in applyChangesInput) {
835
- const changesetA = changesA.diffs.concat(applyChangesInput.resolvedConflicts);
890
+ const changeset = merged.diffs.concat(applyChangesInput.resolvedConflicts);
836
891
  // Apply the merged changes (diffs + resolved conflicts) to the original document
837
- return apply(deepClone(originalDocument), changesetA);
892
+ return apply(deepClone(originalDocument), changeset);
838
893
  }
839
- // If there are no resolved conflicts, use the provided resolved document
894
+ // If the caller provided a fully resolved document, use it as-is.
840
895
  return applyChangesInput.resolvedDocument;
841
896
  };
842
- const newIntermediateDocument = getNewIntermediateDocument();
843
- intermediateDocuments[name] = newIntermediateDocument;
844
- // Update the original document
845
- originalDocuments[name] = newDocumentOrigin;
846
- // ---- Get the new active document
847
- const changelogBA = diff(intermediateDocument, newIntermediateDocument);
848
- const changelogBB = diff(intermediateDocument, activeDocument);
849
- const changesB = merge(changelogBA, changelogBB);
850
- // Auto-conflict resolution: pick only the changes from the first changeset
851
- // TODO: In the future, implement smarter conflict resolution if needed
852
- const changesetB = changesB.diffs.concat(changesB.conflicts.flatMap((it) => it[0]));
853
- const newActiveDocument = coerceValue(OpenAPIDocumentSchemaStrict, apply(deepClone(newIntermediateDocument), changesetB));
897
+ const mergedDocument = getNewActiveDocument();
898
+ const newActiveDocument = coerceValue(OpenAPIDocumentSchemaStrict, mergedDocument);
899
+ // Detect whether the rebase folded in any local edits. When the
900
+ // merged result matches the upstream snapshot the pull was
901
+ // effectively a fast-forward (no unsaved local work to carry
902
+ // over), so the document is clean relative to the registry.
903
+ // When it differs we still hold unpushed changes locally - flag
904
+ // the document as dirty so the push flow can surface them, the
905
+ // same way `git pull --rebase` leaves you "ahead of origin"
906
+ // once your local commits get replayed on top.
907
+ //
908
+ // We compare the pre-coerce merged document against upstream
909
+ // because `coerceValue` normalises the merged result against
910
+ // the strict schema and that normalisation can introduce diffs
911
+ // even for pure fast-forwards. The merged document is what the
912
+ // two-way merge actually produced, so its byte-for-byte equality
913
+ // with upstream is the real fast-forward signal.
914
+ const hasLocalChangesAgainstUpstream = diff(newDocumentOrigin, mergedDocument).length > 0;
915
+ // The merged result becomes the new saved baseline so a subsequent
916
+ // revert restores to the post-rebase state, not to the
917
+ // pre-rebase original. Mirror the same content into the
918
+ // deprecated intermediate map so any lingering consumer reads
919
+ // the post-rebase state too.
920
+ originalDocuments[name] = newActiveDocument;
921
+ intermediateDocuments[name] = deepClone(newActiveDocument);
854
922
  // add the new active document to the workspace but don't re-initialize
855
923
  await addInMemoryDocument({
856
924
  ...input,
@@ -864,6 +932,13 @@ export const createWorkspaceStore = (workspaceProps) => {
864
932
  // Update the original document hash
865
933
  documentHash: generateHash(resolve.raw),
866
934
  initialize: false,
935
+ meta: {
936
+ ...input.meta,
937
+ // Preserve the registry meta
938
+ 'x-scalar-registry-meta': activeDocumentRaw['x-scalar-registry-meta'],
939
+ // Flag local edits that need pushing - see note above.
940
+ 'x-scalar-is-dirty': hasLocalChangesAgainstUpstream,
941
+ },
867
942
  });
868
943
  },
869
944
  };
@@ -1 +1 @@
1
- {"version":3,"file":"build-request-body.d.ts","sourceRoot":"","sources":["../../../../src/request-example/builder/body/build-request-body.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0DAA0D,CAAA;AAKjG,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,CACH;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;KACd,GACD;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,IAAI,CAAA;QACX,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,GACD;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,IAAI,CAAA;QACX,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CACJ,EAAE,CAAA;CACJ,CAAA;AAED,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;KACd,EAAE,CAAA;CACJ,CAAA;AAED,KAAK,GAAG,GAAG;IACT,IAAI,EAAE,KAAK,CAAA;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,GAAG,CAAA;AAKrD;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,aAAa,iBAAiB,GAAG,SAAS;AAC1C,qCAAqC;AACrC,oBAAuB;AACvB,sEAAsE;AACtE,kCAAkC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACvD,WAAW,GAAG,IAqJhB,CAAA"}
1
+ {"version":3,"file":"build-request-body.d.ts","sourceRoot":"","sources":["../../../../src/request-example/builder/body/build-request-body.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0DAA0D,CAAA;AAKjG,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,CACH;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;KACd,GACD;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,IAAI,CAAA;QACX,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,GACD;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,IAAI,CAAA;QACX,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CACJ,EAAE,CAAA;CACJ,CAAA;AAED,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;KACd,EAAE,CAAA;CACJ,CAAA;AAED,KAAK,GAAG,GAAG;IACT,IAAI,EAAE,KAAK,CAAA;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,GAAG,CAAA;AAKrD;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,aAAa,iBAAiB,GAAG,SAAS;AAC1C,qCAAqC;AACrC,oBAAuB;AACvB,sEAAsE;AACtE,kCAAkC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACvD,WAAW,GAAG,IAwMhB,CAAA"}
@@ -1,4 +1,5 @@
1
1
  // import { replaceEnvVariables } from '@scalar/helpers/regex/replace-variables'
2
+ import { isObject } from '@scalar/helpers/object/is-object';
2
3
  import { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy';
3
4
  import { getExampleFromBody } from './get-request-body-example.js';
4
5
  import { getSelectedBodyContentType } from './get-selected-body-content-type.js';
@@ -88,10 +89,7 @@ requestBodyCompositionSelection) => {
88
89
  // Form data - object format (from schema examples)
89
90
  // When the example value is a plain object and content type is form-urlencoded,
90
91
  // convert to URLSearchParams instead of JSON stringifying
91
- if (bodyContentType === 'application/x-www-form-urlencoded' &&
92
- example.value !== null &&
93
- typeof example.value === 'object' &&
94
- !Array.isArray(example.value)) {
92
+ if (bodyContentType === 'application/x-www-form-urlencoded' && isObject(example.value)) {
95
93
  const result = {
96
94
  mode: 'urlencoded',
97
95
  value: [],
@@ -108,6 +106,51 @@ requestBodyCompositionSelection) => {
108
106
  }
109
107
  return result;
110
108
  }
109
+ // Form data - object format (from schema examples)
110
+ if (bodyContentType === 'multipart/form-data' && isObject(example.value)) {
111
+ const result = {
112
+ mode: 'formdata',
113
+ value: [],
114
+ };
115
+ for (const [key, value] of Object.entries(example.value)) {
116
+ if (!key || value === undefined || value === null) {
117
+ continue;
118
+ }
119
+ const partContentType = getMultipartEncodingContentType(requestBody, bodyContentType, key);
120
+ if (value instanceof File) {
121
+ const unwrappedValue = unpackProxyObject(value);
122
+ const encodedValue = partContentType && partContentType !== unwrappedValue.type
123
+ ? new File([unwrappedValue], unwrappedValue.name, {
124
+ type: partContentType,
125
+ lastModified: unwrappedValue.lastModified,
126
+ })
127
+ : unwrappedValue;
128
+ result.value.push({
129
+ type: 'file',
130
+ key,
131
+ value: encodedValue,
132
+ contentType: partContentType,
133
+ });
134
+ continue;
135
+ }
136
+ const serializedValue = typeof value === 'object' && value !== null ? JSON.stringify(unpackProxyObject(value)) : String(value);
137
+ if (partContentType) {
138
+ result.value.push({
139
+ type: 'blob',
140
+ key,
141
+ value: new Blob([serializedValue], { type: partContentType }),
142
+ contentType: partContentType,
143
+ });
144
+ continue;
145
+ }
146
+ result.value.push({
147
+ type: 'text',
148
+ key,
149
+ value: serializedValue,
150
+ });
151
+ }
152
+ return result;
153
+ }
111
154
  // Any other type
112
155
  const exampleValue = example.value !== null && typeof example.value === 'object' ? unpackProxyObject(example.value) : example.value;
113
156
  // File type
@@ -1 +1 @@
1
- {"version":3,"file":"get-example-from-schema.d.ts","sourceRoot":"","sources":["../../../../src/request-example/builder/helpers/get-example-from-schema.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAwf1E,KAAK,2BAA2B,GAAG;IACjC,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,qDAAqD;IACrD,8BAA8B,CAAC,EAAE,OAAO,CAAA;IACxC,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9C,CAAA;AAeD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,YAAY,EACpB,UAAU,2BAA2B,EACrC,mDAMG,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,YAAY,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IACrB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB,CAAM,KACN,OAiKF,CAAA"}
1
+ {"version":3,"file":"get-example-from-schema.d.ts","sourceRoot":"","sources":["../../../../src/request-example/builder/helpers/get-example-from-schema.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAsnB1E,KAAK,2BAA2B,GAAG;IACjC,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,qDAAqD;IACrD,8BAA8B,CAAC,EAAE,OAAO,CAAA;IACxC,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9C,CAAA;AAeD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,YAAY,EACpB,UAAU,2BAA2B,EACrC,mDAMG,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,YAAY,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IACrB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB,CAAM,KACN,OAqKF,CAAA"}
@@ -172,6 +172,105 @@ const mergeExamples = (baseValue, newValue) => {
172
172
  }
173
173
  return newValue;
174
174
  };
175
+ const MAX_SCHEMA_VALIDATION_DEPTH = MAX_LEVELS_DEEP * 5;
176
+ /** Cache composed schema resolution to preserve identity across recursion checks. */
177
+ const composedSchemaResolutionCache = new WeakMap();
178
+ const isValueOfType = (value, targetType) => {
179
+ switch (targetType) {
180
+ case 'string':
181
+ return typeof value === 'string';
182
+ case 'number':
183
+ return typeof value === 'number' && !Number.isNaN(value);
184
+ case 'integer':
185
+ return typeof value === 'number' && Number.isInteger(value);
186
+ case 'boolean':
187
+ return typeof value === 'boolean';
188
+ case 'object':
189
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
190
+ case 'array':
191
+ return Array.isArray(value);
192
+ case 'null':
193
+ return value === null;
194
+ default:
195
+ return false;
196
+ }
197
+ };
198
+ const resolveComposedSchemaMember = (schema) => {
199
+ const rawSchema = getSchemaCacheTarget(schema);
200
+ if (composedSchemaResolutionCache.has(rawSchema)) {
201
+ return composedSchemaResolutionCache.get(rawSchema);
202
+ }
203
+ const resolved = '$ref' in schema ? resolve.schema(schema) : schema;
204
+ composedSchemaResolutionCache.set(rawSchema, resolved);
205
+ return resolved;
206
+ };
207
+ const schemaAllowsValue = (schema, value, seen = new Set(), level = 0) => {
208
+ // Depth guard prevents stack overflows when composed schemas loop through wrapped resolver objects.
209
+ if (level > MAX_SCHEMA_VALIDATION_DEPTH) {
210
+ return true;
211
+ }
212
+ const rawSchema = getSchemaCacheTarget(schema);
213
+ if (seen.has(rawSchema)) {
214
+ return true;
215
+ }
216
+ seen.add(rawSchema);
217
+ if ('type' in schema && schema.type) {
218
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
219
+ const matchesType = types.some((targetType) => {
220
+ if (targetType === 'number' && isValueOfType(value, 'integer')) {
221
+ return true;
222
+ }
223
+ return isValueOfType(value, targetType);
224
+ });
225
+ if (!matchesType) {
226
+ seen.delete(rawSchema);
227
+ return false;
228
+ }
229
+ }
230
+ const anyOf = schema.anyOf;
231
+ if (Array.isArray(anyOf) && anyOf.length > 0) {
232
+ const matchesAnyOf = anyOf.some((item) => {
233
+ const resolved = resolveComposedSchemaMember(item);
234
+ return !!resolved && schemaAllowsValue(resolved, value, seen, level + 1);
235
+ });
236
+ if (!matchesAnyOf) {
237
+ seen.delete(rawSchema);
238
+ return false;
239
+ }
240
+ }
241
+ const oneOf = schema.oneOf;
242
+ if (Array.isArray(oneOf) && oneOf.length > 0) {
243
+ const matchesOneOf = oneOf.some((item) => {
244
+ const resolved = resolveComposedSchemaMember(item);
245
+ return !!resolved && schemaAllowsValue(resolved, value, seen, level + 1);
246
+ });
247
+ if (!matchesOneOf) {
248
+ seen.delete(rawSchema);
249
+ return false;
250
+ }
251
+ }
252
+ const allOf = schema.allOf;
253
+ if (Array.isArray(allOf) && allOf.length > 0) {
254
+ const matchesAllOf = allOf.every((item) => {
255
+ const resolved = resolveComposedSchemaMember(item);
256
+ return !resolved || schemaAllowsValue(resolved, value, seen, level + 1);
257
+ });
258
+ if (!matchesAllOf) {
259
+ seen.delete(rawSchema);
260
+ return false;
261
+ }
262
+ }
263
+ seen.delete(rawSchema);
264
+ return true;
265
+ };
266
+ const INVALID_DEFAULT = Symbol('INVALID_DEFAULT');
267
+ const normalizeSchemaDefault = (schema) => {
268
+ const defaultValue = schema.default;
269
+ if (schemaAllowsValue(schema, defaultValue)) {
270
+ return defaultValue;
271
+ }
272
+ return INVALID_DEFAULT;
273
+ };
175
274
  const getCompositionSelectionKey = (schemaPath, composition) => [...schemaPath, composition].join('.');
176
275
  const getCompositionSelectionIndex = (schemaPath, composition, options, length) => {
177
276
  const rawIndex = options?.compositionSelection?.[getCompositionSelectionKey(schemaPath, composition)];
@@ -477,8 +576,11 @@ export const getExampleFromSchema = (schema, options, { level = 0, parentSchema,
477
576
  return cache(_schema, _schema.example, cacheKey);
478
577
  }
479
578
  if (_schema.default !== undefined) {
480
- seen.delete(targetValue);
481
- return cache(_schema, _schema.default, cacheKey);
579
+ const normalizedDefault = normalizeSchemaDefault(_schema);
580
+ if (normalizedDefault !== INVALID_DEFAULT) {
581
+ seen.delete(targetValue);
582
+ return cache(_schema, normalizedDefault, cacheKey);
583
+ }
482
584
  }
483
585
  if (_schema.const !== undefined) {
484
586
  seen.delete(targetValue);
@@ -1,11 +1,10 @@
1
1
  import type { HttpMethod } from '@scalar/helpers/http/http-methods';
2
- export type Result<T> = {
3
- ok: true;
4
- data: T;
5
- } | {
6
- ok: false;
7
- error: string;
8
- };
2
+ /**
3
+ * Re-exported from `@scalar/helpers/types/result` so the rest of the
4
+ * workspace-store codebase keeps importing `Result` from a colocated
5
+ * module while the canonical definition lives in helpers.
6
+ */
7
+ export type { Result } from '@scalar/helpers/types/result';
9
8
  export type RequestExampleMeta = {
10
9
  path: string;
11
10
  method: HttpMethod;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/request-example/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAEnE,MAAM,MAAM,MAAM,CAAC,CAAC,IAChB;IACE,EAAE,EAAE,IAAI,CAAA;IACR,IAAI,EAAE,CAAC,CAAA;CACR,GACD;IACE,EAAE,EAAE,KAAK,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAEL,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/request-example/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAEnE;;;;GAIG;AACH,YAAY,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAE1D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA"}
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "openapi",
17
17
  "scalar"
18
18
  ],
19
- "version": "0.49.1",
19
+ "version": "0.49.3",
20
20
  "engines": {
21
21
  "node": ">=22"
22
22
  },
@@ -143,12 +143,12 @@
143
143
  "type-fest": "^5.3.1",
144
144
  "vue": "^3.5.30",
145
145
  "yaml": "^2.8.3",
146
- "@scalar/json-magic": "0.12.11",
147
- "@scalar/helpers": "0.5.5",
146
+ "@scalar/helpers": "0.6.0",
148
147
  "@scalar/openapi-upgrader": "0.2.7",
149
- "@scalar/validation": "0.3.2",
150
- "@scalar/types": "0.9.5",
151
- "@scalar/snippetz": "0.9.5"
148
+ "@scalar/json-magic": "0.12.12",
149
+ "@scalar/snippetz": "0.9.6",
150
+ "@scalar/types": "0.9.6",
151
+ "@scalar/validation": "0.3.2"
152
152
  },
153
153
  "devDependencies": {
154
154
  "@google-cloud/storage": "7.16.0",