feat: init, basic serde

This commit is contained in:
orion 2024-03-26 21:59:28 -05:00
parent 6fadf32a68
commit e2eb753317
Signed by: orion
GPG Key ID: 6D4165AE4C928719
16 changed files with 1464 additions and 24 deletions

View File

@ -1,3 +1,2 @@
bun 1.0.11
purescript 0.15.12
nodejs 20.9.0
bun 1.0.35
purescript 0.15.15

BIN
bun.lockb

Binary file not shown.

View File

@ -8,11 +8,12 @@
},
"devDependencies": {
"bun-types": "1.0.11",
"postgres-range": "^1.1.4",
"purs-tidy": "^0.10.0",
"spago": "next"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {}
"dependencies": { "pg-types": "^4.0.2" }
}

877
spago.lock Normal file
View File

@ -0,0 +1,877 @@
workspace:
packages:
pg:
path: ./
dependencies:
- bifunctors
- control
- datetime
- effect
- exceptions
- foldable-traversable
- foreign
- lists
- maybe
- newtype
- node-buffer
- precise-datetime
- prelude
- transformers
- unsafe-coerce
test_dependencies:
- quickcheck
- spec
- spec-quickcheck
build_plan:
- aff
- ansi
- arraybuffer-types
- arrays
- avar
- bifunctors
- catenable-lists
- console
- const
- contravariant
- control
- datetime
- decimals
- distributive
- effect
- either
- enums
- exceptions
- exists
- fixed-points
- foldable-traversable
- foreign
- fork
- formatters
- free
- functions
- functors
- gen
- identity
- integers
- invariant
- js-date
- lazy
- lcg
- lists
- maybe
- mmorph
- newtype
- node-buffer
- nonempty
- now
- nullable
- numbers
- ordered-collections
- orders
- parallel
- parsing
- partial
- pipes
- precise-datetime
- prelude
- profunctor
- quickcheck
- random
- record
- refs
- safe-coerce
- spec
- spec-quickcheck
- st
- strings
- tailrec
- transformers
- tuples
- type-equality
- unfoldable
- unicode
- unsafe-coerce
extra_packages: {}
packages:
aff:
type: registry
version: 7.1.0
integrity: sha256-7hOC6uQO9XBAI5FD8F33ChLjFAiZVfd4BJMqlMh7TNU=
dependencies:
- arrays
- bifunctors
- control
- datetime
- effect
- either
- exceptions
- foldable-traversable
- functions
- maybe
- newtype
- parallel
- prelude
- refs
- tailrec
- transformers
- unsafe-coerce
ansi:
type: registry
version: 7.0.0
integrity: sha256-ZMB6HD+q9CXvn9fRCmJ8dvuDrOVHcjombL3oNOerVnE=
dependencies:
- foldable-traversable
- lists
- strings
arraybuffer-types:
type: registry
version: 3.0.2
integrity: sha256-mQKokysYVkooS4uXbO+yovmV/s8b138Ws3zQvOwIHRA=
dependencies: []
arrays:
type: registry
version: 7.3.0
integrity: sha256-tmcklBlc/muUtUfr9RapdCPwnlQeB3aSrC4dK85gQlc=
dependencies:
- bifunctors
- control
- foldable-traversable
- functions
- maybe
- nonempty
- partial
- prelude
- safe-coerce
- st
- tailrec
- tuples
- unfoldable
- unsafe-coerce
avar:
type: registry
version: 5.0.0
integrity: sha256-e7hf0x4hEpcygXP0LtvfvAQ49Bbj2aWtZT3gqM///0A=
dependencies:
- aff
- effect
- either
- exceptions
- functions
- maybe
bifunctors:
type: registry
version: 6.0.0
integrity: sha256-/gZwC9YhNxZNQpnHa5BIYerCGM2jeX9ukZiEvYxm5Nw=
dependencies:
- const
- either
- newtype
- prelude
- tuples
catenable-lists:
type: registry
version: 7.0.0
integrity: sha256-76vYENhwF4BWTBsjeLuErCH2jqVT4M3R1HX+4RwSftA=
dependencies:
- control
- foldable-traversable
- lists
- maybe
- prelude
- tuples
- unfoldable
console:
type: registry
version: 6.1.0
integrity: sha256-CxmAzjgyuGDmt9FZW51VhV6rBPwR6o0YeKUzA9rSzcM=
dependencies:
- effect
- prelude
const:
type: registry
version: 6.0.0
integrity: sha256-tNrxDW8D8H4jdHE2HiPzpLy08zkzJMmGHdRqt5BQuTc=
dependencies:
- invariant
- newtype
- prelude
contravariant:
type: registry
version: 6.0.0
integrity: sha256-TP+ooAp3vvmdjfQsQJSichF5B4BPDHp3wAJoWchip6c=
dependencies:
- const
- either
- newtype
- prelude
- tuples
control:
type: registry
version: 6.0.0
integrity: sha256-sH7Pg9E96JCPF9PIA6oQ8+BjTyO/BH1ZuE/bOcyj4Jk=
dependencies:
- newtype
- prelude
datetime:
type: registry
version: 6.1.0
integrity: sha256-g/5X5BBegQWLpI9IWD+sY6mcaYpzzlW5lz5NBzaMtyI=
dependencies:
- bifunctors
- control
- either
- enums
- foldable-traversable
- functions
- gen
- integers
- lists
- maybe
- newtype
- numbers
- ordered-collections
- partial
- prelude
- tuples
decimals:
type: registry
version: 7.1.0
integrity: sha256-DriR6lPEfFpjVv7e4JAQkr3ZLf0h17Qg2cAIrhxWV7w=
dependencies:
- maybe
distributive:
type: registry
version: 6.0.0
integrity: sha256-HTDdmEnzigMl+02SJB88j+gAXDx9VKsbvR4MJGDPbOQ=
dependencies:
- identity
- newtype
- prelude
- tuples
- type-equality
effect:
type: registry
version: 4.0.0
integrity: sha256-eBtZu+HZcMa5HilvI6kaDyVX3ji8p0W9MGKy2K4T6+M=
dependencies:
- prelude
either:
type: registry
version: 6.1.0
integrity: sha256-6hgTPisnMWVwQivOu2PKYcH8uqjEOOqDyaDQVUchTpY=
dependencies:
- control
- invariant
- maybe
- prelude
enums:
type: registry
version: 6.0.1
integrity: sha256-HWaD73JFLorc4A6trKIRUeDMdzE+GpkJaEOM1nTNkC8=
dependencies:
- control
- either
- gen
- maybe
- newtype
- nonempty
- partial
- prelude
- tuples
- unfoldable
exceptions:
type: registry
version: 6.0.0
integrity: sha256-y/xTAEIZIARCE+50/u1di0ncebJ+CIwNOLswyOWzMTw=
dependencies:
- effect
- either
- maybe
- prelude
exists:
type: registry
version: 6.0.0
integrity: sha256-A0JQHpTfo1dNOj9U5/Fd3xndlRSE0g2IQWOGor2yXn8=
dependencies:
- unsafe-coerce
fixed-points:
type: registry
version: 7.0.0
integrity: sha256-hTl5fzeG4mzAOFzEzAeNH7kJvJgYCH7x3v2NdX9pOE4=
dependencies:
- exists
- newtype
- prelude
- transformers
foldable-traversable:
type: registry
version: 6.0.0
integrity: sha256-fLeqRYM4jUrZD5H4WqcwUgzU7XfYkzO4zhgtNc3jcWM=
dependencies:
- bifunctors
- const
- control
- either
- functors
- identity
- maybe
- newtype
- orders
- prelude
- tuples
foreign:
type: registry
version: 7.0.0
integrity: sha256-1ORiqoS3HW+qfwSZAppHPWy4/6AQysxZ2t29jcdUMNA=
dependencies:
- either
- functions
- identity
- integers
- lists
- maybe
- prelude
- strings
- transformers
fork:
type: registry
version: 6.0.0
integrity: sha256-X7u0SuCvFbLbzuNEKLBNuWjmcroqMqit4xEzpQwAP7E=
dependencies:
- aff
formatters:
type: registry
version: 7.0.0
integrity: sha256-5JaC9d2p0xoqJWjWxlHH19R4iJwFTBr4j7SlYcLgicE=
dependencies:
- datetime
- fixed-points
- lists
- numbers
- parsing
- prelude
- transformers
free:
type: registry
version: 7.1.0
integrity: sha256-JAumgEsGSzJCNLD8AaFvuX7CpqS5yruCngi6yI7+V5k=
dependencies:
- catenable-lists
- control
- distributive
- either
- exists
- foldable-traversable
- invariant
- lazy
- maybe
- prelude
- tailrec
- transformers
- tuples
- unsafe-coerce
functions:
type: registry
version: 6.0.0
integrity: sha256-adMyJNEnhGde2unHHAP79gPtlNjNqzgLB8arEOn9hLI=
dependencies:
- prelude
functors:
type: registry
version: 5.0.0
integrity: sha256-zfPWWYisbD84MqwpJSZFlvM6v86McM68ob8p9s27ywU=
dependencies:
- bifunctors
- const
- contravariant
- control
- distributive
- either
- invariant
- maybe
- newtype
- prelude
- profunctor
- tuples
- unsafe-coerce
gen:
type: registry
version: 4.0.0
integrity: sha256-f7yzAXWwr+xnaqEOcvyO3ezKdoes8+WXWdXIHDBCAPI=
dependencies:
- either
- foldable-traversable
- identity
- maybe
- newtype
- nonempty
- prelude
- tailrec
- tuples
- unfoldable
identity:
type: registry
version: 6.0.0
integrity: sha256-4wY0XZbAksjY6UAg99WkuKyJlQlWAfTi2ssadH0wVMY=
dependencies:
- control
- invariant
- newtype
- prelude
integers:
type: registry
version: 6.0.0
integrity: sha256-sf+sK26R1hzwl3NhXR7WAu9zCDjQnfoXwcyGoseX158=
dependencies:
- maybe
- numbers
- prelude
invariant:
type: registry
version: 6.0.0
integrity: sha256-RGWWyYrz0Hs1KjPDA+87Kia67ZFBhfJ5lMGOMCEFoLo=
dependencies:
- control
- prelude
js-date:
type: registry
version: 8.0.0
integrity: sha256-6TVF4DWg5JL+jRAsoMssYw8rgOVALMUHT1CuNZt8NRo=
dependencies:
- datetime
- effect
- exceptions
- foreign
- integers
- now
lazy:
type: registry
version: 6.0.0
integrity: sha256-lMsfFOnlqfe4KzRRiW8ot5ge6HtcU3Eyh2XkXcP5IgU=
dependencies:
- control
- foldable-traversable
- invariant
- prelude
lcg:
type: registry
version: 4.0.0
integrity: sha256-h7ME5cthLfbgJOJdsZcSfFpwXsx4rf8YmhebU+3iSYg=
dependencies:
- effect
- integers
- maybe
- partial
- prelude
- random
lists:
type: registry
version: 7.0.0
integrity: sha256-EKF15qYqucuXP2lT/xPxhqy58f0FFT6KHdIB/yBOayI=
dependencies:
- bifunctors
- control
- foldable-traversable
- lazy
- maybe
- newtype
- nonempty
- partial
- prelude
- tailrec
- tuples
- unfoldable
maybe:
type: registry
version: 6.0.0
integrity: sha256-5cCIb0wPwbat2PRkQhUeZO0jcAmf8jCt2qE0wbC3v2Q=
dependencies:
- control
- invariant
- newtype
- prelude
mmorph:
type: registry
version: 7.0.0
integrity: sha256-urZlZNNqGeQFe5D/ClHlR8QgGBNHTMFPtJ5S5IpflTQ=
dependencies:
- free
- functors
- transformers
newtype:
type: registry
version: 5.0.0
integrity: sha256-gdrQu8oGe9eZE6L3wOI8ql/igOg+zEGB5ITh2g+uttw=
dependencies:
- prelude
- safe-coerce
node-buffer:
type: registry
version: 9.0.0
integrity: sha256-PWE2DJ5ruBLCmeA/fUiuySEFmUJ/VuRfyrnCuVZBlu4=
dependencies:
- arraybuffer-types
- effect
- maybe
- nullable
- st
- unsafe-coerce
nonempty:
type: registry
version: 7.0.0
integrity: sha256-54ablJZUHGvvlTJzi3oXyPCuvY6zsrWJuH/dMJ/MFLs=
dependencies:
- control
- foldable-traversable
- maybe
- prelude
- tuples
- unfoldable
now:
type: registry
version: 6.0.0
integrity: sha256-xZ7x37ZMREfs6GCDw/h+FaKHV/3sPWmtqBZRGTxybQY=
dependencies:
- datetime
- effect
nullable:
type: registry
version: 6.0.0
integrity: sha256-yiGBVl3AD+Guy4kNWWeN+zl1gCiJK+oeIFtZtPCw4+o=
dependencies:
- effect
- functions
- maybe
numbers:
type: registry
version: 9.0.1
integrity: sha256-/9M6aeMDBdB4cwYDeJvLFprAHZ49EbtKQLIJsneXLIk=
dependencies:
- functions
- maybe
ordered-collections:
type: registry
version: 3.2.0
integrity: sha256-o9jqsj5rpJmMdoe/zyufWHFjYYFTTsJpgcuCnqCO6PM=
dependencies:
- arrays
- foldable-traversable
- gen
- lists
- maybe
- partial
- prelude
- st
- tailrec
- tuples
- unfoldable
orders:
type: registry
version: 6.0.0
integrity: sha256-nBA0g3/ai0euH8q9pSbGqk53W2q6agm/dECZTHcoink=
dependencies:
- newtype
- prelude
parallel:
type: registry
version: 6.0.0
integrity: sha256-VJbkGD0rAKX+NUEeBJbYJ78bEKaZbgow+QwQEfPB6ko=
dependencies:
- control
- effect
- either
- foldable-traversable
- functors
- maybe
- newtype
- prelude
- profunctor
- refs
- transformers
parsing:
type: registry
version: 10.2.0
integrity: sha256-ZDIdMFAKkst57x6BVa1aUWJnS8smoZnXsZ339Aq1mPA=
dependencies:
- arrays
- control
- effect
- either
- enums
- foldable-traversable
- functions
- identity
- integers
- lazy
- lists
- maybe
- newtype
- nullable
- numbers
- partial
- prelude
- st
- strings
- tailrec
- transformers
- tuples
- unfoldable
- unicode
- unsafe-coerce
partial:
type: registry
version: 4.0.0
integrity: sha256-fwXerld6Xw1VkReh8yeQsdtLVrjfGiVuC5bA1Wyo/J4=
dependencies: []
pipes:
type: registry
version: 8.0.0
integrity: sha256-kvfqGM4cPA/wCcBHbp5psouFw5dZGvku2462x7ZBwSY=
dependencies:
- aff
- lists
- mmorph
- prelude
- tailrec
- transformers
- tuples
precise-datetime:
type: registry
version: 7.0.0
integrity: sha256-F7tzZ7++Ihtg3xjumzwaHQvGQg61UtEAe5MWeOlTzRY=
dependencies:
- arrays
- datetime
- decimals
- either
- enums
- foldable-traversable
- formatters
- integers
- js-date
- lists
- maybe
- newtype
- numbers
- prelude
- strings
- tuples
- unicode
prelude:
type: registry
version: 6.0.1
integrity: sha256-o8p6SLYmVPqzXZhQFd2hGAWEwBoXl1swxLG/scpJ0V0=
dependencies: []
profunctor:
type: registry
version: 6.0.1
integrity: sha256-E58hSYdJvF2Qjf9dnWLPlJKh2Z2fLfFLkQoYi16vsFk=
dependencies:
- control
- distributive
- either
- exists
- invariant
- newtype
- prelude
- tuples
quickcheck:
type: registry
version: 8.0.1
integrity: sha256-ZvpccKQCvgslTXZCNmpYW4bUsFzhZd/kQUr2WmxFTGY=
dependencies:
- arrays
- console
- control
- effect
- either
- enums
- exceptions
- foldable-traversable
- gen
- identity
- integers
- lazy
- lcg
- lists
- maybe
- newtype
- nonempty
- numbers
- partial
- prelude
- record
- st
- strings
- tailrec
- transformers
- tuples
- unfoldable
random:
type: registry
version: 6.0.0
integrity: sha256-CJ611a35MPCE7XQMp0rdC6MCn76znlhisiCRgboAG+Q=
dependencies:
- effect
- integers
record:
type: registry
version: 4.0.0
integrity: sha256-Za5U85bTRJEfGK5Sk4hM41oXy84YQI0I8TL3WUn1Qzg=
dependencies:
- functions
- prelude
- unsafe-coerce
refs:
type: registry
version: 6.0.0
integrity: sha256-Vgwne7jIbD3ZMoLNNETLT8Litw6lIYo3MfYNdtYWj9s=
dependencies:
- effect
- prelude
safe-coerce:
type: registry
version: 2.0.0
integrity: sha256-a1ibQkiUcbODbLE/WAq7Ttbbh9ex+x33VCQ7GngKudU=
dependencies:
- unsafe-coerce
spec:
type: registry
version: 7.6.0
integrity: sha256-+merGdQbL9zWONbnt8S8J9afGJ59MQqGtS0qSd3yu4I=
dependencies:
- aff
- ansi
- arrays
- avar
- bifunctors
- control
- datetime
- effect
- either
- exceptions
- foldable-traversable
- fork
- identity
- integers
- lists
- maybe
- newtype
- now
- ordered-collections
- parallel
- pipes
- prelude
- refs
- strings
- tailrec
- transformers
- tuples
spec-quickcheck:
type: registry
version: 5.0.0
integrity: sha256-iE0iThqZCuDGe3pwg5RvqcL8E5cRQ4txDuloCclOsCs=
dependencies:
- aff
- prelude
- quickcheck
- random
- spec
st:
type: registry
version: 6.2.0
integrity: sha256-z9X0WsOUlPwNx9GlCC+YccCyz8MejC8Wb0C4+9fiBRY=
dependencies:
- partial
- prelude
- tailrec
- unsafe-coerce
strings:
type: registry
version: 6.0.1
integrity: sha256-WssD3DbX4OPzxSdjvRMX0yvc9+pS7n5gyPv5I2Trb7k=
dependencies:
- arrays
- control
- either
- enums
- foldable-traversable
- gen
- integers
- maybe
- newtype
- nonempty
- partial
- prelude
- tailrec
- tuples
- unfoldable
- unsafe-coerce
tailrec:
type: registry
version: 6.1.0
integrity: sha256-Xx19ECVDRrDWpz9D2GxQHHV89vd61dnXxQm0IcYQHGk=
dependencies:
- bifunctors
- effect
- either
- identity
- maybe
- partial
- prelude
- refs
transformers:
type: registry
version: 6.0.0
integrity: sha256-Pzw40HjthX77tdPAYzjx43LK3X5Bb7ZspYAp27wksFA=
dependencies:
- control
- distributive
- effect
- either
- exceptions
- foldable-traversable
- identity
- lazy
- maybe
- newtype
- prelude
- tailrec
- tuples
- unfoldable
tuples:
type: registry
version: 7.0.0
integrity: sha256-1rXgTomes9105BjgXqIw0FL6Fz1lqqUTLWOumhWec1M=
dependencies:
- control
- invariant
- prelude
type-equality:
type: registry
version: 4.0.1
integrity: sha256-Hs9D6Y71zFi/b+qu5NSbuadUQXe5iv5iWx0226vOHUw=
dependencies: []
unfoldable:
type: registry
version: 6.0.0
integrity: sha256-JtikvJdktRap7vr/K4ITlxUX1QexpnqBq0G/InLr6eg=
dependencies:
- foldable-traversable
- maybe
- partial
- prelude
- tuples
unicode:
type: registry
version: 6.0.0
integrity: sha256-QJnTVZpmihEAUiMeYrfkusVoziJWp2hJsgi9bMQLue8=
dependencies:
- foldable-traversable
- maybe
- strings
unsafe-coerce:
type: registry
version: 6.0.0
integrity: sha256-IqIYW4Vkevn8sI+6aUwRGvd87tVL36BBeOr0cGAE7t0=
dependencies: []

View File

@ -1,24 +1,31 @@
package:
name: pg
build:
strict: true
pedantic_packages: true
dependencies:
- prelude
- aff
- bifunctors
- control
- datetime
- effect
- either
- maybe
- exceptions
- foldable-traversable
- console
- foreign
- lists
- maybe
- newtype
- strings
- stringutils
- node-buffer
- precise-datetime
- prelude
- transformers
- tuples
- typelevel-prelude
name: project
- unsafe-coerce
test:
main: Test.Main
dependencies:
- quickcheck
- spec
- spec-quickcheck
workspace:
extra_packages: {}
package_set:
url: https://raw.githubusercontent.com/purescript/package-sets/psc-0.15.10-20230930/packages.json
hash: sha256-nTsd44o7/hrTdk0c6dh0wyBqhFFDJJIeKdQU6L1zv/A=
packageSet:
registry: 50.5.0

View File

@ -0,0 +1,23 @@
module Data.Postgres.Geometry where
import Prelude
import Data.Generic.Rep (class Generic)
import Data.Newtype (class Newtype)
import Data.Show.Generic (genericShow)
newtype Point = Point { x :: Number, y :: Number }
derive instance Newtype Point _
derive instance Generic Point _
derive instance Eq Point
instance Show Point where
show = genericShow
newtype Circle = Circle { center :: Point, radius :: Number }
derive instance Newtype Circle _
derive instance Generic Circle _
derive instance Eq Circle
instance Show Circle where
show = genericShow

View File

@ -0,0 +1,71 @@
import * as Range from 'postgres-range'
/**
* @template T
* @typedef {{upper: T | undefined, lower: T | undefined, lowerIncl: boolean, upperIncl: boolean}} RangeRawRecord
*/
/** @type {<T>(_: Range.Range<T>) => RangeRawRecord<T>} */
export const rangeRawToRecord = r => {
if (r.hasMask(Range.RANGE_EMPTY)) {
return {
upper: undefined,
lower: undefined,
lowerIncl: false,
upperIncl: false,
}
} else {
const upper = r.upper === null ? undefined : r.upper
const lower = r.lower === null ? undefined : r.lower
return {
upper: r.hasMask(Range.RANGE_UB_INF) ? undefined : upper,
lower: r.hasMask(Range.RANGE_LB_INF) ? undefined : lower,
lowerIncl: r.hasMask(Range.RANGE_LB_INC),
upperIncl: r.hasMask(Range.RANGE_UB_INC),
}
}
}
/** @type {<T>(_: RangeRawRecord<T>) => Range.Range<T>} */
export const rangeRawFromRecord = r => {
const upper = r.upper === undefined ? null : r.upper
const lower = r.lower === undefined ? null : r.lower
if (upper === null && lower === null) {
// @ts-ignore
return new Range.Range(lower, upper, Range.RANGE_EMPTY)
}
let mask = 0
if (upper === null) {
mask |= Range.RANGE_UB_INF
} else if (r.upperIncl) {
mask |= Range.RANGE_UB_INC
}
if (lower === null) {
mask |= Range.RANGE_LB_INF
} else if (r.lowerIncl) {
mask |= Range.RANGE_LB_INC
}
return new Range.Range(lower, upper, mask)
}
/** @type {<T>(r: Range.Range<T>) => () => string} */
export const rangeRawSerialize = r => () => {
return Range.serialize(r)
}
/** @type {<T>(r: string) => (f: (s: string) => () => T) => () => Range.Range<T>} */
export const rangeRawParse = r => f => () => {
return Range.parse(r, s => f(s)())
}
/** @type {(r: unknown) => () => Range.Range<unknown>} */
export const readRangeRaw = r => () => {
if (r instanceof Range.Range) {
return r
} else {
throw new TypeError(`expected instance of Range, found ${r}`)
}
}

View File

@ -0,0 +1,122 @@
module Data.Postgres.Range where
import Prelude
import Control.Alt ((<|>))
import Control.Monad.Trans.Class (lift)
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Newtype (class Newtype, unwrap)
import Data.Postgres (class Deserialize, class Rep, class Serialize, RepT, deserialize, serialize, smash)
import Data.Postgres.Raw (Raw)
import Data.Postgres.Raw as Raw
import Data.Show.Generic (genericShow)
import Effect (Effect)
import Foreign (unsafeToForeign)
type RangeRawRecord = { upper :: Raw, lower :: Raw, lowerIncl :: Boolean, upperIncl :: Boolean }
foreign import data RangeRaw :: Type
foreign import readRangeRaw :: Raw -> Effect RangeRaw
foreign import rangeRawToRecord :: RangeRaw -> RangeRawRecord
foreign import rangeRawFromRecord :: RangeRawRecord -> RangeRaw
foreign import rangeRawParse :: String -> (String -> Effect Raw) -> Effect RangeRaw
foreign import rangeRawSerialize :: RangeRaw -> Effect String
rangeFromRaw :: forall a. Deserialize a => RangeRawRecord -> RepT (Range a)
rangeFromRaw raw = do
upper' :: Maybe a <- deserialize raw.upper
lower' :: Maybe a <- deserialize raw.lower
pure $ Range { upper: makeBound raw.upperIncl <$> upper', lower: makeBound raw.lowerIncl <$> lower' }
rangeToRaw :: forall a. Serialize a => Range a -> RepT RangeRawRecord
rangeToRaw r = do
upper' <- serialize $ boundValue <$> upper r
lower' <- serialize $ boundValue <$> lower r
pure $ { upper: upper', lower: lower', upperIncl: fromMaybe false $ boundIsInclusive <$> upper r, lowerIncl: fromMaybe false $ boundIsInclusive <$> lower r }
data Bound a = BoundIncl a | BoundExcl a
derive instance Generic (Bound a) _
derive instance Eq a => Eq (Bound a)
instance Show a => Show (Bound a) where
show = genericShow
boundValue :: forall a. Bound a -> a
boundValue (BoundIncl a) = a
boundValue (BoundExcl a) = a
boundIsInclusive :: forall a. Bound a -> Boolean
boundIsInclusive (BoundIncl _) = true
boundIsInclusive (BoundExcl _) = false
upper :: forall a. Range a -> Maybe (Bound a)
upper = _.upper <<< unwrap
lower :: forall a. Range a -> Maybe (Bound a)
lower = _.lower <<< unwrap
makeBound :: forall a. Boolean -> a -> Bound a
makeBound i a
| i = BoundIncl a
| otherwise = BoundExcl a
newtype Range a = Range { upper :: Maybe (Bound a), lower :: Maybe (Bound a) }
derive instance Generic (Range a) _
derive instance Newtype (Range a) _
derive instance Eq a => Eq (Range a)
instance Show a => Show (Range a) where
show = genericShow
instance (Ord a, Rep a) => Serialize (Range a) where
serialize a = do
raw <- rangeToRaw a
pure $ Raw.unsafeFromForeign $ unsafeToForeign $ rangeRawFromRecord raw
instance (Ord a, Rep a) => Deserialize (Range a) where
deserialize raw = do
range :: RangeRaw <- lift $ readRangeRaw raw
rangeFromRaw $ rangeRawToRecord range
instance Monoid (Range a) where
mempty = Range { upper: Nothing, lower: Nothing }
instance Semigroup (Range a) where
append (Range { upper: au, lower: al }) (Range { upper: bu, lower: bl }) = Range ({ upper: bu <|> au, lower: bl <|> al })
parseSQL :: forall a. Rep a => (String -> RepT a) -> String -> RepT (Range a)
parseSQL fromString sql = do
range <- lift $ rangeRawParse sql $ smash <<< (serialize <=< fromString)
rangeFromRaw $ rangeRawToRecord range
printSQL :: forall a. Rep a => Range a -> RepT String
printSQL range = do
record <- rangeToRaw range
lift $ rangeRawSerialize $ rangeRawFromRecord record
contains :: forall a. Ord a => a -> Range a -> Boolean
contains a r =
let
upperOk
| Just (BoundIncl u) <- upper r = u >= a
| Just (BoundExcl u) <- upper r = u > a
| otherwise = true
lowerOk
| Just (BoundIncl u) <- lower r = u <= a
| Just (BoundExcl u) <- lower r = u < a
| otherwise = true
in
upperOk && lowerOk
gte :: forall a. Ord a => a -> Range a
gte a = Range { upper: Just $ BoundIncl a, lower: Nothing }
gt :: forall a. Ord a => a -> Range a
gt a = Range { upper: Just $ BoundExcl a, lower: Nothing }
lt :: forall a. Ord a => a -> Range a
lt a = Range { upper: Nothing, lower: Just $ BoundExcl a }
lte :: forall a. Ord a => a -> Range a
lte a = Range { upper: Nothing, lower: Just $ BoundIncl a }

30
src/Data.Postgres.Raw.js Normal file
View File

@ -0,0 +1,30 @@
/** @type {(raw: unknown) => string} */
export const rawToString = raw =>
typeof raw === 'undefined'
? 'undefined'
: typeof raw === 'string'
? raw
: typeof raw === 'number' ||
typeof raw === 'boolean' ||
typeof raw === 'symbol'
? raw.toString()
: typeof raw === 'object'
? raw === null
? 'null'
: `[${raw.constructor.name}]`
: 'unknown'
/** @type {(a: unknown) => (b: unknown) => boolean} */
export const rawEq = a => b =>
typeof a === 'undefined' && typeof b === 'undefined'
? true
: typeof a === typeof b &&
['number', 'boolean', 'symbol', 'string'].includes(typeof a)
? a === b
: typeof a === 'object' && typeof b === 'object'
? a === null && b === null
? true
: a instanceof Array && b instanceof Array
? a.every((a_, ix) => rawEq(a_)(b[ix]))
: false
: false

View File

@ -0,0 +1,22 @@
module Data.Postgres.Raw where
import Prelude
import Foreign (Foreign)
import Unsafe.Coerce (unsafeCoerce)
foreign import data Raw :: Type
foreign import rawToString :: Raw -> String
foreign import rawEq :: Raw -> Raw -> Boolean
instance Show Raw where
show = rawToString
instance Eq Raw where
eq = rawEq
unsafeFromForeign :: Foreign -> Raw
unsafeFromForeign = unsafeCoerce
unsafeToForeign :: Raw -> Foreign
unsafeToForeign = unsafeCoerce

37
src/Data.Postgres.js Normal file
View File

@ -0,0 +1,37 @@
import { getTypeParser, setTypeParser } from 'pg-types'
import * as Range from 'postgres-range'
export const null_ = null
export const modifyPgTypes = () => {
const oid = {
'text[]': 1009,
json: 114,
jsonb: 3802,
'json[]': 199,
'jsonb[]': 3807,
timestamp: 1114,
timestamptz: 1184,
'timestamp[]': 1115,
'timestamptz[]': 1185,
tsrange: 3908,
tstzrange: 3910,
}
// @ts-ignore
const asString = a => a
const asStringArray = getTypeParser(oid['text[]'])
const asStringRange = Range.parse
setTypeParser(oid['json'], asString)
setTypeParser(oid['jsonb'], asString)
setTypeParser(oid['json[]'], asStringArray)
setTypeParser(oid['jsonb[]'], asStringArray)
setTypeParser(oid['timestamp'], asString)
setTypeParser(oid['timestamptz'], asString)
setTypeParser(oid['timestamp[]'], asStringArray)
setTypeParser(oid['timestamptz[]'], asStringArray)
setTypeParser(oid['tsrange'], asStringRange)
setTypeParser(oid['tstzrange'], asStringRange)
}

167
src/Data.Postgres.purs Normal file
View File

@ -0,0 +1,167 @@
module Data.Postgres where
import Prelude
import Control.Alt ((<|>))
import Control.Monad.Error.Class (liftEither, liftMaybe)
import Control.Monad.Except (Except, ExceptT, except, runExcept, runExceptT, withExceptT)
import Data.Bifunctor (lmap)
import Data.DateTime (DateTime)
import Data.Generic.Rep (class Generic)
import Data.List.NonEmpty (NonEmptyList)
import Data.Maybe (Maybe(..))
import Data.Newtype (unwrap, wrap)
import Data.Postgres.Raw (Raw)
import Data.Postgres.Raw (unsafeFromForeign, unsafeToForeign) as Raw
import Data.RFC3339String as DateTime.ISO
import Data.Show.Generic (genericShow)
import Data.Traversable (traverse)
import Effect (Effect)
import Effect.Exception (error)
import Foreign (ForeignError)
import Foreign as F
import Node.Buffer (Buffer)
newtype JSON = JSON String
foreign import null_ :: Raw
-- | Important! This effect MUST be evaluated to guarantee
-- | that (de)serialization will work for timestamp and JSON types.
-- |
-- | This mutates the `pg-types`, overriding the default deserialization
-- | behavior for JSON and timestamp types.
foreign import modifyPgTypes :: Effect Unit
-- | The SQL value NULL
data Null = Null
derive instance Generic Null _
derive instance Eq Null
derive instance Ord Null
instance Show Null where
show = genericShow
-- | The serialization & deserialization monad.
type RepT a = ExceptT RepError Effect a
-- | Errors encounterable while serializing & deserializing.
data RepError
= RepErrorTypeMismatch { expected :: String, found :: String }
| RepErrorInvalid String
| RepErrorForeign ForeignError
| RepErrorOther String
| RepErrorMultiple (NonEmptyList RepError)
derive instance Generic RepError _
derive instance Eq RepError
instance Show RepError where
show a = genericShow a
instance Semigroup RepError where
append (RepErrorMultiple as) (RepErrorMultiple bs) = RepErrorMultiple (as <> bs)
append (RepErrorMultiple as) b = RepErrorMultiple (as <> pure b)
append a (RepErrorMultiple bs) = RepErrorMultiple (pure a <> bs)
append a b = RepErrorMultiple (pure a <> pure b)
-- | Flatten to an Effect, rendering any `RepError`s to `String` using `Show`.
smash :: forall a. RepT a -> Effect a
smash = liftEither <=< map (lmap (error <<< show)) <<< runExceptT
-- | Lift an `Except` returned by functions in the `Foreign` module to `RepT`
liftForeign :: forall a. Except (NonEmptyList ForeignError) a -> RepT a
liftForeign = except <<< runExcept <<< withExceptT (RepErrorMultiple <<< map RepErrorForeign)
-- | Serialize data of type `a` to a `Raw` SQL value.
class Serialize a where
serialize :: a -> RepT Raw
-- | Deserialize data of type `a` from a `Raw` SQL value.
class Deserialize a where
deserialize :: Raw -> RepT a
-- | A type which is `Rep`resentable as a SQL value.
class (Serialize a, Deserialize a) <= Rep a
instance (Serialize a, Deserialize a) => Rep a
-- | Coerces the value to `Raw`
unsafeSerializeCoerce :: forall m a. Monad m => a -> m Raw
unsafeSerializeCoerce = pure <<< Raw.unsafeFromForeign <<< F.unsafeToForeign
instance Serialize Raw where
serialize = pure
instance Serialize Null where
serialize _ = unsafeSerializeCoerce null_
-- | `bytea`
instance Serialize Buffer where
serialize = unsafeSerializeCoerce
instance Serialize Int where
serialize = unsafeSerializeCoerce
instance Serialize Boolean where
serialize = unsafeSerializeCoerce
instance Serialize String where
serialize = unsafeSerializeCoerce
instance Serialize Number where
serialize = unsafeSerializeCoerce
instance Serialize Char where
serialize = unsafeSerializeCoerce
instance Serialize DateTime where
serialize = serialize <<< unwrap <<< DateTime.ISO.fromDateTime
instance Serialize a => Serialize (Maybe a) where
serialize (Just a) = serialize a
serialize Nothing = unsafeSerializeCoerce null_
instance Serialize a => Serialize (Array a) where
serialize = unsafeSerializeCoerce <=< traverse serialize
instance Deserialize Raw where
deserialize = pure
-- | `bytea`
instance Deserialize Buffer where
deserialize = liftForeign <<< (F.unsafeReadTagged "Buffer") <<< Raw.unsafeToForeign
instance Deserialize Null where
deserialize = map (const Null) <<< liftForeign <<< F.readNullOrUndefined <<< Raw.unsafeToForeign
instance Deserialize Int where
deserialize = liftForeign <<< F.readInt <<< Raw.unsafeToForeign
instance Deserialize Boolean where
deserialize = liftForeign <<< F.readBoolean <<< Raw.unsafeToForeign
instance Deserialize String where
deserialize = liftForeign <<< F.readString <<< Raw.unsafeToForeign
instance Deserialize Number where
deserialize = liftForeign <<< F.readNumber <<< Raw.unsafeToForeign
instance Deserialize Char where
deserialize = liftForeign <<< F.readChar <<< Raw.unsafeToForeign
instance Deserialize DateTime where
deserialize raw = do
s :: String <- deserialize raw
let invalid = RepErrorInvalid $ "Not a valid ISO8601 string: `" <> s <> "`"
liftMaybe invalid $ DateTime.ISO.toDateTime $ wrap s
instance Deserialize a => Deserialize (Array a) where
deserialize = traverse (deserialize <<< Raw.unsafeFromForeign) <=< liftForeign <<< F.readArray <<< Raw.unsafeToForeign
instance Deserialize a => Deserialize (Maybe a) where
deserialize raw =
let
nothing = const Nothing <$> deserialize @Null raw
just = Just <$> deserialize raw
in
just <|> nothing

4
src/Effect.Postgres.purs Normal file
View File

@ -0,0 +1,4 @@
module Effect.Pg where
import Prelude

View File

@ -1,7 +0,0 @@
module Main where
import Prelude
import Effect (Effect)
main :: Effect Unit
main = pure unit

View File

@ -0,0 +1,74 @@
module Test.Data.Postgres where
import Prelude
import Control.Monad.Error.Class (liftEither)
import Control.Monad.Except (runExceptT)
import Data.DateTime (DateTime(..))
import Data.DateTime.Instant as Instant
import Data.Int as Int
import Data.Maybe (Maybe, fromJust, fromMaybe, maybe)
import Data.Newtype (wrap)
import Data.Postgres (class Rep, Null(..), deserialize, null_, serialize, smash)
import Data.Postgres.Range as Range
import Data.Postgres.Raw (Raw)
import Data.Postgres.Raw as Raw
import Data.RFC3339String as DateTime.ISO
import Data.Tuple.Nested (type (/\), (/\))
import Effect.Class (liftEffect)
import Effect.Console (log)
import Effect.Unsafe (unsafePerformEffect)
import Foreign (unsafeToForeign)
import Partial.Unsafe (unsafePartial)
import Test.QuickCheck (class Arbitrary, (==?))
import Test.Spec (Spec, describe, it)
import Test.Spec.Assertions (shouldEqual)
import Test.Spec.QuickCheck (quickCheck)
asRaw :: forall a. a -> Raw
asRaw = Raw.unsafeFromForeign <<< unsafeToForeign
spec :: Spec Unit
spec =
let
check :: forall @a @x. Eq a => Show a => Arbitrary x => Rep a => String -> (x -> a) -> (a -> Raw) -> Spec Unit
check s xa asRaw_ =
describe s do
it "serialize" $ quickCheck \(x :: x) -> (unsafePerformEffect $ runExceptT $ serialize $ xa x) ==? pure (asRaw_ $ xa x)
it "deserialize" $ quickCheck \(x :: x) -> (unsafePerformEffect $ runExceptT $ deserialize $ asRaw_ $ xa x) ==? pure (xa x)
check_ :: forall @a. Eq a => Show a => Arbitrary a => Rep a => String -> Spec Unit
check_ s = check @a @a s identity asRaw
dateTimeFromArbitrary :: Int -> DateTime
dateTimeFromArbitrary = Instant.toDateTime <<< unsafePartial fromJust <<< Instant.instant <<< wrap <<< Int.toNumber
in
describe "Data.Postgres" do
check_ @Int "Int"
check_ @String "String"
check_ @Boolean "Boolean"
check_ @Number "Number"
check_ @Char "Char"
check @(Maybe String) "Maybe String" identity (maybe null_ asRaw)
check @(Array String) "Array String" identity asRaw
check @DateTime "DateTime" dateTimeFromArbitrary (asRaw <<< DateTime.ISO.fromDateTime)
describe "Null" do
it "serialize" $ liftEffect $ shouldEqual null_ =<< (smash $ serialize Null)
it "deserialize" $ liftEffect $ shouldEqual Null =<< (smash $ deserialize null_)
describe "Range" do
it "deserialize" do
quickCheck \(up /\ lo /\ uinc /\ linc :: Int /\ Int /\ Boolean /\ Boolean) -> unsafePerformEffect do
let
record =
{ upper: unsafePerformEffect $ smash $ serialize up
, lower: unsafePerformEffect $ smash $ serialize lo
, upperIncl: uinc
, lowerIncl: linc
}
raw = asRaw $ Range.rangeRawFromRecord record
exp :: Range.Range Int <- smash $ Range.rangeFromRaw record
act :: Range.Range Int <- smash $ deserialize raw
pure $ exp ==? act

13
test/Test.Main.purs Normal file
View File

@ -0,0 +1,13 @@
module Test.Main where
import Prelude
import Effect (Effect)
import Effect.Aff (launchAff_)
import Test.Data.Postgres as Test.Data.Postgres
import Test.Spec.Reporter (consoleReporter)
import Test.Spec.Runner (runSpec)
main :: Effect Unit
main = launchAff_ $ runSpec [ consoleReporter ] do
Test.Data.Postgres.spec