@depup/firebase__data-connect 0.4.0-depup.0
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/README.md +31 -0
- package/changes.json +10 -0
- package/dist/index.cjs.js +2069 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.esm.js +2039 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.node.cjs.js +2084 -0
- package/dist/index.node.cjs.js.map +1 -0
- package/dist/internal.d.ts +748 -0
- package/dist/node-esm/index.node.esm.js +2054 -0
- package/dist/node-esm/index.node.esm.js.map +1 -0
- package/dist/node-esm/package.json +1 -0
- package/dist/node-esm/src/api/DataConnect.d.ts +159 -0
- package/dist/node-esm/src/api/Mutation.d.ts +61 -0
- package/dist/node-esm/src/api/Reference.d.ts +53 -0
- package/dist/node-esm/src/api/index.d.ts +25 -0
- package/dist/node-esm/src/api/query.d.ts +71 -0
- package/dist/node-esm/src/api.browser.d.ts +18 -0
- package/dist/node-esm/src/api.node.d.ts +18 -0
- 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/AppCheckTokenProvider.d.ts +31 -0
- package/dist/node-esm/src/core/FirebaseAuthProvider.d.ts +36 -0
- package/dist/node-esm/src/core/error.d.ts +53 -0
- 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/core/version.d.ts +23 -0
- package/dist/node-esm/src/index.d.ts +29 -0
- package/dist/node-esm/src/index.node.d.ts +18 -0
- package/dist/node-esm/src/logger.d.ts +20 -0
- package/dist/node-esm/src/network/fetch.d.ts +24 -0
- package/dist/node-esm/src/network/index.d.ts +17 -0
- package/dist/node-esm/src/network/transport/index.d.ts +81 -0
- package/dist/node-esm/src/network/transport/rest.d.ts +49 -0
- package/dist/node-esm/src/register.d.ts +1 -0
- package/dist/node-esm/src/util/encoder.d.ts +22 -0
- package/dist/node-esm/src/util/map.d.ts +17 -0
- package/dist/node-esm/src/util/url.d.ts +20 -0
- package/dist/node-esm/src/util/validateArgs.d.ts +33 -0
- package/dist/private.d.ts +655 -0
- package/dist/public.d.ts +350 -0
- package/dist/src/api/DataConnect.d.ts +159 -0
- package/dist/src/api/Mutation.d.ts +61 -0
- package/dist/src/api/Reference.d.ts +53 -0
- package/dist/src/api/index.d.ts +25 -0
- package/dist/src/api/query.d.ts +71 -0
- package/dist/src/api.browser.d.ts +18 -0
- package/dist/src/api.node.d.ts +18 -0
- 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/AppCheckTokenProvider.d.ts +31 -0
- package/dist/src/core/FirebaseAuthProvider.d.ts +36 -0
- package/dist/src/core/error.d.ts +53 -0
- 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/core/version.d.ts +23 -0
- package/dist/src/index.d.ts +29 -0
- package/dist/src/index.node.d.ts +18 -0
- package/dist/src/logger.d.ts +20 -0
- package/dist/src/network/fetch.d.ts +24 -0
- package/dist/src/network/index.d.ts +17 -0
- package/dist/src/network/transport/index.d.ts +81 -0
- package/dist/src/network/transport/rest.d.ts +49 -0
- package/dist/src/register.d.ts +1 -0
- package/dist/src/tsdoc-metadata.json +11 -0
- package/dist/src/util/encoder.d.ts +22 -0
- package/dist/src/util/map.d.ts +17 -0
- package/dist/src/util/url.d.ts +20 -0
- package/dist/src/util/validateArgs.d.ts +33 -0
- package/package.json +99 -0
|
@@ -0,0 +1,2054 @@
|
|
|
1
|
+
import { FirebaseError, isCloudWorkstation, generateSHA256Hash, pingServer, updateEmulatorBanner } from '@firebase/util';
|
|
2
|
+
import { Logger } from '@firebase/logger';
|
|
3
|
+
import { _isFirebaseServerApp, _removeServiceInstance, getApp, _getProvider, _registerComponent, registerVersion, SDK_VERSION as SDK_VERSION$1 } from '@firebase/app';
|
|
4
|
+
import { Component } from '@firebase/component';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @license
|
|
8
|
+
* Copyright 2024 Google LLC
|
|
9
|
+
*
|
|
10
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
11
|
+
* you may not use this file except in compliance with the License.
|
|
12
|
+
* You may obtain a copy of the License at
|
|
13
|
+
*
|
|
14
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
15
|
+
*
|
|
16
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
* See the License for the specific language governing permissions and
|
|
20
|
+
* limitations under the License.
|
|
21
|
+
*/
|
|
22
|
+
const Code = {
|
|
23
|
+
OTHER: 'other',
|
|
24
|
+
ALREADY_INITIALIZED: 'already-initialized',
|
|
25
|
+
NOT_INITIALIZED: 'not-initialized',
|
|
26
|
+
NOT_SUPPORTED: 'not-supported',
|
|
27
|
+
INVALID_ARGUMENT: 'invalid-argument',
|
|
28
|
+
PARTIAL_ERROR: 'partial-error',
|
|
29
|
+
UNAUTHORIZED: 'unauthorized'
|
|
30
|
+
};
|
|
31
|
+
/** An error returned by a DataConnect operation. */
|
|
32
|
+
class DataConnectError extends FirebaseError {
|
|
33
|
+
constructor(code, message) {
|
|
34
|
+
super(code, message);
|
|
35
|
+
/** @internal */
|
|
36
|
+
this.name = 'DataConnectError';
|
|
37
|
+
// Ensure the instanceof operator works as expected on subclasses of Error.
|
|
38
|
+
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#custom_error_types
|
|
39
|
+
// and https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
|
|
40
|
+
Object.setPrototypeOf(this, DataConnectError.prototype);
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
toString() {
|
|
44
|
+
return `${this.name}[code=${this.code}]: ${this.message}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** An error returned by a DataConnect operation. */
|
|
48
|
+
class DataConnectOperationError extends DataConnectError {
|
|
49
|
+
/** @hideconstructor */
|
|
50
|
+
constructor(message, response) {
|
|
51
|
+
super(Code.PARTIAL_ERROR, message);
|
|
52
|
+
/** @internal */
|
|
53
|
+
this.name = 'DataConnectOperationError';
|
|
54
|
+
this.response = response;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @license
|
|
60
|
+
* Copyright 2024 Google LLC
|
|
61
|
+
*
|
|
62
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
63
|
+
* you may not use this file except in compliance with the License.
|
|
64
|
+
* You may obtain a copy of the License at
|
|
65
|
+
*
|
|
66
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
67
|
+
*
|
|
68
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
69
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
70
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
71
|
+
* See the License for the specific language governing permissions and
|
|
72
|
+
* limitations under the License.
|
|
73
|
+
*/
|
|
74
|
+
/** The semver (www.semver.org) version of the SDK. */
|
|
75
|
+
let SDK_VERSION = '';
|
|
76
|
+
/**
|
|
77
|
+
* SDK_VERSION should be set before any database instance is created
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
function setSDKVersion(version) {
|
|
81
|
+
SDK_VERSION = version;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @license
|
|
86
|
+
* Copyright 2024 Google LLC
|
|
87
|
+
*
|
|
88
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
89
|
+
* you may not use this file except in compliance with the License.
|
|
90
|
+
* You may obtain a copy of the License at
|
|
91
|
+
*
|
|
92
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
93
|
+
*
|
|
94
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
95
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
96
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
97
|
+
* See the License for the specific language governing permissions and
|
|
98
|
+
* limitations under the License.
|
|
99
|
+
*/
|
|
100
|
+
const logger = new Logger('@firebase/data-connect');
|
|
101
|
+
function setLogLevel(logLevel) {
|
|
102
|
+
logger.setLogLevel(logLevel);
|
|
103
|
+
}
|
|
104
|
+
function logDebug(msg) {
|
|
105
|
+
logger.debug(`DataConnect (${SDK_VERSION}): ${msg}`);
|
|
106
|
+
}
|
|
107
|
+
function logError(msg) {
|
|
108
|
+
logger.error(`DataConnect (${SDK_VERSION}): ${msg}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @license
|
|
113
|
+
* Copyright 2024 Google LLC
|
|
114
|
+
*
|
|
115
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
116
|
+
* you may not use this file except in compliance with the License.
|
|
117
|
+
* You may obtain a copy of the License at
|
|
118
|
+
*
|
|
119
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
120
|
+
*
|
|
121
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
122
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
123
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
124
|
+
* See the License for the specific language governing permissions and
|
|
125
|
+
* limitations under the License.
|
|
126
|
+
*/
|
|
127
|
+
const CallerSdkTypeEnum = {
|
|
128
|
+
Base: 'Base', // Core JS SDK
|
|
129
|
+
Generated: 'Generated', // Generated JS SDK
|
|
130
|
+
TanstackReactCore: 'TanstackReactCore', // Tanstack non-generated React SDK
|
|
131
|
+
GeneratedReact: 'GeneratedReact', // Tanstack non-generated Angular SDK
|
|
132
|
+
TanstackAngularCore: 'TanstackAngularCore', // Tanstack non-generated Angular SDK
|
|
133
|
+
GeneratedAngular: 'GeneratedAngular' // Generated Angular SDK
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @license
|
|
138
|
+
* Copyright 2024 Google LLC
|
|
139
|
+
*
|
|
140
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
141
|
+
* you may not use this file except in compliance with the License.
|
|
142
|
+
* You may obtain a copy of the License at
|
|
143
|
+
*
|
|
144
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
145
|
+
*
|
|
146
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
147
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
148
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
149
|
+
* See the License for the specific language governing permissions and
|
|
150
|
+
* limitations under the License.
|
|
151
|
+
*/
|
|
152
|
+
let connectFetch = globalThis.fetch;
|
|
153
|
+
function initializeFetch(fetchImpl) {
|
|
154
|
+
connectFetch = fetchImpl;
|
|
155
|
+
}
|
|
156
|
+
function getGoogApiClientValue(_isUsingGen, _callerSdkType) {
|
|
157
|
+
let str = 'gl-js/ fire/' + SDK_VERSION;
|
|
158
|
+
if (_callerSdkType !== CallerSdkTypeEnum.Base &&
|
|
159
|
+
_callerSdkType !== CallerSdkTypeEnum.Generated) {
|
|
160
|
+
str += ' js/' + _callerSdkType.toLowerCase();
|
|
161
|
+
}
|
|
162
|
+
else if (_isUsingGen || _callerSdkType === CallerSdkTypeEnum.Generated) {
|
|
163
|
+
str += ' js/gen';
|
|
164
|
+
}
|
|
165
|
+
return str;
|
|
166
|
+
}
|
|
167
|
+
async function dcFetch(url, body, { signal }, appId, accessToken, appCheckToken, _isUsingGen, _callerSdkType, _isUsingEmulator) {
|
|
168
|
+
if (!connectFetch) {
|
|
169
|
+
throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!');
|
|
170
|
+
}
|
|
171
|
+
const headers = {
|
|
172
|
+
'Content-Type': 'application/json',
|
|
173
|
+
'X-Goog-Api-Client': getGoogApiClientValue(_isUsingGen, _callerSdkType)
|
|
174
|
+
};
|
|
175
|
+
if (accessToken) {
|
|
176
|
+
headers['X-Firebase-Auth-Token'] = accessToken;
|
|
177
|
+
}
|
|
178
|
+
if (appId) {
|
|
179
|
+
headers['x-firebase-gmpid'] = appId;
|
|
180
|
+
}
|
|
181
|
+
if (appCheckToken) {
|
|
182
|
+
headers['X-Firebase-AppCheck'] = appCheckToken;
|
|
183
|
+
}
|
|
184
|
+
const bodyStr = JSON.stringify(body);
|
|
185
|
+
const fetchOptions = {
|
|
186
|
+
body: bodyStr,
|
|
187
|
+
method: 'POST',
|
|
188
|
+
headers,
|
|
189
|
+
signal
|
|
190
|
+
};
|
|
191
|
+
if (isCloudWorkstation(url) && _isUsingEmulator) {
|
|
192
|
+
fetchOptions.credentials = 'include';
|
|
193
|
+
}
|
|
194
|
+
let response;
|
|
195
|
+
try {
|
|
196
|
+
response = await connectFetch(url, fetchOptions);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
throw new DataConnectError(Code.OTHER, 'Failed to fetch: ' + JSON.stringify(err));
|
|
200
|
+
}
|
|
201
|
+
let jsonResponse;
|
|
202
|
+
try {
|
|
203
|
+
jsonResponse = await response.json();
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
throw new DataConnectError(Code.OTHER, JSON.stringify(e));
|
|
207
|
+
}
|
|
208
|
+
const message = getErrorMessage(jsonResponse);
|
|
209
|
+
if (response.status >= 400) {
|
|
210
|
+
logError('Error while performing request: ' + JSON.stringify(jsonResponse));
|
|
211
|
+
if (response.status === 401) {
|
|
212
|
+
throw new DataConnectError(Code.UNAUTHORIZED, message);
|
|
213
|
+
}
|
|
214
|
+
throw new DataConnectError(Code.OTHER, message);
|
|
215
|
+
}
|
|
216
|
+
if (jsonResponse.errors && jsonResponse.errors.length) {
|
|
217
|
+
const stringified = JSON.stringify(jsonResponse.errors);
|
|
218
|
+
const failureResponse = {
|
|
219
|
+
errors: jsonResponse.errors,
|
|
220
|
+
data: jsonResponse.data
|
|
221
|
+
};
|
|
222
|
+
throw new DataConnectOperationError('DataConnect error while performing request: ' + stringified, failureResponse);
|
|
223
|
+
}
|
|
224
|
+
if (!jsonResponse.extensions) {
|
|
225
|
+
jsonResponse.extensions = {
|
|
226
|
+
dataConnect: []
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return jsonResponse;
|
|
230
|
+
}
|
|
231
|
+
function getErrorMessage(obj) {
|
|
232
|
+
if ('message' in obj && obj.message) {
|
|
233
|
+
return obj.message;
|
|
234
|
+
}
|
|
235
|
+
return JSON.stringify(obj);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const name = "@firebase/data-connect";
|
|
239
|
+
const version = "0.4.0";
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @license
|
|
243
|
+
* Copyright 2025 Google LLC
|
|
244
|
+
*
|
|
245
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
246
|
+
* you may not use this file except in compliance with the License.
|
|
247
|
+
* You may obtain a copy of the License at
|
|
248
|
+
*
|
|
249
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
250
|
+
*
|
|
251
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
252
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
253
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
254
|
+
* See the License for the specific language governing permissions and
|
|
255
|
+
* limitations under the License.
|
|
256
|
+
*/
|
|
257
|
+
class EntityDataObject {
|
|
258
|
+
getServerValue(key) {
|
|
259
|
+
return this.serverValues[key];
|
|
260
|
+
}
|
|
261
|
+
constructor(globalID) {
|
|
262
|
+
this.globalID = globalID;
|
|
263
|
+
this.serverValues = {};
|
|
264
|
+
this.referencedFrom = new Set();
|
|
265
|
+
}
|
|
266
|
+
getServerValues() {
|
|
267
|
+
return this.serverValues;
|
|
268
|
+
}
|
|
269
|
+
toJSON() {
|
|
270
|
+
return {
|
|
271
|
+
globalID: this.globalID,
|
|
272
|
+
map: this.serverValues,
|
|
273
|
+
referencedFrom: Array.from(this.referencedFrom)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
static fromJSON(json) {
|
|
277
|
+
const edo = new EntityDataObject(json.globalID);
|
|
278
|
+
edo.serverValues = json.map;
|
|
279
|
+
edo.referencedFrom = new Set(json.referencedFrom);
|
|
280
|
+
return edo;
|
|
281
|
+
}
|
|
282
|
+
updateServerValue(key, value, requestedFrom) {
|
|
283
|
+
this.serverValues[key] = value;
|
|
284
|
+
this.referencedFrom.add(requestedFrom);
|
|
285
|
+
return Array.from(this.referencedFrom);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @license
|
|
291
|
+
* Copyright 2025 Google LLC
|
|
292
|
+
*
|
|
293
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
294
|
+
* you may not use this file except in compliance with the License.
|
|
295
|
+
* You may obtain a copy of the License at
|
|
296
|
+
*
|
|
297
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
298
|
+
*
|
|
299
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
300
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
301
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
302
|
+
* See the License for the specific language governing permissions and
|
|
303
|
+
* limitations under the License.
|
|
304
|
+
*/
|
|
305
|
+
class InMemoryCacheProvider {
|
|
306
|
+
constructor(_keyId) {
|
|
307
|
+
this._keyId = _keyId;
|
|
308
|
+
this.edos = new Map();
|
|
309
|
+
this.resultTrees = new Map();
|
|
310
|
+
}
|
|
311
|
+
async setResultTree(queryId, rt) {
|
|
312
|
+
this.resultTrees.set(queryId, rt);
|
|
313
|
+
}
|
|
314
|
+
async getResultTree(queryId) {
|
|
315
|
+
return this.resultTrees.get(queryId);
|
|
316
|
+
}
|
|
317
|
+
async updateEntityData(entityData) {
|
|
318
|
+
this.edos.set(entityData.globalID, entityData);
|
|
319
|
+
}
|
|
320
|
+
async getEntityData(globalId) {
|
|
321
|
+
if (!this.edos.has(globalId)) {
|
|
322
|
+
this.edos.set(globalId, new EntityDataObject(globalId));
|
|
323
|
+
}
|
|
324
|
+
// Because of the above, we can guarantee that there will be an EDO at the globalId.
|
|
325
|
+
return this.edos.get(globalId);
|
|
326
|
+
}
|
|
327
|
+
close() {
|
|
328
|
+
// No-op
|
|
329
|
+
return Promise.resolve();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @license
|
|
335
|
+
* Copyright 2025 Google LLC
|
|
336
|
+
*
|
|
337
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
338
|
+
* you may not use this file except in compliance with the License.
|
|
339
|
+
* You may obtain a copy of the License at
|
|
340
|
+
*
|
|
341
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
342
|
+
*
|
|
343
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
344
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
345
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
346
|
+
* See the License for the specific language governing permissions and
|
|
347
|
+
* limitations under the License.
|
|
348
|
+
*/
|
|
349
|
+
const GLOBAL_ID_KEY = '_id';
|
|
350
|
+
const OBJECT_LISTS_KEY = '_objectLists';
|
|
351
|
+
const REFERENCES_KEY = '_references';
|
|
352
|
+
const SCALARS_KEY = '_scalars';
|
|
353
|
+
const ENTITY_DATA_KEYS_KEY = '_entity_data_keys';
|
|
354
|
+
class EntityNode {
|
|
355
|
+
constructor() {
|
|
356
|
+
this.scalars = {};
|
|
357
|
+
this.references = {};
|
|
358
|
+
this.objectLists = {};
|
|
359
|
+
this.entityDataKeys = new Set();
|
|
360
|
+
}
|
|
361
|
+
async loadData(queryId, values, entityIds, acc, cacheProvider) {
|
|
362
|
+
if (values === undefined) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (typeof values !== 'object' || Array.isArray(values)) {
|
|
366
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, 'EntityNode initialized with non-object value');
|
|
367
|
+
}
|
|
368
|
+
if (values === null) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (typeof values === 'object' &&
|
|
372
|
+
entityIds &&
|
|
373
|
+
entityIds[GLOBAL_ID_KEY] &&
|
|
374
|
+
typeof entityIds[GLOBAL_ID_KEY] === 'string') {
|
|
375
|
+
this.globalId = entityIds[GLOBAL_ID_KEY];
|
|
376
|
+
this.entityData = await cacheProvider.getEntityData(this.globalId);
|
|
377
|
+
}
|
|
378
|
+
for (const key in values) {
|
|
379
|
+
if (values.hasOwnProperty(key)) {
|
|
380
|
+
if (typeof values[key] === 'object') {
|
|
381
|
+
if (Array.isArray(values[key])) {
|
|
382
|
+
const ids = entityIds && entityIds[key];
|
|
383
|
+
const objArray = [];
|
|
384
|
+
const scalarArray = [];
|
|
385
|
+
for (const [index, value] of values[key].entries()) {
|
|
386
|
+
if (typeof value === 'object') {
|
|
387
|
+
if (Array.isArray(value)) ;
|
|
388
|
+
else {
|
|
389
|
+
const entityNode = new EntityNode();
|
|
390
|
+
await entityNode.loadData(queryId, value, ids && ids[index], acc, cacheProvider);
|
|
391
|
+
objArray.push(entityNode);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
scalarArray.push(value);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (scalarArray.length > 0 && objArray.length > 0) {
|
|
399
|
+
this.scalars[key] = values[key];
|
|
400
|
+
}
|
|
401
|
+
else if (scalarArray.length > 0) {
|
|
402
|
+
if (this.entityData) {
|
|
403
|
+
const impactedRefs = this.entityData.updateServerValue(key, scalarArray, queryId);
|
|
404
|
+
this.entityDataKeys.add(key);
|
|
405
|
+
acc.add(impactedRefs);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
this.scalars[key] = scalarArray;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else if (objArray.length > 0) {
|
|
412
|
+
this.objectLists[key] = objArray;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this.scalars[key] = [];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
if (values[key] === null) {
|
|
420
|
+
this.scalars[key] = null;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const entityNode = new EntityNode();
|
|
424
|
+
// TODO: Load Data might need to be pushed into ResultTreeProcessor instead.
|
|
425
|
+
await entityNode.loadData(queryId, values[key], entityIds && entityIds[key], acc, cacheProvider);
|
|
426
|
+
this.references[key] = entityNode;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
if (this.entityData) {
|
|
431
|
+
const impactedRefs = this.entityData.updateServerValue(key, values[key], queryId);
|
|
432
|
+
this.entityDataKeys.add(key);
|
|
433
|
+
acc.add(impactedRefs);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
this.scalars[key] = values[key];
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (this.entityData) {
|
|
442
|
+
await cacheProvider.updateEntityData(this.entityData);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
toJSON(mode) {
|
|
446
|
+
const resultObject = {};
|
|
447
|
+
if (mode === EncodingMode.hydrated) {
|
|
448
|
+
if (this.entityData) {
|
|
449
|
+
for (const key of this.entityDataKeys) {
|
|
450
|
+
resultObject[key] = this.entityData.getServerValue(key);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (this.scalars) {
|
|
454
|
+
Object.assign(resultObject, this.scalars);
|
|
455
|
+
}
|
|
456
|
+
if (this.references) {
|
|
457
|
+
for (const key in this.references) {
|
|
458
|
+
if (this.references.hasOwnProperty(key)) {
|
|
459
|
+
resultObject[key] = this.references[key].toJSON(mode);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (this.objectLists) {
|
|
464
|
+
for (const key in this.objectLists) {
|
|
465
|
+
if (this.objectLists.hasOwnProperty(key)) {
|
|
466
|
+
resultObject[key] = this.objectLists[key].map(obj => obj.toJSON(mode));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return resultObject;
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
// Get JSON representation of dehydrated list
|
|
474
|
+
if (this.entityData) {
|
|
475
|
+
resultObject[GLOBAL_ID_KEY] = this.entityData.globalID;
|
|
476
|
+
}
|
|
477
|
+
resultObject[ENTITY_DATA_KEYS_KEY] = Array.from(this.entityDataKeys);
|
|
478
|
+
if (this.scalars) {
|
|
479
|
+
resultObject[SCALARS_KEY] = this.scalars;
|
|
480
|
+
}
|
|
481
|
+
if (this.references) {
|
|
482
|
+
const references = {};
|
|
483
|
+
for (const key in this.references) {
|
|
484
|
+
if (this.references.hasOwnProperty(key)) {
|
|
485
|
+
references[key] = this.references[key].toJSON(mode);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
resultObject[REFERENCES_KEY] = references;
|
|
489
|
+
}
|
|
490
|
+
if (this.objectLists) {
|
|
491
|
+
const objectLists = {};
|
|
492
|
+
for (const key in this.objectLists) {
|
|
493
|
+
if (this.objectLists.hasOwnProperty(key)) {
|
|
494
|
+
objectLists[key] = this.objectLists[key].map(obj => obj.toJSON(mode));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
resultObject[OBJECT_LISTS_KEY] = objectLists;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return resultObject;
|
|
501
|
+
}
|
|
502
|
+
static fromJson(obj) {
|
|
503
|
+
const sdo = new EntityNode();
|
|
504
|
+
if (obj.backingData) {
|
|
505
|
+
sdo.entityData = EntityDataObject.fromJSON(obj.backingData);
|
|
506
|
+
}
|
|
507
|
+
sdo.globalId = obj.globalID;
|
|
508
|
+
sdo.scalars = obj.scalars;
|
|
509
|
+
if (obj.references) {
|
|
510
|
+
const references = {};
|
|
511
|
+
for (const key in obj.references) {
|
|
512
|
+
if (obj.references.hasOwnProperty(key)) {
|
|
513
|
+
references[key] = EntityNode.fromJson(obj.references[key]);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
sdo.references = references;
|
|
517
|
+
}
|
|
518
|
+
if (obj.objectLists) {
|
|
519
|
+
const objectLists = {};
|
|
520
|
+
for (const key in obj.objectLists) {
|
|
521
|
+
if (obj.objectLists.hasOwnProperty(key)) {
|
|
522
|
+
objectLists[key] = obj.objectLists[key].map(obj => EntityNode.fromJson(obj));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
sdo.objectLists = objectLists;
|
|
526
|
+
}
|
|
527
|
+
return sdo;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
// Helpful for storing in persistent cache, which is not available yet.
|
|
531
|
+
var EncodingMode;
|
|
532
|
+
(function (EncodingMode) {
|
|
533
|
+
EncodingMode[EncodingMode["hydrated"] = 0] = "hydrated";
|
|
534
|
+
EncodingMode[EncodingMode["dehydrated"] = 1] = "dehydrated";
|
|
535
|
+
})(EncodingMode || (EncodingMode = {}));
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* @license
|
|
539
|
+
* Copyright 2025 Google LLC
|
|
540
|
+
*
|
|
541
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
542
|
+
* you may not use this file except in compliance with the License.
|
|
543
|
+
* You may obtain a copy of the License at
|
|
544
|
+
*
|
|
545
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
546
|
+
*
|
|
547
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
548
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
549
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
550
|
+
* See the License for the specific language governing permissions and
|
|
551
|
+
* limitations under the License.
|
|
552
|
+
*/
|
|
553
|
+
class ResultTree {
|
|
554
|
+
/**
|
|
555
|
+
* Create a {@link ResultTree} from a dehydrated JSON object.
|
|
556
|
+
* @param value The dehydrated JSON object.
|
|
557
|
+
* @returns The {@link ResultTree}.
|
|
558
|
+
*/
|
|
559
|
+
static fromJson(value) {
|
|
560
|
+
return new ResultTree(EntityNode.fromJson(value.rootStub), value.maxAge, value.cachedAt, value.lastAccessed);
|
|
561
|
+
}
|
|
562
|
+
constructor(rootStub, maxAge = 0, cachedAt, _lastAccessed) {
|
|
563
|
+
this.rootStub = rootStub;
|
|
564
|
+
this.maxAge = maxAge;
|
|
565
|
+
this.cachedAt = cachedAt;
|
|
566
|
+
this._lastAccessed = _lastAccessed;
|
|
567
|
+
}
|
|
568
|
+
isStale() {
|
|
569
|
+
return (Date.now() - new Date(this.cachedAt.getTime()).getTime() >
|
|
570
|
+
this.maxAge * 1000);
|
|
571
|
+
}
|
|
572
|
+
updateMaxAge(maxAgeInSeconds) {
|
|
573
|
+
this.maxAge = maxAgeInSeconds;
|
|
574
|
+
}
|
|
575
|
+
updateAccessed() {
|
|
576
|
+
this._lastAccessed = new Date();
|
|
577
|
+
}
|
|
578
|
+
get lastAccessed() {
|
|
579
|
+
return this._lastAccessed;
|
|
580
|
+
}
|
|
581
|
+
getRootStub() {
|
|
582
|
+
return this.rootStub;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* @license
|
|
588
|
+
* Copyright 2025 Google LLC
|
|
589
|
+
*
|
|
590
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
591
|
+
* you may not use this file except in compliance with the License.
|
|
592
|
+
* You may obtain a copy of the License at
|
|
593
|
+
*
|
|
594
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
595
|
+
*
|
|
596
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
597
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
598
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
599
|
+
* See the License for the specific language governing permissions and
|
|
600
|
+
* limitations under the License.
|
|
601
|
+
*/
|
|
602
|
+
class ImpactedQueryRefsAccumulator {
|
|
603
|
+
constructor(queryId) {
|
|
604
|
+
this.queryId = queryId;
|
|
605
|
+
this.impacted = new Set();
|
|
606
|
+
}
|
|
607
|
+
add(impacted) {
|
|
608
|
+
impacted
|
|
609
|
+
.filter(ref => ref !== this.queryId)
|
|
610
|
+
.forEach(ref => this.impacted.add(ref));
|
|
611
|
+
}
|
|
612
|
+
consumeEvents() {
|
|
613
|
+
const events = Array.from(this.impacted);
|
|
614
|
+
this.impacted.clear();
|
|
615
|
+
return events;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* @license
|
|
621
|
+
* Copyright 2025 Google LLC
|
|
622
|
+
*
|
|
623
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
624
|
+
* you may not use this file except in compliance with the License.
|
|
625
|
+
* You may obtain a copy of the License at
|
|
626
|
+
*
|
|
627
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
628
|
+
*
|
|
629
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
630
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
631
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
632
|
+
* See the License for the specific language governing permissions and
|
|
633
|
+
* limitations under the License.
|
|
634
|
+
*/
|
|
635
|
+
class ResultTreeProcessor {
|
|
636
|
+
/**
|
|
637
|
+
* Hydrate the EntityNode into a JSON object so that it can be returned to the user.
|
|
638
|
+
* @param rootStubObject
|
|
639
|
+
* @returns {string}
|
|
640
|
+
*/
|
|
641
|
+
hydrateResults(rootStubObject) {
|
|
642
|
+
return rootStubObject.toJSON(EncodingMode.hydrated);
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Dehydrate results so that they can be stored in the cache.
|
|
646
|
+
* @param json
|
|
647
|
+
* @param entityIds
|
|
648
|
+
* @param cacheProvider
|
|
649
|
+
* @param queryId
|
|
650
|
+
* @returns {Promise<DehydratedResults>}
|
|
651
|
+
*/
|
|
652
|
+
async dehydrateResults(json, entityIds, cacheProvider, queryId) {
|
|
653
|
+
const acc = new ImpactedQueryRefsAccumulator(queryId);
|
|
654
|
+
const entityNode = new EntityNode();
|
|
655
|
+
await entityNode.loadData(queryId, json, entityIds, acc, cacheProvider);
|
|
656
|
+
return {
|
|
657
|
+
entityNode,
|
|
658
|
+
impacted: acc.consumeEvents()
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* @license
|
|
665
|
+
* Copyright 2025 Google LLC
|
|
666
|
+
*
|
|
667
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
668
|
+
* you may not use this file except in compliance with the License.
|
|
669
|
+
* You may obtain a copy of the License at
|
|
670
|
+
*
|
|
671
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
672
|
+
*
|
|
673
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
674
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
675
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
676
|
+
* See the License for the specific language governing permissions and
|
|
677
|
+
* limitations under the License.
|
|
678
|
+
*/
|
|
679
|
+
class DataConnectCache {
|
|
680
|
+
constructor(authProvider, projectId, connectorConfig, host, cacheSettings) {
|
|
681
|
+
this.authProvider = authProvider;
|
|
682
|
+
this.projectId = projectId;
|
|
683
|
+
this.connectorConfig = connectorConfig;
|
|
684
|
+
this.host = host;
|
|
685
|
+
this.cacheSettings = cacheSettings;
|
|
686
|
+
this.cacheProvider = null;
|
|
687
|
+
this.uid = null;
|
|
688
|
+
this.authProvider.addTokenChangeListener(async (_) => {
|
|
689
|
+
const newUid = this.authProvider.getAuth().getUid();
|
|
690
|
+
// We should only close if the token changes and so does the new UID
|
|
691
|
+
if (this.uid !== newUid) {
|
|
692
|
+
this.cacheProvider?.close();
|
|
693
|
+
this.uid = newUid;
|
|
694
|
+
const identifier = await this.getIdentifier(this.uid);
|
|
695
|
+
this.cacheProvider = this.initializeNewProviders(identifier);
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
async initialize() {
|
|
700
|
+
if (!this.cacheProvider) {
|
|
701
|
+
const identifier = await this.getIdentifier(this.uid);
|
|
702
|
+
this.cacheProvider = this.initializeNewProviders(identifier);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
async getIdentifier(uid) {
|
|
706
|
+
const identifier = `${'memory' // TODO: replace this with indexeddb when persistence is available.
|
|
707
|
+
}-${this.projectId}-${this.connectorConfig.service}-${this.connectorConfig.connector}-${this.connectorConfig.location}-${uid}-${this.host}`;
|
|
708
|
+
const sha256 = await generateSHA256Hash(identifier);
|
|
709
|
+
return sha256;
|
|
710
|
+
}
|
|
711
|
+
initializeNewProviders(identifier) {
|
|
712
|
+
return this.cacheSettings.cacheProvider.initialize(identifier);
|
|
713
|
+
}
|
|
714
|
+
async containsResultTree(queryId) {
|
|
715
|
+
await this.initialize();
|
|
716
|
+
const resultTree = await this.cacheProvider.getResultTree(queryId);
|
|
717
|
+
return resultTree !== undefined;
|
|
718
|
+
}
|
|
719
|
+
async getResultTree(queryId) {
|
|
720
|
+
await this.initialize();
|
|
721
|
+
return this.cacheProvider.getResultTree(queryId);
|
|
722
|
+
}
|
|
723
|
+
async getResultJSON(queryId) {
|
|
724
|
+
await this.initialize();
|
|
725
|
+
const processor = new ResultTreeProcessor();
|
|
726
|
+
const cacheProvider = this.cacheProvider;
|
|
727
|
+
const resultTree = await cacheProvider.getResultTree(queryId);
|
|
728
|
+
if (!resultTree) {
|
|
729
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, `${queryId} not found in cache. Call "update()" first.`);
|
|
730
|
+
}
|
|
731
|
+
return processor.hydrateResults(resultTree.getRootStub());
|
|
732
|
+
}
|
|
733
|
+
async update(queryId, serverValues, entityIds) {
|
|
734
|
+
await this.initialize();
|
|
735
|
+
const processor = new ResultTreeProcessor();
|
|
736
|
+
const cacheProvider = this.cacheProvider;
|
|
737
|
+
const { entityNode: stubDataObject, impacted } = await processor.dehydrateResults(serverValues, entityIds, cacheProvider, queryId);
|
|
738
|
+
const now = new Date();
|
|
739
|
+
await cacheProvider.setResultTree(queryId, new ResultTree(stubDataObject, serverValues.maxAge || this.cacheSettings.maxAgeSeconds, now, now));
|
|
740
|
+
return impacted;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
class MemoryStub {
|
|
744
|
+
constructor() {
|
|
745
|
+
this.type = 'MEMORY';
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* @internal
|
|
749
|
+
*/
|
|
750
|
+
initialize(cacheId) {
|
|
751
|
+
return new InMemoryCacheProvider(cacheId);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* @license
|
|
757
|
+
* Copyright 2024 Google LLC
|
|
758
|
+
*
|
|
759
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
760
|
+
* you may not use this file except in compliance with the License.
|
|
761
|
+
* You may obtain a copy of the License at
|
|
762
|
+
*
|
|
763
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
764
|
+
*
|
|
765
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
766
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
767
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
768
|
+
* See the License for the specific language governing permissions and
|
|
769
|
+
* limitations under the License.
|
|
770
|
+
*/
|
|
771
|
+
/**
|
|
772
|
+
* @internal
|
|
773
|
+
* Abstraction around AppCheck's token fetching capabilities.
|
|
774
|
+
*/
|
|
775
|
+
class AppCheckTokenProvider {
|
|
776
|
+
constructor(app, appCheckProvider) {
|
|
777
|
+
this.appCheckProvider = appCheckProvider;
|
|
778
|
+
if (_isFirebaseServerApp(app) && app.settings.appCheckToken) {
|
|
779
|
+
this.serverAppAppCheckToken = app.settings.appCheckToken;
|
|
780
|
+
}
|
|
781
|
+
this.appCheck = appCheckProvider?.getImmediate({ optional: true });
|
|
782
|
+
if (!this.appCheck) {
|
|
783
|
+
void appCheckProvider
|
|
784
|
+
?.get()
|
|
785
|
+
.then(appCheck => (this.appCheck = appCheck))
|
|
786
|
+
.catch();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
getToken() {
|
|
790
|
+
if (this.serverAppAppCheckToken) {
|
|
791
|
+
return Promise.resolve({ token: this.serverAppAppCheckToken });
|
|
792
|
+
}
|
|
793
|
+
if (!this.appCheck) {
|
|
794
|
+
return new Promise((resolve, reject) => {
|
|
795
|
+
// Support delayed initialization of FirebaseAppCheck. This allows our
|
|
796
|
+
// customers to initialize the RTDB SDK before initializing Firebase
|
|
797
|
+
// AppCheck and ensures that all requests are authenticated if a token
|
|
798
|
+
// becomes available before the timoeout below expires.
|
|
799
|
+
setTimeout(() => {
|
|
800
|
+
if (this.appCheck) {
|
|
801
|
+
this.getToken().then(resolve, reject);
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
resolve(null);
|
|
805
|
+
}
|
|
806
|
+
}, 0);
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
return this.appCheck.getToken();
|
|
810
|
+
}
|
|
811
|
+
addTokenChangeListener(listener) {
|
|
812
|
+
void this.appCheckProvider
|
|
813
|
+
?.get()
|
|
814
|
+
.then(appCheck => appCheck.addTokenListener(listener));
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* @license
|
|
820
|
+
* Copyright 2024 Google LLC
|
|
821
|
+
*
|
|
822
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
823
|
+
* you may not use this file except in compliance with the License.
|
|
824
|
+
* You may obtain a copy of the License at
|
|
825
|
+
*
|
|
826
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
827
|
+
*
|
|
828
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
829
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
830
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
831
|
+
* See the License for the specific language governing permissions and
|
|
832
|
+
* limitations under the License.
|
|
833
|
+
*/
|
|
834
|
+
// @internal
|
|
835
|
+
class FirebaseAuthProvider {
|
|
836
|
+
constructor(_appName, _options, _authProvider) {
|
|
837
|
+
this._appName = _appName;
|
|
838
|
+
this._options = _options;
|
|
839
|
+
this._authProvider = _authProvider;
|
|
840
|
+
this._auth = _authProvider.getImmediate({ optional: true });
|
|
841
|
+
if (!this._auth) {
|
|
842
|
+
_authProvider.onInit(auth => (this._auth = auth));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
getAuth() {
|
|
846
|
+
return this._auth;
|
|
847
|
+
}
|
|
848
|
+
getToken(forceRefresh) {
|
|
849
|
+
if (!this._auth) {
|
|
850
|
+
return new Promise((resolve, reject) => {
|
|
851
|
+
setTimeout(() => {
|
|
852
|
+
if (this._auth) {
|
|
853
|
+
this.getToken(forceRefresh).then(resolve, reject);
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
resolve(null);
|
|
857
|
+
}
|
|
858
|
+
}, 0);
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
return this._auth.getToken(forceRefresh).catch(error => {
|
|
862
|
+
if (error && error.code === 'auth/token-not-initialized') {
|
|
863
|
+
logDebug('Got auth/token-not-initialized error. Treating as null token.');
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
logError('Error received when attempting to retrieve token: ' +
|
|
868
|
+
JSON.stringify(error));
|
|
869
|
+
return Promise.reject(error);
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
addTokenChangeListener(listener) {
|
|
874
|
+
this._auth?.addAuthTokenListener(listener);
|
|
875
|
+
}
|
|
876
|
+
removeTokenChangeListener(listener) {
|
|
877
|
+
this._authProvider
|
|
878
|
+
.get()
|
|
879
|
+
.then(auth => auth.removeAuthTokenListener(listener))
|
|
880
|
+
.catch(err => logError(err));
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* @license
|
|
886
|
+
* Copyright 2024 Google LLC
|
|
887
|
+
*
|
|
888
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
889
|
+
* you may not use this file except in compliance with the License.
|
|
890
|
+
* You may obtain a copy of the License at
|
|
891
|
+
*
|
|
892
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
893
|
+
*
|
|
894
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
895
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
896
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
897
|
+
* See the License for the specific language governing permissions and
|
|
898
|
+
* limitations under the License.
|
|
899
|
+
*/
|
|
900
|
+
const QUERY_STR = 'query';
|
|
901
|
+
const MUTATION_STR = 'mutation';
|
|
902
|
+
const SOURCE_SERVER = 'SERVER';
|
|
903
|
+
const SOURCE_CACHE = 'CACHE';
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* @license
|
|
907
|
+
* Copyright 2026 Google LLC
|
|
908
|
+
*
|
|
909
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
910
|
+
* you may not use this file except in compliance with the License.
|
|
911
|
+
* You may obtain a copy of the License at
|
|
912
|
+
*
|
|
913
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
914
|
+
*
|
|
915
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
916
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
917
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
918
|
+
* See the License for the specific language governing permissions and
|
|
919
|
+
* limitations under the License.
|
|
920
|
+
*/
|
|
921
|
+
function parseEntityIds(result) {
|
|
922
|
+
// Iterate through extensions.dataConnect
|
|
923
|
+
const dataConnectExtensions = result.extensions?.dataConnect;
|
|
924
|
+
const dataCopy = Object.assign(result);
|
|
925
|
+
if (!dataConnectExtensions) {
|
|
926
|
+
return dataCopy;
|
|
927
|
+
}
|
|
928
|
+
const ret = {};
|
|
929
|
+
for (const extension of dataConnectExtensions) {
|
|
930
|
+
const { path } = extension;
|
|
931
|
+
populatePath(path, ret, extension);
|
|
932
|
+
}
|
|
933
|
+
return ret;
|
|
934
|
+
}
|
|
935
|
+
// mutates the object to update the path
|
|
936
|
+
function populatePath(path, toUpdate, extension) {
|
|
937
|
+
let curObj = toUpdate;
|
|
938
|
+
for (const slice of path) {
|
|
939
|
+
if (typeof curObj[slice] !== 'object') {
|
|
940
|
+
curObj[slice] = {};
|
|
941
|
+
}
|
|
942
|
+
curObj = curObj[slice];
|
|
943
|
+
}
|
|
944
|
+
if ('entityId' in extension && extension.entityId) {
|
|
945
|
+
curObj['_id'] = extension.entityId;
|
|
946
|
+
}
|
|
947
|
+
else if ('entityIds' in extension) {
|
|
948
|
+
const entityArr = extension.entityIds;
|
|
949
|
+
for (let i = 0; i < entityArr.length; i++) {
|
|
950
|
+
const entityId = entityArr[i];
|
|
951
|
+
if (typeof curObj[i] === 'undefined') {
|
|
952
|
+
curObj[i] = {};
|
|
953
|
+
}
|
|
954
|
+
curObj[i]._id = entityId;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* @license
|
|
961
|
+
* Copyright 2024 Google LLC
|
|
962
|
+
*
|
|
963
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
964
|
+
* you may not use this file except in compliance with the License.
|
|
965
|
+
* You may obtain a copy of the License at
|
|
966
|
+
*
|
|
967
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
968
|
+
*
|
|
969
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
970
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
971
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
972
|
+
* See the License for the specific language governing permissions and
|
|
973
|
+
* limitations under the License.
|
|
974
|
+
*/
|
|
975
|
+
let encoderImpl;
|
|
976
|
+
let decoderImpl;
|
|
977
|
+
function setEncoder(encoder) {
|
|
978
|
+
encoderImpl = encoder;
|
|
979
|
+
}
|
|
980
|
+
function setDecoder(decoder) {
|
|
981
|
+
decoderImpl = decoder;
|
|
982
|
+
}
|
|
983
|
+
function sortKeysForObj(o) {
|
|
984
|
+
return Object.keys(o)
|
|
985
|
+
.sort()
|
|
986
|
+
.reduce((accumulator, currentKey) => {
|
|
987
|
+
accumulator[currentKey] = o[currentKey];
|
|
988
|
+
return accumulator;
|
|
989
|
+
}, {});
|
|
990
|
+
}
|
|
991
|
+
setEncoder((o) => JSON.stringify(sortKeysForObj(o)));
|
|
992
|
+
setDecoder(s => sortKeysForObj(JSON.parse(s)));
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* @license
|
|
996
|
+
* Copyright 2024 Google LLC
|
|
997
|
+
*
|
|
998
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
999
|
+
* you may not use this file except in compliance with the License.
|
|
1000
|
+
* You may obtain a copy of the License at
|
|
1001
|
+
*
|
|
1002
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1003
|
+
*
|
|
1004
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1005
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1006
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1007
|
+
* See the License for the specific language governing permissions and
|
|
1008
|
+
* limitations under the License.
|
|
1009
|
+
*/
|
|
1010
|
+
function getRefSerializer(queryRef, data, source, fetchTime) {
|
|
1011
|
+
return function toJSON() {
|
|
1012
|
+
return {
|
|
1013
|
+
data,
|
|
1014
|
+
refInfo: {
|
|
1015
|
+
name: queryRef.name,
|
|
1016
|
+
variables: queryRef.variables,
|
|
1017
|
+
connectorConfig: {
|
|
1018
|
+
projectId: queryRef.dataConnect.app.options.projectId,
|
|
1019
|
+
...queryRef.dataConnect.getSettings()
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
fetchTime,
|
|
1023
|
+
source
|
|
1024
|
+
};
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
class QueryManager {
|
|
1028
|
+
async preferCacheResults(queryRef, allowStale = false) {
|
|
1029
|
+
let cacheResult;
|
|
1030
|
+
try {
|
|
1031
|
+
cacheResult = await this.fetchCacheResults(queryRef, allowStale);
|
|
1032
|
+
}
|
|
1033
|
+
catch (e) {
|
|
1034
|
+
// Ignore the error and try to fetch from the server.
|
|
1035
|
+
}
|
|
1036
|
+
if (cacheResult) {
|
|
1037
|
+
return cacheResult;
|
|
1038
|
+
}
|
|
1039
|
+
return this.fetchServerResults(queryRef);
|
|
1040
|
+
}
|
|
1041
|
+
constructor(transport, dc, cache) {
|
|
1042
|
+
this.transport = transport;
|
|
1043
|
+
this.dc = dc;
|
|
1044
|
+
this.cache = cache;
|
|
1045
|
+
this.callbacks = new Map();
|
|
1046
|
+
this.subscriptionCache = new Map();
|
|
1047
|
+
this.queue = [];
|
|
1048
|
+
}
|
|
1049
|
+
async waitForQueuedWrites() {
|
|
1050
|
+
for (const promise of this.queue) {
|
|
1051
|
+
await promise;
|
|
1052
|
+
}
|
|
1053
|
+
this.queue = [];
|
|
1054
|
+
}
|
|
1055
|
+
updateSSR(updatedData) {
|
|
1056
|
+
this.queue.push(this.updateCache(updatedData).then(async (result) => this.publishCacheResultsToSubscribers(result, updatedData.fetchTime)));
|
|
1057
|
+
}
|
|
1058
|
+
async updateCache(result, extensions) {
|
|
1059
|
+
await this.waitForQueuedWrites();
|
|
1060
|
+
if (this.cache) {
|
|
1061
|
+
const entityIds = parseEntityIds(result);
|
|
1062
|
+
const updatedMaxAge = getMaxAgeFromExtensions(extensions);
|
|
1063
|
+
if (updatedMaxAge !== undefined) {
|
|
1064
|
+
this.cache.cacheSettings.maxAgeSeconds = updatedMaxAge;
|
|
1065
|
+
}
|
|
1066
|
+
return this.cache.update(encoderImpl({
|
|
1067
|
+
name: result.ref.name,
|
|
1068
|
+
variables: result.ref.variables,
|
|
1069
|
+
refType: QUERY_STR
|
|
1070
|
+
}), result.data, entityIds);
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
const key = encoderImpl({
|
|
1074
|
+
name: result.ref.name,
|
|
1075
|
+
variables: result.ref.variables,
|
|
1076
|
+
refType: QUERY_STR
|
|
1077
|
+
});
|
|
1078
|
+
this.subscriptionCache.set(key, result);
|
|
1079
|
+
return [key];
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
addSubscription(queryRef, onResultCallback, onCompleteCallback, onErrorCallback, initialCache) {
|
|
1083
|
+
const key = encoderImpl({
|
|
1084
|
+
name: queryRef.name,
|
|
1085
|
+
variables: queryRef.variables,
|
|
1086
|
+
refType: QUERY_STR
|
|
1087
|
+
});
|
|
1088
|
+
const unsubscribe = () => {
|
|
1089
|
+
if (this.callbacks.has(key)) {
|
|
1090
|
+
const callbackList = this.callbacks.get(key);
|
|
1091
|
+
this.callbacks.set(key, callbackList.filter(callback => callback !== subscription));
|
|
1092
|
+
onCompleteCallback?.();
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
const subscription = {
|
|
1096
|
+
userCallback: onResultCallback,
|
|
1097
|
+
errCallback: onErrorCallback,
|
|
1098
|
+
unsubscribe
|
|
1099
|
+
};
|
|
1100
|
+
if (initialCache) {
|
|
1101
|
+
this.updateSSR(initialCache);
|
|
1102
|
+
}
|
|
1103
|
+
const promise = this.preferCacheResults(queryRef, /*allowStale=*/ true);
|
|
1104
|
+
// We want to ignore the error and let subscriptions handle it
|
|
1105
|
+
promise.then(undefined, err => { });
|
|
1106
|
+
if (!this.callbacks.has(key)) {
|
|
1107
|
+
this.callbacks.set(key, []);
|
|
1108
|
+
}
|
|
1109
|
+
this.callbacks
|
|
1110
|
+
.get(key)
|
|
1111
|
+
.push(subscription);
|
|
1112
|
+
return unsubscribe;
|
|
1113
|
+
}
|
|
1114
|
+
async fetchServerResults(queryRef) {
|
|
1115
|
+
await this.waitForQueuedWrites();
|
|
1116
|
+
const key = encoderImpl({
|
|
1117
|
+
name: queryRef.name,
|
|
1118
|
+
variables: queryRef.variables,
|
|
1119
|
+
refType: QUERY_STR
|
|
1120
|
+
});
|
|
1121
|
+
try {
|
|
1122
|
+
const result = await this.transport.invokeQuery(queryRef.name, queryRef.variables);
|
|
1123
|
+
const fetchTime = Date.now().toString();
|
|
1124
|
+
const originalExtensions = result.extensions;
|
|
1125
|
+
const queryResult = {
|
|
1126
|
+
...result,
|
|
1127
|
+
ref: queryRef,
|
|
1128
|
+
source: SOURCE_SERVER,
|
|
1129
|
+
fetchTime,
|
|
1130
|
+
data: result.data,
|
|
1131
|
+
extensions: getDataConnectExtensionsWithoutMaxAge(originalExtensions),
|
|
1132
|
+
toJSON: getRefSerializer(queryRef, result.data, SOURCE_SERVER, fetchTime)
|
|
1133
|
+
};
|
|
1134
|
+
let updatedKeys = [];
|
|
1135
|
+
updatedKeys = await this.updateCache(queryResult, originalExtensions?.dataConnect);
|
|
1136
|
+
this.publishDataToSubscribers(key, queryResult);
|
|
1137
|
+
if (this.cache) {
|
|
1138
|
+
await this.publishCacheResultsToSubscribers(updatedKeys, fetchTime);
|
|
1139
|
+
}
|
|
1140
|
+
else {
|
|
1141
|
+
this.subscriptionCache.set(key, queryResult);
|
|
1142
|
+
}
|
|
1143
|
+
return queryResult;
|
|
1144
|
+
}
|
|
1145
|
+
catch (e) {
|
|
1146
|
+
this.publishErrorToSubscribers(key, e);
|
|
1147
|
+
throw e;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
async fetchCacheResults(queryRef, allowStale = false) {
|
|
1151
|
+
await this.waitForQueuedWrites();
|
|
1152
|
+
let result;
|
|
1153
|
+
if (!this.cache) {
|
|
1154
|
+
result = await this.getFromSubscriberCache(queryRef);
|
|
1155
|
+
}
|
|
1156
|
+
else {
|
|
1157
|
+
result = await this.getFromResultTreeCache(queryRef, allowStale);
|
|
1158
|
+
}
|
|
1159
|
+
if (!result) {
|
|
1160
|
+
throw new DataConnectError(Code.OTHER, 'No cache entry found for query: ' + queryRef.name);
|
|
1161
|
+
}
|
|
1162
|
+
const fetchTime = Date.now().toString();
|
|
1163
|
+
const queryResult = {
|
|
1164
|
+
...result,
|
|
1165
|
+
ref: queryRef,
|
|
1166
|
+
source: SOURCE_CACHE,
|
|
1167
|
+
fetchTime,
|
|
1168
|
+
data: result.data,
|
|
1169
|
+
extensions: result.extensions,
|
|
1170
|
+
toJSON: getRefSerializer(queryRef, result.data, SOURCE_CACHE, fetchTime)
|
|
1171
|
+
};
|
|
1172
|
+
if (this.cache) {
|
|
1173
|
+
const key = encoderImpl({
|
|
1174
|
+
name: queryRef.name,
|
|
1175
|
+
variables: queryRef.variables,
|
|
1176
|
+
refType: QUERY_STR
|
|
1177
|
+
});
|
|
1178
|
+
await this.publishCacheResultsToSubscribers([key], fetchTime);
|
|
1179
|
+
}
|
|
1180
|
+
else {
|
|
1181
|
+
const key = encoderImpl({
|
|
1182
|
+
name: queryRef.name,
|
|
1183
|
+
variables: queryRef.variables,
|
|
1184
|
+
refType: QUERY_STR
|
|
1185
|
+
});
|
|
1186
|
+
this.subscriptionCache.set(key, queryResult);
|
|
1187
|
+
this.publishDataToSubscribers(key, queryResult);
|
|
1188
|
+
}
|
|
1189
|
+
return queryResult;
|
|
1190
|
+
}
|
|
1191
|
+
publishErrorToSubscribers(key, err) {
|
|
1192
|
+
this.callbacks.get(key)?.forEach(subscription => {
|
|
1193
|
+
if (subscription.errCallback) {
|
|
1194
|
+
subscription.errCallback(err);
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
async getFromResultTreeCache(queryRef, allowStale = false) {
|
|
1199
|
+
const key = encoderImpl({
|
|
1200
|
+
name: queryRef.name,
|
|
1201
|
+
variables: queryRef.variables,
|
|
1202
|
+
refType: QUERY_STR
|
|
1203
|
+
});
|
|
1204
|
+
if (!this.cache || !(await this.cache.containsResultTree(key))) {
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
const cacheResult = (await this.cache.getResultJSON(key));
|
|
1208
|
+
const resultTree = await this.cache.getResultTree(key);
|
|
1209
|
+
if (!allowStale && resultTree.isStale()) {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
const result = {
|
|
1213
|
+
source: SOURCE_CACHE,
|
|
1214
|
+
ref: queryRef,
|
|
1215
|
+
data: cacheResult,
|
|
1216
|
+
toJSON: getRefSerializer(queryRef, cacheResult, SOURCE_CACHE, resultTree.cachedAt.toString()),
|
|
1217
|
+
fetchTime: resultTree.cachedAt.toString()
|
|
1218
|
+
};
|
|
1219
|
+
(await this.cache.getResultTree(key)).updateAccessed();
|
|
1220
|
+
return result;
|
|
1221
|
+
}
|
|
1222
|
+
async getFromSubscriberCache(queryRef) {
|
|
1223
|
+
const key = encoderImpl({
|
|
1224
|
+
name: queryRef.name,
|
|
1225
|
+
variables: queryRef.variables,
|
|
1226
|
+
refType: QUERY_STR
|
|
1227
|
+
});
|
|
1228
|
+
if (!this.subscriptionCache.has(key)) {
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
const result = this.subscriptionCache.get(key);
|
|
1232
|
+
result.source = SOURCE_CACHE;
|
|
1233
|
+
result.toJSON = getRefSerializer(result.ref, result.data, SOURCE_CACHE, result.fetchTime);
|
|
1234
|
+
return result;
|
|
1235
|
+
}
|
|
1236
|
+
publishDataToSubscribers(key, queryResult) {
|
|
1237
|
+
if (!this.callbacks.has(key)) {
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const subscribers = this.callbacks.get(key);
|
|
1241
|
+
subscribers.forEach(callback => {
|
|
1242
|
+
callback.userCallback(queryResult);
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
async publishCacheResultsToSubscribers(impactedQueries, fetchTime) {
|
|
1246
|
+
if (!this.cache) {
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
for (const query of impactedQueries) {
|
|
1250
|
+
const callbacks = this.callbacks.get(query);
|
|
1251
|
+
if (!callbacks) {
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1254
|
+
const newJson = (await this.cache.getResultTree(query))
|
|
1255
|
+
.getRootStub()
|
|
1256
|
+
.toJSON(EncodingMode.hydrated);
|
|
1257
|
+
const { name, variables } = decoderImpl(query);
|
|
1258
|
+
const queryRef = {
|
|
1259
|
+
dataConnect: this.dc,
|
|
1260
|
+
refType: QUERY_STR,
|
|
1261
|
+
name,
|
|
1262
|
+
variables
|
|
1263
|
+
};
|
|
1264
|
+
this.publishDataToSubscribers(query, {
|
|
1265
|
+
data: newJson,
|
|
1266
|
+
fetchTime,
|
|
1267
|
+
ref: queryRef,
|
|
1268
|
+
source: SOURCE_CACHE,
|
|
1269
|
+
toJSON: getRefSerializer(queryRef, newJson, SOURCE_CACHE, fetchTime)
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
enableEmulator(host, port) {
|
|
1274
|
+
this.transport.useEmulator(host, port);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function getMaxAgeFromExtensions(extensions) {
|
|
1278
|
+
if (!extensions) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
for (const extension of extensions) {
|
|
1282
|
+
if ('maxAge' in extension &&
|
|
1283
|
+
extension.maxAge !== undefined &&
|
|
1284
|
+
extension.maxAge !== null) {
|
|
1285
|
+
if (extension.maxAge.endsWith('s')) {
|
|
1286
|
+
return Number(extension.maxAge.substring(0, extension.maxAge.length - 1));
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function getDataConnectExtensionsWithoutMaxAge(extensions) {
|
|
1292
|
+
return {
|
|
1293
|
+
dataConnect: extensions.dataConnect?.filter(extension => 'entityId' in extension || 'entityIds' in extension)
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* @license
|
|
1299
|
+
* Copyright 2024 Google LLC
|
|
1300
|
+
*
|
|
1301
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1302
|
+
* you may not use this file except in compliance with the License.
|
|
1303
|
+
* You may obtain a copy of the License at
|
|
1304
|
+
*
|
|
1305
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1306
|
+
*
|
|
1307
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1308
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1309
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1310
|
+
* See the License for the specific language governing permissions and
|
|
1311
|
+
* limitations under the License.
|
|
1312
|
+
*/
|
|
1313
|
+
const PROD_HOST = 'firebasedataconnect.googleapis.com';
|
|
1314
|
+
function urlBuilder(projectConfig, transportOptions) {
|
|
1315
|
+
const { connector, location, projectId: project, service } = projectConfig;
|
|
1316
|
+
const { host, sslEnabled, port } = transportOptions;
|
|
1317
|
+
const protocol = sslEnabled ? 'https' : 'http';
|
|
1318
|
+
const realHost = host || PROD_HOST;
|
|
1319
|
+
let baseUrl = `${protocol}://${realHost}`;
|
|
1320
|
+
if (typeof port === 'number') {
|
|
1321
|
+
baseUrl += `:${port}`;
|
|
1322
|
+
}
|
|
1323
|
+
else if (typeof port !== 'undefined') {
|
|
1324
|
+
logError('Port type is of an invalid type');
|
|
1325
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, 'Incorrect type for port passed in!');
|
|
1326
|
+
}
|
|
1327
|
+
return `${baseUrl}/v1/projects/${project}/locations/${location}/services/${service}/connectors/${connector}`;
|
|
1328
|
+
}
|
|
1329
|
+
function addToken(url, apiKey) {
|
|
1330
|
+
if (!apiKey) {
|
|
1331
|
+
return url;
|
|
1332
|
+
}
|
|
1333
|
+
const newUrl = new URL(url);
|
|
1334
|
+
newUrl.searchParams.append('key', apiKey);
|
|
1335
|
+
return newUrl.toString();
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* @license
|
|
1340
|
+
* Copyright 2024 Google LLC
|
|
1341
|
+
*
|
|
1342
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1343
|
+
* you may not use this file except in compliance with the License.
|
|
1344
|
+
* You may obtain a copy of the License at
|
|
1345
|
+
*
|
|
1346
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1347
|
+
*
|
|
1348
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1349
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1350
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1351
|
+
* See the License for the specific language governing permissions and
|
|
1352
|
+
* limitations under the License.
|
|
1353
|
+
*/
|
|
1354
|
+
class RESTTransport {
|
|
1355
|
+
constructor(options, apiKey, appId, authProvider, appCheckProvider, transportOptions, _isUsingGen = false, _callerSdkType = CallerSdkTypeEnum.Base) {
|
|
1356
|
+
this.apiKey = apiKey;
|
|
1357
|
+
this.appId = appId;
|
|
1358
|
+
this.authProvider = authProvider;
|
|
1359
|
+
this.appCheckProvider = appCheckProvider;
|
|
1360
|
+
this._isUsingGen = _isUsingGen;
|
|
1361
|
+
this._callerSdkType = _callerSdkType;
|
|
1362
|
+
this._host = '';
|
|
1363
|
+
this._location = 'l';
|
|
1364
|
+
this._connectorName = '';
|
|
1365
|
+
this._secure = true;
|
|
1366
|
+
this._project = 'p';
|
|
1367
|
+
this._accessToken = null;
|
|
1368
|
+
this._appCheckToken = null;
|
|
1369
|
+
this._lastToken = null;
|
|
1370
|
+
this._isUsingEmulator = false;
|
|
1371
|
+
// TODO(mtewani): Update U to include shape of body defined in line 13.
|
|
1372
|
+
this.invokeQuery = (queryName, body) => {
|
|
1373
|
+
const abortController = new AbortController();
|
|
1374
|
+
// TODO(mtewani): Update to proper value
|
|
1375
|
+
const withAuth = this.withRetry(() => dcFetch(addToken(`${this.endpointUrl}:executeQuery`, this.apiKey), {
|
|
1376
|
+
name: `projects/${this._project}/locations/${this._location}/services/${this._serviceName}/connectors/${this._connectorName}`,
|
|
1377
|
+
operationName: queryName,
|
|
1378
|
+
variables: body
|
|
1379
|
+
}, abortController, this.appId, this._accessToken, this._appCheckToken, this._isUsingGen, this._callerSdkType, this._isUsingEmulator));
|
|
1380
|
+
return withAuth;
|
|
1381
|
+
};
|
|
1382
|
+
this.invokeMutation = (mutationName, body) => {
|
|
1383
|
+
const abortController = new AbortController();
|
|
1384
|
+
const taskResult = this.withRetry(() => {
|
|
1385
|
+
return dcFetch(addToken(`${this.endpointUrl}:executeMutation`, this.apiKey), {
|
|
1386
|
+
name: `projects/${this._project}/locations/${this._location}/services/${this._serviceName}/connectors/${this._connectorName}`,
|
|
1387
|
+
operationName: mutationName,
|
|
1388
|
+
variables: body
|
|
1389
|
+
}, abortController, this.appId, this._accessToken, this._appCheckToken, this._isUsingGen, this._callerSdkType, this._isUsingEmulator);
|
|
1390
|
+
});
|
|
1391
|
+
return taskResult;
|
|
1392
|
+
};
|
|
1393
|
+
if (transportOptions) {
|
|
1394
|
+
if (typeof transportOptions.port === 'number') {
|
|
1395
|
+
this._port = transportOptions.port;
|
|
1396
|
+
}
|
|
1397
|
+
if (typeof transportOptions.sslEnabled !== 'undefined') {
|
|
1398
|
+
this._secure = transportOptions.sslEnabled;
|
|
1399
|
+
}
|
|
1400
|
+
this._host = transportOptions.host;
|
|
1401
|
+
}
|
|
1402
|
+
const { location, projectId: project, connector, service } = options;
|
|
1403
|
+
if (location) {
|
|
1404
|
+
this._location = location;
|
|
1405
|
+
}
|
|
1406
|
+
if (project) {
|
|
1407
|
+
this._project = project;
|
|
1408
|
+
}
|
|
1409
|
+
this._serviceName = service;
|
|
1410
|
+
if (!connector) {
|
|
1411
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, 'Connector Name required!');
|
|
1412
|
+
}
|
|
1413
|
+
this._connectorName = connector;
|
|
1414
|
+
this.authProvider?.addTokenChangeListener(token => {
|
|
1415
|
+
logDebug(`New Token Available: ${token}`);
|
|
1416
|
+
this._accessToken = token;
|
|
1417
|
+
});
|
|
1418
|
+
this.appCheckProvider?.addTokenChangeListener(result => {
|
|
1419
|
+
const { token } = result;
|
|
1420
|
+
logDebug(`New App Check Token Available: ${token}`);
|
|
1421
|
+
this._appCheckToken = token;
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
get endpointUrl() {
|
|
1425
|
+
return urlBuilder({
|
|
1426
|
+
connector: this._connectorName,
|
|
1427
|
+
location: this._location,
|
|
1428
|
+
projectId: this._project,
|
|
1429
|
+
service: this._serviceName
|
|
1430
|
+
}, { host: this._host, sslEnabled: this._secure, port: this._port });
|
|
1431
|
+
}
|
|
1432
|
+
useEmulator(host, port, isSecure) {
|
|
1433
|
+
this._host = host;
|
|
1434
|
+
this._isUsingEmulator = true;
|
|
1435
|
+
if (typeof port === 'number') {
|
|
1436
|
+
this._port = port;
|
|
1437
|
+
}
|
|
1438
|
+
if (typeof isSecure !== 'undefined') {
|
|
1439
|
+
this._secure = isSecure;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
onTokenChanged(newToken) {
|
|
1443
|
+
this._accessToken = newToken;
|
|
1444
|
+
}
|
|
1445
|
+
async getWithAuth(forceToken = false) {
|
|
1446
|
+
let starterPromise = new Promise(resolve => resolve(this._accessToken));
|
|
1447
|
+
if (this.appCheckProvider) {
|
|
1448
|
+
const appCheckToken = await this.appCheckProvider.getToken();
|
|
1449
|
+
if (appCheckToken) {
|
|
1450
|
+
this._appCheckToken = appCheckToken.token;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
if (this.authProvider) {
|
|
1454
|
+
starterPromise = this.authProvider
|
|
1455
|
+
.getToken(/*forceToken=*/ forceToken)
|
|
1456
|
+
.then(data => {
|
|
1457
|
+
if (!data) {
|
|
1458
|
+
return null;
|
|
1459
|
+
}
|
|
1460
|
+
this._accessToken = data.accessToken;
|
|
1461
|
+
return this._accessToken;
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
else {
|
|
1465
|
+
starterPromise = new Promise(resolve => resolve(''));
|
|
1466
|
+
}
|
|
1467
|
+
return starterPromise;
|
|
1468
|
+
}
|
|
1469
|
+
_setLastToken(lastToken) {
|
|
1470
|
+
this._lastToken = lastToken;
|
|
1471
|
+
}
|
|
1472
|
+
withRetry(promiseFactory, retry = false) {
|
|
1473
|
+
let isNewToken = false;
|
|
1474
|
+
return this.getWithAuth(retry)
|
|
1475
|
+
.then(res => {
|
|
1476
|
+
isNewToken = this._lastToken !== res;
|
|
1477
|
+
this._lastToken = res;
|
|
1478
|
+
return res;
|
|
1479
|
+
})
|
|
1480
|
+
.then(promiseFactory)
|
|
1481
|
+
.catch(err => {
|
|
1482
|
+
// Only retry if the result is unauthorized and the last token isn't the same as the new one.
|
|
1483
|
+
if ('code' in err &&
|
|
1484
|
+
err.code === Code.UNAUTHORIZED &&
|
|
1485
|
+
!retry &&
|
|
1486
|
+
isNewToken) {
|
|
1487
|
+
logDebug('Retrying due to unauthorized');
|
|
1488
|
+
return this.withRetry(promiseFactory, true);
|
|
1489
|
+
}
|
|
1490
|
+
throw err;
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
_setCallerSdkType(callerSdkType) {
|
|
1494
|
+
this._callerSdkType = callerSdkType;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* @license
|
|
1500
|
+
* Copyright 2024 Google LLC
|
|
1501
|
+
*
|
|
1502
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1503
|
+
* you may not use this file except in compliance with the License.
|
|
1504
|
+
* You may obtain a copy of the License at
|
|
1505
|
+
*
|
|
1506
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1507
|
+
*
|
|
1508
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1509
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1510
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1511
|
+
* See the License for the specific language governing permissions and
|
|
1512
|
+
* limitations under the License.
|
|
1513
|
+
*/
|
|
1514
|
+
/**
|
|
1515
|
+
*
|
|
1516
|
+
* @param dcInstance Data Connect instance
|
|
1517
|
+
* @param mutationName name of mutation
|
|
1518
|
+
* @param variables variables to send with mutation
|
|
1519
|
+
* @returns `MutationRef`
|
|
1520
|
+
*/
|
|
1521
|
+
function mutationRef(dcInstance, mutationName, variables) {
|
|
1522
|
+
dcInstance.setInitialized();
|
|
1523
|
+
const ref = {
|
|
1524
|
+
dataConnect: dcInstance,
|
|
1525
|
+
name: mutationName,
|
|
1526
|
+
refType: MUTATION_STR,
|
|
1527
|
+
variables: variables
|
|
1528
|
+
};
|
|
1529
|
+
return ref;
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* @internal
|
|
1533
|
+
*/
|
|
1534
|
+
class MutationManager {
|
|
1535
|
+
constructor(_transport) {
|
|
1536
|
+
this._transport = _transport;
|
|
1537
|
+
this._inflight = [];
|
|
1538
|
+
}
|
|
1539
|
+
executeMutation(mutationRef) {
|
|
1540
|
+
const result = this._transport.invokeMutation(mutationRef.name, mutationRef.variables);
|
|
1541
|
+
const withRefPromise = result.then(res => {
|
|
1542
|
+
const obj = {
|
|
1543
|
+
...res, // Double check that the result is result.data, not just result
|
|
1544
|
+
source: SOURCE_SERVER,
|
|
1545
|
+
ref: mutationRef,
|
|
1546
|
+
fetchTime: Date.now().toLocaleString()
|
|
1547
|
+
};
|
|
1548
|
+
return obj;
|
|
1549
|
+
});
|
|
1550
|
+
this._inflight.push(result);
|
|
1551
|
+
const removePromise = () => (this._inflight = this._inflight.filter(promise => promise !== result));
|
|
1552
|
+
result.then(removePromise, removePromise);
|
|
1553
|
+
return withRefPromise;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Execute Mutation
|
|
1558
|
+
* @param mutationRef mutation to execute
|
|
1559
|
+
* @returns `MutationRef`
|
|
1560
|
+
*/
|
|
1561
|
+
function executeMutation(mutationRef) {
|
|
1562
|
+
return mutationRef.dataConnect._mutationManager.executeMutation(mutationRef);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* @license
|
|
1567
|
+
* Copyright 2024 Google LLC
|
|
1568
|
+
*
|
|
1569
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1570
|
+
* you may not use this file except in compliance with the License.
|
|
1571
|
+
* You may obtain a copy of the License at
|
|
1572
|
+
*
|
|
1573
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1574
|
+
*
|
|
1575
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1576
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1577
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1578
|
+
* See the License for the specific language governing permissions and
|
|
1579
|
+
* limitations under the License.
|
|
1580
|
+
*/
|
|
1581
|
+
const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = 'FIREBASE_DATA_CONNECT_EMULATOR_HOST';
|
|
1582
|
+
/**
|
|
1583
|
+
*
|
|
1584
|
+
* @param fullHost
|
|
1585
|
+
* @returns TransportOptions
|
|
1586
|
+
* @internal
|
|
1587
|
+
*/
|
|
1588
|
+
function parseOptions(fullHost) {
|
|
1589
|
+
const [protocol, hostName] = fullHost.split('://');
|
|
1590
|
+
const isSecure = protocol === 'https';
|
|
1591
|
+
const [host, portAsString] = hostName.split(':');
|
|
1592
|
+
const port = Number(portAsString);
|
|
1593
|
+
return { host, port, sslEnabled: isSecure };
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Class representing Firebase Data Connect
|
|
1597
|
+
*/
|
|
1598
|
+
class DataConnect {
|
|
1599
|
+
// @internal
|
|
1600
|
+
constructor(app,
|
|
1601
|
+
// TODO(mtewani): Replace with _dataConnectOptions in the future
|
|
1602
|
+
dataConnectOptions, _authProvider, _appCheckProvider) {
|
|
1603
|
+
this.app = app;
|
|
1604
|
+
this.dataConnectOptions = dataConnectOptions;
|
|
1605
|
+
this._authProvider = _authProvider;
|
|
1606
|
+
this._appCheckProvider = _appCheckProvider;
|
|
1607
|
+
this.isEmulator = false;
|
|
1608
|
+
this._initialized = false;
|
|
1609
|
+
this._isUsingGeneratedSdk = false;
|
|
1610
|
+
this._callerSdkType = CallerSdkTypeEnum.Base;
|
|
1611
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
1612
|
+
const host = process.env[FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR];
|
|
1613
|
+
if (host) {
|
|
1614
|
+
logDebug('Found custom host. Using emulator');
|
|
1615
|
+
this.isEmulator = true;
|
|
1616
|
+
this._transportOptions = parseOptions(host);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* @internal
|
|
1622
|
+
*/
|
|
1623
|
+
getCache() {
|
|
1624
|
+
return this.cache;
|
|
1625
|
+
}
|
|
1626
|
+
// @internal
|
|
1627
|
+
_useGeneratedSdk() {
|
|
1628
|
+
if (!this._isUsingGeneratedSdk) {
|
|
1629
|
+
this._isUsingGeneratedSdk = true;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
_setCallerSdkType(callerSdkType) {
|
|
1633
|
+
this._callerSdkType = callerSdkType;
|
|
1634
|
+
if (this._initialized) {
|
|
1635
|
+
this._transport._setCallerSdkType(callerSdkType);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
_delete() {
|
|
1639
|
+
_removeServiceInstance(this.app, 'data-connect', JSON.stringify(this.getSettings()));
|
|
1640
|
+
return Promise.resolve();
|
|
1641
|
+
}
|
|
1642
|
+
// @internal
|
|
1643
|
+
getSettings() {
|
|
1644
|
+
const copy = JSON.parse(JSON.stringify(this.dataConnectOptions));
|
|
1645
|
+
delete copy.projectId;
|
|
1646
|
+
return copy;
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* @internal
|
|
1650
|
+
*/
|
|
1651
|
+
setCacheSettings(cacheSettings) {
|
|
1652
|
+
this._cacheSettings = cacheSettings;
|
|
1653
|
+
}
|
|
1654
|
+
// @internal
|
|
1655
|
+
setInitialized() {
|
|
1656
|
+
if (this._initialized) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
if (this._transportClass === undefined) {
|
|
1660
|
+
logDebug('transportClass not provided. Defaulting to RESTTransport.');
|
|
1661
|
+
this._transportClass = RESTTransport;
|
|
1662
|
+
}
|
|
1663
|
+
this._authTokenProvider = new FirebaseAuthProvider(this.app.name, this.app.options, this._authProvider);
|
|
1664
|
+
const connectorConfig = {
|
|
1665
|
+
connector: this.dataConnectOptions.connector,
|
|
1666
|
+
service: this.dataConnectOptions.service,
|
|
1667
|
+
location: this.dataConnectOptions.location
|
|
1668
|
+
};
|
|
1669
|
+
if (this._cacheSettings) {
|
|
1670
|
+
this.cache = new DataConnectCache(this._authTokenProvider, this.app.options.projectId, connectorConfig, this._transportOptions?.host || PROD_HOST, this._cacheSettings);
|
|
1671
|
+
}
|
|
1672
|
+
if (this._appCheckProvider) {
|
|
1673
|
+
this._appCheckTokenProvider = new AppCheckTokenProvider(this.app, this._appCheckProvider);
|
|
1674
|
+
}
|
|
1675
|
+
this._transport = new this._transportClass(this.dataConnectOptions, this.app.options.apiKey, this.app.options.appId, this._authTokenProvider, this._appCheckTokenProvider, undefined, this._isUsingGeneratedSdk, this._callerSdkType);
|
|
1676
|
+
if (this._transportOptions) {
|
|
1677
|
+
this._transport.useEmulator(this._transportOptions.host, this._transportOptions.port, this._transportOptions.sslEnabled);
|
|
1678
|
+
}
|
|
1679
|
+
this._queryManager = new QueryManager(this._transport, this, this.cache);
|
|
1680
|
+
this._mutationManager = new MutationManager(this._transport);
|
|
1681
|
+
this._initialized = true;
|
|
1682
|
+
}
|
|
1683
|
+
// @internal
|
|
1684
|
+
enableEmulator(transportOptions) {
|
|
1685
|
+
if (this._transportOptions &&
|
|
1686
|
+
this._initialized &&
|
|
1687
|
+
!areTransportOptionsEqual(this._transportOptions, transportOptions)) {
|
|
1688
|
+
logError('enableEmulator called after initialization');
|
|
1689
|
+
throw new DataConnectError(Code.ALREADY_INITIALIZED, 'DataConnect instance already initialized!');
|
|
1690
|
+
}
|
|
1691
|
+
this._transportOptions = transportOptions;
|
|
1692
|
+
this.isEmulator = true;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* @internal
|
|
1697
|
+
* @param transportOptions1
|
|
1698
|
+
* @param transportOptions2
|
|
1699
|
+
* @returns
|
|
1700
|
+
*/
|
|
1701
|
+
function areTransportOptionsEqual(transportOptions1, transportOptions2) {
|
|
1702
|
+
return (transportOptions1.host === transportOptions2.host &&
|
|
1703
|
+
transportOptions1.port === transportOptions2.port &&
|
|
1704
|
+
transportOptions1.sslEnabled === transportOptions2.sslEnabled);
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Connect to the DataConnect Emulator
|
|
1708
|
+
* @param dc Data Connect instance
|
|
1709
|
+
* @param host host of emulator server
|
|
1710
|
+
* @param port port of emulator server
|
|
1711
|
+
* @param sslEnabled use https
|
|
1712
|
+
*/
|
|
1713
|
+
function connectDataConnectEmulator(dc, host, port, sslEnabled = false) {
|
|
1714
|
+
// Workaround to get cookies in Firebase Studio
|
|
1715
|
+
if (isCloudWorkstation(host)) {
|
|
1716
|
+
void pingServer(`https://${host}${port ? `:${port}` : ''}`);
|
|
1717
|
+
updateEmulatorBanner('Data Connect', true);
|
|
1718
|
+
}
|
|
1719
|
+
dc.enableEmulator({ host, port, sslEnabled });
|
|
1720
|
+
}
|
|
1721
|
+
function getDataConnect(appOrConnectorConfig, settingsOrConnectorConfig, settings) {
|
|
1722
|
+
let app;
|
|
1723
|
+
let connectorConfig;
|
|
1724
|
+
let realSettings;
|
|
1725
|
+
if ('location' in appOrConnectorConfig) {
|
|
1726
|
+
connectorConfig = appOrConnectorConfig;
|
|
1727
|
+
app = getApp();
|
|
1728
|
+
realSettings = settingsOrConnectorConfig;
|
|
1729
|
+
}
|
|
1730
|
+
else {
|
|
1731
|
+
app = appOrConnectorConfig;
|
|
1732
|
+
connectorConfig = settingsOrConnectorConfig;
|
|
1733
|
+
realSettings = settings;
|
|
1734
|
+
}
|
|
1735
|
+
if (!app || Object.keys(app).length === 0) {
|
|
1736
|
+
app = getApp();
|
|
1737
|
+
}
|
|
1738
|
+
// Options to store in Firebase Component Provider.
|
|
1739
|
+
const serializedOptions = {
|
|
1740
|
+
...connectorConfig,
|
|
1741
|
+
projectId: app.options.projectId
|
|
1742
|
+
};
|
|
1743
|
+
// We should sort the keys before initialization.
|
|
1744
|
+
const sortedSerialized = Object.fromEntries(Object.entries(serializedOptions).sort());
|
|
1745
|
+
const provider = _getProvider(app, 'data-connect');
|
|
1746
|
+
const identifier = JSON.stringify(sortedSerialized);
|
|
1747
|
+
if (provider.isInitialized(identifier)) {
|
|
1748
|
+
const dcInstance = provider.getImmediate({ identifier });
|
|
1749
|
+
const options = provider.getOptions(identifier);
|
|
1750
|
+
const optionsValid = Object.keys(options).length > 0;
|
|
1751
|
+
if (optionsValid) {
|
|
1752
|
+
logDebug('Re-using cached instance');
|
|
1753
|
+
return dcInstance;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
validateDCOptions(connectorConfig);
|
|
1757
|
+
logDebug('Creating new DataConnect instance');
|
|
1758
|
+
// Initialize with options.
|
|
1759
|
+
const dataConnect = provider.initialize({
|
|
1760
|
+
instanceIdentifier: identifier,
|
|
1761
|
+
options: Object.fromEntries(Object.entries({
|
|
1762
|
+
...sortedSerialized
|
|
1763
|
+
}).sort())
|
|
1764
|
+
});
|
|
1765
|
+
if (realSettings?.cacheSettings) {
|
|
1766
|
+
dataConnect.setCacheSettings(realSettings.cacheSettings);
|
|
1767
|
+
}
|
|
1768
|
+
return dataConnect;
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
*
|
|
1772
|
+
* @param dcOptions
|
|
1773
|
+
* @returns {void}
|
|
1774
|
+
* @internal
|
|
1775
|
+
*/
|
|
1776
|
+
function validateDCOptions(dcOptions) {
|
|
1777
|
+
const fields = ['connector', 'location', 'service'];
|
|
1778
|
+
if (!dcOptions) {
|
|
1779
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required');
|
|
1780
|
+
}
|
|
1781
|
+
fields.forEach(field => {
|
|
1782
|
+
if (dcOptions[field] === null ||
|
|
1783
|
+
dcOptions[field] === undefined) {
|
|
1784
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, `${field} Required`);
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1787
|
+
return true;
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Delete DataConnect instance
|
|
1791
|
+
* @param dataConnect DataConnect instance
|
|
1792
|
+
* @returns
|
|
1793
|
+
*/
|
|
1794
|
+
function terminate(dataConnect) {
|
|
1795
|
+
return dataConnect._delete();
|
|
1796
|
+
// TODO(mtewani): Stop pending tasks
|
|
1797
|
+
}
|
|
1798
|
+
const StorageType = {
|
|
1799
|
+
MEMORY: 'MEMORY'
|
|
1800
|
+
};
|
|
1801
|
+
function makeMemoryCacheProvider() {
|
|
1802
|
+
return new MemoryStub();
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
/**
|
|
1806
|
+
* @license
|
|
1807
|
+
* Copyright 2024 Google LLC
|
|
1808
|
+
*
|
|
1809
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1810
|
+
* you may not use this file except in compliance with the License.
|
|
1811
|
+
* You may obtain a copy of the License at
|
|
1812
|
+
*
|
|
1813
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1814
|
+
*
|
|
1815
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1816
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1817
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1818
|
+
* See the License for the specific language governing permissions and
|
|
1819
|
+
* limitations under the License.
|
|
1820
|
+
*/
|
|
1821
|
+
function registerDataConnect(variant) {
|
|
1822
|
+
setSDKVersion(SDK_VERSION$1);
|
|
1823
|
+
_registerComponent(new Component('data-connect', (container, { instanceIdentifier: connectorConfigStr, options }) => {
|
|
1824
|
+
const app = container.getProvider('app').getImmediate();
|
|
1825
|
+
const authProvider = container.getProvider('auth-internal');
|
|
1826
|
+
const appCheckProvider = container.getProvider('app-check-internal');
|
|
1827
|
+
let newOpts = options;
|
|
1828
|
+
if (connectorConfigStr) {
|
|
1829
|
+
newOpts = {
|
|
1830
|
+
...JSON.parse(connectorConfigStr),
|
|
1831
|
+
...newOpts
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
if (!app.options.projectId) {
|
|
1835
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?');
|
|
1836
|
+
}
|
|
1837
|
+
return new DataConnect(app, { ...newOpts, projectId: app.options.projectId }, authProvider, appCheckProvider);
|
|
1838
|
+
}, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
|
|
1839
|
+
registerVersion(name, version, variant);
|
|
1840
|
+
// BUILD_TARGET will be replaced by values like esm, cjs, etc during the compilation
|
|
1841
|
+
registerVersion(name, version, 'esm2020');
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
/**
|
|
1845
|
+
* @license
|
|
1846
|
+
* Copyright 2025 Google LLC
|
|
1847
|
+
*
|
|
1848
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1849
|
+
* you may not use this file except in compliance with the License.
|
|
1850
|
+
* You may obtain a copy of the License at
|
|
1851
|
+
*
|
|
1852
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1853
|
+
*
|
|
1854
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1855
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1856
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1857
|
+
* See the License for the specific language governing permissions and
|
|
1858
|
+
* limitations under the License.
|
|
1859
|
+
*/
|
|
1860
|
+
const QueryFetchPolicy = {
|
|
1861
|
+
PREFER_CACHE: 'PREFER_CACHE',
|
|
1862
|
+
CACHE_ONLY: 'CACHE_ONLY',
|
|
1863
|
+
SERVER_ONLY: 'SERVER_ONLY'
|
|
1864
|
+
};
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* @license
|
|
1868
|
+
* Copyright 2024 Google LLC
|
|
1869
|
+
*
|
|
1870
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1871
|
+
* you may not use this file except in compliance with the License.
|
|
1872
|
+
* You may obtain a copy of the License at
|
|
1873
|
+
*
|
|
1874
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1875
|
+
*
|
|
1876
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1877
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1878
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1879
|
+
* See the License for the specific language governing permissions and
|
|
1880
|
+
* limitations under the License.
|
|
1881
|
+
*/
|
|
1882
|
+
/**
|
|
1883
|
+
* Execute Query
|
|
1884
|
+
* @param queryRef query to execute.
|
|
1885
|
+
* @returns `QueryPromise`
|
|
1886
|
+
*/
|
|
1887
|
+
function executeQuery(queryRef, options) {
|
|
1888
|
+
if (queryRef.refType !== QUERY_STR) {
|
|
1889
|
+
return Promise.reject(new DataConnectError(Code.INVALID_ARGUMENT, `ExecuteQuery can only execute query operations`));
|
|
1890
|
+
}
|
|
1891
|
+
const queryManager = queryRef.dataConnect._queryManager;
|
|
1892
|
+
const fetchPolicy = options?.fetchPolicy ?? QueryFetchPolicy.PREFER_CACHE;
|
|
1893
|
+
switch (fetchPolicy) {
|
|
1894
|
+
case QueryFetchPolicy.SERVER_ONLY:
|
|
1895
|
+
return queryManager.fetchServerResults(queryRef);
|
|
1896
|
+
case QueryFetchPolicy.CACHE_ONLY:
|
|
1897
|
+
return queryManager.fetchCacheResults(queryRef, true);
|
|
1898
|
+
case QueryFetchPolicy.PREFER_CACHE:
|
|
1899
|
+
return queryManager.preferCacheResults(queryRef, false);
|
|
1900
|
+
default:
|
|
1901
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, `Invalid fetch policy: ${fetchPolicy}`);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Execute Query
|
|
1906
|
+
* @param dcInstance Data Connect instance to use.
|
|
1907
|
+
* @param queryName Query to execute
|
|
1908
|
+
* @param variables Variables to execute with
|
|
1909
|
+
* @param initialCache initial cache to use for client hydration
|
|
1910
|
+
* @returns `QueryRef`
|
|
1911
|
+
*/
|
|
1912
|
+
function queryRef(dcInstance, queryName, variables, initialCache) {
|
|
1913
|
+
dcInstance.setInitialized();
|
|
1914
|
+
if (initialCache !== undefined) {
|
|
1915
|
+
dcInstance._queryManager.updateSSR(initialCache);
|
|
1916
|
+
}
|
|
1917
|
+
return {
|
|
1918
|
+
dataConnect: dcInstance,
|
|
1919
|
+
refType: QUERY_STR,
|
|
1920
|
+
name: queryName,
|
|
1921
|
+
variables: variables
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Converts serialized ref to query ref
|
|
1926
|
+
* @param serializedRef ref to convert to `QueryRef`
|
|
1927
|
+
* @returns `QueryRef`
|
|
1928
|
+
*/
|
|
1929
|
+
function toQueryRef(serializedRef) {
|
|
1930
|
+
const { refInfo: { name, variables, connectorConfig } } = serializedRef;
|
|
1931
|
+
return queryRef(getDataConnect(connectorConfig), name, variables);
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
/**
|
|
1935
|
+
* @license
|
|
1936
|
+
* Copyright 2024 Google LLC
|
|
1937
|
+
*
|
|
1938
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1939
|
+
* you may not use this file except in compliance with the License.
|
|
1940
|
+
* You may obtain a copy of the License at
|
|
1941
|
+
*
|
|
1942
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1943
|
+
*
|
|
1944
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1945
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1946
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1947
|
+
* See the License for the specific language governing permissions and
|
|
1948
|
+
* limitations under the License.
|
|
1949
|
+
*/
|
|
1950
|
+
/**
|
|
1951
|
+
* The generated SDK will allow the user to pass in either the variable or the data connect instance with the variable,
|
|
1952
|
+
* and this function validates the variables and returns back the DataConnect instance and variables based on the arguments passed in.
|
|
1953
|
+
* @param connectorConfig
|
|
1954
|
+
* @param dcOrVars
|
|
1955
|
+
* @param vars
|
|
1956
|
+
* @param validateVars
|
|
1957
|
+
* @returns {DataConnect} and {Variables} instance
|
|
1958
|
+
* @internal
|
|
1959
|
+
*/
|
|
1960
|
+
function validateArgs(connectorConfig, dcOrVars, vars, validateVars) {
|
|
1961
|
+
let dcInstance;
|
|
1962
|
+
let realVars;
|
|
1963
|
+
if (dcOrVars && 'enableEmulator' in dcOrVars) {
|
|
1964
|
+
dcInstance = dcOrVars;
|
|
1965
|
+
realVars = vars;
|
|
1966
|
+
}
|
|
1967
|
+
else {
|
|
1968
|
+
dcInstance = getDataConnect(connectorConfig);
|
|
1969
|
+
realVars = dcOrVars;
|
|
1970
|
+
}
|
|
1971
|
+
if (!dcInstance || (!realVars && validateVars)) {
|
|
1972
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, 'Variables required.');
|
|
1973
|
+
}
|
|
1974
|
+
return { dc: dcInstance, vars: realVars };
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
/**
|
|
1978
|
+
* @license
|
|
1979
|
+
* Copyright 2025 Google LLC
|
|
1980
|
+
*
|
|
1981
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1982
|
+
* you may not use this file except in compliance with the License.
|
|
1983
|
+
* You may obtain a copy of the License at
|
|
1984
|
+
*
|
|
1985
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1986
|
+
*
|
|
1987
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1988
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1989
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1990
|
+
* See the License for the specific language governing permissions and
|
|
1991
|
+
* limitations under the License.
|
|
1992
|
+
*/
|
|
1993
|
+
/**
|
|
1994
|
+
* Subscribe to a `QueryRef`
|
|
1995
|
+
* @param queryRefOrSerializedResult query ref or serialized result.
|
|
1996
|
+
* @param observerOrOnNext observer object or next function.
|
|
1997
|
+
* @param onError Callback to call when error gets thrown.
|
|
1998
|
+
* @param onComplete Called when subscription completes.
|
|
1999
|
+
* @returns `SubscriptionOptions`
|
|
2000
|
+
*/
|
|
2001
|
+
function subscribe(queryRefOrSerializedResult, observerOrOnNext, onError, onComplete) {
|
|
2002
|
+
let ref;
|
|
2003
|
+
let initialCache;
|
|
2004
|
+
if ('refInfo' in queryRefOrSerializedResult) {
|
|
2005
|
+
const serializedRef = queryRefOrSerializedResult;
|
|
2006
|
+
const { data, source, fetchTime } = serializedRef;
|
|
2007
|
+
ref = toQueryRef(serializedRef);
|
|
2008
|
+
initialCache = {
|
|
2009
|
+
data,
|
|
2010
|
+
source,
|
|
2011
|
+
fetchTime,
|
|
2012
|
+
ref,
|
|
2013
|
+
toJSON: getRefSerializer(ref, data, source, fetchTime)
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
else {
|
|
2017
|
+
ref = queryRefOrSerializedResult;
|
|
2018
|
+
}
|
|
2019
|
+
let onResult = undefined;
|
|
2020
|
+
if (typeof observerOrOnNext === 'function') {
|
|
2021
|
+
onResult = observerOrOnNext;
|
|
2022
|
+
}
|
|
2023
|
+
else {
|
|
2024
|
+
onResult = observerOrOnNext.onNext;
|
|
2025
|
+
onError = observerOrOnNext.onErr;
|
|
2026
|
+
onComplete = observerOrOnNext.onComplete;
|
|
2027
|
+
}
|
|
2028
|
+
if (!onResult) {
|
|
2029
|
+
throw new DataConnectError(Code.INVALID_ARGUMENT, 'Must provide onNext');
|
|
2030
|
+
}
|
|
2031
|
+
return ref.dataConnect._queryManager.addSubscription(ref, onResult, onComplete, onError, initialCache);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
/**
|
|
2035
|
+
* @license
|
|
2036
|
+
* Copyright 2024 Google LLC
|
|
2037
|
+
*
|
|
2038
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
2039
|
+
* you may not use this file except in compliance with the License.
|
|
2040
|
+
* You may obtain a copy of the License at
|
|
2041
|
+
*
|
|
2042
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
2043
|
+
*
|
|
2044
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2045
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
2046
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2047
|
+
* See the License for the specific language governing permissions and
|
|
2048
|
+
* limitations under the License.
|
|
2049
|
+
*/
|
|
2050
|
+
initializeFetch(fetch);
|
|
2051
|
+
registerDataConnect('node');
|
|
2052
|
+
|
|
2053
|
+
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 };
|
|
2054
|
+
//# sourceMappingURL=index.node.esm.js.map
|