@travetto/model-firestore 8.0.0-alpha.28 → 8.0.0-alpha.29

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,11 +13,11 @@ npm install @travetto/model-firestore
13
13
  yarn add @travetto/model-firestore
14
14
  ```
15
15
 
16
- This module provides an [Firestore](https://firebase.google.com/docs/firestore)-based implementation of the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."). This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [Firestore](https://firebase.google.com/docs/firestore).
16
+ This module provides an [Firestore](https://firebase.google.com/docs/firestore)-based implementation of the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."). This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [Firestore](https://firebase.google.com/docs/firestore).
17
17
 
18
18
  Supported features:
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-indexed/src/types/service.ts#L16)
19
+ * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L10)
20
+ * [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L21)
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/__index__.ts CHANGED
@@ -1,2 +1,2 @@
1
+ export * from './src/config.ts';
1
2
  export * from './src/service.ts';
2
- export * from './src/config.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-firestore",
3
- "version": "8.0.0-alpha.28",
3
+ "version": "8.0.0-alpha.29",
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.6.0",
29
- "@travetto/config": "^8.0.0-alpha.21",
30
- "@travetto/model": "^8.0.0-alpha.22",
31
- "@travetto/model-indexed": "^8.0.0-alpha.24",
32
- "@travetto/runtime": "^8.0.0-alpha.19"
29
+ "@travetto/config": "^8.0.0-alpha.22",
30
+ "@travetto/model": "^8.0.0-alpha.23",
31
+ "@travetto/model-indexed": "^8.0.0-alpha.25",
32
+ "@travetto/runtime": "^8.0.0-alpha.20"
33
33
  },
34
34
  "peerDependencies": {
35
- "@travetto/cli": "^8.0.0-alpha.27"
35
+ "@travetto/cli": "^8.0.0-alpha.28"
36
36
  },
37
37
  "peerDependenciesMeta": {
38
38
  "@travetto/cli": {
package/src/config.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { JSONUtil, Runtime, RuntimeResources } from '@travetto/runtime';
2
1
  import { Config } from '@travetto/config';
3
- import { Schema, SchemaValidator } from '@travetto/schema';
4
2
  import { PostConstruct } from '@travetto/di';
3
+ import { JSONUtil, Runtime, RuntimeResources } from '@travetto/runtime';
4
+ import { Schema, SchemaValidator } from '@travetto/schema';
5
5
 
6
6
  @Schema()
7
7
  class FirestoreModelConfigCredentials {
@@ -34,4 +34,4 @@ export class FirestoreModelConfig {
34
34
  await SchemaValidator.validate(FirestoreModelConfigCredentials, this.credentials);
35
35
  }
36
36
  }
37
- }
37
+ }
package/src/service.ts CHANGED
@@ -1,17 +1,35 @@
1
1
  import { type DocumentData, FieldValue, Firestore, type Query } from '@google-cloud/firestore';
2
2
 
3
- import { castTo, JSONUtil, ShutdownManager, type Class, RuntimeError } from '@travetto/runtime';
4
3
  import { Injectable, PostConstruct } from '@travetto/di';
5
4
  import {
6
- type ModelCrudSupport, ModelRegistryIndex, type ModelStorageSupport, type ModelType, NotFoundError, type OptionalId, ModelCrudUtil,
5
+ type ModelCrudSupport,
6
+ ModelCrudUtil,
7
7
  type ModelListOptions,
8
+ ModelRegistryIndex,
9
+ type ModelStorageSupport,
10
+ type ModelType,
11
+ NotFoundError,
12
+ type OptionalId
8
13
  } from '@travetto/model';
9
14
  import {
10
- type ModelIndexedSupport, type KeyedIndexSelection, type KeyedIndexBody, type ModelPageOptions, ModelIndexedUtil,
11
- type SingleItemIndex, type SortedIndexSelection, type ModelPageResult, type SortedIndex, type FullKeyedIndexBody,
12
- type FullKeyedIndexWithPartialBody, ModelIndexedComputedIndex, warnIfIndexedUniqueIndex, warnIfNonIndexedIndex,
13
- type ModelIndexedSearchOptions, type SortedIndexSelectionType
15
+ type FullKeyedIndexBody,
16
+ type FullKeyedIndexWithPartialBody,
17
+ type KeyedIndexBody,
18
+ type KeyedIndexSelection,
19
+ ModelIndexedComputedIndex,
20
+ type ModelIndexedSearchOptions,
21
+ type ModelIndexedSupport,
22
+ ModelIndexedUtil,
23
+ type ModelPageOptions,
24
+ type ModelPageResult,
25
+ type SingleItemIndex,
26
+ type SortedIndex,
27
+ type SortedIndexSelection,
28
+ type SortedIndexSelectionType,
29
+ warnIfIndexedUniqueIndex,
30
+ warnIfNonIndexedIndex
14
31
  } from '@travetto/model-indexed';
32
+ import { type Class, castTo, JSONUtil, RuntimeError, ShutdownManager } from '@travetto/runtime';
15
33
 
16
34
  import type { FirestoreModelConfig } from './config.ts';
17
35
 
@@ -24,7 +42,6 @@ const setMissingValues = <T>(input: T, missingValue: unknown): T =>
24
42
  */
25
43
  @Injectable()
26
44
  export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupport, ModelIndexedSupport {
27
-
28
45
  static resolveTable(cls: Class, namespace?: string): string {
29
46
  let table = ModelRegistryIndex.getStoreName(cls);
30
47
  if (namespace) {
@@ -37,7 +54,9 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
37
54
  client: Firestore;
38
55
  config: FirestoreModelConfig;
39
56
 
40
- constructor(config: FirestoreModelConfig) { this.config = config; }
57
+ constructor(config: FirestoreModelConfig) {
58
+ this.config = config;
59
+ }
41
60
 
42
61
  #resolveTable(cls: Class): string {
43
62
  return FirestoreModelService.resolveTable(cls, this.config.namespace);
@@ -47,11 +66,11 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
47
66
  return this.client.collection(this.#resolveTable(cls));
48
67
  }
49
68
 
50
- async #getIdByIndex<
51
- T extends ModelType,
52
- K extends KeyedIndexSelection<T>,
53
- S extends SortedIndexSelection<T>
54
- >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<string> {
69
+ async #getIdByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
70
+ cls: Class<T>,
71
+ idx: SingleItemIndex<T, K, S>,
72
+ body: FullKeyedIndexBody<T, K, S>
73
+ ): Promise<string> {
55
74
  ModelCrudUtil.ensureNotSubType(cls);
56
75
  const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
57
76
  const query = [...computed.allParts, ...(computed.idPart ? [computed.idPart] : [])].reduce<Query>(
@@ -73,8 +92,10 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
73
92
  ModelCrudUtil.ensureNotSubType(cls);
74
93
  const computed = ModelIndexedComputedIndex.get(idx, body).validate();
75
94
 
76
- let query = computed.keyedParts.reduce<Query>((result, { path, value, state }) =>
77
- result.where(path.join('.'), '==', (state === 'empty' ? null : value)), this.#getCollection(cls));
95
+ let query = computed.keyedParts.reduce<Query>(
96
+ (result, { path, value, state }) => result.where(path.join('.'), '==', state === 'empty' ? null : value),
97
+ this.#getCollection(cls)
98
+ );
78
99
 
79
100
  for (const { path, value } of idx.sortTemplate) {
80
101
  query = query.orderBy(path.join('.'), value === 1 ? 'asc' : 'desc');
@@ -82,18 +103,18 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
82
103
  return query;
83
104
  }
84
105
 
85
- async * #scanCollection<T extends ModelType>(
106
+ async *#scanCollection<T extends ModelType>(
86
107
  cls: Class<T>,
87
108
  queryBuilder: () => Query,
88
109
  options?: ModelListOptions & ModelPageOptions<number>
89
- ): AsyncIterable<{ items: T[], nextOffset?: number }> {
110
+ ): AsyncIterable<{ items: T[]; nextOffset?: number }> {
90
111
  const limit = options?.limit ?? Number.MAX_SAFE_INTEGER;
91
112
  const batchSize = Math.min(options?.batchSizeHint ?? 100, limit);
92
113
 
93
114
  let offset = options?.offset ?? 0;
94
115
  let produced = 0;
95
116
 
96
- while (!(options?.abort?.aborted) && produced < limit) {
117
+ while (!options?.abort?.aborted && produced < limit) {
97
118
  const query = queryBuilder().limit(batchSize).offset(offset);
98
119
 
99
120
  const { docs, size } = await query.get();
@@ -101,12 +122,11 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
101
122
  break;
102
123
  }
103
124
 
104
- const remaining = (produced + size > limit) ? docs.slice(0, limit - produced) : docs;
125
+ const remaining = produced + size > limit ? docs.slice(0, limit - produced) : docs;
105
126
 
106
127
  offset += size;
107
128
 
108
- const items = await ModelCrudUtil.filterOutNotFound(
109
- remaining.map(item => ModelCrudUtil.load(cls, item.data()!)));
129
+ const items = await ModelCrudUtil.filterOutNotFound(remaining.map(item => ModelCrudUtil.load(cls, item.data()!)));
110
130
  produced += items.length;
111
131
 
112
132
  yield { items, nextOffset: offset };
@@ -126,7 +146,7 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
126
146
  warnIfNonIndexedIndex(this, cls, ModelRegistryIndex.getIndices(cls));
127
147
  }
128
148
  }
129
- async deleteStorage(): Promise<void> { }
149
+ async deleteStorage(): Promise<void> {}
130
150
 
131
151
  async deleteModel<T extends ModelType>(cls: Class<T>): Promise<void> {
132
152
  await this.client.recursiveDelete(this.#getCollection(cls));
@@ -136,7 +156,7 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
136
156
  async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
137
157
  const result = await this.#getCollection(cls).doc(id).get();
138
158
 
139
- if (result && result.exists) {
159
+ if (result?.exists) {
140
160
  return await ModelCrudUtil.load(cls, result.data()!);
141
161
  }
142
162
  throw new NotFoundError(cls, id);
@@ -167,7 +187,9 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
167
187
  const id = item.id;
168
188
  const full = await ModelCrudUtil.naivePartialUpdate(cls, () => this.get(cls, id), item, view);
169
189
  const cleaned = setMissingValues(full, FieldValue.delete());
170
- await this.#getCollection(cls).doc(id).set(cleaned, { mergeFields: Object.keys(full) });
190
+ await this.#getCollection(cls)
191
+ .doc(id)
192
+ .set(cleaned, { mergeFields: Object.keys(full) });
171
193
  return this.get(cls, id);
172
194
  }
173
195
 
@@ -183,7 +205,7 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
183
205
  }
184
206
  }
185
207
 
186
- async * list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
208
+ async *list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
187
209
  for await (const { items } of this.#scanCollection(cls, () => this.#getCollection(cls), options)) {
188
210
  yield items;
189
211
  }
@@ -191,56 +213,52 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
191
213
 
192
214
  // Indexed contract
193
215
 
194
- async getByIndex<
195
- T extends ModelType,
196
- K extends KeyedIndexSelection<T>,
197
- S extends SortedIndexSelection<T>
198
- >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<T> {
216
+ async getByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
217
+ cls: Class<T>,
218
+ idx: SingleItemIndex<T, K, S>,
219
+ body: FullKeyedIndexBody<T, K, S>
220
+ ): Promise<T> {
199
221
  return this.get(cls, await this.#getIdByIndex(cls, idx, body));
200
222
  }
201
223
 
202
- async deleteByIndex<
203
- T extends ModelType,
204
- K extends KeyedIndexSelection<T>,
205
- S extends SortedIndexSelection<T>
206
- >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<void> {
224
+ async deleteByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
225
+ cls: Class<T>,
226
+ idx: SingleItemIndex<T, K, S>,
227
+ body: FullKeyedIndexBody<T, K, S>
228
+ ): Promise<void> {
207
229
  return this.delete(cls, await this.#getIdByIndex(cls, idx, body));
208
230
  }
209
231
 
210
- upsertByIndex<
211
- T extends ModelType,
212
- K extends KeyedIndexSelection<T>,
213
- S extends SortedIndexSelection<T>
214
- >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: OptionalId<T>): Promise<T> {
232
+ upsertByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
233
+ cls: Class<T>,
234
+ idx: SingleItemIndex<T, K, S>,
235
+ body: OptionalId<T>
236
+ ): Promise<T> {
215
237
  return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
216
238
  }
217
239
 
218
- updateByIndex<
219
- T extends ModelType,
220
- K extends KeyedIndexSelection<T>,
221
- S extends SortedIndexSelection<T>
222
- >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: T): Promise<T> {
240
+ updateByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
241
+ cls: Class<T>,
242
+ idx: SingleItemIndex<T, K, S>,
243
+ body: T
244
+ ): Promise<T> {
223
245
  return ModelIndexedUtil.naiveUpdate(this, cls, idx, body);
224
246
  }
225
247
 
226
- async updatePartialByIndex<
227
- T extends ModelType,
228
- K extends KeyedIndexSelection<T>,
229
- S extends SortedIndexSelection<T>
230
- >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexWithPartialBody<T, K, S>): Promise<T> {
248
+ async updatePartialByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
249
+ cls: Class<T>,
250
+ idx: SingleItemIndex<T, K, S>,
251
+ body: FullKeyedIndexWithPartialBody<T, K, S>
252
+ ): Promise<T> {
231
253
  const item = await ModelCrudUtil.naivePartialUpdate(cls, () => this.getByIndex(cls, idx, castTo(body)), castTo(body));
232
254
  return this.update(cls, item);
233
255
  }
234
256
 
235
- async pageByIndex<
236
- T extends ModelType,
237
- K extends KeyedIndexSelection<T>,
238
- S extends SortedIndexSelection<T>
239
- >(
257
+ async pageByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
240
258
  cls: Class<T>,
241
259
  idx: SortedIndex<T, K, S>,
242
260
  body: KeyedIndexBody<T, K>,
243
- options?: ModelPageOptions,
261
+ options?: ModelPageOptions
244
262
  ): Promise<ModelPageResult<T>> {
245
263
  const items: T[] = [];
246
264
  let nextOffset: number | undefined;
@@ -256,11 +274,7 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
256
274
  return { items, nextOffset: nextOffset ? JSONUtil.toBase64(nextOffset) : undefined };
257
275
  }
258
276
 
259
- async * listByIndex<
260
- T extends ModelType,
261
- K extends KeyedIndexSelection<T>,
262
- S extends SortedIndexSelection<T>
263
- >(
277
+ async *listByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
264
278
  cls: Class<T>,
265
279
  idx: SortedIndex<T, K, S>,
266
280
  body: KeyedIndexBody<T, K>,
@@ -281,15 +295,14 @@ export class FirestoreModelService implements ModelCrudSupport, ModelStorageSupp
281
295
 
282
296
  const field = idx.sortTemplate[0].path.join('.');
283
297
 
284
- for await (const { items } of this.#scanCollection(cls,
285
- () => this.#buildIndexQuery(cls, idx, body)
286
- .where(field, '>=', prefix)
287
- .where(field, '<', `${prefix}\uf8ff`),
288
- { limit: 10, ...options })
289
- ) {
298
+ for await (const { items } of this.#scanCollection(
299
+ cls,
300
+ () => this.#buildIndexQuery(cls, idx, body).where(field, '>=', prefix).where(field, '<', `${prefix}\uf8ff`),
301
+ { limit: 10, ...options }
302
+ )) {
290
303
  results.push(...items);
291
304
  }
292
305
 
293
306
  return results;
294
307
  }
295
- }
308
+ }
@@ -1,13 +1,13 @@
1
1
  import cp from 'node:child_process';
2
2
  import fs from 'node:fs/promises';
3
3
 
4
- import { type CliCommandShape, CliCommand, CliModuleFlag } from '@travetto/cli';
5
- import { JSONUtil, Env, ExecUtil, Runtime } from '@travetto/runtime';
6
- import { Registry } from '@travetto/registry';
4
+ import { CliCommand, type CliCommandShape, CliModuleFlag } from '@travetto/cli';
7
5
  import { DependencyRegistryIndex } from '@travetto/di';
6
+ import { ManifestFileUtil } from '@travetto/manifest';
8
7
  import { ModelRegistryIndex } from '@travetto/model';
9
8
  import { isModelIndexedIndex } from '@travetto/model-indexed';
10
- import { ManifestFileUtil } from '@travetto/manifest';
9
+ import { Registry } from '@travetto/registry';
10
+ import { Env, ExecUtil, JSONUtil, Runtime } from '@travetto/runtime';
11
11
 
12
12
  import { FirestoreModelConfig } from '../src/config.ts';
13
13
  import { FirestoreModelService } from '../src/service.ts';
@@ -36,7 +36,6 @@ type FirestoreIndexSet = {
36
36
  */
37
37
  @CliCommand()
38
38
  export class FirestoreIndexesCommand implements CliCommandShape {
39
-
40
39
  indexFile = 'firestore.indexes.json';
41
40
  firebaseFile = 'firebase.json';
42
41
 
@@ -61,14 +60,16 @@ export class FirestoreIndexesCommand implements CliCommandShape {
61
60
  const indices = ModelRegistryIndex.getIndices(cls)
62
61
  .filter(isModelIndexedIndex)
63
62
  // We need at least 2 fields. All 1 field indices are already handled
64
- .filter((idx => (idx.keyTemplate?.length ?? 0) + (idx.sortTemplate?.length ?? 0) >= 2));
63
+ .filter(idx => (idx.keyTemplate?.length ?? 0) + (idx.sortTemplate?.length ?? 0) >= 2);
65
64
 
66
65
  for (const idx of indices) {
67
66
  indexesList.push({
68
67
  collectionGroup: FirestoreModelService.resolveTable(cls, config.namespace),
69
68
  queryScope: 'COLLECTION',
70
- fields: [...idx.keyTemplate, ...idx.sortTemplate]
71
- .map(part => ({ fieldPath: part.path.join('.'), order: part.value === -1 ? 'DESCENDING' : 'ASCENDING' }))
69
+ fields: [...idx.keyTemplate, ...idx.sortTemplate].map(part => ({
70
+ fieldPath: part.path.join('.'),
71
+ order: part.value === -1 ? 'DESCENDING' : 'ASCENDING'
72
+ }))
72
73
  });
73
74
  }
74
75
  }
@@ -83,17 +84,19 @@ export class FirestoreIndexesCommand implements CliCommandShape {
83
84
  await ManifestFileUtil.bufferedFileWrite(this.indexFile, text);
84
85
 
85
86
  const firebaseLocation = Runtime.workspaceRelative(this.firebaseFile);
86
- if (!await fs.stat(firebaseLocation, { throwIfNoEntry: false })) {
87
+ if (!(await fs.stat(firebaseLocation, { throwIfNoEntry: false }))) {
87
88
  await ManifestFileUtil.bufferedFileWrite(firebaseLocation, '{}');
88
89
  }
89
90
 
90
- const firebaseContext = JSONUtil.fromBinaryArray<{ firestore?: { database?: string, indexes?: string }[] }>(await fs.readFile(firebaseLocation));
91
+ const firebaseContext = JSONUtil.fromBinaryArray<{ firestore?: { database?: string; indexes?: string }[] }>(
92
+ await fs.readFile(firebaseLocation)
93
+ );
91
94
  const existing = (firebaseContext.firestore ??= []);
92
95
  const found = existing.find(x => x.database === config.databaseId);
93
96
 
94
97
  let changed = true;
95
98
  if (!found) {
96
- existing.push(({ indexes: this.indexFile, database: config.databaseId }));
99
+ existing.push({ indexes: this.indexFile, database: config.databaseId });
97
100
  } else if (found.indexes !== this.indexFile) {
98
101
  found.indexes = this.indexFile;
99
102
  } else {
@@ -106,11 +109,12 @@ export class FirestoreIndexesCommand implements CliCommandShape {
106
109
 
107
110
  if (this.deploy) {
108
111
  const child = cp.spawn(
109
- 'firebase', ['deploy', '--only', 'firestore:indexes'],
112
+ 'firebase',
113
+ ['deploy', '--only', 'firestore:indexes'],
110
114
  // Complete take over
111
- { stdio: 'inherit' },
115
+ { stdio: 'inherit' }
112
116
  );
113
- await ExecUtil.getResult(child)
117
+ await ExecUtil.getResult(child);
114
118
  }
115
119
  }
116
120
  }
@@ -10,4 +10,4 @@ export const service: ServiceDescriptor = {
10
10
  FIRESTORE_PROJECT_ID: 'trv-local-dev'
11
11
  },
12
12
  image: `ridedott/firestore-emulator:${version}`
13
- };
13
+ };