Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-ashton committed Sep 22, 2024
1 parent 46b18e6 commit 911dc1c
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 88 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firebase-lift",
"version": "0.0.5",
"version": "1.0.0",
"description": "Firebase provides a variety of tools that are amazing. This wraps various aspects of the api.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
97 changes: 87 additions & 10 deletions src/BatchRunner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import firebase from 'firebase/compat/app';
import type firebase from 'firebase/compat/app';
import {
BatchTask,
BatchTaskEmpty,
Expand All @@ -7,14 +7,21 @@ import {
MagicServerTimestampString
} from './models';
import { generateFirestorePathFromObject, defaultEmptyTask } from './misc';
import { DocumentWriteChange } from './FirestoreLiftCollection';

export class BatchRunner {
public firestoreModule: typeof firebase.firestore;
public app: firebase.app.App;
public onDocumentsWritten: (docData: DocumentWriteChange[]) => Promise<void>;

constructor(config: { firestoreModule: typeof firebase.firestore; app: firebase.app.App }) {
constructor(config: {
firestoreModule: typeof firebase.firestore;
app: firebase.app.App;
onDocumentsWritten: (docData: DocumentWriteChange[]) => Promise<void>;
}) {
this.firestoreModule = config.firestoreModule;
this.app = config.app;
this.onDocumentsWritten = config.onDocumentsWritten;
}

// We use a magic string for deletes so we can pass around batches of change sets to be environment agnostic
Expand Down Expand Up @@ -50,11 +57,16 @@ export class BatchRunner {
}

async executeBatch(b: BatchTask[], opts?: { transaction?: firebase.firestore.Transaction }) {
b = b.filter((q) => q); //Filter out falsey

const firestoreInstance = this.firestoreModule('isFakeFirestoreApp' in this.app ? undefined : this.app);

const batch = (opts?.transaction ?? firestoreInstance.batch()) as firebase.firestore.WriteBatch;

const __updatedAtMS = Date.now();

try {
const documentWriteChanges: DocumentWriteChange[] = [];
for (let i = 0; i < b.length; i++) {
let task = b[i];
if (task.type === 'empty') {
Expand All @@ -64,15 +76,53 @@ export class BatchRunner {
if (!task.id) {
throw Error(`Unable to process item. Lacks an id. Collection: ${task.collection}. Task Type: ${task.type}`);
}

if (task.type === 'update' || task.type === 'updateShallow') {
documentWriteChanges.push({
collection: task.collection,
docId: task.id,
__updatedAtMS,
type: 'update',
docChanges: task.doc
});
} else if (task.type === 'delete') {
documentWriteChanges.push({
collection: task.collection,
docId: task.id,
__updatedAtMS,
type: 'delete'
});
} else {
documentWriteChanges.push({
collection: task.collection,
docId: task.id,
__updatedAtMS,
type: 'other'
});
}

let ref = firestoreInstance.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 });
batch.set(
ref,
this.scrubDataPreWrite({ obj: cloneDeep({ ...task.doc, __updatedAtMS }), removeEmptyObjects: false }),
{
merge: true
}
);
break;
case 'set':
batch.set(ref, this.scrubDataPreWrite({ obj: task.doc, removeEmptyObjects: false }), { merge: false });
batch.set(
ref,
this.scrubDataPreWrite({ obj: cloneDeep({ ...task.doc, __updatedAtMS }), removeEmptyObjects: false }),
{
merge: false
}
);
break;
case 'setPath':
let p = generateFirestorePathFromObject(task.pathObj);
Expand All @@ -83,16 +133,16 @@ export class BatchRunner {
return acc[val];
}, task.value);
newPathVal = this.scrubDataPreWrite({ obj: newPathVal, removeEmptyObjects: false });
batch.update(ref, p.path, newPathVal);
batch.update(ref, p.path, newPathVal, '__updatedAtMS', __updatedAtMS);
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 });
newObj = this.scrubDataPreWrite({ obj: cloneDeep(task.doc), removeEmptyObjects: true });
batch.set(ref, { ...newObj, __updatedAtMS }, { merge: true });
break;
case 'updateShallow':
newObj = this.scrubDataPreWrite({ obj: task.doc, removeEmptyObjects: false });
batch.update(ref, newObj);
newObj = this.scrubDataPreWrite({ obj: cloneDeep(task.doc), removeEmptyObjects: false });
batch.update(ref, { ...newObj, __updatedAtMS });
break;
case 'delete':
batch.delete(ref);
Expand All @@ -104,7 +154,21 @@ export class BatchRunner {
}

if (!opts?.transaction) {
return batch.commit().then(() => defaultEmptyTask);
await batch.commit();
try {
await Promise.race([
this.onDocumentsWritten(documentWriteChanges),
new Promise((res) => {
setTimeout(() => {
res(null);
}, 500);
})
]);
} catch (e) {
console.error('error-on-documents-written');
console.error(e);
}
return defaultEmptyTask;
} else {
return defaultEmptyTask;
}
Expand All @@ -115,3 +179,16 @@ export class BatchRunner {
}
}
}

function cloneDeep(obj: any) {
if (obj === null) return null;
let clone = Object.assign({}, obj);
for (let i in clone) {
if (clone[i] != null && typeof clone[i] == 'object') clone[i] = cloneDeep(clone[i]);
}
if (Array.isArray(obj)) {
clone.length = obj.length;
return Array.from(clone);
}
return clone;
}
9 changes: 7 additions & 2 deletions src/FirestoreLift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import {
MagicServerTimestampString,
FirestoreLiftStats
} from './models';
import * as _ from 'lodash';
import { FirestoreLiftCollection } from './FirestoreLiftCollection';
import { BatchRunner } from './BatchRunner';

// Expects a generic of a very specific shape. See examples
export function createFirestoreLift<T>(config: FirestoreLiftInitConfig): T & FirestoreLiftRoot {
const batchRunner = new BatchRunner({ app: config.firebaseApp, firestoreModule: config.firestoreModule });
const batchRunner = new BatchRunner({
app: config.firebaseApp,
firestoreModule: config.firestoreModule,
onDocumentsWritten: config.onDocumentsWritten
});

const pendingFirestoreLift: any = {
_GetStats: () => {
Expand All @@ -35,7 +40,7 @@ export function createFirestoreLift<T>(config: FirestoreLiftInitConfig): T & Fir
s.byCollection[c] = f._stats;
});

return s;
return _.cloneDeep(s);
},
_setFirestoreLiftDisabledStatus: (isDisabled: boolean) => {
Object.keys(config.collections).forEach((collectionName) => {
Expand Down
Loading

0 comments on commit 911dc1c

Please sign in to comment.