@firebase/data-connect 0.3.12 → 0.4.0-canary.78384d32c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/index.cjs.js +1012 -210
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.esm.js +1011 -212
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.node.cjs.js +960 -158
  6. package/dist/index.node.cjs.js.map +1 -1
  7. package/dist/internal.d.ts +311 -25
  8. package/dist/node-esm/index.node.esm.js +959 -160
  9. package/dist/node-esm/index.node.esm.js.map +1 -1
  10. package/dist/node-esm/src/api/DataConnect.d.ts +44 -3
  11. package/dist/node-esm/src/api/Reference.d.ts +2 -0
  12. package/dist/node-esm/src/api/index.d.ts +2 -1
  13. package/dist/node-esm/src/api/query.d.ts +9 -26
  14. package/dist/node-esm/src/api.browser.d.ts +2 -18
  15. package/dist/node-esm/src/api.node.d.ts +2 -1
  16. package/dist/node-esm/src/cache/Cache.d.ts +53 -0
  17. package/dist/node-esm/src/cache/CacheProvider.d.ts +25 -0
  18. package/dist/node-esm/src/cache/EntityDataObject.d.ts +37 -0
  19. package/dist/node-esm/src/cache/EntityNode.d.ts +56 -0
  20. package/dist/node-esm/src/cache/ImpactedQueryRefsAccumulator.d.ts +23 -0
  21. package/dist/node-esm/src/cache/InMemoryCacheProvider.d.ts +30 -0
  22. package/dist/node-esm/src/cache/ResultTree.d.ts +42 -0
  23. package/dist/node-esm/src/cache/ResultTreeProcessor.d.ts +40 -0
  24. package/dist/node-esm/src/cache/cacheUtils.d.ts +20 -0
  25. package/dist/node-esm/src/core/FirebaseAuthProvider.d.ts +3 -1
  26. package/dist/node-esm/src/core/query/QueryManager.d.ts +47 -0
  27. package/dist/node-esm/src/core/query/queryOptions.d.ts +25 -0
  28. package/dist/node-esm/src/core/query/subscribe.d.ts +67 -0
  29. package/dist/node-esm/src/network/fetch.d.ts +2 -5
  30. package/dist/node-esm/src/network/index.d.ts +1 -1
  31. package/dist/node-esm/src/network/transport/index.d.ts +37 -8
  32. package/dist/node-esm/src/network/transport/rest.d.ts +5 -17
  33. package/dist/node-esm/src/util/encoder.d.ts +4 -1
  34. package/dist/node-esm/src/util/url.d.ts +1 -0
  35. package/dist/private.d.ts +278 -17
  36. package/dist/public.d.ts +77 -3
  37. package/dist/src/api/DataConnect.d.ts +44 -3
  38. package/dist/src/api/Reference.d.ts +2 -0
  39. package/dist/src/api/index.d.ts +2 -1
  40. package/dist/src/api/query.d.ts +9 -26
  41. package/dist/src/api.browser.d.ts +2 -18
  42. package/dist/src/api.node.d.ts +2 -1
  43. package/dist/src/cache/Cache.d.ts +53 -0
  44. package/dist/src/cache/CacheProvider.d.ts +25 -0
  45. package/dist/src/cache/EntityDataObject.d.ts +37 -0
  46. package/dist/src/cache/EntityNode.d.ts +56 -0
  47. package/dist/src/cache/ImpactedQueryRefsAccumulator.d.ts +23 -0
  48. package/dist/src/cache/InMemoryCacheProvider.d.ts +30 -0
  49. package/dist/src/cache/ResultTree.d.ts +42 -0
  50. package/dist/src/cache/ResultTreeProcessor.d.ts +40 -0
  51. package/dist/src/cache/cacheUtils.d.ts +20 -0
  52. package/dist/src/core/FirebaseAuthProvider.d.ts +3 -1
  53. package/dist/src/core/query/QueryManager.d.ts +47 -0
  54. package/dist/src/core/query/queryOptions.d.ts +25 -0
  55. package/dist/src/core/query/subscribe.d.ts +67 -0
  56. package/dist/src/network/fetch.d.ts +2 -5
  57. package/dist/src/network/index.d.ts +1 -1
  58. package/dist/src/network/transport/index.d.ts +37 -8
  59. package/dist/src/network/transport/rest.d.ts +5 -17
  60. package/dist/src/util/encoder.d.ts +4 -1
  61. package/dist/src/util/url.d.ts +1 -0
  62. package/package.json +7 -7
  63. package/dist/node-esm/src/core/QueryManager.d.ts +0 -45
  64. package/dist/src/core/QueryManager.d.ts +0 -45
@@ -1,4 +1,4 @@
1
- import { FirebaseError, isCloudWorkstation, pingServer, updateEmulatorBanner } from '@firebase/util';
1
+ import { FirebaseError, isCloudWorkstation, generateSHA256Hash, pingServer, updateEmulatorBanner } from '@firebase/util';
2
2
  import { Logger } from '@firebase/logger';
3
3
  import { _isFirebaseServerApp, _removeServiceInstance, getApp, _getProvider, _registerComponent, registerVersion, SDK_VERSION as SDK_VERSION$1 } from '@firebase/app';
4
4
  import { Component } from '@firebase/component';
@@ -164,7 +164,7 @@ function getGoogApiClientValue(_isUsingGen, _callerSdkType) {
164
164
  }
165
165
  return str;
166
166
  }
167
- function dcFetch(url, body, { signal }, appId, accessToken, appCheckToken, _isUsingGen, _callerSdkType, _isUsingEmulator) {
167
+ async function dcFetch(url, body, { signal }, appId, accessToken, appCheckToken, _isUsingGen, _callerSdkType, _isUsingEmulator) {
168
168
  if (!connectFetch) {
169
169
  throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!');
170
170
  }
@@ -191,41 +191,44 @@ function dcFetch(url, body, { signal }, appId, accessToken, appCheckToken, _isUs
191
191
  if (isCloudWorkstation(url) && _isUsingEmulator) {
192
192
  fetchOptions.credentials = 'include';
193
193
  }
194
- return connectFetch(url, fetchOptions)
195
- .catch(err => {
194
+ let response;
195
+ try {
196
+ response = await connectFetch(url, fetchOptions);
197
+ }
198
+ catch (err) {
196
199
  throw new DataConnectError(Code.OTHER, 'Failed to fetch: ' + JSON.stringify(err));
197
- })
198
- .then(async (response) => {
199
- let jsonResponse = null;
200
- try {
201
- jsonResponse = await response.json();
202
- }
203
- catch (e) {
204
- throw new DataConnectError(Code.OTHER, JSON.stringify(e));
205
- }
206
- const message = getMessage(jsonResponse);
207
- if (response.status >= 400) {
208
- logError('Error while performing request: ' + JSON.stringify(jsonResponse));
209
- if (response.status === 401) {
210
- throw new DataConnectError(Code.UNAUTHORIZED, message);
211
- }
212
- throw new DataConnectError(Code.OTHER, message);
213
- }
214
- return jsonResponse;
215
- })
216
- .then(res => {
217
- if (res.errors && res.errors.length) {
218
- const stringified = JSON.stringify(res.errors);
219
- const response = {
220
- errors: res.errors,
221
- data: res.data
222
- };
223
- throw new DataConnectOperationError('DataConnect error while performing request: ' + stringified, response);
200
+ }
201
+ let jsonResponse;
202
+ try {
203
+ jsonResponse = await response.json();
204
+ }
205
+ catch (e) {
206
+ throw new DataConnectError(Code.OTHER, JSON.stringify(e));
207
+ }
208
+ const message = getErrorMessage(jsonResponse);
209
+ if (response.status >= 400) {
210
+ logError('Error while performing request: ' + JSON.stringify(jsonResponse));
211
+ if (response.status === 401) {
212
+ throw new DataConnectError(Code.UNAUTHORIZED, message);
224
213
  }
225
- return res;
226
- });
214
+ throw new DataConnectError(Code.OTHER, message);
215
+ }
216
+ if (jsonResponse.errors && jsonResponse.errors.length) {
217
+ const stringified = JSON.stringify(jsonResponse.errors);
218
+ const failureResponse = {
219
+ errors: jsonResponse.errors,
220
+ data: jsonResponse.data
221
+ };
222
+ throw new DataConnectOperationError('DataConnect error while performing request: ' + stringified, failureResponse);
223
+ }
224
+ if (!jsonResponse.extensions) {
225
+ jsonResponse.extensions = {
226
+ dataConnect: []
227
+ };
228
+ }
229
+ return jsonResponse;
227
230
  }
228
- function getMessage(obj) {
231
+ function getErrorMessage(obj) {
229
232
  if ('message' in obj && obj.message) {
230
233
  return obj.message;
231
234
  }
@@ -233,7 +236,521 @@ function getMessage(obj) {
233
236
  }
234
237
 
235
238
  const name = "@firebase/data-connect";
236
- const version = "0.3.12";
239
+ const version = "0.4.0-canary.78384d32c";
240
+
241
+ /**
242
+ * @license
243
+ * Copyright 2025 Google LLC
244
+ *
245
+ * Licensed under the Apache License, Version 2.0 (the "License");
246
+ * you may not use this file except in compliance with the License.
247
+ * You may obtain a copy of the License at
248
+ *
249
+ * http://www.apache.org/licenses/LICENSE-2.0
250
+ *
251
+ * Unless required by applicable law or agreed to in writing, software
252
+ * distributed under the License is distributed on an "AS IS" BASIS,
253
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
254
+ * See the License for the specific language governing permissions and
255
+ * limitations under the License.
256
+ */
257
+ class EntityDataObject {
258
+ getServerValue(key) {
259
+ return this.serverValues[key];
260
+ }
261
+ constructor(globalID) {
262
+ this.globalID = globalID;
263
+ this.serverValues = {};
264
+ this.referencedFrom = new Set();
265
+ }
266
+ getServerValues() {
267
+ return this.serverValues;
268
+ }
269
+ toJSON() {
270
+ return {
271
+ globalID: this.globalID,
272
+ map: this.serverValues,
273
+ referencedFrom: Array.from(this.referencedFrom)
274
+ };
275
+ }
276
+ static fromJSON(json) {
277
+ const edo = new EntityDataObject(json.globalID);
278
+ edo.serverValues = json.map;
279
+ edo.referencedFrom = new Set(json.referencedFrom);
280
+ return edo;
281
+ }
282
+ updateServerValue(key, value, requestedFrom) {
283
+ this.serverValues[key] = value;
284
+ this.referencedFrom.add(requestedFrom);
285
+ return Array.from(this.referencedFrom);
286
+ }
287
+ }
288
+
289
+ /**
290
+ * @license
291
+ * Copyright 2025 Google LLC
292
+ *
293
+ * Licensed under the Apache License, Version 2.0 (the "License");
294
+ * you may not use this file except in compliance with the License.
295
+ * You may obtain a copy of the License at
296
+ *
297
+ * http://www.apache.org/licenses/LICENSE-2.0
298
+ *
299
+ * Unless required by applicable law or agreed to in writing, software
300
+ * distributed under the License is distributed on an "AS IS" BASIS,
301
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
302
+ * See the License for the specific language governing permissions and
303
+ * limitations under the License.
304
+ */
305
+ class InMemoryCacheProvider {
306
+ constructor(_keyId) {
307
+ this._keyId = _keyId;
308
+ this.edos = new Map();
309
+ this.resultTrees = new Map();
310
+ }
311
+ async setResultTree(queryId, rt) {
312
+ this.resultTrees.set(queryId, rt);
313
+ }
314
+ async getResultTree(queryId) {
315
+ return this.resultTrees.get(queryId);
316
+ }
317
+ async updateEntityData(entityData) {
318
+ this.edos.set(entityData.globalID, entityData);
319
+ }
320
+ async getEntityData(globalId) {
321
+ if (!this.edos.has(globalId)) {
322
+ this.edos.set(globalId, new EntityDataObject(globalId));
323
+ }
324
+ // Because of the above, we can guarantee that there will be an EDO at the globalId.
325
+ return this.edos.get(globalId);
326
+ }
327
+ close() {
328
+ // No-op
329
+ return Promise.resolve();
330
+ }
331
+ }
332
+
333
+ /**
334
+ * @license
335
+ * Copyright 2025 Google LLC
336
+ *
337
+ * Licensed under the Apache License, Version 2.0 (the "License");
338
+ * you may not use this file except in compliance with the License.
339
+ * You may obtain a copy of the License at
340
+ *
341
+ * http://www.apache.org/licenses/LICENSE-2.0
342
+ *
343
+ * Unless required by applicable law or agreed to in writing, software
344
+ * distributed under the License is distributed on an "AS IS" BASIS,
345
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
346
+ * See the License for the specific language governing permissions and
347
+ * limitations under the License.
348
+ */
349
+ const GLOBAL_ID_KEY = '_id';
350
+ const OBJECT_LISTS_KEY = '_objectLists';
351
+ const REFERENCES_KEY = '_references';
352
+ const SCALARS_KEY = '_scalars';
353
+ const ENTITY_DATA_KEYS_KEY = '_entity_data_keys';
354
+ class EntityNode {
355
+ constructor() {
356
+ this.scalars = {};
357
+ this.references = {};
358
+ this.objectLists = {};
359
+ this.entityDataKeys = new Set();
360
+ }
361
+ async loadData(queryId, values, entityIds, acc, cacheProvider) {
362
+ if (values === undefined) {
363
+ return;
364
+ }
365
+ if (typeof values !== 'object' || Array.isArray(values)) {
366
+ throw new DataConnectError(Code.INVALID_ARGUMENT, 'EntityNode initialized with non-object value');
367
+ }
368
+ if (values === null) {
369
+ return;
370
+ }
371
+ if (typeof values === 'object' &&
372
+ entityIds &&
373
+ entityIds[GLOBAL_ID_KEY] &&
374
+ typeof entityIds[GLOBAL_ID_KEY] === 'string') {
375
+ this.globalId = entityIds[GLOBAL_ID_KEY];
376
+ this.entityData = await cacheProvider.getEntityData(this.globalId);
377
+ }
378
+ for (const key in values) {
379
+ if (values.hasOwnProperty(key)) {
380
+ if (typeof values[key] === 'object') {
381
+ if (Array.isArray(values[key])) {
382
+ const ids = entityIds && entityIds[key];
383
+ const objArray = [];
384
+ const scalarArray = [];
385
+ for (const [index, value] of values[key].entries()) {
386
+ if (typeof value === 'object') {
387
+ if (Array.isArray(value)) ;
388
+ else {
389
+ const entityNode = new EntityNode();
390
+ await entityNode.loadData(queryId, value, ids && ids[index], acc, cacheProvider);
391
+ objArray.push(entityNode);
392
+ }
393
+ }
394
+ else {
395
+ scalarArray.push(value);
396
+ }
397
+ }
398
+ if (scalarArray.length > 0 && objArray.length > 0) {
399
+ this.scalars[key] = values[key];
400
+ }
401
+ else if (scalarArray.length > 0) {
402
+ if (this.entityData) {
403
+ const impactedRefs = this.entityData.updateServerValue(key, scalarArray, queryId);
404
+ this.entityDataKeys.add(key);
405
+ acc.add(impactedRefs);
406
+ }
407
+ else {
408
+ this.scalars[key] = scalarArray;
409
+ }
410
+ }
411
+ else if (objArray.length > 0) {
412
+ this.objectLists[key] = objArray;
413
+ }
414
+ else {
415
+ this.scalars[key] = [];
416
+ }
417
+ }
418
+ else {
419
+ if (values[key] === null) {
420
+ this.scalars[key] = null;
421
+ continue;
422
+ }
423
+ const entityNode = new EntityNode();
424
+ // TODO: Load Data might need to be pushed into ResultTreeProcessor instead.
425
+ await entityNode.loadData(queryId, values[key], entityIds && entityIds[key], acc, cacheProvider);
426
+ this.references[key] = entityNode;
427
+ }
428
+ }
429
+ else {
430
+ if (this.entityData) {
431
+ const impactedRefs = this.entityData.updateServerValue(key, values[key], queryId);
432
+ this.entityDataKeys.add(key);
433
+ acc.add(impactedRefs);
434
+ }
435
+ else {
436
+ this.scalars[key] = values[key];
437
+ }
438
+ }
439
+ }
440
+ }
441
+ if (this.entityData) {
442
+ await cacheProvider.updateEntityData(this.entityData);
443
+ }
444
+ }
445
+ toJSON(mode) {
446
+ const resultObject = {};
447
+ if (mode === EncodingMode.hydrated) {
448
+ if (this.entityData) {
449
+ for (const key of this.entityDataKeys) {
450
+ resultObject[key] = this.entityData.getServerValue(key);
451
+ }
452
+ }
453
+ if (this.scalars) {
454
+ Object.assign(resultObject, this.scalars);
455
+ }
456
+ if (this.references) {
457
+ for (const key in this.references) {
458
+ if (this.references.hasOwnProperty(key)) {
459
+ resultObject[key] = this.references[key].toJSON(mode);
460
+ }
461
+ }
462
+ }
463
+ if (this.objectLists) {
464
+ for (const key in this.objectLists) {
465
+ if (this.objectLists.hasOwnProperty(key)) {
466
+ resultObject[key] = this.objectLists[key].map(obj => obj.toJSON(mode));
467
+ }
468
+ }
469
+ }
470
+ return resultObject;
471
+ }
472
+ else {
473
+ // Get JSON representation of dehydrated list
474
+ if (this.entityData) {
475
+ resultObject[GLOBAL_ID_KEY] = this.entityData.globalID;
476
+ }
477
+ resultObject[ENTITY_DATA_KEYS_KEY] = Array.from(this.entityDataKeys);
478
+ if (this.scalars) {
479
+ resultObject[SCALARS_KEY] = this.scalars;
480
+ }
481
+ if (this.references) {
482
+ const references = {};
483
+ for (const key in this.references) {
484
+ if (this.references.hasOwnProperty(key)) {
485
+ references[key] = this.references[key].toJSON(mode);
486
+ }
487
+ }
488
+ resultObject[REFERENCES_KEY] = references;
489
+ }
490
+ if (this.objectLists) {
491
+ const objectLists = {};
492
+ for (const key in this.objectLists) {
493
+ if (this.objectLists.hasOwnProperty(key)) {
494
+ objectLists[key] = this.objectLists[key].map(obj => obj.toJSON(mode));
495
+ }
496
+ }
497
+ resultObject[OBJECT_LISTS_KEY] = objectLists;
498
+ }
499
+ }
500
+ return resultObject;
501
+ }
502
+ static fromJson(obj) {
503
+ const sdo = new EntityNode();
504
+ if (obj.backingData) {
505
+ sdo.entityData = EntityDataObject.fromJSON(obj.backingData);
506
+ }
507
+ sdo.globalId = obj.globalID;
508
+ sdo.scalars = obj.scalars;
509
+ if (obj.references) {
510
+ const references = {};
511
+ for (const key in obj.references) {
512
+ if (obj.references.hasOwnProperty(key)) {
513
+ references[key] = EntityNode.fromJson(obj.references[key]);
514
+ }
515
+ }
516
+ sdo.references = references;
517
+ }
518
+ if (obj.objectLists) {
519
+ const objectLists = {};
520
+ for (const key in obj.objectLists) {
521
+ if (obj.objectLists.hasOwnProperty(key)) {
522
+ objectLists[key] = obj.objectLists[key].map(obj => EntityNode.fromJson(obj));
523
+ }
524
+ }
525
+ sdo.objectLists = objectLists;
526
+ }
527
+ return sdo;
528
+ }
529
+ }
530
+ // Helpful for storing in persistent cache, which is not available yet.
531
+ var EncodingMode;
532
+ (function (EncodingMode) {
533
+ EncodingMode[EncodingMode["hydrated"] = 0] = "hydrated";
534
+ EncodingMode[EncodingMode["dehydrated"] = 1] = "dehydrated";
535
+ })(EncodingMode || (EncodingMode = {}));
536
+
537
+ /**
538
+ * @license
539
+ * Copyright 2025 Google LLC
540
+ *
541
+ * Licensed under the Apache License, Version 2.0 (the "License");
542
+ * you may not use this file except in compliance with the License.
543
+ * You may obtain a copy of the License at
544
+ *
545
+ * http://www.apache.org/licenses/LICENSE-2.0
546
+ *
547
+ * Unless required by applicable law or agreed to in writing, software
548
+ * distributed under the License is distributed on an "AS IS" BASIS,
549
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
550
+ * See the License for the specific language governing permissions and
551
+ * limitations under the License.
552
+ */
553
+ class ResultTree {
554
+ /**
555
+ * Create a {@link ResultTree} from a dehydrated JSON object.
556
+ * @param value The dehydrated JSON object.
557
+ * @returns The {@link ResultTree}.
558
+ */
559
+ static fromJson(value) {
560
+ return new ResultTree(EntityNode.fromJson(value.rootStub), value.maxAge, value.cachedAt, value.lastAccessed);
561
+ }
562
+ constructor(rootStub, maxAge = 0, cachedAt, _lastAccessed) {
563
+ this.rootStub = rootStub;
564
+ this.maxAge = maxAge;
565
+ this.cachedAt = cachedAt;
566
+ this._lastAccessed = _lastAccessed;
567
+ }
568
+ isStale() {
569
+ return (Date.now() - new Date(this.cachedAt.getTime()).getTime() >
570
+ this.maxAge * 1000);
571
+ }
572
+ updateMaxAge(maxAgeInSeconds) {
573
+ this.maxAge = maxAgeInSeconds;
574
+ }
575
+ updateAccessed() {
576
+ this._lastAccessed = new Date();
577
+ }
578
+ get lastAccessed() {
579
+ return this._lastAccessed;
580
+ }
581
+ getRootStub() {
582
+ return this.rootStub;
583
+ }
584
+ }
585
+
586
+ /**
587
+ * @license
588
+ * Copyright 2025 Google LLC
589
+ *
590
+ * Licensed under the Apache License, Version 2.0 (the "License");
591
+ * you may not use this file except in compliance with the License.
592
+ * You may obtain a copy of the License at
593
+ *
594
+ * http://www.apache.org/licenses/LICENSE-2.0
595
+ *
596
+ * Unless required by applicable law or agreed to in writing, software
597
+ * distributed under the License is distributed on an "AS IS" BASIS,
598
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
599
+ * See the License for the specific language governing permissions and
600
+ * limitations under the License.
601
+ */
602
+ class ImpactedQueryRefsAccumulator {
603
+ constructor(queryId) {
604
+ this.queryId = queryId;
605
+ this.impacted = new Set();
606
+ }
607
+ add(impacted) {
608
+ impacted
609
+ .filter(ref => ref !== this.queryId)
610
+ .forEach(ref => this.impacted.add(ref));
611
+ }
612
+ consumeEvents() {
613
+ const events = Array.from(this.impacted);
614
+ this.impacted.clear();
615
+ return events;
616
+ }
617
+ }
618
+
619
+ /**
620
+ * @license
621
+ * Copyright 2025 Google LLC
622
+ *
623
+ * Licensed under the Apache License, Version 2.0 (the "License");
624
+ * you may not use this file except in compliance with the License.
625
+ * You may obtain a copy of the License at
626
+ *
627
+ * http://www.apache.org/licenses/LICENSE-2.0
628
+ *
629
+ * Unless required by applicable law or agreed to in writing, software
630
+ * distributed under the License is distributed on an "AS IS" BASIS,
631
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
632
+ * See the License for the specific language governing permissions and
633
+ * limitations under the License.
634
+ */
635
+ class ResultTreeProcessor {
636
+ /**
637
+ * Hydrate the EntityNode into a JSON object so that it can be returned to the user.
638
+ * @param rootStubObject
639
+ * @returns {string}
640
+ */
641
+ hydrateResults(rootStubObject) {
642
+ return rootStubObject.toJSON(EncodingMode.hydrated);
643
+ }
644
+ /**
645
+ * Dehydrate results so that they can be stored in the cache.
646
+ * @param json
647
+ * @param entityIds
648
+ * @param cacheProvider
649
+ * @param queryId
650
+ * @returns {Promise<DehydratedResults>}
651
+ */
652
+ async dehydrateResults(json, entityIds, cacheProvider, queryId) {
653
+ const acc = new ImpactedQueryRefsAccumulator(queryId);
654
+ const entityNode = new EntityNode();
655
+ await entityNode.loadData(queryId, json, entityIds, acc, cacheProvider);
656
+ return {
657
+ entityNode,
658
+ impacted: acc.consumeEvents()
659
+ };
660
+ }
661
+ }
662
+
663
+ /**
664
+ * @license
665
+ * Copyright 2025 Google LLC
666
+ *
667
+ * Licensed under the Apache License, Version 2.0 (the "License");
668
+ * you may not use this file except in compliance with the License.
669
+ * You may obtain a copy of the License at
670
+ *
671
+ * http://www.apache.org/licenses/LICENSE-2.0
672
+ *
673
+ * Unless required by applicable law or agreed to in writing, software
674
+ * distributed under the License is distributed on an "AS IS" BASIS,
675
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
676
+ * See the License for the specific language governing permissions and
677
+ * limitations under the License.
678
+ */
679
+ class DataConnectCache {
680
+ constructor(authProvider, projectId, connectorConfig, host, cacheSettings) {
681
+ this.authProvider = authProvider;
682
+ this.projectId = projectId;
683
+ this.connectorConfig = connectorConfig;
684
+ this.host = host;
685
+ this.cacheSettings = cacheSettings;
686
+ this.cacheProvider = null;
687
+ this.uid = null;
688
+ this.authProvider.addTokenChangeListener(async (_) => {
689
+ const newUid = this.authProvider.getAuth().getUid();
690
+ // We should only close if the token changes and so does the new UID
691
+ if (this.uid !== newUid) {
692
+ this.cacheProvider?.close();
693
+ this.uid = newUid;
694
+ const identifier = await this.getIdentifier(this.uid);
695
+ this.cacheProvider = this.initializeNewProviders(identifier);
696
+ }
697
+ });
698
+ }
699
+ async initialize() {
700
+ if (!this.cacheProvider) {
701
+ const identifier = await this.getIdentifier(this.uid);
702
+ this.cacheProvider = this.initializeNewProviders(identifier);
703
+ }
704
+ }
705
+ async getIdentifier(uid) {
706
+ const identifier = `${'memory' // TODO: replace this with indexeddb when persistence is available.
707
+ }-${this.projectId}-${this.connectorConfig.service}-${this.connectorConfig.connector}-${this.connectorConfig.location}-${uid}-${this.host}`;
708
+ const sha256 = await generateSHA256Hash(identifier);
709
+ return sha256;
710
+ }
711
+ initializeNewProviders(identifier) {
712
+ return this.cacheSettings.cacheProvider.initialize(identifier);
713
+ }
714
+ async containsResultTree(queryId) {
715
+ await this.initialize();
716
+ const resultTree = await this.cacheProvider.getResultTree(queryId);
717
+ return resultTree !== undefined;
718
+ }
719
+ async getResultTree(queryId) {
720
+ await this.initialize();
721
+ return this.cacheProvider.getResultTree(queryId);
722
+ }
723
+ async getResultJSON(queryId) {
724
+ await this.initialize();
725
+ const processor = new ResultTreeProcessor();
726
+ const cacheProvider = this.cacheProvider;
727
+ const resultTree = await cacheProvider.getResultTree(queryId);
728
+ if (!resultTree) {
729
+ throw new DataConnectError(Code.INVALID_ARGUMENT, `${queryId} not found in cache. Call "update()" first.`);
730
+ }
731
+ return processor.hydrateResults(resultTree.getRootStub());
732
+ }
733
+ async update(queryId, serverValues, entityIds) {
734
+ await this.initialize();
735
+ const processor = new ResultTreeProcessor();
736
+ const cacheProvider = this.cacheProvider;
737
+ const { entityNode: stubDataObject, impacted } = await processor.dehydrateResults(serverValues, entityIds, cacheProvider, queryId);
738
+ const now = new Date();
739
+ await cacheProvider.setResultTree(queryId, new ResultTree(stubDataObject, serverValues.maxAge || this.cacheSettings.maxAgeSeconds, now, now));
740
+ return impacted;
741
+ }
742
+ }
743
+ class MemoryStub {
744
+ constructor() {
745
+ this.type = 'MEMORY';
746
+ }
747
+ /**
748
+ * @internal
749
+ */
750
+ initialize(cacheId) {
751
+ return new InMemoryCacheProvider(cacheId);
752
+ }
753
+ }
237
754
 
238
755
  /**
239
756
  * @license
@@ -325,6 +842,9 @@ class FirebaseAuthProvider {
325
842
  _authProvider.onInit(auth => (this._auth = auth));
326
843
  }
327
844
  }
845
+ getAuth() {
846
+ return this._auth;
847
+ }
328
848
  getToken(forceRefresh) {
329
849
  if (!this._auth) {
330
850
  return new Promise((resolve, reject) => {
@@ -384,7 +904,7 @@ const SOURCE_CACHE = 'CACHE';
384
904
 
385
905
  /**
386
906
  * @license
387
- * Copyright 2024 Google LLC
907
+ * Copyright 2026 Google LLC
388
908
  *
389
909
  * Licensed under the Apache License, Version 2.0 (the "License");
390
910
  * you may not use this file except in compliance with the License.
@@ -398,11 +918,43 @@ const SOURCE_CACHE = 'CACHE';
398
918
  * See the License for the specific language governing permissions and
399
919
  * limitations under the License.
400
920
  */
401
- let encoderImpl;
402
- function setEncoder(encoder) {
403
- encoderImpl = encoder;
921
+ function parseEntityIds(result) {
922
+ // Iterate through extensions.dataConnect
923
+ const dataConnectExtensions = result.extensions?.dataConnect;
924
+ const dataCopy = Object.assign(result);
925
+ if (!dataConnectExtensions) {
926
+ return dataCopy;
927
+ }
928
+ const ret = {};
929
+ for (const extension of dataConnectExtensions) {
930
+ const { path } = extension;
931
+ populatePath(path, ret, extension);
932
+ }
933
+ return ret;
934
+ }
935
+ // mutates the object to update the path
936
+ function populatePath(path, toUpdate, extension) {
937
+ let curObj = toUpdate;
938
+ for (const slice of path) {
939
+ if (typeof curObj[slice] !== 'object') {
940
+ curObj[slice] = {};
941
+ }
942
+ curObj = curObj[slice];
943
+ }
944
+ if ('entityId' in extension && extension.entityId) {
945
+ curObj['_id'] = extension.entityId;
946
+ }
947
+ else if ('entityIds' in extension) {
948
+ const entityArr = extension.entityIds;
949
+ for (let i = 0; i < entityArr.length; i++) {
950
+ const entityId = entityArr[i];
951
+ if (typeof curObj[i] === 'undefined') {
952
+ curObj[i] = {};
953
+ }
954
+ curObj[i]._id = entityId;
955
+ }
956
+ }
404
957
  }
405
- setEncoder(o => JSON.stringify(o));
406
958
 
407
959
  /**
408
960
  * @license
@@ -420,11 +972,24 @@ setEncoder(o => JSON.stringify(o));
420
972
  * See the License for the specific language governing permissions and
421
973
  * limitations under the License.
422
974
  */
423
- function setIfNotExists(map, key, val) {
424
- if (!map.has(key)) {
425
- map.set(key, val);
426
- }
975
+ let encoderImpl;
976
+ let decoderImpl;
977
+ function setEncoder(encoder) {
978
+ encoderImpl = encoder;
979
+ }
980
+ function setDecoder(decoder) {
981
+ decoderImpl = decoder;
982
+ }
983
+ function sortKeysForObj(o) {
984
+ return Object.keys(o)
985
+ .sort()
986
+ .reduce((accumulator, currentKey) => {
987
+ accumulator[currentKey] = o[currentKey];
988
+ return accumulator;
989
+ }, {});
427
990
  }
991
+ setEncoder((o) => JSON.stringify(sortKeysForObj(o)));
992
+ setDecoder(s => sortKeysForObj(JSON.parse(s)));
428
993
 
429
994
  /**
430
995
  * @license
@@ -442,7 +1007,7 @@ function setIfNotExists(map, key, val) {
442
1007
  * See the License for the specific language governing permissions and
443
1008
  * limitations under the License.
444
1009
  */
445
- function getRefSerializer(queryRef, data, source) {
1010
+ function getRefSerializer(queryRef, data, source, fetchTime) {
446
1011
  return function toJSON() {
447
1012
  return {
448
1013
  data,
@@ -454,32 +1019,65 @@ function getRefSerializer(queryRef, data, source) {
454
1019
  ...queryRef.dataConnect.getSettings()
455
1020
  }
456
1021
  },
457
- fetchTime: Date.now().toLocaleString(),
1022
+ fetchTime,
458
1023
  source
459
1024
  };
460
1025
  };
461
1026
  }
462
1027
  class QueryManager {
463
- constructor(transport) {
1028
+ async preferCacheResults(queryRef, allowStale = false) {
1029
+ let cacheResult;
1030
+ try {
1031
+ cacheResult = await this.fetchCacheResults(queryRef, allowStale);
1032
+ }
1033
+ catch (e) {
1034
+ // Ignore the error and try to fetch from the server.
1035
+ }
1036
+ if (cacheResult) {
1037
+ return cacheResult;
1038
+ }
1039
+ return this.fetchServerResults(queryRef);
1040
+ }
1041
+ constructor(transport, dc, cache) {
464
1042
  this.transport = transport;
465
- this._queries = new Map();
1043
+ this.dc = dc;
1044
+ this.cache = cache;
1045
+ this.callbacks = new Map();
1046
+ this.subscriptionCache = new Map();
1047
+ this.queue = [];
466
1048
  }
467
- track(queryName, variables, initialCache) {
468
- const ref = {
469
- name: queryName,
470
- variables,
471
- refType: QUERY_STR
472
- };
473
- const key = encoderImpl(ref);
474
- const newTrackedQuery = {
475
- ref,
476
- subscriptions: [],
477
- currentCache: initialCache || null,
478
- lastError: null
479
- };
480
- // @ts-ignore
481
- setIfNotExists(this._queries, key, newTrackedQuery);
482
- return this._queries.get(key);
1049
+ async waitForQueuedWrites() {
1050
+ for (const promise of this.queue) {
1051
+ await promise;
1052
+ }
1053
+ this.queue = [];
1054
+ }
1055
+ updateSSR(updatedData) {
1056
+ this.queue.push(this.updateCache(updatedData).then(async (result) => this.publishCacheResultsToSubscribers(result, updatedData.fetchTime)));
1057
+ }
1058
+ async updateCache(result, extensions) {
1059
+ await this.waitForQueuedWrites();
1060
+ if (this.cache) {
1061
+ const entityIds = parseEntityIds(result);
1062
+ const updatedMaxAge = getMaxAgeFromExtensions(extensions);
1063
+ if (updatedMaxAge !== undefined) {
1064
+ this.cache.cacheSettings.maxAgeSeconds = updatedMaxAge;
1065
+ }
1066
+ return this.cache.update(encoderImpl({
1067
+ name: result.ref.name,
1068
+ variables: result.ref.variables,
1069
+ refType: QUERY_STR
1070
+ }), result.data, entityIds);
1071
+ }
1072
+ else {
1073
+ const key = encoderImpl({
1074
+ name: result.ref.name,
1075
+ variables: result.ref.variables,
1076
+ refType: QUERY_STR
1077
+ });
1078
+ this.subscriptionCache.set(key, result);
1079
+ return [key];
1080
+ }
483
1081
  }
484
1082
  addSubscription(queryRef, onResultCallback, onCompleteCallback, onErrorCallback, initialCache) {
485
1083
  const key = encoderImpl({
@@ -487,99 +1085,213 @@ class QueryManager {
487
1085
  variables: queryRef.variables,
488
1086
  refType: QUERY_STR
489
1087
  });
490
- const trackedQuery = this._queries.get(key);
491
- const subscription = {
492
- userCallback: onResultCallback,
493
- onCompleteCallback,
494
- errCallback: onErrorCallback
495
- };
496
1088
  const unsubscribe = () => {
497
- const trackedQuery = this._queries.get(key);
498
- trackedQuery.subscriptions = trackedQuery.subscriptions.filter(sub => sub !== subscription);
499
- onCompleteCallback?.();
500
- };
501
- if (initialCache && trackedQuery.currentCache !== initialCache) {
502
- logDebug('Initial cache found. Comparing dates.');
503
- if (!trackedQuery.currentCache ||
504
- (trackedQuery.currentCache &&
505
- compareDates(trackedQuery.currentCache.fetchTime, initialCache.fetchTime))) {
506
- trackedQuery.currentCache = initialCache;
507
- }
508
- }
509
- if (trackedQuery.currentCache !== null) {
510
- const cachedData = trackedQuery.currentCache.data;
511
- onResultCallback({
512
- data: cachedData,
513
- source: SOURCE_CACHE,
514
- ref: queryRef,
515
- toJSON: getRefSerializer(queryRef, trackedQuery.currentCache.data, SOURCE_CACHE),
516
- fetchTime: trackedQuery.currentCache.fetchTime
517
- });
518
- if (trackedQuery.lastError !== null && onErrorCallback) {
519
- onErrorCallback(undefined);
1089
+ if (this.callbacks.has(key)) {
1090
+ const callbackList = this.callbacks.get(key);
1091
+ this.callbacks.set(key, callbackList.filter(callback => callback !== subscription));
1092
+ onCompleteCallback?.();
520
1093
  }
521
- }
522
- trackedQuery.subscriptions.push({
1094
+ };
1095
+ const subscription = {
523
1096
  userCallback: onResultCallback,
524
1097
  errCallback: onErrorCallback,
525
1098
  unsubscribe
526
- });
527
- if (!trackedQuery.currentCache) {
528
- logDebug(`No cache available for query ${queryRef.name} with variables ${JSON.stringify(queryRef.variables)}. Calling executeQuery.`);
529
- const promise = this.executeQuery(queryRef);
530
- // We want to ignore the error and let subscriptions handle it
531
- promise.then(undefined, err => { });
1099
+ };
1100
+ if (initialCache) {
1101
+ this.updateSSR(initialCache);
1102
+ }
1103
+ const promise = this.preferCacheResults(queryRef, /*allowStale=*/ true);
1104
+ // We want to ignore the error and let subscriptions handle it
1105
+ promise.then(undefined, err => { });
1106
+ if (!this.callbacks.has(key)) {
1107
+ this.callbacks.set(key, []);
532
1108
  }
1109
+ this.callbacks
1110
+ .get(key)
1111
+ .push(subscription);
533
1112
  return unsubscribe;
534
1113
  }
535
- executeQuery(queryRef) {
536
- if (queryRef.refType !== QUERY_STR) {
537
- throw new DataConnectError(Code.INVALID_ARGUMENT, `ExecuteQuery can only execute query operation`);
538
- }
1114
+ async fetchServerResults(queryRef) {
1115
+ await this.waitForQueuedWrites();
539
1116
  const key = encoderImpl({
540
1117
  name: queryRef.name,
541
1118
  variables: queryRef.variables,
542
1119
  refType: QUERY_STR
543
1120
  });
544
- const trackedQuery = this._queries.get(key);
545
- const result = this.transport.invokeQuery(queryRef.name, queryRef.variables);
546
- const newR = result.then(res => {
547
- const fetchTime = new Date().toString();
548
- const result = {
549
- ...res,
550
- source: SOURCE_SERVER,
1121
+ try {
1122
+ const result = await this.transport.invokeQuery(queryRef.name, queryRef.variables);
1123
+ const fetchTime = Date.now().toString();
1124
+ const originalExtensions = result.extensions;
1125
+ const queryResult = {
1126
+ ...result,
551
1127
  ref: queryRef,
552
- toJSON: getRefSerializer(queryRef, res.data, SOURCE_SERVER),
553
- fetchTime
1128
+ source: SOURCE_SERVER,
1129
+ fetchTime,
1130
+ data: result.data,
1131
+ extensions: getDataConnectExtensionsWithoutMaxAge(originalExtensions),
1132
+ toJSON: getRefSerializer(queryRef, result.data, SOURCE_SERVER, fetchTime)
554
1133
  };
555
- trackedQuery.subscriptions.forEach(subscription => {
556
- subscription.userCallback(result);
1134
+ let updatedKeys = [];
1135
+ updatedKeys = await this.updateCache(queryResult, originalExtensions?.dataConnect);
1136
+ this.publishDataToSubscribers(key, queryResult);
1137
+ if (this.cache) {
1138
+ await this.publishCacheResultsToSubscribers(updatedKeys, fetchTime);
1139
+ }
1140
+ else {
1141
+ this.subscriptionCache.set(key, queryResult);
1142
+ }
1143
+ return queryResult;
1144
+ }
1145
+ catch (e) {
1146
+ this.publishErrorToSubscribers(key, e);
1147
+ throw e;
1148
+ }
1149
+ }
1150
+ async fetchCacheResults(queryRef, allowStale = false) {
1151
+ await this.waitForQueuedWrites();
1152
+ let result;
1153
+ if (!this.cache) {
1154
+ result = await this.getFromSubscriberCache(queryRef);
1155
+ }
1156
+ else {
1157
+ result = await this.getFromResultTreeCache(queryRef, allowStale);
1158
+ }
1159
+ if (!result) {
1160
+ throw new DataConnectError(Code.OTHER, 'No cache entry found for query: ' + queryRef.name);
1161
+ }
1162
+ const fetchTime = Date.now().toString();
1163
+ const queryResult = {
1164
+ ...result,
1165
+ ref: queryRef,
1166
+ source: SOURCE_CACHE,
1167
+ fetchTime,
1168
+ data: result.data,
1169
+ extensions: result.extensions,
1170
+ toJSON: getRefSerializer(queryRef, result.data, SOURCE_CACHE, fetchTime)
1171
+ };
1172
+ if (this.cache) {
1173
+ const key = encoderImpl({
1174
+ name: queryRef.name,
1175
+ variables: queryRef.variables,
1176
+ refType: QUERY_STR
557
1177
  });
558
- trackedQuery.currentCache = {
559
- data: res.data,
560
- source: SOURCE_CACHE,
561
- fetchTime
562
- };
563
- return result;
564
- }, err => {
565
- trackedQuery.lastError = err;
566
- trackedQuery.subscriptions.forEach(subscription => {
567
- if (subscription.errCallback) {
568
- subscription.errCallback(err);
569
- }
1178
+ await this.publishCacheResultsToSubscribers([key], fetchTime);
1179
+ }
1180
+ else {
1181
+ const key = encoderImpl({
1182
+ name: queryRef.name,
1183
+ variables: queryRef.variables,
1184
+ refType: QUERY_STR
570
1185
  });
571
- throw err;
1186
+ this.subscriptionCache.set(key, queryResult);
1187
+ this.publishDataToSubscribers(key, queryResult);
1188
+ }
1189
+ return queryResult;
1190
+ }
1191
+ publishErrorToSubscribers(key, err) {
1192
+ this.callbacks.get(key)?.forEach(subscription => {
1193
+ if (subscription.errCallback) {
1194
+ subscription.errCallback(err);
1195
+ }
1196
+ });
1197
+ }
1198
+ async getFromResultTreeCache(queryRef, allowStale = false) {
1199
+ const key = encoderImpl({
1200
+ name: queryRef.name,
1201
+ variables: queryRef.variables,
1202
+ refType: QUERY_STR
1203
+ });
1204
+ if (!this.cache || !(await this.cache.containsResultTree(key))) {
1205
+ return null;
1206
+ }
1207
+ const cacheResult = (await this.cache.getResultJSON(key));
1208
+ const resultTree = await this.cache.getResultTree(key);
1209
+ if (!allowStale && resultTree.isStale()) {
1210
+ return null;
1211
+ }
1212
+ const result = {
1213
+ source: SOURCE_CACHE,
1214
+ ref: queryRef,
1215
+ data: cacheResult,
1216
+ toJSON: getRefSerializer(queryRef, cacheResult, SOURCE_CACHE, resultTree.cachedAt.toString()),
1217
+ fetchTime: resultTree.cachedAt.toString()
1218
+ };
1219
+ (await this.cache.getResultTree(key)).updateAccessed();
1220
+ return result;
1221
+ }
1222
+ async getFromSubscriberCache(queryRef) {
1223
+ const key = encoderImpl({
1224
+ name: queryRef.name,
1225
+ variables: queryRef.variables,
1226
+ refType: QUERY_STR
1227
+ });
1228
+ if (!this.subscriptionCache.has(key)) {
1229
+ return;
1230
+ }
1231
+ const result = this.subscriptionCache.get(key);
1232
+ result.source = SOURCE_CACHE;
1233
+ result.toJSON = getRefSerializer(result.ref, result.data, SOURCE_CACHE, result.fetchTime);
1234
+ return result;
1235
+ }
1236
+ publishDataToSubscribers(key, queryResult) {
1237
+ if (!this.callbacks.has(key)) {
1238
+ return;
1239
+ }
1240
+ const subscribers = this.callbacks.get(key);
1241
+ subscribers.forEach(callback => {
1242
+ callback.userCallback(queryResult);
572
1243
  });
573
- return newR;
1244
+ }
1245
+ async publishCacheResultsToSubscribers(impactedQueries, fetchTime) {
1246
+ if (!this.cache) {
1247
+ return;
1248
+ }
1249
+ for (const query of impactedQueries) {
1250
+ const callbacks = this.callbacks.get(query);
1251
+ if (!callbacks) {
1252
+ continue;
1253
+ }
1254
+ const newJson = (await this.cache.getResultTree(query))
1255
+ .getRootStub()
1256
+ .toJSON(EncodingMode.hydrated);
1257
+ const { name, variables } = decoderImpl(query);
1258
+ const queryRef = {
1259
+ dataConnect: this.dc,
1260
+ refType: QUERY_STR,
1261
+ name,
1262
+ variables
1263
+ };
1264
+ this.publishDataToSubscribers(query, {
1265
+ data: newJson,
1266
+ fetchTime,
1267
+ ref: queryRef,
1268
+ source: SOURCE_CACHE,
1269
+ toJSON: getRefSerializer(queryRef, newJson, SOURCE_CACHE, fetchTime)
1270
+ });
1271
+ }
574
1272
  }
575
1273
  enableEmulator(host, port) {
576
1274
  this.transport.useEmulator(host, port);
577
1275
  }
578
1276
  }
579
- function compareDates(str1, str2) {
580
- const date1 = new Date(str1);
581
- const date2 = new Date(str2);
582
- return date1.getTime() < date2.getTime();
1277
+ function getMaxAgeFromExtensions(extensions) {
1278
+ if (!extensions) {
1279
+ return;
1280
+ }
1281
+ for (const extension of extensions) {
1282
+ if ('maxAge' in extension &&
1283
+ extension.maxAge !== undefined &&
1284
+ extension.maxAge !== null) {
1285
+ if (extension.maxAge.endsWith('s')) {
1286
+ return Number(extension.maxAge.substring(0, extension.maxAge.length - 1));
1287
+ }
1288
+ }
1289
+ }
1290
+ }
1291
+ function getDataConnectExtensionsWithoutMaxAge(extensions) {
1292
+ return {
1293
+ dataConnect: extensions.dataConnect?.filter(extension => 'entityId' in extension || 'entityIds' in extension)
1294
+ };
583
1295
  }
584
1296
 
585
1297
  /**
@@ -598,11 +1310,12 @@ function compareDates(str1, str2) {
598
1310
  * See the License for the specific language governing permissions and
599
1311
  * limitations under the License.
600
1312
  */
1313
+ const PROD_HOST = 'firebasedataconnect.googleapis.com';
601
1314
  function urlBuilder(projectConfig, transportOptions) {
602
1315
  const { connector, location, projectId: project, service } = projectConfig;
603
1316
  const { host, sslEnabled, port } = transportOptions;
604
1317
  const protocol = sslEnabled ? 'https' : 'http';
605
- const realHost = host || `firebasedataconnect.googleapis.com`;
1318
+ const realHost = host || PROD_HOST;
606
1319
  let baseUrl = `${protocol}://${realHost}`;
607
1320
  if (typeof port === 'number') {
608
1321
  baseUrl += `:${port}`;
@@ -732,7 +1445,10 @@ class RESTTransport {
732
1445
  async getWithAuth(forceToken = false) {
733
1446
  let starterPromise = new Promise(resolve => resolve(this._accessToken));
734
1447
  if (this.appCheckProvider) {
735
- this._appCheckToken = (await this.appCheckProvider.getToken())?.token;
1448
+ const appCheckToken = await this.appCheckProvider.getToken();
1449
+ if (appCheckToken) {
1450
+ this._appCheckToken = appCheckToken.token;
1451
+ }
736
1452
  }
737
1453
  if (this.authProvider) {
738
1454
  starterPromise = this.authProvider
@@ -901,6 +1617,12 @@ class DataConnect {
901
1617
  }
902
1618
  }
903
1619
  }
1620
+ /**
1621
+ * @internal
1622
+ */
1623
+ getCache() {
1624
+ return this.cache;
1625
+ }
904
1626
  // @internal
905
1627
  _useGeneratedSdk() {
906
1628
  if (!this._isUsingGeneratedSdk) {
@@ -923,6 +1645,12 @@ class DataConnect {
923
1645
  delete copy.projectId;
924
1646
  return copy;
925
1647
  }
1648
+ /**
1649
+ * @internal
1650
+ */
1651
+ setCacheSettings(cacheSettings) {
1652
+ this._cacheSettings = cacheSettings;
1653
+ }
926
1654
  // @internal
927
1655
  setInitialized() {
928
1656
  if (this._initialized) {
@@ -932,19 +1660,25 @@ class DataConnect {
932
1660
  logDebug('transportClass not provided. Defaulting to RESTTransport.');
933
1661
  this._transportClass = RESTTransport;
934
1662
  }
935
- if (this._authProvider) {
936
- this._authTokenProvider = new FirebaseAuthProvider(this.app.name, this.app.options, this._authProvider);
1663
+ this._authTokenProvider = new FirebaseAuthProvider(this.app.name, this.app.options, this._authProvider);
1664
+ const connectorConfig = {
1665
+ connector: this.dataConnectOptions.connector,
1666
+ service: this.dataConnectOptions.service,
1667
+ location: this.dataConnectOptions.location
1668
+ };
1669
+ if (this._cacheSettings) {
1670
+ this.cache = new DataConnectCache(this._authTokenProvider, this.app.options.projectId, connectorConfig, this._transportOptions?.host || PROD_HOST, this._cacheSettings);
937
1671
  }
938
1672
  if (this._appCheckProvider) {
939
1673
  this._appCheckTokenProvider = new AppCheckTokenProvider(this.app, this._appCheckProvider);
940
1674
  }
941
- this._initialized = true;
942
1675
  this._transport = new this._transportClass(this.dataConnectOptions, this.app.options.apiKey, this.app.options.appId, this._authTokenProvider, this._appCheckTokenProvider, undefined, this._isUsingGeneratedSdk, this._callerSdkType);
943
1676
  if (this._transportOptions) {
944
1677
  this._transport.useEmulator(this._transportOptions.host, this._transportOptions.port, this._transportOptions.sslEnabled);
945
1678
  }
946
- this._queryManager = new QueryManager(this._transport);
1679
+ this._queryManager = new QueryManager(this._transport, this, this.cache);
947
1680
  this._mutationManager = new MutationManager(this._transport);
1681
+ this._initialized = true;
948
1682
  }
949
1683
  // @internal
950
1684
  enableEmulator(transportOptions) {
@@ -984,22 +1718,32 @@ function connectDataConnectEmulator(dc, host, port, sslEnabled = false) {
984
1718
  }
985
1719
  dc.enableEmulator({ host, port, sslEnabled });
986
1720
  }
987
- function getDataConnect(appOrOptions, optionalOptions) {
1721
+ function getDataConnect(appOrConnectorConfig, settingsOrConnectorConfig, settings) {
988
1722
  let app;
989
- let dcOptions;
990
- if ('location' in appOrOptions) {
991
- dcOptions = appOrOptions;
1723
+ let connectorConfig;
1724
+ let realSettings;
1725
+ if ('location' in appOrConnectorConfig) {
1726
+ connectorConfig = appOrConnectorConfig;
992
1727
  app = getApp();
1728
+ realSettings = settingsOrConnectorConfig;
993
1729
  }
994
1730
  else {
995
- dcOptions = optionalOptions;
996
- app = appOrOptions;
1731
+ app = appOrConnectorConfig;
1732
+ connectorConfig = settingsOrConnectorConfig;
1733
+ realSettings = settings;
997
1734
  }
998
1735
  if (!app || Object.keys(app).length === 0) {
999
1736
  app = getApp();
1000
1737
  }
1738
+ // Options to store in Firebase Component Provider.
1739
+ const serializedOptions = {
1740
+ ...connectorConfig,
1741
+ projectId: app.options.projectId
1742
+ };
1743
+ // We should sort the keys before initialization.
1744
+ const sortedSerialized = Object.fromEntries(Object.entries(serializedOptions).sort());
1001
1745
  const provider = _getProvider(app, 'data-connect');
1002
- const identifier = JSON.stringify(dcOptions);
1746
+ const identifier = JSON.stringify(sortedSerialized);
1003
1747
  if (provider.isInitialized(identifier)) {
1004
1748
  const dcInstance = provider.getImmediate({ identifier });
1005
1749
  const options = provider.getOptions(identifier);
@@ -1009,13 +1753,19 @@ function getDataConnect(appOrOptions, optionalOptions) {
1009
1753
  return dcInstance;
1010
1754
  }
1011
1755
  }
1012
- validateDCOptions(dcOptions);
1756
+ validateDCOptions(connectorConfig);
1013
1757
  logDebug('Creating new DataConnect instance');
1014
1758
  // Initialize with options.
1015
- return provider.initialize({
1759
+ const dataConnect = provider.initialize({
1016
1760
  instanceIdentifier: identifier,
1017
- options: dcOptions
1761
+ options: Object.fromEntries(Object.entries({
1762
+ ...sortedSerialized
1763
+ }).sort())
1018
1764
  });
1765
+ if (realSettings?.cacheSettings) {
1766
+ dataConnect.setCacheSettings(realSettings.cacheSettings);
1767
+ }
1768
+ return dataConnect;
1019
1769
  }
1020
1770
  /**
1021
1771
  *
@@ -1045,6 +1795,12 @@ function terminate(dataConnect) {
1045
1795
  return dataConnect._delete();
1046
1796
  // TODO(mtewani): Stop pending tasks
1047
1797
  }
1798
+ const StorageType = {
1799
+ MEMORY: 'MEMORY'
1800
+ };
1801
+ function makeMemoryCacheProvider() {
1802
+ return new MemoryStub();
1803
+ }
1048
1804
 
1049
1805
  /**
1050
1806
  * @license
@@ -1064,13 +1820,16 @@ function terminate(dataConnect) {
1064
1820
  */
1065
1821
  function registerDataConnect(variant) {
1066
1822
  setSDKVersion(SDK_VERSION$1);
1067
- _registerComponent(new Component('data-connect', (container, { instanceIdentifier: settings, options }) => {
1823
+ _registerComponent(new Component('data-connect', (container, { instanceIdentifier: connectorConfigStr, options }) => {
1068
1824
  const app = container.getProvider('app').getImmediate();
1069
1825
  const authProvider = container.getProvider('auth-internal');
1070
1826
  const appCheckProvider = container.getProvider('app-check-internal');
1071
1827
  let newOpts = options;
1072
- if (settings) {
1073
- newOpts = JSON.parse(settings);
1828
+ if (connectorConfigStr) {
1829
+ newOpts = {
1830
+ ...JSON.parse(connectorConfigStr),
1831
+ ...newOpts
1832
+ };
1074
1833
  }
1075
1834
  if (!app.options.projectId) {
1076
1835
  throw new DataConnectError(Code.INVALID_ARGUMENT, 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?');
@@ -1082,6 +1841,28 @@ function registerDataConnect(variant) {
1082
1841
  registerVersion(name, version, 'esm2020');
1083
1842
  }
1084
1843
 
1844
+ /**
1845
+ * @license
1846
+ * Copyright 2025 Google LLC
1847
+ *
1848
+ * Licensed under the Apache License, Version 2.0 (the "License");
1849
+ * you may not use this file except in compliance with the License.
1850
+ * You may obtain a copy of the License at
1851
+ *
1852
+ * http://www.apache.org/licenses/LICENSE-2.0
1853
+ *
1854
+ * Unless required by applicable law or agreed to in writing, software
1855
+ * distributed under the License is distributed on an "AS IS" BASIS,
1856
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1857
+ * See the License for the specific language governing permissions and
1858
+ * limitations under the License.
1859
+ */
1860
+ const QueryFetchPolicy = {
1861
+ PREFER_CACHE: 'PREFER_CACHE',
1862
+ CACHE_ONLY: 'CACHE_ONLY',
1863
+ SERVER_ONLY: 'SERVER_ONLY'
1864
+ };
1865
+
1085
1866
  /**
1086
1867
  * @license
1087
1868
  * Copyright 2024 Google LLC
@@ -1103,8 +1884,22 @@ function registerDataConnect(variant) {
1103
1884
  * @param queryRef query to execute.
1104
1885
  * @returns `QueryPromise`
1105
1886
  */
1106
- function executeQuery(queryRef) {
1107
- return queryRef.dataConnect._queryManager.executeQuery(queryRef);
1887
+ function executeQuery(queryRef, options) {
1888
+ if (queryRef.refType !== QUERY_STR) {
1889
+ return Promise.reject(new DataConnectError(Code.INVALID_ARGUMENT, `ExecuteQuery can only execute query operations`));
1890
+ }
1891
+ const queryManager = queryRef.dataConnect._queryManager;
1892
+ const fetchPolicy = options?.fetchPolicy ?? QueryFetchPolicy.PREFER_CACHE;
1893
+ switch (fetchPolicy) {
1894
+ case QueryFetchPolicy.SERVER_ONLY:
1895
+ return queryManager.fetchServerResults(queryRef);
1896
+ case QueryFetchPolicy.CACHE_ONLY:
1897
+ return queryManager.fetchCacheResults(queryRef, true);
1898
+ case QueryFetchPolicy.PREFER_CACHE:
1899
+ return queryManager.preferCacheResults(queryRef, false);
1900
+ default:
1901
+ throw new DataConnectError(Code.INVALID_ARGUMENT, `Invalid fetch policy: ${fetchPolicy}`);
1902
+ }
1108
1903
  }
1109
1904
  /**
1110
1905
  * Execute Query
@@ -1116,7 +1911,9 @@ function executeQuery(queryRef) {
1116
1911
  */
1117
1912
  function queryRef(dcInstance, queryName, variables, initialCache) {
1118
1913
  dcInstance.setInitialized();
1119
- dcInstance._queryManager.track(queryName, variables, initialCache);
1914
+ if (initialCache !== undefined) {
1915
+ dcInstance._queryManager.updateSSR(initialCache);
1916
+ }
1120
1917
  return {
1121
1918
  dataConnect: dcInstance,
1122
1919
  refType: QUERY_STR,
@@ -1179,7 +1976,7 @@ function validateArgs(connectorConfig, dcOrVars, vars, validateVars) {
1179
1976
 
1180
1977
  /**
1181
1978
  * @license
1182
- * Copyright 2024 Google LLC
1979
+ * Copyright 2025 Google LLC
1183
1980
  *
1184
1981
  * Licensed under the Apache License, Version 2.0 (the "License");
1185
1982
  * you may not use this file except in compliance with the License.
@@ -1207,12 +2004,14 @@ function subscribe(queryRefOrSerializedResult, observerOrOnNext, onError, onComp
1207
2004
  if ('refInfo' in queryRefOrSerializedResult) {
1208
2005
  const serializedRef = queryRefOrSerializedResult;
1209
2006
  const { data, source, fetchTime } = serializedRef;
2007
+ ref = toQueryRef(serializedRef);
1210
2008
  initialCache = {
1211
2009
  data,
1212
2010
  source,
1213
- fetchTime
2011
+ fetchTime,
2012
+ ref,
2013
+ toJSON: getRefSerializer(ref, data, source, fetchTime)
1214
2014
  };
1215
- ref = toQueryRef(serializedRef);
1216
2015
  }
1217
2016
  else {
1218
2017
  ref = queryRefOrSerializedResult;
@@ -1251,5 +2050,5 @@ function subscribe(queryRefOrSerializedResult, observerOrOnNext, onError, onComp
1251
2050
  initializeFetch(fetch);
1252
2051
  registerDataConnect('node');
1253
2052
 
1254
- export { CallerSdkTypeEnum, Code, DataConnect, DataConnectError, DataConnectOperationError, MUTATION_STR, MutationManager, QUERY_STR, SOURCE_CACHE, SOURCE_SERVER, areTransportOptionsEqual, connectDataConnectEmulator, executeMutation, executeQuery, getDataConnect, mutationRef, parseOptions, queryRef, setLogLevel, subscribe, terminate, toQueryRef, validateArgs, validateDCOptions };
2053
+ export { CallerSdkTypeEnum, Code, DataConnect, DataConnectError, DataConnectOperationError, MUTATION_STR, MutationManager, QUERY_STR, QueryFetchPolicy, SOURCE_CACHE, SOURCE_SERVER, StorageType, areTransportOptionsEqual, connectDataConnectEmulator, executeMutation, executeQuery, getDataConnect, makeMemoryCacheProvider, mutationRef, parseOptions, queryRef, setLogLevel, subscribe, terminate, toQueryRef, validateArgs, validateDCOptions };
1255
2054
  //# sourceMappingURL=index.node.esm.js.map