Skip to content

Forced RTDB to adhere to valid types #6726

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

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 8 additions & 3 deletions common/api-review/database.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ export interface DatabaseReference extends Query {
export class DataSnapshot {
child(path: string): DataSnapshot;
exists(): boolean;
exportVal(): any;
exportVal(): JSONValue;
forEach(action: (child: DataSnapshot) => boolean | void): boolean;
hasChild(path: string): boolean;
hasChildren(): boolean;
get key(): string | null;
get priority(): string | number | null;
readonly ref: DatabaseReference;
get size(): number;
toJSON(): object | null;
toJSON(): JSONValue;
val(): any;
}

Expand Down Expand Up @@ -85,6 +85,11 @@ export function goOnline(db: Database): void;
// @public
export function increment(delta: number): object;

// @public (undocumented)
export type JSONValue = string | number | boolean | undefined | null | JSONValue[] | {
[x: string]: JSONValue;
};

// @public
export function limitToFirst(limit: number): QueryConstraint;

Expand Down Expand Up @@ -200,7 +205,7 @@ export function refFromURL(db: Database, url: string): DatabaseReference;
export function remove(ref: DatabaseReference): Promise<void>;

// @public
export function runTransaction(ref: DatabaseReference, transactionUpdate: (currentData: any) => unknown, options?: TransactionOptions): Promise<TransactionResult>;
export function runTransaction(ref: DatabaseReference, transactionUpdate: (currentData: JSONValue) => JSONValue | void, options?: TransactionOptions): Promise<TransactionResult>;

// @public
export function serverTimestamp(): object;
Expand Down
11 changes: 6 additions & 5 deletions packages/database-compat/src/api/Reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ import {
_validatePathString,
_validateWritablePath,
_UserCallback,
_QueryParams
_QueryParams,
JSONValue
} from '@firebase/database';
import {
Compat,
Expand Down Expand Up @@ -87,7 +88,7 @@ export class DataSnapshot implements Compat<ModularDataSnapshot> {
*
* @returns JSON representation of the DataSnapshot contents, or null if empty.
*/
val(): unknown {
val(): JSONValue {
validateArgCount('DataSnapshot.val', 0, 0, arguments.length);
return this._delegate.val();
}
Expand All @@ -97,14 +98,14 @@ export class DataSnapshot implements Compat<ModularDataSnapshot> {
* the entire node contents.
* @returns JSON representation of the DataSnapshot contents, or null if empty.
*/
exportVal(): unknown {
exportVal(): JSONValue {
validateArgCount('DataSnapshot.exportVal', 0, 0, arguments.length);
return this._delegate.exportVal();
}

// Do not create public documentation. This is intended to make JSON serialization work but is otherwise unnecessary
// for end-users
toJSON(): unknown {
toJSON(): JSONValue {
// Optional spacer argument is unnecessary because we're depending on recursion rather than stringifying the content
validateArgCount('DataSnapshot.toJSON', 0, 1, arguments.length);
return this._delegate.toJSON();
Expand Down Expand Up @@ -684,7 +685,7 @@ export class Reference extends Query implements Compat<ModularReference> {
}

transaction(
transactionUpdate: (currentData: unknown) => unknown,
transactionUpdate: (currentData: JSONValue) => void,
onComplete?: (
error: Error | null,
committed: boolean,
Expand Down
7 changes: 4 additions & 3 deletions packages/database-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@

import { FirebaseApp } from '@firebase/app-types';
import { EmulatorMockTokenOptions } from '@firebase/util';
import { JSONValue } from '@firebase/database';

export interface DataSnapshot {
child(path: string): DataSnapshot;
exists(): boolean;
exportVal(): any;
exportVal(): JSONValue;
forEach(action: (a: DataSnapshot) => boolean | void): boolean;
getPriority(): string | number | null;
hasChild(path: string): boolean;
Expand All @@ -30,7 +31,7 @@ export interface DataSnapshot {
numChildren(): number;
ref: Reference;
toJSON(): Object | null;
val(): any;
val(): JSONValue;
}

export interface Database {
Expand Down Expand Up @@ -138,7 +139,7 @@ export interface Reference extends Query {
onComplete?: (a: Error | null) => void
): Promise<void>;
transaction(
transactionUpdate: (a: any) => any,
transactionUpdate: (a: JSONValue) => JSONValue | void,
onComplete?: (a: Error | null, b: boolean, c: DataSnapshot | null) => void,
applyLocally?: boolean
): Promise<TransactionResult>;
Expand Down
1 change: 1 addition & 0 deletions packages/database/src/api.standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export {
update,
child
} from './api/Reference_impl';
export { JSONValue } from './core/snap/Node';
export { increment, serverTimestamp } from './api/ServerValue';
export {
runTransaction,
Expand Down
6 changes: 3 additions & 3 deletions packages/database/src/api/Reference_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { KEY_INDEX } from '../core/snap/indexes/KeyIndex';
import { PathIndex } from '../core/snap/indexes/PathIndex';
import { PRIORITY_INDEX } from '../core/snap/indexes/PriorityIndex';
import { VALUE_INDEX } from '../core/snap/indexes/ValueIndex';
import { Node } from '../core/snap/Node';
import { JSONValue, Node } from '../core/snap/Node';
import { syncPointSetReferenceConstructor } from '../core/SyncPoint';
import { syncTreeSetReferenceConstructor } from '../core/SyncTree';
import { parseRepoInfo } from '../core/util/libs/parser';
Expand Down Expand Up @@ -371,7 +371,7 @@ export class DataSnapshot {
* Array, string, number, boolean, or `null`).
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
exportVal(): any {
exportVal(): JSONValue {
return this._node.val(true);
}

Expand Down Expand Up @@ -442,7 +442,7 @@ export class DataSnapshot {
/**
* Returns a JSON-serializable representation of this object.
*/
toJSON(): object | null {
toJSON(): JSONValue {
return this.exportVal();
}

Expand Down
5 changes: 2 additions & 3 deletions packages/database/src/api/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { getModularInstance, Deferred } from '@firebase/util';

import { repoStartTransaction } from '../core/Repo';
import { PRIORITY_INDEX } from '../core/snap/indexes/PriorityIndex';
import { Node } from '../core/snap/Node';
import { JSONValue, Node } from '../core/snap/Node';
import { validateWritablePath } from '../core/util/validation';

import { DatabaseReference } from './Reference';
Expand Down Expand Up @@ -93,8 +93,7 @@ export class TransactionResult {
*/
export function runTransaction(
ref: DatabaseReference,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transactionUpdate: (currentData: any) => unknown,
transactionUpdate: (currentData: JSONValue) => JSONValue | void,
options?: TransactionOptions
): Promise<TransactionResult> {
ref = getModularInstance(ref);
Expand Down
4 changes: 2 additions & 2 deletions packages/database/src/core/Repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { ReadonlyRestClient } from './ReadonlyRestClient';
import { RepoInfo } from './RepoInfo';
import { ServerActions } from './ServerActions';
import { ChildrenNode } from './snap/ChildrenNode';
import { Node } from './snap/Node';
import { JSONValue, Node } from './snap/Node';
import { nodeFromJSON } from './snap/nodeFromJSON';
import { SnapshotHolder } from './SnapshotHolder';
import {
Expand Down Expand Up @@ -930,7 +930,7 @@ export function repoCallOnCompleteCallback(
export function repoStartTransaction(
repo: Repo,
path: Path,
transactionUpdate: (a: unknown) => unknown,
transactionUpdate: (a: JSONValue) => JSONValue | void,
onComplete: ((error: Error, committed: boolean, node: Node) => void) | null,
unwatcher: () => void,
applyLocally: boolean
Expand Down
11 changes: 6 additions & 5 deletions packages/database/src/core/snap/ChildrenNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
} from './indexes/PriorityIndex';
import { IndexMap } from './IndexMap';
import { LeafNode } from './LeafNode';
import { NamedNode, Node } from './Node';
import { JSONValue, NamedNode, Node } from './Node';
import { priorityHashText, setMaxNode, validatePriorityNode } from './snap';

export interface ChildrenNodeConstructor {
Expand Down Expand Up @@ -194,12 +194,12 @@ export class ChildrenNode implements Node {
private static INTEGER_REGEXP_ = /^(0|[1-9]\d*)$/;

/** @inheritDoc */
val(exportFormat?: boolean): object {
val(exportFormat?: boolean): JSONValue {
if (this.isEmpty()) {
return null;
}

const obj: { [k: string]: unknown } = {};
const obj: { [k: string]: JSONValue } = {};
let numKeys = 0,
maxKey = 0,
allIntegerKeys = true;
Expand All @@ -216,10 +216,11 @@ export class ChildrenNode implements Node {

if (!exportFormat && allIntegerKeys && maxKey < 2 * numKeys) {
// convert to array.
const array: unknown[] = [];
const array: JSONValue[] = [];
// eslint-disable-next-line guard-for-in
for (const key in obj) {
array[key as unknown as number] = obj[key];
// TODO(mtewani): Check the typings for this.
array[key] = obj[key];
}

return array;
Expand Down
13 changes: 11 additions & 2 deletions packages/database/src/core/snap/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ import { Path } from '../util/Path';

import { Index } from './indexes/Index';

export type JSONValue =
| string
| number
| boolean
| undefined
| null
| JSONValue[]
| { [x: string]: JSONValue };

/**
* Node is an interface defining the common functionality for nodes in
* a DataSnapshot.
Expand Down Expand Up @@ -111,13 +120,13 @@ export interface Node {
* each child. It's passed the child name and the child node.
* @returns The first truthy value return by action, or the last falsey one
*/
forEachChild(index: Index, action: (a: string, b: Node) => void): unknown;
forEachChild(index: Index, action: (a: string, b: Node) => void): JSONValue;

/**
* @param exportFormat - True for export format (also wire protocol format).
* @returns Value of this node as JSON.
*/
val(exportFormat?: boolean): unknown;
val(exportFormat?: boolean): JSONValue;

/**
* @returns hash representing the node contents.
Expand Down
2 changes: 1 addition & 1 deletion packages/database/test/exp/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ describe('Database@exp Tests', () => {

async function incrementViaTransaction() {
deferred = new Deferred<void>();
await runTransaction(counterRef, currentData => {
await runTransaction(counterRef, (currentData: number) => {
return currentData + 1;
});
// Wait for the snapshot listener to fire. They are not invoked inline
Expand Down