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