git.iko.soy/rowtype-yoga/purescript-supabase
on main help sign in
.gitignore+1
since splitsnapshots
unifiedsplit
@@ -8,3 +8,4 @@
8/.purs*
8/.purs*
9/.psa*
9/.psa*
10/.spago
10/.spago
11+/.idea
spago.dhall+5
@@ -3,8 +3,10 @@
3 [ "aff"
3 [ "aff"
4 , "aff-promise"
4 , "aff-promise"
5 , "console"
5 , "console"
6+ , "datetime"
6 , "effect"
7 , "effect"
7 , "either"
8 , "either"
9+ , "exceptions"
8 , "fetch"
10 , "fetch"
9 , "fetch-core"
11 , "fetch-core"
10 , "foldable-traversable"
12 , "foldable-traversable"
@@ -20,6 +22,9 @@
20 , "transformers"
22 , "transformers"
21 , "tuples"
23 , "tuples"
22 , "typelevel-prelude"
24 , "typelevel-prelude"
25+ , "unsafe-coerce"
26+ , "unsafe-reference"
27+ , "untagged-union"
23 , "web-file"
28 , "web-file"
24 , "yoga-json"
29 , "yoga-json"
25 ]
30 ]
src/Supabase.pursnew file+13
@@ -0,0 +1,13 @@
1+module Supabase
2+ ( module Supabase.Supabase
3+ , module Supabase.Storage
4+ , module Supabase.AuthHelpers
5+ , module Supabase.Types
6+ , module Supabase.Util
7+ ) where
8+
9+import Supabase.Supabase (class Equals, class Select, AuthResponse, Count(..), CountR, CountResponse, DataR, ErrorR, FilterBuilder, FunctionResponse, QueryBuilder, Response, ResultError, Session, SessionData, StatusR, User, delete, equals, from, getSession, invoke, maybeSingle, onAuthStateChange, range, select, signInWithOtp, signOut, single, update, upsert)
10+import Supabase.Storage (FileOptions, Storage, createSignedUrl, download, fromStorage, remove, storage, upload)
11+import Supabase.AuthHelpers (ClientOptions, CookieOptions, Options, UseUser, createBrowserClient, createBrowserClientWithOptions, useUser)
12+import Supabase.Types (Client)
13+import Supabase.Util (fromEither, fromJSON, toError)
src/Supabase/AuthHelpers.js+3−3
@@ -1,9 +1,9 @@
1+'use client'
1−import { UserProvider, useUser } from "@supabase/auth-helpers-react";
2+import { useUser } from "@supabase/auth-helpers-react";
3+import { createBrowserSupabaseClient } from "@supabase/auth-helpers-nextjs";
2
4
3−export const userProvider = UserProvider;
4export const useUserImpl = useUser;
5export const useUserImpl = useUser;
5
6
6−import { createBrowserSupabaseClient } from "@supabase/auth-helpers-nextjs";
7
7
8export const createBrowserClient = () => createBrowserSupabaseClient();
8export const createBrowserClient = () => createBrowserSupabaseClient();
9
9
src/Supabase/AuthHelpers.purs+5−6
@@ -6,7 +6,6 @@ module Supabase.AuthHelpers
6 , createBrowserClient
6 , createBrowserClient
7 , createBrowserClientWithOptions
7 , createBrowserClientWithOptions
8 , useUser
8 , useUser
9− , userProvider
10 ) where
9 ) where
11
10
12import Prelude
11import Prelude
@@ -16,13 +15,12 @@ import Data.Nullable (Nullable)
16import Data.Nullable as Nullable
15import Data.Nullable as Nullable
17import Effect (Effect)
16import Effect (Effect)
18import Foreign (Foreign)
17import Foreign (Foreign)
19−import React.Basic.Hooks (Hook, ReactComponent, unsafeHook)
18+import React.Basic.Hooks (Hook, unsafeHook)
20import Record.Studio (mapRecordKind)
19import Record.Studio (mapRecordKind)
21import Supabase.Supabase as Supabase
20import Supabase.Supabase as Supabase
22import Supabase.Types (Client)
21import Supabase.Types (Client)
23import Yoga.JSON as YogaJSON
22import Yoga.JSON as YogaJSON
24−
23+import Yoga.JSON (class WriteForeign)
25−foreign import userProvider :: forall props. ReactComponent { supabaseClient :: Client | props }
26
24
27foreign import useUserImpl :: Effect { user :: Nullable Supabase.User }
25foreign import useUserImpl :: Effect { user :: Nullable Supabase.User }
28
26
@@ -46,14 +44,15 @@ type Options =
46 { db :: Maybe String
44 { db :: Maybe String
47 }
45 }
48
46
49−type ClientOptions =
47+type ClientOptions r =
50 { cookieOptions :: Maybe CookieOptions
48 { cookieOptions :: Maybe CookieOptions
51 , options :: Maybe Options
49 , options :: Maybe Options
52 , supabaseKey :: Maybe String
50 , supabaseKey :: Maybe String
53 , supabaseUrl :: Maybe String
51 , supabaseUrl :: Maybe String
52+ | r
54 }
53 }
55
54
56foreign import createBrowserClientWithOptionsInternal :: Foreign -> Effect Client
55foreign import createBrowserClientWithOptionsInternal :: Foreign -> Effect Client
57
56
58−createBrowserClientWithOptions ∷ ClientOptions → Effect Client
57+createBrowserClientWithOptions ∷ forall r. WriteForeign (ClientOptions r) => ClientOptions r → Effect Client
59createBrowserClientWithOptions = YogaJSON.write >>> createBrowserClientWithOptionsInternal
58createBrowserClientWithOptions = YogaJSON.write >>> createBrowserClientWithOptionsInternal
src/Supabase/AuthHelpers/React.jsnew file+3
@@ -0,0 +1,3 @@
1+import { useUser } from "@supabase/auth-helpers-react";
2+
3+export const useUserImpl = useUser;
src/Supabase/AuthHelpers/React.pursnew file+22
@@ -0,0 +1,22 @@
1+module Supabase.AuthHelpers.React where
2+
3+import Prelude
4+
5+import Data.Maybe (Maybe)
6+import Data.Nullable (Nullable)
7+import Data.Nullable as Nullable
8+import Effect (Effect)
9+import Foreign (Foreign)
10+import React.Basic.Hooks (Hook, unsafeHook)
11+import Record.Studio (mapRecordKind)
12+import Supabase.Supabase as Supabase
13+import Supabase.Types (Client)
14+import Yoga.JSON as YogaJSON
15+import Yoga.JSON (class WriteForeign)
16+
17+foreign import useUserImpl :: Effect { user :: Nullable Supabase.User }
18+
19+useUser :: Hook (UseUser) { user :: Maybe Supabase.User }
20+useUser = unsafeHook useUserImpl <#> mapRecordKind Nullable.toMaybe
21+
22+foreign import data UseUser :: Type -> Type
src/Supabase/Realtime.jsnew file+5
@@ -0,0 +1,5 @@
1+export const sendImpl = (what, channel) => channel.send(what)
2+
3+export const onImpl = (listenType, filter, callback, channel) => channel.on(listenType, filter, callback)
4+
5+export const subscribeImpl = (callback,timeout, channel) => channel.subscribe(callback,timeout)
src/Supabase/Realtime.pursnew file+59
@@ -0,0 +1,59 @@
1+module Supabase.Realtime where
2+
3+import Prelude
4+import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, mkEffectFn1, mkEffectFn2, runEffectFn2, runEffectFn3, runEffectFn4)
5+import Supabase.Types (Channel)
6+import Control.Promise (Promise, toAffE)
7+import Effect.Aff (Aff)
8+import Effect (Effect)
9+import Unsafe.Coerce (unsafeCoerce)
10+import Supabase.Realtime.ListenType (RealtimeListenType)
11+import Supabase.Realtime.SubscribeStates (RealtimeSubscribeState, RealtimeSubscribeState(ChannelError))
12+import Effect.Exception (Error, error)
13+import Untagged.Union (UndefinedOr, uorToMaybe)
14+import Data.Time.Duration (Milliseconds)
15+import Data.Maybe (Maybe, Maybe(Nothing), Maybe(Just))
16+import Supabase.Realtime.SubscribeStates (fromString) as SubscribeState
17+import Control.Monad.Error.Class (throwError)
18+
19+data RealtimeResponse = SendingOK | SendingTimedOut | SendingRateLimited
20+
21+foreign import sendImpl :: forall i. EffectFn2 { | i } Channel (Promise String)
22+
23+send :: forall i. { | i } -> Channel -> Aff RealtimeResponse
24+send input channel = do
25+ res <- runEffectFn2 sendImpl input channel # toAffE
26+ case res of
27+ "ok" -> pure SendingOK
28+ "timed out" -> pure SendingTimedOut
29+ "rate limited" -> pure SendingRateLimited
30+ _ -> throwError $ error $ "Supabase.Realtime.send: invalid response: " <> res
31+
32+foreign import onImpl :: forall f cbi. EffectFn4 RealtimeListenType f (EffectFn1 cbi Unit) Channel Unit
33+
34+on :: forall f cbi. RealtimeListenType -> f -> (cbi -> Effect Unit) -> Channel -> Effect Unit
35+on lt filter callback channel = runEffectFn4 onImpl lt filter (mkEffectFn1 callback) channel
36+
37+foreign import subscribeImpl
38+ :: EffectFn3
39+ (EffectFn2 String (UndefinedOr Error) Unit)
40+ Milliseconds
41+ Channel
42+ Unit
43+
44+subscribe
45+ :: Milliseconds
46+ -> (RealtimeSubscribeState -> Maybe Error -> Effect Unit)
47+ -> Channel
48+ -> Effect Unit
49+subscribe timeout cb channel =
50+ runEffectFn3 subscribeImpl
51+ ( mkEffectFn2 \st err ->
52+ case SubscribeState.fromString st of
53+ Nothing ->
54+ cb ChannelError (Just (error $ "Supabase.Realtime.subscribe: invalid subscribe state: " <> st))
55+ Just ok ->
56+ cb ok (uorToMaybe err)
57+ )
58+ timeout
59+ channel
src/Supabase/Realtime/ListenType.pursnew file+18
@@ -0,0 +1,18 @@
1+module Supabase.Realtime.ListenType where
2+
3+import Prelude
4+import Unsafe.Coerce (unsafeCoerce)
5+import Unsafe.Reference (unsafeRefEq)
6+
7+foreign import data RealtimeListenType :: Type
8+instance Eq RealtimeListenType where
9+ eq = unsafeRefEq
10+
11+broadcast :: RealtimeListenType
12+broadcast = unsafeCoerce "broadcast"
13+
14+presence :: RealtimeListenType
15+presence = unsafeCoerce "presence"
16+
17+postgresChanges :: RealtimeListenType
18+postgresChanges = unsafeCoerce "postgres_changes"
src/Supabase/Realtime/SubscribeStates.pursnew file+25
@@ -0,0 +1,25 @@
1+module Supabase.Realtime.SubscribeStates where
2+
3+import Prelude
4+import Unsafe.Coerce (unsafeCoerce)
5+import Unsafe.Reference (unsafeRefEq)
6+import Data.Maybe (Maybe(Nothing), Maybe, Maybe(Just))
7+
8+data RealtimeSubscribeState = Subscribed | TimedOut | Closed | ChannelError
9+derive instance Eq RealtimeSubscribeState
10+
11+toString :: RealtimeSubscribeState -> String
12+toString = case _ of
13+ Subscribed -> "SUBSCRIBED"
14+ TimedOut -> "TIMED_OUT"
15+ Closed -> "CLOSED"
16+ ChannelError -> "CHANNEL_ERROR"
17+
18+fromString :: String -> Maybe RealtimeSubscribeState
19+fromString = case _ of
20+ "SUBSCRIBED" -> Just Subscribed
21+ "TIMED_OUT" -> Just TimedOut
22+ "CLOSED" -> Just Closed
23+ "CHANNEL_ERROR" -> Just ChannelError
24+ _ -> Nothing
25+
src/Supabase/Storage.js+3
@@ -11,3 +11,6 @@ export const downloadImpl = (queryBuilder) => (filePath) => () =>
11
11
12export const removeImpl = (queryBuilder) => (filePaths) => () =>
12export const removeImpl = (queryBuilder) => (filePaths) => () =>
13 queryBuilder.remove(filePaths);
13 queryBuilder.remove(filePaths);
14+
15+export const createSignedUrlImpl = (queryBuilder) => filePath => options => () =>
16+ queryBuilder.createSignedUrl(filePath, options);
src/Supabase/Storage.purs+16−5
@@ -3,9 +3,11 @@ module Supabase.Storage
3 , Storage
3 , Storage
4 , download
4 , download
5 , from
5 , from
6+ , fromStorage
6 , remove
7 , remove
7 , storage
8 , storage
8 , upload
9 , upload
10+ , createSignedUrl
9 ) where
11 ) where
10
12
11import Prelude
13import Prelude
@@ -26,6 +28,7 @@ import Type.Function (type ($))
26import Supabase.Util as Util
28import Supabase.Util as Util
27import Web.File.Blob (Blob)
29import Web.File.Blob (Blob)
28import Web.File.File as File
30import Web.File.File as File
31+import Data.Time.Duration (Seconds)
29
32
30foreign import data Storage :: Type
33foreign import data Storage :: Type
31
34
@@ -35,6 +38,9 @@ foreign import fromImpl :: Storage -> String -> QueryBuilder
35from :: String -> Storage -> QueryBuilder
38from :: String -> Storage -> QueryBuilder
36from = flip fromImpl
39from = flip fromImpl
37
40
41+fromStorage :: String -> Client -> QueryBuilder
42+fromStorage s = from s <<< storage
43+
38type FileOptions = { upsert :: Boolean }
44type FileOptions = { upsert :: Boolean }
39
45
40foreign import uploadImpl :: QueryBuilder -> Fn3 String File.File FileOptions $ Effect (Promise Foreign)
46foreign import uploadImpl :: QueryBuilder -> Fn3 String File.File FileOptions $ Effect (Promise Foreign)
@@ -47,13 +53,18 @@ foreign import downloadImpl :: QueryBuilder -> String -> Effect (Promise { "data
47download :: String -> QueryBuilder -> Aff (SBTypes.Response Blob) -- [TODO] Make this a proper response
53download :: String -> QueryBuilder -> Aff (SBTypes.Response Blob) -- [TODO] Make this a proper response
48download file qb = downloadImpl qb file # Promise.toAffE <#> convert
54download file qb = downloadImpl qb file # Promise.toAffE <#> convert
49 where
55 where
50− convert { "data": d, error: error } =
56+ convert { "data": d, error: error } = do
51− let
52− blob = Nullable.toMaybe d
53− in
54− { "data": blob, error: Nullable.toMaybe error, status: Nothing }
57+ { "data": Nullable.toMaybe d, error: Nullable.toMaybe error, status: Nothing }
55
58
56foreign import removeImpl :: QueryBuilder -> Array String -> Effect (Promise Foreign)
59foreign import removeImpl :: QueryBuilder -> Array String -> Effect (Promise Foreign)
57
60
58remove ∷ Array String → QueryBuilder → Aff { error ∷ Maybe ResultError }
61remove ∷ Array String → QueryBuilder → Aff { error ∷ Maybe ResultError }
59remove file qb = removeImpl qb file # Promise.toAffE >>= Util.fromJSON
62remove file qb = removeImpl qb file # Promise.toAffE >>= Util.fromJSON
63+
64+foreign import createSignedUrlImpl :: QueryBuilder -> String -> Seconds -> Effect (Promise { "data" :: Nullable { signedUrl :: String }, error :: Nullable ResultError })
65+
66+createSignedUrl :: String -> Seconds -> QueryBuilder -> Aff (SBTypes.Response { signedUrl :: String })
67+createSignedUrl file expiry qb = createSignedUrlImpl qb file expiry # Promise.toAffE <#> convert
68+ where
69+ convert { "data": d, error: error } = do
70+ { "data": Nullable.toMaybe d, error: Nullable.toMaybe error, status: Nothing }
src/Supabase/Supabase.js+9
@@ -1,10 +1,15 @@
1export const signInWithOtpImpl = (supabase) => (email) => () =>
1export const signInWithOtpImpl = (supabase) => (email) => () =>
2 supabase.auth.signInWithOtp({ email });
2 supabase.auth.signInWithOtp({ email });
3
3
4+export const signInWithOtpOptionsImpl = (supabase, email, options) =>
5+ supabase.auth.signInWithOtp({ email, options });
6+
4export const getSessionImpl = (supabase) => () => supabase.auth.getSession();
7export const getSessionImpl = (supabase) => () => supabase.auth.getSession();
5export const onAuthStateChangeImpl = (supabase) => (handler) => () =>
8export const onAuthStateChangeImpl = (supabase) => (handler) => () =>
6 supabase.auth.onAuthStateChange((_event, session) => handler(session));
9 supabase.auth.onAuthStateChange((_event, session) => handler(session));
7
10
11+export const getUserImpl = supabase => () => supabase.auth.getUser();
12+
8export const signOutImpl = (supabase) => () => supabase.auth.signOut();
13export const signOutImpl = (supabase) => () => supabase.auth.signOut();
9export const fromImpl = (client) => (table) => client.from(table);
14export const fromImpl = (client) => (table) => client.from(table);
10export const selectQueryImpl = (queryBuilder) => (projection) =>
15export const selectQueryImpl = (queryBuilder) => (projection) =>
@@ -24,6 +29,8 @@ export const eqRunImpl = (builder) => (key, value) => () =>
24
29
25export const eqImpl = (builder) => (key, value) => builder.eq(key, value);
30export const eqImpl = (builder) => (key, value) => builder.eq(key, value);
26
31
32+export const orImpl = conditions => builder => builder.or(...conditions);
33+
27export const singleImpl = (filterBuilder) => () => filterBuilder.single();
34export const singleImpl = (filterBuilder) => () => filterBuilder.single();
28
35
29export const maybeSingleImpl = (filterBuilder) => () =>
36export const maybeSingleImpl = (filterBuilder) => () =>
@@ -42,3 +49,5 @@ export const invokeImpl = (client) => (functionName, body, headers) => () =>
42 body,
49 body,
43 headers,
50 headers,
44 });
51 });
52+
53+export const channel = (channelName) => (client) => () => client.channel(channelName);
src/Supabase/Supabase.purs+63−17
@@ -12,22 +12,28 @@ module Supabase.Supabase
12 , ResultError
12 , ResultError
13 , Session
13 , Session
14 , SessionData
14 , SessionData
15+ , SignInOptions
15 , StatusR
16 , StatusR
17+ , class Or
16 , User
18 , User
17 , class Equals
19 , class Equals
18 , class Select
20 , class Select
19 , delete
21 , delete
20 , equals
22 , equals
23+ , channel
21 , from
24 , from
22 , getSession
25 , getSession
23 , invoke
26 , invoke
27+ , maybeSingle
24 , onAuthStateChange
28 , onAuthStateChange
29+ , or
25 , range
30 , range
26 , select
31 , select
32+ , getUser
27 , signInWithOtp
33 , signInWithOtp
34+ , signInWithOtpOptions
28 , signOut
35 , signOut
29 , single
36 , single
30− , maybeSingle
31 , update
37 , update
32 , upsert
38 , upsert
33 ) where
39 ) where
@@ -35,25 +41,28 @@ module Supabase.Supabase
35import Prelude
41import Prelude
36
42
37import Control.Promise (Promise)
43import Control.Promise (Promise)
38−import Control.Promise as Promise
44+import Data.Either (Either(..))
39import Data.Function.Uncurried (Fn2, Fn3, runFn2, runFn3)
45import Data.Function.Uncurried (Fn2, Fn3, runFn2, runFn3)
40−import Data.Maybe (Maybe)
46+import Data.Maybe (Maybe(..))
41import Data.Nullable (Nullable)
47import Data.Nullable (Nullable)
42−import Data.Nullable as Nullable
43import Data.Tuple (Tuple(..))
48import Data.Tuple (Tuple(..))
44import Effect (Effect)
49import Effect (Effect)
45import Effect.Aff (Aff, Error)
50import Effect.Aff (Aff, Error)
51+import Effect.Exception (error)
46−import Effect.Uncurried (EffectFn1, mkEffectFn1)
52+import Effect.Uncurried (EffectFn1, EffectFn3, mkEffectFn1, runEffectFn3)
47−import Fetch as Fetch
48−import Fetch.Core.Response as YogaJson.Core
49−import Fetch.Internal.Response as FetchInternalResponse
50import Foreign (Foreign)
53import Foreign (Foreign)
54+import Prim.Row (class Union)
51−import Supabase.Types (Client)
55+import Supabase.Types (Channel, ChannelName, Client)
52import Type.Function (type ($))
56import Type.Function (type ($))
53import Type.Row (type (+))
57import Type.Row (type (+))
58+import Yoga.JSON (class ReadForeign, class WriteForeign, writeImpl)
59+import Fetch as Fetch
60+import Fetch.Internal.Response as FetchInternalResponse
61+import Data.Nullable as Nullable
62+import Control.Promise as Promise
54import Supabase.Util as Util
63import Supabase.Util as Util
55import Yoga.JSON (class ReadForeign, class WriteForeign, write) as YogaJson
64import Yoga.JSON (class ReadForeign, class WriteForeign, write) as YogaJson
56−import Yoga.JSON (class ReadForeign, class WriteForeign, writeImpl)
65+import Fetch.Core.Response as YogaJson.Core
57
66
58foreign import data QueryBuilder :: Type
67foreign import data QueryBuilder :: Type
59foreign import data FilterBuilder :: Type
68foreign import data FilterBuilder :: Type
@@ -91,7 +100,7 @@ foreign import selectQueryImpl :: QueryBuilder -> String -> FilterBuilder
91
100
92foreign import selectQueryWithCountImpl :: QueryBuilder -> String -> String -> FilterBuilder
101foreign import selectQueryWithCountImpl :: QueryBuilder -> String -> String -> FilterBuilder
93
102
94−class Select input builder output where
103+class Select input builder output | output -> builder where
95 select :: input -> builder -> output
104 select :: input -> builder -> output
96
105
97instance (YogaJson.ReadForeign output) => Select Unit FilterBuilder (Aff output) where
106instance (YogaJson.ReadForeign output) => Select Unit FilterBuilder (Aff output) where
@@ -120,6 +129,18 @@ else instance (YogaJson.ReadForeign output) => Equals FilterBuilder (Aff output)
120 equals :: String -> String -> FilterBuilder -> Aff output
129 equals :: String -> String -> FilterBuilder -> Aff output
121 equals l r builder = runFn2 (eqRunImpl builder) l r # Promise.toAffE >>= Util.fromJSON
130 equals l r builder = runFn2 (eqRunImpl builder) l r # Promise.toAffE >>= Util.fromJSON
122
131
132+foreign import orImpl :: forall builderIn builderOut. Array String -> builderIn -> builderOut
133+
134+class Or builder output | output -> builder where
135+ or :: Array String -> builder -> output
136+
137+instance Or FilterBuilder FilterBuilder where
138+ or :: Array String -> FilterBuilder -> FilterBuilder
139+ or keys fb = orImpl keys fb
140+else instance Or QueryBuilder FilterBuilder where
141+ or :: Array String -> QueryBuilder -> FilterBuilder
142+ or keys fb = orImpl keys fb
143+
123foreign import singleImpl :: FilterBuilder -> Effect (Promise Foreign)
144foreign import singleImpl :: FilterBuilder -> Effect (Promise Foreign)
124
145
125single :: forall t. YogaJson.ReadForeign t => FilterBuilder -> Aff (Response t)
146single :: forall t. YogaJson.ReadForeign t => FilterBuilder -> Aff (Response t)
@@ -139,24 +160,34 @@ range { from: f, to } = rangeImpl f to >>> Promise.toAffE >=> Util.fromJSON
139
160
140type InternalAuthResponse = { error :: Nullable Error }
161type InternalAuthResponse = { error :: Nullable Error }
141
162
142−foreign import signInWithOtpImpl :: Client -> String -> Effect (Promise InternalAuthResponse)
143−
144type AuthResponse = { error :: Maybe Error }
163type AuthResponse = { error :: Maybe Error }
145
164
165+foreign import signInWithOtpImpl :: Client -> String -> Effect (Promise InternalAuthResponse)
166+
146signInWithOtp :: Client -> String -> Aff AuthResponse
167signInWithOtp :: Client -> String -> Aff AuthResponse
147signInWithOtp client email = signInWithOtpImpl client email # Promise.toAffE <#> \{ error } -> { error: Nullable.toMaybe error }
168signInWithOtp client email = signInWithOtpImpl client email # Promise.toAffE <#> \{ error } -> { error: Nullable.toMaybe error }
148
169
170+type SignInOptions =
171+ ( shouldCreateUser :: Boolean
172+ , captchaToken :: String
173+ )
174+
175+foreign import signInWithOtpOptionsImpl :: forall options. EffectFn3 Client String options (Promise InternalAuthResponse)
176+
177+signInWithOtpOptions :: forall options thru. Union options thru SignInOptions => Client -> String -> { | options } -> Aff AuthResponse
178+signInWithOtpOptions client email options = runEffectFn3 signInWithOtpOptionsImpl client email options # Promise.toAffE <#> \{ error } -> { error: Nullable.toMaybe error }
179+
149foreign import signOutImpl :: Client -> Effect (Promise Unit)
180foreign import signOutImpl :: Client -> Effect (Promise Unit)
150
181
151signOut ∷ Client → Aff Unit
182signOut ∷ Client → Aff Unit
152signOut client = signOutImpl client # Promise.toAffE
183signOut client = signOutImpl client # Promise.toAffE
153
184
154−foreign import onAuthStateChangeImpl :: Client -> (EffectFn1 (Nullable Session) Unit) -> Effect Unit
185+foreign import onAuthStateChangeImpl :: Client -> (EffectFn1 (Nullable Session) Unit) -> Effect { data :: { id :: String, unsubscribe :: Effect Unit } }
155
186
156−onAuthStateChange ∷ Client → (Maybe Session → Effect Unit) → Effect Unit
187+onAuthStateChange ∷ Client → (Maybe Session → Effect Unit) → Effect { data :: { id :: String, unsubscribe :: Effect Unit } }
157onAuthStateChange client handler = onAuthStateChangeImpl client $ mkEffectFn1 (Nullable.toMaybe >>> handler)
188onAuthStateChange client handler = onAuthStateChangeImpl client $ mkEffectFn1 (Nullable.toMaybe >>> handler)
158
189
159−type User = { id :: String, email :: String }
190+type User = { id :: String, email :: String, user_metadata :: Foreign }
160type Session = { user :: User }
191type Session = { user :: User }
161type SessionData = { session :: Maybe Session }
192type SessionData = { session :: Maybe Session }
162
193
@@ -165,7 +196,10 @@ type ResultError = { code :: Maybe String, details :: Maybe String, message :: S
165type DataR d r = (data :: Maybe d | r)
196type DataR d r = (data :: Maybe d | r)
166type ErrorR r = (error ∷ Maybe ResultError | r)
197type ErrorR r = (error ∷ Maybe ResultError | r)
167type CountR r = (count :: Int | r)
198type CountR r = (count :: Int | r)
199+
200+type StatusR :: forall k. k -> Row Type
168type StatusR r = (status ∷ Maybe Int)
201type StatusR r = (status ∷ Maybe Int)
202+
169type Response d = { data :: Maybe d, error ∷ Maybe ResultError, status ∷ Maybe Int }
203type Response d = { data :: Maybe d, error ∷ Maybe ResultError, status ∷ Maybe Int }
170
204
171foreign import getSessionImpl :: Client -> Effect (Promise Foreign)
205foreign import getSessionImpl :: Client -> Effect (Promise Foreign)
@@ -177,10 +211,22 @@ type InternalFunctionResponse d = { "data" :: Nullable d, error :: Nullable { me
177
211
178foreign import invokeImpl :: forall body headers t. Client -> Fn3 String body headers (Effect $ Promise $ InternalFunctionResponse t)
212foreign import invokeImpl :: forall body headers t. Client -> Fn3 String body headers (Effect $ Promise $ InternalFunctionResponse t)
179
213
180−type FunctionResponse d = { "data" :: Maybe d, error :: Maybe { message :: String, context :: Fetch.Response }}
214+type FunctionResponse d = { "data" :: Maybe d, error :: Maybe { message :: String, context :: Fetch.Response } }
181
215
182invoke ∷ forall t body headers. ReadForeign t ⇒ Client → String → body → headers → Aff (FunctionResponse t)
216invoke ∷ forall t body headers. ReadForeign t ⇒ Client → String → body → headers → Aff (FunctionResponse t)
183invoke client fn body headers = runFn3 (invokeImpl client) fn body headers # Promise.toAffE <#> convert
217invoke client fn body headers = runFn3 (invokeImpl client) fn body headers # Promise.toAffE <#> convert
184 where
218 where
185 convertError { message, context } = { message, context: FetchInternalResponse.convert context }
219 convertError { message, context } = { message, context: FetchInternalResponse.convert context }
186 convert { "data": d, error } = { "data": Nullable.toMaybe d, error: Nullable.toMaybe error <#> convertError }
220 convert { "data": d, error } = { "data": Nullable.toMaybe d, error: Nullable.toMaybe error <#> convertError }
221+
222+foreign import channel :: ChannelName -> Client -> Effect Channel
223+
224+foreign import getUserImpl :: Client -> Effect (Promise { data :: Nullable User, error :: Nullable Error })
225+
226+getUser :: Client -> Aff (Either Error User)
227+getUser client = do
228+ res <- getUserImpl client # Promise.toAffE
229+ case Nullable.toMaybe res.data, Nullable.toMaybe res.error of
230+ Just user, _ -> pure $ Right user
231+ _, Just error -> pure $ Left error
232+ _, _ -> pure $ Left (error "Unexpected response error")
src/Supabase/Types.purs+5−3
@@ -1,7 +1,9 @@
1−module Supabase.Types
1+module Supabase.Types where
2− ( Client
3− ) where
4
2
5import Prelude
3import Prelude
6
4
7foreign import data Client :: Type
5foreign import data Client :: Type
6+
7+foreign import data Channel :: Type
8+
9+newtype ChannelName = ChannelName String
test/Main.purs+3
@@ -4,6 +4,9 @@ import Prelude
4
4
5import Effect (Effect)
5import Effect (Effect)
6import Effect.Class.Console (log)
6import Effect.Class.Console (log)
7+import Supabase (Client)
8+import Supabase.Supabase as SB
9+import Unsafe.Coerce (unsafeCoerce)
7
10
8main :: Effect Unit
11main :: Effect Unit
9main = do
12main = do