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

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