@travetto/model-firestore 8.0.0-alpha.1 → 8.0.0-alpha.10

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
@@ -17,7 +17,7 @@ This module provides an [Firestore](https://firebase.google.com/docs/firestore)-
17
17
 
18
18
  Supported features:
19
19
  * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L11)
20
- * [Indexed](https://github.com/travetto/travetto/tree/main/module/model/src/types/indexed.ts#L11)
20
+ * [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L23)
21
21
 
22
22
  Out of the box, by installing the module, everything should be wired up by default.If you need to customize any aspect of the source or config, you can override and register it with the [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support.") module.
23
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-firestore",
3
- "version": "8.0.0-alpha.1",
3
+ "version": "8.0.0-alpha.10",
4
4
  "description": "Firestore backing for the travetto model module.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -26,12 +26,13 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@google-cloud/firestore": "^8.3.0",
29
- "@travetto/config": "^8.0.0-alpha.1",
30
- "@travetto/model": "^8.0.0-alpha.1",
31
- "@travetto/runtime": "^8.0.0-alpha.1"
29
+ "@travetto/config": "^8.0.0-alpha.10",
30
+ "@travetto/model": "^8.0.0-alpha.10",
31
+ "@travetto/model-indexed": "^8.0.0-alpha.10",
32
+ "@travetto/runtime": "^8.0.0-alpha.10"
32
33
  },
33
34
  "peerDependencies": {
34
- "@travetto/cli": "^8.0.0-alpha.1"
35
+ "@travetto/cli": "^8.0.0-alpha.15"
35
36
  },
36
37
  "peerDependenciesMeta": {
37
38
  "@travetto/cli": {
package/src/service.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  import { type DocumentData, FieldValue, Firestore, type Query } from '@google-cloud/firestore';
2
2
 
3
- import { JSONUtil, ShutdownManager, type Class, type DeepPartial } from '@travetto/runtime';
3
+ import { castTo, JSONUtil, ShutdownManager, type Class } from '@travetto/runtime';
4
4
  import { Injectable, PostConstruct } from '@travetto/di';
5
5
  import {
6
- type ModelCrudSupport, ModelRegistryIndex, type ModelStorageSupport,
7
- type ModelIndexedSupport, type ModelType, NotFoundError, type OptionalId,
8
- ModelCrudUtil, ModelIndexedUtil,
6
+ type ModelCrudSupport, ModelRegistryIndex, type ModelStorageSupport, type ModelType, NotFoundError, type OptionalId, ModelCrudUtil,
9
7
  } from '@travetto/model';
8
+ import {
9
+ type ModelIndexedSupport, type KeyedIndexSelection, type KeyedIndexBody, type ListPageOptions, ModelIndexedUtil,
10
+ type SingleItemIndex, type SortedIndexSelection, type ListPageResult, type SortedIndex, type FullKeyedIndexBody,
11
+ type FullKeyedIndexWithPartialBody, ModelIndexedComputedIndex
12
+ } from '@travetto/model-indexed';
10
13
 
11
14
  import type { FirestoreModelConfig } from './config.ts';
12
15
 
@@ -38,8 +41,26 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
38
41
  return this.client.collection(this.#resolveTable(cls));
39
42
  }
40
43
 
44
+ #buildIndexQuery<
45
+ T extends ModelType,
46
+ K extends KeyedIndexSelection<T>,
47
+ S extends SortedIndexSelection<T>
48
+ >(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>): Query {
49
+ ModelCrudUtil.ensureNotSubType(cls);
50
+ const computed = ModelIndexedComputedIndex.get(idx, body).validate();
51
+
52
+ let query = computed.keyedParts.reduce<Query>((result, { path, value, state }) =>
53
+ result.where(path.join('.'), '==', (state === 'empty' ? null : value)), this.#getCollection(cls));
54
+
55
+ for (const { path, value } of idx.sortTemplate) {
56
+ query = query.orderBy(path.join('.'), value === 1 ? 'asc' : 'desc');
57
+ }
58
+ return query;
59
+ }
60
+
41
61
  @PostConstruct()
42
62
  async initializeClient(): Promise<void> {
63
+ globalThis.devProcessWarningExclusions?.push((_, category) => category === 'MetadataLookupWarning');
43
64
  this.client = new Firestore({ ...this.config, useBigInt: true });
44
65
  ShutdownManager.signal.addEventListener('abort', () => this.client.terminate());
45
66
  }
@@ -119,49 +140,89 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
119
140
  }
120
141
 
121
142
  // Indexed
122
- async #getIdByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<string> {
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> {
123
148
  ModelCrudUtil.ensureNotSubType(cls);
124
-
125
- const { fields } = ModelIndexedUtil.computeIndexParts(cls, idx, body);
126
- const query = fields.reduce<Query>(
149
+ const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
150
+ const query = computed.allParts.reduce<Query>(
127
151
  (result, { path, value }) => result.where(path.join('.'), '==', value),
128
152
  this.#getCollection(cls)
129
153
  );
130
154
 
131
155
  const item = await query.get();
132
-
133
- if (item && !item.empty) {
134
- return item.docs[0].id;
156
+ if (!item || item.empty) {
157
+ throw new NotFoundError(`${cls.name} Index=${idx}`, computed.getKey());
135
158
  }
136
- throw new NotFoundError(`${cls.name} Index=${idx}`, ModelIndexedUtil.computeIndexKey(cls, idx, body, { separator: '; ' })?.key);
159
+ return item.docs[0].id;
137
160
  }
138
161
 
139
- async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<T> {
162
+ // Indexed contract
163
+
164
+ async getByIndex<
165
+ T extends ModelType,
166
+ K extends KeyedIndexSelection<T>,
167
+ S extends SortedIndexSelection<T>
168
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<T> {
140
169
  return this.get(cls, await this.#getIdByIndex(cls, idx, body));
141
170
  }
142
171
 
143
- async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
172
+ async deleteByIndex<
173
+ T extends ModelType,
174
+ K extends KeyedIndexSelection<T>,
175
+ S extends SortedIndexSelection<T>
176
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<void> {
144
177
  return this.delete(cls, await this.#getIdByIndex(cls, idx, body));
145
178
  }
146
179
 
147
- upsertByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: OptionalId<T>): Promise<T> {
180
+ upsertByIndex<
181
+ T extends ModelType,
182
+ K extends KeyedIndexSelection<T>,
183
+ S extends SortedIndexSelection<T>
184
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: OptionalId<T>): Promise<T> {
148
185
  return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
149
186
  }
150
187
 
151
- async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): AsyncIterable<T> {
152
- ModelCrudUtil.ensureNotSubType(cls);
153
-
154
- const config = ModelRegistryIndex.getIndex(cls, idx, ['sorted', 'unsorted']);
155
- const { fields, sorted } = ModelIndexedUtil.computeIndexParts(cls, config, body, { emptySortValue: null });
156
- let query = fields.reduce<Query>((result, { path, value }) =>
157
- result.where(path.join('.'), '==', value), this.#getCollection(cls));
158
-
159
- if (sorted) {
160
- query = query.orderBy(sorted.path.join('.'), sorted.dir === 1 ? 'asc' : 'desc');
161
- }
162
-
188
+ updateByIndex<
189
+ T extends ModelType,
190
+ K extends KeyedIndexSelection<T>,
191
+ S extends SortedIndexSelection<T>
192
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: T): Promise<T> {
193
+ return ModelIndexedUtil.naiveUpdate(this, cls, idx, body);
194
+ }
195
+
196
+ async updatePartialByIndex<
197
+ T extends ModelType,
198
+ K extends KeyedIndexSelection<T>,
199
+ S extends SortedIndexSelection<T>
200
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexWithPartialBody<T, K, S>): Promise<T> {
201
+ const item = await ModelCrudUtil.naivePartialUpdate(cls, () => this.getByIndex(cls, idx, castTo(body)), castTo(body));
202
+ return this.update(cls, item);
203
+ }
204
+
205
+ async listByIndex<
206
+ T extends ModelType,
207
+ K extends KeyedIndexSelection<T>,
208
+ S extends SortedIndexSelection<T>
209
+ >(
210
+ cls: Class<T>,
211
+ idx: SortedIndex<T, K, S>,
212
+ 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
+
221
+ const items: T[] = [];
163
222
  for (const item of (await query.get()).docs) {
164
- yield await ModelCrudUtil.load(cls, item.data()!);
223
+ items.push(await ModelCrudUtil.load(cls, item.data()!));
165
224
  }
225
+
226
+ return { items, nextOffset: items.length ? JSONUtil.toBase64(offset + items.length) : undefined };
166
227
  }
167
228
  }