Schmock Plugin Authoring Skill
Plugin Interface
Defined in types/schmock.d.ts:
typescript
interface Plugin {
name: string; // Unique plugin identifier
version?: string; // Plugin version (semver)
process(context: PluginContext, response?: any): PluginResult | Promise<PluginResult>;
onError?(error: Error, context: PluginContext): Error | ResponseResult | void | Promise<Error | ResponseResult | void>;
}
PluginContext
Available in process() and onError():
| Field | Type | Description |
|---|---|---|
path | string | Request path |
route | RouteConfig | Matched route configuration |
method | HttpMethod | HTTP method |
params | Record<string, string> | Route parameters (:id etc.) |
query | Record<string, string> | Query string parameters |
headers | Record<string, string> | Request headers |
body | unknown | Request body |
state | Map<string, unknown> | Shared state between plugins for this request |
routeState | Record<string, unknown> | Route-specific state |
PluginResult
typescript
interface PluginResult {
context: PluginContext; // Updated context (can be modified)
response?: unknown; // Response data (if generated/modified)
}
Pipeline Behavior
- •Plugins are called in order via
.pipe() - •First plugin to set
responsein itsPluginResultis the generator - •Subsequent plugins receive the response and can transform it
- •If no plugin sets a response, the route's generator function is used
typescript
const mock = schmock({});
mock('GET /users', () => defaultData, {})
.pipe(authPlugin()) // Can reject unauthenticated requests
.pipe(cachePlugin()) // Can serve cached responses
.pipe(logPlugin()); // Can log but pass through
Error Handling
onError is called when an error occurs during processing:
- •Return an
Error— replace the error (transformed error propagates) - •Return
ResponseResult— suppress the error, use this as the response - •Return
void/undefined— error propagates unchanged
Reference Implementation: schemaPlugin
See packages/schema/src/index.ts for the canonical plugin pattern:
typescript
export function schemaPlugin(options: SchemaPluginOptions): Plugin {
validateSchema(options.schema);
return {
name: "schema",
version: "1.0.1",
process(context: PluginContext, response?: any) {
// Pass through if another plugin already generated a response
if (response !== undefined && response !== null) {
return { context, response };
}
// Generate response from schema
const generatedResponse = generateFromSchema({ ... });
return { context, response: generatedResponse };
}
};
}
Key patterns:
- •Factory function returns a
Pluginobject - •Validate options eagerly in the factory
- •Check for existing response before generating — respect pipeline order
- •Return
{ context, response }always
BDD Testing for Plugins
Every plugin should have BDD tests. Follow BDD-first development:
- •Write
.featurescenarios describing plugin behavior - •Write
.steps.tswith step implementations - •Implement the plugin to make tests pass
Example scenario structure:
gherkin
Feature: Cache Plugin
As a developer
I want to cache API responses
So that repeated requests are served faster
Scenario: Cache hit returns cached response
Given I create a mock with a cache plugin
When I request "GET /users" twice
Then the second response should be from cache
Templates
Use /plugin-authoring <name> <package> to generate plugin boilerplate:
- •
<name>.ts— Plugin implementation - •
<name>.test.ts— Unit tests - •
<name>.feature— BDD feature file - •
<name>.steps.ts— BDD step definitions
Plugin Development Checklist
- •Define the plugin's purpose and behavior in a
.featurefile (BDD-first!) - •Write step definitions
- •Implement the plugin factory function
- •Handle the "response already exists" case (pass-through or transform)
- •Implement
onErrorif the plugin needs error handling - •Add unit tests for complex internal logic
- •Run
bun test:allto verify