Welcome to the Usage project tutorial! In this series, we are going to build a command that displays usage limits (like a plan overview) within an application.
In this first chapter, we are tackling the most important foundational concept: The Type-Driven Contract.
Imagine you are building a new feature, like a "Usage Settings" panel. You want to plug this feature into a large, existing application framework.
The framework needs to run your code, but it doesn't know what you wrote.
If we just guess, the app might crash when a user clicks the button. We need a way to guarantee that your new code fits perfectly into the framework.
The Use Case: We want to ensure that our call function (which launches the settings) receives exactly the tools it needs (like onDone) and returns exactly what the framework expects (a UI element).
Think of the framework as a wall with an electrical socket. Think of your code as a lamp plug.
LocalJSXCommandCall type defined by the framework. It has a specific shape (three holes, specific voltage).usage.tsx.If your plug has two prongs but the socket requires three, they won't fit. You can't even plug it in.
In programming terms:
LocalJSXCommandCall) defines the rules.
To fulfill this contract, we explicitly tell TypeScript that our function acts as a LocalJSXCommandCall.
First, we need to bring in the "blueprint" (the type) so we know what we are building.
// usage.tsx
import * as React from 'react';
// We import the specific type definition here
import type { LocalJSXCommandCall } from '../../types/command.js';
Now we write our function. Notice the : LocalJSXCommandCall part. This is where we sign the contract.
// usage.tsx - continued
import { Settings } from '../../components/Settings/Settings.js';
// We apply the type to our constant 'call'
export const call: LocalJSXCommandCall = async (onDone, context) => {
// If we don't return a React element here, TypeScript yells at us!
return <Settings onClose={onDone} context={context} defaultTab="Usage" />;
};
What just happened?
onDone (a function to close the panel) and context (data about the app). Because of the contract, we get auto-completion for these!<Settings ... />. This is a React Element. If we tried to return a number or text, the "building inspector" (compiler) would show a red error line.What actually happens when the application tries to run your command?
Because we adhered to the Type-Driven Contract, the framework can trust our code blindly. It knows exactly which inputs to provide and what output to wait for.
Here is the flow of execution:
Let's look at the "Socket" definition. While you don't need to memorize this, it helps to see what the contract actually looks like.
We also use a contract for the command registration in index.ts. This file tells the system about your command.
// index.ts
import type { Command } from '../../commands.js'
// This object must satisfy the 'Command' shape
export default {
type: 'local-jsx',
name: 'usage',
// ... other properties
} satisfies Command
The satisfies Command keyword acts similarly to the colon syntax (: Type). It checks that the object we are exporting matches the Command interface.
By using these types:
LocalJSXCommandCall: Ensures the function logic is correct (in usage.tsx).Command: Ensures the configuration is correct (in index.ts).
We will discuss how index.ts is actually used in the Command Registration chapter.
In this chapter, we learned that a Type-Driven Contract is like a safety standard for our code. By marking our call function with LocalJSXCommandCall, we ensure that our "plug" fits the framework's "socket" perfectly. This prevents bugs and makes coding easier because our tools know exactly what data is available.
Now that our code is "safe" and follows the rules, we need to tell the framework that it exists.
Next Chapter: Command Registration
Generated by Code IQ