git.iko.soy/rowtype-yoga/purescript-supabase
on main help sign in
17 files changed+266−34
since splitsnapshots
unifiedsplit
.gitignore+1 spago.dhall+5 src/Supabase.purs+13 src/Supabase/AuthHelpers.js+3−3 src/Supabase/AuthHelpers.purs+13−6 src/Supabase/AuthHelpers/React.js+3 src/Supabase/AuthHelpers/React.purs+22 src/Supabase/Realtime.js+5 src/Supabase/Realtime.purs+59 src/Supabase/Realtime/ListenType.purs+18 src/Supabase/Realtime/SubscribeStates.purs+25 src/Supabase/Storage.js+3 src/Supabase/Storage.purs+16−5 src/Supabase/Supabase.js+9 src/Supabase/Supabase.purs+63−17 src/Supabase/Types.purs+5−3 test/Main.purs+3
.gitignore+1
@@ -8,3 +8,4 @@
88/.purs*
99/.psa*
1010/.spago
11+/.idea
spago.dhall+5
@@ -3,8 +3,10 @@
33 [ "aff"
44 , "aff-promise"
55 , "console"
6+ , "datetime"
67 , "effect"
78 , "either"
9+ , "exceptions"
810 , "fetch"
911 , "fetch-core"
1012 , "foldable-traversable"
@@ -20,6 +22,9 @@
2022 , "transformers"
2123 , "tuples"
2224 , "typelevel-prelude"
25+ , "unsafe-coerce"
26+ , "unsafe-reference"
27+ , "untagged-union"
2328 , "web-file"
2429 , "yoga-json"
2530 ]
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−import { UserProvider, useUser } from "@supabase/auth-helpers-react";
1+'use client'
2+import { useUser } from "@supabase/auth-helpers-react";
3+import { createBrowserSupabaseClient } from "@supabase/auth-helpers-nextjs";
24
3−export const userProvider = UserProvider;
45export const useUserImpl = useUser;
56
6−import { createBrowserSupabaseClient } from "@supabase/auth-helpers-nextjs";
77
88export const createBrowserClient = () => createBrowserSupabaseClient();
99
src/Supabase/AuthHelpers.purs+13−6
@@ -1,11 +1,11 @@
11module Supabase.AuthHelpers
22 ( ClientOptions
33 , CookieOptions
4+ , Options
45 , UseUser
56 , createBrowserClient
67 , createBrowserClientWithOptions
78 , useUser
8− , userProvider
99 ) where
1010
1111import Prelude
@@ -15,13 +15,12 @@ import Data.Nullable (Nullable)
1515import Data.Nullable as Nullable
1616import Effect (Effect)
1717import Foreign (Foreign)
18−import React.Basic.Hooks (Hook, ReactComponent, unsafeHook)
18+import React.Basic.Hooks (Hook, unsafeHook)
1919import Record.Studio (mapRecordKind)
2020import Supabase.Supabase as Supabase
2121import Supabase.Types (Client)
2222import Yoga.JSON as YogaJSON
23−
24−foreign import userProvider :: forall props. ReactComponent { supabaseClient :: Client | props }
23+import Yoga.JSON (class WriteForeign)
2524
2625foreign import useUserImpl :: Effect { user :: Nullable Supabase.User }
2726
@@ -41,11 +40,19 @@ type CookieOptions =
4140 , secure :: Maybe Boolean
4241 }
4342
44−type ClientOptions =
43+type Options =
44+ { db :: Maybe String
45+ }
46+
47+type ClientOptions r =
4548 { cookieOptions :: Maybe CookieOptions
49+ , options :: Maybe Options
50+ , supabaseKey :: Maybe String
51+ , supabaseUrl :: Maybe String
52+ | r
4653 }
4754
4855foreign import createBrowserClientWithOptionsInternal :: Foreign -> Effect Client
4956
50−createBrowserClientWithOptions ∷ ClientOptions → Effect Client
57+createBrowserClientWithOptions ∷ forall r. WriteForeign (ClientOptions r) => ClientOptions r → Effect Client
5158createBrowserClientWithOptions = 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) => () =>
1111
1212export const removeImpl = (queryBuilder) => (filePaths) => () =>
1313 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
33 , Storage
44 , download
55 , from
6+ , fromStorage
67 , remove
78 , storage
89 , upload
10+ , createSignedUrl
911 ) where
1012
1113import Prelude
@@ -26,6 +28,7 @@ import Type.Function (type ($))
2628import Supabase.Util as Util
2729import Web.File.Blob (Blob)
2830import Web.File.File as File
31+import Data.Time.Duration (Seconds)
2932
3033foreign import data Storage :: Type
3134
@@ -35,6 +38,9 @@ foreign import fromImpl :: Storage -> String -> QueryBuilder
3538from :: String -> Storage -> QueryBuilder
3639from = flip fromImpl
3740
41+fromStorage :: String -> Client -> QueryBuilder
42+fromStorage s = from s <<< storage
43+
3844type FileOptions = { upsert :: Boolean }
3945
4046foreign import uploadImpl :: QueryBuilder -> Fn3 String File.File FileOptions $ Effect (Promise Foreign)
@@ -47,13 +53,18 @@ foreign import downloadImpl :: QueryBuilder -> String -> Effect (Promise { "data
4753download :: String -> QueryBuilder -> Aff (SBTypes.Response Blob) -- [TODO] Make this a proper response
4854download file qb = downloadImpl qb file # Promise.toAffE <#> convert
4955 where
50− convert { "data": d, error: error } =
51− let
52− blob = Nullable.toMaybe d
53− in
54− { "data": blob, error: Nullable.toMaybe error, status: Nothing }
56+ convert { "data": d, error: error } = do
57+ { "data": Nullable.toMaybe d, error: Nullable.toMaybe error, status: Nothing }
5558
5659foreign import removeImpl :: QueryBuilder -> Array String -> Effect (Promise Foreign)
5760
5861remove ∷ Array String → QueryBuilder → Aff { error ∷ Maybe ResultError }
5962remove 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 @@
11export const signInWithOtpImpl = (supabase) => (email) => () =>
22 supabase.auth.signInWithOtp({ email });
33
4+export const signInWithOtpOptionsImpl = (supabase, email, options) =>
5+ supabase.auth.signInWithOtp({ email, options });
6+
47export const getSessionImpl = (supabase) => () => supabase.auth.getSession();
58export const onAuthStateChangeImpl = (supabase) => (handler) => () =>
69 supabase.auth.onAuthStateChange((_event, session) => handler(session));
710
11+export const getUserImpl = supabase => () => supabase.auth.getUser();
12+
813export const signOutImpl = (supabase) => () => supabase.auth.signOut();
914export const fromImpl = (client) => (table) => client.from(table);
1015export const selectQueryImpl = (queryBuilder) => (projection) =>
@@ -24,6 +29,8 @@ export const eqRunImpl = (builder) => (key, value) => () =>
2429
2530export const eqImpl = (builder) => (key, value) => builder.eq(key, value);
2631
32+export const orImpl = conditions => builder => builder.or(...conditions);
33+
2734export const singleImpl = (filterBuilder) => () => filterBuilder.single();
2835
2936export const maybeSingleImpl = (filterBuilder) => () =>
@@ -42,3 +49,5 @@ export const invokeImpl = (client) => (functionName, body, headers) => () =>
4249 body,
4350 headers,
4451 });
52+
53+export const channel = (channelName) => (client) => () => client.channel(channelName);
src/Supabase/Supabase.purs+63−17
@@ -12,22 +12,28 @@ module Supabase.Supabase
1212 , ResultError
1313 , Session
1414 , SessionData
15+ , SignInOptions
1516 , StatusR
17+ , class Or
1618 , User
1719 , class Equals
1820 , class Select
1921 , delete
2022 , equals
23+ , channel
2124 , from
2225 , getSession
2326 , invoke
27+ , maybeSingle
2428 , onAuthStateChange
29+ , or
2530 , range
2631 , select
32+ , getUser
2733 , signInWithOtp
34+ , signInWithOtpOptions
2835 , signOut
2936 , single
30− , maybeSingle
3137 , update
3238 , upsert
3339 ) where
@@ -35,25 +41,28 @@ module Supabase.Supabase
3541import Prelude
3642
3743import Control.Promise (Promise)
38−import Control.Promise as Promise
44+import Data.Either (Either(..))
3945import Data.Function.Uncurried (Fn2, Fn3, runFn2, runFn3)
40−import Data.Maybe (Maybe)
46+import Data.Maybe (Maybe(..))
4147import Data.Nullable (Nullable)
42−import Data.Nullable as Nullable
4348import Data.Tuple (Tuple(..))
4449import Effect (Effect)
4550import Effect.Aff (Aff, Error)
46−import Effect.Uncurried (EffectFn1, mkEffectFn1)
47−import Fetch as Fetch
48−import Fetch.Core.Response as YogaJson.Core
49−import Fetch.Internal.Response as FetchInternalResponse
51+import Effect.Exception (error)
52+import Effect.Uncurried (EffectFn1, EffectFn3, mkEffectFn1, runEffectFn3)
5053import Foreign (Foreign)
51−import Supabase.Types (Client)
54+import Prim.Row (class Union)
55+import Supabase.Types (Channel, ChannelName, Client)
5256import Type.Function (type ($))
5357import 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
5463import Supabase.Util as Util
5564import 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
5766
5867foreign import data QueryBuilder :: Type
5968foreign import data FilterBuilder :: Type
@@ -91,7 +100,7 @@ foreign import selectQueryImpl :: QueryBuilder -> String -> FilterBuilder
91100
92101foreign import selectQueryWithCountImpl :: QueryBuilder -> String -> String -> FilterBuilder
93102
94−class Select input builder output where
103+class Select input builder output | output -> builder where
95104 select :: input -> builder -> output
96105
97106instance (YogaJson.ReadForeign output) => Select Unit FilterBuilder (Aff output) where
@@ -120,6 +129,18 @@ else instance (YogaJson.ReadForeign output) => Equals FilterBuilder (Aff output)
120129 equals :: String -> String -> FilterBuilder -> Aff output
121130 equals l r builder = runFn2 (eqRunImpl builder) l r # Promise.toAffE >>= Util.fromJSON
122131
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+
123144foreign import singleImpl :: FilterBuilder -> Effect (Promise Foreign)
124145
125146single :: forall t. YogaJson.ReadForeign t => FilterBuilder -> Aff (Response t)
@@ -139,24 +160,34 @@ range { from: f, to } = rangeImpl f to >>> Promise.toAffE >=> Util.fromJSON
139160
140161type InternalAuthResponse = { error :: Nullable Error }
141162
142−foreign import signInWithOtpImpl :: Client -> String -> Effect (Promise InternalAuthResponse)
143−
144163type AuthResponse = { error :: Maybe Error }
145164
165+foreign import signInWithOtpImpl :: Client -> String -> Effect (Promise InternalAuthResponse)
166+
146167signInWithOtp :: Client -> String -> Aff AuthResponse
147168signInWithOtp client email = signInWithOtpImpl client email # Promise.toAffE <#> \{ error } -> { error: Nullable.toMaybe error }
148169
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+
149180foreign import signOutImpl :: Client -> Effect (Promise Unit)
150181
151182signOut ∷ Client → Aff Unit
152183signOut client = signOutImpl client # Promise.toAffE
153184
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 } }
155186
156−onAuthStateChange ∷ Client → (Maybe Session → Effect Unit) → Effect Unit
187+onAuthStateChange ∷ Client → (Maybe Session → Effect Unit) → Effect { data :: { id :: String, unsubscribe :: Effect Unit } }
157188onAuthStateChange client handler = onAuthStateChangeImpl client $ mkEffectFn1 (Nullable.toMaybe >>> handler)
158189
159−type User = { id :: String, email :: String }
190+type User = { id :: String, email :: String, user_metadata :: Foreign }
160191type Session = { user :: User }
161192type SessionData = { session :: Maybe Session }
162193
@@ -165,7 +196,10 @@ type ResultError = { code :: Maybe String, details :: Maybe String, message :: S
165196type DataR d r = (data :: Maybe d | r)
166197type ErrorR r = (error ∷ Maybe ResultError | r)
167198type CountR r = (count :: Int | r)
199+
200+type StatusR :: forall k. k -> Row Type
168201type StatusR r = (status ∷ Maybe Int)
202+
169203type Response d = { data :: Maybe d, error ∷ Maybe ResultError, status ∷ Maybe Int }
170204
171205foreign import getSessionImpl :: Client -> Effect (Promise Foreign)
@@ -177,10 +211,22 @@ type InternalFunctionResponse d = { "data" :: Nullable d, error :: Nullable { me
177211
178212foreign import invokeImpl :: forall body headers t. Client -> Fn3 String body headers (Effect $ Promise $ InternalFunctionResponse t)
179213
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 } }
181215
182216invoke ∷ forall t body headers. ReadForeign t ⇒ Client → String → body → headers → Aff (FunctionResponse t)
183217invoke client fn body headers = runFn3 (invokeImpl client) fn body headers # Promise.toAffE <#> convert
184218 where
185219 convertError { message, context } = { message, context: FetchInternalResponse.convert context }
186220 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
2− ( Client
3− ) where
1+module Supabase.Types where
42
53import Prelude
64
75foreign 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
44
55import Effect (Effect)
66import Effect.Class.Console (log)
7+import Supabase (Client)
8+import Supabase.Supabase as SB
9+import Unsafe.Coerce (unsafeCoerce)
710
811main :: Effect Unit
912main = do