@travetto/model-mongo 5.0.0-rc.9 → 5.0.0

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,10 +17,10 @@ This module provides an [mongodb](https://mongodb.com)-based implementation for
17
17
 
18
18
  Supported features:
19
19
  * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11)
20
- * [Streaming](https://github.com/travetto/travetto/tree/main/module/model/src/service/stream.ts#L3)
21
20
  * [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/service/expiry.ts#L11)
22
21
  * [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/service/bulk.ts#L19)
23
22
  * [Indexed](https://github.com/travetto/travetto/tree/main/module/model/src/service/indexed.ts#L12)
23
+ * [Blob](https://github.com/travetto/travetto/tree/main/module/model/src/service/blob.ts#L8)
24
24
  * [Query Crud](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/crud.ts#L11)
25
25
  * [Facet](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/facet.ts#L12)
26
26
  * [Query](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/query.ts#L10)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-mongo",
3
- "version": "5.0.0-rc.9",
3
+ "version": "5.0.0",
4
4
  "description": "Mongo backing for the travetto model module.",
5
5
  "keywords": [
6
6
  "mongo",
@@ -25,13 +25,13 @@
25
25
  "directory": "module/model-mongo"
26
26
  },
27
27
  "dependencies": {
28
- "@travetto/config": "^5.0.0-rc.9",
29
- "@travetto/model": "^5.0.0-rc.9",
30
- "@travetto/model-query": "^5.0.0-rc.9",
28
+ "@travetto/config": "^5.0.0",
29
+ "@travetto/model": "^5.0.0",
30
+ "@travetto/model-query": "^5.0.0",
31
31
  "mongodb": "^6.8.0"
32
32
  },
33
33
  "peerDependencies": {
34
- "@travetto/command": "^5.0.0-rc.9"
34
+ "@travetto/command": "^5.0.0"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@travetto/command": {
package/src/service.ts CHANGED
@@ -1,19 +1,15 @@
1
- // Wildcard import needed here due to packaging issues
2
1
  import {
3
2
  type Db, GridFSBucket, MongoClient, type Sort, type CreateIndexesOptions,
4
3
  type GridFSFile, type IndexSpecification, type Collection, ObjectId,
5
4
  Binary
6
5
  } from 'mongodb';
7
- import { Readable } from 'node:stream';
8
6
  import { pipeline } from 'node:stream/promises';
7
+ import { Readable } from 'node:stream';
9
8
 
10
9
  import {
11
- ModelRegistry, ModelType, OptionalId,
12
- ModelCrudSupport, ModelStorageSupport, ModelStreamSupport,
13
- ModelExpirySupport, ModelBulkSupport, ModelIndexedSupport,
14
- StreamMeta, BulkOp, BulkResponse,
15
- NotFoundError, ExistsError, IndexConfig,
16
- StreamRange
10
+ ModelRegistry, ModelType, OptionalId, ModelCrudSupport, ModelStorageSupport,
11
+ ModelExpirySupport, ModelBulkSupport, ModelIndexedSupport, BulkOp, BulkResponse,
12
+ NotFoundError, ExistsError, IndexConfig, ModelBlobSupport
17
13
  } from '@travetto/model';
18
14
  import {
19
15
  ModelQuery, ModelQueryCrudSupport, ModelQueryFacetSupport, ModelQuerySupport,
@@ -21,7 +17,10 @@ import {
21
17
  QueryVerifier
22
18
  } from '@travetto/model-query';
23
19
 
24
- import { ShutdownManager, type Class, type DeepPartial, AppError, TypedObject, castTo, asFull } from '@travetto/runtime';
20
+ import {
21
+ ShutdownManager, type Class, type DeepPartial, AppError, TypedObject,
22
+ castTo, asFull, BlobMeta, ByteRange, BinaryInput, BinaryUtil
23
+ } from '@travetto/runtime';
25
24
  import { Injectable } from '@travetto/di';
26
25
  import { FieldConfig, SchemaRegistry, SchemaValidator } from '@travetto/schema';
27
26
 
@@ -33,9 +32,9 @@ import { ModelQuerySuggestUtil } from '@travetto/model-query/src/internal/servic
33
32
  import { PointImpl } from '@travetto/model-query/src/internal/model/point';
34
33
  import { ModelQueryExpiryUtil } from '@travetto/model-query/src/internal/service/expiry';
35
34
  import { ModelExpiryUtil } from '@travetto/model/src/internal/service/expiry';
36
- import { enforceRange, StreamModel, STREAMS } from '@travetto/model/src/internal/service/stream';
37
35
  import { AllViewⲐ } from '@travetto/schema/src/internal/types';
38
36
  import { ModelBulkUtil } from '@travetto/model/src/internal/service/bulk';
37
+ import { MODEL_BLOB, ModelBlobNamespace, ModelBlobUtil } from '@travetto/model/src/internal/service/blob';
39
38
 
40
39
  import { MongoUtil, WithId } from './internal/util';
41
40
  import { MongoModelConfig } from './config';
@@ -46,7 +45,7 @@ const asFielded = (cfg: IndexConfig<ModelType>): { [IdxFieldsⲐ]: Sort } => cas
46
45
 
47
46
  type IdxCfg = CreateIndexesOptions;
48
47
 
49
- type StreamRaw = GridFSFile & { metadata: StreamMeta };
48
+ type BlobRaw = GridFSFile & { metadata?: BlobMeta };
50
49
 
51
50
  /**
52
51
  * Mongo-based model source
@@ -54,7 +53,7 @@ type StreamRaw = GridFSFile & { metadata: StreamMeta };
54
53
  @Injectable()
55
54
  export class MongoModelService implements
56
55
  ModelCrudSupport, ModelStorageSupport,
57
- ModelBulkSupport, ModelStreamSupport,
56
+ ModelBulkSupport, ModelBlobSupport,
58
57
  ModelIndexedSupport, ModelQuerySupport,
59
58
  ModelQueryCrudSupport, ModelQueryFacetSupport,
60
59
  ModelQuerySuggestSupport, ModelExpirySupport {
@@ -66,11 +65,11 @@ export class MongoModelService implements
66
65
 
67
66
  constructor(public readonly config: MongoModelConfig) { }
68
67
 
69
- async #describeStreamRaw(location: string): Promise<StreamRaw> {
70
- const files: StreamRaw[] = castTo(await this.#bucket.find({ filename: location }, { limit: 1 }).toArray());
68
+ async #describeBlobRaw(location: string): Promise<BlobRaw> {
69
+ const files: BlobRaw[] = await this.#bucket.find({ filename: location }, { limit: 1 }).toArray();
71
70
 
72
71
  if (!files?.length) {
73
- throw new NotFoundError(STREAMS, location);
72
+ throw new NotFoundError(ModelBlobNamespace, location);
74
73
  }
75
74
 
76
75
  return files[0];
@@ -80,7 +79,7 @@ export class MongoModelService implements
80
79
  this.client = await MongoClient.connect(this.config.url, this.config.options);
81
80
  this.#db = this.client.db(this.config.namespace);
82
81
  this.#bucket = new GridFSBucket(this.#db, {
83
- bucketName: STREAMS,
82
+ bucketName: ModelBlobNamespace,
84
83
  writeConcern: { w: 1 }
85
84
  });
86
85
  await ModelStorageUtil.registerModelChangeListener(this);
@@ -151,10 +150,8 @@ export class MongoModelService implements
151
150
  }
152
151
 
153
152
  async truncateModel<T extends ModelType>(cls: Class<T>): Promise<void> {
154
- if (cls === StreamModel) {
155
- try {
156
- await this.#bucket.drop();
157
- } catch { }
153
+ if (cls === MODEL_BLOB) {
154
+ await this.#bucket.drop().catch(() => { });
158
155
  } else {
159
156
  const col = await this.getStore(cls);
160
157
  await col.deleteMany({});
@@ -283,37 +280,39 @@ export class MongoModelService implements
283
280
  }
284
281
  }
285
282
 
286
- // Stream
287
- async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void> {
283
+ // Blob
284
+ async upsertBlob(location: string, input: BinaryInput, meta?: BlobMeta, overwrite = true): Promise<void> {
285
+ const existing = await this.describeBlob(location).then(() => true, () => false);
286
+ if (!overwrite && existing) {
287
+ return;
288
+ }
289
+ const [stream, blobMeta] = await ModelBlobUtil.getInput(input, meta);
288
290
  const writeStream = this.#bucket.openUploadStream(location, {
289
- contentType: meta.contentType,
290
- metadata: meta
291
+ contentType: blobMeta.contentType,
292
+ metadata: blobMeta,
291
293
  });
294
+ await pipeline(stream, writeStream);
292
295
 
293
- await pipeline(input, writeStream);
294
- }
295
-
296
- async getStream(location: string, range?: StreamRange): Promise<Readable> {
297
- const meta = await this.describeStream(location);
298
-
299
- if (range) {
300
- range = enforceRange(range, meta.size);
301
- range.end! += 1; // range is exclusive
296
+ if (existing) {
297
+ const [read] = await this.#bucket.find({ filename: location, _id: { $ne: writeStream.id } }).toArray();
298
+ await this.#bucket.delete(read._id);
302
299
  }
300
+ }
303
301
 
304
- const res = await this.#bucket.openDownloadStreamByName(location, range);
305
- if (!res) {
306
- throw new NotFoundError(STREAMS, location);
307
- }
308
- return res;
302
+ async getBlob(location: string, range?: ByteRange): Promise<Blob> {
303
+ const meta = await this.describeBlob(location);
304
+ const final = range ? ModelBlobUtil.enforceRange(range, meta.size!) : undefined;
305
+ const mongoRange = final ? { start: final.start, end: final.end + 1 } : undefined;
306
+ const res = (): Readable => this.#bucket.openDownloadStreamByName(location, mongoRange);
307
+ return BinaryUtil.readableBlob(res, { ...meta, range: final });
309
308
  }
310
309
 
311
- async describeStream(location: string): Promise<StreamMeta> {
312
- return (await this.#describeStreamRaw(location)).metadata;
310
+ async describeBlob(location: string): Promise<BlobMeta> {
311
+ return (await this.#describeBlobRaw(location)).metadata ?? {};
313
312
  }
314
313
 
315
- async deleteStream(location: string): Promise<void> {
316
- const fileId = (await this.#describeStreamRaw(location))._id;
314
+ async deleteBlob(location: string): Promise<void> {
315
+ const fileId = (await this.#describeBlobRaw(location))._id;
317
316
  await this.#bucket.delete(fileId);
318
317
  }
319
318