definitely-fine
v0.1.1
Published
Scenario-driven runtime interception toolkit for app and end-to-end tests.
Maintainers
Readme
definitely-fine
definitely-fine is the core package for defining, saving, loading, and activating typed runtime scenarios in application and end-to-end tests.
It lets tests steer selected application behavior by contract while the application still executes its normal route handlers, actions, services, and business logic.
Installation
pnpm add definitely-fineImportant
[!IMPORTANT] Disable runtime interception in production.
If a production process should never honor saved scenarios, create the runtime with
enabled: false. This makes every wrapper fall through to the original implementation even if a scenario id is present.
import { createRuntime } from "definitely-fine";
const runtime = createRuntime<DemoContract>({
enabled: process.env.NODE_ENV !== "production",
});Use this as the default recommendation for application code. Treat interception as test-only unless you have an explicit non-production use case.
Core Idea
You define a contract for the behavior you want to intercept, create a scenario that targets specific function or service calls, save that scenario, then wrap your real implementation with a runtime.
At execution time, an active scenario id decides whether the runtime should intercept a call or fall through to the real implementation.
flowchart LR
subgraph W[Writer process]
W1[createScenario]
W2[define rules]
W3[save scenario]
W4[scenario id]
end
subgraph S[Shared scenario storage]
S1[(persisted scenario JSON)]
end
subgraph R[Runtime process]
R1[runWithRuntimeScenarioContext]
R2[createRuntime wrappers]
R3[wrapped function or service]
R4[real implementation]
R5[scenario-defined return or throw]
end
W1 --> W2 --> W3
W3 --> S1
W3 --> W4
W4 --> R1
R1 --> R2 --> R3
R3 -- load active scenario --> S1
R3 --> R4
R3 --> R5Minimal Example
import {
createRuntime,
createScenario,
runWithRuntimeScenarioContext,
} from "definitely-fine";
type DemoContract = {
services: {
math: {
double(value: number): number;
};
};
functions: {
generateId(prefix: string): string;
};
errors: Record<string, never>;
};
const scenario = createScenario<DemoContract>();
scenario.fn("generateId").onCall(1).returns("generated-1");
scenario.service("math").method("double").onCall(2).returns(99);
await scenario.save();
const runtime = createRuntime<DemoContract>();
const generateId = runtime.wrapSyncFunction("generateId", (prefix) => {
return `${prefix}-live`;
});
const math = runtime.wrapSyncService("math", {
double(value: number): number {
return value * 2;
},
});
const result = runWithRuntimeScenarioContext(
{ scenarioId: scenario.id },
() => {
return {
id: generateId("user"),
firstDouble: math.double(2),
secondDouble: math.double(2),
};
},
);
console.log(result);
// {
// id: "generated-1",
// firstDouble: 4,
// secondDouble: 99,
// }Main APIs
createScenario()
Creates a scenario builder with a stable id.
Use fn() when targeting a standalone function:
import { createScenario } from "definitely-fine";
const scenario = createScenario<DemoContract>();
scenario.fn("generateId").onCall(1).returns("generated-1");
scenario.fn("generateId").onCall(2).throwsMessage("blocked");
await scenario.save();Use service().method() when targeting methods on a wrapped service object:
import { createScenario } from "definitely-fine";
const scenario = createScenario<DemoContract>();
scenario.service("math").method("double").onCall(1).returns(10);
await scenario.save();createRuntime()
Creates a runtime that loads persisted scenarios and wraps implementations.
import { createRuntime } from "definitely-fine";
const runtime = createRuntime<DemoContract>();
const generateId = runtime.wrapSyncFunction("generateId", (prefix) => {
return `${prefix}-live`;
});
const math = runtime.wrapSyncService("math", {
double(value: number): number {
return value * 2;
},
});sequenceDiagram
participant App as App process
participant Runtime as definitely-fine runtime
participant Store as Scenario storage
App->>Runtime: call wrapped function or service method
Runtime->>Runtime: resolve active scenario id
alt no active scenario id
Runtime-->>App: call original implementation
else active scenario id present
Runtime->>Store: load persisted scenario
Store-->>Runtime: scenario JSON or missing
alt matching rule for this call
Runtime-->>App: mocked return or configured throw
else no matching rule
Runtime-->>App: call original implementation
end
endDisable interception explicitly when the runtime must never load or honor scenarios:
const runtime = createRuntime<DemoContract>({
enabled: false,
});runWithRuntimeScenarioContext()
Activates a scenario id for the current execution scope.
const result = runWithRuntimeScenarioContext(
{ scenarioId: scenario.id },
() => {
return generateId("user");
},
);getRuntimeScenarioId()
Returns the active scenario id inside wrapped code.
import { getRuntimeScenarioId } from "definitely-fine";
const generateId = runtime.wrapSyncFunction("generateId", (prefix) => {
return `${prefix}-${getRuntimeScenarioId() ?? "live"}`;
});Storage
By default, the built-in JSON adapter infers its storage directory automatically. When inference succeeds, scenarios are stored under node_modules/.cache/definitely-fine/scenarios.
That means this is valid without providing any storage options:
import { createRuntime, createScenario } from "definitely-fine";
const scenario = createScenario<DemoContract>();
const runtime = createRuntime<DemoContract>();import { createRuntime, createScenario } from "definitely-fine";
const scenario = createScenario<DemoContract>({
directory: ".definitely-fine",
});
const runtime = createRuntime<DemoContract>({
directory: ".definitely-fine",
});If you want full control over persistence, pass a custom adapter instead of relying on the built-in file-backed adapter.
import {
JsonScenarioStorageAdapter,
createRuntime,
createScenario,
} from "definitely-fine";
const adapter = new JsonScenarioStorageAdapter({});
const scenario = createScenario<DemoContract>({ adapter });
const runtime = createRuntime<DemoContract>({ adapter });When To Use It
- You want typed, call-specific overrides such as "return this value on the third call".
- You want browser tests to influence server-side behavior without adding test-only request plumbing everywhere.
- You want app code to keep running through real route handlers, actions, and services.
Related Packages
@definitely-fine/nextjspropagates scenario ids through Next.js route handlers and server actions.@definitely-fine/playwrighthelps Playwright tests create browser contexts with the active scenario header already set.
