Open
Description
Describe your environment
- Operating System version: N/A (type bug)
- Browser version: N/A (type bug)
- Firebase SDK version: 9.0.2
- Firebase Product: database
Describe the problem
The Firebase Realtime Database uses any
to represent a valid JSON value, but this allows customers to accidentally use disallowed values, such as this SO question
Relevant Code:
// Notice that this handler uses `async`, so its return type is Promise<number>, not
// number. We don't handle this case.
const counterTxn = await runTransaction(firebaseRef, async node => {
if (!node) { return 1 };
return +node + 1;
});
This code compiles cleanly because we asked for a handler that takes an any
and returns an any
, but the customer is returning a Promise
thanks to the async
keyword and we definitely don't handle Promises in transaction
.
Suggestion
We should be much more explicit about our type info. For example, if we add the following type:
type JsonValue = string | number | boolean | null | Array<JsonValue> | Record<string, JsonValue>;
We can now be much more explicit in our SDK and turn some runtime errors into compile-time errors
export interface DataSnapshot {
exportVal(): JsonValue;
toJSON(): JsonValue;
val(): JsonValue;
}
export interface OnDisconnect {
set(value: JsonValue, onComplete?: (a: Error | null) => any): Promise<void>
setWithPriority(
value: JsonValue,
priority: number | string | null,
onCompelte?: (a: Error | null) => any
): Promise<any>
update(values: Record<string, JsonValue>, onComplete?: (a: Error | null) => any): Promise<any>;
}
export interface Reference extends Query {
push(value?: JsonValue, onComplete?: (a: Error | null) => any): ThenableReference;
set(value?: JsonValue, onComplete?: (a: Error | null) => any): Promise<any>;
setWithPriority(
newVal: JsonValue,
newPriority: string | number | null,
onComplete?: (a: Error | null) => any
): Promise<any>
transaction(
transactionUpdate: (a: JsonValue) => JsonValue | undefined,
onComplete?: (a: Error | null, b: boolean, c: DataSnapshot | null) => any,
applyLocally?: boolean
): Promise<any>;
update(values: Record<string, JsonValue>, onComplete?: (a: Error | null) => any): Promise<any>;
}
If we used these more descriptive typings, then the Stack Overflow asker would instead have had an error:
Argument of type '() => Promise<number>' is not assignable to parameter of type '(a: JsonValue) => JsonValue | undefined'
in the above sample