-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1878dd3
commit 03c736f
Showing
20 changed files
with
3,039 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
root = true | ||
|
||
# Unix-style newlines with a newline ending every file | ||
[*] | ||
end_of_line = lf | ||
insert_final_newline = true | ||
|
||
indent_style = space | ||
indent_size = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"projects": { | ||
"default": "fir-lift" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
dist | ||
.idea | ||
|
||
.vscode | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
*.pid.lock | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# nyc test coverage | ||
.nyc_output | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# Bower dependency directory (https://bower.io/) | ||
bower_components | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (https://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directories | ||
node_modules/ | ||
jspm_packages/ | ||
|
||
# TypeScript v1 declaration files | ||
typings/ | ||
|
||
# Optional npm cache directory | ||
.npm | ||
|
||
# Optional eslint cache | ||
.eslintcache | ||
|
||
# Optional REPL history | ||
.node_repl_history | ||
|
||
# Output of 'npm pack' | ||
*.tgz | ||
|
||
# Yarn Integrity file | ||
.yarn-integrity | ||
|
||
# dotenv environment variables file | ||
.env | ||
|
||
# next.js build output | ||
.next |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"printWidth": 120, | ||
"tabWidth": 2, | ||
"useTabs": false, | ||
"semi": true, | ||
"trailingComma": "none", | ||
"bracketSpacing": true, | ||
"arrowParens": "always", | ||
"singleQuote": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"firestore": { | ||
"rules": "firestore.rules", | ||
"indexes": "firestore.indexes.json" | ||
}, | ||
"emulators": { | ||
"firestore": { | ||
"port": 8080 | ||
}, | ||
"database": { | ||
"port": 9000 | ||
}, | ||
"ui": { | ||
"enabled": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "firebase-lift", | ||
"version": "0.0.1", | ||
"description": "", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"test": "node dist/tests/index.js", | ||
"ts-build": "tsc ", | ||
"ts-watch": "tsc -w", | ||
"start": "firebase emulators:start" | ||
}, | ||
"devDependencies": { | ||
"@firebase/testing": "^0.19.5", | ||
"@types/json-stable-stringify": "^1.0.32", | ||
"@types/lodash": "^4.14.158", | ||
"@types/md5": "^2.2.0", | ||
"@types/shortid": "^0.0.29", | ||
"nano-test-runner": "^1.2.0", | ||
"prettier": "^2.0.5", | ||
"typescript": "^3.9.3" | ||
}, | ||
"dependencies": { | ||
"firebase": "^7.14.5", | ||
"json-stable-stringify": "^1.0.1", | ||
"lodash": "^4.17.19", | ||
"md5": "^2.2.1", | ||
"shortid": "^2.2.15" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/kevin-ashton/firebase-lift.git" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"bugs": { | ||
"url": "https://github.com/kevin-ashton/firebase-lift/issues" | ||
}, | ||
"homepage": "https://github.com/kevin-ashton/firebase-lift#readme" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# Firebase Lift | ||
|
||
Firebase provides a variety of tools that are amazing. This wraps various aspects of the api. | ||
|
||
## Firestore | ||
|
||
### Features | ||
* Types on returned documents | ||
* Types on various CRUD functions | ||
* Types for query construction | ||
* Ability group queries/doc fetches | ||
* Metrics that track doc read/writes for various collections | ||
|
||
## Limitations | ||
* Firestore caching is always disabled | ||
* Sub collections are not supported | ||
* Server timestamps are not supported | ||
* Array filters are currently not supported | ||
* Only supports basic types string, number, array, maps. No support for geo data, timestamps, etc. | ||
* Increment is limited to a single number increment (no jumping by multiple numbers, or decrementing) | ||
* startAt, startAfter, endAt, endBefore are supported for values but not for firestore docs or query docs. In other words you must use a value and not a firestore document when using those filters. | ||
|
||
## Realtime Database | ||
|
||
### Features | ||
* Add some types for objects/primatives | ||
|
||
### Limitations | ||
* Only covers part of the API. You can access the raw refs to do everything normally without types. | ||
|
||
## Usage | ||
|
||
```ts | ||
import { | ||
createRtdbLift, | ||
createFirestoreLift, | ||
FirestoreLiftCollection | ||
TypedFirebaseObjectOrPrimativeRefGenerator | ||
} from '../RTDB'; | ||
import * as firebase from 'firebase'; | ||
|
||
interface Person { | ||
name: string; | ||
age: number; | ||
} | ||
|
||
interface Book { | ||
title: string; | ||
year: number; | ||
} | ||
|
||
interface Heartbeat { | ||
dateMs: number; | ||
msg: string; | ||
} | ||
|
||
interface DeviceInfo { | ||
dateMs: number; | ||
dId: string; | ||
} | ||
|
||
const app = firebase.initializeApp(testFirebaseConfig); | ||
|
||
const firestoreLiftExample = createFirestoreLift<ExampleFirestore>({ | ||
collections: { | ||
Person: { | ||
collection: 'person' | ||
}, | ||
Book: { | ||
collection: 'book' | ||
} | ||
}, | ||
firebaseApp: app, | ||
firestoreModule: firebase.firestore | ||
}); | ||
|
||
const rtdbLiftExample = createRtdbLift({firebaseApp: app, { | ||
person: (null as unknown) as TypedFirebaseObjectOrPrimativeRefGenerator<Person>, | ||
book: (null as unknown) as TypedFirebaseObjectOrPrimativeRefGenerator<Book> | ||
}}); | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import * as firebase from 'firebase'; | ||
import { | ||
BatchTask, | ||
BatchTaskEmpty, | ||
MagicDeleteString, | ||
MagicIncrementString, | ||
MagicServerTimestampString | ||
} from './models'; | ||
import { generateFirestorePathFromObject, defaultEmptyTask } from './misc'; | ||
|
||
export class BatchRunner { | ||
public firestoreModule: typeof firebase.firestore; | ||
public app: firebase.app.App; | ||
|
||
constructor(config: { firestoreModule: typeof firebase.firestore; app: firebase.app.App }) { | ||
this.firestoreModule = config.firestoreModule; | ||
this.app = config.app; | ||
} | ||
|
||
// We use a magic string for deletes so we can pass around batches of change sets to be environment agnostic | ||
private scrubDataPreWrite(p: { obj: any; removeEmptyObjects: boolean }) { | ||
const { obj, removeEmptyObjects } = p; | ||
|
||
if (typeof obj === 'object') { | ||
let keys = Object.keys(obj); | ||
for (let i = 0; i < keys.length; i++) { | ||
let k = keys[i]; | ||
if (obj[k] === MagicDeleteString) { | ||
obj[k] = this.firestoreModule.FieldValue.delete(); | ||
} else if (obj[k] === MagicIncrementString) { | ||
obj[k] = this.firestoreModule.FieldValue.increment(1); | ||
} else if (obj[k] === undefined || obj[k] === null) { | ||
//Undefined values get coerced to null by the Firestore SDK, which makes no sense for a strongly typed library like this | ||
delete obj[k]; | ||
} else if (typeof obj[k] === 'object') { | ||
if (removeEmptyObjects && Object.keys(obj[k]).length === 0) { | ||
delete obj[k]; | ||
} else { | ||
obj[k] = this.scrubDataPreWrite({ obj: obj[k], removeEmptyObjects }); | ||
} | ||
} else { | ||
obj[k] = obj[k]; | ||
} | ||
} | ||
} | ||
if (typeof obj === 'string' && obj === MagicDeleteString) { | ||
return this.firestoreModule.FieldValue.delete(); | ||
} | ||
return obj; | ||
} | ||
|
||
async executeBatch(b: BatchTask[]): Promise<BatchTaskEmpty> { | ||
let batch = this.firestoreModule(this.app).batch(); | ||
try { | ||
for (let i = 0; i < b.length; i++) { | ||
let task = b[i]; | ||
if (task.type === 'empty') { | ||
continue; | ||
} | ||
|
||
if (!task.id) { | ||
throw Error(`Unable to process item. Lacks an id. Collection: ${task.collection}. Task Type: ${task.type}`); | ||
} | ||
let ref = this.firestoreModule(this.app).collection(task.collection).doc(task.id); | ||
|
||
let newObj; | ||
switch (task.type) { | ||
case 'add': | ||
batch.set(ref, this.scrubDataPreWrite({ obj: task.doc, removeEmptyObjects: false }), { merge: true }); | ||
break; | ||
case 'set': | ||
batch.set(ref, this.scrubDataPreWrite({ obj: task.doc, removeEmptyObjects: false }), { merge: false }); | ||
break; | ||
case 'setPath': | ||
let p = generateFirestorePathFromObject(task.pathObj); | ||
let newPathVal = p.path.split('.').reduce((acc, val) => { | ||
if (acc[val] === undefined) { | ||
throw new Error('Missing value for setPath update'); | ||
} | ||
return acc[val]; | ||
}, task.value); | ||
newPathVal = this.scrubDataPreWrite({ obj: newPathVal, removeEmptyObjects: false }); | ||
batch.update(ref, p.path, newPathVal); | ||
break; | ||
case 'update': | ||
//firestore set merge has the very dumb default behavior of making empty objects overwriting the object entirely | ||
newObj = this.scrubDataPreWrite({ obj: task.doc, removeEmptyObjects: true }); | ||
batch.set(ref, newObj, { merge: true }); | ||
break; | ||
case 'delete': | ||
batch.delete(ref); | ||
break; | ||
default: | ||
// @ts-ignore | ||
throw new Error(`Unknown BatchTask type. Type: ${task.type}`); | ||
} | ||
} | ||
|
||
await batch.commit(); | ||
|
||
// Returning an empty task makes it easier for the helper functions (.add, .update) so they always return a batch type. Makes it so we don't have to check for undefined | ||
return defaultEmptyTask; | ||
} catch (e) { | ||
console.error(e); | ||
throw e; | ||
} | ||
} | ||
} |
Oops, something went wrong.