@twin.org/entity-storage-connector-gcp-firestore 0.0.3-next.13 → 0.0.3-next.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/firestoreEntityStorageConnector.js +290 -63
- package/dist/es/firestoreEntityStorageConnector.js.map +1 -1
- package/dist/types/firestoreEntityStorageConnector.d.ts +32 -4
- package/docs/changelog.md +16 -0
- package/docs/reference/classes/FirestoreEntityStorageConnector.md +160 -16
- package/locales/en.json +3 -1
- package/package.json +3 -3
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
-
import { Firestore } from "@google-cloud/firestore";
|
|
3
|
+
import { Filter, Firestore } from "@google-cloud/firestore";
|
|
4
4
|
import { ContextIdHelper, ContextIdStore } from "@twin.org/context";
|
|
5
5
|
import { BaseError, ComponentFactory, Converter, GeneralError, Guards, HealthStatus, Is, ObjectHelper } from "@twin.org/core";
|
|
6
|
-
import { ComparisonOperator, EntityConditions, EntitySchemaFactory, EntitySchemaHelper, SortDirection } from "@twin.org/entity";
|
|
6
|
+
import { ComparisonOperator, EntityConditions, EntitySchemaFactory, EntitySchemaHelper, LogicalOperator, SortDirection } from "@twin.org/entity";
|
|
7
|
+
import { EntityHelper } from "@twin.org/entity-storage-models";
|
|
7
8
|
/**
|
|
8
9
|
* Class for performing entity storage operations using Firestore.
|
|
9
10
|
*/
|
|
@@ -17,6 +18,17 @@ export class FirestoreEntityStorageConnector {
|
|
|
17
18
|
* @internal
|
|
18
19
|
*/
|
|
19
20
|
static _DEFAULT_LIMIT = 40;
|
|
21
|
+
/**
|
|
22
|
+
* Separator used between context ID parts in Firestore collection names.
|
|
23
|
+
* Must not be "/" which Firestore interprets as a path separator.
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
static _PARTITION_SEPARATOR = ":";
|
|
27
|
+
/**
|
|
28
|
+
* The name for the schema.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
_entitySchemaName;
|
|
20
32
|
/**
|
|
21
33
|
* The schema for the entity.
|
|
22
34
|
* @internal
|
|
@@ -58,6 +70,7 @@ export class FirestoreEntityStorageConnector {
|
|
|
58
70
|
credentials = ObjectHelper.fromBytes(Converter.base64ToBytes(options.config.credentials));
|
|
59
71
|
}
|
|
60
72
|
this._config = options.config;
|
|
73
|
+
this._entitySchemaName = options.entitySchema;
|
|
61
74
|
this._entitySchema = EntitySchemaFactory.get(options.entitySchema);
|
|
62
75
|
this._partitionContextIds = options.partitionContextIds;
|
|
63
76
|
this._primaryKey = EntitySchemaHelper.getPrimaryKey(this._entitySchema);
|
|
@@ -177,14 +190,14 @@ export class FirestoreEntityStorageConnector {
|
|
|
177
190
|
async get(id, secondaryIndex, conditions) {
|
|
178
191
|
Guards.stringValue(FirestoreEntityStorageConnector.CLASS_NAME, "id", id);
|
|
179
192
|
const contextIds = await ContextIdStore.getContextIds();
|
|
180
|
-
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
193
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds, FirestoreEntityStorageConnector._PARTITION_SEPARATOR);
|
|
181
194
|
try {
|
|
182
195
|
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
183
196
|
if (!Is.arrayValue(conditions)) {
|
|
184
197
|
const docRef = collection.doc(id);
|
|
185
198
|
const doc = await docRef.get();
|
|
186
199
|
if (doc.exists) {
|
|
187
|
-
return doc.data();
|
|
200
|
+
return EntityHelper.unPrepareEntity(doc.data(), []);
|
|
188
201
|
}
|
|
189
202
|
}
|
|
190
203
|
// Use conditions to construct a query
|
|
@@ -203,8 +216,7 @@ export class FirestoreEntityStorageConnector {
|
|
|
203
216
|
}
|
|
204
217
|
const querySnapshot = await query.limit(1).get();
|
|
205
218
|
if (!querySnapshot.empty) {
|
|
206
|
-
|
|
207
|
-
return entity;
|
|
219
|
+
return EntityHelper.unPrepareEntity(querySnapshot.docs[0].data(), []);
|
|
208
220
|
}
|
|
209
221
|
}
|
|
210
222
|
catch (err) {
|
|
@@ -220,20 +232,20 @@ export class FirestoreEntityStorageConnector {
|
|
|
220
232
|
async set(entity, conditions) {
|
|
221
233
|
Guards.object(FirestoreEntityStorageConnector.CLASS_NAME, "entity", entity);
|
|
222
234
|
const contextIds = await ContextIdStore.getContextIds();
|
|
223
|
-
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
224
|
-
|
|
235
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds, FirestoreEntityStorageConnector._PARTITION_SEPARATOR);
|
|
236
|
+
const prepared = EntityHelper.prepareEntity(entity, this._entitySchema);
|
|
225
237
|
try {
|
|
226
|
-
const id =
|
|
238
|
+
const id = prepared[this._primaryKey.property];
|
|
227
239
|
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
228
240
|
const docRef = collection.doc(id);
|
|
229
241
|
if (!Is.arrayValue(conditions)) {
|
|
230
|
-
await docRef.set(
|
|
242
|
+
await docRef.set(prepared);
|
|
231
243
|
}
|
|
232
244
|
else {
|
|
233
245
|
await this._firestoreClient.runTransaction(async (transaction) => {
|
|
234
246
|
const docSnapshot = await transaction.get(docRef);
|
|
235
247
|
if (!docSnapshot.exists) {
|
|
236
|
-
transaction.set(docRef,
|
|
248
|
+
transaction.set(docRef, prepared);
|
|
237
249
|
}
|
|
238
250
|
else {
|
|
239
251
|
const data = docSnapshot.data();
|
|
@@ -244,7 +256,7 @@ export class FirestoreEntityStorageConnector {
|
|
|
244
256
|
value: c.value
|
|
245
257
|
}))
|
|
246
258
|
})) {
|
|
247
|
-
transaction.set(docRef,
|
|
259
|
+
transaction.set(docRef, prepared);
|
|
248
260
|
}
|
|
249
261
|
}
|
|
250
262
|
});
|
|
@@ -262,15 +274,13 @@ export class FirestoreEntityStorageConnector {
|
|
|
262
274
|
async setBatch(entities) {
|
|
263
275
|
Guards.arrayValue(FirestoreEntityStorageConnector.CLASS_NAME, "entities", entities);
|
|
264
276
|
const contextIds = await ContextIdStore.getContextIds();
|
|
265
|
-
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
266
|
-
|
|
267
|
-
EntitySchemaHelper.validateEntity(entity, this.getSchema());
|
|
268
|
-
}
|
|
277
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds, FirestoreEntityStorageConnector._PARTITION_SEPARATOR);
|
|
278
|
+
const preparedEntities = entities.map(entity => EntityHelper.prepareEntity(entity, this._entitySchema));
|
|
269
279
|
try {
|
|
270
280
|
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
271
|
-
const chunkSize =
|
|
272
|
-
for (let i = 0; i <
|
|
273
|
-
const chunk =
|
|
281
|
+
const chunkSize = FirestoreEntityStorageConnector._DEFAULT_LIMIT;
|
|
282
|
+
for (let i = 0; i < preparedEntities.length; i += chunkSize) {
|
|
283
|
+
const chunk = preparedEntities.slice(i, i + chunkSize);
|
|
274
284
|
const batch = this._firestoreClient.batch();
|
|
275
285
|
for (const entity of chunk) {
|
|
276
286
|
const id = entity[this._primaryKey.property];
|
|
@@ -290,7 +300,7 @@ export class FirestoreEntityStorageConnector {
|
|
|
290
300
|
*/
|
|
291
301
|
async empty() {
|
|
292
302
|
const contextIds = await ContextIdStore.getContextIds();
|
|
293
|
-
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
303
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds, FirestoreEntityStorageConnector._PARTITION_SEPARATOR);
|
|
294
304
|
try {
|
|
295
305
|
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
296
306
|
const snapshot = await collection.get();
|
|
@@ -317,7 +327,7 @@ export class FirestoreEntityStorageConnector {
|
|
|
317
327
|
async remove(id, conditions) {
|
|
318
328
|
Guards.stringValue(FirestoreEntityStorageConnector.CLASS_NAME, "id", id);
|
|
319
329
|
const contextIds = await ContextIdStore.getContextIds();
|
|
320
|
-
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
330
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds, FirestoreEntityStorageConnector._PARTITION_SEPARATOR);
|
|
321
331
|
try {
|
|
322
332
|
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
323
333
|
const docRef = collection.doc(id);
|
|
@@ -354,7 +364,7 @@ export class FirestoreEntityStorageConnector {
|
|
|
354
364
|
async removeBatch(ids) {
|
|
355
365
|
Guards.arrayValue(FirestoreEntityStorageConnector.CLASS_NAME, "ids", ids);
|
|
356
366
|
const contextIds = await ContextIdStore.getContextIds();
|
|
357
|
-
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
367
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds, FirestoreEntityStorageConnector._PARTITION_SEPARATOR);
|
|
358
368
|
try {
|
|
359
369
|
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
360
370
|
const chunkSize = 500;
|
|
@@ -373,14 +383,12 @@ export class FirestoreEntityStorageConnector {
|
|
|
373
383
|
}
|
|
374
384
|
}
|
|
375
385
|
/**
|
|
376
|
-
* Teardown the storage by deleting all documents
|
|
386
|
+
* Teardown the storage by deleting all documents across all partition collections.
|
|
377
387
|
* @param nodeLoggingComponentType The node logging component type.
|
|
378
388
|
* @returns True if the teardown process was successful.
|
|
379
389
|
*/
|
|
380
390
|
async teardown(nodeLoggingComponentType) {
|
|
381
391
|
const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
|
|
382
|
-
const contextIds = await ContextIdStore.getContextIds();
|
|
383
|
-
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
384
392
|
await nodeLogging?.log({
|
|
385
393
|
level: "info",
|
|
386
394
|
source: FirestoreEntityStorageConnector.CLASS_NAME,
|
|
@@ -388,17 +396,7 @@ export class FirestoreEntityStorageConnector {
|
|
|
388
396
|
message: "storeTearingDown"
|
|
389
397
|
});
|
|
390
398
|
try {
|
|
391
|
-
|
|
392
|
-
const snapshot = await collection.get();
|
|
393
|
-
const chunkSize = 500;
|
|
394
|
-
for (let i = 0; i < snapshot.docs.length; i += chunkSize) {
|
|
395
|
-
const chunk = snapshot.docs.slice(i, i + chunkSize);
|
|
396
|
-
const batch = this._firestoreClient.batch();
|
|
397
|
-
for (const doc of chunk) {
|
|
398
|
-
batch.delete(doc.ref);
|
|
399
|
-
}
|
|
400
|
-
await batch.commit();
|
|
401
|
-
}
|
|
399
|
+
await this.deleteAllPartitionCollections(this._config.collectionName);
|
|
402
400
|
await nodeLogging?.log({
|
|
403
401
|
level: "info",
|
|
404
402
|
source: FirestoreEntityStorageConnector.CLASS_NAME,
|
|
@@ -418,6 +416,86 @@ export class FirestoreEntityStorageConnector {
|
|
|
418
416
|
return false;
|
|
419
417
|
}
|
|
420
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Get a unique list of all the context ids from the storage.
|
|
421
|
+
* @returns The list of unique context ids.
|
|
422
|
+
*/
|
|
423
|
+
async getPartitionContextIds() {
|
|
424
|
+
const partitionContextIds = this._partitionContextIds;
|
|
425
|
+
if (!Is.arrayValue(partitionContextIds)) {
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
const prefix = `${this._config.collectionName}_`;
|
|
430
|
+
const collections = await this._firestoreClient.listCollections();
|
|
431
|
+
const result = [];
|
|
432
|
+
for (const col of collections) {
|
|
433
|
+
if (col.id.startsWith(prefix)) {
|
|
434
|
+
const partitionKey = col.id.slice(prefix.length);
|
|
435
|
+
if (Is.stringValue(partitionKey)) {
|
|
436
|
+
result.push(ContextIdHelper.shortSplit(partitionContextIds, partitionKey, FirestoreEntityStorageConnector._PARTITION_SEPARATOR));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return result;
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
throw new GeneralError(FirestoreEntityStorageConnector.CLASS_NAME, "getPartitionContextIdsFailed", undefined, err);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Create the target connector for performing the migration using a temporary collection name.
|
|
448
|
+
* @param newEntitySchema The name of the new entity schema to create the connector for.
|
|
449
|
+
* @returns Connector for performing the migration.
|
|
450
|
+
*/
|
|
451
|
+
async createTargetConnector(newEntitySchema) {
|
|
452
|
+
const migrationCollectionName = `${this._config.collectionName}Migration${Date.now()}`;
|
|
453
|
+
return new FirestoreEntityStorageConnector({
|
|
454
|
+
entitySchema: newEntitySchema,
|
|
455
|
+
config: { ...this._config, collectionName: migrationCollectionName },
|
|
456
|
+
partitionContextIds: this._partitionContextIds
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Finalize the migration by tearing down the old collections and replacing them with the target collections.
|
|
461
|
+
* @param targetConnector The target connector to finalize the migration with.
|
|
462
|
+
* @param options The options to control how the migration is finalized.
|
|
463
|
+
* @param loggingComponentType The optional component type to use for logging.
|
|
464
|
+
* @returns The final connector pointing at the original collection name.
|
|
465
|
+
*/
|
|
466
|
+
async finalizeMigration(targetConnector, options, loggingComponentType) {
|
|
467
|
+
// Firestore has no collection-rename operation, so we create fresh collections under
|
|
468
|
+
// the original name, copy all documents from the migration collections, then delete the
|
|
469
|
+
// migration collections.
|
|
470
|
+
// Teardown all existing source collections to free up the original collection name prefix.
|
|
471
|
+
await this.teardown(loggingComponentType);
|
|
472
|
+
// Create a new connector at the original collection name but with the new schema.
|
|
473
|
+
const originalCollectionName = this._config.collectionName;
|
|
474
|
+
const finalConnector = new FirestoreEntityStorageConnector({
|
|
475
|
+
entitySchema: targetConnector._entitySchemaName,
|
|
476
|
+
config: { ...targetConnector._config, collectionName: originalCollectionName },
|
|
477
|
+
partitionContextIds: this._partitionContextIds
|
|
478
|
+
});
|
|
479
|
+
if (await finalConnector.bootstrap(loggingComponentType)) {
|
|
480
|
+
// Since there is no rename, we need to copy the data from the migration table to the new table
|
|
481
|
+
const partitions = await targetConnector.getPartitionContextIds();
|
|
482
|
+
const batchSize = options?.batchSize ?? FirestoreEntityStorageConnector._DEFAULT_LIMIT;
|
|
483
|
+
await this.bulkCopy(targetConnector, finalConnector, partitions, batchSize);
|
|
484
|
+
await targetConnector.teardown(loggingComponentType);
|
|
485
|
+
return finalConnector;
|
|
486
|
+
}
|
|
487
|
+
throw new GeneralError(FirestoreEntityStorageConnector.CLASS_NAME, "finalizeMigrationFailedBootstrap", undefined);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Cleanup the migration if a migration fails or needs to be aborted.
|
|
491
|
+
* @param targetConnector The target connector to cleanup.
|
|
492
|
+
* @param options The options to control how the migration is cleaned up.
|
|
493
|
+
* @param loggingComponentType The optional component type to use for logging.
|
|
494
|
+
*/
|
|
495
|
+
async cleanupMigration(targetConnector, options, loggingComponentType) {
|
|
496
|
+
// If something failed the only thing to cleanup is the migration table
|
|
497
|
+
await targetConnector?.teardown?.(loggingComponentType);
|
|
498
|
+
}
|
|
421
499
|
/**
|
|
422
500
|
* Find all the entities which match the conditions.
|
|
423
501
|
* @param conditions The conditions to match for the entities.
|
|
@@ -430,9 +508,44 @@ export class FirestoreEntityStorageConnector {
|
|
|
430
508
|
async query(conditions, sortProperties, properties, cursor, limit) {
|
|
431
509
|
const queryDescription = [];
|
|
432
510
|
const contextIds = await ContextIdStore.getContextIds();
|
|
433
|
-
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
511
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds, FirestoreEntityStorageConnector._PARTITION_SEPARATOR);
|
|
512
|
+
const finalLimit = limit ?? FirestoreEntityStorageConnector._DEFAULT_LIMIT;
|
|
434
513
|
try {
|
|
435
514
|
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
515
|
+
// Firestore has no native substring search. When any condition needs in-memory
|
|
516
|
+
// filtering (string Includes / NotIncludes), fetch all matching docs and filter
|
|
517
|
+
// client-side, using an index-based cursor for pagination.
|
|
518
|
+
if (!Is.empty(conditions) && this.needsPostFilter(conditions)) {
|
|
519
|
+
queryDescription.push("InMemoryFilter");
|
|
520
|
+
let baseQuery = collection;
|
|
521
|
+
if (Is.arrayValue(sortProperties)) {
|
|
522
|
+
for (const { property, sortDirection } of sortProperties) {
|
|
523
|
+
baseQuery = baseQuery.orderBy(property, sortDirection === SortDirection.Ascending ? "asc" : "desc");
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const allSnapshot = await baseQuery.get();
|
|
527
|
+
let allEntities = allSnapshot.docs.map((doc) => EntityHelper.unPrepareEntity(doc.data(), []));
|
|
528
|
+
allEntities = allEntities.filter(e => EntityConditions.check(e, conditions));
|
|
529
|
+
let projected;
|
|
530
|
+
if (Is.arrayValue(properties)) {
|
|
531
|
+
projected = allEntities.map(e => {
|
|
532
|
+
const out = {};
|
|
533
|
+
for (const prop of properties) {
|
|
534
|
+
if (prop in e) {
|
|
535
|
+
out[prop] = e[prop];
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return out;
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
projected = allEntities;
|
|
543
|
+
}
|
|
544
|
+
const start = Is.stringValue(cursor) ? Number.parseInt(cursor, 10) : 0;
|
|
545
|
+
const page = projected.slice(start, start + finalLimit);
|
|
546
|
+
const nextCursor = start + finalLimit < projected.length ? String(start + finalLimit) : undefined;
|
|
547
|
+
return { entities: page, cursor: nextCursor };
|
|
548
|
+
}
|
|
436
549
|
let query = collection;
|
|
437
550
|
if (!Is.empty(conditions)) {
|
|
438
551
|
query = this.applyConditions(query, conditions);
|
|
@@ -451,18 +564,19 @@ export class FirestoreEntityStorageConnector {
|
|
|
451
564
|
}
|
|
452
565
|
queryDescription.push(`Cursor: ${cursor}`);
|
|
453
566
|
}
|
|
454
|
-
|
|
455
|
-
query = query.limit(finalLimit);
|
|
567
|
+
query = query.limit(finalLimit + 1);
|
|
456
568
|
queryDescription.push(`Limit: ${finalLimit}`);
|
|
457
|
-
if (properties) {
|
|
569
|
+
if (Is.arrayValue(properties)) {
|
|
458
570
|
query = query.select(...properties);
|
|
459
571
|
queryDescription.push(`Properties: ${properties.join(", ")}`);
|
|
460
572
|
}
|
|
461
573
|
const querySnapshot = await query.get();
|
|
462
|
-
const
|
|
574
|
+
const hasMore = querySnapshot.docs.length > finalLimit;
|
|
575
|
+
const resultDocs = hasMore ? querySnapshot.docs.slice(0, finalLimit) : querySnapshot.docs;
|
|
576
|
+
const entities = resultDocs.map((doc) => EntityHelper.unPrepareEntity(doc.data(), []));
|
|
463
577
|
let nextCursor;
|
|
464
|
-
if (
|
|
465
|
-
nextCursor =
|
|
578
|
+
if (hasMore) {
|
|
579
|
+
nextCursor = resultDocs[resultDocs.length - 1].ref.path;
|
|
466
580
|
}
|
|
467
581
|
return {
|
|
468
582
|
entities,
|
|
@@ -475,14 +589,24 @@ export class FirestoreEntityStorageConnector {
|
|
|
475
589
|
}
|
|
476
590
|
/**
|
|
477
591
|
* Count all the entities which match the conditions.
|
|
592
|
+
* @param conditions The optional conditions to match for the entities.
|
|
478
593
|
* @returns The total count of entities in the storage.
|
|
479
594
|
*/
|
|
480
|
-
async count() {
|
|
595
|
+
async count(conditions) {
|
|
481
596
|
try {
|
|
482
597
|
const contextIds = await ContextIdStore.getContextIds();
|
|
483
|
-
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
598
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds, FirestoreEntityStorageConnector._PARTITION_SEPARATOR);
|
|
484
599
|
const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
|
|
485
|
-
|
|
600
|
+
if (!Is.empty(conditions) && this.needsPostFilter(conditions)) {
|
|
601
|
+
const allSnapshot = await collection.get();
|
|
602
|
+
const allEntities = allSnapshot.docs.map((doc) => EntityHelper.unPrepareEntity(doc.data(), []));
|
|
603
|
+
return allEntities.filter(e => EntityConditions.check(e, conditions)).length;
|
|
604
|
+
}
|
|
605
|
+
let query = collection;
|
|
606
|
+
if (!Is.empty(conditions)) {
|
|
607
|
+
query = this.applyConditions(query, conditions);
|
|
608
|
+
}
|
|
609
|
+
const snapshot = await query.count().get();
|
|
486
610
|
return snapshot.data().count;
|
|
487
611
|
}
|
|
488
612
|
catch (err) {
|
|
@@ -490,44 +614,147 @@ export class FirestoreEntityStorageConnector {
|
|
|
490
614
|
}
|
|
491
615
|
}
|
|
492
616
|
/**
|
|
493
|
-
*
|
|
617
|
+
* Copy all entities from sourceConnector to destConnector, paging through each partition.
|
|
618
|
+
* @param sourceConnector The connector to read entities from.
|
|
619
|
+
* @param destConnector The connector to write entities to.
|
|
620
|
+
* @param partitions The partition list returned by getPartitionContextIds.
|
|
621
|
+
* @param batchSize The number of entities to read per page.
|
|
622
|
+
* @internal
|
|
623
|
+
*/
|
|
624
|
+
async bulkCopy(sourceConnector, destConnector, partitions, batchSize) {
|
|
625
|
+
let partitionList;
|
|
626
|
+
if (Is.arrayValue(partitions)) {
|
|
627
|
+
partitionList = partitions;
|
|
628
|
+
}
|
|
629
|
+
else if (Is.arrayValue(sourceConnector._partitionContextIds)) {
|
|
630
|
+
partitionList = [];
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
partitionList = [{}];
|
|
634
|
+
}
|
|
635
|
+
for (let i = 0; i < partitionList.length; i++) {
|
|
636
|
+
const partitionKey = ContextIdHelper.combinedContextKey(partitionList[i], sourceConnector._partitionContextIds, FirestoreEntityStorageConnector._PARTITION_SEPARATOR);
|
|
637
|
+
const sourceCollection = sourceConnector._firestoreClient.collection(sourceConnector.collectionName(partitionKey));
|
|
638
|
+
const destCollection = destConnector._firestoreClient.collection(destConnector.collectionName(partitionKey));
|
|
639
|
+
let lastDoc;
|
|
640
|
+
let hasMore = true;
|
|
641
|
+
while (hasMore) {
|
|
642
|
+
let pageQuery = sourceCollection.limit(batchSize);
|
|
643
|
+
if (lastDoc) {
|
|
644
|
+
pageQuery = pageQuery.startAfter(lastDoc);
|
|
645
|
+
}
|
|
646
|
+
const snapshot = await pageQuery.get();
|
|
647
|
+
const docs = snapshot.docs;
|
|
648
|
+
for (let j = 0; j < docs.length; j += batchSize) {
|
|
649
|
+
const chunk = docs.slice(j, j + batchSize);
|
|
650
|
+
const batch = destConnector._firestoreClient.batch();
|
|
651
|
+
for (const doc of chunk) {
|
|
652
|
+
batch.set(destCollection.doc(doc.id), doc.data());
|
|
653
|
+
}
|
|
654
|
+
await batch.commit();
|
|
655
|
+
}
|
|
656
|
+
lastDoc = docs[docs.length - 1];
|
|
657
|
+
hasMore = docs.length === batchSize;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Delete all documents in every collection whose name starts with collectionName_.
|
|
663
|
+
* @param collectionName The base collection name prefix.
|
|
664
|
+
* @internal
|
|
665
|
+
*/
|
|
666
|
+
async deleteAllPartitionCollections(collectionName) {
|
|
667
|
+
const prefix = `${collectionName}_`;
|
|
668
|
+
const collections = await this._firestoreClient.listCollections();
|
|
669
|
+
const chunkSize = 500;
|
|
670
|
+
for (const col of collections) {
|
|
671
|
+
if (col.id.startsWith(prefix)) {
|
|
672
|
+
const snapshot = await col.get();
|
|
673
|
+
for (let i = 0; i < snapshot.docs.length; i += chunkSize) {
|
|
674
|
+
const chunk = snapshot.docs.slice(i, i + chunkSize);
|
|
675
|
+
const batch = this._firestoreClient.batch();
|
|
676
|
+
for (const doc of chunk) {
|
|
677
|
+
batch.delete(doc.ref);
|
|
678
|
+
}
|
|
679
|
+
await batch.commit();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Returns true when any leaf condition requires client-side filtering
|
|
686
|
+
* (Firestore has no native string-contains / not-contains operator).
|
|
687
|
+
* @param condition The condition tree to inspect.
|
|
688
|
+
* @returns True if post-filtering is required.
|
|
689
|
+
* @internal
|
|
690
|
+
*/
|
|
691
|
+
needsPostFilter(condition) {
|
|
692
|
+
if (Is.empty(condition)) {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
if ("conditions" in condition) {
|
|
696
|
+
return condition.conditions.some(c => this.needsPostFilter(c));
|
|
697
|
+
}
|
|
698
|
+
const { comparison, value } = condition;
|
|
699
|
+
if (comparison === ComparisonOperator.NotIncludes) {
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
// Includes on a primitive (string/number) means substring search — not natively supported.
|
|
703
|
+
// Includes on an object means array-contains, which Firestore does support.
|
|
704
|
+
if (comparison === ComparisonOperator.Includes &&
|
|
705
|
+
(value === null || typeof value !== "object")) {
|
|
706
|
+
return true;
|
|
707
|
+
}
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Apply conditions to a Firestore query using composite Filter objects so that
|
|
712
|
+
* OR groups are handled correctly.
|
|
494
713
|
* @param query The initial query.
|
|
495
714
|
* @param condition The condition to apply.
|
|
496
715
|
* @returns The updated query.
|
|
497
716
|
* @internal
|
|
498
717
|
*/
|
|
499
718
|
applyConditions(query, condition) {
|
|
719
|
+
return query.where(this.buildFilter(condition));
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Recursively convert an EntityCondition tree into a Firestore Filter.
|
|
723
|
+
* Only called for native conditions (needsPostFilter must be false).
|
|
724
|
+
* @param condition The condition to convert.
|
|
725
|
+
* @returns A Firestore Filter.
|
|
726
|
+
* @internal
|
|
727
|
+
*/
|
|
728
|
+
buildFilter(condition) {
|
|
500
729
|
if ("conditions" in condition) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
return query;
|
|
730
|
+
const filters = condition.conditions.map(c => this.buildFilter(c));
|
|
731
|
+
return condition.logicalOperator === LogicalOperator.Or
|
|
732
|
+
? Filter.or(...filters)
|
|
733
|
+
: Filter.and(...filters);
|
|
506
734
|
}
|
|
507
|
-
// It's a single condition
|
|
508
735
|
const { property, comparison } = condition;
|
|
509
|
-
// Firestore has no undefined type —
|
|
510
|
-
// For Equals/NotEquals, null already has the correct semantics:
|
|
736
|
+
// Firestore has no undefined type — null has the correct semantics:
|
|
511
737
|
// == null matches documents where the field is null OR missing
|
|
512
738
|
// != null matches documents where the field exists and is not null
|
|
513
739
|
const value = condition.value === undefined ? null : condition.value;
|
|
514
740
|
switch (comparison) {
|
|
515
741
|
case ComparisonOperator.Equals:
|
|
516
|
-
return
|
|
742
|
+
return Filter.where(property, "==", value);
|
|
517
743
|
case ComparisonOperator.NotEquals:
|
|
518
|
-
return
|
|
744
|
+
return Filter.where(property, "!=", value);
|
|
519
745
|
case ComparisonOperator.GreaterThan:
|
|
520
|
-
return
|
|
746
|
+
return Filter.where(property, ">", value);
|
|
521
747
|
case ComparisonOperator.LessThan:
|
|
522
|
-
return
|
|
748
|
+
return Filter.where(property, "<", value);
|
|
523
749
|
case ComparisonOperator.GreaterThanOrEqual:
|
|
524
|
-
return
|
|
750
|
+
return Filter.where(property, ">=", value);
|
|
525
751
|
case ComparisonOperator.LessThanOrEqual:
|
|
526
|
-
return
|
|
752
|
+
return Filter.where(property, "<=", value);
|
|
527
753
|
case ComparisonOperator.In:
|
|
528
|
-
return
|
|
754
|
+
return Filter.where(property, "in", value);
|
|
529
755
|
case ComparisonOperator.Includes:
|
|
530
|
-
|
|
756
|
+
// Object value → array-contains (caller ensured needsPostFilter is false here)
|
|
757
|
+
return Filter.where(property, "array-contains", value);
|
|
531
758
|
case ComparisonOperator.NotIncludes:
|
|
532
759
|
default:
|
|
533
760
|
throw new GeneralError(FirestoreEntityStorageConnector.CLASS_NAME, "unsupportedComparisonOperator", { comparison });
|
|
@@ -1 +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\tHealthStatus,\n\ttype IHealth,\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 * Returns the health status of the component.\n\t * @returns The health status of the component.\n\t */\n\tpublic async health(): Promise<IHealth[]> {\n\t\ttry {\n\t\t\tawait this._firestoreClient.listCollections();\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tsource: FirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\tdata: { projectId: this._config.projectId, collectionName: this._config.collectionName }\n\t\t\t\t}\n\t\t\t];\n\t\t} catch {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tsource: FirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\tmessage: \"connectionFailed\",\n\t\t\t\t\tdata: { projectId: this._config.projectId, collectionName: this._config.collectionName }\n\t\t\t\t}\n\t\t\t];\n\t\t}\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 * Set multiple entities in a batch.\n\t * @param entities The entities to set.\n\t * @returns Nothing.\n\t */\n\tpublic async setBatch(entities: T[]): Promise<void> {\n\t\tGuards.arrayValue(FirestoreEntityStorageConnector.CLASS_NAME, nameof(entities), entities);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tfor (const entity of entities) {\n\t\t\tEntitySchemaHelper.validateEntity(entity, this.getSchema());\n\t\t}\n\n\t\ttry {\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\t\t\tconst chunkSize = 500;\n\t\t\tfor (let i = 0; i < entities.length; i += chunkSize) {\n\t\t\t\tconst chunk = entities.slice(i, i + chunkSize);\n\t\t\t\tconst batch = this._firestoreClient.batch();\n\t\t\t\tfor (const entity of chunk) {\n\t\t\t\t\tconst id = entity[this._primaryKey.property] as string;\n\t\t\t\t\tconst docRef = collection.doc(id);\n\t\t\t\t\tbatch.set(docRef, entity as { [key: string]: unknown });\n\t\t\t\t}\n\t\t\t\tawait batch.commit();\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\"setBatchFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Empty the storage by deleting all entities in the collection.\n\t * @returns Nothing.\n\t */\n\tpublic async empty(): Promise<void> {\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 snapshot = await collection.get();\n\t\t\tconst chunkSize = 500;\n\t\t\tfor (let i = 0; i < snapshot.docs.length; i += chunkSize) {\n\t\t\t\tconst chunk = snapshot.docs.slice(i, i + chunkSize);\n\t\t\t\tconst batch = this._firestoreClient.batch();\n\t\t\t\tfor (const doc of chunk) {\n\t\t\t\t\tbatch.delete(doc.ref);\n\t\t\t\t}\n\t\t\t\tawait batch.commit();\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\"emptyFailed\",\n\t\t\t\tundefined,\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 * Remove multiple entities by their primary key IDs using a Firestore WriteBatch.\n\t * @param ids The ids of the entities to remove.\n\t * @returns Nothing.\n\t */\n\tpublic async removeBatch(ids: string[]): Promise<void> {\n\t\tGuards.arrayValue(FirestoreEntityStorageConnector.CLASS_NAME, nameof(ids), ids);\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 chunkSize = 500;\n\t\t\tfor (let i = 0; i < ids.length; i += chunkSize) {\n\t\t\t\tconst chunk = ids.slice(i, i + chunkSize);\n\t\t\t\tconst batch = this._firestoreClient.batch();\n\t\t\t\tfor (const id of chunk) {\n\t\t\t\t\tconst docRef = collection.doc(id);\n\t\t\t\t\tbatch.delete(docRef);\n\t\t\t\t}\n\t\t\t\tawait batch.commit();\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\"removeBatchFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Teardown the storage by deleting all documents in the partition collection.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns True if the teardown process was successful.\n\t */\n\tpublic async teardown(nodeLoggingComponentType?: string): Promise<boolean> {\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tawait nodeLogging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: FirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: \"storeTearingDown\"\n\t\t});\n\n\t\ttry {\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\t\t\tconst snapshot = await collection.get();\n\t\t\tconst chunkSize = 500;\n\t\t\tfor (let i = 0; i < snapshot.docs.length; i += chunkSize) {\n\t\t\t\tconst chunk = snapshot.docs.slice(i, i + chunkSize);\n\t\t\t\tconst batch = this._firestoreClient.batch();\n\t\t\t\tfor (const doc of chunk) {\n\t\t\t\t\tbatch.delete(doc.ref);\n\t\t\t\t}\n\t\t\t\tawait batch.commit();\n\t\t\t}\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: \"storeTornDown\"\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: \"teardownFailed\",\n\t\t\t\terror: BaseError.fromError(err)\n\t\t\t});\n\t\t\treturn false;\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 * Count all the entities which match the conditions.\n\t * @returns The total count of entities in the storage.\n\t */\n\tpublic async count(): Promise<number> {\n\t\ttry {\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tconst partitionKey = ContextIdHelper.combinedContextKey(\n\t\t\t\tcontextIds,\n\t\t\t\tthis._partitionContextIds\n\t\t\t);\n\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\t\t\tconst snapshot = await collection.count().get();\n\t\t\treturn snapshot.data().count;\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"countFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\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, comparison } = condition;\n\t\t// Firestore has no undefined type — the SDK throws on undefined values.\n\t\t// For Equals/NotEquals, null already has the correct semantics:\n\t\t// == null matches documents where the field is null OR missing\n\t\t// != null matches documents where the field exists and is not null\n\t\tconst value = condition.value === undefined ? null : condition.value;\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 * 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"]}
|
|
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\tFilter,\n\tFirestore,\n\ttype Query,\n\ttype Settings\n} from \"@google-cloud/firestore\";\nimport { ContextIdHelper, ContextIdStore, type IContextIds } from \"@twin.org/context\";\nimport {\n\tBaseError,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tHealthStatus,\n\ttype IHealth,\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\tLogicalOperator,\n\tSortDirection\n} from \"@twin.org/entity\";\nimport {\n\tEntityHelper,\n\ttype IEntityStorageConnector,\n\ttype IEntityStorageMigrationConnector,\n\ttype IMigrationOptions\n} 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<\n\tT = unknown\n> implements IEntityStorageMigrationConnector<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 * Separator used between context ID parts in Firestore collection names.\n\t * Must not be \"/\" which Firestore interprets as a path separator.\n\t * @internal\n\t */\n\tprivate static readonly _PARTITION_SEPARATOR: string = \":\";\n\n\t/**\n\t * The name for the schema.\n\t * @internal\n\t */\n\tprivate readonly _entitySchemaName: string;\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._entitySchemaName = options.entitySchema;\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 * Returns the health status of the component.\n\t * @returns The health status of the component.\n\t */\n\tpublic async health(): Promise<IHealth[]> {\n\t\ttry {\n\t\t\tawait this._firestoreClient.listCollections();\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tsource: FirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\tdata: { projectId: this._config.projectId, collectionName: this._config.collectionName }\n\t\t\t\t}\n\t\t\t];\n\t\t} catch {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tsource: FirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\tmessage: \"connectionFailed\",\n\t\t\t\t\tdata: { projectId: this._config.projectId, collectionName: this._config.collectionName }\n\t\t\t\t}\n\t\t\t];\n\t\t}\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(\n\t\t\tcontextIds,\n\t\t\tthis._partitionContextIds,\n\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t);\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 EntityHelper.unPrepareEntity<T>(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\treturn EntityHelper.unPrepareEntity<T>(querySnapshot.docs[0].data() as 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\"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(\n\t\t\tcontextIds,\n\t\t\tthis._partitionContextIds,\n\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t);\n\n\t\tconst prepared = EntityHelper.prepareEntity(entity, this._entitySchema);\n\n\t\ttry {\n\t\t\tconst id = prepared[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(prepared);\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, prepared as object);\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, prepared as object);\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 * Set multiple entities in a batch.\n\t * @param entities The entities to set.\n\t * @returns Nothing.\n\t */\n\tpublic async setBatch(entities: T[]): Promise<void> {\n\t\tGuards.arrayValue(FirestoreEntityStorageConnector.CLASS_NAME, nameof(entities), entities);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(\n\t\t\tcontextIds,\n\t\t\tthis._partitionContextIds,\n\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t);\n\n\t\tconst preparedEntities = entities.map(entity =>\n\t\t\tEntityHelper.prepareEntity(entity, this._entitySchema)\n\t\t);\n\n\t\ttry {\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\t\t\tconst chunkSize = FirestoreEntityStorageConnector._DEFAULT_LIMIT;\n\t\t\tfor (let i = 0; i < preparedEntities.length; i += chunkSize) {\n\t\t\t\tconst chunk = preparedEntities.slice(i, i + chunkSize);\n\t\t\t\tconst batch = this._firestoreClient.batch();\n\t\t\t\tfor (const entity of chunk) {\n\t\t\t\t\tconst id = entity[this._primaryKey.property] as string;\n\t\t\t\t\tconst docRef = collection.doc(id);\n\t\t\t\t\tbatch.set(docRef, entity as { [key: string]: unknown });\n\t\t\t\t}\n\t\t\t\tawait batch.commit();\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\"setBatchFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Empty the storage by deleting all entities in the collection.\n\t * @returns Nothing.\n\t */\n\tpublic async empty(): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(\n\t\t\tcontextIds,\n\t\t\tthis._partitionContextIds,\n\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t);\n\n\t\ttry {\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\t\t\tconst snapshot = await collection.get();\n\t\t\tconst chunkSize = 500;\n\t\t\tfor (let i = 0; i < snapshot.docs.length; i += chunkSize) {\n\t\t\t\tconst chunk = snapshot.docs.slice(i, i + chunkSize);\n\t\t\t\tconst batch = this._firestoreClient.batch();\n\t\t\t\tfor (const doc of chunk) {\n\t\t\t\t\tbatch.delete(doc.ref);\n\t\t\t\t}\n\t\t\t\tawait batch.commit();\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\"emptyFailed\",\n\t\t\t\tundefined,\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(\n\t\t\tcontextIds,\n\t\t\tthis._partitionContextIds,\n\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t);\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 * Remove multiple entities by their primary key IDs using a Firestore WriteBatch.\n\t * @param ids The ids of the entities to remove.\n\t * @returns Nothing.\n\t */\n\tpublic async removeBatch(ids: string[]): Promise<void> {\n\t\tGuards.arrayValue(FirestoreEntityStorageConnector.CLASS_NAME, nameof(ids), ids);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(\n\t\t\tcontextIds,\n\t\t\tthis._partitionContextIds,\n\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t);\n\n\t\ttry {\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\t\t\tconst chunkSize = 500;\n\t\t\tfor (let i = 0; i < ids.length; i += chunkSize) {\n\t\t\t\tconst chunk = ids.slice(i, i + chunkSize);\n\t\t\t\tconst batch = this._firestoreClient.batch();\n\t\t\t\tfor (const id of chunk) {\n\t\t\t\t\tconst docRef = collection.doc(id);\n\t\t\t\t\tbatch.delete(docRef);\n\t\t\t\t}\n\t\t\t\tawait batch.commit();\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\"removeBatchFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Teardown the storage by deleting all documents across all partition collections.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns True if the teardown process was successful.\n\t */\n\tpublic async teardown(nodeLoggingComponentType?: string): Promise<boolean> {\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\n\t\tawait nodeLogging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: FirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: \"storeTearingDown\"\n\t\t});\n\n\t\ttry {\n\t\t\tawait this.deleteAllPartitionCollections(this._config.collectionName);\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: \"storeTornDown\"\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: \"teardownFailed\",\n\t\t\t\terror: BaseError.fromError(err)\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Get a unique list of all the context ids from the storage.\n\t * @returns The list of unique context ids.\n\t */\n\tpublic async getPartitionContextIds(): Promise<IContextIds[]> {\n\t\tconst partitionContextIds = this._partitionContextIds;\n\t\tif (!Is.arrayValue(partitionContextIds)) {\n\t\t\treturn [];\n\t\t}\n\t\ttry {\n\t\t\tconst prefix = `${this._config.collectionName}_`;\n\t\t\tconst collections = await this._firestoreClient.listCollections();\n\t\t\tconst result: IContextIds[] = [];\n\t\t\tfor (const col of collections) {\n\t\t\t\tif (col.id.startsWith(prefix)) {\n\t\t\t\t\tconst partitionKey = col.id.slice(prefix.length);\n\t\t\t\t\tif (Is.stringValue(partitionKey)) {\n\t\t\t\t\t\tresult.push(\n\t\t\t\t\t\t\tContextIdHelper.shortSplit(\n\t\t\t\t\t\t\t\tpartitionContextIds,\n\t\t\t\t\t\t\t\tpartitionKey,\n\t\t\t\t\t\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t\t\t\t\t\t)\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\treturn result;\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"getPartitionContextIdsFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Create the target connector for performing the migration using a temporary collection name.\n\t * @param newEntitySchema The name of the new entity schema to create the connector for.\n\t * @returns Connector for performing the migration.\n\t */\n\tpublic async createTargetConnector<U>(\n\t\tnewEntitySchema: string\n\t): Promise<IEntityStorageConnector<U>> {\n\t\tconst migrationCollectionName = `${this._config.collectionName}Migration${Date.now()}`;\n\t\treturn new FirestoreEntityStorageConnector<U>({\n\t\t\tentitySchema: newEntitySchema,\n\t\t\tconfig: { ...this._config, collectionName: migrationCollectionName },\n\t\t\tpartitionContextIds: this._partitionContextIds\n\t\t});\n\t}\n\n\t/**\n\t * Finalize the migration by tearing down the old collections and replacing them with the target collections.\n\t * @param targetConnector The target connector to finalize the migration with.\n\t * @param options The options to control how the migration is finalized.\n\t * @param loggingComponentType The optional component type to use for logging.\n\t * @returns The final connector pointing at the original collection name.\n\t */\n\tpublic async finalizeMigration<U>(\n\t\ttargetConnector: FirestoreEntityStorageConnector<U>,\n\t\toptions?: IMigrationOptions<T, U>,\n\t\tloggingComponentType?: string\n\t): Promise<FirestoreEntityStorageConnector<U>> {\n\t\t// Firestore has no collection-rename operation, so we create fresh collections under\n\t\t// the original name, copy all documents from the migration collections, then delete the\n\t\t// migration collections.\n\n\t\t// Teardown all existing source collections to free up the original collection name prefix.\n\t\tawait this.teardown(loggingComponentType);\n\n\t\t// Create a new connector at the original collection name but with the new schema.\n\t\tconst originalCollectionName = this._config.collectionName;\n\t\tconst finalConnector = new FirestoreEntityStorageConnector<U>({\n\t\t\tentitySchema: targetConnector._entitySchemaName,\n\t\t\tconfig: { ...targetConnector._config, collectionName: originalCollectionName },\n\t\t\tpartitionContextIds: this._partitionContextIds\n\t\t});\n\n\t\tif (await finalConnector.bootstrap(loggingComponentType)) {\n\t\t\t// Since there is no rename, we need to copy the data from the migration table to the new table\n\t\t\tconst partitions = await targetConnector.getPartitionContextIds();\n\t\t\tconst batchSize = options?.batchSize ?? FirestoreEntityStorageConnector._DEFAULT_LIMIT;\n\t\t\tawait this.bulkCopy(targetConnector, finalConnector, partitions, batchSize);\n\n\t\t\tawait targetConnector.teardown(loggingComponentType);\n\n\t\t\treturn finalConnector;\n\t\t}\n\t\tthrow new GeneralError(\n\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\"finalizeMigrationFailedBootstrap\",\n\t\t\tundefined\n\t\t);\n\t}\n\n\t/**\n\t * Cleanup the migration if a migration fails or needs to be aborted.\n\t * @param targetConnector The target connector to cleanup.\n\t * @param options The options to control how the migration is cleaned up.\n\t * @param loggingComponentType The optional component type to use for logging.\n\t */\n\tpublic async cleanupMigration<U>(\n\t\ttargetConnector: IEntityStorageConnector<U> | undefined,\n\t\toptions?: IMigrationOptions<T, U>,\n\t\tloggingComponentType?: string\n\t): Promise<void> {\n\t\t// If something failed the only thing to cleanup is the migration table\n\t\tawait targetConnector?.teardown?.(loggingComponentType);\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(\n\t\t\tcontextIds,\n\t\t\tthis._partitionContextIds,\n\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t);\n\n\t\tconst finalLimit = limit ?? FirestoreEntityStorageConnector._DEFAULT_LIMIT;\n\n\t\ttry {\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\n\t\t\t// Firestore has no native substring search. When any condition needs in-memory\n\t\t\t// filtering (string Includes / NotIncludes), fetch all matching docs and filter\n\t\t\t// client-side, using an index-based cursor for pagination.\n\t\t\tif (!Is.empty(conditions) && this.needsPostFilter(conditions)) {\n\t\t\t\tqueryDescription.push(\"InMemoryFilter\");\n\n\t\t\t\tlet baseQuery = collection as Query;\n\n\t\t\t\tif (Is.arrayValue(sortProperties)) {\n\t\t\t\t\tfor (const { property, sortDirection } of sortProperties) {\n\t\t\t\t\t\tbaseQuery = baseQuery.orderBy(\n\t\t\t\t\t\t\tproperty as string,\n\t\t\t\t\t\t\tsortDirection === SortDirection.Ascending ? \"asc\" : \"desc\"\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst allSnapshot = await baseQuery.get();\n\t\t\t\tlet allEntities = allSnapshot.docs.map((doc: DocumentSnapshot) =>\n\t\t\t\t\tEntityHelper.unPrepareEntity<T>(doc.data() as T, [])\n\t\t\t\t);\n\n\t\t\t\tallEntities = allEntities.filter(e => EntityConditions.check(e as Partial<T>, conditions));\n\n\t\t\t\tlet projected: Partial<T>[];\n\t\t\t\tif (Is.arrayValue(properties)) {\n\t\t\t\t\tprojected = allEntities.map(e => {\n\t\t\t\t\t\tconst out: Partial<T> = {};\n\t\t\t\t\t\tfor (const prop of properties) {\n\t\t\t\t\t\t\tif (prop in (e as object)) {\n\t\t\t\t\t\t\t\tout[prop] = e[prop];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn out;\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tprojected = allEntities;\n\t\t\t\t}\n\n\t\t\t\tconst start = Is.stringValue(cursor) ? Number.parseInt(cursor, 10) : 0;\n\t\t\t\tconst page = projected.slice(start, start + finalLimit);\n\t\t\t\tconst nextCursor =\n\t\t\t\t\tstart + finalLimit < projected.length ? String(start + finalLimit) : undefined;\n\n\t\t\t\treturn { entities: page, cursor: nextCursor };\n\t\t\t}\n\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\tquery = query.limit(finalLimit + 1);\n\t\t\tqueryDescription.push(`Limit: ${finalLimit}`);\n\n\t\t\tif (Is.arrayValue(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 hasMore = querySnapshot.docs.length > finalLimit;\n\t\t\tconst resultDocs = hasMore ? querySnapshot.docs.slice(0, finalLimit) : querySnapshot.docs;\n\t\t\tconst entities = resultDocs.map((doc: DocumentSnapshot) =>\n\t\t\t\tEntityHelper.unPrepareEntity<T>(doc.data() as T, [])\n\t\t\t);\n\n\t\t\tlet nextCursor: string | undefined;\n\t\t\tif (hasMore) {\n\t\t\t\tnextCursor = resultDocs[resultDocs.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 * Count all the entities which match the conditions.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The total count of entities in the storage.\n\t */\n\tpublic async count(conditions?: EntityCondition<T>): Promise<number> {\n\t\ttry {\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tconst partitionKey = ContextIdHelper.combinedContextKey(\n\t\t\t\tcontextIds,\n\t\t\t\tthis._partitionContextIds,\n\t\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t\t);\n\n\t\t\tconst collection = this._firestoreClient.collection(this.collectionName(partitionKey));\n\n\t\t\tif (!Is.empty(conditions) && this.needsPostFilter(conditions)) {\n\t\t\t\tconst allSnapshot = await collection.get();\n\t\t\t\tconst allEntities = allSnapshot.docs.map((doc: DocumentSnapshot) =>\n\t\t\t\t\tEntityHelper.unPrepareEntity<T>(doc.data() as T, [])\n\t\t\t\t);\n\t\t\t\treturn allEntities.filter(e => EntityConditions.check(e as Partial<T>, conditions)).length;\n\t\t\t}\n\n\t\t\tlet query = collection as Query;\n\t\t\tif (!Is.empty(conditions)) {\n\t\t\t\tquery = this.applyConditions(query, conditions);\n\t\t\t}\n\n\t\t\tconst snapshot = await query.count().get();\n\t\t\treturn snapshot.data().count;\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFirestoreEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"countFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Copy all entities from sourceConnector to destConnector, paging through each partition.\n\t * @param sourceConnector The connector to read entities from.\n\t * @param destConnector The connector to write entities to.\n\t * @param partitions The partition list returned by getPartitionContextIds.\n\t * @param batchSize The number of entities to read per page.\n\t * @internal\n\t */\n\tprivate async bulkCopy<U>(\n\t\tsourceConnector: FirestoreEntityStorageConnector<U>,\n\t\tdestConnector: FirestoreEntityStorageConnector<U>,\n\t\tpartitions: IContextIds[],\n\t\tbatchSize: number\n\t): Promise<void> {\n\t\tlet partitionList: IContextIds[];\n\t\tif (Is.arrayValue(partitions)) {\n\t\t\tpartitionList = partitions;\n\t\t} else if (Is.arrayValue(sourceConnector._partitionContextIds)) {\n\t\t\tpartitionList = [];\n\t\t} else {\n\t\t\tpartitionList = [{}];\n\t\t}\n\n\t\tfor (let i = 0; i < partitionList.length; i++) {\n\t\t\tconst partitionKey = ContextIdHelper.combinedContextKey(\n\t\t\t\tpartitionList[i],\n\t\t\t\tsourceConnector._partitionContextIds,\n\t\t\t\tFirestoreEntityStorageConnector._PARTITION_SEPARATOR\n\t\t\t);\n\n\t\t\tconst sourceCollection = sourceConnector._firestoreClient.collection(\n\t\t\t\tsourceConnector.collectionName(partitionKey)\n\t\t\t);\n\t\t\tconst destCollection = destConnector._firestoreClient.collection(\n\t\t\t\tdestConnector.collectionName(partitionKey)\n\t\t\t);\n\n\t\t\tlet lastDoc: DocumentSnapshot | undefined;\n\t\t\tlet hasMore = true;\n\n\t\t\twhile (hasMore) {\n\t\t\t\tlet pageQuery = sourceCollection.limit(batchSize);\n\t\t\t\tif (lastDoc) {\n\t\t\t\t\tpageQuery = pageQuery.startAfter(lastDoc);\n\t\t\t\t}\n\t\t\t\tconst snapshot = await pageQuery.get();\n\t\t\t\tconst docs = snapshot.docs;\n\n\t\t\t\tfor (let j = 0; j < docs.length; j += batchSize) {\n\t\t\t\t\tconst chunk = docs.slice(j, j + batchSize);\n\t\t\t\t\tconst batch = destConnector._firestoreClient.batch();\n\t\t\t\t\tfor (const doc of chunk) {\n\t\t\t\t\t\tbatch.set(destCollection.doc(doc.id), doc.data());\n\t\t\t\t\t}\n\t\t\t\t\tawait batch.commit();\n\t\t\t\t}\n\n\t\t\t\tlastDoc = docs[docs.length - 1];\n\t\t\t\thasMore = docs.length === batchSize;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Delete all documents in every collection whose name starts with collectionName_.\n\t * @param collectionName The base collection name prefix.\n\t * @internal\n\t */\n\tprivate async deleteAllPartitionCollections(collectionName: string): Promise<void> {\n\t\tconst prefix = `${collectionName}_`;\n\t\tconst collections = await this._firestoreClient.listCollections();\n\t\tconst chunkSize = 500;\n\t\tfor (const col of collections) {\n\t\t\tif (col.id.startsWith(prefix)) {\n\t\t\t\tconst snapshot = await col.get();\n\t\t\t\tfor (let i = 0; i < snapshot.docs.length; i += chunkSize) {\n\t\t\t\t\tconst chunk = snapshot.docs.slice(i, i + chunkSize);\n\t\t\t\t\tconst batch = this._firestoreClient.batch();\n\t\t\t\t\tfor (const doc of chunk) {\n\t\t\t\t\t\tbatch.delete(doc.ref);\n\t\t\t\t\t}\n\t\t\t\t\tawait batch.commit();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns true when any leaf condition requires client-side filtering\n\t * (Firestore has no native string-contains / not-contains operator).\n\t * @param condition The condition tree to inspect.\n\t * @returns True if post-filtering is required.\n\t * @internal\n\t */\n\tprivate needsPostFilter(condition?: EntityCondition<T>): boolean {\n\t\tif (Is.empty(condition)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (\"conditions\" in condition) {\n\t\t\treturn condition.conditions.some(c => this.needsPostFilter(c));\n\t\t}\n\t\tconst { comparison, value } = condition;\n\t\tif (comparison === ComparisonOperator.NotIncludes) {\n\t\t\treturn true;\n\t\t}\n\t\t// Includes on a primitive (string/number) means substring search — not natively supported.\n\t\t// Includes on an object means array-contains, which Firestore does support.\n\t\tif (\n\t\t\tcomparison === ComparisonOperator.Includes &&\n\t\t\t(value === null || typeof value !== \"object\")\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Apply conditions to a Firestore query using composite Filter objects so that\n\t * OR groups are handled correctly.\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\treturn query.where(this.buildFilter(condition));\n\t}\n\n\t/**\n\t * Recursively convert an EntityCondition tree into a Firestore Filter.\n\t * Only called for native conditions (needsPostFilter must be false).\n\t * @param condition The condition to convert.\n\t * @returns A Firestore Filter.\n\t * @internal\n\t */\n\tprivate buildFilter(condition: EntityCondition<T>): Filter {\n\t\tif (\"conditions\" in condition) {\n\t\t\tconst filters = condition.conditions.map(c => this.buildFilter(c));\n\t\t\treturn condition.logicalOperator === LogicalOperator.Or\n\t\t\t\t? Filter.or(...filters)\n\t\t\t\t: Filter.and(...filters);\n\t\t}\n\t\tconst { property, comparison } = condition;\n\t\t// Firestore has no undefined type — null has the correct semantics:\n\t\t// == null matches documents where the field is null OR missing\n\t\t// != null matches documents where the field exists and is not null\n\t\tconst value = condition.value === undefined ? null : condition.value;\n\t\tswitch (comparison) {\n\t\t\tcase ComparisonOperator.Equals:\n\t\t\t\treturn Filter.where(property, \"==\", value);\n\t\t\tcase ComparisonOperator.NotEquals:\n\t\t\t\treturn Filter.where(property, \"!=\", value);\n\t\t\tcase ComparisonOperator.GreaterThan:\n\t\t\t\treturn Filter.where(property, \">\", value);\n\t\t\tcase ComparisonOperator.LessThan:\n\t\t\t\treturn Filter.where(property, \"<\", value);\n\t\t\tcase ComparisonOperator.GreaterThanOrEqual:\n\t\t\t\treturn Filter.where(property, \">=\", value);\n\t\t\tcase ComparisonOperator.LessThanOrEqual:\n\t\t\t\treturn Filter.where(property, \"<=\", value);\n\t\t\tcase ComparisonOperator.In:\n\t\t\t\treturn Filter.where(property, \"in\", value);\n\t\t\tcase ComparisonOperator.Includes:\n\t\t\t\t// Object value → array-contains (caller ensured needsPostFilter is false here)\n\t\t\t\treturn Filter.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 * 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"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { type IContextIds } from "@twin.org/context";
|
|
1
2
|
import { type IHealth } from "@twin.org/core";
|
|
2
3
|
import { type EntityCondition, type IEntitySchema, SortDirection } from "@twin.org/entity";
|
|
3
|
-
import type
|
|
4
|
+
import { type IEntityStorageConnector, type IEntityStorageMigrationConnector, type IMigrationOptions } from "@twin.org/entity-storage-models";
|
|
4
5
|
import type { IFirestoreEntityStorageConnectorConstructorOptions } from "./models/IFirestoreEntityStorageConnectorConstructorOptions.js";
|
|
5
6
|
/**
|
|
6
7
|
* Class for performing entity storage operations using Firestore.
|
|
7
8
|
*/
|
|
8
|
-
export declare class FirestoreEntityStorageConnector<T = unknown> implements
|
|
9
|
+
export declare class FirestoreEntityStorageConnector<T = unknown> implements IEntityStorageMigrationConnector<T> {
|
|
9
10
|
/**
|
|
10
11
|
* Runtime name for the class.
|
|
11
12
|
*/
|
|
@@ -85,11 +86,37 @@ export declare class FirestoreEntityStorageConnector<T = unknown> implements IEn
|
|
|
85
86
|
*/
|
|
86
87
|
removeBatch(ids: string[]): Promise<void>;
|
|
87
88
|
/**
|
|
88
|
-
* Teardown the storage by deleting all documents
|
|
89
|
+
* Teardown the storage by deleting all documents across all partition collections.
|
|
89
90
|
* @param nodeLoggingComponentType The node logging component type.
|
|
90
91
|
* @returns True if the teardown process was successful.
|
|
91
92
|
*/
|
|
92
93
|
teardown(nodeLoggingComponentType?: string): Promise<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Get a unique list of all the context ids from the storage.
|
|
96
|
+
* @returns The list of unique context ids.
|
|
97
|
+
*/
|
|
98
|
+
getPartitionContextIds(): Promise<IContextIds[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Create the target connector for performing the migration using a temporary collection name.
|
|
101
|
+
* @param newEntitySchema The name of the new entity schema to create the connector for.
|
|
102
|
+
* @returns Connector for performing the migration.
|
|
103
|
+
*/
|
|
104
|
+
createTargetConnector<U>(newEntitySchema: string): Promise<IEntityStorageConnector<U>>;
|
|
105
|
+
/**
|
|
106
|
+
* Finalize the migration by tearing down the old collections and replacing them with the target collections.
|
|
107
|
+
* @param targetConnector The target connector to finalize the migration with.
|
|
108
|
+
* @param options The options to control how the migration is finalized.
|
|
109
|
+
* @param loggingComponentType The optional component type to use for logging.
|
|
110
|
+
* @returns The final connector pointing at the original collection name.
|
|
111
|
+
*/
|
|
112
|
+
finalizeMigration<U>(targetConnector: FirestoreEntityStorageConnector<U>, options?: IMigrationOptions<T, U>, loggingComponentType?: string): Promise<FirestoreEntityStorageConnector<U>>;
|
|
113
|
+
/**
|
|
114
|
+
* Cleanup the migration if a migration fails or needs to be aborted.
|
|
115
|
+
* @param targetConnector The target connector to cleanup.
|
|
116
|
+
* @param options The options to control how the migration is cleaned up.
|
|
117
|
+
* @param loggingComponentType The optional component type to use for logging.
|
|
118
|
+
*/
|
|
119
|
+
cleanupMigration<U>(targetConnector: IEntityStorageConnector<U> | undefined, options?: IMigrationOptions<T, U>, loggingComponentType?: string): Promise<void>;
|
|
93
120
|
/**
|
|
94
121
|
* Find all the entities which match the conditions.
|
|
95
122
|
* @param conditions The conditions to match for the entities.
|
|
@@ -114,7 +141,8 @@ export declare class FirestoreEntityStorageConnector<T = unknown> implements IEn
|
|
|
114
141
|
}>;
|
|
115
142
|
/**
|
|
116
143
|
* Count all the entities which match the conditions.
|
|
144
|
+
* @param conditions The optional conditions to match for the entities.
|
|
117
145
|
* @returns The total count of entities in the storage.
|
|
118
146
|
*/
|
|
119
|
-
count(): Promise<number>;
|
|
147
|
+
count(conditions?: EntityCondition<T>): Promise<number>;
|
|
120
148
|
}
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3-next.14](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-connector-gcp-firestore-v0.0.3-next.13...entity-storage-connector-gcp-firestore-v0.0.3-next.14) (2026-05-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* adding schema migration functionality to all the connectors ([#85](https://github.com/iotaledger/twin-entity-storage/issues/85)) ([fd1555a](https://github.com/iotaledger/twin-entity-storage/commit/fd1555a34380158214a577586dafae821e72a578))
|
|
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.13 to 0.0.3-next.14
|
|
16
|
+
* devDependencies
|
|
17
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.3-next.13 to 0.0.3-next.14
|
|
18
|
+
|
|
3
19
|
## [0.0.3-next.13](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-connector-gcp-firestore-v0.0.3-next.12...entity-storage-connector-gcp-firestore-v0.0.3-next.13) (2026-05-13)
|
|
4
20
|
|
|
5
21
|
|
|
@@ -10,7 +10,7 @@ Class for performing entity storage operations using Firestore.
|
|
|
10
10
|
|
|
11
11
|
## Implements
|
|
12
12
|
|
|
13
|
-
- `
|
|
13
|
+
- `IEntityStorageMigrationConnector`\<`T`\>
|
|
14
14
|
|
|
15
15
|
## Constructors
|
|
16
16
|
|
|
@@ -56,7 +56,7 @@ The class name of the component.
|
|
|
56
56
|
|
|
57
57
|
#### Implementation of
|
|
58
58
|
|
|
59
|
-
`
|
|
59
|
+
`IEntityStorageMigrationConnector.className`
|
|
60
60
|
|
|
61
61
|
***
|
|
62
62
|
|
|
@@ -74,7 +74,7 @@ The health status of the component.
|
|
|
74
74
|
|
|
75
75
|
#### Implementation of
|
|
76
76
|
|
|
77
|
-
`
|
|
77
|
+
`IEntityStorageMigrationConnector.health`
|
|
78
78
|
|
|
79
79
|
***
|
|
80
80
|
|
|
@@ -92,7 +92,7 @@ The schema for the entities.
|
|
|
92
92
|
|
|
93
93
|
#### Implementation of
|
|
94
94
|
|
|
95
|
-
`
|
|
95
|
+
`IEntityStorageMigrationConnector.getSchema`
|
|
96
96
|
|
|
97
97
|
***
|
|
98
98
|
|
|
@@ -118,7 +118,7 @@ True if the bootstrapping process was successful.
|
|
|
118
118
|
|
|
119
119
|
#### Implementation of
|
|
120
120
|
|
|
121
|
-
`
|
|
121
|
+
`IEntityStorageMigrationConnector.bootstrap`
|
|
122
122
|
|
|
123
123
|
***
|
|
124
124
|
|
|
@@ -156,7 +156,7 @@ The object if it can be found or undefined.
|
|
|
156
156
|
|
|
157
157
|
#### Implementation of
|
|
158
158
|
|
|
159
|
-
`
|
|
159
|
+
`IEntityStorageMigrationConnector.get`
|
|
160
160
|
|
|
161
161
|
***
|
|
162
162
|
|
|
@@ -188,7 +188,7 @@ Nothing.
|
|
|
188
188
|
|
|
189
189
|
#### Implementation of
|
|
190
190
|
|
|
191
|
-
`
|
|
191
|
+
`IEntityStorageMigrationConnector.set`
|
|
192
192
|
|
|
193
193
|
***
|
|
194
194
|
|
|
@@ -214,7 +214,7 @@ Nothing.
|
|
|
214
214
|
|
|
215
215
|
#### Implementation of
|
|
216
216
|
|
|
217
|
-
`
|
|
217
|
+
`IEntityStorageMigrationConnector.setBatch`
|
|
218
218
|
|
|
219
219
|
***
|
|
220
220
|
|
|
@@ -232,7 +232,7 @@ Nothing.
|
|
|
232
232
|
|
|
233
233
|
#### Implementation of
|
|
234
234
|
|
|
235
|
-
`
|
|
235
|
+
`IEntityStorageMigrationConnector.empty`
|
|
236
236
|
|
|
237
237
|
***
|
|
238
238
|
|
|
@@ -264,7 +264,7 @@ Nothing.
|
|
|
264
264
|
|
|
265
265
|
#### Implementation of
|
|
266
266
|
|
|
267
|
-
`
|
|
267
|
+
`IEntityStorageMigrationConnector.remove`
|
|
268
268
|
|
|
269
269
|
***
|
|
270
270
|
|
|
@@ -290,7 +290,7 @@ Nothing.
|
|
|
290
290
|
|
|
291
291
|
#### Implementation of
|
|
292
292
|
|
|
293
|
-
`
|
|
293
|
+
`IEntityStorageMigrationConnector.removeBatch`
|
|
294
294
|
|
|
295
295
|
***
|
|
296
296
|
|
|
@@ -298,7 +298,7 @@ Nothing.
|
|
|
298
298
|
|
|
299
299
|
> **teardown**(`nodeLoggingComponentType?`): `Promise`\<`boolean`\>
|
|
300
300
|
|
|
301
|
-
Teardown the storage by deleting all documents
|
|
301
|
+
Teardown the storage by deleting all documents across all partition collections.
|
|
302
302
|
|
|
303
303
|
#### Parameters
|
|
304
304
|
|
|
@@ -316,7 +316,143 @@ True if the teardown process was successful.
|
|
|
316
316
|
|
|
317
317
|
#### Implementation of
|
|
318
318
|
|
|
319
|
-
`
|
|
319
|
+
`IEntityStorageMigrationConnector.teardown`
|
|
320
|
+
|
|
321
|
+
***
|
|
322
|
+
|
|
323
|
+
### getPartitionContextIds() {#getpartitioncontextids}
|
|
324
|
+
|
|
325
|
+
> **getPartitionContextIds**(): `Promise`\<`IContextIds`[]\>
|
|
326
|
+
|
|
327
|
+
Get a unique list of all the context ids from the storage.
|
|
328
|
+
|
|
329
|
+
#### Returns
|
|
330
|
+
|
|
331
|
+
`Promise`\<`IContextIds`[]\>
|
|
332
|
+
|
|
333
|
+
The list of unique context ids.
|
|
334
|
+
|
|
335
|
+
#### Implementation of
|
|
336
|
+
|
|
337
|
+
`IEntityStorageMigrationConnector.getPartitionContextIds`
|
|
338
|
+
|
|
339
|
+
***
|
|
340
|
+
|
|
341
|
+
### createTargetConnector() {#createtargetconnector}
|
|
342
|
+
|
|
343
|
+
> **createTargetConnector**\<`U`\>(`newEntitySchema`): `Promise`\<`IEntityStorageConnector`\<`U`\>\>
|
|
344
|
+
|
|
345
|
+
Create the target connector for performing the migration using a temporary collection name.
|
|
346
|
+
|
|
347
|
+
#### Type Parameters
|
|
348
|
+
|
|
349
|
+
##### U
|
|
350
|
+
|
|
351
|
+
`U`
|
|
352
|
+
|
|
353
|
+
#### Parameters
|
|
354
|
+
|
|
355
|
+
##### newEntitySchema
|
|
356
|
+
|
|
357
|
+
`string`
|
|
358
|
+
|
|
359
|
+
The name of the new entity schema to create the connector for.
|
|
360
|
+
|
|
361
|
+
#### Returns
|
|
362
|
+
|
|
363
|
+
`Promise`\<`IEntityStorageConnector`\<`U`\>\>
|
|
364
|
+
|
|
365
|
+
Connector for performing the migration.
|
|
366
|
+
|
|
367
|
+
#### Implementation of
|
|
368
|
+
|
|
369
|
+
`IEntityStorageMigrationConnector.createTargetConnector`
|
|
370
|
+
|
|
371
|
+
***
|
|
372
|
+
|
|
373
|
+
### finalizeMigration() {#finalizemigration}
|
|
374
|
+
|
|
375
|
+
> **finalizeMigration**\<`U`\>(`targetConnector`, `options?`, `loggingComponentType?`): `Promise`\<`FirestoreEntityStorageConnector`\<`U`\>\>
|
|
376
|
+
|
|
377
|
+
Finalize the migration by tearing down the old collections and replacing them with the target collections.
|
|
378
|
+
|
|
379
|
+
#### Type Parameters
|
|
380
|
+
|
|
381
|
+
##### U
|
|
382
|
+
|
|
383
|
+
`U`
|
|
384
|
+
|
|
385
|
+
#### Parameters
|
|
386
|
+
|
|
387
|
+
##### targetConnector
|
|
388
|
+
|
|
389
|
+
`FirestoreEntityStorageConnector`\<`U`\>
|
|
390
|
+
|
|
391
|
+
The target connector to finalize the migration with.
|
|
392
|
+
|
|
393
|
+
##### options?
|
|
394
|
+
|
|
395
|
+
`IMigrationOptions`\<`T`, `U`\>
|
|
396
|
+
|
|
397
|
+
The options to control how the migration is finalized.
|
|
398
|
+
|
|
399
|
+
##### loggingComponentType?
|
|
400
|
+
|
|
401
|
+
`string`
|
|
402
|
+
|
|
403
|
+
The optional component type to use for logging.
|
|
404
|
+
|
|
405
|
+
#### Returns
|
|
406
|
+
|
|
407
|
+
`Promise`\<`FirestoreEntityStorageConnector`\<`U`\>\>
|
|
408
|
+
|
|
409
|
+
The final connector pointing at the original collection name.
|
|
410
|
+
|
|
411
|
+
#### Implementation of
|
|
412
|
+
|
|
413
|
+
`IEntityStorageMigrationConnector.finalizeMigration`
|
|
414
|
+
|
|
415
|
+
***
|
|
416
|
+
|
|
417
|
+
### cleanupMigration() {#cleanupmigration}
|
|
418
|
+
|
|
419
|
+
> **cleanupMigration**\<`U`\>(`targetConnector`, `options?`, `loggingComponentType?`): `Promise`\<`void`\>
|
|
420
|
+
|
|
421
|
+
Cleanup the migration if a migration fails or needs to be aborted.
|
|
422
|
+
|
|
423
|
+
#### Type Parameters
|
|
424
|
+
|
|
425
|
+
##### U
|
|
426
|
+
|
|
427
|
+
`U`
|
|
428
|
+
|
|
429
|
+
#### Parameters
|
|
430
|
+
|
|
431
|
+
##### targetConnector
|
|
432
|
+
|
|
433
|
+
`IEntityStorageConnector`\<`U`\> \| `undefined`
|
|
434
|
+
|
|
435
|
+
The target connector to cleanup.
|
|
436
|
+
|
|
437
|
+
##### options?
|
|
438
|
+
|
|
439
|
+
`IMigrationOptions`\<`T`, `U`\>
|
|
440
|
+
|
|
441
|
+
The options to control how the migration is cleaned up.
|
|
442
|
+
|
|
443
|
+
##### loggingComponentType?
|
|
444
|
+
|
|
445
|
+
`string`
|
|
446
|
+
|
|
447
|
+
The optional component type to use for logging.
|
|
448
|
+
|
|
449
|
+
#### Returns
|
|
450
|
+
|
|
451
|
+
`Promise`\<`void`\>
|
|
452
|
+
|
|
453
|
+
#### Implementation of
|
|
454
|
+
|
|
455
|
+
`IEntityStorageMigrationConnector.cleanupMigration`
|
|
320
456
|
|
|
321
457
|
***
|
|
322
458
|
|
|
@@ -366,16 +502,24 @@ The matching entities and a cursor for the next page.
|
|
|
366
502
|
|
|
367
503
|
#### Implementation of
|
|
368
504
|
|
|
369
|
-
`
|
|
505
|
+
`IEntityStorageMigrationConnector.query`
|
|
370
506
|
|
|
371
507
|
***
|
|
372
508
|
|
|
373
509
|
### count() {#count}
|
|
374
510
|
|
|
375
|
-
> **count**(): `Promise`\<`number`\>
|
|
511
|
+
> **count**(`conditions?`): `Promise`\<`number`\>
|
|
376
512
|
|
|
377
513
|
Count all the entities which match the conditions.
|
|
378
514
|
|
|
515
|
+
#### Parameters
|
|
516
|
+
|
|
517
|
+
##### conditions?
|
|
518
|
+
|
|
519
|
+
`EntityCondition`\<`T`\>
|
|
520
|
+
|
|
521
|
+
The optional conditions to match for the entities.
|
|
522
|
+
|
|
379
523
|
#### Returns
|
|
380
524
|
|
|
381
525
|
`Promise`\<`number`\>
|
|
@@ -384,4 +528,4 @@ The total count of entities in the storage.
|
|
|
384
528
|
|
|
385
529
|
#### Implementation of
|
|
386
530
|
|
|
387
|
-
`
|
|
531
|
+
`IEntityStorageMigrationConnector.count`
|
package/locales/en.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"error": {
|
|
11
11
|
"firestoreEntityStorageConnector": {
|
|
12
12
|
"firestoreCreationFailed": "Failed to connect to Firestore",
|
|
13
|
+
"getPartitionContextIdsFailed": "Unable to get partition context ids",
|
|
13
14
|
"setEntityFailed": "Failed to set entity \"{id}\"",
|
|
14
15
|
"getEntityFailed": "Failed to get entity \"{id}\"",
|
|
15
16
|
"removeEntityFailed": "Failed to remove entity \"{id}\"",
|
|
@@ -19,7 +20,8 @@
|
|
|
19
20
|
"countFailed": "Unable to count entities",
|
|
20
21
|
"emptyFailed": "Failed to empty entity storage",
|
|
21
22
|
"removeBatchFailed": "Unable to remove batch of entities",
|
|
22
|
-
"teardownFailed": "Unable to teardown entity storage"
|
|
23
|
+
"teardownFailed": "Unable to teardown entity storage",
|
|
24
|
+
"finalizeMigrationFailedBootstrap": "Finalizing migration failed during bootstrap phase"
|
|
23
25
|
}
|
|
24
26
|
},
|
|
25
27
|
"health": {
|
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-next.
|
|
3
|
+
"version": "0.0.3-next.14",
|
|
4
4
|
"description": "Google Cloud Firestore connector for document-based persistence.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
"node": ">=20.0.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@google-cloud/firestore": "8.
|
|
17
|
+
"@google-cloud/firestore": "8.6.0",
|
|
18
18
|
"@twin.org/context": "next",
|
|
19
19
|
"@twin.org/core": "next",
|
|
20
20
|
"@twin.org/entity": "next",
|
|
21
|
-
"@twin.org/entity-storage-models": "0.0.3-next.
|
|
21
|
+
"@twin.org/entity-storage-models": "0.0.3-next.14",
|
|
22
22
|
"@twin.org/logging-models": "next",
|
|
23
23
|
"@twin.org/nameof": "next"
|
|
24
24
|
},
|