Skip to content

Commit 2bd07b8

Browse files
committed
improved examples
1 parent 1a2283a commit 2bd07b8

File tree

3 files changed

+143
-261
lines changed

3 files changed

+143
-261
lines changed

README.md

Lines changed: 34 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This library is an attempt to provide a simple and opinionated way to do all of
1414

1515
```typescript
1616
import { buildValidator } from "model-validator-ts";
17+
import { z } from "zod";
1718

1819
const loginCommand = buildValidator()
1920
// Your usual zod schema
@@ -71,6 +72,35 @@ if (!result.success) {
7172
// { user: { id: string, email: string }, token: string }
7273
console.log(result.result);
7374
}
75+
76+
// Now use in your http handler
77+
app.post("/login", async (req, res) => {
78+
const result = await loginCommand.run(req.body);
79+
if (!result.success) {
80+
return res.status(400).json({
81+
success: false,
82+
errors: result.errors.toObject(),
83+
});
84+
}
85+
return res.status(200).json({
86+
success: true,
87+
result: result.result,
88+
});
89+
});
90+
91+
// Or if using something like trpc
92+
const loginProcedure = publicProcedure
93+
.input(loginCommand.inputSchema)
94+
.mutation(async ({ input }) => {
95+
const result = await loginCommand.run(input);
96+
if (!result.success) {
97+
throw new trpc.TRPCError({
98+
code: "BAD_REQUEST",
99+
message: result.errors.toObject(),
100+
});
101+
}
102+
return result.result;
103+
});
74104
```
75105

76106
For a more complex real-world example with multiple dependencies and rules, check out the [order cancellation example](https://github.com/muniter/model-validator-ts/blob/main/src/order-cancellation.example.ts).
@@ -95,168 +125,12 @@ yarn add model-validator-ts
95125
pnpm add model-validator-ts
96126
```
97127

98-
## Quick Start
99-
100-
### Basic Validation
101-
102-
```typescript
103-
import { buildValidator } from "model-validator-ts";
104-
import { z } from "zod";
105-
106-
const userSchema = z.object({
107-
name: z.string().min(3),
108-
age: z.number().min(18),
109-
email: z.string().email(),
110-
});
111-
112-
// Simple validation without dependencies
113-
const validator = buildValidator().input(userSchema).rule({
114-
fn: async ({ data, bag }) => {
115-
if (await isUserBlacklisted(data.email)) {
116-
return bag.addError("email", "User is blacklisted");
117-
}
118-
},
119-
});
120-
121-
const result = await validator.validate({
122-
name: "John",
123-
age: 25,
124-
125-
});
126-
127-
if (result.success) {
128-
console.log("Valid user:", result.value);
129-
console.log("Context:", result.context);
130-
} else {
131-
console.log("Typed Validation errors to use in your UI:", result.errors.toObject());
132-
}
133-
```
134-
135-
### User Login Example
136-
137-
A complete example showing schema validation, business rules, context passing, and command execution:
138-
139-
```typescript
140-
import { z } from "zod";
141-
import { buildValidator } from "model-validator-ts";
142-
143-
interface User {
144-
id: string;
145-
role: "admin" | "customer";
146-
email: string;
147-
passwordHash: string;
148-
}
128+
## Examples
149129

150-
const loginSchema = z.object({
151-
email: z.string().email(),
152-
password: z.string().min(8),
153-
});
130+
If you want more complex examples, here are a few:
154131

155-
const loginCommand = buildValidator()
156-
.input(loginSchema)
157-
.rule({
158-
fn: async ({ data, bag }) => {
159-
// Data is fully typed from the schema
160-
const user = await userService.findByEmail(data.email);
161-
if (!user) {
162-
return bag.addGlobalError("Invalid email or password");
163-
}
164-
if (!(await userService.validatePassword(user, data.password))) {
165-
return bag.addGlobalError("Invalid email or password");
166-
}
167-
// Pass user to next rules via context
168-
return { context: { user } };
169-
},
170-
})
171-
.rule({
172-
fn: async ({ data, bag, context }) => {
173-
// Access context from previous rule
174-
if (context.user.role === "admin") {
175-
return bag.addError("email", "Admin users cannot login with password");
176-
}
177-
},
178-
})
179-
.command({
180-
execute: async ({ context, bag }) => {
181-
// Execute the business logic
182-
const { user } = context;
183-
return {
184-
user,
185-
token: await userService.generateToken(user),
186-
};
187-
},
188-
});
189-
190-
// Usage in an endpoint
191-
app.post("/login", async (req, res) => {
192-
const result = await loginCommand.run(req.body);
193-
if (!result.success) {
194-
return res.status(400).json({
195-
success: false,
196-
errors: result.errors.toObject(),
197-
});
198-
}
199-
return res.status(200).json({
200-
success: true,
201-
result: result.result,
202-
});
203-
});
204-
```
205-
206-
### Money Transfer with External Services
207-
208-
A more complex example showing dependency injection and errors happening on command execution and rules.
209-
210-
```typescript
211-
const transferMoneySchema = z.object({
212-
fromAccount: z.string(),
213-
toAccount: z.string(),
214-
amount: z.number().positive(),
215-
});
216-
217-
const transferCommand = buildValidator()
218-
.input(transferMoneySchema)
219-
.$deps<{ externalBankService: BankService }>()
220-
.rule({
221-
id: "no-self-transfer",
222-
fn: async ({ data, bag }) => {
223-
if (data.fromAccount === data.toAccount) {
224-
bag.addError("toAccount", "Cannot transfer to same account");
225-
}
226-
},
227-
})
228-
.rule({
229-
id: "balance-check",
230-
fn: async ({ data, deps, bag }) => {
231-
const balance = await deps.externalBankService.checkAccountBalance(
232-
data.fromAccount
233-
);
234-
if (balance < data.amount) {
235-
bag.addError("amount", "Insufficient funds");
236-
}
237-
},
238-
})
239-
.command({
240-
execute: async ({ data, deps, bag }) => {
241-
try {
242-
const result = await deps.externalBankService.executeTransfer(
243-
data.fromAccount,
244-
data.toAccount,
245-
data.amount
246-
);
247-
return result;
248-
} catch (error) {
249-
// Handle runtime errors, or things that just can't be validated before execution
250-
return bag.addGlobalError(`External service error: ${error.message}`);
251-
}
252-
},
253-
});
254-
255-
// Execute with dependencies
256-
const result = await transferCommand
257-
.provide({ externalBankService })
258-
.run({ fromAccount: "acc-123", toAccount: "acc-456", amount: 100 });
259-
```
132+
- [Order Cancellation Example](https://github.com/muniter/model-validator-ts/blob/main/src/order-cancellation.example.ts) - Complex e-commerce validation scenario that evaluate tons of rules neccessary for cancelling an order.
133+
- [User Login Example](https://github.com/muniter/model-validator-ts/blob/main/src/login.example.ts) - Login into an application that has a few special rules.
260134

261135
## API Reference
262136

src/login.example.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
import { buildValidator } from "./index.js";
22
import { z } from "zod";
33

4-
declare const app: {
5-
post(
6-
path: string,
7-
handler: (
8-
req: { body: unknown },
9-
res: { status: (code: number) => { json: (data: unknown) => void } }
10-
) => unknown | Promise<unknown>
11-
): void;
12-
};
13-
144
interface User {
155
id: string;
166
role: "admin" | "customer";
@@ -86,3 +76,14 @@ app.post("/login", async (req, res) => {
8676
result: result.result,
8777
});
8878
});
79+
80+
// Just to make typescript happy
81+
declare const app: {
82+
post(
83+
path: string,
84+
handler: (
85+
req: { body: unknown },
86+
res: { status: (code: number) => { json: (data: unknown) => void } }
87+
) => unknown | Promise<unknown>
88+
): void;
89+
};

0 commit comments

Comments
 (0)