@twin.org/entity-storage-connector-gcp-firestore 0.0.2-next.9 → 0.0.3-next.2

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