Skip to content

Commit

Permalink
feat: zod-lite
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaKGoldberg committed Jun 12, 2024
1 parent a981101 commit 81836d6
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 0 deletions.
58 changes: 58 additions & 0 deletions projects/type-operations/zod-lite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Zod Lite

> A [Learning TypeScript > Type Operations](https://learning-typescript.com/type-operations) 🍲 entree project.
Kneel before Zod!

## Setup

In one terminal, run the TypeScript compiler via the `tsc` script.
For example, to start the TypeScript compiler in watch mode:

```shell
npm run tsc -- --watch
```

In another terminal, run Jest via the `test` script.
For example, to start tests in watch mode:

```shell
npm run test -- --watch
```

## Specification

Create and export the following functions from `src/index.ts`:

- `string()`: creates a schema object representing any primitive string
- `literal<Value>(value)`: creates a schema object of a literal string value
- `union<Values>(values)`: creates a schema object representing multiple allowed type constituents
- `object<Properties>(properties)`: creates a schema object representing an object

Additionally, export an `type Infer<Schema>` that creates a TypeScript type for a schema.

## Example

This code:

```ts
import * as z from "./index";

const spySchema = z.object({
disguise: z.string(),
moniker: z.literal("007"),
plan: z.union([z.literal("active"), z.literal("improvising")]),
});

type Spy = z.Infer<typeof spySchema>;
```

...create a `Spy` type equivalent to:

```ts
type Spy = {
disguise: string;
moniker: "007";
plan: "active" | "improvising";
};
```
4 changes: 4 additions & 0 deletions projects/type-operations/zod-lite/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label": "🍲 Zod Lite",
"position": 3
}
12 changes: 12 additions & 0 deletions projects/type-operations/zod-lite/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
transform: {
"^.+\\.(t|j)sx?$": [
"@swc/jest",
{
jsc: {
target: "es2021",
},
},
],
},
};
8 changes: 8 additions & 0 deletions projects/type-operations/zod-lite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "the-narrow-trail",
"scripts": {
"test": "jest",
"test:solutions": "cross-env TEST_SOLUTIONS=1 jest",
"tsc": "tsc"
}
}
24 changes: 24 additions & 0 deletions projects/type-operations/zod-lite/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, jest, test } from "@jest/globals";
import { expectType } from "tsd";

import * as index from "./index";
import * as solution from "./solution";

const z = process.env.TEST_SOLUTIONS ? solution : index;

const mockRandom = jest.spyOn(Math, "random");

describe("zod-lite", () => {
test("Spy", () => {
const spySchema = z.object({
disguise: z.string(),
moniker: z.literal("007"),
plan: z.union([z.literal("active"), z.literal("improvising")]),
});
expectType<{
disguise: string;
moniker: "007";
plan: "active" | "improvising";
}>({} as index.Infer<typeof spySchema>);
});
});
2 changes: 2 additions & 0 deletions projects/type-operations/zod-lite/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Write your functions and type here! ✨
// You'll need to export them so the tests can use them.
73 changes: 73 additions & 0 deletions projects/type-operations/zod-lite/src/solution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Primitives

export interface SchemaString {
primitive: "string";
zType: "primitive";
}

export const string = (): SchemaString => ({
primitive: "string",
zType: "primitive",
});

// Literals
export interface SchemaLiteral<Value> {
value: Value;
zType: "literal";
}

export const literal = <Value extends string>(
value: Value
): SchemaLiteral<Value> => ({
value,
zType: "literal",
});

// Unions
export interface SchemaUnion<Values extends any[]> {
values: Values;
zType: "union";
}

export const union = <Values extends any[]>(
values: Values
): SchemaUnion<Values> => ({
values,
zType: "union",
});

export type UnwrapSchemaUnion<Values> = Values extends (infer Value)[]
? Infer<Value>
: never;

// Objects
export interface SchemaObject<Properties> {
properties: Properties;
zType: "object";
}

export const object = <Properties>(
properties: Properties
): SchemaObject<Properties> => ({
properties,
zType: "object",
});

export type UnwrapSchemaObject<Properties> = {
[K in keyof Properties]: Infer<Properties[K]>;
};

export type Infer<Schema> =
// Primitives (string)
Schema extends SchemaString
? string
: // Literals
Schema extends SchemaLiteral<infer Value>
? Value
: // Unions
Schema extends SchemaUnion<infer Values>
? UnwrapSchemaUnion<Values>
: // Objects
Schema extends SchemaObject<infer Properties>
? UnwrapSchemaObject<Properties>
: never;
4 changes: 4 additions & 0 deletions projects/type-operations/zod-lite/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.json",
"include": ["src"]
}

0 comments on commit 81836d6

Please sign in to comment.