@depup/mongoose 9.3.3-depup.0 → 9.8.0-depup.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 +3 -9
- package/changes.json +3 -8
- package/eslint.config.mjs +4 -1
- package/lib/aggregate.js +54 -24
- package/lib/cast.js +1 -1
- package/lib/connection.js +20 -8
- package/lib/cursor/aggregationCursor.js +47 -16
- package/lib/cursor/queryCursor.js +23 -7
- package/lib/document.js +240 -105
- package/lib/drivers/node-mongodb-native/collection.js +47 -14
- package/lib/drivers/node-mongodb-native/connection.js +13 -0
- package/lib/error/cast.js +18 -9
- package/lib/error/divergentArray.js +1 -1
- package/lib/error/index.js +1 -1
- package/lib/error/messages.js +1 -0
- package/lib/helpers/clone.js +22 -5
- package/lib/helpers/document/applyDefaults.js +5 -0
- package/lib/helpers/populate/createPopulateQueryFilter.js +9 -1
- package/lib/helpers/populate/getModelsMapForPopulate.js +8 -6
- package/lib/helpers/populate/splitPopulateQuery.js +81 -0
- package/lib/helpers/query/castUpdate.js +4 -0
- package/lib/helpers/schema/getKeysInSchemaOrder.js +13 -16
- package/lib/helpers/update/applyTimestampsToUpdate.js +4 -2
- package/lib/model.js +159 -41
- package/lib/mongoose.js +3 -3
- package/lib/options/schemaTypeOptions.js +14 -0
- package/lib/plugins/saveSubdocs.js +16 -13
- package/lib/query.js +208 -102
- package/lib/queryHelpers.js +13 -12
- package/lib/schema/array.js +4 -6
- package/lib/schema/bigint.js +1 -3
- package/lib/schema/boolean.js +1 -3
- package/lib/schema/buffer.js +1 -3
- package/lib/schema/date.js +8 -8
- package/lib/schema/decimal128.js +1 -3
- package/lib/schema/documentArray.js +3 -5
- package/lib/schema/double.js +1 -3
- package/lib/schema/int32.js +1 -3
- package/lib/schema/map.js +1 -4
- package/lib/schema/number.js +2 -4
- package/lib/schema/objectId.js +7 -3
- package/lib/schema/string.js +20 -5
- package/lib/schema/subdocument.js +2 -4
- package/lib/schema/union.js +50 -0
- package/lib/schema/uuid.js +1 -3
- package/lib/schema.js +40 -14
- package/lib/schemaType.js +93 -12
- package/lib/standardSchema/convertErrorToIssues.js +20 -0
- package/lib/stateMachine.js +11 -6
- package/lib/tracing.js +38 -0
- package/lib/types/array/methods/index.js +4 -4
- package/lib/types/documentArray/methods/index.js +117 -3
- package/lib/types/subdocument.js +4 -2
- package/lib/utils.js +12 -2
- package/lib/validOptions.js +1 -0
- package/package.json +30 -33
- package/{tstyche.config.json → tstyche.json} +2 -1
- package/types/augmentations.d.ts +2 -2
- package/types/document.d.ts +61 -7
- package/types/expressions.d.ts +16 -0
- package/types/index.d.ts +25 -7
- package/types/inferhydrateddoctype.d.ts +1 -1
- package/types/inferrawdoctype.d.ts +6 -3
- package/types/inferschematype.d.ts +10 -2
- package/types/models.d.ts +30 -13
- package/types/mongooseoptions.d.ts +9 -1
- package/types/populate.d.ts +52 -0
- package/types/query.d.ts +39 -12
- package/types/schemaoptions.d.ts +5 -0
- package/types/schematypes.d.ts +10 -0
- package/types/tracing.d.ts +10 -0
- package/types/utility.d.ts +11 -2
- package/lib/helpers/createJSONSchemaTypeDefinition.js +0 -24
package/README.md
CHANGED
|
@@ -13,16 +13,10 @@ npm install @depup/mongoose
|
|
|
13
13
|
|
|
14
14
|
| Field | Value |
|
|
15
15
|
|-------|-------|
|
|
16
|
-
| Original | [mongoose](https://www.npmjs.com/package/mongoose) @ 9.
|
|
17
|
-
| Processed | 2026-
|
|
16
|
+
| Original | [mongoose](https://www.npmjs.com/package/mongoose) @ 9.8.0 |
|
|
17
|
+
| Processed | 2026-07-21 |
|
|
18
18
|
| Smoke test | passed |
|
|
19
|
-
| Deps updated |
|
|
20
|
-
|
|
21
|
-
## Dependency Changes
|
|
22
|
-
|
|
23
|
-
| Dependency | From | To |
|
|
24
|
-
|------------|------|-----|
|
|
25
|
-
| mongodb | ~7.1 | ^7.1.1 |
|
|
19
|
+
| Deps updated | 0 |
|
|
26
20
|
|
|
27
21
|
---
|
|
28
22
|
|
package/changes.json
CHANGED
package/eslint.config.mjs
CHANGED
package/lib/aggregate.js
CHANGED
|
@@ -15,6 +15,8 @@ const { buildMiddlewareFilter } = require('./helpers/buildMiddlewareFilter');
|
|
|
15
15
|
const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators');
|
|
16
16
|
const utils = require('./utils');
|
|
17
17
|
const { modelSymbol } = require('./helpers/symbols');
|
|
18
|
+
const { createTracedChannel } = require('./tracing');
|
|
19
|
+
const { trace: traceAggregate } = createTracedChannel('mongoose:aggregate');
|
|
18
20
|
const read = Query.prototype.read;
|
|
19
21
|
const readConcern = Query.prototype.readConcern;
|
|
20
22
|
|
|
@@ -613,6 +615,9 @@ Aggregate.prototype.graphLookup = function(options) {
|
|
|
613
615
|
*
|
|
614
616
|
* aggregate.sample(3); // Add a pipeline that picks 3 random documents
|
|
615
617
|
*
|
|
618
|
+
* // `$match` before `$sample` samples from the filtered subset
|
|
619
|
+
* aggregate.match({ difficulty: 'easy' }).sample(3);
|
|
620
|
+
*
|
|
616
621
|
* @see $sample https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/#pipe._S_sample
|
|
617
622
|
* @param {number} size number of random documents to pick
|
|
618
623
|
* @return {Aggregate}
|
|
@@ -1032,7 +1037,7 @@ Aggregate.prototype.pipeline = function() {
|
|
|
1032
1037
|
* const base = MyModel.aggregate().match({ test: 1 });
|
|
1033
1038
|
* base.pipelineForUnionWith(); // [{ $match: { test: 1 } }]
|
|
1034
1039
|
*
|
|
1035
|
-
* @return {
|
|
1040
|
+
* @return {PipelineStage[]} The current pipeline with `$unionWith` stage restrictions
|
|
1036
1041
|
* @api public
|
|
1037
1042
|
*/
|
|
1038
1043
|
|
|
@@ -1071,8 +1076,20 @@ Aggregate.prototype.exec = async function exec() {
|
|
|
1071
1076
|
|
|
1072
1077
|
this._optionsForExec();
|
|
1073
1078
|
|
|
1074
|
-
const
|
|
1075
|
-
return
|
|
1079
|
+
const _this = this;
|
|
1080
|
+
return traceAggregate(async function maybeTracedConnectionAggregate() {
|
|
1081
|
+
const cursor = await _this._connection.client.db().aggregate(_this._pipeline, _this.options);
|
|
1082
|
+
return await cursor.toArray();
|
|
1083
|
+
}, () => ({
|
|
1084
|
+
operation: 'aggregate',
|
|
1085
|
+
database: _this._connection.name,
|
|
1086
|
+
serverAddress: _this._connection.host,
|
|
1087
|
+
serverPort: _this._connection.port,
|
|
1088
|
+
args: {
|
|
1089
|
+
pipeline: _this._pipeline,
|
|
1090
|
+
options: _this.options
|
|
1091
|
+
}
|
|
1092
|
+
}));
|
|
1076
1093
|
}
|
|
1077
1094
|
|
|
1078
1095
|
const model = this._model;
|
|
@@ -1090,32 +1107,45 @@ Aggregate.prototype.exec = async function exec() {
|
|
|
1090
1107
|
prepareDiscriminatorPipeline(this._pipeline, this._model.schema);
|
|
1091
1108
|
stringifyFunctionOperators(this._pipeline);
|
|
1092
1109
|
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1110
|
+
const _this = this;
|
|
1111
|
+
return traceAggregate(async function maybeTracedAggregateExec() {
|
|
1112
|
+
const preFilter = buildMiddlewareFilter(_this.options, 'pre');
|
|
1113
|
+
const postFilter = buildMiddlewareFilter(_this.options, 'post');
|
|
1095
1114
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1115
|
+
try {
|
|
1116
|
+
await model.hooks.execPre('aggregate', _this, [], { filter: preFilter });
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
return await model.hooks.execPost('aggregate', _this, [null], { error, filter: postFilter });
|
|
1119
|
+
}
|
|
1101
1120
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1121
|
+
if (!_this._pipeline.length) {
|
|
1122
|
+
throw new MongooseError('Aggregate has empty pipeline');
|
|
1123
|
+
}
|
|
1105
1124
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1125
|
+
const options = clone(_this.options || {});
|
|
1126
|
+
delete options.middleware;
|
|
1108
1127
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1128
|
+
let result;
|
|
1129
|
+
try {
|
|
1130
|
+
const cursor = await collection.aggregate(_this._pipeline, options);
|
|
1131
|
+
result = await cursor.toArray();
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
return await model.hooks.execPost('aggregate', _this, [null], { error, filter: postFilter });
|
|
1134
|
+
}
|
|
1116
1135
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1136
|
+
await model.hooks.execPost('aggregate', _this, [result], { error: null, filter: postFilter });
|
|
1137
|
+
return result;
|
|
1138
|
+
}, () => ({
|
|
1139
|
+
operation: 'aggregate',
|
|
1140
|
+
collection: collection.name,
|
|
1141
|
+
database: model.db?.name,
|
|
1142
|
+
serverAddress: model.db?.host,
|
|
1143
|
+
serverPort: model.db?.port,
|
|
1144
|
+
args: {
|
|
1145
|
+
pipeline: _this._pipeline,
|
|
1146
|
+
options: _this.options
|
|
1147
|
+
}
|
|
1148
|
+
}));
|
|
1119
1149
|
};
|
|
1120
1150
|
|
|
1121
1151
|
/**
|
package/lib/cast.js
CHANGED
|
@@ -413,7 +413,7 @@ function _cast(val, numbertype, context) {
|
|
|
413
413
|
_cast(item, numbertype, context);
|
|
414
414
|
val[nkey] = item;
|
|
415
415
|
} else {
|
|
416
|
-
val[nkey] = numbertype.castForQuery(
|
|
416
|
+
val[nkey] = numbertype.castForQuery(null, item, context);
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
}
|
package/lib/connection.js
CHANGED
|
@@ -1005,10 +1005,7 @@ Connection.prototype.error = function error(err, callback) {
|
|
|
1005
1005
|
Connection.prototype.onOpen = function() {
|
|
1006
1006
|
this.readyState = STATES.connected;
|
|
1007
1007
|
|
|
1008
|
-
|
|
1009
|
-
d.fn.apply(d.ctx, d.args);
|
|
1010
|
-
}
|
|
1011
|
-
this._queue = [];
|
|
1008
|
+
this._flushQueue();
|
|
1012
1009
|
|
|
1013
1010
|
// avoid having the collection subscribe to our event emitter
|
|
1014
1011
|
// to prevent 0.3 warning
|
|
@@ -1021,6 +1018,21 @@ Connection.prototype.onOpen = function() {
|
|
|
1021
1018
|
this.emit('open');
|
|
1022
1019
|
};
|
|
1023
1020
|
|
|
1021
|
+
/**
|
|
1022
|
+
* Flush all buffered operations in `_queue`. Called by `onOpen()` and
|
|
1023
|
+
* by the heartbeat handler when a previously-stale connection recovers.
|
|
1024
|
+
*
|
|
1025
|
+
* @api private
|
|
1026
|
+
*/
|
|
1027
|
+
|
|
1028
|
+
Connection.prototype._flushQueue = function _flushQueue() {
|
|
1029
|
+
const queue = this._queue;
|
|
1030
|
+
this._queue = [];
|
|
1031
|
+
for (const d of queue) {
|
|
1032
|
+
d.fn.apply(d.ctx, d.args);
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1024
1036
|
/**
|
|
1025
1037
|
* Opens the connection with a URI using `MongoClient.connect()`.
|
|
1026
1038
|
*
|
|
@@ -1033,11 +1045,11 @@ Connection.prototype.onOpen = function() {
|
|
|
1033
1045
|
* @param {string} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility.
|
|
1034
1046
|
* @param {number} [options.maxPoolSize=100] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
|
|
1035
1047
|
* @param {number} [options.minPoolSize=0] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
|
|
1036
|
-
* @param {number} [options.serverSelectionTimeoutMS]
|
|
1037
|
-
* @param {number} [options.heartbeatFrequencyMS]
|
|
1048
|
+
* @param {number} [options.serverSelectionTimeoutMS] The MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds before erroring out. If not set, the MongoDB driver defaults to using `30000` (30 seconds).
|
|
1049
|
+
* @param {number} [options.heartbeatFrequencyMS] The MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation.
|
|
1038
1050
|
* @param {boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
|
|
1039
1051
|
* @param {number} [options.socketTimeoutMS=0] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. `socketTimeoutMS` defaults to 0, which means Node.js will not time out the socket due to inactivity. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes.
|
|
1040
|
-
* @param {number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0
|
|
1052
|
+
* @param {number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both.
|
|
1041
1053
|
* @param {boolean} [options.autoCreate=false] Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection.
|
|
1042
1054
|
* @returns {Promise<Connection>}
|
|
1043
1055
|
* @api public
|
|
@@ -1342,7 +1354,7 @@ Connection.prototype.onClose = function onClose(force) {
|
|
|
1342
1354
|
|
|
1343
1355
|
/**
|
|
1344
1356
|
* Retrieves a raw collection instance, creating it if not cached.
|
|
1345
|
-
* This method returns a thin wrapper around a [MongoDB Node.js driver collection](
|
|
1357
|
+
* This method returns a thin wrapper around a [MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html).
|
|
1346
1358
|
* Using a Collection bypasses Mongoose middleware, validation, and casting,
|
|
1347
1359
|
* letting you use [MongoDB Node.js driver](https://mongodb.github.io/node-mongodb-native/) functionality directly.
|
|
1348
1360
|
*
|
|
@@ -10,6 +10,7 @@ const eachAsync = require('../helpers/cursor/eachAsync');
|
|
|
10
10
|
const immediate = require('../helpers/immediate');
|
|
11
11
|
const kareem = require('kareem');
|
|
12
12
|
const util = require('util');
|
|
13
|
+
const { cursorNextChannel } = require('../tracing');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* An AggregationCursor is a concurrency primitive for processing aggregation
|
|
@@ -62,13 +63,7 @@ util.inherits(AggregationCursor, Readable);
|
|
|
62
63
|
*/
|
|
63
64
|
|
|
64
65
|
function _init(model, c, agg) {
|
|
65
|
-
|
|
66
|
-
model.hooks.execPre('aggregate', agg).then(() => onPreComplete(null), err => onPreComplete(err));
|
|
67
|
-
} else {
|
|
68
|
-
model.collection.emitter.once('queue', function() {
|
|
69
|
-
model.hooks.execPre('aggregate', agg).then(() => onPreComplete(null), err => onPreComplete(err));
|
|
70
|
-
});
|
|
71
|
-
}
|
|
66
|
+
model.hooks.execPre('aggregate', agg).then(() => onPreComplete(null), err => onPreComplete(err));
|
|
72
67
|
|
|
73
68
|
function onPreComplete(err) {
|
|
74
69
|
if (err != null) {
|
|
@@ -79,8 +74,28 @@ function _init(model, c, agg) {
|
|
|
79
74
|
c._transforms.push(agg.options.cursor.transform);
|
|
80
75
|
}
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
if (model.collection._shouldBufferCommands() && model.collection.buffer) {
|
|
78
|
+
model.collection.queue.push([
|
|
79
|
+
() => _getRawCursor(model, c, agg)
|
|
80
|
+
]);
|
|
81
|
+
} else {
|
|
82
|
+
_getRawCursor(model, c, agg);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/*!
|
|
88
|
+
* ignore
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
function _getRawCursor(model, aggregationCursor, agg) {
|
|
92
|
+
try {
|
|
93
|
+
const cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
|
|
94
|
+
aggregationCursor.cursor = cursor;
|
|
95
|
+
aggregationCursor.emit('cursor', cursor);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
aggregationCursor._markError(err);
|
|
98
|
+
aggregationCursor.listeners('error').length > 0 && aggregationCursor.emit('error', aggregationCursor._error);
|
|
84
99
|
}
|
|
85
100
|
}
|
|
86
101
|
|
|
@@ -277,14 +292,30 @@ AggregationCursor.prototype.next = async function next() {
|
|
|
277
292
|
if (typeof arguments[0] === 'function') {
|
|
278
293
|
throw new MongooseError('AggregationCursor.prototype.next() no longer accepts a callback');
|
|
279
294
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
const _this = this;
|
|
296
|
+
const model = this.agg._model;
|
|
297
|
+
return cursorNextChannel.trace(function maybeTracedAggCursorNext() {
|
|
298
|
+
return new Promise((resolve, reject) => {
|
|
299
|
+
_next(_this, (err, res) => {
|
|
300
|
+
if (err != null) {
|
|
301
|
+
return reject(err);
|
|
302
|
+
}
|
|
303
|
+
resolve(res);
|
|
304
|
+
});
|
|
286
305
|
});
|
|
287
|
-
})
|
|
306
|
+
}, () => ({
|
|
307
|
+
operation: 'aggregate',
|
|
308
|
+
collection: model?.collection?.name,
|
|
309
|
+
database: model?.db?.name,
|
|
310
|
+
serverAddress: model?.db?.host,
|
|
311
|
+
serverPort: model?.db?.port,
|
|
312
|
+
batchSize: _this.agg.options?.cursor?.batchSize,
|
|
313
|
+
tailable: false,
|
|
314
|
+
args: {
|
|
315
|
+
pipeline: _this.agg._pipeline,
|
|
316
|
+
options: _this.agg.options
|
|
317
|
+
}
|
|
318
|
+
}));
|
|
288
319
|
};
|
|
289
320
|
|
|
290
321
|
/**
|
|
@@ -12,6 +12,7 @@ const kareem = require('kareem');
|
|
|
12
12
|
const immediate = require('../helpers/immediate');
|
|
13
13
|
const { once } = require('events');
|
|
14
14
|
const util = require('util');
|
|
15
|
+
const { cursorNextChannel } = require('../tracing');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* A QueryCursor is a concurrency primitive for processing query results
|
|
@@ -310,14 +311,29 @@ QueryCursor.prototype.next = async function next() {
|
|
|
310
311
|
if (this._closed) {
|
|
311
312
|
throw new MongooseError('Cannot call `next()` on a closed cursor');
|
|
312
313
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
314
|
+
const _this = this;
|
|
315
|
+
return cursorNextChannel.trace(function maybeTracedQueryCursorNext() {
|
|
316
|
+
return new Promise((resolve, reject) => {
|
|
317
|
+
_next(_this, function(error, doc) {
|
|
318
|
+
if (error) {
|
|
319
|
+
return reject(error);
|
|
320
|
+
}
|
|
321
|
+
resolve(doc);
|
|
322
|
+
});
|
|
319
323
|
});
|
|
320
|
-
})
|
|
324
|
+
}, () => ({
|
|
325
|
+
operation: _this.query.op || 'find',
|
|
326
|
+
collection: _this.query.mongooseCollection.name,
|
|
327
|
+
database: _this.model.db?.name,
|
|
328
|
+
serverAddress: _this.model.db?.host,
|
|
329
|
+
serverPort: _this.model.db?.port,
|
|
330
|
+
batchSize: _this.options.batchSize || _this.query.options?.batchSize,
|
|
331
|
+
tailable: _this.options.tailable || _this.query.options?.tailable || false,
|
|
332
|
+
args: {
|
|
333
|
+
filter: _this.query.getFilter(),
|
|
334
|
+
options: _this.query._mongooseOptions
|
|
335
|
+
}
|
|
336
|
+
}));
|
|
321
337
|
};
|
|
322
338
|
|
|
323
339
|
/**
|