UNPKG

10.9 kBJavaScriptView Raw
1"use strict";
2// The MIT License (MIT)
3//
4// Copyright (c) 2022 Firebase
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23Object.defineProperty(exports, "__esModule", { value: true });
24exports.DataSnapshot = void 0;
25const database = require("firebase-admin/database");
26const config_1 = require("../../common/config");
27const path_1 = require("../../common/utilities/path");
28/**
29 * Interface representing a Firebase Realtime database data snapshot.
30 */
31class DataSnapshot {
32 constructor(data, path, // path is undefined for the database root
33 app, instance) {
34 this.app = app;
35 const config = (0, config_1.firebaseConfig)();
36 if (instance) {
37 // SDK always supplies instance, but user's unit tests may not
38 this.instance = instance;
39 }
40 else if (app) {
41 this.instance = app.options.databaseURL;
42 }
43 else if (config.databaseURL) {
44 this.instance = config.databaseURL;
45 }
46 else if (process.env.GCLOUD_PROJECT) {
47 this.instance = "https://" + process.env.GCLOUD_PROJECT + "-default-rtdb.firebaseio.com";
48 }
49 this._path = path;
50 this._data = data;
51 }
52 /**
53 * Returns a [`Reference`](/docs/reference/admin/node/admin.database.Reference)
54 * to the database location where the triggering write occurred. Has
55 * full read and write access.
56 */
57 get ref() {
58 if (!this.app) {
59 // may be unpopulated in user's unit tests
60 throw new Error("Please supply a Firebase app in the constructor for DataSnapshot" +
61 " in order to use the .ref method.");
62 }
63 if (!this._ref) {
64 let db;
65 if (this.instance) {
66 db = database.getDatabaseWithUrl(this.instance, this.app);
67 }
68 else {
69 db = database.getDatabase(this.app);
70 }
71 this._ref = db.ref(this._fullPath());
72 }
73 return this._ref;
74 }
75 /**
76 * The key (last part of the path) of the location of this `DataSnapshot`.
77 *
78 * The last token in a database location is considered its key. For example,
79 * "ada" is the key for the `/users/ada/` node. Accessing the key on any
80 * `DataSnapshot` returns the key for the location that generated it.
81 * However, accessing the key on the root URL of a database returns `null`.
82 */
83 get key() {
84 const segments = (0, path_1.pathParts)(this._fullPath());
85 const last = segments[segments.length - 1];
86 return !last || last === "" ? null : last;
87 }
88 /**
89 * Extracts a JavaScript value from a `DataSnapshot`.
90 *
91 * Depending on the data in a `DataSnapshot`, the `val()` method may return a
92 * scalar type (string, number, or boolean), an array, or an object. It may also
93 * return `null`, indicating that the `DataSnapshot` is empty (contains no
94 * data).
95 *
96 * @return The snapshot's contents as a JavaScript value (Object,
97 * Array, string, number, boolean, or `null`).
98 */
99 val() {
100 const parts = (0, path_1.pathParts)(this._childPath);
101 let source = this._data;
102 if (parts.length) {
103 for (const part of parts) {
104 if (source[part] === undefined) {
105 return null;
106 }
107 source = source[part];
108 }
109 }
110 const node = source !== null && source !== void 0 ? source : null;
111 return this._checkAndConvertToArray(node);
112 }
113 /**
114 * Exports the entire contents of the `DataSnapshot` as a JavaScript object.
115 *
116 * @return The contents of the `DataSnapshot` as a JavaScript value
117 * (Object, Array, string, number, boolean, or `null`).
118 */
119 exportVal() {
120 return this.val();
121 }
122 /**
123 * Gets the priority value of the data in this `DataSnapshot`.
124 *
125 * As an alternative to using priority, applications can order collections by
126 * ordinary properties. See [Sorting and filtering
127 * data](/docs/database/web/lists-of-data#sorting_and_filtering_data).
128 *
129 * @return The priority value of the data.
130 */
131 getPriority() {
132 return 0;
133 }
134 /**
135 * Returns `true` if this `DataSnapshot` contains any data. It is slightly more
136 * efficient than using `snapshot.val() !== null`.
137 *
138 * @return `true` if this `DataSnapshot` contains any data; otherwise, `false`.
139 */
140 exists() {
141 const val = this.val();
142 if (typeof val === "undefined" || val === null) {
143 return false;
144 }
145 if (typeof val === "object" && Object.keys(val).length === 0) {
146 return false;
147 }
148 return true;
149 }
150 /**
151 * Gets a `DataSnapshot` for the location at the specified relative path.
152 *
153 * The relative path can either be a simple child name (for example, "ada") or
154 * a deeper slash-separated path (for example, "ada/name/first").
155 *
156 * @param path A relative path from this location to the desired child
157 * location.
158 * @return The specified child location.
159 */
160 child(childPath) {
161 if (!childPath) {
162 return this;
163 }
164 return this._dup(childPath);
165 }
166 /**
167 * Enumerates the `DataSnapshot`s of the children items.
168 *
169 * Because of the way JavaScript objects work, the ordering of data in the
170 * JavaScript object returned by `val()` is not guaranteed to match the ordering
171 * on the server nor the ordering of `child_added` events. That is where
172 * `forEach()` comes in handy. It guarantees the children of a `DataSnapshot`
173 * can be iterated in their query order.
174 *
175 * If no explicit `orderBy*()` method is used, results are returned
176 * ordered by key (unless priorities are used, in which case, results are
177 * returned by priority).
178 *
179 * @param action A function that is called for each child `DataSnapshot`.
180 * The callback can return `true` to cancel further enumeration.
181 *
182 * @return `true` if enumeration was canceled due to your callback
183 * returning `true`.
184 */
185 forEach(action) {
186 const val = this.val() || {};
187 if (typeof val === "object") {
188 return Object.keys(val).some((key) => action(this.child(key)) === true);
189 }
190 return false;
191 }
192 /**
193 * Returns `true` if the specified child path has (non-`null`) data.
194 *
195 * @param path A relative path to the location of a potential child.
196 * @return `true` if data exists at the specified child path; otherwise,
197 * `false`.
198 */
199 hasChild(childPath) {
200 return this.child(childPath).exists();
201 }
202 /**
203 * Returns whether or not the `DataSnapshot` has any non-`null` child
204 * properties.
205 *
206 * You can use `hasChildren()` to determine if a `DataSnapshot` has any
207 * children. If it does, you can enumerate them using `forEach()`. If it
208 * doesn't, then either this snapshot contains a primitive value (which can be
209 * retrieved with `val()`) or it is empty (in which case, `val()` returns
210 * `null`).
211 *
212 * @return `true` if this snapshot has any children; else `false`.
213 */
214 hasChildren() {
215 const val = this.val();
216 return val !== null && typeof val === "object" && Object.keys(val).length > 0;
217 }
218 /**
219 * Returns the number of child properties of this `DataSnapshot`.
220 *
221 * @return Number of child properties of this `DataSnapshot`.
222 */
223 numChildren() {
224 const val = this.val();
225 return val !== null && typeof val === "object" ? Object.keys(val).length : 0;
226 }
227 /**
228 * Returns a JSON-serializable representation of this object.
229 *
230 * @return A JSON-serializable representation of this object.
231 */
232 toJSON() {
233 return this.val();
234 }
235 /** Recursive function to check if keys are numeric & convert node object to array if they are
236 *
237 * @hidden
238 */
239 _checkAndConvertToArray(node) {
240 if (node === null || typeof node === "undefined") {
241 return null;
242 }
243 if (typeof node !== "object") {
244 return node;
245 }
246 const obj = {};
247 let numKeys = 0;
248 let maxKey = 0;
249 let allIntegerKeys = true;
250 for (const key in node) {
251 if (!node.hasOwnProperty(key)) {
252 continue;
253 }
254 const childNode = node[key];
255 const v = this._checkAndConvertToArray(childNode);
256 if (v === null) {
257 // Empty child node
258 continue;
259 }
260 obj[key] = v;
261 numKeys++;
262 const integerRegExp = /^(0|[1-9]\d*)$/;
263 if (allIntegerKeys && integerRegExp.test(key)) {
264 maxKey = Math.max(maxKey, Number(key));
265 }
266 else {
267 allIntegerKeys = false;
268 }
269 }
270 if (numKeys === 0) {
271 // Empty node
272 return null;
273 }
274 if (allIntegerKeys && maxKey < 2 * numKeys) {
275 // convert to array.
276 const array = [];
277 for (const key of Object.keys(obj)) {
278 array[key] = obj[key];
279 }
280 return array;
281 }
282 return obj;
283 }
284 /** @hidden */
285 _dup(childPath) {
286 const dup = new DataSnapshot(this._data, undefined, this.app, this.instance);
287 [dup._path, dup._childPath] = [this._path, this._childPath];
288 if (childPath) {
289 dup._childPath = (0, path_1.joinPath)(dup._childPath, childPath);
290 }
291 return dup;
292 }
293 /** @hidden */
294 _fullPath() {
295 return (this._path || "") + "/" + (this._childPath || "");
296 }
297}
298exports.DataSnapshot = DataSnapshot;