Skip to content

davidystephenson/kneel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

kneel

Fetch with Zod.

Installation

npm install kneel

Usage

import kneel from 'kneel'
import { z } from 'zod'

const output = await kneel({
  url: 'http://localhost:3000',
  o: z.object({ names: z.array(z.string()) })
})
const uppercaseNames = output.names.map((name) => name.toUpperCase())

const writeOutput = await kneel({
  url: 'http://localhost:3000',
  i: z.object({ name: z.string() }),
  body: { name: 'Zelda Fitzgerald' },
  o: z.object({ count: z.number() })
})
console.log(typeof writeOutput.count) // 'number'

Problem

Making validation functional can require patterns like annotating with unknown.

Without kneel

import { z } from 'zod'

const outputSchema = z.object({ output: z.number() })
type Output = z.infer<typeof schema>
async function read (): Promise<Output> {
  const response = await fetch('http://localhost:3000')
  // Don't use the unvalidated data
  const json: unknown = await response.json()
  return outputSchema.parse(json)
}

const inputSchema = z.object({ input: z.string() })
type Input = z.infer<typeof schema>
async function write (input: Input): Promise<Output> {
  const body = inputSchema.parse(input)
  const response = await fetch('http://localhost:3000', {
    method: 'POST',
    body: JSON.stringify(body)
  })
  const json: unknown = await response.json()
  return outputSchema.parse(json)
}

Solution

kneel requires a Zod schema for a usable response. It also requires a schema if you include a body.

With kneel

import kneel from 'kneel'
import { z } from 'zod'

const outputSchema = z.object({ output: z.number() })
type Output = z.infer<typeof outputSchema>
async function read (input: Input): Promise<Output> {
  return kneel({
    url: 'http://localhost:3000',
    o: outputSchema
  })
}

const inputSchema = z.object({ input: z.string() })
type Input = z.infer<typeof inputSchema>
async function write (input: Input): Promise<Output> {
  return kneel({
    url: 'http://localhost:3000',
    i: inputSchema,
    body: { input: 'increment' },
    o: outputSchema
  })
}

Parameters

Parameter Type Description Required Default Example
url
string
URL to fetch Yes
'http://localhost:3000'
i
ZodSchema
Request body schema No
z.object({
  input: z.string()
})
o
ZodSchema
Response body schema No
z.object({
  output: z.number()
})
body
z.infer<typeof i>
Request body

If i is set

{ input: 'hello' }
method
'GET'
| 'POST'
| 'PUT'
| 'DELETE'
| 'PATCH'
The HTTP method No

'GET', 'POST' if i is set

'PUT'
headers
HeadersInit
Request headers No
{ 'x-api-key': 'token' }
encoding
'application/x-www-form-urlencoded'
| 'multipart/form-data'
| 'text/plain'
| 'application/json'
Request encoding No
'application/json'

if i is set

'text/plain'
debug
boolean
Print request, response, and errors No
false
true

kneel takes a single object with one required parameter:

  • url, a string

You can optionally set:

  • method, a string
  • headers, matching the native fetch headers

Output

You can optionaly set an output schema with:

  • o, a zod schema

If there is an o schema, kneel will parse the response body with .json()and o.parse(), then return it. If there is no o schema, kneel will return void.

Input

You can optionally include a request payload with:

  • i, a zod schema
  • body, a value matching the i schema

The request body will be parsed by the i schema. By default including a body sets the method to 'POST'.

Encoding

By default the request body will be encoded with JSON.stringify() and the Content-Type header will be set to application/json. You can override this with:

  • encoding, which must be either 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain', or 'application/json'.
const inputSchema = z.object({ input: z.string() })
const response = await kneel({
  url: 'http://localhost:3000',
  i: inputSchema,
  body: { input: 'hello' },
  encoding: 'application/x-www-form-urlencoded',
})

The encoding becomes the value of the Content-Type header. application/x-www-form-urlencoded uses new URLSearchParams() to encode the body. multipart/form-data uses new FormData(). text/plain uses String().

KneelProps

You can import the KneelProps type to match the parameters of kneel.

import kneel, { KneelProps } from 'kneel'
import { z, ZodSchema } from 'zod'

const fetchAndLog = async <
  Input,
  InputSchema extends ZodSchema<Input>,
  Output = void
>(props: KneelProps<Input, InputSchema, Output>) => {
  const response = await kneel(props)
  console.log('Response:', response)
}

await fetchAndLog({
  url: 'http://localhost:3000/hello',
  o: z.literal('world')
})
// 'Response: world'

KneelProps takes three generic parameters:

Parameter Extends Description Default Example
Input
Request body
{ input: string }
InputSchema
ZodSchema<Input>
Request body schema
ZodObject<{ name: ZodString }>
Output
Response body
void
{ output: number }

kneelMaker

You can create a kneel function with custom parameter middleware using kneelMaker.

import { kneelMaker } from 'kneel'
import { z } from 'zod'

const kneelHere = kneelMaker({
  make: ({ url, ...rest }) => {
    return { url: `http://localhost:3000${url}`, ...rest }
  }
})
const outputSchema = z.literal('world')
const response = await kneelHere({
  url: '/hello', // Request is sent to 'http://localhost:3000/hello'
  o: outputSchema 
})
console.log(response) // 'world' 
Parameter Type Description Required Default Example
make
<
  Input,
  InputSchema extends ZodSchema<Input>,
  Output = void
> (
  props: KneelProps<Input, InputSchema, Output>
) => KneelProps<Input, InputSchema, Output>
Custom props callback Yes
props => {
  const { url, ...rest } = props
  const urlHere = `http://localhost:3000${url}`
  return { url: urlHere, ...rest }
}
debug
boolean
Print input and output props No
false
true

kneelMaker returns a custom function with the same parameters as kneel. Each time the custom function is called, the props will be passed to the make callback, and the props make returns will be passed to kneel.

Releases

No releases published

Packages

No packages published