contract-drift-detection 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/config.ts","../src/server.ts","../src/utils.ts","../src/dsl.ts","../src/drift-detector.ts","../src/route-context.ts","../src/schema-seeder.ts","../src/spec-loader.ts","../src/state-store.ts","../src/proxy.ts"],"sourcesContent":["import process from 'node:process';\nimport { createCli, toServeConfig, writeStarterConfig } from './config.js';\nimport { createServer } from './server.js';\n\nasync function main(): Promise<void> {\n const cli = createCli();\n const serveCommand = cli.commands.find((command) => command.name() === 'serve');\n const initCommand = cli.commands.find((command) => command.name() === 'init');\n\n serveCommand?.action(async function () {\n const config = toServeConfig(this.opts());\n const server = await createServer(config);\n await server.listen({ port: config.port, host: config.host });\n process.stdout.write(`Mock engine listening on http://${config.host}:${config.port}\\n`);\n });\n\n initCommand?.action(async function () {\n const options = this.opts();\n const targetPath = await writeStarterConfig(process.cwd(), {\n spec: String(options.spec),\n db: String(options.db),\n host: String(options.host),\n port: Number(options.port),\n });\n process.stdout.write(`Created ${targetPath}\\n`);\n });\n\n await cli.parseAsync(process.argv);\n}\n\nawait main().catch((error) => {\n process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\\n`);\n process.exitCode = 1;\n});","import { mkdir, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport type { InitConfig, MockEngineConfig } from './types.js';\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name('contract-drift-detection')\n .description('Stateful OpenAPI mock server with contract drift detection')\n .version('0.1.0');\n\n program\n .command('serve')\n .description('Start the mock engine')\n .requiredOption('--spec <path>', 'Path to an OpenAPI 3.x file')\n .option('--port <port>', 'Port to bind the server', '4010')\n .option('--host <host>', 'Host to bind the server', '0.0.0.0')\n .option('--db <path>', 'JSON database path', '.mock-db.json')\n .option('--cors-origin <origin>', 'CORS origin (default is *)', '*')\n .option('--drift-check <url>', 'Forward traffic to a real backend and validate responses')\n .option('--fallback-to-mock', 'Fallback to mock responses when proxying fails', false)\n .option('--verbose', 'Enable verbose logging', false);\n\n program\n .command('init')\n .description('Create a starter config for the current workspace')\n .option('--spec <path>', 'Default OpenAPI path', 'openapi.yaml')\n .option('--db <path>', 'Default JSON database path', '.mock-db.json')\n .option('--port <port>', 'Default port', '4010')\n .option('--host <host>', 'Default host', '0.0.0.0');\n\n return program;\n}\n\nexport function toServeConfig(options: Record<string, string | boolean | undefined>): MockEngineConfig {\n return {\n specPath: String(options.spec),\n port: Number(options.port ?? 4010),\n host: String(options.host ?? '0.0.0.0'),\n dbPath: String(options.db ?? '.mock-db.json'),\n corsOrigin: String(options.corsOrigin ?? '*'),\n driftCheckTarget: options.driftCheck ? String(options.driftCheck) : undefined,\n fallbackToMockOnProxyError: Boolean(options.fallbackToMock),\n verbose: Boolean(options.verbose),\n };\n}\n\nexport async function writeStarterConfig(cwd: string, initConfig: InitConfig): Promise<string> {\n const targetPath = path.join(cwd, 'contract-drift.config.json');\n await mkdir(cwd, { recursive: true });\n await writeFile(\n targetPath,\n `${JSON.stringify(\n {\n spec: initConfig.spec,\n db: initConfig.db,\n host: initConfig.host,\n port: initConfig.port,\n },\n null,\n 2,\n )}\\n`,\n 'utf8',\n );\n return targetPath;\n}","import path from 'node:path';\nimport Fastify, { type FastifyInstance, type FastifyRequest } from 'fastify';\nimport cors from '@fastify/cors';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport { applyDslMutation } from './dsl.js';\nimport { DriftDetector } from './drift-detector.js';\nimport { buildRouteContexts } from './route-context.js';\nimport { inferSeedCollections, seedFromSchema } from './schema-seeder.js';\nimport { loadOpenApiDocument } from './spec-loader.js';\nimport { JsonStateStore } from './state-store.js';\nimport type { MockEngineConfig, MockOperationObject, RouteContext } from './types.js';\nimport { proxyRequest } from './proxy.js';\nimport { deepMerge, resolveFile } from './utils.js';\n\nfunction sendResponse(\n reply: { code: (statusCode: number) => typeof reply; send: (body?: unknown) => unknown },\n statusCode: number,\n body: unknown,\n) {\n if (statusCode === 204) {\n return reply.code(204).send();\n }\n\n return reply.code(statusCode).send(body);\n}\n\nfunction defaultStatusCode(route: RouteContext): number {\n if (route.method === 'post') {\n return 201;\n }\n\n if (route.method === 'delete') {\n return 204;\n }\n\n return 200;\n}\n\nfunction getRequestBody(request: FastifyRequest): Record<string, unknown> {\n if (!request.body || typeof request.body !== 'object' || Array.isArray(request.body)) {\n return {};\n }\n\n return request.body as Record<string, unknown>;\n}\n\nfunction getCollection(database: { collections: Record<string, unknown[]>; counters: Record<string, number> }, route: RouteContext): Record<string, unknown>[] {\n if (!database.collections[route.resourceName]) {\n database.collections[route.resourceName] = [];\n database.counters[route.resourceName] = 0;\n }\n\n return database.collections[route.resourceName] as Record<string, unknown>[];\n}\n\nfunction getCollectionByName(\n database: { collections: Record<string, unknown[]>; counters: Record<string, number> },\n collectionName: string,\n): Record<string, unknown>[] {\n if (!database.collections[collectionName]) {\n database.collections[collectionName] = [];\n database.counters[collectionName] = 0;\n }\n\n return database.collections[collectionName] as Record<string, unknown>[];\n}\n\nfunction computeIdKey(route: RouteContext): string {\n return route.pathParamName ?? 'id';\n}\n\nfunction normalizeComparable(value: unknown): string | number | boolean | undefined {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n\n return undefined;\n}\n\nfunction nextId(database: { counters: Record<string, number> }, route: RouteContext): number {\n const current = database.counters[route.resourceName] ?? 0;\n const next = current + 1;\n database.counters[route.resourceName] = next;\n return next;\n}\n\nfunction allocateUniqueId(\n database: { counters: Record<string, number> },\n route: RouteContext,\n collection: Record<string, unknown>[],\n idKey: string,\n): number {\n let candidate = nextId(database, route);\n const hasCollision = (value: number) =>\n collection.some((item) => normalizeComparable(item[idKey]) === String(value));\n\n while (hasCollision(candidate)) {\n candidate = nextId(database, route);\n }\n\n return candidate;\n}\n\nfunction resolveEntity(route: RouteContext, collection: Record<string, unknown>[], request: FastifyRequest): Record<string, unknown> | undefined {\n const idKey = computeIdKey(route);\n const rawId = normalizeComparable(\n (request.params as Record<string, unknown>)[idKey] ?? (request.params as Record<string, unknown>).id,\n );\n return collection.find((item) => normalizeComparable(item[idKey]) === rawId);\n}\n\nfunction materializeMockBody(route: RouteContext, request: FastifyRequest): Record<string, unknown> {\n const requestBody = getRequestBody(request);\n const seeded = route.requestBodySchema ? seedFromSchema(route.requestBodySchema) : {};\n\n return deepMerge(\n (seeded && typeof seeded === 'object' && !Array.isArray(seeded) ? seeded : {}) as Record<string, unknown>,\n requestBody,\n );\n}\n\nfunction getNotFoundResponse(route: RouteContext): { statusCode: number; body: { message: string } } {\n return {\n statusCode: 404,\n body: { message: `${route.resourceName} not found` },\n };\n}\n\nfunction handleReadRoute(\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body?: unknown } {\n if (route.isCollection) {\n return { statusCode: 200, body: collection };\n }\n\n const entity = resolveEntity(route, collection, request);\n return entity ? { statusCode: 200, body: entity } : getNotFoundResponse(route);\n}\n\nfunction handleCreateRoute(\n database: { counters: Record<string, number> },\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body: Record<string, unknown> } {\n const entity = materializeMockBody(route, request);\n const idKey = computeIdKey(route);\n if (entity[idKey] === undefined) {\n entity[idKey] = allocateUniqueId(database, route, collection, idKey);\n }\n collection.push(entity);\n return { statusCode: route.successResponse?.statusCode ?? 201, body: entity };\n}\n\nfunction handleUpdateRoute(\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body?: unknown } {\n const current = resolveEntity(route, collection, request);\n if (!current) {\n return getNotFoundResponse(route);\n }\n\n const merged = route.method === 'put'\n ? materializeMockBody(route, request)\n : deepMerge(current, getRequestBody(request));\n\n Object.assign(current, merged);\n return { statusCode: 200, body: current };\n}\n\nfunction handleDeleteRoute(\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body?: unknown } {\n const entity = resolveEntity(route, collection, request);\n if (!entity) {\n return getNotFoundResponse(route);\n }\n\n const index = collection.indexOf(entity);\n collection.splice(index, 1);\n return { statusCode: 204 };\n}\n\nasync function handleMockRoute(\n store: JsonStateStore,\n route: RouteContext,\n request: FastifyRequest,\n): Promise<{ statusCode: number; body?: unknown }> {\n return store.withDatabase(async (database) => {\n const collection = getCollection(database, route);\n const extension = (route.operation as MockOperationObject)['x-mock-state'];\n\n if (extension) {\n const targetCollection = extension.target\n ? getCollectionByName(database, extension.target)\n : collection;\n const response = applyDslMutation(extension, request, targetCollection, computeIdKey(route));\n if (extension.response === 'none') {\n return { statusCode: 204 };\n }\n\n if (extension.response === 'collection') {\n return { statusCode: route.successResponse?.statusCode ?? 200, body: targetCollection };\n }\n\n return { statusCode: route.successResponse?.statusCode ?? defaultStatusCode(route), body: response };\n }\n\n switch (route.method) {\n case 'get': {\n return handleReadRoute(route, collection, request);\n }\n case 'post': {\n return handleCreateRoute(database, route, collection, request);\n }\n case 'put':\n case 'patch': {\n return handleUpdateRoute(route, collection, request);\n }\n case 'delete': {\n return handleDeleteRoute(route, collection, request);\n }\n }\n });\n}\n\nasync function handleProxyRoute(\n config: MockEngineConfig,\n route: RouteContext,\n detector: DriftDetector,\n request: FastifyRequest,\n): Promise<{ statusCode: number; headers: Record<string, string>; body?: unknown }> {\n const rawBody = request.body ? JSON.stringify(request.body) : undefined;\n const targetBaseUrl = config.driftCheckTarget;\n if (!targetBaseUrl) {\n throw new Error('Proxy target is not configured');\n }\n\n const result = await proxyRequest(targetBaseUrl, request.url, {\n method: request.method,\n headers: {\n 'content-type': request.headers['content-type'] ?? 'application/json',\n },\n body: rawBody,\n });\n\n detector.validate(\n route.method,\n route.path,\n result.statusCode,\n route.successResponse?.schema,\n result.body,\n );\n\n return {\n statusCode: result.statusCode,\n headers: Object.fromEntries(result.headers.entries()),\n body: result.body,\n };\n}\n\nasync function registerRoutes(\n app: FastifyInstance,\n document: OpenAPIV3.Document,\n config: MockEngineConfig,\n store: JsonStateStore,\n): Promise<void> {\n const routes = buildRouteContexts(document);\n const detector = new DriftDetector(app.log);\n\n for (const route of routes) {\n app.route({\n method: route.method.toUpperCase(),\n url: route.fastifyPath,\n handler: async (request, reply) => {\n if (config.driftCheckTarget) {\n try {\n const proxied = await handleProxyRoute(config, route, detector, request);\n for (const [headerName, headerValue] of Object.entries(proxied.headers)) {\n if (headerName.toLowerCase() === 'content-length') {\n continue;\n }\n reply.header(headerName, headerValue);\n }\n return reply.code(proxied.statusCode).send(proxied.body);\n } catch (error) {\n app.log.error({ error }, `Proxy execution failed for ${route.method.toUpperCase()} ${route.path}`);\n if (!config.fallbackToMockOnProxyError) {\n throw error;\n }\n }\n }\n\n const mockResponse = await handleMockRoute(store, route, request);\n return sendResponse(reply, mockResponse.statusCode, mockResponse.body);\n },\n });\n }\n}\n\nexport async function createServer(config: MockEngineConfig): Promise<FastifyInstance> {\n const app = Fastify({\n logger: {\n level: config.verbose ? 'debug' : 'error',\n },\n });\n\n await app.register(cors, {\n origin: config.corsOrigin === '*' ? true : config.corsOrigin,\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['content-type', 'authorization'],\n credentials: false,\n });\n\n const document = await loadOpenApiDocument(config.specPath);\n const seedCollections = inferSeedCollections(document);\n const dbPath = resolveFile(path.dirname(config.specPath), config.dbPath);\n const store = new JsonStateStore(dbPath);\n await store.initialize(seedCollections);\n\n app.get('/__health', async () => ({ status: 'ok' }));\n app.get('/__spec', async () => document);\n\n await registerRoutes(app, document, config, store);\n return app;\n}","import path from 'node:path';\nimport type { OpenAPIV3 } from 'openapi-types';\n\nexport function isSchemaObject(\n schema?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,\n): schema is OpenAPIV3.SchemaObject {\n return Boolean(schema) && !('$ref' in (schema as OpenAPIV3.ReferenceObject));\n}\n\nexport function normalizeCollectionName(raw: string): string {\n return raw\n .replace(/[{}]/g, '')\n .split('/')\n .filter(Boolean)\n .at(-1)\n ?.replace(/[^a-zA-Z0-9]+/g, '_')\n .toLowerCase() ?? 'items';\n}\n\nexport function toFastifyPath(openApiPath: string): string {\n return openApiPath.replace(/\\{([^}]+)\\}/g, ':$1');\n}\n\nexport function singularize(value: string): string {\n if (value.endsWith('ies')) {\n return `${value.slice(0, -3)}y`;\n }\n\n if (value.endsWith('s')) {\n return value.slice(0, -1);\n }\n\n return value;\n}\n\nexport function resolveFile(baseDir: string, filePath: string): string {\n return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);\n}\n\nexport function getOperationId(\n method: string,\n openApiPath: string,\n operation: OpenAPIV3.OperationObject,\n): string {\n if (operation.operationId) {\n return operation.operationId;\n }\n\n const sanitizedPath = openApiPath\n .replace(/[{}]/g, '')\n .split('/')\n .filter(Boolean)\n .join('_');\n\n return `${method}_${sanitizedPath || 'root'}`;\n}\n\nexport function inferPathParamName(openApiPath: string): string | undefined {\n const match = openApiPath.match(/\\{([^}]+)\\}/);\n return match?.[1];\n}\n\nexport function deepClone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nexport function deepMerge<T extends Record<string, unknown>>(\n base: T,\n patch: Record<string, unknown>,\n): T {\n const output: Record<string, unknown> = { ...base };\n\n for (const [key, value] of Object.entries(patch)) {\n if (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n output[key] &&\n typeof output[key] === 'object' &&\n !Array.isArray(output[key])\n ) {\n output[key] = deepMerge(\n output[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n output[key] = value;\n }\n }\n\n return output as T;\n}\n\nexport function readContentType(headers: Headers): string {\n return headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() ?? '';\n}\n\nexport function isJsonLikeContentType(contentType: string): boolean {\n return contentType === 'application/json' || contentType.endsWith('+json');\n}\n\nexport function toArray<T>(value: T | T[] | undefined): T[] {\n if (value === undefined) {\n return [];\n }\n\n return Array.isArray(value) ? value : [value];\n}","import type { FastifyRequest } from 'fastify';\nimport type { MockStateExtension } from './types.js';\nimport { deepMerge } from './utils.js';\n\nfunction resolvePathExpression(source: unknown, expression: string): unknown {\n return expression\n .split('.')\n .reduce<unknown>((value, segment) => (value && typeof value === 'object' ? (value as Record<string, unknown>)[segment] : undefined), source);\n}\n\nfunction evaluateTemplate(value: unknown, request: FastifyRequest): unknown {\n if (typeof value === 'string') {\n const match = value.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (!match) {\n return value;\n }\n\n return resolvePathExpression(\n {\n params: request.params,\n query: request.query,\n body: request.body,\n headers: request.headers,\n },\n match[1],\n );\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => evaluateTemplate(entry, request));\n }\n\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => [key, evaluateTemplate(entry, request)]),\n );\n }\n\n return value;\n}\n\nexport function applyDslMutation(\n extension: MockStateExtension,\n request: FastifyRequest,\n collection: Record<string, unknown>[],\n defaultIdKey: string,\n): Record<string, unknown> | Record<string, unknown>[] | null {\n const idKey = extension.find_by ?? defaultIdKey;\n const targetId = (request.params as Record<string, unknown>)[idKey] ?? (request.params as Record<string, unknown>).id;\n const evaluatedAssign = (evaluateTemplate(extension.assign ?? {}, request) ?? {}) as Record<string, unknown>;\n const evaluatedSet = (evaluateTemplate(extension.set ?? {}, request) ?? {}) as Record<string, unknown>;\n\n switch (extension.action) {\n case 'create': {\n const candidate = {\n ...((request.body as Record<string, unknown> | undefined) ?? {}),\n ...evaluatedAssign,\n ...evaluatedSet,\n };\n collection.push(candidate);\n return candidate;\n }\n case 'append': {\n const item = {\n ...evaluatedAssign,\n ...evaluatedSet,\n };\n collection.push(item);\n return collection;\n }\n case 'replace': {\n const nextValue = {\n ...((request.body as Record<string, unknown> | undefined) ?? {}),\n ...evaluatedAssign,\n ...evaluatedSet,\n };\n const index = collection.findIndex((entry) => String(entry[idKey]) === String(targetId));\n if (index >= 0) {\n collection[index] = nextValue;\n }\n return nextValue;\n }\n case 'delete': {\n const index = collection.findIndex((entry) => String(entry[idKey]) === String(targetId));\n if (index >= 0) {\n const [removed] = collection.splice(index, 1);\n return removed;\n }\n return null;\n }\n case 'update':\n default: {\n const entity = collection.find((entry) => String(entry[idKey]) === String(targetId));\n if (!entity) {\n return null;\n }\n const merged = deepMerge(entity, {\n ...evaluatedAssign,\n ...evaluatedSet,\n });\n Object.assign(entity, merged);\n return entity;\n }\n }\n}","import Ajv, { type ErrorObject } from 'ajv';\nimport addFormats from 'ajv-formats';\nimport pc from 'picocolors';\nimport type { FastifyBaseLogger } from 'fastify';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport type { DriftIssue } from './types.js';\n\nfunction formatErrors(errors: ErrorObject[] | null | undefined): string[] {\n return (errors ?? []).map((error) => {\n const location = error.instancePath || '/';\n return `${location} ${error.message ?? 'failed validation'}`.trim();\n });\n}\n\nexport class DriftDetector {\n private readonly ajv = new Ajv({ allErrors: true, strict: false });\n\n constructor(private readonly logger: FastifyBaseLogger) {\n addFormats(this.ajv);\n }\n\n validate(\n method: string,\n path: string,\n statusCode: number,\n schema: OpenAPIV3.SchemaObject | undefined,\n body: unknown,\n ): DriftIssue | null {\n if (!schema || body === undefined || body === null) {\n return null;\n }\n\n const validate = this.ajv.compile(schema);\n const valid = validate(body);\n\n if (valid) {\n return null;\n }\n\n const issue: DriftIssue = {\n method: method.toUpperCase(),\n path,\n statusCode,\n message: `Drift detected for ${method.toUpperCase()} ${path} (${statusCode})`,\n errors: formatErrors(validate.errors),\n };\n\n this.logger.error(\n `${pc.red('DRIFT DETECTED')} ${issue.method} ${issue.path} -> ${issue.errors.join('; ')}`,\n );\n\n return issue;\n }\n}","import type { OpenAPIV3 } from 'openapi-types';\nimport type { HttpMethod, RouteContext } from './types.js';\nimport {\n getOperationId,\n inferPathParamName,\n isSchemaObject,\n normalizeCollectionName,\n toFastifyPath,\n} from './utils.js';\n\nconst SUPPORTED_METHODS: HttpMethod[] = ['get', 'post', 'put', 'patch', 'delete'];\n\nfunction getRequestBodySchema(\n operation: OpenAPIV3.OperationObject,\n): OpenAPIV3.SchemaObject | undefined {\n const content = operation.requestBody && !('$ref' in operation.requestBody)\n ? operation.requestBody.content?.['application/json']\n : undefined;\n\n return isSchemaObject(content?.schema) ? content.schema : undefined;\n}\n\nfunction getSuccessResponse(\n operation: OpenAPIV3.OperationObject,\n): RouteContext['successResponse'] {\n const preferredCodes = ['200', '201', '202', '204'];\n\n for (const code of preferredCodes) {\n const response = operation.responses?.[code];\n if (!response || '$ref' in response) {\n continue;\n }\n\n const schema = response.content?.['application/json']?.schema;\n return {\n statusCode: Number(code),\n schema: isSchemaObject(schema) ? schema : undefined,\n };\n }\n\n return undefined;\n}\n\nexport function buildRouteContexts(document: OpenAPIV3.Document): RouteContext[] {\n const routes: RouteContext[] = [];\n\n for (const [openApiPath, pathItem] of Object.entries(document.paths ?? {})) {\n if (!pathItem || '$ref' in pathItem) {\n continue;\n }\n\n for (const method of SUPPORTED_METHODS) {\n const operation = pathItem[method];\n if (!operation || '$ref' in operation) {\n continue;\n }\n\n const pathParamName = inferPathParamName(openApiPath);\n const resourceName = normalizeCollectionName(\n pathParamName ? openApiPath.replace(/\\/\\{[^}]+\\}$/, '') : openApiPath,\n );\n const route: RouteContext = {\n method,\n path: openApiPath,\n fastifyPath: toFastifyPath(openApiPath),\n operation,\n operationId: getOperationId(method, openApiPath, operation),\n resourceName,\n isCollection: !pathParamName,\n pathParamName,\n requestBodySchema: getRequestBodySchema(operation),\n successResponse: getSuccessResponse(operation),\n };\n routes.push(route);\n }\n }\n\n return routes;\n}","import { faker } from '@faker-js/faker';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport { isSchemaObject, singularize } from './utils.js';\n\nfunction seedPrimitive(schema: OpenAPIV3.SchemaObject): unknown {\n if (schema.enum?.length) {\n return schema.enum[0];\n }\n\n switch (schema.type) {\n case 'string': {\n if (schema.format === 'email') {\n return faker.internet.email();\n }\n\n if (schema.format === 'date-time') {\n return faker.date.recent().toISOString();\n }\n\n if (schema.format === 'uuid') {\n return faker.string.uuid();\n }\n\n return faker.lorem.words(2);\n }\n case 'integer':\n return faker.number.int({ min: 1, max: 1000 });\n case 'number':\n return faker.number.float({ min: 1, max: 1000, fractionDigits: 2 });\n case 'boolean':\n return faker.datatype.boolean();\n default:\n return null;\n }\n}\n\nexport function seedFromSchema(\n schema?: OpenAPIV3.SchemaObject,\n depth = 0,\n): unknown {\n if (!schema || depth > 4) {\n return null;\n }\n\n if (schema.oneOf?.length && isSchemaObject(schema.oneOf[0])) {\n return seedFromSchema(schema.oneOf[0], depth + 1);\n }\n\n if (schema.anyOf?.length && isSchemaObject(schema.anyOf[0])) {\n return seedFromSchema(schema.anyOf[0], depth + 1);\n }\n\n if (schema.allOf?.length) {\n return schema.allOf.reduce<Record<string, unknown>>((accumulator, item) => {\n if (!isSchemaObject(item)) {\n return accumulator;\n }\n\n const seeded = seedFromSchema(item, depth + 1);\n if (seeded && typeof seeded === 'object' && !Array.isArray(seeded)) {\n Object.assign(accumulator, seeded);\n }\n\n return accumulator;\n }, {});\n }\n\n if (schema.type === 'array') {\n const item = isSchemaObject(schema.items) ? schema.items : undefined;\n return [seedFromSchema(item, depth + 1), seedFromSchema(item, depth + 1)].filter(\n (value) => value !== null,\n );\n }\n\n if (schema.type === 'object' || schema.properties) {\n const entries = Object.entries(schema.properties ?? {}).map(([key, value]) => {\n if (!isSchemaObject(value)) {\n return [key, null] as const;\n }\n\n return [key, seedFromSchema(value, depth + 1)] as const;\n });\n\n return Object.fromEntries(entries);\n }\n\n return seedPrimitive(schema);\n}\n\nexport function inferSeedCollections(document: OpenAPIV3.Document): Record<string, unknown[]> {\n const collections: Record<string, unknown[]> = {};\n\n for (const [schemaName, schemaValue] of Object.entries(document.components?.schemas ?? {})) {\n if (!isSchemaObject(schemaValue) || schemaValue.type !== 'object') {\n continue;\n }\n\n const collectionName = `${singularize(schemaName).toLowerCase()}s`;\n collections[collectionName] = Array.from({ length: 3 }, () => seedFromSchema(schemaValue)) as unknown[];\n }\n\n return collections;\n}","import SwaggerParser from '@apidevtools/swagger-parser';\nimport type { OpenAPIV3 } from 'openapi-types';\n\nexport async function loadOpenApiDocument(specPath: string): Promise<OpenAPIV3.Document> {\n const document = (await SwaggerParser.dereference(specPath)) as OpenAPIV3.Document;\n\n if (!document.openapi?.startsWith('3.')) {\n throw new Error(`Only OpenAPI 3.x specs are supported. Received: ${document.openapi}`);\n }\n\n return document;\n}","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { MockDatabase } from './types.js';\nimport { deepClone } from './utils.js';\n\nfunction normalizeDatabase(input: Partial<MockDatabase> | undefined): MockDatabase {\n return {\n collections: input?.collections ?? {},\n counters: input?.counters ?? {},\n };\n}\n\nexport class JsonStateStore {\n readonly filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n\n async initialize(seedCollections: Record<string, unknown[]>): Promise<MockDatabase> {\n await mkdir(path.dirname(this.filePath), { recursive: true });\n\n try {\n const existing = await this.read();\n let changed = false;\n\n for (const [collectionName, items] of Object.entries(seedCollections)) {\n if (!existing.collections[collectionName]) {\n existing.collections[collectionName] = deepClone(items);\n existing.counters[collectionName] = items.length;\n changed = true;\n }\n }\n\n if (changed) {\n await this.write(existing);\n }\n\n return existing;\n } catch {\n const initial: MockDatabase = {\n collections: deepClone(seedCollections),\n counters: Object.fromEntries(\n Object.entries(seedCollections).map(([name, items]) => [name, items.length]),\n ),\n };\n await this.write(initial);\n return initial;\n }\n }\n\n async read(): Promise<MockDatabase> {\n const raw = await readFile(this.filePath, 'utf8');\n return normalizeDatabase(JSON.parse(raw) as Partial<MockDatabase>);\n }\n\n async write(database: MockDatabase): Promise<void> {\n await writeFile(this.filePath, `${JSON.stringify(database, null, 2)}\\n`, 'utf8');\n }\n\n async withDatabase<T>(\n updater: (database: MockDatabase) => Promise<T> | T,\n ): Promise<T> {\n const database = await this.read();\n const result = await updater(database);\n await this.write(database);\n return result;\n }\n}","import { readContentType, isJsonLikeContentType } from './utils.js';\nimport type { ProxyExecutionResult } from './types.js';\n\nexport async function proxyRequest(\n targetBaseUrl: string,\n path: string,\n init: RequestInit,\n): Promise<ProxyExecutionResult> {\n const response = await fetch(new URL(path, targetBaseUrl), init);\n const contentType = readContentType(response.headers);\n\n let body: unknown;\n let rawBody: string | undefined;\n\n if (response.status !== 204) {\n rawBody = await response.text();\n if (rawBody && isJsonLikeContentType(contentType)) {\n body = JSON.parse(rawBody);\n } else if (rawBody) {\n body = rawBody;\n }\n }\n\n return {\n ok: response.ok,\n statusCode: response.status,\n headers: response.headers,\n body,\n rawBody,\n };\n}"],"mappings":"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","names":["path","path","path","mkdir","writeFile","path","mkdir","path","writeFile","path","path"]}
@@ -0,0 +1,69 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ import { OpenAPIV3 } from 'openapi-types';
3
+
4
+ type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
5
+ interface MockEngineConfig {
6
+ specPath: string;
7
+ port: number;
8
+ host: string;
9
+ dbPath: string;
10
+ corsOrigin: string;
11
+ driftCheckTarget?: string;
12
+ fallbackToMockOnProxyError: boolean;
13
+ verbose: boolean;
14
+ }
15
+ interface InitConfig {
16
+ spec: string;
17
+ db: string;
18
+ port: number;
19
+ host: string;
20
+ }
21
+ interface MockDatabase {
22
+ collections: Record<string, unknown[]>;
23
+ counters: Record<string, number>;
24
+ }
25
+ interface RouteContext {
26
+ method: HttpMethod;
27
+ path: string;
28
+ fastifyPath: string;
29
+ operation: OpenAPIV3.OperationObject;
30
+ operationId: string;
31
+ resourceName: string;
32
+ isCollection: boolean;
33
+ pathParamName?: string;
34
+ requestBodySchema?: OpenAPIV3.SchemaObject;
35
+ successResponse?: {
36
+ statusCode: number;
37
+ schema?: OpenAPIV3.SchemaObject;
38
+ };
39
+ }
40
+ interface DriftIssue {
41
+ method: string;
42
+ path: string;
43
+ statusCode: number;
44
+ message: string;
45
+ errors: string[];
46
+ }
47
+ interface MockStateExtension {
48
+ action: 'create' | 'update' | 'delete' | 'replace' | 'append';
49
+ target?: string;
50
+ find_by?: string;
51
+ assign?: Record<string, unknown>;
52
+ set?: Record<string, unknown>;
53
+ response?: 'entity' | 'collection' | 'none';
54
+ }
55
+
56
+ declare function createServer(config: MockEngineConfig): Promise<FastifyInstance>;
57
+
58
+ declare function loadOpenApiDocument(specPath: string): Promise<OpenAPIV3.Document>;
59
+
60
+ declare class JsonStateStore {
61
+ readonly filePath: string;
62
+ constructor(filePath: string);
63
+ initialize(seedCollections: Record<string, unknown[]>): Promise<MockDatabase>;
64
+ read(): Promise<MockDatabase>;
65
+ write(database: MockDatabase): Promise<void>;
66
+ withDatabase<T>(updater: (database: MockDatabase) => Promise<T> | T): Promise<T>;
67
+ }
68
+
69
+ export { type DriftIssue, type HttpMethod, type InitConfig, JsonStateStore, type MockDatabase, type MockEngineConfig, type MockStateExtension, type RouteContext, createServer, loadOpenApiDocument };