@scalar/mock-server 0.9.9 → 0.9.11

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 (50) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/create-mock-server.js +79 -68
  3. package/dist/index.js +1 -5
  4. package/dist/libs/store.js +66 -65
  5. package/dist/routes/mock-any-response.js +70 -63
  6. package/dist/routes/mock-handler-response.js +140 -100
  7. package/dist/routes/respond-with-authorize-page.js +89 -80
  8. package/dist/routes/respond-with-openapi-document.js +32 -35
  9. package/dist/routes/respond-with-token.js +40 -45
  10. package/dist/types.js +2 -5
  11. package/dist/utils/build-handler-context.js +83 -70
  12. package/dist/utils/build-seed-context.js +59 -52
  13. package/dist/utils/create-openapi-definition.js +7 -10
  14. package/dist/utils/execute-handler.js +16 -18
  15. package/dist/utils/execute-seed.js +22 -21
  16. package/dist/utils/find-preferred-response-key.js +9 -9
  17. package/dist/utils/get-open-auth-token-urls.js +45 -35
  18. package/dist/utils/get-operation.js +12 -12
  19. package/dist/utils/handle-authentication.js +106 -101
  20. package/dist/utils/hono-route-from-path.js +6 -6
  21. package/dist/utils/is-authentication-required.js +17 -15
  22. package/dist/utils/log-authentication-instructions.js +105 -110
  23. package/dist/utils/process-openapi-document.js +71 -58
  24. package/dist/utils/set-up-authentication-routes.js +80 -77
  25. package/dist/utils/store-wrapper.js +40 -39
  26. package/package.json +10 -16
  27. package/dist/create-mock-server.js.map +0 -7
  28. package/dist/index.js.map +0 -7
  29. package/dist/libs/store.js.map +0 -7
  30. package/dist/routes/mock-any-response.js.map +0 -7
  31. package/dist/routes/mock-handler-response.js.map +0 -7
  32. package/dist/routes/respond-with-authorize-page.js.map +0 -7
  33. package/dist/routes/respond-with-openapi-document.js.map +0 -7
  34. package/dist/routes/respond-with-token.js.map +0 -7
  35. package/dist/types.js.map +0 -7
  36. package/dist/utils/build-handler-context.js.map +0 -7
  37. package/dist/utils/build-seed-context.js.map +0 -7
  38. package/dist/utils/create-openapi-definition.js.map +0 -7
  39. package/dist/utils/execute-handler.js.map +0 -7
  40. package/dist/utils/execute-seed.js.map +0 -7
  41. package/dist/utils/find-preferred-response-key.js.map +0 -7
  42. package/dist/utils/get-open-auth-token-urls.js.map +0 -7
  43. package/dist/utils/get-operation.js.map +0 -7
  44. package/dist/utils/handle-authentication.js.map +0 -7
  45. package/dist/utils/hono-route-from-path.js.map +0 -7
  46. package/dist/utils/is-authentication-required.js.map +0 -7
  47. package/dist/utils/log-authentication-instructions.js.map +0 -7
  48. package/dist/utils/process-openapi-document.js.map +0 -7
  49. package/dist/utils/set-up-authentication-routes.js.map +0 -7
  50. package/dist/utils/store-wrapper.js.map +0 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @scalar/mock-server
2
2
 
3
+ ## 0.9.11
4
+
5
+ ## 0.9.10
6
+
7
+ ### Patch Changes
8
+
9
+ - [#8466](https://github.com/scalar/scalar/pull/8466): chore: new build pipeline
10
+
3
11
  ## 0.9.9
4
12
 
5
13
  ### Patch Changes
@@ -1,72 +1,83 @@
1
- import { Hono } from "hono";
2
- import { cors } from "hono/cors";
3
- import { buildSeedContext } from "./utils/build-seed-context.js";
4
- import { executeSeed } from "./utils/execute-seed.js";
5
- import { getOperations } from "./utils/get-operation.js";
6
- import { handleAuthentication } from "./utils/handle-authentication.js";
7
- import { honoRouteFromPath } from "./utils/hono-route-from-path.js";
8
- import { isAuthenticationRequired } from "./utils/is-authentication-required.js";
9
- import { logAuthenticationInstructions } from "./utils/log-authentication-instructions.js";
10
- import { processOpenApiDocument } from "./utils/process-openapi-document.js";
11
- import { setUpAuthenticationRoutes } from "./utils/set-up-authentication-routes.js";
12
- import { store } from "./libs/store.js";
13
- import { mockAnyResponse } from "./routes/mock-any-response.js";
14
- import { mockHandlerResponse } from "./routes/mock-handler-response.js";
15
- import { respondWithOpenApiDocument } from "./routes/respond-with-openapi-document.js";
16
- async function createMockServer(configuration) {
17
- const app = new Hono();
18
- const schema = await processOpenApiDocument(configuration?.document ?? configuration?.specification);
19
- const schemas = schema?.components?.schemas;
20
- if (schemas) {
21
- for (const [schemaName, schemaObject] of Object.entries(schemas)) {
22
- const seedCode = schemaObject?.["x-seed"];
23
- if (seedCode && typeof seedCode === "string") {
24
- try {
25
- const existingItems = store.list(schemaName);
26
- if (existingItems.length === 0) {
27
- const seedContext = buildSeedContext(schemaName);
28
- await executeSeed(seedCode, seedContext);
29
- }
30
- } catch (error) {
31
- console.error(`Error seeding schema "${schemaName}":`, error);
1
+ import { Hono } from 'hono';
2
+ import { cors } from 'hono/cors';
3
+ import { buildSeedContext } from './utils/build-seed-context.js';
4
+ import { executeSeed } from './utils/execute-seed.js';
5
+ import { getOperations } from './utils/get-operation.js';
6
+ import { handleAuthentication } from './utils/handle-authentication.js';
7
+ import { honoRouteFromPath } from './utils/hono-route-from-path.js';
8
+ import { isAuthenticationRequired } from './utils/is-authentication-required.js';
9
+ import { logAuthenticationInstructions } from './utils/log-authentication-instructions.js';
10
+ import { processOpenApiDocument } from './utils/process-openapi-document.js';
11
+ import { setUpAuthenticationRoutes } from './utils/set-up-authentication-routes.js';
12
+ import { store } from './libs/store.js';
13
+ import { mockAnyResponse } from './routes/mock-any-response.js';
14
+ import { mockHandlerResponse } from './routes/mock-handler-response.js';
15
+ import { respondWithOpenApiDocument } from './routes/respond-with-openapi-document.js';
16
+ /**
17
+ * Create a mock server instance
18
+ */
19
+ export async function createMockServer(configuration) {
20
+ const app = new Hono();
21
+ /** Dereferenced OpenAPI document */
22
+ const schema = await processOpenApiDocument(configuration?.document ?? configuration?.specification);
23
+ // Seed data from schemas with x-seed extension
24
+ // This happens before routes are set up so data is available immediately
25
+ const schemas = schema?.components?.schemas;
26
+ if (schemas) {
27
+ for (const [schemaName, schemaObject] of Object.entries(schemas)) {
28
+ const seedCode = schemaObject?.['x-seed'];
29
+ if (seedCode && typeof seedCode === 'string') {
30
+ try {
31
+ // Check if collection is empty (idempotent seeding)
32
+ // Use the schema key directly as the collection name
33
+ const existingItems = store.list(schemaName);
34
+ if (existingItems.length === 0) {
35
+ // Build seed context with schema key (used as collection name)
36
+ const seedContext = buildSeedContext(schemaName);
37
+ // Execute seed code
38
+ await executeSeed(seedCode, seedContext);
39
+ }
40
+ }
41
+ catch (error) {
42
+ // Log error but don't fail server startup
43
+ console.error(`Error seeding schema "${schemaName}":`, error);
44
+ }
45
+ }
32
46
  }
33
- }
34
47
  }
35
- }
36
- app.use(cors());
37
- setUpAuthenticationRoutes(app, schema);
38
- logAuthenticationInstructions(
39
- schema?.components?.securitySchemes || {}
40
- );
41
- const paths = schema?.paths ?? {};
42
- Object.keys(paths).forEach((path) => {
43
- const methods = Object.keys(getOperations(paths[path]));
44
- methods.forEach((method) => {
45
- const route = honoRouteFromPath(path);
46
- const operation = schema?.paths?.[path]?.[method];
47
- if (isAuthenticationRequired(operation.security)) {
48
- app[method](route, handleAuthentication(schema, operation));
49
- }
50
- const handlerCode = operation?.["x-handler"];
51
- const hasHandler = handlerCode && typeof handlerCode === "string" && handlerCode.trim().length > 0;
52
- if (hasHandler) {
53
- app[method](route, (c) => mockHandlerResponse(c, operation, configuration));
54
- } else {
55
- app[method](route, (c) => mockAnyResponse(c, operation, configuration));
56
- }
48
+ // CORS headers
49
+ app.use(cors());
50
+ /** Authentication methods defined in the OpenAPI document */
51
+ setUpAuthenticationRoutes(app, schema);
52
+ logAuthenticationInstructions(schema?.components?.securitySchemes || {});
53
+ /** Paths specified in the OpenAPI document */
54
+ const paths = schema?.paths ?? {};
55
+ Object.keys(paths).forEach((path) => {
56
+ const methods = Object.keys(getOperations(paths[path]));
57
+ /** Keys for all operations of a specified path */
58
+ methods.forEach((method) => {
59
+ const route = honoRouteFromPath(path);
60
+ const operation = schema?.paths?.[path]?.[method];
61
+ // Check if authentication is required for this operation
62
+ if (isAuthenticationRequired(operation.security)) {
63
+ app[method](route, handleAuthentication(schema, operation));
64
+ }
65
+ // Check if operation has x-handler extension
66
+ // Validate that it's a non-empty string (consistent with x-seed validation)
67
+ const handlerCode = operation?.['x-handler'];
68
+ const hasHandler = handlerCode && typeof handlerCode === 'string' && handlerCode.trim().length > 0;
69
+ // Route to appropriate handler
70
+ if (hasHandler) {
71
+ app[method](route, (c) => mockHandlerResponse(c, operation, configuration));
72
+ }
73
+ else {
74
+ app[method](route, (c) => mockAnyResponse(c, operation, configuration));
75
+ }
76
+ });
57
77
  });
58
- });
59
- app.get(
60
- "/openapi.json",
61
- (c) => respondWithOpenApiDocument(c, configuration?.document ?? configuration?.specification, "json")
62
- );
63
- app.get(
64
- "/openapi.yaml",
65
- (c) => respondWithOpenApiDocument(c, configuration?.document ?? configuration?.specification, "yaml")
66
- );
67
- return app;
78
+ // OpenAPI JSON file
79
+ app.get('/openapi.json', (c) => respondWithOpenApiDocument(c, configuration?.document ?? configuration?.specification, 'json'));
80
+ // OpenAPI YAML file
81
+ app.get('/openapi.yaml', (c) => respondWithOpenApiDocument(c, configuration?.document ?? configuration?.specification, 'yaml'));
82
+ return app;
68
83
  }
69
- export {
70
- createMockServer
71
- };
72
- //# sourceMappingURL=create-mock-server.js.map
package/dist/index.js CHANGED
@@ -1,5 +1 @@
1
- import { createMockServer } from "./create-mock-server.js";
2
- export {
3
- createMockServer
4
- };
5
- //# sourceMappingURL=index.js.map
1
+ export { createMockServer } from './create-mock-server.js';
@@ -1,70 +1,71 @@
1
- class Store {
2
- data = {};
3
- /**
4
- * Get all items in a collection.
5
- */
6
- list(collection) {
7
- const items = this.data[collection];
8
- return items ? Object.values(items) : [];
9
- }
10
- /**
11
- * Get a single item by ID.
12
- */
13
- get(collection, id) {
14
- return this.data[collection]?.[id];
15
- }
16
- /**
17
- * Create a new item in a collection.
18
- * Auto-generates an ID if not provided.
19
- */
20
- create(collection, data) {
21
- if (!this.data[collection]) {
22
- this.data[collection] = {};
1
+ export class Store {
2
+ data = {};
3
+ /**
4
+ * Get all items in a collection.
5
+ */
6
+ list(collection) {
7
+ const items = this.data[collection];
8
+ return items ? Object.values(items) : [];
23
9
  }
24
- const safeData = data ?? {};
25
- const id = safeData.id ?? crypto.randomUUID();
26
- const item = { ...safeData, id };
27
- this.data[collection][id] = item;
28
- return item;
29
- }
30
- /**
31
- * Update an existing item in a collection.
32
- * Returns null if the item is not found.
33
- */
34
- update(collection, id, data) {
35
- if (!this.data[collection]?.[id]) {
36
- return null;
10
+ /**
11
+ * Get a single item by ID.
12
+ */
13
+ get(collection, id) {
14
+ return this.data[collection]?.[id];
37
15
  }
38
- const safeData = data ?? {};
39
- const updated = { ...this.data[collection][id], ...safeData, id };
40
- this.data[collection][id] = updated;
41
- return updated;
42
- }
43
- /**
44
- * Delete an item from a collection.
45
- * Returns null if the item is not found.
46
- */
47
- delete(collection, id) {
48
- if (!this.data[collection]?.[id]) {
49
- return null;
16
+ /**
17
+ * Create a new item in a collection.
18
+ * Auto-generates an ID if not provided.
19
+ */
20
+ create(collection, data) {
21
+ if (!this.data[collection]) {
22
+ this.data[collection] = {};
23
+ }
24
+ // Handle null/undefined data by defaulting to empty object
25
+ const safeData = data ?? {};
26
+ const id = safeData.id ?? crypto.randomUUID();
27
+ const item = { ...safeData, id };
28
+ this.data[collection][id] = item;
29
+ return item;
50
30
  }
51
- delete this.data[collection][id];
52
- return true;
53
- }
54
- /**
55
- * Clear a specific collection or all collections.
56
- */
57
- clear(collection) {
58
- if (collection) {
59
- delete this.data[collection];
60
- } else {
61
- this.data = {};
31
+ /**
32
+ * Update an existing item in a collection.
33
+ * Returns null if the item is not found.
34
+ */
35
+ update(collection, id, data) {
36
+ if (!this.data[collection]?.[id]) {
37
+ return null;
38
+ }
39
+ // Handle null/undefined data by defaulting to empty object
40
+ const safeData = data ?? {};
41
+ const updated = { ...this.data[collection][id], ...safeData, id };
42
+ this.data[collection][id] = updated;
43
+ return updated;
44
+ }
45
+ /**
46
+ * Delete an item from a collection.
47
+ * Returns null if the item is not found.
48
+ */
49
+ delete(collection, id) {
50
+ if (!this.data[collection]?.[id]) {
51
+ return null;
52
+ }
53
+ delete this.data[collection][id];
54
+ return true;
55
+ }
56
+ /**
57
+ * Clear a specific collection or all collections.
58
+ */
59
+ clear(collection) {
60
+ if (collection) {
61
+ delete this.data[collection];
62
+ }
63
+ else {
64
+ this.data = {};
65
+ }
62
66
  }
63
- }
64
67
  }
65
- const store = new Store();
66
- export {
67
- Store,
68
- store
69
- };
70
- //# sourceMappingURL=store.js.map
68
+ /**
69
+ * Singleton store instance shared across all requests.
70
+ */
71
+ export const store = new Store();
@@ -1,67 +1,74 @@
1
- import { json2xml } from "@scalar/helpers/file/json2xml";
2
- import { getExampleFromSchema } from "@scalar/oas-utils/spec-getters";
3
- import { accepts } from "hono/accepts";
4
- import { findPreferredResponseKey } from "../utils/find-preferred-response-key.js";
5
- function mockAnyResponse(c, operation, options) {
6
- if (options?.onRequest) {
7
- options.onRequest({
8
- context: c,
9
- operation
1
+ import { json2xml } from '@scalar/helpers/file/json2xml';
2
+ import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
3
+ import { accepts } from 'hono/accepts';
4
+ import { findPreferredResponseKey } from '../utils/find-preferred-response-key.js';
5
+ /**
6
+ * Mock any response
7
+ */
8
+ export function mockAnyResponse(c, operation, options) {
9
+ // Call onRequest callback
10
+ if (options?.onRequest) {
11
+ options.onRequest({
12
+ context: c,
13
+ operation,
14
+ });
15
+ }
16
+ // Response
17
+ // default, 200, 201 …
18
+ const preferredResponseKey = findPreferredResponseKey(Object.keys(operation.responses ?? {}));
19
+ const preferredResponse = preferredResponseKey ? operation.responses?.[preferredResponseKey] : null;
20
+ if (!preferredResponse) {
21
+ c.status(500);
22
+ return c.json({ error: 'No response defined for this operation.' });
23
+ }
24
+ // Status code
25
+ const statusCode = Number.parseInt(preferredResponseKey === 'default' ? '200' : (preferredResponseKey ?? '200'), 10);
26
+ // Headers
27
+ const headers = preferredResponse?.headers ?? {};
28
+ Object.keys(headers).forEach((header) => {
29
+ const value = headers[header].schema ? getExampleFromSchema(headers[header].schema) : null;
30
+ if (value !== null) {
31
+ c.header(header, value);
32
+ }
10
33
  });
11
- }
12
- const preferredResponseKey = findPreferredResponseKey(Object.keys(operation.responses ?? {}));
13
- const preferredResponse = preferredResponseKey ? operation.responses?.[preferredResponseKey] : null;
14
- if (!preferredResponse) {
15
- c.status(500);
16
- return c.json({ error: "No response defined for this operation." });
17
- }
18
- const statusCode = Number.parseInt(
19
- preferredResponseKey === "default" ? "200" : preferredResponseKey ?? "200",
20
- 10
21
- );
22
- const headers = preferredResponse?.headers ?? {};
23
- Object.keys(headers).forEach((header) => {
24
- const value = headers[header].schema ? getExampleFromSchema(headers[header].schema) : null;
25
- if (value !== null) {
26
- c.header(header, value);
34
+ // For 204 No Content responses, we should not set Content-Type and should return null body
35
+ if (statusCode === 204) {
36
+ c.status(statusCode);
37
+ return c.body(null);
27
38
  }
28
- });
29
- if (statusCode === 204) {
30
- c.status(statusCode);
31
- return c.body(null);
32
- }
33
- const supportedContentTypes = Object.keys(preferredResponse?.content ?? {});
34
- if (supportedContentTypes.length === 0) {
39
+ const supportedContentTypes = Object.keys(preferredResponse?.content ?? {});
40
+ // If no content types are defined, return the status with no body
41
+ if (supportedContentTypes.length === 0) {
42
+ c.status(statusCode);
43
+ return c.body(null);
44
+ }
45
+ // Content-Type
46
+ const acceptedContentType = accepts(c, {
47
+ header: 'Accept',
48
+ supports: supportedContentTypes,
49
+ default: supportedContentTypes.includes('application/json')
50
+ ? 'application/json'
51
+ : (supportedContentTypes[0] ?? 'text/plain;charset=UTF-8'),
52
+ });
53
+ c.header('Content-Type', acceptedContentType);
54
+ const acceptedResponse = preferredResponse?.content?.[acceptedContentType];
55
+ // Body
56
+ const body = acceptedResponse?.example
57
+ ? acceptedResponse.example
58
+ : acceptedResponse?.schema
59
+ ? getExampleFromSchema(acceptedResponse.schema, {
60
+ emptyString: 'string',
61
+ variables: c.req.param(),
62
+ mode: 'read',
63
+ })
64
+ : null;
35
65
  c.status(statusCode);
36
- return c.body(null);
37
- }
38
- const acceptedContentType = accepts(c, {
39
- header: "Accept",
40
- supports: supportedContentTypes,
41
- default: supportedContentTypes.includes("application/json") ? "application/json" : supportedContentTypes[0] ?? "text/plain;charset=UTF-8"
42
- });
43
- c.header("Content-Type", acceptedContentType);
44
- const acceptedResponse = preferredResponse?.content?.[acceptedContentType];
45
- const body = acceptedResponse?.example ? acceptedResponse.example : acceptedResponse?.schema ? getExampleFromSchema(acceptedResponse.schema, {
46
- emptyString: "string",
47
- variables: c.req.param(),
48
- mode: "read"
49
- }) : null;
50
- c.status(statusCode);
51
- return c.body(
52
- typeof body === "object" ? (
53
- // XML
54
- acceptedContentType?.includes("xml") ? json2xml(body) : (
55
- // JSON
56
- JSON.stringify(body, null, 2)
57
- )
58
- ) : (
59
- // String
60
- body
61
- )
62
- );
66
+ return c.body(typeof body === 'object'
67
+ ? // XML
68
+ acceptedContentType?.includes('xml')
69
+ ? json2xml(body)
70
+ : // JSON
71
+ JSON.stringify(body, null, 2)
72
+ : // String
73
+ body);
63
74
  }
64
- export {
65
- mockAnyResponse
66
- };
67
- //# sourceMappingURL=mock-any-response.js.map