@scalar/mock-server 0.7.2 → 0.8.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 +32 -0
- package/dist/create-mock-server.d.ts.map +1 -1
- package/dist/create-mock-server.js +32 -5
- package/dist/create-mock-server.js.map +2 -2
- package/dist/libs/store.d.ts +35 -0
- package/dist/libs/store.d.ts.map +1 -0
- package/dist/libs/store.js +70 -0
- package/dist/libs/store.js.map +7 -0
- package/dist/routes/mock-any-response.d.ts +2 -2
- package/dist/routes/mock-any-response.d.ts.map +1 -1
- package/dist/routes/mock-any-response.js.map +2 -2
- package/dist/routes/mock-handler-response.d.ts +10 -0
- package/dist/routes/mock-handler-response.d.ts.map +1 -0
- package/dist/routes/mock-handler-response.js +110 -0
- package/dist/routes/mock-handler-response.js.map +7 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/build-handler-context.d.ts +31 -0
- package/dist/utils/build-handler-context.d.ts.map +1 -0
- package/dist/utils/build-handler-context.js +75 -0
- package/dist/utils/build-handler-context.js.map +7 -0
- package/dist/utils/build-seed-context.d.ts +35 -0
- package/dist/utils/build-seed-context.d.ts.map +1 -0
- package/dist/utils/build-seed-context.js +53 -0
- package/dist/utils/build-seed-context.js.map +7 -0
- package/dist/utils/execute-handler.d.ts +14 -0
- package/dist/utils/execute-handler.d.ts.map +1 -0
- package/dist/utils/execute-handler.js +20 -0
- package/dist/utils/execute-handler.js.map +7 -0
- package/dist/utils/execute-seed.d.ts +14 -0
- package/dist/utils/execute-seed.d.ts.map +1 -0
- package/dist/utils/execute-seed.js +24 -0
- package/dist/utils/execute-seed.js.map +7 -0
- package/dist/utils/get-operation.d.ts +2 -2
- package/dist/utils/get-operation.d.ts.map +1 -1
- package/dist/utils/get-operation.js.map +2 -2
- package/dist/utils/handle-authentication.d.ts +2 -2
- package/dist/utils/handle-authentication.d.ts.map +1 -1
- package/dist/utils/handle-authentication.js +38 -31
- package/dist/utils/handle-authentication.js.map +2 -2
- package/dist/utils/process-openapi-document.d.ts +11 -0
- package/dist/utils/process-openapi-document.d.ts.map +1 -0
- package/dist/utils/process-openapi-document.js +59 -0
- package/dist/utils/process-openapi-document.js.map +7 -0
- package/dist/utils/store-wrapper.d.ts +31 -0
- package/dist/utils/store-wrapper.d.ts.map +1 -0
- package/dist/utils/store-wrapper.js +40 -0
- package/dist/utils/store-wrapper.js.map +7 -0
- package/package.json +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @scalar/mock-server
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`9ec8adf`](https://github.com/scalar/scalar/commit/9ec8adfea017333dee5bc3949104232f7dc57f4a)]:
|
|
8
|
+
- @scalar/helpers@0.2.0
|
|
9
|
+
- @scalar/oas-utils@0.6.6
|
|
10
|
+
- @scalar/json-magic@0.8.4
|
|
11
|
+
- @scalar/openapi-parser@0.23.5
|
|
12
|
+
|
|
13
|
+
## 0.8.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- [#7298](https://github.com/scalar/scalar/pull/7298) [`663e436`](https://github.com/scalar/scalar/commit/663e4361892ec0f1f1c94e1b2fbde754bdf92cd6) Thanks [@hanspagel](https://github.com/hanspagel)! - feat: support external references
|
|
18
|
+
|
|
19
|
+
- [#7298](https://github.com/scalar/scalar/pull/7298) [`663e436`](https://github.com/scalar/scalar/commit/663e4361892ec0f1f1c94e1b2fbde754bdf92cd6) Thanks [@hanspagel](https://github.com/hanspagel)! - feat: x-seed to prefill the in-memory database
|
|
20
|
+
|
|
21
|
+
- [#7298](https://github.com/scalar/scalar/pull/7298) [`663e436`](https://github.com/scalar/scalar/commit/663e4361892ec0f1f1c94e1b2fbde754bdf92cd6) Thanks [@hanspagel](https://github.com/hanspagel)! - feat: x-handler to script responses
|
|
22
|
+
|
|
23
|
+
- [#7298](https://github.com/scalar/scalar/pull/7298) [`663e436`](https://github.com/scalar/scalar/commit/663e4361892ec0f1f1c94e1b2fbde754bdf92cd6) Thanks [@hanspagel](https://github.com/hanspagel)! - feat: upgrade all documents to OpenAPI 3.1 now
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [[`e04879c`](https://github.com/scalar/scalar/commit/e04879c65602dfb65393876754f5344751b8953d), [`a164d76`](https://github.com/scalar/scalar/commit/a164d76f21437b3a35210d62a996b6c9d483e5a4), [`bfd814a`](https://github.com/scalar/scalar/commit/bfd814a4219660face190041cc4845182b56ab03), [`86f028d`](https://github.com/scalar/scalar/commit/86f028deb0b456f923edd261f5f4b0fa9b616b7d), [`af54a80`](https://github.com/scalar/scalar/commit/af54a80349269a4269a68f6a372f837177a3537c)]:
|
|
28
|
+
- @scalar/json-magic@0.8.3
|
|
29
|
+
- @scalar/openapi-types@0.5.2
|
|
30
|
+
- @scalar/helpers@0.1.3
|
|
31
|
+
- @scalar/oas-utils@0.6.5
|
|
32
|
+
- @scalar/openapi-parser@0.23.4
|
|
33
|
+
- @scalar/openapi-upgrader@0.1.5
|
|
34
|
+
|
|
3
35
|
## 0.7.2
|
|
4
36
|
|
|
5
37
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-mock-server.d.ts","sourceRoot":"","sources":["../src/create-mock-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-mock-server.d.ts","sourceRoot":"","sources":["../src/create-mock-server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAgB5D;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoFtF"}
|
|
@@ -1,17 +1,38 @@
|
|
|
1
|
-
import { dereference } from "@scalar/openapi-parser";
|
|
2
1
|
import { Hono } from "hono";
|
|
3
2
|
import { cors } from "hono/cors";
|
|
3
|
+
import { buildSeedContext } from "./utils/build-seed-context.js";
|
|
4
|
+
import { executeSeed } from "./utils/execute-seed.js";
|
|
4
5
|
import { getOperations } from "./utils/get-operation.js";
|
|
5
6
|
import { handleAuthentication } from "./utils/handle-authentication.js";
|
|
6
7
|
import { honoRouteFromPath } from "./utils/hono-route-from-path.js";
|
|
7
8
|
import { isAuthenticationRequired } from "./utils/is-authentication-required.js";
|
|
8
9
|
import { logAuthenticationInstructions } from "./utils/log-authentication-instructions.js";
|
|
10
|
+
import { processOpenApiDocument } from "./utils/process-openapi-document.js";
|
|
9
11
|
import { setUpAuthenticationRoutes } from "./utils/set-up-authentication-routes.js";
|
|
12
|
+
import { store } from "./libs/store.js";
|
|
10
13
|
import { mockAnyResponse } from "./routes/mock-any-response.js";
|
|
14
|
+
import { mockHandlerResponse } from "./routes/mock-handler-response.js";
|
|
11
15
|
import { respondWithOpenApiDocument } from "./routes/respond-with-openapi-document.js";
|
|
12
|
-
function createMockServer(configuration) {
|
|
16
|
+
async function createMockServer(configuration) {
|
|
13
17
|
const app = new Hono();
|
|
14
|
-
const
|
|
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);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
15
36
|
app.use(cors());
|
|
16
37
|
setUpAuthenticationRoutes(app, schema);
|
|
17
38
|
logAuthenticationInstructions(
|
|
@@ -26,7 +47,13 @@ function createMockServer(configuration) {
|
|
|
26
47
|
if (isAuthenticationRequired(operation.security)) {
|
|
27
48
|
app[method](route, handleAuthentication(schema, operation));
|
|
28
49
|
}
|
|
29
|
-
|
|
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
|
+
}
|
|
30
57
|
});
|
|
31
58
|
});
|
|
32
59
|
app.get(
|
|
@@ -37,7 +64,7 @@ function createMockServer(configuration) {
|
|
|
37
64
|
"/openapi.yaml",
|
|
38
65
|
(c) => respondWithOpenApiDocument(c, configuration?.document ?? configuration?.specification, "yaml")
|
|
39
66
|
);
|
|
40
|
-
return
|
|
67
|
+
return app;
|
|
41
68
|
}
|
|
42
69
|
export {
|
|
43
70
|
createMockServer
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/create-mock-server.ts"],
|
|
4
|
-
"sourcesContent": ["import
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { OpenAPIV3_1 } from '@scalar/openapi-types'\nimport { Hono } from 'hono'\nimport { cors } from 'hono/cors'\n\nimport type { HttpMethod, MockServerOptions } from '@/types'\nimport { buildSeedContext } from '@/utils/build-seed-context'\nimport { executeSeed } from '@/utils/execute-seed'\nimport { getOperations } from '@/utils/get-operation'\nimport { handleAuthentication } from '@/utils/handle-authentication'\nimport { honoRouteFromPath } from '@/utils/hono-route-from-path'\nimport { isAuthenticationRequired } from '@/utils/is-authentication-required'\nimport { logAuthenticationInstructions } from '@/utils/log-authentication-instructions'\nimport { processOpenApiDocument } from '@/utils/process-openapi-document'\nimport { setUpAuthenticationRoutes } from '@/utils/set-up-authentication-routes'\n\nimport { store } from './libs/store'\nimport { mockAnyResponse } from './routes/mock-any-response'\nimport { mockHandlerResponse } from './routes/mock-handler-response'\nimport { respondWithOpenApiDocument } from './routes/respond-with-openapi-document'\n\n/**\n * Create a mock server instance\n */\nexport async function createMockServer(configuration: MockServerOptions): Promise<Hono> {\n const app = new Hono()\n\n /** Dereferenced OpenAPI document */\n const schema = await processOpenApiDocument(configuration?.document ?? configuration?.specification)\n\n // Seed data from schemas with x-seed extension\n // This happens before routes are set up so data is available immediately\n const schemas = schema?.components?.schemas\n if (schemas) {\n for (const [schemaName, schemaObject] of Object.entries(schemas)) {\n const seedCode = (schemaObject as any)?.['x-seed']\n\n if (seedCode && typeof seedCode === 'string') {\n try {\n // Check if collection is empty (idempotent seeding)\n // Use the schema key directly as the collection name\n const existingItems = store.list(schemaName)\n if (existingItems.length === 0) {\n // Build seed context with schema key (used as collection name)\n const seedContext = buildSeedContext(schemaName)\n\n // Execute seed code\n await executeSeed(seedCode, seedContext)\n }\n } catch (error) {\n // Log error but don't fail server startup\n console.error(`Error seeding schema \"${schemaName}\":`, error)\n }\n }\n }\n }\n\n // CORS headers\n app.use(cors())\n\n /** Authentication methods defined in the OpenAPI document */\n setUpAuthenticationRoutes(app, schema)\n\n logAuthenticationInstructions(\n schema?.components?.securitySchemes || ({} as Record<string, OpenAPIV3_1.SecuritySchemeObject>),\n )\n\n /** Paths specified in the OpenAPI document */\n const paths = schema?.paths ?? {}\n\n Object.keys(paths).forEach((path) => {\n const methods = Object.keys(getOperations(paths[path])) as HttpMethod[]\n\n /** Keys for all operations of a specified path */\n methods.forEach((method) => {\n const route = honoRouteFromPath(path)\n const operation = schema?.paths?.[path]?.[method] as OpenAPIV3_1.OperationObject\n\n // Check if authentication is required for this operation\n if (isAuthenticationRequired(operation.security)) {\n app[method](route, handleAuthentication(schema, operation))\n }\n\n // Check if operation has x-handler extension\n // Validate that it's a non-empty string (consistent with x-seed validation)\n const handlerCode = operation?.['x-handler']\n const hasHandler = handlerCode && typeof handlerCode === 'string' && handlerCode.trim().length > 0\n\n // Route to appropriate handler\n if (hasHandler) {\n app[method](route, (c) => mockHandlerResponse(c, operation, configuration))\n } else {\n app[method](route, (c) => mockAnyResponse(c, operation, configuration))\n }\n })\n })\n\n // OpenAPI JSON file\n app.get('/openapi.json', (c) =>\n respondWithOpenApiDocument(c, configuration?.document ?? configuration?.specification, 'json'),\n )\n\n // OpenAPI YAML file\n app.get('/openapi.yaml', (c) =>\n respondWithOpenApiDocument(c, configuration?.document ?? configuration?.specification, 'yaml'),\n )\n\n return app\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,YAAY;AACrB,SAAS,YAAY;AAGrB,SAAS,wBAAwB;AACjC,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,4BAA4B;AACrC,SAAS,yBAAyB;AAClC,SAAS,gCAAgC;AACzC,SAAS,qCAAqC;AAC9C,SAAS,8BAA8B;AACvC,SAAS,iCAAiC;AAE1C,SAAS,aAAa;AACtB,SAAS,uBAAuB;AAChC,SAAS,2BAA2B;AACpC,SAAS,kCAAkC;AAK3C,eAAsB,iBAAiB,eAAiD;AACtF,QAAM,MAAM,IAAI,KAAK;AAGrB,QAAM,SAAS,MAAM,uBAAuB,eAAe,YAAY,eAAe,aAAa;AAInG,QAAM,UAAU,QAAQ,YAAY;AACpC,MAAI,SAAS;AACX,eAAW,CAAC,YAAY,YAAY,KAAK,OAAO,QAAQ,OAAO,GAAG;AAChE,YAAM,WAAY,eAAuB,QAAQ;AAEjD,UAAI,YAAY,OAAO,aAAa,UAAU;AAC5C,YAAI;AAGF,gBAAM,gBAAgB,MAAM,KAAK,UAAU;AAC3C,cAAI,cAAc,WAAW,GAAG;AAE9B,kBAAM,cAAc,iBAAiB,UAAU;AAG/C,kBAAM,YAAY,UAAU,WAAW;AAAA,UACzC;AAAA,QACF,SAAS,OAAO;AAEd,kBAAQ,MAAM,yBAAyB,UAAU,MAAM,KAAK;AAAA,QAC9D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,IAAI,KAAK,CAAC;AAGd,4BAA0B,KAAK,MAAM;AAErC;AAAA,IACE,QAAQ,YAAY,mBAAoB,CAAC;AAAA,EAC3C;AAGA,QAAM,QAAQ,QAAQ,SAAS,CAAC;AAEhC,SAAO,KAAK,KAAK,EAAE,QAAQ,CAAC,SAAS;AACnC,UAAM,UAAU,OAAO,KAAK,cAAc,MAAM,IAAI,CAAC,CAAC;AAGtD,YAAQ,QAAQ,CAAC,WAAW;AAC1B,YAAM,QAAQ,kBAAkB,IAAI;AACpC,YAAM,YAAY,QAAQ,QAAQ,IAAI,IAAI,MAAM;AAGhD,UAAI,yBAAyB,UAAU,QAAQ,GAAG;AAChD,YAAI,MAAM,EAAE,OAAO,qBAAqB,QAAQ,SAAS,CAAC;AAAA,MAC5D;AAIA,YAAM,cAAc,YAAY,WAAW;AAC3C,YAAM,aAAa,eAAe,OAAO,gBAAgB,YAAY,YAAY,KAAK,EAAE,SAAS;AAGjG,UAAI,YAAY;AACd,YAAI,MAAM,EAAE,OAAO,CAAC,MAAM,oBAAoB,GAAG,WAAW,aAAa,CAAC;AAAA,MAC5E,OAAO;AACL,YAAI,MAAM,EAAE,OAAO,CAAC,MAAM,gBAAgB,GAAG,WAAW,aAAa,CAAC;AAAA,MACxE;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAGD,MAAI;AAAA,IAAI;AAAA,IAAiB,CAAC,MACxB,2BAA2B,GAAG,eAAe,YAAY,eAAe,eAAe,MAAM;AAAA,EAC/F;AAGA,MAAI;AAAA,IAAI;AAAA,IAAiB,CAAC,MACxB,2BAA2B,GAAG,eAAe,YAAY,eAAe,eAAe,MAAM;AAAA,EAC/F;AAEA,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare class Store {
|
|
2
|
+
private data;
|
|
3
|
+
/**
|
|
4
|
+
* Get all items in a collection.
|
|
5
|
+
*/
|
|
6
|
+
list(collection: string): any[];
|
|
7
|
+
/**
|
|
8
|
+
* Get a single item by ID.
|
|
9
|
+
*/
|
|
10
|
+
get(collection: string, id: string): any | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Create a new item in a collection.
|
|
13
|
+
* Auto-generates an ID if not provided.
|
|
14
|
+
*/
|
|
15
|
+
create(collection: string, data: any): any;
|
|
16
|
+
/**
|
|
17
|
+
* Update an existing item in a collection.
|
|
18
|
+
* Returns null if the item is not found.
|
|
19
|
+
*/
|
|
20
|
+
update(collection: string, id: string, data: any): any | null;
|
|
21
|
+
/**
|
|
22
|
+
* Delete an item from a collection.
|
|
23
|
+
* Returns null if the item is not found.
|
|
24
|
+
*/
|
|
25
|
+
delete(collection: string, id: string): boolean | null;
|
|
26
|
+
/**
|
|
27
|
+
* Clear a specific collection or all collections.
|
|
28
|
+
*/
|
|
29
|
+
clear(collection?: string): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Singleton store instance shared across all requests.
|
|
33
|
+
*/
|
|
34
|
+
export declare const store: Store;
|
|
35
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/libs/store.ts"],"names":[],"mappings":"AAMA,qBAAa,KAAK;IAChB,OAAO,CAAC,IAAI,CAAgB;IAE5B;;OAEG;IACH,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,EAAE;IAK/B;;OAEG;IACH,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAIpD;;;OAGG;IACH,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,GAAG;IAe1C;;;OAGG;IACH,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,GAAG,GAAG,IAAI;IAa7D;;;OAGG;IACH,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAUtD;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI;CAOjC;AAED;;GAEG;AACH,eAAO,MAAM,KAAK,OAAc,CAAA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
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] = {};
|
|
23
|
+
}
|
|
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;
|
|
37
|
+
}
|
|
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;
|
|
50
|
+
}
|
|
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 = {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const store = new Store();
|
|
66
|
+
export {
|
|
67
|
+
Store,
|
|
68
|
+
store
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/libs/store.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Simple in-memory store for x-handler operations.\n * Data persists during server lifetime but is reset on server restart.\n */\ntype StoreData = Record<string, Record<string, any>>\n\nexport class Store {\n private data: StoreData = {}\n\n /**\n * Get all items in a collection.\n */\n list(collection: string): any[] {\n const items = this.data[collection]\n return items ? Object.values(items) : []\n }\n\n /**\n * Get a single item by ID.\n */\n get(collection: string, id: string): any | undefined {\n return this.data[collection]?.[id]\n }\n\n /**\n * Create a new item in a collection.\n * Auto-generates an ID if not provided.\n */\n create(collection: string, data: any): any {\n if (!this.data[collection]) {\n this.data[collection] = {}\n }\n\n // Handle null/undefined data by defaulting to empty object\n const safeData = data ?? {}\n const id = safeData.id ?? crypto.randomUUID()\n const item = { ...safeData, id }\n\n this.data[collection][id] = item\n\n return item\n }\n\n /**\n * Update an existing item in a collection.\n * Returns null if the item is not found.\n */\n update(collection: string, id: string, data: any): any | null {\n if (!this.data[collection]?.[id]) {\n return null\n }\n\n // Handle null/undefined data by defaulting to empty object\n const safeData = data ?? {}\n const updated = { ...this.data[collection][id], ...safeData, id }\n this.data[collection][id] = updated\n\n return updated\n }\n\n /**\n * Delete an item from a collection.\n * Returns null if the item is not found.\n */\n delete(collection: string, id: string): boolean | null {\n if (!this.data[collection]?.[id]) {\n return null\n }\n\n delete this.data[collection][id]\n\n return true\n }\n\n /**\n * Clear a specific collection or all collections.\n */\n clear(collection?: string): void {\n if (collection) {\n delete this.data[collection]\n } else {\n this.data = {}\n }\n }\n}\n\n/**\n * Singleton store instance shared across all requests.\n */\nexport const store = new Store()\n"],
|
|
5
|
+
"mappings": "AAMO,MAAM,MAAM;AAAA,EACT,OAAkB,CAAC;AAAA;AAAA;AAAA;AAAA,EAK3B,KAAK,YAA2B;AAC9B,UAAM,QAAQ,KAAK,KAAK,UAAU;AAClC,WAAO,QAAQ,OAAO,OAAO,KAAK,IAAI,CAAC;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAoB,IAA6B;AACnD,WAAO,KAAK,KAAK,UAAU,IAAI,EAAE;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,YAAoB,MAAgB;AACzC,QAAI,CAAC,KAAK,KAAK,UAAU,GAAG;AAC1B,WAAK,KAAK,UAAU,IAAI,CAAC;AAAA,IAC3B;AAGA,UAAM,WAAW,QAAQ,CAAC;AAC1B,UAAM,KAAK,SAAS,MAAM,OAAO,WAAW;AAC5C,UAAM,OAAO,EAAE,GAAG,UAAU,GAAG;AAE/B,SAAK,KAAK,UAAU,EAAE,EAAE,IAAI;AAE5B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,YAAoB,IAAY,MAAuB;AAC5D,QAAI,CAAC,KAAK,KAAK,UAAU,IAAI,EAAE,GAAG;AAChC,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,QAAQ,CAAC;AAC1B,UAAM,UAAU,EAAE,GAAG,KAAK,KAAK,UAAU,EAAE,EAAE,GAAG,GAAG,UAAU,GAAG;AAChE,SAAK,KAAK,UAAU,EAAE,EAAE,IAAI;AAE5B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,YAAoB,IAA4B;AACrD,QAAI,CAAC,KAAK,KAAK,UAAU,IAAI,EAAE,GAAG;AAChC,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,KAAK,UAAU,EAAE,EAAE;AAE/B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAA2B;AAC/B,QAAI,YAAY;AACd,aAAO,KAAK,KAAK,UAAU;AAAA,IAC7B,OAAO;AACL,WAAK,OAAO,CAAC;AAAA,IACf;AAAA,EACF;AACF;AAKO,MAAM,QAAQ,IAAI,MAAM;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OpenAPIV3_1 } from '@scalar/openapi-types';
|
|
2
2
|
import type { Context } from 'hono';
|
|
3
3
|
import type { StatusCode } from 'hono/utils/http-status';
|
|
4
4
|
import type { MockServerOptions } from '../types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Mock any response
|
|
7
7
|
*/
|
|
8
|
-
export declare function mockAnyResponse(c: Context, operation:
|
|
8
|
+
export declare function mockAnyResponse(c: Context, operation: OpenAPIV3_1.OperationObject, options: MockServerOptions): (Response & import("hono").TypedResponse<{
|
|
9
9
|
error: string;
|
|
10
10
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<null, StatusCode, "body">) | (Response & import("hono").TypedResponse<any, import("hono/utils/http-status").ContentfulStatusCode, "body">);
|
|
11
11
|
//# sourceMappingURL=mock-any-response.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-any-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-any-response.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"mock-any-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-any-response.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAGhD;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,EAAE,OAAO,EAAE,iBAAiB;;yPAqF7G"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/mock-any-response.ts"],
|
|
4
|
-
"sourcesContent": ["import { json2xml } from '@scalar/helpers/file/json2xml'\nimport { getExampleFromSchema } from '@scalar/oas-utils/spec-getters'\nimport type {
|
|
5
|
-
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,4BAA4B;AAGrC,SAAS,eAAe;AAIxB,SAAS,gCAAgC;AAKlC,SAAS,gBAAgB,GAAY,
|
|
4
|
+
"sourcesContent": ["import { json2xml } from '@scalar/helpers/file/json2xml'\nimport { getExampleFromSchema } from '@scalar/oas-utils/spec-getters'\nimport type { OpenAPIV3_1 } from '@scalar/openapi-types'\nimport type { Context } from 'hono'\nimport { accepts } from 'hono/accepts'\nimport type { StatusCode } from 'hono/utils/http-status'\n\nimport type { MockServerOptions } from '@/types'\nimport { findPreferredResponseKey } from '@/utils/find-preferred-response-key'\n\n/**\n * Mock any response\n */\nexport function mockAnyResponse(c: Context, operation: OpenAPIV3_1.OperationObject, options: MockServerOptions) {\n // Call onRequest callback\n if (options?.onRequest) {\n options.onRequest({\n context: c,\n operation,\n })\n }\n\n // Response\n // default, 200, 201 \u2026\n const preferredResponseKey = findPreferredResponseKey(Object.keys(operation.responses ?? {}))\n const preferredResponse = preferredResponseKey ? operation.responses?.[preferredResponseKey] : null\n\n if (!preferredResponse) {\n c.status(500)\n\n return c.json({ error: 'No response defined for this operation.' })\n }\n\n // Status code\n const statusCode = Number.parseInt(\n preferredResponseKey === 'default' ? '200' : (preferredResponseKey ?? '200'),\n 10,\n ) as StatusCode\n\n // Headers\n const headers = preferredResponse?.headers ?? {}\n Object.keys(headers).forEach((header) => {\n const value = headers[header].schema ? getExampleFromSchema(headers[header].schema) : null\n if (value !== null) {\n c.header(header, value)\n }\n })\n\n // For 204 No Content responses, we should not set Content-Type and should return null body\n if (statusCode === 204) {\n c.status(statusCode)\n return c.body(null)\n }\n\n const supportedContentTypes = Object.keys(preferredResponse?.content ?? {})\n\n // If no content types are defined, return the status with no body\n if (supportedContentTypes.length === 0) {\n c.status(statusCode)\n return c.body(null)\n }\n\n // Content-Type\n const acceptedContentType = accepts(c, {\n header: 'Accept',\n supports: supportedContentTypes,\n default: supportedContentTypes.includes('application/json')\n ? 'application/json'\n : (supportedContentTypes[0] ?? 'text/plain;charset=UTF-8'),\n })\n\n c.header('Content-Type', acceptedContentType)\n\n const acceptedResponse = preferredResponse?.content?.[acceptedContentType]\n\n // Body\n const body = acceptedResponse?.example\n ? acceptedResponse.example\n : acceptedResponse?.schema\n ? getExampleFromSchema(acceptedResponse.schema, {\n emptyString: 'string',\n variables: c.req.param(),\n mode: 'read',\n })\n : null\n\n c.status(statusCode)\n\n return c.body(\n typeof body === 'object'\n ? // XML\n acceptedContentType?.includes('xml')\n ? json2xml(body)\n : // JSON\n JSON.stringify(body, null, 2)\n : // String\n body,\n )\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,4BAA4B;AAGrC,SAAS,eAAe;AAIxB,SAAS,gCAAgC;AAKlC,SAAS,gBAAgB,GAAY,WAAwC,SAA4B;AAE9G,MAAI,SAAS,WAAW;AACtB,YAAQ,UAAU;AAAA,MAChB,SAAS;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH;AAIA,QAAM,uBAAuB,yBAAyB,OAAO,KAAK,UAAU,aAAa,CAAC,CAAC,CAAC;AAC5F,QAAM,oBAAoB,uBAAuB,UAAU,YAAY,oBAAoB,IAAI;AAE/F,MAAI,CAAC,mBAAmB;AACtB,MAAE,OAAO,GAAG;AAEZ,WAAO,EAAE,KAAK,EAAE,OAAO,0CAA0C,CAAC;AAAA,EACpE;AAGA,QAAM,aAAa,OAAO;AAAA,IACxB,yBAAyB,YAAY,QAAS,wBAAwB;AAAA,IACtE;AAAA,EACF;AAGA,QAAM,UAAU,mBAAmB,WAAW,CAAC;AAC/C,SAAO,KAAK,OAAO,EAAE,QAAQ,CAAC,WAAW;AACvC,UAAM,QAAQ,QAAQ,MAAM,EAAE,SAAS,qBAAqB,QAAQ,MAAM,EAAE,MAAM,IAAI;AACtF,QAAI,UAAU,MAAM;AAClB,QAAE,OAAO,QAAQ,KAAK;AAAA,IACxB;AAAA,EACF,CAAC;AAGD,MAAI,eAAe,KAAK;AACtB,MAAE,OAAO,UAAU;AACnB,WAAO,EAAE,KAAK,IAAI;AAAA,EACpB;AAEA,QAAM,wBAAwB,OAAO,KAAK,mBAAmB,WAAW,CAAC,CAAC;AAG1E,MAAI,sBAAsB,WAAW,GAAG;AACtC,MAAE,OAAO,UAAU;AACnB,WAAO,EAAE,KAAK,IAAI;AAAA,EACpB;AAGA,QAAM,sBAAsB,QAAQ,GAAG;AAAA,IACrC,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,sBAAsB,SAAS,kBAAkB,IACtD,qBACC,sBAAsB,CAAC,KAAK;AAAA,EACnC,CAAC;AAED,IAAE,OAAO,gBAAgB,mBAAmB;AAE5C,QAAM,mBAAmB,mBAAmB,UAAU,mBAAmB;AAGzE,QAAM,OAAO,kBAAkB,UAC3B,iBAAiB,UACjB,kBAAkB,SAChB,qBAAqB,iBAAiB,QAAQ;AAAA,IAC5C,aAAa;AAAA,IACb,WAAW,EAAE,IAAI,MAAM;AAAA,IACvB,MAAM;AAAA,EACR,CAAC,IACD;AAEN,IAAE,OAAO,UAAU;AAEnB,SAAO,EAAE;AAAA,IACP,OAAO,SAAS;AAAA;AAAA,MAEZ,qBAAqB,SAAS,KAAK,IACjC,SAAS,IAAI;AAAA;AAAA,QAEb,KAAK,UAAU,MAAM,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA,MAE9B;AAAA;AAAA,EACN;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OpenAPIV3_1 } from '@scalar/openapi-types';
|
|
2
|
+
import type { Context } from 'hono';
|
|
3
|
+
import type { StatusCode } from 'hono/utils/http-status';
|
|
4
|
+
import type { MockServerOptions } from '../types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Mock response using x-handler code.
|
|
7
|
+
* Executes the handler and returns its result as the response.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mockHandlerResponse(c: Context, operation: OpenAPIV3_1.OperationObject, options: MockServerOptions): Promise<(Response & import("hono").TypedResponse<null, StatusCode, "body">) | (Response & import("hono").TypedResponse<any, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
10
|
+
//# sourceMappingURL=mock-handler-response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-handler-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-handler-response.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAoHhD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,WAAW,CAAC,eAAe,EACtC,OAAO,EAAE,iBAAiB,gMAkE3B"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getExampleFromSchema } from "@scalar/oas-utils/spec-getters";
|
|
2
|
+
import { accepts } from "hono/accepts";
|
|
3
|
+
import { buildHandlerContext } from "../utils/build-handler-context.js";
|
|
4
|
+
import { executeHandler } from "../utils/execute-handler.js";
|
|
5
|
+
function getExampleFromResponse(c, statusCode, responses) {
|
|
6
|
+
if (!responses) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const statusCodeStr = statusCode.toString();
|
|
10
|
+
const response = responses[statusCodeStr] || responses.default;
|
|
11
|
+
if (!response) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const supportedContentTypes = Object.keys(response.content ?? {});
|
|
15
|
+
if (supportedContentTypes.length === 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const acceptedContentType = accepts(c, {
|
|
19
|
+
header: "Accept",
|
|
20
|
+
supports: supportedContentTypes,
|
|
21
|
+
default: supportedContentTypes.includes("application/json") ? "application/json" : supportedContentTypes[0] ?? "text/plain;charset=UTF-8"
|
|
22
|
+
});
|
|
23
|
+
const acceptedResponse = response.content?.[acceptedContentType];
|
|
24
|
+
if (!acceptedResponse) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return acceptedResponse.example !== void 0 ? acceptedResponse.example : acceptedResponse.schema ? getExampleFromSchema(acceptedResponse.schema, {
|
|
28
|
+
emptyString: "string",
|
|
29
|
+
variables: c.req.param(),
|
|
30
|
+
mode: "read"
|
|
31
|
+
}) : null;
|
|
32
|
+
}
|
|
33
|
+
function determineStatusCode(tracking) {
|
|
34
|
+
const { operations } = tracking;
|
|
35
|
+
if (operations.length === 0) {
|
|
36
|
+
return 200;
|
|
37
|
+
}
|
|
38
|
+
const getOperation = operations.find((op) => op.operation === "get");
|
|
39
|
+
if (getOperation) {
|
|
40
|
+
if (getOperation.result === void 0 || getOperation.result === null) {
|
|
41
|
+
return 404;
|
|
42
|
+
}
|
|
43
|
+
return 200;
|
|
44
|
+
}
|
|
45
|
+
const updateOperation = operations.find((op) => op.operation === "update");
|
|
46
|
+
if (updateOperation) {
|
|
47
|
+
if (updateOperation.result === null || updateOperation.result === void 0) {
|
|
48
|
+
return 404;
|
|
49
|
+
}
|
|
50
|
+
return 200;
|
|
51
|
+
}
|
|
52
|
+
const deleteOperation = operations.find((op) => op.operation === "delete");
|
|
53
|
+
if (deleteOperation) {
|
|
54
|
+
if (deleteOperation.result === null || deleteOperation.result === void 0) {
|
|
55
|
+
return 404;
|
|
56
|
+
}
|
|
57
|
+
return 204;
|
|
58
|
+
}
|
|
59
|
+
const createOperation = operations.find((op) => op.operation === "create");
|
|
60
|
+
if (createOperation) {
|
|
61
|
+
return 201;
|
|
62
|
+
}
|
|
63
|
+
return 200;
|
|
64
|
+
}
|
|
65
|
+
async function mockHandlerResponse(c, operation, options) {
|
|
66
|
+
if (options?.onRequest) {
|
|
67
|
+
options.onRequest({
|
|
68
|
+
context: c,
|
|
69
|
+
operation
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const handlerCode = operation?.["x-handler"];
|
|
73
|
+
if (!handlerCode) {
|
|
74
|
+
c.status(500);
|
|
75
|
+
return c.json({ error: "x-handler code not found in operation" });
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const { context, tracking } = await buildHandlerContext(c, operation);
|
|
79
|
+
const { result } = await executeHandler(handlerCode, context);
|
|
80
|
+
const statusCode = determineStatusCode(tracking);
|
|
81
|
+
c.status(statusCode);
|
|
82
|
+
if (statusCode === 204) {
|
|
83
|
+
return c.body(null);
|
|
84
|
+
}
|
|
85
|
+
c.header("Content-Type", "application/json");
|
|
86
|
+
if (result === void 0 || result === null) {
|
|
87
|
+
const exampleResponse = getExampleFromResponse(
|
|
88
|
+
c,
|
|
89
|
+
statusCode,
|
|
90
|
+
operation.responses
|
|
91
|
+
);
|
|
92
|
+
if (exampleResponse !== null) {
|
|
93
|
+
return c.json(exampleResponse);
|
|
94
|
+
}
|
|
95
|
+
return c.json(null);
|
|
96
|
+
}
|
|
97
|
+
return c.json(result);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error("x-handler execution error:", error);
|
|
100
|
+
c.status(500);
|
|
101
|
+
return c.json({
|
|
102
|
+
error: "Handler execution failed",
|
|
103
|
+
message: error instanceof Error ? error.message : String(error)
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
mockHandlerResponse
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=mock-handler-response.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/routes/mock-handler-response.ts"],
|
|
4
|
+
"sourcesContent": ["import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters'\nimport type { OpenAPIV3_1 } from '@scalar/openapi-types'\nimport type { Context } from 'hono'\nimport { accepts } from 'hono/accepts'\nimport type { StatusCode } from 'hono/utils/http-status'\n\nimport type { MockServerOptions } from '@/types'\nimport { buildHandlerContext } from '@/utils/build-handler-context'\nimport { executeHandler } from '@/utils/execute-handler'\n\n/**\n * Get example response from OpenAPI spec for a given status code.\n * Returns the example value if found, or null if not available.\n */\nfunction getExampleFromResponse(\n c: Context,\n statusCode: StatusCode,\n responses: OpenAPIV3_1.ResponsesObject | undefined,\n): any {\n if (!responses) {\n return null\n }\n\n const statusCodeStr = statusCode.toString()\n const response = responses[statusCodeStr] || responses.default\n\n if (!response) {\n return null\n }\n\n const supportedContentTypes = Object.keys(response.content ?? {})\n\n // If no content types are defined, return null\n if (supportedContentTypes.length === 0) {\n return null\n }\n\n // Content-Type negotiation\n const acceptedContentType = accepts(c, {\n header: 'Accept',\n supports: supportedContentTypes,\n default: supportedContentTypes.includes('application/json')\n ? 'application/json'\n : (supportedContentTypes[0] ?? 'text/plain;charset=UTF-8'),\n })\n\n const acceptedResponse = response.content?.[acceptedContentType]\n\n if (!acceptedResponse) {\n return null\n }\n\n // Extract example from example property or generate from schema\n return acceptedResponse.example !== undefined\n ? acceptedResponse.example\n : acceptedResponse.schema\n ? getExampleFromSchema(acceptedResponse.schema, {\n emptyString: 'string',\n variables: c.req.param(),\n mode: 'read',\n })\n : null\n}\n\n/**\n * Determine HTTP status code based on store operation tracking.\n * Prioritizes operations based on semantic meaning:\n * - get > update > delete > create > list\n * This ensures that if a handler performs multiple operations (e.g., get followed by create for logging),\n * the status code reflects the most semantically meaningful operation.\n */\nfunction determineStatusCode(tracking: {\n operations: Array<{ operation: 'get' | 'create' | 'update' | 'delete' | 'list'; result: any }>\n}): StatusCode {\n const { operations } = tracking\n\n // If no operations were performed, default to 200\n if (operations.length === 0) {\n return 200\n }\n\n // Priority order: get > update > delete > create > list\n // Check for get operations first (highest priority)\n const getOperation = operations.find((op) => op.operation === 'get')\n if (getOperation) {\n // Return 404 if get() returned undefined or null\n if (getOperation.result === undefined || getOperation.result === null) {\n return 404\n }\n return 200\n }\n\n // Check for update operations\n const updateOperation = operations.find((op) => op.operation === 'update')\n if (updateOperation) {\n // Return 404 if update() returned null (item not found)\n if (updateOperation.result === null || updateOperation.result === undefined) {\n return 404\n }\n return 200\n }\n\n // Check for delete operations\n const deleteOperation = operations.find((op) => op.operation === 'delete')\n if (deleteOperation) {\n // Return 404 if delete() returned null (item not found)\n if (deleteOperation.result === null || deleteOperation.result === undefined) {\n return 404\n }\n return 204\n }\n\n // Check for create operations\n const createOperation = operations.find((op) => op.operation === 'create')\n if (createOperation) {\n return 201\n }\n\n // Default to 200 for list or any other operation\n return 200\n}\n\n/**\n * Mock response using x-handler code.\n * Executes the handler and returns its result as the response.\n */\nexport async function mockHandlerResponse(\n c: Context,\n operation: OpenAPIV3_1.OperationObject,\n options: MockServerOptions,\n) {\n // Call onRequest callback\n if (options?.onRequest) {\n options.onRequest({\n context: c,\n operation,\n })\n }\n\n // Get x-handler code from operation\n const handlerCode = operation?.['x-handler']\n\n if (!handlerCode) {\n c.status(500)\n return c.json({ error: 'x-handler code not found in operation' })\n }\n\n try {\n // Build handler context with tracking\n const { context, tracking } = await buildHandlerContext(c, operation)\n\n // Execute handler\n const { result } = await executeHandler(handlerCode, context)\n\n // Determine status code based on all store operations, prioritizing semantically meaningful ones\n const statusCode = determineStatusCode(tracking)\n\n // Set status code\n c.status(statusCode)\n\n // For 204 No Content, return null body without Content-Type header\n if (statusCode === 204) {\n return c.body(null)\n }\n\n // Set Content-Type header for other responses\n c.header('Content-Type', 'application/json')\n\n // Return the handler result as JSON\n // Handle undefined/null results gracefully\n if (result === undefined || result === null) {\n // Try to pick up example response from OpenAPI spec if available\n const exampleResponse = getExampleFromResponse(\n c,\n statusCode,\n operation.responses as OpenAPIV3_1.ResponsesObject | undefined,\n )\n if (exampleResponse !== null) {\n return c.json(exampleResponse)\n }\n return c.json(null)\n }\n\n return c.json(result)\n } catch (error) {\n // Log error to console\n console.error('x-handler execution error:', error)\n\n // Return 500 error\n c.status(500)\n return c.json({\n error: 'Handler execution failed',\n message: error instanceof Error ? error.message : String(error),\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,4BAA4B;AAGrC,SAAS,eAAe;AAIxB,SAAS,2BAA2B;AACpC,SAAS,sBAAsB;AAM/B,SAAS,uBACP,GACA,YACA,WACK;AACL,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,WAAW,SAAS;AAC1C,QAAM,WAAW,UAAU,aAAa,KAAK,UAAU;AAEvD,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAEA,QAAM,wBAAwB,OAAO,KAAK,SAAS,WAAW,CAAC,CAAC;AAGhE,MAAI,sBAAsB,WAAW,GAAG;AACtC,WAAO;AAAA,EACT;AAGA,QAAM,sBAAsB,QAAQ,GAAG;AAAA,IACrC,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,sBAAsB,SAAS,kBAAkB,IACtD,qBACC,sBAAsB,CAAC,KAAK;AAAA,EACnC,CAAC;AAED,QAAM,mBAAmB,SAAS,UAAU,mBAAmB;AAE/D,MAAI,CAAC,kBAAkB;AACrB,WAAO;AAAA,EACT;AAGA,SAAO,iBAAiB,YAAY,SAChC,iBAAiB,UACjB,iBAAiB,SACf,qBAAqB,iBAAiB,QAAQ;AAAA,IAC5C,aAAa;AAAA,IACb,WAAW,EAAE,IAAI,MAAM;AAAA,IACvB,MAAM;AAAA,EACR,CAAC,IACD;AACR;AASA,SAAS,oBAAoB,UAEd;AACb,QAAM,EAAE,WAAW,IAAI;AAGvB,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO;AAAA,EACT;AAIA,QAAM,eAAe,WAAW,KAAK,CAAC,OAAO,GAAG,cAAc,KAAK;AACnE,MAAI,cAAc;AAEhB,QAAI,aAAa,WAAW,UAAa,aAAa,WAAW,MAAM;AACrE,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,WAAW,KAAK,CAAC,OAAO,GAAG,cAAc,QAAQ;AACzE,MAAI,iBAAiB;AAEnB,QAAI,gBAAgB,WAAW,QAAQ,gBAAgB,WAAW,QAAW;AAC3E,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,WAAW,KAAK,CAAC,OAAO,GAAG,cAAc,QAAQ;AACzE,MAAI,iBAAiB;AAEnB,QAAI,gBAAgB,WAAW,QAAQ,gBAAgB,WAAW,QAAW;AAC3E,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,WAAW,KAAK,CAAC,OAAO,GAAG,cAAc,QAAQ;AACzE,MAAI,iBAAiB;AACnB,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAMA,eAAsB,oBACpB,GACA,WACA,SACA;AAEA,MAAI,SAAS,WAAW;AACtB,YAAQ,UAAU;AAAA,MAChB,SAAS;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,cAAc,YAAY,WAAW;AAE3C,MAAI,CAAC,aAAa;AAChB,MAAE,OAAO,GAAG;AACZ,WAAO,EAAE,KAAK,EAAE,OAAO,wCAAwC,CAAC;AAAA,EAClE;AAEA,MAAI;AAEF,UAAM,EAAE,SAAS,SAAS,IAAI,MAAM,oBAAoB,GAAG,SAAS;AAGpE,UAAM,EAAE,OAAO,IAAI,MAAM,eAAe,aAAa,OAAO;AAG5D,UAAM,aAAa,oBAAoB,QAAQ;AAG/C,MAAE,OAAO,UAAU;AAGnB,QAAI,eAAe,KAAK;AACtB,aAAO,EAAE,KAAK,IAAI;AAAA,IACpB;AAGA,MAAE,OAAO,gBAAgB,kBAAkB;AAI3C,QAAI,WAAW,UAAa,WAAW,MAAM;AAE3C,YAAM,kBAAkB;AAAA,QACtB;AAAA,QACA;AAAA,QACA,UAAU;AAAA,MACZ;AACA,UAAI,oBAAoB,MAAM;AAC5B,eAAO,EAAE,KAAK,eAAe;AAAA,MAC/B;AACA,aAAO,EAAE,KAAK,IAAI;AAAA,IACpB;AAEA,WAAO,EAAE,KAAK,MAAM;AAAA,EACtB,SAAS,OAAO;AAEd,YAAQ,MAAM,8BAA8B,KAAK;AAGjD,MAAE,OAAO,GAAG;AACZ,WAAO,EAAE,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAChE,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OpenAPIV3_1 } from '@scalar/openapi-types';
|
|
2
2
|
import type { Context } from 'hono';
|
|
3
3
|
/** Available HTTP methods for Hono routes */
|
|
4
4
|
export declare const httpMethods: readonly ["get", "put", "post", "delete", "options", "patch"];
|
|
@@ -28,7 +28,7 @@ type BaseMockServerOptions = {
|
|
|
28
28
|
*/
|
|
29
29
|
onRequest?: (data: {
|
|
30
30
|
context: Context;
|
|
31
|
-
operation:
|
|
31
|
+
operation: OpenAPIV3_1.OperationObject;
|
|
32
32
|
}) => void;
|
|
33
33
|
};
|
|
34
34
|
export type MockServerOptions = RequireAtLeastOne<BaseMockServerOptions, 'specification' | 'document'>;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,6CAA6C;AAC7C,eAAO,MAAM,WAAW,+DAAgE,CAAA;AAExF,wBAAwB;AACxB,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAErD;;GAEG;AACH,KAAK,iBAAiB,CAAC,CAAC,EAAE,IAAI,SAAS,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,GACzF;KACG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;CACzE,CAAC,IAAI,CAAC,CAAA;AAET,KAAK,qBAAqB,GAAG;IAC3B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE5C;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEvC;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,WAAW,CAAC,eAAe,CAAA;KAAE,KAAK,IAAI,CAAA;CACzF,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,qBAAqB,EAAE,eAAe,GAAG,UAAU,CAAC,CAAA"}
|
package/dist/types.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/types.ts"],
|
|
4
|
-
"sourcesContent": ["import type {
|
|
4
|
+
"sourcesContent": ["import type { OpenAPIV3_1 } from '@scalar/openapi-types'\nimport type { Context } from 'hono'\n\n/** Available HTTP methods for Hono routes */\nexport const httpMethods = ['get', 'put', 'post', 'delete', 'options', 'patch'] as const\n\n/** Valid HTTP method */\nexport type HttpMethod = (typeof httpMethods)[number]\n\n/**\n * Represents a partial object where at least one of the given properties is required.\n */\ntype RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &\n {\n [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>\n }[Keys]\n\ntype BaseMockServerOptions = {\n /**\n * The OpenAPI document to use for mocking.\n * Can be a string (URL or file path) or an object.\n *\n * @deprecated Use `document` instead\n */\n specification?: string | Record<string, any>\n\n /**\n * The OpenAPI document to use for mocking.\n * Can be a string (URL or file path) or an object.\n */\n document?: string | Record<string, any>\n\n /**\n * Callback function to be called before each request is processed.\n */\n onRequest?: (data: { context: Context; operation: OpenAPIV3_1.OperationObject }) => void\n}\n\nexport type MockServerOptions = RequireAtLeastOne<BaseMockServerOptions, 'specification' | 'document'>\n"],
|
|
5
5
|
"mappings": "AAIO,MAAM,cAAc,CAAC,OAAO,OAAO,QAAQ,UAAU,WAAW,OAAO;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import type { OpenAPIV3_1 } from '@scalar/openapi-types';
|
|
3
|
+
import type { Context } from 'hono';
|
|
4
|
+
import { type StoreOperationTracking, createStoreWrapper } from './store-wrapper.js';
|
|
5
|
+
/**
|
|
6
|
+
* Context object provided to x-handler code.
|
|
7
|
+
*/
|
|
8
|
+
export type HandlerContext = {
|
|
9
|
+
store: ReturnType<typeof createStoreWrapper>['wrappedStore'];
|
|
10
|
+
faker: typeof faker;
|
|
11
|
+
req: {
|
|
12
|
+
body: any;
|
|
13
|
+
params: Record<string, string>;
|
|
14
|
+
query: Record<string, string>;
|
|
15
|
+
headers: Record<string, string>;
|
|
16
|
+
};
|
|
17
|
+
res: Record<string, any>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Result of building handler context, including operation tracking.
|
|
21
|
+
*/
|
|
22
|
+
type HandlerContextResult = {
|
|
23
|
+
context: HandlerContext;
|
|
24
|
+
tracking: StoreOperationTracking;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Build the handler context from a Hono context.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildHandlerContext(c: Context, operation?: OpenAPIV3_1.OperationObject): Promise<HandlerContextResult>;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=build-handler-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-handler-context.d.ts","sourceRoot":"","sources":["../../src/utils/build-handler-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAEvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAInC,OAAO,EAAE,KAAK,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEjF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,cAAc,CAAC,CAAA;IAC5D,KAAK,EAAE,OAAO,KAAK,CAAA;IACnB,GAAG,EAAE;QACH,IAAI,EAAE,GAAG,CAAA;QACT,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAChC,CAAA;IACD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACzB,CAAA;AAED;;GAEG;AACH,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,cAAc,CAAA;IACvB,QAAQ,EAAE,sBAAsB,CAAA;CACjC,CAAA;AAuDD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,EACV,SAAS,CAAC,EAAE,WAAW,CAAC,eAAe,GACtC,OAAO,CAAC,oBAAoB,CAAC,CA4C/B"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { faker } from "@faker-js/faker";
|
|
2
|
+
import { getExampleFromSchema } from "@scalar/oas-utils/spec-getters";
|
|
3
|
+
import { accepts } from "hono/accepts";
|
|
4
|
+
import { store } from "../libs/store.js";
|
|
5
|
+
import { createStoreWrapper } from "./store-wrapper.js";
|
|
6
|
+
function getExampleFromResponse(c, statusCode, responses) {
|
|
7
|
+
if (!responses) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const response = responses[statusCode] || responses.default;
|
|
11
|
+
if (!response) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const supportedContentTypes = Object.keys(response.content ?? {});
|
|
15
|
+
if (supportedContentTypes.length === 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const acceptedContentType = accepts(c, {
|
|
19
|
+
header: "Accept",
|
|
20
|
+
supports: supportedContentTypes,
|
|
21
|
+
default: supportedContentTypes.includes("application/json") ? "application/json" : supportedContentTypes[0] ?? "text/plain;charset=UTF-8"
|
|
22
|
+
});
|
|
23
|
+
const acceptedResponse = response.content?.[acceptedContentType];
|
|
24
|
+
if (!acceptedResponse) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return acceptedResponse.example !== void 0 ? acceptedResponse.example : acceptedResponse.schema ? getExampleFromSchema(acceptedResponse.schema, {
|
|
28
|
+
emptyString: "string",
|
|
29
|
+
variables: c.req.param(),
|
|
30
|
+
mode: "read"
|
|
31
|
+
}) : null;
|
|
32
|
+
}
|
|
33
|
+
async function buildHandlerContext(c, operation) {
|
|
34
|
+
let body = void 0;
|
|
35
|
+
try {
|
|
36
|
+
const contentType = c.req.header("content-type") ?? "";
|
|
37
|
+
if (contentType.includes("application/json")) {
|
|
38
|
+
body = await c.req.json().catch(() => void 0);
|
|
39
|
+
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
40
|
+
body = await c.req.parseBody().catch(() => void 0);
|
|
41
|
+
} else if (contentType.includes("text/")) {
|
|
42
|
+
body = await c.req.text().catch(() => void 0);
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
const { wrappedStore, tracking } = createStoreWrapper(store);
|
|
47
|
+
const res = {};
|
|
48
|
+
if (operation?.responses) {
|
|
49
|
+
for (const statusCode of Object.keys(operation.responses)) {
|
|
50
|
+
res[statusCode] = getExampleFromResponse(
|
|
51
|
+
c,
|
|
52
|
+
statusCode,
|
|
53
|
+
operation.responses
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
context: {
|
|
59
|
+
store: wrappedStore,
|
|
60
|
+
faker,
|
|
61
|
+
req: {
|
|
62
|
+
body,
|
|
63
|
+
params: c.req.param(),
|
|
64
|
+
query: Object.fromEntries(new URL(c.req.url).searchParams.entries()),
|
|
65
|
+
headers: Object.fromEntries(Object.entries(c.req.header()).map(([key, value]) => [key, value ?? ""]))
|
|
66
|
+
},
|
|
67
|
+
res
|
|
68
|
+
},
|
|
69
|
+
tracking
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
buildHandlerContext
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=build-handler-context.js.map
|