👩🏻💻 Developer Ready: A comprehensive JavaScript solution to generate types for your projects. Works out of the box for most APIs.
🏃🏽 Instant Feedback: Fast, interactive watch mode.
📸 Snapshot Testing: Capture snapshots of your API types and be notified if the API responses changes
Install layj using npm
npm i layjLet's get started by writing the type generators for themoviedb api, in a fille named api.layj.mjs:
import { layj } from "layj";
const get = async (url) => {
return (await fetch(url)).json();
}
layj('movieType', (example) => {
const API_KEY = "**API key**";
example("request movie", async () => {
return await get(`https://api.themoviedb.org/3/movie/11?api_key=${API_KEY}`);
});
example("request movie", async () => {
return await get(`https://api.themoviedb.org/3/movie/110?api_key=${API_KEY}`);
});
example("request movie with video", async () => {
return await get(`https://api.themoviedb.org/3/movie/157336?api_key=${API_KEY}&append_to_response=videos`);
});
example("request movie with video and images", async () => {
return await get(`https://api.themoviedb.org/3/movie/157336?api_key=${API_KEY}&append_to_response=videos,images`);
});
example("request movie no api", async () => {
return await get(`https://api.themoviedb.org/3/movie/11`);
});
});if you run this file, layj will generate the movieType type in a file movieType.ts located in the directory types and a file called movieType.snapshot in the directory snapshots, we will see how you can change this behaviour later.
Under the hood layj made the get requests then generated the types for the return values, at last it combined the types in a single type movieType using the | union type making sure that ovarlaping objects are combined.
import { layj } from "layj";
layj('UserResponse', (example) => {
example("name only", async () => {
return {
name: "Jhon Doe"
}
});
example("name and email address", async () => {
return {
name: "Jhon Doe",
email: "[email protected]",
}
});
example("error", async () => {
return null;
});
example("user with age", async () => {
return {
name: "Jhon Doe",
email: "[email protected]",
age: 99,
}
});
example("user likes", async () => {
return {
name: "Jhon Doe",
likes: [1, "cats", false],
}
});
});Running this file will produce this type:
export type UserResponse = {
name: string,
email: string | undefined,
age: number | undefined,
likes: (number | string | boolean)[] | undefined,
}
| null;As you can see layj was able to combine the different responses into a single type.
Sometimes you now that a part of your respose is allways the same you would want to use literals instead of a more general type for example:
import { layj } from "layj";
layj('literalExample', (example) => {
example("request 1", async () => {
return {
isError:false,
data:"bla bla",
dataType:"type 1",
}
});
example("request 2", async () => {
return {
isError:false,
data:"bla bla",
dataType:"type 2",
}
});
example("request 3", async () => {
return {
isError:false,
data:"bla bla",
dataType:"type 3",
}
});
example("request 4", async () => {
return {
isError:false,
data:"bla bla",
dataType:"type 1",
}
});
example("request 2", async () => {
return {
isError:true,
}
});
}, {
literals:{
isError:true,
dataType:true,
}
});This will generate the current type
export type literalExample = {
isError: true | false,
data: undefined | string,
dataType: "type 2" | "type 1" | "type 3" | undefined,
};useXORif set totruelayj will combine the types usnig XORsnapshotif set tofalselayj will skip creating the type snapshotsthrowOnTypeChangeif set tofalselayj wont throw an error if the generated type is different from the snapshot typejsDocif set totruelayj will generate JSDoc comments instead of typescirpt filessnapshotsDirif set layj will use this directory for the snapshotsoutDirif set layj will use this directory for the generated typesliteralsthis is an object that specifies if the value under a specific path should be used as a literal type instead of the more general type (see example above)
layj commes with a cli that you can use to watch for changes to the type generator files and execute them. To use the cli you can add a watch commad in your pacage.json ex:
{
"scriprs":{
"watch-api-types":"layj watch",
"generate-api-types":"layj generate",
}
}both watch and generate commands accept sevral flags to overwrite de default behaviour
--fignores snapshot errors equivalent to settingthrowOnTypeChange:false--noSnapshotsignores snapshot errors equivalent to settingsnapshot:false--confflag specifies a global conf file by default layj looks forlayj.config.js--outDir,--jsDoc,--useXOR,--snapshotsDirbehave the same as their parameters equivalent
While you are working on a generator file and introduce changes that produce a type different than the one in the snapshot you can press r to ignore the errors and regenerate new snapshots.
layj has 4 levels of configuration, layj combines those to a single config, in this order
- the default config
- the config from
layj.config.jsor the file specified by the--confargument - the config from the command line arguments
- the config provided as argument to the
layjfunction
This means that parameters provided to the layj function will allways overwite all other parameters.
- Before making commits, please open an issue to make sure the changes made are in the scope of layj
- Keep the code simple and short.
- Make sure you don't introduce new unexpected behavour
- Thats it, have fun