@webda/gcp 4.0.0-beta.2 → 4.0.0-beta.4

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.
Files changed (68) hide show
  1. package/README.md +54 -4
  2. package/lib/index.d.ts +4 -4
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/index.js +4 -4
  5. package/lib/index.js.map +1 -1
  6. package/lib/services/firestore.service.d.ts +237 -0
  7. package/lib/services/firestore.service.d.ts.map +1 -0
  8. package/lib/services/firestore.service.js +693 -0
  9. package/lib/services/firestore.service.js.map +1 -0
  10. package/lib/services/index.d.ts +1 -1
  11. package/lib/services/index.d.ts.map +1 -1
  12. package/lib/services/index.js +1 -1
  13. package/lib/services/index.js.map +1 -1
  14. package/lib/services/{kms.d.ts → kms.service.d.ts} +14 -15
  15. package/lib/services/kms.service.d.ts.map +1 -0
  16. package/lib/services/{kms.js → kms.service.js} +18 -16
  17. package/lib/services/kms.service.js.map +1 -0
  18. package/lib/services/{pubsub.d.ts → pubsub.service.d.ts} +23 -4
  19. package/lib/services/pubsub.service.d.ts.map +1 -0
  20. package/lib/services/{pubsub.js → pubsub.service.js} +36 -10
  21. package/lib/services/pubsub.service.js.map +1 -0
  22. package/lib/services/{queue.d.ts → queue.service.d.ts} +32 -16
  23. package/lib/services/queue.service.d.ts.map +1 -0
  24. package/lib/services/{queue.js → queue.service.js} +63 -32
  25. package/lib/services/queue.service.js.map +1 -0
  26. package/lib/services/storage.service.d.ts +268 -0
  27. package/lib/services/storage.service.d.ts.map +1 -0
  28. package/lib/services/{storage.js → storage.service.js} +116 -69
  29. package/lib/services/storage.service.js.map +1 -0
  30. package/package.json +33 -43
  31. package/webda.module.json +1122 -1440
  32. package/lib/index.spec.d.ts +0 -5
  33. package/lib/index.spec.d.ts.map +0 -1
  34. package/lib/index.spec.js +0 -7
  35. package/lib/index.spec.js.map +0 -1
  36. package/lib/services/firestore.d.ts +0 -135
  37. package/lib/services/firestore.d.ts.map +0 -1
  38. package/lib/services/firestore.js +0 -423
  39. package/lib/services/firestore.js.map +0 -1
  40. package/lib/services/firestore.spec.d.ts +0 -2
  41. package/lib/services/firestore.spec.d.ts.map +0 -1
  42. package/lib/services/firestore.spec.js +0 -187
  43. package/lib/services/firestore.spec.js.map +0 -1
  44. package/lib/services/kms.d.ts.map +0 -1
  45. package/lib/services/kms.js.map +0 -1
  46. package/lib/services/kms.spec.d.ts +0 -2
  47. package/lib/services/kms.spec.d.ts.map +0 -1
  48. package/lib/services/kms.spec.js +0 -53
  49. package/lib/services/kms.spec.js.map +0 -1
  50. package/lib/services/pubsub.d.ts.map +0 -1
  51. package/lib/services/pubsub.js.map +0 -1
  52. package/lib/services/pubsub.spec.d.ts +0 -2
  53. package/lib/services/pubsub.spec.d.ts.map +0 -1
  54. package/lib/services/pubsub.spec.js +0 -95
  55. package/lib/services/pubsub.spec.js.map +0 -1
  56. package/lib/services/queue.d.ts.map +0 -1
  57. package/lib/services/queue.js.map +0 -1
  58. package/lib/services/queue.spec.d.ts +0 -2
  59. package/lib/services/queue.spec.d.ts.map +0 -1
  60. package/lib/services/queue.spec.js +0 -117
  61. package/lib/services/queue.spec.js.map +0 -1
  62. package/lib/services/storage.d.ts +0 -215
  63. package/lib/services/storage.d.ts.map +0 -1
  64. package/lib/services/storage.js.map +0 -1
  65. package/lib/services/storage.spec.d.ts +0 -2
  66. package/lib/services/storage.spec.d.ts.map +0 -1
  67. package/lib/services/storage.spec.js +0 -224
  68. package/lib/services/storage.spec.js.map +0 -1
@@ -0,0 +1,693 @@
1
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
2
+ var useValue = arguments.length > 2;
3
+ for (var i = 0; i < initializers.length; i++) {
4
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
5
+ }
6
+ return useValue ? value : void 0;
7
+ };
8
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
9
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
10
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
11
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
12
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
13
+ var _, done = false;
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ var context = {};
16
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
17
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
18
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
19
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
20
+ if (kind === "accessor") {
21
+ if (result === void 0) continue;
22
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
23
+ if (_ = accept(result.get)) descriptor.get = _;
24
+ if (_ = accept(result.set)) descriptor.set = _;
25
+ if (_ = accept(result.init)) initializers.unshift(_);
26
+ }
27
+ else if (_ = accept(result)) {
28
+ if (kind === "field") initializers.unshift(_);
29
+ else descriptor[key] = _;
30
+ }
31
+ }
32
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
+ done = true;
34
+ };
35
+ import { FieldValue, Firestore, Timestamp } from "@google-cloud/firestore";
36
+ import { EventRepository, InstanceCache, MemoryRepository, Store, StoreNotFoundError, StoreParameters, UpdateConditionFailError, useModel, useModelMetadata } from "@webda/core";
37
+ import * as WebdaQL from "@webda/ql";
38
+ /**
39
+ * Firestore error code for a missing document
40
+ */
41
+ const NOT_FOUND = 5;
42
+ /**
43
+ * Firestore error code for an already existing document
44
+ */
45
+ const ALREADY_EXISTS = 6;
46
+ /**
47
+ * Firebase parameters
48
+ */
49
+ export class FireStoreParameters extends StoreParameters {
50
+ /**
51
+ * @override
52
+ * @param params - raw parameters
53
+ * @returns this
54
+ */
55
+ load(params = {}) {
56
+ super.load(params);
57
+ this.compoundIndexes ?? (this.compoundIndexes = []);
58
+ this.collections ?? (this.collections = {});
59
+ return this;
60
+ }
61
+ }
62
+ /**
63
+ * Recursively convert a value to what Firestore can store
64
+ *
65
+ * Dates are kept as is (Firestore stores them as Timestamp), objects exposing a
66
+ * `toJSON` (models, binaries) are serialized first, functions and undefined are dropped
67
+ *
68
+ * @param value - the value to convert
69
+ * @returns the Firestore compatible value
70
+ */
71
+ export function toFirestore(value) {
72
+ if (value === undefined || value === null || value instanceof Date) {
73
+ return value;
74
+ }
75
+ if (Array.isArray(value)) {
76
+ return value.map(v => toFirestore(v));
77
+ }
78
+ if (typeof value === "object") {
79
+ if (typeof value.toJSON === "function") {
80
+ const json = value.toJSON();
81
+ if (json !== value) {
82
+ return toFirestore(json);
83
+ }
84
+ }
85
+ const res = {};
86
+ for (const key of Object.keys(value)) {
87
+ if (value[key] === undefined || typeof value[key] === "function") {
88
+ continue;
89
+ }
90
+ res[key] = toFirestore(value[key]);
91
+ }
92
+ return res;
93
+ }
94
+ return value;
95
+ }
96
+ /**
97
+ * Recursively replace Timestamp by Date
98
+ * @param doc - the Firestore document data
99
+ * @returns the data with Date objects
100
+ */
101
+ export function giveDatesBack(doc) {
102
+ if (doc instanceof Timestamp) {
103
+ return doc.toDate();
104
+ }
105
+ else if (Array.isArray(doc)) {
106
+ return doc.map(d => giveDatesBack(d));
107
+ }
108
+ else if (doc instanceof Object) {
109
+ const res = {};
110
+ Object.keys(doc).forEach(k => {
111
+ res[k] = giveDatesBack(doc[k]);
112
+ });
113
+ return res;
114
+ }
115
+ return doc;
116
+ }
117
+ /**
118
+ * Firestore-backed repository for a single model class
119
+ *
120
+ * Every object is stored as a document of the collection, keyed by its primary key.
121
+ * The inherited MemoryRepository is only used for its helpers, every storage
122
+ * method hits Firestore.
123
+ */
124
+ export class FireStoreRepository extends MemoryRepository {
125
+ /**
126
+ * @param model - the model class
127
+ * @param pks - primary key field names
128
+ * @param firestore - the Firestore client
129
+ * @param collection - the collection name
130
+ * @param indexes - compound indexes declared on the store
131
+ * @param log - logger of the store
132
+ * @param separator - primary key separator
133
+ */
134
+ constructor(model, pks, firestore, collection, indexes = [], log = () => { }, separator) {
135
+ // Pass an empty Map — we do NOT use in-memory storage
136
+ super(model, pks, separator, new Map());
137
+ this.firestore = firestore;
138
+ this.collection = collection;
139
+ this.log = log;
140
+ /**
141
+ * Compound indexes available, keyed by the sorted fields joined with "/"
142
+ */
143
+ this.compoundIndexes = {};
144
+ indexes.forEach(a => {
145
+ const key = Object.keys(a).sort().join("/");
146
+ // Should contain the array of accessible order
147
+ this.compoundIndexes[key] = {};
148
+ Object.keys(a).forEach(field => {
149
+ var _a;
150
+ (_a = this.compoundIndexes[key])[field] ?? (_a[field] = new Set());
151
+ this.compoundIndexes[key][field].add(a[field]);
152
+ });
153
+ });
154
+ }
155
+ /**
156
+ * The backing collection name for this repository's model
157
+ * @returns the collection name
158
+ */
159
+ getCollection() {
160
+ return this.collection;
161
+ }
162
+ /**
163
+ * Get Document Reference
164
+ * @param primaryKey - the object or primary key
165
+ * @returns the document reference
166
+ */
167
+ getDocumentRef(primaryKey) {
168
+ return this.firestore.doc(`${this.collection}/${this.getPrimaryKey(primaryKey).toString()}`);
169
+ }
170
+ /**
171
+ * Deserialize a Firestore document into a model instance
172
+ * @param data - the document data
173
+ * @returns the model instance
174
+ */
175
+ fromFirestore(data) {
176
+ data = giveDatesBack(data);
177
+ const instance = new this.model({});
178
+ if (typeof instance.load === "function") {
179
+ instance.load(data);
180
+ }
181
+ else {
182
+ Object.assign(instance, data);
183
+ }
184
+ return instance;
185
+ }
186
+ /**
187
+ * Verify a write condition
188
+ * @param uid - the object key, for the error
189
+ * @param data - the current data
190
+ * @param field - the field to check
191
+ * @param condition - the expected value
192
+ */
193
+ checkFirestoreCondition(uid, data, field, condition) {
194
+ if (!field) {
195
+ return;
196
+ }
197
+ const current = data?.[field] instanceof Timestamp ? data[field].toDate().toISOString() : data?.[field];
198
+ if (condition instanceof Date) {
199
+ condition = condition.toISOString();
200
+ }
201
+ if (current !== condition) {
202
+ throw new UpdateConditionFailError(uid, field, condition);
203
+ }
204
+ }
205
+ /**
206
+ * Translate Firestore not found error
207
+ * @param uid - the object key
208
+ * @param err - the error raised
209
+ */
210
+ translateError(uid, err) {
211
+ if (err?.code === NOT_FOUND) {
212
+ throw new StoreNotFoundError(uid, this.collection);
213
+ }
214
+ throw err;
215
+ }
216
+ /** @override */
217
+ async get(primaryKey) {
218
+ const ref = this.getDocumentRef(primaryKey);
219
+ const doc = (await ref.get()).data();
220
+ if (!doc) {
221
+ throw new StoreNotFoundError(ref.id, this.collection);
222
+ }
223
+ return this.fromFirestore(doc);
224
+ }
225
+ /** @override */
226
+ async exists(primaryKey) {
227
+ return (await this.getDocumentRef(primaryKey).get()).exists;
228
+ }
229
+ /** @override */
230
+ async create(data, save = true) {
231
+ // Build the item first: a primary key generated by the model is the document key
232
+ const item = this.buildItem(data);
233
+ if (save !== false) {
234
+ try {
235
+ await this.getDocumentRef(item).create(toFirestore(item));
236
+ }
237
+ catch (err) {
238
+ if (err?.code === ALREADY_EXISTS) {
239
+ throw new Error(`Already exists: ${this.getPrimaryKey(item).toString()}`);
240
+ }
241
+ throw err;
242
+ }
243
+ }
244
+ return item;
245
+ }
246
+ /**
247
+ * Implement both update and patch
248
+ * @param merge - merge with the existing document (patch) or replace it (update)
249
+ * @param primaryKey - the object or primary key
250
+ * @param data - the data to write
251
+ * @param conditionField - the field to check
252
+ * @param condition - the expected value
253
+ */
254
+ async setDocument(merge, primaryKey, data, conditionField, condition) {
255
+ const update = toFirestore(data);
256
+ const docRef = this.getDocumentRef(primaryKey);
257
+ await this.firestore.runTransaction(async (t) => {
258
+ const doc = await t.get(docRef);
259
+ if (!doc.exists) {
260
+ throw new StoreNotFoundError(docRef.id, this.collection);
261
+ }
262
+ this.checkFirestoreCondition(docRef.id, doc.data(), conditionField, condition);
263
+ t.set(docRef, update, { merge });
264
+ });
265
+ }
266
+ /** @override */
267
+ async update(data, conditionField, condition) {
268
+ await this.setDocument(false, data, data, conditionField, condition);
269
+ }
270
+ /** @override */
271
+ async patch(primaryKey, data, conditionField, condition) {
272
+ await this.setDocument(true, primaryKey, data, conditionField, condition);
273
+ }
274
+ /** @override */
275
+ async delete(primaryKey, conditionField, condition) {
276
+ const docRef = this.getDocumentRef(primaryKey);
277
+ await this.firestore.runTransaction(async (t) => {
278
+ const doc = await t.get(docRef);
279
+ if (conditionField) {
280
+ this.checkFirestoreCondition(docRef.id, doc.data(), conditionField, condition);
281
+ }
282
+ t.delete(docRef);
283
+ });
284
+ }
285
+ /** @override */
286
+ async removeAttribute(primaryKey, attribute, conditionField, condition) {
287
+ const docRef = this.getDocumentRef(primaryKey);
288
+ await this.firestore.runTransaction(async (t) => {
289
+ const doc = await t.get(docRef);
290
+ if (!doc.exists) {
291
+ throw new StoreNotFoundError(docRef.id, this.collection);
292
+ }
293
+ this.checkFirestoreCondition(docRef.id, doc.data(), conditionField, condition);
294
+ t.update(docRef, {
295
+ [attribute]: FieldValue.delete()
296
+ });
297
+ });
298
+ }
299
+ /** @override */
300
+ async incrementAttributes(primaryKey, info, _conditionField, _condition) {
301
+ const docRef = this.getDocumentRef(primaryKey);
302
+ const entries = Array.isArray(info)
303
+ ? info.map((e) => typeof e === "string" ? { property: e, value: 1 } : { property: e.property, value: e.value ?? 1 })
304
+ : Object.entries(info).map(([property, value]) => ({ property, value: value }));
305
+ const args = {
306
+ _lastUpdate: new Date()
307
+ };
308
+ entries.forEach(p => {
309
+ args[p.property] = FieldValue.increment(p.value);
310
+ });
311
+ try {
312
+ await docRef.update(args);
313
+ }
314
+ catch (err) {
315
+ this.translateError(docRef.id, err);
316
+ }
317
+ }
318
+ /** @override */
319
+ async upsertItemToCollection(primaryKey, collection, item, index, itemWriteConditionField, itemWriteCondition) {
320
+ const docRef = this.getDocumentRef(primaryKey);
321
+ const prop = String(collection);
322
+ if (index !== undefined) {
323
+ await this.firestore.runTransaction(async (t) => {
324
+ const doc = await t.get(docRef);
325
+ if (!doc.exists) {
326
+ throw new StoreNotFoundError(docRef.id, this.collection);
327
+ }
328
+ const data = doc.data();
329
+ data[prop] ?? (data[prop] = []);
330
+ if (itemWriteCondition !== undefined) {
331
+ this.checkFirestoreCondition(docRef.id, data[prop][index], itemWriteConditionField, itemWriteCondition);
332
+ }
333
+ data[prop][index] = toFirestore(item);
334
+ t.update(docRef, {
335
+ [prop]: data[prop],
336
+ _lastUpdate: new Date()
337
+ });
338
+ });
339
+ }
340
+ else {
341
+ try {
342
+ await docRef.update({
343
+ [prop]: FieldValue.arrayUnion(toFirestore(item)),
344
+ _lastUpdate: new Date()
345
+ });
346
+ }
347
+ catch (err) {
348
+ this.translateError(docRef.id, err);
349
+ }
350
+ }
351
+ }
352
+ /** @override */
353
+ async deleteItemFromCollection(primaryKey, collection, index, itemWriteConditionField, itemWriteCondition) {
354
+ const docRef = this.getDocumentRef(primaryKey);
355
+ const prop = String(collection);
356
+ await this.firestore.runTransaction(async (t) => {
357
+ const doc = await t.get(docRef);
358
+ if (!doc.exists) {
359
+ throw new StoreNotFoundError(docRef.id, this.collection);
360
+ }
361
+ const data = doc.data();
362
+ const items = Array.isArray(data[prop]) ? data[prop] : [];
363
+ if (index < 0 || index >= items.length) {
364
+ return;
365
+ }
366
+ if (itemWriteCondition !== undefined) {
367
+ this.checkFirestoreCondition(docRef.id, items[index], itemWriteConditionField, itemWriteCondition);
368
+ }
369
+ items.splice(index, 1);
370
+ t.update(docRef, {
371
+ [prop]: items,
372
+ _lastUpdate: new Date()
373
+ });
374
+ });
375
+ }
376
+ /**
377
+ * Run a parsed query against Firestore
378
+ *
379
+ * Firestore has strong limitations on what can be queried natively, the part of the
380
+ * query that cannot be translated is applied in memory and returned as `filter`
381
+ *
382
+ * @param parsedQuery - the parsed WebdaQL query
383
+ * @returns the results, the in-memory filter and the continuation token
384
+ */
385
+ async find(parsedQuery) {
386
+ const offset = parseInt(parsedQuery.continuationToken || "0");
387
+ const limit = parsedQuery.limit || 1000;
388
+ let query = this.firestore.collection(this.collection);
389
+ if (offset) {
390
+ query = query.offset(offset);
391
+ }
392
+ const filter = new WebdaQL.AndExpression([]);
393
+ let rangeAttribute;
394
+ let toProcess;
395
+ if (!(parsedQuery.filter instanceof WebdaQL.AndExpression)) {
396
+ toProcess = new WebdaQL.AndExpression([parsedQuery.filter]);
397
+ }
398
+ else {
399
+ toProcess = parsedQuery.filter;
400
+ }
401
+ // A FALSE conjunct matches nothing: do not hit Firestore (never fail open)
402
+ if (toProcess.children.some(c => c instanceof WebdaQL.BooleanExpression && !c.value)) {
403
+ return { results: [], filter: true, continuationToken: undefined };
404
+ }
405
+ // A TRUE conjunct is neutral
406
+ toProcess = new WebdaQL.AndExpression(toProcess.children.filter(c => !(c instanceof WebdaQL.BooleanExpression)));
407
+ const queryAttributes = new Set();
408
+ let hasIn = false;
409
+ let hasContains = false;
410
+ toProcess.children.forEach((child) => {
411
+ if (!(child instanceof WebdaQL.ComparisonExpression)) {
412
+ // Do not manage OR yet
413
+ filter.children.push(child);
414
+ return;
415
+ }
416
+ if (child.operator === "LIKE") {
417
+ this.log("WARN", "Firebase do not natively have 'LIKE'");
418
+ // LIKE is not managed by Firestore
419
+ filter.children.push(child);
420
+ return;
421
+ }
422
+ if (child.operator === "IS NULL") {
423
+ // Firestore `== null` does not match documents missing the field
424
+ filter.children.push(child);
425
+ return;
426
+ }
427
+ let operator;
428
+ const attribute = child.attribute.join(".");
429
+ queryAttributes.add(attribute);
430
+ // Translate operators
431
+ if (["=", "IN", "CONTAINS"].includes(child.operator)) {
432
+ // = is permitted on every fields
433
+ if (child.operator === "=") {
434
+ operator = "==";
435
+ }
436
+ else if (child.operator === "IN") {
437
+ operator = "in";
438
+ if (child.value.length > 10) {
439
+ this.log("WARN", "Firebase cannot have more than 10 values for 'in'");
440
+ filter.children.push(child);
441
+ return;
442
+ }
443
+ if (hasIn) {
444
+ this.log("WARN", "Firebase cannot have two 'in' (IN) clause");
445
+ filter.children.push(child);
446
+ return;
447
+ }
448
+ hasIn = true;
449
+ }
450
+ else {
451
+ operator = "array-contains";
452
+ if (hasContains) {
453
+ this.log("WARN", "Firebase cannot have two 'array-contains' (CONTAINS) clause");
454
+ filter.children.push(child);
455
+ return;
456
+ }
457
+ hasContains = true;
458
+ }
459
+ }
460
+ else {
461
+ // Requires compoundIndex
462
+ if (queryAttributes.size > 1 && !this.compoundIndexes[[...queryAttributes.values()].sort().join("/")]) {
463
+ this.log("WARN", "Compound index not defined");
464
+ filter.children.push(child);
465
+ return;
466
+ }
467
+ // Firestore `!= null` matches documents where the field exists and is not null
468
+ const isNotNull = child.operator === "IS NOT NULL";
469
+ if (child.operator !== "!=" && !isNotNull) {
470
+ rangeAttribute ?? (rangeAttribute = attribute);
471
+ // Range need to apply on only one attribute
472
+ if (rangeAttribute !== attribute) {
473
+ filter.children.push(child);
474
+ return;
475
+ }
476
+ }
477
+ operator = isNotNull ? "!=" : child.operator;
478
+ }
479
+ query = query.where(attribute, operator, child.operator === "IS NOT NULL" ? null : child.value);
480
+ });
481
+ // OrderBy have quite some complexity with FireStore
482
+ query = this.handleOrderBy(query, parsedQuery.orderBy, rangeAttribute);
483
+ const res = await query.limit(limit).get();
484
+ let results = res.docs.map(d => this.fromFirestore(d.data()));
485
+ if (filter.children.length) {
486
+ results = results.filter(r => filter.eval(r));
487
+ }
488
+ return {
489
+ results,
490
+ filter: filter.children.length ? filter : true,
491
+ continuationToken: res.docs.length >= limit ? (offset + limit).toString() : undefined
492
+ };
493
+ }
494
+ /**
495
+ * Manage OrderBy complex condition on Firebase
496
+ * @param query - the Firestore query
497
+ * @param orderBy - the requested order
498
+ * @param rangeAttribute - the attribute used in a range condition if any
499
+ * @returns the Firestore query with ordering
500
+ */
501
+ handleOrderBy(query, orderBy, rangeAttribute) {
502
+ // Manage order if index
503
+ if (!orderBy) {
504
+ return query;
505
+ }
506
+ const requiredIndex = orderBy.map(order => order.field).sort();
507
+ let orders;
508
+ // Require index with multiple ORDER BY
509
+ if (requiredIndex.length > 1) {
510
+ orders = this.compoundIndexes[requiredIndex.join("/")];
511
+ if (!orders) {
512
+ this.log("WARN", "Skip orderBy as we are missing the index");
513
+ return query;
514
+ }
515
+ }
516
+ // Range must be the first orderBy
517
+ if (rangeAttribute) {
518
+ if (!orderBy.some(order => {
519
+ // Need to check the permitted orderBy and direction
520
+ if (order.field === rangeAttribute) {
521
+ query = query.orderBy(order.field, order.direction.toLowerCase());
522
+ return true;
523
+ }
524
+ return false;
525
+ })) {
526
+ this.log("WARN", "Skip orderBy as the range attribute is not within ORDER BY expression");
527
+ // If rangeAttribute is not in orderBy then skip the orderBy completely
528
+ return query;
529
+ }
530
+ }
531
+ // Add remaining orderBy from index
532
+ orderBy
533
+ .filter(order => order.field !== rangeAttribute)
534
+ .forEach(order => {
535
+ if (!orders || orders[order.field].has(order.direction.toLowerCase())) {
536
+ query = query.orderBy(order.field, order.direction.toLowerCase());
537
+ }
538
+ else {
539
+ this.log("WARN", "Skip orderBy as the direction does not match index");
540
+ }
541
+ });
542
+ return query;
543
+ }
544
+ /** @override */
545
+ async query(query) {
546
+ const parsed = typeof query === "string" ? WebdaQL.parse(query) : query;
547
+ const { results, continuationToken } = await this.find(parsed);
548
+ return { results, continuationToken };
549
+ }
550
+ /** @override */
551
+ async *iterate(query) {
552
+ const parsed = WebdaQL.parse(query);
553
+ parsed.limit ?? (parsed.limit = 100);
554
+ do {
555
+ const res = await this.find(parsed);
556
+ for (const item of res.results) {
557
+ yield item;
558
+ }
559
+ parsed.continuationToken = res.continuationToken;
560
+ } while (parsed.continuationToken);
561
+ }
562
+ /**
563
+ * Delete every document of the collection (used in tests)
564
+ */
565
+ async clear() {
566
+ const refs = await this.firestore.collection(this.collection).listDocuments();
567
+ for (let i = 0; i < refs.length; i += 500) {
568
+ const batch = this.firestore.batch();
569
+ refs.slice(i, i + 500).forEach(ref => batch.delete(ref));
570
+ await batch.commit();
571
+ }
572
+ }
573
+ }
574
+ let FireStore = (() => {
575
+ var _a;
576
+ let _classSuper = Store;
577
+ let _instanceExtraInitializers = [];
578
+ let _getRepository_decorators;
579
+ return _a = class FireStore extends _classSuper {
580
+ constructor() {
581
+ super(...arguments);
582
+ this.firestore = __runInitializers(this, _instanceExtraInitializers);
583
+ }
584
+ /**
585
+ * Create the Firestore client
586
+ * @override
587
+ */
588
+ computeParameters() {
589
+ super.computeParameters();
590
+ this.firestore ?? (this.firestore = new Firestore({ ignoreUndefinedProperties: true }));
591
+ }
592
+ /**
593
+ * Terminate the Firestore client
594
+ * @override
595
+ */
596
+ async stop() {
597
+ await this.firestore?.terminate().catch(() => {
598
+ /* already terminated */
599
+ });
600
+ await super.stop();
601
+ }
602
+ /**
603
+ * Resolve the collection name for a given model class
604
+ *
605
+ * Resolution order:
606
+ * 1. `parameters.collections[meta.Identifier]` — explicit per-model override
607
+ * 2. `parameters.collection` for the first model configured or when a single model is managed
608
+ * 3. Default — model identifier lowercased with "/" replaced by "_"
609
+ *
610
+ * @param model - the model class
611
+ * @returns the collection name
612
+ */
613
+ resolveCollection(model) {
614
+ const meta = useModelMetadata(model);
615
+ if (!meta) {
616
+ return this.parameters.collection;
617
+ }
618
+ if (this.parameters.collections?.[meta.Identifier]) {
619
+ return this.parameters.collections[meta.Identifier];
620
+ }
621
+ if (this.parameters.collection &&
622
+ ((this.parameters.models?.length ?? 0) <= 1 || this.parameters.models?.[0] === meta.Identifier)) {
623
+ return this.parameters.collection;
624
+ }
625
+ return meta.Identifier.toLowerCase().replace(/\//g, "_");
626
+ }
627
+ /**
628
+ * Build and return a FireStoreRepository for the given model
629
+ *
630
+ * The result is cached per model class via `@InstanceCache`.
631
+ * @param model - the model class
632
+ * @returns a repository backed by Firestore
633
+ */
634
+ getRepository(model) {
635
+ const meta = useModelMetadata(model);
636
+ this.firestore ?? (this.firestore = new Firestore({ ignoreUndefinedProperties: true }));
637
+ const inner = new FireStoreRepository(model, meta.PrimaryKey, this.firestore, this.resolveCollection(model), this.parameters.compoundIndexes, (level, ...args) => this.log(level, ...args), meta.PrimaryKeySeparator);
638
+ // Wrap in EventRepository so typed CRUD events fire; consumers reach them
639
+ // via useRepository(model).on(...).
640
+ return new EventRepository(model, meta.PrimaryKey, inner, meta.PrimaryKeySeparator);
641
+ }
642
+ /**
643
+ * Return the underlying FireStoreRepository for each model this store manages
644
+ * @returns the per-model FireStoreRepository instances
645
+ */
646
+ getRepositories() {
647
+ const repos = [];
648
+ for (const modelId of Object.keys(this._modelsHierarchy ?? {})) {
649
+ let model;
650
+ try {
651
+ model = useModel(modelId);
652
+ }
653
+ catch {
654
+ continue;
655
+ }
656
+ if (!model) {
657
+ continue;
658
+ }
659
+ const repo = this.getRepository(model);
660
+ repos.push((repo.repository ?? repo));
661
+ }
662
+ return repos;
663
+ }
664
+ /**
665
+ * Delete every document of the managed collections (used in tests)
666
+ */
667
+ async __clean() {
668
+ const done = new Set();
669
+ for (const repo of this.getRepositories()) {
670
+ if (done.has(repo.getCollection())) {
671
+ continue;
672
+ }
673
+ done.add(repo.getCollection());
674
+ await repo.clear();
675
+ }
676
+ }
677
+ },
678
+ (() => {
679
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
680
+ _getRepository_decorators = [InstanceCache()];
681
+ __esDecorate(_a, null, _getRepository_decorators, { kind: "method", name: "getRepository", static: false, private: false, access: { has: obj => "getRepository" in obj, get: obj => obj.getRepository }, metadata: _metadata }, null, _instanceExtraInitializers);
682
+ if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
683
+ })(),
684
+ _a;
685
+ })();
686
+ /**
687
+ * Implement Firebase abstraction within Webda
688
+ *
689
+ * @WebdaModda GoogleCloudFireStore
690
+ */
691
+ export default FireStore;
692
+ export { FireStore };
693
+ //# sourceMappingURL=firestore.service.js.map