Skip to content

Commit

Permalink
Make the browser adaptor lazy
Browse files Browse the repository at this point in the history
  • Loading branch information
kossnocorp committed Apr 16, 2020
1 parent c4d3dc0 commit 0887f19
Show file tree
Hide file tree
Showing 29 changed files with 438 additions and 344 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ This change log follows the format documented in [Keep a CHANGELOG].

- **BREAKING**: `ModelUpdate` renamed to `UpdateModel` for consitency with `SetModel` and `MergeModel`.

- **BREAKING**: `ref` now don't generate id if the second argument is omitted, use `id` function to generate new id instead.

- `update` now allows passing partial data into nested fields. Previously only root fields were optional.

- Now the browser adaptor imports `firebase/app` and `firebase/firestore` on-demand (using ESM's `import()`) rather than in the root level of the library. That dramatically improves initial paint time and helps with bundle caching. Now every time you make a small change in the app, the user won't have to download `firestore` modules as well.

### Added

- Added new `upset`, `batch.set` and `transaction.set` functions that sets or updates the value of given document. It replaces `merge` option available in the previous version of Typesaurus.

-

## 5.4.0 - 2020-04-14

### Fixes
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typesaurus",
"version": "5.4.0",
"version": "6.0.0-alpha.1",
"description": "Type-safe ODM for Firestore",
"keywords": [
"Firebase",
Expand Down
40 changes: 0 additions & 40 deletions src/adaptor/browser.ts

This file was deleted.

40 changes: 40 additions & 0 deletions src/adaptor/browser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Browser Firestore adaptor.
*/

import * as firebase from 'firebase/app'
import 'firebase/firestore'
import { getAll } from '../utils'

export default async function adaptor() {
const firestore = firebase.firestore()
// At the moment, the browser's Firestore adaptor doesn't support getAll.
// Get rid of the fallback when the issue is closed:
// https://github.com/firebase/firebase-js-sdk/issues/1176
if (!('getAll' in firestore)) return Object.assign(firestore, { getAll })

return {
firestore,
consts: {
DocumentReference: firebase.firestore.DocumentReference,
Timestamp: firebase.firestore.Timestamp,
FieldValue: firebase.firestore.FieldValue
}
}
}

export function injectAdaptor() {
throw new Error(
'Injecting adaptor is not supported in the browser environment'
)
}

// export type FirestoreQuery = firebase.firestore.Query
// export type FirestoreDocumentReference = firebase.firestore.DocumentReference
// export type FirestoreDocumentData = firebase.firestore.DocumentData
// export type FirestoreTimestamp = firebase.firestore.Timestamp
// export type FirestoreCollectionReference = firebase.firestore.CollectionReference
// export type FirestoreOrderByDirection = firebase.firestore.OrderByDirection
// export type FirestoreWhereFilterOp = firebase.firestore.WhereFilterOp
// export type FirestoreTransaction = firebase.firestore.Transaction
// export type FirebaseWriteBatch = firebase.firestore.WriteBatch
41 changes: 41 additions & 0 deletions src/adaptor/browser/lazy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Lazy browser Firestore adaptor.
*/

import { getAll } from '../utils'

export default async function adaptor() {
const { default: firebase } = await import('firebase/app')
await import('firebase/firestore')

const firestore = firebase.firestore()
// At the moment, the browser's Firestore adaptor doesn't support getAll.
// Get rid of the fallback when the issue is closed:
// https://github.com/firebase/firebase-js-sdk/issues/1176
if (!('getAll' in firestore)) return Object.assign(firestore, { getAll })

return {
firestore,
consts: {
DocumentReference: firebase.firestore.DocumentReference,
Timestamp: firebase.firestore.Timestamp,
FieldValue: firebase.firestore.FieldValue
}
}
}

export function injectAdaptor() {
throw new Error(
'Injecting adaptor is not supported in the browser environment'
)
}

// export type FirestoreQuery = type import('firebase').firestore.Query
// export type FirestoreDocumentReference = firebase.firestore.DocumentReference
// export type FirestoreDocumentData = firebase.firestore.DocumentData
// export type FirestoreTimestamp = firebase.firestore.Timestamp
// export type FirestoreCollectionReference = firebase.firestore.CollectionReference
// export type FirestoreOrderByDirection = firebase.firestore.OrderByDirection
// export type FirestoreWhereFilterOp = firebase.firestore.WhereFilterOp
// export type FirestoreTransaction = firebase.firestore.Transaction
// export type FirebaseWriteBatch = firebase.firestore.WriteBatch
3 changes: 3 additions & 0 deletions src/adaptor/browser/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"module": "./lazy"
}
22 changes: 13 additions & 9 deletions src/adaptor/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,34 @@
import * as firestore from '@google-cloud/firestore'
import * as admin from 'firebase-admin'

export type AdaptorFirestore = () => admin.firestore.Firestore

const adminFirestore = () => admin.firestore()
let currentFirestore: AdaptorFirestore = adminFirestore

export default function store() {
return currentFirestore()
export type Adaptor = {
firestore: admin.firestore.Firestore
consts: AdaptorConsts
}

export type AdaptorFirestore = () => admin.firestore.Firestore

export type AdaptorConsts = {
DocumentReference: typeof admin.firestore.DocumentReference
Timestamp: typeof admin.firestore.Timestamp
FieldValue: typeof admin.firestore.FieldValue
}

const adminFirestore = () => admin.firestore()
let currentFirestore: AdaptorFirestore = adminFirestore

const adminConsts = {
DocumentReference: admin.firestore.DocumentReference,
Timestamp: admin.firestore.Timestamp,
FieldValue: admin.firestore.FieldValue
}
let currentConsts: AdaptorConsts = adminConsts

export function consts(): AdaptorConsts {
return currentConsts
export default async function adaptor() {
return {
firestore: currentFirestore(),
consts: currentConsts
}
}

export function injectAdaptor(
Expand Down
7 changes: 4 additions & 3 deletions src/add/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import firestore from '../adaptor'
import adaptor from '../adaptor'
import { Collection } from '../collection'
import { unwrapData } from '../data'
import { ref } from '../ref'
Expand Down Expand Up @@ -36,8 +36,9 @@ export default async function add<Model>(
collection: Collection<Model>,
data: AddModel<Model>
) {
const firebaseDoc = await firestore()
const a = await adaptor()
const firebaseDoc = await a.firestore
.collection(collection.path)
.add(unwrapData(data))
.add(unwrapData(a, data))
return ref(collection, firebaseDoc.id)
}
9 changes: 4 additions & 5 deletions src/all/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Collection } from '../collection'
import firestore from '../adaptor'
import adaptor from '../adaptor'
import { doc, Doc } from '../doc'
import { ref } from '../ref'
import { wrapData } from '../data'
Expand Down Expand Up @@ -29,10 +29,9 @@ import { wrapData } from '../data'
export default async function all<Model>(
collection: Collection<Model>
): Promise<Doc<Model>[]> {
const firebaseSnap = await firestore()
.collection(collection.path)
.get()
const a = await adaptor()
const firebaseSnap = await a.firestore.collection(collection.path).get()
return firebaseSnap.docs.map(d =>
doc(ref(collection, d.id), wrapData(d.data()) as Model)
doc(ref(collection, d.id), wrapData(a, d.data()) as Model)
)
}
92 changes: 56 additions & 36 deletions src/batch/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import firestore from '../adaptor'
import adaptor, { Adaptor, FirebaseWriteBatch } from '../adaptor'
import { Collection } from '../collection'
import { Ref } from '../ref'
import { unwrapData } from '../data'
Expand Down Expand Up @@ -200,7 +200,12 @@ export interface Batch {
* @returns The batch API object.
*/
export function batch(): Batch {
const firestoreBatch = firestore().batch()
const commands: BatchCommand[] = []

const firestoreBatch = lazy(async () => {
const { firestore } = await adaptor()
return firestore.batch()
})

function set<Model>(
collectionOrRef: Collection<Model> | Ref<Model>,
Expand All @@ -222,12 +227,12 @@ export function batch(): Batch {
data = idOrData as SetModel<Model>
}

const firestoreDoc = firestore()
.collection(collection.path)
.doc(id)
// ^ above
// TODO: Refactor code above and below because is all the same as in the regular set function
firestoreBatch.set(firestoreDoc, unwrapData(data))
commands.push((adaptor, firestoreBatch) => {
const firestoreDoc = adaptor.firestore.collection(collection.path).doc(id)
// ^ above
// TODO: Refactor code above and below because is all the same as in the regular set function
firestoreBatch.set(firestoreDoc, unwrapData(adaptor, data))
})
}

function upset<Model>(
Expand All @@ -250,12 +255,14 @@ export function batch(): Batch {
data = idOrData as UpsetModel<Model>
}

const firestoreDoc = firestore()
.collection(collection.path)
.doc(id)
// ^ above
// TODO: Refactor code above and below because is all the same as in the regular set function
firestoreBatch.set(firestoreDoc, unwrapData(data), { merge: true })
commands.push((adaptor, firestoreBatch) => {
const firestoreDoc = adaptor.firestore.collection(collection.path).doc(id)
// ^ above
// TODO: Refactor code above and below because is all the same as in the regular set function
firestoreBatch.set(firestoreDoc, unwrapData(adaptor, data), {
merge: true
})
})
}

function update<Model>(
Expand All @@ -278,21 +285,21 @@ export function batch(): Batch {
data = idOrData as Model
}

const firebaseDoc = firestore()
.collection(collection.path)
.doc(id)
const updateData = Array.isArray(data)
? data.reduce(
(acc, { key, value }) => {
acc[Array.isArray(key) ? key.join('.') : key] = value
return acc
},
{} as { [key: string]: any }
)
: data
// ^ above
// TODO: Refactor code above because is all the same as in the regular update function
firestoreBatch.update(firebaseDoc, unwrapData(updateData))
commands.push((adaptor, firestoreBatch) => {
const firebaseDoc = adaptor.firestore.collection(collection.path).doc(id)
const updateData = Array.isArray(data)
? data.reduce(
(acc, { key, value }) => {
acc[Array.isArray(key) ? key.join('.') : key] = value
return acc
},
{} as { [key: string]: any }
)
: data
// ^ above
// TODO: Refactor code above because is all the same as in the regular update function
firestoreBatch.update(firebaseDoc, unwrapData(adaptor, updateData))
})
}

function remove<Model>(
Expand All @@ -311,17 +318,30 @@ export function batch(): Batch {
id = ref.id
}

const firebaseDoc = firestore()
.collection(collection.path)
.doc(id)
// ^ above
// TODO: Refactor code above because is all the same as in the regular remove function
firestoreBatch.delete(firebaseDoc)
commands.push((adaptor, firestoreBatch) => {
const firebaseDoc = adaptor.firestore.collection(collection.path).doc(id)
// ^ above
// TODO: Refactor code above because is all the same as in the regular remove function
firestoreBatch.delete(firebaseDoc)
})
}

async function commit() {
await firestoreBatch.commit()
const a = await adaptor()
const b = a.firestore.batch()
commands.forEach(fn => fn(a, b))
await b.commit()
}

return { set, upset, update, remove, commit }
}

type BatchCommand = (adaptor: Adaptor, batch: FirebaseWriteBatch) => void

function lazy<Type>(fn: () => Promise<Type>): () => Promise<Type> {
let instance: Type | undefined = undefined
return async () => {
if (instance === undefined) instance = await fn()
return instance
}
}
Loading

0 comments on commit 0887f19

Please sign in to comment.