-
Notifications
You must be signed in to change notification settings - Fork 63
refactor: remove lodash dependency #331
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,8 +22,27 @@ | |
|
|
||
| import { Change } from 'firebase-functions/v1'; | ||
| import { firestore, app } from 'firebase-admin'; | ||
| import { has, get, isEmpty, isPlainObject, mapValues } from 'lodash'; | ||
| import { inspect } from 'util'; | ||
|
|
||
| function isPlainObject(value: any): boolean { | ||
| return ( | ||
| typeof value === 'object' && | ||
| value !== null && | ||
| Object.getPrototypeOf(value) === Object.prototype | ||
| ); | ||
| } | ||
|
|
||
| function mapValues<T, U>( | ||
| obj: Record<string, T>, | ||
| fn: (val: T) => U | ||
| ): Record<string, U> { | ||
| const res: Record<string, U> = {}; | ||
| for (const key in obj) { | ||
| if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
| res[key] = fn(obj[key]); | ||
| } | ||
| } | ||
| return res; | ||
| } | ||
|
Comment on lines
+34
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the goal of this PR is to leverage modern ES6+ features, we can simplify the function mapValues<T, U>(
obj: Record<string, T>,
fn: (val: T) => U
): Record<string, U> {
const res: Record<string, U> = {};
for (const [key, val] of Object.entries(obj)) {
res[key] = fn(val);
}
return res;
} |
||
|
|
||
| import { testApp } from '../app'; | ||
|
|
||
|
|
@@ -76,30 +95,32 @@ export function makeDocumentSnapshot( | |
| ) { | ||
| let firestoreService; | ||
| let project; | ||
| if (has(options, 'app')) { | ||
| firestoreService = firestore(options.firebaseApp); | ||
| project = get(options, 'app.options.projectId'); | ||
| const legacyApp = options?.firebaseApp || (options as any)?.app; | ||
| if (legacyApp) { | ||
| firestoreService = firestore(legacyApp); | ||
| project = legacyApp.options?.projectId; | ||
| } else { | ||
| firestoreService = firestore(testApp().getApp()); | ||
| project = process.env.GCLOUD_PROJECT; | ||
| } | ||
|
|
||
| const resource = `projects/${project}/databases/(default)/documents/${refPath}`; | ||
| const proto = isEmpty(data) | ||
| ? resource | ||
| : { | ||
| fields: objectToValueProto(data), | ||
| createTime: dateToTimestampProto( | ||
| get(options, 'createTime', new Date().toISOString()) | ||
| ), | ||
| updateTime: dateToTimestampProto( | ||
| get(options, 'updateTime', new Date().toISOString()) | ||
| ), | ||
| name: resource, | ||
| }; | ||
| const proto = | ||
| !data || Object.keys(data).length === 0 | ||
| ? resource | ||
| : { | ||
| fields: objectToValueProto(data), | ||
| createTime: dateToTimestampProto( | ||
| options?.createTime ?? new Date().toISOString() | ||
| ), | ||
| updateTime: dateToTimestampProto( | ||
| options?.updateTime ?? new Date().toISOString() | ||
| ), | ||
| name: resource, | ||
| }; | ||
|
|
||
| const readTimeProto = dateToTimestampProto( | ||
| get(options, 'readTime') || new Date().toISOString() | ||
| options?.readTime || new Date().toISOString() | ||
| ); | ||
| return firestoreService.snapshot_(proto, readTimeProto, 'json'); | ||
| } | ||
|
|
@@ -206,8 +227,8 @@ export function objectToValueProto(data: object) { | |
| }; | ||
| } | ||
| if (val instanceof firestore.DocumentReference) { | ||
| const projectId: string = get(val, '_referencePath.projectId'); | ||
| const database: string = get(val, '_referencePath.databaseId'); | ||
| const projectId: string = (val as any)._referencePath?.projectId; | ||
| const database: string = (val as any)._referencePath?.databaseId; | ||
| const referenceValue: string = [ | ||
| 'projects', | ||
| projectId, | ||
|
|
@@ -266,7 +287,11 @@ export function clearFirestoreData(options: { projectId: string } | string) { | |
|
|
||
| if (typeof options === 'string') { | ||
| projectId = options; | ||
| } else if (typeof options === 'object' && has(options, 'projectId')) { | ||
| } else if ( | ||
| typeof options === 'object' && | ||
| options && | ||
| 'projectId' in options | ||
| ) { | ||
| projectId = options.projectId; | ||
| } else { | ||
| throw new Error('projectId not specified'); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -20,7 +20,11 @@ | |||||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
| // SOFTWARE. | ||||||
|
|
||||||
| import { has, merge, random, get } from 'lodash'; | ||||||
| import { merge } from 'ts-deepmerge'; | ||||||
|
|
||||||
| function random(min: number, max: number): number { | ||||||
| return Math.floor(Math.random() * (max - min + 1)) + min; | ||||||
| } | ||||||
|
|
||||||
| import { | ||||||
| CloudFunction, | ||||||
|
|
@@ -124,13 +128,16 @@ export function wrapV1<T>( | |||||
| export function wrapV1<T>( | ||||||
| cloudFunction: CloudFunction<T> | ||||||
| ): WrappedScheduledFunction | WrappedFunction<T, CloudFunction<T>> { | ||||||
| if (!has(cloudFunction, '__endpoint')) { | ||||||
| if (!cloudFunction || !('__endpoint' in cloudFunction)) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If To make this check completely safe, ensure
Suggested change
|
||||||
| throw new Error( | ||||||
| 'Wrap can only be called on functions written with the firebase-functions SDK.' | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| if (has(cloudFunction, '__endpoint.scheduleTrigger')) { | ||||||
| if ( | ||||||
| cloudFunction?.__endpoint && | ||||||
| 'scheduleTrigger' in cloudFunction.__endpoint | ||||||
| ) { | ||||||
| const scheduledWrapped: WrappedScheduledFunction = ( | ||||||
| options: ContextOptions | ||||||
| ) => { | ||||||
|
|
@@ -147,19 +154,21 @@ export function wrapV1<T>( | |||||
| return scheduledWrapped; | ||||||
| } | ||||||
|
|
||||||
| if (has(cloudFunction, '__endpoint.httpsTrigger')) { | ||||||
| if (cloudFunction?.__endpoint && 'httpsTrigger' in cloudFunction.__endpoint) { | ||||||
| throw new Error( | ||||||
| 'Wrap function is only available for `onCall` HTTP functions, not `onRequest`.' | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| if (!has(cloudFunction, 'run')) { | ||||||
| if (!cloudFunction || !('run' in cloudFunction)) { | ||||||
| throw new Error( | ||||||
| 'This library can only be used with functions written with firebase-functions v1.0.0 and above' | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| const isCallableFunction = has(cloudFunction, '__endpoint.callableTrigger'); | ||||||
| const isCallableFunction = | ||||||
| !!cloudFunction?.__endpoint && | ||||||
| 'callableTrigger' in cloudFunction.__endpoint; | ||||||
|
|
||||||
| let wrapped: WrappedFunction<T, typeof cloudFunction> = (data, options) => { | ||||||
| // Although in Typescript we require `options` some of our JS samples do not pass it. | ||||||
|
|
@@ -183,8 +192,7 @@ export function wrapV1<T>( | |||||
| const defaultContext = _makeDefaultContext(cloudFunction, _options, data); | ||||||
|
|
||||||
| if ( | ||||||
| has(defaultContext, 'eventType') && | ||||||
| defaultContext.eventType !== undefined && | ||||||
| defaultContext?.eventType && | ||||||
| defaultContext.eventType.match(/firebase.database/) | ||||||
| ) { | ||||||
| defaultContext.authType = 'UNAUTHENTICATED'; | ||||||
|
|
@@ -208,7 +216,7 @@ export function _makeResourceName( | |||||
| const wildcardRegex = new RegExp('{[^/{}]*}', 'g'); | ||||||
| let resourceName = resource.replace(wildcardRegex, (wildcard) => { | ||||||
| let wildcardNoBraces = wildcard.slice(1, -1); // .slice removes '{' and '}' from wildcard | ||||||
| let sub = get(params, wildcardNoBraces); | ||||||
| let sub = params?.[wildcardNoBraces]; | ||||||
| return sub || wildcardNoBraces + random(1, 9); | ||||||
| }); | ||||||
| return resourceName; | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom
isPlainObjectimplementation does not handle objects created withObject.create(null)(which have anullprototype). In Lodash,_.isPlainObjectreturnstruefor such objects.To ensure full compatibility with Lodash's behavior and prevent unexpected serialization failures when users pass clean dictionary objects (created via
Object.create(null)), update the helper to also check for anullprototype.