@travetto/model-firestore 8.0.0-alpha.11 → 8.0.0-alpha.13

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 (2) hide show
  1. package/package.json +6 -6
  2. package/src/service.ts +76 -63
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-firestore",
3
- "version": "8.0.0-alpha.11",
3
+ "version": "8.0.0-alpha.13",
4
4
  "description": "Firestore backing for the travetto model module.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -26,13 +26,13 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@google-cloud/firestore": "^8.3.0",
29
- "@travetto/config": "^8.0.0-alpha.10",
30
- "@travetto/model": "^8.0.0-alpha.10",
31
- "@travetto/model-indexed": "^8.0.0-alpha.11",
32
- "@travetto/runtime": "^8.0.0-alpha.10"
29
+ "@travetto/config": "^8.0.0-alpha.12",
30
+ "@travetto/model": "^8.0.0-alpha.12",
31
+ "@travetto/model-indexed": "^8.0.0-alpha.13",
32
+ "@travetto/runtime": "^8.0.0-alpha.11"
33
33
  },
34
34
  "peerDependencies": {
35
- "@travetto/cli": "^8.0.0-alpha.15"
35
+ "@travetto/cli": "^8.0.0-alpha.17"
36
36
  },
37
37
  "peerDependenciesMeta": {
38
38
  "@travetto/cli": {
package/src/service.ts CHANGED
@@ -4,10 +4,11 @@ import { castTo, JSONUtil, ShutdownManager, type Class } from '@travetto/runtime
4
4
  import { Injectable, PostConstruct } from '@travetto/di';
5
5
  import {
6
6
  type ModelCrudSupport, ModelRegistryIndex, type ModelStorageSupport, type ModelType, NotFoundError, type OptionalId, ModelCrudUtil,
7
+ type ModelListOptions,
7
8
  } from '@travetto/model';
8
9
  import {
9
- type ModelIndexedSupport, type KeyedIndexSelection, type KeyedIndexBody, type ListPageOptions, ModelIndexedUtil,
10
- type SingleItemIndex, type SortedIndexSelection, type ListPageResult, type SortedIndex, type FullKeyedIndexBody,
10
+ type ModelIndexedSupport, type KeyedIndexSelection, type KeyedIndexBody, type ModelPageOptions, ModelIndexedUtil,
11
+ type SingleItemIndex, type SortedIndexSelection, type ModelPageResult, type SortedIndex, type FullKeyedIndexBody,
11
12
  type FullKeyedIndexWithPartialBody, ModelIndexedComputedIndex
12
13
  } from '@travetto/model-indexed';
13
14
 
@@ -41,6 +42,25 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
41
42
  return this.client.collection(this.#resolveTable(cls));
42
43
  }
43
44
 
45
+ async #getIdByIndex<
46
+ T extends ModelType,
47
+ K extends KeyedIndexSelection<T>,
48
+ S extends SortedIndexSelection<T>
49
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<string> {
50
+ ModelCrudUtil.ensureNotSubType(cls);
51
+ const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
52
+ const query = [...computed.allParts, ...(computed.idPart ? [computed.idPart] : [])].reduce<Query>(
53
+ (result, { path, value }) => result.where(path.join('.'), '==', value),
54
+ this.#getCollection(cls)
55
+ );
56
+
57
+ const item = await query.get();
58
+ if (!item || item.empty) {
59
+ throw new NotFoundError(`${cls.name} Index=${idx}`, computed.getKey());
60
+ }
61
+ return item.docs[0].id;
62
+ }
63
+
44
64
  #buildIndexQuery<
45
65
  T extends ModelType,
46
66
  K extends KeyedIndexSelection<T>,
@@ -58,6 +78,39 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
58
78
  return query;
59
79
  }
60
80
 
81
+ async * #scanCollection<T extends ModelType>(
82
+ cls: Class<T>,
83
+ queryBuilder: () => Query,
84
+ options?: ModelListOptions & ModelPageOptions<number>
85
+ ): AsyncIterable<{ items: T[], nextOffset?: number }> {
86
+ const limit = options?.limit ?? Number.MAX_SAFE_INTEGER;
87
+ const batchSize = Math.min(options?.batchSizeHint ?? 100, limit);
88
+
89
+ let offset = options?.offset ?? 0;
90
+ let produced = 0;
91
+
92
+ while (!(options?.abort?.aborted) && produced < limit) {
93
+ const query = queryBuilder().limit(batchSize).offset(offset);
94
+
95
+ let { docs } = await query.get();
96
+ if (docs.length === 0) {
97
+ break;
98
+ }
99
+
100
+ if (produced + docs.length > limit) {
101
+ docs = docs.slice(0, limit - produced);
102
+ }
103
+
104
+ offset += docs.length;
105
+
106
+ const items = await ModelCrudUtil.filterOutNotFound(
107
+ docs.map(item => ModelCrudUtil.load(cls, item.data()!)));
108
+ produced += items.length;
109
+
110
+ yield { items, nextOffset: offset };
111
+ }
112
+ }
113
+
61
114
  @PostConstruct()
62
115
  async initializeClient(): Promise<void> {
63
116
  globalThis.devProcessWarningExclusions?.push((_, category) => category === 'MetadataLookupWarning');
@@ -69,10 +122,9 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
69
122
  async createStorage(): Promise<void> { }
70
123
  async deleteStorage(): Promise<void> { }
71
124
 
72
- async deleteModel(cls: Class): Promise<void> {
73
- for await (const item of this.list(cls)) {
74
- await this.delete(cls, item.id);
75
- }
125
+ async deleteModel<T extends ModelType>(cls: Class<T>): Promise<void> {
126
+ const docs = await this.#getCollection(cls).listDocuments();
127
+ await Promise.all(docs.map(doc => doc.delete()));
76
128
  }
77
129
 
78
130
  // Crud
@@ -126,39 +178,12 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
126
178
  }
127
179
  }
128
180
 
129
- async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
130
- const batch = await this.#getCollection(cls).select().get();
131
- for (const item of batch.docs) {
132
- try {
133
- yield await this.get(cls, item.id);
134
- } catch (error) {
135
- if (!(error instanceof NotFoundError)) {
136
- throw error;
137
- }
138
- }
181
+ async * list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
182
+ for await (const { items } of this.#scanCollection(cls, () => this.#getCollection(cls), options)) {
183
+ yield items;
139
184
  }
140
185
  }
141
186
 
142
- // Indexed
143
- async #getIdByIndex<
144
- T extends ModelType,
145
- K extends KeyedIndexSelection<T>,
146
- S extends SortedIndexSelection<T>
147
- >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<string> {
148
- ModelCrudUtil.ensureNotSubType(cls);
149
- const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
150
- const query = [...computed.allParts, ...(computed.idPart ? [computed.idPart] : [])].reduce<Query>(
151
- (result, { path, value }) => result.where(path.join('.'), '==', value),
152
- this.#getCollection(cls)
153
- );
154
-
155
- const item = await query.get();
156
- if (!item || item.empty) {
157
- throw new NotFoundError(`${cls.name} Index=${idx}`, computed.getKey());
158
- }
159
- return item.docs[0].id;
160
- }
161
-
162
187
  // Indexed contract
163
188
 
164
189
  async getByIndex<
@@ -210,20 +235,20 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
210
235
  cls: Class<T>,
211
236
  idx: SortedIndex<T, K, S>,
212
237
  body: KeyedIndexBody<T, K>,
213
- options?: ListPageOptions,
214
- ): Promise<ListPageResult<T>> {
215
- const offset = options?.offset ? JSONUtil.fromBase64<number>(options.offset) : 0;
216
- const limit = options?.limit ?? 100;
217
- const query = this.#buildIndexQuery(cls, idx, body)
218
- .limit(limit)
219
- .offset(offset);
220
-
238
+ options?: ModelPageOptions,
239
+ ): Promise<ModelPageResult<T>> {
221
240
  const items: T[] = [];
222
- for (const item of (await query.get()).docs) {
223
- items.push(await ModelCrudUtil.load(cls, item.data()!));
241
+ let nextOffset: number | undefined;
242
+ for await (const batch of this.#scanCollection(cls, () => this.#buildIndexQuery(cls, idx, body), {
243
+ limit: 100,
244
+ ...options,
245
+ offset: options?.offset ? JSONUtil.fromBase64<number>(options.offset) : 0
246
+ })) {
247
+ items.push(...batch.items);
248
+ nextOffset = batch.nextOffset;
224
249
  }
225
250
 
226
- return { items, nextOffset: items.length ? JSONUtil.toBase64(offset + items.length) : undefined };
251
+ return { items, nextOffset: nextOffset ? JSONUtil.toBase64(nextOffset) : undefined };
227
252
  }
228
253
 
229
254
  async * listByIndex<
@@ -234,22 +259,10 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
234
259
  cls: Class<T>,
235
260
  idx: SortedIndex<T, K, S>,
236
261
  body: KeyedIndexBody<T, K>,
237
- ): AsyncIterable<T> {
238
- let offset = 0;
239
- while (offset >= 0) {
240
- const query = this.#buildIndexQuery(cls, idx, body)
241
- .limit(100)
242
- .offset(offset);
243
-
244
- const { docs: items } = await query.get();
245
- if (items.length === 0) {
246
- offset = -1;
247
- } else {
248
- for (const item of items) {
249
- yield await ModelCrudUtil.load(cls, item.data()!);
250
- }
251
- offset += items.length;
252
- }
262
+ options?: ModelListOptions
263
+ ): AsyncIterable<T[]> {
264
+ for await (const { items } of this.#scanCollection(cls, () => this.#buildIndexQuery(cls, idx, body), options)) {
265
+ yield items;
253
266
  }
254
267
  }
255
268
  }