@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.
@@ -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
- const entity = querySnapshot.docs[0].data();
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
- EntitySchemaHelper.validateEntity(entity, this.getSchema());
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 = entity[this._primaryKey.property];
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(entity);
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, entity);
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, entity);
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
- for (const entity of entities) {
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 = 500;
272
- for (let i = 0; i < entities.length; i += chunkSize) {
273
- const chunk = entities.slice(i, i + chunkSize);
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 in the partition collection.
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
- const collection = this._firestoreClient.collection(this.collectionName(partitionKey));
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
- const finalLimit = limit ?? FirestoreEntityStorageConnector._DEFAULT_LIMIT;
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 entities = querySnapshot.docs.map((doc) => doc.data());
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 (entities.length === finalLimit) {
465
- nextCursor = querySnapshot.docs[querySnapshot.docs.length - 1].ref.path;
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
- const snapshot = await collection.count().get();
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
- * Apply conditions to a Firestore query.
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
- // It's a group of conditions
502
- for (const c of condition.conditions) {
503
- query = this.applyConditions(query, c);
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 — the SDK throws on undefined values.
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 query.where(property, "==", value);
742
+ return Filter.where(property, "==", value);
517
743
  case ComparisonOperator.NotEquals:
518
- return query.where(property, "!=", value);
744
+ return Filter.where(property, "!=", value);
519
745
  case ComparisonOperator.GreaterThan:
520
- return query.where(property, ">", value);
746
+ return Filter.where(property, ">", value);
521
747
  case ComparisonOperator.LessThan:
522
- return query.where(property, "<", value);
748
+ return Filter.where(property, "<", value);
523
749
  case ComparisonOperator.GreaterThanOrEqual:
524
- return query.where(property, ">=", value);
750
+ return Filter.where(property, ">=", value);
525
751
  case ComparisonOperator.LessThanOrEqual:
526
- return query.where(property, "<=", value);
752
+ return Filter.where(property, "<=", value);
527
753
  case ComparisonOperator.In:
528
- return query.where(property, "in", value);
754
+ return Filter.where(property, "in", value);
529
755
  case ComparisonOperator.Includes:
530
- return query.where(property, "array-contains", value);
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":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAEN,SAAS,EAGT,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EACN,SAAS,EACT,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,MAAM,EACN,YAAY,EAEZ,EAAE,EACF,YAAY,EACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,kBAAkB,EAElB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAGlB,aAAa,EACb,MAAM,kBAAkB,CAAC;AAQ1B;;GAEG;AACH,MAAM,OAAO,+BAA+B;IAC3C;;OAEG;IACI,MAAM,CAAU,UAAU,qCAAqD;IAEtF;;;OAGG;IACK,MAAM,CAAU,cAAc,GAAW,EAAE,CAAC;IAEpD;;;OAGG;IACc,aAAa,CAAmB;IAEjD;;;OAGG;IACc,oBAAoB,CAAY;IAEjD;;;OAGG;IACc,WAAW,CAA2B;IAEvD;;;OAGG;IACc,OAAO,CAAyC;IAEjE;;;OAGG;IACc,gBAAgB,CAAY;IAE7C;;;OAGG;IACH,YAAY,OAA2D;QACtE,MAAM,CAAC,MAAM,CAAC,+BAA+B,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QACpF,MAAM,CAAC,WAAW,CACjB,+BAA+B,CAAC,UAAU,0BAE1C,OAAO,CAAC,YAAY,CACpB,CAAC;QACF,MAAM,CAAC,MAAM,CACZ,+BAA+B,CAAC,UAAU,oBAE1C,OAAO,CAAC,MAAM,CACd,CAAC;QACF,MAAM,CAAC,WAAW,CACjB,+BAA+B,CAAC,UAAU,8BAE1C,OAAO,CAAC,MAAM,CAAC,SAAS,CACxB,CAAC;QACF,MAAM,CAAC,WAAW,CACjB,+BAA+B,CAAC,UAAU,mCAE1C,OAAO,CAAC,MAAM,CAAC,cAAc,CAC7B,CAAC;QAEF,IAAI,WAAiC,CAAC;QACtC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3C,MAAM,CAAC,YAAY,CAClB,+BAA+B,CAAC,UAAU,gCAE1C,OAAO,CAAC,MAAM,CAAC,WAAW,CAC1B,CAAC;YACF,WAAW,GAAG,YAAY,CAAC,SAAS,CACnC,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CACnD,CAAC;QACH,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;QAC9B,IAAI,CAAC,aAAa,GAAG,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACnE,IAAI,CAAC,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC;QACxD,IAAI,CAAC,WAAW,GAAG,kBAAkB,CAAC,aAAa,CAAI,IAAI,CAAC,aAAa,CAAC,CAAC;QAE3E,MAAM,gBAAgB,GAAa;YAClC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;YACjC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;YACnC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;YAC3C,eAAe,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,eAAe;YACvD,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO;YACvC,WAAW;SACX,CAAC;QAEF,IAAI,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3C,gBAAgB,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;YAC9C,gBAAgB,CAAC,GAAG,GAAG,KAAK,CAAC;QAC9B,CAAC;QAED,IAAI,CAAC,gBAAgB,GAAG,IAAI,SAAS,CAAC,gBAAgB,CAAC,CAAC;IACzD,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,+BAA+B,CAAC,UAAU,CAAC;IACnD,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM;QAClB,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,gBAAgB,CAAC,eAAe,EAAE,CAAC;YAC9C,OAAO;gBACN;oBACC,MAAM,EAAE,+BAA+B,CAAC,UAAU;oBAClD,MAAM,EAAE,YAAY,CAAC,EAAE;oBACvB,WAAW,EAAE,mBAAmB;oBAChC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE;iBACxF;aACD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACR,OAAO;gBACN;oBACC,MAAM,EAAE,+BAA+B,CAAC,UAAU;oBAClD,MAAM,EAAE,YAAY,CAAC,KAAK;oBAC1B,WAAW,EAAE,mBAAmB;oBAChC,OAAO,EAAE,kBAAkB;oBAC3B,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE;iBACxF;aACD,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,IAAI,CAAC,aAA8B,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,SAAS,CAAC,wBAAiC;QACvD,MAAM,WAAW,GAAG,gBAAgB,CAAC,WAAW,CAAoB,wBAAwB,CAAC,CAAC;QAE9F,IAAI,CAAC;YACJ,MAAM,WAAW,EAAE,GAAG,CAAC;gBACtB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,+BAA+B,CAAC,UAAU;gBAClD,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,OAAO,EAAE,mBAAmB;gBAC5B,IAAI,EAAE;oBACL,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;oBACjC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;iBAC3C;aACD,CAAC,CAAC;YAEH,yDAAyD;YACzD,yDAAyD;YACzD,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC1F,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAClC,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;YAEvB,MAAM,WAAW,EAAE,GAAG,CAAC;gBACtB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,+BAA+B,CAAC,UAAU;gBAClD,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,OAAO,EAAE,kBAAkB;gBAC3B,IAAI,EAAE;oBACL,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;oBACjC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;iBAC3C;aACD,CAAC,CAAC;YAEH,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,WAAW,EAAE,GAAG,CAAC;gBACtB,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,+BAA+B,CAAC,UAAU;gBAClD,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,OAAO,EAAE,yBAAyB;gBAClC,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC;gBAC/B,IAAI,EAAE;oBACL,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;oBACjC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;iBAC3C;aACD,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,GAAG,CACf,EAAU,EACV,cAAwB,EACxB,UAAoD;QAEpD,MAAM,CAAC,WAAW,CAAC,+BAA+B,CAAC,UAAU,QAAc,EAAE,CAAC,CAAC;QAE/E,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;YAEvF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAChC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAClC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;gBAE/B,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;oBAChB,OAAO,GAAG,CAAC,IAAI,EAAO,CAAC;gBACxB,CAAC;YACF,CAAC;YAED,sCAAsC;YACtC,IAAI,KAAK,GAAU,UAAU,CAAC;YAE9B,IAAI,cAAc,EAAE,CAAC;gBACpB,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,cAAwB,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACP,0DAA0D;gBAC1D,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,QAAkB,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YACpE,CAAC;YAED,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC/B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;oBACpC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,QAAkB,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;gBAC1E,CAAC;YACF,CAAC;YAED,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACjD,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;gBAC1B,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAO,CAAC;gBACjD,OAAO,MAAM,CAAC;YACf,CAAC;QACF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,+BAA+B,CAAC,UAAU,EAC1C,iBAAiB,EACjB,EAAE,EAAE,EAAE,EACN,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,GAAG,CAAC,MAAS,EAAE,UAAoD;QAC/E,MAAM,CAAC,MAAM,CAAC,+BAA+B,CAAC,UAAU,YAAkB,MAAM,CAAC,CAAC;QAElF,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,kBAAkB,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAE5D,IAAI,CAAC;YACJ,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAW,CAAC;YAEvD,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;YAEvF,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAElC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAChC,MAAM,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACP,MAAM,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,KAAK,EAAC,WAAW,EAAC,EAAE;oBAC9D,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;oBAElD,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;wBACzB,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;oBACjC,CAAC;yBAAM,CAAC;wBACP,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAO,CAAC;wBAErC,IACC,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE;4BAC5B,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gCAChC,QAAQ,EAAE,CAAC,CAAC,QAAkB;gCAC9B,UAAU,EAAE,kBAAkB,CAAC,MAAM;gCACrC,KAAK,EAAE,CAAC,CAAC,KAAK;6BACd,CAAC,CAAC;yBACH,CAAC,EACD,CAAC;4BACF,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;wBACjC,CAAC;oBACF,CAAC;gBACF,CAAC,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,+BAA+B,CAAC,UAAU,EAC1C,iBAAiB,EACjB,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EACjB,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,QAAQ,CAAC,QAAa;QAClC,MAAM,CAAC,UAAU,CAAC,+BAA+B,CAAC,UAAU,cAAoB,QAAQ,CAAC,CAAC;QAE1F,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC/B,kBAAkB,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;YACvF,MAAM,SAAS,GAAG,GAAG,CAAC;YACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;gBACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;gBAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;gBAC5C,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;oBAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAW,CAAC;oBACvD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBAClC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAoC,CAAC,CAAC;gBACzD,CAAC;gBACD,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;YACtB,CAAC;QACF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,+BAA+B,CAAC,UAAU,EAC1C,gBAAgB,EAChB,SAAS,EACT,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,KAAK;QACjB,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;YACvF,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC;YACxC,MAAM,SAAS,GAAG,GAAG,CAAC;YACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;gBAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;gBACpD,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;gBAC5C,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;oBACzB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACvB,CAAC;gBACD,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;YACtB,CAAC;QACF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,+BAA+B,CAAC,UAAU,EAC1C,aAAa,EACb,SAAS,EACT,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,MAAM,CAClB,EAAU,EACV,UAAoD;QAEpD,MAAM,CAAC,WAAW,CAAC,+BAA+B,CAAC,UAAU,QAAc,EAAE,CAAC,CAAC;QAE/E,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;YACvF,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAElC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAChC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACP,MAAM,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,KAAK,EAAC,WAAW,EAAC,EAAE;oBAC9D,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;oBAElD,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;wBACxB,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAO,CAAC;wBACrC,IACC,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE;4BAC5B,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gCAChC,QAAQ,EAAE,CAAC,CAAC,QAAkB;gCAC9B,UAAU,EAAE,kBAAkB,CAAC,MAAM;gCACrC,KAAK,EAAE,CAAC,CAAC,KAAK;6BACd,CAAC,CAAC;yBACH,CAAC,EACD,CAAC;4BACF,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;wBAC5B,CAAC;oBACF,CAAC;gBACF,CAAC,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,+BAA+B,CAAC,UAAU,EAC1C,oBAAoB,EACpB,EAAE,EAAE,EAAE,EACN,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,WAAW,CAAC,GAAa;QACrC,MAAM,CAAC,UAAU,CAAC,+BAA+B,CAAC,UAAU,SAAe,GAAG,CAAC,CAAC;QAEhF,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;YACvF,MAAM,SAAS,GAAG,GAAG,CAAC;YACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;gBAChD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;gBAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;gBAC5C,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;oBACxB,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBAClC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACtB,CAAC;gBACD,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;YACtB,CAAC;QACF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,+BAA+B,CAAC,UAAU,EAC1C,mBAAmB,EACnB,SAAS,EACT,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,QAAQ,CAAC,wBAAiC;QACtD,MAAM,WAAW,GAAG,gBAAgB,CAAC,WAAW,CAAoB,wBAAwB,CAAC,CAAC;QAC9F,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,MAAM,WAAW,EAAE,GAAG,CAAC;YACtB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,+BAA+B,CAAC,UAAU;YAClD,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,OAAO,EAAE,kBAAkB;SAC3B,CAAC,CAAC;QAEH,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;YACvF,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC;YACxC,MAAM,SAAS,GAAG,GAAG,CAAC;YACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;gBAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;gBACpD,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;gBAC5C,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;oBACzB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACvB,CAAC;gBACD,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;YACtB,CAAC;YAED,MAAM,WAAW,EAAE,GAAG,CAAC;gBACtB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,+BAA+B,CAAC,UAAU;gBAClD,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,OAAO,EAAE,eAAe;aACxB,CAAC,CAAC;YAEH,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,WAAW,EAAE,GAAG,CAAC;gBACtB,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,+BAA+B,CAAC,UAAU;gBAClD,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,OAAO,EAAE,gBAAgB;gBACzB,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC;aAC/B,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAED;;;;;;;;OAQG;IACI,KAAK,CAAC,KAAK,CACjB,UAA+B,EAC/B,cAAsE,EACtE,UAAwB,EACxB,MAAe,EACf,KAAc;QAWd,MAAM,gBAAgB,GAAa,EAAE,CAAC;QAEtC,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;YACvF,IAAI,KAAK,GAAG,UAAmB,CAAC;YAEhC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3B,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;gBAChD,gBAAgB,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YACpE,CAAC;YAED,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;gBACnC,KAAK,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,cAAc,EAAE,CAAC;oBAC1D,KAAK,GAAG,KAAK,CAAC,OAAO,CACpB,QAAkB,EAClB,aAAa,KAAK,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAC1D,CAAC;gBACH,CAAC;gBACD,gBAAgB,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;YAClE,CAAC;YAED,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC;gBAChE,IAAI,SAAS,EAAE,MAAM,EAAE,CAAC;oBACvB,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;gBACrC,CAAC;gBACD,gBAAgB,CAAC,IAAI,CAAC,WAAW,MAAM,EAAE,CAAC,CAAC;YAC5C,CAAC;YAED,MAAM,UAAU,GAAG,KAAK,IAAI,+BAA+B,CAAC,cAAc,CAAC;YAC3E,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAChC,gBAAgB,CAAC,IAAI,CAAC,UAAU,UAAU,EAAE,CAAC,CAAC;YAE9C,IAAI,UAAU,EAAE,CAAC;gBAChB,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,GAAI,UAAuB,CAAC,CAAC;gBAClD,gBAAgB,CAAC,IAAI,CAAC,eAAe,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/D,CAAC;YAED,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;YACxC,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAqB,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAO,CAAC,CAAC;YAEpF,IAAI,UAA8B,CAAC;YACnC,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACpC,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;YACzE,CAAC;YAED,OAAO;gBACN,QAAQ;gBACR,MAAM,EAAE,UAAU;aAClB,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,+BAA+B,CAAC,UAAU,EAC1C,aAAa,EACb,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EACjD,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,KAAK;QACjB,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;YACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CACtD,UAAU,EACV,IAAI,CAAC,oBAAoB,CACzB,CAAC;YAEF,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;YACvF,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC;YAChD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,+BAA+B,CAAC,UAAU,EAC1C,aAAa,EACb,SAAS,EACT,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;;;OAMG;IACK,eAAe,CAAC,KAAY,EAAE,SAA6B;QAClE,IAAI,YAAY,IAAI,SAAS,EAAE,CAAC;YAC/B,6BAA6B;YAC7B,KAAK,MAAM,CAAC,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;gBACtC,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACxC,CAAC;YACD,OAAO,KAAK,CAAC;QACd,CAAC;QACD,0BAA0B;QAC1B,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,SAAS,CAAC;QAC3C,wEAAwE;QACxE,gEAAgE;QAChE,kEAAkE;QAClE,sEAAsE;QACtE,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC;QACrE,QAAQ,UAAU,EAAE,CAAC;YACpB,KAAK,kBAAkB,CAAC,MAAM;gBAC7B,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3C,KAAK,kBAAkB,CAAC,SAAS;gBAChC,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3C,KAAK,kBAAkB,CAAC,WAAW;gBAClC,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC1C,KAAK,kBAAkB,CAAC,QAAQ;gBAC/B,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC1C,KAAK,kBAAkB,CAAC,kBAAkB;gBACzC,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3C,KAAK,kBAAkB,CAAC,eAAe;gBACtC,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3C,KAAK,kBAAkB,CAAC,EAAE;gBACzB,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAkB,CAAC,CAAC;YACxD,KAAK,kBAAkB,CAAC,QAAQ;gBAC/B,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;YACvD,KAAK,kBAAkB,CAAC,WAAW,CAAC;YACpC;gBACC,MAAM,IAAI,YAAY,CACrB,+BAA+B,CAAC,UAAU,EAC1C,+BAA+B,EAC/B,EAAE,UAAU,EAAE,CACd,CAAC;QACJ,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,cAAc,CAAC,YAAqB;QAC3C,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,YAAY,IAAI,SAAS,EAAE,CAAC;IACtE,CAAC","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 { IEntityStorageConnector } from "@twin.org/entity-storage-models";
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 IEntityStorageConnector<T> {
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 in the partition collection.
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
- - `IEntityStorageConnector`\<`T`\>
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
- `IEntityStorageConnector.className`
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
- `IEntityStorageConnector.health`
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
- `IEntityStorageConnector.getSchema`
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
- `IEntityStorageConnector.bootstrap`
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
- `IEntityStorageConnector.get`
159
+ `IEntityStorageMigrationConnector.get`
160
160
 
161
161
  ***
162
162
 
@@ -188,7 +188,7 @@ Nothing.
188
188
 
189
189
  #### Implementation of
190
190
 
191
- `IEntityStorageConnector.set`
191
+ `IEntityStorageMigrationConnector.set`
192
192
 
193
193
  ***
194
194
 
@@ -214,7 +214,7 @@ Nothing.
214
214
 
215
215
  #### Implementation of
216
216
 
217
- `IEntityStorageConnector.setBatch`
217
+ `IEntityStorageMigrationConnector.setBatch`
218
218
 
219
219
  ***
220
220
 
@@ -232,7 +232,7 @@ Nothing.
232
232
 
233
233
  #### Implementation of
234
234
 
235
- `IEntityStorageConnector.empty`
235
+ `IEntityStorageMigrationConnector.empty`
236
236
 
237
237
  ***
238
238
 
@@ -264,7 +264,7 @@ Nothing.
264
264
 
265
265
  #### Implementation of
266
266
 
267
- `IEntityStorageConnector.remove`
267
+ `IEntityStorageMigrationConnector.remove`
268
268
 
269
269
  ***
270
270
 
@@ -290,7 +290,7 @@ Nothing.
290
290
 
291
291
  #### Implementation of
292
292
 
293
- `IEntityStorageConnector.removeBatch`
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 in the partition collection.
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
- `IEntityStorageConnector.teardown`
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
- `IEntityStorageConnector.query`
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
- `IEntityStorageConnector.count`
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.13",
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.5.0",
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.13",
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
  },