@scalar/workspace-store 0.57.0 → 0.58.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/dist/entities/auth/schema.d.ts +25 -0
- package/dist/entities/auth/schema.d.ts.map +1 -1
- package/dist/helpers/for-each-path-item-operation.d.ts +10 -0
- package/dist/helpers/for-each-path-item-operation.d.ts.map +1 -1
- package/dist/helpers/for-each-path-item-operation.js +67 -10
- package/dist/helpers/get-resolved-ref.d.ts.map +1 -1
- package/dist/helpers/get-resolved-ref.js +9 -0
- package/dist/mutators/operation/parameters.d.ts.map +1 -1
- package/dist/mutators/operation/parameters.js +6 -1
- package/dist/navigation/get-navigation-options.d.ts.map +1 -1
- package/dist/navigation/get-navigation-options.js +6 -3
- package/dist/navigation/helpers/traverse-tags.d.ts +3 -2
- package/dist/navigation/helpers/traverse-tags.d.ts.map +1 -1
- package/dist/navigation/helpers/traverse-tags.js +140 -18
- package/dist/request-example/builder/body/get-request-body-example.d.ts +12 -0
- 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 +25 -12
- package/dist/request-example/builder/index.d.ts +1 -1
- package/dist/request-example/builder/index.d.ts.map +1 -1
- package/dist/request-example/builder/index.js +1 -1
- package/dist/request-example/index.d.ts +1 -1
- package/dist/request-example/index.d.ts.map +1 -1
- package/dist/request-example/index.js +1 -1
- package/dist/schemas/navigation.d.ts +16 -2
- package/dist/schemas/navigation.d.ts.map +1 -1
- package/dist/schemas/navigation.js +1 -0
- package/dist/schemas/reference-config/index.d.ts +6 -1
- package/dist/schemas/reference-config/index.d.ts.map +1 -1
- package/dist/schemas/reference-config/settings.d.ts +5 -0
- package/dist/schemas/reference-config/settings.d.ts.map +1 -1
- package/dist/schemas/v3.1/openapi/index.d.ts +3 -0
- package/dist/schemas/v3.1/openapi/index.d.ts.map +1 -1
- package/dist/schemas/v3.1/openapi/index.js +7 -0
- package/dist/schemas/v3.1/strict/openapi-document.d.ts +175 -0
- package/dist/schemas/v3.1/strict/openapi-document.d.ts.map +1 -1
- package/dist/schemas/v3.1/strict/tag.d.ts +12 -0
- package/dist/schemas/v3.1/strict/tag.d.ts.map +1 -1
- package/dist/schemas/v3.1/strict/tag.js +6 -0
- package/dist/schemas/v3.2/strict/openapi-document.d.ts +70 -0
- package/dist/schemas/v3.2/strict/openapi-document.d.ts.map +1 -1
- package/dist/schemas/workspace-specification/index.d.ts +1 -1
- package/dist/schemas/workspace.d.ts +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +177 -16
- package/package.json +14 -14
package/dist/server.js
CHANGED
|
@@ -1,20 +1,81 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import { cwd } from 'node:process';
|
|
3
|
+
import { upgrade as upgradeAsyncApi } from '@scalar/asyncapi-upgrader';
|
|
3
4
|
import { parseJsonPointerSegments } from '@scalar/helpers/json/parse-json-pointer-segments';
|
|
4
5
|
import { getValueAtPath } from '@scalar/helpers/object/get-value-at-path';
|
|
6
|
+
import { preventPollution } from '@scalar/helpers/object/prevent-pollution';
|
|
7
|
+
import { extensions as bundleExtensions } from '@scalar/json-magic/bundle';
|
|
5
8
|
import { fetchUrls, readFiles } from '@scalar/json-magic/bundle/plugins/node';
|
|
6
9
|
import { escapeJsonPointer } from '@scalar/json-magic/helpers/escape-json-pointer';
|
|
10
|
+
import { createMagicProxy, getRaw } from '@scalar/json-magic/magic-proxy';
|
|
7
11
|
import { upgrade } from '@scalar/openapi-upgrader';
|
|
12
|
+
import { asyncApiObjectSchema } from '@scalar/schemas/asyncapi/3.1';
|
|
13
|
+
import { coerce } from '@scalar/validation';
|
|
14
|
+
import { deepClone } from './helpers/deep-clone.js';
|
|
8
15
|
import { forEachPathItemOperation, getResolvedPathItem } from './helpers/for-each-path-item-operation.js';
|
|
9
16
|
import { keyOf } from './helpers/general.js';
|
|
10
17
|
import { getResolvedRef } from './helpers/get-resolved-ref.js';
|
|
11
|
-
import {
|
|
18
|
+
import { mergeObjects } from './helpers/merge-object.js';
|
|
19
|
+
import { createNavigation, traverseAsyncApiDocument } from './navigation/index.js';
|
|
12
20
|
import { extensions } from './schemas/extensions.js';
|
|
21
|
+
import { isAsyncApiDocument } from './schemas/type-guards.js';
|
|
13
22
|
import { coerceValue } from './schemas/typebox-coerce.js';
|
|
14
23
|
import { OpenAPIDocumentSchema, } from './schemas/v3.1/strict/openapi-document.js';
|
|
15
24
|
const DEFAULT_ASSETS_FOLDER = 'assets';
|
|
16
25
|
export const WORKSPACE_FILE_NAME = 'scalar-workspace.json';
|
|
17
26
|
const httpMethods = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']);
|
|
27
|
+
/**
|
|
28
|
+
* Wraps a document so local `$ref`s resolve while the store inspects it.
|
|
29
|
+
*
|
|
30
|
+
* Navigation building and externalization both read through `getResolvedRef`, which needs the
|
|
31
|
+
* `$ref-value` the magic proxy supplies. Without it a `$ref`'d path item reads as a bare `{ $ref }`,
|
|
32
|
+
* so its operations reach neither the sidebar nor the generated chunks and disappear from the
|
|
33
|
+
* rendered document with no error. This is the same treatment the client store gives every document.
|
|
34
|
+
*
|
|
35
|
+
* Resolution is lazy and local, so this stays synchronous and never fetches. The proxy is only ever
|
|
36
|
+
* read through: everything the store keeps is unwrapped with `getRaw` first, because `$ref-value` is
|
|
37
|
+
* enumerable on a proxy and serializing one would inline every referenced value beside its `$ref`.
|
|
38
|
+
*/
|
|
39
|
+
const resolveLocalReferences = (document) => createMagicProxy(document);
|
|
40
|
+
/**
|
|
41
|
+
* The keys `@scalar/json-magic` bundling parks external documents under.
|
|
42
|
+
*
|
|
43
|
+
* Sourced straight from the bundler's own `extensions` defaults so the two cannot drift: if bundling
|
|
44
|
+
* ever renames a bucket, this follows without a silent break.
|
|
45
|
+
*
|
|
46
|
+
* Both are needed. `x-ext` holds the bundled documents that rewritten references resolve against,
|
|
47
|
+
* and `x-ext-urls` maps each bucket key back to the URL it came from — `restoreOriginalRefs` reads it
|
|
48
|
+
* to turn the local pointers back into the references the author wrote.
|
|
49
|
+
*
|
|
50
|
+
* Note that both are served: they stay on the stored document and ship in the workspace payload,
|
|
51
|
+
* because the generated chunks keep their `#/x-ext/…` pointers and the client resolves those against
|
|
52
|
+
* the document root. Only the client's export path (`purgeInternalDocumentKeys`) strips them.
|
|
53
|
+
*/
|
|
54
|
+
const BUNDLED_EXTERNAL_KEYS = [bundleExtensions.externalDocuments, bundleExtensions.externalDocumentsMappings];
|
|
55
|
+
/**
|
|
56
|
+
* Copies the buckets bundling parks external documents in onto the coerced document.
|
|
57
|
+
*
|
|
58
|
+
* Bundling does not inline an external reference — it moves the target under `x-ext` and rewrites the
|
|
59
|
+
* `$ref` to a local pointer into that bucket. The OpenAPI schema does not model those keys, so
|
|
60
|
+
* coercion drops them and leaves every rewritten reference dangling, which is how a split-file
|
|
61
|
+
* document loses the operations it keeps in its other files.
|
|
62
|
+
*
|
|
63
|
+
* Copied by reference, deliberately. `upgrade` hands back the very object it was given when the
|
|
64
|
+
* document is already 3.1, so the served workspace ends up sharing these buckets with the caller's
|
|
65
|
+
* document — but that is what the store already does with every other field coercion passes through
|
|
66
|
+
* untouched (`info` among them), so cloning only these two would buy consistency nowhere. It would
|
|
67
|
+
* also be the worst place to pay for it: `x-ext` holds every external document that was bundled in,
|
|
68
|
+
* so cloning it roughly doubles peak memory at ingest, and `deepClone` recurses per level and throws
|
|
69
|
+
* on input nested a few thousand deep. Cloning the caller's document as a whole is the fix, and it
|
|
70
|
+
* belongs with the aliasing the store already has rather than here.
|
|
71
|
+
*/
|
|
72
|
+
const preserveBundledExternals = (source, target) => {
|
|
73
|
+
for (const key of BUNDLED_EXTERNAL_KEYS) {
|
|
74
|
+
if (source[key] !== undefined) {
|
|
75
|
+
target[key] = source[key];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
18
79
|
/**
|
|
19
80
|
* Filters an OpenAPI PathsObject to only include standard HTTP methods.
|
|
20
81
|
* Removes any vendor extensions or other non-HTTP properties.
|
|
@@ -44,7 +105,12 @@ export function filterHttpMethodsOnly(paths) {
|
|
|
44
105
|
const filteredMethods = {};
|
|
45
106
|
forEachPathItemOperation(pathItemRef, (method, operation) => {
|
|
46
107
|
if (httpMethods.has(method.toLowerCase())) {
|
|
47
|
-
|
|
108
|
+
// Unwrapped because the caller hands us a resolved document. A magic proxy enumerates a
|
|
109
|
+
// virtual `$ref-value`, so storing one would inline every referenced component beside its
|
|
110
|
+
// `$ref` when the chunk is written — and a self-referential schema would never finish
|
|
111
|
+
// serializing. Unwrapping one level is enough: the proxy wraps lazily, so a raw target's
|
|
112
|
+
// children are already raw.
|
|
113
|
+
filteredMethods[method] = getRaw(getResolvedRef(operation) ?? operation);
|
|
48
114
|
}
|
|
49
115
|
});
|
|
50
116
|
if (Object.keys(filteredMethods).length > 0) {
|
|
@@ -116,11 +182,14 @@ export function externalizePathReferences(document, meta) {
|
|
|
116
182
|
: `./chunks/${meta.name}/operations/${escapedPath}/${type}.json#`;
|
|
117
183
|
result[path][type] = { '$ref': ref, $global: true };
|
|
118
184
|
}
|
|
119
|
-
else if (type !== '$ref') {
|
|
120
|
-
// Skip the
|
|
121
|
-
// externalized on its own and
|
|
122
|
-
// emit a hybrid entry
|
|
123
|
-
|
|
185
|
+
else if (type !== '$ref' && type !== '$ref-value') {
|
|
186
|
+
// Skip the reference plumbing merged in by getResolvedPathItem. The referenced path item is
|
|
187
|
+
// externalized on its own and its operations are externalized above, so keeping the `$ref`
|
|
188
|
+
// would emit a hybrid entry carrying both a component reference and inlined operation
|
|
189
|
+
// references. `$ref-value` is meant to be virtual and never belongs in a stored document.
|
|
190
|
+
//
|
|
191
|
+
// Unwrapped for the same reason as in filterHttpMethodsOnly: what is kept here is stored.
|
|
192
|
+
result[path][type] = getRaw(pathItemRecord[type]);
|
|
124
193
|
}
|
|
125
194
|
});
|
|
126
195
|
});
|
|
@@ -184,6 +253,55 @@ export async function createServerWorkspaceStore(workspaceProps) {
|
|
|
184
253
|
* for that document.
|
|
185
254
|
*/
|
|
186
255
|
const assets = {};
|
|
256
|
+
/**
|
|
257
|
+
* Adds an AsyncAPI document to the workspace.
|
|
258
|
+
*
|
|
259
|
+
* AsyncAPI keeps its content under `channels` and `operations` instead of `paths`, so none of the
|
|
260
|
+
* OpenAPI externalization applies: there are no path operations to split into chunks, and the
|
|
261
|
+
* consumers read channels and operations straight off the stored document. The document is
|
|
262
|
+
* therefore kept whole, and only the AsyncAPI upgrader runs so 1.x/2.x documents reach the 3.x
|
|
263
|
+
* shape the traversal and renderer expect.
|
|
264
|
+
*
|
|
265
|
+
* @param document - The AsyncAPI document to process and add
|
|
266
|
+
* @param meta - The document name plus any metadata to merge onto the stored document
|
|
267
|
+
*/
|
|
268
|
+
const addAsyncApiDocumentSync = (document, { name, documentMeta }, navigationOptions) => {
|
|
269
|
+
// Capture the original version before the upgrader bumps `asyncapi` to the latest.
|
|
270
|
+
const originalAasVersion = document.asyncapi;
|
|
271
|
+
// Clone first: the upgrader and the traversal both write to the document they are handed, and
|
|
272
|
+
// the caller may keep using the object it passed in.
|
|
273
|
+
// The upgrader is typed against the loose `UnknownObject` shape; the result is a valid 3.x
|
|
274
|
+
// AsyncAPI document, so cast it back.
|
|
275
|
+
const asyncApiDocument = upgradeAsyncApi(deepClone(document));
|
|
276
|
+
// Coerced against the AsyncAPI schema for the same reason the OpenAPI path coerces against its
|
|
277
|
+
// own: the traversal and every consumer downstream expect a normalized document. Skipping it
|
|
278
|
+
// leaves `info` missing on a partial document (which the traversal reads unguarded) and passes
|
|
279
|
+
// shapes like `channels: null` straight through to the browser. Merged rather than assigned, so
|
|
280
|
+
// nothing the schema does not model is dropped.
|
|
281
|
+
mergeObjects(asyncApiDocument, coerce(asyncApiObjectSchema, deepClone(asyncApiDocument)));
|
|
282
|
+
// Nothing is externalized, so the document owns no chunks. The empty entry keeps `get()` and
|
|
283
|
+
// chunk generation well defined for the document name.
|
|
284
|
+
assets[name] = {};
|
|
285
|
+
// Traversed before the spread below: its last act is a top-level `x-scalar-order` write on the
|
|
286
|
+
// document, and a snapshot taken first would both miss it and preserve whatever stale order the
|
|
287
|
+
// input arrived with.
|
|
288
|
+
const navigation = traverseAsyncApiDocument(name,
|
|
289
|
+
// Resolved so the traversal can follow references the same way the client store does. A
|
|
290
|
+
// channel names its messages by `$ref` into `components.messages`, and an unresolved
|
|
291
|
+
// traversal falls back to the map key — so the sidebar reads `planetCreated` where the
|
|
292
|
+
// rendered page reads "Planet Created".
|
|
293
|
+
resolveLocalReferences(asyncApiDocument), navigationOptions ?? workspaceProps.navigationOptions);
|
|
294
|
+
workspace.documents[name] = {
|
|
295
|
+
...documentMeta,
|
|
296
|
+
...asyncApiDocument,
|
|
297
|
+
'x-original-aas-version': originalAasVersion,
|
|
298
|
+
[extensions.document.navigation]: navigation,
|
|
299
|
+
};
|
|
300
|
+
// A document carrying both discriminators is ingested as AsyncAPI here, but `getDocumentType`
|
|
301
|
+
// checks OpenAPI first — so leaving `openapi` in place would hand an OpenAPI renderer a
|
|
302
|
+
// navigation tree of channel entries. The document is stored as the type it was read as.
|
|
303
|
+
delete workspace.documents[name]['openapi'];
|
|
304
|
+
};
|
|
187
305
|
/**
|
|
188
306
|
* Adds a new document to the workspace.
|
|
189
307
|
*
|
|
@@ -201,19 +319,37 @@ export async function createServerWorkspaceStore(workspaceProps) {
|
|
|
201
319
|
*/
|
|
202
320
|
const addDocumentSync = (document, meta, navigationOptions) => {
|
|
203
321
|
const { name, ...documentMeta } = meta;
|
|
204
|
-
|
|
322
|
+
// The name is caller-supplied and used as a computed key on both `workspace.documents` and
|
|
323
|
+
// `assets`, so a name like `__proto__` would write straight onto Object.prototype. Rejected
|
|
324
|
+
// here rather than filtered, and `addDocument` turns the throw into a skipped document.
|
|
325
|
+
preventPollution(name, 'server workspace document name');
|
|
326
|
+
// AsyncAPI documents get their own ingestion path, mirroring the client store. The OpenAPI
|
|
327
|
+
// upgrade and coerce steps would strip `channels` and `operations`, inject an empty
|
|
328
|
+
// `openapi: ''` that breaks the type discriminator, and add an empty `paths` object.
|
|
329
|
+
if (isAsyncApiDocument(document)) {
|
|
330
|
+
addAsyncApiDocumentSync(document, { name, documentMeta }, navigationOptions);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const upgradedDocument = upgrade(document, '3.1');
|
|
334
|
+
const documentV3 = coerceValue(OpenAPIDocumentSchema, upgradedDocument);
|
|
335
|
+
preserveBundledExternals(upgradedDocument, documentV3);
|
|
336
|
+
// Everything that inspects the document reads through this; everything that stores a piece of it
|
|
337
|
+
// stores the raw `documentV3` or a `getRaw` of the piece.
|
|
338
|
+
const resolvedDocument = resolveLocalReferences(documentV3);
|
|
205
339
|
// add the assets
|
|
206
340
|
assets[meta.name] = {
|
|
341
|
+
// Components need no resolution: they are externalized as authored, and the client resolves the
|
|
342
|
+
// references inside them the same way it resolves the ones this store leaves behind.
|
|
207
343
|
components: documentV3.components,
|
|
208
|
-
operations:
|
|
344
|
+
operations: resolvedDocument.paths && escapePaths(filterHttpMethodsOnly(resolvedDocument.paths)),
|
|
209
345
|
};
|
|
210
346
|
const options = workspaceProps.mode === 'ssr'
|
|
211
347
|
? { mode: workspaceProps.mode, name, baseUrl: workspaceProps.baseUrl }
|
|
212
348
|
: { mode: workspaceProps.mode, name, directory: workspaceProps.directory ?? DEFAULT_ASSETS_FOLDER };
|
|
213
349
|
const components = externalizeComponentReferences(documentV3, options);
|
|
214
|
-
const paths = externalizePathReferences(
|
|
350
|
+
const paths = externalizePathReferences(resolvedDocument, options);
|
|
215
351
|
// Build the sidebar entries
|
|
216
|
-
const navigation = createNavigation(name,
|
|
352
|
+
const navigation = createNavigation(name, resolvedDocument, navigationOptions ?? workspaceProps.navigationOptions);
|
|
217
353
|
// The document is now a minimal version with externalized references to components and operations.
|
|
218
354
|
// These references will be resolved asynchronously when needed through the workspace's get() method.
|
|
219
355
|
workspace.documents[meta.name] = {
|
|
@@ -235,12 +371,37 @@ export async function createServerWorkspaceStore(workspaceProps) {
|
|
|
235
371
|
* @param input - The document input containing the document source and metadata
|
|
236
372
|
*/
|
|
237
373
|
const addDocument = async (input, navigationOptions) => {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
374
|
+
// Captured so a failed add restores exactly what was there. The assets are written partway
|
|
375
|
+
// through, and several steps throw before that point, so clearing the key outright would strip
|
|
376
|
+
// a working document's chunks whenever a name is reused — leaving the workspace pointing at
|
|
377
|
+
// references that resolve to nothing.
|
|
378
|
+
//
|
|
379
|
+
// The snapshot is taken before the first await, so two adds racing under the same name would
|
|
380
|
+
// both capture the pre-state and the failing one could put back what the other just replaced.
|
|
381
|
+
// Adds are not serialized per name: concurrent adds sharing a name are already last-write-wins,
|
|
382
|
+
// and callers are expected to await one before starting another.
|
|
383
|
+
const assetsBeforeAdd = assets[input.name];
|
|
384
|
+
try {
|
|
385
|
+
const document = await loadDocument(input);
|
|
386
|
+
if (!document.ok) {
|
|
387
|
+
console.warn(`Failed to load document "${input.name}"`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
addDocumentSync(document.data, { name: input.name, ...input.meta }, navigationOptions);
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
// Honours the contract above: a document that cannot be processed is skipped rather than
|
|
394
|
+
// taking the workspace with it, since the initial documents are ingested together and one
|
|
395
|
+
// malformed description should not fail an entire documentation build. Loading is inside the
|
|
396
|
+
// try too, so a document that cannot even be serialized is skipped the same way.
|
|
397
|
+
if (assetsBeforeAdd === undefined) {
|
|
398
|
+
delete assets[input.name];
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
assets[input.name] = assetsBeforeAdd;
|
|
402
|
+
}
|
|
403
|
+
console.warn(`Failed to process document "${input.name}"`, error);
|
|
242
404
|
}
|
|
243
|
-
addDocumentSync(document.data, { name: input.name, ...input.meta }, navigationOptions);
|
|
244
405
|
};
|
|
245
406
|
// Load and process all initial documents in parallel
|
|
246
407
|
await Promise.all(workspaceProps.documents.map((document) => addDocument(document)));
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"openapi",
|
|
17
17
|
"scalar"
|
|
18
18
|
],
|
|
19
|
-
"version": "0.
|
|
19
|
+
"version": "0.58.1",
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=22"
|
|
22
22
|
},
|
|
@@ -144,23 +144,23 @@
|
|
|
144
144
|
],
|
|
145
145
|
"dependencies": {
|
|
146
146
|
"@scalar/typebox": "0.1.3",
|
|
147
|
-
"js-base64": "^3.
|
|
148
|
-
"type-fest": "^5.
|
|
147
|
+
"js-base64": "^3.9.2",
|
|
148
|
+
"type-fest": "^5.8.0",
|
|
149
149
|
"vue": "^3.5.40",
|
|
150
150
|
"yaml": "^2.9.0",
|
|
151
|
-
"@scalar/asyncapi-upgrader": "0.1.
|
|
152
|
-
"@scalar/
|
|
153
|
-
"@scalar/
|
|
154
|
-
"@scalar/
|
|
155
|
-
"@scalar/schemas": "0.8.
|
|
156
|
-
"@scalar/snippetz": "0.9.
|
|
157
|
-
"@scalar/types": "0.18.
|
|
158
|
-
"@scalar/validation": "0.6.
|
|
151
|
+
"@scalar/asyncapi-upgrader": "0.1.7",
|
|
152
|
+
"@scalar/helpers": "0.11.1",
|
|
153
|
+
"@scalar/json-magic": "0.13.2",
|
|
154
|
+
"@scalar/openapi-upgrader": "0.2.15",
|
|
155
|
+
"@scalar/schemas": "0.8.3",
|
|
156
|
+
"@scalar/snippetz": "0.9.28",
|
|
157
|
+
"@scalar/types": "0.18.2",
|
|
158
|
+
"@scalar/validation": "0.6.3"
|
|
159
159
|
},
|
|
160
160
|
"devDependencies": {
|
|
161
|
-
"@google-cloud/storage": "7.
|
|
162
|
-
"fake-indexeddb": "6.2.
|
|
163
|
-
"fastify": "^5.
|
|
161
|
+
"@google-cloud/storage": "7.21.0",
|
|
162
|
+
"fake-indexeddb": "6.2.5",
|
|
163
|
+
"fastify": "^5.11.2",
|
|
164
164
|
"vite": "8.1.5",
|
|
165
165
|
"vitest": "4.1.10"
|
|
166
166
|
},
|