Welcome to the first chapter of the Schemas project tutorial! In this series, we will explore how to build a flexible, reactive system using Zod schemas.
In this chapter, we will look at the foundation of our reactive system: the Event-Based Configuration Registry.
Imagine you are building an automated coding assistant. You have a specific requirement:
Use Case: Every time the assistant finishes writing a code file, you want to automatically run a "linter" to check for syntax errors.
Hardcoding this logic deep inside your code makes the system brittle. What if you later want to run a security scan instead? Or send a notification?
We need a flexible Rulebook. We need a way to say: "When X happens, check if it looks like Y, and if so, do Z."
HooksSchema
The HooksSchema is the master registry. It acts like a centralized dispatcher. It maps Lifecycle Events to a list of rules.
Here is the high-level hierarchy:
WriteFile?").eslint command").Think of this system like a 911 dispatcher's manual:
Let's look at how we structure the data to solve our "Lint on Save" use case.
First, we look at the top-level object. The keys of this object correspond to specific moments in the application's lifecycle, known as HOOK_EVENTS.
// The top-level registry
const settings = {
// We listen for the 'tool_finish' event
"tool_finish": [
// Rules go here...
]
}
Explanation: This tells the system, "I only care about adding behavior when a tool finishes its job." If an event isn't listed here, the system ignores it.
Inside the event array, we add a Matcher. We don't want to lint every tool (like "ReadFile" or "ListDir"). We only match "WriteFile".
// Inside "tool_finish":
[
{
// The filter
"matcher": "WriteFile",
"hooks": [
// Actions go here...
]
}
]
Explanation: The matcher field acts as a gatekeeper. If the tool name matches "WriteFile", we proceed to the hooks. If not, we skip it.
Finally, we define the action. This is the Hook.
// Inside "hooks":
[
{
"type": "command",
"command": "npm run lint",
"statusMessage": "Linting file..."
}
]
Explanation: This describes what to do. In this case, it executes a shell command. We will learn about the different types of hooks (commands, prompts, etc.) in Polymorphic Hook Definitions.
How does the system process this registry? Let's visualize the flow.
When an event occurs in the application, the HooksSchema logic takes over.
tool_finish.
Let's look at the actual Zod definition from hooks.ts.
This defines the structure of a single rule (Filter + Actions).
// hooks.ts
export const HookMatcherSchema = lazySchema(() =>
z.object({
// The filter string (e.g., "WriteFile")
matcher: z.string().optional(),
// The list of actions to perform
hooks: z.array(HookCommandSchema()),
}),
)
Explanation:
matcher: This is optional. If you leave it empty, it acts like a wildcard (matches everything).hooks: This uses HookCommandSchema, which we will detail in the next chapter.This wraps everything together into the master lookup table.
// hooks.ts
export const HooksSchema = lazySchema(() =>
// Maps Event Names -> Array of Matchers
z.partialRecord(
z.enum(HOOK_EVENTS),
z.array(HookMatcherSchema())
),
)
Explanation:
z.enum(HOOK_EVENTS): This ensures you can only use valid event names (like tool_init, tool_finish) as keys. You can't make up random events.z.partialRecord: This is key! It means you don't have to define rules for every single event. You can define just one, or all of them.z.array(HookMatcherSchema): Each event can have multiple independent rules (e.g., one rule to Lint, another rule to Git Commit).In this chapter, we learned:
But waitβwhat exactly goes inside that hooks array? In our example, we used a command, but can we ask the AI to think? Can we make an HTTP request?
Yes! The system is polymorphic. Read on to find out how.
Next Chapter: Polymorphic Hook Definitions
Generated by Code IQ