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