alex-tan / postgrest-client / Postgrest.Client

postgrest-client

This library allows you to construct and execute postgrest requests with additional type safety. Here's what Api.People might look like:

import Json.Decode exposing (..)
import Json.Encode as JE
import Postgrest.Client as P


-- Optional, but recommended to have a type that
-- represents your primary key.
type PersonID
    = PersonID Int

-- And a way to unwrap it...
personID : PersonID -> Int
personID (PersonID id) =
    id

-- Define the record you would fetch back from the server.
type alias Person =
    { id : PersonID
    , name : String
    }

-- Define a submission record, without the primary key.
type alias PersonSubmission =
    { name : String
    }

-- Decoders are written using Json.Decode
decodeUnit : Decoder Person
decodeUnit =
    map2 Person
        (field "id" <| map PersonID int)
        (field "name" string)

-- Encoders are written using Json.Encode
encode : PersonSubmission -> JE.Value
encode person =
    JE.object
        [ ( "name", JE.string person.name )
        ]

-- Tell Postgrest.Client the column name of your primary key and
-- how to convert it into a parameter.
primaryKey : P.PrimaryKey PersonID
primaryKey =
    P.primaryKey ( "id", P.int << personID )

-- Tell Postgrest.Client the URL of the postgrest endpoint and how
-- to decode an individual record from it. Postgrest will combine
-- the decoder with a list decoder automatically when necessary.
endpoint : P.Endpoint Person
endpoint =
    P.endpoint "/people" decodeUnit

-- Fetch many records. If you want to specify parameters use `setParams`
getMany : P.Request (List Person)
getMany =
    P.getMany endpoint

-- Delete by primary key. This is a convenience function that reduces
-- the likelihood that you delete more than one record by specifying incorrect
-- parameters.
delete : PersonID -> P.Request PersonID
delete =
    P.deleteByPrimaryKey endpoint primaryKey

-- Create a record.
post : PersonSubmission -> P.Request Person
post =
    P.postOne endpoint << encode

Here's how you could use it:

import Api.People as People
import Postgrest.Client as P

jwt : P.JWT
jwt =
    P.jwt "abcdefghijklmnopqrstuvwxyz1234"

type Msg
    = PersonCreated (Result P.Error Person)
    | PeopleLoaded (Result P.Error (List Person))
    | PersonDeleted (Result P.Error PersonID)

toCmd =
    P.toCmd jwt

cmdExamples =
    [ People.post
        { name = "Yasujirō Ozu"
        }
        |> P.toCmd jwt PersonCreated
    , People.getMany
        [ P.order [ P.asc "name" ], P.limit 10 ]
        |> toCmd PeopleLoaded
    , Person.delete personID
        |> toCmd PersonDeleted
    ]

Request Construction and Modification


type alias Endpoint a =
Postgrest.Internal.Endpoint.Endpoint a

Think of an Endpoint as a combination between a base url like /schools and an elm/json Decoder. The endpoint can be passed to other functions in this library, sometimes along with PrimaryKey to make constructing certain types of requests easier.


type alias Request r =
Postgrest.Internal.Requests.Request r

Request can be used with toCmd and toTask to make a request.

endpoint : String -> Json.Decode.Decoder a -> Endpoint a

The simplest way to define an endpoint. You provide it the URL and a decoder. It can then be used to quickly construct POST, GET, PATCH, and DELETE requests. The decoder provided should just be a decoder of the record itself, not a decoder of an object inside an array.

decodePerson : Decoder Person
decodePerson =
    map2 Person
        (field "first_name" string)
        (field "last_name" string)

peopleEndpoint : P.Endpoint Person
peopleEndpoint =
    P.endpoint "/rest/people" decodePerson

customEndpoint : String -> Json.Decode.Decoder a -> { defaultSelect : Maybe (List Selectable), defaultOrder : Maybe (List ColumnOrder) } -> Endpoint a

Define an endpoint with extra options. To quickly construct POST, GET, PATCH, and DELETE requests. defaultOrder and defaultSelect can be overriden by using setParams once a request is constructed.

peopleEndpoint : P.Endpoint Person
peopleEndpoint =
    P.endpoint "/rest/people"
        decodePerson
        { defaultSelect = Just [ P.attribute "id", P.attribute "name" ]
        , defaultOrder = Just [ P.asc "name" ]
        }

Endpoint a

getMany : Endpoint a -> Request (List a)

Used to GET multiple records from the provided endpoint. Converts your endpoint decoder into (list decoder) to decode multiple records.

endpoint : P.Endpoint Person
endpoint =
    P.endpoint "/people" decodePerson

getAll : P.Request (List Person)
getAll =
    P.getMany endpoint
        |> P.setParams [ P.limit 20 ]

getOne : Endpoint a -> Request a

Used to GET a single record. Converts your endpoint decoder into (index 0 decoder) to extract it from postgrest's JSON array response and sets limit=1 in the parameters. If you're requesting by primary key see getOneByPrimaryKey.

endpoint : P.Endpoint Person
endpoint =
    P.endpoint "/people" decodePerson

getOnePersonByName : String -> P.Request Person
getOnePersonByName name =
    P.getOne endpoint
        |> P.setParams [ P.param "name" <| P.eq name ]

postOne : Endpoint a -> Json.Encode.Value -> Request a

Used to create a single record at the endpoint you provide and an encoded JSON value.

endpoint : P.Endpoint Person
endpoint =
    P.endpoint "/people" decodePerson

encodePerson : Person -> JE.Value
encodePerson p =
    object
        [ ( "first_name", JE.string p.firstName )
        , ( "last_name", JE.string p.lastName )
        ]

post : PersonForm -> P.Request Person
post submission =
    P.postOne endpoint (encodePerson submission)

getByPrimaryKey : Endpoint a -> PrimaryKey p -> p -> Request a

Used to GET a single record by primary key. This is the recommended way to do a singular GET request assuming your table has a primary key.

endpoint : P.Endpoint Person
endpoint =
    P.endpoint "/people" decodePerson

primaryKey : P.PrimaryKey Int
primaryKey =
    P.primaryKey ( "id", P.int )

getByPrimaryKey : Int -> P.Request Person
getByPrimaryKey =
    P.getByPrimaryKey endpoint primaryKey

patchByPrimaryKey : Endpoint a -> PrimaryKey p -> p -> Json.Encode.Value -> Request a

Used to PATCH a single record by primary key. This is the recommended way to do a PATCH request assuming your table has a primary key. The decoder will decode the record after it's been patched if the request is successful.

endpoint : P.Endpoint Person
endpoint =
    P.endpoint "/people" decodePerson

primaryKey =
    P.primaryKey ( "id", P.int )

updatePerson : PersonForm -> Int -> P.Request Person
updatePerson submission id =
    P.patchByPrimaryKey endpoint primaryKey (encodeSubmission submission)

-- Would create a request to patch to "/people?id=eq.3"
updatePerson form 3

deleteByPrimaryKey : Endpoint a -> PrimaryKey p -> p -> Request p

Used to DELETE a single record by primary key. This is the recommended way to do a DELETE request if your table has a primary key. The decoder will decode the record after it's been patched if the request is successful.

endpoint : P.Endpoint Person
endpoint =
    P.endpoint "/people" decodePerson

primaryKey =
    P.primaryKey ( "id", P.int )

delete : Int -> P.Request Int
delete =
    P.deleteByPrimaryKey endpoint primaryKey

-- Would create a request to DELETE to "/people?id=eq.3"
-- and the success value would be the ID passed in.
-- So your Msg would look like:
-- | DeleteSuccess (Result P.Error Int)
delete 3

Request Options

setParams : Params -> Request a -> Request a

Used to set the parameters of your request.

getPeople : P.Request (List Person)
getPeople =
    P.getMany endpoint
        |> P.setParams
            [ P.order [ P.asc "first_name" ]
            , P.limit 20
            ]

setTimeout : Basics.Float -> Request a -> Request a

Sets the timeout of your request. The behaviour is the same of that in the elm/http package.

Generic Requests

get : String -> { params : Params, decoder : Json.Decode.Decoder a } -> Request a

The most basic way to make a get request.

post : String -> { params : Params, decoder : Json.Decode.Decoder a, body : Json.Encode.Value } -> Request a

The most basic way to make a post request.

unsafePatch : String -> { body : Json.Encode.Value, decoder : Json.Decode.Decoder a, params : Params } -> Request a

Titled unsafe because if you provide incorrect or no parameters it will make a PATCH request to all resources the requesting user has access to at that endpoint. Use with caution. See Block Full-Table Operations.

unsafeDelete : String -> UnsafeDeleteOptions a -> Request a

Titled unsafe because if you provide incorrect or no parameters it will make a DELETE request to all resources the requesting user has access to at that endpoint. Use with caution. See Block Full-Table Operations.

Request Authentication


type alias JWT =
Postgrest.Internal.JWT.JWT

The type used to store the JWT string.

jwt : String -> JWT

Pass the jwt string into this function to make it a JWT. This is used with toCmd and toTask to make requests.

myJWT =
    P.jwt "abcdef"

jwtString : JWT -> String

If you've already created a JWT with jwt you can extract the original string with this function.

myJWT = P.jwt "abcdef"

jwtString myJWT -- "abcdef"

Execution

toCmd : JWT -> (Result Error a -> msg) -> Request a -> Platform.Cmd.Cmd msg

Takes a JWT, Msg and a Request and turns it into a Cmd.

toTask : JWT -> Request a -> Task Error a

Takes a JWT and a Request and turns it into a Task.

Primary Keys


type PrimaryKey pk

Can be used together with endpoint to make request construction easier. See primaryKey and endpoint.

primaryKey : ( String, pk -> Value ) -> PrimaryKey pk

Used to construct a primary key made up of one column. Takes a tuple of the column name of your primary key and a function to convert your elm representation of that primary key into a postgrest parameter.

primaryKey : P.PrimaryKey Int
primaryKey =
    primaryKey ( "id", P.int )

is the simplest example. If you have custom type to represent your primary key you could do this:

type ID
    = ID Int

idToInt : ID -> Int
idToInt (ID id) =
    id

primaryKey : P.PrimaryKey ID
primaryKey =
    P.primaryKey ( "id", P.int << idToInt )

primaryKey2 : ( String, pk -> Value ) -> ( String, pk -> Value ) -> PrimaryKey pk

Used to construct a primary key made up of two columns. Takes two tuples, each with a column name and a function to convert your elm representation of that primary key into a postgrest parameter.

primaryKey2 ( "id", P.int )

is the simplest example. If you have custom type to represent your primary key you could do this:

type alias ParentID =
    Int

type alias Category =
    String

type alias MyPrimaryKey =
    ( ParentID, Category )

primaryKey : P.PrimaryKey MyPrimaryKey
primaryKey =
    P.primaryKey2
        ( "parent_id", P.int << Tuple.first )
        ( "category", P.string << Tuple.second )

primaryKey3 : ( String, pk -> Value ) -> ( String, pk -> Value ) -> ( String, pk -> Value ) -> PrimaryKey pk

Used to construct primary keys that are made up of three columns. See primaryKey2 for a similar example of how this could be used.

Errors


type Error
    = Timeout
    | BadUrl String
    | NetworkError
    | BadStatus Basics.Int String PostgrestErrorJSON
    | BadBody String

Error Looks a lot like Http.Error except BadStatus includes a second argument, PostgrestErrorJSON with any details that postgrest might have given us about a failed request.


type alias PostgrestErrorJSON =
{ message : Maybe String
, details : Maybe String
, hint : Maybe String
, code : Maybe String 
}

Contains any details postgrest might have given us about a failed request.

toHttpError : Error -> Http.Error

Converts the custom HTTP error used by this package into an elm/http Error. This can be useful if you're using Task.map2, Task.map3, etc... and each of the tasks need to have the same error type.

URL Parameter Construction


type alias Param =
Postgrest.Internal.Params.Param

An individual postgrest parameter.


type alias Params =
List Param

A list of Param.


type alias Selectable =
Postgrest.Internal.Params.Selectable

A type representing which attributes and resources you want to select. It also contains parameters that target nested resources.


type alias ColumnOrder =
Postgrest.Internal.Params.ColumnOrder

A type to specify whether you want an order to be ascending or descending, and optionally whether you want nulls to be first or last.


type alias Value =
Postgrest.Internal.Params.Value

Type that can be represented in the queries: strings, ints and lists.


type alias Operator =
Postgrest.Internal.Params.Operator

A type that represents the operator of a query. In name=eq.John the operator would be the =.

Select

select : List Selectable -> Param

A constructor for the select parameter.

P.select
    [ P.attribute "id"
    , P.attribute "title"
    , P.resource "user" <|
        P.attributes
            [ "email"
            , "name"
            ]
    ]

allAttributes : List Selectable

When you want to select all attributes. This is only useful when used to select attributes of a resource or override default parameters in another function since postgrest returns all attributes by default.

attribute : String -> Selectable

When you want to select a certain column.

attributes : List String -> List Selectable

Shorthand for attributes, when you don't need to specify nested resources:

-- Short version
attributes [ "id" "name" ]

-- Long version
[ attribute "id"
, attribute "name"
]

resource : String -> List Selectable -> Selectable

When you want to select a nested resource with no special parameters for the nested resources. If you do want to specify parameters, see resourceWithParams.

resourceWithParams : String -> Params -> List Selectable -> Selectable

When you want to select a nested resource with special praameters.

[ P.select
    [ P.resource "sites"
        [ P.resourceWithParams "streams"
            [ P.order [ P.asc "name" ]
            ]
            allAttributes
        ]
    ]
]
    |> toQueryString

-- select=sites(streams(*))&sites.streams.order=name.asc

Converting/Combining Parameters

combineParams : Params -> Params -> Params

Takes a default set of params and a custom set of params and prefers the second set. Useful when you're constructing reusable functions that make similar queries.

concatParams : List Params -> Params

Takes a list of Params and combines them, preferring the last sets first.

normalizeParams : Params -> List ( String, String )

Takes Params and returns the parameters as a list of (Key, Value) strings.

toQueryString : Params -> String

Takes Params and returns a query string such as foo=eq.bar&baz=is.true

Param

param : String -> Operator -> Param

A constructor for an individual postgrest parameter.

param "name" (eq (string "John"))

or : List Param -> Param

Join multiple conditions together with or.

[ or
    [ param "age" <| gte <| int 14
    , param "age" <| lte <| int 18
    ]
]
|> toQueryString

-- or=(age.gte.14,age.lte.18)

and : List Param -> Param

Join multiple conditions together with and.

[ and
    [ param "grade" <| gte <| int 90
    , param "student" <| true
    , or
        [ param "age" <| gte <| int 14
        , param "age" <| null
        ]
    ]
]
|> toQueryString

-- and=(grade.gte.90,student.is.true,or(age.gte.14,age.is.null))

nestedParam : List String -> Param -> Param

When you want to specify an operator for a nested resource manually. It is recommended to use resourceWithParams though.

[ select
    [ attribute "*"
    , resource "actors" allAttributes
    ]
, nestedParam [ "actors" ] <| limit 10
, nestedParam [ "actors" ] <| offset 2
]
|> toQueryString
-- "select=*,actors(*)&actors.limit=10&actors.offset=2"

Operators

eq : Value -> Operator

Used to indicate you need a column to be equal to a certain value.

gt : Value -> Operator

Used to indicate you need a column to be greater than a certain value.

gte : Value -> Operator

Used to indicate you need a column to be greater than or equal than a certain value.

inList : (a -> Value) -> List a -> Operator

Used to indicate you need a column to be within a certain list of values.

param "name" <| inList string [ "Chico", "Harpo", "Groucho" ]

-- name=in.(\"Chico\",\"Harpo\",\"Groucho\")"

limit : Basics.Int -> Param

Limit the number of records that can be returned.

limit 10

lt : Value -> Operator

Used to indicate you need a column to be less than a certain value.

lte : Value -> Operator

Used to indicate you need a column to be less than or equal than a certain value.

neq : Value -> Operator

Used to indicate you need a column to be not equal to a certain value.

not : Operator -> Operator

Negate a condition.

[ param "my_tsv" <| not <| phfts (Just "english") "The Fat Cats"
]
|> toQueryString
-- my_tsv=not.phfts(english).The%20Fat%20Cats

true : Operator

When you need a column value to be true

-- foo=is.true
[ P.param "foo" P.true ]
    |> toQueryString

false : Operator

When you need a column value to be false

-- foo=is.false
[ P.param "foo" P.false ]
    |> toQueryString

null : Operator

Query, specifying that a value should be null.

param "age" <| null

value : Value -> Operator

When you don't want to use a specific type after the equals sign in the query, you can use value to set anything you want.

offset : Basics.Int -> Param

Specify the offset in the query.

offset 10

ilike : String -> Operator

ILIKE operator (use * in place of %)

param "text" <| ilike "foo*bar"

like : String -> Operator

LIKE operator (use * in place of %)

param "text" <| like "foo*bar"

contains : List Value -> Operator

Use the cs operator.

param "tag" <| contains <| List.map string [ "Chico", "Harpo", "Groucho" ]

-- tag=cs.(\"Chico\",\"Harpo\",\"Groucho\")"

containedIn : List Value -> Operator

Use the cd operator.

param "tag" <| containedIn <| List.map string [ "Chico", "Harpo", "Groucho" ]

-- tag=cd.(\"Chico\",\"Harpo\",\"Groucho\")"

Values

string : String -> Value

Normalize a string into a postgrest value.

int : Basics.Int -> Value

Normalize an int into a postgrest value.

list : List Value -> Value

This is available if you need it, but more likely you'll want to use inList.

Order

order : List ColumnOrder -> Param

A constructor for the limit parameter.

order (asc "name")

order (desc "name")

asc : String -> ColumnOrder

Used in combination with order to sort results ascending.

P.order [ P.asc "name" ]

desc : String -> ColumnOrder

Used in combination with order to sort results descending.

P.order [ P.desc "name" ]

nullsfirst : ColumnOrder -> ColumnOrder

Sort so that nulls will come first.

order [ asc "age" |> nullsfirst ]

nullslast : ColumnOrder -> ColumnOrder

Sort so that nulls will come last.

order [ asc "age" |> nullslast ]

Full-Text Search

plfts : Maybe Postgrest.Internal.Params.Language -> String -> Operator

Full-Text Search using plainto_tsquery

phfts : Maybe Postgrest.Internal.Params.Language -> String -> Operator

Full-Text Search using phraseto_tsquery

fts : Maybe Postgrest.Internal.Params.Language -> String -> Operator

Full-Text Search using to_tsquery

[ param "my_tsv" <| fts (Just "french") "amusant" ]
    |> toQueryString

"my_tsv=fts(french).amusant"