1 | ;
|
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.
|
23 | Object.defineProperty(exports, "__esModule", { value: true });
|
24 | exports.DataSnapshot = void 0;
|
25 | const database = require("firebase-admin/database");
|
26 | const config_1 = require("../../common/config");
|
27 | const path_1 = require("../../common/utilities/path");
|
28 | /**
|
29 | * Interface representing a Firebase Realtime database data snapshot.
|
30 | */
|
31 | class 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 | }
|
298 | exports.DataSnapshot = DataSnapshot;
|