Skip to content

Commit 59af965

Browse files
authored
Merge pull request #151 from FirebasePrivate/mpmcdonald-datastore-event-types
Granular Datastore event types
2 parents 33b839e + 7d14c60 commit 59af965

File tree

2 files changed

+135
-45
lines changed

2 files changed

+135
-45
lines changed

spec/providers/datastore.spec.ts

Lines changed: 90 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,75 +33,136 @@ describe('Datastore Functions', () => {
3333
delete process.env.GCLOUD_PROJECT;
3434
});
3535

36-
describe('document builders', () => {
37-
function expectedTrigger(resource: string) {
36+
describe('document builders and event types', () => {
37+
function expectedTrigger(resource: string, eventType: string) {
3838
return {
3939
eventTrigger: {
4040
resource,
41-
eventType: `providers/${datastore.provider}/eventTypes/document.write`,
41+
eventType: `providers/${datastore.provider}/eventTypes/${eventType}`,
4242
},
4343
};
4444
}
4545

4646
it('should allow terse constructors', () => {
4747
let resource = 'projects/project1/databases/(default)/documents/users/{uid}';
4848
let cloudFunction = datastore.document('users/{uid}').onWrite(() => null);
49-
expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource));
49+
expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, 'document.write'));
5050
});
5151

5252
it('should allow custom namespaces', () => {
5353
let resource = 'projects/project1/databases/(default)/documents@v2/users/{uid}';
5454
let cloudFunction = datastore.namespace('v2').document('users/{uid}').onWrite(() => null);
55-
expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource));
55+
expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, 'document.write'));
5656
});
5757

5858
it('should allow custom databases', () => {
5959
let resource = 'projects/project1/databases/myDB/documents/users/{uid}';
6060
let cloudFunction = datastore.database('myDB').document('users/{uid}').onWrite(() => null);
61-
expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource));
61+
expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, 'document.write'));
6262
});
6363

6464
it('should allow both custom database and namespace', () => {
6565
let resource = 'projects/project1/databases/myDB/documents@v2/users/{uid}';
6666
let cloudFunction = datastore.database('myDB').namespace('v2').document('users/{uid}').onWrite(() => null);
67-
expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource));
67+
expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, 'document.write'));
68+
});
69+
70+
it('onCreate should have the "document.create" eventType', () => {
71+
let resource = 'projects/project1/databases/(default)/documents/users/{uid}';
72+
let eventType = datastore.document('users/{uid}').onCreate(() => null).__trigger.eventTrigger.eventType;
73+
expect(eventType).to.eq(expectedTrigger(resource, 'document.create').eventTrigger.eventType);
74+
});
75+
76+
it('onUpdate should have the "document.update" eventType', () => {
77+
let resource = 'projects/project1/databases/(default)/documents/users/{uid}';
78+
let eventType = datastore.document('users/{uid}').onUpdate(() => null).__trigger.eventTrigger.eventType;
79+
expect(eventType).to.eq(expectedTrigger(resource, 'document.update').eventTrigger.eventType);
80+
});
81+
82+
it('onDelete should have the "document.delete" eventType', () => {
83+
let resource = 'projects/project1/databases/(default)/documents/users/{uid}';
84+
let eventType = datastore.document('users/{uid}').onDelete(() => null).__trigger.eventTrigger.eventType;
85+
expect(eventType).to.eq(expectedTrigger(resource, 'document.delete').eventTrigger.eventType);
6886
});
6987
});
7088

7189
describe('dataConstructor', () => {
72-
let testEvent = {
73-
'data': {
74-
'oldValue': {
75-
'fields': {
76-
'key1': {
77-
'booleanValue': false,
78-
},
79-
'key2': {
80-
'integerValue': '111',
81-
},
90+
function constructData(oldValue: object, value: object) {
91+
return {
92+
'data': {
93+
'oldValue': oldValue,
94+
'value': value,
95+
},
96+
};
97+
}
98+
99+
function createOldValue() {
100+
return {
101+
'fields': {
102+
'key1': {
103+
'booleanValue': false,
104+
},
105+
'key2': {
106+
'integerValue': '111',
82107
},
83108
},
84-
'value': {
85-
'fields': {
86-
'key1': {
87-
'booleanValue': true,
88-
},
89-
'key2': {
90-
'integerValue': '123',
91-
},
109+
};
110+
}
111+
112+
function createValue() {
113+
return {
114+
'fields': {
115+
'key1': {
116+
'booleanValue': true,
117+
},
118+
'key2': {
119+
'integerValue': '123',
92120
},
93121
},
94-
},
95-
};
122+
};
123+
}
96124

97-
it('constructs appropriate fields and getters for event.data', () => {
125+
it('constructs appropriate fields and getters for event.data on "document.write" events', () => {
98126
let testFunction = datastore.document('path').onWrite((event) => {
99127
expect(event.data.data()).to.deep.equal({key1: true, key2: 123});
100128
expect(event.data.get('key1')).to.equal(true);
101129
expect(event.data.previous.data()).to.deep.equal({key1: false, key2: 111});
102130
expect(event.data.previous.get('key1')).to.equal(false);
103131
});
104-
return testFunction(testEvent);
132+
let data = constructData(createOldValue(), createValue());
133+
return testFunction(data);
134+
});
135+
136+
it('constructs appropriate fields and getters for event.data on "document.create" events', () => {
137+
let testFunction = datastore.document('path').onCreate((event) => {
138+
expect(event.data.data()).to.deep.equal({key1: true, key2: 123});
139+
expect(event.data.get('key1')).to.equal(true);
140+
expect(event.data.previous).to.equal(null);
141+
});
142+
let data = constructData(null, createValue());
143+
return testFunction(data);
144+
});
145+
146+
it('constructs appropriate fields and getters for event.data on "document.update" events', () => {
147+
let testFunction = datastore.document('path').onUpdate((event) => {
148+
expect(event.data.data()).to.deep.equal({key1: true, key2: 123});
149+
expect(event.data.get('key1')).to.equal(true);
150+
expect(event.data.previous.data()).to.deep.equal({key1: false, key2: 111});
151+
expect(event.data.previous.get('key1')).to.equal(false);
152+
});
153+
let data = constructData(createOldValue(), createValue());
154+
return testFunction(data);
155+
});
156+
157+
it('constructs appropriate fields and getters for event.data on "document.delete" events', () => {
158+
let testFunction = datastore.document('path').onDelete((event) => {
159+
expect(event.data.data()).to.deep.equal({});
160+
expect(event.data.get('key1')).to.equal(null);
161+
expect(event.data.previous.data()).to.deep.equal({key1: false, key2: 111});
162+
expect(event.data.previous.get('key1')).to.equal(false);
163+
});
164+
let data = constructData(createOldValue(), null);
165+
return testFunction(data);
105166
});
106167
});
107168

src/providers/datastore.ts

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,49 @@ export class DocumentBuilder {
6969
// TODO what validation do we want to do here?
7070
}
7171

72-
onWrite(handler: (event: Event<any>) => PromiseLike<any> | any): CloudFunction<any> {
73-
const dataConstructor = (raw: Event<any>) => {
74-
if (raw.data instanceof DeltaDocumentSnapshot) {
75-
return raw.data;
76-
}
77-
return new DeltaDocumentSnapshot(
78-
_.get(raw.data, 'value.fields', {}),
79-
_.get(raw.data, 'oldValue.fields', {})
80-
);
81-
};
82-
return makeCloudFunction({
83-
provider, handler,
84-
resource: this.resource,
85-
eventType: 'document.write',
86-
dataConstructor,
87-
});
72+
/** Respond to all document writes (creates, updates, or deletes). */
73+
onWrite(handler: (event: Event<DeltaDocumentSnapshot>) => PromiseLike<any> |
74+
any): CloudFunction<DeltaDocumentSnapshot> {
75+
return this.onOperation(handler, 'document.write');
76+
}
77+
78+
/** Respond only to document creations. */
79+
onCreate(handler: (event: Event<DeltaDocumentSnapshot>) => PromiseLike<any> |
80+
any): CloudFunction<DeltaDocumentSnapshot> {
81+
return this.onOperation(handler, 'document.create');
82+
}
83+
84+
/** Respond only to document updates. */
85+
onUpdate(handler: (event: Event<DeltaDocumentSnapshot>) => PromiseLike<any> |
86+
any): CloudFunction<DeltaDocumentSnapshot> {
87+
return this.onOperation(handler, 'document.update');
88+
}
89+
90+
/** Respond only to document deletions. */
91+
onDelete(handler: (event: Event<DeltaDocumentSnapshot>) => PromiseLike<any> |
92+
any): CloudFunction<DeltaDocumentSnapshot> {
93+
return this.onOperation(handler, 'document.delete');
94+
}
95+
96+
private onOperation(
97+
handler: (event: Event<DeltaDocumentSnapshot>) => PromiseLike<any> | any,
98+
eventType: string): CloudFunction<DeltaDocumentSnapshot> {
99+
100+
const dataConstructor = (raw: Event<any>) => {
101+
if (raw.data instanceof DeltaDocumentSnapshot) {
102+
return raw.data;
103+
}
104+
return new DeltaDocumentSnapshot(
105+
_.get(raw.data, 'value.fields', {}),
106+
_.get(raw.data, 'oldValue.fields', {})
107+
);
108+
};
109+
return makeCloudFunction({
110+
provider, handler,
111+
resource: this.resource,
112+
eventType: eventType,
113+
dataConstructor,
114+
});
88115
}
89116
}
90117

@@ -108,6 +135,8 @@ export class DeltaDocumentSnapshot {
108135
return _.get(this.data(), key, null);
109136
}
110137

138+
// Note: this is an expected assymetry between event.data and
139+
// event.data.previous until we move the latter to event.previous
111140
get previous(): DeltaDocumentSnapshot {
112141
if (_.isEmpty(this._old)) {
113142
return null;

0 commit comments

Comments
 (0)