zackoverflow
minizod
Tiny Zod implementation.
Why
Zod is a dense library, and its module structure (or lack thereof) makes it difficult for bundlers to tree-shake unused modules.
Additionally, using Zod in vals requires the await import
syntax which means having to wrap every schema in a Promise and awaiting it. This is extremely annoying.
So this is a lil-tiny-smol Zod meant for use in vals. A noteworthy use-case is using minizod
to generate tyep-safe API calls to run vals outside of Val Town (such as client-side).
Type-safe API call example
We can use minizod
to create type safe HTTP handlers and generate the corresponding code to call them using Val Town's API, all in a type-safe manner.
First, create a schema for a function. The following example defines a schema for a function that takes a { name: string }
parameter and returns a Promise<{ text: string }>
.
const minizodExampleSchema = () =>
@zackoverflow.minizod().chain((z) =>
z
.func()
.args(z.tuple().item(z.object({ name: z.string() })))
.ret(z.promise().return(z.object({ text: z.string() })))
);
With a function schema, you can then create an implementation and export it as a val:
const minizodExample = @me.minizodExampleSchema().impl(async (
{ name },
) => ({ text: `Hello, ${name}!` })).json()
In the above example, we call .impl()
on a function schema and pass in a closure which implements the actual body of the function. Here, we simply return a greeting to the name passed in.
We can call this val, and it will automatically parse and validate the args we give it:
// Errors at compile time and runtime for us!
const response = @me.minizodExample({ name: 420 })
Alternatively, we can use the .json()
function to use it as a JSON HTTP handler:
const minizodExample = @me.minizodExampleSchema().impl(async (
{ name },
) => ({ text: `Hello, ${name}!` })).json() // <-- this part
We can now call minizodExample
through Val Town's API. Since we defined a schema for it, we know exactly the types of its arguments and return, which means we can generate type-safe code to call the API:
let generatedApiCode =
@zackoverflow.minizodFunctionGenerateTypescript(
// put your username here
"zackoverflow",
"minizodExample",
// put your auth token here
"my auth token",
@me.minizodExampleSchema(),
);
This generates the following the code:
export const fetchMinizodExample = async (
...args: [{ name: string }]
): Promise<Awaited<Promise<{ text: string }>>> =>
await fetch(`https://api.val.town/v1/run/zackoverflow.minizodExample`, {
method: "POST",
body: JSON.stringify({
args: [...args],
}),
headers: {
Authorization: "Bearer ksafajslfkjal;kjf;laksjl;fajsdf",
},
}).then((res) => res.json());
Subscribe to RSS feeds with e-mail notifications
This lets you subscribe to RSS feeds. It checks periodically for any new posts from any of your RSS feed subscriptions, and then sends you an e-mail with the link to the any new posts.
Getting started
1. Generate auth keys
Follow this to get your auth keys, and export your public keys. This will be used to e-mail yourself since @std.email
is preferred over console.email
2. Create a @me.rssEmail
val
You can do that by clicking this link and hitting 'Run'.
Or you can copy-paste this code into a new val:
const rssEmail = "you@youremail.com"
3. Fork this val
Hit 'Fork' on this val and run it. Then you can schedule the val to run every hour or whatever duration you'd like.
4. Add RSS feeds to @me.rssFeeds
If you look at your vals, you should find a new one called rssFeeds
. It should look similar to this:
let rssFeeds = [
"https://cprimozic.net/rss.xml",
"https://matklad.github.io/feed.xml",
"https://journal.stuffwithstuff.com/rss.xml",
"https://lexi-lambda.github.io/feeds/all.rss.xml",
];
This is supposed to be an array containing the links of each RSS feed you'd like to subscribe to (in the form of JS strings).
To add RSS feeds, you can update this val by adding a new string containing the new RSS link.
Resetting the cache
If for any reason you would like to reset the cache, you can clear the keys of rssCache
or use this convenience function to do so.
@zackoverflow.rssResetCache(@me.rssCache)
Lispaas (lisp as a service)
A mini lisp interpreter
How to use:
To execute code:
const result = @zackoverflow.lisp(" (+ 1 2)")
To just parse and return the AST:
const ast = @zackoverflow.lisp("(+ 1 2)", true)
The value returned is the last expression of the program, for example:
const lispResult = @zackoverflow.lisp("(+ 1 2) (+ 400 20)")
console.log('Val', lispResult.val === 420)
Example: Compute Fibonacci sequence
let result = @zackoverflow.lisp(`
(defun fib (x)
(if (<= x 1)
x
(defun impl (i n-1 n-2)
(if (= x i)
(+ n-1 n-2)
(impl (+ i 1) (+ n-1 n-2) n-1)))
(impl 2 1 0)))
(assert-eq 0 (fib 0))
(assert-eq 1 (fib 1))
(assert-eq 1 (fib 2))
(assert-eq 2 (fib 3))
(assert-eq 3 (fib 4))
(assert-eq 5 (fib 5))
(assert-eq 8 (fib 6))
(assert-eq 13 (fib 7))
`);
Documentation
Functions
You can define a function like so:
(defun hello (x) (print x))
Rest/variadic arguments are also supported
(defun variable-amount-of-args (...args) (print args))
(variable-amount-of-args "Hello" "World!")
Lists
Define a list like so:
(let ((my-list (list 1 2 3 4)))
(print my-list)
(print (list-get my-list 1)))
Internally, a list is just a Javascript array. So indexing is O(1), but that does mean cdr
requires copying (vs the linked list implementation).
Plists
Property lists, or records. Internally these are Javascript objects.
Create a plist like so:
(set null :key "Value")
TODO