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