@twin.org/entity-storage-connector-gcp-firestore 0.0.2-next.9 → 0.0.3-next.2
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 +1 -1
- package/dist/{esm/index.mjs → es/firestoreEntityStorageConnector.js} +105 -87
- package/dist/es/firestoreEntityStorageConnector.js.map +1 -0
- package/dist/es/index.js +6 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/models/IFirestoreEntityStorageConnectorConfig.js +4 -0
- package/dist/es/models/IFirestoreEntityStorageConnectorConfig.js.map +1 -0
- package/dist/es/models/IFirestoreEntityStorageConnectorConstructorOptions.js +2 -0
- package/dist/es/models/IFirestoreEntityStorageConnectorConstructorOptions.js.map +1 -0
- package/dist/types/firestoreEntityStorageConnector.d.ts +14 -9
- package/dist/types/index.d.ts +3 -3
- package/dist/types/models/IFirestoreEntityStorageConnectorConfig.d.ts +1 -1
- package/dist/types/models/IFirestoreEntityStorageConnectorConstructorOptions.d.ts +5 -1
- package/docs/changelog.md +63 -0
- package/docs/reference/classes/FirestoreEntityStorageConnector.md +38 -24
- package/docs/reference/interfaces/IFirestoreEntityStorageConnectorConfig.md +1 -1
- package/docs/reference/interfaces/IFirestoreEntityStorageConnectorConstructorOptions.md +8 -0
- package/locales/en.json +1 -5
- package/package.json +13 -10
- package/dist/cjs/index.cjs +0 -389
- package/dist/types/models/IEntityWithIndexing.d.ts +0 -14
- package/dist/types/models/IValueType.d.ts +0 -13
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install @twin.org/entity-storage-connector-gcp-firestore
|
|
|
13
13
|
The tests developed are functional tests and need an instance of Firestore up and running. To run Firestore locally:
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
docker run -d --name twin-entity-storage-firestore -p
|
|
16
|
+
docker run -d --name twin-entity-storage-firestore -p 20200:8080 gcr.io/google.com/cloudsdktool/cloud-sdk:emulators gcloud beta emulators firestore start --host-port=0.0.0.0:8080
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Afterwards you can run the tests as follows:
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
import { Firestore } from '@google-cloud/firestore';
|
|
2
|
-
import { Guards, Is, ObjectHelper, Converter, ComponentFactory, BaseError, GeneralError } from '@twin.org/core';
|
|
3
|
-
import { EntitySchemaFactory, EntitySchemaHelper, SortDirection, ComparisonOperator } from '@twin.org/entity';
|
|
4
|
-
|
|
5
1
|
// Copyright 2024 IOTA Stiftung.
|
|
6
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { Firestore } from "@google-cloud/firestore";
|
|
4
|
+
import { ContextIdHelper, ContextIdStore } from "@twin.org/context";
|
|
5
|
+
import { BaseError, ComponentFactory, Converter, GeneralError, Guards, Is, ObjectHelper } from "@twin.org/core";
|
|
6
|
+
import { ComparisonOperator, EntityConditions, EntitySchemaFactory, EntitySchemaHelper, SortDirection } from "@twin.org/entity";
|
|
7
7
|
/**
|
|
8
8
|
* Class for performing entity storage operations using Firestore.
|
|
9
9
|
*/
|
|
10
|
-
class FirestoreEntityStorageConnector {
|
|
10
|
+
export class FirestoreEntityStorageConnector {
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @internal
|
|
12
|
+
* Runtime name for the class.
|
|
14
13
|
*/
|
|
15
|
-
static
|
|
14
|
+
static CLASS_NAME = "FirestoreEntityStorageConnector";
|
|
16
15
|
/**
|
|
17
|
-
*
|
|
16
|
+
* Limit the number of entities when finding.
|
|
17
|
+
* @internal
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
static _DEFAULT_LIMIT = 40;
|
|
20
20
|
/**
|
|
21
21
|
* The schema for the entity.
|
|
22
22
|
* @internal
|
|
23
23
|
*/
|
|
24
24
|
_entitySchema;
|
|
25
|
+
/**
|
|
26
|
+
* The keys to use from the context ids to create partitions.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
_partitionContextIds;
|
|
25
30
|
/**
|
|
26
31
|
* The primary key.
|
|
27
32
|
* @internal
|
|
@@ -37,28 +42,24 @@ class FirestoreEntityStorageConnector {
|
|
|
37
42
|
* @internal
|
|
38
43
|
*/
|
|
39
44
|
_firestoreClient;
|
|
40
|
-
/**
|
|
41
|
-
* The Firestore collection.
|
|
42
|
-
* @internal
|
|
43
|
-
*/
|
|
44
|
-
_collection;
|
|
45
45
|
/**
|
|
46
46
|
* Create a new instance of FirestoreEntityStorageConnector.
|
|
47
47
|
* @param options The options for the connector.
|
|
48
48
|
*/
|
|
49
49
|
constructor(options) {
|
|
50
|
-
Guards.object(
|
|
51
|
-
Guards.stringValue(
|
|
52
|
-
Guards.object(
|
|
53
|
-
Guards.stringValue(
|
|
54
|
-
Guards.stringValue(
|
|
50
|
+
Guards.object(FirestoreEntityStorageConnector.CLASS_NAME, "options", options);
|
|
51
|
+
Guards.stringValue(FirestoreEntityStorageConnector.CLASS_NAME, "options.entitySchema", options.entitySchema);
|
|
52
|
+
Guards.object(FirestoreEntityStorageConnector.CLASS_NAME, "options.config", options.config);
|
|
53
|
+
Guards.stringValue(FirestoreEntityStorageConnector.CLASS_NAME, "options.config.projectId", options.config.projectId);
|
|
54
|
+
Guards.stringValue(FirestoreEntityStorageConnector.CLASS_NAME, "options.config.collectionName", options.config.collectionName);
|
|
55
55
|
let credentials;
|
|
56
56
|
if (!Is.empty(options.config.credentials)) {
|
|
57
|
-
Guards.stringBase64(
|
|
57
|
+
Guards.stringBase64(FirestoreEntityStorageConnector.CLASS_NAME, "options.config.credentials", options.config.credentials);
|
|
58
58
|
credentials = ObjectHelper.fromBytes(Converter.base64ToBytes(options.config.credentials));
|
|
59
59
|
}
|
|
60
60
|
this._config = options.config;
|
|
61
61
|
this._entitySchema = EntitySchemaFactory.get(options.entitySchema);
|
|
62
|
+
this._partitionContextIds = options.partitionContextIds;
|
|
62
63
|
this._primaryKey = EntitySchemaHelper.getPrimaryKey(this._entitySchema);
|
|
63
64
|
const firestoreOptions = {
|
|
64
65
|
projectId: this._config.projectId,
|
|
@@ -73,7 +74,20 @@ class FirestoreEntityStorageConnector {
|
|
|
73
74
|
firestoreOptions.ssl = false;
|
|
74
75
|
}
|
|
75
76
|
this._firestoreClient = new Firestore(firestoreOptions);
|
|
76
|
-
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns the class name of the component.
|
|
80
|
+
* @returns The class name of the component.
|
|
81
|
+
*/
|
|
82
|
+
className() {
|
|
83
|
+
return FirestoreEntityStorageConnector.CLASS_NAME;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get the schema for the entities.
|
|
87
|
+
* @returns The schema for the entities.
|
|
88
|
+
*/
|
|
89
|
+
getSchema() {
|
|
90
|
+
return this._entitySchema;
|
|
77
91
|
}
|
|
78
92
|
/**
|
|
79
93
|
* Bootstrap the component by creating and initializing any resources it needs.
|
|
@@ -85,7 +99,7 @@ class FirestoreEntityStorageConnector {
|
|
|
85
99
|
try {
|
|
86
100
|
await nodeLogging?.log({
|
|
87
101
|
level: "info",
|
|
88
|
-
source:
|
|
102
|
+
source: FirestoreEntityStorageConnector.CLASS_NAME,
|
|
89
103
|
ts: Date.now(),
|
|
90
104
|
message: "firestoreCreating",
|
|
91
105
|
data: {
|
|
@@ -100,7 +114,7 @@ class FirestoreEntityStorageConnector {
|
|
|
100
114
|
await testDoc.delete();
|
|
101
115
|
await nodeLogging?.log({
|
|
102
116
|
level: "info",
|
|
103
|
-
source:
|
|
117
|
+
source: FirestoreEntityStorageConnector.CLASS_NAME,
|
|
104
118
|
ts: Date.now(),
|
|
105
119
|
message: "firestoreCreated",
|
|
106
120
|
data: {
|
|
@@ -113,7 +127,7 @@ class FirestoreEntityStorageConnector {
|
|
|
113
127
|
catch (err) {
|
|
114
128
|
await nodeLogging?.log({
|
|
115
129
|
level: "error",
|
|
116
|
-
source:
|
|
130
|
+
source: FirestoreEntityStorageConnector.CLASS_NAME,
|
|
117
131
|
ts: Date.now(),
|
|
118
132
|
message: "firestoreCreationFailed",
|
|
119
133
|
error: BaseError.fromError(err),
|
|
@@ -125,13 +139,6 @@ class FirestoreEntityStorageConnector {
|
|
|
125
139
|
return false;
|
|
126
140
|
}
|
|
127
141
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Get the schema for the entities.
|
|
130
|
-
* @returns The schema for the entities.
|
|
131
|
-
*/
|
|
132
|
-
getSchema() {
|
|
133
|
-
return this._entitySchema;
|
|
134
|
-
}
|
|
135
142
|
/**
|
|
136
143
|
* Get an entity.
|
|
137
144
|
* @param id The id of the entity to get.
|
|
@@ -140,17 +147,20 @@ class FirestoreEntityStorageConnector {
|
|
|
140
147
|
* @returns The object if it can be found or undefined.
|
|
141
148
|
*/
|
|
142
149
|
async get(id, secondaryIndex, conditions) {
|
|
143
|
-
Guards.stringValue(
|
|
150
|
+
Guards.stringValue(FirestoreEntityStorageConnector.CLASS_NAME, "id", id);
|
|
151
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
152
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
144
153
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
154
|
+
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
155
|
+
if (!Is.arrayValue(conditions)) {
|
|
156
|
+
const docRef = collection.doc(id);
|
|
147
157
|
const doc = await docRef.get();
|
|
148
158
|
if (doc.exists) {
|
|
149
159
|
return doc.data();
|
|
150
160
|
}
|
|
151
161
|
}
|
|
152
|
-
// Use
|
|
153
|
-
let query =
|
|
162
|
+
// Use conditions to construct a query
|
|
163
|
+
let query = collection;
|
|
154
164
|
if (secondaryIndex) {
|
|
155
165
|
query = query.where(secondaryIndex, "==", id);
|
|
156
166
|
}
|
|
@@ -165,11 +175,12 @@ class FirestoreEntityStorageConnector {
|
|
|
165
175
|
}
|
|
166
176
|
const querySnapshot = await query.limit(1).get();
|
|
167
177
|
if (!querySnapshot.empty) {
|
|
168
|
-
|
|
178
|
+
const entity = querySnapshot.docs[0].data();
|
|
179
|
+
return entity;
|
|
169
180
|
}
|
|
170
181
|
}
|
|
171
182
|
catch (err) {
|
|
172
|
-
throw new GeneralError(
|
|
183
|
+
throw new GeneralError(FirestoreEntityStorageConnector.CLASS_NAME, "getEntityFailed", { id }, err);
|
|
173
184
|
}
|
|
174
185
|
}
|
|
175
186
|
/**
|
|
@@ -179,46 +190,40 @@ class FirestoreEntityStorageConnector {
|
|
|
179
190
|
* @returns Nothing.
|
|
180
191
|
*/
|
|
181
192
|
async set(entity, conditions) {
|
|
182
|
-
Guards.object(
|
|
193
|
+
Guards.object(FirestoreEntityStorageConnector.CLASS_NAME, "entity", entity);
|
|
194
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
195
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
183
196
|
EntitySchemaHelper.validateEntity(entity, this.getSchema());
|
|
184
197
|
try {
|
|
185
198
|
const id = entity[this._primaryKey.property];
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
if (entityCopy.valueArray && Is.array(entityCopy.valueArray)) {
|
|
189
|
-
const valueArrayFields = entityCopy.valueArray
|
|
190
|
-
.filter((item) => Is.notEmpty(item))
|
|
191
|
-
.map(item => `${item.field}:${item.value}`);
|
|
192
|
-
entityCopy.valueArrayFields = valueArrayFields;
|
|
193
|
-
}
|
|
194
|
-
const docRef = this._collection.doc(id);
|
|
199
|
+
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
200
|
+
const docRef = collection.doc(id);
|
|
195
201
|
if (!Is.arrayValue(conditions)) {
|
|
196
|
-
await docRef.set(
|
|
202
|
+
await docRef.set(entity);
|
|
197
203
|
}
|
|
198
204
|
else {
|
|
199
205
|
await this._firestoreClient.runTransaction(async (transaction) => {
|
|
200
206
|
const docSnapshot = await transaction.get(docRef);
|
|
201
207
|
if (!docSnapshot.exists) {
|
|
202
|
-
transaction.set(docRef,
|
|
208
|
+
transaction.set(docRef, entity);
|
|
203
209
|
}
|
|
204
210
|
else {
|
|
205
211
|
const data = docSnapshot.data();
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
transaction.set(docRef, entityCopy);
|
|
212
|
+
if (EntityConditions.check(data, {
|
|
213
|
+
conditions: conditions.map(c => ({
|
|
214
|
+
property: c.property,
|
|
215
|
+
comparison: ComparisonOperator.Equals,
|
|
216
|
+
value: c.value
|
|
217
|
+
}))
|
|
218
|
+
})) {
|
|
219
|
+
transaction.set(docRef, entity);
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
});
|
|
218
223
|
}
|
|
219
224
|
}
|
|
220
225
|
catch (err) {
|
|
221
|
-
throw new GeneralError(
|
|
226
|
+
throw new GeneralError(FirestoreEntityStorageConnector.CLASS_NAME, "setEntityFailed", { id: entity.id }, err);
|
|
222
227
|
}
|
|
223
228
|
}
|
|
224
229
|
/**
|
|
@@ -228,9 +233,12 @@ class FirestoreEntityStorageConnector {
|
|
|
228
233
|
* @returns Nothing.
|
|
229
234
|
*/
|
|
230
235
|
async remove(id, conditions) {
|
|
231
|
-
Guards.stringValue(
|
|
236
|
+
Guards.stringValue(FirestoreEntityStorageConnector.CLASS_NAME, "id", id);
|
|
237
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
238
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
232
239
|
try {
|
|
233
|
-
const
|
|
240
|
+
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
241
|
+
const docRef = collection.doc(id);
|
|
234
242
|
if (!Is.arrayValue(conditions)) {
|
|
235
243
|
await docRef.delete();
|
|
236
244
|
}
|
|
@@ -239,14 +247,13 @@ class FirestoreEntityStorageConnector {
|
|
|
239
247
|
const docSnapshot = await transaction.get(docRef);
|
|
240
248
|
if (docSnapshot.exists) {
|
|
241
249
|
const data = docSnapshot.data();
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
if (conditionsMet) {
|
|
250
|
+
if (EntityConditions.check(data, {
|
|
251
|
+
conditions: conditions.map(c => ({
|
|
252
|
+
property: c.property,
|
|
253
|
+
comparison: ComparisonOperator.Equals,
|
|
254
|
+
value: c.value
|
|
255
|
+
}))
|
|
256
|
+
})) {
|
|
250
257
|
transaction.delete(docRef);
|
|
251
258
|
}
|
|
252
259
|
}
|
|
@@ -254,7 +261,7 @@ class FirestoreEntityStorageConnector {
|
|
|
254
261
|
}
|
|
255
262
|
}
|
|
256
263
|
catch (err) {
|
|
257
|
-
throw new GeneralError(
|
|
264
|
+
throw new GeneralError(FirestoreEntityStorageConnector.CLASS_NAME, "removeEntityFailed", { id }, err);
|
|
258
265
|
}
|
|
259
266
|
}
|
|
260
267
|
/**
|
|
@@ -262,15 +269,18 @@ class FirestoreEntityStorageConnector {
|
|
|
262
269
|
* @param conditions The conditions to match for the entities.
|
|
263
270
|
* @param sortProperties The optional sort order.
|
|
264
271
|
* @param properties The optional properties to return, defaults to all.
|
|
265
|
-
* @param cursor The cursor to request the next
|
|
266
|
-
* @param
|
|
272
|
+
* @param cursor The cursor to request the next chunk of entities.
|
|
273
|
+
* @param limit The suggested number of entities to return in each chunk.
|
|
267
274
|
* @returns The matching entities and a cursor for the next page.
|
|
268
275
|
*/
|
|
269
|
-
async query(conditions, sortProperties, properties, cursor,
|
|
276
|
+
async query(conditions, sortProperties, properties, cursor, limit) {
|
|
270
277
|
const queryDescription = [];
|
|
278
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
279
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
271
280
|
try {
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
282
|
+
let query = collection;
|
|
283
|
+
if (!Is.empty(conditions)) {
|
|
274
284
|
query = this.applyConditions(query, conditions);
|
|
275
285
|
queryDescription.push(`Conditions: ${JSON.stringify(conditions)}`);
|
|
276
286
|
}
|
|
@@ -287,9 +297,9 @@ class FirestoreEntityStorageConnector {
|
|
|
287
297
|
}
|
|
288
298
|
queryDescription.push(`Cursor: ${cursor}`);
|
|
289
299
|
}
|
|
290
|
-
const
|
|
291
|
-
query = query.limit(
|
|
292
|
-
queryDescription.push(`Limit: ${
|
|
300
|
+
const finalLimit = limit ?? FirestoreEntityStorageConnector._DEFAULT_LIMIT;
|
|
301
|
+
query = query.limit(finalLimit);
|
|
302
|
+
queryDescription.push(`Limit: ${finalLimit}`);
|
|
293
303
|
if (properties) {
|
|
294
304
|
query = query.select(...properties);
|
|
295
305
|
queryDescription.push(`Properties: ${properties.join(", ")}`);
|
|
@@ -297,7 +307,7 @@ class FirestoreEntityStorageConnector {
|
|
|
297
307
|
const querySnapshot = await query.get();
|
|
298
308
|
const entities = querySnapshot.docs.map((doc) => doc.data());
|
|
299
309
|
let nextCursor;
|
|
300
|
-
if (entities.length ===
|
|
310
|
+
if (entities.length === finalLimit) {
|
|
301
311
|
nextCursor = querySnapshot.docs[querySnapshot.docs.length - 1].ref.path;
|
|
302
312
|
}
|
|
303
313
|
return {
|
|
@@ -306,7 +316,7 @@ class FirestoreEntityStorageConnector {
|
|
|
306
316
|
};
|
|
307
317
|
}
|
|
308
318
|
catch (err) {
|
|
309
|
-
throw new GeneralError(
|
|
319
|
+
throw new GeneralError(FirestoreEntityStorageConnector.CLASS_NAME, "queryFailed", { queryDescription: queryDescription.join("; ") }, err);
|
|
310
320
|
}
|
|
311
321
|
}
|
|
312
322
|
/**
|
|
@@ -315,14 +325,14 @@ class FirestoreEntityStorageConnector {
|
|
|
315
325
|
* @internal
|
|
316
326
|
*/
|
|
317
327
|
async collectionDelete() {
|
|
318
|
-
const collection = this.
|
|
328
|
+
const collection = this._firestoreClient.collection(this.collectionName());
|
|
319
329
|
const batchSize = 500;
|
|
320
330
|
const query = collection.limit(batchSize);
|
|
321
331
|
try {
|
|
322
332
|
await this.deleteQueryBatch(query, batchSize);
|
|
323
333
|
}
|
|
324
334
|
catch (error) {
|
|
325
|
-
throw new GeneralError(
|
|
335
|
+
throw new GeneralError(FirestoreEntityStorageConnector.CLASS_NAME, "collectionDeleteFailed", { collectionName: this._config.collectionName }, error);
|
|
326
336
|
}
|
|
327
337
|
}
|
|
328
338
|
/**
|
|
@@ -359,8 +369,9 @@ class FirestoreEntityStorageConnector {
|
|
|
359
369
|
return query.where(property, "in", value);
|
|
360
370
|
case ComparisonOperator.Includes:
|
|
361
371
|
return query.where(property, "array-contains", value);
|
|
372
|
+
case ComparisonOperator.NotIncludes:
|
|
362
373
|
default:
|
|
363
|
-
throw new GeneralError(
|
|
374
|
+
throw new GeneralError(FirestoreEntityStorageConnector.CLASS_NAME, "unsupportedComparisonOperator", { comparison });
|
|
364
375
|
}
|
|
365
376
|
}
|
|
366
377
|
/**
|
|
@@ -382,6 +393,13 @@ class FirestoreEntityStorageConnector {
|
|
|
382
393
|
await this.deleteQueryBatch(query, batchSize);
|
|
383
394
|
}
|
|
384
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Get the collection name based on partition key.
|
|
398
|
+
* @returns The collection name.
|
|
399
|
+
* @internal
|
|
400
|
+
*/
|
|
401
|
+
collectionName(partitionKey) {
|
|
402
|
+
return `${this._config.collectionName}_${partitionKey ?? "default"}`;
|
|
403
|
+
}
|
|
385
404
|
}
|
|
386
|
-
|
|
387
|
-
export { FirestoreEntityStorageConnector };
|
|
405
|
+
//# sourceMappingURL=firestoreEntityStorageConnector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firestoreEntityStorageConnector.js","sourceRoot":"","sources":["../../src/firestoreEntityStorageConnector.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\ttype DocumentSnapshot,\n\tFirestore,\n\ttype Query,\n\ttype Settings\n} from \"@google-cloud/firestore\";\nimport { ContextIdHelper, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tBaseError,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tObjectHelper\n} from \"@twin.org/core\";\nimport {\n\tComparisonOperator,\n\ttype EntityCondition,\n\tEntityConditions,\n\tEntitySchemaFactory,\n\tEntitySchemaHelper,\n\ttype IEntitySchema,\n\ttype IEntitySchemaProperty,\n\tSortDirection\n} from \"@twin.org/entity\";\nimport type { IEntityStorageConnector } from \"@twin.org/entity-storage-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { JWTInput } from \"google-auth-library\";\nimport type { IFirestoreEntityStorageConnectorConfig } from \"./models/IFirestoreEntityStorageConnectorConfig.js\";\nimport type { IFirestoreEntityStorageConnectorConstructorOptions } from \"./models/IFirestoreEntityStorageConnectorConstructorOptions.js\";\n\n/**\n * Class for performing entity storage operations using Firestore.\n */\nexport class FirestoreEntityStorageConnector<T = unknown> implements IEntityStorageConnector<T> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<FirestoreEntityStorageConnector>();\n\n\t/**\n\t * Limit the number of entities when finding.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_LIMIT: number = 40;\n\n\t/**\n\t * The schema for the entity.\n\t * @internal\n\t */\n\tprivate readonly _entitySchema: IEntitySchema<T>;\n\n\t/**\n\t * The keys to use from the context ids to create partitions.\n\t * @internal\n\t */\n\tprivate readonly _partitionContextIds?: string[];\n\n\t/**\n\t * The primary key.\n\t * @internal\n\t */\n\tprivate readonly _primaryKey: IEntitySchemaProperty<T>;\n\n\t/**\n\t * The configuration for the connector.\n\t * @internal\n\t */\n\tprivate readonly _config: IFirestoreEntityStorageConnectorConfig;\n\n\t/**\n\t * The Firestore client.\n\t * @internal\n\t */\n\tprivate readonly _firestoreClient: Firestore;\n\n\t/**\n\t * Create a new instance of FirestoreEntityStorageConnector.\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options: IFirestoreEntityStorageConnectorConstructorOptions) {\n\t\tGuards.object(FirestoreEntityStorageConnector.CLASS_NAME, nameof(options), options);\n\t\tGuards.stringValue(\n\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.entitySchema),\n\t\t\toptions.entitySchema\n\t\t);\n\t\tGuards.object<IFirestoreEntityStorageConnectorConfig>(\n\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.config),\n\t\t\toptions.config\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.config.projectId),\n\t\t\toptions.config.projectId\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.config.collectionName),\n\t\t\toptions.config.collectionName\n\t\t);\n\n\t\tlet credentials: JWTInput | undefined;\n\t\tif (!Is.empty(options.config.credentials)) {\n\t\t\tGuards.stringBase64(\n\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\tnameof(options.config.credentials),\n\t\t\t\toptions.config.credentials\n\t\t\t);\n\t\t\tcredentials = ObjectHelper.fromBytes<JWTInput>(\n\t\t\t\tConverter.base64ToBytes(options.config.credentials)\n\t\t\t);\n\t\t}\n\n\t\tthis._config = options.config;\n\t\tthis._entitySchema = EntitySchemaFactory.get(options.entitySchema);\n\t\tthis._partitionContextIds = options.partitionContextIds;\n\t\tthis._primaryKey = EntitySchemaHelper.getPrimaryKey<T>(this._entitySchema);\n\n\t\tconst firestoreOptions: Settings = {\n\t\t\tprojectId: this._config.projectId,\n\t\t\tdatabaseId: this._config.databaseId,\n\t\t\tcollectionName: this._config.collectionName,\n\t\t\tmaxIdleChannels: this._config.settings?.maxIdleChannels,\n\t\t\ttimeout: this._config.settings?.timeout,\n\t\t\tcredentials\n\t\t};\n\n\t\tif (Is.stringValue(this._config.endpoint)) {\n\t\t\tfirestoreOptions.host = this._config.endpoint;\n\t\t\tfirestoreOptions.ssl = false;\n\t\t}\n\n\t\tthis._firestoreClient = new Firestore(firestoreOptions);\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn FirestoreEntityStorageConnector.CLASS_NAME;\n\t}\n\n\t/**\n\t * Get the schema for the entities.\n\t * @returns The schema for the entities.\n\t */\n\tpublic getSchema(): IEntitySchema {\n\t\treturn this._entitySchema as IEntitySchema;\n\t}\n\n\t/**\n\t * Bootstrap the component by creating and initializing any resources it needs.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns True if the bootstrapping process was successful.\n\t */\n\tpublic async bootstrap(nodeLoggingComponentType?: string): Promise<boolean> {\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\n\t\ttry {\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"firestoreCreating\",\n\t\t\t\tdata: {\n\t\t\t\t\tprojectId: this._config.projectId,\n\t\t\t\t\tcollectionName: this._config.collectionName\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Firestore doesn't require explicit collection creation\n\t\t\t// Perform a small write operation to ensure connectivity\n\t\t\tconst testDoc = this._firestoreClient.collection(this._config.collectionName).doc(\"test\");\n\t\t\tawait testDoc.set({ test: true });\n\t\t\tawait testDoc.delete();\n\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"firestoreCreated\",\n\t\t\t\tdata: {\n\t\t\t\t\tprojectId: this._config.projectId,\n\t\t\t\t\tcollectionName: this._config.collectionName\n\t\t\t\t}\n\t\t\t});\n\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: FirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"firestoreCreationFailed\",\n\t\t\t\terror: BaseError.fromError(err),\n\t\t\t\tdata: {\n\t\t\t\t\tprojectId: this._config.projectId,\n\t\t\t\t\tcollectionName: this._config.collectionName\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Get an entity.\n\t * @param id The id of the entity to get.\n\t * @param secondaryIndex The optional secondary index to use.\n\t * @param conditions The optional conditions to apply to the query.\n\t * @returns The object if it can be found or undefined.\n\t */\n\tpublic async get(\n\t\tid: string,\n\t\tsecondaryIndex?: keyof T,\n\t\tconditions?: { property: keyof T; value: unknown }[]\n\t): Promise<T | undefined> {\n\t\tGuards.stringValue(FirestoreEntityStorageConnector.CLASS_NAME, nameof(id), id);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\ttry {\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\n\t\t\tif (!Is.arrayValue(conditions)) {\n\t\t\t\tconst docRef = collection.doc(id);\n\t\t\t\tconst doc = await docRef.get();\n\n\t\t\t\tif (doc.exists) {\n\t\t\t\t\treturn doc.data() as T;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Use conditions to construct a query\n\t\t\tlet query: Query = collection;\n\n\t\t\tif (secondaryIndex) {\n\t\t\t\tquery = query.where(secondaryIndex as string, \"==\", id);\n\t\t\t} else {\n\t\t\t\t// If no secondaryIndex, include primary key in conditions\n\t\t\t\tquery = query.where(this._primaryKey.property as string, \"==\", id);\n\t\t\t}\n\n\t\t\tif (Is.arrayValue(conditions)) {\n\t\t\t\tfor (const condition of conditions) {\n\t\t\t\t\tquery = query.where(condition.property as string, \"==\", condition.value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst querySnapshot = await query.limit(1).get();\n\t\t\tif (!querySnapshot.empty) {\n\t\t\t\tconst entity = querySnapshot.docs[0].data() as T;\n\t\t\t\treturn entity;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"getEntityFailed\",\n\t\t\t\t{ id },\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Set an entity.\n\t * @param entity The entity to set.\n\t * @param conditions The optional conditions to apply to the update.\n\t * @returns Nothing.\n\t */\n\tpublic async set(entity: T, conditions?: { property: keyof T; value: unknown }[]): Promise<void> {\n\t\tGuards.object(FirestoreEntityStorageConnector.CLASS_NAME, nameof(entity), entity);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tEntitySchemaHelper.validateEntity(entity, this.getSchema());\n\n\t\ttry {\n\t\t\tconst id = entity[this._primaryKey.property] as string;\n\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\n\t\t\tconst docRef = collection.doc(id);\n\n\t\t\tif (!Is.arrayValue(conditions)) {\n\t\t\t\tawait docRef.set(entity);\n\t\t\t} else {\n\t\t\t\tawait this._firestoreClient.runTransaction(async transaction => {\n\t\t\t\t\tconst docSnapshot = await transaction.get(docRef);\n\n\t\t\t\t\tif (!docSnapshot.exists) {\n\t\t\t\t\t\ttransaction.set(docRef, entity);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst data = docSnapshot.data() as T;\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tEntityConditions.check(data, {\n\t\t\t\t\t\t\t\tconditions: conditions.map(c => ({\n\t\t\t\t\t\t\t\t\tproperty: c.property as string,\n\t\t\t\t\t\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\t\t\t\t\t\tvalue: c.value\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\ttransaction.set(docRef, entity);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"setEntityFailed\",\n\t\t\t\t{ id: entity.id },\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Remove the entity.\n\t * @param id The id of the entity to remove.\n\t * @param conditions The optional conditions to apply to the delete.\n\t * @returns Nothing.\n\t */\n\tpublic async remove(\n\t\tid: string,\n\t\tconditions?: { property: keyof T; value: unknown }[]\n\t): Promise<void> {\n\t\tGuards.stringValue(FirestoreEntityStorageConnector.CLASS_NAME, nameof(id), id);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\ttry {\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\t\t\tconst docRef = collection.doc(id);\n\n\t\t\tif (!Is.arrayValue(conditions)) {\n\t\t\t\tawait docRef.delete();\n\t\t\t} else {\n\t\t\t\tawait this._firestoreClient.runTransaction(async transaction => {\n\t\t\t\t\tconst docSnapshot = await transaction.get(docRef);\n\n\t\t\t\t\tif (docSnapshot.exists) {\n\t\t\t\t\t\tconst data = docSnapshot.data() as T;\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tEntityConditions.check(data, {\n\t\t\t\t\t\t\t\tconditions: conditions.map(c => ({\n\t\t\t\t\t\t\t\t\tproperty: c.property as string,\n\t\t\t\t\t\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\t\t\t\t\t\tvalue: c.value\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\ttransaction.delete(docRef);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"removeEntityFailed\",\n\t\t\t\t{ id },\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Find all the entities which match the conditions.\n\t * @param conditions The conditions to match for the entities.\n\t * @param sortProperties The optional sort order.\n\t * @param properties The optional properties to return, defaults to all.\n\t * @param cursor The cursor to request the next chunk of entities.\n\t * @param limit The suggested number of entities to return in each chunk.\n\t * @returns The matching entities and a cursor for the next page.\n\t */\n\tpublic async query(\n\t\tconditions?: EntityCondition<T>,\n\t\tsortProperties?: { property: keyof T; sortDirection: SortDirection }[],\n\t\tproperties?: (keyof T)[],\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\t/**\n\t\t * The entities, which can be partial if a limited keys list was provided.\n\t\t */\n\t\tentities: Partial<T>[];\n\t\t/**\n\t\t * An optional cursor, when defined can be used to call find to get more entities.\n\t\t */\n\t\tcursor?: string;\n\t}> {\n\t\tconst queryDescription: string[] = [];\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\ttry {\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\t\t\tlet query = collection as Query;\n\n\t\t\tif (!Is.empty(conditions)) {\n\t\t\t\tquery = this.applyConditions(query, conditions);\n\t\t\t\tqueryDescription.push(`Conditions: ${JSON.stringify(conditions)}`);\n\t\t\t}\n\n\t\t\tif (Is.arrayValue(sortProperties)) {\n\t\t\t\tfor (const { property, sortDirection } of sortProperties) {\n\t\t\t\t\tquery = query.orderBy(\n\t\t\t\t\t\tproperty as string,\n\t\t\t\t\t\tsortDirection === SortDirection.Ascending ? \"asc\" : \"desc\"\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tqueryDescription.push(`Sort: ${JSON.stringify(sortProperties)}`);\n\t\t\t}\n\n\t\t\tif (Is.stringValue(cursor)) {\n\t\t\t\tconst cursorDoc = await this._firestoreClient.doc(cursor).get();\n\t\t\t\tif (cursorDoc?.exists) {\n\t\t\t\t\tquery = query.startAfter(cursorDoc);\n\t\t\t\t}\n\t\t\t\tqueryDescription.push(`Cursor: ${cursor}`);\n\t\t\t}\n\n\t\t\tconst finalLimit = limit ?? FirestoreEntityStorageConnector._DEFAULT_LIMIT;\n\t\t\tquery = query.limit(finalLimit);\n\t\t\tqueryDescription.push(`Limit: ${finalLimit}`);\n\n\t\t\tif (properties) {\n\t\t\t\tquery = query.select(...(properties as string[]));\n\t\t\t\tqueryDescription.push(`Properties: ${properties.join(\", \")}`);\n\t\t\t}\n\n\t\t\tconst querySnapshot = await query.get();\n\t\t\tconst entities = querySnapshot.docs.map((doc: DocumentSnapshot) => doc.data() as T);\n\n\t\t\tlet nextCursor: string | undefined;\n\t\t\tif (entities.length === finalLimit) {\n\t\t\t\tnextCursor = querySnapshot.docs[querySnapshot.docs.length - 1].ref.path;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tentities,\n\t\t\t\tcursor: nextCursor\n\t\t\t};\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"queryFailed\",\n\t\t\t\t{ queryDescription: queryDescription.join(\"; \") },\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Delete all entities in the collection.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tpublic async collectionDelete(): Promise<void> {\n\t\tconst collection = this._firestoreClient.collection(this.collectionName());\n\t\tconst batchSize = 500;\n\t\tconst query = collection.limit(batchSize);\n\n\t\ttry {\n\t\t\tawait this.deleteQueryBatch(query, batchSize);\n\t\t} catch (error) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"collectionDeleteFailed\",\n\t\t\t\t{ collectionName: this._config.collectionName },\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Apply conditions to a Firestore query.\n\t * @param query The initial query.\n\t * @param condition The condition to apply.\n\t * @returns The updated query.\n\t * @internal\n\t */\n\tprivate applyConditions(query: Query, condition: EntityCondition<T>): Query {\n\t\tif (\"conditions\" in condition) {\n\t\t\t// It's a group of conditions\n\t\t\tfor (const c of condition.conditions) {\n\t\t\t\tquery = this.applyConditions(query, c);\n\t\t\t}\n\t\t\treturn query;\n\t\t}\n\t\t// It's a single condition\n\t\tconst { property, value, comparison } = condition;\n\t\tswitch (comparison) {\n\t\t\tcase ComparisonOperator.Equals:\n\t\t\t\treturn query.where(property, \"==\", value);\n\t\t\tcase ComparisonOperator.NotEquals:\n\t\t\t\treturn query.where(property, \"!=\", value);\n\t\t\tcase ComparisonOperator.GreaterThan:\n\t\t\t\treturn query.where(property, \">\", value);\n\t\t\tcase ComparisonOperator.LessThan:\n\t\t\t\treturn query.where(property, \"<\", value);\n\t\t\tcase ComparisonOperator.GreaterThanOrEqual:\n\t\t\t\treturn query.where(property, \">=\", value);\n\t\t\tcase ComparisonOperator.LessThanOrEqual:\n\t\t\t\treturn query.where(property, \"<=\", value);\n\t\t\tcase ComparisonOperator.In:\n\t\t\t\treturn query.where(property, \"in\", value as unknown[]);\n\t\t\tcase ComparisonOperator.Includes:\n\t\t\t\treturn query.where(property, \"array-contains\", value);\n\t\t\tcase ComparisonOperator.NotIncludes:\n\t\t\tdefault:\n\t\t\t\tthrow new GeneralError(\n\t\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\t\"unsupportedComparisonOperator\",\n\t\t\t\t\t{ comparison }\n\t\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Delete all entities in the collection.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async deleteQueryBatch(query: Query, batchSize: number): Promise<void> {\n\t\tconst snapshot = await query.get();\n\n\t\tif (snapshot.size === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst batch = this._firestoreClient.batch();\n\t\tfor (const doc of snapshot.docs) {\n\t\t\tbatch.delete(doc.ref);\n\t\t}\n\n\t\tawait batch.commit();\n\n\t\tif (snapshot.size === batchSize) {\n\t\t\tawait this.deleteQueryBatch(query, batchSize);\n\t\t}\n\t}\n\n\t/**\n\t * Get the collection name based on partition key.\n\t * @returns The collection name.\n\t * @internal\n\t */\n\tprivate collectionName(partitionKey?: string): string {\n\t\treturn `${this._config.collectionName}_${partitionKey ?? \"default\"}`;\n\t}\n}\n"]}
|
package/dist/es/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
export * from "./firestoreEntityStorageConnector.js";
|
|
4
|
+
export * from "./models/IFirestoreEntityStorageConnectorConfig.js";
|
|
5
|
+
export * from "./models/IFirestoreEntityStorageConnectorConstructorOptions.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,sCAAsC,CAAC;AACrD,cAAc,oDAAoD,CAAC;AACnE,cAAc,gEAAgE,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./firestoreEntityStorageConnector.js\";\nexport * from \"./models/IFirestoreEntityStorageConnectorConfig.js\";\nexport * from \"./models/IFirestoreEntityStorageConnectorConstructorOptions.js\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IFirestoreEntityStorageConnectorConfig.js","sourceRoot":"","sources":["../../../src/models/IFirestoreEntityStorageConnectorConfig.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Configuration for the Firestore Entity Storage Connector.\n */\nexport interface IFirestoreEntityStorageConnectorConfig {\n\t/**\n\t * The GCP project ID.\n\t */\n\tprojectId: string;\n\n\t/**\n\t * The database ID, if omitted default database will be used.\n\t */\n\tdatabaseId?: string;\n\n\t/**\n\t * The name of the collection for the storage.\n\t */\n\tcollectionName: string;\n\n\t/**\n\t * The GCP credentials, a base64 encoded version of the JWTInput data type.\n\t */\n\tcredentials?: string;\n\n\t/**\n\t * It's usually only used with an emulator (e.g., \"localhost:20200\").\n\t */\n\tendpoint?: string;\n\n\t/**\n\t * Optional settings for Firestore client initialization.\n\t */\n\tsettings?: {\n\t\t/**\n\t\t * The maximum number of idle channels to keep open.\n\t\t */\n\t\tmaxIdleChannels?: number;\n\n\t\t/**\n\t\t * The custom timeout for requests (in milliseconds).\n\t\t */\n\t\ttimeout?: number;\n\t};\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IFirestoreEntityStorageConnectorConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IFirestoreEntityStorageConnectorConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IFirestoreEntityStorageConnectorConfig } from \"./IFirestoreEntityStorageConnectorConfig.js\";\n\n/**\n * Options for the Firestore Entity Storage Connector constructor.\n */\nexport interface IFirestoreEntityStorageConnectorConstructorOptions {\n\t/**\n\t * The schema for the entity.\n\t */\n\tentitySchema: string;\n\n\t/**\n\t * The keys to use from the context ids to create partitions.\n\t */\n\tpartitionContextIds?: string[];\n\n\t/**\n\t * The type of logging component to use, defaults to no logging.\n\t */\n\tloggingComponentType?: string;\n\n\t/**\n\t * The configuration for the connector.\n\t */\n\tconfig: IFirestoreEntityStorageConnectorConfig;\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type EntityCondition, type IEntitySchema, SortDirection } from "@twin.org/entity";
|
|
2
2
|
import type { IEntityStorageConnector } from "@twin.org/entity-storage-models";
|
|
3
|
-
import type { IFirestoreEntityStorageConnectorConstructorOptions } from "./models/IFirestoreEntityStorageConnectorConstructorOptions";
|
|
3
|
+
import type { IFirestoreEntityStorageConnectorConstructorOptions } from "./models/IFirestoreEntityStorageConnectorConstructorOptions.js";
|
|
4
4
|
/**
|
|
5
5
|
* Class for performing entity storage operations using Firestore.
|
|
6
6
|
*/
|
|
@@ -8,23 +8,28 @@ export declare class FirestoreEntityStorageConnector<T = unknown> implements IEn
|
|
|
8
8
|
/**
|
|
9
9
|
* Runtime name for the class.
|
|
10
10
|
*/
|
|
11
|
-
readonly CLASS_NAME: string;
|
|
11
|
+
static readonly CLASS_NAME: string;
|
|
12
12
|
/**
|
|
13
13
|
* Create a new instance of FirestoreEntityStorageConnector.
|
|
14
14
|
* @param options The options for the connector.
|
|
15
15
|
*/
|
|
16
16
|
constructor(options: IFirestoreEntityStorageConnectorConstructorOptions);
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
* @
|
|
20
|
-
* @returns True if the bootstrapping process was successful.
|
|
18
|
+
* Returns the class name of the component.
|
|
19
|
+
* @returns The class name of the component.
|
|
21
20
|
*/
|
|
22
|
-
|
|
21
|
+
className(): string;
|
|
23
22
|
/**
|
|
24
23
|
* Get the schema for the entities.
|
|
25
24
|
* @returns The schema for the entities.
|
|
26
25
|
*/
|
|
27
26
|
getSchema(): IEntitySchema;
|
|
27
|
+
/**
|
|
28
|
+
* Bootstrap the component by creating and initializing any resources it needs.
|
|
29
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
30
|
+
* @returns True if the bootstrapping process was successful.
|
|
31
|
+
*/
|
|
32
|
+
bootstrap(nodeLoggingComponentType?: string): Promise<boolean>;
|
|
28
33
|
/**
|
|
29
34
|
* Get an entity.
|
|
30
35
|
* @param id The id of the entity to get.
|
|
@@ -61,14 +66,14 @@ export declare class FirestoreEntityStorageConnector<T = unknown> implements IEn
|
|
|
61
66
|
* @param conditions The conditions to match for the entities.
|
|
62
67
|
* @param sortProperties The optional sort order.
|
|
63
68
|
* @param properties The optional properties to return, defaults to all.
|
|
64
|
-
* @param cursor The cursor to request the next
|
|
65
|
-
* @param
|
|
69
|
+
* @param cursor The cursor to request the next chunk of entities.
|
|
70
|
+
* @param limit The suggested number of entities to return in each chunk.
|
|
66
71
|
* @returns The matching entities and a cursor for the next page.
|
|
67
72
|
*/
|
|
68
73
|
query(conditions?: EntityCondition<T>, sortProperties?: {
|
|
69
74
|
property: keyof T;
|
|
70
75
|
sortDirection: SortDirection;
|
|
71
|
-
}[], properties?: (keyof T)[], cursor?: string,
|
|
76
|
+
}[], properties?: (keyof T)[], cursor?: string, limit?: number): Promise<{
|
|
72
77
|
/**
|
|
73
78
|
* The entities, which can be partial if a limited keys list was provided.
|
|
74
79
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from "./firestoreEntityStorageConnector";
|
|
2
|
-
export * from "./models/IFirestoreEntityStorageConnectorConfig";
|
|
3
|
-
export * from "./models/IFirestoreEntityStorageConnectorConstructorOptions";
|
|
1
|
+
export * from "./firestoreEntityStorageConnector.js";
|
|
2
|
+
export * from "./models/IFirestoreEntityStorageConnectorConfig.js";
|
|
3
|
+
export * from "./models/IFirestoreEntityStorageConnectorConstructorOptions.js";
|
|
@@ -19,7 +19,7 @@ export interface IFirestoreEntityStorageConnectorConfig {
|
|
|
19
19
|
*/
|
|
20
20
|
credentials?: string;
|
|
21
21
|
/**
|
|
22
|
-
* It's usually only used with an emulator (e.g., "localhost:
|
|
22
|
+
* It's usually only used with an emulator (e.g., "localhost:20200").
|
|
23
23
|
*/
|
|
24
24
|
endpoint?: string;
|
|
25
25
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IFirestoreEntityStorageConnectorConfig } from "./IFirestoreEntityStorageConnectorConfig";
|
|
1
|
+
import type { IFirestoreEntityStorageConnectorConfig } from "./IFirestoreEntityStorageConnectorConfig.js";
|
|
2
2
|
/**
|
|
3
3
|
* Options for the Firestore Entity Storage Connector constructor.
|
|
4
4
|
*/
|
|
@@ -7,6 +7,10 @@ export interface IFirestoreEntityStorageConnectorConstructorOptions {
|
|
|
7
7
|
* The schema for the entity.
|
|
8
8
|
*/
|
|
9
9
|
entitySchema: string;
|
|
10
|
+
/**
|
|
11
|
+
* The keys to use from the context ids to create partitions.
|
|
12
|
+
*/
|
|
13
|
+
partitionContextIds?: string[];
|
|
10
14
|
/**
|
|
11
15
|
* The type of logging component to use, defaults to no logging.
|
|
12
16
|
*/
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# @twin.org/entity-storage-connector-gcp-firestore - Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3-next.2](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-gcp-firestore-v0.0.3-next.1...entity-storage-connector-gcp-firestore-v0.0.3-next.2) (2025-11-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* **entity-storage-connector-gcp-firestore:** Synchronize repo versions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @twin.org/entity-storage-models bumped from 0.0.3-next.1 to 0.0.3-next.2
|
|
16
|
+
* devDependencies
|
|
17
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.3-next.1 to 0.0.3-next.2
|
|
18
|
+
|
|
19
|
+
## [0.0.3-next.1](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-gcp-firestore-v0.0.3-next.0...entity-storage-connector-gcp-firestore-v0.0.3-next.1) (2025-11-10)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* add context id features ([#55](https://github.com/twinfoundation/entity-storage/issues/55)) ([99c15a2](https://github.com/twinfoundation/entity-storage/commit/99c15a257539b61d9da63649ce573ebf47699fc9))
|
|
25
|
+
* Add entity storage connector for GCP Firestore ([#21](https://github.com/twinfoundation/entity-storage/issues/21)) ([10bfbf0](https://github.com/twinfoundation/entity-storage/commit/10bfbf0ba7dfe1e9de14d8059426c370476749d4))
|
|
26
|
+
* add production release automation ([1eb4c8e](https://github.com/twinfoundation/entity-storage/commit/1eb4c8ee3eb099defdfc2d063ae44935276dcae8))
|
|
27
|
+
* add validate-locales ([e66ef0d](https://github.com/twinfoundation/entity-storage/commit/e66ef0de26ca2f82b3fe89bb5c7a15a0978a9644))
|
|
28
|
+
* CosmosDB Entity Storage Connector ([#20](https://github.com/twinfoundation/entity-storage/issues/20)) ([0ae8371](https://github.com/twinfoundation/entity-storage/commit/0ae8371d81ce7e20c0b0397144499dc3e17ffa0a))
|
|
29
|
+
* eslint migration to flat config ([f033b64](https://github.com/twinfoundation/entity-storage/commit/f033b64984c0e6a8129d929c9dd816dcc1b8dab0))
|
|
30
|
+
* logging naming consistency ([f99d12d](https://github.com/twinfoundation/entity-storage/commit/f99d12dea04b6d4f2b5632ff5473e9ec7d5f9055))
|
|
31
|
+
* synchronised storage ([#44](https://github.com/twinfoundation/entity-storage/issues/44)) ([94e10e2](https://github.com/twinfoundation/entity-storage/commit/94e10e26d1feec801449dc04af7a9757ac7495ff))
|
|
32
|
+
* update dependencies ([7ccc0c4](https://github.com/twinfoundation/entity-storage/commit/7ccc0c429125d073dc60b3de6cf101abc8cc6cba))
|
|
33
|
+
* update framework core ([b59a380](https://github.com/twinfoundation/entity-storage/commit/b59a380bb7fba2b43610f69074dcdee24a4737da))
|
|
34
|
+
* use shared store mechanism ([#34](https://github.com/twinfoundation/entity-storage/issues/34)) ([68b6b71](https://github.com/twinfoundation/entity-storage/commit/68b6b71e7a96d7d016cd57bfff36775b56bf3f93))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Bug Fixes
|
|
38
|
+
|
|
39
|
+
* query params force coercion ([dd6aa87](https://github.com/twinfoundation/entity-storage/commit/dd6aa87efdfb60bab7d6756a86888863c45c51a7))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
### Dependencies
|
|
43
|
+
|
|
44
|
+
* The following workspace dependencies were updated
|
|
45
|
+
* dependencies
|
|
46
|
+
* @twin.org/entity-storage-models bumped from 0.0.3-next.0 to 0.0.3-next.1
|
|
47
|
+
* devDependencies
|
|
48
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.3-next.0 to 0.0.3-next.1
|
|
49
|
+
|
|
50
|
+
## [0.0.2-next.10](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-gcp-firestore-v0.0.2-next.9...entity-storage-connector-gcp-firestore-v0.0.2-next.10) (2025-10-09)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Features
|
|
54
|
+
|
|
55
|
+
* add validate-locales ([e66ef0d](https://github.com/twinfoundation/entity-storage/commit/e66ef0de26ca2f82b3fe89bb5c7a15a0978a9644))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Dependencies
|
|
59
|
+
|
|
60
|
+
* The following workspace dependencies were updated
|
|
61
|
+
* dependencies
|
|
62
|
+
* @twin.org/entity-storage-models bumped from 0.0.2-next.9 to 0.0.2-next.10
|
|
63
|
+
* devDependencies
|
|
64
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.2-next.9 to 0.0.2-next.10
|
|
65
|
+
|
|
3
66
|
## [0.0.2-next.9](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-gcp-firestore-v0.0.2-next.8...entity-storage-connector-gcp-firestore-v0.0.2-next.9) (2025-10-02)
|
|
4
67
|
|
|
5
68
|
|
|
@@ -36,39 +36,27 @@ The options for the connector.
|
|
|
36
36
|
|
|
37
37
|
### CLASS\_NAME
|
|
38
38
|
|
|
39
|
-
> `readonly` **CLASS\_NAME**: `string`
|
|
39
|
+
> `readonly` `static` **CLASS\_NAME**: `string`
|
|
40
40
|
|
|
41
41
|
Runtime name for the class.
|
|
42
42
|
|
|
43
|
-
#### Implementation of
|
|
44
|
-
|
|
45
|
-
`IEntityStorageConnector.CLASS_NAME`
|
|
46
|
-
|
|
47
43
|
## Methods
|
|
48
44
|
|
|
49
|
-
###
|
|
50
|
-
|
|
51
|
-
> **bootstrap**(`nodeLoggingComponentType?`): `Promise`\<`boolean`\>
|
|
52
|
-
|
|
53
|
-
Bootstrap the component by creating and initializing any resources it needs.
|
|
54
|
-
|
|
55
|
-
#### Parameters
|
|
56
|
-
|
|
57
|
-
##### nodeLoggingComponentType?
|
|
45
|
+
### className()
|
|
58
46
|
|
|
59
|
-
`string`
|
|
47
|
+
> **className**(): `string`
|
|
60
48
|
|
|
61
|
-
|
|
49
|
+
Returns the class name of the component.
|
|
62
50
|
|
|
63
51
|
#### Returns
|
|
64
52
|
|
|
65
|
-
`
|
|
53
|
+
`string`
|
|
66
54
|
|
|
67
|
-
|
|
55
|
+
The class name of the component.
|
|
68
56
|
|
|
69
57
|
#### Implementation of
|
|
70
58
|
|
|
71
|
-
`IEntityStorageConnector.
|
|
59
|
+
`IEntityStorageConnector.className`
|
|
72
60
|
|
|
73
61
|
***
|
|
74
62
|
|
|
@@ -90,9 +78,35 @@ The schema for the entities.
|
|
|
90
78
|
|
|
91
79
|
***
|
|
92
80
|
|
|
81
|
+
### bootstrap()
|
|
82
|
+
|
|
83
|
+
> **bootstrap**(`nodeLoggingComponentType?`): `Promise`\<`boolean`\>
|
|
84
|
+
|
|
85
|
+
Bootstrap the component by creating and initializing any resources it needs.
|
|
86
|
+
|
|
87
|
+
#### Parameters
|
|
88
|
+
|
|
89
|
+
##### nodeLoggingComponentType?
|
|
90
|
+
|
|
91
|
+
`string`
|
|
92
|
+
|
|
93
|
+
The node logging component type.
|
|
94
|
+
|
|
95
|
+
#### Returns
|
|
96
|
+
|
|
97
|
+
`Promise`\<`boolean`\>
|
|
98
|
+
|
|
99
|
+
True if the bootstrapping process was successful.
|
|
100
|
+
|
|
101
|
+
#### Implementation of
|
|
102
|
+
|
|
103
|
+
`IEntityStorageConnector.bootstrap`
|
|
104
|
+
|
|
105
|
+
***
|
|
106
|
+
|
|
93
107
|
### get()
|
|
94
108
|
|
|
95
|
-
> **get**(`id`, `secondaryIndex?`, `conditions?`): `Promise`\<`
|
|
109
|
+
> **get**(`id`, `secondaryIndex?`, `conditions?`): `Promise`\<`T` \| `undefined`\>
|
|
96
110
|
|
|
97
111
|
Get an entity.
|
|
98
112
|
|
|
@@ -118,7 +132,7 @@ The optional conditions to apply to the query.
|
|
|
118
132
|
|
|
119
133
|
#### Returns
|
|
120
134
|
|
|
121
|
-
`Promise`\<`
|
|
135
|
+
`Promise`\<`T` \| `undefined`\>
|
|
122
136
|
|
|
123
137
|
The object if it can be found or undefined.
|
|
124
138
|
|
|
@@ -194,7 +208,7 @@ Nothing.
|
|
|
194
208
|
|
|
195
209
|
### query()
|
|
196
210
|
|
|
197
|
-
> **query**(`conditions?`, `sortProperties?`, `properties?`, `cursor?`, `
|
|
211
|
+
> **query**(`conditions?`, `sortProperties?`, `properties?`, `cursor?`, `limit?`): `Promise`\<\{ `entities`: `Partial`\<`T`\>[]; `cursor?`: `string`; \}\>
|
|
198
212
|
|
|
199
213
|
Find all the entities which match the conditions.
|
|
200
214
|
|
|
@@ -222,9 +236,9 @@ The optional properties to return, defaults to all.
|
|
|
222
236
|
|
|
223
237
|
`string`
|
|
224
238
|
|
|
225
|
-
The cursor to request the next
|
|
239
|
+
The cursor to request the next chunk of entities.
|
|
226
240
|
|
|
227
|
-
#####
|
|
241
|
+
##### limit?
|
|
228
242
|
|
|
229
243
|
`number`
|
|
230
244
|
|
|
@@ -40,7 +40,7 @@ The GCP credentials, a base64 encoded version of the JWTInput data type.
|
|
|
40
40
|
|
|
41
41
|
> `optional` **endpoint**: `string`
|
|
42
42
|
|
|
43
|
-
It's usually only used with an emulator (e.g., "localhost:
|
|
43
|
+
It's usually only used with an emulator (e.g., "localhost:20200").
|
|
44
44
|
|
|
45
45
|
***
|
|
46
46
|
|
|
@@ -12,6 +12,14 @@ The schema for the entity.
|
|
|
12
12
|
|
|
13
13
|
***
|
|
14
14
|
|
|
15
|
+
### partitionContextIds?
|
|
16
|
+
|
|
17
|
+
> `optional` **partitionContextIds**: `string`[]
|
|
18
|
+
|
|
19
|
+
The keys to use from the context ids to create partitions.
|
|
20
|
+
|
|
21
|
+
***
|
|
22
|
+
|
|
15
23
|
### loggingComponentType?
|
|
16
24
|
|
|
17
25
|
> `optional` **loggingComponentType**: `string`
|
package/locales/en.json
CHANGED
|
@@ -13,11 +13,7 @@
|
|
|
13
13
|
"removeEntityFailed": "Failed to remove entity \"{id}\"",
|
|
14
14
|
"queryFailed": "The query failed when issuing the following command \"{queryDescription}\"",
|
|
15
15
|
"collectionDeleteFailed": "Failed to delete collection \"{collectionName}\"",
|
|
16
|
-
"unsupportedComparisonOperator": "Comparison operator \"{comparison}\" is not supported"
|
|
17
|
-
"firestoreClientNotInitialized": "Firestore client not initialized",
|
|
18
|
-
"undefinedProperty": "Property \"{key}\" is undefined. Firestore does not support undefined values.",
|
|
19
|
-
"missingProjectId": "Project ID is required",
|
|
20
|
-
"documentDoesNotExist": "The document with id {id} does not exist."
|
|
16
|
+
"unsupportedComparisonOperator": "Comparison operator \"{comparison}\" is not supported"
|
|
21
17
|
}
|
|
22
18
|
}
|
|
23
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/entity-storage-connector-gcp-firestore",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3-next.2",
|
|
4
4
|
"description": "Entity Storage connector implementation using GCP Firestore storage",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,27 +14,26 @@
|
|
|
14
14
|
"node": ">=20.0.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@google-cloud/firestore": "
|
|
17
|
+
"@google-cloud/firestore": "8.0.0",
|
|
18
|
+
"@twin.org/context": "next",
|
|
18
19
|
"@twin.org/core": "next",
|
|
19
20
|
"@twin.org/entity": "next",
|
|
20
|
-
"@twin.org/entity-storage-models": "0.0.
|
|
21
|
+
"@twin.org/entity-storage-models": "0.0.3-next.2",
|
|
21
22
|
"@twin.org/logging-models": "next",
|
|
22
23
|
"@twin.org/nameof": "next"
|
|
23
24
|
},
|
|
24
|
-
"main": "./dist/
|
|
25
|
-
"module": "./dist/esm/index.mjs",
|
|
25
|
+
"main": "./dist/es/index.js",
|
|
26
26
|
"types": "./dist/types/index.d.ts",
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
29
|
"types": "./dist/types/index.d.ts",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"import": "./dist/es/index.js",
|
|
31
|
+
"default": "./dist/es/index.js"
|
|
32
32
|
},
|
|
33
33
|
"./locales/*.json": "./locales/*.json"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
|
-
"dist/
|
|
37
|
-
"dist/esm",
|
|
36
|
+
"dist/es",
|
|
38
37
|
"dist/types",
|
|
39
38
|
"locales",
|
|
40
39
|
"docs"
|
|
@@ -53,5 +52,9 @@
|
|
|
53
52
|
"connector",
|
|
54
53
|
"adapter",
|
|
55
54
|
"integration"
|
|
56
|
-
]
|
|
55
|
+
],
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "git+https://github.com/twinfoundation/entity-storage/issues"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://twindev.org"
|
|
57
60
|
}
|
package/dist/cjs/index.cjs
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var firestore = require('@google-cloud/firestore');
|
|
4
|
-
var core = require('@twin.org/core');
|
|
5
|
-
var entity = require('@twin.org/entity');
|
|
6
|
-
|
|
7
|
-
// Copyright 2024 IOTA Stiftung.
|
|
8
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
9
|
-
/**
|
|
10
|
-
* Class for performing entity storage operations using Firestore.
|
|
11
|
-
*/
|
|
12
|
-
class FirestoreEntityStorageConnector {
|
|
13
|
-
/**
|
|
14
|
-
* Limit the number of entities when finding.
|
|
15
|
-
* @internal
|
|
16
|
-
*/
|
|
17
|
-
static _PAGE_SIZE = 40;
|
|
18
|
-
/**
|
|
19
|
-
* Runtime name for the class.
|
|
20
|
-
*/
|
|
21
|
-
CLASS_NAME = "FirestoreEntityStorageConnector";
|
|
22
|
-
/**
|
|
23
|
-
* The schema for the entity.
|
|
24
|
-
* @internal
|
|
25
|
-
*/
|
|
26
|
-
_entitySchema;
|
|
27
|
-
/**
|
|
28
|
-
* The primary key.
|
|
29
|
-
* @internal
|
|
30
|
-
*/
|
|
31
|
-
_primaryKey;
|
|
32
|
-
/**
|
|
33
|
-
* The configuration for the connector.
|
|
34
|
-
* @internal
|
|
35
|
-
*/
|
|
36
|
-
_config;
|
|
37
|
-
/**
|
|
38
|
-
* The Firestore client.
|
|
39
|
-
* @internal
|
|
40
|
-
*/
|
|
41
|
-
_firestoreClient;
|
|
42
|
-
/**
|
|
43
|
-
* The Firestore collection.
|
|
44
|
-
* @internal
|
|
45
|
-
*/
|
|
46
|
-
_collection;
|
|
47
|
-
/**
|
|
48
|
-
* Create a new instance of FirestoreEntityStorageConnector.
|
|
49
|
-
* @param options The options for the connector.
|
|
50
|
-
*/
|
|
51
|
-
constructor(options) {
|
|
52
|
-
core.Guards.object(this.CLASS_NAME, "options", options);
|
|
53
|
-
core.Guards.stringValue(this.CLASS_NAME, "options.entitySchema", options.entitySchema);
|
|
54
|
-
core.Guards.object(this.CLASS_NAME, "options.config", options.config);
|
|
55
|
-
core.Guards.stringValue(this.CLASS_NAME, "options.config.projectId", options.config.projectId);
|
|
56
|
-
core.Guards.stringValue(this.CLASS_NAME, "options.config.collectionName", options.config.collectionName);
|
|
57
|
-
let credentials;
|
|
58
|
-
if (!core.Is.empty(options.config.credentials)) {
|
|
59
|
-
core.Guards.stringBase64(this.CLASS_NAME, "options.config.credentials", options.config.credentials);
|
|
60
|
-
credentials = core.ObjectHelper.fromBytes(core.Converter.base64ToBytes(options.config.credentials));
|
|
61
|
-
}
|
|
62
|
-
this._config = options.config;
|
|
63
|
-
this._entitySchema = entity.EntitySchemaFactory.get(options.entitySchema);
|
|
64
|
-
this._primaryKey = entity.EntitySchemaHelper.getPrimaryKey(this._entitySchema);
|
|
65
|
-
const firestoreOptions = {
|
|
66
|
-
projectId: this._config.projectId,
|
|
67
|
-
databaseId: this._config.databaseId,
|
|
68
|
-
collectionName: this._config.collectionName,
|
|
69
|
-
maxIdleChannels: this._config.settings?.maxIdleChannels,
|
|
70
|
-
timeout: this._config.settings?.timeout,
|
|
71
|
-
credentials
|
|
72
|
-
};
|
|
73
|
-
if (core.Is.stringValue(this._config.endpoint)) {
|
|
74
|
-
firestoreOptions.host = this._config.endpoint;
|
|
75
|
-
firestoreOptions.ssl = false;
|
|
76
|
-
}
|
|
77
|
-
this._firestoreClient = new firestore.Firestore(firestoreOptions);
|
|
78
|
-
this._collection = this._firestoreClient.collection(this._config.collectionName);
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Bootstrap the component by creating and initializing any resources it needs.
|
|
82
|
-
* @param nodeLoggingComponentType The node logging component type.
|
|
83
|
-
* @returns True if the bootstrapping process was successful.
|
|
84
|
-
*/
|
|
85
|
-
async bootstrap(nodeLoggingComponentType) {
|
|
86
|
-
const nodeLogging = core.ComponentFactory.getIfExists(nodeLoggingComponentType);
|
|
87
|
-
try {
|
|
88
|
-
await nodeLogging?.log({
|
|
89
|
-
level: "info",
|
|
90
|
-
source: this.CLASS_NAME,
|
|
91
|
-
ts: Date.now(),
|
|
92
|
-
message: "firestoreCreating",
|
|
93
|
-
data: {
|
|
94
|
-
projectId: this._config.projectId,
|
|
95
|
-
collectionName: this._config.collectionName
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
// Firestore doesn't require explicit collection creation
|
|
99
|
-
// Perform a small write operation to ensure connectivity
|
|
100
|
-
const testDoc = this._firestoreClient.collection(this._config.collectionName).doc("test");
|
|
101
|
-
await testDoc.set({ test: true });
|
|
102
|
-
await testDoc.delete();
|
|
103
|
-
await nodeLogging?.log({
|
|
104
|
-
level: "info",
|
|
105
|
-
source: this.CLASS_NAME,
|
|
106
|
-
ts: Date.now(),
|
|
107
|
-
message: "firestoreCreated",
|
|
108
|
-
data: {
|
|
109
|
-
projectId: this._config.projectId,
|
|
110
|
-
collectionName: this._config.collectionName
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
catch (err) {
|
|
116
|
-
await nodeLogging?.log({
|
|
117
|
-
level: "error",
|
|
118
|
-
source: this.CLASS_NAME,
|
|
119
|
-
ts: Date.now(),
|
|
120
|
-
message: "firestoreCreationFailed",
|
|
121
|
-
error: core.BaseError.fromError(err),
|
|
122
|
-
data: {
|
|
123
|
-
projectId: this._config.projectId,
|
|
124
|
-
collectionName: this._config.collectionName
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Get the schema for the entities.
|
|
132
|
-
* @returns The schema for the entities.
|
|
133
|
-
*/
|
|
134
|
-
getSchema() {
|
|
135
|
-
return this._entitySchema;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Get an entity.
|
|
139
|
-
* @param id The id of the entity to get.
|
|
140
|
-
* @param secondaryIndex The optional secondary index to use.
|
|
141
|
-
* @param conditions The optional conditions to apply to the query.
|
|
142
|
-
* @returns The object if it can be found or undefined.
|
|
143
|
-
*/
|
|
144
|
-
async get(id, secondaryIndex, conditions) {
|
|
145
|
-
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
146
|
-
try {
|
|
147
|
-
if (!core.Is.stringValue(secondaryIndex) && !core.Is.arrayValue(conditions)) {
|
|
148
|
-
const docRef = this._collection.doc(id);
|
|
149
|
-
const doc = await docRef.get();
|
|
150
|
-
if (doc.exists) {
|
|
151
|
-
return doc.data();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
// Use secondaryIndex and/or conditions to construct a query
|
|
155
|
-
let query = this._collection;
|
|
156
|
-
if (secondaryIndex) {
|
|
157
|
-
query = query.where(secondaryIndex, "==", id);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
// If no secondaryIndex, include primary key in conditions
|
|
161
|
-
query = query.where(this._primaryKey.property, "==", id);
|
|
162
|
-
}
|
|
163
|
-
if (core.Is.arrayValue(conditions)) {
|
|
164
|
-
for (const condition of conditions) {
|
|
165
|
-
query = query.where(condition.property, "==", condition.value);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const querySnapshot = await query.limit(1).get();
|
|
169
|
-
if (!querySnapshot.empty) {
|
|
170
|
-
return querySnapshot.docs[0].data();
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
catch (err) {
|
|
174
|
-
throw new core.GeneralError(this.CLASS_NAME, "getEntityFailed", { id }, err);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Set an entity.
|
|
179
|
-
* @param entity The entity to set.
|
|
180
|
-
* @param conditions The optional conditions to apply to the update.
|
|
181
|
-
* @returns Nothing.
|
|
182
|
-
*/
|
|
183
|
-
async set(entity$1, conditions) {
|
|
184
|
-
core.Guards.object(this.CLASS_NAME, "entity", entity$1);
|
|
185
|
-
entity.EntitySchemaHelper.validateEntity(entity$1, this.getSchema());
|
|
186
|
-
try {
|
|
187
|
-
const id = entity$1[this._primaryKey.property];
|
|
188
|
-
const entityCopy = { ...entity$1 };
|
|
189
|
-
// Handle indexing field
|
|
190
|
-
if (entityCopy.valueArray && core.Is.array(entityCopy.valueArray)) {
|
|
191
|
-
const valueArrayFields = entityCopy.valueArray
|
|
192
|
-
.filter((item) => core.Is.notEmpty(item))
|
|
193
|
-
.map(item => `${item.field}:${item.value}`);
|
|
194
|
-
entityCopy.valueArrayFields = valueArrayFields;
|
|
195
|
-
}
|
|
196
|
-
const docRef = this._collection.doc(id);
|
|
197
|
-
if (!core.Is.arrayValue(conditions)) {
|
|
198
|
-
await docRef.set(entityCopy);
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
await this._firestoreClient.runTransaction(async (transaction) => {
|
|
202
|
-
const docSnapshot = await transaction.get(docRef);
|
|
203
|
-
if (!docSnapshot.exists) {
|
|
204
|
-
transaction.set(docRef, entityCopy);
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
const data = docSnapshot.data();
|
|
208
|
-
let conditionsMet = true;
|
|
209
|
-
for (const condition of conditions) {
|
|
210
|
-
if (data[condition.property] !== condition.value) {
|
|
211
|
-
conditionsMet = false;
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (conditionsMet) {
|
|
216
|
-
transaction.set(docRef, entityCopy);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
catch (err) {
|
|
223
|
-
throw new core.GeneralError(this.CLASS_NAME, "setEntityFailed", { entity: entity$1 }, err);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Remove the entity.
|
|
228
|
-
* @param id The id of the entity to remove.
|
|
229
|
-
* @param conditions The optional conditions to apply to the delete.
|
|
230
|
-
* @returns Nothing.
|
|
231
|
-
*/
|
|
232
|
-
async remove(id, conditions) {
|
|
233
|
-
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
234
|
-
try {
|
|
235
|
-
const docRef = this._collection.doc(id);
|
|
236
|
-
if (!core.Is.arrayValue(conditions)) {
|
|
237
|
-
await docRef.delete();
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
await this._firestoreClient.runTransaction(async (transaction) => {
|
|
241
|
-
const docSnapshot = await transaction.get(docRef);
|
|
242
|
-
if (docSnapshot.exists) {
|
|
243
|
-
const data = docSnapshot.data();
|
|
244
|
-
let conditionsMet = true;
|
|
245
|
-
for (const condition of conditions) {
|
|
246
|
-
if (data[condition.property] !== condition.value) {
|
|
247
|
-
conditionsMet = false;
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
if (conditionsMet) {
|
|
252
|
-
transaction.delete(docRef);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
catch (err) {
|
|
259
|
-
throw new core.GeneralError(this.CLASS_NAME, "removeEntityFailed", { id }, err);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Find all the entities which match the conditions.
|
|
264
|
-
* @param conditions The conditions to match for the entities.
|
|
265
|
-
* @param sortProperties The optional sort order.
|
|
266
|
-
* @param properties The optional properties to return, defaults to all.
|
|
267
|
-
* @param cursor The cursor to request the next page of entities.
|
|
268
|
-
* @param pageSize The suggested number of entities to return in each chunk.
|
|
269
|
-
* @returns The matching entities and a cursor for the next page.
|
|
270
|
-
*/
|
|
271
|
-
async query(conditions, sortProperties, properties, cursor, pageSize) {
|
|
272
|
-
const queryDescription = [];
|
|
273
|
-
try {
|
|
274
|
-
let query = this._collection;
|
|
275
|
-
if (conditions) {
|
|
276
|
-
query = this.applyConditions(query, conditions);
|
|
277
|
-
queryDescription.push(`Conditions: ${JSON.stringify(conditions)}`);
|
|
278
|
-
}
|
|
279
|
-
if (core.Is.arrayValue(sortProperties)) {
|
|
280
|
-
for (const { property, sortDirection } of sortProperties) {
|
|
281
|
-
query = query.orderBy(property, sortDirection === entity.SortDirection.Ascending ? "asc" : "desc");
|
|
282
|
-
}
|
|
283
|
-
queryDescription.push(`Sort: ${JSON.stringify(sortProperties)}`);
|
|
284
|
-
}
|
|
285
|
-
if (core.Is.stringValue(cursor)) {
|
|
286
|
-
const cursorDoc = await this._firestoreClient.doc(cursor).get();
|
|
287
|
-
if (cursorDoc?.exists) {
|
|
288
|
-
query = query.startAfter(cursorDoc);
|
|
289
|
-
}
|
|
290
|
-
queryDescription.push(`Cursor: ${cursor}`);
|
|
291
|
-
}
|
|
292
|
-
const limit = pageSize ?? FirestoreEntityStorageConnector._PAGE_SIZE;
|
|
293
|
-
query = query.limit(limit);
|
|
294
|
-
queryDescription.push(`Limit: ${limit}`);
|
|
295
|
-
if (properties) {
|
|
296
|
-
query = query.select(...properties);
|
|
297
|
-
queryDescription.push(`Properties: ${properties.join(", ")}`);
|
|
298
|
-
}
|
|
299
|
-
const querySnapshot = await query.get();
|
|
300
|
-
const entities = querySnapshot.docs.map((doc) => doc.data());
|
|
301
|
-
let nextCursor;
|
|
302
|
-
if (entities.length === limit) {
|
|
303
|
-
nextCursor = querySnapshot.docs[querySnapshot.docs.length - 1].ref.path;
|
|
304
|
-
}
|
|
305
|
-
return {
|
|
306
|
-
entities,
|
|
307
|
-
cursor: nextCursor
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
catch (err) {
|
|
311
|
-
throw new core.GeneralError(this.CLASS_NAME, "queryFailed", { queryDescription: queryDescription.join("; ") }, err);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Delete all entities in the collection.
|
|
316
|
-
* @returns Nothing.
|
|
317
|
-
* @internal
|
|
318
|
-
*/
|
|
319
|
-
async collectionDelete() {
|
|
320
|
-
const collection = this._collection;
|
|
321
|
-
const batchSize = 500;
|
|
322
|
-
const query = collection.limit(batchSize);
|
|
323
|
-
try {
|
|
324
|
-
await this.deleteQueryBatch(query, batchSize);
|
|
325
|
-
}
|
|
326
|
-
catch (error) {
|
|
327
|
-
throw new core.GeneralError(this.CLASS_NAME, "collectionDeleteFailed", { collectionName: this._config.collectionName }, error);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Apply conditions to a Firestore query.
|
|
332
|
-
* @param query The initial query.
|
|
333
|
-
* @param condition The condition to apply.
|
|
334
|
-
* @returns The updated query.
|
|
335
|
-
* @internal
|
|
336
|
-
*/
|
|
337
|
-
applyConditions(query, condition) {
|
|
338
|
-
if ("conditions" in condition) {
|
|
339
|
-
// It's a group of conditions
|
|
340
|
-
for (const c of condition.conditions) {
|
|
341
|
-
query = this.applyConditions(query, c);
|
|
342
|
-
}
|
|
343
|
-
return query;
|
|
344
|
-
}
|
|
345
|
-
// It's a single condition
|
|
346
|
-
const { property, value, comparison } = condition;
|
|
347
|
-
switch (comparison) {
|
|
348
|
-
case entity.ComparisonOperator.Equals:
|
|
349
|
-
return query.where(property, "==", value);
|
|
350
|
-
case entity.ComparisonOperator.NotEquals:
|
|
351
|
-
return query.where(property, "!=", value);
|
|
352
|
-
case entity.ComparisonOperator.GreaterThan:
|
|
353
|
-
return query.where(property, ">", value);
|
|
354
|
-
case entity.ComparisonOperator.LessThan:
|
|
355
|
-
return query.where(property, "<", value);
|
|
356
|
-
case entity.ComparisonOperator.GreaterThanOrEqual:
|
|
357
|
-
return query.where(property, ">=", value);
|
|
358
|
-
case entity.ComparisonOperator.LessThanOrEqual:
|
|
359
|
-
return query.where(property, "<=", value);
|
|
360
|
-
case entity.ComparisonOperator.In:
|
|
361
|
-
return query.where(property, "in", value);
|
|
362
|
-
case entity.ComparisonOperator.Includes:
|
|
363
|
-
return query.where(property, "array-contains", value);
|
|
364
|
-
default:
|
|
365
|
-
throw new core.GeneralError(this.CLASS_NAME, "unsupportedComparisonOperator", { comparison });
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Delete all entities in the collection.
|
|
370
|
-
* @returns Nothing.
|
|
371
|
-
* @internal
|
|
372
|
-
*/
|
|
373
|
-
async deleteQueryBatch(query, batchSize) {
|
|
374
|
-
const snapshot = await query.get();
|
|
375
|
-
if (snapshot.size === 0) {
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
const batch = this._firestoreClient.batch();
|
|
379
|
-
for (const doc of snapshot.docs) {
|
|
380
|
-
batch.delete(doc.ref);
|
|
381
|
-
}
|
|
382
|
-
await batch.commit();
|
|
383
|
-
if (snapshot.size === batchSize) {
|
|
384
|
-
await this.deleteQueryBatch(query, batchSize);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
exports.FirestoreEntityStorageConnector = FirestoreEntityStorageConnector;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { IValueType } from "./IValueType";
|
|
2
|
-
/**
|
|
3
|
-
* Interface representing an entity with indexing fields.
|
|
4
|
-
*/
|
|
5
|
-
export interface IEntityWithIndexing {
|
|
6
|
-
/**
|
|
7
|
-
* The value array.
|
|
8
|
-
*/
|
|
9
|
-
valueArray?: IValueType[];
|
|
10
|
-
/**
|
|
11
|
-
* The value array fields.
|
|
12
|
-
*/
|
|
13
|
-
valueArrayFields?: string[];
|
|
14
|
-
}
|