Rewrite Client Library

The following changes were made:
- Modify index.js to mainly be the entry point for the client.
- Write Base.js that has both RESTful and Realtime functionalities.
- Delete Subscription.js as its functionality has already been merged into Base.js
- Rewrite BaseRequest.js to accomodate new changes.
- Write Filters.js to put filters into pure functions.
This commit is contained in:
Angelico committed 2020-01-15 12:43:59 +08:00
1 parent 7d4c5397cb
commit 70f06dcd42
5 files changed
+237 -105

No files matched your search

+144
View File
@@ -0,0 +1,144 @@
import { Socket } from 'phoenix-channels'
import BaseRequest from './BaseRequest'
class Base {
constructor(tableName, restUrl, apiSocket, uuid) {
this.tableName = tableName
this.restUrl = restUrl
this.apiSocket = apiSocket
this.uuid = uuid
this.socket = null
this.channel = null
this.listeners = {}
this.queryFilters = []
}
createListener() {
this.socket = new Socket(apiSocket)
this.channel = this.socket.channel(this.tableName)
}
on(eventType, callbackFunction) {
if (this.socket == null) this.createListener()
var ref = this.channel.on(eventType, callbackFunction)
this.listeners[eventType] = ref
}
subscribe() {
if (this.socket == null) this.createListener()
this.socket.connect()
if (this.channel.state !== 'joined') {
this.channel
.join()
.receive('ok', resp => console.log('Joined Realtime successfully ', resp))
.receive('error', resp => console.log('Unable to join ', resp))
.receive('timeout', () => console.log('Networking issue. Still waiting...'))
}
}
unsubscribe() {
for (var ref in this.listeners) {
let eventName = this.listeners[ref]
this.off(eventName, ref)
}
this.socket.disconnect()
}
request(method) {
let path = `${this.restUrl}/${this.tableName}`
return new BaseRequest(method, path)
}
addFilters(request, options) {
if (Object.keys(options).length != 0) {
Object.keys(options).forEach(option => {
let setting = options[option]
request.set(option, setting)
})
}
// loop through this.queryFilters
this.queryFilters.forEach(queryFilter => {
let columnName = queryFilter.columnName
let operator = queryFilter.operator
let criteria = queryFilter.criteria
request.filter(columnName, operator, criteria)
})
}
select(columnQuery = '*', options = {}) {
let method = 'get'
let request = this.request(method)
request.select(columnQuery)
this.addFilters(request, options)
return request
}
insert(data, options = {}) {
let method = 'post'
let request = this.request(method)
if (!Array.isArray(data)) {
return {
body: null,
status: 400,
statusCode: 400,
statusText: 'Data type should be an array.',
}
}
data.forEach(datum => {
request.send(datum)
})
this.addFilters(request, options)
return request
}
update(data, options = {}) {
let method = 'patch'
let request = this.request(method)
request.send(data)
this.addFilters(request, options)
return request
}
delete(options = {}) {
let method = 'delete'
let request = this.request(method)
this.addFilters(request, options)
return request
}
}
// pre-empts if any of the filters are used before select
const filters = ['eq', 'gt', 'lt', 'gte', 'lte', 'like', 'ilike', 'is', 'in', 'not']
filters.forEach(
operator =>
(Base.prototype[operator] = function filterValue(columnName, criteria) {
this.queryFilters.push({
columnName,
operator,
criteria,
})
return this
})
)
export default Base
+32 -4
View File
@@ -4,6 +4,7 @@
*/
import { Request } from 'superagent'
import * as Filters from './utils/Filters'
const contentRangeStructure = /^(\d+)-(\d+)\/(\d+)$/
@@ -21,6 +22,19 @@ class BaseRequest extends Request {
super(method, path)
this.set('Accept', 'application/json')
this.filterMap = {
eq: (columnName, criteria) => Filters.Eq(columnName, criteria),
gt: (columnName, criteria) => Filters.Gt(columnName, criteria),
lt: (columnName, criteria) => Filters.Lt(columnName, criteria),
gte: (columnName, criteria) => Filters.Gte(columnName, criteria),
lte: (columnName, criteria) => Filters.Lte(columnName, criteria),
like: (columnName, criteria) => Filters.Like(columnName, criteria),
ilike: (columnName, criteria) => Filters.Ilike(columnName, criteria),
is: (columnName, criteria) => Filters.Is(columnName, criteria),
in: (columnName, criteria) => Filters.In(columnName, criteria),
not: (columnName, criteria) => Filters.Not(columnName, criteria),
}
// Fix for superagent disconnect on client & server.
if (!this.get) {
this.get = this.getHeader
@@ -28,7 +42,7 @@ class BaseRequest extends Request {
}
/**
* Set auth using special formats. If only one string paramter is passed, it
* Set auth using special formats. If only one string parameter is passed, it
* is interpreted as a bearer token. If an object and nothing else is passed,
* `user` and `pass` keys are extracted from it and used for basic auth.
*
@@ -51,6 +65,20 @@ class BaseRequest extends Request {
return super.auth(user, pass)
}
/**
* All in one conveniencd function for all filters available.
*
* @param {string} Name of the database column.
* @param {string} Name of filter operator be utilised.
* @param { object | array | string | integer | boolean | null } Value to compare to. Exact data type of criteria would depend on the operator used.
* @returns {BaseRequest} The API request object.
*/
filter(columnName, operator, criteria){
let newQuery = this.filterMap[operator.toLowerCase()](columnName, criteria)
return this.query(newQuery)
}
/**
* Takes a query object and translates it to a PostgREST filter query string.
* All values are prefixed with `eq.`.
@@ -179,15 +207,15 @@ class BaseRequest extends Request {
* For all of the PostgREST filters add a shortcut method to use it.
*
* @param {string} The name of the column.
* @param {any} The value of the column to be filtered.
* @param { object | array | string | integer | boolean | null } The value of the column to be filtered.
* @returns {BaseRequest} The API request object.
*/
const filters = ['eq', 'gt', 'lt', 'gte', 'lte', 'like', 'ilike', 'is', 'in', 'not']
filters.forEach(filter =>
BaseRequest.prototype[filter] = function filterValue (name, value) {
return this.query(`${name}=${filter}.${Array.isArray(value) ? value.join(',') : value}`)
BaseRequest.prototype[filter] = function filterValue (columnName, criteria) {
return this.filter(columnName, filter, criteria)
}
)
-44
View File
@@ -1,44 +0,0 @@
import { Socket } from 'phoenix-channels'
export default class BaseChannel {
constructor(tableName, apiSocket, uuid){
this.socket = new Socket(apiSocket)
this.uuid = uuid
this.tableName = tableName
this.channel = this.socket.channel(tableName)
this.start()
this.socket.onOpen(() => {
console.log('Socket connected')
})
this.socket.onClose(() => {
console.log('Socket disconnected')
})
}
on(eventName, callbackFunction){
this.channel.on(eventName, callbackFunction)
}
start(){
this.socket.connect()
if(this.channel.state !== 'joined'){
this.channel
.join()
.receive('ok', resp => console.log('Joined Realtime successfully ', resp))
.receive('error', resp => console.log('Unable to join ', resp))
.receive('timeout', () => console.log('Networking issue. Still waiting...'))
}
}
stop(){
for (var ref in this.listeners){
let eventName = this.listeners[ref]
this.off(eventName, ref)
}
this.socket.disconnect()
}
}
+22 -57
View File
@@ -1,82 +1,47 @@
import BaseRequest from './BaseRequest'
import Subscription from './Subscription'
import { uuid } from './utils/Helpers'
import Base from './Base'
class SupabaseClient {
constructor(supabaseUrl, supabaseKey, options) {
constructor(supabaseUrl, supabaseKey, options = {}) {
this.supabaseUrl = supabaseUrl
this.supabaseKey = supabaseKey
// this will be the case for now
this.restUrl = supabaseUrl
this.apiSocket = ''
this.restUrl = null
this.apiSocket = null
this.subscriptions = {}
this.authenticate(supabaseUrl, supabaseKey)
}
/**
* @todo
*/
subscribe(tableName) {
let uuid = uuid()
authenticate(supabaseUrl, supabaseKey){
// do something to utilise parameters and receive
// the restUrl & apiSocket
var restUrl = supabaseUrl
var apiSocket = ''
this.subscriptions[uuid] = new Subscription(tableName, this.apiSocket, uuid)
return this.subscriptions[uuid]
this.restUrl = restUrl
this.apiSocket = apiSocket
}
unsubscribe(subscription){
subscription.stop()
delete this.subscriptions[subscription.uuid]
from(tableName){
let identifier = uuid()
this.subscriptions[identifier] = new Base(tableName, this.restUrl, this.apiSocket, identifier)
return this.subscriptions[identifier]
}
/**
* Convenience wrapper for starting a PostgREST request builder. Adds the
* API URL to the provided path.
*
* @param {string} The HTTP method of the request.
* @param {string} The path of the request.
* @returns {BaseRequest} The API request object.
*/
request(method, path) {
return new BaseRequest(method, this.restUrl + path)
removeSubscription(mySubscription){
mySubscription.unsubscribe()
delete this.subscriptions[mySubscription.uuid]
}
}
/**
* Basic HTTP method functions for quick chaining.
*
* @param {string} The path of the request.
* @returns {BaseRequest} The API request object.
*/
const methods = ['POST', 'GET', 'PATCH', 'DELETE']
methods.forEach(method => {
SupabaseClient.prototype[method.toLowerCase()] = function requestMethod(tableName) {
let path = `/${tableName}`
return this.request(method, path)
}
})
const createClient = (supabaseUrl, supabaseKey, options = {}) => {
return new SupabaseClient(supabaseUrl, supabaseKey, options)
}
export { createClient }
/**
* TO BE REMOVED SOON
*/
// const defaultAwesomeFunction = (name) => {
// const returnStr = `I am the Default Awesome Function, fellow comrade! - ${name}`;
// return returnStr;
// };
// const awesomeFunction = () => 'I am just an Awesome Function';
// export default defaultAwesomeFunction;
// export { awesomeFunction };
@@ -0,0 +1,39 @@
export function Eq(columnName, filterValue){
return `${columnName}=eq.${filterValue}`
}
export function Gt(columnName, filterValue){
return `${columnName}=gt.${filterValue}`
}
export function Lt(columnName, filterValue){
return `${columnName}=lt.${filterValue}`
}
export function Gte(columnName, filterValue){
return `${columnName}=gte.${filterValue}`
}
export function Lte(columnName, filterValue){
return `${columnName}=lte.${filterValue}`
}
export function Like(columnName, stringPattern){
return `${columnName}=like.${stringPattern}`
}
export function Ilike(columnName, stringPattern){
return `${columnName}=ilike.${stringPattern}`
}
export function Is(columnName, filterValue){
return `${columnName}=is.${filterValue}`
}
export function In(columnName, filterArray){
return `${columnName}=in.${filterArray.join(',')}`
}
export function Not(columnName, filterValue){
return `${columnName}=not.${filterValue}`
}