Skip to content

Added custom createSnapshot support #291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions packages/@posva/vuefire-core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
walkSet
} from './utils'

const DEFAULT_OPTIONS = { maxRefDepth: 2, reset: true }
const DEFAULT_OPTIONS = { maxRefDepth: 2, reset: true, serialize: createSnapshot }
export { DEFAULT_OPTIONS as firestoreOptions }

export * from './rtdb'

Expand Down Expand Up @@ -94,7 +95,7 @@ export function bindCollection (
added: ({ newIndex, doc }) => {
arraySubs.splice(newIndex, 0, Object.create(null))
const subs = arraySubs[newIndex]
const snapshot = createSnapshot(doc)
const snapshot = options.serialize(doc)
const [data, refs] = extractRefs(snapshot)
// NOTE use ops
ops.add(array, newIndex, data)
Expand All @@ -118,7 +119,7 @@ export function bindCollection (
// NOTE use ops
const oldData = ops.remove(array, oldIndex)[0]
// const oldData = array.splice(oldIndex, 1)[0]
const snapshot = createSnapshot(doc)
const snapshot = options.serialize(doc)
const [data, refs] = extractRefs(snapshot, oldData)
// NOTE use ops
ops.add(array, newIndex, data)
Expand Down Expand Up @@ -224,7 +225,7 @@ function subscribeToDocument (
if (doc.exists) {
updateDataFromDocumentSnapshot(
{
snapshot: createSnapshot(doc),
snapshot: options.serialize(doc),
target,
path,
ops,
Expand Down Expand Up @@ -273,7 +274,7 @@ export function bindDocument (
if (doc.exists) {
updateDataFromDocumentSnapshot(
{
snapshot: createSnapshot(doc),
snapshot: options.serialize(doc),
target: vm,
path: key,
subs,
Expand Down
10 changes: 6 additions & 4 deletions packages/@posva/vuefire-core/src/rtdb.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createRecordFromRTDBSnapshot, indexForKey } from './utils'

const DEFAULT_OPTIONS = { reset: true }
const DEFAULT_OPTIONS = { reset: true, serialize: createRecordFromRTDBSnapshot }

export { DEFAULT_OPTIONS as rtdbOptions }

export function rtdbBindAsObject (
{ vm, key, document, resolve, reject, ops },
Expand All @@ -10,7 +12,7 @@ export function rtdbBindAsObject (
const listener = document.on(
'value',
snapshot => {
ops.set(vm, key, createRecordFromRTDBSnapshot(snapshot))
ops.set(vm, key, options.serialize(snapshot))
},
reject
)
Expand All @@ -37,7 +39,7 @@ export function rtdbBindAsArray (
'child_added',
(snapshot, prevKey) => {
const index = prevKey ? indexForKey(array, prevKey) + 1 : 0
ops.add(array, index, createRecordFromRTDBSnapshot(snapshot))
ops.add(array, index, options.serialize(snapshot))
},
reject
)
Expand All @@ -56,7 +58,7 @@ export function rtdbBindAsArray (
ops.set(
array,
indexForKey(array, snapshot.key),
createRecordFromRTDBSnapshot(snapshot)
options.serialize(snapshot)
)
},
reject
Expand Down
6 changes: 3 additions & 3 deletions packages/@posva/vuefire-core/src/utils.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/**
* @typedef {firebase.firestore.DocumentReference | firebase.firestore.CollectionReference} Reference
*/

/**
*
* @param {firebase.firestore.DocumentSnapshot} doc
* @return {DocumentData}
* @returns {DocumentData}
*/
export function createSnapshot (doc) {
// defaults everything to false, so no need to set
Expand All @@ -25,8 +26,7 @@ function isObject (o) {
/**
*
* @param {any} o
* should be o is Date https://github.com/Microsoft/TypeScript/issues/26297
* @returns {boolean}
* @returns {o is Date}
*/
function isTimestamp (o) {
return o.toDate
Expand Down
83 changes: 83 additions & 0 deletions packages/@posva/vuefire-core/test/options.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { bindDocument, walkSet, firestoreOptions, bindCollection } from '../src'
import { db, createOps } from '@posva/vuefire-test-helpers'

describe('options', () => {
let collection, document, vm, resolve, reject, ops
beforeEach(async () => {
collection = db.collection()
document = collection.doc()
ops = createOps(walkSet)
vm = {}
await document.update({ foo: 'foo' })
})

it('allows customizing serialize when calling bindDocument', async () => {
const spy = jest.fn(() => ({ bar: 'foo' }))
await new Promise((res, rej) => {
resolve = jest.fn(res)
reject = jest.fn(rej)
bindDocument(
{ vm, key: 'foo', document, resolve, reject, ops },
{ serialize: spy }
)
})
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith(
expect.objectContaining({ data: expect.any(Function) })
)
expect(vm.foo).toEqual({ bar: 'foo' })
})

it('allows customizing serialize when calling bindCollection', async () => {
const spy = jest.fn(() => ({ bar: 'foo' }))
await new Promise((res, rej) => {
resolve = jest.fn(res)
reject = jest.fn(rej)
bindCollection(
{ vm, key: 'foo', collection, resolve, reject, ops },
{ serialize: spy }
)
})
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith(
expect.objectContaining({ data: expect.any(Function) })
)
expect(vm.foo).toEqual([{ bar: 'foo' }])
})

it('can set options globally for bindDocument', async () => {
const { serialize } = firestoreOptions
const spy = jest.fn(() => ({ bar: 'foo' }))
firestoreOptions.serialize = spy
await new Promise((res, rej) => {
resolve = jest.fn(res)
reject = jest.fn(rej)
bindDocument({ vm, key: 'foo', document, resolve, reject, ops })
})
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith(
expect.objectContaining({ data: expect.any(Function) })
)
expect(vm.foo).toEqual({ bar: 'foo' })
// restore it
firestoreOptions.serialize = serialize
})

it('can set options globally for bindCollection', async () => {
const { serialize } = firestoreOptions
const spy = jest.fn(() => ({ bar: 'foo' }))
firestoreOptions.serialize = spy
await new Promise((res, rej) => {
resolve = jest.fn(res)
reject = jest.fn(rej)
bindCollection({ vm, key: 'foo', collection, resolve, reject, ops })
})
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith(
expect.objectContaining({ data: expect.any(Function) })
)
expect(vm.foo).toEqual([{ bar: 'foo' }])
// restore it
firestoreOptions.serialize = serialize
})
})
3 changes: 1 addition & 2 deletions packages/@posva/vuefire-core/test/rtdb/as-array.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { rtdbBindAsArray, walkSet } from '../../src'
import { createOps } from '@posva/vuefire-test-helpers'
import { MockFirebase } from '@posva/vuefire-test-helpers'
import { MockFirebase, createOps } from '@posva/vuefire-test-helpers'

describe('RTDB collection', () => {
let collection, vm, resolve, reject, unbind
Expand Down
132 changes: 132 additions & 0 deletions packages/@posva/vuefire-core/test/rtdb/options.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import {
walkSet,
rtdbBindAsObject,
rtdbBindAsArray,
rtdbOptions
} from '../../src'
import { MockFirebase, createOps } from '@posva/vuefire-test-helpers'

describe('options', () => {
let collection, document, vm, unbind
const ops = createOps(walkSet)
beforeEach(async () => {
collection = new MockFirebase().child('data')
document = new MockFirebase().child('data')
vm = {}
})

afterEach(() => {
unbind && unbind()
})

it('allows customizing serialize when calling bindDocument', async () => {
const spy = jest.fn(() => ({ bar: 'foo' }))
await new Promise((resolve, reject) => {
unbind = rtdbBindAsObject(
{
vm,
key: 'item',
document,
resolve,
reject,
ops
},
{ serialize: spy }
)
document.set({ foo: 'foo' })
document.flush()
})

expect(spy).toHaveBeenCalledTimes(2)
expect(spy).toHaveBeenLastCalledWith(
expect.objectContaining({ val: expect.any(Function) })
)
expect(vm.item).toEqual({ bar: 'foo' })
})

it('allows customizing serialize when calling bindCollection', async () => {
const spy = jest.fn(() => ({ bar: 'foo' }))

await new Promise((resolve, reject) => {
unbind = rtdbBindAsArray(
{
vm,
key: 'items',
collection,
resolve,
reject,
ops
},
{ serialize: spy }
)
collection.push({ foo: 'foo' })
collection.flush()
})

expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith(
expect.objectContaining({ val: expect.any(Function) })
)
expect(vm.items).toEqual([{ bar: 'foo' }])
})

it('can set options globally for bindDocument', async () => {
const { serialize } = rtdbOptions
const spy = jest.fn(() => ({ bar: 'foo' }))
rtdbOptions.serialize = spy

await new Promise((resolve, reject) => {
unbind = rtdbBindAsObject(
{
vm,
key: 'item',
document,
resolve,
reject,
ops
},
{ serialize: spy }
)
document.set({ foo: 'foo' })
document.flush()
})

expect(spy).toHaveBeenCalledTimes(2)
expect(spy).toBeCalledWith(
expect.objectContaining({ val: expect.any(Function) })
)
expect(vm.item).toEqual({ bar: 'foo' })
// restore it
rtdbOptions.serialize = serialize
})

it('can set options globally for bindCollection', async () => {
const { serialize } = rtdbOptions
const spy = jest.fn(() => ({ bar: 'foo' }))
rtdbOptions.serialize = spy

await new Promise((resolve, reject) => {
unbind = rtdbBindAsArray(
{
vm,
key: 'items',
collection,
resolve,
reject,
ops
},
{ serialize: spy }
)
collection.push({ foo: 'foo' })
collection.flush()
})

expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith(
expect.objectContaining({ val: expect.any(Function) })
)
expect(vm.items).toEqual([{ bar: 'foo' }])
// restore it
rtdbOptions.serialize = serialize
})
})
32 changes: 32 additions & 0 deletions packages/documentation/docs/api/vuefire.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,37 @@ Vue.use(firestorePlugin, options)

- `bindName`: name for the [`$bind`](#bind) method added to all Vue components. Defaults to `$bind`.
- `unbindName`: name for the [`$unbind`](#unbind) method added to all Vue components. Defaults to `$unbind`.
- `createSnapshot`: a function to provide a custom binding strategy when a document is received from firebase

`createSnapshot` is a function that receives a [DocumentSnapshot](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot) as argument and returns the object that is going to be bound to your component instance.

NOTE: when using the `createSnapshot` option you won't have the document `id` automatically bound to your data property. It's supposed to be used only for advanced cases, like adding `distance` to snapshots when using [Geofirestore](https://github.com/geofirestore/geofirestore-js/).

For instance:
```js
export default {
data () {
return {
todos: []
}
},
created () {
const options = {
createSnapshot: (documentSnapshot) => {
// the object below is going to be bound to todos.
// Since isTodo and customId aren't enumerable properties,
// they won't be passed when persisting data to firestore.
return Object.defineProperties(documentSnapshot.data(), {
isTodo: { value: true },
// mannually adding the id
customId: { value: documentSnapshot.id }
})
}
}
this.$bind('todos', db.collection('todos'), options)
}
}
```

## `firestore` option

Expand Down Expand Up @@ -120,6 +151,7 @@ Object that can contain the following properties:

- `maxRefDepth`: How many levels of nested references should be automatically bound. Defaults to 2, meaning that References inside of References inside of documents bound with `$bind` will automatically be bound too.
- `reset`: Allows to define the behavior when a reference is unbound. Defaults to `true`, which resets the property in the vue instance to `null` for documents and to an empty array `[]` for collections. It can also be set to a function returning a value to customize the value set. Setting it to `false` will keep the data as-is when unbounding.
- `createSnapshot`: A function that defines a custom snapshot binding strategy. It works just like the global createSnapshot in [firestorePlugin](#firestoreplugin) but only for the reference being bound.

## \$unbind

Expand Down
Loading