@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
package/dist/index.esm.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { _isFirebaseServerApp, _removeServiceInstance, getApp, _getProvider, _registerComponent, registerVersion, SDK_VERSION as SDK_VERSION$1 } from '@firebase/app';
2
2
  import { Component } from '@firebase/component';
3
- import { FirebaseError, isCloudWorkstation, pingServer, updateEmulatorBanner } from '@firebase/util';
3
+ import { FirebaseError, generateSHA256Hash, isCloudWorkstation, pingServer, updateEmulatorBanner } from '@firebase/util';
4
4
  import { Logger } from '@firebase/logger';
5
5
 
6
6
  const name = "@firebase/data-connect";
7
- const version = "0.3.12";
7
+ const version = "0.4.0-canary.78384d32c";
8
8
 
9
9
  /**
10
10
  * @license
@@ -32,6 +32,572 @@ function setSDKVersion(version) {
32
32
  SDK_VERSION = version;
33
33
  }
34
34
 
35
+ /**
36
+ * @license
37
+ * Copyright 2024 Google LLC
38
+ *
39
+ * Licensed under the Apache License, Version 2.0 (the "License");
40
+ * you may not use this file except in compliance with the License.
41
+ * You may obtain a copy of the License at
42
+ *
43
+ * http://www.apache.org/licenses/LICENSE-2.0
44
+ *
45
+ * Unless required by applicable law or agreed to in writing, software
46
+ * distributed under the License is distributed on an "AS IS" BASIS,
47
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
48
+ * See the License for the specific language governing permissions and
49
+ * limitations under the License.
50
+ */
51
+ const Code = {
52
+ OTHER: 'other',
53
+ ALREADY_INITIALIZED: 'already-initialized',
54
+ NOT_INITIALIZED: 'not-initialized',
55
+ NOT_SUPPORTED: 'not-supported',
56
+ INVALID_ARGUMENT: 'invalid-argument',
57
+ PARTIAL_ERROR: 'partial-error',
58
+ UNAUTHORIZED: 'unauthorized'
59
+ };
60
+ /** An error returned by a DataConnect operation. */
61
+ class DataConnectError extends FirebaseError {
62
+ constructor(code, message) {
63
+ super(code, message);
64
+ /** @internal */
65
+ this.name = 'DataConnectError';
66
+ // Ensure the instanceof operator works as expected on subclasses of Error.
67
+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#custom_error_types
68
+ // and https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
69
+ Object.setPrototypeOf(this, DataConnectError.prototype);
70
+ }
71
+ /** @internal */
72
+ toString() {
73
+ return `${this.name}[code=${this.code}]: ${this.message}`;
74
+ }
75
+ }
76
+ /** An error returned by a DataConnect operation. */
77
+ class DataConnectOperationError extends DataConnectError {
78
+ /** @hideconstructor */
79
+ constructor(message, response) {
80
+ super(Code.PARTIAL_ERROR, message);
81
+ /** @internal */
82
+ this.name = 'DataConnectOperationError';
83
+ this.response = response;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * @license
89
+ * Copyright 2025 Google LLC
90
+ *
91
+ * Licensed under the Apache License, Version 2.0 (the "License");
92
+ * you may not use this file except in compliance with the License.
93
+ * You may obtain a copy of the License at
94
+ *
95
+ * http://www.apache.org/licenses/LICENSE-2.0
96
+ *
97
+ * Unless required by applicable law or agreed to in writing, software
98
+ * distributed under the License is distributed on an "AS IS" BASIS,
99
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
100
+ * See the License for the specific language governing permissions and
101
+ * limitations under the License.
102
+ */
103
+ class EntityDataObject {
104
+ getServerValue(key) {
105
+ return this.serverValues[key];
106
+ }
107
+ constructor(globalID) {
108
+ this.globalID = globalID;
109
+ this.serverValues = {};
110
+ this.referencedFrom = new Set();
111
+ }
112
+ getServerValues() {
113
+ return this.serverValues;
114
+ }
115
+ toJSON() {
116
+ return {
117
+ globalID: this.globalID,
118
+ map: this.serverValues,
119
+ referencedFrom: Array.from(this.referencedFrom)
120
+ };
121
+ }
122
+ static fromJSON(json) {
123
+ const edo = new EntityDataObject(json.globalID);
124
+ edo.serverValues = json.map;
125
+ edo.referencedFrom = new Set(json.referencedFrom);
126
+ return edo;
127
+ }
128
+ updateServerValue(key, value, requestedFrom) {
129
+ this.serverValues[key] = value;
130
+ this.referencedFrom.add(requestedFrom);
131
+ return Array.from(this.referencedFrom);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * @license
137
+ * Copyright 2025 Google LLC
138
+ *
139
+ * Licensed under the Apache License, Version 2.0 (the "License");
140
+ * you may not use this file except in compliance with the License.
141
+ * You may obtain a copy of the License at
142
+ *
143
+ * http://www.apache.org/licenses/LICENSE-2.0
144
+ *
145
+ * Unless required by applicable law or agreed to in writing, software
146
+ * distributed under the License is distributed on an "AS IS" BASIS,
147
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
148
+ * See the License for the specific language governing permissions and
149
+ * limitations under the License.
150
+ */
151
+ class InMemoryCacheProvider {
152
+ constructor(_keyId) {
153
+ this._keyId = _keyId;
154
+ this.edos = new Map();
155
+ this.resultTrees = new Map();
156
+ }
157
+ async setResultTree(queryId, rt) {
158
+ this.resultTrees.set(queryId, rt);
159
+ }
160
+ async getResultTree(queryId) {
161
+ return this.resultTrees.get(queryId);
162
+ }
163
+ async updateEntityData(entityData) {
164
+ this.edos.set(entityData.globalID, entityData);
165
+ }
166
+ async getEntityData(globalId) {
167
+ if (!this.edos.has(globalId)) {
168
+ this.edos.set(globalId, new EntityDataObject(globalId));
169
+ }
170
+ // Because of the above, we can guarantee that there will be an EDO at the globalId.
171
+ return this.edos.get(globalId);
172
+ }
173
+ close() {
174
+ // No-op
175
+ return Promise.resolve();
176
+ }
177
+ }
178
+
179
+ /**
180
+ * @license
181
+ * Copyright 2025 Google LLC
182
+ *
183
+ * Licensed under the Apache License, Version 2.0 (the "License");
184
+ * you may not use this file except in compliance with the License.
185
+ * You may obtain a copy of the License at
186
+ *
187
+ * http://www.apache.org/licenses/LICENSE-2.0
188
+ *
189
+ * Unless required by applicable law or agreed to in writing, software
190
+ * distributed under the License is distributed on an "AS IS" BASIS,
191
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
192
+ * See the License for the specific language governing permissions and
193
+ * limitations under the License.
194
+ */
195
+ const GLOBAL_ID_KEY = '_id';
196
+ const OBJECT_LISTS_KEY = '_objectLists';
197
+ const REFERENCES_KEY = '_references';
198
+ const SCALARS_KEY = '_scalars';
199
+ const ENTITY_DATA_KEYS_KEY = '_entity_data_keys';
200
+ class EntityNode {
201
+ constructor() {
202
+ this.scalars = {};
203
+ this.references = {};
204
+ this.objectLists = {};
205
+ this.entityDataKeys = new Set();
206
+ }
207
+ async loadData(queryId, values, entityIds, acc, cacheProvider) {
208
+ if (values === undefined) {
209
+ return;
210
+ }
211
+ if (typeof values !== 'object' || Array.isArray(values)) {
212
+ throw new DataConnectError(Code.INVALID_ARGUMENT, 'EntityNode initialized with non-object value');
213
+ }
214
+ if (values === null) {
215
+ return;
216
+ }
217
+ if (typeof values === 'object' &&
218
+ entityIds &&
219
+ entityIds[GLOBAL_ID_KEY] &&
220
+ typeof entityIds[GLOBAL_ID_KEY] === 'string') {
221
+ this.globalId = entityIds[GLOBAL_ID_KEY];
222
+ this.entityData = await cacheProvider.getEntityData(this.globalId);
223
+ }
224
+ for (const key in values) {
225
+ if (values.hasOwnProperty(key)) {
226
+ if (typeof values[key] === 'object') {
227
+ if (Array.isArray(values[key])) {
228
+ const ids = entityIds && entityIds[key];
229
+ const objArray = [];
230
+ const scalarArray = [];
231
+ for (const [index, value] of values[key].entries()) {
232
+ if (typeof value === 'object') {
233
+ if (Array.isArray(value)) ;
234
+ else {
235
+ const entityNode = new EntityNode();
236
+ await entityNode.loadData(queryId, value, ids && ids[index], acc, cacheProvider);
237
+ objArray.push(entityNode);
238
+ }
239
+ }
240
+ else {
241
+ scalarArray.push(value);
242
+ }
243
+ }
244
+ if (scalarArray.length > 0 && objArray.length > 0) {
245
+ this.scalars[key] = values[key];
246
+ }
247
+ else if (scalarArray.length > 0) {
248
+ if (this.entityData) {
249
+ const impactedRefs = this.entityData.updateServerValue(key, scalarArray, queryId);
250
+ this.entityDataKeys.add(key);
251
+ acc.add(impactedRefs);
252
+ }
253
+ else {
254
+ this.scalars[key] = scalarArray;
255
+ }
256
+ }
257
+ else if (objArray.length > 0) {
258
+ this.objectLists[key] = objArray;
259
+ }
260
+ else {
261
+ this.scalars[key] = [];
262
+ }
263
+ }
264
+ else {
265
+ if (values[key] === null) {
266
+ this.scalars[key] = null;
267
+ continue;
268
+ }
269
+ const entityNode = new EntityNode();
270
+ // TODO: Load Data might need to be pushed into ResultTreeProcessor instead.
271
+ await entityNode.loadData(queryId, values[key], entityIds && entityIds[key], acc, cacheProvider);
272
+ this.references[key] = entityNode;
273
+ }
274
+ }
275
+ else {
276
+ if (this.entityData) {
277
+ const impactedRefs = this.entityData.updateServerValue(key, values[key], queryId);
278
+ this.entityDataKeys.add(key);
279
+ acc.add(impactedRefs);
280
+ }
281
+ else {
282
+ this.scalars[key] = values[key];
283
+ }
284
+ }
285
+ }
286
+ }
287
+ if (this.entityData) {
288
+ await cacheProvider.updateEntityData(this.entityData);
289
+ }
290
+ }
291
+ toJSON(mode) {
292
+ const resultObject = {};
293
+ if (mode === EncodingMode.hydrated) {
294
+ if (this.entityData) {
295
+ for (const key of this.entityDataKeys) {
296
+ resultObject[key] = this.entityData.getServerValue(key);
297
+ }
298
+ }
299
+ if (this.scalars) {
300
+ Object.assign(resultObject, this.scalars);
301
+ }
302
+ if (this.references) {
303
+ for (const key in this.references) {
304
+ if (this.references.hasOwnProperty(key)) {
305
+ resultObject[key] = this.references[key].toJSON(mode);
306
+ }
307
+ }
308
+ }
309
+ if (this.objectLists) {
310
+ for (const key in this.objectLists) {
311
+ if (this.objectLists.hasOwnProperty(key)) {
312
+ resultObject[key] = this.objectLists[key].map(obj => obj.toJSON(mode));
313
+ }
314
+ }
315
+ }
316
+ return resultObject;
317
+ }
318
+ else {
319
+ // Get JSON representation of dehydrated list
320
+ if (this.entityData) {
321
+ resultObject[GLOBAL_ID_KEY] = this.entityData.globalID;
322
+ }
323
+ resultObject[ENTITY_DATA_KEYS_KEY] = Array.from(this.entityDataKeys);
324
+ if (this.scalars) {
325
+ resultObject[SCALARS_KEY] = this.scalars;
326
+ }
327
+ if (this.references) {
328
+ const references = {};
329
+ for (const key in this.references) {
330
+ if (this.references.hasOwnProperty(key)) {
331
+ references[key] = this.references[key].toJSON(mode);
332
+ }
333
+ }
334
+ resultObject[REFERENCES_KEY] = references;
335
+ }
336
+ if (this.objectLists) {
337
+ const objectLists = {};
338
+ for (const key in this.objectLists) {
339
+ if (this.objectLists.hasOwnProperty(key)) {
340
+ objectLists[key] = this.objectLists[key].map(obj => obj.toJSON(mode));
341
+ }
342
+ }
343
+ resultObject[OBJECT_LISTS_KEY] = objectLists;
344
+ }
345
+ }
346
+ return resultObject;
347
+ }
348
+ static fromJson(obj) {
349
+ const sdo = new EntityNode();
350
+ if (obj.backingData) {
351
+ sdo.entityData = EntityDataObject.fromJSON(obj.backingData);
352
+ }
353
+ sdo.globalId = obj.globalID;
354
+ sdo.scalars = obj.scalars;
355
+ if (obj.references) {
356
+ const references = {};
357
+ for (const key in obj.references) {
358
+ if (obj.references.hasOwnProperty(key)) {
359
+ references[key] = EntityNode.fromJson(obj.references[key]);
360
+ }
361
+ }
362
+ sdo.references = references;
363
+ }
364
+ if (obj.objectLists) {
365
+ const objectLists = {};
366
+ for (const key in obj.objectLists) {
367
+ if (obj.objectLists.hasOwnProperty(key)) {
368
+ objectLists[key] = obj.objectLists[key].map(obj => EntityNode.fromJson(obj));
369
+ }
370
+ }
371
+ sdo.objectLists = objectLists;
372
+ }
373
+ return sdo;
374
+ }
375
+ }
376
+ // Helpful for storing in persistent cache, which is not available yet.
377
+ var EncodingMode;
378
+ (function (EncodingMode) {
379
+ EncodingMode[EncodingMode["hydrated"] = 0] = "hydrated";
380
+ EncodingMode[EncodingMode["dehydrated"] = 1] = "dehydrated";
381
+ })(EncodingMode || (EncodingMode = {}));
382
+
383
+ /**
384
+ * @license
385
+ * Copyright 2025 Google LLC
386
+ *
387
+ * Licensed under the Apache License, Version 2.0 (the "License");
388
+ * you may not use this file except in compliance with the License.
389
+ * You may obtain a copy of the License at
390
+ *
391
+ * http://www.apache.org/licenses/LICENSE-2.0
392
+ *
393
+ * Unless required by applicable law or agreed to in writing, software
394
+ * distributed under the License is distributed on an "AS IS" BASIS,
395
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
396
+ * See the License for the specific language governing permissions and
397
+ * limitations under the License.
398
+ */
399
+ class ResultTree {
400
+ /**
401
+ * Create a {@link ResultTree} from a dehydrated JSON object.
402
+ * @param value The dehydrated JSON object.
403
+ * @returns The {@link ResultTree}.
404
+ */
405
+ static fromJson(value) {
406
+ return new ResultTree(EntityNode.fromJson(value.rootStub), value.maxAge, value.cachedAt, value.lastAccessed);
407
+ }
408
+ constructor(rootStub, maxAge = 0, cachedAt, _lastAccessed) {
409
+ this.rootStub = rootStub;
410
+ this.maxAge = maxAge;
411
+ this.cachedAt = cachedAt;
412
+ this._lastAccessed = _lastAccessed;
413
+ }
414
+ isStale() {
415
+ return (Date.now() - new Date(this.cachedAt.getTime()).getTime() >
416
+ this.maxAge * 1000);
417
+ }
418
+ updateMaxAge(maxAgeInSeconds) {
419
+ this.maxAge = maxAgeInSeconds;
420
+ }
421
+ updateAccessed() {
422
+ this._lastAccessed = new Date();
423
+ }
424
+ get lastAccessed() {
425
+ return this._lastAccessed;
426
+ }
427
+ getRootStub() {
428
+ return this.rootStub;
429
+ }
430
+ }
431
+
432
+ /**
433
+ * @license
434
+ * Copyright 2025 Google LLC
435
+ *
436
+ * Licensed under the Apache License, Version 2.0 (the "License");
437
+ * you may not use this file except in compliance with the License.
438
+ * You may obtain a copy of the License at
439
+ *
440
+ * http://www.apache.org/licenses/LICENSE-2.0
441
+ *
442
+ * Unless required by applicable law or agreed to in writing, software
443
+ * distributed under the License is distributed on an "AS IS" BASIS,
444
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
445
+ * See the License for the specific language governing permissions and
446
+ * limitations under the License.
447
+ */
448
+ class ImpactedQueryRefsAccumulator {
449
+ constructor(queryId) {
450
+ this.queryId = queryId;
451
+ this.impacted = new Set();
452
+ }
453
+ add(impacted) {
454
+ impacted
455
+ .filter(ref => ref !== this.queryId)
456
+ .forEach(ref => this.impacted.add(ref));
457
+ }
458
+ consumeEvents() {
459
+ const events = Array.from(this.impacted);
460
+ this.impacted.clear();
461
+ return events;
462
+ }
463
+ }
464
+
465
+ /**
466
+ * @license
467
+ * Copyright 2025 Google LLC
468
+ *
469
+ * Licensed under the Apache License, Version 2.0 (the "License");
470
+ * you may not use this file except in compliance with the License.
471
+ * You may obtain a copy of the License at
472
+ *
473
+ * http://www.apache.org/licenses/LICENSE-2.0
474
+ *
475
+ * Unless required by applicable law or agreed to in writing, software
476
+ * distributed under the License is distributed on an "AS IS" BASIS,
477
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
478
+ * See the License for the specific language governing permissions and
479
+ * limitations under the License.
480
+ */
481
+ class ResultTreeProcessor {
482
+ /**
483
+ * Hydrate the EntityNode into a JSON object so that it can be returned to the user.
484
+ * @param rootStubObject
485
+ * @returns {string}
486
+ */
487
+ hydrateResults(rootStubObject) {
488
+ return rootStubObject.toJSON(EncodingMode.hydrated);
489
+ }
490
+ /**
491
+ * Dehydrate results so that they can be stored in the cache.
492
+ * @param json
493
+ * @param entityIds
494
+ * @param cacheProvider
495
+ * @param queryId
496
+ * @returns {Promise<DehydratedResults>}
497
+ */
498
+ async dehydrateResults(json, entityIds, cacheProvider, queryId) {
499
+ const acc = new ImpactedQueryRefsAccumulator(queryId);
500
+ const entityNode = new EntityNode();
501
+ await entityNode.loadData(queryId, json, entityIds, acc, cacheProvider);
502
+ return {
503
+ entityNode,
504
+ impacted: acc.consumeEvents()
505
+ };
506
+ }
507
+ }
508
+
509
+ /**
510
+ * @license
511
+ * Copyright 2025 Google LLC
512
+ *
513
+ * Licensed under the Apache License, Version 2.0 (the "License");
514
+ * you may not use this file except in compliance with the License.
515
+ * You may obtain a copy of the License at
516
+ *
517
+ * http://www.apache.org/licenses/LICENSE-2.0
518
+ *
519
+ * Unless required by applicable law or agreed to in writing, software
520
+ * distributed under the License is distributed on an "AS IS" BASIS,
521
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
522
+ * See the License for the specific language governing permissions and
523
+ * limitations under the License.
524
+ */
525
+ class DataConnectCache {
526
+ constructor(authProvider, projectId, connectorConfig, host, cacheSettings) {
527
+ this.authProvider = authProvider;
528
+ this.projectId = projectId;
529
+ this.connectorConfig = connectorConfig;
530
+ this.host = host;
531
+ this.cacheSettings = cacheSettings;
532
+ this.cacheProvider = null;
533
+ this.uid = null;
534
+ this.authProvider.addTokenChangeListener(async (_) => {
535
+ const newUid = this.authProvider.getAuth().getUid();
536
+ // We should only close if the token changes and so does the new UID
537
+ if (this.uid !== newUid) {
538
+ this.cacheProvider?.close();
539
+ this.uid = newUid;
540
+ const identifier = await this.getIdentifier(this.uid);
541
+ this.cacheProvider = this.initializeNewProviders(identifier);
542
+ }
543
+ });
544
+ }
545
+ async initialize() {
546
+ if (!this.cacheProvider) {
547
+ const identifier = await this.getIdentifier(this.uid);
548
+ this.cacheProvider = this.initializeNewProviders(identifier);
549
+ }
550
+ }
551
+ async getIdentifier(uid) {
552
+ const identifier = `${'memory' // TODO: replace this with indexeddb when persistence is available.
553
+ }-${this.projectId}-${this.connectorConfig.service}-${this.connectorConfig.connector}-${this.connectorConfig.location}-${uid}-${this.host}`;
554
+ const sha256 = await generateSHA256Hash(identifier);
555
+ return sha256;
556
+ }
557
+ initializeNewProviders(identifier) {
558
+ return this.cacheSettings.cacheProvider.initialize(identifier);
559
+ }
560
+ async containsResultTree(queryId) {
561
+ await this.initialize();
562
+ const resultTree = await this.cacheProvider.getResultTree(queryId);
563
+ return resultTree !== undefined;
564
+ }
565
+ async getResultTree(queryId) {
566
+ await this.initialize();
567
+ return this.cacheProvider.getResultTree(queryId);
568
+ }
569
+ async getResultJSON(queryId) {
570
+ await this.initialize();
571
+ const processor = new ResultTreeProcessor();
572
+ const cacheProvider = this.cacheProvider;
573
+ const resultTree = await cacheProvider.getResultTree(queryId);
574
+ if (!resultTree) {
575
+ throw new DataConnectError(Code.INVALID_ARGUMENT, `${queryId} not found in cache. Call "update()" first.`);
576
+ }
577
+ return processor.hydrateResults(resultTree.getRootStub());
578
+ }
579
+ async update(queryId, serverValues, entityIds) {
580
+ await this.initialize();
581
+ const processor = new ResultTreeProcessor();
582
+ const cacheProvider = this.cacheProvider;
583
+ const { entityNode: stubDataObject, impacted } = await processor.dehydrateResults(serverValues, entityIds, cacheProvider, queryId);
584
+ const now = new Date();
585
+ await cacheProvider.setResultTree(queryId, new ResultTree(stubDataObject, serverValues.maxAge || this.cacheSettings.maxAgeSeconds, now, now));
586
+ return impacted;
587
+ }
588
+ }
589
+ class MemoryStub {
590
+ constructor() {
591
+ this.type = 'MEMORY';
592
+ }
593
+ /**
594
+ * @internal
595
+ */
596
+ initialize(cacheId) {
597
+ return new InMemoryCacheProvider(cacheId);
598
+ }
599
+ }
600
+
35
601
  /**
36
602
  * @license
37
603
  * Copyright 2024 Google LLC
@@ -95,58 +661,6 @@ class AppCheckTokenProvider {
95
661
  }
96
662
  }
97
663
 
98
- /**
99
- * @license
100
- * Copyright 2024 Google LLC
101
- *
102
- * Licensed under the Apache License, Version 2.0 (the "License");
103
- * you may not use this file except in compliance with the License.
104
- * You may obtain a copy of the License at
105
- *
106
- * http://www.apache.org/licenses/LICENSE-2.0
107
- *
108
- * Unless required by applicable law or agreed to in writing, software
109
- * distributed under the License is distributed on an "AS IS" BASIS,
110
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
111
- * See the License for the specific language governing permissions and
112
- * limitations under the License.
113
- */
114
- const Code = {
115
- OTHER: 'other',
116
- ALREADY_INITIALIZED: 'already-initialized',
117
- NOT_INITIALIZED: 'not-initialized',
118
- NOT_SUPPORTED: 'not-supported',
119
- INVALID_ARGUMENT: 'invalid-argument',
120
- PARTIAL_ERROR: 'partial-error',
121
- UNAUTHORIZED: 'unauthorized'
122
- };
123
- /** An error returned by a DataConnect operation. */
124
- class DataConnectError extends FirebaseError {
125
- constructor(code, message) {
126
- super(code, message);
127
- /** @internal */
128
- this.name = 'DataConnectError';
129
- // Ensure the instanceof operator works as expected on subclasses of Error.
130
- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#custom_error_types
131
- // and https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
132
- Object.setPrototypeOf(this, DataConnectError.prototype);
133
- }
134
- /** @internal */
135
- toString() {
136
- return `${this.name}[code=${this.code}]: ${this.message}`;
137
- }
138
- }
139
- /** An error returned by a DataConnect operation. */
140
- class DataConnectOperationError extends DataConnectError {
141
- /** @hideconstructor */
142
- constructor(message, response) {
143
- super(Code.PARTIAL_ERROR, message);
144
- /** @internal */
145
- this.name = 'DataConnectOperationError';
146
- this.response = response;
147
- }
148
- }
149
-
150
664
  /**
151
665
  * @license
152
666
  * Copyright 2024 Google LLC
@@ -201,6 +715,9 @@ class FirebaseAuthProvider {
201
715
  _authProvider.onInit(auth => (this._auth = auth));
202
716
  }
203
717
  }
718
+ getAuth() {
719
+ return this._auth;
720
+ }
204
721
  getToken(forceRefresh) {
205
722
  if (!this._auth) {
206
723
  return new Promise((resolve, reject) => {
@@ -260,7 +777,7 @@ const SOURCE_CACHE = 'CACHE';
260
777
 
261
778
  /**
262
779
  * @license
263
- * Copyright 2024 Google LLC
780
+ * Copyright 2026 Google LLC
264
781
  *
265
782
  * Licensed under the Apache License, Version 2.0 (the "License");
266
783
  * you may not use this file except in compliance with the License.
@@ -274,11 +791,43 @@ const SOURCE_CACHE = 'CACHE';
274
791
  * See the License for the specific language governing permissions and
275
792
  * limitations under the License.
276
793
  */
277
- let encoderImpl;
278
- function setEncoder(encoder) {
279
- encoderImpl = encoder;
794
+ function parseEntityIds(result) {
795
+ // Iterate through extensions.dataConnect
796
+ const dataConnectExtensions = result.extensions?.dataConnect;
797
+ const dataCopy = Object.assign(result);
798
+ if (!dataConnectExtensions) {
799
+ return dataCopy;
800
+ }
801
+ const ret = {};
802
+ for (const extension of dataConnectExtensions) {
803
+ const { path } = extension;
804
+ populatePath(path, ret, extension);
805
+ }
806
+ return ret;
807
+ }
808
+ // mutates the object to update the path
809
+ function populatePath(path, toUpdate, extension) {
810
+ let curObj = toUpdate;
811
+ for (const slice of path) {
812
+ if (typeof curObj[slice] !== 'object') {
813
+ curObj[slice] = {};
814
+ }
815
+ curObj = curObj[slice];
816
+ }
817
+ if ('entityId' in extension && extension.entityId) {
818
+ curObj['_id'] = extension.entityId;
819
+ }
820
+ else if ('entityIds' in extension) {
821
+ const entityArr = extension.entityIds;
822
+ for (let i = 0; i < entityArr.length; i++) {
823
+ const entityId = entityArr[i];
824
+ if (typeof curObj[i] === 'undefined') {
825
+ curObj[i] = {};
826
+ }
827
+ curObj[i]._id = entityId;
828
+ }
829
+ }
280
830
  }
281
- setEncoder(o => JSON.stringify(o));
282
831
 
283
832
  /**
284
833
  * @license
@@ -296,11 +845,24 @@ setEncoder(o => JSON.stringify(o));
296
845
  * See the License for the specific language governing permissions and
297
846
  * limitations under the License.
298
847
  */
299
- function setIfNotExists(map, key, val) {
300
- if (!map.has(key)) {
301
- map.set(key, val);
302
- }
848
+ let encoderImpl;
849
+ let decoderImpl;
850
+ function setEncoder(encoder) {
851
+ encoderImpl = encoder;
852
+ }
853
+ function setDecoder(decoder) {
854
+ decoderImpl = decoder;
303
855
  }
856
+ function sortKeysForObj(o) {
857
+ return Object.keys(o)
858
+ .sort()
859
+ .reduce((accumulator, currentKey) => {
860
+ accumulator[currentKey] = o[currentKey];
861
+ return accumulator;
862
+ }, {});
863
+ }
864
+ setEncoder((o) => JSON.stringify(sortKeysForObj(o)));
865
+ setDecoder(s => sortKeysForObj(JSON.parse(s)));
304
866
 
305
867
  /**
306
868
  * @license
@@ -318,7 +880,7 @@ function setIfNotExists(map, key, val) {
318
880
  * See the License for the specific language governing permissions and
319
881
  * limitations under the License.
320
882
  */
321
- function getRefSerializer(queryRef, data, source) {
883
+ function getRefSerializer(queryRef, data, source, fetchTime) {
322
884
  return function toJSON() {
323
885
  return {
324
886
  data,
@@ -330,32 +892,65 @@ function getRefSerializer(queryRef, data, source) {
330
892
  ...queryRef.dataConnect.getSettings()
331
893
  }
332
894
  },
333
- fetchTime: Date.now().toLocaleString(),
895
+ fetchTime,
334
896
  source
335
897
  };
336
898
  };
337
899
  }
338
900
  class QueryManager {
339
- constructor(transport) {
901
+ async preferCacheResults(queryRef, allowStale = false) {
902
+ let cacheResult;
903
+ try {
904
+ cacheResult = await this.fetchCacheResults(queryRef, allowStale);
905
+ }
906
+ catch (e) {
907
+ // Ignore the error and try to fetch from the server.
908
+ }
909
+ if (cacheResult) {
910
+ return cacheResult;
911
+ }
912
+ return this.fetchServerResults(queryRef);
913
+ }
914
+ constructor(transport, dc, cache) {
340
915
  this.transport = transport;
341
- this._queries = new Map();
916
+ this.dc = dc;
917
+ this.cache = cache;
918
+ this.callbacks = new Map();
919
+ this.subscriptionCache = new Map();
920
+ this.queue = [];
342
921
  }
343
- track(queryName, variables, initialCache) {
344
- const ref = {
345
- name: queryName,
346
- variables,
347
- refType: QUERY_STR
348
- };
349
- const key = encoderImpl(ref);
350
- const newTrackedQuery = {
351
- ref,
352
- subscriptions: [],
353
- currentCache: initialCache || null,
354
- lastError: null
355
- };
356
- // @ts-ignore
357
- setIfNotExists(this._queries, key, newTrackedQuery);
358
- return this._queries.get(key);
922
+ async waitForQueuedWrites() {
923
+ for (const promise of this.queue) {
924
+ await promise;
925
+ }
926
+ this.queue = [];
927
+ }
928
+ updateSSR(updatedData) {
929
+ this.queue.push(this.updateCache(updatedData).then(async (result) => this.publishCacheResultsToSubscribers(result, updatedData.fetchTime)));
930
+ }
931
+ async updateCache(result, extensions) {
932
+ await this.waitForQueuedWrites();
933
+ if (this.cache) {
934
+ const entityIds = parseEntityIds(result);
935
+ const updatedMaxAge = getMaxAgeFromExtensions(extensions);
936
+ if (updatedMaxAge !== undefined) {
937
+ this.cache.cacheSettings.maxAgeSeconds = updatedMaxAge;
938
+ }
939
+ return this.cache.update(encoderImpl({
940
+ name: result.ref.name,
941
+ variables: result.ref.variables,
942
+ refType: QUERY_STR
943
+ }), result.data, entityIds);
944
+ }
945
+ else {
946
+ const key = encoderImpl({
947
+ name: result.ref.name,
948
+ variables: result.ref.variables,
949
+ refType: QUERY_STR
950
+ });
951
+ this.subscriptionCache.set(key, result);
952
+ return [key];
953
+ }
359
954
  }
360
955
  addSubscription(queryRef, onResultCallback, onCompleteCallback, onErrorCallback, initialCache) {
361
956
  const key = encoderImpl({
@@ -363,99 +958,213 @@ class QueryManager {
363
958
  variables: queryRef.variables,
364
959
  refType: QUERY_STR
365
960
  });
366
- const trackedQuery = this._queries.get(key);
367
- const subscription = {
368
- userCallback: onResultCallback,
369
- onCompleteCallback,
370
- errCallback: onErrorCallback
371
- };
372
961
  const unsubscribe = () => {
373
- const trackedQuery = this._queries.get(key);
374
- trackedQuery.subscriptions = trackedQuery.subscriptions.filter(sub => sub !== subscription);
375
- onCompleteCallback?.();
376
- };
377
- if (initialCache && trackedQuery.currentCache !== initialCache) {
378
- logDebug('Initial cache found. Comparing dates.');
379
- if (!trackedQuery.currentCache ||
380
- (trackedQuery.currentCache &&
381
- compareDates(trackedQuery.currentCache.fetchTime, initialCache.fetchTime))) {
382
- trackedQuery.currentCache = initialCache;
962
+ if (this.callbacks.has(key)) {
963
+ const callbackList = this.callbacks.get(key);
964
+ this.callbacks.set(key, callbackList.filter(callback => callback !== subscription));
965
+ onCompleteCallback?.();
383
966
  }
384
- }
385
- if (trackedQuery.currentCache !== null) {
386
- const cachedData = trackedQuery.currentCache.data;
387
- onResultCallback({
388
- data: cachedData,
389
- source: SOURCE_CACHE,
390
- ref: queryRef,
391
- toJSON: getRefSerializer(queryRef, trackedQuery.currentCache.data, SOURCE_CACHE),
392
- fetchTime: trackedQuery.currentCache.fetchTime
393
- });
394
- if (trackedQuery.lastError !== null && onErrorCallback) {
395
- onErrorCallback(undefined);
396
- }
397
- }
398
- trackedQuery.subscriptions.push({
967
+ };
968
+ const subscription = {
399
969
  userCallback: onResultCallback,
400
970
  errCallback: onErrorCallback,
401
971
  unsubscribe
402
- });
403
- if (!trackedQuery.currentCache) {
404
- logDebug(`No cache available for query ${queryRef.name} with variables ${JSON.stringify(queryRef.variables)}. Calling executeQuery.`);
405
- const promise = this.executeQuery(queryRef);
406
- // We want to ignore the error and let subscriptions handle it
407
- promise.then(undefined, err => { });
972
+ };
973
+ if (initialCache) {
974
+ this.updateSSR(initialCache);
975
+ }
976
+ const promise = this.preferCacheResults(queryRef, /*allowStale=*/ true);
977
+ // We want to ignore the error and let subscriptions handle it
978
+ promise.then(undefined, err => { });
979
+ if (!this.callbacks.has(key)) {
980
+ this.callbacks.set(key, []);
408
981
  }
982
+ this.callbacks
983
+ .get(key)
984
+ .push(subscription);
409
985
  return unsubscribe;
410
986
  }
411
- executeQuery(queryRef) {
412
- if (queryRef.refType !== QUERY_STR) {
413
- throw new DataConnectError(Code.INVALID_ARGUMENT, `ExecuteQuery can only execute query operation`);
414
- }
987
+ async fetchServerResults(queryRef) {
988
+ await this.waitForQueuedWrites();
415
989
  const key = encoderImpl({
416
990
  name: queryRef.name,
417
991
  variables: queryRef.variables,
418
992
  refType: QUERY_STR
419
993
  });
420
- const trackedQuery = this._queries.get(key);
421
- const result = this.transport.invokeQuery(queryRef.name, queryRef.variables);
422
- const newR = result.then(res => {
423
- const fetchTime = new Date().toString();
424
- const result = {
425
- ...res,
426
- source: SOURCE_SERVER,
994
+ try {
995
+ const result = await this.transport.invokeQuery(queryRef.name, queryRef.variables);
996
+ const fetchTime = Date.now().toString();
997
+ const originalExtensions = result.extensions;
998
+ const queryResult = {
999
+ ...result,
427
1000
  ref: queryRef,
428
- toJSON: getRefSerializer(queryRef, res.data, SOURCE_SERVER),
429
- fetchTime
1001
+ source: SOURCE_SERVER,
1002
+ fetchTime,
1003
+ data: result.data,
1004
+ extensions: getDataConnectExtensionsWithoutMaxAge(originalExtensions),
1005
+ toJSON: getRefSerializer(queryRef, result.data, SOURCE_SERVER, fetchTime)
430
1006
  };
431
- trackedQuery.subscriptions.forEach(subscription => {
432
- subscription.userCallback(result);
1007
+ let updatedKeys = [];
1008
+ updatedKeys = await this.updateCache(queryResult, originalExtensions?.dataConnect);
1009
+ this.publishDataToSubscribers(key, queryResult);
1010
+ if (this.cache) {
1011
+ await this.publishCacheResultsToSubscribers(updatedKeys, fetchTime);
1012
+ }
1013
+ else {
1014
+ this.subscriptionCache.set(key, queryResult);
1015
+ }
1016
+ return queryResult;
1017
+ }
1018
+ catch (e) {
1019
+ this.publishErrorToSubscribers(key, e);
1020
+ throw e;
1021
+ }
1022
+ }
1023
+ async fetchCacheResults(queryRef, allowStale = false) {
1024
+ await this.waitForQueuedWrites();
1025
+ let result;
1026
+ if (!this.cache) {
1027
+ result = await this.getFromSubscriberCache(queryRef);
1028
+ }
1029
+ else {
1030
+ result = await this.getFromResultTreeCache(queryRef, allowStale);
1031
+ }
1032
+ if (!result) {
1033
+ throw new DataConnectError(Code.OTHER, 'No cache entry found for query: ' + queryRef.name);
1034
+ }
1035
+ const fetchTime = Date.now().toString();
1036
+ const queryResult = {
1037
+ ...result,
1038
+ ref: queryRef,
1039
+ source: SOURCE_CACHE,
1040
+ fetchTime,
1041
+ data: result.data,
1042
+ extensions: result.extensions,
1043
+ toJSON: getRefSerializer(queryRef, result.data, SOURCE_CACHE, fetchTime)
1044
+ };
1045
+ if (this.cache) {
1046
+ const key = encoderImpl({
1047
+ name: queryRef.name,
1048
+ variables: queryRef.variables,
1049
+ refType: QUERY_STR
433
1050
  });
434
- trackedQuery.currentCache = {
435
- data: res.data,
436
- source: SOURCE_CACHE,
437
- fetchTime
438
- };
439
- return result;
440
- }, err => {
441
- trackedQuery.lastError = err;
442
- trackedQuery.subscriptions.forEach(subscription => {
443
- if (subscription.errCallback) {
444
- subscription.errCallback(err);
445
- }
1051
+ await this.publishCacheResultsToSubscribers([key], fetchTime);
1052
+ }
1053
+ else {
1054
+ const key = encoderImpl({
1055
+ name: queryRef.name,
1056
+ variables: queryRef.variables,
1057
+ refType: QUERY_STR
446
1058
  });
447
- throw err;
1059
+ this.subscriptionCache.set(key, queryResult);
1060
+ this.publishDataToSubscribers(key, queryResult);
1061
+ }
1062
+ return queryResult;
1063
+ }
1064
+ publishErrorToSubscribers(key, err) {
1065
+ this.callbacks.get(key)?.forEach(subscription => {
1066
+ if (subscription.errCallback) {
1067
+ subscription.errCallback(err);
1068
+ }
1069
+ });
1070
+ }
1071
+ async getFromResultTreeCache(queryRef, allowStale = false) {
1072
+ const key = encoderImpl({
1073
+ name: queryRef.name,
1074
+ variables: queryRef.variables,
1075
+ refType: QUERY_STR
1076
+ });
1077
+ if (!this.cache || !(await this.cache.containsResultTree(key))) {
1078
+ return null;
1079
+ }
1080
+ const cacheResult = (await this.cache.getResultJSON(key));
1081
+ const resultTree = await this.cache.getResultTree(key);
1082
+ if (!allowStale && resultTree.isStale()) {
1083
+ return null;
1084
+ }
1085
+ const result = {
1086
+ source: SOURCE_CACHE,
1087
+ ref: queryRef,
1088
+ data: cacheResult,
1089
+ toJSON: getRefSerializer(queryRef, cacheResult, SOURCE_CACHE, resultTree.cachedAt.toString()),
1090
+ fetchTime: resultTree.cachedAt.toString()
1091
+ };
1092
+ (await this.cache.getResultTree(key)).updateAccessed();
1093
+ return result;
1094
+ }
1095
+ async getFromSubscriberCache(queryRef) {
1096
+ const key = encoderImpl({
1097
+ name: queryRef.name,
1098
+ variables: queryRef.variables,
1099
+ refType: QUERY_STR
1100
+ });
1101
+ if (!this.subscriptionCache.has(key)) {
1102
+ return;
1103
+ }
1104
+ const result = this.subscriptionCache.get(key);
1105
+ result.source = SOURCE_CACHE;
1106
+ result.toJSON = getRefSerializer(result.ref, result.data, SOURCE_CACHE, result.fetchTime);
1107
+ return result;
1108
+ }
1109
+ publishDataToSubscribers(key, queryResult) {
1110
+ if (!this.callbacks.has(key)) {
1111
+ return;
1112
+ }
1113
+ const subscribers = this.callbacks.get(key);
1114
+ subscribers.forEach(callback => {
1115
+ callback.userCallback(queryResult);
448
1116
  });
449
- return newR;
1117
+ }
1118
+ async publishCacheResultsToSubscribers(impactedQueries, fetchTime) {
1119
+ if (!this.cache) {
1120
+ return;
1121
+ }
1122
+ for (const query of impactedQueries) {
1123
+ const callbacks = this.callbacks.get(query);
1124
+ if (!callbacks) {
1125
+ continue;
1126
+ }
1127
+ const newJson = (await this.cache.getResultTree(query))
1128
+ .getRootStub()
1129
+ .toJSON(EncodingMode.hydrated);
1130
+ const { name, variables } = decoderImpl(query);
1131
+ const queryRef = {
1132
+ dataConnect: this.dc,
1133
+ refType: QUERY_STR,
1134
+ name,
1135
+ variables
1136
+ };
1137
+ this.publishDataToSubscribers(query, {
1138
+ data: newJson,
1139
+ fetchTime,
1140
+ ref: queryRef,
1141
+ source: SOURCE_CACHE,
1142
+ toJSON: getRefSerializer(queryRef, newJson, SOURCE_CACHE, fetchTime)
1143
+ });
1144
+ }
450
1145
  }
451
1146
  enableEmulator(host, port) {
452
1147
  this.transport.useEmulator(host, port);
453
1148
  }
454
1149
  }
455
- function compareDates(str1, str2) {
456
- const date1 = new Date(str1);
457
- const date2 = new Date(str2);
458
- return date1.getTime() < date2.getTime();
1150
+ function getMaxAgeFromExtensions(extensions) {
1151
+ if (!extensions) {
1152
+ return;
1153
+ }
1154
+ for (const extension of extensions) {
1155
+ if ('maxAge' in extension &&
1156
+ extension.maxAge !== undefined &&
1157
+ extension.maxAge !== null) {
1158
+ if (extension.maxAge.endsWith('s')) {
1159
+ return Number(extension.maxAge.substring(0, extension.maxAge.length - 1));
1160
+ }
1161
+ }
1162
+ }
1163
+ }
1164
+ function getDataConnectExtensionsWithoutMaxAge(extensions) {
1165
+ return {
1166
+ dataConnect: extensions.dataConnect?.filter(extension => 'entityId' in extension || 'entityIds' in extension)
1167
+ };
459
1168
  }
460
1169
 
461
1170
  /**
@@ -499,11 +1208,12 @@ const CallerSdkTypeEnum = {
499
1208
  * See the License for the specific language governing permissions and
500
1209
  * limitations under the License.
501
1210
  */
1211
+ const PROD_HOST = 'firebasedataconnect.googleapis.com';
502
1212
  function urlBuilder(projectConfig, transportOptions) {
503
1213
  const { connector, location, projectId: project, service } = projectConfig;
504
1214
  const { host, sslEnabled, port } = transportOptions;
505
1215
  const protocol = sslEnabled ? 'https' : 'http';
506
- const realHost = host || `firebasedataconnect.googleapis.com`;
1216
+ const realHost = host || PROD_HOST;
507
1217
  let baseUrl = `${protocol}://${realHost}`;
508
1218
  if (typeof port === 'number') {
509
1219
  baseUrl += `:${port}`;
@@ -551,7 +1261,7 @@ function getGoogApiClientValue(_isUsingGen, _callerSdkType) {
551
1261
  }
552
1262
  return str;
553
1263
  }
554
- function dcFetch(url, body, { signal }, appId, accessToken, appCheckToken, _isUsingGen, _callerSdkType, _isUsingEmulator) {
1264
+ async function dcFetch(url, body, { signal }, appId, accessToken, appCheckToken, _isUsingGen, _callerSdkType, _isUsingEmulator) {
555
1265
  if (!connectFetch) {
556
1266
  throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!');
557
1267
  }
@@ -578,41 +1288,44 @@ function dcFetch(url, body, { signal }, appId, accessToken, appCheckToken, _isUs
578
1288
  if (isCloudWorkstation(url) && _isUsingEmulator) {
579
1289
  fetchOptions.credentials = 'include';
580
1290
  }
581
- return connectFetch(url, fetchOptions)
582
- .catch(err => {
1291
+ let response;
1292
+ try {
1293
+ response = await connectFetch(url, fetchOptions);
1294
+ }
1295
+ catch (err) {
583
1296
  throw new DataConnectError(Code.OTHER, 'Failed to fetch: ' + JSON.stringify(err));
584
- })
585
- .then(async (response) => {
586
- let jsonResponse = null;
587
- try {
588
- jsonResponse = await response.json();
589
- }
590
- catch (e) {
591
- throw new DataConnectError(Code.OTHER, JSON.stringify(e));
592
- }
593
- const message = getMessage(jsonResponse);
594
- if (response.status >= 400) {
595
- logError('Error while performing request: ' + JSON.stringify(jsonResponse));
596
- if (response.status === 401) {
597
- throw new DataConnectError(Code.UNAUTHORIZED, message);
598
- }
599
- throw new DataConnectError(Code.OTHER, message);
600
- }
601
- return jsonResponse;
602
- })
603
- .then(res => {
604
- if (res.errors && res.errors.length) {
605
- const stringified = JSON.stringify(res.errors);
606
- const response = {
607
- errors: res.errors,
608
- data: res.data
609
- };
610
- throw new DataConnectOperationError('DataConnect error while performing request: ' + stringified, response);
1297
+ }
1298
+ let jsonResponse;
1299
+ try {
1300
+ jsonResponse = await response.json();
1301
+ }
1302
+ catch (e) {
1303
+ throw new DataConnectError(Code.OTHER, JSON.stringify(e));
1304
+ }
1305
+ const message = getErrorMessage(jsonResponse);
1306
+ if (response.status >= 400) {
1307
+ logError('Error while performing request: ' + JSON.stringify(jsonResponse));
1308
+ if (response.status === 401) {
1309
+ throw new DataConnectError(Code.UNAUTHORIZED, message);
611
1310
  }
612
- return res;
613
- });
1311
+ throw new DataConnectError(Code.OTHER, message);
1312
+ }
1313
+ if (jsonResponse.errors && jsonResponse.errors.length) {
1314
+ const stringified = JSON.stringify(jsonResponse.errors);
1315
+ const failureResponse = {
1316
+ errors: jsonResponse.errors,
1317
+ data: jsonResponse.data
1318
+ };
1319
+ throw new DataConnectOperationError('DataConnect error while performing request: ' + stringified, failureResponse);
1320
+ }
1321
+ if (!jsonResponse.extensions) {
1322
+ jsonResponse.extensions = {
1323
+ dataConnect: []
1324
+ };
1325
+ }
1326
+ return jsonResponse;
614
1327
  }
615
- function getMessage(obj) {
1328
+ function getErrorMessage(obj) {
616
1329
  if ('message' in obj && obj.message) {
617
1330
  return obj.message;
618
1331
  }
@@ -729,7 +1442,10 @@ class RESTTransport {
729
1442
  async getWithAuth(forceToken = false) {
730
1443
  let starterPromise = new Promise(resolve => resolve(this._accessToken));
731
1444
  if (this.appCheckProvider) {
732
- this._appCheckToken = (await this.appCheckProvider.getToken())?.token;
1445
+ const appCheckToken = await this.appCheckProvider.getToken();
1446
+ if (appCheckToken) {
1447
+ this._appCheckToken = appCheckToken.token;
1448
+ }
733
1449
  }
734
1450
  if (this.authProvider) {
735
1451
  starterPromise = this.authProvider
@@ -898,6 +1614,12 @@ class DataConnect {
898
1614
  }
899
1615
  }
900
1616
  }
1617
+ /**
1618
+ * @internal
1619
+ */
1620
+ getCache() {
1621
+ return this.cache;
1622
+ }
901
1623
  // @internal
902
1624
  _useGeneratedSdk() {
903
1625
  if (!this._isUsingGeneratedSdk) {
@@ -920,6 +1642,12 @@ class DataConnect {
920
1642
  delete copy.projectId;
921
1643
  return copy;
922
1644
  }
1645
+ /**
1646
+ * @internal
1647
+ */
1648
+ setCacheSettings(cacheSettings) {
1649
+ this._cacheSettings = cacheSettings;
1650
+ }
923
1651
  // @internal
924
1652
  setInitialized() {
925
1653
  if (this._initialized) {
@@ -929,19 +1657,25 @@ class DataConnect {
929
1657
  logDebug('transportClass not provided. Defaulting to RESTTransport.');
930
1658
  this._transportClass = RESTTransport;
931
1659
  }
932
- if (this._authProvider) {
933
- this._authTokenProvider = new FirebaseAuthProvider(this.app.name, this.app.options, this._authProvider);
1660
+ this._authTokenProvider = new FirebaseAuthProvider(this.app.name, this.app.options, this._authProvider);
1661
+ const connectorConfig = {
1662
+ connector: this.dataConnectOptions.connector,
1663
+ service: this.dataConnectOptions.service,
1664
+ location: this.dataConnectOptions.location
1665
+ };
1666
+ if (this._cacheSettings) {
1667
+ this.cache = new DataConnectCache(this._authTokenProvider, this.app.options.projectId, connectorConfig, this._transportOptions?.host || PROD_HOST, this._cacheSettings);
934
1668
  }
935
1669
  if (this._appCheckProvider) {
936
1670
  this._appCheckTokenProvider = new AppCheckTokenProvider(this.app, this._appCheckProvider);
937
1671
  }
938
- this._initialized = true;
939
1672
  this._transport = new this._transportClass(this.dataConnectOptions, this.app.options.apiKey, this.app.options.appId, this._authTokenProvider, this._appCheckTokenProvider, undefined, this._isUsingGeneratedSdk, this._callerSdkType);
940
1673
  if (this._transportOptions) {
941
1674
  this._transport.useEmulator(this._transportOptions.host, this._transportOptions.port, this._transportOptions.sslEnabled);
942
1675
  }
943
- this._queryManager = new QueryManager(this._transport);
1676
+ this._queryManager = new QueryManager(this._transport, this, this.cache);
944
1677
  this._mutationManager = new MutationManager(this._transport);
1678
+ this._initialized = true;
945
1679
  }
946
1680
  // @internal
947
1681
  enableEmulator(transportOptions) {
@@ -981,22 +1715,32 @@ function connectDataConnectEmulator(dc, host, port, sslEnabled = false) {
981
1715
  }
982
1716
  dc.enableEmulator({ host, port, sslEnabled });
983
1717
  }
984
- function getDataConnect(appOrOptions, optionalOptions) {
1718
+ function getDataConnect(appOrConnectorConfig, settingsOrConnectorConfig, settings) {
985
1719
  let app;
986
- let dcOptions;
987
- if ('location' in appOrOptions) {
988
- dcOptions = appOrOptions;
1720
+ let connectorConfig;
1721
+ let realSettings;
1722
+ if ('location' in appOrConnectorConfig) {
1723
+ connectorConfig = appOrConnectorConfig;
989
1724
  app = getApp();
1725
+ realSettings = settingsOrConnectorConfig;
990
1726
  }
991
1727
  else {
992
- dcOptions = optionalOptions;
993
- app = appOrOptions;
1728
+ app = appOrConnectorConfig;
1729
+ connectorConfig = settingsOrConnectorConfig;
1730
+ realSettings = settings;
994
1731
  }
995
1732
  if (!app || Object.keys(app).length === 0) {
996
1733
  app = getApp();
997
1734
  }
1735
+ // Options to store in Firebase Component Provider.
1736
+ const serializedOptions = {
1737
+ ...connectorConfig,
1738
+ projectId: app.options.projectId
1739
+ };
1740
+ // We should sort the keys before initialization.
1741
+ const sortedSerialized = Object.fromEntries(Object.entries(serializedOptions).sort());
998
1742
  const provider = _getProvider(app, 'data-connect');
999
- const identifier = JSON.stringify(dcOptions);
1743
+ const identifier = JSON.stringify(sortedSerialized);
1000
1744
  if (provider.isInitialized(identifier)) {
1001
1745
  const dcInstance = provider.getImmediate({ identifier });
1002
1746
  const options = provider.getOptions(identifier);
@@ -1006,13 +1750,19 @@ function getDataConnect(appOrOptions, optionalOptions) {
1006
1750
  return dcInstance;
1007
1751
  }
1008
1752
  }
1009
- validateDCOptions(dcOptions);
1753
+ validateDCOptions(connectorConfig);
1010
1754
  logDebug('Creating new DataConnect instance');
1011
1755
  // Initialize with options.
1012
- return provider.initialize({
1756
+ const dataConnect = provider.initialize({
1013
1757
  instanceIdentifier: identifier,
1014
- options: dcOptions
1758
+ options: Object.fromEntries(Object.entries({
1759
+ ...sortedSerialized
1760
+ }).sort())
1015
1761
  });
1762
+ if (realSettings?.cacheSettings) {
1763
+ dataConnect.setCacheSettings(realSettings.cacheSettings);
1764
+ }
1765
+ return dataConnect;
1016
1766
  }
1017
1767
  /**
1018
1768
  *
@@ -1042,6 +1792,12 @@ function terminate(dataConnect) {
1042
1792
  return dataConnect._delete();
1043
1793
  // TODO(mtewani): Stop pending tasks
1044
1794
  }
1795
+ const StorageType = {
1796
+ MEMORY: 'MEMORY'
1797
+ };
1798
+ function makeMemoryCacheProvider() {
1799
+ return new MemoryStub();
1800
+ }
1045
1801
 
1046
1802
  /**
1047
1803
  * @license
@@ -1061,13 +1817,16 @@ function terminate(dataConnect) {
1061
1817
  */
1062
1818
  function registerDataConnect(variant) {
1063
1819
  setSDKVersion(SDK_VERSION$1);
1064
- _registerComponent(new Component('data-connect', (container, { instanceIdentifier: settings, options }) => {
1820
+ _registerComponent(new Component('data-connect', (container, { instanceIdentifier: connectorConfigStr, options }) => {
1065
1821
  const app = container.getProvider('app').getImmediate();
1066
1822
  const authProvider = container.getProvider('auth-internal');
1067
1823
  const appCheckProvider = container.getProvider('app-check-internal');
1068
1824
  let newOpts = options;
1069
- if (settings) {
1070
- newOpts = JSON.parse(settings);
1825
+ if (connectorConfigStr) {
1826
+ newOpts = {
1827
+ ...JSON.parse(connectorConfigStr),
1828
+ ...newOpts
1829
+ };
1071
1830
  }
1072
1831
  if (!app.options.projectId) {
1073
1832
  throw new DataConnectError(Code.INVALID_ARGUMENT, 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?');
@@ -1079,6 +1838,28 @@ function registerDataConnect(variant) {
1079
1838
  registerVersion(name, version, 'esm2020');
1080
1839
  }
1081
1840
 
1841
+ /**
1842
+ * @license
1843
+ * Copyright 2025 Google LLC
1844
+ *
1845
+ * Licensed under the Apache License, Version 2.0 (the "License");
1846
+ * you may not use this file except in compliance with the License.
1847
+ * You may obtain a copy of the License at
1848
+ *
1849
+ * http://www.apache.org/licenses/LICENSE-2.0
1850
+ *
1851
+ * Unless required by applicable law or agreed to in writing, software
1852
+ * distributed under the License is distributed on an "AS IS" BASIS,
1853
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1854
+ * See the License for the specific language governing permissions and
1855
+ * limitations under the License.
1856
+ */
1857
+ const QueryFetchPolicy = {
1858
+ PREFER_CACHE: 'PREFER_CACHE',
1859
+ CACHE_ONLY: 'CACHE_ONLY',
1860
+ SERVER_ONLY: 'SERVER_ONLY'
1861
+ };
1862
+
1082
1863
  /**
1083
1864
  * @license
1084
1865
  * Copyright 2024 Google LLC
@@ -1100,8 +1881,22 @@ function registerDataConnect(variant) {
1100
1881
  * @param queryRef query to execute.
1101
1882
  * @returns `QueryPromise`
1102
1883
  */
1103
- function executeQuery(queryRef) {
1104
- return queryRef.dataConnect._queryManager.executeQuery(queryRef);
1884
+ function executeQuery(queryRef, options) {
1885
+ if (queryRef.refType !== QUERY_STR) {
1886
+ return Promise.reject(new DataConnectError(Code.INVALID_ARGUMENT, `ExecuteQuery can only execute query operations`));
1887
+ }
1888
+ const queryManager = queryRef.dataConnect._queryManager;
1889
+ const fetchPolicy = options?.fetchPolicy ?? QueryFetchPolicy.PREFER_CACHE;
1890
+ switch (fetchPolicy) {
1891
+ case QueryFetchPolicy.SERVER_ONLY:
1892
+ return queryManager.fetchServerResults(queryRef);
1893
+ case QueryFetchPolicy.CACHE_ONLY:
1894
+ return queryManager.fetchCacheResults(queryRef, true);
1895
+ case QueryFetchPolicy.PREFER_CACHE:
1896
+ return queryManager.preferCacheResults(queryRef, false);
1897
+ default:
1898
+ throw new DataConnectError(Code.INVALID_ARGUMENT, `Invalid fetch policy: ${fetchPolicy}`);
1899
+ }
1105
1900
  }
1106
1901
  /**
1107
1902
  * Execute Query
@@ -1113,7 +1908,9 @@ function executeQuery(queryRef) {
1113
1908
  */
1114
1909
  function queryRef(dcInstance, queryName, variables, initialCache) {
1115
1910
  dcInstance.setInitialized();
1116
- dcInstance._queryManager.track(queryName, variables, initialCache);
1911
+ if (initialCache !== undefined) {
1912
+ dcInstance._queryManager.updateSSR(initialCache);
1913
+ }
1117
1914
  return {
1118
1915
  dataConnect: dcInstance,
1119
1916
  refType: QUERY_STR,
@@ -1176,7 +1973,7 @@ function validateArgs(connectorConfig, dcOrVars, vars, validateVars) {
1176
1973
 
1177
1974
  /**
1178
1975
  * @license
1179
- * Copyright 2024 Google LLC
1976
+ * Copyright 2025 Google LLC
1180
1977
  *
1181
1978
  * Licensed under the Apache License, Version 2.0 (the "License");
1182
1979
  * you may not use this file except in compliance with the License.
@@ -1204,12 +2001,14 @@ function subscribe(queryRefOrSerializedResult, observerOrOnNext, onError, onComp
1204
2001
  if ('refInfo' in queryRefOrSerializedResult) {
1205
2002
  const serializedRef = queryRefOrSerializedResult;
1206
2003
  const { data, source, fetchTime } = serializedRef;
2004
+ ref = toQueryRef(serializedRef);
1207
2005
  initialCache = {
1208
2006
  data,
1209
2007
  source,
1210
- fetchTime
2008
+ fetchTime,
2009
+ ref,
2010
+ toJSON: getRefSerializer(ref, data, source, fetchTime)
1211
2011
  };
1212
- ref = toQueryRef(serializedRef);
1213
2012
  }
1214
2013
  else {
1215
2014
  ref = queryRefOrSerializedResult;
@@ -1236,5 +2035,5 @@ function subscribe(queryRefOrSerializedResult, observerOrOnNext, onError, onComp
1236
2035
  */
1237
2036
  registerDataConnect();
1238
2037
 
1239
- 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 };
2038
+ 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 };
1240
2039
  //# sourceMappingURL=index.esm.js.map