Skip to content

Add setDoc() #3139

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

Merged
merged 61 commits into from
Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
da82b5d
Add Firestore, initializeFirestore, getFirestore to Firestore lite
schmidt-sebastian May 26, 2020
30bfc3f
Review
schmidt-sebastian May 27, 2020
f49096f
add one more tests
schmidt-sebastian May 27, 2020
da3403f
Fix lint
schmidt-sebastian May 27, 2020
bd3596e
Add lite to build
schmidt-sebastian May 27, 2020
3aac6f4
Add private
schmidt-sebastian May 27, 2020
6f05e2f
Fix Path
schmidt-sebastian May 27, 2020
634145a
Remove unused import
schmidt-sebastian May 27, 2020
8187cb7
Update package.json
schmidt-sebastian May 27, 2020
5760b02
Simplify
schmidt-sebastian May 27, 2020
4ba0f4e
Merge branch 'mrschmidt/initializefirestore' of github.com:firebase/f…
schmidt-sebastian May 27, 2020
e40faf4
Merge branch 'master' into mrschmidt/initializefirestore
schmidt-sebastian May 27, 2020
f243a6c
Add DocumentReference
schmidt-sebastian May 27, 2020
d48405b
Feedback
schmidt-sebastian May 28, 2020
804fe94
Typo
schmidt-sebastian May 28, 2020
d244bcd
Lint fix
schmidt-sebastian May 28, 2020
bc342a3
Update integration.test.ts
schmidt-sebastian May 28, 2020
5297d47
Update rollup.config.lite.js
schmidt-sebastian May 28, 2020
867d9c0
One more test
schmidt-sebastian May 28, 2020
084fdcf
Update reference.ts
schmidt-sebastian May 28, 2020
ca44064
Add CollectionReference
schmidt-sebastian May 28, 2020
920c0e2
Merge branch 'master' into mrschmidt/initializefirestore
schmidt-sebastian May 28, 2020
d5745f0
Merge
schmidt-sebastian May 28, 2020
b636d27
Merge
schmidt-sebastian May 28, 2020
31ba3ba
Add DocumentSnapshot
schmidt-sebastian May 28, 2020
505792f
Add exports
schmidt-sebastian May 28, 2020
3750a00
Add getDoc()
schmidt-sebastian May 28, 2020
5d3e2aa
Fix build
schmidt-sebastian May 28, 2020
58a84d1
More integration test fixes
schmidt-sebastian May 29, 2020
6f91171
Fix Integration tests
schmidt-sebastian May 29, 2020
c63977f
Add DocumentReference
schmidt-sebastian May 29, 2020
6352026
Add deleteDoc()
schmidt-sebastian May 29, 2020
be02305
Cleanup
schmidt-sebastian May 29, 2020
518631d
Merge
schmidt-sebastian May 29, 2020
6318fa4
Update datastore.ts
schmidt-sebastian May 29, 2020
a4ff5fd
Add setDoc()
schmidt-sebastian May 29, 2020
72b0b8b
Add setDoc()
schmidt-sebastian May 29, 2020
efa58c4
Add converter
schmidt-sebastian May 30, 2020
7649b67
Throw when user data parsing fails
schmidt-sebastian May 30, 2020
ca9ab39
Update test names
schmidt-sebastian May 30, 2020
1a81864
Simplify
schmidt-sebastian May 30, 2020
068d5a5
Simplify
schmidt-sebastian May 30, 2020
94e22df
Merge branch 'mrschmidt/newgetdocument' into mrschmidt/deletedoc
schmidt-sebastian May 30, 2020
555a16c
Merge branch 'mrschmidt/deletedoc' of github.com:firebase/firebase-js…
schmidt-sebastian May 30, 2020
61b7c4c
Merge branch 'mrschmidt/deletedoc' into mrschmidt/setdoc
schmidt-sebastian May 30, 2020
d8abac1
Simplify
schmidt-sebastian May 30, 2020
8a7fc93
Add documentID() (#3137)
schmidt-sebastian Jun 1, 2020
c3e3375
Merge
schmidt-sebastian Jun 3, 2020
835ad93
Merge
schmidt-sebastian Jun 3, 2020
bd5a998
Merge
schmidt-sebastian Jun 3, 2020
f6553cb
Merge branch 'mrschmidt/snapshot' of github.com:firebase/firebase-js-…
schmidt-sebastian Jun 3, 2020
25f01e8
Merge
schmidt-sebastian Jun 3, 2020
2685912
Simplify
schmidt-sebastian Jun 4, 2020
6a34bbb
Merge
schmidt-sebastian Jun 4, 2020
3361a63
Feedback
schmidt-sebastian Jun 4, 2020
27bed90
Merge
schmidt-sebastian Jun 4, 2020
6f8521a
Merge branch 'master' into mrschmidt/newgetdocument
schmidt-sebastian Jun 4, 2020
4142278
Merge
schmidt-sebastian Jun 4, 2020
a005ed5
Merge
schmidt-sebastian Jun 4, 2020
d7200bd
Merge
schmidt-sebastian Jun 5, 2020
6af39e9
Merge
schmidt-sebastian Jun 5, 2020
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
3 changes: 2 additions & 1 deletion packages/firestore/lite/index.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export {
doc,
parent,
getDoc,
deleteDoc
deleteDoc,
setDoc
} from './src/api/reference';

// TOOD(firestorelite): Add tests when setDoc() is available
Expand Down
31 changes: 4 additions & 27 deletions packages/firestore/lite/src/api/field_path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,30 @@

import * as firestore from '../../index';

import { BaseFieldPath } from '../../../src/api/field_path';
import { cast } from './util';
import {
DOCUMENT_KEY_NAME,
FieldPath as InternalFieldPath
} from '../../../src/model/path';
import { validateNamedArrayAtLeastNumberOfElements } from '../../../src/util/input_validation';
import { Code, FirestoreError } from '../../../src/util/error';
import { DOCUMENT_KEY_NAME } from '../../../src/model/path';

/**
* A FieldPath refers to a field in a document. The path may consist of a single
* field name (referring to a top-level field in the document), or a list of
* field names (referring to a nested field in the document).
*/
export class FieldPath implements firestore.FieldPath {
export class FieldPath extends BaseFieldPath implements firestore.FieldPath {
// Note: This class is stripped down a copy of the FieldPath class in the
// legacy SDK. The changes are:
// - The `documentId()` static method has been removed
// - Input validation is limited to errors that cannot be caught by the
// TypeScript transpiler.

/** Internal representation of a Firestore field path. */
_internalPath: InternalFieldPath;

/**
* Creates a FieldPath from the provided field names. If more than one field
* name is provided, the path will point to a nested field in a document.
*
* @param fieldNames A list of field names.
*/
constructor(...fieldNames: string[]) {
validateNamedArrayAtLeastNumberOfElements(
'FieldPath',
fieldNames,
'fieldNames',
1
);

const emptyElement = fieldNames.indexOf('');
if (emptyElement !== -1) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
`Invalid field name at argument $(i + 1). ` +
'Field names must not be empty.'
);
}

this._internalPath = new InternalFieldPath(fieldNames);
super(fieldNames);
}

isEqual(other: firestore.FieldPath): boolean {
Expand Down
50 changes: 49 additions & 1 deletion packages/firestore/lite/src/api/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import * as firestore from '../../index';
import { Document } from '../../../src/model/document';
import { DocumentKey } from '../../../src/model/document_key';
import { Firestore } from './database';
import { DocumentKeyReference } from '../../../src/api/user_data_reader';
import {
DocumentKeyReference,
UserDataReader
} from '../../../src/api/user_data_reader';
import { Query as InternalQuery } from '../../../src/core/query';
import { FirebaseFirestore, FirestoreDataConverter } from '../../index';
import { ResourcePath } from '../../../src/model/path';
Expand All @@ -32,6 +35,9 @@ import {
} from '../../../src/remote/datastore';
import { hardAssert } from '../../../src/util/assert';
import { DeleteMutation, Precondition } from '../../../src/model/mutation';
import { PlatformSupport } from '../../../src/platform/platform';
import { applyFirestoreDataConverter } from '../../../src/api/database';
import { DatabaseId } from '../../../src/core/database_info';
import { cast } from './util';
import {
validateArgType,
Expand Down Expand Up @@ -271,6 +277,36 @@ export function getDoc<T>(
});
}

export function setDoc<T>(
reference: firestore.DocumentReference<T>,
data: T,
options?: firestore.SetOptions
): Promise<void> {
const ref = cast(reference, DocumentReference);

const [convertedValue] = applyFirestoreDataConverter(
ref._converter,
data,
'setDoc'
);

// Kick off configuring the client, which freezes the settings.
const configureClient = ref.firestore._ensureClientConfigured();
const dataReader = newUserDataReader(
ref.firestore._databaseId,
ref.firestore._settings!
);

const parsed = dataReader.parseSetData('setDoc', convertedValue, options);

return configureClient.then(datastore =>
invokeCommitRpc(
datastore,
parsed.toMutations(ref._key, Precondition.none())
)
);
}

export function deleteDoc(
reference: firestore.DocumentReference
): Promise<void> {
Expand All @@ -283,3 +319,15 @@ export function deleteDoc(
])
);
}

function newUserDataReader(
databaseId: DatabaseId,
settings: firestore.Settings
): UserDataReader {
const serializer = PlatformSupport.getPlatform().newSerializer(databaseId);
return new UserDataReader(
databaseId,
!!settings.ignoreUndefinedProperties,
serializer
);
}
13 changes: 12 additions & 1 deletion packages/firestore/lite/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { initializeApp } from '@firebase/app-exp';
import * as firestore from '../index';

import { initializeFirestore } from '../src/api/database';
import { doc, collection } from '../src/api/reference';
import { doc, collection, setDoc } from '../src/api/reference';
import {
DEFAULT_PROJECT_ID,
DEFAULT_SETTINGS
Expand Down Expand Up @@ -55,3 +55,14 @@ export function withTestDoc(
return fn(doc(collection(db, 'test-collection')));
});
}

export function withTestDocAndInitialData(
data: firestore.DocumentData,
fn: (doc: firestore.DocumentReference) => void | Promise<void>
): Promise<void> {
return withTestDb(async db => {
const ref = doc(collection(db, 'test-collection'));
await setDoc(ref, data);
return fn(ref);
});
}
137 changes: 134 additions & 3 deletions packages/firestore/lite/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,27 @@ import {
getFirestore,
initializeFirestore
} from '../src/api/database';
import { withTestDb, withTestDoc } from './helpers';
import {
withTestDb,
withTestDbSettings,
withTestDoc,
withTestDocAndInitialData
} from './helpers';
import {
parent,
collection,
CollectionReference,
doc,
DocumentReference,
getDoc,
deleteDoc
deleteDoc,
setDoc
} from '../src/api/reference';
import { FieldPath } from '../src/api/field_path';
import {
DEFAULT_PROJECT_ID,
DEFAULT_SETTINGS
} from '../../test/integration/util/settings';
import { expectEqual, expectNotEqual } from '../../test/util/helpers';
import { FieldValue } from '../../src/api/field_value';

Expand Down Expand Up @@ -169,7 +180,127 @@ describe('getDoc()', () => {
});
});

// TODO(firestorelite): Expand test coverage once we can write docs
it('can get an existing document', () => {
return withTestDocAndInitialData({ val: 1 }, async docRef => {
const docSnap = await getDoc(docRef);
expect(docSnap.exists()).to.be.true;
});
});
});

describe('deleteDoc()', () => {
it('can delete a non-existing document', () => {
return withTestDoc(docRef => deleteDoc(docRef));
});

it('can delete an existing document', () => {
return withTestDoc(async docRef => {
await setDoc(docRef, {});
await deleteDoc(docRef);
const docSnap = await getDoc(docRef);
expect(docSnap.exists()).to.be.false;
});
});
});

describe('setDoc()', () => {
it('can set a new document', () => {
return withTestDoc(async docRef => {
await setDoc(docRef, { val: 1 });
const docSnap = await getDoc(docRef);
expect(docSnap.data()).to.deep.equal({ val: 1 });
});
});

it('can merge a document', () => {
return withTestDocAndInitialData({ foo: 1 }, async docRef => {
await setDoc(docRef, { bar: 2 }, { merge: true });
const docSnap = await getDoc(docRef);
expect(docSnap.data()).to.deep.equal({ foo: 1, bar: 2 });
});
});

it('can merge a document with mergeFields', () => {
return withTestDocAndInitialData({ foo: 1 }, async docRef => {
await setDoc(
docRef,
{ foo: 'ignored', bar: 2, baz: { foobar: 3 } },
{ mergeFields: ['bar', new FieldPath('baz', 'foobar')] }
);
const docSnap = await getDoc(docRef);
expect(docSnap.data()).to.deep.equal({
foo: 1,
bar: 2,
baz: { foobar: 3 }
});
});
});

it('throws when user input fails validation', () => {
return withTestDoc(async docRef => {
expect(() => setDoc(docRef, { val: undefined })).to.throw(
'Function setDoc() called with invalid data. Unsupported field value: undefined (found in field val)'
);
});
});

it("can ignore 'undefined'", () => {
return withTestDbSettings(
DEFAULT_PROJECT_ID,
{ ...DEFAULT_SETTINGS, ignoreUndefinedProperties: true },
async db => {
const docRef = doc(collection(db, 'test-collection'));
await setDoc(docRef, { val: undefined });
const docSnap = await getDoc(docRef);
expect(docSnap.data()).to.deep.equal({});
}
);
});
});

describe('DocumentSnapshot', () => {
it('can represent missing data', () => {
return withTestDoc(async docRef => {
const docSnap = await getDoc(docRef);
expect(docSnap.exists()).to.be.false;
expect(docSnap.data()).to.be.undefined;
});
});

it('can return data', () => {
return withTestDocAndInitialData({ foo: 1 }, async docRef => {
const docSnap = await getDoc(docRef);
expect(docSnap.exists()).to.be.true;
expect(docSnap.data()).to.deep.equal({ foo: 1 });
});
});

it('can return single field', () => {
return withTestDocAndInitialData({ foo: 1, bar: 2 }, async docRef => {
const docSnap = await getDoc(docRef);
expect(docSnap.get('foo')).to.equal(1);
expect(docSnap.get(new FieldPath('bar'))).to.equal(2);
});
});

it('can return nested field', () => {
return withTestDocAndInitialData({ foo: { bar: 1 } }, async docRef => {
const docSnap = await getDoc(docRef);
expect(docSnap.get('foo.bar')).to.equal(1);
expect(docSnap.get(new FieldPath('foo', 'bar'))).to.equal(1);
});
});

it('is properly typed', () => {
return withTestDocAndInitialData({ foo: 1 }, async docRef => {
const docSnap = await getDoc(docRef);
let documentData = docSnap.data()!; // "data" is typed as nullable
if (docSnap.exists()) {
documentData = docSnap.data(); // "data" is typed as non-null
}
expect(documentData).to.deep.equal({ foo: 1 });
});
});
});

describe('deleteDoc()', () => {
Expand Down
Loading