@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 +19 -11
- package/package.json +6 -6
- package/src/config.ts +33 -11
- package/src/internal/util.ts +16 -4
- package/src/service.ts +25 -22
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#
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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": "
|
|
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": "^
|
|
30
|
-
"@travetto/model": "^
|
|
31
|
-
"@travetto/model-query": "^
|
|
32
|
-
"mongodb": "^7.
|
|
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": "^
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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(':')}@`;
|
package/src/internal/util.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type IndexDescriptionInfo
|
|
4
4
|
} from 'mongodb';
|
|
5
5
|
|
|
6
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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?:
|
|
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
|
-
|
|
103
|
-
|
|
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:
|
|
288
|
-
const existing = await this.
|
|
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
|
|
293
|
-
const writeStream = this.#bucket.openUploadStream(location, { metadata:
|
|
294
|
-
await pipeline(
|
|
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
|
|
304
|
-
const final = range ?
|
|
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
|
|
309
|
+
return BinaryMetadataUtil.makeBlob(() => this.#bucket.openDownloadStreamByName(location, mongoRange), { ...metadata, range: final });
|
|
307
310
|
}
|
|
308
311
|
|
|
309
|
-
async
|
|
310
|
-
const result = await this.#db.collection<{ metadata:
|
|
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
|
|
320
|
-
await this.#db.collection<{ metadata:
|
|
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
|
|
325
|
+
{ $set: { metadata, contentType: metadata.contentType! } },
|
|
323
326
|
);
|
|
324
327
|
}
|
|
325
328
|
|