Skip to content

Commit 547085a

Browse files
Merge branch 'master' into mrschmidt-dropacksinschemamigration
2 parents e2aa083 + 9dbf6b0 commit 547085a

File tree

11 files changed

+145
-123
lines changed

11 files changed

+145
-123
lines changed

packages/firestore/src/api/database.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,8 +594,11 @@ export class Transaction implements firestore.Transaction {
594594
const doc = docs[0];
595595
if (doc instanceof NoDocument) {
596596
return new DocumentSnapshot(this._firestore, ref._key, null, false);
597+
} else if (doc instanceof Document) {
598+
return new DocumentSnapshot(this._firestore, ref._key, doc, false);
599+
} else {
600+
fail('MaybeDocument is neither Document nor NoDocument');
597601
}
598-
return new DocumentSnapshot(this._firestore, ref._key, doc, false);
599602
});
600603
}
601604

packages/firestore/src/core/sync_engine.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -679,20 +679,16 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
679679
queryView.targetId
680680
);
681681
this.limboDocumentRefs.removeReferencesForId(queryView.targetId);
682-
let p = PersistencePromise.resolve();
683-
limboKeys.forEach(limboKey => {
684-
p = p.next(() => {
685-
return this.limboDocumentRefs
686-
.containsKey(null, limboKey)
687-
.next(isReferenced => {
688-
if (!isReferenced) {
689-
// We removed the last reference for this key
690-
this.removeLimboTarget(limboKey);
691-
}
692-
});
693-
});
694-
});
695-
await p.toPromise();
682+
await PersistencePromise.forEach(limboKeys.toArray(), limboKey => {
683+
return this.limboDocumentRefs
684+
.containsKey(null, limboKey)
685+
.next(isReferenced => {
686+
if (!isReferenced) {
687+
// We removed the last reference for this key
688+
this.removeLimboTarget(limboKey);
689+
}
690+
});
691+
}).toPromise();
696692
}
697693
}
698694

packages/firestore/src/local/indexeddb_persistence.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -376,14 +376,12 @@ export class IndexedDbPersistence implements Persistence {
376376
client => activeClients.indexOf(client) === -1
377377
);
378378
})
379-
.next(() => {
379+
.next(() =>
380380
// Delete metadata for clients that are no longer considered active.
381-
let p = PersistencePromise.resolve();
382-
inactiveClients.forEach(inactiveClient => {
383-
p = p.next(() => metadataStore.delete(inactiveClient.clientId));
384-
});
385-
return p;
386-
})
381+
PersistencePromise.forEach(inactiveClients, inactiveClient =>
382+
metadataStore.delete(inactiveClient.clientId)
383+
)
384+
)
387385
.next(() => {
388386
// Retrieve the minimum change ID from the set of active clients.
389387

packages/firestore/src/local/local_store.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -301,13 +301,11 @@ export class LocalStore {
301301
return PersistencePromise.resolve([]);
302302
}
303303
})
304-
.next(ackedBatches => {
305-
let p = PersistencePromise.resolve();
306-
ackedBatches.forEach(batch => {
307-
p = p.next(() => this.mutationQueue.removeMutationBatch(txn, batch));
308-
});
309-
return p;
310-
});
304+
.next(ackedBatches =>
305+
PersistencePromise.forEach(ackedBatches, batch =>
306+
this.mutationQueue.removeMutationBatch(txn, batch)
307+
)
308+
);
311309
}
312310

313311
/* Accept locally generated Mutations and commit them to storage. */
@@ -979,17 +977,15 @@ export class LocalStore {
979977
batches: MutationBatch[]
980978
): PersistencePromise<DocumentKeySet> {
981979
let affectedDocs = documentKeySet();
982-
983-
let p = PersistencePromise.resolve();
984980
for (const batch of batches) {
985981
for (const mutation of batch.mutations) {
986982
const key = mutation.key;
987983
affectedDocs = affectedDocs.add(key);
988984
}
989-
p = p.next(() => this.mutationQueue.removeMutationBatch(txn, batch));
990985
}
991-
992-
return p.next(() => affectedDocs);
986+
return PersistencePromise.forEach(batches, batch =>
987+
this.mutationQueue.removeMutationBatch(txn, batch)
988+
).next(() => affectedDocs);
993989
}
994990

995991
private applyWriteToRemoteDocuments(

packages/firestore/src/local/persistence_promise.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,15 @@ export class PersistencePromise<T> {
205205
return results;
206206
});
207207
}
208+
209+
static forEach<T>(
210+
elements: T[],
211+
callback: (T) => PersistencePromise<void>
212+
): PersistencePromise<void> {
213+
let p = PersistencePromise.resolve();
214+
for (const element of elements) {
215+
p = p.next(() => callback(element));
216+
}
217+
return p;
218+
}
208219
}

packages/firestore/src/model/document.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,32 @@ export interface DocumentOptions {
2626
hasLocalMutations: boolean;
2727
}
2828

29-
export class Document {
29+
/**
30+
* The result of a lookup for a given path may be an existing document or a
31+
* marker that this document does not exist at a given version.
32+
*/
33+
export abstract class MaybeDocument {
34+
constructor(readonly key: DocumentKey, readonly version: SnapshotVersion) {}
35+
36+
static compareByKey(d1: MaybeDocument, d2: MaybeDocument): number {
37+
return DocumentKey.comparator(d1.key, d2.key);
38+
}
39+
}
40+
41+
/**
42+
* Represents a document in Firestore with a key, version, data and whether the
43+
* data has local mutations applied to it.
44+
*/
45+
export class Document extends MaybeDocument {
3046
readonly hasLocalMutations: boolean;
3147

3248
constructor(
33-
readonly key: DocumentKey,
34-
readonly version: SnapshotVersion,
49+
key: DocumentKey,
50+
version: SnapshotVersion,
3551
readonly data: ObjectValue,
3652
options: DocumentOptions
3753
) {
54+
super(key, version);
3855
this.hasLocalMutations = options.hasLocalMutations;
3956
}
4057

@@ -68,10 +85,6 @@ export class Document {
6885
);
6986
}
7087

71-
static compareByKey(d1: MaybeDocument, d2: MaybeDocument): number {
72-
return DocumentKey.comparator(d1.key, d2.key);
73-
}
74-
7588
static compareByField(field: FieldPath, d1: Document, d2: Document): number {
7689
const v1 = d1.field(field);
7790
const v2 = d2.field(field);
@@ -88,8 +101,10 @@ export class Document {
88101
* Version is set to 0 if we don't point to any specific time, otherwise it
89102
* denotes time we know it didn't exist at.
90103
*/
91-
export class NoDocument {
92-
constructor(readonly key: DocumentKey, readonly version: SnapshotVersion) {}
104+
export class NoDocument extends MaybeDocument {
105+
constructor(key: DocumentKey, version: SnapshotVersion) {
106+
super(key, version);
107+
}
93108

94109
toString(): string {
95110
return `NoDocument(${this.key}, ${this.version})`;
@@ -102,14 +117,4 @@ export class NoDocument {
102117
other.key.isEqual(this.key)
103118
);
104119
}
105-
106-
static compareByKey(d1: MaybeDocument, d2: MaybeDocument): number {
107-
return DocumentKey.comparator(d1.key, d2.key);
108-
}
109120
}
110-
111-
/**
112-
* A union type representing either a full document or a deleted document.
113-
* The NoDocument is used when it doesn't exist on the server.
114-
*/
115-
export type MaybeDocument = Document | NoDocument;

packages/firestore/src/remote/watch_change.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class DocumentWatchChange {
6161
* The new document or NoDocument if it was deleted. Is null if the
6262
* document went out of view without the server sending a new document.
6363
*/
64-
public newDoc: Document | NoDocument | null
64+
public newDoc: MaybeDocument | null
6565
) {}
6666
}
6767

packages/firestore/test/integration/api/validation.test.ts

Lines changed: 64 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -560,37 +560,53 @@ apiDescribe('Validation:', persistence => {
560560
);
561561

562562
validationIt(persistence, 'Field paths must not have empty segments', db => {
563-
const badFieldPaths = ['', 'foo..baz', '.foo', 'foo.'];
564-
const promises = [];
565-
for (const fieldPath of badFieldPaths) {
566-
const reason =
567-
`Invalid field path (${fieldPath}). Paths must not be ` +
568-
`empty, begin with '.', end with '.', or contain '..'`;
569-
promises.push(expectFieldPathToFail(db, fieldPath, reason));
570-
}
571-
return Promise.all(promises);
563+
const docRef = db.collection('test').doc();
564+
return docRef
565+
.set({ test: 1 })
566+
.then(() => {
567+
return docRef.get();
568+
})
569+
.then(snapshot => {
570+
const badFieldPaths = ['', 'foo..baz', '.foo', 'foo.'];
571+
const promises = [];
572+
for (const fieldPath of badFieldPaths) {
573+
const reason =
574+
`Invalid field path (${fieldPath}). Paths must not be ` +
575+
`empty, begin with '.', end with '.', or contain '..'`;
576+
promises.push(expectFieldPathToFail(snapshot, fieldPath, reason));
577+
}
578+
return Promise.all(promises);
579+
});
572580
});
573581

574582
validationIt(
575583
persistence,
576584
'Field paths must not have invalid segments',
577585
db => {
578-
const badFieldPaths = [
579-
'foo~bar',
580-
'foo*bar',
581-
'foo/bar',
582-
'foo[1',
583-
'foo]1',
584-
'foo[1]'
585-
];
586-
const promises = [];
587-
for (const fieldPath of badFieldPaths) {
588-
const reason =
589-
`Invalid field path (${fieldPath}). Paths must not ` +
590-
`contain '~', '*', '/', '[', or ']'`;
591-
promises.push(expectFieldPathToFail(db, fieldPath, reason));
592-
}
593-
return Promise.all(promises);
586+
const docRef = db.collection('test').doc();
587+
return docRef
588+
.set({ test: 1 })
589+
.then(() => {
590+
return docRef.get();
591+
})
592+
.then(snapshot => {
593+
const badFieldPaths = [
594+
'foo~bar',
595+
'foo*bar',
596+
'foo/bar',
597+
'foo[1',
598+
'foo]1',
599+
'foo[1]'
600+
];
601+
const promises = [];
602+
for (const fieldPath of badFieldPaths) {
603+
const reason =
604+
`Invalid field path (${fieldPath}). Paths must not ` +
605+
`contain '~', '*', '/', '[', or ']'`;
606+
promises.push(expectFieldPathToFail(snapshot, fieldPath, reason));
607+
}
608+
return Promise.all(promises);
609+
});
594610
}
595611
);
596612

@@ -953,37 +969,33 @@ function expectWriteToFail(
953969
* they fail with the specified reason.
954970
*/
955971
function expectFieldPathToFail(
956-
db: firestore.FirebaseFirestore,
972+
snapshot: firestore.DocumentSnapshot,
957973
path: string,
958974
reason: string
959975
): Promise<void> {
960976
// Get an arbitrary snapshot we can use for testing.
961-
const docRef = db.collection('test').doc();
962-
return docRef
963-
.set({ test: 1 })
964-
.then(() => {
965-
return docRef.get();
966-
})
967-
.then(snapshot => {
968-
// Snapshot paths.
969-
expect(() => snapshot.get(path)).to.throw(
970-
'Function DocumentSnapshot.get() called with invalid data. ' + reason
971-
);
977+
return Promise.resolve().then(() => {
978+
// Snapshot paths.
979+
expect(() => snapshot.get(path)).to.throw(
980+
'Function DocumentSnapshot.get() called with invalid data. ' + reason
981+
);
972982

973-
// Query filter / order fields.
974-
const coll = db.collection('test-collection');
975-
// <=, etc omitted for brevity since the code path is trivially
976-
// shared.
977-
expect(() => coll.where(path, '==', 1)).to.throw(
978-
'Function Query.where() called with invalid data. ' + reason
979-
);
980-
expect(() => coll.orderBy(path)).to.throw(
981-
'Function Query.orderBy() called with invalid data. ' + reason
982-
);
983+
const db = snapshot.ref.firestore;
983984

984-
// Update paths.
985-
const data = {} as { [field: string]: number };
986-
data[path] = 1;
987-
return expectUpdateToFail(db, data, reason);
988-
});
985+
// Query filter / order fields.
986+
const coll = db.collection('test-collection');
987+
// <=, etc omitted for brevity since the code path is trivially
988+
// shared.
989+
expect(() => coll.where(path, '==', 1)).to.throw(
990+
'Function Query.where() called with invalid data. ' + reason
991+
);
992+
expect(() => coll.orderBy(path)).to.throw(
993+
'Function Query.orderBy() called with invalid data. ' + reason
994+
);
995+
996+
// Update paths.
997+
const data = {} as { [field: string]: number };
998+
data[path] = 1;
999+
return expectUpdateToFail(db, data, reason);
1000+
});
9891001
}

packages/firestore/test/unit/local/indexeddb_persistence.test.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,9 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => {
290290
const sdb = new SimpleDb(db);
291291
return sdb.runTransaction('readwrite', [DbMutationBatch.store], txn => {
292292
const store = txn.store(DbMutationBatch.store);
293-
let p = PersistencePromise.resolve();
294-
for (const testMutation of testMutations) {
295-
p = p.next(() => store.put(testMutation));
296-
}
297-
return p;
293+
return PersistencePromise.forEach(testMutations, testMutation =>
294+
store.put(testMutation)
295+
);
298296
});
299297
}).then(() =>
300298
withDb(4, db => {
@@ -306,14 +304,11 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => {
306304
const store = txn.store<DbMutationBatchKey, DbMutationBatch>(
307305
DbMutationBatch.store
308306
);
309-
let p = PersistencePromise.resolve();
310-
for (const testMutation of testMutations) {
311-
p = p.next(() =>
312-
store.get(testMutation.batchId).next(mutationBatch => {
313-
expect(mutationBatch).to.deep.equal(testMutation);
314-
})
315-
);
316-
}
307+
let p = PersistencePromise.forEach(testMutations, testMutation =>
308+
store.get(testMutation.batchId).next(mutationBatch => {
309+
expect(mutationBatch).to.deep.equal(testMutation);
310+
})
311+
);
317312
p = p.next(() => {
318313
store
319314
.add({} as any) // tslint:disable-line:no-any

packages/firestore/test/unit/local/persistence_promise.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,13 @@ describe('PersistencePromise', () => {
213213
})
214214
.toPromise();
215215
});
216+
217+
it('executes forEach in order', async () => {
218+
let result = '';
219+
await PersistencePromise.forEach(['a', 'b', 'c'], el => {
220+
result += el;
221+
return PersistencePromise.resolve();
222+
}).toPromise;
223+
expect(result).to.equal('abc');
224+
});
216225
});

0 commit comments

Comments
 (0)