@firebase/data-connect 0.3.11-20250716004940 → 0.3.11-caching-fdc.9f17eac6e
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.
- package/dist/index.cjs.js +931 -164
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +928 -166
- package/dist/index.esm.js.map +1 -1
- package/dist/index.node.cjs.js +866 -101
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/internal.d.ts +249 -16
- package/dist/node-esm/index.node.esm.js +865 -103
- package/dist/node-esm/index.node.esm.js.map +1 -1
- package/dist/node-esm/src/api/DataConnect.d.ts +27 -4
- package/dist/node-esm/src/api/index.d.ts +2 -1
- package/dist/node-esm/src/api/query.d.ts +2 -34
- package/dist/node-esm/src/api.browser.d.ts +3 -18
- package/dist/node-esm/src/api.node.d.ts +1 -0
- package/dist/node-esm/src/cache/Cache.d.ts +52 -0
- package/dist/node-esm/src/cache/CacheProvider.d.ts +26 -0
- package/dist/node-esm/src/cache/EntityDataObject.d.ts +41 -0
- package/dist/node-esm/src/cache/EntityNode.d.ts +63 -0
- package/dist/node-esm/src/cache/ImpactedQueryRefsAccumulator.d.ts +21 -0
- package/dist/node-esm/src/cache/InMemoryCacheProvider.d.ts +31 -0
- package/dist/node-esm/src/cache/IndexedDBCacheProvider.d.ts +39 -0
- package/dist/node-esm/src/cache/ResultTree.d.ts +39 -0
- package/dist/node-esm/src/cache/ResultTreeProcessor.d.ts +28 -0
- package/dist/node-esm/src/core/AppCheckTokenProvider.d.ts +1 -1
- package/dist/node-esm/src/core/FirebaseAuthProvider.d.ts +3 -1
- package/dist/node-esm/src/core/{QueryManager.d.ts → query/QueryManager.d.ts} +17 -15
- package/dist/node-esm/src/core/query/queryOptions.d.ts +25 -0
- package/dist/node-esm/src/core/query/subscribe.d.ts +67 -0
- package/dist/node-esm/src/network/fetch.d.ts +1 -1
- package/dist/node-esm/src/network/transport/rest.d.ts +2 -2
- package/dist/node-esm/src/util/encoder.d.ts +3 -0
- package/dist/node-esm/src/util/url.d.ts +1 -0
- package/dist/private.d.ts +248 -15
- package/dist/public.d.ts +54 -4
- package/dist/src/api/DataConnect.d.ts +27 -4
- package/dist/src/api/index.d.ts +2 -1
- package/dist/src/api/query.d.ts +2 -34
- package/dist/src/api.browser.d.ts +3 -18
- package/dist/src/api.node.d.ts +1 -0
- package/dist/src/cache/Cache.d.ts +52 -0
- package/dist/src/cache/CacheProvider.d.ts +26 -0
- package/dist/src/cache/EntityDataObject.d.ts +41 -0
- package/dist/src/cache/EntityNode.d.ts +63 -0
- package/dist/src/cache/ImpactedQueryRefsAccumulator.d.ts +21 -0
- package/dist/src/cache/InMemoryCacheProvider.d.ts +31 -0
- package/dist/src/cache/IndexedDBCacheProvider.d.ts +39 -0
- package/dist/src/cache/ResultTree.d.ts +39 -0
- package/dist/src/cache/ResultTreeProcessor.d.ts +28 -0
- package/dist/src/core/AppCheckTokenProvider.d.ts +1 -1
- package/dist/src/core/FirebaseAuthProvider.d.ts +3 -1
- package/dist/src/core/{QueryManager.d.ts → query/QueryManager.d.ts} +17 -15
- package/dist/src/core/query/queryOptions.d.ts +25 -0
- package/dist/src/core/query/subscribe.d.ts +67 -0
- package/dist/src/network/fetch.d.ts +1 -1
- package/dist/src/network/transport/rest.d.ts +2 -2
- package/dist/src/util/encoder.d.ts +3 -0
- package/dist/src/util/url.d.ts +1 -0
- package/package.json +7 -7
package/dist/index.cjs.js
CHANGED
|
@@ -8,7 +8,7 @@ var util = require('@firebase/util');
|
|
|
8
8
|
var logger$1 = require('@firebase/logger');
|
|
9
9
|
|
|
10
10
|
const name = "@firebase/data-connect";
|
|
11
|
-
const version = "0.3.11-
|
|
11
|
+
const version = "0.3.11-caching-fdc.9f17eac6e";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @license
|
|
@@ -36,69 +36,6 @@ function setSDKVersion(version) {
|
|
|
36
36
|
SDK_VERSION = version;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* @license
|
|
41
|
-
* Copyright 2024 Google LLC
|
|
42
|
-
*
|
|
43
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
44
|
-
* you may not use this file except in compliance with the License.
|
|
45
|
-
* You may obtain a copy of the License at
|
|
46
|
-
*
|
|
47
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
48
|
-
*
|
|
49
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
50
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
51
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
52
|
-
* See the License for the specific language governing permissions and
|
|
53
|
-
* limitations under the License.
|
|
54
|
-
*/
|
|
55
|
-
/**
|
|
56
|
-
* @internal
|
|
57
|
-
* Abstraction around AppCheck's token fetching capabilities.
|
|
58
|
-
*/
|
|
59
|
-
class AppCheckTokenProvider {
|
|
60
|
-
constructor(app$1, appCheckProvider) {
|
|
61
|
-
this.appCheckProvider = appCheckProvider;
|
|
62
|
-
if (app._isFirebaseServerApp(app$1) && app$1.settings.appCheckToken) {
|
|
63
|
-
this.serverAppAppCheckToken = app$1.settings.appCheckToken;
|
|
64
|
-
}
|
|
65
|
-
this.appCheck = appCheckProvider?.getImmediate({ optional: true });
|
|
66
|
-
if (!this.appCheck) {
|
|
67
|
-
void appCheckProvider
|
|
68
|
-
?.get()
|
|
69
|
-
.then(appCheck => (this.appCheck = appCheck))
|
|
70
|
-
.catch();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
getToken() {
|
|
74
|
-
if (this.serverAppAppCheckToken) {
|
|
75
|
-
return Promise.resolve({ token: this.serverAppAppCheckToken });
|
|
76
|
-
}
|
|
77
|
-
if (!this.appCheck) {
|
|
78
|
-
return new Promise((resolve, reject) => {
|
|
79
|
-
// Support delayed initialization of FirebaseAppCheck. This allows our
|
|
80
|
-
// customers to initialize the RTDB SDK before initializing Firebase
|
|
81
|
-
// AppCheck and ensures that all requests are authenticated if a token
|
|
82
|
-
// becomes available before the timoeout below expires.
|
|
83
|
-
setTimeout(() => {
|
|
84
|
-
if (this.appCheck) {
|
|
85
|
-
this.getToken().then(resolve, reject);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
resolve(null);
|
|
89
|
-
}
|
|
90
|
-
}, 0);
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return this.appCheck.getToken();
|
|
94
|
-
}
|
|
95
|
-
addTokenChangeListener(listener) {
|
|
96
|
-
void this.appCheckProvider
|
|
97
|
-
?.get()
|
|
98
|
-
.then(appCheck => appCheck.addTokenListener(listener));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
39
|
/**
|
|
103
40
|
* @license
|
|
104
41
|
* Copyright 2024 Google LLC
|
|
@@ -178,6 +115,769 @@ function logError(msg) {
|
|
|
178
115
|
logger.error(`DataConnect (${SDK_VERSION}): ${msg}`);
|
|
179
116
|
}
|
|
180
117
|
|
|
118
|
+
/**
|
|
119
|
+
* @license
|
|
120
|
+
* Copyright 2025 Google LLC
|
|
121
|
+
*
|
|
122
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
123
|
+
* you may not use this file except in compliance with the License.
|
|
124
|
+
* You may obtain a copy of the License at
|
|
125
|
+
*
|
|
126
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
127
|
+
*
|
|
128
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
129
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
130
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
131
|
+
* See the License for the specific language governing permissions and
|
|
132
|
+
* limitations under the License.
|
|
133
|
+
*/
|
|
134
|
+
class ImpactedQueryRefsAccumulator {
|
|
135
|
+
constructor() {
|
|
136
|
+
this.impacted = new Set();
|
|
137
|
+
}
|
|
138
|
+
add(impacted) {
|
|
139
|
+
impacted.forEach(ref => this.impacted.add(ref));
|
|
140
|
+
}
|
|
141
|
+
consumeEvents() {
|
|
142
|
+
const events = Array.from(this.impacted);
|
|
143
|
+
this.impacted.clear();
|
|
144
|
+
return events;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @license
|
|
150
|
+
* Copyright 2025 Google LLC
|
|
151
|
+
*
|
|
152
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
153
|
+
* you may not use this file except in compliance with the License.
|
|
154
|
+
* You may obtain a copy of the License at
|
|
155
|
+
*
|
|
156
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
157
|
+
*
|
|
158
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
159
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
160
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
161
|
+
* See the License for the specific language governing permissions and
|
|
162
|
+
* limitations under the License.
|
|
163
|
+
*/
|
|
164
|
+
class EntityDataObject {
|
|
165
|
+
getMap() {
|
|
166
|
+
return this.map;
|
|
167
|
+
}
|
|
168
|
+
getStorableMap(map) {
|
|
169
|
+
const newMap = {};
|
|
170
|
+
for (const key in map) {
|
|
171
|
+
if (map.hasOwnProperty(key)) {
|
|
172
|
+
newMap[key] = map[key];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return newMap;
|
|
176
|
+
}
|
|
177
|
+
toStorableJson() {
|
|
178
|
+
return {
|
|
179
|
+
globalID: this.globalID,
|
|
180
|
+
map: this.getStorableMap(this.map),
|
|
181
|
+
queriesReferenced: this.queriesReferenced
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
static fromStorableJson(json) {
|
|
185
|
+
const bdo = new EntityDataObject(json.globalID);
|
|
186
|
+
bdo.map = json.map;
|
|
187
|
+
bdo.queriesReferenced = json.queriesReferenced;
|
|
188
|
+
return bdo;
|
|
189
|
+
}
|
|
190
|
+
constructor(globalID) {
|
|
191
|
+
this.globalID = globalID;
|
|
192
|
+
this.map = {};
|
|
193
|
+
this.queriesReferenced = new Set();
|
|
194
|
+
}
|
|
195
|
+
updateServerValue(key, value, requestedFrom) {
|
|
196
|
+
this.map[key] = value;
|
|
197
|
+
this.queriesReferenced.add(requestedFrom);
|
|
198
|
+
return Array.from(this.queriesReferenced);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @license
|
|
204
|
+
* Copyright 2025 Google LLC
|
|
205
|
+
*
|
|
206
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
207
|
+
* you may not use this file except in compliance with the License.
|
|
208
|
+
* You may obtain a copy of the License at
|
|
209
|
+
*
|
|
210
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
211
|
+
*
|
|
212
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
213
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
214
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
215
|
+
* See the License for the specific language governing permissions and
|
|
216
|
+
* limitations under the License.
|
|
217
|
+
*/
|
|
218
|
+
const GLOBAL_ID_KEY = '_id';
|
|
219
|
+
class EntityNode {
|
|
220
|
+
constructor(acc = new ImpactedQueryRefsAccumulator()) {
|
|
221
|
+
this.acc = acc;
|
|
222
|
+
this.scalars = {};
|
|
223
|
+
this.references = {};
|
|
224
|
+
this.objectLists = {};
|
|
225
|
+
this.impactedQueryRefs = new Set();
|
|
226
|
+
}
|
|
227
|
+
async loadData(queryId, values, cacheProvider) {
|
|
228
|
+
if (values === undefined && cacheProvider === undefined) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (typeof values !== 'object' || Array.isArray(values)) {
|
|
232
|
+
throw new DataConnectError('invalid-argument', 'EntityNode initialized with non-object value');
|
|
233
|
+
}
|
|
234
|
+
if (values === null) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (values.hasOwnProperty(GLOBAL_ID_KEY) &&
|
|
238
|
+
typeof values[GLOBAL_ID_KEY] === 'string') {
|
|
239
|
+
this.globalId = values[GLOBAL_ID_KEY];
|
|
240
|
+
// TODO: Add current query id to BDO
|
|
241
|
+
this.entityData = await cacheProvider.getBdo(this.globalId);
|
|
242
|
+
}
|
|
243
|
+
for (const key in values) {
|
|
244
|
+
if (values.hasOwnProperty(key)) {
|
|
245
|
+
if (key === GLOBAL_ID_KEY) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (typeof values[key] === 'object') {
|
|
249
|
+
if (Array.isArray(values[key])) {
|
|
250
|
+
const objArray = [];
|
|
251
|
+
const scalarArray = [];
|
|
252
|
+
for (const value of values[key]) {
|
|
253
|
+
if (typeof value === 'object') {
|
|
254
|
+
if (Array.isArray(value)) ;
|
|
255
|
+
else {
|
|
256
|
+
const entityNode = new EntityNode(this.acc);
|
|
257
|
+
await entityNode.loadData(queryId, value, cacheProvider);
|
|
258
|
+
objArray.push(entityNode);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
scalarArray.push(value);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (scalarArray.length > 0 && objArray.length > 0) {
|
|
266
|
+
throw new DataConnectError('invalid-argument', 'Sparse array detected.');
|
|
267
|
+
}
|
|
268
|
+
if (scalarArray.length > 0) {
|
|
269
|
+
if (this.entityData) {
|
|
270
|
+
const impactedRefs = this.entityData.updateServerValue(key, scalarArray, queryId);
|
|
271
|
+
this.acc.add(impactedRefs);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
this.scalars[key] = scalarArray;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else if (objArray.length > 0) {
|
|
278
|
+
this.objectLists[key] = objArray;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.scalars[key] = [];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
if (values[key] === null) {
|
|
286
|
+
this.scalars[key] = null;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const stubDataObject = new EntityNode(this.acc);
|
|
290
|
+
await stubDataObject.loadData(queryId, values[key], cacheProvider);
|
|
291
|
+
this.references[key] = stubDataObject;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
if (this.entityData) {
|
|
296
|
+
// TODO: Track only the fields we need for the BDO
|
|
297
|
+
const impactedRefs = this.entityData.updateServerValue(key, values[key], queryId);
|
|
298
|
+
this.acc.add(impactedRefs);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
this.scalars[key] = values[key];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (this.entityData) {
|
|
307
|
+
await cacheProvider.updateBackingData(this.entityData);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
toJson() {
|
|
311
|
+
const resultObject = {};
|
|
312
|
+
const entityDataMap = this.entityData?.getMap();
|
|
313
|
+
for (const key in entityDataMap) {
|
|
314
|
+
if (entityDataMap?.hasOwnProperty(key)) {
|
|
315
|
+
resultObject[key] = entityDataMap[key];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Scalars should never have stubdataobjects
|
|
319
|
+
for (const key in this.scalars) {
|
|
320
|
+
if (this.scalars.hasOwnProperty(key)) {
|
|
321
|
+
resultObject[key] = this.scalars[key];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (const key in this.references) {
|
|
325
|
+
if (this.references.hasOwnProperty(key)) {
|
|
326
|
+
resultObject[key] = this.references[key].toJson();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const key in this.objectLists) {
|
|
330
|
+
if (this.objectLists.hasOwnProperty(key)) {
|
|
331
|
+
resultObject[key] = this.objectLists[key].map(obj => {
|
|
332
|
+
return obj.toJson();
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return resultObject;
|
|
337
|
+
}
|
|
338
|
+
static parseMap(map, isSdo = false) {
|
|
339
|
+
const newMap = {};
|
|
340
|
+
for (const key in map) {
|
|
341
|
+
if (map.hasOwnProperty(key)) {
|
|
342
|
+
if (Array.isArray(map[key])) {
|
|
343
|
+
newMap[key] = map[key].map(value => isSdo ? EntityNode.fromStorableJson(value) : value);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
newMap[key] = isSdo
|
|
347
|
+
? EntityNode.fromStorableJson(map[key])
|
|
348
|
+
: map[key];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return newMap;
|
|
353
|
+
}
|
|
354
|
+
static fromStorableJson(obj) {
|
|
355
|
+
const sdo = new EntityNode();
|
|
356
|
+
if (obj.backingData) {
|
|
357
|
+
sdo.entityData = EntityDataObject.fromStorableJson(obj.backingData);
|
|
358
|
+
}
|
|
359
|
+
sdo.acc = new ImpactedQueryRefsAccumulator();
|
|
360
|
+
sdo.globalId = obj.globalID;
|
|
361
|
+
sdo.impactedQueryRefs = new Set();
|
|
362
|
+
sdo.scalars = EntityNode.parseMap(obj.scalars);
|
|
363
|
+
sdo.references = EntityNode.parseMap(obj.references);
|
|
364
|
+
sdo.objectLists = EntityNode.parseMap(obj.objectLists, true);
|
|
365
|
+
return sdo;
|
|
366
|
+
}
|
|
367
|
+
getStorableMap(map) {
|
|
368
|
+
const newMap = {};
|
|
369
|
+
for (const key in map) {
|
|
370
|
+
if (map.hasOwnProperty(key)) {
|
|
371
|
+
if (Array.isArray(map[key])) {
|
|
372
|
+
newMap[key] = map[key].map(value => value.toStorableJson());
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
newMap[key] = map[key].toStorableJson();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return newMap;
|
|
380
|
+
}
|
|
381
|
+
toStorableJson() {
|
|
382
|
+
const obj = {
|
|
383
|
+
globalID: this.globalId,
|
|
384
|
+
scalars: this.scalars,
|
|
385
|
+
references: this.getStorableMap(this.references),
|
|
386
|
+
objectLists: this.getStorableMap(this.objectLists)
|
|
387
|
+
};
|
|
388
|
+
if (this.entityData) {
|
|
389
|
+
obj.backingData = this.entityData.toStorableJson();
|
|
390
|
+
}
|
|
391
|
+
return obj;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* @license
|
|
397
|
+
* Copyright 2025 Google LLC
|
|
398
|
+
*
|
|
399
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
400
|
+
* you may not use this file except in compliance with the License.
|
|
401
|
+
* You may obtain a copy of the License at
|
|
402
|
+
*
|
|
403
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
404
|
+
*
|
|
405
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
406
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
407
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
408
|
+
* See the License for the specific language governing permissions and
|
|
409
|
+
* limitations under the License.
|
|
410
|
+
*/
|
|
411
|
+
class ResultTree {
|
|
412
|
+
static parse(value) {
|
|
413
|
+
const rt = new ResultTree(value.data, EntityNode.fromStorableJson(value.rootStub), value.ttlInMs, value.cachedAt, value.lastAccessed);
|
|
414
|
+
return rt;
|
|
415
|
+
}
|
|
416
|
+
constructor(data, rootStub, ttlInMs = 30000, cachedAt, _lastAccessed) {
|
|
417
|
+
this.data = data;
|
|
418
|
+
this.rootStub = rootStub;
|
|
419
|
+
this.ttlInMs = ttlInMs;
|
|
420
|
+
this.cachedAt = cachedAt;
|
|
421
|
+
this._lastAccessed = _lastAccessed;
|
|
422
|
+
}
|
|
423
|
+
isStale() {
|
|
424
|
+
return (Date.now() - new Date(this.cachedAt.getTime()).getTime() > this.ttlInMs);
|
|
425
|
+
}
|
|
426
|
+
updateTtl(ttlInMs) {
|
|
427
|
+
this.ttlInMs = ttlInMs;
|
|
428
|
+
}
|
|
429
|
+
updateAccessed() {
|
|
430
|
+
this._lastAccessed = new Date();
|
|
431
|
+
}
|
|
432
|
+
get lastAccessed() {
|
|
433
|
+
return this._lastAccessed;
|
|
434
|
+
}
|
|
435
|
+
getRootStub() {
|
|
436
|
+
return this.rootStub;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* @license
|
|
442
|
+
* Copyright 2025 Google LLC
|
|
443
|
+
*
|
|
444
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
445
|
+
* you may not use this file except in compliance with the License.
|
|
446
|
+
* You may obtain a copy of the License at
|
|
447
|
+
*
|
|
448
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
449
|
+
*
|
|
450
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
451
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
452
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
453
|
+
* See the License for the specific language governing permissions and
|
|
454
|
+
* limitations under the License.
|
|
455
|
+
*/
|
|
456
|
+
const BDO_OBJECT_STORE_NAME = 'data-connect-bdos';
|
|
457
|
+
const SRT_OBJECT_STORE_NAME = 'data-connect-srts';
|
|
458
|
+
class IndexedDBCacheProvider {
|
|
459
|
+
isIdbAvailable() {
|
|
460
|
+
return typeof window !== 'undefined' && 'indexedDB' in window;
|
|
461
|
+
}
|
|
462
|
+
// TODO: Figure out how to deal with caching across tabs.
|
|
463
|
+
// We could use the web locks api
|
|
464
|
+
constructor(cacheId) {
|
|
465
|
+
this.cacheId = cacheId;
|
|
466
|
+
this.bdos = new Map();
|
|
467
|
+
this.resultTrees = new Map();
|
|
468
|
+
this.initialized = false;
|
|
469
|
+
if (!this.isIdbAvailable()) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
this.idbManager = new IndexedDbManager(this.cacheId);
|
|
473
|
+
this.idbManager.open(1);
|
|
474
|
+
}
|
|
475
|
+
async initialize() {
|
|
476
|
+
// load BDOs
|
|
477
|
+
if (this.initialized) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const db = await this.idbManager.dbPromise;
|
|
481
|
+
const resultTrees = await this.idbManager.readFromDb(db);
|
|
482
|
+
resultTrees.forEach((resultTree, key) => {
|
|
483
|
+
this.resultTrees.set(key, resultTree);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
async commitBdoChanges(backingData) {
|
|
487
|
+
// TODO: Move these into a new function.
|
|
488
|
+
if (!this.isIdbAvailable()) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
await this.initialize();
|
|
492
|
+
void this.idbManager.updateBdo(backingData);
|
|
493
|
+
}
|
|
494
|
+
async commitResultTreeChanges(queryId, rt) {
|
|
495
|
+
if (!this.isIdbAvailable()) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
await this.initialize();
|
|
499
|
+
void this.idbManager.updateResultTree(rt, queryId);
|
|
500
|
+
}
|
|
501
|
+
async setResultTree(queryId, rt) {
|
|
502
|
+
this.resultTrees.set(queryId, rt);
|
|
503
|
+
// maybe this needs to be async?
|
|
504
|
+
// TODO: replace array with valid data
|
|
505
|
+
await this.commitResultTreeChanges(queryId, rt);
|
|
506
|
+
}
|
|
507
|
+
async getResultTree(queryId) {
|
|
508
|
+
await this.initialize();
|
|
509
|
+
const ret = this.resultTrees.get(queryId);
|
|
510
|
+
return ret;
|
|
511
|
+
}
|
|
512
|
+
createGlobalId() {
|
|
513
|
+
return crypto.randomUUID();
|
|
514
|
+
}
|
|
515
|
+
async getBdo(globalId) {
|
|
516
|
+
await this.initialize();
|
|
517
|
+
if (!this.bdos.has(globalId)) {
|
|
518
|
+
this.bdos.set(globalId, new EntityDataObject(globalId));
|
|
519
|
+
}
|
|
520
|
+
// Because of the above, we can guarantee that there will be a BDO at the globalId.
|
|
521
|
+
return this.bdos.get(globalId);
|
|
522
|
+
}
|
|
523
|
+
async updateBackingData(backingData) {
|
|
524
|
+
this.bdos.set(backingData.globalID, backingData);
|
|
525
|
+
await this.commitBdoChanges(backingData);
|
|
526
|
+
}
|
|
527
|
+
async close() {
|
|
528
|
+
await this.idbManager.close();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const dbName = 'data-connect';
|
|
532
|
+
class IndexedDbManager {
|
|
533
|
+
constructor(cacheId) {
|
|
534
|
+
this.cacheId = cacheId;
|
|
535
|
+
this.alreadyRead = false;
|
|
536
|
+
}
|
|
537
|
+
open(version) {
|
|
538
|
+
this.dbPromise = new Promise((dbResolve, dbReject) => {
|
|
539
|
+
// TODO: See when, or if ever, cacheId is null
|
|
540
|
+
const request = indexedDB.open(`${dbName}-${this.cacheId}`, version);
|
|
541
|
+
request.onupgradeneeded = event => {
|
|
542
|
+
// TODO: Handle what happens if the version changes.
|
|
543
|
+
const db = event.target.result;
|
|
544
|
+
db.createObjectStore(BDO_OBJECT_STORE_NAME);
|
|
545
|
+
db.createObjectStore(SRT_OBJECT_STORE_NAME);
|
|
546
|
+
dbResolve(db);
|
|
547
|
+
};
|
|
548
|
+
request.onsuccess = async (event) => {
|
|
549
|
+
const db = event.target.result;
|
|
550
|
+
dbResolve(db);
|
|
551
|
+
};
|
|
552
|
+
request.onerror = error => {
|
|
553
|
+
// TODO(mtewani): Use proper error.
|
|
554
|
+
dbReject(error);
|
|
555
|
+
};
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
async updateBdo(backingData) {
|
|
559
|
+
const db = await this.dbPromise;
|
|
560
|
+
db.transaction([BDO_OBJECT_STORE_NAME], 'readwrite')
|
|
561
|
+
.objectStore(BDO_OBJECT_STORE_NAME)
|
|
562
|
+
.put(backingData, backingData.globalID);
|
|
563
|
+
}
|
|
564
|
+
async updateResultTree(rt, queryId) {
|
|
565
|
+
const db = await this.dbPromise;
|
|
566
|
+
const objectStore = db
|
|
567
|
+
.transaction([SRT_OBJECT_STORE_NAME], 'readwrite')
|
|
568
|
+
.objectStore(SRT_OBJECT_STORE_NAME);
|
|
569
|
+
// TODO: What happens if you override an existing entry?
|
|
570
|
+
// TODO: We should first check whether the tree is hydrated or not.
|
|
571
|
+
// TODO: We should make sure that everything has been written.
|
|
572
|
+
objectStore.put(rt.getRootStub().toStorableJson(), queryId);
|
|
573
|
+
}
|
|
574
|
+
async readFromDb(db) {
|
|
575
|
+
const resultTrees = new Map();
|
|
576
|
+
if (this.alreadyRead) {
|
|
577
|
+
return resultTrees;
|
|
578
|
+
}
|
|
579
|
+
const bdos = new Map();
|
|
580
|
+
const tx = db.transaction([BDO_OBJECT_STORE_NAME, SRT_OBJECT_STORE_NAME], 'readonly');
|
|
581
|
+
const bdoStore = tx.objectStore(BDO_OBJECT_STORE_NAME);
|
|
582
|
+
const srtStore = tx.objectStore(SRT_OBJECT_STORE_NAME);
|
|
583
|
+
const bdoComplete = new Promise((resolve, reject) => {
|
|
584
|
+
const openCursor = bdoStore.openCursor();
|
|
585
|
+
openCursor.onsuccess = event => {
|
|
586
|
+
const cursor = event.target
|
|
587
|
+
.result;
|
|
588
|
+
if (cursor) {
|
|
589
|
+
bdos.set(cursor.key, EntityDataObject.fromStorableJson(cursor.value));
|
|
590
|
+
cursor.continue();
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
// No more entries
|
|
594
|
+
resolve(null);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
openCursor.onerror = error => {
|
|
598
|
+
console.error(error);
|
|
599
|
+
reject(error);
|
|
600
|
+
};
|
|
601
|
+
});
|
|
602
|
+
const srtComplete = new Promise((resolve, reject) => {
|
|
603
|
+
const openCursor = srtStore.openCursor();
|
|
604
|
+
openCursor.onsuccess = event => {
|
|
605
|
+
const cursor = event.target
|
|
606
|
+
.result;
|
|
607
|
+
if (cursor) {
|
|
608
|
+
const srt = ResultTree.parse(cursor.value);
|
|
609
|
+
resultTrees.set(cursor.key, srt);
|
|
610
|
+
cursor.continue();
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
// No more entries
|
|
614
|
+
resolve(null);
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
openCursor.onerror = error => {
|
|
618
|
+
console.error(error);
|
|
619
|
+
reject(error);
|
|
620
|
+
};
|
|
621
|
+
});
|
|
622
|
+
await Promise.all([bdoComplete, srtComplete]);
|
|
623
|
+
return resultTrees;
|
|
624
|
+
}
|
|
625
|
+
async close() {
|
|
626
|
+
const db = await this.dbPromise;
|
|
627
|
+
db.close();
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* @license
|
|
633
|
+
* Copyright 2025 Google LLC
|
|
634
|
+
*
|
|
635
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
636
|
+
* you may not use this file except in compliance with the License.
|
|
637
|
+
* You may obtain a copy of the License at
|
|
638
|
+
*
|
|
639
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
640
|
+
*
|
|
641
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
642
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
643
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
644
|
+
* See the License for the specific language governing permissions and
|
|
645
|
+
* limitations under the License.
|
|
646
|
+
*/
|
|
647
|
+
class InMemoryCacheProvider {
|
|
648
|
+
constructor(_keyId) {
|
|
649
|
+
this._keyId = _keyId;
|
|
650
|
+
this.bdos = new Map();
|
|
651
|
+
this.resultTrees = new Map();
|
|
652
|
+
}
|
|
653
|
+
setResultTree(queryId, rt) {
|
|
654
|
+
this.resultTrees.set(queryId, rt);
|
|
655
|
+
return Promise.resolve();
|
|
656
|
+
}
|
|
657
|
+
// TODO: Should this be in the cache provider? This seems common along all CacheProviders.
|
|
658
|
+
async getResultTree(queryId) {
|
|
659
|
+
return this.resultTrees.get(queryId);
|
|
660
|
+
}
|
|
661
|
+
createGlobalId() {
|
|
662
|
+
return crypto.randomUUID();
|
|
663
|
+
}
|
|
664
|
+
updateBackingData(backingData) {
|
|
665
|
+
this.bdos.set(backingData.globalID, backingData);
|
|
666
|
+
return Promise.resolve();
|
|
667
|
+
}
|
|
668
|
+
async getBdo(globalId) {
|
|
669
|
+
if (!this.bdos.has(globalId)) {
|
|
670
|
+
this.bdos.set(globalId, new EntityDataObject(globalId));
|
|
671
|
+
}
|
|
672
|
+
// Because of the above, we can guarantee that there will be a BDO at the globalId.
|
|
673
|
+
return this.bdos.get(globalId);
|
|
674
|
+
}
|
|
675
|
+
close() {
|
|
676
|
+
// TODO: Noop
|
|
677
|
+
return Promise.resolve();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* @license
|
|
683
|
+
* Copyright 2025 Google LLC
|
|
684
|
+
*
|
|
685
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
686
|
+
* you may not use this file except in compliance with the License.
|
|
687
|
+
* You may obtain a copy of the License at
|
|
688
|
+
*
|
|
689
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
690
|
+
*
|
|
691
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
692
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
693
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
694
|
+
* See the License for the specific language governing permissions and
|
|
695
|
+
* limitations under the License.
|
|
696
|
+
*/
|
|
697
|
+
class ResultTreeProcessor {
|
|
698
|
+
hydrateResults(rootStubObject) {
|
|
699
|
+
return JSON.stringify(rootStubObject.toJson());
|
|
700
|
+
}
|
|
701
|
+
async dehydrateResults(json, cacheProvider, acc, queryId) {
|
|
702
|
+
const stubDataObject = new EntityNode(acc);
|
|
703
|
+
await stubDataObject.loadData(queryId, json, cacheProvider);
|
|
704
|
+
return {
|
|
705
|
+
stubDataObject,
|
|
706
|
+
data: JSON.stringify(stubDataObject.toStorableJson())
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* @license
|
|
713
|
+
* Copyright 2025 Google LLC
|
|
714
|
+
*
|
|
715
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
716
|
+
* you may not use this file except in compliance with the License.
|
|
717
|
+
* You may obtain a copy of the License at
|
|
718
|
+
*
|
|
719
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
720
|
+
*
|
|
721
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
722
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
723
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
724
|
+
* See the License for the specific language governing permissions and
|
|
725
|
+
* limitations under the License.
|
|
726
|
+
*/
|
|
727
|
+
class DataConnectCache {
|
|
728
|
+
constructor(authProvider, projectId, connectorConfig, host, cacheSettings) {
|
|
729
|
+
this.authProvider = authProvider;
|
|
730
|
+
this.projectId = projectId;
|
|
731
|
+
this.connectorConfig = connectorConfig;
|
|
732
|
+
this.host = host;
|
|
733
|
+
this.cacheSettings = cacheSettings;
|
|
734
|
+
this.cacheProvider = null;
|
|
735
|
+
this.uid = null;
|
|
736
|
+
this.authProvider.addTokenChangeListener(async (_) => {
|
|
737
|
+
const newUid = this.authProvider.getAuth().getUid();
|
|
738
|
+
// We should only close if the token changes and so does the new UID
|
|
739
|
+
if (this.uid !== newUid) {
|
|
740
|
+
await this.cacheProvider?.close();
|
|
741
|
+
this.uid = newUid;
|
|
742
|
+
const identifier = await this.getIdentifier(this.uid);
|
|
743
|
+
this.cacheProvider = this.initializeNewProviders(identifier);
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
async initialize() {
|
|
748
|
+
if (!this.cacheProvider) {
|
|
749
|
+
const identifier = await this.getIdentifier(this.uid);
|
|
750
|
+
this.cacheProvider = this.initializeNewProviders(identifier);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
async getIdentifier(uid) {
|
|
754
|
+
const identifier = `${this.cacheSettings?.cacheProvider instanceof IndexedDBStub
|
|
755
|
+
? 'persistent'
|
|
756
|
+
: 'memory'}-${this.projectId}-${this.connectorConfig.service}-${this.connectorConfig.connector}-${this.connectorConfig.location}-${uid}-${this.host}`;
|
|
757
|
+
const sha256 = await util.generateSHA256HashBrowser(identifier);
|
|
758
|
+
return sha256;
|
|
759
|
+
}
|
|
760
|
+
initializeNewProviders(identifier) {
|
|
761
|
+
let cacheProvider;
|
|
762
|
+
if (this.cacheSettings) {
|
|
763
|
+
cacheProvider =
|
|
764
|
+
this.cacheSettings.cacheProvider?.type === 'MEMORY' ||
|
|
765
|
+
!util.isIndexedDBAvailable()
|
|
766
|
+
? new InMemoryCacheProvider(identifier)
|
|
767
|
+
: new IndexedDBCacheProvider(identifier);
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
logDebug('IndexedDB is not available. Using In-Memory Cache Provider instead.');
|
|
771
|
+
cacheProvider = new InMemoryCacheProvider(identifier);
|
|
772
|
+
}
|
|
773
|
+
return cacheProvider;
|
|
774
|
+
}
|
|
775
|
+
async containsResultTree(queryId) {
|
|
776
|
+
await this.initialize();
|
|
777
|
+
const resultTree = await this.cacheProvider.getResultTree(queryId);
|
|
778
|
+
return resultTree !== undefined;
|
|
779
|
+
}
|
|
780
|
+
async getResultTree(queryId) {
|
|
781
|
+
await this.initialize();
|
|
782
|
+
const cacheProvider = this.cacheProvider;
|
|
783
|
+
return cacheProvider.getResultTree(queryId);
|
|
784
|
+
}
|
|
785
|
+
async getResultJSON(queryId) {
|
|
786
|
+
await this.initialize();
|
|
787
|
+
const processor = new ResultTreeProcessor();
|
|
788
|
+
const cacheProvider = this.cacheProvider;
|
|
789
|
+
const resultTree = await cacheProvider.getResultTree(queryId);
|
|
790
|
+
if (!resultTree) {
|
|
791
|
+
throw new DataConnectError('invalid-argument', `${queryId} not found in cache. Call "update() first."`);
|
|
792
|
+
}
|
|
793
|
+
return processor.hydrateResults(resultTree.getRootStub());
|
|
794
|
+
}
|
|
795
|
+
async update(queryId, serverValues) {
|
|
796
|
+
const processor = new ResultTreeProcessor();
|
|
797
|
+
const acc = new ImpactedQueryRefsAccumulator();
|
|
798
|
+
const cacheProvider = this.cacheProvider;
|
|
799
|
+
const { data, stubDataObject } = await processor.dehydrateResults(serverValues, cacheProvider, acc, queryId);
|
|
800
|
+
const now = new Date();
|
|
801
|
+
// TODO: Check if ttl actually gets passed.
|
|
802
|
+
// TODO: Check API Proposal fields.
|
|
803
|
+
await cacheProvider.setResultTree(queryId, new ResultTree(data, stubDataObject, serverValues.ttl, now, now));
|
|
804
|
+
return acc.consumeEvents();
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
class IndexedDBStub {
|
|
808
|
+
constructor() {
|
|
809
|
+
this.type = 'PERSISTENT';
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
class MemoryStub {
|
|
813
|
+
constructor() {
|
|
814
|
+
this.type = 'MEMORY';
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* @license
|
|
820
|
+
* Copyright 2024 Google LLC
|
|
821
|
+
*
|
|
822
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
823
|
+
* you may not use this file except in compliance with the License.
|
|
824
|
+
* You may obtain a copy of the License at
|
|
825
|
+
*
|
|
826
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
827
|
+
*
|
|
828
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
829
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
830
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
831
|
+
* See the License for the specific language governing permissions and
|
|
832
|
+
* limitations under the License.
|
|
833
|
+
*/
|
|
834
|
+
/**
|
|
835
|
+
* @internal
|
|
836
|
+
* Abstraction around AppCheck's token fetching capabilities.
|
|
837
|
+
*/
|
|
838
|
+
class AppCheckTokenProvider {
|
|
839
|
+
constructor(app$1, appCheckProvider) {
|
|
840
|
+
this.appCheckProvider = appCheckProvider;
|
|
841
|
+
if (app._isFirebaseServerApp(app$1) && app$1.settings.appCheckToken) {
|
|
842
|
+
this.serverAppAppCheckToken = app$1.settings.appCheckToken;
|
|
843
|
+
}
|
|
844
|
+
this.appCheck = appCheckProvider?.getImmediate({ optional: true });
|
|
845
|
+
if (!this.appCheck) {
|
|
846
|
+
void appCheckProvider
|
|
847
|
+
?.get()
|
|
848
|
+
.then(appCheck => (this.appCheck = appCheck))
|
|
849
|
+
.catch();
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
getToken() {
|
|
853
|
+
if (this.serverAppAppCheckToken) {
|
|
854
|
+
return Promise.resolve({ token: this.serverAppAppCheckToken });
|
|
855
|
+
}
|
|
856
|
+
if (!this.appCheck) {
|
|
857
|
+
return new Promise((resolve, reject) => {
|
|
858
|
+
// Support delayed initialization of FirebaseAppCheck. This allows our
|
|
859
|
+
// customers to initialize the RTDB SDK before initializing Firebase
|
|
860
|
+
// AppCheck and ensures that all requests are authenticated if a token
|
|
861
|
+
// becomes available before the timoeout below expires.
|
|
862
|
+
setTimeout(() => {
|
|
863
|
+
if (this.appCheck) {
|
|
864
|
+
this.getToken().then(resolve, reject);
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
resolve(null);
|
|
868
|
+
}
|
|
869
|
+
}, 0);
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
return this.appCheck.getToken();
|
|
873
|
+
}
|
|
874
|
+
addTokenChangeListener(listener) {
|
|
875
|
+
void this.appCheckProvider
|
|
876
|
+
?.get()
|
|
877
|
+
.then(appCheck => appCheck.addTokenListener(listener));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
181
881
|
/**
|
|
182
882
|
* @license
|
|
183
883
|
* Copyright 2024 Google LLC
|
|
@@ -205,6 +905,9 @@ class FirebaseAuthProvider {
|
|
|
205
905
|
_authProvider.onInit(auth => (this._auth = auth));
|
|
206
906
|
}
|
|
207
907
|
}
|
|
908
|
+
getAuth() {
|
|
909
|
+
return this._auth;
|
|
910
|
+
}
|
|
208
911
|
getToken(forceRefresh) {
|
|
209
912
|
if (!this._auth) {
|
|
210
913
|
return new Promise((resolve, reject) => {
|
|
@@ -279,14 +982,20 @@ const SOURCE_CACHE = 'CACHE';
|
|
|
279
982
|
* limitations under the License.
|
|
280
983
|
*/
|
|
281
984
|
let encoderImpl;
|
|
985
|
+
let decoderImpl;
|
|
282
986
|
function setEncoder(encoder) {
|
|
283
987
|
encoderImpl = encoder;
|
|
284
988
|
}
|
|
989
|
+
function setDecoder(decoder) {
|
|
990
|
+
decoderImpl = decoder;
|
|
991
|
+
}
|
|
992
|
+
// TODO(mtewani): Fix issue where if fields are out of order, caching breaks.
|
|
285
993
|
setEncoder(o => JSON.stringify(o));
|
|
994
|
+
setDecoder(s => JSON.parse(s));
|
|
286
995
|
|
|
287
996
|
/**
|
|
288
997
|
* @license
|
|
289
|
-
* Copyright
|
|
998
|
+
* Copyright 2025 Google LLC
|
|
290
999
|
*
|
|
291
1000
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
292
1001
|
* you may not use this file except in compliance with the License.
|
|
@@ -300,11 +1009,11 @@ setEncoder(o => JSON.stringify(o));
|
|
|
300
1009
|
* See the License for the specific language governing permissions and
|
|
301
1010
|
* limitations under the License.
|
|
302
1011
|
*/
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
1012
|
+
const QueryFetchPolicy = {
|
|
1013
|
+
PREFER_CACHE: 'PREFER_CACHE',
|
|
1014
|
+
CACHE_ONLY: 'CACHE_ONLY',
|
|
1015
|
+
SERVER_ONLY: 'SERVER_ONLY'
|
|
1016
|
+
};
|
|
308
1017
|
|
|
309
1018
|
/**
|
|
310
1019
|
* @license
|
|
@@ -334,32 +1043,35 @@ function getRefSerializer(queryRef, data, source) {
|
|
|
334
1043
|
...queryRef.dataConnect.getSettings()
|
|
335
1044
|
}
|
|
336
1045
|
},
|
|
337
|
-
fetchTime: Date.now().toLocaleString(),
|
|
1046
|
+
fetchTime: Date.now().toLocaleString(), // TODO: Fix the fetch time here.
|
|
338
1047
|
source
|
|
339
1048
|
};
|
|
340
1049
|
};
|
|
341
1050
|
}
|
|
342
1051
|
class QueryManager {
|
|
343
|
-
constructor(transport) {
|
|
1052
|
+
constructor(transport, cache, dc) {
|
|
344
1053
|
this.transport = transport;
|
|
345
|
-
this.
|
|
1054
|
+
this.cache = cache;
|
|
1055
|
+
this.dc = dc;
|
|
1056
|
+
this.callbacks = new Map();
|
|
1057
|
+
this.queue = [];
|
|
1058
|
+
}
|
|
1059
|
+
async waitForQueuedWrites() {
|
|
1060
|
+
for (const promise of this.queue) {
|
|
1061
|
+
await promise;
|
|
1062
|
+
}
|
|
1063
|
+
this.queue = [];
|
|
1064
|
+
}
|
|
1065
|
+
updateSSR(updatedData) {
|
|
1066
|
+
this.queue.push(this.updateCache(updatedData).then(async (result) => this.publishCacheResultsToSubscribers(result)));
|
|
346
1067
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
1068
|
+
async updateCache(result) {
|
|
1069
|
+
await this.waitForQueuedWrites();
|
|
1070
|
+
return this.cache.update(encoderImpl({
|
|
1071
|
+
name: result.ref.name,
|
|
1072
|
+
variables: result.ref.variables,
|
|
351
1073
|
refType: QUERY_STR
|
|
352
|
-
};
|
|
353
|
-
const key = encoderImpl(ref);
|
|
354
|
-
const newTrackedQuery = {
|
|
355
|
-
ref,
|
|
356
|
-
subscriptions: [],
|
|
357
|
-
currentCache: initialCache || null,
|
|
358
|
-
lastError: null
|
|
359
|
-
};
|
|
360
|
-
// @ts-ignore
|
|
361
|
-
setIfNotExists(this._queries, key, newTrackedQuery);
|
|
362
|
-
return this._queries.get(key);
|
|
1074
|
+
}), result.data);
|
|
363
1075
|
}
|
|
364
1076
|
addSubscription(queryRef, onResultCallback, onErrorCallback, initialCache) {
|
|
365
1077
|
const key = encoderImpl({
|
|
@@ -367,98 +1079,119 @@ class QueryManager {
|
|
|
367
1079
|
variables: queryRef.variables,
|
|
368
1080
|
refType: QUERY_STR
|
|
369
1081
|
});
|
|
370
|
-
const trackedQuery = this._queries.get(key);
|
|
371
|
-
const subscription = {
|
|
372
|
-
userCallback: onResultCallback,
|
|
373
|
-
errCallback: onErrorCallback
|
|
374
|
-
};
|
|
375
1082
|
const unsubscribe = () => {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (initialCache && trackedQuery.currentCache !== initialCache) {
|
|
380
|
-
logDebug('Initial cache found. Comparing dates.');
|
|
381
|
-
if (!trackedQuery.currentCache ||
|
|
382
|
-
(trackedQuery.currentCache &&
|
|
383
|
-
compareDates(trackedQuery.currentCache.fetchTime, initialCache.fetchTime))) {
|
|
384
|
-
trackedQuery.currentCache = initialCache;
|
|
1083
|
+
if (this.callbacks.has(key)) {
|
|
1084
|
+
const callbackList = this.callbacks.get(key);
|
|
1085
|
+
this.callbacks.set(key, callbackList.filter(callback => callback.userCallback !== onResultCallback));
|
|
385
1086
|
}
|
|
1087
|
+
};
|
|
1088
|
+
if (initialCache) {
|
|
1089
|
+
this.updateSSR(initialCache);
|
|
386
1090
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
toJSON: getRefSerializer(queryRef, trackedQuery.currentCache.data, SOURCE_CACHE),
|
|
394
|
-
fetchTime: trackedQuery.currentCache.fetchTime
|
|
395
|
-
});
|
|
396
|
-
if (trackedQuery.lastError !== null && onErrorCallback) {
|
|
397
|
-
onErrorCallback(undefined);
|
|
398
|
-
}
|
|
1091
|
+
logDebug(`Cache not available for query ${queryRef.name} with variables ${JSON.stringify(queryRef.variables)}. Calling executeQuery.`);
|
|
1092
|
+
const promise = this.executeQuery(queryRef);
|
|
1093
|
+
// We want to ignore the error and let subscriptions handle it
|
|
1094
|
+
promise.then(undefined, err => { });
|
|
1095
|
+
if (!this.callbacks.has(key)) {
|
|
1096
|
+
this.callbacks.set(key, []);
|
|
399
1097
|
}
|
|
400
|
-
|
|
1098
|
+
this.callbacks.get(key).push({
|
|
401
1099
|
userCallback: onResultCallback,
|
|
402
1100
|
errCallback: onErrorCallback,
|
|
403
1101
|
unsubscribe
|
|
404
1102
|
});
|
|
405
|
-
if (!trackedQuery.currentCache) {
|
|
406
|
-
logDebug(`No cache available for query ${queryRef.name} with variables ${JSON.stringify(queryRef.variables)}. Calling executeQuery.`);
|
|
407
|
-
const promise = this.executeQuery(queryRef);
|
|
408
|
-
// We want to ignore the error and let subscriptions handle it
|
|
409
|
-
promise.then(undefined, err => { });
|
|
410
|
-
}
|
|
411
1103
|
return unsubscribe;
|
|
412
1104
|
}
|
|
413
|
-
executeQuery(queryRef) {
|
|
1105
|
+
async executeQuery(queryRef, options) {
|
|
1106
|
+
await this.waitForQueuedWrites();
|
|
414
1107
|
if (queryRef.refType !== QUERY_STR) {
|
|
415
|
-
throw new DataConnectError(Code.INVALID_ARGUMENT, `ExecuteQuery can only execute query
|
|
1108
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, `ExecuteQuery can only execute query operations`);
|
|
416
1109
|
}
|
|
417
1110
|
const key = encoderImpl({
|
|
418
1111
|
name: queryRef.name,
|
|
419
1112
|
variables: queryRef.variables,
|
|
420
1113
|
refType: QUERY_STR
|
|
421
1114
|
});
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
1115
|
+
if (options?.fetchPolicy !== QueryFetchPolicy.SERVER_ONLY &&
|
|
1116
|
+
(await this.cache.containsResultTree(key)) &&
|
|
1117
|
+
!(await this.cache.getResultTree(key)).isStale()) {
|
|
1118
|
+
const cacheResult = JSON.parse(await this.cache.getResultJSON(key));
|
|
1119
|
+
const resultTree = await this.cache.getResultTree(key);
|
|
1120
|
+
const result = {
|
|
1121
|
+
source: SOURCE_CACHE,
|
|
1122
|
+
ref: queryRef,
|
|
1123
|
+
data: cacheResult,
|
|
1124
|
+
toJSON: getRefSerializer(queryRef, cacheResult, SOURCE_CACHE),
|
|
1125
|
+
fetchTime: resultTree.cachedAt.toString()
|
|
1126
|
+
};
|
|
1127
|
+
(await this.cache.getResultTree(key)).updateAccessed();
|
|
1128
|
+
logDebug(`Cache found for query ${queryRef.name} with variables ${JSON.stringify(queryRef.variables)}. Calling executeQuery`);
|
|
1129
|
+
return result;
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
if (options?.fetchPolicy === QueryFetchPolicy.SERVER_ONLY) {
|
|
1133
|
+
logDebug(`Skipping cache for fetch policy "serverOnly"`);
|
|
1134
|
+
}
|
|
1135
|
+
else {
|
|
1136
|
+
logDebug(`No Cache found for query ${queryRef.name} with variables ${JSON.stringify(queryRef.variables)}. Calling executeQuery`);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
try {
|
|
1140
|
+
const res = await this.transport.invokeQuery(queryRef.name, queryRef.variables);
|
|
425
1141
|
const fetchTime = new Date().toString();
|
|
426
1142
|
const result = {
|
|
427
|
-
|
|
1143
|
+
data: res.data,
|
|
428
1144
|
source: SOURCE_SERVER,
|
|
429
1145
|
ref: queryRef,
|
|
430
1146
|
toJSON: getRefSerializer(queryRef, res.data, SOURCE_SERVER),
|
|
431
1147
|
fetchTime
|
|
432
1148
|
};
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
source: SOURCE_CACHE,
|
|
439
|
-
fetchTime
|
|
440
|
-
};
|
|
1149
|
+
if (await this.cache.containsResultTree(key)) {
|
|
1150
|
+
(await this.cache.getResultTree(key)).updateAccessed();
|
|
1151
|
+
}
|
|
1152
|
+
const impactedQueries = await this.cache.update(key, result.data);
|
|
1153
|
+
await this.publishCacheResultsToSubscribers(impactedQueries);
|
|
441
1154
|
return result;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
|
|
1155
|
+
}
|
|
1156
|
+
catch (err) {
|
|
1157
|
+
this.callbacks.get(key)?.forEach(subscription => {
|
|
445
1158
|
if (subscription.errCallback) {
|
|
446
1159
|
subscription.errCallback(err);
|
|
447
1160
|
}
|
|
448
1161
|
});
|
|
449
1162
|
throw err;
|
|
450
|
-
}
|
|
451
|
-
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
async publishCacheResultsToSubscribers(impactedQueries) {
|
|
1166
|
+
for (const query of impactedQueries) {
|
|
1167
|
+
const callbacks = this.callbacks.get(query);
|
|
1168
|
+
if (!callbacks) {
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
const newJson = (await this.cache.getResultTree(query))
|
|
1172
|
+
.getRootStub()
|
|
1173
|
+
.toJson();
|
|
1174
|
+
const { name, variables } = decoderImpl(query);
|
|
1175
|
+
const queryRef = {
|
|
1176
|
+
dataConnect: this.dc,
|
|
1177
|
+
refType: QUERY_STR,
|
|
1178
|
+
name,
|
|
1179
|
+
variables
|
|
1180
|
+
};
|
|
1181
|
+
callbacks.forEach(callback => {
|
|
1182
|
+
callback.userCallback({
|
|
1183
|
+
data: newJson,
|
|
1184
|
+
fetchTime: new Date().toISOString(),
|
|
1185
|
+
source: SOURCE_CACHE,
|
|
1186
|
+
ref: queryRef
|
|
1187
|
+
});
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
452
1190
|
}
|
|
453
1191
|
enableEmulator(host, port) {
|
|
454
1192
|
this.transport.useEmulator(host, port);
|
|
455
1193
|
}
|
|
456
1194
|
}
|
|
457
|
-
function compareDates(str1, str2) {
|
|
458
|
-
const date1 = new Date(str1);
|
|
459
|
-
const date2 = new Date(str2);
|
|
460
|
-
return date1.getTime() < date2.getTime();
|
|
461
|
-
}
|
|
462
1195
|
|
|
463
1196
|
/**
|
|
464
1197
|
* @license
|
|
@@ -501,11 +1234,12 @@ const CallerSdkTypeEnum = {
|
|
|
501
1234
|
* See the License for the specific language governing permissions and
|
|
502
1235
|
* limitations under the License.
|
|
503
1236
|
*/
|
|
1237
|
+
const PROD_HOST = 'firebasedataconnect.googleapis.com';
|
|
504
1238
|
function urlBuilder(projectConfig, transportOptions) {
|
|
505
1239
|
const { connector, location, projectId: project, service } = projectConfig;
|
|
506
1240
|
const { host, sslEnabled, port } = transportOptions;
|
|
507
1241
|
const protocol = sslEnabled ? 'https' : 'http';
|
|
508
|
-
const realHost = host ||
|
|
1242
|
+
const realHost = host || PROD_HOST;
|
|
509
1243
|
let baseUrl = `${protocol}://${realHost}`;
|
|
510
1244
|
if (typeof port === 'number') {
|
|
511
1245
|
baseUrl += `:${port}`;
|
|
@@ -585,7 +1319,7 @@ function dcFetch(url, body, { signal }, appId, accessToken, appCheckToken, _isUs
|
|
|
585
1319
|
throw new DataConnectError(Code.OTHER, 'Failed to fetch: ' + JSON.stringify(err));
|
|
586
1320
|
})
|
|
587
1321
|
.then(async (response) => {
|
|
588
|
-
let jsonResponse
|
|
1322
|
+
let jsonResponse;
|
|
589
1323
|
try {
|
|
590
1324
|
jsonResponse = await response.json();
|
|
591
1325
|
}
|
|
@@ -615,7 +1349,7 @@ function dcFetch(url, body, { signal }, appId, accessToken, appCheckToken, _isUs
|
|
|
615
1349
|
});
|
|
616
1350
|
}
|
|
617
1351
|
function getMessage(obj) {
|
|
618
|
-
if ('message' in obj) {
|
|
1352
|
+
if ('message' in obj && obj.message) {
|
|
619
1353
|
return obj.message;
|
|
620
1354
|
}
|
|
621
1355
|
return JSON.stringify(obj);
|
|
@@ -731,7 +1465,10 @@ class RESTTransport {
|
|
|
731
1465
|
async getWithAuth(forceToken = false) {
|
|
732
1466
|
let starterPromise = new Promise(resolve => resolve(this._accessToken));
|
|
733
1467
|
if (this.appCheckProvider) {
|
|
734
|
-
|
|
1468
|
+
const appCheckToken = await this.appCheckProvider.getToken();
|
|
1469
|
+
if (appCheckToken) {
|
|
1470
|
+
this._appCheckToken = appCheckToken.token;
|
|
1471
|
+
}
|
|
735
1472
|
}
|
|
736
1473
|
if (this.authProvider) {
|
|
737
1474
|
starterPromise = this.authProvider
|
|
@@ -934,20 +1671,27 @@ class DataConnect {
|
|
|
934
1671
|
if (this._authProvider) {
|
|
935
1672
|
this._authTokenProvider = new FirebaseAuthProvider(this.app.name, this.app.options, this._authProvider);
|
|
936
1673
|
}
|
|
1674
|
+
const connectorConfig = {
|
|
1675
|
+
connector: this.dataConnectOptions.connector,
|
|
1676
|
+
service: this.dataConnectOptions.service,
|
|
1677
|
+
location: this.dataConnectOptions.location
|
|
1678
|
+
};
|
|
1679
|
+
this.cache = new DataConnectCache(this._authTokenProvider, this.app.options.projectId, connectorConfig, this._transportOptions?.host || PROD_HOST, this.dataConnectOptions.cacheSettings);
|
|
937
1680
|
if (this._appCheckProvider) {
|
|
938
1681
|
this._appCheckTokenProvider = new AppCheckTokenProvider(this.app, this._appCheckProvider);
|
|
939
1682
|
}
|
|
940
|
-
this._initialized = true;
|
|
941
1683
|
this._transport = new this._transportClass(this.dataConnectOptions, this.app.options.apiKey, this.app.options.appId, this._authTokenProvider, this._appCheckTokenProvider, undefined, this._isUsingGeneratedSdk, this._callerSdkType);
|
|
942
1684
|
if (this._transportOptions) {
|
|
943
1685
|
this._transport.useEmulator(this._transportOptions.host, this._transportOptions.port, this._transportOptions.sslEnabled);
|
|
944
1686
|
}
|
|
945
|
-
this._queryManager = new QueryManager(this._transport);
|
|
1687
|
+
this._queryManager = new QueryManager(this._transport, this.cache, this);
|
|
946
1688
|
this._mutationManager = new MutationManager(this._transport);
|
|
1689
|
+
this._initialized = true;
|
|
947
1690
|
}
|
|
948
1691
|
// @internal
|
|
949
1692
|
enableEmulator(transportOptions) {
|
|
950
|
-
if (this.
|
|
1693
|
+
if (this._transportOptions &&
|
|
1694
|
+
this._initialized &&
|
|
951
1695
|
!areTransportOptionsEqual(this._transportOptions, transportOptions)) {
|
|
952
1696
|
logError('enableEmulator called after initialization');
|
|
953
1697
|
throw new DataConnectError(Code.ALREADY_INITIALIZED, 'DataConnect instance already initialized!');
|
|
@@ -982,21 +1726,31 @@ function connectDataConnectEmulator(dc, host, port, sslEnabled = false) {
|
|
|
982
1726
|
}
|
|
983
1727
|
dc.enableEmulator({ host, port, sslEnabled });
|
|
984
1728
|
}
|
|
985
|
-
function getDataConnect(
|
|
1729
|
+
function getDataConnect(appOrConnectorConfig, settingsOrConnectorConfig, settings // TODO: This doesn't serialize well because it has a function.
|
|
1730
|
+
) {
|
|
986
1731
|
let app$1;
|
|
987
|
-
let
|
|
988
|
-
|
|
989
|
-
|
|
1732
|
+
let connectorConfig;
|
|
1733
|
+
let realSettings;
|
|
1734
|
+
if ('location' in appOrConnectorConfig) {
|
|
1735
|
+
connectorConfig = appOrConnectorConfig;
|
|
990
1736
|
app$1 = app.getApp();
|
|
1737
|
+
realSettings = settingsOrConnectorConfig;
|
|
991
1738
|
}
|
|
992
1739
|
else {
|
|
993
|
-
|
|
994
|
-
|
|
1740
|
+
app$1 = appOrConnectorConfig;
|
|
1741
|
+
connectorConfig = settingsOrConnectorConfig;
|
|
1742
|
+
realSettings = settings;
|
|
995
1743
|
}
|
|
996
1744
|
if (!app$1 || Object.keys(app$1).length === 0) {
|
|
997
1745
|
app$1 = app.getApp();
|
|
998
1746
|
}
|
|
1747
|
+
const dcOptions = {
|
|
1748
|
+
...realSettings,
|
|
1749
|
+
...connectorConfig,
|
|
1750
|
+
projectId: app$1.options.projectId
|
|
1751
|
+
};
|
|
999
1752
|
const provider = app._getProvider(app$1, 'data-connect');
|
|
1753
|
+
// TODO: Deal with the parsing of these options properly.
|
|
1000
1754
|
const identifier = JSON.stringify(dcOptions);
|
|
1001
1755
|
if (provider.isInitialized(identifier)) {
|
|
1002
1756
|
const dcInstance = provider.getImmediate({ identifier });
|
|
@@ -1007,7 +1761,7 @@ function getDataConnect(appOrOptions, optionalOptions) {
|
|
|
1007
1761
|
return dcInstance;
|
|
1008
1762
|
}
|
|
1009
1763
|
}
|
|
1010
|
-
validateDCOptions(
|
|
1764
|
+
validateDCOptions(connectorConfig);
|
|
1011
1765
|
logDebug('Creating new DataConnect instance');
|
|
1012
1766
|
// Initialize with options.
|
|
1013
1767
|
return provider.initialize({
|
|
@@ -1042,6 +1796,12 @@ function terminate(dataConnect) {
|
|
|
1042
1796
|
return dataConnect._delete();
|
|
1043
1797
|
// TODO(mtewani): Stop pending tasks
|
|
1044
1798
|
}
|
|
1799
|
+
const StorageType = {
|
|
1800
|
+
MEMORY: 'MEMORY'
|
|
1801
|
+
};
|
|
1802
|
+
function makeMemoryCacheProvider() {
|
|
1803
|
+
return new MemoryStub();
|
|
1804
|
+
}
|
|
1045
1805
|
|
|
1046
1806
|
/**
|
|
1047
1807
|
* @license
|
|
@@ -1100,8 +1860,8 @@ function registerDataConnect(variant) {
|
|
|
1100
1860
|
* @param queryRef query to execute.
|
|
1101
1861
|
* @returns `QueryPromise`
|
|
1102
1862
|
*/
|
|
1103
|
-
function executeQuery(queryRef) {
|
|
1104
|
-
return queryRef.dataConnect._queryManager.executeQuery(queryRef);
|
|
1863
|
+
function executeQuery(queryRef, options) {
|
|
1864
|
+
return queryRef.dataConnect._queryManager.executeQuery(queryRef, options);
|
|
1105
1865
|
}
|
|
1106
1866
|
/**
|
|
1107
1867
|
* Execute Query
|
|
@@ -1113,12 +1873,14 @@ function executeQuery(queryRef) {
|
|
|
1113
1873
|
*/
|
|
1114
1874
|
function queryRef(dcInstance, queryName, variables, initialCache) {
|
|
1115
1875
|
dcInstance.setInitialized();
|
|
1116
|
-
|
|
1876
|
+
if (initialCache !== undefined) {
|
|
1877
|
+
dcInstance._queryManager.updateSSR(initialCache);
|
|
1878
|
+
}
|
|
1117
1879
|
return {
|
|
1118
1880
|
dataConnect: dcInstance,
|
|
1119
1881
|
refType: QUERY_STR,
|
|
1120
1882
|
name: queryName,
|
|
1121
|
-
variables
|
|
1883
|
+
variables: variables
|
|
1122
1884
|
};
|
|
1123
1885
|
}
|
|
1124
1886
|
/**
|
|
@@ -1176,7 +1938,7 @@ function validateArgs(connectorConfig, dcOrVars, vars, validateVars) {
|
|
|
1176
1938
|
|
|
1177
1939
|
/**
|
|
1178
1940
|
* @license
|
|
1179
|
-
* Copyright
|
|
1941
|
+
* Copyright 2025 Google LLC
|
|
1180
1942
|
*
|
|
1181
1943
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1182
1944
|
* you may not use this file except in compliance with the License.
|
|
@@ -1241,16 +2003,21 @@ exports.Code = Code;
|
|
|
1241
2003
|
exports.DataConnect = DataConnect;
|
|
1242
2004
|
exports.DataConnectError = DataConnectError;
|
|
1243
2005
|
exports.DataConnectOperationError = DataConnectOperationError;
|
|
2006
|
+
exports.IndexedDBStub = IndexedDBStub;
|
|
1244
2007
|
exports.MUTATION_STR = MUTATION_STR;
|
|
1245
2008
|
exports.MutationManager = MutationManager;
|
|
2009
|
+
exports.PersistentStub = MemoryStub;
|
|
1246
2010
|
exports.QUERY_STR = QUERY_STR;
|
|
2011
|
+
exports.QueryFetchPolicy = QueryFetchPolicy;
|
|
1247
2012
|
exports.SOURCE_CACHE = SOURCE_CACHE;
|
|
1248
2013
|
exports.SOURCE_SERVER = SOURCE_SERVER;
|
|
2014
|
+
exports.StorageType = StorageType;
|
|
1249
2015
|
exports.areTransportOptionsEqual = areTransportOptionsEqual;
|
|
1250
2016
|
exports.connectDataConnectEmulator = connectDataConnectEmulator;
|
|
1251
2017
|
exports.executeMutation = executeMutation;
|
|
1252
2018
|
exports.executeQuery = executeQuery;
|
|
1253
2019
|
exports.getDataConnect = getDataConnect;
|
|
2020
|
+
exports.makeMemoryCacheProvider = makeMemoryCacheProvider;
|
|
1254
2021
|
exports.mutationRef = mutationRef;
|
|
1255
2022
|
exports.parseOptions = parseOptions;
|
|
1256
2023
|
exports.queryRef = queryRef;
|