@scalar/mock-server 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/create-asyncapi-mock-server.d.ts +51 -0
  3. package/dist/create-asyncapi-mock-server.d.ts.map +1 -0
  4. package/dist/create-asyncapi-mock-server.js +51 -0
  5. package/dist/index.d.ts +4 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -0
  8. package/dist/routes/mock-any-response.d.ts.map +1 -1
  9. package/dist/routes/mock-any-response.js +9 -7
  10. package/dist/routes/mock-handler-response.d.ts.map +1 -1
  11. package/dist/routes/mock-handler-response.js +6 -5
  12. package/dist/transports/index.d.ts +9 -0
  13. package/dist/transports/index.d.ts.map +1 -0
  14. package/dist/transports/index.js +9 -0
  15. package/dist/transports/sse.d.ts +11 -0
  16. package/dist/transports/sse.d.ts.map +1 -0
  17. package/dist/transports/sse.js +37 -0
  18. package/dist/transports/types.d.ts +94 -0
  19. package/dist/transports/types.d.ts.map +1 -0
  20. package/dist/transports/types.js +1 -0
  21. package/dist/transports/websocket.d.ts +10 -0
  22. package/dist/transports/websocket.d.ts.map +1 -0
  23. package/dist/transports/websocket.js +45 -0
  24. package/dist/utils/build-handler-context.d.ts.map +1 -1
  25. package/dist/utils/build-handler-context.js +6 -4
  26. package/dist/utils/deserialize-parameter.d.ts +76 -0
  27. package/dist/utils/deserialize-parameter.d.ts.map +1 -0
  28. package/dist/utils/deserialize-parameter.js +297 -0
  29. package/dist/utils/find-preferred-response-key.d.ts +14 -1
  30. package/dist/utils/find-preferred-response-key.d.ts.map +1 -1
  31. package/dist/utils/find-preferred-response-key.js +41 -7
  32. package/dist/utils/generate-message.d.ts +12 -0
  33. package/dist/utils/generate-message.d.ts.map +1 -0
  34. package/dist/utils/generate-message.js +41 -0
  35. package/dist/utils/normalize-response-body.d.ts +5 -0
  36. package/dist/utils/normalize-response-body.d.ts.map +1 -0
  37. package/dist/utils/normalize-response-body.js +25 -0
  38. package/dist/utils/process-asyncapi-document.d.ts +23 -0
  39. package/dist/utils/process-asyncapi-document.d.ts.map +1 -0
  40. package/dist/utils/process-asyncapi-document.js +56 -0
  41. package/dist/utils/resolve-channels.d.ts +11 -0
  42. package/dist/utils/resolve-channels.d.ts.map +1 -0
  43. package/dist/utils/resolve-channels.js +100 -0
  44. package/dist/utils/validate-request.d.ts +2 -2
  45. package/dist/utils/validate-request.d.ts.map +1 -1
  46. package/dist/utils/validate-request.js +173 -22
  47. package/package.json +5 -3
@@ -0,0 +1,100 @@
1
+ import { ASYNCAPI_WEBSOCKET_PROTOCOLS, getAllChannelMessages, getAsyncApiServers, getChannelOperations, resolveChannel, } from '@scalar/workspace-store/channel-example';
2
+ import { getNameFromRef } from '@scalar/workspace-store/helpers/get-name-from-ref';
3
+ import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
4
+ import { honoRouteFromPath } from '../utils/hono-route-from-path.js';
5
+ /** Extract a plain JSON Schema from a message `payload`, unwrapping the Multi Format Schema Object. */
6
+ function extractPayloadSchema(payload) {
7
+ const resolved = getResolvedRef(payload);
8
+ if (!resolved || typeof resolved !== 'object') {
9
+ return undefined;
10
+ }
11
+ // Multi Format Schema Object: `{ schemaFormat, schema }`. The actual schema lives under `schema`.
12
+ if ('schemaFormat' in resolved && 'schema' in resolved) {
13
+ return getResolvedRef(resolved.schema);
14
+ }
15
+ return resolved;
16
+ }
17
+ /**
18
+ * Adapt a resolved AsyncAPI message into the transport-facing {@link ResolvedMessage} shape: a plain
19
+ * payload schema (ready for `getExampleFromSchema`), any defined examples, and the content type.
20
+ *
21
+ * @param key - The message's key in `channel.messages` (what operation `$ref`s point at).
22
+ * @param message - The dereferenced AsyncAPI message object.
23
+ */
24
+ function toResolvedMessage(key, message, defaultContentType) {
25
+ const resolved = message;
26
+ const examples = Array.isArray(resolved.examples)
27
+ ? resolved.examples
28
+ .map((example) => getResolvedRef(example))
29
+ .filter((example) => example?.payload !== undefined)
30
+ .map((example) => example.payload)
31
+ : [];
32
+ return {
33
+ id: resolved.name ?? key,
34
+ payload: extractPayloadSchema(resolved.payload),
35
+ examples,
36
+ contentType: resolved.contentType ?? defaultContentType,
37
+ };
38
+ }
39
+ /**
40
+ * Collect the connection protocols (lower-cased) advertised for a channel. Server protocols come
41
+ * from the shared workspace-store resolver (the same one the API client uses), so the mock and the
42
+ * client classify a document identically. A WebSocket binding is an additional reliable hint when
43
+ * the channel declares no servers.
44
+ */
45
+ function resolveProtocols(document, channel) {
46
+ const protocols = new Set();
47
+ for (const server of getAsyncApiServers(document, { channel: channel, webSocketOnly: false })) {
48
+ if (server.protocol) {
49
+ protocols.add(server.protocol);
50
+ }
51
+ }
52
+ const bindings = getResolvedRef(channel.bindings);
53
+ if (bindings?.ws) {
54
+ protocols.add(ASYNCAPI_WEBSOCKET_PROTOCOLS[0]);
55
+ }
56
+ return [...protocols];
57
+ }
58
+ /**
59
+ * Normalize an AsyncAPI 3.1 document into a flat list of {@link ResolvedChannel}s a transport can
60
+ * serve. Channel, message, operation, and server resolution are delegated to
61
+ * `@scalar/workspace-store/channel-example` — the same layer the API client uses to connect to
62
+ * channels — so the mock and the client agree on how a document maps to channels and operations
63
+ * (including operation traits). This function only adapts that output into the transport types.
64
+ */
65
+ export function resolveChannels(document) {
66
+ const channelNames = Object.keys(document.channels ?? {});
67
+ return channelNames.flatMap((channelName) => {
68
+ const resolved = resolveChannel(document, channelName);
69
+ if (!resolved) {
70
+ return [];
71
+ }
72
+ const { channel, channelAddress } = resolved;
73
+ const messageEntries = getAllChannelMessages(document, channel);
74
+ const messages = messageEntries.map(({ name, message }) => toResolvedMessage(name, message, document.defaultContentType));
75
+ // Keyed by the `channels.*.messages` key (what operation `$ref`s point at), which can differ
76
+ // from a message's resolved `id` when the message declares a `name`.
77
+ const messagesByKey = new Map(messageEntries.map(({ name }, index) => [name, messages[index]]));
78
+ const operations = getChannelOperations(document, channelName).map(({ operationName, operation, action }) => {
79
+ // An operation may scope itself to a subset of the channel's messages; otherwise use all.
80
+ const operationMessages = Array.isArray(operation.messages)
81
+ ? operation.messages
82
+ .map((message) => message?.['$ref'])
83
+ .filter((ref) => typeof ref === 'string')
84
+ .map((ref) => messagesByKey.get(getNameFromRef(ref, ['channels', channelName, 'messages']) ?? ''))
85
+ .filter((message) => message !== undefined)
86
+ : messages;
87
+ return { id: operationName, action, messages: operationMessages };
88
+ });
89
+ return [
90
+ {
91
+ id: channelName,
92
+ address: channelAddress,
93
+ route: honoRouteFromPath(`/${channelAddress.replace(/^\//, '')}`),
94
+ protocols: resolveProtocols(document, channel),
95
+ operations,
96
+ messages,
97
+ },
98
+ ];
99
+ });
100
+ }
@@ -7,8 +7,8 @@ import type { MiddlewareHandler } from 'hono';
7
7
  * violation the middleware short-circuits with a `422` and a `application/problem+json` body;
8
8
  * otherwise it calls `next()` and the normal mock handler runs.
9
9
  *
10
- * TODO: Parity follow-ups, intentionally deferred in this slice response validation,
11
- * header/cookie parameter validation, non-JSON body validation, and validation proxy mode.
10
+ * TODO: Parity follow-ups, intentionally deferred response validation, non-JSON body validation,
11
+ * and validation proxy mode.
12
12
  */
13
13
  export declare const validateRequest: (operation: OpenAPIV3_1.OperationObject, pathItemParameters?: OpenAPIV3_1.PathItemObject["parameters"]) => MiddlewareHandler;
14
14
  //# sourceMappingURL=validate-request.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-request.d.ts","sourceRoot":"","sources":["../../src/utils/validate-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAMxD,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAmLtD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,WAAW,CAAC,eAAe,EACtC,qBAAqB,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,KAC5D,iBAyDF,CAAA"}
1
+ {"version":3,"file":"validate-request.d.ts","sourceRoot":"","sources":["../../src/utils/validate-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAMxD,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAuRtD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,WAAW,CAAC,eAAe,EACtC,qBAAqB,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,KAC5D,iBAkKF,CAAA"}
@@ -2,22 +2,66 @@ import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref
2
2
  import { getResolvedRefDeep } from '@scalar/workspace-store/helpers/get-resolved-ref-deep';
3
3
  import Ajv2020 from 'ajv/dist/2020.js';
4
4
  import addFormats from 'ajv-formats';
5
+ import { getCookie } from 'hono/cookie';
6
+ import { deserializeArrayParameter, deserializeObjectParameter, getObjectPropertyNames, isArraySchema, isObjectSchema, resolveSerialization, } from './deserialize-parameter.js';
5
7
  /**
6
- * Build a JSON Schema object for the parameters declared `in` the given location.
8
+ * Header parameters named `Accept`, `Content-Type`, or `Authorization` are defined elsewhere in
9
+ * OpenAPI (through `content` and `security`), so the spec says such parameter definitions SHALL be
10
+ * ignored. We compare case-insensitively because header names are case-insensitive.
11
+ */
12
+ const IGNORED_HEADER_PARAMETERS = new Set(['accept', 'content-type', 'authorization']);
13
+ /**
14
+ * Collapse a `key -> string[]` map (as returned by Hono's `c.req.queries()`) into a `key -> string |
15
+ * string[]` map: a single value becomes a plain string, while repeated keys keep their array so
16
+ * array-valued object properties are not silently truncated to the first value.
17
+ */
18
+ const mapRepeatedValues = (values) => {
19
+ const map = {};
20
+ for (const [key, list] of Object.entries(values)) {
21
+ map[key] = list.length > 1 ? list : (list[0] ?? '');
22
+ }
23
+ return map;
24
+ };
25
+ /**
26
+ * Build a JSON Schema object for the parameters declared `in` the given location, alongside a
27
+ * descriptor for each declared parameter.
7
28
  *
8
29
  * Parameters arrive as strings, so the caller compiles these with `coerceTypes: true` to let
9
- * `type: integer`/`boolean` validate correctly. Returns `null` when there is nothing to validate.
30
+ * `type: integer`/`boolean` validate correctly. The descriptors let the middleware gather only
31
+ * declared values and deserialize array parameters by their `style`/`explode`. Returns `null` when
32
+ * there is nothing to validate.
10
33
  */
11
34
  const buildParameterSchema = (parameters, location) => {
12
35
  const properties = {};
13
36
  const required = [];
37
+ const descriptors = [];
14
38
  for (const parameterOrRef of parameters ?? []) {
15
39
  const parameter = getResolvedRef(parameterOrRef);
16
40
  if (parameter?.in !== location) {
17
41
  continue;
18
42
  }
19
- if (parameter.schema) {
20
- properties[parameter.name] = getResolvedRefDeep(parameter.schema);
43
+ // Per the OpenAPI spec, these header parameters are defined elsewhere and ignored as parameters.
44
+ if (location === 'header' && IGNORED_HEADER_PARAMETERS.has(parameter.name.toLowerCase())) {
45
+ continue;
46
+ }
47
+ const resolvedSchema = parameter.schema
48
+ ? getResolvedRefDeep(parameter.schema)
49
+ : undefined;
50
+ const { style, explode } = resolveSerialization(location, parameter.style, parameter.explode);
51
+ // Property names let exploded `form` objects be gathered from matching top-level query keys. They
52
+ // are collected through any `anyOf`/`oneOf`/`allOf` composition so a composed object schema does not
53
+ // fall back to free-form gathering and claim unrelated keys.
54
+ const propertyNames = getObjectPropertyNames(resolvedSchema);
55
+ descriptors.push({
56
+ name: parameter.name,
57
+ style,
58
+ explode,
59
+ isArray: isArraySchema(resolvedSchema),
60
+ isObject: isObjectSchema(resolvedSchema),
61
+ propertyNames,
62
+ });
63
+ if (resolvedSchema) {
64
+ properties[parameter.name] = resolvedSchema;
21
65
  }
22
66
  if (parameter.required) {
23
67
  required.push(parameter.name);
@@ -27,7 +71,7 @@ const buildParameterSchema = (parameters, location) => {
27
71
  return null;
28
72
  }
29
73
  // Allow undeclared parameters to pass through; we only enforce what the operation declares.
30
- return { type: 'object', properties, required, additionalProperties: true };
74
+ return { schema: { type: 'object', properties, required, additionalProperties: true }, parameters: descriptors };
31
75
  };
32
76
  /**
33
77
  * Merge path-item-level parameters with operation-level parameters.
@@ -81,6 +125,10 @@ const compileValidators = (operation, pathItemParameters) => {
81
125
  addFormats(bodyAjv);
82
126
  // Path-item parameters apply to every operation, so fold them in before building the schemas.
83
127
  const parameters = mergeParameters(pathItemParameters, operation.parameters);
128
+ const pathParameters = buildParameterSchema(parameters, 'path');
129
+ const queryParameters = buildParameterSchema(parameters, 'query');
130
+ const headerParameters = buildParameterSchema(parameters, 'header');
131
+ const cookieParameters = buildParameterSchema(parameters, 'cookie');
84
132
  const requestBody = getResolvedRef(operation.requestBody);
85
133
  // Build the body schema defensively; resolving a malformed `$ref` should not crash setup.
86
134
  let bodySchema = null;
@@ -92,22 +140,35 @@ const compileValidators = (operation, pathItemParameters) => {
92
140
  console.error('Error resolving request body schema, skipping body validation:', error);
93
141
  }
94
142
  return {
95
- path: compileSchema(parameterAjv, buildParameterSchema(parameters, 'path'), 'path parameter'),
96
- query: compileSchema(parameterAjv, buildParameterSchema(parameters, 'query'), 'query parameter'),
143
+ path: compileSchema(parameterAjv, pathParameters?.schema ?? null, 'path parameter'),
144
+ query: compileSchema(parameterAjv, queryParameters?.schema ?? null, 'query parameter'),
145
+ header: compileSchema(parameterAjv, headerParameters?.schema ?? null, 'header parameter'),
146
+ cookie: compileSchema(parameterAjv, cookieParameters?.schema ?? null, 'cookie parameter'),
147
+ pathParameters: pathParameters?.parameters ?? [],
148
+ queryParameters: queryParameters?.parameters ?? [],
149
+ headerParameters: headerParameters?.parameters ?? [],
150
+ cookieParameters: cookieParameters?.parameters ?? [],
97
151
  body: compileSchema(bodyAjv, bodySchema, 'request body'),
98
152
  // Required-body enforcement is independent of whether the body schema compiles.
99
153
  bodyRequired: requestBody?.required === true,
100
154
  };
101
155
  };
102
156
  /**
103
- * Map Ajv errors into our violation shape. For path/query parameters the offending parameter name
104
- * is prepended to the message so the response is readable without cross-referencing the pointer.
157
+ * Map Ajv errors into our violation shape. For non-body parameters (path, query, header, cookie) the
158
+ * offending parameter name is prepended to the message so the response is readable without
159
+ * cross-referencing the pointer.
105
160
  */
106
161
  const mapErrors = (errors, location) => (errors ?? []).map((error) => {
107
162
  const missingProperty = typeof error.params === 'object' && error.params && 'missingProperty' in error.params
108
163
  ? String(error.params.missingProperty)
109
164
  : '';
110
165
  const path = error.instancePath || (missingProperty ? `/${missingProperty}` : '');
166
+ // A missing parameter surfaces as a top-level `required` error whose `missingProperty` is the
167
+ // parameter name. Phrase it as "<name> is required" instead of the confusing, self-referential
168
+ // "<name> must have required property '<name>'" that Ajv produces against the synthetic wrapper.
169
+ if (location !== 'body' && missingProperty && !error.instancePath) {
170
+ return { location, path, message: `${missingProperty} is required` };
171
+ }
111
172
  const name = error.instancePath.replace(/^\//, '') || missingProperty;
112
173
  const message = location !== 'body' && name ? `${name} ${error.message ?? ''}`.trim() : (error.message ?? '');
113
174
  return { location, path, message };
@@ -129,25 +190,115 @@ const isJsonRequest = (c) => {
129
190
  * violation the middleware short-circuits with a `422` and a `application/problem+json` body;
130
191
  * otherwise it calls `next()` and the normal mock handler runs.
131
192
  *
132
- * TODO: Parity follow-ups, intentionally deferred in this slice response validation,
133
- * header/cookie parameter validation, non-JSON body validation, and validation proxy mode.
193
+ * TODO: Parity follow-ups, intentionally deferred response validation, non-JSON body validation,
194
+ * and validation proxy mode.
134
195
  */
135
196
  export const validateRequest = (operation, pathItemParameters) => {
136
197
  const validators = compileValidators(operation, pathItemParameters);
137
198
  return async (c, next) => {
138
199
  const violations = [];
139
- // Path parameters (coerced from strings)
140
- if (validators.path) {
141
- const data = { ...c.req.param() };
142
- if (!validators.path(data)) {
143
- violations.push(...mapErrors(validators.path.errors, 'path'));
200
+ /**
201
+ * Gather declared parameter values for one location into a plain object for validation. Array and
202
+ * object parameters are deserialized by their `style`/`explode`; everything else is read as a
203
+ * single string. Values are looked up by declared name (rather than dumping every request value)
204
+ * so header names match case-insensitively and only declared parameters are enforced.
205
+ *
206
+ * `getValues` (repeated query keys) is only supplied for the query location, where exploded arrays
207
+ * need it. `getMap` returns the full key/value map for the location and is supplied for query and
208
+ * cookie, where exploded-`form` objects (and query `deepObject`) spread properties across keys —
209
+ * for cookies each object property is its own cookie (`Cookie: r=100; g=200`).
210
+ */
211
+ const gather = (descriptors, getValue, getValues, getMap) => {
212
+ // Names of every declared parameter in this location, so a free-form exploded object does not
213
+ // swallow a sibling parameter's key (e.g. a required free-form object satisfied by `?limit=5`).
214
+ const declaredNames = new Set(descriptors.map((descriptor) => descriptor.name));
215
+ const readAsObject = (descriptor) => deserializeObjectParameter({
216
+ style: descriptor.style,
217
+ explode: descriptor.explode,
218
+ single: getValue(descriptor.name),
219
+ map: getMap?.(),
220
+ name: descriptor.name,
221
+ propertyNames: descriptor.propertyNames,
222
+ reservedKeys: declaredNames,
223
+ });
224
+ const readAsArray = (descriptor) => deserializeArrayParameter({
225
+ style: descriptor.style,
226
+ explode: descriptor.explode,
227
+ single: getValue(descriptor.name),
228
+ multi: getValues?.(descriptor.name),
229
+ });
230
+ const data = {};
231
+ for (const descriptor of descriptors) {
232
+ let value;
233
+ if (descriptor.isArray && descriptor.isObject) {
234
+ // A schema composed with `anyOf`/`oneOf`/`allOf` can look like both an array and an object, and
235
+ // form encoding cannot say which the client meant. Prefer a non-empty array — that only happens
236
+ // when the request carries the parameter's own (possibly repeated) key — and fall back to the
237
+ // object reading otherwise. This parses array-style input (`?filter=1&filter=2`) as an array while
238
+ // still letting object-style input (properties spread across other keys) be gathered as an object.
239
+ // A lone empty value (`?filter=`) deserializes to `[]`, which is not array-shaped input, so it must
240
+ // not short-circuit the object fallback; the empty array is only kept when no object value is found.
241
+ const asArray = readAsArray(descriptor);
242
+ value = Array.isArray(asArray) && asArray.length > 0 ? asArray : (readAsObject(descriptor) ?? asArray);
243
+ }
244
+ else if (descriptor.isObject) {
245
+ value = readAsObject(descriptor);
246
+ }
247
+ else if (descriptor.isArray) {
248
+ value = readAsArray(descriptor);
249
+ }
250
+ else {
251
+ value = getValue(descriptor.name);
252
+ }
253
+ if (value !== undefined) {
254
+ data[descriptor.name] = value;
255
+ }
144
256
  }
145
- }
146
- // Query parameters (coerced from strings)
147
- if (validators.query) {
148
- const data = { ...c.req.query() };
149
- if (!validators.query(data)) {
150
- violations.push(...mapErrors(validators.query.errors, 'query'));
257
+ return data;
258
+ };
259
+ // Validate each parameter location with its own request accessors. Values are coerced from strings.
260
+ // The differences between locations are only the accessors:
261
+ // - query supplies `getValues` (repeated keys feed exploded arrays) and a `getMap` that keeps
262
+ // repeated keys as arrays, so an array-valued object property (`filter[tags]=a&filter[tags]=b`)
263
+ // survives deserialization;
264
+ // - cookie supplies a `getMap` (each exploded `form` object property is its own cookie);
265
+ // - header names are matched case-insensitively by Hono's `c.req.header`.
266
+ const parameterLocations = [
267
+ {
268
+ validator: validators.path,
269
+ descriptors: validators.pathParameters,
270
+ location: 'path',
271
+ getValue: (name) => c.req.param(name),
272
+ },
273
+ {
274
+ validator: validators.query,
275
+ descriptors: validators.queryParameters,
276
+ location: 'query',
277
+ getValue: (name) => c.req.query(name),
278
+ getValues: (name) => c.req.queries(name),
279
+ getMap: () => mapRepeatedValues(c.req.queries()),
280
+ },
281
+ {
282
+ validator: validators.header,
283
+ descriptors: validators.headerParameters,
284
+ location: 'header',
285
+ getValue: (name) => c.req.header(name),
286
+ },
287
+ {
288
+ validator: validators.cookie,
289
+ descriptors: validators.cookieParameters,
290
+ location: 'cookie',
291
+ getValue: (name) => getCookie(c, name),
292
+ getMap: () => getCookie(c),
293
+ },
294
+ ];
295
+ for (const { validator, descriptors, location, getValue, getValues, getMap } of parameterLocations) {
296
+ if (!validator) {
297
+ continue;
298
+ }
299
+ const data = gather(descriptors, getValue, getValues, getMap);
300
+ if (!validator(data)) {
301
+ violations.push(...mapErrors(validator.errors, location));
151
302
  }
152
303
  }
153
304
  // Request body — only `application/json` in this slice
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "swagger",
17
17
  "cli"
18
18
  ],
19
- "version": "0.11.0",
19
+ "version": "0.12.0",
20
20
  "engines": {
21
21
  "node": ">=22"
22
22
  },
@@ -52,15 +52,17 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@faker-js/faker": "10.4.0",
55
+ "@hono/node-ws": "^1.2.0",
55
56
  "ajv": "^8.17.1",
56
57
  "ajv-formats": "^3.0.1",
57
58
  "hono": "^4.12.7",
58
59
  "yaml": "^2.8.3",
59
60
  "@scalar/helpers": "0.8.2",
61
+ "@scalar/openapi-upgrader": "0.2.9",
60
62
  "@scalar/json-magic": "0.12.16",
61
63
  "@scalar/openapi-types": "0.9.1",
62
- "@scalar/openapi-upgrader": "0.2.9",
63
- "@scalar/workspace-store": "0.54.3"
64
+ "@scalar/workspace-store": "0.54.5",
65
+ "@scalar/types": "0.15.0"
64
66
  },
65
67
  "devDependencies": {
66
68
  "@types/node": "^24.1.0",