@cerebral/firebase
install
NPM
npm install @cerebral/firebase
description
The Firebase provider is a Cerebral friendly wrapper around the Firebase client. By default the Firebase client is heavily event based, even just getting some value, handling authentication etc. This is useful in some types of apps, but Cerebral has a very straight forward way of thinking about side effects. You will find that a lot of the API exposed by the Firebase client is simplified!
instantiate
import {Controller} from 'cerebral'
import FirebaseProvider from '@cerebral/firebase'
const controller = Controller({
providers: [
FirebaseProvider({
config: {
apiKey: '{apiKey}',
authDomain: '{authDomain}',
databaseURL: '{databaseURL}',
storageBucket: '{storageBucket}'
},
specPrefix: 'CJ'
})
]
})
Important notes
-
The Cerebral firebase provider uses dot notation to keep consistency with Cerebral itself
-
All factories supports template tags, allowing you to dynamically create paths and points to values
error
FirebaseProviderError (base)
import {FirebaseProviderError} from '@cerebral/firebase'
{
name: 'HttpProviderError',
message: 'Some firebase error message'
stack: '...'
}
FirebaseProviderAuthenticationError
import {FirebaseProviderAuthenticationError} from '@cerebral/firebase'
{
name: 'HttpProviderError',
message: 'Some firebase error message'
code: 10
stack: '...'
}
set
Write data to this database location. This will overwrite any data at this location and all child locations. Passing null for the new value is equivalent to calling remove(); all data at this location or any child location will be deleted.
action
function someAction({firebase}) {
return firebase.set('foo.bar', 'baz')
}
factory
import {props} from 'cerebral/tags'
import {set} from '@cerebral/firebase/operators'
export default [
set('foo.bar', props`foo`),
set('foo.bar', props`foo`), {
success: [],
error: []
}
]
output
No output
update
As opposed to the set() method, update() can be use to selectively update only the referenced properties at the current location (instead of replacing all the child properties at the current location).
action
function someAction({firebase}) {
return firebase.update('some.path', {
'foo': 'bar',
'items.item1.isAwesome': true
})
}
factory
import {props} from 'cerebral/tags'
import {update} from '@cerebral/firebase/operators'
export default [
update('some.path', {
'foo.bar': props`bar`,
'foo.baz': props`baz`
}),
update('some.path', {
'foo.bar': props`bar`,
'foo.baz': props`baz`
}), {
success: [],
error: []
}
]
output
No output
push
Generates a new child location using a unique key and returns its reference from the action. An example being {key: "-KWKImT_t3SLmkJ4s3-w"}
.
action
function someAction({firebase}) {
return firebase.push('users', {
name: 'Bob'
})
}
factory
import {state} from 'cerebral/tags'
import {push} from '@cerebral/firebase/operators'
export default [
push('users', state`newUser`),
push('users', state`newUser`), {
success: [],
error: []
}
]
output
{key: 'theAddedKey'}
remove
Remove the data at this database location.
action
function someAction({ firebase}) {
return firebase.remove('foo.bar')
}
factory
import {props, string} from 'cerebral/tags'
import {remove} from '@cerebral/firebase/operators'
export default [
remove(string`users.${props`userKey`}`),
remove(string`users.${props`userKey`}`), {
success: [],
error: []
}
]
output
No output
transaction
Atomically modifies the data at the provided location.
Unlike a normal set(), which just overwrites the data regardless of its previous value, transaction() is used to modify the existing value to a new value, ensuring there are no conflicts with other clients writing to the same location at the same time.
To accomplish this, you pass transaction() an update function which is used to transform the current value into a new value. If another client writes to the location before your new value is successfully written, your update function will be called again with the new current value, and the write will be retried. This will happen repeatedly until your write succeeds without conflict or you abort the transaction by not returning a value from your update function.
action
function someAction({firebase}) {
function transactionFunction(currentData){
if (currentData === null) {
return { foo: 'bar' }
}
return
}
return firebase.transaction('some.transaction.path', transactionFunction)
.then((result) => {
if(result.committed){
return {result: result.value}
} else {
throw new Error('Transaction failed')
}
})
}
factory
import {transaction} from '@cerebral/firebase/operators'
function transactionFunction(currentData){
if (currentData === null) {
return { foo: 'bar' }
}
return
}
export default [
transaction('foo.bar', transactionFunction),
transaction('foo.bar', transactionFunction), {
success: [],
error: []
}
]
output
{committed: true, value: 'new value'}
Note: Modifying data with set() will cancel any pending transactions at that location, so extreme care should be taken if mixing set() and transaction() to update the same data.
Note: When using transactions with Security and Firebase Rules in place, be aware that a client needs .read access in addition to .write access in order to perform a transaction. This is because the client-side nature of transactions requires the client to read the data in order to transactionally update it.
value
action
function someAction({ firebase }) {
return firebase.value('someKey.foo', {
limitToFirst: 5,
limitToLast: 5,
startAt: 5,
endAt: 5,
equalTo: 5,
orderByChild: 'count',
orderByKey: true,
orderByValue: true,
asArray: false,
})
}
factory
import {value} from '@cerebral/firebase/operators'
import {state} from 'cerebral/tags'
export default [
value('foo.bar', {
asArray: true,
orderByChild: state`currentOrderChild`
}),
value('foo.bar'), {
success: [],
error: []
}
]
output
{key: 'keyYouLookedUpValueOn', value: 'the value'}
[{key: 'keyYouLookedUpValueOn', value: 'the value'}, {key: 'keyYouLookedUpValueOn', value: 'the value'}]
onValue
action
function someAction({ firebase }) {
firebase.onValue('someKey.foo', 'someModule.fooUpdated', {
payload: {},
});
}
This will NOT immediately grab the value and trigger the signal passed, the first event is discarded for more predictable behaviour. To grab existing value, just use value
.
To stop listening for updates to the value:
function someAction({ firebase }) {
firebase.off('someKey.foo', 'onValue');
}
factory
import {onValue} from '@cerebral/firebase/operators'
import {string, signal} from 'cerebral/tags'
export default [
onValue(string`foo.bar`, signal`some.signal`)
]
onChildAdded
action
function someAction({ firebase }) {
firebase.onChildAdded('posts', 'posts.postAdded', {
payload: {},
limitToFirst: 5,
limitToLast: 5,
startAt: 5,
endAt: 5,
equalTo: 5,
orderByChild: 'count',
orderByKey: true,
orderByValue: true
});
}
This will immediately grab and trigger the signal posts.postAdded
for every post grabbed. Note this is just registering a listener, not returning a value from the action. The signal is triggered with the payload: { key: 'someKey', value: {} }
.
To stop listening for updates to the posts:
function someAction({ firebase }) {
firebase.off('posts', 'onChildAdded');
}
factory
import {state, string, signal} from 'cerebral/tags'
import {onChildAdded} from '@cerebral/firebase/operators'
export default [
onChildAdded(string`foo.bar`, signal`some.signal`, {
orderByChild: 'count',
limitToFirst: state`config.limitToFirst`
})
]
onChildRemoved
action
function someAction({ firebase }) {
firebase.onChildRemoved('posts', 'posts.postRemoved', {
});
}
This will trigger the signal posts.postRemoved
whenever a post is removed from the selection. The signal is triggered with the payload: { key: 'someKey' }
.
To stop listening:
function someAction({ firebase }) {
firebase.off('posts', 'onChildRemoved');
}
factory
import {onChildRemoved} from '@cerebral/firebase/operators'
import {string, signal} from 'cerebral/tags'
export default [
onChildRemoved(string`foo.bar`, signal`some.signal`, {
})
]
onChildChanged
action
function someAction({ firebase }) {
firebase.onChildChanged('posts', 'posts.postChanged', {
});
}
This will trigger the signal posts.postChanged
whenever a post is changed in the selection. The signal is triggered with the payload: { key: 'someKey', value: {} }
.
To stop listening:
function someAction({ firebase }) {
firebase.off('posts', 'onChildChanged');
}
factory
import {onChildChanged} from '@cerebral/firebase/operators'
import {string, signal} from 'cerebral/tags'
export default [
onChildChanged(string`foo.bar`, signal`some.signal`, {
})
]
off
action
function someAction({ firebase }) {
firebase.off('posts', 'onChildChanged')
firebase.off('posts', 'onChildChanged')
firebase.off('posts.*', 'onChildChanged')
firebase.off('posts.*')
}
factory
import {string} from 'cerebral/tags'
import {off} from '@cerebral/firebase/operators'
export default [
off('posts', 'onChildChanged'),
off(string`posts.${state`selectedPostKey`}`)
]
task
If you are using the firebase-queue and need to create tasks, you can do that with:
action
function someAction({ firebase, state }) {
return firebase.task('create_post', {
uid: state.get('app.user.uid'),
text: state.get('posts.newPostText')
})
}
This will add a task at queue/tasks
. There is no output from a resolved task, it just resolves when the action has been processed.
factory
import {state, props} from 'cerebral/tags'
import {task} from '@cerebral/firebase/operators'
export default [
task('some_task', {
uid: state`user.uid`,
data: props`data`
}),
task('some_task', {
uid: state`user.uid`,
data: props`data`
}), {
success: [],
error: []
}
]
output
No output
getUser
Will resolve to {user: {}}
if user exists. If user was redirected from Facebook/Google etc. as part of first sign in, this method will handle the confirmed registration of the user.
action
function someAction({ firebase }) {
return firebase.getUser()
}
factory
import {getUser} from '@cerebral/firebase/operators'
export default [
getUser(),
getUser(), {
success: [],
error: []
}
]
output
{user: {}}
signInAnonymously
This login will method will resolve to existing anonymous or create a new one for you.
action
function someAction({ firebase }) {
return firebase.signInAnonymously()
}
factory
import {signInAnonymously} from '@cerebral/firebase/operators'
export default [
signInAnonymously(),
signInAnonymously(), {
success: [],
error: []
}
]
output
{user: {}}
createUserWithEmailAndPassword
Register a new user with email and password.
action
function someAction({ firebase, state }) {
const email = state.get('register.email')
const password = state.get('register.password')
return firebase.createUserWithEmailAndPassword(email, password)
}
factory
import {state} from 'cerebral/tags'
import {createUserWithEmailAndPassword} from '@cerebral/firebase/operators'
export default [
createUserWithEmailAndPassword(state`newUser.email`, state`newUser.password`),
createUserWithEmailAndPassword(state`newUser.email`, state`newUser.password`), {
success: [],
error: []
}
]
output
{user: {}}
signInWithEmailAndPassword
Sign in a user with email and password.
action
function someAction({ firebase, state }) {
const email = state.get('register.email')
const password = state.get('register.password')
return firebase.signInWithEmailAndPassword(email, password)
}
factory
import {props} from 'cerebral/tags'
import {signInWithEmailAndPassword} from '@cerebral/firebase/operators'
export default [
signInWithEmailAndPassword(props`email`, props`password`),
signInWithEmailAndPassword(props`email`, props`password`), {
success: [],
error: []
}
]
output
{user: {}}
signInWith{PROVIDER}
Sign in a user with Facebook, Google or Github.
action
function someAction({ firebase, state }) {
return firebase.signInWithFacebook({
redirect: false,
scopes: []
})
}
factory
import {state} from 'cerebral/tags'
import {signInWithFacebook} from '@cerebral/firebase/operators'
export default [
signInWithFacebook({
redirect: state`useragent.media.small`
}),
signInWithFacebook({
redirect: state`useragent.media.small`
}), {
success: [],
error: []
}
]
output
Either this will be a redirect or it outputs:
{user: {}}
Similar you can sign in with Google or GitHub.
Just use signInWithGoogle
or signInWithGithub
instead of signInWithFacebook
.
signInWithCustomToken
Sign in a custom token.
action
function someAction({ firebase, state }) {
return firebase.signInWithCustomToken(state.get('token'))
}
factory
import {props, state} from 'cerebral/tags'
import {signInWithCustomToken} from '@cerebral/firebase/operators'
export default [
signInWithCustomToken(props`token`),
signInWithCustomToken(state`token`), {
success: [],
error: []
}
]
output
{user: {}}
linkWithFacebook{PROVIDER}
Link an anonymous account with Facebook, Google or Github.
action
function someAction({ firebase, state }) {
return firebase.linkWithFacebook({
redirect: false,
scopes: []
})
}
factory
import {state} from 'cerebral/tags'
import {linkWithFacebook} from '@cerebral/firebase/operators'
export default [
linkWithFacebook({
redirect: state`useragent.media.small`
}),
linkWithFacebook({
redirect: state`useragent.media.small`
}), {
success: [],
error: []
}
]
output
Either this will be a redirect or it outputs:
{user: {}}
Similar you can sign in with Google or GitHub.
Just use linkWithGoogle
or linkWithGithub
instead of linkWithFacebook
.
signOut
Sign out user. getUser will now not resolve a user anymore.
action
function someAction({ firebase }) {
return firebase.signOut()
}
factory
import {signOut} from '@cerebral/firebase/operators'
export default [
signOut(),
signOut(), {
success: [],
error: []
}
]
output
No output
sendPasswordResetEmail
action
function someAction({ firebase, state }) {
return firebase.sendPasswordResetEmail(state.get('user.email'))
}
factory
import {state} from 'cerebral/tags'
import {sendPasswordResetEmail} from '@cerebral/firebase/operators'
export default [
sendPasswordResetEmail(state`user.email`),
sendPasswordResetEmail(state`user.email`), {
success: [],
error: []
}
]
output
No output
setOnDisconnect
Sets a value when Firebase detects user has disconnected.
action
function someAction({ firebase, state }) {
firebase.setOnDisconnect(`activeUsers.${state.get('app.user.uid')}`, 'someValue')
}
factory
import {state} from 'cerebral/tags'
import {setOnDisconnect} from '@cerebral/firebase/operators'
export default [
setOnDisconnect(string`activeUsers.${state`app.user.uid`}`, null)
]
output
No output
cancelOnDisconnect
Cancel setting a value when Firebase detects disconnect.
action
function someAction({ firebase, state }) {
firebase.cancelOnDisconnect()
}
factory
import {state} from 'cerebral/tags'
import {cancelOnDisconnect} from '@cerebral/firebase/operators'
export default [
cancelOnDisconnect()
]
output
No output
put
Upload a new file at the given location. Please note that the file is not stored inside the realtime database but into Google Cloud Storage (please consult filrebase documentation). This means that you need to take care of storage security as well.
Note that put
expects a folder as first argument and will use the name of the provided file. If you want to control the filename, add this in the options. In this case, make sure to respect file type and extension...
action
function someAction({ firebase, props }) {
return firebase.put('folderName', props.file, {
progress({progress, bytesTransferred, totalBytes, state}) {
},
filename: 'customName.png'
})
}
factory
import {props, signal, string, state} from 'cerebral/tags'
import {put} from '@cerebral/firebase/operators'
export default [
put(string`posts.all.${props`postId`}`, props`file`, {
progress: signal`gallery.progress`
progress: state`gallery.progress`
}),
put(string`posts.all.${props`postId`}`, props`file`, {
progress: signal`gallery.progress`
progress: state`gallery.progress`
}), {
success: [],
error: []
}
]
output
{url: 'urlToFile', filename: 'nameOfFile'}
delete
Use delete
to remove an uploaded file. Specify the containing folder and filename.
action
function someAction({ firebase, props }) {
return firebase.delete('folderName', props.fileName)
}
factory
import {props, state, string} from 'cerebral/tags'
import {delete as firebaseDelete} from '@cerebral/firebase/operators'
export default [
firebaseDelete(
string`posts.all.${props`postId`}`,
state`posts.all.${props`postId`}.imageName`
),
firebaseDelete(
string`posts.all.${props`postId`}`,
state`posts.all.${props`postId`}.imageName`
), {
success: [],
error: []
}
]
output
No output