Back to packages list

Vals using zod

Description from the NPM package:
TypeScript-first schema declaration and validation library with static type inference
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { fetch } from "https://esm.town/v/std/fetch";
export const bookReservationOnResy = async ({
reservation,
venue,
auth,
}: {
reservation: {
// How many seats to book
seats: number;
// e.g. 2023-05-01 (interpreted as May 05, 2023)
date: string;
// e.g. [17:00, 22:00]
timeRange: [string, string];
};
venue:
| {
type: "id";
id: string;
}
| {
type: "slug";
slug: string;
city: string;
};
auth: {
email: string;
password: string;
};
}) => {
const { z } = await import("npm:zod");
const RESY_API_URL = "https://api.resy.com";
const RESY_DEFAULT_HEADERS = {
accept: "application/json, text/plain, */*",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
authorization: "ResyAPI api_key=\"VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5\"",
"x-origin": "https://resy.com",
origin: "https://resy.com/",
dnt: "1",
referer: "https://resy.com/",
"content-type": "application/json",
};
const BookedReservationResponseSchema = z.object({
resy_token: z.string(),
reservation_id: z.number(),
});
const SlotDetailsResponseSchema = z.object({
book_token: z.object({
value: z.string(),
date_expires: z.string(),
}),
});
const FindResponseSchema = z.object({
results: z.object({
venues: z
.object({
venue: z.object({
id: z.object({
resy: z.number(),
}),
name: z.string(),
location: z.object({
time_zone: z.string(),
}),
}),
slots: z
.object({
availability: z.object({
id: z.number(),
}),
config: z.object({
id: z.number(),
// Reservations are booked with the token
token: z.string(),
type: z.string(),
}),
date: z.object({
start: z.string(),
end: z.string(),
}),
payment: z.object({
cancellation_fee: z.number().nullish(),
deposit_fee: z.number().nullish(),
is_paid: z.boolean(),
}),
})
.array(),
})
.array()
.default([]),
}),
});
const PaymentMethodSchema = z.object({
id: z.number(),
type: z.string(),
});
const AuthenticationResponseSchema = z.object({
id: z.number(),
token: z.string(),

Useful when working with ZodFormData and you need to parse a text field that contains JSON.

See https://www.val.town/v/freecrayon/zodStringToJSON_example for an example of how to use it.

1
2
3
4
5
6
7
8
9
10
11
12
13
import { z } from "npm:zod";
const zodStringToJSON = () =>
z.string().transform((str, ctx): z.infer<z.ZodType<unknown>> => {
try {
return JSON.parse(str);
} catch (e) {
ctx.addIssue({ code: "custom", message: "Invalid JSON" });
return z.NEVER;
}
});
export { zodStringToJSON };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { z, ZodError } from "npm:zod";
const bodySchema = z.object({
body: z.string(),
headers: z.record(z.string(), z.string()),
}).partial();
const searchParamsSchema = z.object({
status: z.coerce.number().min(100).max(599).default(200),
statusText: z.string().default(undefined),
data: z.string(),
}).partial();
async function getBody(req: Request): Promise<Object> {
try {
return await req.json();
} catch (e) {
if (e instanceof SyntaxError) {
return {};
}
throw e;
}
}
export default async function(req: Request): Promise<Response> {
const searchParams = new URL(req.url).searchParams;
const searchParamsObj = new Map();
searchParams.forEach((v, k) => {
if (searchParamsObj.has(k)) {
searchParamsObj.set(k, [searchParamsObj[k]].concat(v));
} else {
searchParamsObj.set(k, v);
}
});
try {
const body = await getBody(req);
const bodyParsed = await bodySchema.parse(body);
const searchParamsParsed = await searchParamsSchema.parse(
Object.fromEntries(searchParamsObj.entries()),
);
const headers = new Headers(bodyParsed.headers);
const bodyResponse = bodyParsed.body ?? searchParamsParsed.data ?? undefined;
return new Response(bodyResponse, {
headers: bodyParsed.headers,
statusText: searchParamsParsed.statusText,
status: searchParamsParsed.status,
});
} catch (e) {
console.error(e);
if (e instanceof ZodError) {
return Response.json(JSON.stringify(e.message));
}
return Response.error();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { z } from "npm:zod";
export const ROWS = 9;
export const COLS = 9;
export const Slot = z.number().gte(0).lte(9);
export type Slot = z.infer<typeof Slot>;
export const Sudoku = z.array(z.array(Slot).length(COLS)).length(ROWS);
export type Sudoku = z.infer<typeof Sudoku>;
export function print(sudoku: Sudoku) {
sudoku = Sudoku.parse(sudoku);
for (let row = 0; row < ROWS; row++) {
let line = "";
for (let col = 0; col < COLS; col++) {
line += sudoku[row][col] + " ";
}
console.log(line);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { z } from "npm:zod";
const paramsSchema = z.object({
slug: z.coerce.string(),
estimatedProb: z.coerce.number().min(0).max(1),
bankroll: z.coerce.number().min(0),
deferenceFactor: z.coerce.number().min(0).max(1).optional().default(0.5),
});
// Copied from https://github.com/Will-Howard/manifolio/blob/master/manifolio-ui/lib/calculate.ts
function calculateNaiveKellyBet({
marketProb,
estimatedProb,
deferenceFactor,
bankroll,
}: { marketProb: number; estimatedProb: number; deferenceFactor: number; bankroll: number }): {
amount: number;
outcome: "YES" | "NO";
} {
const outcome = estimatedProb > marketProb ? "YES" : "NO";
const marketWinProb = outcome === "YES" ? marketProb : 1 - marketProb;
// kellyFraction * ((p - p_market) / (1 - p_market))
const fraction = deferenceFactor
* (Math.abs(estimatedProb - marketProb) / (1 - marketWinProb));
// clamp fraction between 0 and 1
const clampedFraction = Math.min(Math.max(fraction, 0), 1);
const amount = bankroll * clampedFraction;
return {
amount,
outcome,
};
}
async function getMarket(slug: string) {
const res = await fetch(`https://api.manifold.markets/v0/slug/${slug}`);
if (!res.ok) {
const body = await res.text();
throw new Error(body ?? "Error fetching market");
}
return res.json();
}
export default async function(req: Request): Promise<Response> {
const searchParams = new URL(req.url).searchParams;
const params = paramsSchema.parse(Object.fromEntries(searchParams.entries())) as z.infer<typeof paramsSchema>;
const market = await getMarket(params.slug);
const kelly = calculateNaiveKellyBet({
marketProb: market.probability,
estimatedProb: params.estimatedProb,
deferenceFactor: params.deferenceFactor,
bankroll: params.bankroll,
});
return Response.json(kelly);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { HttpVerb } from "https://esm.town/v/xkonti/httpUtils";
import { z } from "npm:zod";
import { zodToJsonSchema } from "npm:zod-to-json-schema";
export function getSchemaDesc(schema: z.Schema) {
return zodToJsonSchema(schema, {
name: "schema",
target: "openApi3",
}).definitions.schema;
}
export interface EndpointDefinition {
verb: HttpVerb;
path: string;
requestSchema: z.Schema | null;
requestDesc: string | null;
responseSchema: z.Schema | null;
responseDesc: string | null;
desc: string;
operationId: string;
}
export function getPathsDesc(endpoints: EndpointDefinition[]) {
const paths: any = {};
for (const endpoint of endpoints) {
// This is to allow multiple endpoints with the same path but different verbs
if (!paths[endpoint.path]) {
paths[endpoint.path] = {};
}
paths[endpoint.path][endpoint.verb.toLowerCase()] = {
description: endpoint.desc,
operationId: endpoint.operationId,
requestBody: endpoint.requestSchema
? {
description: endpoint.requestDesc ?? "Request body",
required: true,
content: {
"application/json": {
schema: getSchemaDesc(endpoint.requestSchema),
},
},
}
: undefined,
responses: {
"200": {
description: endpoint.responseDesc ?? "Success",
content: {
"application/json": {
schema: getSchemaDesc(endpoint.responseSchema),
},
},
},
},
};
}
return paths;
}
export function getOpenApiSpec(
url: string,
title: string,
description: string,
version: string,
endpoints: EndpointDefinition[],
) {
return {
openapi: "3.1.0",
info: {
title,
description,
version,
},
servers: [
{
url,
},
],
paths: getPathsDesc(endpoints),
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { z } from "npm:zod";
export const COLS = 7;
export const ROWS = 6;
export type Winner = {
kind: "winner";
winner: number;
};
export type Draw = {
kind: "draw";
};
export type Result = Winner | Draw;
export type InProgress = {
status: "in_progress";
next_player: number;
};
export type Over = {
status: "over";
result: Result;
};
export type Status = InProgress | Over;
const stringOrNumber = z.union([z.string(), z.number()]);
export const PlayerI = z.number().nonnegative().lte(1);
export const Slot = z.nullable(PlayerI);
export type Slot = z.infer<typeof Slot>;
export const Column = z.array(Slot).length(6);
export type Column = z.infer<typeof Column>;
export const Board = z.array(Column).length(7);
export type Board = z.infer<typeof Board>;
export const State = z.object({
next_player: PlayerI,
board: Board,
});
export type State = z.infer<typeof State>;
export const Action = z.object({
player: z.coerce.number().nonnegative().lte(1),
column: z.coerce.number().nonnegative().lt(COLS),
});
export type Action = z.infer<typeof Action>;
export function get(state: State, col: number, row: number): Slot {
return state.board[col][row];
}
export function set(state: State, col: number, row: number, slot: Slot): void {
state.board[col][row] = slot;
}
export type ActionCheck =
| "ok"
| "wrong_player"
| "column_full"
| "column_out_of_bounds";
export function check_action(state: State, action: Action): ActionCheck {
if (action.player !== state.next_player) {
return "wrong_player";
}
if (action.column < 0 || action.column >= COLS) {
return "column_out_of_bounds";
}
if (get(state, action.column, ROWS - 1) !== null) {
return "column_full";
}
return "ok";
}
function check_slots_eq(a: Slot, b: Slot, c: Slot, d: Slot): Slot {
if (a === b && b === c && c === d) {
return a;
}
return null;
}
export function status(state: State): Status {
// Check Vertical Win
for (let col = 0; col < COLS; col++) {
for (let row = 0; row < 3; row++) {
let check = check_slots_eq(
get(state, col, row + 0),
get(state, col, row + 1),
get(state, col, row + 2),
get(state, col, row + 3)
);
if (check !== null) {
return { status: "over", result: { kind: "winner", winner: check } };
}
}
}
// Check Horizontal Win
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { getModelBuilder } from "https://esm.town/v/webup/getModelBuilder";
export const parserSampleJSONZod = (async () => {
const { z } = await import("npm:zod");
const { PromptTemplate } = await import("npm:langchain/prompts");
const { StructuredOutputParser } = await import(
"npm:langchain/output_parsers"
);
// We can use zod to define a schema for the output using the `fromZodSchema` method of `StructuredOutputParser`.
const parser = StructuredOutputParser.fromZodSchema(z.object({
answer: z.string().describe("answer to the user's question"),
sources: z.array(z.string()).describe(
"sources used to answer the question, should be websites.",
),
}));
const formatInstructions = parser.getFormatInstructions();
const prompt = new PromptTemplate({
template:
"Answer the users question as best as possible.\n{format_instructions}\n{question}",
inputVariables: ["question"],
partialVariables: { format_instructions: formatInstructions },
});
const builder = await getModelBuilder();
const model = await builder();
const input = await prompt.format({
question: "What is the capital of France?",
});
const response = await model.call(input);
console.log(input);
return await parser.parse(response);
})();
1
2
3
4
export const zodUnserializable = (async () => {
const z = await import("npm:zod");
return z.object({ test: z.string() });
})();
1
2
3
4
5
6
7
export const zodUnserializable = (async () => {
const z = await import("npm:zod");
await setTimeout(() => {}, 100);
const x = z.object({ test: z.string() }); // type .object after z
return x;
})();
// Forked from @tmcw.zodExample

Usage:

async (rawInput) => {
  const validators = await @devdoshi.validators();
  const inputSchema = validators.inputSchema();
  const input = inputSchema.safeParse(rawInput);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export const validators = async () => {
const { z } = await import("npm:zod");
const inputSchema = () =>
z.object({
ip: z.string().min(1),
port: z.number(),
securityGroupId: z.string().min(1),
aws: z.object({
secretAccessKey: z.string().min(1),
accessKeyId: z.string().min(1),
region: z.string().min(1),
}),
});
return {
inputSchema,
};
};

This val allows you to send me an email at a certain datetime. Fun!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { emailServiceCreate } from "https://esm.town/v/neverstew/emailServiceCreate";
export let scheduleEmail = async (req: express.Request, res: express.Response) => {
const z = await import("npm:zod");
const params = z.object({
sendAt: z.string().datetime(),
content: z.string(),
});
const sendAt = req.query.sendAt;
const content = req.query.content;
const parsedParams = params.safeParse({ sendAt, content });
if (parsedParams.success) {
await emailServiceCreate(parsedParams.data);
res.send(`Scheduled for ${sendAt}`);
}
else {
res.send(JSON.stringify(parsedParams.error.format()));
}
};

Zod

Zod works great out-of-the-box with Val Town: just import it as npm:zod and parse something! Here's an example below.

Tips

  • Right now it's not possible to create a script val that immediately returns a Zod parser because its parsers can't be serialized. It's best to either expose a method that returns a parser, or to create and use the parser in the same val.
1
2
3
4
5
6
export const zodExample = (async () => {
const z = await import("npm:zod");
await setTimeout(() => {}, 100);
const x = z.object({ test: z.string() }); // type .object after z
return x.safeParse({ test: "bar" });
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { fetch } from "https://esm.town/v/std/fetch";
import process from "node:process";
export const runAgent = (async () => {
const { z } = await import("npm:zod");
const { ChatOpenAI } = await import("npm:langchain/chat_models/openai");
const { ChatAnthropic } = await import("npm:langchain/chat_models/anthropic");
const { DynamicTool, Tool, SerpAPI } = await import("npm:langchain/tools");
const { initializeAgentExecutorWithOptions } = await import(
"npm:langchain/agents"
);
const cheerio = await import("npm:cheerio");
const { LLMChain } = await import("npm:langchain/chains");
const { ChatPromptTemplate, HumanMessagePromptTemplate } = await import(
"npm:langchain/prompts"
);
const { StructuredOutputParser, OutputFixingParser } = await import(
"npm:langchain/output_parsers"
);
const model = new ChatOpenAI({
openAIApiKey: process.env.OPENAI_API_KEY,
modelName: "gpt-4",
maxTokens: 2048,
});
const anthropicModel = new ChatAnthropic({
modelName: "claude-v1",
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
temperature: 0,
});
// I had an idea where the agent could scrape individual PR pages, didn't implement
const outputParser = StructuredOutputParser.fromZodSchema(z.array(
z.object({
contributor: z.string().describe(
"The name of the main contributor of the PR",
),
description: z.string().describe(
"A description of what the pull request is for",
),
isFirstContribution: z.boolean().describe(
"Whether it is the contributor's first contribution",
),
pullRequestNumber: z.number().describe("The number of the pull request"),
}).describe("An objects representing a pull request"),
));
const outputFixingParser = OutputFixingParser.fromLLM(model, outputParser);
const tools = [
new DynamicTool({
name: "langchain-release-summarizer",
description:
"Extracts information about the pull requests merged as part of a LangChain release. Takes a GitHub URL as input.",
func: async (input, runManager) => {
const response = await fetch(input.trim());
const pageContent = await response.text();
const $ = cheerio.load(pageContent);
const releaseNotes = $("#repo-content-pjax-container").text();
const prExtractionChain = new LLMChain({
llm: anthropicModel,
prompt: ChatPromptTemplate.fromPromptMessages([
HumanMessagePromptTemplate.fromTemplate(`{query}\n\n{pageContent}`),
]),
outputParser: outputFixingParser,
outputKey: "pullRequests",
});
const summarizationResult = await prExtractionChain.call({
query:
`The following webpage contains the release notes for LangChain, an open source framework for building apps with LLMs.
List all of the pull requests mentioned in the release notes.
Extract the name of the main contributor, a description of the pull request, whether it is their first contribution, and the number of the pull request.
Be extremely verbose!`,
pageContent: releaseNotes,
}, runManager?.getChild());
return JSON.stringify(summarizationResult.pullRequests);
},
}),
new SerpAPI(process.env.SERPAPI_API_KEY, {
location: "Austin,Texas,United States",
hl: "en",
gl: "us",
}),
];
const agent = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "chat-conversational-react-description",
});
const result = await agent.call({
input: `Generate a Twitter thread announcing a new LangChain release.
The release notes are available at this URL: https://github.com/hwchase17/langchainjs/releases/tag/0.0.84.
The release notes include a short description of each merged pull request and the contributor who built the feature.
The thread must start with a header tweet summarizing all the changes in the release.
The thread must contain a tweet for each pull request merged as part of the release that adds a significant feature, and must go into deep detail about what the contribution adds.
If you don't know what something mentioned in the release notes is, look it up with the provided tool so that you can get full context.
Each tweet should also thank the contributor by name, and congratulate them if it is their first contribution and put a medal emoji 🥇 next to their name.
Try to avoid repetitive language in your tweets.
Be extremely verbose and descriptive in your final response.
Below is an example of the format that tweets in the final output Twitter thread should follow. Individual tweets should be separated by a "-----" sequence:
-----Header tweet-----
@LangChainAI 🦜🔗 JS/TS 0.0.83 out with loads of @GoogleAI and PaLM!
💬 Google Vertex AI chat model + embeddings
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { fetch } from "https://esm.town/v/std/fetch";
export const bookReservationOnResy = async ({
reservation,
venue,
auth,
}: {
reservation: {
// How many seats to book
seats: number;
// e.g. 2023-05-01 (interpreted as May 05, 2023)
date: string;
// e.g. [17:00, 22:00]
timeRange: [string, string];
};
venue:
| {
type: "id";
id: string;
}
| {
type: "slug";
slug: string;
city: string;
};
auth: {
email: string;
password: string;
};
}) => {
const { z } = await import("npm:zod");
const RESY_API_URL = "https://api.resy.com";
const RESY_DEFAULT_HEADERS = {
accept: "application/json, text/plain, */*",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
authorization: 'ResyAPI api_key="VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5"',
"x-origin": "https://resy.com",
origin: "https://resy.com/",
dnt: "1",
referer: "https://resy.com/",
"content-type": "application/json",
};
const BookedReservationResponseSchema = z.object({
resy_token: z.string(),
reservation_id: z.number(),
});
const SlotDetailsResponseSchema = z.object({
book_token: z.object({
value: z.string(),
date_expires: z.string(),
}),
});
const FindResponseSchema = z.object({
results: z.object({
venues: z
.object({
venue: z.object({
id: z.object({
resy: z.number(),
}),
name: z.string(),
location: z.object({
time_zone: z.string(),
}),
}),
slots: z
.object({
availability: z.object({
id: z.number(),
}),
config: z.object({
id: z.number(),
// Reservations are booked with the token
token: z.string(),
type: z.string(),
}),
date: z.object({
start: z.string(),
end: z.string(),
}),
payment: z.object({
cancellation_fee: z.number().nullish(),
deposit_fee: z.number().nullish(),
is_paid: z.boolean(),
}),
})
.array(),
})
.array()
.default([]),
}),
});
const PaymentMethodSchema = z.object({
id: z.number(),
type: z.string(),
});
const AuthenticationResponseSchema = z.object({
id: z.number(),
token: z.string(),
1
Next