Skip to content

Commit 3224972

Browse files
authored
Merge pull request #18 from bmthd/feature/decorators
Feature/decorators
2 parents ae2399d + 725bdc2 commit 3224972

File tree

5 files changed

+158
-0
lines changed

5 files changed

+158
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { User } from "./domain";
2+
3+
/** Mock database */
4+
export const db = {
5+
user: {
6+
update: async ({ where, data }: { where: { id: string }; data: User }) => {
7+
// Simulate a database update operation
8+
console.log(`Updating user with id ${where.id} with data`, data);
9+
return { id: where.id, ...data }; // Return the updated user object
10+
},
11+
},
12+
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { cookies } from "next/headers";
2+
import { unauthorized } from "next/navigation";
3+
import * as v from "valibot";
4+
import { failure, type ResultAsync } from "./result";
5+
6+
export const withValidate = <T extends v.TupleItems>(...schemas: T) => {
7+
return <R>(
8+
handler: (...args: v.InferOutput<v.TupleSchema<T, any>>) => ResultAsync<R, string>,
9+
) => {
10+
return async (...args: v.InferOutput<v.TupleSchema<T, any>>): ResultAsync<R, string> => {
11+
const validationResult = v.safeParse(v.tuple(schemas), args);
12+
if (!validationResult.success) {
13+
return failure("Invalid arguments");
14+
}
15+
return handler(...args);
16+
};
17+
};
18+
};
19+
20+
export const withAuth =
21+
<T extends () => void>(handler: T) =>
22+
async (): Promise<void> => {
23+
const cookie = await cookies();
24+
const authToken = cookie.get("auth-token")?.value;
25+
if (!authToken) {
26+
return unauthorized();
27+
}
28+
return handler();
29+
};
30+
31+
export function pipe<T1, T2>(value: T1, fn: (value: T1) => T2): T2;
32+
export function pipe<T1, T2, T3>(value: T1, fn1: (value: T1) => T2, fn2: (value: T2) => T3): T3;
33+
export function pipe<T1, T2, T3, T4>(
34+
value: T1,
35+
fn1: (value: T1) => T2,
36+
fn2: (value: T2) => T3,
37+
fn3: (value: T3) => T4,
38+
): T4;
39+
export function pipe<T1, T2, T3, T4, T5>(
40+
value: T1,
41+
fn1: (value: T1) => T2,
42+
fn2: (value: T2) => T3,
43+
fn3: (value: T3) => T4,
44+
fn4: (value: T4) => T5,
45+
): T5;
46+
export function pipe<T1, T2, T3, T4, T5, T6>(
47+
value: T1,
48+
fn1: (value: T1) => T2,
49+
fn2: (value: T2) => T3,
50+
fn3: (value: T3) => T4,
51+
fn4: (value: T4) => T5,
52+
fn5: (value: T5) => T6,
53+
): T6;
54+
export function pipe(value: unknown, ...fns: Function[]): unknown {
55+
return fns.reduce((acc, fn) => fn(acc), value);
56+
}
57+
58+
export function pipeAsync<T1, T2>(value: T1, fn: (value: T1) => Promise<T2>): Promise<T2>;
59+
export function pipeAsync<T1, T2, T3>(
60+
value: T1,
61+
fn1: (value: T1) => Promise<T2>,
62+
fn2: (value: T2) => Promise<T3>,
63+
): Promise<T3>;
64+
export function pipeAsync<T1, T2, T3, T4>(
65+
value: T1,
66+
fn1: (value: T1) => Promise<T2>,
67+
fn2: (value: T2) => Promise<T3>,
68+
fn3: (value: T3) => Promise<T4>,
69+
): Promise<T4>;
70+
export function pipeAsync<T1, T2, T3, T4, T5>(
71+
value: T1,
72+
fn1: (value: T1) => Promise<T2>,
73+
fn2: (value: T2) => Promise<T3>,
74+
fn3: (value: T3) => Promise<T4>,
75+
fn4: (value: T4) => Promise<T5>,
76+
): Promise<T5>;
77+
export function pipeAsync<T1, T2, T3, T4, T5, T6>(
78+
value: T1,
79+
fn1: (value: T1) => Promise<T2>,
80+
fn2: (value: T2) => Promise<T3>,
81+
fn3: (value: T3) => Promise<T4>,
82+
fn4: (value: T4) => Promise<T5>,
83+
fn5: (value: T5) => Promise<T6>,
84+
): Promise<T6>;
85+
export async function pipeAsync(
86+
value: unknown,
87+
...fns: ((...args: unknown[]) => Promise<unknown>)[]
88+
): Promise<unknown> {
89+
let result = value;
90+
for (const fn of fns) {
91+
result = await fn(result);
92+
}
93+
return result;
94+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as v from "valibot";
2+
3+
export const userSchema = () =>
4+
v.object({
5+
name: v.string(),
6+
email: v.pipe(v.string(), v.email()),
7+
});
8+
9+
export type User = v.InferOutput<ReturnType<typeof userSchema>>;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export type Success<T> = {
2+
success: true;
3+
data: T;
4+
};
5+
6+
export type Failure<E> = {
7+
success: false;
8+
error: E;
9+
};
10+
11+
export type Result<T, E> = Success<T> | Failure<E>;
12+
13+
export type ResultAsync<T, E> = Promise<Result<T, E>>;
14+
15+
export function success<T>(data: T): Success<T> {
16+
return { success: true, data };
17+
}
18+
19+
export function failure<E>(error: E): Failure<E> {
20+
return { success: false, error };
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use server";
2+
import * as v from "valibot";
3+
import { db } from "./db";
4+
import { withValidate } from "./decorators";
5+
import { type User, userSchema } from "./domain";
6+
import { failure, type ResultAsync, success } from "./result";
7+
8+
export const updateUser = withValidate(
9+
v.string(),
10+
userSchema(),
11+
)(async (id, user): ResultAsync<User, string> => {
12+
// ^? (id: string, user: User)
13+
try {
14+
const updatedUser = await db.user.update({
15+
where: { id },
16+
data: user,
17+
});
18+
return success(updatedUser);
19+
} catch {
20+
return failure("Failed to update user");
21+
}
22+
});

0 commit comments

Comments
 (0)