@travetto/model-mongo 7.1.4 → 8.0.0-alpha.1

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
@@ -43,7 +43,7 @@ export class Init {
43
43
  }
44
44
  ```
45
45
 
46
- where the [MongoModelConfig](https://github.com/travetto/travetto/tree/main/module/model-mongo/src/config.ts#L10) is defined by:
46
+ where the [MongoModelConfig](https://github.com/travetto/travetto/tree/main/module/model-mongo/src/config.ts#L24) is defined by:
47
47
 
48
48
  **Code: Structure of MongoModelConfig**
49
49
  ```typescript
@@ -80,7 +80,10 @@ export class MongoModelConfig {
80
80
  /**
81
81
  * Mongo client options
82
82
  */
83
- options: mongo.MongoClientOptions = {};
83
+ @Field({ type: Object })
84
+ options: Omit<mongo.MongoClientOptions, 'cert'> & {
85
+ cert?: | Buffer | string | BinaryType | (BinaryType | Buffer | string)[];
86
+ } = {};
84
87
  /**
85
88
  * Allow storage modification at runtime
86
89
  */
@@ -101,9 +104,8 @@ export class MongoModelConfig {
101
104
  /**
102
105
  * Load all the ssl certs as needed
103
106
  */
104
- async postConstruct(): Promise<void> {
105
- const resolve = (file: string): Promise<string> => RuntimeResources.resolve(file).catch(() => file);
106
-
107
+ @PostConstruct()
108
+ async finalizeConfig(): Promise<void> {
107
109
  if (this.connectionString) {
108
110
  const details = new URL(this.connectionString);
109
111
  this.hosts ??= details.hostname.split(',').filter(host => !!host);
@@ -129,22 +131,23 @@ export class MongoModelConfig {
129
131
  const options = this.options;
130
132
  if (options.ssl) {
131
133
  if (options.cert) {
132
- options.cert = await Promise.all([options.cert].flat(2).map(data => Buffer.isBuffer(data) ? data : resolve(data)));
134
+ options.cert = (await Promise.all([options.cert].flat(2).map(readCert)))
135
+ .map(BinaryUtil.binaryArrayToUint8Array);
133
136
  }
134
137
  if (options.tlsCertificateKeyFile) {
135
- options.tlsCertificateKeyFile = await resolve(options.tlsCertificateKeyFile);
138
+ options.tlsCertificateKeyFile = await RuntimeResources.resolve(options.tlsCertificateKeyFile);
136
139
  }
137
140
  if (options.tlsCAFile) {
138
- options.tlsCAFile = await resolve(options.tlsCAFile);
141
+ options.tlsCAFile = await RuntimeResources.resolve(options.tlsCAFile);
139
142
  }
140
143
  if (options.tlsCRLFile) {
141
- options.tlsCRLFile = await resolve(options.tlsCRLFile);
144
+ options.tlsCRLFile = await RuntimeResources.resolve(options.tlsCRLFile);
142
145
  }
143
146
  }
144
147
 
145
148
  if (!Runtime.production) {
146
149
  options.waitQueueTimeoutMS = 0;
147
- options.serverSelectionTimeoutMS = TimeUtil.asMillis(1, 's');
150
+ options.serverSelectionTimeoutMS = 1000;
148
151
  }
149
152
  }
150
153
 
@@ -155,7 +158,12 @@ export class MongoModelConfig {
155
158
  const hosts = this.hosts!
156
159
  .map(host => (this.srvRecord || host.includes(':')) ? host : `${host}:${this.port ?? 27017}`)
157
160
  .join(',');
158
- const optionString = new URLSearchParams(Object.entries(this.options)).toString();
161
+ const optionString = new URLSearchParams(
162
+ Object.entries(this.options)
163
+ .filter((pair): pair is [string, string | number | boolean] => ['string', 'number', 'boolean'].includes(typeof pair[1]))
164
+ .map(([k, v]) => [k, `${v}`])
165
+ )
166
+ .toString();
159
167
  let creds = '';
160
168
  if (this.username) {
161
169
  creds = `${[this.username, this.password].filter(part => !!part).join(':')}@`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-mongo",
3
- "version": "7.1.4",
3
+ "version": "8.0.0-alpha.1",
4
4
  "type": "module",
5
5
  "description": "Mongo backing for the travetto model module.",
6
6
  "keywords": [
@@ -26,13 +26,13 @@
26
26
  "directory": "module/model-mongo"
27
27
  },
28
28
  "dependencies": {
29
- "@travetto/config": "^7.1.4",
30
- "@travetto/model": "^7.1.4",
31
- "@travetto/model-query": "^7.1.4",
32
- "mongodb": "^7.0.0"
29
+ "@travetto/config": "^8.0.0-alpha.1",
30
+ "@travetto/model": "^8.0.0-alpha.1",
31
+ "@travetto/model-query": "^8.0.0-alpha.1",
32
+ "mongodb": "^7.1.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@travetto/cli": "^7.1.4"
35
+ "@travetto/cli": "^8.0.0-alpha.1"
36
36
  },
37
37
  "peerDependenciesMeta": {
38
38
  "@travetto/cli": {
package/src/config.ts CHANGED
@@ -1,7 +1,21 @@
1
1
  import type mongo from 'mongodb';
2
2
 
3
- import { type TimeSpan, TimeUtil, RuntimeResources, Runtime } from '@travetto/runtime';
3
+ import { type TimeSpan, Runtime, RuntimeResources, BinaryUtil, CodecUtil, type BinaryType, type BinaryArray } from '@travetto/runtime';
4
4
  import { Config } from '@travetto/config';
5
+ import { Field } from '@travetto/schema';
6
+ import { PostConstruct } from '@travetto/di';
7
+
8
+ const readCert = async (input: BinaryType | string): Promise<BinaryArray> => {
9
+ if (BinaryUtil.isBinaryType(input)) {
10
+ return BinaryUtil.toBinaryArray(input);
11
+ } else {
12
+ try {
13
+ return await RuntimeResources.readBinaryArray(input);
14
+ } catch {
15
+ return CodecUtil.fromUTF8String(input);
16
+ }
17
+ }
18
+ };
5
19
 
6
20
  /**
7
21
  * Mongo model config
@@ -39,7 +53,10 @@ export class MongoModelConfig {
39
53
  /**
40
54
  * Mongo client options
41
55
  */
42
- options: mongo.MongoClientOptions = {};
56
+ @Field({ type: Object })
57
+ options: Omit<mongo.MongoClientOptions, 'cert'> & {
58
+ cert?: | Buffer | string | BinaryType | (BinaryType | Buffer | string)[];
59
+ } = {};
43
60
  /**
44
61
  * Allow storage modification at runtime
45
62
  */
@@ -60,9 +77,8 @@ export class MongoModelConfig {
60
77
  /**
61
78
  * Load all the ssl certs as needed
62
79
  */
63
- async postConstruct(): Promise<void> {
64
- const resolve = (file: string): Promise<string> => RuntimeResources.resolve(file).catch(() => file);
65
-
80
+ @PostConstruct()
81
+ async finalizeConfig(): Promise<void> {
66
82
  if (this.connectionString) {
67
83
  const details = new URL(this.connectionString);
68
84
  this.hosts ??= details.hostname.split(',').filter(host => !!host);
@@ -88,22 +104,23 @@ export class MongoModelConfig {
88
104
  const options = this.options;
89
105
  if (options.ssl) {
90
106
  if (options.cert) {
91
- options.cert = await Promise.all([options.cert].flat(2).map(data => Buffer.isBuffer(data) ? data : resolve(data)));
107
+ options.cert = (await Promise.all([options.cert].flat(2).map(readCert)))
108
+ .map(BinaryUtil.binaryArrayToUint8Array);
92
109
  }
93
110
  if (options.tlsCertificateKeyFile) {
94
- options.tlsCertificateKeyFile = await resolve(options.tlsCertificateKeyFile);
111
+ options.tlsCertificateKeyFile = await RuntimeResources.resolve(options.tlsCertificateKeyFile);
95
112
  }
96
113
  if (options.tlsCAFile) {
97
- options.tlsCAFile = await resolve(options.tlsCAFile);
114
+ options.tlsCAFile = await RuntimeResources.resolve(options.tlsCAFile);
98
115
  }
99
116
  if (options.tlsCRLFile) {
100
- options.tlsCRLFile = await resolve(options.tlsCRLFile);
117
+ options.tlsCRLFile = await RuntimeResources.resolve(options.tlsCRLFile);
101
118
  }
102
119
  }
103
120
 
104
121
  if (!Runtime.production) {
105
122
  options.waitQueueTimeoutMS = 0;
106
- options.serverSelectionTimeoutMS = TimeUtil.asMillis(1, 's');
123
+ options.serverSelectionTimeoutMS = 1000;
107
124
  }
108
125
  }
109
126
 
@@ -114,7 +131,12 @@ export class MongoModelConfig {
114
131
  const hosts = this.hosts!
115
132
  .map(host => (this.srvRecord || host.includes(':')) ? host : `${host}:${this.port ?? 27017}`)
116
133
  .join(',');
117
- const optionString = new URLSearchParams(Object.entries(this.options)).toString();
134
+ const optionString = new URLSearchParams(
135
+ Object.entries(this.options)
136
+ .filter((pair): pair is [string, string | number | boolean] => ['string', 'number', 'boolean'].includes(typeof pair[1]))
137
+ .map(([k, v]) => [k, `${v}`])
138
+ )
139
+ .toString();
118
140
  let creds = '';
119
141
  if (this.username) {
120
142
  creds = `${[this.username, this.password].filter(part => !!part).join(':')}@`;
@@ -3,7 +3,7 @@ import {
3
3
  type IndexDescriptionInfo
4
4
  } from 'mongodb';
5
5
 
6
- import { AppError, castTo, type Class, toConcrete, TypedObject } from '@travetto/runtime';
6
+ import { RuntimeError, CodecUtil, castTo, type Class, toConcrete, TypedObject, BinaryUtil } from '@travetto/runtime';
7
7
  import { type DistanceUnit, type PageableModelQuery, type WhereClause, ModelQueryUtil } from '@travetto/model-query';
8
8
  import type { ModelType, IndexField, IndexConfig } from '@travetto/model';
9
9
  import { DataUtil, SchemaRegistryIndex, type Point } from '@travetto/schema';
@@ -50,7 +50,19 @@ export class MongoUtil {
50
50
  }
51
51
 
52
52
  static uuid(value: string): Binary {
53
- return new Binary(Buffer.from(value.replaceAll('-', ''), 'hex'), Binary.SUBTYPE_UUID);
53
+ try {
54
+ return new Binary(
55
+ BinaryUtil.binaryArrayToUint8Array(
56
+ CodecUtil.fromHexString(value.replaceAll('-', ''))
57
+ ),
58
+ Binary.SUBTYPE_UUID
59
+ );
60
+ } catch (err) {
61
+ if (err instanceof RuntimeError && err.message === 'Invalid hex string') {
62
+ return null!;
63
+ }
64
+ throw err;
65
+ }
54
66
  }
55
67
 
56
68
  static idToString(id: string | ObjectId | Binary): string {
@@ -59,7 +71,7 @@ export class MongoUtil {
59
71
  } else if (id instanceof ObjectId) {
60
72
  return id.toHexString();
61
73
  } else {
62
- return Buffer.from(id.buffer).toString('hex');
74
+ return CodecUtil.toHexString(id.buffer);
63
75
  }
64
76
  }
65
77
 
@@ -104,7 +116,7 @@ export class MongoUtil {
104
116
  const temp = value[firstKey];
105
117
  out._id = { [firstKey]: Array.isArray(temp) ? temp.map(subValue => this.uuid(subValue)) : this.uuid(`${temp}`) };
106
118
  } else {
107
- throw new AppError('Invalid id query');
119
+ throw new RuntimeError('Invalid id query');
108
120
  }
109
121
  } else if ((isPlain && !firstKey.startsWith('$')) || value?.constructor?.Ⲑid) {
110
122
  if (recursive) {
package/src/service.ts CHANGED
@@ -1,8 +1,7 @@
1
- import { pipeline } from 'node:stream/promises';
2
-
3
1
  import {
2
+ type Binary,
4
3
  type Db, GridFSBucket, MongoClient, type GridFSFile, type Collection,
5
- type ObjectId, type Binary, type RootFilterOperators, type Filter,
4
+ type ObjectId, type RootFilterOperators, type Filter,
6
5
  type WithId as MongoWithId,
7
6
  } from 'mongodb';
8
7
 
@@ -10,7 +9,7 @@ import {
10
9
  ModelRegistryIndex, type ModelType, type OptionalId, type ModelCrudSupport, type ModelStorageSupport,
11
10
  type ModelExpirySupport, type ModelBulkSupport, type ModelIndexedSupport, type BulkOperation, type BulkResponse,
12
11
  NotFoundError, ExistsError, type ModelBlobSupport,
13
- ModelCrudUtil, ModelIndexedUtil, ModelStorageUtil, ModelExpiryUtil, ModelBulkUtil, ModelBlobUtil
12
+ ModelCrudUtil, ModelIndexedUtil, ModelStorageUtil, ModelExpiryUtil, ModelBulkUtil
14
13
  } from '@travetto/model';
15
14
  import {
16
15
  type ModelQuery, type ModelQueryCrudSupport, type ModelQueryFacetSupport, type ModelQuerySupport,
@@ -21,16 +20,16 @@ import {
21
20
 
22
21
  import {
23
22
  ShutdownManager, type Class, type DeepPartial, TypedObject,
24
- castTo, asFull, type BlobMeta, type ByteRange, type BinaryInput, BinaryUtil
23
+ castTo, asFull, type BinaryMetadata, type ByteRange, type BinaryType, BinaryUtil, BinaryMetadataUtil,
25
24
  } from '@travetto/runtime';
26
- import { Injectable } from '@travetto/di';
25
+ import { Injectable, PostConstruct } from '@travetto/di';
27
26
 
28
27
  import { MongoUtil, type PlainIdx, type WithId } from './internal/util.ts';
29
28
  import type { MongoModelConfig } from './config.ts';
30
29
 
31
30
  const ListIndexSymbol = Symbol();
32
31
 
33
- type BlobRaw = GridFSFile & { metadata?: BlobMeta };
32
+ type BlobRaw = GridFSFile & { metadata?: BinaryMetadata };
34
33
 
35
34
  type MongoTextSearch = RootFilterOperators<unknown>['$text'];
36
35
 
@@ -99,8 +98,12 @@ export class MongoModelService implements
99
98
  return files[0];
100
99
  }
101
100
 
102
- async postConstruct(): Promise<void> {
103
- this.client = await MongoClient.connect(this.config.url, this.config.connectionOptions);
101
+ @PostConstruct()
102
+ async initializeClient(): Promise<void> {
103
+ this.client = await MongoClient.connect(this.config.url, {
104
+ ...this.config.connectionOptions,
105
+ useBigInt64: true,
106
+ });
104
107
  this.#db = this.client.db(this.config.namespace);
105
108
  this.#bucket = new GridFSBucket(this.#db, {
106
109
  bucketName: ModelBlobNamespace,
@@ -284,14 +287,14 @@ export class MongoModelService implements
284
287
  }
285
288
 
286
289
  // Blob
287
- async upsertBlob(location: string, input: BinaryInput, meta?: BlobMeta, overwrite = true): Promise<void> {
288
- const existing = await this.getBlobMeta(location).then(() => true, () => false);
290
+ async upsertBlob(location: string, input: BinaryType, metadata?: BinaryMetadata, overwrite = true): Promise<void> {
291
+ const existing = await this.getBlobMetadata(location).then(() => true, () => false);
289
292
  if (!overwrite && existing) {
290
293
  return;
291
294
  }
292
- const [stream, blobMeta] = await ModelBlobUtil.getInput(input, meta);
293
- const writeStream = this.#bucket.openUploadStream(location, { metadata: blobMeta });
294
- await pipeline(stream, writeStream);
295
+ const resolved = await BinaryMetadataUtil.compute(input, metadata);
296
+ const writeStream = this.#bucket.openUploadStream(location, { metadata: resolved });
297
+ await BinaryUtil.pipeline(input, writeStream);
295
298
 
296
299
  if (existing) {
297
300
  const [read] = await this.#bucket.find({ filename: location, _id: { $ne: writeStream.id } }).toArray();
@@ -300,14 +303,14 @@ export class MongoModelService implements
300
303
  }
301
304
 
302
305
  async getBlob(location: string, range?: ByteRange): Promise<Blob> {
303
- const meta = await this.getBlobMeta(location);
304
- const final = range ? ModelBlobUtil.enforceRange(range, meta.size!) : undefined;
306
+ const metadata = await this.getBlobMetadata(location);
307
+ const final = range ? BinaryMetadataUtil.enforceRange(range, metadata) : undefined;
305
308
  const mongoRange = final ? { start: final.start, end: final.end + 1 } : undefined;
306
- return BinaryUtil.readableBlob(() => this.#bucket.openDownloadStreamByName(location, mongoRange), { ...meta, range: final });
309
+ return BinaryMetadataUtil.makeBlob(() => this.#bucket.openDownloadStreamByName(location, mongoRange), { ...metadata, range: final });
307
310
  }
308
311
 
309
- async getBlobMeta(location: string): Promise<BlobMeta> {
310
- const result = await this.#db.collection<{ metadata: BlobMeta }>(`${ModelBlobNamespace}.files`).findOne({ filename: location });
312
+ async getBlobMetadata(location: string): Promise<BinaryMetadata> {
313
+ const result = await this.#db.collection<{ metadata: BinaryMetadata }>(`${ModelBlobNamespace}.files`).findOne({ filename: location });
311
314
  return result!.metadata;
312
315
  }
313
316
 
@@ -316,10 +319,10 @@ export class MongoModelService implements
316
319
  await this.#bucket.delete(fileId);
317
320
  }
318
321
 
319
- async updateBlobMeta(location: string, meta: BlobMeta): Promise<void> {
320
- await this.#db.collection<{ metadata: BlobMeta }>(`${ModelBlobNamespace}.files`).findOneAndUpdate(
322
+ async updateBlobMetadata(location: string, metadata: BinaryMetadata): Promise<void> {
323
+ await this.#db.collection<{ metadata: BinaryMetadata }>(`${ModelBlobNamespace}.files`).findOneAndUpdate(
321
324
  { filename: location },
322
- { $set: { metadata: meta, contentType: meta.contentType! } },
325
+ { $set: { metadata, contentType: metadata.contentType! } },
323
326
  );
324
327
  }
325
328