@travetto/model-mongo 7.0.0-rc.2 → 7.0.0-rc.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.
package/README.md CHANGED
@@ -84,9 +84,9 @@ export class MongoModelConfig {
84
84
  options: mongo.MongoClientOptions = {};
85
85
 
86
86
  /**
87
- * Should we auto create the db
87
+ * Allow storage modification at runtime
88
88
  */
89
- autoCreate?: boolean;
89
+ modifyStorage?: boolean;
90
90
 
91
91
  /**
92
92
  * Frequency of culling for cullable content
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-mongo",
3
- "version": "7.0.0-rc.2",
3
+ "version": "7.0.0-rc.3",
4
4
  "description": "Mongo backing for the travetto model module.",
5
5
  "keywords": [
6
6
  "mongo",
@@ -25,10 +25,10 @@
25
25
  "directory": "module/model-mongo"
26
26
  },
27
27
  "dependencies": {
28
- "@travetto/cli": "^7.0.0-rc.2",
29
- "@travetto/config": "^7.0.0-rc.2",
30
- "@travetto/model": "^7.0.0-rc.2",
31
- "@travetto/model-query": "^7.0.0-rc.2",
28
+ "@travetto/cli": "^7.0.0-rc.3",
29
+ "@travetto/config": "^7.0.0-rc.3",
30
+ "@travetto/model": "^7.0.0-rc.3",
31
+ "@travetto/model-query": "^7.0.0-rc.3",
32
32
  "mongodb": "^7.0.0"
33
33
  },
34
34
  "travetto": {
package/src/config.ts CHANGED
@@ -43,9 +43,9 @@ export class MongoModelConfig {
43
43
  options: mongo.MongoClientOptions = {};
44
44
 
45
45
  /**
46
- * Should we auto create the db
46
+ * Allow storage modification at runtime
47
47
  */
48
- autoCreate?: boolean;
48
+ modifyStorage?: boolean;
49
49
 
50
50
  /**
51
51
  * Frequency of culling for cullable content
@@ -1,5 +1,6 @@
1
1
  import {
2
- Binary, type CreateIndexesOptions, type Filter, type FindCursor, type IndexDirection, ObjectId, type WithId as MongoWithId
2
+ Binary, type CreateIndexesOptions, type Filter, type FindCursor, type IndexDirection, ObjectId, type WithId as MongoWithId,
3
+ type IndexDescriptionInfo
3
4
  } from 'mongodb';
4
5
 
5
6
  import { AppError, castTo, Class, toConcrete, TypedObject } from '@travetto/runtime';
@@ -212,4 +213,33 @@ export class MongoUtil {
212
213
 
213
214
  return castTo(cursor);
214
215
  }
216
+
217
+ static isIndexChanged(existing: IndexDescriptionInfo, [pendingKey, pendingOptions]: [BasicIdx, CreateIndexesOptions]): boolean {
218
+ // Config changed
219
+ if (
220
+ !!existing.unique !== !!pendingOptions.unique ||
221
+ !!existing.sparse !== !!pendingOptions.sparse ||
222
+ existing.expireAfterSeconds !== pendingOptions.expireAfterSeconds ||
223
+ existing.bucketSize !== pendingOptions.bucketSize
224
+ ) {
225
+ return true;
226
+ }
227
+ const pendingKeySet = new Set(Object.keys(pendingKey));
228
+ const existingKeySet = new Set(Object.keys(existing.key));
229
+
230
+ if (pendingKeySet.size !== existingKeySet.size) {
231
+ return true;
232
+ }
233
+
234
+ const overlap = pendingKeySet.intersection(existingKeySet);
235
+ if (overlap.size !== pendingKeySet.size) {
236
+ return true;
237
+ }
238
+ for (const key of overlap) {
239
+ if (existing.key[key] !== pendingKey[key]) {
240
+ return false;
241
+ }
242
+ }
243
+ return false;
244
+ }
215
245
  }
package/src/service.ts CHANGED
@@ -3,7 +3,7 @@ import { pipeline } from 'node:stream/promises';
3
3
  import {
4
4
  type Db, GridFSBucket, MongoClient, type GridFSFile, type Collection,
5
5
  type ObjectId, type Binary, type RootFilterOperators, type Filter,
6
- type WithId as MongoWithId
6
+ type WithId as MongoWithId,
7
7
  } from 'mongodb';
8
8
 
9
9
  import {
@@ -106,7 +106,7 @@ export class MongoModelService implements
106
106
  bucketName: ModelBlobNamespace,
107
107
  writeConcern: { w: 1 }
108
108
  });
109
- await ModelStorageUtil.registerModelChangeListener(this);
109
+ await ModelStorageUtil.storageInitialization(this);
110
110
  ShutdownManager.onGracefulShutdown(() => this.client.close());
111
111
  ModelExpiryUtil.registerCull(this);
112
112
  }
@@ -126,23 +126,34 @@ export class MongoModelService implements
126
126
  await this.#db.dropDatabase();
127
127
  }
128
128
 
129
- async establishIndices<T extends ModelType>(cls: Class<T>): Promise<void> {
129
+ async upsertModel(cls: Class): Promise<void> {
130
130
  const col = await this.getStore(cls);
131
- const creating = MongoUtil.getIndices(cls, ModelRegistryIndex.getConfig(cls).indices);
132
- if (creating.length) {
133
- console.debug('Creating indexes', { indices: creating });
134
- for (const toCreate of creating) {
135
- await col.createIndex(...toCreate);
136
- }
137
- }
138
- }
131
+ const indices = MongoUtil.getIndices(cls, ModelRegistryIndex.getConfig(cls).indices);
132
+ const existingIndices = (await col.indexes().catch(() => [])).filter(idx => idx.name !== '_id_');
139
133
 
140
- async createModel(cls: Class): Promise<void> {
141
- await this.establishIndices(cls);
142
- }
134
+ const pendingMap = Object.fromEntries(indices.map(pair => [pair[1].name!, pair]));
135
+ const existingMap = Object.fromEntries(existingIndices.map(idx => [idx.name!, idx.key]));
143
136
 
144
- async changeModel(cls: Class): Promise<void> {
145
- await this.establishIndices(cls);
137
+ for (const idx of existingIndices) {
138
+ if (!idx.name) {
139
+ continue;
140
+ }
141
+ const pending = pendingMap[idx.name];
142
+ if (!pending) {
143
+ console.debug('Deleting index', { indices: idx.name });
144
+ await col.dropIndex(idx.name);
145
+ } else if (MongoUtil.isIndexChanged(idx, pending)) {
146
+ console.debug('Updating index', { indices: idx.name });
147
+ await col.dropIndex(idx.name);
148
+ await col.createIndex(...pending);
149
+ }
150
+ }
151
+ for (const [name, idx] of Object.entries(pendingMap)) {
152
+ if (!existingMap[name]) {
153
+ console.debug('Creating index', { indices: name });
154
+ await col.createIndex(...idx);
155
+ }
156
+ }
146
157
  }
147
158
 
148
159
  async truncateModel<T extends ModelType>(cls: Class<T>): Promise<void> {
@@ -158,7 +169,7 @@ export class MongoModelService implements
158
169
  * Get mongo collection
159
170
  */
160
171
  async getStore<T extends ModelType>(cls: Class<T>): Promise<Collection<T>> {
161
- return this.#db.collection(ModelRegistryIndex.getStoreName(cls).toLowerCase().replace(/[^A-Za-z0-9_]+/g, '_'));
172
+ return this.#db.collection(ModelRegistryIndex.getStoreName(cls));
162
173
  }
163
174
 
164
175
  // Crud